mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Properly handle bound functions in event listeners and objects with 'handleEvent' methods (bug 746622). r=vporof,mratcliffe
--HG-- rename : browser/devtools/debugger/test/browser_dbg_break-on-dom-event.js => browser/devtools/debugger/test/browser_dbg_break-on-dom-event-01.js rename : browser/devtools/debugger/test/browser_dbg_break-on-dom-event.js => browser/devtools/debugger/test/browser_dbg_break-on-dom-event-02.js rename : browser/devtools/debugger/test/browser_dbg_event-listeners.js => browser/devtools/debugger/test/browser_dbg_event-listeners-01.js rename : browser/devtools/debugger/test/doc_event-listeners.html => browser/devtools/debugger/test/doc_event-listeners-01.html rename : browser/devtools/debugger/test/doc_event-listeners.html => browser/devtools/debugger/test/doc_event-listeners-03.html extra : rebase_source : 5ff8e76fd2a8518ea007c7594dcc544cb0cc0808
This commit is contained in:
parent
30679ed261
commit
5bd95bcead
@ -1719,7 +1719,10 @@ EventListeners.prototype = {
|
||||
|
||||
// Add all the listeners in the debugger view event linsteners container.
|
||||
for (let listener of aResponse.listeners) {
|
||||
let definitionSite = yield this._getDefinitionSite(listener.function);
|
||||
let definitionSite;
|
||||
if (listener.function.class == "Function") {
|
||||
definitionSite = yield this._getDefinitionSite(listener.function);
|
||||
}
|
||||
listener.function.url = definitionSite;
|
||||
DebuggerView.EventListeners.addListener(listener, { staged: true });
|
||||
}
|
||||
@ -1740,18 +1743,18 @@ EventListeners.prototype = {
|
||||
* @param object aFunction
|
||||
* The grip of the function to get the definition site for.
|
||||
* @return object
|
||||
* A promise that is resolved with the function's owner source url,
|
||||
* or rejected if an error occured.
|
||||
* A promise that is resolved with the function's owner source url.
|
||||
*/
|
||||
_getDefinitionSite: function(aFunction) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
gThreadClient.pauseGrip(aFunction).getDefinitionSite(aResponse => {
|
||||
if (aResponse.error) {
|
||||
deferred.reject("Error getting function definition site: " + aResponse.message);
|
||||
} else {
|
||||
deferred.resolve(aResponse.url);
|
||||
// Don't make this error fatal, because it would break the entire events pane.
|
||||
const msg = "Error getting function definition site: " + aResponse.message;
|
||||
DevToolsUtils.reportException("_getDefinitionSite", msg);
|
||||
}
|
||||
deferred.resolve(aResponse.url);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
|
@ -51,8 +51,9 @@ support-files =
|
||||
doc_editor-mode.html
|
||||
doc_empty-tab-01.html
|
||||
doc_empty-tab-02.html
|
||||
doc_event-listeners.html
|
||||
doc_event-listeners-01.html
|
||||
doc_event-listeners-02.html
|
||||
doc_event-listeners-03.html
|
||||
doc_frame-parameters.html
|
||||
doc_function-display-name.html
|
||||
doc_function-search.html
|
||||
@ -115,8 +116,9 @@ skip-if = os == 'win' # bug 1005274
|
||||
[browser_dbg_break-on-dom-06.js]
|
||||
[browser_dbg_break-on-dom-07.js]
|
||||
[browser_dbg_break-on-dom-08.js]
|
||||
[browser_dbg_break-on-dom-event.js]
|
||||
[browser_dbg_break-on-dom-event-01.js]
|
||||
skip-if = os == "mac" || e10s # Bug 895426
|
||||
[browser_dbg_break-on-dom-event-02.js]
|
||||
[browser_dbg_breakpoints-actual-location.js]
|
||||
[browser_dbg_breakpoints-actual-location2.js]
|
||||
[browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js]
|
||||
@ -151,7 +153,8 @@ skip-if = true # Bug 933950 (leaky test)
|
||||
[browser_dbg_debugger-statement.js]
|
||||
[browser_dbg_editor-contextmenu.js]
|
||||
[browser_dbg_editor-mode.js]
|
||||
[browser_dbg_event-listeners.js]
|
||||
[browser_dbg_event-listeners-01.js]
|
||||
[browser_dbg_event-listeners-02.js]
|
||||
[browser_dbg_file-reload.js]
|
||||
[browser_dbg_function-display-name.js]
|
||||
[browser_dbg_global-method-override.js]
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Tests that the break-on-dom-events request works.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_event-listeners.html";
|
||||
const TAB_URL = EXAMPLE_URL + "doc_event-listeners-01.html";
|
||||
|
||||
let gClient, gThreadClient, gInput, gButton;
|
||||
|
@ -0,0 +1,110 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the break-on-dom-events request works even for bound event
|
||||
* listeners and handler objects with 'handleEvent' methods.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_event-listeners-03.html";
|
||||
|
||||
let gClient, gThreadClient;
|
||||
|
||||
function test() {
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init(() => true);
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
let transport = DebuggerServer.connectPipe();
|
||||
gClient = new DebuggerClient(transport);
|
||||
gClient.connect((aType, aTraits) => {
|
||||
is(aType, "browser",
|
||||
"Root actor should identify itself as a browser.");
|
||||
|
||||
addTab(TAB_URL)
|
||||
.then(() => attachThreadActorForUrl(gClient, TAB_URL))
|
||||
.then(aThreadClient => gThreadClient = aThreadClient)
|
||||
.then(pauseDebuggee)
|
||||
.then(testBreakOnClick)
|
||||
.then(closeConnection)
|
||||
.then(finish)
|
||||
.then(null, aError => {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function pauseDebuggee() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
|
||||
is(aPacket.type, "paused",
|
||||
"We should now be paused.");
|
||||
is(aPacket.why.type, "debuggerStatement",
|
||||
"The debugger statement was hit.");
|
||||
|
||||
gThreadClient.resume(deferred.resolve);
|
||||
});
|
||||
|
||||
// Spin the event loop before causing the debuggee to pause, to allow
|
||||
// this function to return first.
|
||||
executeSoon(() => triggerButtonClick("initialSetup"));
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// Test pause on a single event.
|
||||
function testBreakOnClick() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
// Test calling pauseOnDOMEvents from a running state.
|
||||
gThreadClient.pauseOnDOMEvents(["click"], (aPacket) => {
|
||||
is(aPacket.error, undefined,
|
||||
"The pause-on-click request completed successfully.");
|
||||
let handlers = ["clicker"];
|
||||
|
||||
gClient.addListener("paused", function tester(aEvent, aPacket) {
|
||||
is(aPacket.why.type, "pauseOnDOMEvents",
|
||||
"A hidden breakpoint was hit.");
|
||||
|
||||
switch(handlers.length) {
|
||||
case 1:
|
||||
is(aPacket.frame.where.line, 26, "Found the clicker handler.");
|
||||
handlers.push("handleEventClick");
|
||||
break;
|
||||
case 2:
|
||||
is(aPacket.frame.where.line, 36, "Found the handleEventClick handler.");
|
||||
handlers.push("boundHandleEventClick");
|
||||
break;
|
||||
case 3:
|
||||
is(aPacket.frame.where.line, 46, "Found the boundHandleEventClick handler.");
|
||||
gClient.removeListener("paused", tester);
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
gThreadClient.resume(() => triggerButtonClick(handlers.slice(-1)));
|
||||
});
|
||||
|
||||
triggerButtonClick(handlers.slice(-1));
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function triggerButtonClick(aNodeId) {
|
||||
let button = content.document.getElementById(aNodeId);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, button);
|
||||
}
|
||||
|
||||
function closeConnection() {
|
||||
let deferred = promise.defer();
|
||||
gClient.close(deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
removeTab(gBrowser.selectedTab);
|
||||
gClient = null;
|
||||
gThreadClient = null;
|
||||
});
|
@ -5,7 +5,7 @@
|
||||
* Tests that the eventListeners request works.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_event-listeners.html";
|
||||
const TAB_URL = EXAMPLE_URL + "doc_event-listeners-01.html";
|
||||
|
||||
let gClient;
|
||||
|
||||
@ -87,6 +87,7 @@ function testEventListeners(aThreadClient) {
|
||||
let types = [];
|
||||
|
||||
for (let l of listeners) {
|
||||
info("Listener for the "+l.type+" event.");
|
||||
let node = l.node;
|
||||
ok(node, "There is a node property.");
|
||||
ok(node.object, "There is a node object property.");
|
130
browser/devtools/debugger/test/browser_dbg_event-listeners-02.js
Normal file
130
browser/devtools/debugger/test/browser_dbg_event-listeners-02.js
Normal file
@ -0,0 +1,130 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the eventListeners request works when bound functions are used as
|
||||
* event listeners.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_event-listeners-03.html";
|
||||
|
||||
let gClient;
|
||||
|
||||
function test() {
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init(() => true);
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
let transport = DebuggerServer.connectPipe();
|
||||
gClient = new DebuggerClient(transport);
|
||||
gClient.connect((aType, aTraits) => {
|
||||
is(aType, "browser",
|
||||
"Root actor should identify itself as a browser.");
|
||||
|
||||
addTab(TAB_URL)
|
||||
.then(() => attachThreadActorForUrl(gClient, TAB_URL))
|
||||
.then(pauseDebuggee)
|
||||
.then(testEventListeners)
|
||||
.then(closeConnection)
|
||||
.then(finish)
|
||||
.then(null, aError => {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function pauseDebuggee(aThreadClient) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
|
||||
is(aPacket.type, "paused",
|
||||
"We should now be paused.");
|
||||
is(aPacket.why.type, "debuggerStatement",
|
||||
"The debugger statement was hit.");
|
||||
|
||||
deferred.resolve(aThreadClient);
|
||||
});
|
||||
|
||||
// Spin the event loop before causing the debuggee to pause, to allow
|
||||
// this function to return first.
|
||||
executeSoon(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
content.document.querySelector("button"),
|
||||
content);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testEventListeners(aThreadClient) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
aThreadClient.eventListeners(aPacket => {
|
||||
if (aPacket.error) {
|
||||
let msg = "Error getting event listeners: " + aPacket.message;
|
||||
ok(false, msg);
|
||||
deferred.reject(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
is(aPacket.listeners.length, 3,
|
||||
"Found all event listeners.");
|
||||
|
||||
promise.all(aPacket.listeners.map(listener => {
|
||||
const lDeferred = promise.defer();
|
||||
aThreadClient.pauseGrip(listener.function).getDefinitionSite(aResponse => {
|
||||
if (aResponse.error) {
|
||||
const msg = "Error getting function definition site: " + aResponse.message;
|
||||
ok(false, msg);
|
||||
lDeferred.reject(msg);
|
||||
return;
|
||||
}
|
||||
listener.function.url = aResponse.url;
|
||||
lDeferred.resolve(listener);
|
||||
});
|
||||
return lDeferred.promise;
|
||||
})).then(listeners => {
|
||||
is (listeners.length, 3, "Found three event listeners.");
|
||||
for (let l of listeners) {
|
||||
let node = l.node;
|
||||
ok(node, "There is a node property.");
|
||||
ok(node.object, "There is a node object property.");
|
||||
ok(node.selector == "window" ||
|
||||
content.document.querySelectorAll(node.selector).length == 1,
|
||||
"The node property is a unique CSS selector.");
|
||||
|
||||
let func = l.function;
|
||||
ok(func, "There is a function property.");
|
||||
is(func.type, "object", "The function form is of type 'object'.");
|
||||
is(func.class, "Function", "The function form is of class 'Function'.");
|
||||
is(func.url, TAB_URL, "The function url is correct.");
|
||||
|
||||
is(l.type, "click", "This is a click event listener.");
|
||||
is(l.allowsUntrusted, true,
|
||||
"'allowsUntrusted' property has the right value.");
|
||||
is(l.inSystemEventGroup, false,
|
||||
"'inSystemEventGroup' property has the right value.");
|
||||
is(l.isEventHandler, false,
|
||||
"'isEventHandler' property has the right value.");
|
||||
is(l.capturing, false,
|
||||
"Capturing property has the right value.");
|
||||
}
|
||||
|
||||
aThreadClient.resume(deferred.resolve);
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function closeConnection() {
|
||||
let deferred = promise.defer();
|
||||
gClient.close(deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
removeTab(gBrowser.selectedTab);
|
||||
gClient = null;
|
||||
});
|
63
browser/devtools/debugger/test/doc_event-listeners-03.html
Normal file
63
browser/devtools/debugger/test/doc_event-listeners-03.html
Normal file
@ -0,0 +1,63 @@
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Bound event listeners test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<button id="initialSetup">initialSetup</button>
|
||||
<button id="clicker">clicker</button>
|
||||
<button id="handleEventClick">handleEventClick</button>
|
||||
<button id="boundHandleEventClick">boundHandleEventClick</button>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.addEventListener("load", function onload() {
|
||||
window.removeEventListener("load", onload);
|
||||
function initialSetup(event) {
|
||||
var button = document.getElementById("initialSetup");
|
||||
button.removeEventListener("click", initialSetup);
|
||||
debugger;
|
||||
}
|
||||
|
||||
function clicker(event) {
|
||||
window.foobar = "clicker";
|
||||
}
|
||||
|
||||
function handleEventClick() {
|
||||
var button = document.getElementById("handleEventClick");
|
||||
// Create a long prototype chain to test for weird edge cases.
|
||||
button.addEventListener("click", Object.create(Object.create(this)));
|
||||
}
|
||||
|
||||
handleEventClick.prototype.handleEvent = function() {
|
||||
window.foobar = "handleEventClick";
|
||||
};
|
||||
|
||||
function boundHandleEventClick() {
|
||||
var button = document.getElementById("boundHandleEventClick");
|
||||
this.handleEvent = this.handleEvent.bind(this);
|
||||
button.addEventListener("click", this);
|
||||
}
|
||||
|
||||
boundHandleEventClick.prototype.handleEvent = function() {
|
||||
window.foobar = "boundHandleEventClick";
|
||||
};
|
||||
|
||||
var button = document.getElementById("clicker");
|
||||
// Bind more than once to test for weird edge cases.
|
||||
var boundClicker = clicker.bind(this).bind(this).bind(this);
|
||||
button.addEventListener("click", boundClicker);
|
||||
|
||||
new handleEventClick();
|
||||
new boundHandleEventClick();
|
||||
|
||||
var initButton = document.getElementById("initialSetup");
|
||||
initButton.addEventListener("click", initialSetup);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1257,7 +1257,29 @@ ThreadActor.prototype = {
|
||||
let l = Object.create(null);
|
||||
l.type = handler.type;
|
||||
let listener = handler.listenerObject;
|
||||
l.script = this.globalDebugObject.makeDebuggeeValue(listener).script;
|
||||
let listenerDO = this.globalDebugObject.makeDebuggeeValue(listener);
|
||||
// If the listener is an object with a 'handleEvent' method, use that.
|
||||
if (listenerDO.class == "Object" || listenerDO.class == "XULElement") {
|
||||
// For some events we don't have permission to access the
|
||||
// 'handleEvent' property when running in content scope.
|
||||
if (!listenerDO.unwrap()) {
|
||||
continue;
|
||||
}
|
||||
let heDesc;
|
||||
while (!heDesc && listenerDO) {
|
||||
heDesc = listenerDO.getOwnPropertyDescriptor("handleEvent");
|
||||
listenerDO = listenerDO.proto;
|
||||
}
|
||||
if (heDesc && heDesc.value) {
|
||||
listenerDO = heDesc.value;
|
||||
}
|
||||
}
|
||||
// When the listener is a bound function, we are actually interested in
|
||||
// the target function.
|
||||
while (listenerDO.isBoundFunction) {
|
||||
listenerDO = listenerDO.boundTargetFunction;
|
||||
}
|
||||
l.script = listenerDO.script;
|
||||
// Chrome listeners won't be converted to debuggee values, since their
|
||||
// compartment is not added as a debuggee.
|
||||
if (!l.script)
|
||||
@ -1827,9 +1849,32 @@ ThreadActor.prototype = {
|
||||
listenerForm.capturing = handler.capturing;
|
||||
listenerForm.allowsUntrusted = handler.allowsUntrusted;
|
||||
listenerForm.inSystemEventGroup = handler.inSystemEventGroup;
|
||||
listenerForm.isEventHandler = !!node["on" + listenerForm.type];
|
||||
let handlerName = "on" + listenerForm.type;
|
||||
listenerForm.isEventHandler = false;
|
||||
if (typeof node.hasAttribute !== "undefined") {
|
||||
listenerForm.isEventHandler = !!node.hasAttribute(handlerName);
|
||||
}
|
||||
if (!!node[handlerName]) {
|
||||
listenerForm.isEventHandler = !!node[handlerName];
|
||||
}
|
||||
// Get the Debugger.Object for the listener object.
|
||||
let listenerDO = this.globalDebugObject.makeDebuggeeValue(listener);
|
||||
// If the listener is an object with a 'handleEvent' method, use that.
|
||||
if (listenerDO.class == "Object" || listenerDO.class == "XULElement") {
|
||||
let heDesc;
|
||||
while (!heDesc && listenerDO) {
|
||||
heDesc = listenerDO.getOwnPropertyDescriptor("handleEvent");
|
||||
listenerDO = listenerDO.proto;
|
||||
}
|
||||
if (heDesc && heDesc.value) {
|
||||
listenerDO = heDesc.value;
|
||||
}
|
||||
}
|
||||
// When the listener is a bound function, we are actually interested in
|
||||
// the target function.
|
||||
while (listenerDO.isBoundFunction) {
|
||||
listenerDO = listenerDO.boundTargetFunction;
|
||||
}
|
||||
listenerForm.function = this.createValueGrip(listenerDO);
|
||||
listeners.push(listenerForm);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user