Skip to content

Commit 9d24174

Browse files
authored
Merge pull request #1483 from paulvanbrenk/webkitdebugger15.1
Webkitdebugger for new node debugger protocol
2 parents 101ed9b + 3fb4c47 commit 9d24174

31 files changed

+862
-358
lines changed

Common/Product/SharedProject/CommonProjectNode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1549,7 +1549,7 @@ protected override ConfigProvider CreateConfigProvider() {
15491549
/// Returns resolved value of the current working directory property.
15501550
/// </summary>
15511551
public string GetWorkingDirectory() {
1552-
string workDir = CommonUtils.UnquotePath(GetProjectProperty(CommonConstants.WorkingDirectory, resetCache: false));
1552+
var workDir = CommonUtils.UnquotePath(GetProjectProperty(CommonConstants.WorkingDirectory, resetCache: false));
15531553
return CommonUtils.GetAbsoluteDirectoryPath(ProjectHome, workDir);
15541554
}
15551555

Common/Product/SharedProject/CommonUtils.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ internal static Uri MakeUri(string path, bool isDirectory, UriKind kind, string
4646
}
4747

4848
return new Uri(path, kind);
49-
5049
} catch (UriFormatException ex) {
5150
throw new ArgumentException("Path was invalid", throwParameterName, ex);
5251
} catch (ArgumentException ex) {
@@ -62,6 +61,13 @@ public static string NormalizePath(string path) {
6261
return null;
6362
}
6463

64+
const string MdhaPrefix = "mdha:";
65+
66+
// webkit debugger prepends with 'mdha'
67+
if (path.StartsWith(MdhaPrefix, StringComparison.OrdinalIgnoreCase)) {
68+
path = path.Substring(MdhaPrefix.Length);
69+
}
70+
6571
var uri = MakeUri(path, false, UriKind.RelativeOrAbsolute);
6672
if (uri.IsAbsoluteUri) {
6773
if (uri.IsFile) {

Common/Product/SharedProject/ProjectNode.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4496,7 +4496,6 @@ public virtual int IsDocumentInProject(string mkDoc, out int found, VSDOCUMENTPR
44964496
}
44974497

44984498
return VSConstants.S_OK;
4499-
45004499
}
45014500

45024501
protected virtual bool IncludeNonMemberItemInProject(HierarchyNode node) {

Nodejs/Product/Nodejs/Debugger/DebugEngine/AD7Engine.cs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,6 @@ public sealed class AD7Engine : IDebugEngine2, IDebugEngineLaunch2, IDebugProgra
8585
/// </summary>
8686
public const string WaitOnNormalExitSetting = "WAIT_ON_NORMAL_EXIT";
8787

88-
/// <summary>
89-
/// Specifies if the output should be redirected to the visual studio output window.
90-
/// </summary>
91-
public const string RedirectOutputSetting = "REDIRECT_OUTPUT";
92-
9388
/// <summary>
9489
/// Specifies options which should be passed to the Node runtime before the script. If
9590
/// the interpreter options should include a semicolon then it should be escaped as a double
@@ -517,11 +512,6 @@ int IDebugEngineLaunch2.LaunchSuspended(string pszServer, IDebugPort2 port, stri
517512
debugOptions |= NodeDebugOptions.WaitOnNormalExit;
518513
}
519514
break;
520-
case RedirectOutputSetting:
521-
if (Boolean.TryParse(setting[1], out value) && value) {
522-
debugOptions |= NodeDebugOptions.RedirectOutput;
523-
}
524-
break;
525515
case DirMappingSetting:
526516
string[] dirs = setting[1].Split('|');
527517
if (dirs.Length == 2) {
@@ -565,9 +555,10 @@ int IDebugEngineLaunch2.LaunchSuspended(string pszServer, IDebugPort2 port, stri
565555

566556
AttachEvents(_process);
567557

568-
var adProcessId = new AD_PROCESS_ID();
569-
adProcessId.ProcessIdType = (uint)enum_AD_PROCESS_ID.AD_PROCESS_ID_SYSTEM;
570-
adProcessId.dwProcessId = (uint)_process.Id;
558+
var adProcessId = new AD_PROCESS_ID() {
559+
ProcessIdType = (uint)enum_AD_PROCESS_ID.AD_PROCESS_ID_SYSTEM,
560+
dwProcessId = (uint)_process.Id
561+
};
571562

572563
EngineUtils.RequireOk(port.GetProcess(adProcessId, out process));
573564
LiveLogger.WriteLine("AD7Engine LaunchSuspended returning S_OK");

Nodejs/Product/Nodejs/Debugger/NodeConstants.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
//*********************************************************//
1616

1717
namespace Microsoft.NodejsTools.Debugger {
18-
sealed class NodeConstants {
18+
internal sealed class NodeConstants {
1919
public const string ScriptWrapBegin = "(function (exports, require, module, __filename, __dirname) { ";
2020
public const string ScriptWrapEnd = "\n});";
2121
}

Nodejs/Product/Nodejs/Debugger/NodeDebugOptions.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
namespace Microsoft.NodejsTools.Debugger {
2020
[Flags]
21-
enum NodeDebugOptions {
21+
internal enum NodeDebugOptions {
2222
None,
2323
/// <summary>
2424
/// Passing this flag to the debugger will cause it to wait for input on an abnormal (non-zero)
@@ -29,15 +29,5 @@ enum NodeDebugOptions {
2929
/// Passing this flag to the debugger will cause it to wait for input on a normal (zero) exit code.
3030
/// </summary>
3131
WaitOnNormalExit = 0x02,
32-
/// <summary>
33-
/// Passing this flag will cause output to standard out to be redirected via the debugger
34-
/// so it can be outputted in the Visual Studio debug output window.
35-
/// </summary>
36-
RedirectOutput = 0x04,
37-
38-
/// <summary>
39-
/// Set if you do not want to create a window
40-
/// </summary>
41-
CreateNoWindow = 0x40
4232
}
4333
}

Nodejs/Product/Nodejs/Debugger/NodeDebugger.cs

Lines changed: 89 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ namespace Microsoft.NodejsTools.Debugger {
3636
/// <summary>
3737
/// Handles all interactions with a Node process which is being debugged.
3838
/// </summary>
39-
sealed class NodeDebugger : IDisposable {
40-
public readonly int MainThreadId = 1;
39+
internal sealed class NodeDebugger : IDisposable {
40+
public const int MainThreadId = 1;
4141
private readonly Dictionary<int, NodeBreakpointBinding> _breakpointBindings = new Dictionary<int, NodeBreakpointBinding>();
4242
private readonly IDebuggerClient _client;
4343
private readonly IDebuggerConnection _connection;
@@ -76,6 +76,13 @@ private NodeDebugger() {
7676
_fileNameMapper = new LocalFileNameMapper();
7777
}
7878

79+
public NodeDebugger(Uri debuggerEndpointUri, int id)
80+
: this() {
81+
_debuggerEndpointUri = debuggerEndpointUri;
82+
_id = id;
83+
_attached = true;
84+
}
85+
7986
public NodeDebugger(
8087
string exe,
8188
string script,
@@ -87,71 +94,102 @@ public NodeDebugger(
8794
bool createNodeWindow = true)
8895
: this() {
8996
// Select debugger port for a local connection
90-
ushort debuggerPortOrDefault = NodejsConstants.DefaultDebuggerPort;
91-
if (debuggerPort != null) {
92-
debuggerPortOrDefault = debuggerPort.Value;
93-
} else {
94-
List<int> activeConnections =
95-
(from listener in IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()
96-
select listener.Port).ToList();
97-
if (activeConnections.Contains(debuggerPortOrDefault)) {
98-
debuggerPortOrDefault = (ushort)Enumerable.Range(new Random().Next(5859, 6000), 60000).Except(activeConnections).First();
99-
}
97+
var debuggerPortOrDefault = debuggerPort ?? GetDebuggerPort();
98+
_debuggerEndpointUri = new UriBuilder { Scheme = "tcp", Host = "localhost", Port = debuggerPortOrDefault }.Uri;
99+
100+
_process = StartNodeProcessWithDebug(exe, script, dir, env, interpreterOptions, debugOptions, debuggerPortOrDefault, createNodeWindow);
101+
}
102+
103+
private static ushort GetDebuggerPort() {
104+
var debuggerPortOrDefault = NodejsConstants.DefaultDebuggerPort;
105+
106+
var activeConnections = (from listener in IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()
107+
select listener.Port).ToList();
108+
109+
if (activeConnections.Contains(debuggerPortOrDefault)) {
110+
debuggerPortOrDefault = (ushort)Enumerable.Range(new Random().Next(5859, 6000), 60000).Except(activeConnections).First();
100111
}
101112

102-
_debuggerEndpointUri = new UriBuilder { Scheme = "tcp", Host = "localhost", Port = debuggerPortOrDefault }.Uri;
113+
return debuggerPortOrDefault;
114+
}
115+
116+
public static NodeProcess StartNodeProcessWithDebug(
117+
string exe,
118+
string script,
119+
string dir,
120+
string env,
121+
string interpreterOptions,
122+
NodeDebugOptions debugOptions,
123+
ushort? debuggerPort = null,
124+
bool createNodeWindow = true) {
125+
// Select debugger port for a local connection
126+
var debuggerPortOrDefault = debuggerPort ?? GetDebuggerPort();
127+
128+
// Node usage: node [options] [ -e script | script.js ] [arguments]
129+
var allArgs = $"--debug-brk={debuggerPortOrDefault} --nolazy {interpreterOptions} \"{CommonUtils.UnquotePath(script)}\""; /* unquote the path so we can safely add quotes */
130+
131+
return StartNodeProcess(exe, dir, env, debugOptions, debuggerPortOrDefault, allArgs, createNodeWindow);
132+
}
133+
134+
public static NodeProcess StartNodeProcessWithInspect(
135+
string exe,
136+
string script,
137+
string dir,
138+
string env,
139+
string interpreterOptions,
140+
NodeDebugOptions debugOptions,
141+
ushort? debuggerPort = null,
142+
bool createNodeWindow = true) {
143+
// Select debugger port for a local connection
144+
var debuggerPortOrDefault = debuggerPort ?? GetDebuggerPort();
103145

104146
// Node usage: node [options] [ -e script | script.js ] [arguments]
105-
string allArgs = string.Format(CultureInfo.InvariantCulture,
106-
"--debug-brk={0} --nolazy {1} {2}",
107-
debuggerPortOrDefault,
108-
interpreterOptions,
109-
script
110-
);
147+
var allArgs = $"--inspect={debuggerPortOrDefault} --debug-brk --nolazy {interpreterOptions} \"{CommonUtils.UnquotePath(script)}\""; /* unquote the path so we can safely add quotes */
111148

149+
return StartNodeProcess(exe, dir, env, debugOptions, debuggerPortOrDefault, allArgs, createNodeWindow);
150+
}
151+
152+
// starts the nodeprocess in debug mode without hooking up our debugger, this way we can attach the WebKit debugger as a next step.
153+
private static NodeProcess StartNodeProcess(
154+
string exe,
155+
string dir,
156+
string env,
157+
NodeDebugOptions
158+
debugOptions,
159+
ushort debuggerPortOrDefault,
160+
string allArgs,
161+
bool createNodeWindow) {
112162
var psi = new ProcessStartInfo(exe, allArgs) {
113163
CreateNoWindow = !createNodeWindow,
114164
WorkingDirectory = dir,
115165
UseShellExecute = false
116166
};
117167

118168
if (env != null) {
119-
string[] envValues = env.Split('\0');
120-
foreach (string curValue in envValues) {
121-
string[] nameValue = curValue.Split(new[] { '=' }, 2);
122-
if (nameValue.Length == 2 && !String.IsNullOrWhiteSpace(nameValue[0])) {
169+
var envValues = env.Split('\0');
170+
foreach (var curValue in envValues) {
171+
var nameValue = curValue.Split(new[] { '=' }, 2);
172+
if (nameValue.Length == 2 && !string.IsNullOrWhiteSpace(nameValue[0])) {
123173
psi.EnvironmentVariables[nameValue[0]] = nameValue[1];
124174
}
125175
}
126176
}
127177

128-
_process = new NodeProcess(
178+
return new NodeProcess(
129179
psi,
130-
debugOptions.HasFlag(NodeDebugOptions.WaitOnAbnormalExit),
131-
debugOptions.HasFlag(NodeDebugOptions.WaitOnNormalExit),
132-
true);
133-
}
134-
135-
public NodeDebugger(Uri debuggerEndpointUri, int id)
136-
: this() {
137-
_debuggerEndpointUri = debuggerEndpointUri;
138-
_id = id;
139-
_attached = true;
180+
waitOnAbnormal: debugOptions.HasFlag(NodeDebugOptions.WaitOnAbnormalExit),
181+
waitOnNormal: debugOptions.HasFlag(NodeDebugOptions.WaitOnNormalExit),
182+
enableRaisingEvents: true,
183+
debuggerPort: debuggerPortOrDefault);
140184
}
141185

142186
#region Public Process API
143187

144-
public int Id {
145-
get { return _id != null ? _id.Value : _process.Id; }
146-
}
188+
public int Id => _id != null ? _id.Value : _process.Id;
147189

148-
private NodeThread MainThread {
149-
get { return _threads[MainThreadId]; }
150-
}
190+
private NodeThread MainThread => _threads[MainThreadId];
151191

152-
public bool HasExited {
153-
get { return !_connection.Connected; }
154-
}
192+
public bool HasExited => !_connection.Connected;
155193

156194
/// <summary>
157195
/// Gets or sets a value indicating whether executed remote debugging process.
@@ -236,7 +274,7 @@ public async Task BreakAllAsync() {
236274
// We need to get the backtrace before we break, so we request the backtrace
237275
// and follow up with firing the appropriate event for the break
238276
tokenSource = new CancellationTokenSource(_timeout);
239-
bool running = await PerformBacktraceAsync(tokenSource.Token).ConfigureAwait(false);
277+
var running = await PerformBacktraceAsync(tokenSource.Token).ConfigureAwait(false);
240278
Debug.Assert(!running);
241279

242280
// Fallback to firing step complete event
@@ -424,16 +462,12 @@ public void ClearExceptionTreatment() {
424462
/// <summary>
425463
/// Gets a next command identifier.
426464
/// </summary>
427-
private int CommandId {
428-
get { return Interlocked.Increment(ref _commandId); }
429-
}
465+
private int CommandId => Interlocked.Increment(ref _commandId);
430466

431467
/// <summary>
432468
/// Gets a source mapper.
433469
/// </summary>
434-
public SourceMapper SourceMapper {
435-
get { return _sourceMapper; }
436-
}
470+
public SourceMapper SourceMapper => _sourceMapper;
437471

438472
/// <summary>
439473
/// Gets or sets a file name mapper.
@@ -650,7 +684,7 @@ private async Task<bool> ProcessBreakpointBreakAsync(
650684
}
651685

652686
SetBreakpointCommand result = await SetBreakpointAsync(breakpoint, cancellationToken: cancellationToken).ConfigureAwait(false);
653-
687+
654688
// Treat rebound breakpoint binding as fully bound
655689
NodeBreakpointBinding reboundbreakpointBinding = CreateBreakpointBinding(breakpoint, result.BreakpointId, result.ScriptId, breakpoint.GetPosition(SourceMapper).FileName, result.Line, result.Column, true);
656690
HandleBindBreakpointSuccess(reboundbreakpointBinding, breakpoint);
@@ -712,7 +746,7 @@ private void OnExceptionEvent(object sender, ExceptionEventArgs args) {
712746

713747
var lookupCommand = new LookupCommand(CommandId, _resultFactory, new[] { exception.ErrorNumber.Value });
714748
string errorCodeFromLookup = null;
715-
749+
716750
if (await TrySendRequestAsync(lookupCommand).ConfigureAwait(false)) {
717751
errorCodeFromLookup = lookupCommand.Results[errorNumber][0].StringValue;
718752
_errorCodes[errorNumber] = errorCodeFromLookup;
@@ -1049,7 +1083,7 @@ internal async Task UpdateBreakpointBindingAsync(
10491083

10501084
internal async Task<int?> GetBreakpointHitCountAsync(int breakpointId, CancellationToken cancellationToken = new CancellationToken()) {
10511085
var listBreakpointsCommand = new ListBreakpointsCommand(CommandId);
1052-
1086+
10531087
int hitCount;
10541088
if (await TrySendRequestAsync(listBreakpointsCommand, cancellationToken).ConfigureAwait(false) &&
10551089
listBreakpointsCommand.Breakpoints.TryGetValue(breakpointId, out hitCount)) {
@@ -1160,7 +1194,7 @@ internal async Task<NodeEvaluationResult> SetVariableValueAsync(
11601194
return false;
11611195
}
11621196
}
1163-
1197+
11641198
#endregion
11651199

11661200
#region Debugging Events
@@ -1211,7 +1245,7 @@ private bool GetOrAddModule(NodeModule module, out NodeModule value, NodeStackFr
12111245
javaScriptFileName = FileNameMapper.GetLocalFileName(javaScriptFileName);
12121246

12131247
// Try to get mapping for JS file
1214-
if(stackFrame != null) {
1248+
if (stackFrame != null) {
12151249
line = stackFrame.Line;
12161250
column = stackFrame.Column;
12171251
}
@@ -1255,8 +1289,6 @@ public NodeModule GetModuleForFilePath(string filePath) {
12551289
internal void Close() {
12561290
}
12571291

1258-
1259-
12601292
#region IDisposable
12611293

12621294
public void Dispose() {

Nodejs/Product/Nodejs/Debugger/NodeEvaluationResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace Microsoft.NodejsTools.Debugger {
2424
/// <summary>
2525
/// Represents the result of an evaluation of an expression against a given stack frame.
2626
/// </summary>
27-
class NodeEvaluationResult {
27+
internal class NodeEvaluationResult {
2828
private readonly Regex _stringLengthExpression = new Regex(@"\.\.\. \(length: ([0-9]+)\)$", RegexOptions.Compiled);
2929

3030
/// <summary>

0 commit comments

Comments
 (0)