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: 78761
  • Created:
  • Creator: David.humphrey
  • Is current revision? No
  • Comment /* Objective */

Revision Content

Objective

This page describes how to create Custom (C++) Events that can be used to pass data, meaning that you can add extra parameters and query them. 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

This is no easy task. 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 heard in your english class there's a lot in a name. As of Bon Echo Alpha 2, 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 is it.

The Trunk

You may have wondered why is it that you need to download (or check out) the source it's because you will have to modify the source. But wait does that mean my stuff won't work with anyone else's version of Firefox? Yes. Unless you get your patches into the trunk. Why isn't there a better way of doing this? That's a very good question.

mozilla/dom/public/nsIDOMClassInfo.h

The change you make here is really rather small but it is incredibly important. If you peruse nsIDOMClassInfo.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, well 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

As you probably know most Mozilla is based on macros (if you don't know you should probably go do Creating Custom Firefox Extensions with the Mozilla Build System and then come back). Lots of macros. Your modifications 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 is dependant on that enum you modified
  2. Keep your stuff organized. Same reason as above.

You will make thes two mods:

  • 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 for your event.

The change you want to make are in nsEventListenerManager::CreateEvent(). You will find that there is a bunch 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 it's 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 recomend using a function the way the everyone else does. You can find the prototypes for these function in nsIPrivateDOMEvent.

Your Event

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

  • Create an scriptiable interface called nsIDOM{YourEvenName}.
  • Implement an interface called nsIDOM{YourEventName}. This interface has to implement nsIDOMEvent. Make sure to make it scriptable.

Example TBP

  • Extend nsDOMEvent and implement your nsIDOM{YourEventName}. 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 function).

Example TBP

Dispatching Your Event in Java Script

Here is how to dispatch your event in java Script.

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

Dispatching your Event in C++

For those of you who have never doing this (this should be almost all of you).

 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

<h3 name="Objective"> Objective </h3>
<p>This page describes how to create Custom (C++) Events that can be used to pass data, meaning that you can add extra parameters and query them.  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>This is no easy task. 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. I'll go over some (very little) of it so do this <em>first</em> then come back and do this tutorial.
</li></ul>
<h3 name="What.27s_In_A_Name"> What's In A Name </h3>
<p>Despite whatever you may have heard in your english class there's a lot in a name. As of Bon Echo Alpha 2, 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 is it.
</p>
<h3 name="The_Trunk"> The Trunk </h3>
<p>You may have wondered why is it that you need to download (or check out) the source it's because you will have to modify the source. But wait does that mean my stuff won't work with anyone else's version of Firefox? Yes. Unless you get your patches into the trunk. Why isn't there a better way of doing this? That's a very good question.
</p>
<h4 name="mozilla.2Fdom.2Fpublic.2FnsIDOMClassInfo.h"> mozilla/dom/public/nsIDOMClassInfo.h </h4>
<p>The change you make here is really rather small but it is incredibly important. If you peruse nsIDOMClassInfo.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, eDOMClassInfoIDCount, 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, well 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).
</p>
<h4 name="mozilla.2Fdom.2Fsrc.2Fbase.2FnsDOMClassInfo.cpp"> mozilla/dom/src/base/nsDOMClassInfo.cpp </h4>
<p>As you probably know most Mozilla is based on macros (if you don't know you should probably go do <a href="en/Creating_Custom_Firefox_Extensions_with_the_Mozilla_Build_System">Creating Custom Firefox Extensions with the Mozilla Build System</a> and then come back). Lots of macros. Your modifications 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 is dependant on that enum you modified
</li><li>Keep your stuff organized. Same reason as above.
</li></ol>
<p>You will make thes two mods:
</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 {truncated name} 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 CreateEvent method which acts as a factory method for your event.
</p><p>The change you want to make are in nsEventListenerManager::CreateEvent(). You will find that there is a bunch 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 nsEventListenerManager::CreateEvent() like this:
</p>
<pre class="eval">if (aEventType.LowerCaseEqualsLiteral("nsmyevent")){ //note the lowercase it's 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 recomend using a function the way the everyone else does. You can find the prototypes for these 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 it must do the following:
</p>
<ul><li> Create an scriptiable interface called nsIDOM{YourEvenName}.
</li><li> Implement an interface called nsIDOM{YourEventName}. This interface has to implement nsIDOMEvent. Make sure to make it scriptable.
</li></ul>
<p><span class="comment">Example TBP</span>
</p>
<ul><li> Extend nsDOMEvent and implement your nsIDOM{YourEventName}. 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 function).
</li></ul>
<p><span class="comment">Example TBP</span>
</p>
<h3 name="Dispatching_Your_Event_in_Java_Script"> Dispatching Your Event in Java Script </h3>
<p>Here is how to dispatch your event in java Script. 
</p>
<pre class="eval"> var vent = document.createEvent("nsDOMMyEvent");
 vent.initEvent("nsDOMMyEvent", true, true);
 window.dispatchEvent(vent);
</pre>
<h3 name="Dispatching_your_Event_in_C.2B.2B"> Dispatching your Event in C++ </h3>
<p>For those of you who have never doing this (this should be almost all of you).
</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