1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 // This file contains event parsers that are then used by developer tools in
6 // order to find information about events affecting an HTML element.
10 const {Cc, Ci, Cu} = require("chrome");
12 loader.lazyGetter(this, "eventListenerService", () => {
13 return Cc["@mozilla.org/eventlistenerservice;1"]
14 .getService(Ci.nsIEventListenerService);
20 getListeners: function(node) {
21 let global = node.ownerGlobal.wrappedJSObject;
22 let hasJQuery = global.jQuery && global.jQuery.fn && global.jQuery.fn.jquery;
28 let jQuery = global.jQuery;
32 let data = jQuery._data || jQuery.data;
34 let eventsObj = data(node, "events");
35 for (let type in eventsObj) {
36 let events = eventsObj[type];
37 for (let key in events) {
38 let event = events[key];
39 if (typeof event === "object" || typeof event === "function") {
42 handler: event.handler || event,
50 handlers.push(eventInfo);
57 let entry = jQuery(node)[0];
63 for (let type in entry.events) {
64 let events = entry.events[type];
65 for (let key in events) {
66 if (typeof events[key] === "function") {
77 handlers.push(eventInfo);
86 id: "jQuery live events",
87 hasListeners: function(node) {
88 return jQueryLiveGetListeners(node, true);
90 getListeners: function(node) {
91 return jQueryLiveGetListeners(node, false);
93 normalizeHandler: function(handlerDO) {
95 [".event.proxy/", ".event.proxy/", "*"],
99 let name = handlerDO.displayName;
105 for (let path of paths) {
106 if (name.contains(path[0])) {
109 for (let point of path) {
110 let names = handlerDO.environment.names();
112 for (let varName of names) {
113 let temp = handlerDO.environment.getVariable(varName);
118 let displayName = temp.displayName;
123 if (temp.class === "Function" &&
124 (displayName.contains(point) || point === "*")) {
139 hasListeners: function(node) {
142 if (node.nodeName.toLowerCase() === "html") {
143 listeners = eventListenerService.getListenerInfoFor(node.ownerGlobal) || [];
145 listeners = eventListenerService.getListenerInfoFor(node) || [];
148 for (let listener of listeners) {
149 if (listener.listenerObject && listener.type) {
156 getListeners: function(node) {
158 let listeners = eventListenerService.getListenerInfoFor(node);
160 // The Node actor's getEventListenerInfo knows that when an html tag has
161 // been passed we need the window object so we don't need to account for
162 // event hoisting here as we did in hasListeners.
164 for (let listenerObj of listeners) {
165 let listener = listenerObj.listenerObject;
167 // If there is no JS event listener skip this.
173 capturing: listenerObj.capturing,
174 type: listenerObj.type,
178 handlers.push(eventInfo);
186 function jQueryLiveGetListeners(node, boolOnEventFound) {
187 let global = node.ownerGlobal.wrappedJSObject;
188 let hasJQuery = global.jQuery && global.jQuery.fn && global.jQuery.fn.jquery;
194 let jQuery = global.jQuery;
196 let data = jQuery._data || jQuery.data;
199 // Live events are added to the document and bubble up to all elements.
200 // Any element matching the specified selector will trigger the live
202 let events = data(global.document, "events");
204 for (let type in events) {
205 let eventHolder = events[type];
207 for (let idx in eventHolder) {
208 if (typeof idx !== "string" || isNaN(parseInt(idx, 10))) {
212 let event = eventHolder[idx];
213 let selector = event.selector;
215 if (!selector && event.data) {
216 selector = event.data.selector || event.data || event.selector;
219 if (!selector || !node.ownerDocument) {
225 matches = node.matches && node.matches(selector);
227 // Invalid selector, do nothing.
230 if (boolOnEventFound && matches) {
238 if (!boolOnEventFound && (typeof event === "object" || typeof event === "function")) {
240 type: event.origType || event.type.substr(selector.length + 1),
241 handler: event.handler || event,
249 if (!eventInfo.type && event.data && event.data.live) {
250 eventInfo.type = event.data.live;
253 handlers.push(eventInfo);
259 if (boolOnEventFound) {
265 this.EventParsers = function EventParsers() {
266 if (this._eventParsers.size === 0) {
267 for (let parserObj of parsers) {
268 this.registerEventParser(parserObj);
273 exports.EventParsers = EventParsers;
275 EventParsers.prototype = {
276 _eventParsers: new Map(), // NOTE: This is shared amongst all instances.
279 return this._eventParsers;
283 * Register a new event parser to be used in the processing of event info.
285 * @param {Object} parserObj
286 * Each parser must contain the following properties:
287 * - parser, which must take the following form:
289 * id {String}: "jQuery events", // Unique id.
290 * getListeners: function(node) { }, // Function that takes a node and
291 * // returns an array of eventInfo
292 * // objects (see below).
294 * hasListeners: function(node) { }, // Optional function that takes a
295 * // node and returns a boolean
296 * // indicating whether a node has
297 * // listeners attached.
299 * normalizeHandler: function(fnDO) { }, // Optional function that takes a
300 * // Debugger.Object instance and
301 * // climbs the scope chain to get
302 * // the function that should be
303 * // displayed in the event bubble
304 * // see the following url for
306 * // https://developer.mozilla.org/
307 * // docs/Tools/Debugger-API/
311 * An eventInfo object should take the following form:
313 * type {String}: "click",
314 * handler {Function}: event handler,
315 * tags {String}: "jQuery,Live", // These tags will be displayed as
316 * // attributes in the events popup.
317 * hide: { // Hide or show fields:
318 * debugger: false, // Debugger icon
319 * type: false, // Event type e.g. click
320 * filename: false, // Filename
321 * capturing: false, // Capturing
322 * dom0: false // DOM 0
325 * override: { // The following can be overridden:
327 * origin: "http://www.mozilla.com",
328 * searchString: 'onclick="doSomething()"',
334 registerEventParser: function(parserObj) {
335 let parserId = parserObj.id;
338 throw new Error("Cannot register new event parser with id " + parserId);
340 if (this._eventParsers.has(parserId)) {
341 throw new Error("Duplicate event parser id " + parserId);
344 this._eventParsers.set(parserId, {
345 getListeners: parserObj.getListeners,
346 hasListeners: parserObj.hasListeners,
347 normalizeHandler: parserObj.normalizeHandler
352 * Removes parser that matches a given parserId.
354 * @param {String} parserId
355 * id of the event parser to unregister.
357 unregisterEventParser: function(parserId) {
358 this._eventParsers.delete(parserId);
364 destroy: function() {
365 for (let [id] of this._eventParsers) {
366 this.unregisterEventParser(id, true);