Add to iPhoto

  • Revision slug: Mozilla/js-ctypes/Examples/Add_to_iPhoto
  • Revision title: Add to iPhoto
  • Revision id: 111001
  • Created:
  • Creator: Sheppy
  • Is current revision? No
  • Comment 4 words added, 1 words removed

Revision Content

{{ gecko_minversion_header("1.9.3") }}

{{ draft() }}

This extension for Mac OS X serves as a demonstration of how to use js-ctypes to call Mac OS X Carbon, Core Foundation, and other system frameworks from an extension written entirely in JavaScript.

Note: This extension relies on changes added to Gecko as of the April 16, 2010 nightly of mozilla-central.

You can download an installable version of this extension on AMO.

Once installed, when you right-click on an image, you'll see among the options in the contextual menu an option to "Add Image to iPhoto". Choose it, and iPhoto will start up (if it's not already running) and import the image.

Declaring the APIs

The first thing we have to do is declare the Mac OS X APIs we'll be using. This extension uses a number of methods and data types, as well as constants, from three system frameworks.

Since a lot of this stuff is repetitive, we'll only look at selected parts of the code to get an idea how things work. You can download the extension and poke through the code inside it if you'd like to see all of it.

For the sake of organization, I chose to implement each system framework (and, mind you, I only declare the APIs I actually use, not all of them) as a JavaScript object containing all the types and methods that framework's API.

Note: In a few cases, this code takes the easy way out by using ctypes.voidptr_t instead of a typed pointer. That's not really the ideal way to do things but saved some time for this simple example. Some of this may change as I refine the example in the future; I'll update the article if and when that happens.

Some global types

There are a few global data types used by all of our frameworks. These are declared near the top of the code:

const OSStatus = ctypes.int32_t;
const CFIndex = ctypes.long;
const OptionBits = ctypes.uint32_t;
OSStatus
Used to represent the status code resulting from an operation.
CFIndex
A Core Foundation long integer type used to represent indexes into lists. I debated including this in the CoreFoundation object, and probably would have if I were being more formal, but opted against it for clarity's sake.
OptionBits
A 32-bit bit field data type.

Core Foundation

The majority of the system routines we'll be using come from Core Foundation. Among these are routines for managing CFString, CFURL, and CFArray objects, among others. These are core system data formats that are used by other frameworks, and we'll be making use of them.

The Core Foundation API is implemented by the CoreFoundation object, which consists of two methods to initialize and shut down the library, a reference to the library, and all the types and methods declared to support Core Foundation.

Initializing Core Foundation

The init() method, which sets everything up, looks like this:

  init: function() {
    this.lib = ctypes.open("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation");
    
    // declaring all the APIs goes here
  }

Shutting down Core Foundation

While the Core Foundation system framework itself doesn't need to be shut down, we do need to close the library we opened using the js-ctypes API; that's where the shutdown() method comes in:

  shutdown: function() {
    this.lib.close();
  }

Select API declarations

Let's take a look at a few of the key APIs we declare for Core Foundation, to see how it's done.

CFRange

CFRange is a structure that identifies a range; that is, it identifies an offset to an item in a list and a number of items. In C, the declaration looks like this:

typedef struct {
    CFIndex location;
    CFIndex length;
} CFRange;

To declare this for use with js-ctypes, we use the following code:

this.CFRange = new ctypes.StructType("CFRange",
                    [ {'location': ctypes.int32_t},
                      {'length': ctypes.int32_t}]);

This defines CoreFoundation.CFRange to represent this data type, comprised of two 32-bit integer fields called location and length.

Generic CFType routines

All Core Foundation data types are based upon a core CFType data type. Basic CFType routines handle memory management, dumping CFType objects to the console, comparing CFType values, and so forth. We'll be using a number of these methods, but for brevity's sake, since these are generally simple declarations, let's look at only the CFRelease() and CFRetain() declarations.

In C, these are declared thusly:

void CFRelease(CFTypeRef cf);
void CFRetain(CFTypeRef cf);

In JavaScript, this translates to:

    this.CFRelease = this.lib.declare("CFRelease",
                                ctypes.default_abi,
                                ctypes.void_t,
                                ctypes.voidptr_t);        // input: object to release
    
    this.CFRetain = this.lib.declare("CFRetain",
                                ctypes.default_abi,
                                ctypes.void_t,
                                ctypes.voidptr_t);        // input: object to retain

These methods are used to manage the reference counting for Core Foundation objects.

CFString

A CFString is an opaque data type that contains a string. The string can be stored in any of a number of encodings, so you use assorted functions that know how to cope with different encodings to set and get values of CFStrings, as well as to perform typical string operations.

The first declaration to be done here is to actually declare the CFStringRef data type; this is an opaque pointer to a CFString object.

this.CFStringRef = new ctypes.PointerType("CFStringRef");

Now that we've declared the core type, we can declare the methods we use that work with CFString objects. Let's take a look at one of these.

    this.CFStringCreateWithCharacters = this.lib.declare("CFStringCreateWithCharacters",
                                ctypes.default_abi,
                                this.CFStringRef,         // returns a new CFStringRef
                                ctypes.voidptr_t,         // allocator
                                ctypes.jschar.ptr,        // pointer to the Unicode string
                                ctypes.int32_t);          // length of the string

CFStringCreateWithCharacters() is used to create a new CFString object using a Unicode string as the source string, which is copied into the new CFString object. It returns a CFStringRef, which is a pointer to the new string, and accepts, as input, three parameters: an allocator, which is a pointer to a routine that will allocate the memory to contain the new object (we use the ctypes.voidptr_t type for this), a pointer to the Unicode string to copy into the new string object (ctypes.jschar.ptr), and the length of the Unicode string in characters.

CFURL

The CFURL type is used to describe a URL. It differs from a string in that it offers URL-specific methods for managing the content, and includes methods for converting between URLs and file system routine data formats such as FSRef and Unix pathnames. We use a few of these routines because the Launch Services routine we'll be using to launch iPhoto and pass it the image to import uses CFURL for the file references.

Let's take a look at two of the routines declared here:

    this.CFURLCreateFromFileSystemRepresentation = this.lib.declare("CFURLCreateFromFileSystemRepresentation",
                                ctypes.default_abi,
                                this.CFURLRef,            // returns
                                ctypes.voidptr_t,         // input: allocator
                                ctypes.unsigned_char.ptr, // input: pointer to string
                                CFIndex,                  // input: string length
                                ctypes.bool)              // input: isDirectory

This method is used to convert a Unix pathname into an URL. The interesting things to note about the declaration of CoreFoundation.CFURLCreateFromFileSystemRepresentation() are:

  • It returns a CFURLRef, which is an opaque pointer similar to the CFStringRef we noted above.
  • The pathname is specified as a value of type ctypes.unsigned_char.ptr, which is a pointer to an unsigned character. "File system representation" strings on Mac OS X are in UTF-8 format.
  • We use our CFIndex type here to specify the length of the string.
    this.CFURLGetFSRef = this.lib.declare("CFURLGetFSRef",
                                ctypes.default_abi,
                                ctypes.bool,              // Returns a bool
                                this.CFURLRef,            // input: URL to convert
                                ctypes.voidptr_t);        // input: Pointer to FSRef to fill

The CoreFoundation.CFURLGetFSRef() method is used to fill out a Carbon FSRef structure to describe the location of a file represented by a CFURL object. The main reason I include this here is because of the last parameter, which should be a pointer to an FSRef, but that's not declared until we get around to declaring the Carbon API, and I think that's worth noting.

CFArray

The CFArray type is used to create arrays of objects; the objects in the array can be of any type, thanks to a set of callbacks you can provide to handle managing their memory and performing operations such as comparisons. The most interesting thing we'll look at here is how to reference the system-provided default callback record, which is exported by Core Foundation under the name kCFTypeArrayCallBacks.

In C, the callback structure, and the predefined callback record, look like this:

typedef const void *	(*CFArrayRetainCallBack)(CFAllocatorRef allocator, const void *value);
typedef void		(*CFArrayReleaseCallBack)(CFAllocatorRef allocator, const void *value);
typedef CFStringRef	(*CFArrayCopyDescriptionCallBack)(const void *value);
typedef Boolean		(*CFArrayEqualCallBack)(const void *value1, const void *value2);
typedef struct {
    CFIndex				version;
    CFArrayRetainCallBack		retain;
    CFArrayReleaseCallBack		release;
    CFArrayCopyDescriptionCallBack	copyDescription;
    CFArrayEqualCallBack		equal;
} CFArrayCallBacks;

CF_EXPORT const CFArrayCallBacks kCFTypeArrayCallBacks;

The kCFTypeArrayCallBacks constant refers to a predefined callback structure referencing callback routines for managing arrays whose values are all CFType-based objects, such as CFURL, which is what we'll be using.

We need to be able to reference that predefined structure from our code. To do that, we first need to declare the CFArrayCallBacks structure:

    this.CFArrayCallBacks = new ctypes.StructType("CFArrayCallBacks",
      [ {'version': CFIndex},
        {'retain': ctypes.voidptr_t},
        {'release': ctypes.voidptr_t},
        {'copyDescription': ctypes.voidptr_t},
        {'equal': ctypes.voidptr_t} ]);

Having done this, we can then import the kCFTypeArrayCallBacks structure. This is done using the js-ctypes library object's declare() method, just like importing a function:

    this.kCFTypeArrayCallBacks = this.lib.declare("kCFTypeArrayCallBacks",
                              this.CFArrayCallBacks);
Note: For the record, this is the part that requires a nightly build of Firefox 3.7a5pre dated April 16, 2010 or later; this capability was introduced in that build.
CFMutableArray

One thing about Core Foundation types that is interesting is the use of regular and mutable versions of the same data types. For example, the CFArray type describes an array, but CFArray objects can't be changed once they've been created.

However, obviously there are cases in which you'll want to be able to manipulate the contents of an array by adding and removing items, sorting them, and so forth. That's where the CFMutableArray type comes into play. All CFArray functions accept CFMutableArray objects, so you can use CFMutableArray with any routine that accepts a CFArray as input, but CFMutableArray supports additional functions that let you change the contents of the array.

There's nothing particularly interesting about how we declare this API, but it will be noteworthy when we look at how we use CFMutableArray objects with methods that accept a CFArray as input, so I introduce this concept here.

Carbon

The Carbon API is the core operating system API derived from the classic Mac operating system. We actually aren't using any Carbon methods, but we are using one Carbon data type, the previously mentioned FSRef structure. FSRef is an opaque object describing a file.

In C, the FSRef is declared thusly:

struct FSRef {
  UInt8               hidden[80];             /* private to File Manager; •• need symbolic constant */
};
typedef struct FSRef                    FSRef;

We declare it using js-ctypes like this:

    this.struct_FSRef = new ctypes.StructType("FSRef",
                [ {"hidden": ctypes.char.array(80)}]);

The Carbon library init() and shutdown() routines are otherwise similar to how we do things for Core Foundation.

Application Services

The Application Services framework consists of a number of different APIs that provide special services to applications. The Application Services API we'll be using is the Launch Services API, which is used to launch applications and open files in default (or specific, in our case) applications.

The function we'll be using is LSOpenURLsWithRole(), whose declaration looks like this:

this.LSOpenURLsWithRole = this.lib.declare("LSOpenURLsWithRole",
                                ctypes.default_abi,                 // ABI type
                                OSStatus,                           // Returns OSStatus
                                CoreFoundation.CFArrayRef,          // Array of files to open in the app
                                OptionBits,                         // Roles mask
                                ctypes.voidptr_t,                   // inAEParam
                                this.struct_LSApplicationParameters.ptr, // description of the app to launch
                                ctypes.voidptr_t,                   // PSN array pointer
                                CFIndex);                           // max PSN count

This function returns an OSStatus indicating the result of the launch attempt, and accepts these parameters:

  • CFArrayRef providing a list of CFURL objects for the files to open in the application.
  • An OptionBits value providing a bit field of special options.
  • A pointer to an Apple Event parameter; we aren't using this, so I'm just using a voidptr_t here.
  • A pointer to an LSApplicationParameters structure that describes what application to launch
  • A pointer to an array to receive the serial numbers of the launched applications; we're not using this field, but if you do, you'll probably have to declare this differently.
  • A CFIndex indicating the size of the array specified by the previous parameter.

The LSApplicationParameters structure is declared like this:

    this.struct_LSApplicationParameters = new ctypes.StructType('LSApplicationParameters',
                                              [ {'version': CFIndex},
                                                {'flags': OptionBits},
                                                {'application': ctypes.voidptr_t},  // FSRef of application to launch
                                                {'asyncLaunchRefCon': ctypes.voidptr_t},
                                                {'environment': ctypes.voidptr_t},  // CFDictionaryRef
                                                {'argv': ctypes.voidptr_t},         // CFArrayRef of args
                                                {'initialEvent': ctypes.voidptr_t}]); // AppleEvent *

Most of these fields, we won't be using. We'll get a look at how we use this shortly.

There are also a few constants used for the flags field in the LSApplicationParameters structure:

    this.kLSRolesNone = 1;
    this.kLSRolesViewer = 2;
    this.kLSRolesEditor = 4;
    this.kLSRolesAll = 0xffffffff;

Implementing the extension

Now that the Mac OS X APIs we'll be using have been declared, we can write the core of the extension itself. This is done in the iPhoto object in the extension's code.

Hooking up to the context menu

On startup, we find the content area's context menu and add an event listener to it that will be called when the context menu is displayed. We'll use our handler for this event to add the "Add Image to iPhoto" option if the user has right-clicked on an image.

if (document.getElementById("contentAreaContextMenu")) {
  document.getElementById("contentAreaContextMenu").addEventListener("popupshowing", iPhoto.onPopup, false);
}

Responding when the context menu is clicked

When the user right-clicks an image, our handler gets called:

onPopup: function() {
  var node = iPhoto.getCurrentNode();

  var item = document.getElementById("add-to-iphoto_menuitem");

  if (item) {
    item.hidden = (node == null); // Hide it if we're not on an image
  }
}

This code finds the image node the user right-clicked in by calling our getCurrentNode() method, then sets the {{ xulmeth("menuitem.hidden", "hidden") }} state of the "Add Image to iPhoto" menu item based on whether or not an image node was found.

Revision Source

<p>{{ gecko_minversion_header("1.9.3") }}</p>
<p>{{ draft() }}</p>
<p>This extension for Mac OS X serves as a demonstration of how to use js-ctypes to call Mac OS X Carbon, Core Foundation, and other system frameworks from an extension written entirely in JavaScript.</p>
<div class="note"><strong>Note:</strong> This extension relies on changes added to Gecko as of the April 16, 2010 nightly of mozilla-central.</div>
<p>You can <a class=" link-https" href="https://addons.mozilla.org/en-US/firefox/addon/146328/" title="https://addons.mozilla.org/en-US/firefox/addon/146328/">download an installable version</a> of this extension on AMO.</p>
<p>Once installed, when you right-click on an image, you'll see among the options in the contextual menu an option to "Add Image to iPhoto". Choose it, and iPhoto will start up (if it's not already running) and import the image.</p>
<h2>Declaring the APIs</h2>
<p>The first thing we have to do is declare the Mac OS X APIs we'll be using. This extension uses a number of methods and data types, as well as constants, from three system frameworks.</p>
<p>Since a lot of this stuff is repetitive, we'll only look at selected parts of the code to get an idea how things work. You can download the extension and poke through the code inside it if you'd like to see all of it.</p>
<p>For the sake of organization, I chose to implement each system framework (and, mind you, I only declare the APIs I actually use, not all of them) as a JavaScript object containing all the types and methods that framework's API.</p>
<div class="note"><strong>Note:</strong> In a few cases, this code takes the easy way out by using <code>ctypes.voidptr_t</code> instead of a typed pointer. That's not really the ideal way to do things but saved some time for this simple example. Some of this may change as I refine the example in the future; I'll update the article if and when that happens.</div>
<h3>Some global types</h3>
<p>There are a few global data types used by all of our frameworks. These are declared near the top of the code:</p>
<pre class="brush: js">const OSStatus = ctypes.int32_t;
const CFIndex = ctypes.long;
const OptionBits = ctypes.uint32_t;
</pre>
<dl> <dt><code>OSStatus</code></dt> <dd>Used to represent the status code resulting from an operation.</dd> <dt><code>CFIndex</code></dt> <dd>A Core Foundation long integer type used to represent indexes into lists. I debated including this in the <code>CoreFoundation</code> object, and probably would have if I were being more formal, but opted against it for clarity's sake.</dd> <dt><code>OptionBits</code></dt> <dd>A 32-bit bit field data type.</dd>
</dl>
<h3>Core Foundation</h3>
<p>The majority of the system routines we'll be using come from Core Foundation. Among these are routines for managing <a class=" external" href="http://developer.apple.com/Mac/library/documentation/CoreFoundation/Reference/CFStringRef/Reference/reference.html" title="http://developer.apple.com/Mac/library/documentation/CoreFoundation/Reference/CFStringRef/Reference/reference.html"><code>CFString</code></a>, <a class=" external" href="http://developer.apple.com/mac/library/documentation/CoreFoundation/Reference/CFURLRef/Reference/reference.html" title="http://developer.apple.com/mac/library/documentation/CoreFoundation/Reference/CFURLRef/Reference/reference.html"><code>CFURL</code></a>, and <a class=" external" href="http://developer.apple.com/mac/library/DOCUMENTATION/CoreFoundation/Reference/CFArrayRef/Reference/reference.html" title="http://developer.apple.com/mac/library/DOCUMENTATION/CoreFoundation/Reference/CFArrayRef/Reference/reference.html"><code>CFArray</code></a> objects, among others. These are core system data formats that are used by other frameworks, and we'll be making use of them.</p>
<p>The Core Foundation API is implemented by the <code>CoreFoundation</code> object, which consists of two methods to initialize and shut down the library, a reference to the library, and all the types and methods declared to support Core Foundation.</p>
<h4>Initializing Core Foundation</h4>
<p>The <code>init()</code> method, which sets everything up, looks like this:</p>
<pre class="brush: js">  init: function() {
    this.lib = ctypes.open("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation");
    
    // declaring all the APIs goes here
  }
</pre>
<h4>Shutting down Core Foundation</h4>
<p>While the Core Foundation system framework itself doesn't need to be shut down, we do need to close the library we opened using the js-ctypes API; that's where the <code>shutdown()</code> method comes in:</p>
<pre class="brush: js">  shutdown: function() {
    this.lib.close();
  }
</pre>
<h4>Select API declarations</h4>
<p>Let's take a look at a few of the key APIs we declare for Core Foundation, to see how it's done.</p>
<h5>CFRange</h5>
<p><code>CFRange</code> is a structure that identifies a <em>range</em>; that is, it identifies an offset to an item in a list and a number of items. In C, the declaration looks like this:</p>
<pre class="brush: cpp">typedef struct {
    CFIndex location;
    CFIndex length;
} CFRange;
</pre>
<p>To declare this for use with js-ctypes, we use the following code:</p>
<pre class="brush: js">this.CFRange = new ctypes.StructType("CFRange",
                    [ {'location': ctypes.int32_t},
                      {'length': ctypes.int32_t}]);
</pre>
<p>This defines <code>CoreFoundation.CFRange</code> to represent this data type, comprised of two 32-bit integer fields called <code>location</code> and <code>length</code>.</p>
<h5>Generic CFType routines</h5>
<p>All Core Foundation data types are based upon a core <code>CFType</code> data type. Basic <code>CFType</code> routines handle memory management, dumping <code>CFType</code> objects to the console, comparing <code>CFType</code> values, and so forth. We'll be using a number of these methods, but for brevity's sake, since these are generally simple declarations, let's look at only the <code>CFRelease()</code> and <code>CFRetain()</code> declarations.</p>
<p>In C, these are declared thusly:</p>
<pre class="brush: cpp">void CFRelease(CFTypeRef cf);
void CFRetain(CFTypeRef cf);
</pre>
<p>In JavaScript, this translates to:</p>
<pre class="brush: js">    this.CFRelease = this.lib.declare("CFRelease",
                                ctypes.default_abi,
                                ctypes.void_t,
                                ctypes.voidptr_t);        // input: object to release
    
    this.CFRetain = this.lib.declare("CFRetain",
                                ctypes.default_abi,
                                ctypes.void_t,
                                ctypes.voidptr_t);        // input: object to retain
</pre>
<p>These methods are used to manage the reference counting for Core Foundation objects.</p>
<h5>CFString</h5>
<p>A <code>CFString</code> is an opaque data type that contains a string. The string can be stored in any of a number of encodings, so you use assorted functions that know how to cope with different encodings to set and get values of <code>CFString</code>s, as well as to perform typical string operations.</p>
<p>The first declaration to be done here is to actually declare the <code>CFStringRef</code> data type; this is an opaque pointer to a <code>CFString</code> object.</p>
<pre class="brush: js">this.CFStringRef = new ctypes.PointerType("CFStringRef");
</pre>
<p>Now that we've declared the core type, we can declare the methods we use that work with <code>CFString</code> objects. Let's take a look at one of these.</p>
<pre>    this.CFStringCreateWithCharacters = this.lib.declare("CFStringCreateWithCharacters",
                                ctypes.default_abi,
                                this.CFStringRef,         // returns a new CFStringRef
                                ctypes.voidptr_t,         // allocator
                                ctypes.jschar.ptr,        // pointer to the Unicode string
                                ctypes.int32_t);          // length of the string
</pre>
<p><code>CFStringCreateWithCharacters()</code> is used to create a new <code>CFString</code> object using a Unicode string as the source string, which is copied into the new <code>CFString</code> object. It returns a <code>CFStringRef</code>, which is a pointer to the new string, and accepts, as input, three parameters: an allocator, which is a pointer to a routine that will allocate the memory to contain the new object (we use the <code>ctypes.voidptr_t</code> type for this), a pointer to the Unicode string to copy into the new string object (<code>ctypes.jschar.ptr</code>), and the length of the Unicode string in characters.</p>
<h5>CFURL</h5>
<p>The <code>CFURL</code> type is used to describe a URL. It differs from a string in that it offers URL-specific methods for managing the content, and includes methods for converting between URLs and file system routine data formats such as <code>FSRef</code> and Unix pathnames. We use a few of these routines because the Launch Services routine we'll be using to launch iPhoto and pass it the image to import uses <code>CFURL</code> for the file references.</p>
<p>Let's take a look at two of the routines declared here:</p>
<pre class="brush: js">    this.CFURLCreateFromFileSystemRepresentation = this.lib.declare("CFURLCreateFromFileSystemRepresentation",
                                ctypes.default_abi,
                                this.CFURLRef,            // returns
                                ctypes.voidptr_t,         // input: allocator
                                ctypes.unsigned_char.ptr, // input: pointer to string
                                CFIndex,                  // input: string length
                                ctypes.bool)              // input: isDirectory
</pre>
<p>This method is used to convert a Unix pathname into an URL. The interesting things to note about the declaration of <code>CoreFoundation.CFURLCreateFromFileSystemRepresentation()</code> are:</p>
<ul> <li>It returns a <code>CFURLRef</code>, which is an opaque pointer similar to the <code>CFStringRef</code> we noted above.</li> <li>The pathname is specified as a value of type <code>ctypes.unsigned_char.ptr</code>, which is a pointer to an unsigned character. "File system representation" strings on Mac OS X are in UTF-8 format.</li> <li>We use our <code>CFIndex</code> type here to specify the length of the string.</li>
</ul>
<pre class="brush: js">    this.CFURLGetFSRef = this.lib.declare("CFURLGetFSRef",
                                ctypes.default_abi,
                                ctypes.bool,              // Returns a bool
                                this.CFURLRef,            // input: URL to convert
                                ctypes.voidptr_t);        // input: Pointer to FSRef to fill
</pre>
<p>The <code>CoreFoundation.CFURLGetFSRef()</code> method is used to fill out a Carbon <code>FSRef</code> structure to describe the location of a file represented by a <code>CFURL</code> object. The main reason I include this here is because of the last parameter, which should be a pointer to an <code>FSRef</code>, but that's not declared until we get around to declaring the Carbon API, and I think that's worth noting.</p>
<h5>CFArray</h5>
<p>The <code>CFArray</code> type is used to create arrays of objects; the objects in the array can be of any type, thanks to a set of callbacks you can provide to handle managing their memory and performing operations such as comparisons. The most interesting thing we'll look at here is how to reference the system-provided default callback record, which is exported by Core Foundation under the name <code>kCFTypeArrayCallBacks</code>.</p>
<p>In C, the callback structure, and the predefined callback record, look like this:</p>
<pre class="brush: cpp">typedef const void *	(*CFArrayRetainCallBack)(CFAllocatorRef allocator, const void *value);
typedef void		(*CFArrayReleaseCallBack)(CFAllocatorRef allocator, const void *value);
typedef CFStringRef	(*CFArrayCopyDescriptionCallBack)(const void *value);
typedef Boolean		(*CFArrayEqualCallBack)(const void *value1, const void *value2);
typedef struct {
    CFIndex				version;
    CFArrayRetainCallBack		retain;
    CFArrayReleaseCallBack		release;
    CFArrayCopyDescriptionCallBack	copyDescription;
    CFArrayEqualCallBack		equal;
} CFArrayCallBacks;

CF_EXPORT const CFArrayCallBacks kCFTypeArrayCallBacks;
</pre>
<p>The <code>kCFTypeArrayCallBacks</code> constant refers to a predefined callback structure referencing callback routines for managing arrays whose values are all <code>CFType</code>-based objects, such as <code>CFURL</code>, which is what we'll be using.</p>
<p>We need to be able to reference that predefined structure from our code. To do that, we first need to declare the <code>CFArrayCallBacks</code> structure:</p>
<pre class="brush: js">    this.CFArrayCallBacks = new ctypes.StructType("CFArrayCallBacks",
      [ {'version': CFIndex},
        {'retain': ctypes.voidptr_t},
        {'release': ctypes.voidptr_t},
        {'copyDescription': ctypes.voidptr_t},
        {'equal': ctypes.voidptr_t} ]);
</pre>
<p>Having done this, we can then import the <code>kCFTypeArrayCallBacks</code> structure. This is done using the js-ctypes library object's <code>declare()</code> method, just like importing a function:</p>
<pre class="brush: js">    this.kCFTypeArrayCallBacks = this.lib.declare("kCFTypeArrayCallBacks",
                              this.CFArrayCallBacks);
</pre>
<div class="note"><strong>Note:</strong> For the record, this is the part that requires a nightly build of Firefox 3.7a5pre dated April 16, 2010 or later; this capability was introduced in that build.</div>
<h5>CFMutableArray</h5>
<p>One thing about Core Foundation types that is interesting is the use of regular and mutable versions of the same data types. For example, the <code>CFArray</code> type describes an array, but <code>CFArray</code> objects can't be changed once they've been created.</p>
<p>However, obviously there are cases in which you'll want to be able to manipulate the contents of an array by adding and removing items, sorting them, and so forth. That's where the <code>CFMutableArray</code> type comes into play. All <code>CFArray</code> functions accept <code>CFMutableArray</code> objects, so you can use <code>CFMutableArray</code> with any routine that accepts a <code>CFArray</code> as input, but <code>CFMutableArray</code> supports additional functions that let you change the contents of the array.</p>
<p>There's nothing particularly interesting about how we declare this API, but it will be noteworthy when we look at how we use <code>CFMutableArray</code> objects with methods that accept a <code>CFArray</code> as input, so I introduce this concept here.</p>
<h3>Carbon</h3>
<p>The Carbon API is the core operating system API derived from the classic Mac operating system. We actually aren't using any Carbon methods, but we are using one Carbon data type, the previously mentioned <code>FSRef</code> structure. <code>FSRef</code> is an opaque object describing a file.</p>
<p>In C, the <code>FSRef</code> is declared thusly:</p>
<pre class="brush: cpp">struct FSRef {
  UInt8               hidden[80];             /* private to File Manager; •• need symbolic constant */
};
typedef struct FSRef                    FSRef;
</pre>
<p>We declare it using js-ctypes like this:</p>
<pre class="brush: js">    this.struct_FSRef = new ctypes.StructType("FSRef",
                [ {"hidden": ctypes.char.array(80)}]);
</pre>
<p>The Carbon library <code>init()</code> and <code>shutdown()</code> routines are otherwise similar to how we do things for Core Foundation.</p>
<h3>Application Services</h3>
<p>The Application Services framework consists of a number of different APIs that provide special services to applications. The Application Services API we'll be using is the Launch Services API, which is used to launch applications and open files in default (or specific, in our case) applications.</p>
<p>The function we'll be using is <a class=" external" href="http://developer.apple.com/mac/library/documentation/Carbon/Reference/LaunchServicesReference/Reference/reference.html#//apple_ref/c/func/LSOpenURLsWithRole" title="http://developer.apple.com/mac/library/documentation/Carbon/Reference/LaunchServicesReference/Reference/reference.html#//apple_ref/c/func/LSOpenURLsWithRole"><code>LSOpenURLsWithRole()</code></a>, whose declaration looks like this:</p>
<pre class="brush: js">this.LSOpenURLsWithRole = this.lib.declare("LSOpenURLsWithRole",
                                ctypes.default_abi,                 // ABI type
                                OSStatus,                           // Returns OSStatus
                                CoreFoundation.CFArrayRef,          // Array of files to open in the app
                                OptionBits,                         // Roles mask
                                ctypes.voidptr_t,                   // inAEParam
                                this.struct_LSApplicationParameters.ptr, // description of the app to launch
                                ctypes.voidptr_t,                   // PSN array pointer
                                CFIndex);                           // max PSN count
</pre>
<p>This function returns an <code>OSStatus</code> indicating the result of the launch attempt, and accepts these parameters:</p>
<ul> <li>A <code>CFArrayRef</code> providing a list of CFURL objects for the files to open in the application.</li> <li>An <code>OptionBits</code> value providing a bit field of special options.</li> <li>A pointer to an Apple Event parameter; we aren't using this, so I'm just using a <code>voidptr_t</code> here.</li> <li>A pointer to an <code>LSApplicationParameters</code> structure that describes what application to launch</li> <li>A pointer to an array to receive the serial numbers of the launched applications; we're not using this field, but if you do, you'll probably have to declare this differently.</li> <li>A <code>CFIndex</code> indicating the size of the array specified by the previous parameter.</li>
</ul>
<p>The <code>LSApplicationParameters</code> structure is declared like this:</p>
<pre class="brush: js">    this.struct_LSApplicationParameters = new ctypes.StructType('LSApplicationParameters',
                                              [ {'version': CFIndex},
                                                {'flags': OptionBits},
                                                {'application': ctypes.voidptr_t},  // FSRef of application to launch
                                                {'asyncLaunchRefCon': ctypes.voidptr_t},
                                                {'environment': ctypes.voidptr_t},  // CFDictionaryRef
                                                {'argv': ctypes.voidptr_t},         // CFArrayRef of args
                                                {'initialEvent': ctypes.voidptr_t}]); // AppleEvent *
</pre>
<p>Most of these fields, we won't be using. We'll get a look at how we use this shortly.</p>
<p>There are also a few constants used for the <code>flags</code> field in the <code>LSApplicationParameters</code> structure:</p>
<pre class="brush: js">    this.kLSRolesNone = 1;
    this.kLSRolesViewer = 2;
    this.kLSRolesEditor = 4;
    this.kLSRolesAll = 0xffffffff;
</pre>
<h2>Implementing the extension</h2>
<p>Now that the Mac OS X APIs we'll be using have been declared, we can write the core of the extension itself. This is done in the <code>iPhoto</code> object in the extension's code.</p>
<h3>Hooking up to the context menu</h3>
<p>On startup, we find the content area's context menu and add an event listener to it that will be called when the context menu is displayed. We'll use our handler for this event to add the "Add Image to iPhoto" option if the user has right-clicked on an image.</p>
<pre class="brush: js">if (document.getElementById("contentAreaContextMenu")) {
  document.getElementById("contentAreaContextMenu").addEventListener("popupshowing", iPhoto.onPopup, false);
}
</pre>
<h3>Responding when the context menu is clicked</h3>
<p>When the user right-clicks an image, our handler gets called:</p>
<pre class="brush: js">onPopup: function() {
  var node = iPhoto.getCurrentNode();

  var item = document.getElementById("add-to-iphoto_menuitem");

  if (item) {
    item.hidden = (node == null); // Hide it if we're not on an image
  }
}
</pre>
<p>This code finds the image node the user right-clicked in by calling our <code>getCurrentNode()</code> method, then sets the {{ xulmeth("menuitem.hidden", "hidden") }} state of the "Add Image to iPhoto" menu item based on whether or not an image node was found.</p>
Revert to this revision