forked from mirrors/gecko-dev
Bug 1492497 - [devtools] Add a way to disable (and re-enable) event listener for a given node. r=ochameau,devtools-backward-compat-reviewers,bomsy.
This patch adds a checkbox at the end of each event listeners in the EventTooltip, which allow the user to disable/re-enable a given event listener. This is done by managing a Map of nsIEventListenerInfo object in the NodeActor, which we populate from `getEventListenerInfo`. Each `nsIEventListenerInfo` is assigned a generated id, which can then be used to call the new NodeActor methods, `(enable|disable)EventListener`. We don't support disabling jquery/React event listeners at the moment, so we display the checkbox for them as well, but disabled. Differential Revision: https://phabricator.services.mozilla.com/D135133
This commit is contained in:
parent
0c74c88e49
commit
a232d2448c
9 changed files with 462 additions and 27 deletions
|
|
@ -27,6 +27,7 @@ support-files =
|
||||||
doc_markup_events_react_production_16.2.0.html
|
doc_markup_events_react_production_16.2.0.html
|
||||||
doc_markup_events_react_production_16.2.0_jsx.html
|
doc_markup_events_react_production_16.2.0_jsx.html
|
||||||
doc_markup_events-source_map.html
|
doc_markup_events-source_map.html
|
||||||
|
doc_markup_events_toggle.html
|
||||||
doc_markup_flashing.html
|
doc_markup_flashing.html
|
||||||
doc_markup_html_mixed_case.html
|
doc_markup_html_mixed_case.html
|
||||||
doc_markup_image_and_canvas.html
|
doc_markup_image_and_canvas.html
|
||||||
|
|
@ -146,6 +147,7 @@ skip-if = true # Bug 1177550
|
||||||
[browser_markup_events_react_production_16.2.0.js]
|
[browser_markup_events_react_production_16.2.0.js]
|
||||||
[browser_markup_events_react_production_16.2.0_jsx.js]
|
[browser_markup_events_react_production_16.2.0_jsx.js]
|
||||||
[browser_markup_events_source_map.js]
|
[browser_markup_events_source_map.js]
|
||||||
|
[browser_markup_events_toggle.js]
|
||||||
[browser_markup_events-windowed-host.js]
|
[browser_markup_events-windowed-host.js]
|
||||||
[browser_markup_flex_display_badge.js]
|
[browser_markup_flex_display_badge.js]
|
||||||
[browser_markup_flex_display_badge_telemetry.js]
|
[browser_markup_flex_display_badge_telemetry.js]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,262 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
/* import-globals-from helper_events_test_runner.js */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Test that event listeners can be disabled and re-enabled from the markup view event bubble.
|
||||||
|
|
||||||
|
const TEST_URL = URL_ROOT_SSL + "doc_markup_events_toggle.html";
|
||||||
|
|
||||||
|
loadHelperScript("helper_events_test_runner.js");
|
||||||
|
|
||||||
|
add_task(async function() {
|
||||||
|
const { inspector, toolbox } = await openInspectorForURL(TEST_URL);
|
||||||
|
const { resourceCommand } = toolbox.commands;
|
||||||
|
await inspector.markup.expandAll();
|
||||||
|
await selectNode("#target", inspector);
|
||||||
|
|
||||||
|
info(
|
||||||
|
"Click on the target element to make sure the event listeners are properly set"
|
||||||
|
);
|
||||||
|
// There's a "mouseup" event listener that is `console.info` (so we can check "native" events).
|
||||||
|
// In order to know if it was called, we listen for the next console.info resource.
|
||||||
|
let {
|
||||||
|
onResource: onConsoleInfoMessage,
|
||||||
|
} = await resourceCommand.waitForNextResource(
|
||||||
|
resourceCommand.TYPES.CONSOLE_MESSAGE,
|
||||||
|
{
|
||||||
|
ignoreExistingResources: true,
|
||||||
|
predicate(resource) {
|
||||||
|
return resource.message.level == "info";
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await safeSynthesizeMouseEventAtCenterInContentPage("#target");
|
||||||
|
|
||||||
|
let data = await getTargetElementHandledEventData();
|
||||||
|
is(data.click, 1, `target handled one "click" event`);
|
||||||
|
is(data.mousedown, 1, `target handled one "mousedown" event`);
|
||||||
|
await onConsoleInfoMessage;
|
||||||
|
ok(true, `the "mouseup" event listener (console.info) was called`);
|
||||||
|
|
||||||
|
info("Check that the event tooltip has the expected content");
|
||||||
|
const container = await getContainerForSelector("#target", inspector);
|
||||||
|
const eventTooltipBadge = container.elt.querySelector(
|
||||||
|
".inspector-badge.interactive[data-event]"
|
||||||
|
);
|
||||||
|
ok(eventTooltipBadge, "The event tooltip badge is displayed");
|
||||||
|
|
||||||
|
const tooltip = inspector.markup.eventDetailsTooltip;
|
||||||
|
let onTooltipShown = tooltip.once("shown");
|
||||||
|
eventTooltipBadge.click();
|
||||||
|
await onTooltipShown;
|
||||||
|
ok(true, "The tooltip is shown");
|
||||||
|
|
||||||
|
Assert.deepEqual(
|
||||||
|
getAsciiHeadersViz(tooltip),
|
||||||
|
["click [x]", "mousedown [x]", "mouseup [x]"],
|
||||||
|
"The expected events are displayed, all enabled"
|
||||||
|
);
|
||||||
|
|
||||||
|
const [
|
||||||
|
clickHeader,
|
||||||
|
mousedownHeader,
|
||||||
|
mouseupHeader,
|
||||||
|
] = getHeadersInEventTooltip(tooltip);
|
||||||
|
|
||||||
|
info("Uncheck the mousedown event checkbox");
|
||||||
|
await toggleEventListenerCheckbox(tooltip, mousedownHeader);
|
||||||
|
Assert.deepEqual(
|
||||||
|
getAsciiHeadersViz(tooltip),
|
||||||
|
["click [x]", "mousedown []", "mouseup [x]"],
|
||||||
|
"mousedown checkbox was unchecked"
|
||||||
|
);
|
||||||
|
await safeSynthesizeMouseEventAtCenterInContentPage("#target");
|
||||||
|
data = await getTargetElementHandledEventData();
|
||||||
|
is(data.click, 2, `target handled another "click" event…`);
|
||||||
|
is(data.mousedown, 1, `… but not a mousedown one`);
|
||||||
|
|
||||||
|
info("Uncheck the click event checkbox");
|
||||||
|
await toggleEventListenerCheckbox(tooltip, clickHeader);
|
||||||
|
Assert.deepEqual(
|
||||||
|
getAsciiHeadersViz(tooltip),
|
||||||
|
["click []", "mousedown []", "mouseup [x]"],
|
||||||
|
"click checkbox was unchecked"
|
||||||
|
);
|
||||||
|
await safeSynthesizeMouseEventAtCenterInContentPage("#target");
|
||||||
|
data = await getTargetElementHandledEventData();
|
||||||
|
is(data.click, 2, `click event listener was disabled`);
|
||||||
|
is(data.mousedown, 1, `and mousedown still is disabled as well`);
|
||||||
|
|
||||||
|
info("Uncheck the mouseup event checkbox");
|
||||||
|
await toggleEventListenerCheckbox(tooltip, mouseupHeader);
|
||||||
|
Assert.deepEqual(
|
||||||
|
getAsciiHeadersViz(tooltip),
|
||||||
|
["click []", "mousedown []", "mouseup []"],
|
||||||
|
"mouseup checkbox was unchecked"
|
||||||
|
);
|
||||||
|
|
||||||
|
({
|
||||||
|
onResource: onConsoleInfoMessage,
|
||||||
|
} = await resourceCommand.waitForNextResource(
|
||||||
|
resourceCommand.TYPES.CONSOLE_MESSAGE,
|
||||||
|
{
|
||||||
|
ignoreExistingResources: true,
|
||||||
|
predicate(resource) {
|
||||||
|
return resource.message.level == "info";
|
||||||
|
},
|
||||||
|
}
|
||||||
|
));
|
||||||
|
const onTimeout = wait(500).then(() => "TIMEOUT");
|
||||||
|
await safeSynthesizeMouseEventAtCenterInContentPage("#target");
|
||||||
|
const raceResult = await Promise.race([onConsoleInfoMessage, onTimeout]);
|
||||||
|
is(
|
||||||
|
raceResult,
|
||||||
|
"TIMEOUT",
|
||||||
|
"The mouseup event didn't trigger a console.info call, meaning the event listener was disabled"
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Re-enable the mousedown event");
|
||||||
|
await toggleEventListenerCheckbox(tooltip, mousedownHeader);
|
||||||
|
Assert.deepEqual(
|
||||||
|
getAsciiHeadersViz(tooltip),
|
||||||
|
["click []", "mousedown [x]", "mouseup []"],
|
||||||
|
"mousedown checkbox is checked again"
|
||||||
|
);
|
||||||
|
await safeSynthesizeMouseEventAtCenterInContentPage("#target");
|
||||||
|
data = await getTargetElementHandledEventData();
|
||||||
|
is(data.click, 2, `no additional "click" event were handled`);
|
||||||
|
is(
|
||||||
|
data.mousedown,
|
||||||
|
2,
|
||||||
|
`but we did get a new "mousedown", the event listener was re-enabled`
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Hide the tooltip and show it again");
|
||||||
|
const tooltipHidden = tooltip.once("hidden");
|
||||||
|
tooltip.hide();
|
||||||
|
await tooltipHidden;
|
||||||
|
|
||||||
|
onTooltipShown = tooltip.once("shown");
|
||||||
|
eventTooltipBadge.click();
|
||||||
|
await onTooltipShown;
|
||||||
|
ok(true, "The tooltip is shown again");
|
||||||
|
|
||||||
|
Assert.deepEqual(
|
||||||
|
getAsciiHeadersViz(tooltip),
|
||||||
|
["click []", "mousedown [x]", "mouseup []"],
|
||||||
|
"Only mousedown checkbox is checked"
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Re-enable mouseup events");
|
||||||
|
await toggleEventListenerCheckbox(
|
||||||
|
tooltip,
|
||||||
|
getHeadersInEventTooltip(tooltip).at(-1)
|
||||||
|
);
|
||||||
|
Assert.deepEqual(
|
||||||
|
getAsciiHeadersViz(tooltip),
|
||||||
|
["click []", "mousedown [x]", "mouseup [x]"],
|
||||||
|
"mouseup is checked again"
|
||||||
|
);
|
||||||
|
|
||||||
|
({
|
||||||
|
onResource: onConsoleInfoMessage,
|
||||||
|
} = await resourceCommand.waitForNextResource(
|
||||||
|
resourceCommand.TYPES.CONSOLE_MESSAGE,
|
||||||
|
{
|
||||||
|
ignoreExistingResources: true,
|
||||||
|
predicate(resource) {
|
||||||
|
return resource.message.level == "info";
|
||||||
|
},
|
||||||
|
}
|
||||||
|
));
|
||||||
|
await safeSynthesizeMouseEventAtCenterInContentPage("#target");
|
||||||
|
await onConsoleInfoMessage;
|
||||||
|
ok(true, "The mouseup event was re-enabled");
|
||||||
|
data = await getTargetElementHandledEventData();
|
||||||
|
is(data.click, 2, `"click" is still disabled`);
|
||||||
|
is(
|
||||||
|
data.mousedown,
|
||||||
|
3,
|
||||||
|
`we received a new "mousedown" event as part of the click`
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Close DevTools to check that event listeners are re-enabled");
|
||||||
|
await closeToolbox();
|
||||||
|
await safeSynthesizeMouseEventAtCenterInContentPage("#target");
|
||||||
|
data = await getTargetElementHandledEventData();
|
||||||
|
is(
|
||||||
|
data.click,
|
||||||
|
3,
|
||||||
|
`a new "click" event was handled after the devtools was closed`
|
||||||
|
);
|
||||||
|
is(
|
||||||
|
data.mousedown,
|
||||||
|
4,
|
||||||
|
`a new "mousedown" event was handled after the devtools was closed`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function getHeadersInEventTooltip(tooltip) {
|
||||||
|
return Array.from(tooltip.panel.querySelectorAll(".event-header"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an array of string representing a header in its state, e.g.
|
||||||
|
* [
|
||||||
|
* "click [x]",
|
||||||
|
* "mousedown []",
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* represents an event tooltip with a click and a mousedown event, where the mousedown
|
||||||
|
* event has been disabled.
|
||||||
|
*
|
||||||
|
* @param {EventTooltip} tooltip
|
||||||
|
* @returns Array<String>
|
||||||
|
*/
|
||||||
|
function getAsciiHeadersViz(tooltip) {
|
||||||
|
return getHeadersInEventTooltip(tooltip).map(
|
||||||
|
el =>
|
||||||
|
`${el.querySelector(".event-tooltip-event-type").textContent} [${
|
||||||
|
getHeaderCheckbox(el).checked ? "x" : ""
|
||||||
|
}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHeaderCheckbox(headerEl) {
|
||||||
|
return headerEl.querySelector("input[type=checkbox]");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleEventListenerCheckbox(tooltip, headerEl) {
|
||||||
|
const onEventToggled = tooltip.once("event-tooltip-listener-toggled");
|
||||||
|
const checkbox = getHeaderCheckbox(headerEl);
|
||||||
|
const previousValue = checkbox.checked;
|
||||||
|
EventUtils.synthesizeMouseAtCenter(
|
||||||
|
getHeaderCheckbox(headerEl),
|
||||||
|
{},
|
||||||
|
headerEl.ownerGlobal
|
||||||
|
);
|
||||||
|
await onEventToggled;
|
||||||
|
is(checkbox.checked, !previousValue, "The checkbox was toggled");
|
||||||
|
is(
|
||||||
|
headerEl.classList.contains("content-expanded"),
|
||||||
|
false,
|
||||||
|
"Clicking on the checkbox did not expand the header"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns Promise<Object> The object keys are event names (e.g. "click", "mousedown"), and
|
||||||
|
* the values are number representing the number of time the event was handled.
|
||||||
|
* Note that "mouseup" isn't handled here.
|
||||||
|
*/
|
||||||
|
function getTargetElementHandledEventData() {
|
||||||
|
return SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() {
|
||||||
|
// In doc_markup_events_toggle.html , we count the events handled by the target in
|
||||||
|
// a stringified object in dataset.handledEvents.
|
||||||
|
return JSON.parse(
|
||||||
|
content.document.getElementById("target").dataset.handledEvents
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Toggle Event Listeners</h1>
|
||||||
|
<button id="target" onclick="handleEvent(event)">Target</button>
|
||||||
|
<script>
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function handleEvent(e) {
|
||||||
|
const data = JSON.parse(e.target.dataset.handledEvents || "{}");
|
||||||
|
data[e.type] = (data[e.type] || 0) + 1;
|
||||||
|
e.target.dataset.handledEvents = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const domEventsElement = document.getElementById("target");
|
||||||
|
// adding regular event listener
|
||||||
|
domEventsElement.addEventListener("mousedown", handleEvent);
|
||||||
|
// and a "native" event listener
|
||||||
|
domEventsElement.addEventListener("mouseup", console.info)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -169,6 +169,19 @@ async function checkEventsForNode(test, inspector) {
|
||||||
ok
|
ok
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const checkbox = header.querySelector("input[type=checkbox]");
|
||||||
|
ok(checkbox, "The event toggling checkbox is displayed");
|
||||||
|
const disabled = checkbox.hasAttribute("disabled");
|
||||||
|
// We can't disable React/jQuery events at the moment, so ensure that for those,
|
||||||
|
// the checkbox is disabled.
|
||||||
|
const shouldBeDisabled =
|
||||||
|
expected[i].attributes?.includes("React") ||
|
||||||
|
expected[i].attributes?.includes("jQuery");
|
||||||
|
ok(
|
||||||
|
disabled === shouldBeDisabled,
|
||||||
|
`The checkbox is ${shouldBeDisabled ? "disabled" : "enabled"}\n`
|
||||||
|
);
|
||||||
|
|
||||||
info(`${label} END`);
|
info(`${label} END`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,12 @@ MarkupElementContainer.prototype = extend(MarkupContainer.prototype, {
|
||||||
const toolbox = this.markup.toolbox;
|
const toolbox = this.markup.toolbox;
|
||||||
|
|
||||||
// Create the EventTooltip which will populate the tooltip content.
|
// Create the EventTooltip which will populate the tooltip content.
|
||||||
const eventTooltip = new EventTooltip(tooltip, listenerInfo, toolbox);
|
const eventTooltip = new EventTooltip(
|
||||||
|
tooltip,
|
||||||
|
listenerInfo,
|
||||||
|
toolbox,
|
||||||
|
this.node
|
||||||
|
);
|
||||||
|
|
||||||
// Disable the image preview tooltip while we display the event details
|
// Disable the image preview tooltip while we display the event details
|
||||||
this.markup._disableImagePreviewTooltip();
|
this.markup._disableImagePreviewTooltip();
|
||||||
|
|
|
||||||
|
|
@ -27,17 +27,24 @@ class EventTooltip {
|
||||||
* A list of event listeners
|
* A list of event listeners
|
||||||
* @param {Toolbox} toolbox
|
* @param {Toolbox} toolbox
|
||||||
* Toolbox used to select debugger panel
|
* Toolbox used to select debugger panel
|
||||||
|
* @param {NodeFront} nodeFront
|
||||||
|
* The nodeFront we're displaying event listeners for.
|
||||||
*/
|
*/
|
||||||
constructor(tooltip, eventListenerInfos, toolbox) {
|
constructor(tooltip, eventListenerInfos, toolbox, nodeFront) {
|
||||||
this._tooltip = tooltip;
|
this._tooltip = tooltip;
|
||||||
this._toolbox = toolbox;
|
this._toolbox = toolbox;
|
||||||
this._eventEditors = new WeakMap();
|
this._eventEditors = new WeakMap();
|
||||||
|
this._nodeFront = nodeFront;
|
||||||
|
this._eventListenersAbortController = new AbortController();
|
||||||
|
|
||||||
// Used in tests: add a reference to the EventTooltip instance on the HTMLTooltip.
|
// Used in tests: add a reference to the EventTooltip instance on the HTMLTooltip.
|
||||||
this._tooltip.eventTooltip = this;
|
this._tooltip.eventTooltip = this;
|
||||||
|
|
||||||
this._headerClicked = this._headerClicked.bind(this);
|
this._headerClicked = this._headerClicked.bind(this);
|
||||||
this._debugClicked = this._debugClicked.bind(this);
|
this._eventToggleCheckboxChanged = this._eventToggleCheckboxChanged.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
this._subscriptions = [];
|
this._subscriptions = [];
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|
@ -109,7 +116,7 @@ class EventTooltip {
|
||||||
filename.setAttribute("title", newURI);
|
filename.setAttribute("title", newURI);
|
||||||
|
|
||||||
// This is emitted for testing.
|
// This is emitted for testing.
|
||||||
this._tooltip.emit("event-tooltip-source-map-ready");
|
this._tooltip.emitForTests("event-tooltip-source-map-ready");
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -160,6 +167,27 @@ class EventTooltip {
|
||||||
attributesBox.appendChild(capturing);
|
attributesBox.appendChild(capturing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toggleListenerCheckbox = doc.createElementNS(XHTML_NS, "input");
|
||||||
|
toggleListenerCheckbox.type = "checkbox";
|
||||||
|
toggleListenerCheckbox.className =
|
||||||
|
"event-tooltip-listener-toggle-checkbox";
|
||||||
|
if (listener.eventListenerInfoId) {
|
||||||
|
toggleListenerCheckbox.checked = listener.enabled;
|
||||||
|
toggleListenerCheckbox.setAttribute(
|
||||||
|
"data-event-listener-info-id",
|
||||||
|
listener.eventListenerInfoId
|
||||||
|
);
|
||||||
|
toggleListenerCheckbox.addEventListener(
|
||||||
|
"change",
|
||||||
|
this._eventToggleCheckboxChanged,
|
||||||
|
{ signal: this._eventListenersAbortController.signal }
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toggleListenerCheckbox.checked = true;
|
||||||
|
toggleListenerCheckbox.setAttribute("disabled", true);
|
||||||
|
}
|
||||||
|
header.appendChild(toggleListenerCheckbox);
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
const editor = new Editor(config);
|
const editor = new Editor(config);
|
||||||
this._eventEditors.set(content, {
|
this._eventEditors.set(content, {
|
||||||
|
|
@ -182,10 +210,21 @@ class EventTooltip {
|
||||||
}
|
}
|
||||||
|
|
||||||
_addContentListeners(header) {
|
_addContentListeners(header) {
|
||||||
header.addEventListener("click", this._headerClicked);
|
header.addEventListener("click", this._headerClicked, {
|
||||||
|
signal: this._eventListenersAbortController.signal,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_headerClicked(event) {
|
_headerClicked(event) {
|
||||||
|
// Clicking on the checkbox shouldn't impact the header (checkbox state change is
|
||||||
|
// handled in _eventToggleCheckboxChanged).
|
||||||
|
if (
|
||||||
|
event.target.classList.contains("event-tooltip-listener-toggle-checkbox")
|
||||||
|
) {
|
||||||
|
event.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.target.classList.contains("event-tooltip-debugger-icon")) {
|
if (event.target.classList.contains("event-tooltip-debugger-icon")) {
|
||||||
this._debugClicked(event);
|
this._debugClicked(event);
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
@ -241,7 +280,7 @@ class EventTooltip {
|
||||||
content.scrollIntoView(false);
|
content.scrollIntoView(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._tooltip.emit("event-tooltip-ready");
|
this._tooltip.emitForTests("event-tooltip-ready");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -266,6 +305,17 @@ class EventTooltip {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _eventToggleCheckboxChanged(event) {
|
||||||
|
const checkbox = event.currentTarget;
|
||||||
|
const id = checkbox.getAttribute("data-event-listener-info-id");
|
||||||
|
if (checkbox.checked) {
|
||||||
|
await this._nodeFront.enableEventListener(id);
|
||||||
|
} else {
|
||||||
|
await this._nodeFront.disableEventListener(id);
|
||||||
|
}
|
||||||
|
this._tooltip.emitForTests("event-tooltip-listener-toggled");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse URI and return {url, line, column}; or return null if it can't be parsed.
|
* Parse URI and return {url, line, column}; or return null if it can't be parsed.
|
||||||
*/
|
*/
|
||||||
|
|
@ -308,24 +358,16 @@ class EventTooltip {
|
||||||
this._tooltip.eventTooltip = null;
|
this._tooltip.eventTooltip = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerNodes = this.container.querySelectorAll(".event-header");
|
if (this._eventListenersAbortController) {
|
||||||
|
this._eventListenersAbortController.abort();
|
||||||
for (const node of headerNodes) {
|
this._eventListenersAbortController = null;
|
||||||
node.removeEventListener("click", this._headerClicked);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sourceNodes = this.container.querySelectorAll(
|
|
||||||
".event-tooltip-debugger-icon"
|
|
||||||
);
|
|
||||||
for (const node of sourceNodes) {
|
|
||||||
node.removeEventListener("click", this._debugClicked);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const unsubscribe of this._subscriptions) {
|
for (const unsubscribe of this._subscriptions) {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
this._toolbox = this._tooltip = null;
|
this._toolbox = this._tooltip = this._nodeFront = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -385,9 +385,11 @@ class DOMEventCollector extends MainEventCollector {
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventInfo = {
|
const eventInfo = {
|
||||||
|
nsIEventListenerInfo: listener,
|
||||||
capturing: listener.capturing,
|
capturing: listener.capturing,
|
||||||
type: listener.type,
|
type: listener.type,
|
||||||
handler: handler,
|
handler: handler,
|
||||||
|
enabled: listener.enabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
handlers.push(eventInfo);
|
handlers.push(eventInfo);
|
||||||
|
|
@ -820,16 +822,20 @@ class EventCollector {
|
||||||
*
|
*
|
||||||
* @param {DOMNode} node
|
* @param {DOMNode} node
|
||||||
* The node for which events are to be gathered.
|
* The node for which events are to be gathered.
|
||||||
* @return {Array}
|
* @return {Array<Object>}
|
||||||
* An array containing objects in the following format:
|
* An array containing objects in the following format:
|
||||||
* {
|
* {
|
||||||
* type: type, // e.g. "click"
|
* {String} type: The event type, e.g. "click"
|
||||||
* handler: handler, // The function called when event is triggered.
|
* {Function} handler: The function called when event is triggered.
|
||||||
* tags: "jQuery", // Comma separated list of tags displayed
|
* {Boolean} enabled: Whether the listener is enabled or not (event listeners can
|
||||||
* // inside event bubble.
|
* be disabled via the inspector)
|
||||||
* hide: { // Flags for hiding certain properties.
|
* {String} tags: Comma separated list of tags displayed inside event bubble (e.g. "JQuery")
|
||||||
* capturing: true,
|
* {Object} hide: Flags for hiding certain properties.
|
||||||
|
* {Boolean} capturing
|
||||||
* }
|
* }
|
||||||
|
* {Boolean} native
|
||||||
|
* {String|undefined} sourceActor: The sourceActor id of the event listener
|
||||||
|
* {nsIEventListenerInfo|undefined} nsIEventListenerInfo
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
getEventListeners(node) {
|
getEventListeners(node) {
|
||||||
|
|
@ -895,7 +901,10 @@ class EventCollector {
|
||||||
* hide: {
|
* hide: {
|
||||||
* capturing: true
|
* capturing: true
|
||||||
* },
|
* },
|
||||||
* native: false
|
* native: false,
|
||||||
|
* enabled: true
|
||||||
|
* sourceActor: "sourceActor.1234",
|
||||||
|
* nsIEventListenerInfo: nsIEventListenerInfo {…},
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line complexity
|
// eslint-disable-next-line complexity
|
||||||
|
|
@ -923,6 +932,7 @@ class EventCollector {
|
||||||
const tags = listener.tags || "";
|
const tags = listener.tags || "";
|
||||||
const type = listener.type || "";
|
const type = listener.type || "";
|
||||||
let isScriptBoundToNonScriptElement = false;
|
let isScriptBoundToNonScriptElement = false;
|
||||||
|
const enabled = !!listener.enabled;
|
||||||
let functionSource = handler.toString();
|
let functionSource = handler.toString();
|
||||||
let line = 0;
|
let line = 0;
|
||||||
let column = null;
|
let column = null;
|
||||||
|
|
@ -1039,6 +1049,8 @@ class EventCollector {
|
||||||
hide: typeof override.hide !== "undefined" ? override.hide : hide,
|
hide: typeof override.hide !== "undefined" ? override.hide : hide,
|
||||||
native,
|
native,
|
||||||
sourceActor,
|
sourceActor,
|
||||||
|
nsIEventListenerInfo: listener.nsIEventListenerInfo,
|
||||||
|
enabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hide the debugger icon for DOM0 and native listeners. DOM0 listeners are
|
// Hide the debugger icon for DOM0 and native listeners. DOM0 listeners are
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,9 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, {
|
||||||
this.walker = walker;
|
this.walker = walker;
|
||||||
this.rawNode = node;
|
this.rawNode = node;
|
||||||
this._eventCollector = new EventCollector(this.walker.targetActor);
|
this._eventCollector = new EventCollector(this.walker.targetActor);
|
||||||
|
// Map<id -> nsIEventListenerInfo> that we maintain to be able to disable/re-enable event listeners
|
||||||
|
// The id is generated from getEventListenerInfo
|
||||||
|
this._nsIEventListenersInfo = new Map();
|
||||||
|
|
||||||
// Store the original display type and scrollable state and whether or not the node is
|
// Store the original display type and scrollable state and whether or not the node is
|
||||||
// displayed to track changes when reflows occur.
|
// displayed to track changes when reflows occur.
|
||||||
|
|
@ -159,6 +162,16 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, {
|
||||||
this._waitForFrameLoadIntervalId = null;
|
this._waitForFrameLoadIntervalId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._nsIEventListenersInfo) {
|
||||||
|
// Re-enable all event listeners that we might have disabled
|
||||||
|
for (const nsIEventListenerInfo of this._nsIEventListenersInfo.values()) {
|
||||||
|
if (!nsIEventListenerInfo.enabled) {
|
||||||
|
nsIEventListenerInfo.enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._nsIEventListenersInfo = null;
|
||||||
|
}
|
||||||
|
|
||||||
this._eventCollector.destroy();
|
this._eventCollector.destroy();
|
||||||
this._eventCollector = null;
|
this._eventCollector = null;
|
||||||
this.rawNode = null;
|
this.rawNode = null;
|
||||||
|
|
@ -560,7 +573,56 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, {
|
||||||
* Get all event listeners that are listening on this node.
|
* Get all event listeners that are listening on this node.
|
||||||
*/
|
*/
|
||||||
getEventListenerInfo: function() {
|
getEventListenerInfo: function() {
|
||||||
return this._eventCollector.getEventListeners(this.rawNode);
|
this._nsIEventListenersInfo.clear();
|
||||||
|
|
||||||
|
const eventListenersData = this._eventCollector.getEventListeners(
|
||||||
|
this.rawNode
|
||||||
|
);
|
||||||
|
let counter = 0;
|
||||||
|
for (const eventListenerData of eventListenersData) {
|
||||||
|
if (eventListenerData.nsIEventListenerInfo) {
|
||||||
|
const id = `event-listener-info-${++counter}`;
|
||||||
|
this._nsIEventListenersInfo.set(
|
||||||
|
id,
|
||||||
|
eventListenerData.nsIEventListenerInfo
|
||||||
|
);
|
||||||
|
|
||||||
|
eventListenerData.eventListenerInfoId = id;
|
||||||
|
// remove the nsIEventListenerInfo since we don't want to send it to the client.
|
||||||
|
delete eventListenerData.nsIEventListenerInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eventListenersData;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable a specific event listener given its associated id
|
||||||
|
*
|
||||||
|
* @param {String} eventListenerInfoId
|
||||||
|
*/
|
||||||
|
disableEventListener: function(eventListenerInfoId) {
|
||||||
|
const nsEventListenerInfo = this._nsIEventListenersInfo.get(
|
||||||
|
eventListenerInfoId
|
||||||
|
);
|
||||||
|
if (!nsEventListenerInfo) {
|
||||||
|
throw new Error("Unkown nsEventListenerInfo");
|
||||||
|
}
|
||||||
|
nsEventListenerInfo.enabled = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Re-)enable a specific event listener given its associated id
|
||||||
|
*
|
||||||
|
* @param {String} eventListenerInfoId
|
||||||
|
*/
|
||||||
|
enableEventListener: function(eventListenerInfoId) {
|
||||||
|
const nsEventListenerInfo = this._nsIEventListenersInfo.get(
|
||||||
|
eventListenerInfoId
|
||||||
|
);
|
||||||
|
if (!nsEventListenerInfo) {
|
||||||
|
throw new Error("Unkown nsEventListenerInfo");
|
||||||
|
}
|
||||||
|
nsEventListenerInfo.enabled = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,18 @@ const nodeSpec = generateActorSpec({
|
||||||
events: RetVal("json"),
|
events: RetVal("json"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
enableEventListener: {
|
||||||
|
request: {
|
||||||
|
eventListenerInfoId: Arg(0),
|
||||||
|
},
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
disableEventListener: {
|
||||||
|
request: {
|
||||||
|
eventListenerInfoId: Arg(0),
|
||||||
|
},
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
modifyAttributes: {
|
modifyAttributes: {
|
||||||
request: {
|
request: {
|
||||||
modifications: Arg(0, "array:json"),
|
modifications: Arg(0, "array:json"),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue