mobile/android/components/PromptService.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/components/PromptService.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,835 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +const Ci = Components.interfaces;
     1.8 +const Cc = Components.classes;
     1.9 +const Cr = Components.results;
    1.10 +const Cu = Components.utils;
    1.11 +
    1.12 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.13 +Cu.import("resource://gre/modules/Services.jsm");
    1.14 +Cu.import("resource://gre/modules/Prompt.jsm");
    1.15 +
    1.16 +var gPromptService = null;
    1.17 +
    1.18 +function PromptService() {
    1.19 +  gPromptService = this;
    1.20 +}
    1.21 +
    1.22 +PromptService.prototype = {
    1.23 +  classID: Components.ID("{9a61149b-2276-4a0a-b79c-be994ad106cf}"),
    1.24 +
    1.25 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),
    1.26 +
    1.27 +  /* ----------  nsIPromptFactory  ---------- */
    1.28 +  // XXX Copied from nsPrompter.js.
    1.29 +  getPrompt: function getPrompt(domWin, iid) {
    1.30 +    let doc = this.getDocument();
    1.31 +    if (!doc) {
    1.32 +      let fallback = this._getFallbackService();
    1.33 +      return fallback.QueryInterface(Ci.nsIPromptFactory).getPrompt(domWin, iid);
    1.34 +    }
    1.35 +
    1.36 +    let p = new InternalPrompt(domWin, doc);
    1.37 +    p.QueryInterface(iid);
    1.38 +    return p;
    1.39 +  },
    1.40 +
    1.41 +  /* ----------  private memebers  ---------- */
    1.42 +
    1.43 +  _getFallbackService: function _getFallbackService() {
    1.44 +    return Components.classesByID["{7ad1b327-6dfa-46ec-9234-f2a620ea7e00}"]
    1.45 +                     .getService(Ci.nsIPromptService);
    1.46 +  },
    1.47 +
    1.48 +  getDocument: function getDocument() {
    1.49 +    let win = Services.wm.getMostRecentWindow("navigator:browser");
    1.50 +    return win ? win.document : null;
    1.51 +  },
    1.52 +
    1.53 +  // nsIPromptService and nsIPromptService2 methods proxy to our Prompt class
    1.54 +  // if we can show in-document popups, or to the fallback service otherwise.
    1.55 +  callProxy: function(aMethod, aArguments) {
    1.56 +    let prompt;
    1.57 +    let doc = this.getDocument();
    1.58 +    if (!doc) {
    1.59 +      let fallback = this._getFallbackService();
    1.60 +      return fallback[aMethod].apply(fallback, aArguments);
    1.61 +    }
    1.62 +    let domWin = aArguments[0];
    1.63 +    prompt = new InternalPrompt(domWin, doc);
    1.64 +    return prompt[aMethod].apply(prompt, Array.prototype.slice.call(aArguments, 1));
    1.65 +  },
    1.66 +
    1.67 +  /* ----------  nsIPromptService  ---------- */
    1.68 +
    1.69 +  alert: function() {
    1.70 +    return this.callProxy("alert", arguments);
    1.71 +  },
    1.72 +  alertCheck: function() {
    1.73 +    return this.callProxy("alertCheck", arguments);
    1.74 +  },
    1.75 +  confirm: function() {
    1.76 +    return this.callProxy("confirm", arguments);
    1.77 +  },
    1.78 +  confirmCheck: function() {
    1.79 +    return this.callProxy("confirmCheck", arguments);
    1.80 +  },
    1.81 +  confirmEx: function() {
    1.82 +    return this.callProxy("confirmEx", arguments);
    1.83 +  },
    1.84 +  prompt: function() {
    1.85 +    return this.callProxy("prompt", arguments);
    1.86 +  },
    1.87 +  promptUsernameAndPassword: function() {
    1.88 +    return this.callProxy("promptUsernameAndPassword", arguments);
    1.89 +  },
    1.90 +  promptPassword: function() {
    1.91 +    return this.callProxy("promptPassword", arguments);
    1.92 +  },
    1.93 +  select: function() {
    1.94 +    return this.callProxy("select", arguments);
    1.95 +  },
    1.96 +
    1.97 +  /* ----------  nsIPromptService2  ---------- */
    1.98 +  promptAuth: function() {
    1.99 +    return this.callProxy("promptAuth", arguments);
   1.100 +  },
   1.101 +  asyncPromptAuth: function() {
   1.102 +    return this.callProxy("asyncPromptAuth", arguments);
   1.103 +  }
   1.104 +};
   1.105 +
   1.106 +function InternalPrompt(aDomWin, aDocument) {
   1.107 +  this._domWin = aDomWin;
   1.108 +  this._doc = aDocument;
   1.109 +}
   1.110 +
   1.111 +InternalPrompt.prototype = {
   1.112 +  _domWin: null,
   1.113 +  _doc: null,
   1.114 +
   1.115 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt, Ci.nsIAuthPrompt, Ci.nsIAuthPrompt2]),
   1.116 +
   1.117 +  /* ---------- internal methods ---------- */
   1.118 +  _getPrompt: function _getPrompt(aTitle, aText, aButtons, aCheckMsg, aCheckState) {
   1.119 +    let p = new Prompt({
   1.120 +      window: this._domWin,
   1.121 +      title: aTitle,
   1.122 +      message: aText,
   1.123 +      buttons: aButtons || [
   1.124 +        PromptUtils.getLocaleString("OK"),
   1.125 +        PromptUtils.getLocaleString("Cancel")
   1.126 +      ]
   1.127 +    });
   1.128 +    return p;
   1.129 +  },
   1.130 +
   1.131 +  addCheckbox: function addCheckbox(aPrompt, aCheckMsg, aCheckState) {
   1.132 +    // Don't bother to check for aCheckSate. For nsIPomptService interfaces, aCheckState is an
   1.133 +    // out param and is required to be defined. If we've gotten here without it, something
   1.134 +    // has probably gone wrong and we should fail
   1.135 +    if (aCheckMsg) {
   1.136 +      aPrompt.addCheckbox({
   1.137 +        label: PromptUtils.cleanUpLabel(aCheckMsg),
   1.138 +        checked: aCheckState.value
   1.139 +      });
   1.140 +    }
   1.141 +
   1.142 +    return aPrompt;
   1.143 +  },
   1.144 +
   1.145 +  addTextbox: function(prompt, value, autofocus, hint) {
   1.146 +    prompt.addTextbox({
   1.147 +      value: (value !== null) ? value : "",
   1.148 +      autofocus: autofocus,
   1.149 +      hint: hint
   1.150 +    });
   1.151 +  },
   1.152 +
   1.153 +  addPassword: function(prompt, value, autofocus, hint) {
   1.154 +    prompt.addPassword({
   1.155 +      value: (value !== null) ? value : "",
   1.156 +      autofocus: autofocus,
   1.157 +      hint: hint
   1.158 +    });
   1.159 +  },
   1.160 +
   1.161 +  /* Shows a native prompt, and then spins the event loop for this thread while we wait
   1.162 +   * for a response
   1.163 +   */
   1.164 +  showPrompt: function showPrompt(aPrompt) {
   1.165 +    if (this._domWin) {
   1.166 +      PromptUtils.fireDialogEvent(this._domWin, "DOMWillOpenModalDialog");
   1.167 +      let winUtils = this._domWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
   1.168 +      winUtils.enterModalState();
   1.169 +    }
   1.170 +
   1.171 +    let retval = null;
   1.172 +    aPrompt.show(function(data) {
   1.173 +      retval = data;
   1.174 +    });
   1.175 +
   1.176 +    // Spin this thread while we wait for a result
   1.177 +    let thread = Services.tm.currentThread;
   1.178 +    while (retval == null)
   1.179 +      thread.processNextEvent(true);
   1.180 +
   1.181 +    if (this._domWin) {
   1.182 +      let winUtils = this._domWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
   1.183 +      winUtils.leaveModalState();
   1.184 +      PromptUtils.fireDialogEvent(this._domWin, "DOMModalDialogClosed");
   1.185 +    }
   1.186 +
   1.187 +    return retval;
   1.188 +  },
   1.189 +
   1.190 +  /*
   1.191 +   * ---------- interface disambiguation ----------
   1.192 +   *
   1.193 +   * XXX Copied from nsPrompter.js.
   1.194 +   *
   1.195 +   * nsIPrompt and nsIAuthPrompt share 3 method names with slightly
   1.196 +   * different arguments. All but prompt() have the same number of
   1.197 +   * arguments, so look at the arg types to figure out how we're being
   1.198 +   * called. :-(
   1.199 +   */
   1.200 +  prompt: function prompt() {
   1.201 +    if (gPromptService.inContentProcess)
   1.202 +      return gPromptService.callProxy("prompt", [null].concat(Array.prototype.slice.call(arguments)));
   1.203 +
   1.204 +    // also, the nsIPrompt flavor has 5 args instead of 6.
   1.205 +    if (typeof arguments[2] == "object")
   1.206 +      return this.nsIPrompt_prompt.apply(this, arguments);
   1.207 +    else
   1.208 +      return this.nsIAuthPrompt_prompt.apply(this, arguments);
   1.209 +  },
   1.210 +
   1.211 +  promptUsernameAndPassword: function promptUsernameAndPassword() {
   1.212 +    // Both have 6 args, so use types.
   1.213 +    if (typeof arguments[2] == "object")
   1.214 +      return this.nsIPrompt_promptUsernameAndPassword.apply(this, arguments);
   1.215 +    else
   1.216 +      return this.nsIAuthPrompt_promptUsernameAndPassword.apply(this, arguments);
   1.217 +  },
   1.218 +
   1.219 +  promptPassword: function promptPassword() {
   1.220 +    // Both have 5 args, so use types.
   1.221 +    if (typeof arguments[2] == "object")
   1.222 +      return this.nsIPrompt_promptPassword.apply(this, arguments);
   1.223 +    else
   1.224 +      return this.nsIAuthPrompt_promptPassword.apply(this, arguments);
   1.225 +  },
   1.226 +
   1.227 +  /* ----------  nsIPrompt  ---------- */
   1.228 +
   1.229 +  alert: function alert(aTitle, aText) {
   1.230 +    let p = this._getPrompt(aTitle, aText, [ PromptUtils.getLocaleString("OK") ]);
   1.231 +    p.setHint("alert");
   1.232 +    this.showPrompt(p);
   1.233 +  },
   1.234 +
   1.235 +  alertCheck: function alertCheck(aTitle, aText, aCheckMsg, aCheckState) {
   1.236 +    let p = this._getPrompt(aTitle, aText, [ PromptUtils.getLocaleString("OK") ]);
   1.237 +    this.addCheckbox(p, aCheckMsg, aCheckState);
   1.238 +    let data = this.showPrompt(p);
   1.239 +    if (aCheckState && data.button > -1)
   1.240 +      aCheckState.value = data.checkbox0;
   1.241 +  },
   1.242 +
   1.243 +  confirm: function confirm(aTitle, aText) {
   1.244 +    let p = this._getPrompt(aTitle, aText);
   1.245 +    p.setHint("confirm");
   1.246 +    let data = this.showPrompt(p);
   1.247 +    return (data.button == 0);
   1.248 +  },
   1.249 +
   1.250 +  confirmCheck: function confirmCheck(aTitle, aText, aCheckMsg, aCheckState) {
   1.251 +    let p = this._getPrompt(aTitle, aText, null);
   1.252 +    this.addCheckbox(p, aCheckMsg, aCheckState);
   1.253 +    let data = this.showPrompt(p);
   1.254 +    let ok = data.button == 0;
   1.255 +    if (aCheckState && data.button > -1)
   1.256 +      aCheckState.value = data.checkbox0;
   1.257 +    return ok;
   1.258 +  },
   1.259 +
   1.260 +  confirmEx: function confirmEx(aTitle, aText, aButtonFlags, aButton0,
   1.261 +                      aButton1, aButton2, aCheckMsg, aCheckState) {
   1.262 +    let buttons = [];
   1.263 +    let titles = [aButton0, aButton1, aButton2];
   1.264 +    for (let i = 0; i < 3; i++) {
   1.265 +      let bTitle = null;
   1.266 +      switch (aButtonFlags & 0xff) {
   1.267 +        case Ci.nsIPromptService.BUTTON_TITLE_OK :
   1.268 +          bTitle = PromptUtils.getLocaleString("OK");
   1.269 +          break;
   1.270 +        case Ci.nsIPromptService.BUTTON_TITLE_CANCEL :
   1.271 +          bTitle = PromptUtils.getLocaleString("Cancel");
   1.272 +          break;
   1.273 +        case Ci.nsIPromptService.BUTTON_TITLE_YES :
   1.274 +          bTitle = PromptUtils.getLocaleString("Yes");
   1.275 +          break;
   1.276 +        case Ci.nsIPromptService.BUTTON_TITLE_NO :
   1.277 +          bTitle = PromptUtils.getLocaleString("No");
   1.278 +          break;
   1.279 +        case Ci.nsIPromptService.BUTTON_TITLE_SAVE :
   1.280 +          bTitle = PromptUtils.getLocaleString("Save");
   1.281 +          break;
   1.282 +        case Ci.nsIPromptService.BUTTON_TITLE_DONT_SAVE :
   1.283 +          bTitle = PromptUtils.getLocaleString("DontSave");
   1.284 +          break;
   1.285 +        case Ci.nsIPromptService.BUTTON_TITLE_REVERT :
   1.286 +          bTitle = PromptUtils.getLocaleString("Revert");
   1.287 +          break;
   1.288 +        case Ci.nsIPromptService.BUTTON_TITLE_IS_STRING :
   1.289 +          bTitle = PromptUtils.cleanUpLabel(titles[i]);
   1.290 +        break;
   1.291 +      }
   1.292 +
   1.293 +      if (bTitle)
   1.294 +        buttons.push(bTitle);
   1.295 +
   1.296 +      aButtonFlags >>= 8;
   1.297 +    }
   1.298 +
   1.299 +    let p = this._getPrompt(aTitle, aText, buttons);
   1.300 +    this.addCheckbox(p, aCheckMsg, aCheckState);
   1.301 +    let data = this.showPrompt(p);
   1.302 +    if (aCheckState && data.button > -1)
   1.303 +      aCheckState.value = data.checkbox0;
   1.304 +    return data.button;
   1.305 +  },
   1.306 +
   1.307 +  nsIPrompt_prompt: function nsIPrompt_prompt(aTitle, aText, aValue, aCheckMsg, aCheckState) {
   1.308 +    let p = this._getPrompt(aTitle, aText, null, aCheckMsg, aCheckState);
   1.309 +    p.setHint("prompt");
   1.310 +    this.addTextbox(p, aValue.value, true);
   1.311 +    this.addCheckbox(p, aCheckMsg, aCheckState);
   1.312 +    let data = this.showPrompt(p);
   1.313 +
   1.314 +    let ok = data.button == 0;
   1.315 +    if (aCheckState && data.button > -1)
   1.316 +      aCheckState.value = data.checkbox0;
   1.317 +    if (ok)
   1.318 +      aValue.value = data.textbox0;
   1.319 +    return ok;
   1.320 +  },
   1.321 +
   1.322 +  nsIPrompt_promptPassword: function nsIPrompt_promptPassword(
   1.323 +      aTitle, aText, aPassword, aCheckMsg, aCheckState) {
   1.324 +    let p = this._getPrompt(aTitle, aText, null);
   1.325 +    this.addPassword(p, aPassword.value, true, PromptUtils.getLocaleString("password", "passwdmgr"));
   1.326 +    this.addCheckbox(p, aCheckMsg, aCheckState);
   1.327 +    let data = this.showPrompt(p);
   1.328 +
   1.329 +    let ok = data.button == 0;
   1.330 +    if (aCheckState && data.button > -1)
   1.331 +      aCheckState.value = data.checkbox0;
   1.332 +    if (ok)
   1.333 +      aPassword.value = data.password0;
   1.334 +    return ok;
   1.335 +  },
   1.336 +
   1.337 +  nsIPrompt_promptUsernameAndPassword: function nsIPrompt_promptUsernameAndPassword(
   1.338 +      aTitle, aText, aUsername, aPassword, aCheckMsg, aCheckState) {
   1.339 +    let p = this._getPrompt(aTitle, aText, null);
   1.340 +    this.addTextbox(p, aUsername.value, true, PromptUtils.getLocaleString("username", "passwdmgr"));
   1.341 +    this.addPassword(p, aPassword.value, false, PromptUtils.getLocaleString("password", "passwdmgr"));
   1.342 +    this.addCheckbox(p, aCheckMsg, aCheckState);
   1.343 +    let data = this.showPrompt(p);
   1.344 +
   1.345 +    let ok = data.button == 0;
   1.346 +    if (aCheckState && data.button > -1)
   1.347 +      aCheckState.value = data.checkbox0;
   1.348 +
   1.349 +    if (ok) {
   1.350 +      aUsername.value = data.textbox0;
   1.351 +      aPassword.value = data.password0;
   1.352 +    }
   1.353 +    return ok;
   1.354 +  },
   1.355 +
   1.356 +  select: function select(aTitle, aText, aCount, aSelectList, aOutSelection) {
   1.357 +    let p = this._getPrompt(aTitle, aText, [ PromptUtils.getLocaleString("OK") ]);
   1.358 +    p.addMenulist({ values: aSelectList });
   1.359 +    let data = this.showPrompt(p);
   1.360 +
   1.361 +    let ok = data.button == 0;
   1.362 +    if (ok)
   1.363 +      aOutSelection.value = data.menulist0;
   1.364 +
   1.365 +    return ok;
   1.366 +  },
   1.367 +
   1.368 +  /* ----------  nsIAuthPrompt  ---------- */
   1.369 +
   1.370 +  nsIAuthPrompt_prompt : function (title, text, passwordRealm, savePassword, defaultText, result) {
   1.371 +    // TODO: Port functions from nsLoginManagerPrompter.js to here
   1.372 +    if (defaultText)
   1.373 +      result.value = defaultText;
   1.374 +    return this.nsIPrompt_prompt(title, text, result, null, {});
   1.375 +  },
   1.376 +
   1.377 +  nsIAuthPrompt_promptUsernameAndPassword : function (aTitle, aText, aPasswordRealm, aSavePassword, aUser, aPass) {
   1.378 +    return nsIAuthPrompt_loginPrompt(aTitle, aText, aPasswordRealm, aSavePassword, aUser, aPass);
   1.379 +  },
   1.380 +
   1.381 +  nsIAuthPrompt_promptPassword : function (aTitle, aText, aPasswordRealm, aSavePassword, aPass) {
   1.382 +    return nsIAuthPrompt_loginPrompt(aTitle, aText, aPasswordRealm, aSavePassword, null, aPass);
   1.383 +  },
   1.384 +
   1.385 +  nsIAuthPrompt_loginPrompt: function(aTitle, aPasswordRealm, aSavePassword, aUser, aPass) {
   1.386 +    let checkMsg = null;
   1.387 +    let check = { value: false };
   1.388 +    let [hostname, realm, aUser] = PromptUtils.getHostnameAndRealm(aPasswordRealm);
   1.389 +
   1.390 +    let canSave = PromptUtils.canSaveLogin(hostname, aSavePassword);
   1.391 +    if (canSave) {
   1.392 +      // Look for existing logins.
   1.393 +      let foundLogins = PromptUtils.pwmgr.findLogins({}, hostname, null, realm);
   1.394 +      [checkMsg, check] = PromptUtils.getUsernameAndPassword(foundLogins, aUser, aPass);
   1.395 +    }
   1.396 +
   1.397 +    let ok = false;
   1.398 +    if (aUser)
   1.399 +      ok = this.nsIPrompt_promptUsernameAndPassword(aTitle, aText, aUser, aPass, checkMsg, check);
   1.400 +    else
   1.401 +      ok = this.nsIPrompt_promptPassword(aTitle, aText, aPass, checkMsg, check);
   1.402 +
   1.403 +    if (ok && canSave && check.value)
   1.404 +      PromptUtils.savePassword(hostname, realm, aUser, aPass);
   1.405 +
   1.406 +    return ok;
   1.407 +  },
   1.408 +
   1.409 +  /* ----------  nsIAuthPrompt2  ---------- */
   1.410 +
   1.411 +  promptAuth: function promptAuth(aChannel, aLevel, aAuthInfo) {
   1.412 +    let checkMsg = null;
   1.413 +    let check = { value: false };
   1.414 +    let message = PromptUtils.makeDialogText(aChannel, aAuthInfo);
   1.415 +    let [username, password] = PromptUtils.getAuthInfo(aAuthInfo);
   1.416 +    let [hostname, httpRealm] = PromptUtils.getAuthTarget(aChannel, aAuthInfo);
   1.417 +    let foundLogins = PromptUtils.pwmgr.findLogins({}, hostname, null, httpRealm);
   1.418 +
   1.419 +    let canSave = PromptUtils.canSaveLogin(hostname, null);
   1.420 +    if (canSave)
   1.421 +      [checkMsg, check] = PromptUtils.getUsernameAndPassword(foundLogins, username, password);
   1.422 +
   1.423 +    if (username.value && password.value) {
   1.424 +      PromptUtils.setAuthInfo(aAuthInfo, username.value, password.value);
   1.425 +    }
   1.426 +
   1.427 +    let canAutologin = false;
   1.428 +    if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY &&
   1.429 +        !(aAuthInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) &&
   1.430 +        Services.prefs.getBoolPref("signon.autologin.proxy"))
   1.431 +      canAutologin = true;
   1.432 +
   1.433 +    let ok = canAutologin;
   1.434 +    if (!ok && aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)
   1.435 +      ok = this.nsIPrompt_promptPassword(null, message, password, checkMsg, check);
   1.436 +    else if (!ok)
   1.437 +      ok = this.nsIPrompt_promptUsernameAndPassword(null, message, username, password, checkMsg, check);
   1.438 +
   1.439 +    PromptUtils.setAuthInfo(aAuthInfo, username.value, password.value);
   1.440 +
   1.441 +    if (ok && canSave && check.value)
   1.442 +      PromptUtils.savePassword(foundLogins, username, password, hostname, httpRealm);
   1.443 +
   1.444 +    return ok;
   1.445 +  },
   1.446 +
   1.447 +  _asyncPrompts: {},
   1.448 +  _asyncPromptInProgress: false,
   1.449 +
   1.450 +  _doAsyncPrompt : function() {
   1.451 +    if (this._asyncPromptInProgress)
   1.452 +      return;
   1.453 +
   1.454 +    // Find the first prompt key we have in the queue
   1.455 +    let hashKey = null;
   1.456 +    for (hashKey in this._asyncPrompts)
   1.457 +      break;
   1.458 +
   1.459 +    if (!hashKey)
   1.460 +      return;
   1.461 +
   1.462 +    // If login manger has logins for this host, defer prompting if we're
   1.463 +    // already waiting on a master password entry.
   1.464 +    let prompt = this._asyncPrompts[hashKey];
   1.465 +    let prompter = prompt.prompter;
   1.466 +    let [hostname, httpRealm] = PromptUtils.getAuthTarget(prompt.channel, prompt.authInfo);
   1.467 +    let foundLogins = PromptUtils.pwmgr.findLogins({}, hostname, null, httpRealm);
   1.468 +    if (foundLogins.length > 0 && PromptUtils.pwmgr.uiBusy)
   1.469 +      return;
   1.470 +
   1.471 +    this._asyncPromptInProgress = true;
   1.472 +    prompt.inProgress = true;
   1.473 +
   1.474 +    let self = this;
   1.475 +
   1.476 +    let runnable = {
   1.477 +      run: function() {
   1.478 +        let ok = false;
   1.479 +        try {
   1.480 +          ok = prompter.promptAuth(prompt.channel, prompt.level, prompt.authInfo);
   1.481 +        } catch (e) {
   1.482 +          Cu.reportError("_doAsyncPrompt:run: " + e + "\n");
   1.483 +        }
   1.484 +
   1.485 +        delete self._asyncPrompts[hashKey];
   1.486 +        prompt.inProgress = false;
   1.487 +        self._asyncPromptInProgress = false;
   1.488 +
   1.489 +        for (let consumer of prompt.consumers) {
   1.490 +          if (!consumer.callback)
   1.491 +            // Not having a callback means that consumer didn't provide it
   1.492 +            // or canceled the notification
   1.493 +            continue;
   1.494 +
   1.495 +          try {
   1.496 +            if (ok)
   1.497 +              consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
   1.498 +            else
   1.499 +              consumer.callback.onAuthCancelled(consumer.context, true);
   1.500 +          } catch (e) { /* Throw away exceptions caused by callback */ }
   1.501 +        }
   1.502 +        self._doAsyncPrompt();
   1.503 +      }
   1.504 +    }
   1.505 +
   1.506 +    Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
   1.507 +  },
   1.508 +
   1.509 +  asyncPromptAuth: function asyncPromptAuth(aChannel, aCallback, aContext, aLevel, aAuthInfo) {
   1.510 +    let cancelable = null;
   1.511 +    try {
   1.512 +      // If the user submits a login but it fails, we need to remove the
   1.513 +      // notification bar that was displayed. Conveniently, the user will
   1.514 +      // be prompted for authentication again, which brings us here.
   1.515 +      //this._removeLoginNotifications();
   1.516 +
   1.517 +      cancelable = {
   1.518 +        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
   1.519 +        callback: aCallback,
   1.520 +        context: aContext,
   1.521 +        cancel: function() {
   1.522 +          this.callback.onAuthCancelled(this.context, false);
   1.523 +          this.callback = null;
   1.524 +          this.context = null;
   1.525 +        }
   1.526 +      };
   1.527 +      let [hostname, httpRealm] = PromptUtils.getAuthTarget(aChannel, aAuthInfo);
   1.528 +      let hashKey = aLevel + "|" + hostname + "|" + httpRealm;
   1.529 +      let asyncPrompt = this._asyncPrompts[hashKey];
   1.530 +      if (asyncPrompt) {
   1.531 +        asyncPrompt.consumers.push(cancelable);
   1.532 +        return cancelable;
   1.533 +      }
   1.534 +
   1.535 +      asyncPrompt = {
   1.536 +        consumers: [cancelable],
   1.537 +        channel: aChannel,
   1.538 +        authInfo: aAuthInfo,
   1.539 +        level: aLevel,
   1.540 +        inProgress : false,
   1.541 +        prompter: this
   1.542 +      }
   1.543 +
   1.544 +      this._asyncPrompts[hashKey] = asyncPrompt;
   1.545 +      this._doAsyncPrompt();
   1.546 +    } catch (e) {
   1.547 +      Cu.reportError("PromptService: " + e + "\n");
   1.548 +      throw e;
   1.549 +    }
   1.550 +    return cancelable;
   1.551 +  }
   1.552 +};
   1.553 +
   1.554 +let PromptUtils = {
   1.555 +  getLocaleString: function pu_getLocaleString(aKey, aService) {
   1.556 +    if (aService == "passwdmgr")
   1.557 +      return this.cleanUpLabel(this.passwdBundle.GetStringFromName(aKey));
   1.558 +
   1.559 +    return this.cleanUpLabel(this.bundle.GetStringFromName(aKey));
   1.560 +  },
   1.561 +
   1.562 +  //
   1.563 +  // Copied from chrome://global/content/commonDialog.js
   1.564 +  //
   1.565 +  cleanUpLabel: function cleanUpLabel(aLabel) {
   1.566 +    // This is for labels which may contain embedded access keys.
   1.567 +    // If we end in (&X) where X represents the access key, optionally preceded
   1.568 +    // by spaces and/or followed by the ':' character,
   1.569 +    // remove the access key placeholder + leading spaces from the label.
   1.570 +    // Otherwise a character preceded by one but not two &s is the access key.
   1.571 +
   1.572 +    // Note that if you change the following code, see the comment of
   1.573 +    // nsTextBoxFrame::UpdateAccessTitle.
   1.574 +    if (!aLabel)
   1.575 +      return "";
   1.576 +
   1.577 +    if (/ *\(\&([^&])\)(:)?$/.test(aLabel)) {
   1.578 +      aLabel = RegExp.leftContext + RegExp.$2;
   1.579 +    } else if (/^(.*[^&])?\&(([^&]).*$)/.test(aLabel)) {
   1.580 +      aLabel = RegExp.$1 + RegExp.$2;
   1.581 +    }
   1.582 +
   1.583 +    // Special code for using that & symbol
   1.584 +    aLabel = aLabel.replace(/\&\&/g, "&");
   1.585 +
   1.586 +    return aLabel;
   1.587 +  },
   1.588 +
   1.589 +  get pwmgr() {
   1.590 +    delete this.pwmgr;
   1.591 +    return this.pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
   1.592 +  },
   1.593 +
   1.594 +  getHostnameAndRealm: function pu_getHostnameAndRealm(aRealmString) {
   1.595 +    let httpRealm = /^.+ \(.+\)$/;
   1.596 +    if (httpRealm.test(aRealmString))
   1.597 +      return [null, null, null];
   1.598 +
   1.599 +    let uri = Services.io.newURI(aRealmString, null, null);
   1.600 +    let pathname = "";
   1.601 +
   1.602 +    if (uri.path != "/")
   1.603 +      pathname = uri.path;
   1.604 +
   1.605 +    let formattedHostname = this._getFormattedHostname(uri);
   1.606 +    return [formattedHostname, formattedHostname + pathname, uri.username];
   1.607 +  },
   1.608 +
   1.609 +  canSaveLogin: function pu_canSaveLogin(aHostname, aSavePassword) {
   1.610 +    let canSave = !this._inPrivateBrowsing && this.pwmgr.getLoginSavingEnabled(aHostname)
   1.611 +    if (aSavePassword)
   1.612 +      canSave = canSave && (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY)
   1.613 +    return canSave;
   1.614 +  },
   1.615 +
   1.616 +  getUsernameAndPassword: function pu_getUsernameAndPassword(aFoundLogins, aUser, aPass) {
   1.617 +    let checkLabel = null;
   1.618 +    let check = { value: false };
   1.619 +    let selectedLogin;
   1.620 +
   1.621 +    checkLabel = this.getLocaleString("saveButton", "passwdmgr");
   1.622 +
   1.623 +    // XXX Like the original code, we can't deal with multiple
   1.624 +    // account selection. (bug 227632)
   1.625 +    if (aFoundLogins.length > 0) {
   1.626 +      selectedLogin = aFoundLogins[0];
   1.627 +
   1.628 +      // If the caller provided a username, try to use it. If they
   1.629 +      // provided only a password, this will try to find a password-only
   1.630 +      // login (or return null if none exists).
   1.631 +      if (aUser.value)
   1.632 +        selectedLogin = this.findLogin(aFoundLogins, "username", aUser.value);
   1.633 +
   1.634 +      if (selectedLogin) {
   1.635 +        check.value = true;
   1.636 +        aUser.value = selectedLogin.username;
   1.637 +        // If the caller provided a password, prefer it.
   1.638 +        if (!aPass.value)
   1.639 +          aPass.value = selectedLogin.password;
   1.640 +      }
   1.641 +    }
   1.642 +
   1.643 +    return [checkLabel, check];
   1.644 +  },
   1.645 +
   1.646 +  findLogin: function pu_findLogin(aLogins, aName, aValue) {
   1.647 +    for (let i = 0; i < aLogins.length; i++)
   1.648 +      if (aLogins[i][aName] == aValue)
   1.649 +        return aLogins[i];
   1.650 +    return null;
   1.651 +  },
   1.652 +
   1.653 +  savePassword: function pu_savePassword(aLogins, aUser, aPass, aHostname, aRealm) {
   1.654 +    let selectedLogin = this.findLogin(aLogins, "username", aUser.value);
   1.655 +
   1.656 +    // If we didn't find an existing login, or if the username
   1.657 +    // changed, save as a new login.
   1.658 +    if (!selectedLogin) {
   1.659 +      // add as new
   1.660 +      var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
   1.661 +      newLogin.init(aHostname, null, aRealm, aUser.value, aPass.value, "", "");
   1.662 +      this.pwmgr.addLogin(newLogin);
   1.663 +    } else if (aPass.value != selectedLogin.password) {
   1.664 +      // update password
   1.665 +      this.updateLogin(selectedLogin, aPass.value);
   1.666 +    } else {
   1.667 +      this.updateLogin(selectedLogin);
   1.668 +    }
   1.669 +  },
   1.670 +
   1.671 +  updateLogin: function pu_updateLogin(aLogin, aPassword) {
   1.672 +    let now = Date.now();
   1.673 +    let propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag);
   1.674 +    if (aPassword) {
   1.675 +      propBag.setProperty("password", aPassword);
   1.676 +      // Explicitly set the password change time here (even though it would
   1.677 +      // be changed automatically), to ensure that it's exactly the same
   1.678 +      // value as timeLastUsed.
   1.679 +      propBag.setProperty("timePasswordChanged", now);
   1.680 +    }
   1.681 +    propBag.setProperty("timeLastUsed", now);
   1.682 +    propBag.setProperty("timesUsedIncrement", 1);
   1.683 +
   1.684 +    this.pwmgr.modifyLogin(aLogin, propBag);
   1.685 +  },
   1.686 +
   1.687 +  // JS port of http://mxr.mozilla.org/mozilla-central/source/embedding/components/windowwatcher/src/nsPrompt.cpp#388
   1.688 +  makeDialogText: function pu_makeDialogText(aChannel, aAuthInfo) {
   1.689 +    let isProxy    = (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY);
   1.690 +    let isPassOnly = (aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD);
   1.691 +
   1.692 +    let username = aAuthInfo.username;
   1.693 +    let [displayHost, realm] = this.getAuthTarget(aChannel, aAuthInfo);
   1.694 +
   1.695 +    // Suppress "the site says: $realm" when we synthesized a missing realm.
   1.696 +    if (!aAuthInfo.realm && !isProxy)
   1.697 +    realm = "";
   1.698 +
   1.699 +    // Trim obnoxiously long realms.
   1.700 +    if (realm.length > 150) {
   1.701 +      realm = realm.substring(0, 150);
   1.702 +      // Append "..." (or localized equivalent).
   1.703 +      realm += this.ellipsis;
   1.704 +    }
   1.705 +
   1.706 +    let text;
   1.707 +    if (isProxy)
   1.708 +      text = this.bundle.formatStringFromName("EnterLoginForProxy", [realm, displayHost], 2);
   1.709 +    else if (isPassOnly)
   1.710 +      text = this.bundle.formatStringFromName("EnterPasswordFor", [username, displayHost], 2);
   1.711 +    else if (!realm)
   1.712 +      text = this.bundle.formatStringFromName("EnterUserPasswordFor", [displayHost], 1);
   1.713 +    else
   1.714 +      text = this.bundle.formatStringFromName("EnterLoginForRealm", [realm, displayHost], 2);
   1.715 +
   1.716 +    return text;
   1.717 +  },
   1.718 +
   1.719 +  // JS port of http://mxr.mozilla.org/mozilla-central/source/embedding/components/windowwatcher/public/nsPromptUtils.h#89
   1.720 +  getAuthHostPort: function pu_getAuthHostPort(aChannel, aAuthInfo) {
   1.721 +    let uri = aChannel.URI;
   1.722 +    let res = { host: null, port: -1 };
   1.723 +    if (aAuthInfo.flags & aAuthInfo.AUTH_PROXY) {
   1.724 +      let proxy = aChannel.QueryInterface(Ci.nsIProxiedChannel);
   1.725 +      res.host = proxy.proxyInfo.host;
   1.726 +      res.port = proxy.proxyInfo.port;
   1.727 +    } else {
   1.728 +      res.host = uri.host;
   1.729 +      res.port = uri.port;
   1.730 +    }
   1.731 +    return res;
   1.732 +  },
   1.733 +
   1.734 +  getAuthTarget : function pu_getAuthTarget(aChannel, aAuthInfo) {
   1.735 +    let hostname, realm;
   1.736 +    // If our proxy is demanding authentication, don't use the
   1.737 +    // channel's actual destination.
   1.738 +    if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
   1.739 +        if (!(aChannel instanceof Ci.nsIProxiedChannel))
   1.740 +          throw "proxy auth needs nsIProxiedChannel";
   1.741 +
   1.742 +      let info = aChannel.proxyInfo;
   1.743 +      if (!info)
   1.744 +        throw "proxy auth needs nsIProxyInfo";
   1.745 +
   1.746 +      // Proxies don't have a scheme, but we'll use "moz-proxy://"
   1.747 +      // so that it's more obvious what the login is for.
   1.748 +      let idnService = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
   1.749 +      hostname = "moz-proxy://" + idnService.convertUTF8toACE(info.host) + ":" + info.port;
   1.750 +      realm = aAuthInfo.realm;
   1.751 +      if (!realm)
   1.752 +        realm = hostname;
   1.753 +
   1.754 +      return [hostname, realm];
   1.755 +    }
   1.756 +    hostname = this.getFormattedHostname(aChannel.URI);
   1.757 +
   1.758 +    // If a HTTP WWW-Authenticate header specified a realm, that value
   1.759 +    // will be available here. If it wasn't set or wasn't HTTP, we'll use
   1.760 +    // the formatted hostname instead.
   1.761 +    realm = aAuthInfo.realm;
   1.762 +    if (!realm)
   1.763 +      realm = hostname;
   1.764 +
   1.765 +    return [hostname, realm];
   1.766 +  },
   1.767 +
   1.768 +  getAuthInfo : function pu_getAuthInfo(aAuthInfo) {
   1.769 +    let flags = aAuthInfo.flags;
   1.770 +    let username = {value: ""};
   1.771 +    let password = {value: ""};
   1.772 +
   1.773 +    if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain)
   1.774 +      username.value = aAuthInfo.domain + "\\" + aAuthInfo.username;
   1.775 +    else
   1.776 +      username.value = aAuthInfo.username;
   1.777 +
   1.778 +    password.value = aAuthInfo.password
   1.779 +
   1.780 +    return [username, password];
   1.781 +  },
   1.782 +
   1.783 +  setAuthInfo : function (aAuthInfo, username, password) {
   1.784 +    var flags = aAuthInfo.flags;
   1.785 +    if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
   1.786 +      // Domain is separated from username by a backslash
   1.787 +      var idx = username.indexOf("\\");
   1.788 +      if (idx == -1) {
   1.789 +        aAuthInfo.username = username;
   1.790 +      } else {
   1.791 +        aAuthInfo.domain   =  username.substring(0, idx);
   1.792 +        aAuthInfo.username =  username.substring(idx+1);
   1.793 +      }
   1.794 +    } else {
   1.795 +      aAuthInfo.username = username;
   1.796 +    }
   1.797 +    aAuthInfo.password = password;
   1.798 +  },
   1.799 +
   1.800 +  getFormattedHostname : function pu_getFormattedHostname(uri) {
   1.801 +    let scheme = uri.scheme;
   1.802 +    let hostname = scheme + "://" + uri.host;
   1.803 +
   1.804 +    // If the URI explicitly specified a port, only include it when
   1.805 +    // it's not the default. (We never want "http://foo.com:80")
   1.806 +    port = uri.port;
   1.807 +    if (port != -1) {
   1.808 +      let handler = Services.io.getProtocolHandler(scheme);
   1.809 +      if (port != handler.defaultPort)
   1.810 +        hostname += ":" + port;
   1.811 +    }
   1.812 +    return hostname;
   1.813 +  },
   1.814 +
   1.815 +  fireDialogEvent: function(aDomWin, aEventName) {
   1.816 +    // accessing the document object can throw if this window no longer exists. See bug 789888.
   1.817 +    try {
   1.818 +      if (!aDomWin.document)
   1.819 +        return;
   1.820 +      let event = aDomWin.document.createEvent("Events");
   1.821 +      event.initEvent(aEventName, true, true);
   1.822 +      let winUtils = aDomWin.QueryInterface(Ci.nsIInterfaceRequestor)
   1.823 +                           .getInterface(Ci.nsIDOMWindowUtils);
   1.824 +      winUtils.dispatchEventToChromeOnly(aDomWin, event);
   1.825 +    } catch(ex) {
   1.826 +    }
   1.827 +  }
   1.828 +};
   1.829 +
   1.830 +XPCOMUtils.defineLazyGetter(PromptUtils, "passwdBundle", function () {
   1.831 +  return Services.strings.createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
   1.832 +});
   1.833 +
   1.834 +XPCOMUtils.defineLazyGetter(PromptUtils, "bundle", function () {
   1.835 +  return Services.strings.createBundle("chrome://global/locale/commonDialogs.properties");
   1.836 +});
   1.837 +
   1.838 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PromptService]);

mercurial