Bug 954193 - reorganize purplexpcom, part 2: rewrite the core service, account list, status handling and protocol plugin loading in JS components that don't depend on libpurple.
authorFlorian Quèze <florian@instantbird.org>
Wed, 09 Nov 2011 01:16:17 +0100
changeset 18434 466e354185cf4e97d5604c3403b3205f2d389e57
parent 18433 9d0c3d6cdf63063b630180d701e0e3a9ea227059
child 18435 b8402474dd532ae716c165f45af73153282d2ab8
push id1103
push usermbanner@mozilla.com
push dateTue, 18 Mar 2014 07:44:06 +0000
treeherdercomm-beta@50c6279a0af0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs954193
Bug 954193 - reorganize purplexpcom, part 2: rewrite the core service, account list, status handling and protocol plugin loading in JS components that don't depend on libpurple.
chat/components/public/Makefile.in
chat/components/public/ibILogger.idl
chat/components/public/imIAccount.idl
chat/components/public/imIAccountsService.idl
chat/components/public/imIContactsService.idl
chat/components/public/imIConversationsService.idl
chat/components/public/imICoreService.idl
chat/components/public/imIUserStatusInfo.idl
chat/components/public/prplIProtocol.idl
chat/components/public/purpleIConversation.idl
chat/components/src/Makefile.in
chat/components/src/imAccounts.js
chat/components/src/imAccounts.manifest
chat/components/src/imCommands.js
chat/components/src/imContacts.js
chat/components/src/imConversations.js
chat/components/src/imCore.js
chat/components/src/imCore.manifest
chat/modules/imServices.jsm
chat/modules/imThemes.jsm
chat/modules/imXPCOMUtils.jsm
chat/modules/jsProtoHelper.jsm
chat/protocols/jsTest/jsTestProtocol.js
chat/protocols/jsTest/jsTestProtocol.manifest
chat/protocols/overrides/facebookOverrideProtocol.js
chat/protocols/overrides/gtalkOverrideProtocol.js
chat/protocols/overrides/overrideProtocols.manifest
chat/protocols/twitter/twitter.js
chat/protocols/twitter/twitter.manifest
im/components/ibCommandLineHandler.js
im/components/ibStatusCommandLineHandler.js
im/components/mintrayr/content/mintrayr.js
im/content/aboutDialog.xul
im/content/account.js
im/content/account.xml
im/content/accountWizard.js
im/content/accounts.js
im/content/addbuddy.js
im/content/blist.js
im/content/debug/debug.js
im/content/debug/fake/fake.js
im/content/joinchat.js
im/content/menus.js
im/content/proxies.js
im/locales/en-US/chrome/instantbird/core.properties
im/modules/ibCore.jsm
purple/purplexpcom/public/purpleIProtocol.idl
purple/purplexpcom/public/purpleIRequest.idl
--- a/chat/components/public/Makefile.in
+++ b/chat/components/public/Makefile.in
@@ -40,15 +40,20 @@ srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= chat
 
 XPIDLSRCS	= \
 		ibILogger.idl \
+		imIAccount.idl \
+		imIAccountsService.idl \
 		imICommandsService.idl \
 		imIContactsService.idl \
 		imIConversationsService.idl \
+		imICoreService.idl \
+		imIUserStatusInfo.idl \
+		prplIProtocol.idl \
 		purpleIConversation.idl \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
--- a/chat/components/public/ibILogger.idl
+++ b/chat/components/public/ibILogger.idl
@@ -33,17 +33,17 @@
  * 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 ***** */
 
 #include "nsISupports.idl"
 #include "nsISimpleEnumerator.idl"
 
-interface purpleIAccount;
+interface imIAccount;
 interface imIAccountBuddy;
 interface imIBuddy;
 interface imIContact;
 interface purpleIConversation;
 
 [scriptable, uuid(164ff6c3-ca64-4880-b8f3-67eb1817955f)]
 interface ibILog: nsISupports {
   readonly attribute AUTF8String path;
@@ -51,10 +51,10 @@ interface ibILog: nsISupports {
 };
 
 [scriptable, uuid(ab38c01c-2245-4279-9437-1d6bcc69d556)]
 interface ibILogger: nsISupports {
   nsISimpleEnumerator getLogsForAccountBuddy(in imIAccountBuddy aAccountBuddy);
   nsISimpleEnumerator getLogsForBuddy(in imIBuddy aBuddy);
   nsISimpleEnumerator getLogsForContact(in imIContact aContact);
   nsISimpleEnumerator getLogsForConversation(in purpleIConversation aConversation);
-  nsISimpleEnumerator getSystemLogsForAccount(in purpleIAccount aAccount);
+  nsISimpleEnumerator getSystemLogsForAccount(in imIAccount aAccount);
 };
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imIAccount.idl
@@ -0,0 +1,296 @@
+/* ***** 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 the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * 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 ***** */
+
+#include "nsISupports.idl"
+#include "purpleIConversation.idl"
+#include "purpleIProxy.idl"
+#include "imIUserStatusInfo.idl"
+
+interface imITag;
+interface imIBuddy;
+interface imIAccountBuddy;
+interface imIAccount;
+interface prplIProtocol;
+
+/*
+ * Used to join chat rooms.
+ */
+
+[ptr] native GHashTablePtr(GHashTable);
+
+[scriptable, uuid(ea7ab156-d25e-46cc-93ef-29f77b3c0795)]
+interface purpleIChatRoomFieldValues: nsISupports {
+  AUTF8String getValue(in AUTF8String aIdentifier);
+  void setValue(in AUTF8String aIdentifier, in AUTF8String aValue);
+
+  [noscript] readonly attribute GHashTablePtr hashTable;
+};
+
+[scriptable, uuid(19dff981-b125-4a70-bc1a-efc783d07137)]
+interface purpleIChatRoomField: nsISupports {
+  readonly attribute AUTF8String label;
+  readonly attribute AUTF8String identifier;
+  readonly attribute boolean required;
+
+  const short TYPE_TEXT = 0;
+  const short TYPE_PASSWORD = 1;
+  const short TYPE_INT = 2;
+
+  readonly attribute short type;
+  readonly attribute long min;
+  readonly attribute long max;
+};
+
+
+/*
+ * This interface should be implemented by the protocol plugin.
+ */
+[scriptable, uuid(fb1b29cb-63ba-4335-9f44-63aea3f616a3)]
+interface prplIAccount: nsISupports {
+  readonly attribute imIAccount imAccount;
+
+  // observe should only be called by the imIAccount
+  // implementation to report user status changes that affect this account.
+  void observe(in nsISupports aObj, in string aEvent,
+               [optional] in wstring aData);
+
+  /* Uninitialize the prplIAccount instance. This is typically done
+     automatically at shutdown (by the core service) or as part of
+     the 'remove' method. */
+  void unInit();
+
+  void connect();
+  void disconnect();
+
+  purpleIConversation createConversation(in AUTF8String aName);
+
+  // Used when the user wants to add a buddy to the buddy list
+  void addBuddy(in imITag aTag, in AUTF8String aName);
+
+  // Used while loading the buddy list at startup.
+  imIAccountBuddy loadBuddy(in imIBuddy aBuddy, in imITag aTag);
+
+  /* Request more info on a buddy (typically a chat buddy).
+   * The result (if any) will be provided by a user-info-received
+   * notification dispatched through the observer service:
+   *  - aSubject will be an nsISimpleEnumerator of purpleITooltipInfo,
+   *  Do NOT store aSubject. Use it right away. The memory it points to
+   *  will be freed right after the notification is dispatched.
+   *  - aData will be aBuddyName.
+   */
+  void requestBuddyInfo(in AUTF8String aBuddyName);
+
+  readonly attribute boolean canJoinChat;
+  nsISimpleEnumerator getChatRoomFields();
+  purpleIChatRoomFieldValues getChatRoomDefaultFieldValues([optional] in AUTF8String aDefaultChatName);
+  /*
+   * Create a new chat conversation if it doesn't already exist.
+   */
+  void joinChat(in purpleIChatRoomFieldValues aComponents);
+
+  readonly attribute AUTF8String normalizedName;
+
+  attribute purpleIProxyInfo proxyInfo;
+
+  // protocol specific options: those functions set the protocol
+  // specific options for the PurpleAccount
+  void setBool(in string aName, in boolean aVal);
+  void setInt(in string aName, in long aVal);
+  void setString(in string aName, in string aVal);
+
+  /* When a connection error occurred, this value indicates the type of error */
+  readonly attribute short connectionErrorReason;
+
+  /* Possible connection error reasons:
+     ERROR_NETWORK_ERROR and ERROR_ENCRYPTION_ERROR are not fatal and
+     should enable the automatic reconnection feature. */
+  const short NO_ERROR = -1;
+  const short ERROR_NETWORK_ERROR = 0;
+  const short ERROR_INVALID_USERNAME = 1;
+  const short ERROR_AUTHENTICATION_FAILED = 2;
+  const short ERROR_AUTHENTICATION_IMPOSSIBLE = 3;
+  const short ERROR_NO_SSL_SUPPORT = 4;
+  const short ERROR_ENCRYPTION_ERROR = 5;
+  const short ERROR_NAME_IN_USE = 6;
+  const short ERROR_INVALID_SETTINGS = 7;
+  const short ERROR_CERT_NOT_PROVIDED = 8;
+  const short ERROR_CERT_UNTRUSTED = 9;
+  const short ERROR_CERT_EXPIRED = 10;
+  const short ERROR_CERT_NOT_ACTIVATED = 11;
+  const short ERROR_CERT_HOSTNAME_MISMATCH = 12;
+  const short ERROR_CERT_FINGERPRINT_MISMATCH = 13;
+  const short ERROR_CERT_SELF_SIGNED = 14;
+  const short ERROR_CERT_OTHER_ERROR = 15;
+  const short ERROR_OTHER_ERROR = 16;
+
+  /* From PurpleConnectionFlags */
+
+  //   PURPLE_CONNECTION_HTML
+  //    Connection sends/receives in 'HTML'.
+  readonly attribute boolean HTMLEnabled;
+
+  //   PURPLE_CONNECTION_NO_BGCOLOR
+  //    Connection does not send/receive background colors.
+  readonly attribute boolean noBackgroundColors;
+
+  //   PURPLE_CONNECTION_AUTO_RESP
+  //    Send auto responses when away.
+  readonly attribute boolean autoResponses;
+
+  //   PURPLE_CONNECTION_FORMATTING_WBFO
+  //    The text buffer must be formatted as a whole.
+  readonly attribute boolean singleFormatting;
+
+  //   PURPLE_CONNECTION_NO_NEWLINES
+  //    No new lines are allowed in outgoing messages.
+  readonly attribute boolean noNewlines;
+
+  //   PURPLE_CONNECTION_NO_FONTSIZE
+  //    Connection does not send/receive font sizes.
+  readonly attribute boolean noFontSizes;
+
+  //   PURPLE_CONNECTION_NO_URLDESC
+  //    Connection does not support descriptions with links.
+  readonly attribute boolean noUrlDesc;
+
+  //   PURPLE_CONNECTION_NO_IMAGES
+  //    Connection does not support sending of images.
+  readonly attribute boolean noImages;
+
+  // This is currently used only by Twitter.
+  readonly attribute PRInt32 maxMessageLength;
+};
+
+/* This interface should be implemented by the im core. It inherits
+from prplIAccount and in most cases will forward the calls for the
+inherited members to a prplIAccount account instance implemented by
+the protocol plugin. */
+[scriptable, uuid(20a85b44-e220-4f23-85bf-f8523d1a2b08)]
+interface imIAccount: prplIAccount {
+  /* Check if autologin is enabled for this account, connect it now. */
+  void checkAutoLogin();
+
+  /* Delete the account (from the preferences, mozStorage, and call unInit). */
+  void remove();
+
+  /* Cancel the timer that automatically reconnects the account that were
+     disconnected with a non fatal error */
+  void cancelReconnection();
+
+  readonly attribute AUTF8String name;
+  readonly attribute AUTF8String id;
+  readonly attribute PRUint32 numericId;
+  readonly attribute prplIProtocol protocol;
+  readonly attribute prplIAccount prplAccount;
+
+  // Save account specific preferences to disk.
+  void save();
+
+  attribute boolean autoLogin;
+
+  /* This is the value when the preference firstConnectionState is not set.
+     It indicates that the account has already been successfully connected at
+     least once with the current parameters. */
+  const short FIRST_CONNECTION_OK = 0;
+  /* Set when the account has never had a successful connection
+     with the current parameters */
+  const short FIRST_CONNECTION_UNKNOWN = 1;
+  /* Set when the account is trying to connect for the first time
+     with the current parameters (removed after a successsful connection) */
+  const short FIRST_CONNECTION_PENDING = 2;
+  /* Set at startup when the previous state was pending */
+  const short FIRST_CONNECTION_CRASHED = 4;
+
+  attribute short firstConnectionState;
+
+  // FIXME password should be in password manager
+  attribute AUTF8String password;
+
+  attribute AUTF8String alias;
+
+  /* While an account is connecting, this attribute contains a message
+     indicating the current step of the connection */
+  readonly attribute AUTF8String connectionStateMsg;
+
+  /* Number of the reconnection attempt
+   *  0 means that no automatic reconnection currently pending
+   *  n means the nth reconnection attempt is pending
+   */
+  readonly attribute PRUint16 reconnectAttempt;
+
+  /* Time stamp of the next reconnection attempt */
+  readonly attribute PRInt64 timeOfNextReconnect;
+
+  /* Time stamp of the last connection (value not reliable if not connected) */
+  readonly attribute PRInt64 timeOfLastConnect;
+
+  /* Additional possible connection error reasons:
+   * (Use a big enough number that it can't conflict with error
+   *  codes used in prplIAccount).
+   */
+  const short ERROR_UNKNOWN_PRPL = 42;
+  const short ERROR_CRASHED = 43;
+  const short ERROR_MISSING_PASSWORD = 44;
+
+  /* A message describing the connection error */
+  readonly attribute AUTF8String connectionErrorMessage;
+
+  /* Info about the connection state and flags */
+  const short STATE_DISCONNECTED = 0;
+  const short STATE_CONNECTED = 1;
+  const short STATE_CONNECTING = 2;
+  const short STATE_DISCONNECTING = 3;
+
+  readonly attribute short connectionState;
+
+  /* The following 4 properties use the above connectionState value. */
+  readonly attribute boolean disconnected;
+  readonly attribute boolean connected;
+  readonly attribute boolean connecting;
+  readonly attribute boolean disconnecting;
+
+  /* The imIUserStatusInfo instance this account should observe for
+     status changes. When this is null (the default value), the
+     account will observe the global status. */
+  attribute imIUserStatusInfo observedStatusInfo;
+  // Same as above, but never null (it fallbacks to the global status info).
+  attribute imIUserStatusInfo statusInfo;
+
+  // imIAccount also implements an observe method but this
+  // observe should only be called by the prplIAccount
+  // implementations to report connection status changes.
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imIAccountsService.idl
@@ -0,0 +1,97 @@
+/* ***** 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 the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * 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 ***** */
+
+#include "nsISupports.idl"
+#include "nsISimpleEnumerator.idl"
+#include "imIAccount.idl"
+
+[scriptable, uuid(b3b6459a-5c26-47b8-8e9c-ba838b6f632a)]
+interface imIAccountsService: nsISupports {
+  void initAccounts();
+  void unInitAccounts();
+
+  /* This attribute is set to AUTOLOGIN_ENABLED by default. It can be set to
+     any other value before the initialization of this service to prevent
+     accounts with autoLogin enabled from being connected when libpurple is
+     initialized.
+     Any value other than the ones listed below will disable autoLogin and
+     display a generic message in the Account Manager. */
+  attribute short autoLoginStatus;
+
+  const short AUTOLOGIN_ENABLED = 0;
+  const short AUTOLOGIN_USER_DISABLED = 1;
+  const short AUTOLOGIN_SAFE_MODE = 2;
+  const short AUTOLOGIN_CRASH = 3;
+  const short AUTOLOGIN_START_OFFLINE = 4;
+
+  /* The method should be used to connect all accounts with autoLogin enabled.
+     Some use cases:
+       - if the autologin was disabled at startup
+       - after a loss of internet connectivity that disconnected all accounts.
+  */
+  void processAutoLogin();
+
+  imIAccount getAccountById(in AUTF8String aAccountId);
+
+  /* will throw NS_ERROR_FAILURE if not found */
+  imIAccount getAccountByNumericId(in PRUint32 aAccountId);
+
+  nsISimpleEnumerator getAccounts();
+
+  /* will fire the event account-added */
+  imIAccount createAccount(in AUTF8String aName, in AUTF8String aPrpl);
+
+  /* will fire the event account-removed */
+  void deleteAccount(in AUTF8String aAccountId);
+};
+
+/*
+ account related notifications sent to nsIObserverService:
+   - account-added: a new account has been created
+   - account-removed: the account has been deleted
+   - account-connecting: the account is being connected
+   - account-connected: the account is now connected
+   - account-connect-error: the account is disconnect with an error.
+     (before account-disconnecting)
+   - account-disconnecting: the account is being disconnected
+   - account-disconnected: the account is now disconnected
+   - account-updated: when some settings have changed
+   - account-list-updated: when the list of account is reordered.
+   These events can be watched using an nsIObserver.
+   The associated imIAccount will be given as a parameter
+   (except for account-list-updated).
+*/
--- a/chat/components/public/imIContactsService.idl
+++ b/chat/components/public/imIContactsService.idl
@@ -39,35 +39,52 @@
 #include "nsIObserver.idl"
 #include "nsISimpleEnumerator.idl"
 #include "purpleIConversation.idl"
 
 interface imITag;
 interface imIContact;
 interface imIBuddy;
 interface imIAccountBuddy;
-interface purpleIProtocol;
+interface imIAccount;
+interface prplIProtocol;
 
 [scriptable, uuid(f1619b49-310b-47aa-ab1c-238aba084c62)]
 interface imIContactsService: nsISupports {
   void initContacts();
   void unInitContacts();
 
   imIContact getContactById(in long aId);
   imIBuddy getBuddyById(in long aId);
   imIBuddy getBuddyByNameAndProtocol(in AUTF8String aNormalizedName,
-                                     in purpleIProtocol aPrpl);
+                                     in prplIProtocol aPrpl);
 
   // These 3 functions are called by the protocol plugins when
   // synchronizing the buddy list with the server stored list,
   // or after user operations have been performed.
   void accountBuddyAdded(in imIAccountBuddy aAccountBuddy);
   void accountBuddyRemoved(in imIAccountBuddy aAccountBuddy);
   void accountBuddyMoved(in imIAccountBuddy aAccountBuddy,
                          in imITag aOldTag, in imITag aNewTag);
+
+  // These methods are called by the imIAccountsService implementation
+  // to keep the accounts table in sync with accounts stored in the
+  // preferences.
+
+  // Called when an account is created or loaded to store the new
+  // account or ensure it doesn't conflict with an existing account
+  // (to detect database corruption).
+  // Will throw if a stored account has the id aId but a different
+  // username or prplId.
+  void storeAccount(in PRUint32 aId, in AUTF8String aUserName,
+                    in AUTF8String aPrplId);
+  // Check if an account id already exists in the database.
+  boolean accountIdExists(in PRUint32 aId);
+  // Called when deleting an account to remove it from blist.sqlite.
+  void forgetAccount(in PRUint32 aId);
 };
 
 [scriptable, uuid(f799a9c2-23f2-4fd1-96fb-515bad238f8c)]
 interface imITagsService: nsISupports {
   // Create a new tags or return the existing one if it already exists
   imITag createTag(in AUTF8String aName);
   // Get an existing tag by (numeric) id. Returns null if not found.
   imITag getTagById(in long aId);
@@ -250,17 +267,17 @@ interface imIContact: imIStatusInfo {
   void notifyObservers(in nsISupports aObj, in string aEvent,
                        [optional] in wstring aData);
 };
 
 
 [scriptable, uuid(fea582a0-3839-4d80-bcab-0ff82ae8f97f)]
 interface imIBuddy: imIStatusInfo {
   readonly attribute long id;
-  readonly attribute purpleIProtocol protocol;
+  readonly attribute prplIProtocol protocol;
   readonly attribute AUTF8String userName; // may be formatted
   readonly attribute AUTF8String normalizedName; // normalized userName
   // The optional server alias is in displayName (inherited from imIStatusInfo)
   // displayName = serverAlias || userName.
 
   readonly attribute imIContact contact;
   readonly attribute imIAccountBuddy preferredAccountBuddy;
   void getAccountBuddies([optional] out unsigned long accountBuddyCount,
@@ -325,17 +342,17 @@ interface imIBuddy: imIStatusInfo {
  * its tags and nsObserverService.
  */
 [scriptable, uuid(114d24ff-56a1-4fd6-9822-4992efb6e036)]
 interface imIAccountBuddy: imIStatusInfo {
   // The setter is for internal use only. buddy will be set by the
   // Contacts service when accountBuddyAdded is called on this
   // instance of imIAccountBuddy.
            attribute imIBuddy buddy;
-  readonly attribute purpleIAccount account;
+  readonly attribute imIAccount account;
   // Setting the tag will move the buddy to a different group on the
   // server-stored buddy list.
            attribute imITag tag;
   readonly attribute AUTF8String userName;
   readonly attribute AUTF8String normalizedName;
            attribute AUTF8String serverAlias;
 
   // remove the buddy from the buddy list of this account.
--- a/chat/components/public/imIConversationsService.idl
+++ b/chat/components/public/imIConversationsService.idl
@@ -88,11 +88,11 @@ interface imIConversationsService: nsISu
   void getUIConversations([optional] out unsigned long conversationCount,
                           [retval, array, size_is(conversationCount)] out imIConversation conversations);
   imIConversation getUIConversation(in purpleIConversation aConversation);
   imIConversation getUIConversationByContactId(in long aId);
 
   nsISimpleEnumerator getConversations();
   purpleIConversation getConversationById(in unsigned long aId);
   purpleIConversation getConversationByNameAndAccount(in AUTF8String aName,
-                                                      in purpleIAccount aAccount,
+                                                      in imIAccount aAccount,
                                                       in boolean aIsChat);
 };
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imICoreService.idl
@@ -0,0 +1,54 @@
+/* ***** 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 the Instantbird messenging client, released
+ * 2007.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * 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 ***** */
+
+#include "nsISupports.idl"
+#include "imIUserStatusInfo.idl"
+#include "nsISimpleEnumerator.idl"
+#include "prplIProtocol.idl"
+
+[scriptable, uuid(205d4b2b-1ccf-4879-9ef1-f08942566151)]
+interface imICoreService: nsISupports {
+  void init();
+  void quit(); // will emit a prpl-quit notification.
+
+  // returns an enumerator on a pplIProtocol array
+  nsISimpleEnumerator getProtocols();
+
+  prplIProtocol getProtocolById(in AUTF8String aProtocolId);
+
+  readonly attribute imIUserStatusInfo globalUserStatus;
+};
new file mode 100644
--- /dev/null
+++ b/chat/components/public/imIUserStatusInfo.idl
@@ -0,0 +1,76 @@
+/* ***** 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 the Instantbird messenging client, released
+ * 2010.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * 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 ***** */
+
+#include "nsISupports.idl"
+#include "nsIObserver.idl"
+
+//forward declarations
+interface nsILocalFile;
+interface nsIFileURL;
+
+[scriptable, uuid(817918fa-1f4b-4254-9cdb-f906da91c45d)]
+interface imIUserStatusInfo: nsISupports {
+
+  readonly attribute AUTF8String statusText;
+
+  // See imIStatusInfo in imIContactsService.idl for the values.
+  readonly attribute short statusType;
+
+  // Only works with STATUS_OFFLINE, STATUS_UNAVAILABLE, STATUS_AWAY,
+  // STATUS_AVAILABLE and STATUS_INVISIBLE.
+  // - When called with the status type STATUS_UNSET, only the status
+  //   message will be changed.
+  // - When called with STATUS_OFFLINE, the aMessage parameter is ignored.
+  void setStatus(in short aStatus, in AUTF8String aMessage);
+
+  /* Will fire an user-icon-changed notificaton. */
+  void setUserIcon(in nsILocalFile aIconFile);
+
+  nsIFileURL getUserIcon();
+
+  /* The setter will fire an user-display-name-changed notificaton. */
+  attribute AUTF8String displayName;
+
+  void addObserver(in nsIObserver aObserver);
+  void removeObserver(in nsIObserver aObserver);
+  /* Observers will receive the following notifications:
+   *   status-changed (when either the status type or text has changed)
+   *   user-icon-changed
+   *   user-display-name-changed
+   *   idle-time-changed
+   */
+};
rename from purple/purplexpcom/public/purpleIProtocol.idl
rename to chat/components/public/prplIProtocol.idl
--- a/purple/purplexpcom/public/purpleIProtocol.idl
+++ b/chat/components/public/prplIProtocol.idl
@@ -32,32 +32,25 @@
  * 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 ***** */
 
 #include "nsISupports.idl"
 #include "nsISimpleEnumerator.idl"
-
-/*
- * This is the libpurple protocol
- */
+#include "imIAccount.idl"
 
-%{C++
-#pragma GCC visibility push(default)
-#include <libpurple/prpl.h>
-#pragma GCC visibility pop
-%}
+[scriptable, uuid(7d302db0-3813-4c51-8372-c7eb5fc9f3d3)]
+interface prplIProtocol: nsISupports {
+  // aId is the prpl id, this method is used so that classes
+  // implementing several protocol plugins can know which protocol is
+  // desired for this instance.
+  void init(in AUTF8String aId);
 
-[ptr] native PurpleNativePluginProtocolInfo(PurplePluginProtocolInfo);
-interface purpleIAccount;
-
-[scriptable, uuid(0a51b511-4ce7-4f4c-a109-43023b72fc5a)]
-interface purpleIProtocol: nsISupports {
   readonly attribute AUTF8String name;
   readonly attribute AUTF8String id;
   readonly attribute AUTF8String normalizedName;
 
   // returns a chrome URI pointing to a folder that contains the files:
   // icon.png icon32.png and icon48.png
   readonly attribute AUTF8String iconBaseURI;
 
@@ -118,27 +111,23 @@ interface purpleIProtocol: nsISupports {
   // are native to this protocol.
   // Used as a hint that unknown commands should not be sent as messages.
   readonly attribute boolean slashCommandsNative;
 
   // Indicates if the protocol plugin can use a purpleIProxy for the
   // account, or uses the Mozilla proxy settings for all accounts.
   readonly attribute boolean usePurpleProxy;
 
-  // The key is the unique identifier needed to get account data
-  // stored in the preferences.
-  purpleIAccount getAccount(in AUTF8String aKey, in AUTF8String aName);
+  // Get the protocol specific part of an already initialized
+  // imIAccount instance.
+  prplIAccount getAccount(in imIAccount aImAccount);
 };
 
 [scriptable, uuid(20c4971a-f7c2-4781-8e85-69fee7b83a3d)]
 interface purpleIUsernameSplit: nsISupports {
   readonly attribute AUTF8String label;
   readonly attribute AUTF8String defaultValue;
   readonly attribute char separator;
 
   /* reverse is PR_TRUE if the separator should be found starting at
      the end of the string, PR_FALSE otherwise. */
   readonly attribute PRBool reverse;
 };
-
-%{C++
-#define JS_PROTOCOL_PLUGIN_CATEGORY "js-protocol-plugin"
-%}
--- a/chat/components/public/purpleIConversation.idl
+++ b/chat/components/public/purpleIConversation.idl
@@ -36,32 +36,32 @@
  * ***** END LICENSE BLOCK ***** */
 
 
 #include "nsISupports.idl"
 #include "nsISimpleEnumerator.idl"
 #include "nsIObserver.idl"
 
 interface imIAccountBuddy;
-interface purpleIAccount;
+interface imIAccount;
 interface nsIURI;
 interface nsIDOMDocument;
 
 /*
  * This is the XPCOM purple conversation component, a proxy for PurpleConversation.
  */
 
 [scriptable, uuid(74fca337-b8e7-4e5d-81cd-8aba3dba9208)]
 interface purpleIConversation: nsISupports {
 
   /* Indicate if this conversation implements purpleIConvIM or purpleIConvChat */
   readonly attribute boolean isChat;
 
   /* The account used for this conversation */
-  readonly attribute purpleIAccount account;
+  readonly attribute imIAccount account;
 
   /* The name of the conversation, typically in English */
   readonly attribute AUTF8String name;
 
   /* The normalized name of the conversation (suitable for a log file name) */
   readonly attribute AUTF8String normalizedName;
 
   /* The title of the conversation, typically localized */
--- a/chat/components/src/Makefile.in
+++ b/chat/components/src/Makefile.in
@@ -37,20 +37,23 @@
 DEPTH		= ../../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 EXTRA_COMPONENTS = \
+		imAccounts.manifest \
+		imCommands.js imCommands.manifest \
 		imContacts.js imContacts.manifest \
 		imConversations.js imConversations.manifest \
-		imCommands.js imCommands.manifest \
+		imCore.js imCore.manifest \
 		logger.manifest \
 		smileProtocolHandler.js smileProtocolHandler.manifest \
 		$(NULL)
 
 EXTRA_PP_COMPONENTS = \
+		imAccounts.js \
 		logger.js \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imAccounts.js
@@ -0,0 +1,741 @@
+/* ***** 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 the Instantbird messenging client, released
+ * 2011.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Romain Bezut <romain@bezut.info>
+ *
+ * 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+Cu.import("resource:///modules/imServices.jsm");
+
+const kPrefAutologinPending = "messenger.accounts.autoLoginPending";
+const kPrefMessengerAccounts = "messenger.accounts";
+const kPrefAccountPrefix = "messenger.account.";
+const kAccountKeyPrefix = "account";
+const kAccountOptionPrefPrefix = "options.";
+const kPrefAccountName = "name";
+const kPrefAccountPrpl = "prpl";
+const kPrefAccountPassword = "password";
+const kPrefAccountAutoLogin = "autoLogin";
+const kPrefAccountAlias = "alias";
+const kPrefAccountFirstConnectionState = "firstConnectionState";
+
+var SavePrefTimer = {
+  saveNow: function() {
+    if (this._timer) {
+      clearTimeout(this._timer);
+      this._timer = null;
+    }
+    Services.prefs.savePrefFile(null);
+  },
+  _timer: null,
+  unInitTimer: function() {
+    if (this._timer)
+      this.saveNow();
+  },
+  initTimer: function() {
+    if (!this._timer)
+      this._timer = setTimeout(this.saveNow.bind(this), 5000);
+  }
+};
+
+var AutoLoginCounter = {
+  _count: 0,
+  startAutoLogin: function() {
+    ++this._count;
+    if (this._count != 1)
+      return;
+    Services.prefs.setIntPref(kPrefAutologinPending, Date.now() / 1000);
+    SavePrefTimer.saveNow();
+  },
+  finishedAutoLogin: function() {
+    --this._count;
+    if (this._count != 0)
+      return;
+    Services.prefs.deleteBranch(kPrefAutologinPending);
+    SavePrefTimer.initTimer();
+  }
+};
+
+function UnknownProtocol(aPrplId)
+{
+  this.id = aPrplId;
+}
+UnknownProtocol.prototype = {
+  __proto__: ClassInfo("prplIProtocol", "Unknown protocol"),
+  get name() "",
+  get normalizedName() this.name,
+  get iconBaseURI() "chrome://instantbird/skin/prpl-unknown/",
+  getOptions: function() EmptyEnumerator,
+  getUsernameSplit: function() EmptyEnumerator,
+  get usernameEmptyText() "",
+
+  getAccount: function(aKey, aName) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+  accountExists: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+
+  // false seems an acceptable default for all options
+  // (they should never be called anyway).
+  get uniqueChatName() false,
+  get chatHasTopic() false,
+  get noPassword() false,
+  get newMailNotification() false,
+  get imagesInIM() false,
+  get passwordOptional() true,
+  get usePointSize() true,
+  get registerNoScreenName() false,
+  get slashCommandsNative() false,
+  get usePurpleProxy() false
+};
+
+// aName and aPrplId are provided as parameter only if this is a new
+// account that doesn't exist in the preferences. In this case, these
+// 2 values should be stored.
+function imAccount(aKey, aName, aPrplId)
+{
+  if (aKey.indexOf(kAccountKeyPrefix) != 0)
+    throw Cr.NS_ERROR_INVALID_ARG;
+
+  this.id = aKey;
+  this.numericId = parseInt(aKey.substr(kAccountKeyPrefix.length));
+  this.prefBranch = Services.prefs.getBranch(kPrefAccountPrefix + aKey + ".");
+
+  if (aName) {
+    this.name = aName;
+    this.prefBranch.setCharPref(kPrefAccountName, aName);
+    this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_UNKNOWN;
+  }
+  else
+    this.name = this.prefBranch.getCharPref(kPrefAccountName);
+
+  let prplId = aPrplId;
+  if (prplId)
+    this.prefBranch.setCharPref(kPrefAccountPrpl, prplId);
+  else
+    prplId = this.prefBranch.getCharPref(kPrefAccountPrpl);
+
+  // Get the protocol plugin, or fallback to an UnknownProtocol instance.
+  this.protocol = Services.core.getProtocolById(prplId);
+  if (!this.protocol) {
+    this.protocol = new UnknownProtocol(prplId);
+    this._connectionErrorReason = Ci.imIAccount.ERROR_UNKNOWN_PRPL;
+    return;
+  }
+
+  // Ensure the account is correctly stored in blist.sqlite.
+  Services.contacts.storeAccount(this.numericId, this.name, prplId);
+
+  // Get the prplIAccount from the protocol plugin.
+  this.prplAccount = this.protocol.getAccount(this);
+
+  // Send status change notifications to the account.
+  this.observedStatusInfo = null; // (To execute the setter).
+
+  // If we have never finished the first connection attempt for this account,
+  // mark the account as having caused a crash.
+  if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_PENDING)
+    this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_CRASHED;
+
+  // Check for errors that should prevent connection attempts.
+  if (this._passwordRequired && !this.password)
+    this._connectionErrorReason = Ci.imIAccount.ERROR_MISSING_PASSWORD;
+  else if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_CRASHED)
+    this._connectionErrorReason = Ci.imIAccount.ERROR_CRASHED;
+}
+
+imAccount.prototype = {
+  __proto__: ClassInfo(["imIAccount", "prplIAccount"], "im account object"),
+
+  name: "",
+  id: "",
+  numericId: 0,
+  protocol: null,
+  prplAccount: null,
+  connectionState: Ci.imIAccount.STATE_DISCONNECTED,
+  connectionStateMsg: "",
+  connectionErrorMessage: "",
+  _connectionErrorReason: Ci.prplIAccount.NO_ERROR,
+  get connectionErrorReason() {
+    if (this._connectionErrorReason != Ci.prplIAccount.NO_ERROR)
+      return this._connectionErrorReason;
+    else
+      return this.prplAccount.connectionErrorReason;
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "account-connect-progress")
+      this.connectionStateMsg = aData;
+    else if (aTopic == "account-connecting") {
+      if (this.prplAccount.connectionErrorReason != Ci.prplIAccount.NO_ERROR) {
+        delete this.connectionErrorMessage;
+        if (this.timeOfNextReconnect - Date.now() > 1000) {
+          // This is a manual reconnection, reset the auto-reconnect stuff
+          this.timeOfLastConnect = 0;
+          this.cancelReconnection();
+        }
+      }
+      if (this.firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_OK)
+        this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_PENDING;
+      this.connectionState = Ci.imIAccount.STATE_CONNECTING;
+    }
+    else if (aTopic == "account-connected") {
+      this.connectionState = Ci.imIAccount.STATE_CONNECTED;
+      this._finishedAutoLogin();
+      this.timeOfLastConnect = Date.now();
+      if (this.firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_OK)
+        this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_OK;
+      delete this.connectionStateMsg;
+    }
+    else if (aTopic == "account-disconnecting") {
+      this.connectionState = Ci.imIAccount.STATE_DISCONNECTING;
+      this.connectionErrorMessage = aData;
+      delete this.connectionStateMsg;
+      this._finishedAutoLogin();
+
+      let firstConnectionState = this.firstConnectionState;
+      if (firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_OK &&
+          firstConnectionState != Ci.imIAccount.FIRST_CONNECTION_CRASHED)
+        this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_UNKNOWN;
+
+      let connectionErrorReason = this.prplAccount.connectionErrorReason;
+      if (connectionErrorReason != Ci.prplIAccount.NO_ERROR) {
+        if (connectionErrorReason == Ci.prplIAccount.ERROR_NETWORK_ERROR ||
+            connectionErrorReason == Ci.prplIAccount.ERROR_ENCRYPTION_ERROR)
+          this._startReconnectTimer();
+        this._sendNotification("account-connect-error");
+      }
+    }
+    else if (aTopic == "account-disconnected")
+      this.connectionState = Ci.imIAccount.STATE_DISCONNECTED;
+    else
+      throw Cr.NS_ERROR_UNEXPECTED;
+    this._sendNotification(aTopic, aData);
+  },
+
+  _observedStatusInfo: null,
+  get observedStatusInfo() this._observedStatusInfo,
+  _statusObserver: null,
+  set observedStatusInfo(aUserStatusInfo) {
+    if (!this.prplAccount)
+      return;
+    if (this._statusObserver)
+      this.statusInfo.removeObserver(this._statusObserver);
+    this._observedStatusInfo = aUserStatusInfo;
+    if (!this._statusObserver) {
+      let prplAccount = this.prplAccount;
+      this._statusObserver = {
+        observe: function(aSubject, aTopic, aData) {
+          prplAccount.observe(aSubject, aTopic, aData);
+        }
+      };
+    }
+    this.statusInfo.addObserver(this._statusObserver);
+  },
+  get statusInfo() this._observedStatusInfo || Services.core.globalUserStatus,
+
+  reconnectAttempt: 0,
+  timeOfLastConnect: 0,
+  timeOfNextReconnect: 0,
+  _reconnectTimer: null,
+  _startReconnectTimer: function() {
+    /* If the last successful connection is older than 10 seconds, reset the
+     number of reconnection attemps. */
+    const kTimeBeforeSuccessfulConnection = 10;
+    if (this.timeOfLastConnect &&
+        this.timeOfLastConnect + kTimeBeforeSuccessfulConnection * 1000 < Date.now()) {
+      delete this.reconnectAttempt;
+      delete this.timeOfLastConnect;
+    }
+
+    let timers =
+      Services.prefs.getCharPref("messenger.accounts.reconnectTimer").split(",");
+    let delay = timers[Math.max(this.reconnectAttempt, timers.length - 1)];
+    let msDelay = parseInt(delay) * 1000;
+    ++this.reconnectAttempt;
+    this.timeOfNextReconnect = Date.now() + msDelay;
+    this._reconnectTimer = setTimeout(this.connect.bind(this), msDelay);
+  },
+
+  _sendNotification: function(aTopic, aData) {
+    Services.obs.notifyObservers(this, aTopic, aData);
+  },
+
+  get firstConnectionState() {
+    try {
+      return this.prefBranch.getIntPref(kPrefAccountFirstConnectionState);
+    } catch (e) {
+      return Ci.imIAccount.FIRST_CONNECTION_OK;
+    }
+  },
+  set firstConnectionState(aState) {
+    if (aState == Ci.imIAccount.FIRST_CONNECTION_OK)
+      this.prefBranch.deleteBranch(kPrefAccountFirstConnectionState);
+    else {
+      this.prefBranch.setIntPref(kPrefAccountFirstConnectionState, aState);
+      // We want to save this pref immediately when trying to connect.
+      if (aState == Ci.imIAccount.FIRST_CONNECTION_PENDING)
+        SavePrefTimer.saveNow();
+      else
+        SavePrefTimer.initTimer();
+    }
+  },
+
+  get normalizedName() this._ensurePrplAccount.normalizedName,
+
+  _sendUpdateNotification: function() {
+    this._sendNotification("account-updated");
+  },
+
+  set alias(val) {
+    if (val)
+      this.prefBranch.setCharPref(kPrefAccountAlias, val);
+    else
+      this.prefBranch.deleteBranch(kPrefAccountAlias);
+    this._sendUpdateNotification();
+  },
+  get alias() {
+    try {
+      return this.prefBranch.getCharPref(kPrefAccountAlias);
+    } catch (e) {
+      return "";
+    }
+  },
+
+  get password() {
+    try {
+      return this.prefBranch.getCharPref(kPrefAccountPassword);
+    } catch (e) {
+      return "";
+    }
+  },
+  get _passwordRequired()
+    !this.protocol.noPassword && !this.protocol.passwordOptional,
+  set password(aPassword) {
+    this.prefBranch.setCharPref(kPrefAccountPassword, aPassword);
+    if (aPassword &&
+        this._connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD) {
+      if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_CRASHED)
+        this._connectionErrorReason = Ci.imIAccount.ERROR_CRASHED;
+      else
+        this._connectionErrorReason = Ci.imIAccount.NO_ERROR;
+    }
+    else if (!aPassword && this._passwordRequired)
+      this._connectionErrorReason = Ci.imIAccount.ERROR_MISSING_PASSWORD;
+    this._sendUpdateNotification();
+  },
+
+  get autoLogin() {
+    let autoLogin = true;
+    try {
+      autoLogin = this.prefBranch.getBoolPref(kPrefAccountAutoLogin);
+    } catch (e) { }
+    return autoLogin;
+  },
+  set autoLogin(val) {
+    this.prefBranch.setBoolPref(kPrefAccountAutoLogin, val);
+    SavePrefTimer.initTimer();
+    this._sendUpdateNotification();
+  },
+  _autoLoginPending: false,
+  checkAutoLogin: function() {
+    // No auto-login if: the account has an error at the imIAccount level
+    // (unknown protocol, missing password, first connection crashed),
+    // the account is already connected or connecting, or autoLogin is off.
+    if (this._connectionErrorReason != Ci.prplIAccount.NO_ERROR ||
+        this.connecting || this.connected || !this.autoLogin)
+      return;
+
+    this._autoLoginPending = true;
+    AutoLoginCounter.startAutoLogin();
+    try {
+      this.connect();
+    } catch (e) {
+      Cu.reportError(e);
+      this._finishedAutoLogin();
+    }
+  },
+  _finishedAutoLogin: function() {
+    if (!this.hasOwnProperty("_autoLoginPending"))
+      return;
+    delete this._autoLoginPending;
+    AutoLoginCounter.finishedAutoLogin();
+  },
+
+  remove: function() {
+    this.unInit();
+    Services.contacts.forgetAccount(this.numericId);
+    this.prefBranch.deleteBranch("");
+  },
+  unInit: function() {
+    // remove any pending reconnection timer.
+    this.cancelReconnection();
+
+    // remove any pending autologin preference used for crash detection.
+    this._finishedAutoLogin();
+
+    // If the first connection was pending on quit, we set it back to unknown.
+    if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_PENDING)
+      this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_UNKNOWN;
+
+    // and make sure we cleanup the save pref timer.
+    SavePrefTimer.unInitTimer();
+
+    if (this.prplAccount)
+      this.prplAccount.unInit();
+
+    delete this.protocol;
+    delete this.prplAccount;
+  },
+
+  get _ensurePrplAccount() {
+    if (this.prplAccount)
+      return this.prplAccount;
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  connect: function() { this._ensurePrplAccount.connect(); },
+  disconnect: function() { this._ensurePrplAccount.disconnect(); },
+
+  get disconnected() this.connectionState == Ci.imIAccount.STATE_DISCONNECTED,
+  get connected() this.connectionState == Ci.imIAccount.STATE_CONNECTED,
+  get connecting() this.connectionState == Ci.imIAccount.STATE_CONNECTING,
+  get disconnecting() this.connectionState == Ci.imIAccount.STATE_DISCONNECTING,
+
+  cancelReconnection: function() {
+    if (this._reconnectTimer) {
+      clearTimeout(this._reconnectTimer);
+      delete this._reconnectTimer;
+    }
+    delete this.reconnectAttempt;
+    delete this.timeOfNextReconnect;
+  },
+  createConversation: function(aName)
+    this._ensurePrplAccount.createConversation(aName),
+  addBuddy: function(aTag, aName) {
+    this._ensurePrplAccount.addBuddy(aTag, aName);
+  },
+  loadBuddy: function(aBuddy, aTag)
+    this._ensurePrplAccount.loadBuddy(aBuddy, aTag), // FIXME for unknown proto
+  requestBuddyInfo: function(aBuddyName) {
+    this._ensurePrplAccount.requestBuddyInfo(aBuddyName);
+  },
+  getChatRoomFields: function() this._ensurePrplAccount.getChatRoomFields(),
+  getChatRoomDefaultFieldValues: function(aDefaultChatName)
+    this._ensurePrplAccount.getChatRoomDefaultFieldValues(aDefaultChatName),
+  get canJoinChat() this.prplAccount ? this.prplAccount.canJoinChat : false,
+  joinChat: function(aComponents) {
+    this._ensurePrplAccount.joinChat(aComponents);
+  },
+  setBool: function(aName, aVal) {
+    this.prefBranch.setBoolPref(kAccountOptionPrefPrefix + aName, aVal);
+    if (this.prplAccount)
+      this.prplAccount.setBool(aName, aVal);
+    SavePrefTimer.initTimer();
+  },
+  setInt: function(aName, aVal) {
+    this.prefBranch.setIntPref(kAccountOptionPrefPrefix + aName, aVal);
+    if (this.prplAccount)
+      this.prplAccount.setInt(aName, aVal);
+    SavePrefTimer.initTimer();
+  },
+  setString: function(aName, aVal) {
+    this.prefBranch.setCharPref(kAccountOptionPrefPrefix + aName, aVal);
+    if (this.prplAccount)
+      this.prplAccount.setString(aName, aVal);
+    SavePrefTimer.initTimer();
+  },
+  save: function() { SavePrefTimer.saveNow(); },
+
+  get HTMLEnabled() this._ensurePrplAccount.HTMLEnabled,
+  get noBackgroundColors() this._ensurePrplAccount.noBackgroundColors,
+  get autoResponses() this._ensurePrplAccount.autoResponses,
+  get singleFormatting() this._ensurePrplAccount.singleFormatting,
+  get noNewlines() this._ensurePrplAccount.noNewlines,
+  get noFontSizes() this._ensurePrplAccount.noFontSizes,
+  get noUrlDesc() this._ensurePrplAccount.noUrlDesc,
+  get noImages() this._ensurePrplAccount.noImages,
+  get maxMessageLength() this._ensurePrplAccount.maxMessageLength,
+
+  get proxyInfo() this._ensurePrplAccount.proxyInfo,
+  set proxyInfo(val) { this._ensurePrplAccount.proxyInfo = val; }
+};
+
+function AccountsService() { }
+AccountsService.prototype = {
+  initAccounts: function() {
+    this._initAutoLoginStatus();
+    this._accounts = [];
+    this._accountsById = {};
+    for each (let account in this._accountList.split(",")) {
+      try {
+        account.trim();
+        if (!account)
+          throw Cr.NS_ERROR_INVALID_ARG;
+        let newAccount = new imAccount(account);
+        this._accounts.push(newAccount);
+        this._accountsById[newAccount.numericId] = newAccount;
+      } catch (e) {
+        Cu.reportError(e);
+        dump(e + " " + e.toSource() + "\n");
+      }
+    }
+
+    this._prefObserver = this.observe.bind(this);
+    Services.prefs.addObserver(kPrefMessengerAccounts, this._prefObserver, false);
+  },
+
+  _observingAccountListChange: true,
+  _prefObserver: null,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic != "nsPref:changed" || aData != kPrefMessengerAccounts ||
+       !this._observingAccountListChange)
+      return;
+
+    this._accounts =
+      this._accountList.split(",").map(String.trim)
+          .filter(function (k) k.indexOf(kAccountKeyPrefix) == 0)
+          .map(function (k) parseInt(k.substr(kAccountKeyPrefix.length)))
+          .map(this.getAccountByNumericId, this)
+          .filter(function (a) a);
+
+    Services.obs.notifyObservers(this, "account-list-updated", null);
+  },
+
+  get _accountList() Services.prefs.getCharPref(kPrefMessengerAccounts),
+  set _accountList(aNewList) {
+    this._observingAccountListChange = false;
+    Services.prefs.setCharPref(kPrefMessengerAccounts, aNewList);
+    delete this._observingAccountListChange;
+  },
+
+  unInitAccounts: function() {
+    for each (let account in this._accounts)
+      account.unInit();
+    delete this._accounts;
+    delete this._accountsById;
+    Services.prefs.removeObserver(kPrefMessengerAccounts, this._prefObserver);
+    delete this._prefObserver;
+  },
+
+  autoLoginStatus: Ci.imIAccountsService.AUTOLOGIN_ENABLED,
+  _initAutoLoginStatus: function() {
+    /* If auto-login is already disabled, do nothing */
+    if (this.autoLoginStatus != Ci.imIAccountsService.AUTOLOGIN_ENABLED)
+      return;
+
+    let prefs = Services.prefs;
+    if (!prefs.getIntPref("messenger.startup.action")) {
+      // the value 0 means that we start without connecting the accounts
+      this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_USER_DISABLED;
+      return;
+    }
+
+    /* Disable auto-login if we are running in safe mode */
+    if (Services.appinfo.inSafeMode) {
+      this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_SAFE_MODE;
+      return;
+    }
+
+    /* If the application was started offline, disable auto-login */
+    if (!Services.io.manageOfflineStatus && Services.io.offline) {
+      this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_START_OFFLINE;
+      return;
+    }
+
+    /* Check if we crashed at the last startup during autologin */
+    let autoLoginPending;
+    if (prefs.getPrefType(kPrefAutologinPending) == prefs.PREF_INVALID ||
+        !(autoLoginPending = prefs.getIntPref(kPrefAutologinPending))) {
+      // if the pref isn't set, then we haven't crashed: keep autologin enabled
+      return;
+    }
+
+    // Last autologin hasn't finished properly.
+    // For now, assume it's because of a crash.
+    this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_CRASH;
+    prefs.deleteBranch(kPrefAutologinPending);
+
+#ifdef MOZ_CRASHREPORTER
+    try {
+      // Try to get more info with breakpad
+      let lastCrashTime = 0;
+
+      /* Locate the LastCrash file */
+      let lastCrash =
+        Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties)
+        .get("UAppData", Ci.nsILocalFile);
+      lastCrash.append("Crash Reports");
+      lastCrash.append("LastCrash");
+      if (lastCrash.exists()) {
+        /* Ok, the file exists, now let's try to read it */
+        let is = Cc["@mozilla.org/network/file-input-stream;1"]
+                 .createInstance(Ci.nsIFileInputStream);
+        let sis = Cc["@mozilla.org/scriptableinputstream;1"]
+                  .createInstance(Ci.nsIScriptableInputStream);
+        is.init(lastCrash, -1, 0, 0);
+        sstream.init(sis);
+
+        lastCrashTime = parseInt(sstream.read(lastCrash.fileSize));
+
+        sstream.close();
+        fstream.close();
+      }
+      // The file not existing is totally acceptable, it just means that
+      // either we never crashed or breakpad is not enabled.
+      // In this case, lastCrashTime will keep its 0 initialization value.
+
+      /*dump("autoLoginPending = " + autoLoginPending +
+             ", lastCrash = " + lastCrashTime +
+             ", difference = " + lastCrashTime - autoLoginPending + "\n");*/
+
+      if (lastCrashTime < autoLoginPending) {
+        // the last crash caught by breakpad is older than our last autologin
+        // attempt.
+        // If breakpad is currently enabled, we can be confident that
+        // autologin was interrupted for an exterior reason
+        // (application killed by the user, power outage, ...)
+        try {
+          Services.appinfo.QueryInterface(Ci.nsICrashReporter)
+                  .annotateCrashReport("=", "");
+        } catch (e) {
+          // This should fail with NS_ERROR_INVALID_ARG if breakpad is enabled,
+          // and NS_ERROR_NOT_INITIALIZED if it is not.
+          if (e != Cr.NS_ERROR_NOT_INITIALIZED)
+            this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_ENABLED;
+        }
+      }
+    } catch (e) {
+      // if we failed to get the last crash time, then keep the
+      // AUTOLOGIN_CRASH value in mAutoLoginStatus and return.
+      return;
+    }
+#endif
+  },
+
+  processAutoLogin: function() {
+    if (Services.io.offline)
+      throw Cr.NS_ERROR_FAILURE;
+
+    for each (let account in this._accounts)
+      account.checkAutoLogin();
+
+    // Make sure autologin is now enabled, so that we don't display a
+    // message stating that it is disabled and asking the user if it
+    // should be processed now.
+    this.autoLoginStatus = Ci.imIAccountsService.AUTOLOGIN_ENABLED;
+
+    // Notify observers so that any message stating that autologin is
+    // disabled can be removed
+    Services.observers(this, "autologin-processed", null);
+  },
+
+  getAccountById: function(aAccountId) {
+    if (aAccountId.indexOf(kAccountKeyPrefix) != 0)
+      throw Cr.NS_ERROR_INVALID_ARG;
+
+    let id = parseInt(aAccountId.substr(kAccountKeyPrefix.length));
+    return this.getAccountByNumericId(id);
+  },
+
+  getAccountByNumericId: function(aAccountId) this._accountsById[aAccountId],
+  getAccounts: function() new nsSimpleEnumerator(this._accounts),
+
+  createAccount: function(aName, aPrpl) {
+    // Ensure an account with the same name and protocol doesn't already exist.
+    let prpl = Services.core.getProtocolById(aPrpl);
+    if (!prpl)
+      throw Cr.NS_ERROR_UNEXPECTED;
+    if (prpl.accountExists(aName)) {
+      Cu.reportError("Attempted to create a duplicate account!");
+      throw Cr.NS_ERROR_ALREADY_INITIALIZED;
+    }
+
+    /* First get a unique id for the new account. */
+    let id;
+    let found = true;
+    for (id = 1; found; ++id) {
+      if (this._accountsById.hasOwnProperty(id))
+        continue;
+
+      /* id isn't used by a known account, double check it isn't
+       already used in the sqlite database. This should never
+       happen, except if we have a corrupted profile. */
+      found = Services.contacts.accountIdExists(id);
+      if (found)
+        Services.console.logStringMessage("No account " + id + " but there is some data in the buddy list for an account with this number. Your profile may be corrupted.");
+    }
+
+    /* Actually create the new account. */
+    let key = kAccountKeyPrefix + id;
+    let account = new imAccount(key, aName, aPrpl);
+
+    /* Keep it in the local account lists. */
+    this._accounts.push(account);
+    this._accountsById[id] = account;
+
+    /* Save the account list pref. */
+    let list = this._accountList;
+    this._accountList = list ? list + "," + key : key;
+
+    Services.obs.notifyObservers(account, "account-added", null);
+    return account;
+  },
+
+  deleteAccount: function(aAccountId) {
+    let account = this.getAccountById(aAccountId);
+    if (!account)
+      throw Cr.NS_ERROR_INVALID_ARG;
+
+    let index = this._accounts.indexOf(account);
+    if (index == -1)
+      throw Cr.NS_ERROR_UNEXPECTED;
+
+    let id = account.numericId;
+    Services.obs.notifyObservers(account, "account-removed", null);
+    account.remove();
+    this._accounts.splice(index, 1);
+    delete this._accountsById[id];
+
+    /* Update the account list pref. */
+    let list = this._accountList;
+    this._accountList =
+      list.split(",").filter(function (k) k.trim() != aAccountId).join(",");
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.imIAccountsService]),
+  classDescription: "Accounts",
+  classID: Components.ID("{a94b5427-cd8d-40cf-b47e-b67671953e70}"),
+  contractID: "@instantbird.org/purple/accounts-service;1"
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([AccountsService]);
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imAccounts.manifest
@@ -0,0 +1,2 @@
+component {a94b5427-cd8d-40cf-b47e-b67671953e70} imAccounts.js
+contract @instantbird.org/purple/accounts-service;1 {a94b5427-cd8d-40cf-b47e-b67671953e70}
--- a/chat/components/src/imCommands.js
+++ b/chat/components/src/imCommands.js
@@ -146,17 +146,17 @@ CommandsService.prototype = {
         get helpString()
           bundle.formatStringFromName("statusCommand",
                                       [this.name,
                                        bundle.GetStringFromName(this.name)],
                                       2),
         usageContext: Ci.imICommand.CONTEXT_ALL,
         priority: Ci.imICommand.PRIORITY_HIGH,
         run: function(aMsg) {
-          Services.core.setStatus(statusValue, aMsg);
+          Services.core.globalUserStatus.setStatus(statusValue, aMsg);
           return true;
         }
       });
     }
   },
   unInitCommands: function() {
     delete this._commands;
   },
--- a/chat/components/src/imContacts.js
+++ b/chat/components/src/imContacts.js
@@ -34,48 +34,105 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 Cu.import("resource:///modules/imXPCOMUtils.jsm");
 Cu.import("resource:///modules/imServices.jsm");
 
+var gDBConnection = null;
+
+function getDBConnection()
+{
+  const NS_APP_USER_PROFILE_50_DIR = "ProfD";
+  let dbFile =
+    Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties)
+    .get(NS_APP_USER_PROFILE_50_DIR, Ci.nsIFile);
+  dbFile.append("blist.sqlite");
+
+  let conn =
+    Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService)
+                                        .openDatabase(dbFile);
+  if (!conn.connectionReady)
+    throw Cr.NS_ERROR_UNEXPECTED;
+
+  // Grow blist db in 512KB increments.
+  conn.setGrowthIncrement(512 * 1024, "");
+
+  // Create tables and indexes.
+  [
+    "CREATE TABLE IF NOT EXISTS accounts (" +
+      "id INTEGER PRIMARY KEY, " +
+      "name VARCHAR, " +
+      "prpl VARCHAR)",
+
+    "CREATE TABLE IF NOT EXISTS contacts (" +
+      "id INTEGER PRIMARY KEY, " +
+      "firstname VARCHAR, " +
+      "lastname VARCHAR, " +
+      "alias VARCHAR)",
+
+    "CREATE TABLE IF NOT EXISTS buddies (" +
+      "id INTEGER PRIMARY KEY, " +
+      "key VARCHAR NOT NULL, " +
+      "name VARCHAR NOT NULL, " +
+      "srv_alias VARCHAR, " +
+      "position INTEGER, " +
+      "icon BLOB, " +
+      "contact_id INTEGER)",
+    "CREATE INDEX IF NOT EXISTS buddies_contactindex " +
+      "ON buddies (contact_id)",
+
+    "CREATE TABLE IF NOT EXISTS tags (" +
+      "id INTEGER PRIMARY KEY, " +
+      "name VARCHAR UNIQUE NOT NULL, " +
+      "position INTEGER)",
+
+    "CREATE TABLE IF NOT EXISTS contact_tag (" +
+      "contact_id INTEGER NOT NULL, " +
+      "tag_id INTEGER NOT NULL)",
+    "CREATE INDEX IF NOT EXISTS contact_tag_contactindex " +
+      "ON contact_tag (contact_id)",
+    "CREATE INDEX IF NOT EXISTS contact_tag_tagindex " +
+      "ON contact_tag (tag_id)",
+
+    "CREATE TABLE IF NOT EXISTS account_buddy (" +
+      "account_id INTEGER NOT NULL, " +
+      "buddy_id INTEGER NOT NULL, " +
+      "status VARCHAR, " +
+      "tag_id INTEGER)",
+    "CREATE INDEX IF NOT EXISTS account_buddy_accountindex " +
+      "ON account_buddy (account_id)",
+    "CREATE INDEX IF NOT EXISTS account_buddy_buddyindex " +
+      "ON account_buddy (buddy_id)"
+  ].forEach(conn.executeSimpleSQL);
+
+  return conn;
+}
+
 // Wrap all the usage of DBConn inside a transaction that will be
 // commited automatically at the end of the event loop spin so that
 // we flush buddy list data to disk only once per event loop spin.
 var gDBConnWithPendingTransaction = null;
 this.__defineGetter__("DBConn", function() {
   if (gDBConnWithPendingTransaction)
     return gDBConnWithPendingTransaction;
 
-  let conn = Services.core.storageConnection;
-  gDBConnWithPendingTransaction = conn;
-  conn.beginTransaction();
+  if (!gDBConnection)
+    gDBConnection = getDBConnection();
+  gDBConnWithPendingTransaction = gDBConnection;
+  gDBConnection.beginTransaction();
   executeSoon(function() {
     gDBConnWithPendingTransaction.commitTransaction();
     gDBConnWithPendingTransaction = null;
   });
-  return conn;
+  return gDBConnection;
 });
 
-var AccountsById = { };
-function getAccountById(aId) {
-  if (AccountsById.hasOwnProperty(aId))
-    return AccountsById[aId];
-
-  let account = null;
-  try {
-    account = Services.core.getAccountByNumericId(aId);
-  } catch (x) { /* Not found */ }
-
-  AccountsById[aId] = account;
-  return account;
-}
-
 function TagsService() { }
 TagsService.prototype = {
   get wrappedJSObject() this,
   createTag: function(aName) {
     // If the tag already exists, we don't want to create a duplicate.
     let tag = this.getTagByName(aName);
     if (tag)
       return tag;
@@ -1149,35 +1206,35 @@ ContactsService.prototype = {
     while (statement.executeStep())
       new Buddy(statement.getInt32(0), statement.getUTF8String(1),
                 statement.getUTF8String(2), statement.getUTF8String(3),
                 statement.getInt32(4));
     // FIXME is there a way to enforce that all AccountBuddies of a Buddy have the same protocol?
 
     statement = DBConn.createStatement("SELECT account_id, buddy_id, tag_id FROM account_buddy");
     while (statement.executeStep()) {
-      let account = getAccountById(statement.getInt32(0));
+      let account =
+        Services.accounts.getAccountByNumericId(statement.getInt32(0));
       let buddy = BuddiesById[statement.getInt32(1)];
       let tag = TagsById[statement.getInt32(2)];
       try {
         let ab = account.loadBuddy(buddy, tag);
         if (ab)
           buddy._addAccount(ab, tag);
       } catch (e) {
         // FIXME ab shouldn't be NULL (once purpleAccount is finished)
-        // It currently doesn't work write with unknown protocols.
+        // It currently doesn't work right with unknown protocols.
         Components.utils.reportError(e);
         dump(e + "\n");
       }
     }
 
     otherContactsTag._initHiddenTags();
   },
   unInitContacts: function() {
-    AccountsById = { };
     Tags = [];
     TagsById = { };
     // Avoid shutdown leaks caused by references to native components
     // implementing imIAccountBuddy.
     for each (let buddy in BuddiesById)
       buddy.destroy();
     BuddiesById = { };
     ContactsById = { };
@@ -1261,16 +1318,55 @@ ContactsService.prototype = {
     statement.params.oldTagId = aOldTag.id;
     statement.params.newTagId = aNewTag.id;
     statement.execute();
 
     buddy.observe(aAccountBuddy, "account-buddy-moved");
     ContactsById[buddy.contact.id]._moved(aOldTag, aNewTag);
   },
 
+  storeAccount: function(aId, aUserName, aPrplId) {
+    let statement =
+      DBConn.createStatement("SELECT name, prpl FROM accounts WHERE id = :id");
+    statement.params.id = aId;
+    if (statement.executeStep()) {
+      if (statement.getUTF8String(0) == aUserName &&
+          statement.getUTF8String(1) == aPrplId)
+        return; // The account is already stored correctly.
+      throw Cr.NS_ERROR_UNEXPECTED; // Corrupted database?!?
+    }
+
+    // Actually store the account.
+    statement = DBConn.createStatement("INSERT INTO accounts (id, name, prpl) " +
+                                       "VALUES(:id, :userName, :prplId)");
+    statement.params.id = aId;
+    statement.params.userName = aUserName;
+    statement.params.prplId = aPrplId;
+    statement.execute();
+  },
+  accountIdExists: function(aId) {
+    let statement =
+      DBConn.createStatement("SELECT id FROM accounts WHERE id = :id");
+    statement.params.id = aId;
+    return statement.executeStep();
+  },
+  forgetAccount: function(aId) {
+    let statement =
+      DBConn.createStatement("DELETE FROM accounts WHERE id = :accountId");
+    statement.params.accountId = aId;
+    statement.execute();
+
+    // removing the account from the accounts table is not enought,
+    // we need to remove all the associated account_buddy entries too
+    statement = DBConn.createStatement("DELETE FROM account_buddy " +
+                                       "WHERE account_id = :accountId");
+    statement.params.accountId = aId;
+    statement.execute();
+  },
+
   QueryInterface: XPCOMUtils.generateQI([Ci.imIContactsService]),
   classDescription: "Contacts",
   classID: Components.ID("{8c3725dd-ee26-489d-8135-736015af8c7f}"),
   contractID: "@instantbird.org/purple/contacts-service;1"
 };
 
 const NSGetFactory = XPCOMUtils.generateNSGetFactory([ContactsService,
                                                       TagsService]);
--- a/chat/components/src/imConversations.js
+++ b/chat/components/src/imConversations.js
@@ -127,16 +127,17 @@ UIConversation.prototype = {
     if (this._observedContact) {
       this._observedContact.removeObserver(this);
       aContactId.value = this._observedContact.id;
       delete this._observedContact;
     }
     else
       aContactId.value = 0;
 
+    delete this._currentTargetId;
     this.notifyObservers(this, "ui-conversation-closed");
     Services.obs.notifyObservers(this, "ui-conversation-closed", null);
     return true;
   },
 
   _unreadMessageCount: 0,
   get unreadMessageCount() this._unreadMessageCount,
   _unreadTargetedMessageCount: 0,
@@ -160,16 +161,19 @@ UIConversation.prototype = {
     this._lastNotifiedUnreadCount = this._unreadIncomingMessageCount;
   },
   getMessages: function(aMessageCount) {
     if (aMessageCount)
       aMessageCount.value = this._messages.length;
     return this._messages;
   },
   checkClose: function() {
+    if (!this._currentTargetId)
+      return true; // already closed.
+
     if (!Services.prefs.getBoolPref("messenger.conversations.alwaysClose") &&
         (this.isChat && !this.left ||
          !this.isChat && this.unreadIncomingMessageCount != 0))
       return false;
 
     this.close();
     return true;
   },
@@ -282,16 +286,17 @@ UIConversation.prototype = {
   unInit: function() {
     for each (let conv in this._purpleConv)
       conv.unInit();
     this._purpleConv = {}; // Prevent .close from failing.
   },
   close: function() {
     for each (let conv in this._purpleConv)
       conv.close();
+    delete this._currentTargetId;
     this.notifyObservers(this, "ui-conversation-closed");
     Services.obs.notifyObservers(this, "ui-conversation-closed", null);
   },
   addObserver: function(aObserver) {
     if (this._observers.indexOf(aObserver) == -1)
       this._observers.push(aObserver);
   },
   removeObserver: function(aObserver) {
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imCore.js
@@ -0,0 +1,368 @@
+/* ***** 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 the Instantbird messenging client, released
+ * 2011.
+ *
+ * The Initial Developer of the Original Code is
+ * Florian QUEZE <florian@instantbird.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Cu.import("resource:///modules/imServices.jsm");
+Cu.import("resource:///modules/imXPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "categoryManager",
+                                   "@mozilla.org/categorymanager;1",
+                                   "nsICategoryManager");
+
+const kQuitApplicationGranted = "quit-application-granted";
+const kProtocolPluginCategory = "im-protocol-plugin";
+
+const kPrefReportIdle =        "messenger.status.reportIdle";
+const kPrefUserIconFilename =  "messenger.status.userIconFileName";
+const kPrefUserDisplayname =   "messenger.status.userDisplayName";
+const kPrefTimeBeforeIdle =    "messenger.status.timeBeforeIdle";
+const kPrefAwayWhenIdle =      "messenger.status.awayWhenIdle";
+const kPrefDefaultMessage =    "messenger.status.defaultIdleAwayMessage";
+
+const NS_IOSERVICE_GOING_OFFLINE_TOPIC = "network:offline-about-to-go-offline";
+const NS_IOSERVICE_OFFLINE_STATUS_TOPIC = "network:offline-status-changed";
+
+function UserStatus()
+{
+  this._observers = [];
+
+  if (Services.prefs.getBoolPref(kPrefReportIdle))
+    this._addIdleObserver();
+  Services.prefs.addObserver(kPrefReportIdle, this, false);
+
+  if (Services.io.offline)
+    this._offline = true;
+  Services.obs.addObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, false);
+  Services.obs.addObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false);
+}
+UserStatus.prototype = {
+  __proto__: ClassInfo("imIUserStatusInfo", "User status info"),
+
+  unInit: function() {
+    this._observers = [];
+    Services.prefs.removeObserver(kPrefReportIdle, this);
+    if (this._observingIdleness)
+      this._removeIdleObserver();
+    Services.obs.removeObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC);
+    Services.obs.removeObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+  },
+  _observingIdleness: false,
+  _addIdleObserver: function() {
+    this._observingIdleness = true;
+    this._idleService =
+      Cc["@mozilla.org/widget/idleservice;1"].getService(Ci.nsIIdleService);
+    Services.obs.addObserver(this, "im-sent", false);
+
+    this._timeBeforeIdle = Services.prefs.getIntPref(kPrefTimeBeforeIdle);
+    if (this._timeBeforeIdle < 0)
+      this._timeBeforeIdle = 0;
+    Services.prefs.addObserver(kPrefTimeBeforeIdle, this, false);
+    if (this._timeBeforeIdle)
+      this._idleService.addIdleObserver(this, this._timeBeforeIdle);
+  },
+  _removeIdleObserver: function() {
+    if (this._timeBeforeIdle)
+      this._idleService.removeIdleObserver(this, this._timeBeforeIdle);
+
+    Services.prefs.removeObserver(kPrefTimeBeforeIdle, this);
+    delete this._timeBeforeIdle;
+
+    Services.obs.removeObserver(this, "im-sent");
+    delete this._idleService;
+    delete this._observingIdleness;
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "nsPref:changed") {
+      if (aData == kPrefReportIdle) {
+        let reportIdle = Services.prefs.getBoolPref(kPrefReportIdle);
+        if (reportIdle && !this._observingIdleness)
+          this._addIdleObserver();
+        else if (!reportIdle && this._observingIdleness)
+        this._removeIdleObserver();
+      }
+      else if (aData == kPrefTimeBeforeIdle) {
+        let timeBeforeIdle = Services.prefs.getIntPref(kPrefTimeBeforeIdle);
+        if (timeBeforeIdle != this._timeBeforeIdle) {
+          if (this._timeBeforeIdle)
+            this._idleService.removeIdleObserver(this, this._timeBeforeIdle);
+          this._timeBeforeIdle = timeBeforeIdle;
+          if (this._timeBeforeIdle)
+            this._idleService.addIdleObserver(this, this._timeBeforeIdle);
+        }
+      }
+      else
+        throw Cr.NS_ERROR_UNEXPECTED;
+    }
+    else if (aTopic == NS_IOSERVICE_GOING_OFFLINE_TOPIC)
+      this._offline = true;
+    else if (aTopic == NS_IOSERVICE_OFFLINE_STATUS_TOPIC && aData == "online")
+      this._offline = false;
+    else
+      this._checkIdle();
+  },
+
+  _offlineStatusType: Ci.imIStatusInfo.STATUS_AVAILABLE,
+  set offline(aOffline) {
+    let statusType = this.statusType;
+    let statusText = this.statusText;
+    if (aOffline)
+      this._offlineStatusType = Ci.imIStatusInfo.STATUS_OFFLINE;
+    else
+      delete this._offlineStatusType;
+    if (this.statusType != statusType || this.statusText != statusText)
+      this._notifyObservers("status-changed", this.statusText);
+  },
+
+  _idleTime: 0,
+  get idleTime() this._idleTime,
+  set idleTime(aIdleTime) {
+    this._idleTime = aIdleTime;
+    this._notifyObservers("idle-time-changed", aIdleTime);
+  },
+  _idle: false,
+  _idleStatusText: "",
+  _idleStatusType: Ci.imIStatusInfo.STATUS_AVAILABLE,
+  _checkIdle: function() {
+    let idleTime = this._idleService.idleTime / 1000;
+    let idle = this._timeBeforeIdle && idleTime >= this._timeBeforeIdle;
+    if (idle == this._idle)
+      return;
+
+    let statusType = this.statusType;
+    let statusText = this.statusText;
+    this._idle = idle;
+    if (idle) {
+      this.idleTime = idleTime;
+      if (Services.prefs.getBoolPref(kPrefAwayWhenIdle)) {
+        this._idleStatusType = Ci.imIStatusInfo.STATUS_AWAY;
+        this._idleStatusText =
+          Services.prefs.getComplexValue(kPrefDefaultMessage,
+                                         Ci.nsIPrefLocalizedString).data;
+      }
+    }
+    else {
+      this.idleTime = 0;
+      delete this._idleStatusType;
+      delete this._idleStatusText;
+    }
+    if (this.statusType != statusType || this.statusText != statusText)
+      this._notifyObservers("status-changed", this.statusText);
+  },
+
+  _statusText: "",
+  get statusText() this._statusText || this._idleStatusText,
+  _statusType: Ci.imIStatusInfo.STATUS_AVAILABLE,
+  get statusType() Math.min(this._statusType, this._idleStatusType, this._offlineStatusType),
+  setStatus: function(aStatus, aMessage) {
+    if (aStatus != Ci.imIStatusInfo.STATUS_UNKNOWN)
+      this._statusType = aStatus;
+    if (aStatus != Ci.imIStatusInfo.STATUS_OFFLINE)
+      this._statusText = aMessage;
+    this._notifyObservers("status-changed", aMessage);
+  },
+
+  _getProfileDir: function()
+    Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties)
+      .get("ProfD" /*NS_APP_USER_PROFILE_50_DIR*/, Ci.nsIFile),
+  setUserIcon: function(aIconFile) {
+    let folder = this._getProfileDir();
+
+    let newName = "";
+    if (aIconFile) {
+      // Get the extension (remove trailing dots - invalid Windows extension).
+      let ext = aIconFile.leafName.replace(/.*(\.[a-z0-9]+)\.*/i, "$1");
+      // newName = userIcon-<timestamp(now)>.<aIconFile.extension>
+      newName = "userIcon-" + (Date.now() / 1000) + ext;
+
+      // Copy the new icon file to newName in the profile folder.
+      aIconFile.copyTo(folder, newName);
+    }
+
+    // Get the previous file name before saving the new file name.
+    let oldFileName = Services.prefs.getCharPref(kPrefUserIconFilename);
+    Services.prefs.setCharPref(kPrefUserIconFilename, newName);
+
+    // Now that the new icon has been copied to the profile directory
+    // and the pref value changed, we can remove the old icon. Ignore
+    // failures so that we always fire the user-icon-changed notification.
+    try {
+      if (oldFileName) {
+        folder.append(oldFileName);
+        if (folder.exists())
+          folder.remove(false);
+      }
+    } catch (e) {
+      Cu.reportError(e);
+    }
+
+    this._notifyObservers("user-icon-changed", newName);
+  },
+  getUserIcon: function() {
+    let filename = Services.prefs.getCharPref(kPrefUserIconFilename);
+    if (!filename)
+      return null; // No icon has been set.
+
+    let file = this._getProfileDir();
+    file.append(filename);
+
+    if (!file.exists()) {
+      Services.console.logStringMessage("Invalid userIconFileName preference");
+      return null;
+    }
+
+    return Services.io.newFileURI(file);
+  },
+
+  get displayName() Services.prefs.getCharPref(kPrefUserDisplayname),
+  set displayName(aDisplayName) {
+    Services.prefs.setCharPref(kPrefUserDisplayname, aDisplayName);
+    this._notifyObservers("user-display-name-changed", aDisplayName);
+  },
+
+  addObserver: function(aObserver) {
+    if (this._observers.indexOf(aObserver) == -1)
+      this._observers.push(aObserver);
+  },
+  removeObserver: function(aObserver) {
+    this._observers = this._observers.filter(function(o) o !== aObserver);
+  },
+  _notifyObservers: function(aTopic, aData) {
+    for each (let observer in this._observers)
+      observer.observe(this, aTopic, aData);
+  }
+};
+
+var gCoreService;
+function CoreService() { gCoreService = this; }
+CoreService.prototype = {
+  _initialized: false,
+  globalUserStatus: null,
+  init: function() {
+    if (this._initialized)
+      throw Cr.NS_ERROR_ALREADY_INITIALIZED;
+
+    Services.obs.addObserver(this, kQuitApplicationGranted, false);
+    this._initialized = true;
+
+    Services.cmd.initCommands();
+    this._protos = {};
+
+    this.globalUserStatus = new UserStatus();
+    this.globalUserStatus.addObserver({
+      observe: function(aSubject, aTopic, aData) {
+        Services.obs.notifyObservers(aSubject, aTopic, aData);
+      }
+    });
+
+    Services.accounts.initAccounts();
+    Services.contacts.initContacts();
+    Services.conversations.initConversations();
+  },
+  observe: function(aObject, aTopic, aData) {
+    if (aTopic == kQuitApplicationGranted)
+      this.quit();
+  },
+  quit: function() {
+    if (!this._initialized)
+      throw Cr.NS_ERROR_NOT_INITIALIZED;
+
+    Services.obs.removeObserver(this, kQuitApplicationGranted);
+    Services.obs.notifyObservers(this, "prpl-quit", null);
+
+    Services.conversations.unInitConversations();
+    Services.accounts.unInitAccounts();
+    Services.contacts.unInitContacts();
+    Services.cmd.unInitCommands();
+
+    this.globalUserStatus.unInit();
+    delete this.globalUserStatus;
+    delete this._protos;
+    delete this._initialized;
+  },
+
+  getProtocols: function() {
+    if (!this._initialized)
+      throw Cr.NS_ERROR_NOT_INITIALIZED;
+
+    let protocols = [];
+    let entries = categoryManager.enumerateCategory(kProtocolPluginCategory);
+    while (entries.hasMoreElements()) {
+      let id = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
+      let proto = this.getProtocolById(id);
+      if (proto)
+        protocols.push(proto);
+    }
+    return new nsSimpleEnumerator(protocols);
+  },
+
+  getProtocolById: function(aPrplId) {
+    if (!this._initialized)
+      throw Cr.NS_ERROR_NOT_INITIALIZED;
+
+    if (this._protos.hasOwnProperty(aPrplId))
+      return this._protos[aPrplId];
+
+    let cid;
+    try {
+      cid = categoryManager.getCategoryEntry(kProtocolPluginCategory, aPrplId);
+    } catch (e) {
+      return null; // no protocol registered for this id.
+    }
+
+    let proto = null;
+    try {
+      proto = Cc[cid].createInstance(Ci.prplIProtocol);
+    } catch (e) {
+      // This is a real error, the protocol is registered and failed to init.
+      Cu.reportError(e);
+    }
+    if (!proto)
+      return null;
+
+    proto.init(aPrplId);
+    this._protos[aPrplId] = proto;
+    return proto;
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.imICoreService]),
+  classDescription: "Core",
+  classID: Components.ID("{073f5953-853c-4a38-bd81-255510c31c2e}"),
+  contractID: "@instantbird.org/purple/core-service;1"
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([CoreService]);
new file mode 100644
--- /dev/null
+++ b/chat/components/src/imCore.manifest
@@ -0,0 +1,2 @@
+component {073f5953-853c-4a38-bd81-255510c31c2e} imCore.js
+contract @instantbird.org/purple/core-service;1 {073f5953-853c-4a38-bd81-255510c31c2e}
--- a/chat/modules/imServices.jsm
+++ b/chat/modules/imServices.jsm
@@ -35,19 +35,22 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 let EXPORTED_SYMBOLS = ["Services"];
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(Services, "accounts",
+                                   "@instantbird.org/purple/accounts-service;1",
+                                   "imIAccountsService");
 XPCOMUtils.defineLazyServiceGetter(Services, "core",
-                                   "@instantbird.org/purple/core;1",
-                                   "purpleICoreService");
+                                   "@instantbird.org/purple/core-service;1",
+                                   "imICoreService");
 XPCOMUtils.defineLazyServiceGetter(Services, "cmd",
                                    "@instantbird.org/purple/commands-service;1",
                                    "imICommandsService");
 XPCOMUtils.defineLazyServiceGetter(Services, "contacts",
                                    "@instantbird.org/purple/contacts-service;1",
                                    "imIContactsService");
 XPCOMUtils.defineLazyServiceGetter(Services, "conversations",
                                    "@instantbird.org/purple/conversations-service;1",
--- a/chat/modules/imThemes.jsm
+++ b/chat/modules/imThemes.jsm
@@ -426,17 +426,17 @@ const messageReplacements = {
   userIconPath: function (aMsg) {
     // If the protocol plugin provides an icon for the message, use it.
     let iconURL = aMsg.iconURL;
     if (iconURL)
       return iconURL;
 
     // For outgoing messages, use the current user icon.
     if (aMsg.outgoing) {
-      iconURL = Services.core.getUserIcon();
+      iconURL = aMsg.conversation.account.statusInfo.getUserIcon();
       if (iconURL)
         return iconURL.spec;
     }
 
     // Fallback to the theme's default icons.
     return (aMsg.incoming ? "Incoming" : "Outgoing") + "/buddy_icon.png";
   },
   senderScreenName: function(aMsg) aMsg.who,
--- a/chat/modules/imXPCOMUtils.jsm
+++ b/chat/modules/imXPCOMUtils.jsm
@@ -122,16 +122,21 @@ function executeSoon(aFunction)
  * shared by all generic objects implemented in this file. */
 function ClassInfo(aInterfaces, aDescription)
 {
   if (!(this instanceof ClassInfo))
     return new ClassInfo(aInterfaces, aDescription);
 
   if (!Array.isArray(aInterfaces))
     aInterfaces = [aInterfaces];
+
+  for each (let i in aInterfaces)
+    if (typeof i == "string" && !(i in Ci))
+      Services.console.logStringMessage("ClassInfo: unknown interface " + i);
+
   this._interfaces =
     aInterfaces.map(function (i) typeof i == "string" ? Ci[i] : i);
 
   this.classDescription = aDescription || "JS Proto Object";
 }
 ClassInfo.prototype = {
   QueryInterface: function ClassInfo_QueryInterface(iid) {
     if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIClassInfo) ||
--- a/chat/modules/jsProtoHelper.jsm
+++ b/chat/modules/jsProtoHelper.jsm
@@ -53,113 +53,112 @@ const {classes: Cc, interfaces: Ci, resu
 
 Cu.import("resource:///modules/imXPCOMUtils.jsm");
 Cu.import("resource:///modules/imServices.jsm");
 
 initLogModule("jsProtoHelper");
 
 function normalize(aString) aString.replace(/[^a-z0-9]/gi, "").toLowerCase()
 
-XPCOMUtils.defineLazyGetter(this, "AccountBase", function()
-  Components.Constructor("@instantbird.org/purple/account;1",
-                         "purpleIAccountBase")
-);
-
 const ForwardAccountPrototype = {
-  __proto__: ClassInfo("purpleIAccount", "generic account object"),
-  _init: function _init(aProtoInstance, aBase) {
+  __proto__: ClassInfo("prplIAccount", "generic account object"),
+  _init: function _init(aBase) {
     this._base = aBase;
-    this._base.concreteAccount = this;
-    this._protocol = aProtoInstance;
   },
-  get base() this._base.purpleIAccountBase,
 
-  checkAutoLogin: function() this._base.checkAutoLogin(),
-  remove: function() this._base.remove(),
-  UnInit: function() this._base.UnInit(),
+  observe: function(aSubject, aTopic, aData) {
+    this._base.observe(aSubject, aTopic, aData);
+  },
+  unInit: function() this._base.unInit(),
   connect: function() this._base.connect(),
   disconnect: function() this._base.disconnect(),
-  cancelReconnection: function() this._base.cancelReconnection(),
   createConversation: function(aName) this._base.createConversation(aName),
   addBuddy: function(aTag, aName) this._base.addBuddy(aTag, aName),
   loadBuddy: function(aBuddy, aTag) this._base.loadBuddy(aBuddy, aTag),
   requestBuddyInfo: function(aBuddyName) this._base.requestBuddyInfo(aBuddyName),
   getChatRoomFields: function() this._base.getChatRoomFields(),
   getChatRoomDefaultFieldValues: function(aDefaultChatName)
     this._base.getChatRoomDefaultFieldValues(aDefaultChatName),
   joinChat: function(aComponents) this._base.joinChat(aComponents),
   setBool: function(aName, aVal) this._base.setBool(aName, aVal),
   setInt: function(aName, aVal) this._base.setInt(aName, aVal),
   setString: function(aName, aVal) this._base.setString(aName, aVal),
-  save: function() this._base.save(),
 
-  // grep attribute purpleIAccount.idl |sed 's/.* //;s/;//;s/\(.*\)/  get \1() this._base.\1,/'
-  // Exception: the protocol getter is handled locally.
   get canJoinChat() this._base.canJoinChat,
-  get name() this._base.name,
   get normalizedName() this._base.normalizedName,
-  get id() this._base.id,
-  get numericId() this._base.numericId,
-  get protocol() this._protocol,
-  get autoLogin() this._base.autoLogin,
-  get firstConnectionState() this._base.firstConnectionState,
-  get password() this._base.password,
-  get rememberPassword() this._base.rememberPassword,
-  get alias() this._base.alias,
   get proxyInfo() this._base.proxyInfo,
-  get connectionStateMsg() this._base.connectionStateMsg,
   get connectionErrorReason() this._base.connectionErrorReason,
-  get reconnectAttempt() this._base.reconnectAttempt,
-  get timeOfNextReconnect() this._base.timeOfNextReconnect,
-  get timeOfLastConnect() this._base.timeOfLastConnect,
-  get connectionErrorMessage() this._base.connectionErrorMessage,
-  get connectionState() this._base.connectionState,
-  get disconnected() this._base.disconnected,
-  get connected() this._base.connected,
-  get connecting() this._base.connecting,
-  get disconnecting() this._base.disconnecting,
   get HTMLEnabled() this._base.HTMLEnabled,
   get noBackgroundColors() this._base.noBackgroundColors,
   get autoResponses() this._base.autoResponses,
   get singleFormatting() this._base.singleFormatting,
   get noNewlines() this._base.noNewlines,
   get noFontSizes() this._base.noFontSizes,
   get noUrlDesc() this._base.noUrlDesc,
   get noImages() this._base.noImages,
   get maxMessageLength() this._base.maxMessageLength,
 
-  // grep attribute purpleIAccount.idl |grep -v readonly |sed 's/.* //;s/;//;s/\(.*\)/  set \1(val) { this._base.\1 = val; },/'
-  set autoLogin(val) { this._base.autoLogin = val; },
-  set firstConnectionState(val) { this._base.firstConnectionState = val; },
-  set password(val) { this._base.password = val; },
-  set rememberPassword(val) { this._base.rememberPassword = val; },
-  set alias(val) { this._base.alias = val; },
   set proxyInfo(val) { this._base.proxyInfo = val; }
 };
 
 const GenericAccountPrototype = {
-  __proto__: ForwardAccountPrototype,
-  _init: function _init(aProtoInstance, aKey, aName) {
-    ForwardAccountPrototype._init.call(this, aProtoInstance, new AccountBase());
-    this._base.init(aKey, aName, aProtoInstance);
+  __proto__: ClassInfo("prplIAccount", "generic account object"),
+  _init: function _init(aProtocol, aImAccount) {
+    this.protocol = aProtocol;
+    this.imAccount = aImAccount;
+  },
+  observe: function(aSubject, aTopic, aData) {},
+  unInit: function() {},
+  connect: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+  disconnect: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+  createConversation: function(aName) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+  joinChat: function(aComponents) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+  setBool: function(aName, aVal) {},
+  setInt: function(aName, aVal) {},
+  setString: function(aName, aVal) {},
+
+  get name() this.imAccount.name,
+  get connected() this.imAccount.connected,
+  get connecting() this.imAccount.connecting,
+  get disconnected() this.imAccount.disconnected,
+  get disconnecting() this.imAccount.disconnecting,
+  _connectionErrorReason: Ci.prplIAccount.NO_ERROR,
+  get connectionErrorReason() this._connectionErrorReason,
+
+  reportConnected: function() {
+    this.imAccount.observe(this, "account-connected", null);
+  },
+  reportConnecting: function(aConnectionStateMsg) {
+    if (!this.connecting)
+      this.imAccount.observe(this, "account-connecting", null);
+    if (aConnectionStateMsg)
+      this.imAccount.observe(this, "account-connect-progress", aConnectionStateMsg);
+  },
+  reportDisconnected: function() {
+    this.imAccount.observe(this, "account-disconnected", null);
+  },
+  reportDisconnecting: function(aConnectionErrorReason, aConnectionErrorMessage) {
+    this._connectionErrorReason = aConnectionErrorReason;
+    this.imAccount.observe(this, "account-disconnecting", aConnectionErrorMessage);
   },
 
   addBuddy: function(aTag, aName) {
     Services.contacts
             .accountBuddyAdded(new AccountBuddy(this, null, aTag, aName));
   },
   loadBuddy: function(aBuddy, aTag) {
    try {
-     return new AccountBuddy(this, aBuddy, aTag) ;
+     return new AccountBuddy(this, aBuddy, aTag);
    } catch (x) {
      dump(x + "\n");
      return null;
    }
   },
   requestBuddyInfo: function(aBuddyName) {},
+  get canJoinChat() false,
   getChatRoomFields: function() {
     if (!this.chatRoomFields)
       return EmptyEnumerator;
 
     let fields = [];
     for (let fieldName in this.chatRoomFields)
       fields.push(new ChatRoomField(fieldName, this.chatRoomFields[fieldName]));
     return new nsSimpleEnumerator(fields);
@@ -185,33 +184,43 @@ const GenericAccountPrototype = {
     this.prefs.prefHasUserValue(aName) ?
       this.prefs["get" + aType + "Pref"](aName) :
       this.protocol._getOptionDefault(aName),
   getInt: function(aName) this.getPref(aName, "Int"),
   getString: function(aName) this.getPref(aName, "Char"),
   getBool: function(aName) this.getPref(aName, "Bool"),
 
   get prefs() this._prefs ||
-    (this._prefs = Services.prefs.getBranch("messenger.account." + this.id +
-                                            ".options.")),
+    (this._prefs = Services.prefs.getBranch("messenger.account." +
+                                            this.imAccount.id + ".options.")),
 
   get normalizedName() normalize(this.name),
   get proxyInfo() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
-  set proxyInfo(val) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; }
+  set proxyInfo(val) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
+
+  get HTMLEnabled() true,
+  get noBackgroundColors() true,
+  get autoResponses() false,
+  get singleFormatting() false,
+  get noNewlines() false,
+  get noFontSizes() false,
+  get noUrlDesc() false,
+  get noImages() true,
+  get maxMessageLength() 0
 };
 
 
 const GenericAccountBuddyPrototype = {
   __proto__: ClassInfo("imIAccountBuddy", "generic account buddy object"),
   _init: function(aAccount, aBuddy, aTag, aUserName) {
     if (!aBuddy && !aUserName)
       throw "aUserName is required when aBuddy is null";
 
     this._tag = aTag;
-    this._account = aAccount;
+    this._account = aAccount.imAccount;
     this._buddy = aBuddy;
     this._userName = aUserName;
   },
 
   get account() this._account,
   set buddy(aBuddy) {
     if (this._buddy)
       throw Cr.NS_ERROR_ALREADY_INITIALIZED;
@@ -366,17 +375,17 @@ function Message(aWho, aMessage, aObject
 Message.prototype = GenericMessagePrototype;
 
 
 const GenericConversationPrototype = {
   __proto__: ClassInfo("purpleIConversation", "generic conversation object"),
   flags: Ci.nsIClassInfo.DOM_OBJECT,
 
   _init: function(aAccount, aName) {
-    this.account = aAccount;
+    this.account = aAccount.imAccount;
     this._name = aName;
     this._observers = [];
     Services.conversations.addConversation(this);
   },
 
   _id: 0,
   get id() this._id,
   set id(aId) {
@@ -592,18 +601,22 @@ ChatRoomFieldValues.prototype = {
     this.values.hasOwnProperty(aIdentifier) ? this.values[aIdentifier] : null,
   setValue: function(aIdentifier, aValue) {
     this.values[aIdentifier] = aValue;
   }
 };
 
 // the name getter needs to be implemented
 const GenericProtocolPrototype = {
-  __proto__: ClassInfo("purpleIProtocol", "Generic protocol object"),
+  __proto__: ClassInfo("prplIProtocol", "Generic protocol object"),
 
+  init: function(aId) {
+    if (aId != this.id)
+      throw NS_ERROR_NOT_IMPLEMENTED;
+  },
   get id() "prpl-" + this.normalizedName,
   get normalizedName() normalize(this.name),
   get iconBaseURI() "chrome://instantbird/skin/prpl-generic/",
 
   getAccount: function(aKey, aName) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
 
   _getOptionDefault: function(aName) {
     if (this.options && this.options.hasOwnProperty(aName))
@@ -666,34 +679,34 @@ const GenericProtocolPrototype = {
   get registerNoScreenName() false,
   get slashCommandsNative() false,
   get usePurpleProxy() false,
 
   get classDescription() this.name + " Protocol",
   get contractID() "@instantbird.org/purple/" + this.normalizedName + ";1"
 };
 
-function ForwardAccount(aProtocol, aBaseAccount)
+function ForwardAccount(aBaseAccount)
 {
-  this._init(aProtocol, aBaseAccount);
+  this._init(aBaseAccount);
 }
 ForwardAccount.prototype = ForwardAccountPrototype;
 
 // the baseId property should be set to the prpl id of the base protocol plugin
 // and the name getter is required.
 const ForwardProtocolPrototype = {
   __proto__: GenericProtocolPrototype,
 
   get base() {
     if (!this.hasOwnProperty("_base"))
       this._base = Services.core.getProtocolById(this.baseId);
     return this._base;
   },
-  getAccount: function(aKey, aName)
-    new ForwardAccount(this, this.base.getAccount(aKey, aName)),
+  getAccount: function(aImAccount)
+    new ForwardAccount(this.base.getAccount(aImAccount)),
 
   get iconBaseURI() this.base.iconBaseURI,
   getOptions: function() this.base.getOptions(),
   getUsernameSplit: function() this.base.getUsernameSplit(),
   accountExists: function(aName) this.base.accountExists(aName),
   get uniqueChatName() this.base.uniqueChatName,
   get chatHasTopic() this.base.chatHasTopic,
   get noPassword() this.base.noPassword,
--- a/chat/protocols/jsTest/jsTestProtocol.js
+++ b/chat/protocols/jsTest/jsTestProtocol.js
@@ -61,19 +61,19 @@ Conversation.prototype = {
     this.writeMessage("/dev/null", "Thanks! I appreciate your attention.",
                       {incoming: true, autoResponse: true});
   },
 
   get name() "/dev/null",
 };
 Conversation.prototype.__proto__ = GenericConvIMPrototype;
 
-function Account(aProtoInstance, aKey, aName)
+function Account(aProtoInstance, aImAccount)
 {
-  this._init(aProtoInstance, aKey, aName);
+  this._init(aProtoInstance, aImAccount);
 }
 Account.prototype = {
   connect: function() {
     this.base.connecting();
     // do something here
     this.base.connected();
     setTimeout((function() {
       this._conv = new Conversation(this);
@@ -113,17 +113,17 @@ jsTestProtocol.prototype = {
     "int" : {label: "Integer option", default: 42},
     "list": {label: "Select option",  default: {"option1": "Default option",
                                                 "option2": "Other option"}}
   },
   usernameSplits: [
     {label: "Server", separator: "@", defaultValue: "default.server",
      reverse: true}
   ],
-  getAccount: function(aKey, aName) new Account(this, aKey, aName),
+  getAccount: function(aImAccount) new Account(this, aImAccount),
   classID: Components.ID("{a0774c5a-4aea-458b-9fbc-8d3cbf1a4630}"),
 };
 jsTestProtocol.prototype.__proto__ = GenericProtocolPrototype;
 
 function overrideTestProtocol() { }
 overrideTestProtocol.prototype = {
   get normalizedName() "override",
   get name() "Override Test",
--- a/chat/protocols/jsTest/jsTestProtocol.manifest
+++ b/chat/protocols/jsTest/jsTestProtocol.manifest
@@ -1,6 +1,6 @@
 component {a0774c5a-4aea-458b-9fbc-8d3cbf1a4630} jsTestProtocol.js
 contract @instantbird.org/purple/jstest;1 {a0774c5a-4aea-458b-9fbc-8d3cbf1a4630}
-category js-protocol-plugin @instantbird.org/purple/jstest;1 @instantbird.org/purple/jstest;1
+category im-protocol-plugin prpl-jstest @instantbird.org/purple/jstest;1
 component {88795348-8a4b-4018-890d-5314cb08ec4d} jsTestProtocol.js
 contract @instantbird.org/purple/override;1 {88795348-8a4b-4018-890d-5314cb08ec4d}
-category js-protocol-plugin @instantbird.org/purple/override;1 @instantbird.org/purple/override;1
+category im-protocol-plugin prpl-override @instantbird.org/purple/override;1
--- a/chat/protocols/overrides/facebookOverrideProtocol.js
+++ b/chat/protocols/overrides/facebookOverrideProtocol.js
@@ -56,20 +56,20 @@ UsernameSplit.prototype = {
 function facebookProtocol() { }
 facebookProtocol.prototype = {
   __proto__: ForwardProtocolPrototype,
   get normalizedName() "facebook",
   get name() "Facebook Chat",
   get iconBaseURI() "chrome://prpl-facebook/skin/",
   get baseId() "prpl-jabber",
 
-  getAccount: function(aKey, aName) {
-    let account = ForwardProtocolPrototype.getAccount.call(this, aKey, aName);
+  getAccount: function(aImAccount) {
+    let account = ForwardProtocolPrototype.getAccount.call(this, aImAccount);
     account.__defineGetter__("canJoinChat", function() false);
-    account.setString("connection_security", "opportunistic_tls");
+    aImAccount.setString("connection_security", "opportunistic_tls");
     return account;
   },
   getOptions: function() EmptyEnumerator,
   getUsernameSplit: function() {
     var splits = this.base.getUsernameSplit();
     let newSplits = [];
     while (splits.hasMoreElements()) {
       let split = splits.getNext();
--- a/chat/protocols/overrides/gtalkOverrideProtocol.js
+++ b/chat/protocols/overrides/gtalkOverrideProtocol.js
@@ -45,21 +45,21 @@ function gtalkProtocol() {
 }
 gtalkProtocol.prototype = {
   __proto__: ForwardProtocolPrototype,
   get normalizedName() "gtalk",
   get name() "Google Talk",
   get iconBaseURI() "chrome://prpl-gtalk/skin/",
   get baseId() "prpl-jabber",
 
-  getAccount: function(aKey, aName) {
-    let account = ForwardProtocolPrototype.getAccount.call(this, aKey, aName);
+  getAccount: function(aImAccount) {
+    let account = ForwardProtocolPrototype.getAccount.call(this, aImAccount);
     let connectServer =
-      /@g(oogle)?mail.com(\/|$)/.test(aName) ? "" : "talk.google.com";
-    account.setString("connect_server", connectServer);
+      /@g(oogle)?mail.com(\/|$)/.test(aImAccount.name) ? "" : "talk.google.com";
+    aImAccount.setString("connect_server", connectServer);
     return account;
   },
   getOptions: function() EmptyEnumerator,
 
   classID: Components.ID("{ad8a6454-2f5a-40c2-86ca-30062408125e}")
 };
 
 const NSGetFactory = XPCOMUtils.generateNSGetFactory([gtalkProtocol]);
--- a/chat/protocols/overrides/overrideProtocols.manifest
+++ b/chat/protocols/overrides/overrideProtocols.manifest
@@ -1,6 +1,6 @@
 component {ad8a6454-2f5a-40c2-86ca-30062408125e} gtalkOverrideProtocol.js
 contract @instantbird.org/purple/gtalk;1 {ad8a6454-2f5a-40c2-86ca-30062408125e}
-category js-protocol-plugin @instantbird.org/purple/gtalk;1 @instantbird.org/purple/gtalk;1
+category im-protocol-plugin prpl-gtalk @instantbird.org/purple/gtalk;1
 component {61bc3528-df53-4481-a61a-74c3a2e8c9fd} facebookOverrideProtocol.js
 contract @instantbird.org/purple/facebook;1 {61bc3528-df53-4481-a61a-74c3a2e8c9fd}
-category js-protocol-plugin @instantbird.org/purple/facebook;1 @instantbird.org/purple/facebook;1
+category im-protocol-plugin prpl-facebook @instantbird.org/purple/facebook;1
--- a/chat/protocols/twitter/twitter.js
+++ b/chat/protocols/twitter/twitter.js
@@ -313,23 +313,21 @@ Conversation.prototype = {
     this.notifyObservers(new nsSimpleEnumerator([chatBuddy]),
                          "chat-buddy-add");
   },
   get name() this.nick + " timeline",
   get title() _("timeline", this.nick),
   get nick() "@" + this.account.name
 };
 
-function Account(aProtoInstance, aKey, aName)
+function Account(aProtocol, aImAccount)
 {
-  this._init(aProtoInstance, aKey, aName);
+  this._init(aProtocol, aImAccount);
   this._knownMessageIds = {};
   this._userInfo = {};
-
-  Services.obs.addObserver(this, "status-changed", false);
 }
 Account.prototype = {
   __proto__: GenericAccountPrototype,
 
   get HTMLEnabled() false,
   get maxMessageLength() 140,
 
   consumerKey: "TSuyS1ieRAkB3qWv8yyEw",
@@ -347,17 +345,17 @@ Account.prototype = {
   _enabled: false,
 
   token: "",
   tokenSecret: "",
   connect: function() {
     if (this.connected || this.connecting)
       return;
 
-    this.base.connecting();
+    this.reportConnecting();
     this._enabled = true;
 
     // Read the OAuth token from the prefs
     let prefValue = {};
     try {
       prefValue = JSON.parse(this.prefs.getCharPref("oauth"));
     } catch(e) { }
     if (prefValue.hasOwnProperty(this.consumerKey)) {
@@ -375,22 +373,25 @@ Account.prototype = {
       this.requestToken();
       return;
     }
 
     LOG("Connecting using existing token");
     this.getTimelines();
   },
 
-  // Currently only used for "status-changed" notification.
   observe: function(aSubject, aTopic, aMsg) {
+    // Currently only used for "status-changed" notification.
+    if (aTopic != "status-changed")
+      return;
+
     if (!this._enabled)
       return;
 
-    let statusType = aSubject.currentStatusType;
+    let statusType = aSubject.statusType;
     if (statusType == Ci.imIStatusInfo.STATUS_OFFLINE) {
       // This will remove the _enabled value...
       this.disconnect();
       // ...set it again:
       this._enabled = true;
     }
     else if (statusType > Ci.imIStatusInfo.STATUS_OFFLINE)
       this.connect();
@@ -511,18 +512,17 @@ Account.prototype = {
                                   new Date(date) / 1000);
     }, null, this);
   },
   addBuddy: function(aTag, aName) {
     this.follow(aName);
   },
 
   getTimelines: function() {
-    this.base
-        .connecting(_("connection.requestTimelines"));
+    this.reportConnecting(_("connection.requestTimelines"));
 
     // If we have a last known message ID, append it as a get parameter.
     let lastMsgParam = this.prefs.prefHasUserValue("lastMessageId") ?
       "&since_id=" + this.prefs.getCharPref("lastMessageId") : "";
     let getParams = "?include_entities=1&count=200" + lastMsgParam;
     this._pendingRequests = [
       this.signAndSend("1/statuses/home_timeline.json" + getParams, null, null,
                        this.onTimelineReceived, this.onTimelineError, this),
@@ -639,17 +639,17 @@ Account.prototype = {
       return;
     }
 
     // Less than 2 auth errors is probably just some flakiness of the
     // twitter servers, ignore and reset this._timelineAuthError.
     if (this._timelineAuthError)
       delete this._timelineAuthError;
 
-    this.base.connected();
+    this.reportConnected();
 
     // If the conversation already exists, notify it we are back online.
     if (this._timeline)
       this._timeline.notifyObservers(this._timeline, "update-buddy-status");
 
     this._timelineBuffer.sort(this.sortByDate);
     this.displayMessages(this._timelineBuffer);
 
@@ -681,17 +681,17 @@ Account.prototype = {
     this._streamingRequest =
       this.signAndSend("https://userstream.twitter.com/2/user.json",
                        null, track ? [["track", track]] : [],
                        this.openStream, this.onStreamError, this);
     this._streamingRequest.onprogress = this.onDataAvailable.bind(this);
   },
   onStreamError: function(aError) {
     delete this._streamingRequest;
-    this.gotDisconnected(this._base.ERROR_NETWORK_ERROR, aError);
+    this.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR, aError);
   },
   onDataAvailable: function(aRequest) {
     let text = aRequest.target.responseText;
     let newText = this._pendingData + text.slice(this._receivedLength);
     DEBUG("Received data: " + newText);
     let messages = newText.split(/\r\n?/);
     this._pendingData = messages.pop();
     this._receivedLength = text.length;
@@ -729,51 +729,51 @@ Account.prototype = {
           this.timeline.systemMessage(_("event." + event, user.screen_name),
                                       false, new Date(msg.created_at) / 1000);
         }
       }
     }
   },
 
   requestToken: function() {
-    this.base.connecting(_("connection.initAuth"));
+    this.reportConnecting(_("connection.initAuth"));
     let oauthParams =
       [["oauth_callback", encodeURIComponent(this.completionURI)]];
     this.signAndSend("oauth/request_token", null, [],
                      this.onRequestTokenReceived, this.onError, this,
                      oauthParams);
   },
   onRequestTokenReceived: function(aData) {
     LOG("Received request token.");
     let data = this._parseURLData(aData);
     if (!data.oauth_callback_confirmed ||
         !data.oauth_token || !data.oauth_token_secret) {
-      this.gotDisconnected(this._base.ERROR_OTHER_ERROR,
+      this.gotDisconnected(Ci.prplIAccount.ERROR_OTHER_ERROR,
                            _("connection.failedToken"));
       return;
     }
     this.token = data.oauth_token;
     this.tokenSecret = data.oauth_token_secret;
 
     this.requestAuthorization();
   },
   requestAuthorization: function() {
-    this.base.connecting(_("connection.requestAuth"));
+    this.reportConnecting(_("connection.requestAuth"));
     const url = this.baseURI + "oauth/authorize?oauth_token=";
     this._browserRequest = {
       get promptText() _("authPrompt"),
       account: this,
       url: url + this.token,
       _active: true,
       cancelled: function() {
         if (!this._active)
           return;
 
         this.account
-            .gotDisconnected(this.account._base.ERROR_AUTHENTICATION_FAILED,
+            .gotDisconnected(Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED,
                              _("connection.error.authCancelled"));
       },
       loaded: function(aWindow, aWebProgress) {
         if (!this._active)
           return;
 
         this._listener = {
           QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
@@ -820,24 +820,24 @@ Account.prototype = {
     this._browserRequest._active = false;
     if ("_listener" in this._browserRequest)
       this._browserRequest._listener._cleanUp();
     delete this._browserRequest;
   },
   onAuthorizationReceived: function(aData) {
     let data = this._parseURLData(aData.split("?")[1]);
     if (data.oauth_token != this.token || !data.oauth_verifier) {
-      this.gotDisconnected(this._base.ERROR_OTHER_ERROR,
+      this.gotDisconnected(Ci.prplIAccount.ERROR_OTHER_ERROR,
                            _("connection.error.authFailed"));
       return;
     }
     this.requestAccessToken(data.oauth_verifier);
   },
   requestAccessToken: function(aTokenVerifier) {
-    this.base.connecting(_("connection.requestAccess"));
+    this.reportConnecting(_("connection.requestAccess"));
     this.signAndSend("oauth/access_token", null, [],
                      this.onAccessTokenReceived, this.onError, this,
                      [["oauth_verifier", aTokenVerifier]]);
   },
   onAccessTokenReceived: function(aData) {
     LOG("Received access token.");
     let result = this._parseURLData(aData);
     if (result.screen_name && result.screen_name != this.name) {
@@ -873,52 +873,50 @@ Account.prototype = {
     delete this.token;
     delete this.tokenSecret;
   },
   gotDisconnected: function(aError, aErrorMessage) {
     if (this.disconnected || this.disconnecting)
       return;
 
     if (aError === undefined)
-      aError = this._base.NO_ERROR;
+      aError = Ci.prplIAccount.NO_ERROR;
     let connected = this.connected;
-    this.base.disconnecting(aError, aErrorMessage);
+    this.reportDisconnecting(aError, aErrorMessage);
     this.cleanUp();
     if (this._timeline && connected)
       this._timeline.notifyObservers(this._timeline, "update-conv-chatleft");
     delete this._enabled;
-    this.base.disconnected();
+    this.reportDisconnected();
   },
-  UnInit: function() {
+  unInit: function() {
     this.cleanUp();
     // If we've received any messages, update the last known message.
     let newestMessageId = "";
     for (let id in this._knownMessageIds) {
       // Compare the length of the ids first, and then the text.
       // This avoids converting tweet ids into rounded numbers.
       if (id.length > newestMessageId.length ||
           (id.length == newestMessageId.length && id > newestMessageId))
         newestMessageId = id;
     }
     if (newestMessageId)
       this.prefs.setCharPref("lastMessageId", newestMessageId);
-    Services.obs.removeObserver(this, "status-changed");
-    this._base.UnInit();
   },
   disconnect: function() {
     this.gotDisconnected();
   },
 
   onError: function(aException) {
     if (aException == "offline") {
-      this.gotDisconnected(this._base.ERROR_NETWORK_ERROR,
+      this.gotDisconnected(Ci.prplIAccount.ERROR_NETWORK_ERROR,
                            _("connection.error.noNetwork"));
     }
     else
-      this.gotDisconnected(this._base.ERROR_OTHER_ERROR, aException.toString());
+      this.gotDisconnected(Ci.prplIAccount.ERROR_OTHER_ERROR, aException.toString());
   },
 
   onRequestedInfoReceived: function(aData) {
     let user = JSON.parse(aData);
     this._userInfo[user.screen_name] = user;
     this.requestBuddyInfo(user.screen_name);
   },
   requestBuddyInfo: function(aBuddyName) {
@@ -982,13 +980,13 @@ function TwitterProtocol() { }
 TwitterProtocol.prototype = {
   __proto__: GenericProtocolPrototype,
   get name() "Twitter",
   get iconBaseURI() "chrome://prpl-twitter/skin/",
   get noPassword() true,
   options: {
     "track": {get label() _("options.track"), default: ""}
   },
-  getAccount: function(aKey, aName) new Account(this, aKey, aName),
+  getAccount: function(aImAccount) new Account(this, aImAccount),
   classID: Components.ID("{31082ff6-1de8-422b-ab60-ca0ac0b2af13}")
 };
 
 const NSGetFactory = XPCOMUtils.generateNSGetFactory([TwitterProtocol]);
--- a/chat/protocols/twitter/twitter.manifest
+++ b/chat/protocols/twitter/twitter.manifest
@@ -1,3 +1,3 @@
 component {31082ff6-1de8-422b-ab60-ca0ac0b2af13} twitter.js
 contract @instantbird.org/purple/twitter;1 {31082ff6-1de8-422b-ab60-ca0ac0b2af13}
-category js-protocol-plugin @instantbird.org/purple/twitter;1 @instantbird.org/purple/twitter;1
+category im-protocol-plugin prpl-twitter @instantbird.org/purple/twitter;1
--- a/im/components/ibCommandLineHandler.js
+++ b/im/components/ibCommandLineHandler.js
@@ -32,33 +32,33 @@
  * 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource:///modules/imServices.jsm");
 Cu.import("resource:///modules/imXPCOMUtils.jsm");
 Cu.import("resource:///modules/ibCore.jsm");
 
 function ibCommandLineHandler() { }
 
 ibCommandLineHandler.prototype = {
   handle: function clh_handle(cmdLine) {
     if (cmdLine.handleFlag("preferences", false)) {
       Core.showPreferences();
       cmdLine.preventDefault = true;
       return;
     }
 
     if (cmdLine.handleFlag("n", false)) {
-      Components.classes["@instantbird.org/purple/core;1"]
-                .getService(Ci.purpleICoreService)
-                .autoLoginStatus = Ci.purpleICoreService.AUTOLOGIN_USER_DISABLED;
+      Services.accounts.autoLoginStatus =
+        Ci.imIAccountsService.AUTOLOGIN_USER_DISABLED;
     }
 
     // Initialize the core only at the first real startup,
     // not when clicking the dock.
     if (cmdLine.state == cmdLine.STATE_INITIAL_LAUNCH) {
       // If the core failed to init, don't show the buddy list
       if (!Core.init())
         cmdLine.preventDefault = true;
--- a/im/components/ibStatusCommandLineHandler.js
+++ b/im/components/ibStatusCommandLineHandler.js
@@ -55,18 +55,18 @@ StatusCLH.prototype = {
       return;
 
     let statusParam = cmdLine.getArgument(statusIndex + 1).toLowerCase();
 
     // Remove the arguments since they've been handled.
     cmdLine.removeArguments(statusIndex, statusIndex + 1);
 
     // We're keeping the old status message here.
-    Services.core.setStatus(Status.toFlag(statusParam),
-                            Services.core.currentStatusMessage);
+    let us = Services.core.globalUserStatus;
+    us.setStatus(Status.toFlag(statusParam), us.statusText);
 
     // Only perform the default action (i.e. loading the buddy list) if
     // Instantbird is launched with a status flag.
     if (cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH)
       cmdLine.preventDefault = true;
   },
 
   // Follow the guidelines in nsICommandLineHandler.idl for the help info
--- a/im/components/mintrayr/content/mintrayr.js
+++ b/im/components/mintrayr/content/mintrayr.js
@@ -123,14 +123,14 @@ var gMinTrayR = {
 
     if (this._icon.isMinimized)
       this._icon.restore();
     else
       this._icon.minimize();
   },
 
   setStatus: function MinTrayR_setStatus(aStatusParam) {
-    Services.core.setStatus(Status.toFlag(aStatusParam),
-                            Services.core.currentStatusMessage);
+    let us = Services.core.globalUserStatus;
+    us.setStatus(Status.toFlag(aStatusParam), us.statusText);
   }
 };
 
 window.addEventListener("load", gMinTrayR.load, true);
--- a/im/content/aboutDialog.xul
+++ b/im/content/aboutDialog.xul
@@ -77,17 +77,17 @@
           var versionField = document.getElementById("versionField");
           versionField.value = versionField.value + xai.version
                              + ' (' + xai.appBuildID + ')';
 
           versionField = document.getElementById("geckoVersionField");
           versionField.value = versionField.value + xai.platformVersion
                              + ' (' + xai.platformBuildID + ')';
 
-          var pcs = Components.classes["@instantbird.org/purple/core;1"]
+          var pcs = Components.classes["@instantbird.org/libpurple/core;1"]
                               .getService(Components.interfaces.purpleICoreService)
           versionField = document.getElementById("libpurpleVersionField");
           versionField.value = versionField.value + pcs.version;
 
           versionField = document.getElementById("userAgentField");
           versionField.value = navigator.userAgent;
 
           var button = document.documentElement.getButton("extra2");
--- a/im/content/account.js
+++ b/im/content/account.js
@@ -33,17 +33,17 @@
  * 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 autoJoinPref = "autoJoin";
 
 const events = [
-  "purple-quit"
+  "prpl-quit"
 ];
 
 var account = {
   onload: function account_onload() {
     this.account = window.arguments[0];
     this.proto = this.account.protocol;
     document.getElementById("accountName").value = this.account.name;
     document.getElementById("protocolName").value = this.proto.name || this.proto.id;
@@ -89,29 +89,30 @@ var account = {
 
     addObservers(this, events);
     window.addEventListener("unload", this.unload);
   },
   unload: function account_unload() {
     removeObservers(account, events);
   },
   observe: function account_observe(aObject, aTopic, aData) {
-    if (aTopic == "purple-quit") {
+    if (aTopic == "prpl-quit") {
       // libpurple is being uninitialized. Close this dialog.
       window.close();
     }
   },
 
   displayProxyDescription: function account_displayProxyDescription() {
     var type = this.proxy.type;
     var bundle = document.getElementById("proxiesBundle");
     var proxy;
     var result;
     if (type == Ci.purpleIProxyInfo.useGlobal) {
-      proxy = Services.core.globalProxy;
+      proxy = Cc["@instantbird.org/libpurple/core;1"]
+              .getService(Ci.purpleICoreService).globalProxy;
       type = proxy.type;
     }
     else
       proxy = this.proxy;
 
     if (type == Ci.purpleIProxyInfo.noProxy)
       result = bundle.getString("proxies.directConnection");
 
@@ -317,29 +318,29 @@ var account = {
         break;
       default:
         throw "unknown preference type " + opt.type;
       }
     }
 
     if (connectionInfoHasChanged) {
       /* This is the first time we try to connect with these parameters */
-      this.account.firstConnectionState = this.account.FIRST_CONNECTION_SET;
+      this.account.firstConnectionState = this.account.FIRST_CONNECTION_UNKNOWN;
 
       if (this.account.connecting) {
         this.account.disconnect();
         this.account.connect();
         return;
       }
       let errorReason = this.account.connectionErrorReason;
       if (this.account.disconnected &&
-          errorReason != Ci.purpleIAccount.NO_ERROR &&
-          errorReason != Ci.purpleIAccount.ERROR_MISSING_PASSWORD &&
-          errorReason != Ci.purpleIAccount.ERROR_CRASHED &&
-          errorReason != Ci.purpleIAccount.ERROR_UNKNOWN_PRPL) {
+          errorReason != Ci.prplIAccount.NO_ERROR &&
+          errorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD &&
+          errorReason != Ci.imIAccount.ERROR_CRASHED &&
+          errorReason != Ci.imIAccount.ERROR_UNKNOWN_PRPL) {
         this.account.connect();
       }
     }
   },
 
   getProtoOptions: function account_getProtoOptions() {
     return getIter(this.proto.getOptions());
   },
--- a/im/content/account.xml
+++ b/im/content/account.xml
@@ -90,17 +90,17 @@
         this.setAttribute("protocol", proto.name);
         this.setAttribute("prplicon", proto.iconBaseURI + "icon32.png");
         var state = "Unknown";
         if (this._account.connected) {
           state = "connected";
           this.refreshConnectedLabel();
         } else if (this._account.disconnected) {
           state = "disconnected";
-          if (this._account.connectionErrorReason != Ci.purpleIAccount.NO_ERROR)
+          if (this._account.connectionErrorReason != Ci.prplIAccount.NO_ERROR)
             this.updateConnectionError();
           else
             this.removeAttribute("error");
         } else if (this._account.connecting) {
           state = "connecting";
           this.updateConnectionState();
         } else if (this._account.disconnecting) {
           state = "connected";
@@ -131,22 +131,22 @@
      <method name="updateConnectionError">
       <body>
       <![CDATA[
         var bundle = document.getElementById("accountsBundle");
         const key = "account.connection.error";
         var account = this._account;
         var text;
         let errorReason = account.connectionErrorReason;
-        if (errorReason == Ci.purpleIAccount.ERROR_UNKNOWN_PRPL)
+        if (errorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL)
           text = bundle.getFormattedString(key + "UnknownPrpl",
                                            [account.protocol.id]);
-        else if (errorReason == Ci.purpleIAccount.ERROR_MISSING_PASSWORD)
+        else if (errorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD)
           text = bundle.getString(key + "MissingPassword");
-        else if (errorReason == Ci.purpleIAccount.ERROR_CRASHED)
+        else if (errorReason == Ci.imIAccount.ERROR_CRASHED)
           text = bundle.getString(key + "CrashedAccount");
         else
           text = account.connectionErrorMessage;
         text = bundle.getFormattedString(key, [text]);
 
         this.setAttribute("error", "true");
         var error = document.getAnonymousElementByAttribute(this, "anonid",
                                                             "error");
@@ -225,17 +225,17 @@
      <method name="restoreItems">
        <body>
        <![CDATA[
          // Called after a removal and reinsertion of the binding
          this._buttons = null;
          this._connectedLabel = null;
          if (this._account.connected)
            this.refreshConnectedLabel();
-         if (this._account.connectionErrorReason == Ci.purpleIAccount.NO_ERROR)
+         if (this._account.connectionErrorReason == Ci.prplIAccount.NO_ERROR)
            this.updateConnectionState();
          else
            this.updateConnectionError();
        ]]>
        </body>
      </method>
 
      <method name="destroy">
--- a/im/content/accountWizard.js
+++ b/im/content/accountWizard.js
@@ -34,17 +34,17 @@
  * 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 PREF_EXTENSIONS_GETMOREPROTOCOLSURL = "extensions.getMoreProtocolsURL";
 
 const events = [
-  "purple-quit"
+  "prpl-quit"
 ];
 
 var accountWizard = {
   onload: function aw_onload() {
     accountWizard.setGetMoreProtocols();
 
     var protoList = document.getElementById("protolist");
     var protos = [];
@@ -65,17 +65,17 @@ var accountWizard = {
 
     addObservers(this, events);
     window.addEventListener("unload", this.unload);
   },
   unload: function aw_unload() {
     removeObservers(accountWizard, events);
   },
   observe: function am_observe(aObject, aTopic, aData) {
-    if (aTopic == "purple-quit") {
+    if (aTopic == "prpl-quit") {
       // libpurple is being uninitialized. We can't create any new
       // account so keeping this wizard open would be pointless, close it.
       window.close();
     }
   },
 
   getUsername: function aw_getUsername() {
     // If the first username textbox is empty, make sure we return an empty
@@ -217,17 +217,18 @@ var accountWizard = {
   },
 
   displayProxyDescription: function aw_displayProxyDescription() {
     var type = this.proxy.type;
     var bundle = document.getElementById("proxiesBundle");
     var proxy;
     var result;
     if (type == Ci.purpleIProxyInfo.useGlobal) {
-      proxy = Services.core.globalProxy;
+      proxy = Cc["@instantbird.org/libpurple/core;1"]
+              .getService(Ci.purpleICoreService).globalProxy;
       type = proxy.type;
     }
     else
       proxy = this.proxy;
 
     if (type == Ci.purpleIProxyInfo.noProxy)
       result = bundle.getString("proxies.directConnection");
 
@@ -433,21 +434,19 @@ var accountWizard = {
     for (let i = 0; i < this.prefs.length; ++i) {
       let opt = this.prefs[i];
       let label = bundle.getFormattedString("accountColon", [opt.opt.label]);
       rows.appendChild(this.createSummaryRow(label, opt.value));
     }
   },
 
   createAccount: function aw_createAccount() {
-    var acc = Services.core.createAccount(this.username, this.proto.id);
-    if (!this.proto.noPassword) {
+    var acc = Services.accounts.createAccount(this.username, this.proto.id);
+    if (!this.proto.noPassword)
       acc.password = this.password;
-      acc.rememberPassword = true;
-    }
     if (this.alias)
       acc.alias = this.alias;
     //FIXME: newMailNotification
 
     for (let i = 0; i < this.prefs.length; ++i) {
       let option = this.prefs[i];
       let opt = option.opt;
       switch(opt.type) {
--- a/im/content/accounts.js
+++ b/im/content/accounts.js
@@ -35,17 +35,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
 
 // This is the list of notifications that the account manager window observes
 const events = [
-  "purple-quit",
+  "prpl-quit",
   "account-list-updated",
   "account-added",
   "account-updated",
   "account-removed",
   "account-connected",
   "account-connecting",
   "account-disconnected",
   "account-disconnecting",
@@ -121,17 +121,17 @@ var gAccountManager = {
     // The selected item is still selected
     accountList.selectedItem.buttons.setFocus();
     accountList.ensureSelectedElementIsVisible();
 
     // We need to refresh the disabled menu items
     this.disableCommandItems();
   },
   observe: function am_observe(aObject, aTopic, aData) {
-    if (aTopic == "purple-quit") {
+    if (aTopic == "prpl-quit") {
       // libpurple is being uninitialized. We don't need the account
       // manager window anymore, close it.
       this.close();
       return;
     }
     else if (aTopic == "autologin-processed") {
       var notification = document.getElementById("accountsNotificationBox")
                                  .getNotificationWithValue("autoLoginStatus");
@@ -139,26 +139,26 @@ var gAccountManager = {
         notification.close();
       return;
     }
     else if (aTopic == "network:offline-status-changed") {
       this.setOffline(aData == "offline");
       return;
     }
     else if (aTopic == "status-changed") {
-      this.setOffline(aObject.currentStatusType == Ci.imIStatusInfo.STATUS_OFFLINE);
+      this.setOffline(aObject.statusType == Ci.imIStatusInfo.STATUS_OFFLINE);
       return;
     }
     else if (aTopic == "account-list-updated") {
       this._updateAccountList();
       return;
     }
 
-    if (!(aObject instanceof Ci.purpleIAccount))
-      throw "Bad notification.";
+    // The following notification handlers need an account.
+    aObject.QueryInterface(Ci.imIAccount);
 
     if (aTopic == "account-added") {
       document.getElementById("accountsDesk").selectedIndex = 1;
       var elt = document.createElement("richlistitem");
       this.accountList.appendChild(elt);
       elt.build(aObject);
       if (this.accountList.getRowCount() == 1)
         this.accountList.selectedIndex = 0;
@@ -198,16 +198,19 @@ var gAccountManager = {
       const stateEvents = {
         "account-connected": "connected",
         "account-connecting": "connecting",
         "account-disconnected": "disconnected",
         "account-disconnecting": "disconnecting"
       };
       if (aTopic in stateEvents) {
         let elt = document.getElementById(aObject.id);
+        if (!elt)
+          return; // probably disconnecting a removed account.
+
         if (aTopic == "account-connecting") {
           elt.removeAttribute("error");
           elt.updateConnectionState();
         }
         else {
           if (aTopic == "account-connected")
             elt.refreshConnectedLabel();
         }
@@ -278,17 +281,17 @@ var gAccountManager = {
       if (prompts.confirmEx(window, promptTitle, promptMessage, flags,
                             deleteButton, null, null, promptCheckbox, checkbox))
         return;
 
       if (checkbox.value)
         Services.prefs.setBoolPref("messenger.accounts.promptOnDelete", false);
     }
 
-    Services.core.deleteAccount(selectedItem.id);
+    Services.accounts.deleteAccount(selectedItem.id);
   },
   new: function am_new() {
     this.openDialog("chrome://instantbird/content/accountWizard.xul");
   },
   edit: function am_edit() {
     this.openDialog("chrome://instantbird/content/account.xul",
                     this.accountList.selectedItem.account);
   },
@@ -321,18 +324,18 @@ var gAccountManager = {
       return;
 
     let account = selectedItem.account;
     let activeCommandName = account.disconnected ? "connect" : "disconnect";
     let activeCommandElt = document.getElementById("cmd_" + activeCommandName);
     let isCommandDisabled =
       (this.isOffline ||
        (account.disconnected &&
-        (account.connectionErrorReason == Ci.purpleIAccount.ERROR_UNKNOWN_PRPL ||
-         account.connectionErrorReason == Ci.purpleIAccount.ERROR_MISSING_PASSWORD)));
+        (account.connectionErrorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL ||
+         account.connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD)));
 
     [[activeCommandElt, isCommandDisabled],
      [document.getElementById("cmd_moveup"), accountList.selectedIndex == 0],
      [document.getElementById("cmd_movedown"),
       accountList.selectedIndex == accountList.itemCount - 1]
     ].forEach(function (aEltArray) {
       let [elt, state] = aEltArray;
       if (state)
@@ -441,94 +444,94 @@ var gAccountManager = {
       newIndex = 0;
     else if (newIndex >= accountList.itemCount)
       newIndex = accountList.itemCount - 1;
     array.splice(newIndex, 0, selectedID);
 
     Services.prefs.setCharPref("messenger.accounts", array.join(","));
   },
 
-  getAccounts: function am_getAccounts() getIter(Services.core.getAccounts()),
+  getAccounts: function am_getAccounts() getIter(Services.accounts.getAccounts()),
 
   openDialog: function am_openDialog(aUrl, aArgs) {
     this.modalDialog = true;
     window.openDialog(aUrl, "", "chrome,modal,titlebar,centerscreen", aArgs);
     this.modalDialog = false;
   },
   setAutoLoginNotification: function am_setAutoLoginNotification() {
-    var pcs = Services.core;
-    var autoLoginStatus = pcs.autoLoginStatus;
+    var as = Services.accounts;
+    var autoLoginStatus = as.autoLoginStatus;
     let isOffline = false;
     let crashCount = 0;
     for (let acc in this.getAccounts())
       if (acc.autoLogin && acc.firstConnectionState == acc.FIRST_CONNECTION_CRASHED)
         ++crashCount;
 
-    if (autoLoginStatus == pcs.AUTOLOGIN_ENABLED && crashCount == 0) {
-      this.setOffline(isOffline ||
-                      pcs.currentStatusType == Ci.imIStatusInfo.STATUS_OFFLINE);
+    if (autoLoginStatus == as.AUTOLOGIN_ENABLED && crashCount == 0) {
+      let status = Services.core.globalUserStatus.statusType;
+      this.setOffline(isOffline || status == Ci.imIStatusInfo.STATUS_OFFLINE);
       return;
     }
 
     var bundle = document.getElementById("accountsBundle");
     var box = document.getElementById("accountsNotificationBox");
     var priority = box.PRIORITY_INFO_HIGH;
     var connectNowButton = {
       accessKey: bundle.getString("accountsManager.notification.button.accessKey"),
       callback: this.processAutoLogin,
       label: bundle.getString("accountsManager.notification.button.label")
     };
     var label;
 
     switch (autoLoginStatus) {
-      case pcs.AUTOLOGIN_USER_DISABLED:
+      case as.AUTOLOGIN_USER_DISABLED:
         label = bundle.getString("accountsManager.notification.userDisabled.label");
         break;
 
-      case pcs.AUTOLOGIN_SAFE_MODE:
+      case as.AUTOLOGIN_SAFE_MODE:
         label = bundle.getString("accountsManager.notification.safeMode.label");
         break;
 
-      case pcs.AUTOLOGIN_START_OFFLINE:
+      case as.AUTOLOGIN_START_OFFLINE:
         label = bundle.getString("accountsManager.notification.startOffline.label");
         isOffline = true;
         break;
 
-      case pcs.AUTOLOGIN_CRASH:
+      case as.AUTOLOGIN_CRASH:
         label = bundle.getString("accountsManager.notification.crash.label");
         priority = box.PRIORITY_WARNING_MEDIUM;
         break;
 
       /* One or more accounts made the application crash during their connection.
          If none, this function has already returned */
-      case pcs.AUTOLOGIN_ENABLED:
+      case as.AUTOLOGIN_ENABLED:
         if (!("PluralForm" in window))
           Components.utils.import("resource://gre/modules/PluralForm.jsm");
         label = bundle.getString("accountsManager.notification.singleCrash.label");
         label = PluralForm.get(crashCount, label).replace("#1", crashCount);
         priority = box.PRIORITY_WARNING_MEDIUM;
         connectNowButton.callback = this.processCrashedAccountsLogin;
         break;
 
       default:
         label = bundle.getString("accountsManager.notification.other.label");
     }
-    this.setOffline(isOffline ||
-                    pcs.currentStatusType == Ci.imIStatusInfo.STATUS_OFFLINE);
+    let status = Services.core.globalUserStatus.statusType;
+    this.setOffline(isOffline || status == Ci.imIStatusInfo.STATUS_OFFLINE);
 
     box.appendNotification(label, "autologinStatus", null, priority, [connectNowButton]);
   },
   processAutoLogin: function am_processAutoLogin() {
     var ioService = Services.io;
     if (ioService.offline) {
       ioService.manageOfflineStatus = false;
       ioService.offline = false;
     }
 
-    Services.core.processAutoLogin();
+    Services.accounts.processAutoLogin();
 
     gAccountManager.accountList.selectedItem.buttons.setFocus();
   },
   processCrashedAccountsLogin: function am_processCrashedAccountsLogin() {
     for (let acc in gAccountManager.getAccounts())
       if (acc.disconnected && acc.autoLogin &&
           acc.firstConnectionState == acc.FIRST_CONNECTION_CRASHED)
         acc.connect();
--- a/im/content/addbuddy.js
+++ b/im/content/addbuddy.js
@@ -39,17 +39,17 @@
 var addBuddy = {
   onload: function ab_onload() {
     this.buildAccountList();
     this.buildTagList();
   },
 
   buildAccountList: function ab_buildAccountList() {
     var accountList = document.getElementById("accountlist");
-    for (let acc in getIter(Services.core.getAccounts())) {
+    for (let acc in getIter(Services.accounts.getAccounts())) {
       if (!acc.connected)
         continue;
       var proto = acc.protocol;
       var item = accountList.appendItem(acc.name, acc.id, proto.name);
       item.setAttribute("image", proto.iconBaseURI + "icon.png");
       item.setAttribute("class", "menuitem-iconic");
     }
     if (!accountList.itemCount) {
@@ -71,17 +71,17 @@ var addBuddy = {
       tagList.appendItem(tag.name, tag.id);
     });
     tagList.selectedIndex = 0;
   },
 
   getValue: function ab_getValue(aId) document.getElementById(aId).value,
 
   create: function ab_create() {
-    var account = Services.core.getAccountById(this.getValue("accountlist"));
+    var account = Services.accounts.getAccountById(this.getValue("accountlist"));
     var name = this.getValue("name");
 
     var tag;
     var taglist = document.getElementById("taglist");
     var items = taglist.getElementsByAttribute("label", taglist.label);
     if (items.length)
       tag = Services.tags.getTagById(items[0].value);
     else
--- a/im/content/blist.js
+++ b/im/content/blist.js
@@ -43,17 +43,17 @@ const events = ["contact-availability-ch
                 "contact-tag-removed",
                 "showing-ui-conversation",
                 "status-changed",
                 "tag-hidden",
                 "tag-shown",
                 "ui-conversation-hidden",
                 "user-display-name-changed",
                 "user-icon-changed",
-                "purple-quit"];
+                "prpl-quit"];
 
 const showOfflineBuddiesPref = "messenger.buddies.showOffline";
 
 var gBuddyListContextMenu = null;
 
 function buddyListContextMenu(aXulMenu) {
   this.target  = document.popupNode;
   this.menu    = aXulMenu;
@@ -308,17 +308,17 @@ buddyListContextMenu.prototype = {
       !!document.getElementById("context-show-offline-buddies")
                 .getAttribute("checked");
     Services.prefs.setBoolPref(showOfflineBuddiesPref, newValue);
   }
 };
 
 var buddyList = {
   observe: function bl_observe(aSubject, aTopic, aMsg) {
-    if (aTopic == "purple-quit") {
+    if (aTopic == "prpl-quit") {
       window.close();
       return;
     }
 
     if (aTopic == "nsPref:changed" && aMsg == showOfflineBuddiesPref) {
       let showOffline = Services.prefs.getBoolPref(showOfflineBuddiesPref);
       this._showOffline = showOffline;
       let item = document.getElementById("context-show-offline-buddies");
@@ -395,22 +395,22 @@ var buddyList = {
           this.showOtherContacts();
         else if (!document.getElementById("group" + aTag.id))
           this.displayGroup(aTag);
       }, this);
     }
   },
 
   displayUserIcon: function bl_displayUserIcon() {
-    let icon = Services.core.getUserIcon();
+    let icon = Services.core.globalUserStatus.getUserIcon();
     document.getElementById("userIcon").src = icon ? icon.spec : "";
   },
 
   displayUserDisplayName: function bl_displayUserDisplayName() {
-    let displayName = Services.core.userDisplayName;
+    let displayName = Services.core.globalUserStatus.displayName;
     let elt = document.getElementById("displayName");
     if (displayName)
       elt.removeAttribute("usingDefault");
     else {
       let bundle = document.getElementById("instantbirdBundle");
       displayName = bundle.getString("displayNameEmptyText");
       elt.setAttribute("usingDefault", displayName);
     }
@@ -423,35 +423,35 @@ var buddyList = {
     let statusString = Status.toLabel(aStatusType);
     let statusTypeIcon = document.getElementById("statusTypeIcon");
     statusTypeIcon.setAttribute("status", aStatusType);
     statusTypeIcon.setAttribute("tooltiptext", statusString);
     return statusString;
   },
 
   displayCurrentStatus: function bl_displayCurrentStatus() {
-    let pcs = Services.core;
-    let status = Status.toAttribute(pcs.currentStatusType);
-    let message = status == "offline" ? "" : pcs.currentStatusMessage;
+    let us = Services.core.globalUserStatus;
+    let status = Status.toAttribute(us.statusType);
+    let message = status == "offline" ? "" : us.statusText;
     let statusString = this.displayStatusType(status);
     let statusMessage = document.getElementById("statusMessage");
     if (message)
       statusMessage.removeAttribute("usingDefault");
     else {
       statusMessage.setAttribute("usingDefault", statusString);
       message = statusString;
     }
     statusMessage.setAttribute("value", message);
     statusMessage.setAttribute("tooltiptext", message);
   },
 
   editStatus: function bl_editStatus(aEvent) {
     let status = aEvent.originalTarget.getAttribute("status");
     if (status == "offline")
-      Services.core.setStatus(Ci.imIStatusInfo.STATUS_OFFLINE, "");
+      Services.core.globalUserStatus.setStatus(Ci.imIStatusInfo.STATUS_OFFLINE, "");
     else if (status)
       this.startEditStatus(status);
   },
 
   startEditStatus: function bl_startEditStatus(aStatusType) {
     let currentStatusType =
       document.getElementById("statusTypeIcon").getAttribute("status");
     if (aStatusType != currentStatusType) {
@@ -471,17 +471,17 @@ var buddyList = {
     let elt = document.getElementById("statusMessage");
     if (!elt.hasAttribute("editing")) {
       elt.setAttribute("editing", "true");
       elt.addEventListener("keypress", this.statusMessageKeyPress);
       elt.addEventListener("blur", this.statusMessageBlur);
       if (elt.hasAttribute("usingDefault")) {
         if ("_statusTypeBeforeEditing" in this &&
             this._statusTypeBeforeEditing == "offline")
-          elt.setAttribute("value", Services.core.currentStatusMessage);
+          elt.setAttribute("value", Services.core.globalUserStatus.statusMessage);
         else
           elt.removeAttribute("value");
       }
       if (!("TextboxSpellChecker" in window))
         Components.utils.import("resource:///modules/imTextboxUtils.jsm");
       TextboxSpellChecker.registerTextbox(elt);
       // force binding attachmant by forcing layout
       elt.getBoundingClientRect();
@@ -535,17 +535,17 @@ var buddyList = {
         else if (statusType == "offline")
           newStatus = Ci.imIStatusInfo.STATUS_OFFLINE;
         delete this._statusTypeBeforeEditing;
         delete this._statusTypeEditing;
       }
       // apply the new status only if it is different from the current one
       if (newStatus != Ci.imIStatusInfo.STATUS_UNKNOWN ||
           elt.value != elt.getAttribute("value"))
-        Services.core.setStatus(newStatus, elt.value);
+        Services.core.globalUserStatus.setStatus(newStatus, elt.value);
     }
     else if ("_statusTypeBeforeEditing" in this) {
       this.displayStatusType(this._statusTypeBeforeEditing);
       delete this._statusTypeBeforeEditing;
       delete this._statusTypeEditing;
     }
 
     if (elt.hasAttribute("usingDefault"))
@@ -560,17 +560,17 @@ var buddyList = {
     const nsIFilePicker = Components.interfaces.nsIFilePicker;
     let fp = Components.classes["@mozilla.org/filepicker;1"]
                        .createInstance(nsIFilePicker);
     let bundle = document.getElementById("instantbirdBundle");
     fp.init(window, bundle.getString("userIconFilePickerTitle"),
             nsIFilePicker.modeOpen);
     fp.appendFilters(nsIFilePicker.filterImages);
     if (fp.show() == nsIFilePicker.returnOK)
-      Services.core.setUserIcon(fp.file);
+      Services.core.globalUserStatus.setUserIcon(fp.file);
   },
 
   displayNameClick: function bl_displayNameClick() {
     let elt = document.getElementById("displayName");
     if (!elt.hasAttribute("editing")) {
       elt.setAttribute("editing", "true");
       if (elt.hasAttribute("usingDefault"))
         elt.removeAttribute("value");
@@ -612,17 +612,17 @@ var buddyList = {
     }
   },
 
   finishEditDisplayName: function bl_finishEditDisplayName(aSave) {
     clearTimeout(this._stopEditDisplayNameTimeout);
     let elt = document.getElementById("displayName");
     // Apply the new display name only if it is different from the current one.
     if (aSave && elt.value != elt.getAttribute("value"))
-      Services.core.userDisplayName = elt.value;
+      Services.core.globalUserStatus.displayName = elt.value;
     else if (elt.hasAttribute("usingDefault"))
       elt.setAttribute("value", elt.getAttribute("usingDefault"));
 
     elt.removeAttribute("editing");
     elt.removeEventListener("keypress", this.displayNameKeyPress, false);
     elt.removeEventListener("blur", this.displayNameBlur, false);
   },
 
--- a/im/content/debug/debug.js
+++ b/im/content/debug/debug.js
@@ -61,33 +61,32 @@ function debug_enumerateProtocols()
            opt.name + (opt.masked ? "(masked)" : "") + "\t" +
            type[opt.type][1]() + "\n");
     }
   }
 }
 
 function debug_connectAccount(aProto, aName, aPassword)
 {
-  var pcs = Services.core;
-  var proto = pcs.getProtocolById(aProto);
+  var proto = Services.core.getProtocolById(aProto);
   if (!proto)
     throw "Couldn't get protocol " + aProto;
 
-  var acc = pcs.createAccount(aName, proto);
+  var acc = Services.accounts.createAccount(aName, proto);
   acc.password = aPassword;
   dump("trying to connect to " + proto.name +
        " (" + proto.id + ") with " + aName + "\n");
   acc.connect();
 }
 
 function debug_dumpBuddyList()
 {
   let formatBuddy = (function(buddy) "  " + buddy.name + "\n   " + buddy.getAccounts().map(function(a) a.name).join(" "));
   let formatGroup = (function(aGroup) " Group " + aGroup.id + ": " + aGroup.name + "\n" + aGroup.getBuddies().map(formatBuddy).join("\n"));
-  dump("Buddy list:\n\n" + Services.core.getTags().map(formatGroup).join("\n\n") + "\n\n");
+  dump("Buddy list:\n\n" + Services.tags.getTags().map(formatGroup).join("\n\n") + "\n\n");
 }
 
 function dumpStack(offset, max_depth)
 {
   if (!offset || offset<0) offset = 0;
   if (!max_depth) max_depth = 10;
   var frame = Components.stack;
   while(--max_depth && (frame=frame.caller)) {
--- a/im/content/debug/fake/fake.js
+++ b/im/content/debug/fake/fake.js
@@ -298,48 +298,48 @@ var fake = {
                 {time: makeDate("10:43:32"), outgoing: true, conversation: chat});
     new Message("Andrew", "Instantbird is great!",
                 {time: makeDate("10:43:52"), incoming: true, conversation: chat});
     new Message("Daniel", "Sure! I love it! <3",
                 {time: makeDate("10:44:01"), incoming: true, conversation: chat});
     new Message("Florian", "Thanks :)",
                 {time: makeDate("10:44:12"), incoming: true, conversation: chat});
 
-    Services.core.userDisplayName = "Tom Smith";
+    Services.core.globalUserStatus.displayName = "Tom Smith";
     // Ugly :-(
     document.getElementById("userIcon").src = ib_icon_url;
   },
   deleteAccounts: function f_deleteAccounts() {
-    if (!Services.core.getAccounts().hasMoreElements())
+    if (!Services.accounts.getAccounts().hasMoreElements())
       return;
 
     var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                             .getService(Components.interfaces.nsIPromptService);
     if (!prompts.confirm(window, "Are you sure you want to delete all accounts?",
                          "You are about to delete " + nbaccounts + " accounts. Are you sure?"))
       throw "user aborted the operation";
 
-    for (let acc in getIter(Services.core.getAccounts()))
-      Services.core.deleteAccount(acc.id);
+    for (let acc in getIter(Services.accounts.getAccounts()))
+      Services.accounts.deleteAccount(acc.id);
   }
 };
 
 this.addEventListener("load", fake.load);
 
 var gLastAccountId = 0;
 function Account(aName, aProto)
 {
   this.name = aName;
   this.protocol = Services.core.getProtocolById(aProto);
   this.id = "account" + (++gLastAccountId);
 
   dump("account " + aName + " created\n");
 }
 Account.prototype = {
-  __proto__: ClassInfo("purpleIAccount", "generic account object"),
+  __proto__: ClassInfo("imIAccount", "generic account object"),
   protocol: null,
   password: "",
   autoLogin: true,
   rememberPassword: true,
   alias: "",
   proxyInfo: null,
   connectionStageMsg: "",
   connectionErrorReason: -1,
--- a/im/content/joinchat.js
+++ b/im/content/joinchat.js
@@ -169,10 +169,10 @@ var joinChat = {
         autojoin.push(name);
         prefBranch.setCharPref(autoJoinPref, autojoin.join(","));
       }
     }
 
     return true;
   },
 
-  getAccounts: function jc_getAccounts() getIter(Services.core.getAccounts())
+  getAccounts: function jc_getAccounts() getIter(Services.accounts.getAccounts())
 };
--- a/im/content/menus.js
+++ b/im/content/menus.js
@@ -45,17 +45,17 @@ if (!("Services" in window))
   Components.utils.import("resource:///modules/imServices.jsm");
 if (!("Core" in window))
   Components.utils.import("resource:///modules/ibCore.jsm");
 
 var menus = {
   supportsCommand: function(aCmd)
     aCmd == "cmd_addbuddy" || aCmd == "cmd_joinchat",
   isCommandEnabled: function(aCmd) {
-    let enumerator = Services.core.getAccounts();
+    let enumerator = Services.accounts.getAccounts();
     while (enumerator.hasMoreElements()) {
       let acc = enumerator.getNext();
       if (acc.connected && (aCmd == "cmd_addbuddy" || acc.canJoinChat))
         return true;
     }
     return false;
   },
   doCommand: function(aCmd) {
@@ -171,17 +171,17 @@ var menus = {
 
   joinChat: function menu_joinChat() {
     this.openDialog("Messenger:JoinChat", joinChatWindow);
   },
 
   checkCurrentStatusType: function menu_checkCurrentStatusType(aItems) {
     if (!("Status" in window))
       Components.utils.import("resource:///modules/imStatusUtils.jsm");
-    let status = Status.toAttribute(Services.core.currentStatusType);
+    let status = Status.toAttribute(Services.core.globalUserStatus.statusType);
     if (status == "away")
       status = "unavailable";
 
     aItems.forEach(function (aId) {
       let elt = document.getElementById(aId);
       if (elt.getAttribute("status") == status)
         elt.setAttribute("checked", "true");
       else
@@ -201,15 +201,15 @@ var menus = {
       return; // is this really possible?
 
     let blist = Services.wm.getMostRecentWindow("Messenger:blist");
     if (blist) {
       blist.focus();
       blist.buddyList.startEditStatus(status);
     }
     else {
-      Services.core.setStatus(Status.toFlag(status),
-                              Services.core.currentStatusMessage);
+      let us = Services.core.globalUserStatus;
+      us.setStatus(Status.toFlag(status), us.statusText);
     }
   }
 };
 
 window.addEventListener("load", function() { this.controllers.insertControllerAt(0, menus); });
--- a/im/content/proxies.js
+++ b/im/content/proxies.js
@@ -37,17 +37,18 @@
 
 var gProxies = {
   load: function proxy_load() {
 
   // check the environment
   // see what the global settings are
   // build a list of existing proxies
 
-    var pcs = Services.core;
+    var pcs = Cc["@instantbird.org/libpurple/core;1"]
+              .getService(Ci.purpleICoreService);
 
     var proxyInfoCtr = Components.Constructor("@instantbird.org/purple/proxyinfo;1",
                                               "purpleIProxyInfo");
     var proxyInfo;
     var account = window.arguments[0];
     if (account) {
       proxyInfo = new proxyInfoCtr();
       proxyInfo.type = Ci.purpleIProxyInfo.useGlobal;
@@ -147,17 +148,18 @@ var gProxies = {
     }
   },
 
   getValue: function proxy_getValue(aId) {
     return document.getElementById(aId).value;
   },
 
   accept: function proxy_accept() {
-    var pcs = Services.core;
+    var pcs = Cc["@instantbird.org/libpurple/core;1"]
+              .getService(Ci.purpleICoreService);
     var promptService = Services.prompt;
     var item = document.getElementById("proxylist").selectedItem;
     if (item.id == "newProxy") {
       var type = this.getValue("proxyType");
       var host = this.getValue("hostname");
       var port = this.getValue("port");
       if (!host || !port) {
         promptService.alert(window, bundle.getString("proxies.alert.invalidInput.title"),
--- a/im/locales/en-US/chrome/instantbird/core.properties
+++ b/im/locales/en-US/chrome/instantbird/core.properties
@@ -1,12 +1,10 @@
 startupFailure.title=Instantbird - Start up failure
 startupFailure.apologize=Instantbird encountered a serious error and cannot start, we apologize for the inconvenience.
 startupFailure.update=An updated version will probably be available shortly to fix the problem.
 
-startupFailure.purplexpcomFileError=Description: The file "purplexpcom.xpt" is missing or corrupted.
-startupFailure.xpcomRegistrationError=Description: XPCOM registration of the purplexpcom component failed.
-startupFailure.purplexpcomInitError=An exception occurred while initializing purplexpcom: %S
-startupFailure.libpurpleError=An error occurred while checking whether libpurple is usable.
-startupFailure.noProtocolLoaded=Description: No protocol plugin could be loaded.
+startupFailure.purplexpcomFileError=Description: The file "instantbird.xpt" is missing or corrupted.
+startupFailure.xpcomRegistrationError=Description: XPCOM registration of the core component failed.
+startupFailure.purplexpcomInitError=An exception occurred while initializing the core component: %S
 
 startupFailure.buttonUpdate=Check for Updates
 startupFailure.buttonClose=Close Instantbird
--- a/im/modules/ibCore.jsm
+++ b/im/modules/ibCore.jsm
@@ -65,42 +65,29 @@ var Core = {
       // don't worry too much about this exception.
     }
 
     if (!Ci.purpleICoreService) {
       this._promptError("startupFailure.purplexpcomFileError");
       return false;
     }
 
-    if (!Components.classes["@instantbird.org/purple/core;1"]) {
+    if (!Components.classes["@instantbird.org/purple/core-service;1"]) {
       this._promptError("startupFailure.xpcomRegistrationError");
       return false;
     }
 
     try {
-      var pcs = Services.core;
-      pcs.init();
+      Services.core.init();
     }
     catch (e) {
       this._promptError("startupFailure.purplexpcomInitError", e);
       return false;
     }
 
-    if (!pcs.version) {
-      this._promptError("startupFailure.libpurpleError");
-      return false;
-    }
-
-    if (!pcs.getProtocols().hasMoreElements()) {
-      this._promptError("startupFailure.noProtocolLoaded");
-      this.uninitPurpleCore();
-      return false;
-    }
-
-    Services.conversations.initConversations();
     Conversations.init();
     Notifications.init();
     Sounds.init();
 #ifdef XP_WIN
     // For windows seven, initialize the jump list module.
     const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
     if (WINTASKBAR_CONTRACTID in Cc &&
         Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
@@ -165,27 +152,27 @@ var Core = {
     else
       prompter.checkForUpdates();
   },
 
   getIter: function(aEnumerator) {
     while (aEnumerator.hasMoreElements())
       yield aEnumerator.getNext();
   },
-  getAccounts: function() this.getIter(Services.core.getAccounts()),
+  getAccounts: function() this.getIter(Services.accounts.getAccounts()),
 
   /* This function pops up the account manager if no account is
    * connected or connecting.
    * When called during startup (aIsStarting == true), it will also
    * look for crashed accounts.
    */
   _showAccountManagerIfNeeded: function (aIsStarting) {
     // If the current status is offline, we don't need the account manager
     let isOffline =
-      Services.core.currentStatusType == Ci.imIStatusInfo.STATUS_OFFLINE;
+      Services.core.globalUserStatus.statusType == Ci.imIStatusInfo.STATUS_OFFLINE;
     if (isOffline && !aIsStarting)
       return;
 
     let hasActiveAccount = false;
     let hasCrashedAccount = false;
     for (let acc in this.getAccounts()) {
       if (acc.connected || acc.connecting)
         hasActiveAccount = true;
@@ -201,17 +188,17 @@ var Core = {
        In case of connection failure after an automatic reconnection attempt,
        we don't want to popup the account manager */
     if ((!hasActiveAccount && !isOffline) || (aIsStarting && hasCrashedAccount))
       this.showAccounts();
   },
 
   observe: function(aSubject, aTopic, aMsg) {
     if (aTopic == "account-connected") {
-      let account = aSubject.QueryInterface(Components.interfaces.purpleIAccount);
+      let account = aSubject.QueryInterface(Ci.imIAccount);
       if (!account.canJoinChat)
         return;
 
       let pref = "messenger.account." + account.id + ".autoJoin";
       if (Services.prefs.prefHasUserValue(pref)) {
         let autojoin = Services.prefs.getCharPref(pref);
         if (autojoin) {
           autojoin = autojoin.split(",");
@@ -220,17 +207,17 @@ var Core = {
             account.joinChat(values);
           }
         }
       }
       return;
     }
 
     if (aTopic == "account-disconnected") {
-      let account = aSubject.QueryInterface(Ci.purpleIAccount);
+      let account = aSubject.QueryInterface(Ci.imIAccount);
       if (account.reconnectAttempt <= 1)
         this._showAccountManagerIfNeeded(false);
       return;
     }
 
     if (aTopic == "browser-request") {
       Services.ww.openWindow(null,
                              "chrome://instantbird/content/browserRequest.xul",
--- a/purple/purplexpcom/public/purpleIRequest.idl
+++ b/purple/purplexpcom/public/purpleIRequest.idl
@@ -32,21 +32,21 @@
  * 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 ***** */
 
 #include "nsISupports.idl"
 
-interface purpleIAccount;
+interface imIAccount;
 interface nsIDOMWindowInternal;
 interface nsIWebProgress;
 
 [scriptable, uuid(b89dbb38-0de4-11e0-b3d0-0002e304243c)]
 interface purpleIRequestBrowser: nsISupports {
   readonly attribute AUTF8String promptText;
-  readonly attribute purpleIAccount account;
+  readonly attribute imIAccount account;
   readonly attribute AUTF8String url;
   void cancelled();
   void loaded(in nsIDOMWindowInternal aWindow,
               in nsIWebProgress aWebProgress);
 };