Mozilla + Firefox extension development

Wednesday, January 26, 2005

Showing Javascript errors in chrome code

You can get Firefox to show Javascript errors in chrome code by turning on the javascript.options.showInConsole preference. (Which can be done simply by visiting about:config in the browser.)

Another useful preference to turn on is javascript.options.strict, which sends lots of warnings to the console.

(Thanks to Rob Miller for these tips!)

Thursday, November 25, 2004

Passing objects between a Javascript-based XPCOM component and its client

XPCOM IDL allows you to pass parameters of certain types only (e.g., string, integer), in addition to objects with XPCOM interfaces (e.g., nsISupports). In order to pass anything else (e.g., a Javascript array, a Java object) from the client of a Javascript-based XPCOM component into the component, you need to wrap the object to be passed in another XPCOM object implemented using Javascript:

function WrapperClass(object) {
this.wrappedJSObject = this;
this.object = object;
}

WrapperClass.prototype = {
QueryInterface : function(iid) {
if (!iid.equals(Components.interfaces.nsISupports)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return this;
}
}

Now you can call the first XPCOM component passing in any kind of object by wrapping it first:
theComponent.foo(new WrapperClass(theObject))
And inside foo(), the wrapped object can be retrieved as follows:
foo : function(arg) {
var theActualObject = arg.wrappedJSObject.object;
}
In the interface definition of theComponent (in your IDL file), you need to declare that foo() takes an nsISupports parameter.

Wednesday, November 17, 2004

Build your Firefox extension with Ant

You need to download 4 things:
Structure your Firefox extension's source directory as follows:
  • your-extension-dir/
    • build.xml
    • do.bat
    • src/
      • install.rdf
      • chrome/
        • content/
          • contents.rdf
          • *.xul, *.js,
        • locale/
          • en-US/
          • ...
        • skin/
          • classic/
          • ...
      • components/
        • *.idl
        • *.js
      • defaults/
    • build/ (containing intermediate generated files)
    • dist/ (containing the final distribution XPI file)
The build.xml file should look like this:

<?xml version="1.0" ?>
<project name="your-project-name" default="createDistributionXPI">
<!-- Tool directories : *** Make sure you fix these to match your own environment *** -->
<property name="gecko_sdk_path" location="C:\Tools\gecko-sdk" />
<property name="buildtools_path" location="C:\Tools\buildtools" />

<!-- Derived tool directories -->
<property name="xpidl_exe" location="${gecko_sdk_path}/bin/xpidl.exe" />
<property name="IDLs_path" location="${gecko_sdk_path}/idl" />
<property name="libIDL_path" location="${buildtools_path}/windows/bin/x86" />

<!-- Project directories -->
<property name="src_dir" location="./src" />
<property name="build_dir" location="./build" />
<property name="dist_dir" location="./dist" />
<property name="components_dir" location="${src_dir}/components" />

<!-- Custom tasks -->
<taskdef resource="net/sf/antcontrib/antcontrib.properties"/>

<!-- Targets -->
<target name="createChromeJAR">
<zip destfile="${build_dir}/${ant.project.name}.jar" update="true"
basedir="${src_dir}/chrome"
includes="content/**, locale/**, skin/**"
/>
</target>

<target name="createComponentInterfaceXPTs">
<foreach target="compileIDL" param="idl_file">
<path>
<fileset dir="${src_dir}/components" includes="*.idl" />
</path>
</foreach>
</target>

<target name="compileIDL">
<exec executable="${xpidl_exe}" dir="${build_dir}">
<env key="Path" path="${env.Path};${libIDL_path}" />
<arg line="-m typelib -w -v -I ${IDLs_path} -I ${components_dir} ${idl_file}" />
</exec>
</target>

<target name="createDistributionXPI" depends="createChromeJAR, createComponentInterfaceXPTs">
<zip destfile="./dist/${ant.project.name}.xpi" update="true">
<zipfileset dir="${build_dir}" includes="${ant.project.name}.jar" prefix="chrome" />
<zipfileset dir="${src_dir}/components" includes="*.js" prefix="components" />
<zipfileset dir="${build_dir}" includes="*.xpt" prefix="components" />
<zipfileset dir="${src_dir}/defaults" includes="**" prefix="defaults" />
<zipfileset dir="${src_dir}" includes="install.rdf" />
</zip>
<copy file="${dist_dir}/${ant.project.name}.xpi" tofile="${dist_dir}/${ant.project.name}.xpi.zip" />
</target>
</project>


You need to change the 3 strings in blue toward the beginning of the file to match your project's name and your tool installations. Note that the dist/ directory will contain both an XPI file and a ZIP file. The ZIP file is just a copy of the XPI file with a .ZIP extension so that you can use WinZip to open it up and inspect its content.

Finally, you should create a batch file in your project's directory that looks something like this:

call c:\Tools\apache-ant-1.6.2\bin\ant
"C:\Program Files\Mozilla Firefox\firefox.exe"

One more note: it's probably convenient to put a link to your project's XPI file on Firefox's toolbar because you will need to reinstall the extension many times:
  • Navigate Firefox to the dist/ directory containing the XPI (not to the XPI itself)
  • Drag the link to the XPI file to the toolbar
Oh, if you delete a file in your src directory, it might still be stuck on one of the generated zip files. Make sure to delete all files in the build and dist directory for a clean build.

http://ant-contrib.sourceforge.net/

Sunday, November 14, 2004

Find xpidl.exe in Gecko SDK for compiling IDL files

If you are writing a new XPCOM component and need to use xpidl.exe to compile your IDL files, you can find xpidl.exe in the Gecko SDK, the latest version of which is here:

ftp://ftp.mozilla.org/pub/mozilla.org/mozilla/releases/mozilla1.7.3/

Yes, you can alternatively download the whole freaking mozilla codebase just to build xpidl.exe.

But hold on, it's not done yet. xpidl.exe probably complains that it can't find libIDL-0.6.dll in the path. You need to download the Netscape build tools,

http://ftp.mozilla.org/pub/mozilla.org/mozilla/source/wintools.zip

unzip them into some temporary folder, say C:\buildtools, and include C:\buildtools\windows\bin\x86 in your path.

And then xpidl.exe will whine some more, e.g., it can't read nsISupports.idl and nsrootidl.idl. Just copy those files from gecko-sdk\idl over to your current directory and try to compile again.

By the way, this is how I was told to run xpidl.exe:

xpidl.exe -m typelib -w -v -I $XPIDL_INC -o nsIMyInterface nsIMyInterface.idl

Setting XPIDL_INC to include gecko-sdk\idl doesn't seem to help.

Isn't Open Source wonderful? All the information you need is out there, all for free. You just need to spend 20 hours to find them, and your life has just been reduced by 40 days due to frustration. By the end of those 20 hours, you can't even remember what you initially set out to do.

Resources, tutorials, samples... on developing extensions

Web:

http://www.mozilla.org/projects/firefox/extensions/

How to write Firefox extensions:
http://extensions.roachfiend.com/howto.php

Writing Firefox/Thunderbird Extensions
http://www.bengoodger.com/software/mb/extensions/howto.html

From "the law of averages >> Writing Mozilla Extensions":
http://www.yergler.net/blog/archives/2004/05/20/writing-mozilla-extensions
http://devedge.netscape.com/viewsource/2002/toolbar/
GUID Generator: http://extensions.roachfiend.com/cgi-bin/guid.pl
Firefox Extension RDF Generator: http://ted.mielczarek.org

Books:

"Creating Applications with Mozilla" by David Boswell, Brian King, Ian Oeschger, Pete Collins & Eric Murphy

Miscellaneous:

XUL Tutorial:
http://www.xulplanet.com/tutorials/xultu/

XUL Reference:
http://www.xulplanet.com/references/

Thursday, November 04, 2004

Cannot get to elements inside an overlay during load time

With Firefox, it seems that elements inside an overlay cannot be referenced (by document.getElementById(...)) while the parent document hasn't been fully loaded. In fact, even inside an onload handler of the parent document, document.getElementById(...) still fails to return elements inside the overlay.

For initialization code (running at load time of your extension) that needs to reference elements inside an overlay, you need to hook up a load handler inside the overlay, as follows:
<overlay>

...
<script>
window.addEventListener("load", foo, false);
</script>
</overlay>
The function foo now can use document.getElementById(...) to get elements inside the overlay.

Mozilla doesn't seem to have this problem.

When nothing happens

It could be that...
  1. Your Javascript code has syntax errors. Do you have an extra or a missing parenthesis somewhere?

Reasons for Firefox hanging

If you just install some extension and Firefox hangs upon loading, it could be due to some of the following reasons:
  1. A file inside the extension package is misnamed. I once gave a XUL file an .RDF extension and Firefox just hung. No error message. It sat at around 14MB of memory and showed no CPU usage.
  2. Something is wrong with the extension's install.rdf file or the content\contents.rdf file. Maybe the URI of the extension is not parsed properly (see another topic on that).
Well, what can you do? You can delete Firefox's profile on your machine (if you don't miss your usage data) by killing the Firefox subdirectory in:
C:\Documents and Settings\{your user id}\Application Data\Mozilla
if you're using Windows. Perhaps there's something more specific in that directory to kill, but I haven't investigated. Note that reinstalling Firefox doesn't help.

onload event only fires on top XUL element in Firefox

In Firefox, it seems that you can only hook up onload event handlers for the topmost element of a XUL file. onload event does not fire for the other elements in a XUL file or for any element in an overlay file.

Instead of,
    <someElement onload="...">

...
</someElement>
use,
    <someElement>

<script>...</script>
...
</someElement>

Calling Java code in custom JARs from Javascript

This is perhaps the easiest way to call Java code in your own custom JAR files from Javascript code in your extension:


var cl = new Packages.java.net.URLClassLoader(
[
new Packages.java.net.URL(
'http://foo.net/bar.jar')
]
);

var aClass = Packages.java.lang.Class.forName("HelloWorld", true, cl);
var aStaticMethod = aClass.getMethod("getGreeting", []);
var greeting = aStaticMethod.invoke(null, []);
alert(greeting);


If you would rather include your JAR file in your extension package, you might need to map a chrome URL to a file path, which can be done using jslib.

Please beware that the code above might freeze the browser for a short while as the JVM loads.

URIs of extensions

Beware that the URI of your extension as specified in the contents.rdf file in the content\ directory of your extension package might cause your extension to fail upon loading.

Both Mozilla and Firefox seem to have problems dealing with URIs that contain period "." and slashes "/" as well as long URIs. A URI like this one,
"urn:mozilla:package:edu.mit.csail.haystack.myExtension",
doesn't sit well with Mozilla and Firefox.