Bug 183118

Summary: Web Inspector: support breakpoints for arbitrary event names
Product: WebKit Reporter: Devin Rousso <hi>
Component: Web InspectorAssignee: Devin Rousso <hi>
Status: RESOLVED FIXED    
Severity: Normal CC: bburg, commit-queue, ews-watchlist, hi, inspector-bugzilla-changes, joepeck, mattbaker, rniwa, tsavell, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: All   
OS: All   
See Also: https://bugs.webkit.org/show_bug.cgi?id=188672
Bug Depends on:    
Bug Blocks: 188708, 183138    
Attachments:
Description Flags
Patch
none
[Image] After Patch is applied - Pause
none
Archive of layout-test-results from ews102 for mac-sierra
none
Archive of layout-test-results from ews105 for mac-sierra-wk2
none
Archive of layout-test-results from ews113 for mac-sierra
none
Patch
none
[Image] After Patch is applied - Popover
none
Patch
none
[Image] Xcode Symbolic Breakpoint - UI Inspiration
none
[Image] EventBreakpoint.svg
none
Patch
none
Archive of layout-test-results from ews103 for mac-sierra
none
Archive of layout-test-results from ews104 for mac-sierra-wk2
none
Archive of layout-test-results from ews116 for mac-sierra
none
Patch
none
Patch
none
Patch none

Devin Rousso
Reported 2018-02-25 23:24:39 PST
Similar to XHR breakpoints, we should have the ability to set a breakpoint on all "click" events such that each time a registered event listener for "click" is about to fire, we break.
Attachments
Patch (55.33 KB, patch)
2018-02-25 23:52 PST, Devin Rousso
no flags
[Image] After Patch is applied - Pause (101.87 KB, image/png)
2018-02-25 23:53 PST, Devin Rousso
no flags
Archive of layout-test-results from ews102 for mac-sierra (2.18 MB, application/zip)
2018-02-26 00:53 PST, EWS Watchlist
no flags
Archive of layout-test-results from ews105 for mac-sierra-wk2 (2.57 MB, application/zip)
2018-02-26 00:58 PST, EWS Watchlist
no flags
Archive of layout-test-results from ews113 for mac-sierra (2.95 MB, application/zip)
2018-02-26 03:05 PST, EWS Watchlist
no flags
Patch (55.33 KB, patch)
2018-02-26 12:14 PST, Devin Rousso
no flags
[Image] After Patch is applied - Popover (524.21 KB, image/png)
2018-03-09 16:26 PST, Devin Rousso
no flags
Patch (56.46 KB, patch)
2018-03-13 23:13 PDT, Devin Rousso
no flags
[Image] Xcode Symbolic Breakpoint - UI Inspiration (50.62 KB, image/png)
2018-07-30 14:19 PDT, Joseph Pecoraro
no flags
[Image] EventBreakpoint.svg (1.94 KB, image/svg+xml)
2018-07-30 14:23 PDT, Joseph Pecoraro
no flags
Patch (56.28 KB, patch)
2018-08-02 00:56 PDT, Devin Rousso
no flags
Archive of layout-test-results from ews103 for mac-sierra (2.30 MB, application/zip)
2018-08-02 02:01 PDT, EWS Watchlist
no flags
Archive of layout-test-results from ews104 for mac-sierra-wk2 (2.82 MB, application/zip)
2018-08-02 02:08 PDT, EWS Watchlist
no flags
Archive of layout-test-results from ews116 for mac-sierra (3.03 MB, application/zip)
2018-08-02 02:39 PDT, EWS Watchlist
no flags
Patch (56.30 KB, patch)
2018-08-02 09:30 PDT, Devin Rousso
no flags
Patch (62.69 KB, patch)
2018-08-15 09:53 PDT, Devin Rousso
no flags
Patch (66.75 KB, patch)
2018-08-16 18:09 PDT, Devin Rousso
no flags
Devin Rousso
Comment 1 2018-02-25 23:52:53 PST
Devin Rousso
Comment 2 2018-02-25 23:53:09 PST
Created attachment 334594 [details] [Image] After Patch is applied - Pause
EWS Watchlist
Comment 3 2018-02-26 00:53:24 PST Comment hidden (obsolete)
EWS Watchlist
Comment 4 2018-02-26 00:53:26 PST Comment hidden (obsolete)
EWS Watchlist
Comment 5 2018-02-26 00:58:43 PST Comment hidden (obsolete)
EWS Watchlist
Comment 6 2018-02-26 00:58:44 PST Comment hidden (obsolete)
EWS Watchlist
Comment 7 2018-02-26 03:05:41 PST Comment hidden (obsolete)
EWS Watchlist
Comment 8 2018-02-26 03:05:43 PST Comment hidden (obsolete)
Devin Rousso
Comment 9 2018-02-26 12:14:29 PST
Blaze Burg
Comment 10 2018-02-27 15:16:06 PST
Comment on attachment 334633 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=334633&action=review > Source/WebCore/ChangeLog:8 > + Test: inspector/dom-debugger/event-listener-breakpoints.html This is a cool patch, but I want to nail down the use cases/behavior of the new feature before getting into the weeds with code comments. It's not obvious from the screenshot how this will be used by a developer. Is it to pause on an event to see if it fires at all prior to adding an event listener? an easy way to set breakpoints on event listeners which may be scattered throughout the web app? As it is written, this patch will only pause when an event listener is about to run for the specified event type. I think it'd be useful for the backend to support pausing for *any* event regardless of whether it has a listener in the code right now or not. One option, which may be preferable anyway, is for Inspector to inject its own event listener so that the existing code path "just works" even if there is no listener in the page's code. If we don't do something like that, certain events such as wheel/scroll events will never actually be dispatched on the main thread if there are no listeners on the page. > Source/WebInspectorUI/ChangeLog:7 > + Please include high-level description of the design/implementation.
Devin Rousso
Comment 11 2018-02-28 01:38:42 PST
Comment on attachment 334633 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=334633&action=review >> Source/WebCore/ChangeLog:8 >> + Test: inspector/dom-debugger/event-listener-breakpoints.html > > This is a cool patch, but I want to nail down the use cases/behavior of the new feature before getting into the weeds with code comments. > > It's not obvious from the screenshot how this will be used by a developer. Is it to pause on an event to see if it fires at all prior to adding an event listener? an easy way to set breakpoints on event listeners which may be scattered throughout the web app? > As it is written, this patch will only pause when an event listener is about to run for the specified event type. I think it'd be useful for the backend to support pausing for *any* event regardless of whether it has a listener in the code right now or not. > > One option, which may be preferable anyway, is for Inspector to inject its own event listener so that the existing code path "just works" even if there is no listener in the page's code. If we don't do something like that, certain events such as wheel/scroll events will never actually be dispatched on the main thread if there are no listeners on the page. My "use case" is to debug complex pages where an unexpected `event.preventDefault()` is getting called somewhere, preventing the desired event listener from being run. As far as pausing for *any* event, I was thinking that that would be a followup of sorts. This patch is more of a "proof of concept". My "vision" for doing that would be to add a checkbox to the EventListenerBreakpointPopover that says something like "Force Pause if no Event Listeners are registered". The "followup" patch to this <https://webkit.org/b/183138> uses the same instrumentation point to pause the next time the given event is fired for the given listener on the given element (basically trying to be as specific as possible), since a callback might be used for multiple event listeners or the element might have multiple callbacks for the same event. Just so that it's also written down somewhere, I plan to add global breakpoints similar to XHR's "All Requests" for `setTimeout`, `setInterval`, and `requestAnimationFrame`. In the future, some of this logic could also probably play into breaking whenever a Promise resolves/rejects, but that probably would require additional instrumentation points. >> Source/WebInspectorUI/ChangeLog:7 >> + > > Please include high-level description of the design/implementation. The vast majority of this is UI, but I can see how it could be confusing. I'll give an explanation of how it all ties together.
Matt Baker
Comment 12 2018-03-08 14:35:49 PST
Screen shots showing the UI that allows the user to actually add, for example, a "click" event breakpoint on an element would be helpful.
Devin Rousso
Comment 13 2018-03-08 23:43:00 PST
(In reply to Matt Baker from comment #12) > Screen shots showing the UI that allows the user to actually add, for > example, a "click" event breakpoint on an element would be helpful. Right now the UI is just a popover with an input field. There's nothing "special" about it. I'll do a build overnight and post something tomorrow. :)
Devin Rousso
Comment 14 2018-03-09 16:26:38 PST
Created attachment 335486 [details] [Image] After Patch is applied - Popover
Devin Rousso
Comment 15 2018-03-13 23:13:39 PDT
Radar WebKit Bug Importer
Comment 16 2018-05-21 14:50:45 PDT
Joseph Pecoraro
Comment 17 2018-07-30 14:19:18 PDT
Created attachment 346100 [details] [Image] Xcode Symbolic Breakpoint - UI Inspiration
Joseph Pecoraro
Comment 18 2018-07-30 14:23:52 PDT
Created attachment 346101 [details] [Image] EventBreakpoint.svg
Joseph Pecoraro
Comment 19 2018-07-30 14:35:39 PDT
Comment on attachment 335762 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=335762&action=review This looks great to me! But the patch no longer applies. Rebaseline it, and address my few comments above, and lets get this in! I may have some additional comments on the next round after I get to play with this. > Source/WebInspectorUI/UserInterface/Controllers/DOMDebuggerManager.js:213 > + if (this._eventListenerBreakpoints.includes(breakpoint)) > + return; > + > + if (this._eventListenerBreakpoints.some((item) => item.eventName === breakpoint.eventName)) > + return; Given we always do the bottom expression I guess we can drop the includes check, because it is just redundant. > Source/WebInspectorUI/UserInterface/Controllers/EventListenerBreakpointTreeController.js:26 > +WI.EventListenerBreakpointTreeController = class EventListenerBreakpointTreeController extends WI.Object May not need to extend WI.Object if you don't dispatch events. > Source/WebInspectorUI/UserInterface/Models/EventListenerBreakpoint.js:31 > + Nit: I like throwing validation lines in here, as it'll show what arguments are required and their expected types. For example here I would probably just do at least: console.assert(typeof eventName === "string") > Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.js:1237 > + popover.show(event.target.element, [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_Y, WI.RectEdge.MAX_X]); After reading this, and pointing my finger in various directions like a complete fool, I wonder if it would be easier to have aliases of these: WI.Popover.Present.Above WI.Popover.Present.Below WI.Popover.Present.After (which would be Left or Right depending on RTL) WI.Popover.Present.Before (which would be Left or Right depending on RTL) > Source/WebInspectorUI/UserInterface/Views/EventListenerBreakpointPopover.js:57 > + label.textContent = WI.UIString("Break on listeners for event with name:"); UI: In the screenshot the <div> and <input> look a little too close together. We may want to run this past someone with UI chops. This label also feels too long. Take a look at the Xcode UI for adding Symbolic breakpoints. I think we could do something simpler like that. > Source/WebInspectorUI/UserInterface/Views/EventListenerBreakpointPopover.js:59 > + this._inputElement = contentElement.appendChild(document.createElement("input")); I'd like to see a placeholder for this. Perhaps just: this._inputElement.placeholder = "click" Would be enough for users to know what to type in here. We may even be able to include autocompletion for a bunch of event names. WI.ScriptTimelineRecord.EventType.displayName has 146 of them, but some look out of date. We could do better and autogenerate from EventNames.h. > Source/WebInspectorUI/UserInterface/Views/EventListenerBreakpointTreeElement.js:33 > + if (!className) > + className = WI.BreakpointTreeElement.GenericLineIconStyleClassName; UI: I'd like the default to be a blue [E] icon. The core ideas being: • [E] means Event / Event Listener in other places in our UI • We pretty consistently use blue [#] icons to mean breakpoints Basically modify EventListener.svg to have the color of Exception.svg. NOTE: I just did that to produce the attached EventBreakpoint.svg. > LayoutTests/inspector/dom-debugger/event-listener-breakpoints-expected.txt:6 > +Adding Breakpoint... This would read better if you included the event name in the test. Perhaps something like: Adding Breakpoint (click)... Adding "click" Event Breakpoint... > LayoutTests/inspector/dom-debugger/event-listener-breakpoints-expected.txt:35 > +-- Running test teardown. I would like to see another test for a custom event name and dispatch: (it could be a separate test file if you want to keep this fundamental) https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events If I understand things correctly if code on the page dispatches a custom event "TestEvent", and we have a breakpoint on that name, we could potentially show the dispatchEvent(...) in the backtrace, which would be pretty cool. This also brings to mind that we should have a `$event` exposed to the console when breaking inside of an event listener, like we do `$exception` when breaking inside of an exception handler (catch block). That can be a separate enhancement, but we should file a bug report on that. Technically this could be different from `window.event` but maybe that is good enough for an initial implementation. > LayoutTests/inspector/dom-debugger/event-listener-breakpoints.html:37 > + return new Promise((resolve, reject) => { > + InspectorTest.evaluateInPage(`clickBody()`, (error) => { > + if (error) > + reject(error); > + else > + resolve(); > + }); > + }); > + } This ma be the same as just: return InspectorTest.evaluateInPage(`clickBody()`);
Devin Rousso
Comment 20 2018-08-02 00:56:40 PDT
Created attachment 346363 [details] Patch This patch is mainly a rebase with a few style changes. My build is currently broken, so I was unable to test it. :/
EWS Watchlist
Comment 21 2018-08-02 02:01:15 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 22 2018-08-02 02:01:17 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 23 2018-08-02 02:08:14 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 24 2018-08-02 02:08:16 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 25 2018-08-02 02:39:43 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 26 2018-08-02 02:39:45 PDT Comment hidden (obsolete)
Devin Rousso
Comment 27 2018-08-02 09:30:16 PDT
Created attachment 346384 [details] Patch Oops. Missed one test result.
Devin Rousso
Comment 28 2018-08-15 09:53:56 PDT
Joseph Pecoraro
Comment 29 2018-08-16 12:15:39 PDT
Comment on attachment 347172 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=347172&action=review I have a few remaining comments / questions. 1. How does this behave when we have multiple event listeners for a single event? Does it pause in each handler, just the first handler? Test Page: document.body.addEventListener("click", () => { console.log(1); }); document.body.addEventListener("click", () => { console.log(2); }); Scenario: 1. Inspect test page 2. Add Event breakpoint for "click" 3. Click the body of the page => What happens? Do we pause in just (1), or both (1) and (2)? 2. Do event breakpoints properly persist across page loads? Test Page: document.body.addEventListener("click", () => { console.log(1); }); Scenario: 1. Inspect test page 2. Add Event breakpoint for "click" 3. Reload the page 4. Click the body => Do we pause? This could have a test. I expect this to work, but this is a backend implementation detail that is testable and should be tested. > Source/WebInspectorUI/ChangeLog:15 > + Event listener breakpoints are created in the Debugger tab in a new "Event Listener Breakpoints" Should we just call these "Event Breakpoints" instead of "Event Listener Breakpoints"? > Source/WebInspectorUI/UserInterface/Controllers/DOMDebuggerManager.js:36 > + this._eventListenerBreakpointSetting = new WI.Setting("event-listener-breakpoint", []); I feel like this should be plural: `this._eventListenerBreakpointsSettings` > Source/WebInspectorUI/UserInterface/Views/EventListenerBreakpointPopover.js:60 > + this._inputElement = contentElement.appendChild(document.createElement("input")); > + this._inputElement.placeholder = "click"; Pretty much everywhere we create input elements we end up doing: this._inputElement.spellcheck = false; So we will probably want to do that here as well!
Devin Rousso
Comment 30 2018-08-16 12:36:14 PDT
Comment on attachment 347172 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=347172&action=review (In reply to Joseph Pecoraro from comment #29) > 1. How does this behave when we have multiple event listeners for a single > event? Does it pause in each handler, just the first handler? > > Test Page: > document.body.addEventListener("click", () => { console.log(1); }); > document.body.addEventListener("click", () => { console.log(2); }); > > Scenario: > 1. Inspect test page > 2. Add Event breakpoint for "click" > 3. Click the body of the page > => What happens? > > Do we pause in just (1), or both (1) and (2)? Both (1) and (2). > 2. Do event breakpoints properly persist across page loads? > > Test Page: > document.body.addEventListener("click", () => { console.log(1); }); > > Scenario: > 1. Inspect test page > 2. Add Event breakpoint for "click" > 3. Reload the page > 4. Click the body > => Do we pause? Yes, it persists across page loads and navigations. One thing to note, however, is that the breakpoint is global (not specific to a url/domain) and will persist, similar to "All XHR" or "Uncaught Exception" breakpoints. > This could have a test. I expect this to work, but this is a backend > implementation detail that is testable and should be tested. I've spoken with Brian in the past about this, and he suggested that I avoid writing tests that involve a reload as they tend to not work :/ >> Source/WebInspectorUI/ChangeLog:15 >> + Event listener breakpoints are created in the Debugger tab in a new "Event Listener Breakpoints" > > Should we just call these "Event Breakpoints" instead of "Event Listener Breakpoints"? I remember having a reason for calling this "Event Listener Breakpoints", but I don't actually remember what it was. :| Event Breakpoints just sounds cleaner (plus we can "expand" this to more "events", like `requestAnimationFrame` or `setTimeout`). >> Source/WebInspectorUI/UserInterface/Controllers/DOMDebuggerManager.js:36 >> + this._eventListenerBreakpointSetting = new WI.Setting("event-listener-breakpoint", []); > > I feel like this should be plural: `this._eventListenerBreakpointsSettings` Yup. My mistake. `this._eventListenerBreakpointsSetting`
Joseph Pecoraro
Comment 31 2018-08-16 13:10:13 PDT
(In reply to Devin Rousso from comment #30) > Comment on attachment 347172 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=347172&action=review > > (In reply to Joseph Pecoraro from comment #29) > > 1. How does this behave when we have multiple event listeners for a single > > event? Does it pause in each handler, just the first handler? > > > > Test Page: > > document.body.addEventListener("click", () => { console.log(1); }); > > document.body.addEventListener("click", () => { console.log(2); }); > > > > Scenario: > > 1. Inspect test page > > 2. Add Event breakpoint for "click" > > 3. Click the body of the page > > => What happens? > > > > Do we pause in just (1), or both (1) and (2)? > > Both (1) and (2). Fantastic! > > 2. Do event breakpoints properly persist across page loads? > > > > Test Page: > > document.body.addEventListener("click", () => { console.log(1); }); > > > > Scenario: > > 1. Inspect test page > > 2. Add Event breakpoint for "click" > > 3. Reload the page > > 4. Click the body > > => Do we pause? > > Yes, it persists across page loads and navigations. One thing to note, > however, is that the breakpoint is global (not specific to a url/domain) and > will persist, similar to "All XHR" or "Uncaught Exception" breakpoints. > > > This could have a test. I expect this to work, but this is a backend > > implementation detail that is testable and should be tested. > > I've spoken with Brian in the past about this, and he suggested that I avoid > writing tests that involve a reload as they tend to not work :/ I had also had issues with this in the past but I think we've solved them. `InspectorTest.reloadPage()` returns a Promise that should be good enough to use. FWIW I recommend writing such a test in its own file, not in this same file. That way if this does somehow become flakey we won't lose all test coverage. Something like this (pseudocode) seems reasonable. I like the idea of testing the `load` event because its so tricky otherwise, but it could be something more basic like `click`. async test() { await setEventBreakpoint("load"); InspectorTest.reloadPage(); await WI.debuggerManager.awaitEvent(WI.DebuggerManager.Event.Paused); InspectorTest.pass("Pause after page reloaded."); // Verify its a load event... WI.debuggerManager.resume(); }
Joseph Pecoraro
Comment 32 2018-08-16 13:11:26 PDT
> > > Do we pause in just (1), or both (1) and (2)? > > > > Both (1) and (2). > > Fantastic! Oh and this can be tested as well. We probably should. It seems pretty straightforward.
Joseph Pecoraro
Comment 33 2018-08-16 13:31:46 PDT
Comment on attachment 347172 [details] Patch r=me, but lets try to add those additional tests.
Devin Rousso
Comment 34 2018-08-16 18:09:04 PDT
WebKit Commit Bot
Comment 35 2018-08-16 19:37:54 PDT
Comment on attachment 347331 [details] Patch Clearing flags on attachment: 347331 Committed r234974: <https://trac.webkit.org/changeset/234974>
WebKit Commit Bot
Comment 36 2018-08-16 19:37:56 PDT
All reviewed patches have been landed. Closing bug.
Joseph Pecoraro
Comment 37 2018-08-17 10:29:28 PDT
Comment on attachment 347331 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=347331&action=review Thanks for adding the additional tests! > LayoutTests/inspector/dom-debugger/event-breakpoint-with-navigation-expected.txt:7 > +Reloading WebInspector... This is reloading the inspected page, not WebInspector. > LayoutTests/inspector/dom-debugger/event-breakpoint-with-navigation.html:16 > + name: `DOMDebugger.EventWithNavigation.AddBreakpoint`, Weird name. I'd probably go with DOMDebugger.EventBreakpoint.TriggerAfterNavigation or some such. Doesn't really matter though. > LayoutTests/inspector/dom-debugger/event-breakpoint-with-navigation.html:60 > + teardown(resolve, reject) { This teardown isn't really needed unless we were to add other tests. The Test WebInspector frontend doesn't persist settings. > LayoutTests/inspector/dom-debugger/event-breakpoints.html:201 > + name: `DOMDebugger.Event.AddMultipleBreakpoints`, This is a poor name. It's not adding multiple breakpoints, its pausing on multiple listeners. Or pausing multiple times for the same event.
Truitt Savell
Comment 38 2018-08-17 13:02:32 PDT
Looks like the new test inspector/dom-debugger/event-breakpoint-with-navigation.html is a flakey timeout. Test History: https://webkit-test-results.webkit.org/dashboards/flakiness_dashboard.html#showAllRuns=true&tests=inspector%2Fdom-debugger%2Fevent-breakpoint-with-navigation.html also in the trac log the test is called: inspector/dom-debugger/event-breakpoints-with-navigation.html: Added. ^ has event-breakpoints while the test file name is event-breakpoint.
Devin Rousso
Comment 39 2018-08-17 14:29:52 PDT
(In reply to Truitt Savell from comment #38) > Looks like the new test > inspector/dom-debugger/event-breakpoint-with-navigation.html is a flakey > timeout. > > Test History: > https://webkit-test-results.webkit.org/dashboards/flakiness_dashboard. > html#showAllRuns=true&tests=inspector%2Fdom-debugger%2Fevent-breakpoint-with- > navigation.html Marked as flaky in: https://trac.webkit.org/changeset/234996/webkit
Note You need to log in before you can comment on or make changes to this bug.