Bumping manifests a=b2g-bump
[gecko.git] / toolkit / devtools / event-parsers.js
blob28cfd279afdaf0b4dfec3f8c8ba08b83215b73f7
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.
8 "use strict";
10 const {Cc, Ci, Cu} = require("chrome");
12 loader.lazyGetter(this, "eventListenerService", () => {
13   return Cc["@mozilla.org/eventlistenerservice;1"]
14            .getService(Ci.nsIEventListenerService);
15 });
17 let parsers = [
18   {
19     id: "jQuery events",
20     getListeners: function(node) {
21       let global = node.ownerGlobal.wrappedJSObject;
22       let hasJQuery = global.jQuery && global.jQuery.fn && global.jQuery.fn.jquery;
24       if (!hasJQuery) {
25         return;
26       }
28       let jQuery = global.jQuery;
29       let handlers = [];
31       // jQuery 1.2+
32       let data = jQuery._data || jQuery.data;
33       if (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") {
40               let eventInfo = {
41                 type: type,
42                 handler: event.handler || event,
43                 tags: "jQuery",
44                 hide: {
45                   capturing: true,
46                   dom0: true
47                 }
48               };
50               handlers.push(eventInfo);
51             }
52           }
53         }
54       }
56       // JQuery 1.0 & 1.1
57       let entry = jQuery(node)[0];
59       if (!entry) {
60         return handlers;
61       }
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") {
67             let eventInfo = {
68               type: type,
69               handler: events[key],
70               tags: "jQuery",
71               hide: {
72                 capturing: true,
73                 dom0: true
74               }
75             };
77             handlers.push(eventInfo);
78           }
79         }
80       }
82       return handlers;
83     }
84   },
85   {
86     id: "jQuery live events",
87     hasListeners: function(node) {
88       return jQueryLiveGetListeners(node, true);
89     },
90     getListeners: function(node) {
91       return jQueryLiveGetListeners(node, false);
92     },
93     normalizeHandler: function(handlerDO) {
94       let paths = [
95         [".event.proxy/", ".event.proxy/", "*"],
96         [".proxy/", "*"]
97       ];
99       let name = handlerDO.displayName;
101       if (!name) {
102         return handlerDO;
103       }
105       for (let path of paths) {
106         if (name.contains(path[0])) {
107           path.splice(0, 1);
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);
114               if (!temp) {
115                 continue;
116               }
118               let displayName = temp.displayName;
119               if (!displayName) {
120                 continue;
121               }
123               if (temp.class === "Function" &&
124                   (displayName.contains(point) || point === "*")) {
125                 handlerDO = temp;
126                 break;
127               }
128             }
129           }
130           break;
131         }
132       }
134       return handlerDO;
135     }
136   },
137   {
138     id: "DOM events",
139     hasListeners: function(node) {
140       let listeners;
142       if (node.nodeName.toLowerCase() === "html") {
143         listeners = eventListenerService.getListenerInfoFor(node.ownerGlobal) || [];
144       } else {
145         listeners = eventListenerService.getListenerInfoFor(node) || [];
146       }
148       for (let listener of listeners) {
149         if (listener.listenerObject && listener.type) {
150           return true;
151         }
152       }
154       return false;
155     },
156     getListeners: function(node) {
157       let handlers = [];
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.
168         if (!listener) {
169           continue;
170         }
172         let eventInfo = {
173           capturing: listenerObj.capturing,
174           type: listenerObj.type,
175           handler: listener
176         };
178         handlers.push(eventInfo);
179       }
181       return handlers;
182     }
183   }
186 function jQueryLiveGetListeners(node, boolOnEventFound) {
187   let global = node.ownerGlobal.wrappedJSObject;
188   let hasJQuery = global.jQuery && global.jQuery.fn && global.jQuery.fn.jquery;
190   if (!hasJQuery) {
191     return;
192   }
194   let jQuery = global.jQuery;
195   let handlers = [];
196   let data = jQuery._data || jQuery.data;
198   if (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
201     // event.
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))) {
209           continue;
210         }
212         let event = eventHolder[idx];
213         let selector = event.selector;
215         if (!selector && event.data) {
216           selector = event.data.selector || event.data || event.selector;
217         }
219         if (!selector || !node.ownerDocument) {
220           continue;
221         }
223         let matches;
224         try {
225           matches = node.matches && node.matches(selector);
226         } catch(e) {
227           // Invalid selector, do nothing.
228         }
230         if (boolOnEventFound && matches) {
231           return true;
232         }
234         if (!matches) {
235           continue;
236         }
238         if (!boolOnEventFound && (typeof event === "object" || typeof event === "function")) {
239           let eventInfo = {
240             type: event.origType || event.type.substr(selector.length + 1),
241             handler: event.handler || event,
242             tags: "jQuery,Live",
243             hide: {
244               dom0: true,
245               capturing: true
246             }
247           };
249           if (!eventInfo.type && event.data && event.data.live) {
250             eventInfo.type = event.data.live;
251           }
253           handlers.push(eventInfo);
254         }
255       }
256     }
257   }
259   if (boolOnEventFound) {
260     return false;
261   }
262   return handlers;
265 this.EventParsers = function EventParsers() {
266   if (this._eventParsers.size === 0) {
267     for (let parserObj of parsers) {
268       this.registerEventParser(parserObj);
269     }
270   }
273 exports.EventParsers = EventParsers;
275 EventParsers.prototype = {
276   _eventParsers: new Map(), // NOTE: This is shared amongst all instances.
278   get parsers() {
279     return this._eventParsers;
280   },
282   /**
283    * Register a new event parser to be used in the processing of event info.
284    *
285    * @param {Object} parserObj
286    *        Each parser must contain the following properties:
287    *        - parser, which must take the following form:
288    *   {
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).
293    *
294    *     hasListeners: function(node) { },     // Optional function that takes a
295    *                                           // node and returns a boolean
296    *                                           // indicating whether a node has
297    *                                           // listeners attached.
298    *
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
305    *                                           // details:
306    *                                           //   https://developer.mozilla.org/
307    *                                           //   docs/Tools/Debugger-API/
308    *                                           //   Debugger.Object
309    *   }
310    *
311    * An eventInfo object should take the following form:
312    *   {
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
323    *     },
324    *
325    *     override: {                        // The following can be overridden:
326    *       type: "click",
327    *       origin: "http://www.mozilla.com",
328    *       searchString: 'onclick="doSomething()"',
329    *       DOM0: true,
330    *       capturing: true
331    *     }
332    *   }
333    */
334   registerEventParser: function(parserObj) {
335     let parserId = parserObj.id;
337     if (!parserId) {
338       throw new Error("Cannot register new event parser with id " + parserId);
339     }
340     if (this._eventParsers.has(parserId)) {
341       throw new Error("Duplicate event parser id " + parserId);
342     }
344     this._eventParsers.set(parserId, {
345       getListeners: parserObj.getListeners,
346       hasListeners: parserObj.hasListeners,
347       normalizeHandler: parserObj.normalizeHandler
348     });
349   },
351   /**
352    * Removes parser that matches a given parserId.
353    *
354    * @param {String} parserId
355    *        id of the event parser to unregister.
356    */
357   unregisterEventParser: function(parserId) {
358     this._eventParsers.delete(parserId);
359   },
361   /**
362    * Tidy up parsers.
363    */
364   destroy: function() {
365     for (let [id] of this._eventParsers) {
366       this.unregisterEventParser(id, true);
367     }
368   }