Skip to content
Permalink
Browse files
Web Inspector: Add Function Breakpoints/Tracepoints (like Symbolic Br…
…eakpoints)

https://bugs.webkit.org/show_bug.cgi?id=142914
<rdar://problem/20242025>

Reviewed by Patrick Angle.

Existing JavaScript breakpoints are only useful so long as the developer knows the source code
location of the function. Web Inspector does provide a bunch of ways to find that location (e.g.
Search Tab, hovering a symbol when paused to see it's definition, etc.), but that makes adding a
breakpoint into a multi-step process. To make matters worse, it's not uncommon for there to be
different functions with the same name, meaning that if the developer wants to add breakpoints to
all of them it becomes a geometric problem (and that's not even considering the hassle of having to
duplicate any breakpoint configuration each time).

Symbolic breakpoints are a single-step way to add a single breakpoint that will be used across all
functions that share the same name, regardless of when they are created or where they are located.

* Source/JavaScriptCore/debugger/Debugger.h:
(JSC::Debugger::Observer::willEnter): Added.
(JSC::Debugger::Observer::applyBreakpoints): Added.
* Source/JavaScriptCore/debugger/Debugger.cpp:
(JSC::Debugger::forEachRegisteredCodeBlock): Added.
(JSC::Debugger::applyBreakpoints):
(JSC::Debugger::callEvent):
Add hooks to allow `InspectorDebuggerAgent` to also do things whenever a new `CodeBlock` is created
and when a new `CallFrame` is about to be entered (i.e. a function call).

* Source/JavaScriptCore/inspector/protocol/Debugger.json:
* Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h:
(Inspector::InspectorDebuggerAgent::SymbolicBreakpoint::operator== const): Added.
* Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp:
(Inspector::functionName): Added.
(Inspector::InspectorDebuggerAgent::addSymbolicBreakpoint): Added.
(Inspector::InspectorDebuggerAgent::removeSymbolicBreakpoint): Added.
(Inspector::InspectorDebuggerAgent::willEnter): Added.
(Inspector::InspectorDebuggerAgent::applyBreakpoints): Added.
(Inspector::InspectorDebuggerAgent::SymbolicBreakpoint::matches): Added.
Add commands for adding/removing symbolic breakpoints, leveraging the above `Debugger` hooks to mark
matching `CodeBlock` as having a (symbolic) breakpoint so that debug hooks will not be ignored.

* Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js:
(WI.DebuggerManager):
(WI.DebuggerManager.prototype.async initializeTarget):
(WI.DebuggerManager.pauseReasonFromPayload):
(WI.DebuggerManager.prototype.symbolicBreakpointsForSymbol): Added.
(WI.DebuggerManager.prototype.addSymbolicBreakpoint): Added.
(WI.DebuggerManager.prototype.removeSymbolicBreakpoint): Added.
(WI.DebuggerManager.prototype._setSymbolicBreakpoint): Added.
(WI.DebuggerManager.prototype._removeSymbolicBreakpoint): Added.
(WI.DebuggerManager.prototype._handleSymbolicBreakpointDisabledStateChanged): Added.
(WI.DebuggerManager.prototype._handleSymbolicBreakpointEditablePropertyChanged): Added.
(WI.DebuggerManager.prototype._handleSymbolicBreakpointActionsChanged): Added.
Manage adding/removing symbolic breakpoints, including handling state changes for existing symbolic
breakpoints (e.g. disabled, actions, etc.).

* Source/WebInspectorUI/UserInterface/Models/SymbolicBreakpoint.js: Added.
(WI.SymbolicBreakpoint):
(WI.SymbolicBreakpoint.supported):
(WI.SymbolicBreakpoint.fromJSON):
(WI.SymbolicBreakpoint.prototype.get symbol):
(WI.SymbolicBreakpoint.prototype.get caseSensitive):
(WI.SymbolicBreakpoint.prototype.get isRegex):
(WI.SymbolicBreakpoint.prototype.get displayName):
(WI.SymbolicBreakpoint.prototype.get editable):
(WI.SymbolicBreakpoint.prototype.matches):
(WI.SymbolicBreakpoint.prototype.equals):
(WI.SymbolicBreakpoint.prototype.remove):
(WI.SymbolicBreakpoint.prototype.saveIdentityToCookie):
(WI.SymbolicBreakpoint.prototype.toJSON):
Create a model object to represent a symbolic breakpoint.

* Source/WebInspectorUI/UserInterface/Views/BreakpointPopover.js:
(WI.BreakpointPopover.show):
* Source/WebInspectorUI/UserInterface/Views/SymbolicBreakpointPopover.js: Added.
(WI.SymbolicBreakpointPopover):
(WI.SymbolicBreakpointPopover.get supportsEditing):
(WI.SymbolicBreakpointPopover.prototype.populateContent):
(WI.SymbolicBreakpointPopover.prototype.createBreakpoint):
(WI.SymbolicBreakpointPopover.prototype._updateSymbolCodeMirrorMode):
* Source/WebInspectorUI/UserInterface/Views/SymbolicBreakpointPopover.css: Added.
(.popover .edit-breakpoint-popover-content .symbol.editor):
(.popover .edit-breakpoint-popover-content .symbol.editor > .CodeMirror):
(.popover .edit-breakpoint-popover-content .symbol.editor + label):
(.popover .edit-breakpoint-popover-content .symbol.editor + label + label):
* Source/WebInspectorUI/UserInterface/Views/SymbolicBreakpointTreeElement.js: Added.
(WI.SymbolicBreakpointTreeElement):
* Source/WebInspectorUI/UserInterface/Views/SymbolicBreakpointTreeElement.css: Added.
(.item.breakpoint.symbolic:not(.paused) .icon):
(@media(prefers-color-scheme: dark) .item.breakpoint.symbolic:not(.paused) .icon):
* Source/WebInspectorUI/UserInterface/Views/NavigationSidebarPanel.js:
(WI.NavigationSidebarPanel.prototype._isTreeElementWithoutRepresentedObject):
* Source/WebInspectorUI/UserInterface/Views/SourcesNavigationSidebarPanel.js:
(WI.SourcesNavigationSidebarPanel):
(WI.SourcesNavigationSidebarPanel.prototype.closed):
(WI.SourcesNavigationSidebarPanel.prototype.willDismissPopover):
(WI.SourcesNavigationSidebarPanel.prototype._willDismissSymbolicBreakpointPopover): Added.
(WI.SourcesNavigationSidebarPanel.prototype._insertDebuggerTreeElement):
(WI.SourcesNavigationSidebarPanel.prototype._addBreakpoint):
(WI.SourcesNavigationSidebarPanel.prototype._updatePauseReasonSection):
(WI.SourcesNavigationSidebarPanel.prototype._handleTreeSelectionDidChange):
(WI.SourcesNavigationSidebarPanel.prototype._populateCreateBreakpointContextMenu):
Add various UI elements for `WI.SymbolicBreakpoint`.

* Source/WebInspectorUI/UserInterface/Base/Setting.js:
* Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js:
(WI.SettingsTabContentView.prototype._createExperimentalSettingsView):
Create an experimental setting for symbolic breakpoints, as they currently do not work for
- native functions (e.g. `Event.prototype.preventDefault`)
- functions that changed their `name` after creation (e.g. `foo.name = "bar"`)

* Source/WebInspectorUI/UserInterface/Base/ObjectStore.js:
(WI.ObjectStore._open):
Increment the database version for the new symbolic breakpoint object store.

* Source/WebInspectorUI/UserInterface/Base/ReferencePage.js:
Create a link to a reference page (to be created).

* Source/WebInspectorUI/UserInterface/Main.html:
* Source/WebInspectorUI/UserInterface/Images/TypeIcons.svg:
* Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js:

* Source/WebInspectorUI/UserInterface/Test.html:
* LayoutTests/inspector/debugger/symbolic-breakpoint.html: Added.
* LayoutTests/inspector/debugger/symbolic-breakpoint-expected.txt: Added.
* LayoutTests/inspector/debugger/symbolic-breakpoint-exact-case-insensitive.html: Added.
* LayoutTests/inspector/debugger/symbolic-breakpoint-exact-case-insensitive-expected.txt: Added.
* LayoutTests/inspector/debugger/symbolic-breakpoint-exact-case-sensitive.html: Added.
* LayoutTests/inspector/debugger/symbolic-breakpoint-exact-case-sensitive-expected.txt: Added.
* LayoutTests/inspector/debugger/symbolic-breakpoint-regex-case-insensitive.html: Added.
* LayoutTests/inspector/debugger/symbolic-breakpoint-regex-case-insensitive-expected.txt: Added.
* LayoutTests/inspector/debugger/symbolic-breakpoint-regex-case-sensitive.html: Added.
* LayoutTests/inspector/debugger/symbolic-breakpoint-regex-case-sensitive-expected.txt: Added.

Canonical link: https://commits.webkit.org/253374@main
  • Loading branch information
dcrousso committed Aug 12, 2022
1 parent 6394c9b commit 631ebd578babc70634907386b4f103a80f4c754f
Show file tree
Hide file tree
Showing 32 changed files with 2,161 additions and 5 deletions.
@@ -0,0 +1,174 @@
CONSOLE MESSAGE: BREAKPOINT ACTION LOG 1
CONSOLE MESSAGE: BREAKPOINT ACTION LOG 2
CONSOLE MESSAGE: BREAKPOINT ACTION LOG 3
CONSOLE MESSAGE: BREAKPOINT ACTION LOG 4
Tests for SymbolicBreakpoint.


== Running test suite: SymbolicBreakpoint
-- Running test case: SymbolicBreakpoint.ExactCaseInsensitive.Hit
Adding breakpoint...
Triggering breakpoint...
PASS: Should pause.

Removing breakpoint...
Triggering breakpoint...
PASS: Should not pause.

-- Running test case: SymbolicBreakpoint.ExactCaseInsensitive.Hit.Options.Condition

Setting condition to 'false'...

Triggering breakpoint...
PASS: Should not pause.

Triggering breakpoint...
PASS: Should not pause.

Setting condition to 'true'...

Triggering breakpoint...
PASS: Should pause.

Triggering breakpoint...
PASS: Should pause.

-- Running test case: SymbolicBreakpoint.ExactCaseInsensitive.Hit.Options.Condition.ConsoleCommandLineAPI
Adding saved console value 'false'...

Setting condition to saved console value...

Triggering breakpoint...
PASS: Should not pause.

Triggering breakpoint...
PASS: Should not pause.

Adding saved console value 'true'...
Setting condition to saved console value...

Triggering breakpoint...
PASS: Should pause.

Triggering breakpoint...
PASS: Should pause.

-- Running test case: SymbolicBreakpoint.ExactCaseInsensitive.Hit.Options.IgnoreCount

Setting ignoreCount to '2'...

Triggering breakpoint...
PASS: Should not pause.

Triggering breakpoint...
PASS: Should not pause.

Triggering breakpoint...
PASS: Should pause.

Triggering breakpoint...
PASS: Should pause.

-- Running test case: SymbolicBreakpoint.ExactCaseInsensitive.Hit.Options.Action.Log

Adding log action...

Triggering breakpoint...
PASS: Should execute breakpoint action.
PASS: Should pause.

Editing log action...

Triggering breakpoint...
PASS: Should execute breakpoint action.
PASS: Should pause.

Editing log action...
Enabling auto-continue...

Triggering breakpoint...
PASS: Should execute breakpoint action.
PASS: Should not pause.

Editing log action...

Triggering breakpoint...
PASS: Should execute breakpoint action.
PASS: Should not pause.

-- Running test case: SymbolicBreakpoint.ExactCaseInsensitive.Hit.Options.Actions.Evaluate

Adding evaluate action...

Triggering breakpoint...
PASS: Should execute breakpoint action.
PASS: Should pause.

Editing evaluate action...

Triggering breakpoint...
PASS: Should execute breakpoint action.
PASS: Should pause.

Editing evaluate action...
Enabling auto-continue...

Triggering breakpoint...
PASS: Should execute breakpoint action.
PASS: Should not pause.

Editing evaluate action...

Triggering breakpoint...
PASS: Should execute breakpoint action.
PASS: Should not pause.

-- Running test case: SymbolicBreakpoint.ExactCaseInsensitive.Hit.Options.Actions.Evaluate.ConsoleCommandLineAPI
Adding saved console value '1'...

Adding evaluate action using saved console value...

Triggering breakpoint...
PASS: Should execute breakpoint action.
PASS: Should pause.

Adding saved console value '2'...
Editing evaluate action using saved console value...

Triggering breakpoint...
PASS: Should execute breakpoint action.
PASS: Should pause.

Adding saved console value '3'...
Editing evaluate action using saved console value...
Enabling auto-continue...

Triggering breakpoint...
PASS: Should execute breakpoint action.
PASS: Should not pause.

Adding saved console value '4'...
Editing evaluate action using saved console value...

Triggering breakpoint...
PASS: Should execute breakpoint action.
PASS: Should not pause.

-- Running test case: SymbolicBreakpoint.ExactCaseInsensitive.Miss
Adding breakpoint...
Triggering breakpoint...
PASS: Should not pause.

Removing breakpoint...
Triggering breakpoint...
PASS: Should not pause.

-- Running test case: SymbolicBreakpoint.ExactCaseInsensitive.Disabled
Adding breakpoint...
Triggering breakpoint...
PASS: Should not pause.

Removing breakpoint...
Triggering breakpoint...
PASS: Should not pause.

@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html>
<head>
<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
<script src="resources/breakpoint-options-utilities.js"></script>
<script>
function triggerSymbolicBreakpoint() {
TestPage.dispatchEventToFrontend("TestPage-SymbolicBreakpoint");
}

function test()
{
WI.settings.experimentalEnableSymbolicBreakpoints.value = true;

function triggerBreakpoint() {
return Promise.all([
InspectorTest.awaitEvent("TestPage-SymbolicBreakpoint"),
InspectorTest.evaluateInPage(`triggerSymbolicBreakpoint()`),
]);
}

let suite = InspectorTest.createAsyncSuite("SymbolicBreakpoint");

suite.addTestCase({
name: "SymbolicBreakpoint.ExactCaseInsensitive.Hit",
async test() {
let pauseCount = 0;
let pausedListener = WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Paused, (event) => {
++pauseCount;
WI.debuggerManager.resume();
});

InspectorTest.log("Adding breakpoint...");
let breakpoint = new WI.SymbolicBreakpoint("tRiGgErSyMbOlIcBrEaKpOiNt", {caseSensitive: false});
WI.debuggerManager.addSymbolicBreakpoint(breakpoint);
InspectorTest.assert(WI.debuggerManager.symbolicBreakpointsForSymbol("triggerSymbolicBreakpoint")[0] === breakpoint, "Should match breakpoint.");

InspectorTest.log("Triggering breakpoint...");
await triggerBreakpoint();
InspectorTest.expectEqual(pauseCount, 1, "Should pause.");

InspectorTest.newline();

InspectorTest.log("Removing breakpoint...");
breakpoint.remove();

InspectorTest.log("Triggering breakpoint...");
await triggerBreakpoint();
InspectorTest.expectEqual(pauseCount, 1, "Should not pause.");

WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.Paused, pausedListener);
},
});

InspectorTest.BreakpointOptions.addTestCases(suite, {
testCaseNamePrefix: "ExactCaseInsensitive.Hit.",
createBreakpoint() {
return new WI.SymbolicBreakpoint("tRiGgErSyMbOlIcBrEaKpOiNt", {caseSensitive: false});
},
triggerBreakpoint,
});

suite.addTestCase({
name: "SymbolicBreakpoint.ExactCaseInsensitive.Miss",
async test() {
let pauseCount = 0;
let pausedListener = WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Paused, (event) => {
++pauseCount;
WI.debuggerManager.resume();
});

InspectorTest.log("Adding breakpoint...");
let breakpoint = new WI.SymbolicBreakpoint("tRiGgEr", {caseSensitive: false});
WI.debuggerManager.addSymbolicBreakpoint(breakpoint);
InspectorTest.assert(!WI.debuggerManager.symbolicBreakpointsForSymbol("triggerSymbolicBreakpoint").length, "Should not match breakpoint.");

InspectorTest.log("Triggering breakpoint...");
await triggerBreakpoint();
InspectorTest.expectEqual(pauseCount, 0, "Should not pause.");

InspectorTest.newline();

InspectorTest.log("Removing breakpoint...");
breakpoint.remove();

InspectorTest.log("Triggering breakpoint...");
await triggerBreakpoint();
InspectorTest.expectEqual(pauseCount, 0, "Should not pause.");

WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.Paused, pausedListener);
},
});

suite.addTestCase({
name: "SymbolicBreakpoint.ExactCaseInsensitive.Disabled",
async test() {
let pauseCount = 0;
let pausedListener = WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Paused, (event) => {
++pauseCount;
WI.debuggerManager.resume();
});

InspectorTest.log("Adding breakpoint...");
let breakpoint = new WI.SymbolicBreakpoint("tRiGgErSyMbOlIcBrEaKpOiNt", {caseSensitive: false, disabled: true});
WI.debuggerManager.addSymbolicBreakpoint(breakpoint);
InspectorTest.assert(!WI.debuggerManager.symbolicBreakpointsForSymbol("triggerSymbolicBreakpoint").length, "Should not match breakpoint.");

InspectorTest.log("Triggering breakpoint...");
await triggerBreakpoint();
InspectorTest.expectEqual(pauseCount, 0, "Should not pause.");

InspectorTest.newline();

InspectorTest.log("Removing breakpoint...");
breakpoint.remove();

InspectorTest.log("Triggering breakpoint...");
await triggerBreakpoint();
InspectorTest.expectEqual(pauseCount, 0, "Should not pause.");

WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.Paused, pausedListener);
},
});

suite.runTestCasesAndFinish();
}
</script>
</head>
<body onload="runTest()">
<p>Tests for SymbolicBreakpoint.</p>
</body>
</html>

0 comments on commit 631ebd5

Please sign in to comment.