commit e538d5c362b488d35a27ead7e7230b32aec4c6f1
parent eb11db3022c46017b5a35206cba7a3d7f78a705e
Author: Julian Descottes <jdescottes@mozilla.com>
Date: Thu, 9 Oct 2025 09:11:40 +0000
Bug 1992769 - [devtools] Sanitize event breakpoints based on event ids supported by the server r=devtools-reviewers,ochameau
After retrieving the list of supported event breakpoints from the server cleanup the debugger store to remove unsupported events.
Differential Revision: https://phabricator.services.mozilla.com/D267965
Diffstat:
5 files changed, 152 insertions(+), 64 deletions(-)
diff --git a/devtools/client/debugger/src/reducers/event-listeners.js b/devtools/client/debugger/src/reducers/event-listeners.js
@@ -32,8 +32,26 @@ export function initialEventListenerState() {
function update(state = initialEventListenerState(), action) {
switch (action.type) {
- case "RECEIVE_EVENT_LISTENER_TYPES":
- return { ...state, categories: action.categories };
+ case "RECEIVE_EVENT_LISTENER_TYPES": {
+ // The stored breakpoints in asyncStorage might not match the ones
+ // supported by the backend, either for backward compatibility reasons,
+ // or because the asyncStorage contains stale data.
+ //
+ // Sanitize active breakpoints based on currently supported events.
+ const supportedEventIds = action.categories
+ .map(category => category.events)
+ .flat()
+ .map(event => event.id);
+
+ state.byPanel.breakpoint.active = state.active.filter(id =>
+ supportedEventIds.includes(id)
+ );
+
+ return {
+ ...state,
+ categories: action.categories,
+ };
+ }
case "UPDATE_EVENT_LISTENERS":
if (action.panelKey == "tracer") {
diff --git a/devtools/client/debugger/test/mochitest/browser_aj.toml b/devtools/client/debugger/test/mochitest/browser_aj.toml
@@ -164,6 +164,9 @@ fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and
["browser_dbg-event-breakpoints-fission.js"]
+["browser_dbg-event-breakpoints-unsupported.js"]
+fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
+
["browser_dbg-event-breakpoints.js"]
fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
skip-if = ["os == 'mac' && os_version == '14.70' && processor == 'x86_64'"] # Bug 1959062
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg-event-breakpoints-unsupported.js b/devtools/client/debugger/test/mochitest/browser_dbg-event-breakpoints-unsupported.js
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+// Tests early event breakpoints and event breakpoints in a remote frame.
+
+"use strict";
+
+const BAD_EVENT_ID = "event.dom-mutation.DOMSubtreeModified";
+
+add_task(async function () {
+ info(
+ "Open and close the debugger a first time to prime the preferences and async storage"
+ );
+ let dbg = await initDebugger("doc-event-breakpoints.html");
+ await dbg.toolbox.closeToolbox();
+
+ info("Update the event listeners breakpoint data with an invalid event id");
+ const staleEventListenerBreakpointsState = {
+ categories: [],
+ byPanel: {
+ breakpoint: {
+ active: [BAD_EVENT_ID],
+ expanded: [],
+ },
+ tracer: { expanded: [] },
+ },
+ logEventBreakpoints: false,
+ active: [],
+ expanded: [],
+ };
+ await asyncStorage.setItem(
+ "debugger.event-listener-breakpoints",
+ staleEventListenerBreakpointsState
+ );
+
+ // Note: DO NOT use initDebugger here, as it would clear storage and
+ // preferences.
+ info("Reopen the debugger without resetting preferences");
+ const toolbox = await openNewTabAndToolbox(
+ EXAMPLE_URL + "doc-event-breakpoints.html",
+ "jsdebugger"
+ );
+ dbg = createDebuggerContext(toolbox);
+ await waitForSources(dbg, "event-breakpoints.js");
+
+ await selectSource(dbg, "event-breakpoints.js");
+ await waitForSelectedSource(dbg, "event-breakpoints.js");
+ const eventBreakpointsSource = findSource(dbg, "event-breakpoints.js");
+
+ info("Check that an event breakpoint can still be set");
+ await toggleEventBreakpoint(dbg, "Mouse", "event.mouse.click");
+
+ invokeInTab("clickHandler");
+ await waitForPaused(dbg);
+ await assertPausedAtSourceAndLine(dbg, eventBreakpointsSource.id, 12);
+
+ const sanitizedEventListenerBreakpointsState = await asyncStorage.getItem(
+ "debugger.event-listener-breakpoints"
+ );
+ const sanitizedActive =
+ sanitizedEventListenerBreakpointsState.byPanel.breakpoint.active;
+ ok(!sanitizedActive.includes(BAD_EVENT_ID));
+ ok(sanitizedActive.includes("event.mouse.click"));
+
+ await resume(dbg);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg-event-breakpoints.js b/devtools/client/debugger/test/mochitest/browser_dbg-event-breakpoints.js
@@ -308,68 +308,6 @@ add_task(async function () {
is(timerGroupCheckbox.checked, true);
});
-function getEventListenersPanel(dbg) {
- return findElementWithSelector(dbg, ".event-listeners-pane .event-listeners");
-}
-
-async function toggleEventBreakpoint(
- dbg,
- eventBreakpointGroup,
- eventBreakpointName
-) {
- const eventCheckbox = await getEventBreakpointCheckbox(
- dbg,
- eventBreakpointGroup,
- eventBreakpointName
- );
- eventCheckbox.scrollIntoView();
- info(`Toggle ${eventBreakpointName} breakpoint`);
- const onEventListenersUpdate = waitForDispatch(
- dbg.store,
- "UPDATE_EVENT_LISTENERS"
- );
- const checked = eventCheckbox.checked;
- eventCheckbox.click();
- await onEventListenersUpdate;
-
- info("Wait for the event breakpoint checkbox to be toggled");
- // Wait for he UI to be toggled, otherwise, the reducer may not be fully updated
- await waitFor(() => {
- return eventCheckbox.checked == !checked;
- });
-}
-
-async function getEventBreakpointCheckbox(
- dbg,
- eventBreakpointGroup,
- eventBreakpointName
-) {
- if (!getEventListenersPanel(dbg)) {
- // Event listeners panel is collapsed, expand it
- findElementWithSelector(
- dbg,
- `.event-listeners-pane ._header .header-label`
- ).click();
- await waitFor(() => getEventListenersPanel(dbg));
- }
-
- const groupCheckbox = findElementWithSelector(
- dbg,
- `input[value="${eventBreakpointGroup}"]`
- );
- const groupEl = groupCheckbox.closest(".event-listener-group");
- let groupEventsUl = groupEl.querySelector("ul");
- if (!groupEventsUl) {
- info(
- `Expand ${eventBreakpointGroup} and wait for the sub list to be displayed`
- );
- groupEl.querySelector(".event-listener-expand").click();
- groupEventsUl = await waitFor(() => groupEl.querySelector("ul"));
- }
-
- return findElementWithSelector(dbg, `input[value="${eventBreakpointName}"]`);
-}
-
async function invokeOnElement(selector, action) {
await SpecialPowers.focus(gBrowser.selectedBrowser);
await SpecialPowers.spawn(
diff --git a/devtools/client/debugger/test/mochitest/head.js b/devtools/client/debugger/test/mochitest/head.js
@@ -196,3 +196,65 @@ function assertCursorPosition(dbg, expectedLine, expectedColumn, message) {
function selectContextMenuItem(dbg, selector) {
return selectDebuggerContextMenuItem(dbg, selector);
}
+
+function getEventListenersPanel(dbg) {
+ return findElementWithSelector(dbg, ".event-listeners-pane .event-listeners");
+}
+
+async function toggleEventBreakpoint(
+ dbg,
+ eventBreakpointGroup,
+ eventBreakpointName
+) {
+ const eventCheckbox = await getEventBreakpointCheckbox(
+ dbg,
+ eventBreakpointGroup,
+ eventBreakpointName
+ );
+ eventCheckbox.scrollIntoView();
+ info(`Toggle ${eventBreakpointName} breakpoint`);
+ const onEventListenersUpdate = waitForDispatch(
+ dbg.store,
+ "UPDATE_EVENT_LISTENERS"
+ );
+ const checked = eventCheckbox.checked;
+ eventCheckbox.click();
+ await onEventListenersUpdate;
+
+ info("Wait for the event breakpoint checkbox to be toggled");
+ // Wait for he UI to be toggled, otherwise, the reducer may not be fully updated
+ await waitFor(() => {
+ return eventCheckbox.checked == !checked;
+ });
+}
+
+async function getEventBreakpointCheckbox(
+ dbg,
+ eventBreakpointGroup,
+ eventBreakpointName
+) {
+ if (!getEventListenersPanel(dbg)) {
+ // Event listeners panel is collapsed, expand it
+ findElementWithSelector(
+ dbg,
+ `.event-listeners-pane ._header .header-label`
+ ).click();
+ await waitFor(() => getEventListenersPanel(dbg));
+ }
+
+ const groupCheckbox = findElementWithSelector(
+ dbg,
+ `input[value="${eventBreakpointGroup}"]`
+ );
+ const groupEl = groupCheckbox.closest(".event-listener-group");
+ let groupEventsUl = groupEl.querySelector("ul");
+ if (!groupEventsUl) {
+ info(
+ `Expand ${eventBreakpointGroup} and wait for the sub list to be displayed`
+ );
+ groupEl.querySelector(".event-listener-expand").click();
+ groupEventsUl = await waitFor(() => groupEl.querySelector("ul"));
+ }
+
+ return findElementWithSelector(dbg, `input[value="${eventBreakpointName}"]`);
+}