summaryrefslogtreecommitdiffstats
path: root/mailnews/base/src/msgAsyncPrompter.js
blob: ae114683ae905a10e5a632a9b66ed828fbd6273c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

Components.utils.import("resource://gre/modules/Deprecated.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/Task.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource:///modules/gloda/log4moz.js");

var Ci = Components.interfaces;
var Cc = Components.classes;

function runnablePrompter(asyncPrompter, hashKey) {
  this._asyncPrompter = asyncPrompter;
  this._hashKey = hashKey;
}

runnablePrompter.prototype = {
  _asyncPrompter: null,
  _hashKey: null,

  _promiseAuthPrompt: function(listener) {
    return new Promise((resolve, reject) => {
      try {
        listener.onPromptStartAsync({ onAuthResult: resolve });
      } catch (e) {
        if (e.result == Components.results.NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED) {
          // Fall back to onPromptStart, for add-ons compat
          Deprecated.warning("onPromptStart has been replaced by onPromptStartAsync",
                             "https://bugzilla.mozilla.org/show_bug.cgi?id=1176399");
          let ok = listener.onPromptStart();
          resolve(ok);
        } else {
          reject(e);
        }
      }
    });
  },

  run: Task.async(function *() {
    yield Services.logins.initializationPromise;
    this._asyncPrompter._log.debug("Running prompt for " + this._hashKey);
    let prompter = this._asyncPrompter._pendingPrompts[this._hashKey];
    let ok = false;
    try {
      ok = yield this._promiseAuthPrompt(prompter.first);
    } catch (ex) {
      Components.utils.reportError("runnablePrompter:run: " + ex + "\n");
      prompter.first.onPromptCanceled();
    }

    delete this._asyncPrompter._pendingPrompts[this._hashKey];

    for (var consumer of 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--;

    this._asyncPrompter._log.debug("Finished running prompter for " + this._hashKey);
    this._asyncPrompter._doAsyncAuthPrompt();
  })
};

function msgAsyncPrompter() {
  this._pendingPrompts = {};
  // By default, only log warnings to the error console and errors to dump().
  // You can use the preferences:
  //   msgAsyncPrompter.logging.console
  //   msgAsyncPrompter.logging.dump
  // To change this up.  Values should be one of:
  //   Fatal/Error/Warn/Info/Config/Debug/Trace/All
  this._log = Log4Moz.getConfiguredLogger("msgAsyncPrompter",
                                          Log4Moz.Level.Warn,
                                          Log4Moz.Level.Warn);
}

msgAsyncPrompter.prototype = {
  classID: Components.ID("{49b04761-23dd-45d7-903d-619418a4d319}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMsgAsyncPrompter]),

  _pendingPrompts: null,
  _asyncPromptInProgress: 0,
  _log: null,

  queueAsyncAuthPrompt: function(aKey, aJumpQueue, 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;
    if (aJumpQueue) {
      this._asyncPromptInProgress++;

      this._log.debug("Forcing runnablePrompter for " + aKey);

      let runnable = new runnablePrompter(this, aKey);
      Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
    }
    else
      this._doAsyncAuthPrompt();
  },

  _doAsyncAuthPrompt: function() {
    if (this._asyncPromptInProgress > 0) {
      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++;

    this._log.debug("Dispatching runnablePrompter for " + hashKey);

    let runnable = new runnablePrompter(this, hashKey);
    Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
  }
};

var components = [msgAsyncPrompter];
var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);