Bug 578691 - Add contact support to form autocomplete (Maemo) [r=vingtetun]
authorMark Finkle <mfinkle@mozilla.com>
Fri, 30 Jul 2010 10:40:26 -0400
changeset 66417 21795b8b9a8b622cb8daa313e070d8ba8effab56
parent 66416 ba396f2efc5b7bd244ef34dd732fffaa030f20ee
child 66418 9e8c970cc961e2b7fb9e3907c0b5fa01c92b209a
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvingtetun
bugs578691
Bug 578691 - Add contact support to form autocomplete (Maemo) [r=vingtetun]
mobile/Makefile.in
mobile/chrome/content/browser-ui.js
mobile/chrome/tests/Makefile.in
mobile/chrome/tests/browser_contacts.js
mobile/components/FormAutoComplete.js
mobile/components/Makefile.in
mobile/components/MobileComponents.manifest
mobile/makefiles.sh
mobile/modules/Makefile.in
mobile/modules/contacts.jsm
mobile/modules/linuxTypes.jsm
--- a/mobile/Makefile.in
+++ b/mobile/Makefile.in
@@ -38,17 +38,17 @@
 
 DEPTH      = ..
 topsrcdir  = @top_srcdir@
 srcdir     = @srcdir@
 VPATH      = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
-DIRS       = chrome locales components themes app
+DIRS       = chrome locales components modules themes app
 
 ifndef LIBXUL_SDK
 PARALLEL_DIRS += $(DEPTH)/xulrunner/tools/redit
 endif
 
 ifdef WINCE
 DIRS += installer/wince
 endif
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -44,16 +44,21 @@ XPCOMUtils.defineLazyGetter(this, "Plura
   return PluralForm;
 });
 
 XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
   Cu.import("resource://gre/modules/PlacesUtils.jsm");
   return PlacesUtils;
 });
 
+XPCOMUtils.defineLazyGetter(this, "Contacts", function() {
+  Cu.import("resource:///modules/contacts.jsm");
+  return Contacts;
+});
+
 const TOOLBARSTATE_LOADING  = 1;
 const TOOLBARSTATE_LOADED   = 2;
 
 [
   [
     "gHistSvc",
     "@mozilla.org/browser/nav-history-service;1",
     [Ci.nsINavHistoryService, Ci.nsIBrowserHistory]
@@ -395,16 +400,18 @@ var BrowserUI = {
       ConsoleView.init();
 
       // Init the sync system
       WeaveGlue.init();
     });
 
     FormHelperUI.init();
     FindHelperUI.init();
+
+    Contacts.init();
   },
 
   uninit: function() {
     ExtensionsView.uninit();
     ConsoleView.uninit();
   },
 
   update: function(aState) {
@@ -1674,17 +1681,17 @@ var FormHelperUI = {
   /** Retrieve the autocomplete list from the autocomplete service for an element */
   _getAutocompleteSuggestions: function _formHelperGetAutocompleteSuggestions(aElement) {
     if (!aElement.isAutocomplete)
       return [];
 
     let suggestions = [];
 
     let autocompleteService = Cc["@mozilla.org/satchel/form-autocomplete;1"].getService(Ci.nsIFormAutoComplete);
-    let results = autocompleteService.autoCompleteSearch(aElement.name, aElement.value, aElement, null);
+    let results = autocompleteService.autoCompleteSearch(aElement.name, aElement.value, null, null);
     if (results.matchCount > 0) {
       for (let i = 0; i < results.matchCount; i++) {
         let value = results.getValueAt(i);
         suggestions.push(value);
       }
     }
 
     return suggestions;
--- a/mobile/chrome/tests/Makefile.in
+++ b/mobile/chrome/tests/Makefile.in
@@ -56,16 +56,17 @@ include $(topsrcdir)/config/rules.mk
   browser_rect.js \
   browser_click_content.js \
   browser_forms.js \
   remote_forms.js \
   browser_viewport.js \
   browser_navigation.js \
   browser_preferences_basic.js \
   browser_sessionstore.js \
+  browser_contacts.js \
   browser_blank_01.html \
   browser_blank_02.html \
   browser_select.html \
   browser_click_content.html \
   browser_forms.html \
   browser_viewport_00.html \
   browser_viewport_01.html \
   browser_viewport_02.html \
new file mode 100644
--- /dev/null
+++ b/mobile/chrome/tests/browser_contacts.js
@@ -0,0 +1,54 @@
+
+// pull in the Contacts service
+Components.utils.import("resource:///modules/contacts.jsm");
+
+function test() {
+  ok(Contacts, "Contacts class exists");
+  for (var fname in tests) {
+    tests[fname]();
+  }
+}
+
+let MockContactsProvider = {
+  getContacts: function() {
+    let contacts = [
+      {
+        fullName: "-Billy One",
+        emails: [],
+        phoneNumbers: ["999-888-7777"]
+      },
+      {
+        fullName: "-Billy Two",
+        emails: ["billy.two@fake.com", "btwo@work.com"],
+        phoneNumbers: ["111-222-3333", "123-123-1234"]
+      },
+      {
+        fullName: "-Joe Schmo",
+        emails: ["joeschmo@foo.com"],
+        phoneNumbers: ["555-555-5555"]
+      }
+    ];
+
+    return contacts;
+  }
+};
+
+Contacts.addProvider(MockContactsProvider);
+
+let fac = Cc["@mozilla.org/satchel/form-autocomplete;1"].getService(Ci.nsIFormAutoComplete);
+
+let tests = {
+  testBasicMatch: function() {
+    let results = fac.autoCompleteSearch("email", "-Billy", null, null);
+    ok(results.matchCount == 2, "Found 2 emails '-Billy'");
+
+    results = fac.autoCompleteSearch("tel", "-Billy", null, null);
+    ok(results.matchCount == 3, "Found 3 phone numbers '-Billy'");
+
+    results = fac.autoCompleteSearch("skip", "-Billy", null, null);
+    ok(results.matchCount == 0, "Found nothing for a non-contact field");
+
+    results = fac.autoCompleteSearch("phone", "-Jo", null, null);
+    ok(results.matchCount == 1, "Found 1 phone number '-Jo'");
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mobile/components/FormAutoComplete.js
@@ -0,0 +1,171 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Form Autocomplete Plus.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Mark Finkle <mfinkle@mozilla.com>
+ *  Dan Mills <thunder@mozilla.com>
+ *  Justin Dolske <dolske@mozilla.com>
+ *  Michael Hanson <mhanson@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function LOG() {
+  return; // comment out for verbose debugging
+  let msg = Array.join(arguments, " ");
+  dump(msg + "\n");
+  Cu.reportError(msg);
+}
+
+// Lazily get the base Form AutoComplete Search
+XPCOMUtils.defineLazyGetter(this, "FAC", function() {
+  return Components.classesByID["{c11c21b2-71c9-4f87-a0f8-5e13f50495fd}"]
+                   .getService(Ci.nsIFormAutoComplete);
+});
+
+XPCOMUtils.defineLazyGetter(this, "Contacts", function() {
+  Cu.import("resource:///modules/contacts.jsm");
+  return Contacts;
+});
+
+function FormAutoComplete() {
+  LOG("new FAC");
+}
+
+FormAutoComplete.prototype = {
+  classDescription: "Form AutoComplete Plus",
+  classID: Components.ID("{cccd414c-3ec2-4cc5-9dc4-36c87cc3c4fe}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFormAutoComplete]),
+
+  // Specify the html5 types that we want and some values to guess
+  contactTypes: {
+    email: /^(?:.*(?:e-?mail|recipients?).*|(send_)?to(_b?cc)?)$/i,
+    tel: /^(?:tel(?:ephone)?|.*phone.*)$/i
+  },
+
+  checkQueryType: function checkQueryType(aName, aField) {
+    // If we have an input field with the desired html5 type, take it!
+    if (aField != null) {
+      let type = aField.getAttribute("type");
+      if (this.contactTypes[type] != null)
+        return type;
+    }
+
+    // Grab attributes to check for contact inputs
+    let props = [aName];
+    if (aField != null) {
+      let attributes = ["class", "id", "rel"];
+      attributes.forEach(function(attr) {
+        if (aField.hasAttribute(attr))
+          props.push(aField.getAttribute(attr));
+      });
+    }
+
+    // Check the gathered properties for contact-like values
+    for (let [type, regex] in Iterator(this.contactTypes)) {
+      if (props.some(function(prop) prop.search(regex) != -1))
+        return type;
+    }
+    return null;
+  },
+
+  findContact: function findContact(aQuery, aType, aResult, aDupCheck) {
+    // Match the name and show the email for now..
+    Contacts.find({ fullName: aQuery }).forEach(function(contact) {
+      // Might not have an email for some reason... ?
+      try {
+        LOG("findContact", "Contact " + contact.fullName);
+
+        let suggestions;
+        switch (aType) {
+          case "email":
+            suggestions = contact.emails;
+            break;
+          case "tel":
+            suggestions = contact.phoneNumbers;
+            break;
+          default:
+            LOG("unknown type!", aType);
+            return;
+        }
+
+        for each (let suggestion in suggestions) {
+          if (aDupCheck[suggestion])
+            continue;
+          aDupCheck[suggestion] = true;
+
+          let data = contact.fullName + " <" + suggestion + ">";
+          aResult.appendMatch(suggestion, data, null, "contact");
+        }
+      }
+      catch(ex) {
+        LOG("findContact error", ex);
+      }
+    });
+  },
+
+  autoCompleteSearch: function autoCompleteSearch(aName, aQuery, aField, aPrev) {
+    LOG("autocomplete search", Array.slice(arguments));
+
+    let result = Cc["@mozilla.org/autocomplete/simple-result;1"].createInstance(Ci.nsIAutoCompleteSimpleResult);
+    result.setSearchString(aQuery);
+
+    // Don't allow duplicates get merged into the final results
+    let dupCheck = {};
+
+    // Use the base form autocomplete for non-contact searches
+    let normal = FAC.autoCompleteSearch(aName, aQuery, aField, aPrev);
+    if (normal.matchCount > 0) {
+      for (let i = 0; i < normal.matchCount; i++) {
+        dupCheck[normal.getValueAt(i)] = true;
+        result.appendMatch(normal.getValueAt(i), normal.getCommentAt(i), normal.getImageAt(i), normal.getStyleAt(i));
+      }
+    }
+
+    // Do searches for certain input fields
+    let type = this.checkQueryType(aName, aField);
+    if (type != null)
+      this.findContact(aQuery, type, result, dupCheck);
+
+    let resultCode = result.matchCount ? "RESULT_SUCCESS" : "RESULT_NOMATCH";
+    result.setSearchResult(Ci.nsIAutoCompleteResult[resultCode]);
+    return result;
+  }
+};
+
+let components = [FormAutoComplete];
+const NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/mobile/components/Makefile.in
+++ b/mobile/components/Makefile.in
@@ -65,14 +65,15 @@ EXTRA_COMPONENTS = \
         XPIDialogService.js \
         DownloadManagerUI.js \
         HelperAppDialog.js \
         PromptService.js \
         BrowserCLH.js \
         ContentDispatchChooser.js \
         AutoCompleteCache.js \
         AddonUpdateService.js \
+        FormAutoComplete.js \
 	$(NULL)
 
 DIRS =  phone \
         $(NULL)
 
 include $(topsrcdir)/config/rules.mk
--- a/mobile/components/MobileComponents.manifest
+++ b/mobile/components/MobileComponents.manifest
@@ -77,8 +77,12 @@ contract @mozilla.org/autocomplete/searc
 component {f570982e-4f15-48ab-b6a0-ed851ac551b2} AutoCompleteCache.js
 contract @mozilla.org/browser/autocomplete-observer;1 {f570982e-4f15-48ab-b6a0-ed851ac551b2}
 category bookmark-observers BrowserBookmarkObserver @mozilla.org/browser/autocomplete-observer;1
 
 # AddonUpdateService.js
 component {93c8824c-9b87-45ae-bc90-5b82a1e4d877} AddonUpdateService.js
 contract @mozilla.org/browser/addon-update-service;1 {93c8824c-9b87-45ae-bc90-5b82a1e4d877}
 category update-timer AddonUpdateService @mozilla.org/browser/addon-update-service;1,getService,auto-addon-background-update-timer,extensions.autoupdate.interval,86400
+
+# FormAutoComplete.js
+component {cccd414c-3ec2-4cc5-9dc4-36c87cc3c4fe} FormAutoComplete.js
+contract @mozilla.org/satchel/form-autocomplete;1 {cccd414c-3ec2-4cc5-9dc4-36c87cc3c4fe}
--- a/mobile/makefiles.sh
+++ b/mobile/makefiles.sh
@@ -42,16 +42,17 @@ toolkit/locales/Makefile
 security/manager/locales/Makefile
 mobile/app/Makefile
 $MOZ_BRANDING_DIRECTORY/Makefile
 $MOZ_BRANDING_DIRECTORY/locales/Makefile
 mobile/chrome/Makefile
 mobile/chrome/tests/Makefile
 mobile/components/Makefile
 mobile/components/phone/Makefile
+mobile/modules/Makefile
 mobile/installer/Makefile
 mobile/locales/Makefile
 mobile/Makefile
 mobile/themes/Makefile
 mobile/themes/core/Makefile"
 
 if test -n "$MOZ_UPDATE_PACKAGING"; then
    add_makefiles "
new file mode 100644
--- /dev/null
+++ b/mobile/modules/Makefile.in
@@ -0,0 +1,53 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Mozilla Mobile Browser.
+#
+# The Initial Developer of the Original Code is
+# the Mozilla Foundation <http://www.mozilla.org/>.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Mark Finkle <mfinkle@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH      = ../..
+topsrcdir  = @top_srcdir@
+srcdir     = @srcdir@
+VPATH      = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_JS_MODULES = \
+  linuxTypes.jsm \
+  $(NULL)
+
+EXTRA_PP_JS_MODULES = \
+  contacts.jsm \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/mobile/modules/contacts.jsm
@@ -0,0 +1,154 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Mobile Browser.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Mark Finkle <mfinkle@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let EXPORTED_SYMBOLS = ["Contacts"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+
+let Contacts = {
+  _providers: [],
+  _contacts: [],
+
+  _load: function _load() {
+    this._contacts = [];
+
+    this._providers.forEach(function(provider) {
+      this._contacts = this._contacts.concat(provider.getContacts());
+    }, this)
+  },
+
+  init: function init() {
+    // Not much to do for now
+    this._load();
+  },
+
+  refresh: function refresh() {
+    // Pretty simple for now
+    this._load();
+  },
+
+  addProvider: function(aProvider) {
+    this._providers.push(aProvider);
+    this.refresh();
+  },
+
+  find: function find(aMatch) {
+    let results = [];
+
+    if (!this._contacts.length)
+      return results;
+
+    for (let field in aMatch) {
+      // Simple string-only partial matching
+      let match = aMatch[field];
+      this._contacts.forEach(function(aContact) {
+        if (field in aContact && aContact[field].indexOf(match) != -1)
+          results.push(aContact);
+      });
+    }
+    return results;
+  }
+};
+
+#ifdef XP_UNIX
+Cu.import("resource:///modules/linuxTypes.jsm");
+
+function EBookProvider() {
+  EBook.init();
+}
+
+EBookProvider.prototype = {
+  getContacts: function() {
+    if (!EBook.lib) {
+      Cu.reportError("EBook not loaded")
+      return [];
+    }
+
+    let gError = new GLib.GError.ptr;
+    let book = EBook.openSystem(gError.address());
+    if (!book) {
+      Cu.reportError("EBook.openSystem: " + gError.contents.message.readString())
+      return [];
+    }
+
+    if (!EBook.openBook(book, false, gError.address())) {
+      Cu.reportError("EBook.openBook: " + gError.contents.message.readString())
+      return [];
+    }
+
+    let query = EBook.queryAnyFieldContains("");
+    if (query) {
+      let gList = new GLib.GList.ptr();
+      if (!EBook.getContacts(book, query, gList.address(),  gError.address())) {
+        Cu.reportError("EBook.getContacts: " + gError.contents.message.readString())
+        return [];
+      }
+
+      let contacts = [];
+      while (gList && !gList.isNull()) {
+        let fullName = EBook.getContactField(gList.contents.data, EBook.E_CONTACT_FULL_NAME);
+        if (!fullName.isNull()) {
+          let contact = {};
+          contact.fullName = fullName.readString();
+          contact.emails = [];
+          contact.phoneNumbers = [];
+
+          for (let emailIndex=EBook.E_CONTACT_EMAIL_FIRST; emailIndex<=EBook.E_CONTACT_EMAIL_LAST; emailIndex++) {
+            let email = EBook.getContactField(gList.contents.data, emailIndex);
+            if (!email.isNull())
+              contact.emails.push(email.readString());
+          }
+
+          for (let phoneIndex=EBook.E_CONTACT_PHONE_FIRST; phoneIndex<=EBook.E_CONTACT_PHONE_LAST; phoneIndex++) {
+            let phone = EBook.getContactField(gList.contents.data, phoneIndex);
+            if (!phone.isNull())
+              contact.phoneNumbers.push(phone.readString());
+          }
+
+          contacts.push(contact);
+        }
+        gList = ctypes.cast(gList.contents.next, GLib.GList.ptr);
+      }
+      return contacts;
+    }
+    return [];
+  }
+};
+
+Contacts.addProvider(new EBookProvider);
+#endif
new file mode 100644
--- /dev/null
+++ b/mobile/modules/linuxTypes.jsm
@@ -0,0 +1,116 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Mobile Browser.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Mark Finkle <mfinkle@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let EXPORTED_SYMBOLS = ["GLib", "EBook"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+
+let GLib = {
+  lib: null,
+
+  init: function glib_init() {
+    if (this.lib)
+      return;
+
+    this.lib = true; // TODO hook up the real glib
+    
+    this.GError = new ctypes.StructType("GError", [
+      {"domain": ctypes.int32_t},
+      {"code": ctypes.int32_t},
+      {"message": ctypes.char.ptr}
+    ]);
+
+    this.GList = new ctypes.StructType("GList", [
+      {"data": ctypes.voidptr_t},
+      {"next": ctypes.voidptr_t},
+      {"prev": ctypes.voidptr_t}
+    ]);
+  }
+};
+
+let EBook = {
+  lib: null,
+  
+  E_CONTACT_FULL_NAME: 4,
+  E_CONTACT_EMAIL_1: 8,
+  E_CONTACT_EMAIL_2: 9,
+  E_CONTACT_EMAIL_3: 10,
+  E_CONTACT_EMAIL_4: 11,
+  E_CONTACT_PHONE_BUSINESS: 17,
+  E_CONTACT_PHONE_BUSINESS_2: 18,
+  E_CONTACT_PHONE_HOME: 23,
+  E_CONTACT_PHONE_HOME_2: 24,
+  E_CONTACT_PHONE_MOBILE: 27,
+
+  E_CONTACT_EMAIL_FIRST: 8,
+  E_CONTACT_EMAIL_LAST: 11,
+  E_CONTACT_PHONE_FIRST: 16,
+  E_CONTACT_PHONE_LAST: 34,
+
+  init: function ebook_init() {
+    if (this.lib)
+      return;
+
+    GLib.init();
+
+    try {
+      // Shipping on N900
+      this.lib = ctypes.open("libebook-1.2.so.5");
+    } catch (e) {
+      try {
+        // Shipping on Ubuntu
+        this.lib = ctypes.open("libebook-1.2.so.9");
+      } catch (e) {
+        Cu.reportError("EBook: couldn't load libebook:\n" + e)
+        this.lib = null;
+        return;
+      }
+    }
+
+    this.EBook = new ctypes.StructType("EBook");
+    this.EQuery = new ctypes.StructType("EQuery");
+
+    this.openSystem = this.lib.declare("e_book_new_system_addressbook", ctypes.default_abi, EBook.EBook.ptr, GLib.GError.ptr.ptr);
+    this.openBook = this.lib.declare("e_book_open", ctypes.default_abi, ctypes.bool, EBook.EBook.ptr, ctypes.bool, GLib.GError.ptr.ptr);
+
+    this.queryAnyFieldContains = this.lib.declare("e_book_query_any_field_contains", ctypes.default_abi, EBook.EQuery.ptr, ctypes.char.ptr);
+
+    this.getContacts = this.lib.declare("e_book_get_contacts", ctypes.default_abi, ctypes.bool, EBook.EBook.ptr, EBook.EQuery.ptr, GLib.GList.ptr.ptr, GLib.GError.ptr.ptr);
+    this.getContactField = this.lib.declare("e_contact_get_const", ctypes.default_abi, ctypes.char.ptr, ctypes.voidptr_t, ctypes.uint32_t);
+  }
+};