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]);