Part of Bug 338549 Mailnews account password prompts at startup no longer serial - land new service to help with controlling prompts (no effect on existing code yet). r=bienvenu,sr=Neil
authorMark Banner <bugzilla@standard8.plus.com>
Wed, 28 Oct 2009 15:05:41 +0000
changeset 4257 739135304464245f57135f959463f0281f12e7a0
parent 4256 2959a8440c9d7785b2e51049aa7f8e11eb8eac69
child 4258 5ea92a448a5b397d4e128988b2da5242bf8ac4c8
push idunknown
push userunknown
push dateunknown
reviewersbienvenu, Neil
bugs338549
Part of Bug 338549 Mailnews account password prompts at startup no longer serial - land new service to help with controlling prompts (no effect on existing code yet). r=bienvenu,sr=Neil
mailnews/base/public/Makefile.in
mailnews/base/public/nsIMsgAsyncPrompter.idl
mailnews/base/public/nsMsgBaseCID.h
mailnews/base/src/Makefile.in
mailnews/base/src/msgAsyncPrompter.js
--- a/mailnews/base/public/Makefile.in
+++ b/mailnews/base/public/Makefile.in
@@ -98,12 +98,13 @@ XPIDLSRCS	= \
 		nsISpamSettings.idl	        \
 		nsIMapiRegistry.idl \
 		nsIMsgCustomColumnHandler.idl \
 		nsIMsgShutdown.idl \
 		nsMsgFolderFlags.idl \
 		nsMsgMessageFlags.idl \
 		nsIStopwatch.idl \
 		nsIMsgUserFeedbackListener.idl \
+		nsIMsgAsyncPrompter.idl \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/mailnews/base/public/nsIMsgAsyncPrompter.idl
@@ -0,0 +1,92 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 Mailnews Async Prompter.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mark Banner <bugzilla@standard8.plus.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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"
+
+interface nsIMsgAsyncPromptListener;
+
+/**
+ * The nsIMsgAsyncPrompter is intended to provide a way to make asynchoronous
+ * message prompts into synchronous ones - so that the user is only prompted
+ * with one at a time.
+ */
+[scriptable, uuid(f32be63a-c7cb-4c66-8536-b0202cb4604d)]
+interface nsIMsgAsyncPrompter : nsISupports {
+  /**
+   * Queues an async prompt request. If there are none queued then this will be
+   * actioned straight away, otherwise the prompt will be queued for action
+   * once previous prompt(s) have been cleared.
+   *
+   * Queued prompts using the same aKey may be amalgamated into one prompt to
+   * save repeated prompts to the user.
+   *
+   * @param aKey    A key to determine whether or not the queued prompts can
+   *                be combined.
+   * @param aCaller An nsIMsgAsyncPromptListener to call back to when the prompt
+   *                is ready to be made.
+   */
+  void queueAsyncAuthPrompt(in ACString aKey,
+                            in nsIMsgAsyncPromptListener aCaller);
+};
+
+/**
+ * This is used in combination with nsIMsgAsyncPrompter.
+ */
+[scriptable, uuid(fb5307a3-39d0-462e-92c8-c5c288a2612f)]
+interface nsIMsgAsyncPromptListener : nsISupports {
+  /**
+   * Called when the listener should do its prompt. The listener
+   * should not return until the prompt is complete.
+   *
+   * @return  True if there is auth information available following the prompt,
+   *          false otherwise.
+   */
+  boolean onPromptStart();
+
+  /**
+   * Called in the case that the queued prompt was combined with another and
+   * there is now authentication information available.
+   */
+  void onPromptAuthAvailable();
+
+  /**
+   * Called in the case that the queued prompt was combined with another but
+   * the prompt was canceled.
+   */
+  void onPromptCanceled();
+};
--- a/mailnews/base/public/nsMsgBaseCID.h
+++ b/mailnews/base/public/nsMsgBaseCID.h
@@ -521,9 +521,16 @@
 #define NS_MSGSHUTDOWNSERVICE_CID          \
 { /* 483c8abb-ecf9-48a3-a394-2c604b603bd5 */    \
   0x483c8abb, 0xecf9, 0x48a3, \
     { 0xa3, 0x94, 0x2c, 0x60, 0x4b, 0x60, 0x3b, 0xd5 }}
 
 #define NS_MSGSHUTDOWNSERVICE_CONTRACTID   \
   "@mozilla.org/messenger/msgshutdownservice;1"
 
+//
+// msgAsyncPrompter (only contract id for utility purposes as the CID is defined
+// in js).
+//
+#define NS_MSGASYNCPROMPTER_CONTRACTID \
+  "@mozilla.org/messenger/msgAsyncPrompter;1"
+
 #endif // nsMessageBaseCID_h__
--- a/mailnews/base/src/Makefile.in
+++ b/mailnews/base/src/Makefile.in
@@ -172,16 +172,17 @@ endif
 EXPORTS = \
 		nsMsgRDFDataSource.h \
 		nsMsgRDFUtils.h \
 		nsMailDirServiceDefs.h \
 		$(NULL)
 
 EXTRA_COMPONENTS = \
 		nsMailNewsCommandLineHandler.js \
+		msgAsyncPrompter.js \
 		$(NULL)
 
 EXTRA_JS_MODULES = \
 		virtualFolderWrapper.js \
 		$(NULL)
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
new file mode 100644
--- /dev/null
+++ b/mailnews/base/src/msgAsyncPrompter.js
@@ -0,0 +1,149 @@
+/* ***** 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 Mailnews Async Prompter.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mark Banner <bugzilla@standard8.plus.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://app/modules/gloda/log4moz.js");
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+function runnablePrompter(asyncPrompter, hashKey) {
+  this._asyncPrompter = asyncPrompter;
+  this._hashKey = hashKey;
+}
+
+runnablePrompter.prototype = {
+  _asyncPrompter: null,
+  _hashKey: null,
+
+  run: function() {
+    this._asyncPrompter._log.debug("Running prompt for " + this._hashKey);
+    let prompter = this._asyncPrompter._pendingPrompts[this._hashKey];
+    let ok = false;
+    try {
+      ok = prompter.first.onPromptStart();
+    }
+    catch (ex) {
+      Components.utils.reportError("runnablePrompter:run: " + ex + "\n");
+    }
+
+    delete this._asyncPrompter._pendingPrompts[this._hashKey];
+
+    for each (var consumer in prompter.consumers) {
+      try {
+        if (ok)
+          consumer.onPromptAuthAvailable();
+        else
+          consumer.onPromptCanceled();
+      }
+      catch (ex) {
+        // Log the error for extension devs and others to pick up.
+        Components.utils.reportError("runnablePrompter:run: consumer.onPrompt* reported an exception: " + ex + "\n");
+      }
+    }
+    this._asyncPrompter._asyncPromptInProgress = false;
+
+    this._asyncPrompter._log.debug("Finished running prompter for " + this._hashKey);
+    this._asyncPrompter._doAsyncAuthPrompt();
+  }
+};
+
+function msgAsyncPrompter() {
+  this._pendingPrompts = {};
+  this._threadManager = Cc["@mozilla.org/thread-manager;1"]
+                          .getService(Ci.nsIThreadManager);
+  this._log = Log4Moz.getConfiguredLogger("msgAsyncPrompter",
+                                          Log4Moz.Level.Debug,
+                                          Log4Moz.Level.Debug,
+                                          Log4Moz.Level.Debug);
+}
+
+msgAsyncPrompter.prototype = {
+  classDescription: "msgAsyncPrompter",
+  contractID: "@mozilla.org/messenger/msgAsyncPrompter;1",
+  classID: Components.ID("{49b04761-23dd-45d7-903d-619418a4d319}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMsgAsyncPrompter]),
+
+  _pendingPrompts: null,
+  _asyncPromptInProgress: false,
+  _threadManager: null,
+  _log: null,
+
+  queueAsyncAuthPrompt: function(aKey, aCaller) {
+    if (aKey in this._pendingPrompts) {
+      this._log.debug("Prompt bound to an existing one in the queue, key: " + aKey);
+      this._pendingPrompts[aKey].consumers.push(aCaller);
+      return;
+    }
+
+    this._log.debug("Adding new prompt to the queue, key: " + aKey);
+    let asyncPrompt = {
+      first: aCaller,
+      consumers: []
+    };
+
+    this._pendingPrompts[aKey] = asyncPrompt;
+    this._doAsyncAuthPrompt();
+  },
+
+  _doAsyncAuthPrompt: function() {
+    if (this._asyncPromptInProgress) {
+      this._log.debug("_doAsyncAuthPrompt bypassed - prompt already in progress");
+      return;
+    }
+
+    // Find the first prompt key we have in the queue.
+    let hashKey = null;
+    for (hashKey in this._pendingPrompts)
+      break;
+
+    if (!hashKey)
+      return;
+
+    this._asyncPromptInProgress = true;
+
+    this._log.debug("Dispatching runnablePrompter for " + hashKey);
+
+    let runnable = new runnablePrompter(this, hashKey);
+    this._threadManager.mainThread.dispatch(runnable,
+                                            Ci.nsIThread.DISPATCH_NORMAL);
+  }
+};
+
+function NSGetModule(compMgr, fileSpec) {
+  return XPCOMUtils.generateModule([msgAsyncPrompter]);
+}