How to write
Firefox extensions in Flex!
Boil
some water - extension basics
install.rdf
– extension information for Firefox.
chrome.manifest
– connects the extension declaration to our component
overlay.xul
– our hook to the browser UI
overlay.js
– our first entry point.
historia.xul
- our extension UI
Salt
and Pepper - Passing Data between Firefox and Flex
Historia.html
wrapper (Flex app)
Appendix:
Navigation Tab – overcome Firefox security restrictions
Changes
needed for navigation tab
I have been a big fan of Firefox since day one. I did some research and found how to load
a Flex application in a Firefox extension, and I would like to share my findings.
In this article I will show how to build a simple extension that allows showing the browsing
history in an AdvancedDataGrid component.
|
I am not too well acquainted with Firefox development. Writing this Tutorial was my first time writing an extension. If there are any overlooked issues here, and you know what you are talking about, please reply and I will update the article.
The article is Windows XP and Flex 3 oriented. I have not tested this extension on other Operating systems; however it should come down only to file system locations. If anyone can supply exact changes required for Linux, Mac… I will gladly update.
|
<ext path>\Historia\ install.rdf //Install Manifest chrome.manifest //hook to GUI locale\ //enable locale support (optional) en-us\ overlay.dtd historia.dtd chrome\ content\ // In here will also be the Flex app and JavaScript. overlay.js overlay,xul historia.xul ….
Please read here https://developer.mozilla.org/en/Building_an_Extension for a more detailed
explanation (Hello word tutorial - short).
|
<?xml version="1.0"?> <RDF
xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#"> <Description
about="urn:mozilla:install-manifest">
<em:id>historia@strawberrypixel.net</em:id> <em:name>FireFlex -
History</em:name>
<em:version>1.0</em:version> <em:description>History in Firefox and Flex</em:description> <em:creator>Strawberrypixel.com</em:creator> <!-- optional items --> <em:homepageURL>http://www.strawberrypixel.com/blog</em:homepageURL> <!-- em:optionsURL>chrome://sampleext/content/settings.xul</em:optionsURL> <em:aboutURL>chrome://sampleext/content/about.xul</em:aboutURL> <em:iconURL>chrome://sampleext/skin/mainicon.png</em:iconURL> <em:updateURL>http://sampleextension.mozdev.org/update.rdf</em:updateURL-->
<!-- Firefox --> <em:targetApplication> <Description> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <em:minVersion>3.0</em:minVersion> <em:maxVersion>3.0.*.*</em:maxVersion> </Description> </em:targetApplication>
</Description>
</RDF>
|
Notice the id tag – must be a unique identifier for your extension, emails are good identifiers.
|
content historia content/ overlay chrome://browser/content/browser.xul chrome://historia/content/overlay.xul
locale historia en-US locale/en-US/ |
We tell the browser here where the content of the
extension is and how we will hook to the
browser UI (detailed in the xul file).
|
<?xml version="1.0"?> <!DOCTYPE overlay SYSTEM "chrome://historia/locale/overlay.dtd"> <overlay id="historia-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <script src="overlay.js"/>
<menupopup id="menu_ToolsPopup"> <menuitem id="historia-hello" label="&historia;" oncommand="Historia.onMenuItemCommand(event);"/> </menupopup>
</overlay> |
Our component will be initiated from the Tools menu. You
can hook in many different places,
see the XUL reference https://developer.mozilla.org/En/XUL_Reference.
|
var Historia = { onLoad: function() { // initialization code this.initialized = true; },
onMenuItemCommand: function() { window.open("chrome://historia/content/historia.xul", "", "chrome");
} }; |
In our entry point we open a Firefox window and load the historia.xul which is the xml that
describes the UI of our component (we will load our flex app in here later).
|
<?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> <!DOCTYPE window SYSTEM "chrome://historia/locale/historia.dtd">
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="&title.label;" height="300" width="400"> <script src="overlay.js"/> <hbox align="center"> <description flex="1">&separate.label;</description> <button label="&close.label;" oncommand="close();"/> </hbox> <iframe id="ifr" name="ifr" src="" flex="1"/> </window> |
Locale files: Files include the string resources for display by language (see the zip file).
Open the Profile folder. For me it is C:\Documents and Settings\[my user]\Application Data\Mozilla\Firefox\Profiles\[default]\extensions\.
You should see all the other extensions you have installed there.
Create a text file with the same id as we put in the rdf file (historia@strawberrypixel.net).
In it put the path to your extensions folder: for me it is c:\dev-flex3\Historia.
Close all Firefox instances and open a new Firefox instance. You will first see a popup:

Click Tools->historia and you will see our window:

Lets go back to historia.xul (our add on window) and add the onload event handler:
|
<!DOCTYPE window SYSTEM "chrome://historia/locale/historia.dtd">
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="&title.label;" onload="AppUtil.init(window)" height="300" width="400"> <script src="overlay.js"/> |
I add a secondary entry point through which I will load the swf.
In the overlay.s file:
|
var AppUtil = {
init: function(winRef) { try {
//save window ref this.windowReference = winRef;
//get reference to iframe and load the Flex app html var doc = winRef.document; var ifrRef = this.getIframeRef();
//init Flex-FireFox comm
class Comm.init(winRef,ifrRef);
// returns file path for
the extension var filePath = getExtDirPath(EXT_ID);
//build url to the Flex app to be loaded var appurl = "file://"+filePath+"/content/historia.html";
//listen for Iframe load event ifrRef.addEventListener("pageshow", function(e) {AppUtil.swfLoaded(e);}, true);
//load page ifrRef.webNavigation.loadURI(appurl, Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
} catch(e) { alert(e.message) }; },
swfLoaded: function(e) { try {
//swf
is loaded and ready for data input var ifrRef = this.getIframeRef();
//load the history var xml = HistoryUtil.loadHistory(Comm.getWindowReference()); Comm.pushData(xml); } catch(e) { alert(e.message) }; },
getIframeRef: function() { var doc = this.windowReference.document; return doc.getElementById("ifr"); } }; |
Run it and you get a security sandbox violation:

Why?
Loading a local file, Flash player has strict security rules.
How do I get past this?
Add a trust file!
A trust file is a text file with a file path in it, telling the flash player that the content in that directory is trusted (http://livedocs.adobe.com/flex/201/html/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Book_Parts&file=security2_117_46.html, google: flashplayertrust)
To the AppUtil init function add:
|
var AppUtil = {
init: function(winRef) { try {
//save window ref this.windowReference = winRef;
//Make sure trust file exists FileUtils.handleTrustFile();
//get reference to iframe and load the Flex app html var doc = winRef.document; var ifrRef = this.getIframeRef(); |
As part of the instantiation process I added code that creates the trust file. This way the add on
can load the swf and avoid security errors.
|
var FileUtils = {
handleTrustFile: function() {
//check if exists var file = Components.classes["@mozilla.org/file/directory_service;1"]. getService(Components.interfaces.nsIProperties). get("WinD", Components.interfaces.nsIFile);
file.append("system32"); file.append("Macromed"); file.append("Flash"); file.append("FlashPlayerTrust"); file.append("historia.cfg"); if(!file.exists()) {
var extdir = getExtDirPath(EXT_ID);
// file is nsIFile, data is a string var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]. createInstance(Components.interfaces.nsIFileOutputStream);
// use 0x02 | 0x10 to open file for appending. foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0); foStream.write(extdir, extdir.length); foStream.close();
} } }; |
Note: This code has only been tested on Windows XP, It
should according to documentation
work on other Windows versions but for other OS types,
changes are needed in path.If anyone
has this information please send it to me and I will update the Tutorial.
Now restart FireFox and click Tools->historia and TA-DAH !!!

The principal is easy – use XML data.
Look above at the AppUtil.swfLoaded() event handler, there I create the xml wit the HistoryUtil.loadHistory
function and pass it to Flex using the Comm class (explained later).
|
var HistoryUtil = {
loadHistory: function(windowReference, forUri) { try { var historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"] .getService(Components.interfaces.nsINavHistoryService);
// no query parameters will get all history var options = historyService.getNewQueryOptions();
// no query parameters will return everything var query = historyService.getNewQuery(); if(forUri) { query.uri = forUri; query.hasUri = true; }
// execute the query var result = historyService.executeQuery(query, options); var resultNode = result.root;
//open access resultNode.containerOpen = true; try { var i; var document = windowReference.document;
/*create xml document of format:
<history>
<historyEntry uri="http://www.google.com/
"
title="ChromeWindow document access - Google
Search"
accessCount="1"
time="1233607085609375" icon=""/>
<historyEntry uri="http://www.mozilla.com/en-US/products/download.html”
title="Mozilla Download"
accessCount="1"
time="1234253870640625" icon=""/>
</history>;
*/ var doc = document.implementation.createDocument("", "", null);
var historyRootElem = doc.createElement("history"); doc.appendChild(historyRootElem);
for(i=0;i<resultNode.childCount;i++) { var histEntry= resultNode.getChild(i); this.addHistoryNode(histEntry,historyRootElem,doc); }
//serialize the DOM tree to xml string using the xmlserializer var serializer = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"].createInstance(Components.interfaces.nsIDOMSerializer); var xml = serializer.serializeToString(doc); return xml; } finally { resultNode.containerOpen = false; }
} catch(e) { alert(e.message) }; },
/*adds
a single history Entry node to tree like so:
<historyEntry uri="http://www.mozilla.com/en-US/products/download.html
"
title="Mozilla Download"
accessCount="1"
time="1234253870640625" icon=""/>*/ addHistoryNode: function(nsINavHistoryResultNode, parent,doc) { try { var histElem = doc.createElement("historyEntry"); histElem.setAttribute("uri", nsINavHistoryResultNode.uri); histElem.setAttribute("title", nsINavHistoryResultNode.title); histElem.setAttribute("accessCount", nsINavHistoryResultNode.accessCount); histElem.setAttribute("time", nsINavHistoryResultNode.time);
histElem.setAttribute("icon", nsINavHistoryResultNode.icon); parent.appendChild(histElem); } catch(e) { alert(e.message) };
} };
|
Notice the use of xmlserializer to turn the DOM tree into an xml string ready for passing to flex.
It is a very simple class used to pass data to and From the Flex app through the Flex app Wrapper.
I use and incoming queue and outgoing queue for passing data. This is important for passing data
when the extension is not loaded in a separate window, but loaded in a Tab for instance.
Then you will encounter FireFox security restrictions. See the appendix for how to handle these restrictions.
|
var Comm = { init: function(windowReference,iframeRef) { this.windowReference = windowReference; this.iframeRef = iframeRef; }, pushData: function(data) { this.iframeRef.contentWindow.inqueue = data; },
getWindowReference: function() { return this.windowReference; }, getIframeRef: function() { return this.iframeRef; } }; |
|
// Major version of Flash required var requiredMajorVersion = 9; // Minor version of Flash required var requiredMinorVersion = 0; // Minor version of Flash required var requiredRevision = 28; // ----------------------------------------------------------------------------- // --> </script> </head>
<body scroll="no"> <script language="JavaScript" type="text/javascript"> var appReady = false; var _outqueue = new Array(); var _inqueue = new Array();
__defineGetter__("outqueue", function() { if(_outqueue.length > 0) return _outqueue.pop(); else return null; }); __defineSetter__("outqueue", function(y) { });
__defineGetter__("inqueue", function() { }); __defineSetter__("inqueue", function(y) { _inqueue.push(y); loadHistory();});
/**Call back
from swf, when creationComplete
is called**/ function setAppReady() { try { appReady = true; loadHistory();
} catch(e) { alert(e.message); } }
/**Check if
there is data in the inqueue and if so load it to swf**/ function loadHistory() { if(appReady) { /**flash player elemnt
id is Historia**/
var flashRef = document.getElementById('Historia'); var xml = null; if(_inqueue.length > 0) xml = _inqueue.pop(); if(xml) flashRef.loadHistory(xml); } }
<!-- // Version check for the Flash Player that has the ability to start Player Product Install (6.0r65) var hasProductInstall = DetectFlashVer(6, 0, 65); . . . |
The Flex code is very straight forward. In the creationComplete we notify the wrapper the
Application is ready with the ExternalInterface and “setAppReady” event name.
|
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" backgroundGradientAlphas="[0.67, 1.0]" backgroundGradientColors="[#FFFFFF, #FFFFFF]" xmlns:ns1="*" creationComplete="init(event)" horizontalScrollPolicy="off" verticalScrollPolicy="off"> <mx:Script> <![CDATA[ import mx.controls.Alert; /**called from the wrapper to load the history entries xml**/ public function loadHistory(xmlstr:String):void { var xml:XML = new XML(xmlstr); historyView.loadHistory(xml); }
/**add listener for loadHistory call from wrapper and fire app ready event**/ private function init(event:Event):void {
ExternalInterface.addCallback("loadHistory", loadHistory); callWrapper(); }
/**fire app ready event to wrapper**/ public function callWrapper():void {
if (ExternalInterface.available) { var wrapperFunction:String = "setAppReady"; var s:String = ExternalInterface.call(wrapperFunction,null); } }
]]> </mx:Script> <mx:ViewStack id="viewstack1" > <mx:Canvas label="View 1" > <ns1:HistoryView id="historyView" width="621" height="550" horizontalScrollPolicy="off"> </ns1:HistoryView> </mx:Canvas> </mx:ViewStack> <mx:Style source="main.css"/> </mx:Application> |
There are more issues to attend to before you can publish your extension, such as packaging the
files in a jar and more. All this information can be found
on the
(https://developer.mozilla.org/En).
The problem:
If you try to load the same Flex app in an iframe that is located inside a Firefox navigation tab,
You will receive security errors from Firefox.
Why?
Firefox wraps elements with the XPCNativeWrapper. When you for instance access
ifrRef.contentWindow in an Iframe loaded inside a navigation Tab you get, instead of a XUL
Window Object, a XPCNativeWrapper which wraps a Window object. Every property/method
access is checked by the wrapper to see if there are sufficient privileges to allow this action.
I found while playing around with this that the content inside and Iframe XUL component, loaded
in a navigation Tab, to be un-trusted. I found one way around this:
Using the wrappedJSObject, which is a property of the XPCNativeWrapper.
It allows overriding the security checks and accessing the wrapped content inside.
|
Note, I am not too well acquainted with Firefox security. Writing this Tutorial was my first time writing an extension. If there are security risks here, or if there are better ways to allow access, please reply and I will update the article. Seems to me, since the component does not access the web it should be safe. |
Edit the overlay.js
|
var Historia = { onLoad: function() { // initialization code this.initialized = true; },
onMenuItemCommand: function() {
//change here
openAndReuseOneTabPerURL("chrome://historia/content/historia.xul");
} };
//nice little function I found on the MDC. function openAndReuseOneTabPerURL(url) { var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator); var browserEnumerator = wm.getEnumerator("navigator:browser");
// Check each browser instance for our URL var found = false; while (!found && browserEnumerator.hasMoreElements()) { var browserWin = browserEnumerator.getNext(); var tabbrowser = browserWin.getBrowser();
// Check each tab of this browser instance var numTabs = tabbrowser.browsers.length; for(var index=0; index<numTabs; index++) { var currentBrowser = tabbrowser.getBrowserAtIndex(index); if (url == currentBrowser.currentURI.spec) {
// The URL is already opened. Select this tab. tabbrowser.selectedTab = tabbrowser.mTabs[index];
// Focus *this* browser-window browserWin.focus();
found = true; break; } } } |
And…
|
var Comm = { init: function(windowReference,iframeRef) { this.windowReference = windowReference; this.iframeRef = iframeRef; }, pushData: function(data) {
//CHANGE HERE
this.iframeRef.contentWindow.wrappedJSObject.inqueue = data; },
getWindowReference: function() { return this.windowReference; }, getIframeRef: function() { return this.iframeRef; } }; |
And…
|
var AppUtil = {
init: function(winRef) { try {
//save window ref this.windowReference = winRef;
//get reference to iframe and load the Flex app html var doc = winRef.document; var ifrRef = this.getIframeRef();
//init Flex-FireFox comm class Comm.init(winRef,ifrRef);
// returns file path for the extension var filePath = getExtDirPath(EXT_ID); //build url to the Flex app to be loaded var appurl = "file://"+filePath+"/content/historia.html"; //listen for Iframe load event
//CHANGE HERE
ifrRef.addEventListener("load", function(e) {AppUtil.swfLoaded(e);}, true);
//load page ifrRef.webNavigation.loadURI(appurl, Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
} catch(e) { alert(e.message) |
That’s it.
Download historia.zip