Skip to content

Commit f7cf5ac

Browse files
authored
Merge pull request #19 from NativeScript/feat/devtools-inspector
feat: start the inspector for ns debug windows on port 43000
2 parents 4727ab3 + b7d803b commit f7cf5ac

8 files changed

Lines changed: 371 additions & 46 deletions

File tree

‎ConsoleTestApp/Program.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
try {
3737
// runtime_devtools_start may not be present in non-devtools builds of the
3838
// native DLL; guard the call and continue if the symbol is missing.
39-
devtoolsPtr = runtime_devtools_start(runtime, 42000);
39+
devtoolsPtr = runtime_devtools_start(runtime, 43000);
4040
} catch (EntryPointNotFoundException) {
4141
devtoolsPtr = IntPtr.Zero;
4242
}

‎NativeScriptWindowsDemo/RuntimeHost.cs‎

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ internal sealed class RuntimeHost : IDisposable
5353
private static extern void runtime_free_js_error(IntPtr ptr);
5454

5555
[DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_has_devtools))]
56+
[return: MarshalAs(UnmanagedType.U1)]
5657
private static extern bool runtime_has_devtools();
5758

5859
[DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_pump_timers))]
@@ -98,6 +99,21 @@ public void PumpTimers()
9899
[DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_free_string))]
99100
private static extern void runtime_free_string(IntPtr ptr);
100101

102+
[DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_devtools_wait_for_debugger))]
103+
[return: MarshalAs(UnmanagedType.U1)]
104+
private static extern bool runtime_devtools_wait_for_debugger(long runtime, uint timeoutMs);
105+
106+
// iOS uses 41000 and Android 42000.
107+
private const ushort DevtoolsPort = 43000;
108+
private const uint DebuggerWaitTimeoutMs = 30000;
109+
110+
// LocalFolder sentinel files shared with the CLI's WindowsApplicationManager.
111+
private const string InspectorMarker = "ns-inspector";
112+
private const string DebugBreakMarker = "ns-debugbreak";
113+
private const string DebuggerStartedMarker = "ns-debugger-started";
114+
115+
private bool _waitForDebugger;
116+
101117
public string DevtoolsFrontendUrl { get; private set; }
102118

103119
private bool _devtoolsAvailable;
@@ -118,7 +134,7 @@ private void StartDevtoolsSafely()
118134
IntPtr urlPtr = IntPtr.Zero;
119135
try
120136
{
121-
urlPtr = runtime_devtools_start(_runtime, 42000);
137+
urlPtr = runtime_devtools_start(_runtime, DevtoolsPort);
122138
if (urlPtr == IntPtr.Zero) return;
123139
var wsUrl = Marshal.PtrToStringUTF8(urlPtr);
124140
DevtoolsFrontendUrl = wsUrl != null
@@ -127,6 +143,8 @@ private void StartDevtoolsSafely()
127143
if (DevtoolsFrontendUrl != null)
128144
{
129145
_devtoolsAvailable = true;
146+
// The server may fall back to the next free port.
147+
WriteMarker(DebuggerStartedMarker, new Uri(wsUrl).Port.ToString());
130148
System.Diagnostics.Debug.WriteLine($"[NativeScript DevTools] {DevtoolsFrontendUrl}");
131149
}
132150
}
@@ -178,31 +196,48 @@ public void Initialize()
178196
}
179197
}
180198
catch { }
181-
if (ConsumeDebugBreakMarker())
199+
DeleteMarker(DebuggerStartedMarker);
200+
var debugBreak = ConsumeMarker(DebugBreakMarker);
201+
if (ConsumeMarker(InspectorMarker) || debugBreak)
202+
{
182203
StartDevtoolsSafely();
204+
_waitForDebugger = debugBreak && _devtoolsAvailable;
205+
}
183206
#endif
184207
_initialized = true;
185208
}
186209

187210
#if DEBUG
188-
/// <summary>
189-
/// Returns true and deletes the marker if the CLI wrote ns-debugbreak to LocalFolder,
190-
/// matching the Android sentinel-file pattern used by the NativeScript CLI.
191-
/// </summary>
192-
private static bool ConsumeDebugBreakMarker()
211+
private static string MarkerPath(string name) =>
212+
Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, name);
213+
214+
private static bool ConsumeMarker(string name)
193215
{
194216
try
195217
{
196-
var markerPath = System.IO.Path.Combine(
197-
Windows.Storage.ApplicationData.Current.LocalFolder.Path,
198-
"ns-debugbreak");
199-
if (!System.IO.File.Exists(markerPath))
218+
var markerPath = MarkerPath(name);
219+
if (!File.Exists(markerPath))
200220
return false;
201-
System.IO.File.Delete(markerPath);
221+
File.Delete(markerPath);
202222
return true;
203223
}
204224
catch { return false; }
205225
}
226+
227+
private static void WriteMarker(string name, string contents)
228+
{
229+
try { File.WriteAllText(MarkerPath(name), contents); }
230+
catch (Exception ex)
231+
{
232+
System.Diagnostics.Debug.WriteLine($"[NativeScript DevTools] Could not write {name}: {ex.Message}");
233+
}
234+
}
235+
236+
private static void DeleteMarker(string name)
237+
{
238+
try { File.Delete(MarkerPath(name)); }
239+
catch { }
240+
}
206241
#endif
207242

208243
/// Reads a file that may live inside a sealed app.nsbundle instead of on disk: tries the
@@ -231,6 +266,15 @@ public void RunMainScript()
231266
if (!_initialized)
232267
throw new InvalidOperationException("Runtime must be initialized before running scripts.");
233268

269+
#if DEBUG
270+
if (_waitForDebugger)
271+
{
272+
// --debug-brk: hold the first script until DevTools attaches.
273+
_waitForDebugger = false;
274+
if (!runtime_devtools_wait_for_debugger(_runtime, DebuggerWaitTimeoutMs))
275+
System.Diagnostics.Debug.WriteLine("[NativeScript DevTools] No debugger attached, continuing.");
276+
}
277+
#endif
234278
var entryPath = ResolveEntryScriptPath();
235279
if (entryPath == null)
236280
{

‎TestApp/RuntimeHost.cs‎

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ internal sealed class RuntimeHost : IDisposable
5353
private static extern void runtime_free_js_error(IntPtr ptr);
5454

5555
[DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_has_devtools))]
56+
[return: MarshalAs(UnmanagedType.U1)]
5657
private static extern bool runtime_has_devtools();
5758

5859
[DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_pump_timers))]
@@ -98,6 +99,21 @@ public void PumpTimers()
9899
[DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_free_string))]
99100
private static extern void runtime_free_string(IntPtr ptr);
100101

102+
[DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_devtools_wait_for_debugger))]
103+
[return: MarshalAs(UnmanagedType.U1)]
104+
private static extern bool runtime_devtools_wait_for_debugger(long runtime, uint timeoutMs);
105+
106+
// iOS uses 41000 and Android 42000.
107+
private const ushort DevtoolsPort = 43000;
108+
private const uint DebuggerWaitTimeoutMs = 30000;
109+
110+
// LocalFolder sentinel files shared with the CLI's WindowsApplicationManager.
111+
private const string InspectorMarker = "ns-inspector";
112+
private const string DebugBreakMarker = "ns-debugbreak";
113+
private const string DebuggerStartedMarker = "ns-debugger-started";
114+
115+
private bool _waitForDebugger;
116+
101117
public string DevtoolsFrontendUrl { get; private set; }
102118

103119
private bool _devtoolsAvailable;
@@ -118,7 +134,7 @@ private void StartDevtoolsSafely()
118134
IntPtr urlPtr = IntPtr.Zero;
119135
try
120136
{
121-
urlPtr = runtime_devtools_start(_runtime, 42000);
137+
urlPtr = runtime_devtools_start(_runtime, DevtoolsPort);
122138
if (urlPtr == IntPtr.Zero) return;
123139
var wsUrl = Marshal.PtrToStringUTF8(urlPtr);
124140
DevtoolsFrontendUrl = wsUrl != null
@@ -127,6 +143,8 @@ private void StartDevtoolsSafely()
127143
if (DevtoolsFrontendUrl != null)
128144
{
129145
_devtoolsAvailable = true;
146+
// The server may fall back to the next free port.
147+
WriteMarker(DebuggerStartedMarker, new Uri(wsUrl).Port.ToString());
130148
System.Diagnostics.Debug.WriteLine($"[NativeScript DevTools] {DevtoolsFrontendUrl}");
131149
}
132150
}
@@ -178,31 +196,48 @@ public void Initialize()
178196
}
179197
}
180198
catch { }
181-
if (ConsumeDebugBreakMarker())
199+
DeleteMarker(DebuggerStartedMarker);
200+
var debugBreak = ConsumeMarker(DebugBreakMarker);
201+
if (ConsumeMarker(InspectorMarker) || debugBreak)
202+
{
182203
StartDevtoolsSafely();
204+
_waitForDebugger = debugBreak && _devtoolsAvailable;
205+
}
183206
#endif
184207
_initialized = true;
185208
}
186209

187210
#if DEBUG
188-
/// <summary>
189-
/// Returns true and deletes the marker if the CLI wrote ns-debugbreak to LocalFolder,
190-
/// matching the Android sentinel-file pattern used by the NativeScript CLI.
191-
/// </summary>
192-
private static bool ConsumeDebugBreakMarker()
211+
private static string MarkerPath(string name) =>
212+
Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, name);
213+
214+
private static bool ConsumeMarker(string name)
193215
{
194216
try
195217
{
196-
var markerPath = System.IO.Path.Combine(
197-
Windows.Storage.ApplicationData.Current.LocalFolder.Path,
198-
"ns-debugbreak");
199-
if (!System.IO.File.Exists(markerPath))
218+
var markerPath = MarkerPath(name);
219+
if (!File.Exists(markerPath))
200220
return false;
201-
System.IO.File.Delete(markerPath);
221+
File.Delete(markerPath);
202222
return true;
203223
}
204224
catch { return false; }
205225
}
226+
227+
private static void WriteMarker(string name, string contents)
228+
{
229+
try { File.WriteAllText(MarkerPath(name), contents); }
230+
catch (Exception ex)
231+
{
232+
System.Diagnostics.Debug.WriteLine($"[NativeScript DevTools] Could not write {name}: {ex.Message}");
233+
}
234+
}
235+
236+
private static void DeleteMarker(string name)
237+
{
238+
try { File.Delete(MarkerPath(name)); }
239+
catch { }
240+
}
206241
#endif
207242

208243
/// Reads a file that may live inside a sealed app.nsbundle instead of on disk: tries the
@@ -231,6 +266,15 @@ public void RunMainScript()
231266
if (!_initialized)
232267
throw new InvalidOperationException("Runtime must be initialized before running scripts.");
233268

269+
#if DEBUG
270+
if (_waitForDebugger)
271+
{
272+
// --debug-brk: hold the first script until DevTools attaches.
273+
_waitForDebugger = false;
274+
if (!runtime_devtools_wait_for_debugger(_runtime, DebuggerWaitTimeoutMs))
275+
System.Diagnostics.Debug.WriteLine("[NativeScript DevTools] No debugger attached, continuing.");
276+
}
277+
#endif
234278
var entryPath = ResolveEntryScriptPath();
235279
if (entryPath == null)
236280
{

‎nativescript/src/lib.rs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,23 @@ pub extern "C" fn runtime_devtools_pump(_runtime: i64) {
408408
});
409409
}
410410

411+
/// Blocks until a DevTools frontend attaches, then pauses on the next statement.
412+
#[cfg(feature = "devtools")]
413+
#[no_mangle]
414+
pub extern "C" fn runtime_devtools_wait_for_debugger(_runtime: i64, timeout_ms: u32) -> bool {
415+
std::panic::catch_unwind(|| {
416+
DEVTOOLS.with(|d| {
417+
if let Ok(mut guard) = d.try_borrow_mut() {
418+
if let Some(s) = guard.as_mut() {
419+
return s.wait_for_debugger(std::time::Duration::from_millis(timeout_ms as u64));
420+
}
421+
}
422+
false
423+
})
424+
})
425+
.unwrap_or(false)
426+
}
427+
411428
/// Drain the JS timer queue on the calling thread.
412429
///
413430
/// Must be called regularly on the V8/UI thread so that `setTimeout` /

‎runtime-devtools/README.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use runtime_devtools::{DevtoolsServer, DevtoolsServerConfig};
99

1010
// On the V8 thread, after creating the isolate and context:
1111
let mut server = DevtoolsServer::attach(
12-
&DevtoolsServerConfig::default(), // host: 127.0.0.1, port: 42000
12+
&DevtoolsServerConfig::default(), // host: 127.0.0.1, port: 43000
1313
isolate,
1414
context,
1515
)?;
@@ -27,11 +27,12 @@ server.pump_messages(); // dispatches pending CDP messages to V8
2727
3. Serves CDP discovery endpoints (`/json/version`, `/json/list`) as plain HTTP.
2828
4. Upgrades `/devtools/page/runtime` connections to WebSocket and bridges them to V8.
2929
5. Implements `run_message_loop_on_pause` so breakpoints work correctly.
30+
6. `wait_for_debugger(timeout)` blocks until a frontend attaches, then pauses on the next statement (`--debug-brk`).
3031

3132
## Connecting
3233

3334
Open the `frontend_url` from `DevtoolsEndpoint` in Chrome, or use:
3435

3536
```
36-
chrome://inspect → Configure → add 127.0.0.1:42000
37+
chrome://inspect → Configure → add 127.0.0.1:43000
3738
```

0 commit comments

Comments
 (0)