Labs/Jetpack/JEP/28
HEY
This page has been superseded by documentation contained in the Jetpack SDK.
Contents
Abstract
The Cuddlefish Minilib builds on JEP 25 to provide a basic infrastructure for developing traditional XULRunner Extensions and applications. It will also be the basis for Jetpack, as Jetpack itself is a XULRunner Extension.
It's assumed that readers of this document are familiar with JEP 25 and the SecurableModule standard.
Motivation and Rationale
Cuddlefish will allow many of the core benefits of Jetpack and CommonJS to be leveraged by any XULRunner Extension or application.
Additionally, by moving Jetpack to a SecurableModule-based architecture, a number of deficiencies with its runtime will be resolved, such as the inability to look at the API documentation and use the built-in code editor simultaneously.
Requirements and Scope
To address issues present in traditional Extension development, Cuddlefish provides mechanisms for:
- writing and executing test cases, inspired by Python's nose package,
- tracking JS objects of interest to aid in memory profiling and leak detection,
- registering callbacks that perform cleanup tasks when modules are unloaded,
- easily reporting errors with full stack tracebacks,
- dropping-in optional third-party packages or SecurableModules.
Cuddlefish should also have the following characteristics:
- Beautiful, concise documentation.
- A rigorous test suite ensuring that Cuddlefish doesn't break as the Mozilla platform evolves.
- Solid developer ergonomics ensuring that developers can easily find out why something they're doing isn't working.
As is implied by the word "minilib", Cuddlefish is intended to be very small and only contain the bare minimum of functionality that all extensions need. This means, for instance, that a module that wraps clipboard access, or a module that computes SHA1 hashes, will not be included in Cuddlefish—though such modules may certainly be bundled with an extension and easily loaded using Cuddlefish.
The following are outside the scope of Cuddlefish:
- Cuddlefish doesn't "touch" the command line at all; it's just a drop-in library to include in existing XULRunner Extensions or applications. As such, the following are out of scope:
- A tool for creating new XULRunner Extension or application directory structures "from scratch".
- A build system for creating XPIs or XULRunner applications.
- A documentation system.
- A package management system.
- Functionality specific to any particular XULRunner application, such as Firefox or Thunderbird.
- Any user interface, such as a debugger or a DOM inspector. Cuddlefish doesn't make any assumptions about whether you're using XUL, HTML, stdout, or something else; the exception to this is the logging console, which obviously needs something to output to, but multiple targets will be built-in to the minilib, with the option for clients to provide their own.
Usage
Cuddlefish consists of a single directory that can be dropped-into an extension's directory structure and loaded either through a <script> tag in a chrome-privileged document or a JS module.
For example, assuming that the Cuddlefish directory is placed in the extension's chrome/content/lib directory, a file at chrome/content/main.html can load it like so:
<html> <script src="lib/cuddlefish.js"></script> <script> var loader = new Cuddlefish.Loader({rootPath: "lib/"}); loader.runScript("console.log('hello');"); loader.unload(); </script> </html>
The Loader class encapsulates the Cuddlefish library and runtime; the caller has full control (and responsibility!) over its lifecycle. This means, for instance, that a document can instantiate a Loader that continues existing long after the document is closed; alternatively, a JS module can instantiate a Loader that stops existing well before the containing application process terminates. This freedom of control allows for a wide range of behavior, including the ability for application-wide functionality to be "streamed in" and automatically updated without requiring a restart.
Globals
Every script and module loaded by Cuddlefish comes with a handful of globals to make life easier for developers.
In addition to the globals provided by the SecurableModule standard and the Mozilla platform, each module has the following global variables that reflect idioms present in most Mozilla platform/extension code:
- Cc is mapped to Components.classes.
- Ci is mapped to Components.interfaces.
- Cu is mapped to Components.utils.
- Cr is mapped to Components.results.
In addition to these, the following globals are available to all modules.
__url__
The __url__ global is a string identifying the URL from which the module or script's code has been retrieved. If the module or script has no identifiable URL, this value may be null.
console
console is an object with methods similar to Firebug's console object:
console.log(object[, object, ...])
Logs an informational message to the console. Depending on console's underlying implementation and user interface, you may be able to introspect into the properties of non-primitive objects that are logged.
console.info(object[, object, ...])
A synonym for console.log().
console.warn(object[, object, ...])
Logs a warning message to the console.
console.error(object[, object, ...])
Logs an error message to the console.
console.debug(object[, object, ...])
Logs a debug message to the console.
console.exception(exception)
Logs the given exception instance as an error, outputting information about the exception's stack traceback if one is available.
console.trace()
Inserts a stack trace into the console at the point this function is called.
memory
memory is an object that exposes functionality to track objects of interest and help diagnose and prevent memory leaks.
memory.track(object, [bin])
Marks object for being tracked, and categorizes it with the given bin name. If bin isn't specified, the memory tracker attempts to infer a bin name by first checking the object's constructor.name; if that fails or results in the generic Object, the stack is inspected and the name of the current function being executed—which is assumed to be a constructor function—is used. If that fails, then the object is placed in a bin named generic.
memory.getObjects([bin])
Returns an Array containing information about tracked objects that have been categorized with the given bin name. If bin isn't provided, information about all live tracked objects are returned.
Each element of the array is an object with the following keys:
weakref | A weak reference to the object being tracked. Call get() on this object to retrieve its strong reference; if a strong reference to the object no longer exists, get() will return null. |
created | A Date representing the date and time that memory.track() was called on the object being tracked. |
filename | The name of the file that called memory.track() on the object being tracked. |
lineNo | The line number of the file that called memory.track() on the object being tracked. |
memory.getBins()
Returns an Array containing the names of all bins that aren't currently empty.
Modules
The following modules come built-in with Cuddlefish and can be retrieved with a call to require().
file
The file module provides access to the local filesystem.
file.dirname(path)
Returns the path of a file’s containing directory, albeit the parent directory if the file is a directory.
file.list(path)
Returns an array of files in the given directory.
file.join(...)
Takes a variable number of strings, joins them on the file system’s path separator, and returns the result.
securable-module
The securable-module module allows for the recursive loading and sandboxing of SecurableModules. This allows, for instance, the creation of "mini platforms" that manage the sandboxed evaluation of code.
The module's interface is the same as that specified in JEP 25.
unit-test
The unit-test module makes it easy to find and run unit tests.
Test Runner Objects
Each function which represents a test case is passed a single argument test, which represents the test runner. It has the following methods:
test.pass([message])
Marks a test as passing, with the given optional message.
test.fail([message])
Marks a test as failing, with the given optional message.
test.exception(e)
Marks a test as failing due to the given exception having been thrown. This can be put in a catch clause.
test.assert(a[, message])
Ensures that a has a truthy value. A test is marked as passing or failing depending on the result, logging message with it.
test.assertEqual(a, b[, message])
Simply ensures that a == b without recursing into a or b. A test is marked as passing or failing depending on the result, logging message with it.
test.assertNotEqual(a, b[, message])
Simply ensures that a != b without recursing into a or b. A test is marked as passing or failing depending on the result, logging message with it.
test.assertMatches(string, regexp[, message])
Ensures that the given string matches the given regular expression. If it does, marks a test as passing, otherwise marks a test as failing. message is logged with the pass or fail.
test.assertRaises(func, predicate[, message])
Calls the function func with no arguments, expecting an exception to be raised. If nothing is raised, marks the test as failing. If an exception is raised, the exception's message property is compared with predicate: if predicate is a string, then a simple equality comparison is done with message. Otherwise, if predicate is a regular expression, message is tested against it. Depending on the outcome, a test is marked as passing or failing, and message is logged.
test.waitUntilDone([timeout])
Puts the test runner into asynchronous testing mode, waiting up to timeout milliseconds for test.done() to be called. This is intended for use in situations where a test suite schedules a callback, calls test.waitUntilDone, and then calls test.done() in the callback.
test.done()
Marks a test as being complete. Assumes a previous call to test.waitUntilDone().
Functions
unit-test.findAndRunTests(options)
options should contain the following properties:
dirs | A list of absolute paths representing directories to search for tests in. It's assumed that all of these directories are also in the module search path, i.e. any JS files found in them are SecurableModules that can be loaded via a call to require(). |
onDone | A function to call when testing is complete. |
The list of directories is searched for SecurableModules that start with the prefix test-. Each module matching this criteria is expected to export functions that are test cases or a suite of test cases; each is called with a single argument, which is a Test Runner Object.
xhr
The xhr module provides access to XMLHttpRequest functionality, also known as AJAX.
xhr.XMLHttpRequest()
Creates an XMLHttpRequest. For more information, see Using XMLHttpRequest.
xhr.getRequestCount()
Returns the number of XMLHttpRequest objects that are alive (i.e., currently active or about to be).
observer-service
The observer-service module provides access to the application-wide observer service singleton.
For a list of common observer topics across a variety of Mozilla-based applications, see Observer Notifications.
Observer Callbacks
Observer callbacks are functions of the following form:
function callback(subject, data) { /* Respond to the event notification here... */ }
In the above example, subject is any JavaScript object, as is data. The particulars of what the two contain are specific to the notification topic.
Functions
observer-service.add(topic, callback)
Adds an observer callback to be triggered whenever a notification matching the string topic is broadcast throughout the application.
observer-service.remove(topic, callback)
Unsubscribes callback from being triggered whenever a notification matching the string topic is broadcast throughout the application.
observer-service.notify(topic, subject, data)
Broadcasts a notification event with the string topic, passing subject and data to all applicable observers in the application.
preferences-service
The preferences-service module provides access to the application-wide preferences service singleton.
preferences-service.set(name, value)
Sets the application preference name to value. value must be a string, boolean, or number.
preferences-service.get(name, defaultValue)
Gets the application preference name, returning defaultValue if no such preference exists.
preferences-service.has(name)
Returns whether or not the application preference name exists.
preferences-service.isSet(name)
Returns whether or not the application preference name both exists and has been set to a non-default value by the user (or a program acting on the user's behalf).
preferences-service.reset(name)
Clears a non-default, user-set value from the application preference name. If no user-set value is defined on name, the function does nothing.
timer
The timer module provides access to web-like timing functionality.
timer.setTimeout(callback, ms)
Schedules callback to be called in ms milliseconds. Returns an integer ID that can later be used to undo this scheduling, if callback hasn't yet been called.
timer.clearTimeout(id)
Given an integer ID passed back from setTimeout(), prevents the callback with the ID from being called (if it hasn't yet been called).
url
The url module provides functionality for the parsing and retrieving of URLs.
url.toFilename(url)
Attempts to convert the given URL to a native file path. This function will automatically attempt to resolve non-file protocols, such as the resource: protocol, to their place on the file system. An exception is raised if the URL can't be converted; otherwise, the native file path is returned as a string.
url.fromFilename(path)
Converts the given native file path to a file: URL.
url.resolve(base, relative)
Returns an absolute URL given a base URL and a relative URL.
url.parse(url)
Parses the URL and returns an object with following keys:
scheme | The name of the protocol in the URL. |
userPass | The username:password part of the URL; null if not present. |
host | The host of the URL, null if not present. |
port | The port number of the URL, null if none was specified. |
path | The path component of the URL. |
traceback
The traceback module contains functionality similar to Python's traceback module.
JSON Traceback Objects
Tracebacks are stored in JSON format. The stack is represented as an array in which the most recent stack frame is the last element; each element thus represents a stack frame and has the following keys:
filename | The name of the file that the stack frame takes place in. |
lineNo | The line number is being executed at the stack frame. |
funcName | The name of the function being executed at the stack frame, or null if the function is anonymous or the stack frame is being executed in a top-level script or module. |
Functions
traceback.fromException(exception)
Attempts to extract the traceback from exception, returning the JSON representation of the traceback or null if no traceback could be extracted.
traceback.get()
Returns the JSON representation of the stack at the point that this function is called.
traceback.format([tbOrException])
Given a JSON representation of the stack or an exception instance, returns a formatted plain text representation of it, similar to Python's formatted stack tracebacks. If no argument is provided, the stack at the point this function is called is used.
unload
The unload module allows modules to register callbacks that are called when Cuddlefish is unloaded. It is similar to the SecurableModule of the same name in the Narwhal platform.
unload.when(callback)
Registers callback to be called when unload.send() is called.
unload.send()
Sends an unload signal, thereby triggering all callbacks registered via unload.when(). In general, this function need not be manually called; it is automatically triggered when the containing Cuddlefish Loader's unload() method is called.
Reference Implementation
An in-progress reference implementation for Cuddlefish can be found at:
http://hg.mozilla.org/users/avarma_mozilla.com/jep-28/
Just check out the Mercurial repository and read the README.
It's also possible to play around with the latest version of the Minilib in Firefox and Thunderbird using the Cuddlefish Lab extension:
https://addons.mozilla.org/en-US/firefox/addon/14634/
Once you've installed the extension, just go to the "Tools" menu and select "Cuddlefish Lab" to start experimenting with code.