Call Native API from Google Chrome Extension on Mac OS X

Posted by Yoshimasa Niwa on 06/18, 2010

While the last WWDC, Apple released new extension feature for their Safari web browser. The competitor Google already released Google Chrome and it also has the extension feature.

Actually I’m using Safari as a default web browser but sometimes I’m using Google Chrome. In this use case, I need to send some URL to the Safari – a default application which is defined in the preferences to open URL on Mac OS X. I googled extensions then found there are no good extension to do that. If we don’t have it, we just create it!

To do this, we need to launch application from Google Chrome but it requires to call native code which Mac OS X provides. Fortunately, Google Chrome extension has a way to run native code inside extension, making a NPAPI plugin like FlashPlayer then including it to background HTML of the extension. This article describe how to build an extension using NPAPI plugin to call native code inside it.

Getting Started

I push all source codes to my github repository so that you can download, tweak and make good things. It includes XCode project, some configurations and plugin source code, extension JavaScript and HTML code. If you already have git command, just clone whole repository from github. If not, just take it as Zip or Tar from Download Source button on github and inflate it.

% git clone git://github.com/niw/open_with_default

Prepare to Use Experimental APIs

UPDATE 7/28/2010: This section is obsolete. The APIs to add items are out from experimental APIs, we can use it without any options. Just download Google Chrome from DevChannel then run it!

This extension adds an item into the the context menus of the links. We need to use experimental APIs which is not enabled in the current released Google Chrome. You need to update your Google Chrome from their DevChannel. Then when launching it, we need to use --enable-experimental-extension-apis option. You can add it from Terminal.app like

% open /Applications/Google\ Chrome.app --args --enable-experimental-extension-apis

Build It then Test It!

Open OpenWithDefault.xcodeproj then select Build from the menu to build the extension. The unpacked extension are placed in Extension folder. As previous section, run Google Chrome downloaded from DevChannel then open “Extension” page. Enable “Developer mode” then click “Load unpacked extension”, select Extension folder to load it in Google Chrome.

You will see the “Open with Default” item in the context menu of the links, then select it to send URL to the default application, like Safari.

Load NPAPI Plugins to Extension

To make a basic Google Chrome extension, please read this article. NPAPI Plugins are like Flash Player, we load it to background.html using <embed>. type is defined when creating the NPAPI plugin.

<body onload="init()">
<embed type="application/x-open-with-default" id="pluginId">
</body>

Then, call method defined in the plugin using id from JavaScript.

var plugin = document.getElementById("pluginId");
plugin.open("http://twitter.com/");

This open is the method provided from the NPAPI plugin we’re creating.

Create NPAPI Plugin

NPAPI(Netscape Plugin Application Programming Interface) is a C API set to create plugins for web browsers. And the plugin can provide some methods to JavaScript world via NPAPI so that we can call native API inside the extension.

On Mac OS X, we need to create a Bundle which end with .plugin with special Info.plist. This is an important part of the sample Info.plist in this extension.

<key>WebPluginMIMETypes</key>
<dict>
    <key>application/x-open-with-default</key>
    <dict>
        <key>WebPluginTypeDescription</key>
        <string>A Plugin for Chrome Extension</string>
    </dict>
</dict>
<key>WebPluginName</key>
<string>${PRODUCT_NAME}</string>
<key>WebPluginDescription</key>
<string>A Plugin for Chrome Extension</string>

We need to use same MIME type as used in type attribute of embed tag, in this case application/x-open-with-default. And then defined some required functions to initialize and register the plugin via NPAPI.

NPError NP_Initialize(NPNetscapeFuncs* browserFuncs) {  
    npnfuncs = browserFuncs;
    return NPERR_NO_ERROR;
}

NPError NP_GetEntryPoints(NPPluginFuncs* pluginFuncs) {
    // Register entry points to initialize this plugin.
    pluginFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
    pluginFuncs->size = sizeof(pluginFuncs);
    pluginFuncs->newp = NPP_New;
    pluginFuncs->destroy = NPP_Destroy;
    pluginFuncs->getvalue = NPP_GetValue;
    return NPERR_NO_ERROR;
}

void NP_Shutdown(void) {
    // No operations here, but this method is required.
}

We need define all these three functions, even we do nothing in NP_Shutdown. In NP_GetEntryPoints, we need to register at least these 3 functions, newp, destroy and getvalue. newp and destroy will be called when this plugin is loaded, unloaded on the page. getvalue will be called when user call this plugin from JavaScript.

Create Plugin Class for JavaScript

In the getvalue function, in this case NPP_GetValue, we get an instance of the plugin class which user can use from JavaScript like the code described above section.

// If we didn't create any plugin instance, we create it.
if(!plugin_instance) {
    plugin_instance = npnfuncs->createobject(instance, &plugin_class);
    npnfuncs->retainobject(plugin_instance);
}
*(NPObject **)value = plugin_instance;

This plugin_class is a definition of the object which is shown in the JavaScript, and we need to increment the reference count of the plugin instance. in the destroy, in this case NPP_Destroy, we decrease it so that NPAPI release the memory for this object when the plugin is unloaded.

When user call some methods to this object like open, in this case plugin_invoke which is defined in plugin_class will be called with name of the method, arguments. in this function, we can do anything what we want to do in the native world, of course calling native API like opening the URL with default application.

The method name is passed as NPIdentifier, and arguments are passed as array of NPVariant. We can convert these variables into normal UTF-8 strings etc.

// Convert from NPIdentifier to UTF-8 string
NPUTF8 *name = npnfuncs->utf8fromidentifier(methodName);

// Convert NPVariant to UTF-8 string
if(NPVARIANT_IS_STRING(args[0])) {
    NPString str = NPVARIANT_TO_STRING(args[0]);
    // do something with str.UTF8Characters and str.UTF8Length
}

Making Extension using Native API

Actually making extensions using NPAPI plugins is not easy but powerful. We can extend Google Chrome like adding some native feature like lookup Dictionary.app etc. Hopefully, this extension must be the good example and helps you to create another good extensions. Enjoy!

Share

Comments

  • Ciantic said at 06/19, 2010
    Great example for NPAPI.

    Currently trying to create NPAPI plugin for Windows 7 Chrome to max-height (WIN+Shift+Up arrow) the created window (OnCreated).
  • imiaou said at 07/20, 2010
    great post.Thanks
Your name
Your email
(optional)
Your url
(optional)
Your comment