Open main menu

CDOT Wiki β

Changes

Real World Mozilla Adding Chrome to FirstXpcom Lab

7,423 bytes added, 11:58, 23 March 2007
no edit summary
Previously we created an XPCOM component called [[Dive into Mozilla First XPCOM Component|FirstXpcom]], tested it in the JavaScript Shell, and wrote xpcshell unit tests. However, we haven't really done anything with it yet. Ideally we'd like to add our component to the browser and create some UI in order to allow the user to access its functionality.
We'll work in stages to create a simple UI for accessing FirstXpcom, first as a separate chrome extension, before integrating it with the build system. Our goal will be to add a custom dialog box to the browser, accessible via the menu bar. This dialog box will allow the user to access the functionality in our XPCOM component via JavaScript that we'll write.
= Creating the FirstXPCOM Chrome Extension =
== Generating the extension automatically == We've now gone through the process of [http://developer.mozilla.org/en/docs/Building_an_Extension creating an extension] by hand twice (e.g., writing install.rdf, creating the proper directory structure, etc.), so we won't cover the process again. Instead, we'll use a handy [http://ted.mielczarek.org/code/mozilla/extensionwiz/ on-line wizard] to do the work for us. Use the following values/options: * '''Your Name:''' ''Your Name''* '''Extension Name:''' First XPCOM Chrome* '''Extension Short Name:''' firstxpcomchrome* '''Extension ID:''' firstxpcomchrome@senecac.on.ca* '''Version:''' 0.1* '''Target Applications''' Firefox Minimum Version=2.0.0.* Maximum Version=3.0a3pre After clicking '''Click Create Extension''', navigate to your desktop and locate the generated file, '''firstxpcomchrome.zip'''. Extract this file somewhere (e.g., '''c:\temp\firstxpcomchrome'''). Now create a file in your profile's extension folder (%Application Data%\Mozilla\Firefox\Profile\''<development_profile>''\extensions) named '''firstxpcomchrome@senecac.on.ca'''. This file should contain the full path to your unzipped extension (perhaps '''c:\temp\firstxpcomchrome'''):  $ cd Application\ Data/Mozilla/Firefox/Profiles/''development_profile''/extensions $ echo c:\temp\firstxpcomchrome > firstxpcomchrome@senecac.on.ca Restart Firefox and try out your new extension. By default you should have a new red menu item, '''Tools > Your localized menuitem''' (you should also see '''firstxpcomchrome''' listed in the '''Add-on manager''', along with '''firstxpcom'''). == Review of XUL Overlays == The [http://ted.mielczarek.org/code/mozilla/extensionwiz/ extension wizard] generated our extension's generic structure and files, but also created a browser [http://developer.mozilla.org/en/docs/XUL_Overlays overlay] and associated JavaScript file--these are what allow the custom menu item to be added. The files that interest us most are: * firstxpcomchrome/content/firefoxOverlay.xul* firstxpcomchrome/content/overlay.js* firstxpcomchrome/chrome.manifest Together the '''firefoxOverlay.xul ==''' and '''chrome.manifest''' files provide a way to add the custom menu item to the browser's Tools menu. In '''chrome.manifest''' we see:  overlay chrome://browser/content/browser.xul chrome://firstxpcomchrome/content/firefoxOverlay.xul This says to overlay the XUL found in '''firefoxOverlay.xul''' with [http://lxr.mozilla.org/seamonkey/source/browser/base/content/browser.xul browser.xul]--the XUL file defining the browser's UI. In '''firefoxOverlay.xul''' we find the following overlay (emphasis added):
<pre>
<overlay id="firstxpcomchrome-overlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
'''<script src="overlay.js"/>'''
<stringbundleset id="stringbundleset">
<stringbundle id="firstxpcomchrome-strings" src="chrome://firstxpcomchrome/locale/firstxpcomchrome.properties"/>
</stringbundleset>
'''<menupopup id="menu_ToolsPopup">
<menuitem id="firstxpcomchrome-hello" label="&firstxpcomchrome.label;"
oncommand="firstxpcomchrome.onMenuItemCommand(event);"/>
</menupopup>'''
</overlay>
</pre>
== Exploring the other generated files ==
The [http://ted.mielczarek.org/code/mozilla/extensionwiz/ extension wizard] also generated localization code, which you can see being used above, for example: <pre><!DOCTYPE overlay SYSTEM "chrome://firstxpcomchrome/locale/firstxpcomchrome.dtd">... <menuitem ... label="&firstxpcomchrome.label;" ... /> </pre> Here an ENTITY from the referenced DTD is used instead of a hard-coded string. Looking at the file '''firstxpcomchrome/locale/en-US/firstxpcomchrome.dtd''' we can see the mapping to English, which is what gets displayed at runtime: <pre><!ENTITY firstxpcomchrome.label "Your localized menuitem"></pre> You'll notice too that the menu item appears red. This is caused by a custom CSS property defined in '''firstxpcomchrome/skin/overlay.css'''. You can see it being referenced in '''firefoxOverlay.xul''' <pre><?xml-stylesheet href= "chrome://firstxpcomchrome/skin/overlay.js css" type="text/css"?></pre> The property itself, which modifies '''firstxpcomchrome-hello''' is defined as follows: <pre>/* This is just an example. You shouldn't do this. */#firstxpcomchrome-hello{ color: red ! important;}</pre> Using skins and localization are topics in their own right. We'll discuss them again in future labs. == Adding our own code == The '''firefoxOverlay.xul''' file specifies that when the menu item is the '''firstxpcomchrome.onMenuItemCommand''' method will be called: <pre> <menuitem id="firstxpcomchrome-hello" label="&firstxpcomchrome.label;" '''oncommand="firstxpcomchrome.onMenuItemCommand(event)''';"/></pre> This is perfect for our needs. It means that we need to add our code to '''firstxpcomchrome.onMenuItemCommand''' and it will be called a the appropriate time. Here is '''firstxpcomchrome/content/overlay.js''':
<pre>
// initialization code
this.initialized = true;
this.strings = document.getElementById("firstxpcomchrome-strings");
this.firstxpcom = Components.classes["@senecac.on.ca/firstxpcom;1"]
.createInstance(Components.interfaces.IFirstXpcom);
this.firstxpcom.name = "First XPCOM";
},
showDialog: function() { var params = {inn: {name:this.firstxpcom.name}, out: null}; window.openDialog("chrome://firstxpcomchrome/content/firstxpcomdialog.xul", "", "chrome, dialog, modal, resizable=yes", params).focus(); return params.out },
onMenuItemCommand: function(e) {
result = this.showDialog();
// Use the Alerts Service to display the results to the user.
var alertsService = Components.classes["@mozilla.org/alerts-service;1"]
.getService(Components.interfaces.nsIAlertsService);
alertsService.showAlertNotification(null, this.firstxpcom.name, this.total,
false, "", null);
},
showDialog: function() {
var params = {inn: {name:this.firstxpcom.name}, out: null};
window.openDialog("chrome://firstxpcomchrome/content/firstxpcomdialog.xul", "",
"chrome, dialog, modal, resizable=yes", params).focus();
return params.out
},
};
window.addEventListener("load", function(e) { firstxpcomchrome.onLoad(e); }, false);
</pre>
 
Let's begin with the final line, a load listener which insures that our code is run when the browser starts-up. Our object's '''onLoad''' function takes care of general initialization tasks, including creating an instance of '''FirstXpcom''' that we'll use throughout the life of our extension:
 
firstxpcom: null,
...
onLoad: function(e) {
// initialization code
this.initialized = true;
this.firstxpcom = Components.classes["@senecac.on.ca/firstxpcom;1"]
.createInstance(Components.interfaces.IFirstXpcom);
this.firstxpcom.name = "First XPCOM";
 
Just as we did in our unit tests and with the JS Shell, we create an instance of '''firstxpcom''' and then QI (i.e., ''"query interface"'') it to '''IFirstXpcom'''. Now we can call its methods, for example, setting the '''name''' attribute. We do the same later in '''onMenuCommand''':
 
this.total = this.'''firstxpcom.add'''(this.total, result.value);
this.'''firstxpcom.name''' = result.name;
 
Accessing our C++ XPCOM methods is as easy as calling any other JavaScript function.
 
The first part of '''onMenuCommand''' deals with displaying and using the dialog box we'll write, which we'll return to below. However, let's skip to the end of this function and discuss the following code:
 
// Use the Alerts Service to display the results to the user.
var alertsService = Components.classes["@mozilla.org/alerts-service;1"]
.getService(Components.interfaces.nsIAlertsService);
alertsService.showAlertNotification(null, this.firstxpcom.name, this.total,
false, "", null);
 
I chose to use the [http://developer.mozilla.org/en/docs/nsIAlertsService nsIAlertsService] , which creates an animated pop-up over the task list, rather than displaying the info to the user with an alert() for a number of reasons. First, I want to show that now you know how to create an XPCOM component in C++, and also how to use it in JavaScript, you can use any of the [http://developer.mozilla.org/en/docs/Interfaces hundreds of objects and interfaces] available in the Mozilla platform--the nsIAlertsService is no different from IFirstXpcom. I also wanted to draw your attention to another method of instantiating a component in JavaScript. Compare the following two code snippets:
 
this.firstxpcom = Components.classes["@senecac.on.ca/firstxpcom;1"]
.'''createInstance'''(Components.interfaces.IFirstXpcom);
var alertsService = Components.classes["@mozilla.org/alerts-service;1"]
.'''getService'''(Components.interfaces.nsIAlertsService);
 
In the former case we use '''createInstance''', which gives us a new unique instance. In the latter we use '''getService''', which returns a shared instance of an existing component (i.e., a Singleton). Unlike IFirstXpcom, which can be created many times by different callers, the nsIAlertsService is a shared component, because only one pop-up message at a time can be shown to the user.
 
</dialog>
</pre>
 
 
== chrome.manifest ==
 
content firstxpcomchrome content/
locale firstxpcomchrome en-US locale/en-US/
skin firstxpcomchrome classic/1.0 skin/
overlay chrome://browser/content/browser.xul chrome://firstxpcomchrome/content/firefoxOverlay.xul