How to write Firefox extensions in Flex!

Introduction.. 1

Boil some water - extension basics. 2

install.rdf – extension information for Firefox. 2

chrome.manifest – connects the extension declaration to our component 3

overlay.xul – our hook to the browser UI 3

overlay.js – our first entry point. 4

historia.xul - our extension UI 4

Test your infrastructure. 4

Add 1 cup of Flex. 5

AppUtil 5

Salt and Pepper - Passing Data between Firefox and Flex. 9

HistoryUtil 9

Comm... 11

Historia.html wrapper (Flex app) 12

Histotria.mxml 13

Bon appétit 14

Appendix: Navigation Tab – overcome Firefox security restrictions. 15

Changes needed for navigation tab. 15

Download.. 17

 

Introduction

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.

 

Boil some water - extension basics

<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).

install.rdf – extension information for Firefox

<?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.

 

chrome.manifest – connects the extension declaration to our component

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).

 

overlay.xul – our hook to the browser UI

<?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.

 

overlay.js – our first entry point.

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).

 

historia.xul - our extension UI

<?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).

 

Test your infrastructure

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:

Add 1 cup of Flex

AppUtil

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 !!!

 

Salt and Pepper - Passing Data between Firefox and Flex

HistoryUtil

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.

 

Comm

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;

  }

};

Historia.html wrapper (Flex app)

// 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);

. . .

Histotria.mxml

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>

 

Bon appétit

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 Mozilla Developer Center

(https://developer.mozilla.org/En).

 

 

Appendix: Navigation Tab – overcome Firefox security restrictions

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.

 

Changes needed for navigation tab

Edit the overlay.js

var Historia = {

  onLoad: function() {

    // initialization code

    this.initialized = true;

  },

 

  onMenuItemCommand: function() {

    //change here

     window.open("chrome://historia/content/historia.xul", "", "chrome");

     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.inqueue = data;

    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("pageshow", function(e) {AppUtil.swfLoaded(e);}, true);

     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

Download historia.zip

 

Back to my site