瀏覽代碼

Debugger API improvements (#1382)

Jither 2 年之前
父節點
當前提交
3c45b8c8d8

+ 28 - 0
Jint.Tests/Runtime/Debugger/BreakPointTests.cs

@@ -192,6 +192,7 @@ debugger;
             bool didBreak = false;
             engine.DebugHandler.Break += (sender, info) =>
             {
+                Assert.Equal(PauseType.DebuggerStatement, info.PauseType);
                 didBreak = true;
                 return StepMode.None;
             };
@@ -232,6 +233,33 @@ debugger;
             Assert.False(didBreak);
         }
 
+        [Fact]
+        public void DebuggerStatementDoesNotTriggerBreakWhenAtBreakPoint()
+        {
+            string script = @"'dummy';
+debugger;
+'dummy';";
+
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script)
+                .InitialStepMode(StepMode.None));
+
+            int breakCount = 0;
+
+            engine.DebugHandler.BreakPoints.Set(new BreakPoint(2, 0));
+
+            engine.DebugHandler.Break += (sender, info) =>
+            {
+                Assert.Equal(PauseType.Break, info.PauseType);
+                breakCount++;
+                return StepMode.None;
+            };
+
+            engine.Execute(script);
+            Assert.Equal(1, breakCount);
+        }
+
         [Fact]
         public void BreakPointDoesNotTriggerBreakWhenStepping()
         {

+ 1 - 1
Jint.Tests/Runtime/Debugger/DebugHandlerTests.cs

@@ -31,7 +31,7 @@ namespace Jint.Tests.Runtime.Debugger
 
                 if (info.ReachedLiteral("target"))
                 {
-                    var obj = info.CurrentScopeChain.Global.GetBindingValue("obj") as ObjectInstance;
+                    var obj = info.CurrentScopeChain[0].GetBindingValue("obj") as ObjectInstance;
                     var prop = obj.GetOwnProperty("name");
                     // This is where reentrance would occur:
                     var value = prop.Get.Invoke(engine);

+ 4 - 3
Jint.Tests/Runtime/Debugger/ScopeTests.cs

@@ -299,7 +299,8 @@ namespace Jint.Tests.Runtime.Debugger
             {
                 Assert.Collection(info.CurrentScopeChain,
                     scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a"),
-                    scope => AssertScope(scope, DebugScopeType.Closure, "b", "power"), // a, this, arguments shadowed by local
+                    // a, arguments shadowed by local - but still exist in this scope
+                    scope => AssertScope(scope, DebugScopeType.Closure, "a", "arguments", "b", "power"),
                     scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"),
                     scope => AssertScope(scope, DebugScopeType.Global, "add"));
             });
@@ -327,7 +328,7 @@ namespace Jint.Tests.Runtime.Debugger
                 Assert.Collection(info.CurrentScopeChain,
                     scope => AssertScope(scope, DebugScopeType.Block, "y"),
                     scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
-                    scope => AssertScope(scope, DebugScopeType.Script, "x", "z"), // y shadowed
+                    scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"), // y is shadowed, but still in the scope
                     scope => AssertScope(scope, DebugScopeType.Global, "add"));
             });
         }
@@ -391,7 +392,7 @@ namespace Jint.Tests.Runtime.Debugger
                     scope => AssertScope(scope, DebugScopeType.Block, "x"),
                     scope => AssertScope(scope, DebugScopeType.Block, "y"),
                     scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
-                    scope => AssertScope(scope, DebugScopeType.Script, "z"), // x, y shadowed
+                    scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"), // x, y are shadowed, but still in the scope
                     scope => AssertScope(scope, DebugScopeType.Global, "add"));
             });
         }

+ 48 - 1
Jint.Tests/Runtime/Debugger/StepModeTests.cs

@@ -413,7 +413,7 @@ namespace Jint.Tests.Runtime.Debugger
                 function test()
                 {
                     'dummy';
-                    'øummy';
+                    'dummy';
                 }";
 
             var engine = new Engine(options => options
@@ -432,5 +432,52 @@ namespace Jint.Tests.Runtime.Debugger
 
             Assert.Equal(1, stepCount);
         }
+
+        [Fact]
+        public void SkipIsTriggeredWhenRunning()
+        {
+            string script = @"
+                'step';
+                'skip';
+                'skip';
+                debugger;
+                'step';
+                'step';
+                ";
+
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script)
+                .InitialStepMode(StepMode.Into));
+
+            int stepCount = 0;
+            int skipCount = 0;
+
+            engine.DebugHandler.Step += (sender, info) =>
+            {
+                Assert.True(TestHelpers.IsLiteral(info.CurrentNode, "step"));
+                stepCount++;
+                // Start running after first step
+                return stepCount == 1 ? StepMode.None : StepMode.Into;
+            };
+
+            engine.DebugHandler.Skip += (sender, info) =>
+            {
+                Assert.True(TestHelpers.IsLiteral(info.CurrentNode, "skip"));
+                skipCount++;
+                return StepMode.None;
+            };
+
+            engine.DebugHandler.Break += (sender, info) =>
+            {
+                // Back to stepping after debugger statement
+                return StepMode.Into;
+            };
+
+            engine.Execute(script);
+
+            Assert.Equal(2, skipCount);
+            Assert.Equal(3, stepCount);
+        }
     }
 }

+ 134 - 4
Jint.Tests/Runtime/EngineTests.cs

@@ -1,11 +1,13 @@
 using System.Globalization;
 using System.Reflection;
 using Esprima;
+using Esprima.Ast;
 using Jint.Native;
 using Jint.Native.Array;
 using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime.Debugger;
+using Jint.Tests.Runtime.Debugger;
 using Xunit.Abstractions;
 
 #pragma warning disable 618
@@ -1584,13 +1586,11 @@ var prep = function (fn) { fn(); };
             Assert.NotNull(debugInfo.CallStack);
             Assert.NotNull(debugInfo.CurrentNode);
             Assert.NotNull(debugInfo.CurrentScopeChain);
-            Assert.NotNull(debugInfo.CurrentScopeChain.Global);
-            Assert.NotNull(debugInfo.CurrentScopeChain.Local);
 
             Assert.Equal(2, debugInfo.CallStack.Count);
             Assert.Equal("func1", debugInfo.CurrentCallFrame.FunctionName);
-            var globalScope = debugInfo.CurrentScopeChain.Global;
-            var localScope = debugInfo.CurrentScopeChain.Local;
+            var globalScope = debugInfo.CurrentScopeChain.Single(s => s.ScopeType == DebugScopeType.Global);
+            var localScope = debugInfo.CurrentScopeChain.Single(s => s.ScopeType == DebugScopeType.Local);
             Assert.Contains("global", globalScope.BindingNames);
             Assert.Equal(true, globalScope.GetBindingValue("global").AsBoolean());
             Assert.Contains("local", localScope.BindingNames);
@@ -2882,6 +2882,136 @@ x.test = {
             Assert.True(array[1].IsUndefined());
         }
 
+        [Fact]
+        public void ExecuteShouldTriggerBeforeEvaluateEvent()
+        {
+            TestBeforeEvaluateEvent(
+                (engine, code) => engine.Execute(code),
+                expectedSource: "<anonymous>"
+            );
+        }
+
+        [Fact]
+        public void ExecuteWithSourceShouldTriggerBeforeEvaluateEvent()
+        {
+            TestBeforeEvaluateEvent(
+                (engine, code) => engine.Execute(code, "mysource"),
+                expectedSource: "mysource"
+            );
+        }
+
+        [Fact]
+        public void ExecuteWithParserOptionsShouldTriggerBeforeEvaluateEvent()
+        {
+            TestBeforeEvaluateEvent(
+                (engine, code) => engine.Execute(code, ParserOptions.Default),
+                expectedSource: "<anonymous>"
+            );
+        }
+
+        [Fact]
+        public void ExecuteWithSourceAndParserOptionsShouldTriggerBeforeEvaluateEvent()
+        {
+            TestBeforeEvaluateEvent(
+                (engine, code) => engine.Execute(code, "mysource", ParserOptions.Default),
+                expectedSource: "mysource"
+            );
+        }
+
+        [Fact]
+        public void EvaluateShouldTriggerBeforeEvaluateEvent()
+        {
+            TestBeforeEvaluateEvent(
+                (engine, code) => engine.Evaluate(code),
+                expectedSource: "<anonymous>"
+            );
+        }
+
+        [Fact]
+        public void EvaluateWithSourceShouldTriggerBeforeEvaluateEvent()
+        {
+            TestBeforeEvaluateEvent(
+                (engine, code) => engine.Evaluate(code, "mysource"),
+                expectedSource: "mysource"
+            );
+        }
+
+        [Fact]
+        public void EvaluateWithParserOptionsShouldTriggerBeforeEvaluateEvent()
+        {
+            TestBeforeEvaluateEvent(
+                (engine, code) => engine.Evaluate(code, ParserOptions.Default),
+                expectedSource: "<anonymous>"
+            );
+        }
+
+        [Fact]
+        public void EvaluateWithSourceAndParserOptionsShouldTriggerBeforeEvaluateEvent()
+        {
+            TestBeforeEvaluateEvent(
+                (engine, code) => engine.Evaluate(code, "mysource", ParserOptions.Default),
+                expectedSource: "mysource"
+            );
+        }
+
+        [Fact]
+        public void ImportModuleShouldTriggerBeforeEvaluateEvents()
+        {
+            var engine = new Engine();
+
+            const string module1 = "import dummy from 'module2';";
+            const string module2 = "export default 'dummy';";
+
+            var beforeEvaluateTriggeredCount = 0;
+            engine.DebugHandler.BeforeEvaluate += (sender, ast) =>
+            {
+                beforeEvaluateTriggeredCount++;
+                Assert.Equal(engine, sender);
+
+                switch (beforeEvaluateTriggeredCount)
+                {
+                    case 1:
+                        Assert.Equal("module1", ast.Location.Source);
+                        Assert.Collection(ast.Body,
+                            node => Assert.IsType<ImportDeclaration>(node)
+                        );
+                        break;
+                    case 2:
+                        Assert.Equal("module2", ast.Location.Source);
+                        Assert.Collection(ast.Body,
+                            node => Assert.IsType<ExportDefaultDeclaration>(node)
+                        );
+                        break;
+                }
+            };
+
+            engine.AddModule("module1", module1);
+            engine.AddModule("module2", module2);
+            engine.ImportModule("module1");
+
+            Assert.Equal(2, beforeEvaluateTriggeredCount);
+        }
+
+        private static void TestBeforeEvaluateEvent(Action<Engine, string> call, string expectedSource)
+        {
+            var engine = new Engine();
+
+            const string script = "'dummy';";
+
+            var beforeEvaluateTriggered = false;
+            engine.DebugHandler.BeforeEvaluate += (sender, ast) =>
+            {
+                beforeEvaluateTriggered = true;
+                Assert.Equal(engine, sender);
+                Assert.Equal(expectedSource, ast.Location.Source);
+                Assert.Collection(ast.Body, node => Assert.True(TestHelpers.IsLiteral(node, "dummy")));
+            };
+
+            call(engine, script);
+
+            Assert.True(beforeEvaluateTriggered);
+        }
+
         private class Wrapper
         {
             public Testificate Test { get; set; }

+ 0 - 17
Jint.Tests/Runtime/Modules/DefaultModuleLoaderTests.cs

@@ -35,23 +35,6 @@ public class DefaultModuleLoaderTests
         Assert.StartsWith(exc.Specifier, specifier);
     }
 
-    [Fact]
-    public void ShouldTriggerLoadedEvent()
-    {
-        var loader = new DefaultModuleLoader(ModuleTests.GetBasePath());
-        bool triggered = false;
-        loader.Loaded += (sender, source, module) =>
-        {
-            Assert.Equal(loader, sender);
-            Assert.NotNull(source);
-            Assert.NotNull(module);
-            triggered = true;
-        };
-        var engine = new Engine(options => options.EnableModules(loader));
-        engine.ImportModule("./modules/format-name.js");
-        Assert.True(triggered);
-    }
-
     [Fact]
     public void ShouldResolveBareSpecifiers()
     {

+ 5 - 0
Jint/Engine.Modules.cs

@@ -41,6 +41,11 @@ namespace Jint
                 module = LoaderFromModuleLoader(moduleResolution);
             }
 
+            if (module is SourceTextModuleRecord sourceTextModule)
+            {
+                DebugHandler.OnBeforeEvaluate(sourceTextModule._source);
+            }
+
             return module;
         }
 

+ 3 - 1
Jint/Engine.cs

@@ -292,6 +292,8 @@ namespace Jint
         /// </summary>
         private Engine ScriptEvaluation(ScriptRecord scriptRecord)
         {
+            DebugHandler.OnBeforeEvaluate(scriptRecord.EcmaScriptCode);
+
             var globalEnv = Realm.GlobalEnv;
 
             var scriptContext = new ExecutionContext(
@@ -363,7 +365,7 @@ namespace Jint
 
             Action<JsValue> SettleWith(FunctionInstance settle) => value =>
             {
-                settle.Call(JsValue.Undefined, new[] {value});
+                settle.Call(JsValue.Undefined, new[] { value });
                 RunAvailableContinuations();
             };
 

+ 1 - 1
Jint/Runtime/Debugger/BreakLocation.cs

@@ -18,7 +18,7 @@ public sealed record BreakLocation
 
     }
 
-    public BreakLocation(string source, Esprima.Position position) : this(source, position.Line, position.Column)
+    public BreakLocation(string? source, Esprima.Position position) : this(source, position.Line, position.Column)
     {
     }
 

+ 59 - 46
Jint/Runtime/Debugger/DebugHandler.cs

@@ -1,3 +1,4 @@
+using System.Runtime.CompilerServices;
 using Esprima;
 using Esprima.Ast;
 using Jint.Native;
@@ -7,6 +8,7 @@ namespace Jint.Runtime.Debugger
 {
     public enum PauseType
     {
+        Skip,
         Step,
         Break,
         DebuggerStatement
@@ -14,6 +16,7 @@ namespace Jint.Runtime.Debugger
 
     public class DebugHandler
     {
+        public delegate void BeforeEvaluateEventHandler(object sender, Program ast);
         public delegate StepMode DebugEventHandler(object sender, DebugInformation e);
 
         private readonly Engine _engine;
@@ -21,7 +24,12 @@ namespace Jint.Runtime.Debugger
         private int _steppingDepth;
 
         /// <summary>
-        /// The Step event is triggered before the engine executes a step-eligible AST node.
+        /// Triggered before the engine executes/evaluates the parsed AST of a script or module.
+        /// </summary>
+        public event BeforeEvaluateEventHandler? BeforeEvaluate;
+
+        /// <summary>
+        /// The Step event is triggered before the engine executes a step-eligible execution point.
         /// </summary>
         /// <remarks>
         /// If the current step mode is <see cref="StepMode.None"/>, this event is never triggered. The script may
@@ -39,12 +47,21 @@ namespace Jint.Runtime.Debugger
         /// </remarks>
         public event DebugEventHandler? Break;
 
+
+        /// <summary>
+        /// The Skip event is triggered for each execution point, when the point doesn't trigger a <see cref="Step"/>
+        /// or <see cref="Break"/> event.
+        /// </summary>
+        public event DebugEventHandler? Skip;
+
         internal DebugHandler(Engine engine, StepMode initialStepMode)
         {
             _engine = engine;
             HandleNewStepMode(initialStepMode);
         }
 
+        private bool IsStepping => _engine.CallStack.Count <= _steppingDepth;
+
         /// <summary>
         /// The location of the current (step-eligible) AST node being executed.
         /// </summary>
@@ -73,7 +90,7 @@ namespace Jint.Runtime.Debugger
             {
                 throw new DebugEvaluationException("Jint has no active evaluation context");
             }
-            int callStackSize = _engine.CallStack.Count;
+            var callStackSize = _engine.CallStack.Count;
 
             var list = new JintStatementList(null, script.Body);
             Completion result;
@@ -123,6 +140,14 @@ namespace Jint.Runtime.Debugger
             }
         }
 
+        internal void OnBeforeEvaluate(Program ast)
+        {
+            if (ast != null)
+            {
+                BeforeEvaluate?.Invoke(_engine, ast);
+            }
+        }
+
         internal void OnStep(Node node)
         {
             // Don't reenter if we're already paused (e.g. when evaluating a getter in a Break/Step handler)
@@ -132,11 +157,7 @@ namespace Jint.Runtime.Debugger
             }
             _paused = true;
 
-            CheckBreakPointAndPause(
-                new BreakLocation(node.Location.Source!, node.Location.Start),
-                node: node,
-                location: null,
-                returnValue: null);
+            CheckBreakPointAndPause(node, node.Location);
         }
 
         internal void OnReturnPoint(Node functionBody, JsValue returnValue)
@@ -152,67 +173,58 @@ namespace Jint.Runtime.Debugger
             var functionBodyEnd = bodyLocation.End;
             var location = Location.From(functionBodyEnd, functionBodyEnd, bodyLocation.Source);
 
-            CheckBreakPointAndPause(
-                new BreakLocation(bodyLocation.Source!, bodyLocation.End),
-                node: null,
-                location: location,
-                returnValue: returnValue);
-        }
-
-        internal void OnDebuggerStatement(Statement statement)
-        {
-            // Don't reenter if we're already paused
-            if (_paused)
-            {
-                return;
-            }
-            _paused = true;
-
-            bool isStepping = _engine.CallStack.Count <= _steppingDepth;
-
-            // Even though we're at a debugger statement, if we're stepping, ignore the statement. OnStep already
-            // takes care of pausing.
-            if (!isStepping)
-            {
-                Pause(PauseType.DebuggerStatement, statement);
-            }
-
-            _paused = false;
+            CheckBreakPointAndPause(node: null, location, returnValue);
         }
 
         private void CheckBreakPointAndPause(
-            BreakLocation breakLocation,
             Node? node,
-            Location? location = null,
+            Location location,
             JsValue? returnValue = null)
         {
-            CurrentLocation = location ?? node?.Location;
-            var breakpoint = BreakPoints.FindMatch(this, breakLocation);
+            CurrentLocation = location;
 
-            bool isStepping = _engine.CallStack.Count <= _steppingDepth;
+            // Even if we matched a breakpoint, if we're stepping, the reason we're pausing is the step.
+            // Still, we need to include the breakpoint at this location, in case the debugger UI needs to update
+            // e.g. a hit count.
+            var breakLocation = new BreakLocation(location.Source, location.Start);
+            var breakPoint = BreakPoints.FindMatch(this, breakLocation);
 
-            if (breakpoint != null || isStepping)
+            PauseType pauseType;
+
+            if (IsStepping)
+            {
+                pauseType = PauseType.Step;
+            }
+            else if (breakPoint != null)
+            {
+                pauseType = PauseType.Break;
+            }
+            else if (node?.Type == Nodes.DebuggerStatement &&
+                _engine.Options.Debugger.StatementHandling == DebuggerStatementHandling.Script)
             {
-                // Even if we matched a breakpoint, if we're stepping, the reason we're pausing is the step.
-                // Still, we need to include the breakpoint at this location, in case the debugger UI needs to update
-                // e.g. a hit count.
-                Pause(isStepping ? PauseType.Step : PauseType.Break, node!, location, returnValue, breakpoint);
+                pauseType = PauseType.DebuggerStatement;
+            }
+            else
+            {
+                pauseType = PauseType.Skip;
             }
 
+            Pause(pauseType, node, location, returnValue, breakPoint);
+
             _paused = false;
         }
 
         private void Pause(
             PauseType type,
-            Node node,
-            Location? location = null,
+            Node? node,
+            Location location,
             JsValue? returnValue = null,
             BreakPoint? breakPoint = null)
         {
             var info = new DebugInformation(
                 engine: _engine,
                 currentNode: node,
-                currentLocation: location ?? node.Location,
+                currentLocation: location,
                 returnValue: returnValue,
                 currentMemoryUsage: _engine.CurrentMemoryUsage,
                 pauseType: type,
@@ -222,6 +234,7 @@ namespace Jint.Runtime.Debugger
             StepMode? result = type switch
             {
                 // Conventionally, sender should be DebugHandler - but Engine is more useful
+                PauseType.Skip => Skip?.Invoke(_engine, info),
                 PauseType.Step => Step?.Invoke(_engine, info),
                 PauseType.Break => Break?.Invoke(_engine, info),
                 PauseType.DebuggerStatement => Break?.Invoke(_engine, info),

+ 2 - 2
Jint/Runtime/Debugger/DebugInformation.cs

@@ -14,7 +14,7 @@ namespace Jint.Runtime.Debugger
 
         internal DebugInformation(
             Engine engine,
-            Node currentNode,
+            Node? currentNode,
             Location currentLocation,
             JsValue? returnValue,
             long currentMemoryUsage,
@@ -51,7 +51,7 @@ namespace Jint.Runtime.Debugger
         /// The AST Node that will be executed on next step.
         /// Note that this will be null when execution is at a return point.
         /// </summary>
-        public Node CurrentNode { get; }
+        public Node? CurrentNode { get; }
 
         /// <summary>
         /// The current source Location.

+ 4 - 5
Jint/Runtime/Debugger/DebugScope.cs

@@ -10,13 +10,12 @@ namespace Jint.Runtime.Debugger
     public sealed class DebugScope
     {
         private readonly EnvironmentRecord _record;
-        private readonly List<string> _bindingNames;
+        private string[]? _bindingNames;
 
-        internal DebugScope(DebugScopeType type, EnvironmentRecord record, List<string> bindingNames, bool isTopLevel)
+        internal DebugScope(DebugScopeType type, EnvironmentRecord record, bool isTopLevel)
         {
             ScopeType = type;
             _record = record;
-            _bindingNames = bindingNames;
             BindingObject = record is ObjectEnvironmentRecord objEnv ? objEnv._bindingObject : null;
             IsTopLevel = isTopLevel;
         }
@@ -37,9 +36,9 @@ namespace Jint.Runtime.Debugger
         public bool IsTopLevel { get; }
 
         /// <summary>
-        /// Names of all non-shadowed bindings in the scope.
+        /// Names of all bindings in the scope.
         /// </summary>
-        public IReadOnlyList<string> BindingNames => _bindingNames;
+        public IReadOnlyList<string> BindingNames => _bindingNames ??= _record.GetAllBindingNames();
 
         /// <summary>
         /// Binding object for ObjectEnvironmentRecords - that is, Global scope and With scope. Null for other scopes.

+ 2 - 47
Jint/Runtime/Debugger/DebugScopes.cs

@@ -5,7 +5,6 @@ namespace Jint.Runtime.Debugger
 {
     public sealed class DebugScopes : IReadOnlyList<DebugScope>
     {
-        private readonly HashSet<string> _foundBindings = new();
         private readonly List<DebugScope> _scopes = new();
 
         internal DebugScopes(EnvironmentRecord environment)
@@ -13,23 +12,6 @@ namespace Jint.Runtime.Debugger
             Populate(environment);
         }
 
-        /// <summary>
-        /// Shortcut to Global scope.
-        /// </summary>
-        /// <remarks>
-        /// Note that this only includes the object environment record of the Global scope - i.e. it doesn't
-        /// include block scope bindings (let/const).
-        /// </remarks>
-        public DebugScope? Global { get; private set; }
-
-        /// <summary>
-        /// Shortcut to Local scope.
-        /// </summary>
-        /// <remarks>
-        /// Note that this is only present inside functions, and doesn't include block scope bindings (let/const)
-        /// </remarks>
-        public DebugScope? Local { get; private set; }
-
         public DebugScope this[int index] => _scopes[index];
         public int Count => _scopes.Count;
 
@@ -77,37 +59,10 @@ namespace Jint.Runtime.Debugger
 
         private void AddScope(DebugScopeType type, EnvironmentRecord record, bool isTopLevel = false)
         {
-            var bindings = new List<string>();
-            PopulateBindings(bindings, record);
-
-            if (bindings.Count > 0)
+            if (record.HasBindings())
             {
-                var scope = new DebugScope(type, record, bindings, isTopLevel);
+                var scope = new DebugScope(type, record, isTopLevel);
                 _scopes.Add(scope);
-                switch (type)
-                {
-                    case DebugScopeType.Global:
-                        Global = scope;
-                        break;
-                    case DebugScopeType.Local:
-                        Local = scope;
-                        break;
-                }
-            }
-        }
-
-        private void PopulateBindings(List<string> bindings, EnvironmentRecord record)
-        {
-            var bindingNames = record.GetAllBindingNames();
-
-            foreach (var name in bindingNames)
-            {
-                // Only add non-shadowed bindings
-                if (!_foundBindings.Contains(name))
-                {
-                    bindings.Add(name);
-                    _foundBindings.Add(name);
-                }
             }
         }
 

+ 5 - 0
Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs

@@ -153,6 +153,11 @@ namespace Jint.Runtime.Environments
 
         public sealed override JsValue WithBaseObject() => Undefined;
 
+        public sealed override bool HasBindings()
+        {
+            return _dictionary?.Count > 0;
+        }
+
         /// <inheritdoc />
         internal sealed override string[] GetAllBindingNames()
         {

+ 2 - 0
Jint/Runtime/Environments/EnvironmentRecord.cs

@@ -85,6 +85,8 @@ namespace Jint.Runtime.Environments
 
         public abstract JsValue WithBaseObject();
 
+        public abstract bool HasBindings();
+
         /// <summary>
         /// Returns an array of all the defined binding names
         /// </summary>

+ 5 - 0
Jint/Runtime/Environments/GlobalEnvironmentRecord.cs

@@ -376,6 +376,11 @@ namespace Jint.Runtime.Environments
             _varNames.Add(name);
         }
 
+        public sealed override bool HasBindings()
+        {
+            return _declarativeRecord.HasBindings() || _globalObject?._properties?.Count > 0 || _global._properties?.Count > 0;
+        }
+
         internal override string[] GetAllBindingNames()
         {
             // JT: Rather than introduce a new method for the debugger, I'm reusing this one,

+ 4 - 0
Jint/Runtime/Environments/ObjectEnvironmentRecord.cs

@@ -177,6 +177,10 @@ namespace Jint.Runtime.Environments
 
         public override JsValue WithBaseObject() => _withEnvironment ? _bindingObject : Undefined;
 
+        public sealed override bool HasBindings()
+        {
+            return _bindingObject._properties?.Count > 0;
+        }
 
         internal override string[] GetAllBindingNames()
         {

+ 1 - 2
Jint/Runtime/Interpreter/Statements/JintDebuggerStatement.cs

@@ -22,9 +22,8 @@ namespace Jint.Runtime.Interpreter.Statements
 
                     System.Diagnostics.Debugger.Break();
                     break;
+                // DebugHandler handles DebuggerStatementHandling.Script during OnStep
                 case DebuggerStatementHandling.Script:
-                    engine.DebugHandler?.OnDebuggerStatement(_statement);
-                    break;
                 case DebuggerStatementHandling.Ignore:
                     break;
             }

+ 0 - 12
Jint/Runtime/Modules/DefaultModuleLoader.cs

@@ -5,19 +5,9 @@ namespace Jint.Runtime.Modules;
 
 public sealed class DefaultModuleLoader : IModuleLoader
 {
-    public delegate void ModuleLoadedEventHandler(object sender, string source, Module module);
-
     private readonly Uri _basePath;
     private readonly bool _restrictToBasePath;
 
-    /// <summary>
-    /// The Loaded event is triggered after a module is loaded and parsed.
-    /// </summary>
-    /// <remarks>
-    /// The event is not triggered if a module was loaded but failed to parse.
-    /// </remarks>
-    public event ModuleLoadedEventHandler? Loaded;
-
     public DefaultModuleLoader(string basePath) : this(basePath, true)
     {
 
@@ -152,8 +142,6 @@ public sealed class DefaultModuleLoader : IModuleLoader
             module = null;
         }
 
-        Loaded?.Invoke(this, source, module);
-
         return module;
     }
 

+ 1 - 1
Jint/Runtime/Modules/SourceTextModuleRecord.cs

@@ -32,7 +32,7 @@ internal sealed record ExportEntry(
 /// </summary>
 internal class SourceTextModuleRecord : CyclicModuleRecord
 {
-    private readonly Module _source;
+    internal readonly Module _source;
     private ExecutionContext _context;
     private ObjectInstance _importMeta;
     private readonly List<ImportEntry> _importEntries;