зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
0c74c88e49
Коммит
a232d2448c
|
@ -27,6 +27,7 @@ support-files =
|
|||
doc_markup_events_react_production_16.2.0.html
|
||||
doc_markup_events_react_production_16.2.0_jsx.html
|
||||
doc_markup_events-source_map.html
|
||||
doc_markup_events_toggle.html
|
||||
doc_markup_flashing.html
|
||||
doc_markup_html_mixed_case.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_jsx.js]
|
||||
[browser_markup_events_source_map.js]
|
||||
[browser_markup_events_toggle.js]
|
||||
[browser_markup_events-windowed-host.js]
|
||||
[browser_markup_flex_display_badge.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
|
||||
);
|
||||
|
||||
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`);
|
||||
}
|
||||
|
||||
|
|
|
@ -75,7 +75,12 @@ MarkupElementContainer.prototype = extend(MarkupContainer.prototype, {
|
|||
const toolbox = this.markup.toolbox;
|
||||
|
||||
// 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
|
||||
this.markup._disableImagePreviewTooltip();
|
||||
|
|
|
@ -27,17 +27,24 @@ class EventTooltip {
|
|||
* A list of event listeners
|
||||
* @param {Toolbox} toolbox
|
||||
* 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._toolbox = toolbox;
|
||||
this._eventEditors = new WeakMap();
|
||||
this._nodeFront = nodeFront;
|
||||
this._eventListenersAbortController = new AbortController();
|
||||
|
||||
// Used in tests: add a reference to the EventTooltip instance on the HTMLTooltip.
|
||||
this._tooltip.eventTooltip = this;
|
||||
|
||||
this._headerClicked = this._headerClicked.bind(this);
|
||||
this._debugClicked = this._debugClicked.bind(this);
|
||||
this._eventToggleCheckboxChanged = this._eventToggleCheckboxChanged.bind(
|
||||
this
|
||||
);
|
||||
|
||||
this._subscriptions = [];
|
||||
|
||||
const config = {
|
||||
|
@ -109,7 +116,7 @@ class EventTooltip {
|
|||
filename.setAttribute("title", newURI);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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
|
||||
const editor = new Editor(config);
|
||||
this._eventEditors.set(content, {
|
||||
|
@ -182,10 +210,21 @@ class EventTooltip {
|
|||
}
|
||||
|
||||
_addContentListeners(header) {
|
||||
header.addEventListener("click", this._headerClicked);
|
||||
header.addEventListener("click", this._headerClicked, {
|
||||
signal: this._eventListenersAbortController.signal,
|
||||
});
|
||||
}
|
||||
|
||||
_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")) {
|
||||
this._debugClicked(event);
|
||||
event.stopPropagation();
|
||||
|
@ -241,7 +280,7 @@ class EventTooltip {
|
|||
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.
|
||||
*/
|
||||
|
@ -308,24 +358,16 @@ class EventTooltip {
|
|||
this._tooltip.eventTooltip = null;
|
||||
}
|
||||
|
||||
const headerNodes = this.container.querySelectorAll(".event-header");
|
||||
|
||||
for (const node of headerNodes) {
|
||||
node.removeEventListener("click", this._headerClicked);
|
||||
}
|
||||
|
||||
const sourceNodes = this.container.querySelectorAll(
|
||||
".event-tooltip-debugger-icon"
|
||||
);
|
||||
for (const node of sourceNodes) {
|
||||
node.removeEventListener("click", this._debugClicked);
|
||||
if (this._eventListenersAbortController) {
|
||||
this._eventListenersAbortController.abort();
|
||||
this._eventListenersAbortController = null;
|
||||
}
|
||||
|
||||
for (const unsubscribe of this._subscriptions) {
|
||||
unsubscribe();
|
||||
}
|
||||
|
||||
this._toolbox = this._tooltip = null;
|
||||
this._toolbox = this._tooltip = this._nodeFront = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -385,9 +385,11 @@ class DOMEventCollector extends MainEventCollector {
|
|||
}
|
||||
|
||||
const eventInfo = {
|
||||
nsIEventListenerInfo: listener,
|
||||
capturing: listener.capturing,
|
||||
type: listener.type,
|
||||
handler: handler,
|
||||
enabled: listener.enabled,
|
||||
};
|
||||
|
||||
handlers.push(eventInfo);
|
||||
|
@ -820,16 +822,20 @@ class EventCollector {
|
|||
*
|
||||
* @param {DOMNode} node
|
||||
* The node for which events are to be gathered.
|
||||
* @return {Array}
|
||||
* @return {Array<Object>}
|
||||
* An array containing objects in the following format:
|
||||
* {
|
||||
* type: type, // e.g. "click"
|
||||
* handler: handler, // The function called when event is triggered.
|
||||
* tags: "jQuery", // Comma separated list of tags displayed
|
||||
* // inside event bubble.
|
||||
* hide: { // Flags for hiding certain properties.
|
||||
* capturing: true,
|
||||
* {String} type: The event type, e.g. "click"
|
||||
* {Function} handler: The function called when event is triggered.
|
||||
* {Boolean} enabled: Whether the listener is enabled or not (event listeners can
|
||||
* be disabled via the inspector)
|
||||
* {String} tags: Comma separated list of tags displayed inside event bubble (e.g. "JQuery")
|
||||
* {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) {
|
||||
|
@ -895,7 +901,10 @@ class EventCollector {
|
|||
* hide: {
|
||||
* capturing: true
|
||||
* },
|
||||
* native: false
|
||||
* native: false,
|
||||
* enabled: true
|
||||
* sourceActor: "sourceActor.1234",
|
||||
* nsIEventListenerInfo: nsIEventListenerInfo {…},
|
||||
* }
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
|
@ -923,6 +932,7 @@ class EventCollector {
|
|||
const tags = listener.tags || "";
|
||||
const type = listener.type || "";
|
||||
let isScriptBoundToNonScriptElement = false;
|
||||
const enabled = !!listener.enabled;
|
||||
let functionSource = handler.toString();
|
||||
let line = 0;
|
||||
let column = null;
|
||||
|
@ -1039,6 +1049,8 @@ class EventCollector {
|
|||
hide: typeof override.hide !== "undefined" ? override.hide : hide,
|
||||
native,
|
||||
sourceActor,
|
||||
nsIEventListenerInfo: listener.nsIEventListenerInfo,
|
||||
enabled,
|
||||
};
|
||||
|
||||
// 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.rawNode = node;
|
||||
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
|
||||
// displayed to track changes when reflows occur.
|
||||
|
@ -159,6 +162,16 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, {
|
|||
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 = null;
|
||||
this.rawNode = null;
|
||||
|
@ -560,7 +573,56 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, {
|
|||
* Get all event listeners that are listening on this node.
|
||||
*/
|
||||
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"),
|
||||
},
|
||||
},
|
||||
enableEventListener: {
|
||||
request: {
|
||||
eventListenerInfoId: Arg(0),
|
||||
},
|
||||
response: {},
|
||||
},
|
||||
disableEventListener: {
|
||||
request: {
|
||||
eventListenerInfoId: Arg(0),
|
||||
},
|
||||
response: {},
|
||||
},
|
||||
modifyAttributes: {
|
||||
request: {
|
||||
modifications: Arg(0, "array:json"),
|
||||
|
|
Загрузка…
Ссылка в новой задаче