GeckoViewPrompt.sys.mjs (24454B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs"; 6 7 const lazy = {}; 8 9 ChromeUtils.defineESModuleGetters(lazy, { 10 DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs", 11 GeckoViewPrompter: "resource://gre/modules/GeckoViewPrompter.sys.mjs", 12 GeckoViewClipboardPermission: 13 "resource://gre/modules/GeckoViewClipboardPermission.sys.mjs", 14 }); 15 16 const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewPrompt"); 17 18 export class PromptFactory { 19 constructor() { 20 this.wrappedJSObject = this; 21 } 22 23 handleEvent(aEvent) { 24 switch (aEvent.type) { 25 case "mozshowdropdown": 26 case "mozshowdropdown-sourcetouch": 27 this._handleSelect( 28 aEvent.composedTarget, 29 aEvent.composedTarget.isCombobox 30 ); 31 break; 32 case "MozOpenDateTimePicker": 33 this._handleDateTime(aEvent.composedTarget); 34 break; 35 case "click": 36 this._handleClick(aEvent); 37 break; 38 case "DOMPopupBlocked": 39 this._handlePopupBlocked(aEvent); 40 break; 41 case "DOMRedirectBlocked": 42 this._handleRedirectBlocked(aEvent); 43 break; 44 } 45 } 46 47 _handleClick(aEvent) { 48 const target = aEvent.composedTarget; 49 const className = ChromeUtils.getClassName(target); 50 if (className !== "HTMLInputElement" && className !== "HTMLSelectElement") { 51 return; 52 } 53 54 if ( 55 target.isContentEditable || 56 target.disabled || 57 target.readOnly || 58 !target.willValidate 59 ) { 60 // target.willValidate is false when any associated fieldset is disabled, 61 // in which case this element is treated as disabled per spec. 62 return; 63 } 64 65 if (className === "HTMLSelectElement") { 66 if (!target.isCombobox) { 67 this._handleSelect(target, /* aIsDropDown = */ false); 68 return; 69 } 70 // combobox select is handled by mozshowdropdown. 71 return; 72 } 73 74 const type = target.type; 75 if (type === "month" || type === "week") { 76 // If there's a shadow root, the MozOpenDateTimePicker event takes care 77 // of this. Right now for these input types there's never a shadow root. 78 // Once we support UA widgets for month/week inputs (see bug 888320), we 79 // can remove this. 80 if (!target.openOrClosedShadowRoot) { 81 this._handleDateTime(target); 82 aEvent.preventDefault(); 83 } 84 } 85 } 86 87 _generateSelectItems(aElement) { 88 const win = aElement.ownerGlobal; 89 let id = 0; 90 const map = {}; 91 92 const items = (function enumList(elem, disabled) { 93 const items = []; 94 const children = elem.children; 95 for (let i = 0; i < children.length; i++) { 96 const child = children[i]; 97 if (win.getComputedStyle(child).display === "none") { 98 continue; 99 } 100 const item = { 101 id: String(id), 102 disabled: disabled || child.disabled, 103 }; 104 if (win.HTMLOptGroupElement.isInstance(child)) { 105 item.label = child.label; 106 item.items = enumList(child, item.disabled); 107 } else if (win.HTMLOptionElement.isInstance(child)) { 108 item.label = child.label || child.text; 109 item.selected = child.selected; 110 } else if (win.HTMLHRElement.isInstance(child)) { 111 item.separator = true; 112 } else { 113 continue; 114 } 115 items.push(item); 116 map[id++] = child; 117 } 118 return items; 119 })(aElement); 120 121 return [items, map, id]; 122 } 123 124 _handleSelect(aElement, aIsDropDown) { 125 const win = aElement.ownerGlobal; 126 const [items] = this._generateSelectItems(aElement); 127 128 if (aIsDropDown) { 129 aElement.openInParentProcess = true; 130 } 131 132 const prompt = new lazy.GeckoViewPrompter(win); 133 134 // Something changed the <select> while it was open. 135 const deferredUpdate = new lazy.DeferredTask(() => { 136 // Inner contents in choice prompt are updated. 137 const [newItems] = this._generateSelectItems(aElement); 138 prompt.update({ 139 type: "choice", 140 mode: aElement.multiple ? "multiple" : "single", 141 choices: newItems, 142 }); 143 }, 0); 144 const mut = new win.MutationObserver(() => { 145 deferredUpdate.arm(); 146 }); 147 mut.observe(aElement, { 148 childList: true, 149 subtree: true, 150 attributes: true, 151 }); 152 153 const dismissPrompt = () => prompt.dismiss(); 154 aElement.addEventListener("blur", dismissPrompt, { mozSystemGroup: true }); 155 const hidedropdown = event => { 156 if (aElement === event.target) { 157 prompt.dismiss(); 158 } 159 }; 160 const chromeEventHandler = aElement.ownerGlobal.docShell.chromeEventHandler; 161 chromeEventHandler.addEventListener("mozhidedropdown", hidedropdown, { 162 mozSystemGroup: true, 163 }); 164 165 prompt.asyncShowPrompt( 166 { 167 type: "choice", 168 mode: aElement.multiple ? "multiple" : "single", 169 choices: items, 170 }, 171 result => { 172 deferredUpdate.disarm(); 173 mut.disconnect(); 174 aElement.removeEventListener("blur", dismissPrompt, { 175 mozSystemGroup: true, 176 }); 177 chromeEventHandler.removeEventListener( 178 "mozhidedropdown", 179 hidedropdown, 180 { mozSystemGroup: true } 181 ); 182 183 if (aIsDropDown) { 184 aElement.openInParentProcess = false; 185 } 186 // OK: result 187 // Cancel: !result 188 if (!result || result.choices === undefined) { 189 return; 190 } 191 192 const [, map, id] = this._generateSelectItems(aElement); 193 let dispatchEvents = false; 194 if (!aElement.multiple) { 195 const elem = map[result.choices[0]]; 196 if (elem && win.HTMLOptionElement.isInstance(elem)) { 197 dispatchEvents = !elem.selected; 198 elem.selected = true; 199 } else { 200 console.error("Invalid id for select result: " + result.choices[0]); 201 } 202 } else { 203 for (let i = 0; i < id; i++) { 204 const elem = map[i]; 205 const index = result.choices.indexOf(String(i)); 206 if ( 207 win.HTMLOptionElement.isInstance(elem) && 208 elem.selected !== index >= 0 209 ) { 210 // Current selected is not the same as the new selected state. 211 dispatchEvents = true; 212 elem.selected = !elem.selected; 213 } 214 result.choices[index] = undefined; 215 } 216 for (let i = 0; i < result.choices.length; i++) { 217 if (result.choices[i] !== undefined && result.choices[i] !== null) { 218 console.error( 219 "Invalid id for select result: " + result.choices[i] 220 ); 221 break; 222 } 223 } 224 } 225 226 if (dispatchEvents) { 227 this._dispatchEvents(aElement); 228 } 229 } 230 ); 231 } 232 233 _handleDateTime(aElement) { 234 const win = aElement.ownerGlobal; 235 const prompt = new lazy.GeckoViewPrompter(win); 236 237 const chromeEventHandler = aElement.ownerGlobal.docShell.chromeEventHandler; 238 const dismissPrompt = () => prompt.dismiss(); 239 // Some controls don't have UA widget (bug 888320) 240 { 241 const dateTimeBoxElement = aElement.dateTimeBoxElement; 242 if (["month", "week"].includes(aElement.type) && !dateTimeBoxElement) { 243 aElement.addEventListener("blur", dismissPrompt, { 244 mozSystemGroup: true, 245 }); 246 } else { 247 chromeEventHandler.addEventListener( 248 "MozCloseDateTimePicker", 249 dismissPrompt 250 ); 251 252 dateTimeBoxElement.dispatchEvent( 253 new win.CustomEvent("MozSetDateTimePickerState", { detail: true }) 254 ); 255 } 256 } 257 258 prompt.asyncShowPrompt( 259 { 260 type: "datetime", 261 mode: aElement.type, 262 value: aElement.value, 263 min: aElement.min, 264 max: aElement.max, 265 step: aElement.step, 266 }, 267 result => { 268 // Some controls don't have UA widget (bug 888320) 269 const dateTimeBoxElement = aElement.dateTimeBoxElement; 270 if (["month", "week"].includes(aElement.type) && !dateTimeBoxElement) { 271 aElement.removeEventListener("blur", dismissPrompt, { 272 mozSystemGroup: true, 273 }); 274 } else { 275 chromeEventHandler.removeEventListener( 276 "MozCloseDateTimePicker", 277 dismissPrompt 278 ); 279 dateTimeBoxElement.dispatchEvent( 280 new win.CustomEvent("MozSetDateTimePickerState", { detail: false }) 281 ); 282 } 283 284 // OK: result 285 // Cancel: !result 286 if ( 287 !result || 288 result.datetime === undefined || 289 result.datetime === aElement.value 290 ) { 291 return; 292 } 293 aElement.value = result.datetime; 294 this._dispatchEvents(aElement); 295 } 296 ); 297 } 298 299 _dispatchEvents(aElement) { 300 // Fire both "input" and "change" events for <select> and <input> for 301 // date/time. 302 aElement.dispatchEvent( 303 new aElement.ownerGlobal.Event("input", { bubbles: true, composed: true }) 304 ); 305 aElement.dispatchEvent( 306 new aElement.ownerGlobal.Event("change", { bubbles: true }) 307 ); 308 } 309 310 _handlePopupBlocked(aEvent) { 311 const dwi = aEvent.requestingWindow; 312 const popupWindowURISpec = aEvent.popupWindowURI 313 ? aEvent.popupWindowURI.displaySpec 314 : "about:blank"; 315 316 const prompt = new lazy.GeckoViewPrompter(aEvent.requestingWindow); 317 prompt.asyncShowPrompt( 318 { 319 type: "popup", 320 targetUri: popupWindowURISpec, 321 }, 322 ({ response }) => { 323 if (response && dwi) { 324 dwi.open( 325 popupWindowURISpec, 326 aEvent.popupWindowName, 327 aEvent.popupWindowFeatures 328 ); 329 } 330 } 331 ); 332 } 333 334 _handleRedirectBlocked(aEvent) { 335 const dwi = aEvent.requestingWindow; 336 const redirectURISpec = aEvent.redirectURI.spec; 337 338 const prompt = new lazy.GeckoViewPrompter(aEvent.requestingWindow); 339 prompt.asyncShowPrompt( 340 { 341 type: "redirect", 342 targetUri: redirectURISpec, 343 }, 344 ({ response }) => { 345 if (response && dwi) { 346 dwi.top.location.href = redirectURISpec; 347 } 348 } 349 ); 350 } 351 352 /* ---------- nsIPromptFactory ---------- */ 353 getPrompt(aDOMWin, aIID) { 354 // Delegated to login manager here, which in turn calls back into us via nsIPromptService. 355 if (aIID.equals(Ci.nsIAuthPrompt2) || aIID.equals(Ci.nsIAuthPrompt)) { 356 try { 357 const pwmgr = Cc[ 358 "@mozilla.org/passwordmanager/authpromptfactory;1" 359 ].getService(Ci.nsIPromptFactory); 360 return pwmgr.getPrompt(aDOMWin, aIID); 361 } catch (e) { 362 console.error("Delegation to password manager failed:", e); 363 } 364 } 365 366 const p = new PromptDelegate(aDOMWin); 367 p.QueryInterface(aIID); 368 return p; 369 } 370 371 /* ---------- private memebers ---------- */ 372 373 // nsIPromptService methods proxy to our Prompt class 374 callProxy(aMethod, aArguments) { 375 const prompt = new PromptDelegate(aArguments[0]); 376 let promptArgs; 377 if (BrowsingContext.isInstance(aArguments[0])) { 378 // Called by BrowsingContext prompt method, strip modalType. 379 [, , /*browsingContext*/ /*modalType*/ ...promptArgs] = aArguments; 380 } else { 381 [, /*domWindow*/ ...promptArgs] = aArguments; 382 } 383 return prompt[aMethod].apply(prompt, promptArgs); 384 } 385 386 /* ---------- nsIPromptService ---------- */ 387 388 alert() { 389 return this.callProxy("alert", arguments); 390 } 391 alertBC() { 392 return this.callProxy("alert", arguments); 393 } 394 alertCheck() { 395 return this.callProxy("alertCheck", arguments); 396 } 397 alertCheckBC() { 398 return this.callProxy("alertCheck", arguments); 399 } 400 confirm() { 401 return this.callProxy("confirm", arguments); 402 } 403 confirmBC() { 404 return this.callProxy("confirm", arguments); 405 } 406 confirmCheck() { 407 return this.callProxy("confirmCheck", arguments); 408 } 409 confirmCheckBC() { 410 return this.callProxy("confirmCheck", arguments); 411 } 412 confirmEx() { 413 return this.callProxy("confirmEx", arguments); 414 } 415 confirmExBC() { 416 return this.callProxy("confirmEx", arguments); 417 } 418 prompt() { 419 return this.callProxy("prompt", arguments); 420 } 421 promptBC() { 422 return this.callProxy("prompt", arguments); 423 } 424 promptUsernameAndPassword() { 425 return this.callProxy("promptUsernameAndPassword", arguments); 426 } 427 promptUsernameAndPasswordBC() { 428 return this.callProxy("promptUsernameAndPassword", arguments); 429 } 430 promptPassword() { 431 return this.callProxy("promptPassword", arguments); 432 } 433 promptPasswordBC() { 434 return this.callProxy("promptPassword", arguments); 435 } 436 select() { 437 return this.callProxy("select", arguments); 438 } 439 selectBC() { 440 return this.callProxy("select", arguments); 441 } 442 promptAuth() { 443 return this.callProxy("promptAuth", arguments); 444 } 445 promptAuthBC() { 446 return this.callProxy("promptAuth", arguments); 447 } 448 asyncPromptAuth() { 449 return this.callProxy("asyncPromptAuth", arguments); 450 } 451 confirmUserPaste() { 452 return lazy.GeckoViewClipboardPermission.confirmUserPaste(...arguments); 453 } 454 } 455 456 PromptFactory.prototype.classID = Components.ID( 457 "{076ac188-23c1-4390-aa08-7ef1f78ca5d9}" 458 ); 459 PromptFactory.prototype.QueryInterface = ChromeUtils.generateQI([ 460 "nsIPromptFactory", 461 "nsIPromptService", 462 ]); 463 464 class PromptDelegate { 465 constructor(aParent) { 466 this._prompter = new lazy.GeckoViewPrompter(aParent); 467 } 468 469 BUTTON_TYPE_POSITIVE = 0; 470 BUTTON_TYPE_NEUTRAL = 1; 471 BUTTON_TYPE_NEGATIVE = 2; 472 473 /* ---------- internal methods ---------- */ 474 475 _addText(aTitle, aText, aMsg) { 476 return Object.assign(aMsg, { 477 title: aTitle, 478 msg: aText, 479 }); 480 } 481 482 _addCheck(aCheckMsg, aCheckState, aMsg) { 483 return Object.assign(aMsg, { 484 hasCheck: !!aCheckMsg, 485 checkMsg: aCheckMsg, 486 checkValue: aCheckState && aCheckState.value, 487 }); 488 } 489 490 /* ---------- nsIPrompt ---------- */ 491 492 alert(aTitle, aText) { 493 this.alertCheck(aTitle, aText); 494 } 495 496 alertCheck(aTitle, aText, aCheckMsg, aCheckState) { 497 const result = this._prompter.showPrompt( 498 this._addText( 499 aTitle, 500 aText, 501 this._addCheck(aCheckMsg, aCheckState, { 502 type: "alert", 503 }) 504 ) 505 ); 506 if (result && aCheckState) { 507 aCheckState.value = !!result.checkValue; 508 } 509 } 510 511 confirm(aTitle, aText) { 512 // Button 0 is OK. 513 return this.confirmCheck(aTitle, aText); 514 } 515 516 confirmCheck(aTitle, aText, aCheckMsg, aCheckState) { 517 // Button 0 is OK. 518 return ( 519 this.confirmEx( 520 aTitle, 521 aText, 522 Ci.nsIPrompt.STD_OK_CANCEL_BUTTONS, 523 /* aButton0 */ null, 524 /* aButton1 */ null, 525 /* aButton2 */ null, 526 aCheckMsg, 527 aCheckState 528 ) == 0 529 ); 530 } 531 532 confirmEx( 533 aTitle, 534 aText, 535 aButtonFlags, 536 aButton0, 537 aButton1, 538 aButton2, 539 aCheckMsg, 540 aCheckState 541 ) { 542 const btnMap = Array(3).fill(null); 543 const btnTitle = Array(3).fill(null); 544 const btnCustomTitle = Array(3).fill(null); 545 const savedButtonId = []; 546 for (let i = 0; i < 3; i++) { 547 const btnFlags = aButtonFlags >> (i * 8); 548 switch (btnFlags & 0xff) { 549 case Ci.nsIPrompt.BUTTON_TITLE_OK: 550 btnMap[this.BUTTON_TYPE_POSITIVE] = i; 551 btnTitle[this.BUTTON_TYPE_POSITIVE] = "ok"; 552 break; 553 case Ci.nsIPrompt.BUTTON_TITLE_CANCEL: 554 btnMap[this.BUTTON_TYPE_NEGATIVE] = i; 555 btnTitle[this.BUTTON_TYPE_NEGATIVE] = "cancel"; 556 break; 557 case Ci.nsIPrompt.BUTTON_TITLE_YES: 558 btnMap[this.BUTTON_TYPE_POSITIVE] = i; 559 btnTitle[this.BUTTON_TYPE_POSITIVE] = "yes"; 560 break; 561 case Ci.nsIPrompt.BUTTON_TITLE_NO: 562 btnMap[this.BUTTON_TYPE_NEGATIVE] = i; 563 btnTitle[this.BUTTON_TYPE_NEGATIVE] = "no"; 564 break; 565 case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING: 566 // We don't know if this is positive/negative/neutral, so save for later. 567 savedButtonId.push(i); 568 break; 569 case Ci.nsIPrompt.BUTTON_TITLE_SAVE: 570 case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE: 571 case Ci.nsIPrompt.BUTTON_TITLE_REVERT: 572 // Not supported; fall-through. 573 default: 574 break; 575 } 576 } 577 578 // Put saved buttons into available slots. 579 for (let i = 0; i < 3 && savedButtonId.length; i++) { 580 if (btnMap[i] === null) { 581 btnMap[i] = savedButtonId.shift(); 582 btnTitle[i] = "custom"; 583 btnCustomTitle[i] = [aButton0, aButton1, aButton2][btnMap[i]]; 584 } 585 } 586 587 const result = this._prompter.showPrompt( 588 this._addText( 589 aTitle, 590 aText, 591 this._addCheck(aCheckMsg, aCheckState, { 592 type: "button", 593 btnTitle, 594 btnCustomTitle, 595 }) 596 ) 597 ); 598 if (result && aCheckState) { 599 aCheckState.value = !!result.checkValue; 600 } 601 return result && result.button in btnMap ? btnMap[result.button] : -1; 602 } 603 604 prompt(aTitle, aText, aValue, aCheckMsg, aCheckState) { 605 const result = this._prompter.showPrompt( 606 this._addText( 607 aTitle, 608 aText, 609 this._addCheck(aCheckMsg, aCheckState, { 610 type: "text", 611 value: aValue.value, 612 }) 613 ) 614 ); 615 // OK: result && result.text !== undefined 616 // Cancel: result && result.text === undefined 617 // Error: !result 618 if (result && aCheckState) { 619 aCheckState.value = !!result.checkValue; 620 } 621 if (!result || result.text === undefined) { 622 return false; 623 } 624 aValue.value = result.text || ""; 625 return true; 626 } 627 628 promptPassword(aTitle, aText, aPassword) { 629 return this._promptUsernameAndPassword( 630 aTitle, 631 aText, 632 /* aUsername */ undefined, 633 aPassword 634 ); 635 } 636 637 promptUsernameAndPassword(aTitle, aText, aUsername, aPassword) { 638 const msg = { 639 type: "auth", 640 mode: aUsername ? "auth" : "password", 641 options: { 642 flags: aUsername ? 0 : Ci.nsIAuthInformation.ONLY_PASSWORD, 643 username: aUsername ? aUsername.value : undefined, 644 password: aPassword.value, 645 }, 646 }; 647 const result = this._prompter.showPrompt(this._addText(aTitle, aText, msg)); 648 // OK: result && result.password !== undefined 649 // Cancel: result && result.password === undefined 650 // Error: !result 651 if (!result || result.password === undefined) { 652 return false; 653 } 654 if (aUsername) { 655 aUsername.value = result.username || ""; 656 } 657 aPassword.value = result.password || ""; 658 return true; 659 } 660 661 select(aTitle, aText, aSelectList, aOutSelection) { 662 const choices = Array.prototype.map.call(aSelectList, (item, index) => ({ 663 id: String(index), 664 label: item, 665 disabled: false, 666 selected: false, 667 })); 668 const result = this._prompter.showPrompt( 669 this._addText(aTitle, aText, { 670 type: "choice", 671 mode: "single", 672 choices, 673 }) 674 ); 675 // OK: result 676 // Cancel: !result 677 if (!result || result.choices === undefined) { 678 return false; 679 } 680 aOutSelection.value = Number(result.choices[0]); 681 return true; 682 } 683 684 _getAuthMsg(aChannel, aLevel, aAuthInfo) { 685 let username; 686 if ( 687 aAuthInfo.flags & Ci.nsIAuthInformation.NEED_DOMAIN && 688 aAuthInfo.domain 689 ) { 690 username = aAuthInfo.domain + "\\" + aAuthInfo.username; 691 } else { 692 username = aAuthInfo.username; 693 } 694 return this._addText( 695 /* title */ null, 696 this._getAuthText(aChannel, aAuthInfo), 697 { 698 type: "auth", 699 mode: 700 aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD 701 ? "password" 702 : "auth", 703 options: { 704 flags: aAuthInfo.flags, 705 uri: aChannel && aChannel.URI.displaySpec, 706 level: aLevel, 707 username, 708 password: aAuthInfo.password, 709 }, 710 } 711 ); 712 } 713 714 _fillAuthInfo(aAuthInfo, aResult) { 715 if (!aResult || aResult.password === undefined) { 716 return false; 717 } 718 719 aAuthInfo.password = aResult.password || ""; 720 if (aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD) { 721 return true; 722 } 723 724 const username = aResult.username || ""; 725 if (aAuthInfo.flags & Ci.nsIAuthInformation.NEED_DOMAIN) { 726 // Domain is separated from username by a backslash 727 var idx = username.indexOf("\\"); 728 if (idx >= 0) { 729 aAuthInfo.domain = username.substring(0, idx); 730 aAuthInfo.username = username.substring(idx + 1); 731 return true; 732 } 733 } 734 aAuthInfo.username = username; 735 return true; 736 } 737 738 promptAuth(aChannel, aLevel, aAuthInfo) { 739 const result = this._prompter.showPrompt( 740 this._getAuthMsg(aChannel, aLevel, aAuthInfo) 741 ); 742 // OK: result && result.password !== undefined 743 // Cancel: result && result.password === undefined 744 // Error: !result 745 return this._fillAuthInfo(aAuthInfo, result); 746 } 747 748 async asyncPromptAuth(aChannel, aLevel, aAuthInfo) { 749 const result = await this._prompter.asyncShowPromptPromise( 750 this._getAuthMsg(aChannel, aLevel, aAuthInfo) 751 ); 752 // OK: result && result.password !== undefined 753 // Cancel: result && result.password === undefined 754 // Error: !result 755 return this._fillAuthInfo(aAuthInfo, result); 756 } 757 758 _getAuthText(aChannel, aAuthInfo) { 759 const isProxy = aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY; 760 const isPassOnly = aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD; 761 const isCrossOrig = 762 aAuthInfo.flags & Ci.nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE; 763 764 const username = aAuthInfo.username; 765 const authTarget = this._getAuthTarget(aChannel, aAuthInfo); 766 const { displayHost } = authTarget; 767 let { realm } = authTarget; 768 769 // Suppress "the site says: $realm" when we synthesized a missing realm. 770 if (!aAuthInfo.realm && !isProxy) { 771 realm = ""; 772 } 773 774 // Trim obnoxiously long realms. 775 if (realm.length > 50) { 776 realm = realm.substring(0, 50) + "\u2026"; 777 } 778 779 const bundle = Services.strings.createBundle( 780 "chrome://global/locale/commonDialogs.properties" 781 ); 782 let text; 783 if (isProxy) { 784 text = bundle.formatStringFromName("EnterLoginForProxy3", [ 785 realm, 786 displayHost, 787 ]); 788 } else if (isPassOnly) { 789 text = bundle.formatStringFromName("EnterPasswordFor", [ 790 username, 791 displayHost, 792 ]); 793 } else if (isCrossOrig) { 794 text = bundle.formatStringFromName("EnterUserPasswordForCrossOrigin2", [ 795 displayHost, 796 ]); 797 } else if (!realm) { 798 text = bundle.formatStringFromName("EnterUserPasswordFor2", [ 799 displayHost, 800 ]); 801 } else { 802 text = bundle.formatStringFromName("EnterLoginForRealm3", [ 803 realm, 804 displayHost, 805 ]); 806 } 807 808 return text; 809 } 810 811 _getAuthTarget(aChannel, aAuthInfo) { 812 // If our proxy is demanding authentication, don't use the 813 // channel's actual destination. 814 if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) { 815 if (!(aChannel instanceof Ci.nsIProxiedChannel)) { 816 throw new Error("proxy auth needs nsIProxiedChannel"); 817 } 818 const info = aChannel.proxyInfo; 819 if (!info) { 820 throw new Error("proxy auth needs nsIProxyInfo"); 821 } 822 // Proxies don't have a scheme, but we'll use "moz-proxy://" 823 // so that it's more obvious what the login is for. 824 const idnService = Cc["@mozilla.org/network/idn-service;1"].getService( 825 Ci.nsIIDNService 826 ); 827 const displayHost = 828 "moz-proxy://" + 829 idnService.convertUTF8toACE(info.host) + 830 ":" + 831 info.port; 832 let realm = aAuthInfo.realm; 833 if (!realm) { 834 realm = displayHost; 835 } 836 return { displayHost, realm }; 837 } 838 839 const displayHost = 840 aChannel.URI.scheme + "://" + aChannel.URI.displayHostPort; 841 // If a HTTP WWW-Authenticate header specified a realm, that value 842 // will be available here. If it wasn't set or wasn't HTTP, we'll use 843 // the formatted hostname instead. 844 let realm = aAuthInfo.realm; 845 if (!realm) { 846 realm = displayHost; 847 } 848 return { displayHost, realm }; 849 } 850 } 851 852 PromptDelegate.prototype.QueryInterface = ChromeUtils.generateQI(["nsIPrompt"]);