// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2009
// http://flock.com
//
// This file may be used under the terms of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// 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.
//
// END FLOCK GPL

const CC = Components.classes;
const CI = Components.interfaces;
const CR = Components.results;
const CU = Components.utils;

CU.import("resource:///modules/FlockXPCOMUtils.jsm");
FlockXPCOMUtils.debug = false;

CU.import("resource:///modules/FlockSvcUtils.jsm");
CU.import("resource:///modules/FlockStringBundleHelpers.jsm");
CU.import("resource:///modules/FlockXMLUtils.jsm");
CU.import("resource:///modules/FlockWebmailUtils.jsm");
CU.import("resource:///modules/json_parse.jsm");

const MODULE_NAME = "Gmail";
const CLASS_NAME = "Gmail Service";
const CLASS_SHORT_NAME = "gmail";
const CLASS_TITLE = "Gmail";
const CLASS_ID = Components.ID("{1e34e47c-ed9a-4148-9946-f8de40723542}");
const CONTRACT_ID = "@flock.com/gmail-service;1";

const SERVICE_ENABLED_PREF = "flock.service.gmail.enabled";
const AUTO_SET_PRIMARY_PREF = "flock.webmail.autoSetPrimaryAccount";
const DISPLAY_UNREAD_COUNT_PREF = "flock.webmail.displayUnreadMessageCount";
const REFRESH_INTERVAL_PREF = "flock.webmail.refreshInterval";

const GMAIL_URL = "https://mail.google.com";
const GMAIL_FAVICON = "chrome://flock/content/services/gmail/favicon.png";
const GMAIL_STRING_BUNDLE = "chrome://flock/locale/services/gmail.properties";

const FLOCK_ERROR_CONTRACTID = "@flock.com/error;1";

const STR_EMAIL = "email";
const STR_UNREAD = "unreadMessages";
const STR_UI_VERSION = "uiVersion";
const STR_WAITING_TO_SEND = "waitingToSend";

const DOMAIN = "google.com";
const MAX_UNREAD_COUNT = 20;  // Gmail max unread messages to retrieve
const DISPLAY_DATA_PACKET_STRUCTURE = false;

var PREFS = null;

/**************************************************************************
 * Component: Flock GmailService
 **************************************************************************/

// Constructor
function GmailService() {
  PREFS = CC["@mozilla.org/preferences-service;1"]
                .getService(CI.nsIPrefBranch);
  this._init();

  var ws = FlockSvcUtils.flockIWebService;
  ws.addDefaultMethod(this, "url");
  ws.addDefaultMethod(this, "isHidden");
  ws.addDefaultMethod(this, "getStringBundle");

  var lws = FlockSvcUtils.flockILoginWebService;
  lws.addDefaultMethod(this, "loginURL");
  lws.addDefaultMethod(this, "tooltip");
  lws.addDefaultMethod(this, "getAccount");
  lws.addDefaultMethod(this, "getAuthenticatedAccount");
  lws.addDefaultMethod(this, "getAccountUrns");
  lws.addDefaultMethod(this, "logout");
  lws.addDefaultMethod(this, "docRepresentsSuccessfulLogin");
  lws.addDefaultMethod(this, "getAccountIDFromDocument");
  lws.addDefaultMethod(this, "getCredentialsFromForm");
  lws.addDefaultMethod(this, "ownsLoginForm");
  lws.addDefaultMethod(this, "getSessionValue");

  FlockSvcUtils.flockIRichContentDropHandler
               .addDefaultMethod(this, "_handleTextareaDrop");
}


/**************************************************************************
 * Flock GmailService: XPCOM Component Creation
 **************************************************************************/

GmailService.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME,
  CLASS_ID,
  CONTRACT_ID,
  GmailService,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.nsIObserver,
    CI.nsITimerCallback,
    CI.flockIPollingService,
    CI.flockIWebService,
    CI.flockILoginWebService,
    CI.flockIMailWebService,
    CI.flockIRichContentDropHandler,
    CI.flockIMigratable
  ]
);

// FlockXPCOMUtils.genericModule() categories
GmailService.prototype._xpcom_categories = [
  { category: "wsm-startup" },
  { category: "flockMigratable" },
  { category: "flockWebService", entry: CLASS_SHORT_NAME },
  { category: "flockRichContentHandler", entry: CLASS_SHORT_NAME }
];


/**************************************************************************
 * Flock GmailService: Private Data and Methods
 **************************************************************************/

// Service initialization
GmailService.prototype._init =
function gmSvc__init() {
  FlockSvcUtils.getLogger(this).init(CLASS_SHORT_NAME);
  this._logger.info(".init()");

  // Determine whether this service has been disabled, and unregister if so
  if (PREFS.getPrefType(SERVICE_ENABLED_PREF) &&
      !PREFS.getBoolPref(SERVICE_ENABLED_PREF))
  {
    this._logger.info("Pref " + SERVICE_ENABLED_PREF
                              + " set to FALSE... not initializing.");
    this.deleteCategories();
    return;
  }

  profiler = CC["@flock.com/profiler;1"].getService(CI.flockIProfiler);
  var evtID = profiler.profileEventStart("gmail-init");

  var prefs = CC["@mozilla.org/preferences-service;1"]
              .getService(CI.nsIPrefBranch2);
  prefs.addObserver(REFRESH_INTERVAL_PREF, this, false);

  this._accountClassCtor = GmailAccount;
  // getWD() also adds the "_acUtils" property.
  FlockSvcUtils.getWD(this);
  FlockSvcUtils.initService(this, true);

  this._authMgr = CC["@flock.com/authmgr;1"]
                  .getService(CI.flockIAuthenticationManager);
  this._authMgr.joinNetwork(CONTRACT_ID, "google");

  // Initialize member that specifies path for localized string bundle
  this._stringBundlePath = GMAIL_STRING_BUNDLE;

  profiler.profileEventEnd(evtID, "");
  this.api = new GMailAPI();
}

// Migrate coop objects
GmailService.prototype._migrateFromCoop =
function gmSvc__migrateFromCoop(aContext) {
  this._logger.debug("_migrateFromCoop(...)");
  if (aContext.majorVersion < 2) {
    // 2.0 changes
    var accountUrns = this.getAccountUrns();
    while (accountUrns.hasMore()) {
      var account = this.getAccount(accountUrns.getNext());
      if (account instanceof CI.flockIWebServiceAccount) {
        account.setCustomParam(STR_EMAIL, "");
        account.setCustomParam("isSessionExpired", false);

        // Migrate old coop urn (USER@gmail.com or user@googlemail.com) to
        // new urn (USER)
        var urn = account.urn.match(/(.+:(.+))@g(?:|oogle)mail\.com/);
        if (urn) {
          // Elements 0, 1 and 2 are valid if urn is non-null
          // Format:
          //   urn[0]: urn:flock:<service>:account:<accountId>@gmail.com
          //   urn[1]: urn:flock:<service>:account:<accountId>
          //   urn[2]: <accountId>
          this._logger.debug("  urn[0]: " + urn[0] + "\n  urn[1]: " + urn[1]
                             + "\n  urn[2]: " + urn[2]);
          account.coopObj.accountId = urn[2];
          account.coopObj.changeId(urn[1]);
          this._coop.get(urn[0] + ":accountParam")
                    .changeId(urn[1] + ":accountParam");

          // Update the urn entry in the Password Manager
          var oldAcctId = urn[0].replace(/^urn:flock:gmail:account:/, "");
          var pw = this._acUtils.removePassword(this.urn + ":" + oldAcctId);
          if (pw) {
            this._acUtils.setPassword(this.urn + ":" + urn[2],
                                      pw.username,
                                      pw.password);
          }
        }
      }
    }
  }
  // Makes this a generator-iterator
  yield true;
};


/**************************************************************************
 * Flock GmailService: flockIWebService Implementation
 **************************************************************************/

// readonly attribute AString urn;
GmailService.prototype.urn = "urn:" + CLASS_SHORT_NAME + ":service";

// readonly attribute AString title;
GmailService.prototype.title = CLASS_TITLE;

// readonly attribute AString icon;
GmailService.prototype.icon = GMAIL_FAVICON;

// readonly attribute AString contractId;
// ALMOST duplicated by nsIClassInfo::contractID
// with different case: contractId != contractID
// FIXME: File a bug for this as IDL is case-insensitive.
GmailService.prototype.contractId = CONTRACT_ID;

// readonly attribute AString shortName;
GmailService.prototype.shortName = CLASS_SHORT_NAME;

// readonly attribute boolean needPassword;
GmailService.prototype.needPassword = true;

// flockIWebServiceAccount addAccount(in AString aAccountId,
//                                    in boolean aIsTransient,
//                                    in flockIListener aFlockListener);
GmailService.prototype.addAccount =
function gmSvc_addAccount(aAccountId, aIsTransient, aFlockListener) {
  this._logger.debug("addAccount('" + aAccountId + "', "
                                    + aIsTransient + ", aFlockListener)");

  if (!aAccountId) {
    if (aFlockListener) {
      var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
      // XXX See bug 10749 - flockIError cleanup
      error.errorCode = 9999;
      aFlockListener.onError(error, "addAccount");
    }
    return null;
  }

  var pw = this._acUtils.getPassword(this.urn + ":" + aAccountId);
  var user = pw ? pw.username : aAccountId;
  var acctId = aAccountId.replace(/@g(?:|oogle)mail\.com$/, "");

  // Create a new account and add it to coop if it doesn't already exist
  var accountUrn =
    this._acUtils.createAccount(this, acctId, user, null, aIsTransient);

  // Instantiate account component
  var account = this.getAccount(accountUrn);
  account.setParam("service", this._coopObj);

  // Get refresh interval from prefs and ensure it's within bounds
  FlockWebmailUtils.setRefreshInterval(account);

  // Add custom parameters to be used
  account.setCustomParam(STR_EMAIL, "");
  account.setCustomParam("isPrimary", false);
  account.setCustomParam("isSessionExpired", false);
  account.setCustomParam("hasNewMessages", false);
  account.setCustomParam("lastPrimaryDate", 0);
  account.setCustomParam(STR_UNREAD,
                         FlockWebmailUtils.createEmptyMessageAsJSON());
  account.setCustomParam(STR_UI_VERSION, "1");
  account.setCustomParam(STR_WAITING_TO_SEND, "");
  account.setCustomParam("isUTF16Encoded", false);

  if (aFlockListener) {
    aFlockListener.onSuccess(account, "addAccount");
  }
  return account;
}

// void removeAccount(in AString aUrn);
GmailService.prototype.removeAccount =
function gmSvc_removeAccount(aUrn) {
  this._logger.debug("removeAccount('" + aUrn + "')");
  this._acUtils.removeAccount(aUrn);
  FlockWebmailUtils.ensurePrimaryAccountExists();
}

// DEFAULT: flockIWebServiceAccount getAccount(in AString aUrn);
// DEFAULT: nsIStringEnumerator getAccountUrns();
// DEFAULT: void logout();


/**************************************************************************
 * Flock GmailService: flockILoginWebService Implementation
 **************************************************************************/

// readonly attribute AString domains;
GmailService.prototype.__defineGetter__("domains",
function gmSvc_getDomains() {
  this._logger.debug("Getting property: domains");
  return this._WebDetective.getString(CLASS_SHORT_NAME, "domains", DOMAIN);
});

// DEFAULT: boolean docRepresentsSuccessfulLogin(in nsIDOMHTMLDocument aDocument);
// DEFAULT: AString getAccountIDFromDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: flockILoginInfo getCredentialsFromForm(in nsIDOMHTMLFormElement aForm);

// DEFAULT: boolean ownsDocument(in nsIDOMHTMLDocument aDocument);
GmailService.prototype.ownsDocument =
function gmSvc_ownsDocument(aDocument)
{
  this._logger.debug("domain: " + aDocument.domain);

  // We need to override the default ownsDocument in this case because
  // multiple services share the same login page (google.com in this case)
  return this._WebDetective.detect(this.shortName,
                                   "ownsDocument",
                                   aDocument,
                                   null);
}

// DEFAULT: boolean ownsLoginForm(in nsIDOMHTMLFormElement aForm);

/**
 * @see flockILoginWebService#updateAccountStatusFromDocument
 */
GmailService.prototype.updateAccountStatusFromDocument =
function gmSvc_updateAccountStatusFromDocument(aDocument,
                                               aAccount,
                                               aFlockListener)
{
  if (aAccount) {
    // We are currently logged in to aAccount
    this._authMgr.reportCompleteLoginToNetwork(aAccount);
    if (!this._acUtils.ensureOnlyAuthenticatedAccount(aAccount)) {
      // Just logged in
      FlockWebmailUtils.onLogin(aAccount);
      if (aFlockListener) {
        aFlockListener.onSuccess(aAccount, null);
      }
    }

    // Store the UI Version: default to the first version
    var uiVersion = "1";
    if (this._WebDetective.detect(this.shortName,
                                  "uiVersion2",
                                  aDocument,
                                  null))
    {
      uiVersion = "2";
    }
    aAccount.setCustomParam(STR_UI_VERSION, uiVersion);

    // Save the email address, if necessary
    this._saveEmail(aAccount, aDocument);

    // Do we have a pending compose message?
    FlockWebmailUtils.checkForPendingComposeMessage(aAccount);
  } else if (this._WebDetective
                 .detect(this.shortName, "loggedout", aDocument, null))
  {
    // Detected a logged out state
    this._acUtils.markAllAccountsAsLoggedOut(this);
    this._authMgr.reportCompleteLogoutFromNetwork(this.contractId);

    // Ensure the webmail icon does not indicate new unread messages while
    // logged out of the primary account
    if (!this._acUtils.primaryAccountIsHot()) {
      FlockWebmailUtils.lightIcon(false);
    }
  }
}

/**************************************************************************
 * Flock GmailService: flockIPollingService Implementation
 **************************************************************************/

// void refresh(in AString aUrn, in flockIPollingListener aPollingListener);
GmailService.prototype.refresh =
function gmSvc_refresh(aURN, aPollingListener) {
  this._logger.debug("GmailAccount refresh('" + aURN + "')");

  var account = this.getAccount(aURN);
  if (!account.isAuthenticated()) {
    account.coopObj.isPollable = false;
    aPollingListener.onResult();
    return;
  }
  var username = account.getParam("name");
  var inst = this;
  var refreshListener = {
    onSuccess: function gmSvc_refreshSuccess(aSubject, aTopic) {
      inst._logger.debug("refresh_onSuccess: " + aURN + ": " + aSubject);
      var messages = aSubject;
      if (!messages) {
        // If messages is null, we probably have an inbox without any unread
        // messages
        var nsJSON = CC["@mozilla.org/dom/json;1"].createInstance(CI.nsIJSON);
        messages = nsJSON.decode(account.getCustomParam(STR_UNREAD));
        messages =
          FlockWebmailUtils.createEmptyMessageAsJSON(messages.newestMessageDate);
      }
      FlockWebmailUtils.handleRefreshedMessages(inst, aURN, messages);
      aPollingListener.onResult();
    },
    onError: function gmSvc_refreshError(aFlockError, aTopic) {
      inst._logger.error("unable to refresh unread messages for: " + aURN);
      aPollingListener.onError(aFlockError);
      if (aFlockError.errorCode == aFlockError.WEBMAIL_API_INVALID_XML &&
          inst._WebDetective.detectCookies(CLASS_SHORT_NAME,
                                           "loggedoutcookies",
                                           null))
      {
        // Light the webmail icon
        FlockWebmailUtils.handleAuthError(account);
      }
    }
  };
  this.api.getUnreadMessages(username, refreshListener);
}


/**************************************************************************
 * Flock GmailService: nsIObserver Implementation
 **************************************************************************/

GmailService.prototype.observe =
function gmSvc_observe(aSubject, aTopic, aState) {
  switch (aTopic) {
    case "nsPref:changed":
      if (aState == REFRESH_INTERVAL_PREF) {
        this._logger.debug("Observer called: refresh interval changed\n");
        FlockWebmailUtils.setRefreshIntervalForAllAccounts(this);
      }
      break;
  }
}


/**************************************************************************
 * Flock GmailService: flockIRichContentDropHandler Implementation
 **************************************************************************/

// boolean handleDrop(in nsITransferable aFlavours,
//                    in nsIDOMHTMLElement aTargetElement);
GmailService.prototype.handleDrop =
function gmSvc_handleDrop(aFlavours, aTargetElement) {
  // Handle textarea drops
  if (aTargetElement instanceof CI.nsIDOMHTMLTextAreaElement) {
    return FlockWebmailUtils.handleTextareaDrop(this,
                                                aFlavours,
                                                aTargetElement);
  }

  // Default handling otherwise
  return false;
};


/**************************************************************************
 * Flock GmailService: flockIMigratable Implementation
 **************************************************************************/

GmailService.prototype.__defineGetter__("migrationName",
function gmSvc_getter_migrationName() {
  this._logger.debug("_getter_migrationName()");
  return flockGetString("common/migrationNames", "migration.name.gmail");
});

// boolean needsMigration(in string aOldVersion);
GmailService.prototype.needsMigration =
function gmSvc_needsMigration(aOldVersion) {
  this._logger.debug("needsMigration(" + aOldVersion + ")");
  // Version: major.minor.revision
  var major = parseInt(aOldVersion[0], 10);
  var minor = parseInt(aOldVersion[2], 10);
  if (major == 1 && (minor == 1 || minor == 2)) {
    // Grackle and Grouse: This service was added for Grackle and changed for
    // Ibis.
    return true;
  }
  return false;
};

// nsISupports startMigration(in string aOldVersion,
//                            in flockIMigrationProgressListener aListener);
GmailService.prototype.startMigration =
function gmSvc_startMigration(aOldVersion, aFlockMigrationProgressListener) {
  this._logger.debug("startMigration(" + aOldVersion
                     + ", aFlockMigrationProgressListener)");
  var ctxt = {
    majorVersion: parseInt(aOldVersion[0], 10),
    minorVersion: parseInt(aOldVersion[2], 10),
    listener: aFlockMigrationProgressListener
  };
  return { wrappedJSObject: ctxt };
};

// boolean doMigrationWork(in nsISupports aMigrationContext);
GmailService.prototype.doMigrationWork =
function gmSvc_doMigrationWork(aMigrationContext) {
  this._logger.debug("doMigrationWork(aMigrationContext)");
  var context = aMigrationContext.wrappedJSObject;
  if (!context.gmailMigrator) {
    context.gmailMigrator = this._migrateFromCoop(context);
  }
  if (context.gmailMigrator.next()) {
    context.gmailMigrator = null;
  }
  return (context.gmailMigrator != null);
};

// void finishMigration(in nsISupports aMigrationContext);
GmailService.prototype.finishMigration =
function gmSvc_finishMigration(aMigrationContext) {
  this._logger.debug("finishMigration(aMigrationContext)");
};

GmailService.prototype._saveEmail =
function gmAcct__saveEmail(aAccount, aDocument) {
  // Email already saved, don't do it again
  if (aAccount.getCustomParam(STR_EMAIL)) {
    this._logger.debug("_saveEmail() called");
    return;
  }
  var results = FlockSvcUtils.newResults();
  if (this._WebDetective
          .detect(CLASS_SHORT_NAME, "accountinfo", aDocument, results))
  {
    aAccount.setCustomParam(STR_EMAIL, results.getPropertyAsAString(STR_EMAIL));
    this._logger.debug("_saveEmail(): " + aAccount.getCustomParam(STR_EMAIL));
  }
};

/**************************************************************************
 * Component: Flock GmailAccount
 **************************************************************************/

// Constructor
function GmailAccount() {
  FlockSvcUtils.getLogger(this).init(CLASS_SHORT_NAME + ":Account");
  this._logger.info(".init()");
  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getCoop(this);
  // BC: Do not use FlockSvcUtils.getWD() here, because it would load
  //     the Web detective file a second time
  this._WebDetective = CC["@flock.com/web-detective;1"]
                       .getService(CI.flockIWebDetective);

  var wsa = FlockSvcUtils.flockIWebServiceAccount;
  wsa.addDefaultMethod(this, "getService");
  wsa.addDefaultMethod(this, "isAuthenticated");
  wsa.addDefaultMethod(this, "getParam");
  wsa.addDefaultMethod(this, "setParam");
  wsa.addDefaultMethod(this, "getCustomParam");
  wsa.addDefaultMethod(this, "getAllCustomParams");
  wsa.addDefaultMethod(this, "setCustomParam");

  // TODO XXX: Setting a private _shortName when adding the
  //           flockIWebmailAccount default methods should only be a
  //           temporary measure until the account has reference to its service
  this._shortName = CLASS_SHORT_NAME;
  var mwsa = FlockSvcUtils.flockIWebmailAccount;
  mwsa.addDefaultMethod(this, "getInboxURL");
  mwsa.addDefaultMethod(this, "getUnreadMessages");
  mwsa.addDefaultMethod(this, "getUpgradeAccount");
  mwsa.addDefaultMethod(this, "isPrimary");
  mwsa.addDefaultMethod(this, "isSessionExpired");
  mwsa.addDefaultMethod(this, "refreshUnreadMessageList");
  mwsa.addDefaultMethod(this, "setAsPrimary");
}


/**************************************************************************
 * Flock GmailAccount: XPCOM Component Creation
 **************************************************************************/

// Use genericComponent() for the goodness it provides (QI, nsIClassInfo, etc),
// but do NOT add GmailAccount to the list of constructors passed
// to FlockXPCOMUtils.genericModule().
GmailAccount.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME + " Account",
  "",
  "",
  GmailAccount,
  0,
  [
    CI.flockIWebServiceAccount,
    CI.flockIWebmailAccount
  ]
  );

/**************************************************************************
 * Flock GmailAccount: flockIWebServiceAccount Implementation
 **************************************************************************/

// readonly attribute AString urn;
GmailAccount.prototype.urn = "";

// void login(in flockIListener aFlockListener);
GmailAccount.prototype.login =
function gmAcct_login(aFlockListener) {
  this._logger.debug("login()");
  if (!this._acUtils.ensureOnlyAuthenticatedAccount(this)) {
    // Just logged in
    FlockWebmailUtils.onLogin(this);
  }
  if (aFlockListener) {
    aFlockListener.onSuccess(this, "login");
  }
};

// void logout(in flockIListener aFlockListener);
GmailAccount.prototype.logout =
function gmAcct_logout(aFlockListener) {
  this._logger.debug("logout()");
  if (this.isAuthenticated()) {
    this.coopObj.isPollable = false;
    if (this.isPrimary()) {
      FlockWebmailUtils.lightIcon(false);
    }
    this.getService().logout();
  }
  if (aFlockListener) {
    aFlockListener.onSuccess(this, "logout");
  }
}

// void keep();
GmailAccount.prototype.keep =
function gmAcct_keep() {
  this._logger.debug("keep()");
  this.coopObj.isTransient = false;
  var urn = this.urn.replace("account:", "service:").replace("flock:", "");
  this._acUtils.makeTempPasswordPermanent(urn);

  // Automatically set the webmail account as the default mail client on the
  // first time the user configures a webmail account
  if (PREFS.getPrefType("flock.webmail.promptAsDefaultMailer") &&
      PREFS.getBoolPref("flock.webmail.promptAsDefaultMailer"))
  {
    PREFS.setBoolPref("flock.webmail.promptAsDefaultMailer", false);
    PREFS.setBoolPref("flock.webmail.useAsDefaultMailer", true);
  }
}

// DEFAULT: void isAuthenticated();
// DEFAULT: nsIVariant getParam(in AString aParamName);
// DEFAULT: void setParam(in AString aParamName, in nsIVariant aValue);
// DEFAULT: nsIVariant getCustomParam(in AString aParamName);
// DEFAULT: nsIPropertyBag getAllCustomParams();
// DEFAULT: void setCustomParam(in AString aParamName, in nsIVariant aValue);


/**************************************************************************
 * Flock GmailAccount: flockIWebmailAccount Implementation
 **************************************************************************/

// AString getComposeURL(in AString aMailParams);
GmailAccount.prototype.getComposeURL =
function gmAcct_getComposeURL(aMailParams) {
  this._logger.debug("getComposeURL('" + aMailParams + "')");
  var params = FlockWebmailUtils.reformatMailParams(this, aMailParams, ",");
  var url = this._WebDetective
                .getString(CLASS_SHORT_NAME,
                           "composeURL_v" + this.getCustomParam(STR_UI_VERSION),
                           "")
                .replace("%params%", params);
  this._logger.debug("compose URL is: " + url);
  return url;
}

// AString getEmailAddress();
GmailAccount.prototype.getEmailAddress =
function gmAcct_getEmailAddress() {
  this._logger.debug("getEmailAddress()");
  var email = this.getCustomParam(STR_EMAIL);
  if (!email) {
    email = this.coopObj.accountId;
  }
  this._logger.debug("email address is: " + email);
  return email;
};

// void onComposeURLLoaded(in AString aComposeURL,
//                         in nsIDOMXULElement aTabBrowser);
GmailAccount.prototype.onComposeURLLoaded =
function gmAcct_onComposeURLLoaded(aComposeURL, aTabBrowser) {
  this._logger.debug("onComposeURLLoaded()");
  // Do post-processing depending on which version of the UI is loaded
  switch (this.getCustomParam(STR_UI_VERSION)) {
    case "2":
      this._onComposeURLLoadedVersion2(aComposeURL, aTabBrowser);
      break;
    case "1":
    default:
      this._onComposeURLLoadedVersion1(aComposeURL, aTabBrowser);
      break;
  }
}

// DEFAULT: AString getInboxURL();
// DEFAULT: AString getUnreadMessages(in boolean aCheckNow);
// DEFAULT: AString getUpgradeAccount();
// DEFAULT: boolean isPrimary();
// DEFAULT: boolean isSessionExpired();
// DEFAULT: void refreshUnreadMessageList();
// DEFAULT: void setAsPrimary();


/*************************************************************************
 * Flock GmailAccount: Private Data and Methods
 *************************************************************************/

GmailAccount.prototype._onComposeURLLoadedVersion1 =
function gmAcct__onComposeURLLoadedVersion1(aComposeURL, aTabBrowser) {
  this._logger.debug("_onComposeURLLoadedVersion1()");
  // Gmail has an older version of its composer, in which newlines are accepted
  // but not displayed. This is the default composer for some locales until they
  // are brought up to date. In this case, we need to replace the newlines in
  // the message with "<br />". To get to the message text, we need to drill
  // down through a frame and two iframes to get to the composer's document.
  var inst = this;
  // Make sure the frame has been loaded onto the page
  var frameListener = {
    onSuccess: function fListener_onSuccess(aSubj, aTopic) {
      inst._logger.info("frameListener: onSuccess()");
      // The frame doesn't have an id so we have to look for it by name.
      // Unfortunately, this name isn't unique so we have to check each element
      // it finds.
      var frame = null;
      var frameName = inst._WebDetective
                          .getString(CLASS_SHORT_NAME,
                                     "composerFrameName_v1",
                                     "main");
      var elements = aTabBrowser.contentDocument.getElementsByName(frameName);
      for (var i = 0; i < elements.length; i++) {
        if (elements[i] instanceof CI.nsIDOMHTMLFrameElement) {
          frame = elements[i];
          break;
        }
      }
      if (frame) {
        // Wait for frame's contents to load
        var iFrame1Listener = {
          onSuccess: function if1Listener_onSuccess(aSubj, aTopic) {
            inst._logger.info("iFrame1Listener: onSuccess()");
            // Get the frame's iFrame
            var iFrameId = inst._WebDetective
                               .getString(CLASS_SHORT_NAME,
                                          "composerIFrame1Id_v1",
                                          "v1");
            var iFrame1 = frame.contentDocument.getElementById(iFrameId);
            if (iFrame1 &&
                iFrame1 instanceof CI.nsIDOMHTMLIFrameElement)
            {
              // Get the iFrame's content
              var iFrame2Listener = {
                onSuccess: function if2Listener_onSuccess(aSubj, aTopic) {
                  inst._logger.info("iFrame2Listener: onSuccess()");
                  // Get the actual composer's iFrame
                  iFrameId = inst._WebDetective
                                 .getString(CLASS_SHORT_NAME,
                                            "composerIFrame2Id_v1",
                                            "hc_compose");
                  var iFrame2 = iFrame1.contentDocument
                                       .getElementById(iFrameId);
                  if (iFrame2 &&
                      iFrame2 instanceof CI.nsIDOMHTMLIFrameElement)
                  {
                    // We cannot set designMode = on yet, because this will
                    // interfere with a script on the page and cause the editor
                    // to lock up. (See bugs 13229 and 12145)

                    // Get the message content
                    var bodyListener = {
                      onSuccess: function bListener_onSuccess(aSubj, aTopic) {
                        inst._logger.info("bodyListener: onSuccess()");
                        // Ensure the frame is editable (See bug 12145)
                        iFrame2.contentDocument.designMode = "on";
                        // Finally! Replace newlines with breaks
                        var body = iFrame2.contentDocument.body;
                        body.innerHTML = body.innerHTML.replace(/\x0A/g,
                                                                "<br />");
                      },
                      onError: function bListener_onError(aFlockError, aTopic) {
                        inst._logger.error("bodyListener: onError()");
                        // Ensure the frame is editable (See bug 12145)
                        iFrame2.contentDocument.designMode = "on";
                      }
                    };
                    // Wait for the message body to get filled
                    inst._WebDetective
                        .asyncDetect(CLASS_SHORT_NAME, "composerBody_v1",
                                     iFrame2, null, bodyListener, 200, 50);
                  }
                },
                onError: function if2Listener_onError(aFlockError, aTopic) {
                  inst._logger.error("iFrame2Listener: onError()");
                }
              };
              // Wait for iFrame1 to get filled
              inst._WebDetective
                  .asyncDetect(CLASS_SHORT_NAME, "composerIFrame2_v1",
                               iFrame1, null, iFrame2Listener, 200, 50);
            }
          },
          onError: function if1Listener_onError(aFlockError, aTopic) {
            inst._logger.error("iFrame1Listener: onError()");
          }
        };
        // Wait for the frame to get filled
        inst._WebDetective
            .asyncDetect(CLASS_SHORT_NAME, "composerIFrame1_v1",
                         frame, null, iFrame1Listener, 200, 50 );
      }
    },
    onError: function fListener_onError(aFlockError, aTopic) {
      inst._logger.error("frameListener: onError()");
    }
  };
  // Wait for the main frame to show up
  inst._WebDetective
      .asyncDetect(CLASS_SHORT_NAME, "composerFrame_v1",
                   aTabBrowser, null, frameListener, 250, 80);
}

GmailAccount.prototype._onComposeURLLoadedVersion2 =
function gmAcct__onComposeURLLoadedVersion2(aComposeURL, aTabBrowser) {
  this._logger.debug("_onComposeURLLoadedVersion2()");
  // Gmail's "newer" composer used to accept mailto parameters properly but this
  // functionality stopped on January 10, 2008. This function will plug in data
  // to all of the message fields after the page has loaded. If the mailto
  // functionality is restored, this post-processing can be turned off by
  // changing the "onComposeURLLoadedVersion2" string in gmail.xml to "none".
  var action = this._WebDetective.getString(CLASS_SHORT_NAME,
                                            "onComposeURLLoadedVersion2",
                                            "none");
  if (action == "none") {
    return;
  }
  var inst = this;
  // Make sure the iFrame has been loaded onto the page
  var iFrame1Listener = {
    onSuccess: function if1Listener_onSuccess(aSubj, aTopic) {
      inst._logger.info("iFrame1Listener: onSuccess()");
      // Get the top iframe
      var iFrameId = inst._WebDetective
                         .getString(CLASS_SHORT_NAME,
                                    "composerIFrame1Id_v2",
                                    "canvas_frame");
      var iFrame1 = aTabBrowser.contentDocument.getElementById(iFrameId);
      if (iFrame1 && iFrame1 instanceof CI.nsIDOMHTMLIFrameElement) {
        // Wait for iframe's form to load
        var formListener = {
          onSuccess: function formListener_onSuccess(aSubj, aTopic) {
            inst._logger.info("formListener: onSuccess()");
            // Parse the mailto parameters and values out of the compose URL
            var params = FlockWebmailUtils.parseMailParams(inst, aComposeURL);
            var frameDoc = iFrame1.contentDocument;
            var results = FlockSvcUtils.newResults();
            function _getFormElement(aType, aName) {
              if (inst._WebDetective
                      .detect(CLASS_SHORT_NAME, aType, frameDoc, results))
              {
                return results.getPropertyAsInterface(aName, CI.nsIDOMNode);
              }
              return null;
            }
            // To - if there's a "to" parameter, then look for the "To:" field
            // on the page and set its value to the parameter
            if (params.to) {
              var toElement = _getFormElement("composerTo_v2", "to");
              if (toElement) {
                toElement.value = params.to;
              }
            }
            // Cc
            if (params.cc) {
              var ccElement = _getFormElement("composerCc_v2", "cc");
              if (ccElement) {
                ccElement.value = params.cc;
                // Show the Cc field and hide the "Add Cc" link
                var addCcElement = _getFormElement("composerAddCc_v2",
                                                   "add_cc");
                if (addCcElement) {
                  // We're not able to remove or hide the element, so we have
                  // to simulate a click on the link
                  var win = CC["@mozilla.org/appshell/window-mediator;1"]
                            .getService(CI.nsIWindowMediator)
                            .getMostRecentWindow("navigator:browser");
                  var evt = frameDoc.createEvent("MouseEvents");
                  evt.initMouseEvent("click", true, true, win, 0, 0, 0, 0, 0,
                                     false, false, false, false, 0, null);
                  addCcElement.dispatchEvent(evt);
                }
              }
            }
            // Bcc
            if (params.bcc) {
              var bccElement = _getFormElement("composerBcc_v2", "bcc");
              if (bccElement) {
                bccElement.value = params.bcc;
                // Show the Bcc field and hide the "Add Bcc" link
                var addBccElement = _getFormElement("composerAddBcc_v2",
                                                   "add_bcc");
                if (addBccElement) {
                  // We're not able to remove or hide the element, so we have
                  // to simulate a click on the link
                  var win = CC["@mozilla.org/appshell/window-mediator;1"]
                            .getService(CI.nsIWindowMediator)
                            .getMostRecentWindow("navigator:browser");
                  var evt = frameDoc.createEvent("MouseEvents");
                  evt.initMouseEvent("click", true, true, win, 0, 0, 0, 0, 0,
                                     false, false, false, false, 0, null);
                  addBccElement.dispatchEvent(evt);
                }
              }
            }
            // Subject
            if (params.subject) {
              var subjElement = _getFormElement("composerSubject_v2",
                                                "subject");
              if (subjElement) {
                subjElement.value = params.subject;
              }
            }
            // Body - we always run this code, even when there is no body
            // parameter, because we want to ensure that the page is editable
            // (See bug 12145)
            var editorListener = {
              onSuccess: function edListener_onSuccess(aSubj, aTopic) {
                inst._logger.info("editorListener: onSuccess()");
                // What loaded: Plain text or Rich text editor?
                var editor = results.getPropertyAsInterface("node",
                                                            CI.nsIDOMNode);
                if (editor instanceof CI.nsIDOMHTMLTextAreaElement) {
                  // Plain Text editor
                  if (params.body) {
                    // We prepend the body data to the TEXTAREA data, in case
                    // the user has a signature
                    editor.value = params.body + editor.value;
                    // Set the focus to clear the drop-down that occurs when the
                    // "to" field is filled.
                    editor.focus();
                  }
                  return;
                }
                // Rich Text editor
                if (editor instanceof CI.nsIDOMHTMLIFrameElement) {
                  // Ensure the page is editable (See bug 12145)
                  editor.contentDocument.designMode = "on";
                  if (params.body) {
                    // Get the message content
                    var bodyListener = {
                      onSuccess: function bListener_onSuccess(aSubj, aTopic) {
                        inst._logger.info("bodyListener: onSuccess()");
                        var body = editor.contentDocument.body;
                        // We prepend the body data to the message body, in case
                        // the user has a signature
                        body.innerHTML = params.body.replace(/\x0A/g, "<br />")
                                       + body.innerHTML;
                        // Set the focus to clear the drop-down that occurs when
                        // the "to" field is filled
                        body.focus();
                      },
                      onError: function bListener_onError(aFlockError, aTopic) {
                        inst._logger.error("bodyListener: onError()");
                      }
                    };
                    // Wait for the iframe to get filled
                    inst._WebDetective
                        .asyncDetect(CLASS_SHORT_NAME, "composerBody_v2",
                                     editor, null, bodyListener, 200, 50);
                  }
                }
              },
              onError: function edListener_onError(aFlockError, aTopic) {
                inst._logger.error("editorListener: onError()");
              }
            };
            // Wait for the editor to be shown
            inst._WebDetective
                .asyncDetect(CLASS_SHORT_NAME, "composerEditor_v2",
                             iFrame1, results, editorListener, 200, 25);
          },
          onError: function formListener_onError(aFlockError, aTopic) {
            inst._logger.error("formListener: onError()");
          }
        };
        // Wait for the iframe to get filled
        inst._WebDetective
            .asyncDetect(CLASS_SHORT_NAME, "composerForm_v2",
                         iFrame1, null, formListener, 250, 50);
      }
    },
    onError: function if1Listener_onError(aFlockError, aTopic) {
      inst._logger.error("iFrame1Listener: onError()");
    }
  };
  // Wait for the iFrame to get loaded
  inst._WebDetective
      .asyncDetect(CLASS_SHORT_NAME, "composerIFrame1_v2",
                   aTabBrowser, null, iFrame1Listener, 250, 80);
}


/**************************************************************************
 * XPCOM Support - Module Construction
 **************************************************************************/

// Create array of components.
var gComponentsArray = [GmailService];

// Generate a module for XPCOM to find.
var NSGetModule = FlockXPCOMUtils.generateNSGetModule(MODULE_NAME,
                                                      gComponentsArray);

/**************************************************************************
 * END XPCOM Support
 **************************************************************************/


/**************************************************************************
 * GMAIL API Start
 **************************************************************************/


function GMailAPI() {
  FlockSvcUtils.getLogger(this).init(CLASS_SHORT_NAME + ":API");
  this._logger.info(".init()");
  this._WebDetective = CC["@flock.com/web-detective;1"]
                       .getService(CI.flockIWebDetective);
}


/**************************************************************************
 * Modified from Firefox extension GMail Notifier
 * https://addons.mozilla.org/en-US/firefox/addon/173
 * The Initial Developer of the Original Code is
 * Doron Rosenberg.
 **************************************************************************/


GMailAPI.prototype._unreadMessagesCallback =
function gmAPI__unreadMessagesCallback(aData, aFlockListener, aThis) {
  // Creates a regular expression from a Web Detective string
  function _getRegExp(aWDStr, aFlags) {
    var re = aThis._WebDetective.getString(CLASS_SHORT_NAME, aWDStr, null);
    return new RegExp(re, aFlags);
  }

  // Returns an array of specific data packets
  function _getDataPacket(aDataPacket, aWDStr) {
    if (!aDataPacket) {
      return null;
    }

    var re = _getRegExp(aWDStr, "g");
    return aDataPacket.match(re);
  }

  // Returns -1 if there was a problem retrieving the inbox count
  function _getInboxCount(aDataPacket) {
    var inbox = _getDataPacket(aDataPacket, "inboxDataPacketRegExp");
    if (!(inbox && inbox.length > 0)) {
      return -1;
    }

    // inbox[0] because we only care about the first element in the array
    var re = _getRegExp("inboxCountRegExp");
    var count = inbox[0].match(re);
    return (count && count[1]) ? count[1] : -1;
  }

  function _parseMessages(aMessageArray, aParsedMessages) {
    var inboxUrl = aThis._WebDetective
                        .getString(CLASS_SHORT_NAME, "inboxURL", DOMAIN);
    var parsedMessages = aParsedMessages;

    // Ensure we're displaying UTF-8 strings to handle non-English characters
    // as well
    function _convertToUTF8(aStr) {
      var val = aStr;
      var conv = CC["@mozilla.org/intl/utf8converterservice;1"]
                 .getService(CI.nsIUTF8ConverterService);
      try {
        val = conv.convertStringToUTF8(val, "utf-8", false);
      } catch (ex) {
        var logger = CC["@flock.com/logger;1"].getService(CI.flockILogger);
        logger.init("Gmail unread message conversion error: " + ex);
      }
      return flockXMLDecode(val);
    }

    // Ignore the first element as it only holds the "t" descriptor
    for (var i = 1; i < aMessageArray.length; i++) {
      var msgs = aMessageArray[i];
      if (msgs.length < 7) {
        continue;
      }

      // Dump the contents of each data packet we're processing
      if (DISPLAY_DATA_PACKET_STRUCTURE) {
        aThis._logger.debug("Content of the next data packet...");

        for (var j = 0; j < msgs.length; j++) {
          aThis._logger.debug("dataPacket[" + j + "]: " + msgs[j]);
        }
      }

      var msgFolder = msgs[8];
      var inboxFolder = /\^i/;
      if (!inboxFolder.test(msgFolder)) {
        // Only process unread INBOX messages; See Bug 15695
        aThis._logger.debug("Skipping this non-Inbox unread message");
        continue;
      }

      // Special Case: Check to see if the draft message was saved in the
      //               inbox as well. See bug 15765.
      var isDraft = /\^r/.test(msgFolder);

      // Retrieve the message id of the newest message to be used as an index
      // for message sorting as well as the newest message date
      var messageID = parseInt(msgs[0], 16);

      // Because Gmail lists all people in the email/conversation (From,
      // separated by commas), only list the last person in the email list
      var froms = msgs[4].split(",");
      var from = froms[froms.length - 1].match(/<b>(.+)<\/b>/);
      if (!from && isDraft) {
        // Special Case: The 'from' field in a draft message has a slightly
        //               different format. See bug 15765.
        from = froms[froms.length - 1].match(/>(.+)<\//);
      }
      var title = msgs[6].match(/<b>(.+)<\/b>/);

      parsedMessages.messages.push({
        index: messageID,
        from: from ? _convertToUTF8(from[1]) : "",
        subject: _convertToUTF8(title[1]),
        url: inboxUrl + "/" + msgs[0]
      });

      if (messageID > parsedMessages.newestMessageDate) {
        // Bug 12211: Gmail does not provide the same date format across
        // different languages so we will use the message id instead as
        // a fake "message date" since all indicators point to this value
        // always incrementing upwards
        parsedMessages.newestMessageDate = messageID;
      }
    }

    return parsedMessages;
  }

  // Retrieve the number of unread messages in the inbox
  var inboxUnread = _getInboxCount(aData);
  if (inboxUnread > 0) {
    var parsedMessages = { newestMessageDate: 0, messages: [] };

    var messageData = _getDataPacket(aData, "messageDataPacketRegExp");
    aThis._logger.debug("found " + messageData.length + " data packets");

    var re = _getRegExp("messageRegExp");
    for each (var message in messageData) {
      var msg = message.match(re)[1];
      // The data packet received may not be valid JSON.  Use json_parse to
      // parse this data as it has been tweaked to be more forgiving than
      // Mozilla's JSON module.  Null values encountered are now returned as
      // empty strings.  See bug 15973.
      msg = (new json_parse)(msg);
      parsedMessages = _parseMessages(msg, parsedMessages);
      aThis._logger.debug("unread message running total: "
                          + parsedMessages.messages.length);
    }

    // Sort comparison helper function
    // Sort the unread messages in reverse order
    var sortBy =
    function _unreadMessagesCallback_sortBy(aElem1, aElem2) {
      return aElem2.index - aElem1.index;
    };

    var unreadMessages = {
      lastCheckedDate: Date.now(),
      newestMessageDate: parsedMessages.newestMessageDate,
      unreadMessageCount: inboxUnread,
      messages: parsedMessages.messages.sort(sortBy)
    };

    var nsJSON = CC["@mozilla.org/dom/json;1"].createInstance(CI.nsIJSON);
    aFlockListener.onSuccess(nsJSON.encode(unreadMessages),
                             "unreadMessagesCallback");
    return;
  } else if (inboxUnread == 0) {
    // The user did not have any unread messages so there are no 't'-messages to
    // process.
    aFlockListener.onSuccess(null, "unreadMessagesCallback");
    return;
  } else {
    aThis._logger.debug("Uh oh, we're having trouble getting the number of "
                        + "unread messages from the inbox.  We may not be "
                        + "receiving any data packets.");
  }

  FlockSvcUtils.getLogger(this)
               .debug("Unable to retrieve unread messages from data packet "
                      + "(data packet may be a non 't'-message)");
  var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
  // XXX See bug 10749 - flockIError cleanup
  error.errorCode = CI.flockIError.WEBMAIL_API_INVALID_XML;
  aFlockListener.onError(error, "unreadMessagesCallback");
}

GMailAPI.prototype.getUnreadMessages =
function gmAPI_getUnreadMessages(aUserName, aFlockListener) {
  var unreadQueryUrl = GMAIL_URL +
                       this._WebDetective.getString(CLASS_SHORT_NAME,
                                                    "unreadQuery",
                                                    DOMAIN);
  this._call(aUserName, unreadQueryUrl, null, null, GMAIL_URL,
             this._unreadMessagesCallback, aFlockListener);
}

GMailAPI.prototype._call =
function gmAPI__call(aUsername, aURL, aPostData,
                     aCookieData, aReferrer,
                     aCallbackFunc, aFlockListener)
{
  // the IO service
  var ioService = CC["@mozilla.org/network/io-service;1"]
                  .getService(CI.nsIIOService);

  // create an nsIURI
  var uri = ioService.newURI(aURL, null, null);

  //nsIInputStream
  var uploadStream = CC["@mozilla.org/io/string-input-stream;1"]
                     .createInstance(CI.nsIStringInputStream);

  if (aPostData) {
    uploadStream.setData(aPostData, aPostData.length);
  }

  // clean up if old channel exists.  Should never happen really!
  if (this.channel) {
    this.channel.cancel(CR.NS_BINDING_ABORTED);
    this.channel = null;
  }

  // get a channel for that nsIURI
  this.channel = ioService.newChannelFromURI(uri);

  // get a httpchannel and make it a post
  var httpChannel = this.channel.QueryInterface(CI.nsIHttpChannel);

  // set a referrer
  if (aReferrer) {
    var referrerUri = ioService.newURI(aReferrer, null, null);
    httpChannel.referrer = referrerUri;
  }

  if (aPostData) {
    var uploadChannel = this.channel.QueryInterface(CI.nsIUploadChannel);
    uploadChannel.setUploadStream(uploadStream,
                                  "application/x-www-form-urlencoded",
                                  -1);

    // order important - setUploadStream resets to get/put
    httpChannel.requestMethod = "POST";
  }

  if (aCookieData) {
    // httpChannel.setRequestHeader("Cookie", aCookieData, false);
    for (var run = 0; run < aCookieData.length; run++) {
      httpChannel.setRequestHeader("Cookie", aCookieData[run], true);
    }
  }

  this.channel.loadFlags |= CI.flockIChannel.SELF_ORIGINATING;

  var observer =
    new this.observer(aCallbackFunc, aUsername, this, aFlockListener);

  this._logger.info("observer notificationCallbacks start");
  this.channel.notificationCallbacks = observer;
  this._logger.info("observer notificationCallbacks done");
  this.channel.asyncOpen(observer, null);
  this._logger.info("observer asyncOpen done");
}

GMailAPI.prototype.observer =
function gmAPI_observer(aCallbackFunc, aUsername, aThis, aFlockListener) {
  aThis._logger.info("observer('" + aUsername + "')");
  var myThis = aThis;

  return ({
    data : "",

    onStartRequest : function gmAPI_obs_onStartRequest(aRequest, aContext) {
      this.data = "";
    },

    onDataAvailable : function gmAPI_obs_onDataAvailable(aRequest,
                                                         aContext,
                                                         aStream,
                                                         aSourceOffset,
                                                         aLength)
    {
      var scriptableInputStream = CC["@mozilla.org/scriptableinputstream;1"]
                                  .createInstance(CI.nsIScriptableInputStream);
      scriptableInputStream.init(aStream);

      this.data += scriptableInputStream.read(aLength);
      myThis._logger.info("observer " + this.data);
    },

    onStopRequest : function gmAPI_obs_onStopRequest(aRequest,
                                                     aContext,
                                                     aStatus)
    {
      aCallbackFunc(this.data, aFlockListener, myThis);
    },

    onChannelRedirect : function gmAPI_obs_onChannelRedirect(aOldChannel,
                                                             aNewChannel,
                                                             aFlags)
    {
      if (aOldChannel == myThis.channel) {
        myThis.channel = aNewChannel;
      }
    },

    // nsIInterfaceRequestor
    getInterface : function gmAPI_obs_getInterface(aIID) {
      try {
        return this.QueryInterface(aIID);
      } catch (e) {
        throw CR.NS_NOINTERFACE;
      }
    },

    // nsIProgressEventSink (to shut up annoying debug exceptions
    onProgress : function gmAPI_obs_onProgress(aRequest, aContext,
                                               aProgress, aProgressMax) {},

    onStatus : function gmAPI_obs_onStatus(aRequest, aContext,
                                           aStatus, aStatusArg) {},

    // nsIHttpEventSink (to shut up annoying debug exceptions
    onRedirect : function gmAPI_obs_onRedirect(aOldChannel, aNewChannel) {},

    QueryInterface : function gmAPI_obs_QueryInterface(aIID) {
      if (aIID.equals(CI.nsISupports) ||
          aIID.equals(CI.nsIDocShell) ||
          aIID.equals(CI.nsIInterfaceRequestor) ||
          aIID.equals(CI.nsIChannelEventSink) ||
          aIID.equals(CI.nsISupportsWeakReference) ||
          aIID.equals(CI.nsIProgressEventSink) ||
          aIID.equals(CI.nsIPrompt) ||
          aIID.equals(CI.nsIHttpEventSink) ||
          aIID.equals(CI.nsIDocShellTreeItem) ||
          aIID.equals(CI.nsIStreamListener))
      {
        return this;
      }

      throw CR.NS_NOINTERFACE;
    }
  });
}


/**************************************************************************
 * GMAIL API End
 **************************************************************************/

