1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/trace"
  5 ],
  6 function(FBTrace) {
  7 
  8 // ********************************************************************************************* //
  9 // Constants
 10 
 11 var Events = {};
 12 
 13 // ********************************************************************************************* //
 14 
 15 Events.dispatch = function(listeners, name, args)
 16 {
 17     if (!listeners)
 18     {
 19         if (FBTrace.DBG_DISPATCH)
 20             FBTrace.sysout("Events.dispatch "+name+" without listeners");
 21 
 22         return;
 23     }
 24 
 25     try
 26     {
 27         if (FBTrace.DBG_DISPATCH)
 28             var noMethods = [];
 29 
 30         for (var i = 0; i < listeners.length; ++i)
 31         {
 32             var listener = listeners[i];
 33             if (!listener)
 34             {
 35                 if (FBTrace.DBG_DISPATCH || FBTrace.DBG_ERRORS)
 36                     FBTrace.sysout("Events.dispatch ERROR "+i+" "+name+" to null listener.");
 37                 continue;
 38             }
 39 
 40             if (listener[name])
 41             {
 42                 try
 43                 {
 44                     listener[name].apply(listener, args);
 45                 }
 46                 catch(exc)
 47                 {
 48                     if (FBTrace.DBG_ERRORS)
 49                     {
 50                         if (exc.stack)
 51                         {
 52                             var stack = exc.stack;
 53                             exc.stack = stack.split('\n');
 54                         }
 55 
 56                         var culprit = listeners[i] ? listeners[i].dispatchName : null;
 57                         FBTrace.sysout("EXCEPTION in Events.dispatch "+(culprit?culprit+".":"")+
 58                             name+": "+exc+" in "+(exc.fileName?exc.fileName:"")+
 59                             (exc.lineNumber?":"+exc.lineNumber:""), exc);
 60                     }
 61                 }
 62             }
 63             else
 64             {
 65                 if (FBTrace.DBG_DISPATCH)
 66                     noMethods.push(listener);
 67             }
 68         }
 69 
 70         if (FBTrace.DBG_DISPATCH)
 71             FBTrace.sysout("Events.dispatch "+name+" to "+listeners.length+" listeners, "+
 72                 noMethods.length+" had no such method:", noMethods);
 73     }
 74     catch (exc)
 75     {
 76         if (FBTrace.DBG_ERRORS)
 77         {
 78             if (exc.stack)
 79             {
 80                 var stack = exc.stack;
 81                 exc.stack = stack.split('\n');
 82             }
 83 
 84             var culprit = listeners[i] ? listeners[i].dispatchName : null;
 85             FBTrace.sysout("Exception in Events.dispatch "+(culprit?culprit+".":"")+ name+
 86                 ": "+exc, exc);
 87         }
 88     }
 89 };
 90 
 91 Events.dispatch2 = function(listeners, name, args)
 92 {
 93     try
 94     {
 95         if (FBTrace.DBG_DISPATCH)
 96             var noMethods = [];
 97 
 98         if (!listeners)
 99         {
100             if (FBTrace.DBG_DISPATCH)
101                 FBTrace.sysout("dispatch2, no listeners for "+name);
102             return;
103         }
104 
105         for (var i = 0; i < listeners.length; ++i)
106         {
107             var listener = listeners[i];
108             if (listener[name])
109             {
110                 var result = listener[name].apply(listener, args);
111 
112                 if (FBTrace.DBG_DISPATCH)
113                     FBTrace.sysout("dispatch2 "+name+" to #"+i+" of "+listeners.length+
114                         " listeners, result "+result, {result: result, listener: listeners[i],
115                         fn: listener[name].toSource()});
116 
117                 if (result)
118                     return result;
119             }
120             else
121             {
122                 if (FBTrace.DBG_DISPATCH)
123                     noMethods.push(listener);
124             }
125         }
126 
127         if (FBTrace.DBG_DISPATCH && noMethods.length == listeners.length)
128             FBTrace.sysout("Events.dispatch2 "+name+" to "+listeners.length+" listeners, "+
129                 noMethods.length+" had no such method:", noMethods);
130     }
131     catch (exc)
132     {
133         if (typeof(FBTrace) != "undefined" && FBTrace.DBG_ERRORS)
134         {
135             if (exc.stack)
136                 exc.stack = exc.stack.split('/n');
137 
138             FBTrace.sysout(" Exception in lib.dispatch2 "+ name+" exc:"+exc, exc);
139         }
140     }
141 };
142 
143 // ********************************************************************************************* //
144 // Events
145 
146 Events.cancelEvent = function(event)
147 {
148     event.stopPropagation();
149     event.preventDefault();
150 };
151 
152 Events.isLeftClick = function(event, allowKeyModifiers)
153 {
154     return event.button == 0 && (allowKeyModifiers || this.noKeyModifiers(event));
155 };
156 
157 Events.isMiddleClick = function(event, allowKeyModifiers)
158 {
159     return event.button == 1 && (allowKeyModifiers || this.noKeyModifiers(event));
160 };
161 
162 Events.isRightClick = function(event, allowKeyModifiers)
163 {
164     return event.button == 2 && (allowKeyModifiers || this.noKeyModifiers(event));
165 };
166 
167 Events.isSingleClick = function(event)
168 {
169     return event instanceof MouseEvent && event.detail == 1;
170 };
171 
172 Events.isDoubleClick = function(event)
173 {
174     return event instanceof MouseEvent && event.detail == 2;
175 };
176 
177 Events.noKeyModifiers = function(event)
178 {
179     return !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey;
180 };
181 
182 Events.isControlClick = function(event)
183 {
184     return event.button == 0 && this.isControl(event);
185 };
186 
187 Events.isShiftClick = function(event)
188 {
189     return event.button == 0 && this.isShift(event);
190 };
191 
192 Events.isControl = function(event)
193 {
194     return (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey;
195 };
196 
197 Events.isAlt = function(event)
198 {
199     return event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey;
200 };
201 
202 Events.isAltClick = function(event)
203 {
204     return event.button == 0 && this.isAlt(event);
205 };
206 
207 Events.isControlShift = function(event)
208 {
209     return (event.metaKey || event.ctrlKey) && event.shiftKey && !event.altKey;
210 };
211 
212 Events.isControlAlt = function(event)
213 {
214     return (event.metaKey || event.ctrlKey) && !event.shiftKey && event.altKey;
215 };
216 
217 Events.isShift = function(event)
218 {
219     return event.shiftKey && !event.metaKey && !event.ctrlKey && !event.altKey;
220 };
221 
222 // ********************************************************************************************* //
223 // DOM Events
224 
225 const eventTypes =
226 {
227     composition: [
228         "composition",
229         "compositionstart",
230         "compositionend"
231     ],
232 
233     contextmenu: [
234         "contextmenu"
235     ],
236 
237     drag: [
238         "dragenter",
239         "dragover",
240         "dragexit",
241         "dragdrop",
242         "draggesture"
243     ],
244 
245     focus: [
246         "focus",
247         "blur"
248     ],
249 
250     form: [
251         "submit",
252         "reset",
253         "change",
254         "select",
255         "input"
256     ],
257 
258     key: [
259         "keydown",
260         "keyup",
261         "keypress"
262     ],
263 
264     load: [
265         "load",
266         "beforeunload",
267         "unload",
268         "abort",
269         "error"
270     ],
271 
272     mouse: [
273         "mousedown",
274         "mouseup",
275         "click",
276         "dblclick",
277         "mouseover",
278         "mouseout",
279         "mousemove"
280     ],
281 
282     mutation: [
283         "DOMSubtreeModified",
284         "DOMNodeInserted",
285         "DOMNodeRemoved",
286         "DOMNodeRemovedFromDocument",
287         "DOMNodeInsertedIntoDocument",
288         "DOMAttrModified",
289         "DOMCharacterDataModified"
290     ],
291 
292     paint: [
293         "paint",
294         "resize",
295         "scroll"
296     ],
297 
298     scroll: [
299         "overflow",
300         "underflow",
301         "overflowchanged"
302     ],
303 
304     text: [
305         "text"
306     ],
307 
308     ui: [
309         "DOMActivate",
310         "DOMFocusIn",
311         "DOMFocusOut"
312     ],
313 
314     xul: [
315         "popupshowing",
316         "popupshown",
317         "popuphiding",
318         "popuphidden",
319         "close",
320         "command",
321         "broadcast",
322         "commandupdate"
323     ],
324 
325     clipboard: [
326         "cut",
327         "copy",
328         "paste"
329     ],
330 
331     touch: [
332         "touchstart",
333         "touchend",
334         "touchmove",
335         "touchenter",
336         "touchleave",
337         "touchcancel"
338     ]
339 };
340 
341 Events.getEventTypes = function(family)
342 {
343     var types = [];
344     for (var eventFamily in eventTypes)
345     {
346         if (!family || family == eventFamily)
347         {
348             for (type in eventTypes[eventFamily])
349                 types.push(eventTypes[eventFamily][type]);
350         }
351     }
352 
353     return types;
354 };
355 
356 Events.isEventFamily = function(eventType)
357 {
358     for (var family in eventTypes)
359     {
360         if (eventType == family)
361             return true;
362     }
363 
364     return false;
365 };
366 
367 Events.getEventFamily = function(eventType)
368 {
369     if (!this.families)
370     {
371         this.families = {};
372 
373         for (var family in eventTypes)
374         {
375             var types = eventTypes[family];
376             for (var i = 0; i < types.length; ++i)
377                 this.families[types[i]] = family;
378         }
379     }
380 
381     return this.families[eventType];
382 };
383 
384 Events.attachAllListeners = function(object, listener)
385 {
386     for (var family in eventTypes)
387     {
388         if (family != "mutation" || Firebug.attachMutationEvents)
389             this.attachFamilyListeners(family, object, listener);
390     }
391 };
392 
393 Events.detachAllListeners = function(object, listener)
394 {
395     for (var family in eventTypes)
396     {
397         if (family != "mutation" || Firebug.attachMutationEvents)
398             this.detachFamilyListeners(family, object, listener);
399     }
400 };
401 
402 Events.attachFamilyListeners = function(family, object, listener)
403 {
404     var types = eventTypes[family];
405     for (var i = 0; i < types.length; ++i)
406         object.addEventListener(types[i], listener, false);
407 };
408 
409 Events.detachFamilyListeners = function(family, object, listener)
410 {
411     var types = eventTypes[family];
412     for (var i = 0; i < types.length; ++i)
413         object.removeEventListener(types[i], listener, false);
414 };
415 
416 // ********************************************************************************************* //
417 // Event Listeners (+ support for tracking)
418 
419 var listeners = [];
420 
421 Events.addEventListener = function(parent, eventId, listener, capturing)
422 {
423     if (FBTrace.DBG_EVENTLISTENERS)
424     {
425         for (var i=0; i<listeners.length; i++)
426         {
427             var l = listeners[i];
428             if (l.parent == parent && l.eventId == eventId && l.listener == listener &&
429                 l.capturing == capturing)
430             {
431                 FBTrace.sysout("Events.addEventListener; ERROR already registered!", l);
432                 return;
433             }
434         }
435     }
436 
437     parent.addEventListener(eventId, listener, capturing);
438 
439     if (FBTrace.DBG_EVENTLISTENERS)
440     {
441         var frames = [];
442         for (var frame = Components.stack; frame; frame = frame.caller)
443             frames.push(frame.filename + " (" + frame.lineNumber + ")");
444 
445         frames.shift();
446 
447         var pid = (typeof(parent.location) != "undefined" ? (parent.location + "") : typeof(parent));
448 
449         listeners.push({
450             parentId: pid,
451             eventId: eventId,
452             capturing: capturing,
453             listener: listener,
454             stack: frames,
455             parent: parent,
456         });
457     }
458 }
459 
460 Events.removeEventListener = function(parent, eventId, listener, capturing)
461 {
462     try
463     {
464         parent.removeEventListener(eventId, listener, capturing);
465     }
466     catch (e)
467     {
468         if (FBTrace.DBG_ERRORS)
469             FBTrace.sysout("events.removeEventListener; (" + eventId + ") " + e, e);
470     }
471 
472     if (FBTrace.DBG_EVENTLISTENERS)
473     {
474         for (var i=0; i<listeners.length; i++)
475         {
476             var l = listeners[i];
477             if (l.parent == parent && l.eventId == eventId && l.listener == listener &&
478                 l.capturing == capturing)
479             {
480                 listeners.splice(i, 1);
481                 return;
482             }
483         }
484 
485         var frames = [];
486         for (var frame = Components.stack; frame; frame = frame.caller)
487             frames.push(frame.filename + " (" + frame.lineNumber + ")");
488 
489         frames.shift();
490 
491         var info = {
492             eventId: eventId,
493             capturing: capturing,
494             listener: listener,
495             stack: frames,
496         };
497 
498         // xxxHonza: it's not necessary to pollute the tracing console with this message.
499         //FBTrace.sysout("Events.removeEventListener; ERROR not registered!", info);
500     }
501 }
502 
503 if (FBTrace.DBG_EVENTLISTENERS && typeof(Firebug) != "undefined")
504 {
505     Firebug.Events = {};
506     Firebug.Events.getRegisteredListeners = function()
507     {
508         return listeners;
509     };
510 }
511 
512 // ********************************************************************************************* //
513 
514 return Events;
515 
516 // ********************************************************************************************* //
517 });
518