Hack 86. Create a Chrome Package
Packages are the fundamental concept underlying most chrome content. Here's how to make one.
Pieces of software installed on top of Firefox are called extensions, themes, locales, or add-ons. The name extension and those other names are all product bundle names. Extension product bundles, for example, are handled by the Extension Manager. Inside these product bundles is the lower-level nuts-and-bolts concept of a package. This hack covers the different package representations and the extra steps that are needed for packages to be formally recognized by Firefox in the chrome registry.
An extension's packages include the extension's user-interface (UI) content, plus some additional information. This information makes up the extra bits and pieces needed for the extension to work in Firefox. Users see an extension as something that carries out a particular task for them. This encompasses the UI and the code to implement any functionality. A package includes these files, but it also serves as the name of the extension as it is used in file paths and in RDF files. Packages underlie all themes, locales, and extensions for Firefox.
Here are the steps needed to bring a package into existence:
After these steps, the package is ready for further bundling into an XPI file [Hack #88] and final distribution.
8.4.1. Create a Local Folder Hierarchy
Before you begin to write your extension, the first thing you should decide on is the folder structure on disk for development. It should closely mirror the structure of the JAR file or packaged folders that you will create. Let's have a look at an example.
On disk, a typical structure looks like this:
chromedit content chromeedit.xul locale en-US chromedit.properties fr-FR chromedit.properties skin classic chromedit.css groovy chromedit.css
8.4.2. Create a Package Representation
The local folder hierarchy must be converted into a formal package. You have a choice between a flat file structure or a JAR file. A package must use one or the other format. Firefox's standard install provides packages in JAR format.
22.214.171.124 Flat file structure
If you choose this option, it means that files arranged on disk stay as is and are not compressed or reorganized. This is suitable for small packages. There is no set rule regarding how many files you need to have to prefer this approach. It is entirely up to you. However, a good rule of thumb is that if there are more than four or five files, they should be compressed in a JAR for space-saving purposes. The choice of the flat structure means that the files stay in the same folders as they are on local disk. That makes life easier if you are debugging a package. Here's that standard directory layout for the chromedit package:
chromedit content locale en-US fr-FR ... skin classic groovy ...
There's just one top-level directory, named after the package. This listing is the same as the previous listing.
126.96.36.199 JAR representation
At the most abstract level, the JAR representation of a package is a simple JAR filename, the same name as the extension's package name. In the case of this sample JAR file, the filename is chromedit.jar. The internal hierarchy in the JAR file looks like this:
content chromedit locale en-US chromedit fr-FR chromedit ... skin classic chromedit groovy chromedit ...
In this case, there are three top-level directories: content/, locale/, and skin/. The files are compressed in ZIP format, leaving a smaller footprint on disk and making for a smaller download. Contained within the JAR file are the subdirectories, with their hierarchy intact. In this case, though, the package name dangles underneath the other details. A JAR file can contain more than one package.
188.8.131.52 Default URL names for package files
At this point, the files, whether flat or in a JAR file, are each accessible via a resource:// URL if they are placed in the Firefox install area. However, once registered, the package gains full security privileges and file access is achieved via chrome:// URLs, as we're about to see.
8.4.3. Register a Package with the Chrome Registry
Having the right folders and files gets you only halfway to where you want to go. Once the package is bundled and installed [Hack #88], it will just sit in its destination directory anonymously unless it provides some information needed by Firefox to recognize it internally. This process is known as chrome registration and is enabled by RDF files known as manifests.
184.108.40.206 Make a contents.rdf manifest file
The nature of the term manifest is not something be dwelled upon; what is more important is the form that manifest files take and how they work. The form they take in chrome registration is one or more contents.rdf files placed in the folder hierarchy. They tell Firefox that, if it finds a contents.rdf file in a particular folder, this folder will be one of the core content, skin, or locale folders that matches up with special chrome:// URLs to access files internally.
Here is a standard boilerplate contents.rdf file to be placed in the content/ folder of an extension or package; all contents.rdf files look almost identical to this one:
<?xml version="1.0"?> <RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:chrome="http://www.mozilla.org/rdf/chrome#"> <RDF:Seq about="urn:mozilla:package:root"> <RDF:li resource="urn:mozilla:package:chromedit" /> </RDF:Seq> <!-- Extension information --> <RDF:Description about="urn:mozilla:package:chromedit" chrome:description="A Simple User Profile File Editor" chrome:name="chromedit"> </RDF:Description> </RDF:RDF>
The bolded information is the only part that varies between packages (in the simple case). This RDF file has standard XML syntax and uses two namespaces: RDF and chrome. The most important items are the type of the manifest and the description. Lines 4 through 6 state the name of the package, which is also the folder name of the package. This information is added to the package root, which holds the full list of packages. That parent resource is called urn:mozilla:package:root.
Lines 7 through 11 tell us a little bit about the extensionhere, the name and a description. These attributes are largely redundant, due to the frontend metadata contained in another file, install.rdf [Hack #88] . However, for completeness, Table 8-1 provides a complete list of attributes.
When Firefox starts up for the first time after you install the extension, it will read this information in and make the necessary chrome registry changes. It knows where to find these files because it is told where to look in install.rdf [Hack #88] .
220.127.116.11 Use the new chrome: URLs
<RDF:Seq about="urn:mozilla:package:root"> <RDF:li resource="urn:mozilla:package:chromedit" /> </RDF:Seq>
For completeness, here are the contents.rdf files for skin/ and locale/ of the extension, necessary to register chrome URLs for these portions:
When filenames are omitted from the URL, such as in the previous examples, the URL resolves to the package name and the default extension for each component. So, chrome://chromedit/content/ looks for chromedit.xul in that folder. The default extension for skin/ is .css, and .dtd is the default for locale/.
This example shows how the skin part of a chrome: URL is registered:
<?xml version="1.0"?> <RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:chrome="http://www.mozilla.org/rdf/chrome#"> <RDF:Seq about="urn:mozilla:skin:root"> <RDF:li resource="urn:mozilla:skin:classic/1.0" /> </RDF:Seq> <RDF:Description about="urn:mozilla:skin:classic/1.0"> <chrome:packages> <RDF:Seq about="urn:mozilla:skin:classic/1.0:packages"> <RDF:li resource="urn:mozilla:skin:classic/1.0:chromedit"/> </RDF:Seq> </chrome:packages> </RDF:Description> </RDF:RDF>
This example shows how the locale part of a chrome: URL is registered:
<?xml version="1.0"?> <RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:chrome="http://www.mozilla.org/rdf/chrome#"> <RDF:Seq about="urn:mozilla:locale:root"> <RDF:li resource="urn:mozilla:locale:en-US"/> </RDF:Seq> <RDF:Description about="urn:mozilla:locale:en-US" chrome:displayName="English(US)" chrome:name="en-US"> <chrome:packages> <RDF:Seq about="urn:mozilla:locale:en-US:packages"> <RDF:li resource="urn:mozilla:locale:en-US:chromedit"/> </RDF:Seq> </chrome:packages> </RDF:Description> </RDF:RDF>