Creating Custom Events That Can Pass Data

  • Revision slug: Creating_Custom_Events_That_Can_Pass_Data
  • Revision title: Creating Custom Events That Can Pass Data
  • Revision id: 78771
  • Created:
  • Creator: sdwilsh
  • Is current revision? No
  • Comment /* mozilla/dom/public/nsIDOMClassInfo.h */ File is now nsIDOMClassInfoID.h

Revision Content

This page describes how to implement custom DOM events that can be used to pass data (meaning that you can add extra parameters and query them) in C++. For example, you want Firefox to perform an action whenever something (i.e., something other than the standard mouse/keyboard type events) happens and, depending on the data passed along with this event, you want Firefox to react with a differently.

Requirements

In order to do this you must be able to do all of the following:

What's in a name

Despite whatever you may have been taught in your English class, there's a lot in a name when it's an event name. As of Gecko 1.8, if your event names do not start with "nsDOM" and their interfaces do not start with "nsIDOM" then you can forget about passing data. You will still be able to throw events, but that's it.

The trunk

You needed to download (or check out) the trunk source because you will have to modify it in order to implement your event. Be aware that this means your stuff won't work with everyone else's version of Firefox unless you get your patches into the trunk. At the time of writing the author is not aware of a way to do this that doesn't involve modifications to trunk.

What follows a list of the files you'll need to modify, as well as a discussion of each change.

mozilla/dom/public/nsIDOMClassInfoID.h

The change you make here is really rather small but it is incredibly important. If you peruse nsIDOMClassInfoID.h you'll find an enum named nsDOMClassInfoID. You'll want to add an entry to it. Your entry should be second last. The last one, eDOMClassInfoIDCount, is a counter. Your entry should look like this:

eDOMClassInfo_{truncated name}_id, 

Here's the fun part: Above I mentioned that you must name your events as nsDOM. However, here you want to put the other part of the name (e.g. if you have an event named nsDOMMyFirstEvent your nsDOMClassInfoID entry would be eDOMClassInfo_MyFirstEvent_id).

mozilla/dom/src/base/nsDOMClassInfo.cpp

Mozilla contains many convenience macros to make changes like the one you're making easier. Your modification of this file is basically just two macro calls. Try to make sure of two things:

  1. Keep your stuff at the back. You know never know what depends on that enum you modified
  2. Keep your stuff organized. Same reason as above.

You need to make the following two modifications:

  • around line 1000:
NS_DEFINE_CLASSINFO_DATA({truncated name}, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) 
  • around line 2900:
DOM_CLASSINFO_MAP_BEGIN({truncated name}, nsIDOM{truncatedName})
  DOM_CLASSINFO_MAP_ENTRY(nsIDOM{truncated name})
  DOM_CLASSINFO_EVENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END

Remember, {truncated name} is the same as above.

mozilla/content/events/src/nsEventListenerManager.cpp

This is quite an important file since this holds the CreateEvent method which acts as a factory method DOM events. The change you want to make is in nsEventListenerManager::CreateEvent(). You will find that there is a bunch of code like:

if (aEventType.LowerCaseEqualsLiteral("{somethingsomething}event"))
  return NS_{somethingSomething}Event(aDOMEvent, aPresContext, nsnull);

You can either have a function like this or write the code straight in nsEventListenerManager::CreateEvent() like this:

if (aEventType.LowerCaseEqualsLiteral("nsmyevent")){ //note: the lowercase is important!
  nsDOMEvent* it = new nsDOMMyEvent(aPresContext, aEvent);
  if (nsnull == it) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return CallQueryInterface(it, aDOMEvent);  
}

In general though I'd strongly recommend using a function the way that everyone else does. You can find the prototypes for the function in nsIPrivateDOMEvent.

Your event

In order for your event to work you must do the following:

  • Create a scriptable interface called nsIDOM{YourEventName} inheriting from nsIDOMEvent.

Example TBP

  • Implement the interface you created with a class that inherits from nsDOMEvent. Use nsDOMEvent.h's NS_FORWARD_TO_NSDOMEVENT macro so that you don't have to forward manually (unless you plan on overriding one of nsDOMEvent's original functions).

Example TBP

Note for extension developers

In order for your event to work the way it is described here it must be derived from nsDOMEvent. The problem that extension developers will hit is that you are not allowed derive from nsDOMEvent in an extension. You can try to rewrite code but it will have to be a lot of code because nsDOMEvent uses a lot of code outside of itself which you, again, cannot access from an extension. This remains true even if you mark you extension code as "internal".

Dispatching your event in JavaScript

Here is how to dispatch your event in JavaScript.

var event = document.createEvent("nsDOMMyEvent");
event.initEvent("nsDOMMyEvent", true, true);
window.dispatchEvent(event);

Dispatching your event in C++

The following shows how to dispatch your event in C++:

 nsCOMPtr<nsIWindowWatcher> wwatcher
               (do_GetService("@mozilla.org/embedcomp/window-watcher;1"));
 // The window watcher will be able to give me a handle to the window
 
  nsCOMPtr<nsIDOMWindow> aWindow;
  // a handle to the window
  
  nsCOMPtr<nsIDOMDocument> aDoc;
  // a handle to the document
  
  nsCOMPtr<nsIDOMEventTarget> tWindow;
  // the target window (really the same window as above but you need
  // a different inteface :/
  
  nsCOMPtr<nsIDOMEvent> event;
  //this will be the event we throw. 
  
  nsCOMPtr<nsIDOMMyEvent> myEvent;
  //this is a handle to the your inteface to the event.
  
  wwatcher->GetActiveWindow(getter_AddRefs(aWindow));
  //get the Active window.
  
  aWindow->GetDocument(getter_AddRefs(aDoc));
  //get the document from the window.
  
  nsCOMPtr<nsIDOMDocumentEvent> docEvent = do_QueryInterface(aDoc);
  //change interfaces so you can create interfaces.
  
  docEvent->CreateEvent(NS_LITERAL_STRING("nsDOMMyEvent"), getter_AddRefs(
  //create the event

  event->InitEvent(NS_LITERAL_STRING("nsDOMMyEvent"), PR_TRUE, PR_TRUE);
  //initialize it

  tWindow = do_QueryInterface(aWindow);
  //use the window for a target.

  myEvent = do_QueryInterface(event);

  myEvent->SetMyProperty(45);
  //set my property of my event to (e.g.) 45.

  PRBool defaultActionEnabledWin;
  PRBool defaultActionEnabledDoc;

  nsCOMPtr<nsIPrivateDOMEvent> privEvt(do_QueryInterface(event));
  privEvt->SetTrusted(PR_TRUE);
  //make the event trusted

  tWindow->DispatchEvent(event, &defaultActionEnabledWin);
  //dispatch it (i.e. send it out into the wild)

Revision Source

<p>This page describes how to implement custom DOM events that can be used to pass data (meaning that you can add extra parameters and query them) in C++. For example, you want Firefox to perform an action whenever <em>something</em> (i.e., something other than the standard mouse/keyboard type events) happens and, depending on the data passed along with this event, you want Firefox to react with a differently.
</p>
<h3 name="Requirements"> Requirements </h3>
<p>In order to do this you must be able to do all of the following:
</p>
<ul><li> <a href="en/Download_Mozilla_Source_Code">Download Mozilla Source Code</a>
</li><li> <a href="en/Build_Documentation">Build Mozilla</a>
</li><li> <a href="en/Creating_Custom_Firefox_Extensions_with_the_Mozilla_Build_System">Creating Custom Firefox Extensions with the Mozilla Build System</a>.  This is the foundation for everything we'll do.
</li></ul>
<h3 name="What.27s_in_a_name"> What's in a name </h3>
<p>Despite whatever you may have been taught in your English class, there's a lot in a name when it's an event name. As of Gecko 1.8, if your event names do not start with "nsDOM" and their interfaces do not start with "nsIDOM" then you can forget about passing data. You will still be able to throw events, but that's it.
</p>
<h3 name="The_trunk"> The trunk </h3>
<p>You needed to download (or check out) the trunk source because you will have to modify it in order to implement your event.  Be aware that this means your stuff won't work with everyone else's version of Firefox unless you get your patches into the trunk.  At the time of writing the author is not aware of a way to do this that doesn't involve modifications to trunk.  
</p><p>What follows a list of the files you'll need to modify, as well as a discussion of each change.
</p>
<h4 name="mozilla.2Fdom.2Fpublic.2FnsIDOMClassInfoID.h"> mozilla/dom/public/nsIDOMClassInfoID.h </h4>
<p>The change you make here is really rather small but it is incredibly important. If you peruse nsIDOMClassInfoID.h you'll find an enum named <a class="external" href="http://landfill.mozilla.org/mxr-test/mozilla1.8.x/ident?i=nsDOMClassInfoID">nsDOMClassInfoID</a>. You'll want to add an entry to it. Your entry should be second last. The last one, <code>eDOMClassInfoIDCount</code>, is a counter. Your entry should look like this:
</p>
<pre class="eval">eDOMClassInfo_{truncated name}_id, 
</pre>
<p>Here's the fun part: Above I mentioned that you must name your events as nsDOM.  However, here you want to put the other part of the name (e.g. if you have an event named <code>nsDOMMyFirstEvent</code> your <code>nsDOMClassInfoID</code> entry would be <code>eDOMClassInfo_MyFirstEvent_id</code>).
</p>
<h4 name="mozilla.2Fdom.2Fsrc.2Fbase.2FnsDOMClassInfo.cpp"> mozilla/dom/src/base/nsDOMClassInfo.cpp </h4>
<p>Mozilla contains many convenience macros to make changes like the one you're making easier.  Your modification of this file is basically just two macro calls. Try to make sure of two things:
</p>
<ol><li>Keep your stuff at the back. You know never know what depends on that enum you modified
</li><li>Keep your stuff organized. Same reason as above.
</li></ol>
<p>You need to make the following two modifications:
</p>
<ul><li> around line 1000: 
</li></ul>
<pre class="eval">NS_DEFINE_CLASSINFO_DATA({truncated name}, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) 
</pre>
<ul><li> around line 2900: 
</li></ul>
<pre class="eval">DOM_CLASSINFO_MAP_BEGIN({truncated name}, nsIDOM{truncatedName})
  DOM_CLASSINFO_MAP_ENTRY(nsIDOM{truncated name})
  DOM_CLASSINFO_EVENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
</pre>
<p>Remember, <code>{truncated name}</code> is the same as above.
</p>
<h4 name="mozilla.2Fcontent.2Fevents.2Fsrc.2FnsEventListenerManager.cpp"> mozilla/content/events/src/nsEventListenerManager.cpp </h4>
<p>This is quite an important file since this holds the <code>CreateEvent</code> method which acts as a factory method DOM events. The change you want to make is in <code>nsEventListenerManager::CreateEvent()</code>. You will find that there is a bunch of code like:
</p>
<pre class="eval">if (aEventType.LowerCaseEqualsLiteral("{somethingsomething}event"))
  return NS_{somethingSomething}Event(aDOMEvent, aPresContext, nsnull);
</pre>
<p>You can either have a function like this or write the code straight in <code>nsEventListenerManager::CreateEvent()</code> like this:
</p>
<pre class="eval">if (aEventType.LowerCaseEqualsLiteral("nsmyevent")){ //note: the lowercase is important!
  nsDOMEvent* it = new nsDOMMyEvent(aPresContext, aEvent);
  if (nsnull == it) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return CallQueryInterface(it, aDOMEvent);  
}
</pre>
<p>In general though I'd strongly recommend using a function the way that everyone else does. You can find the prototypes for the function in <a class="external" href="http://landfill.mozilla.org/mxr-test/mozilla1.8.x/source/content/events/public/nsIPrivateDOMEvent.h#71">nsIPrivateDOMEvent</a>.
</p>
<h3 name="Your_event"> Your event </h3>
<p>In order for your event to work you must do the following:
</p>
<ul><li> Create a scriptable interface called <code>nsIDOM{YourEventName}</code> inheriting from <code>nsIDOMEvent</code>.
</li></ul>
<p><span class="comment">Example TBP</span>
</p>
<ul><li> Implement the interface you created with a class that inherits from <code>nsDOMEvent</code>. Use <a class="external" href="http://landfill.mozilla.org/mxr-test/mozilla1.8.x/ident?i=NS_FORWARD_TO_NSDOMEVENT">nsDOMEvent.h's NS_FORWARD_TO_NSDOMEVENT</a> macro so that you don't have to forward manually (unless you plan on overriding one of nsDOMEvent's original functions).
</li></ul>
<p><span class="comment">Example TBP</span>
</p>
<h4 name="Note_for_extension_developers"> Note for extension developers </h4>
<p>In order for your event to work the way it is described here it <strong>must</strong> be derived from nsDOMEvent. The problem that extension developers will hit is that you are not allowed derive from nsDOMEvent in an extension. You can try to rewrite code but it will have to be a lot of code because nsDOMEvent uses a lot of code outside of itself which you, again, cannot access from an extension. This remains true even if you mark you extension code as "internal".
</p>
<h3 name="Dispatching_your_event_in_JavaScript"> Dispatching your event in JavaScript </h3>
<p>Here is how to dispatch your event in JavaScript. 
</p>
<pre class="eval">var event = document.createEvent("nsDOMMyEvent");
event.initEvent("nsDOMMyEvent", true, true);
window.dispatchEvent(event);
</pre>
<h3 name="Dispatching_your_event_in_C.2B.2B"> Dispatching your event in C++ </h3>
<p>The following shows how to dispatch your event in C++:
</p>
<pre class="eval"> nsCOMPtr&lt;nsIWindowWatcher&gt; wwatcher
               (do_GetService("@mozilla.org/embedcomp/window-watcher;1"));
 // The window watcher will be able to give me a handle to the window
 
  nsCOMPtr&lt;nsIDOMWindow&gt; aWindow;
  // a handle to the window
  
  nsCOMPtr&lt;nsIDOMDocument&gt; aDoc;
  // a handle to the document
  
  nsCOMPtr&lt;nsIDOMEventTarget&gt; tWindow;
  // the target window (really the same window as above but you need
  // a different inteface :/
  
  nsCOMPtr&lt;nsIDOMEvent&gt; event;
  //this will be the event we throw. 
  
  nsCOMPtr&lt;nsIDOMMyEvent&gt; myEvent;
  //this is a handle to the your inteface to the event.
  
  wwatcher-&gt;GetActiveWindow(getter_AddRefs(aWindow));
  //get the Active window.
  
  aWindow-&gt;GetDocument(getter_AddRefs(aDoc));
  //get the document from the window.
  
  nsCOMPtr&lt;nsIDOMDocumentEvent&gt; docEvent = do_QueryInterface(aDoc);
  //change interfaces so you can create interfaces.
  
  docEvent-&gt;CreateEvent(NS_LITERAL_STRING("nsDOMMyEvent"), getter_AddRefs(
  //create the event

  event-&gt;InitEvent(NS_LITERAL_STRING("nsDOMMyEvent"), PR_TRUE, PR_TRUE);
  //initialize it

  tWindow = do_QueryInterface(aWindow);
  //use the window for a target.

  myEvent = do_QueryInterface(event);

  myEvent-&gt;SetMyProperty(45);
  //set my property of my event to (e.g.) 45.

  PRBool defaultActionEnabledWin;
  PRBool defaultActionEnabledDoc;

  nsCOMPtr&lt;nsIPrivateDOMEvent&gt; privEvt(do_QueryInterface(event));
  privEvt-&gt;SetTrusted(PR_TRUE);
  //make the event trusted

  tWindow-&gt;DispatchEvent(event, &amp;defaultActionEnabledWin);
  //dispatch it (i.e. send it out into the wild)
</pre>
Revert to this revision