diff options
Diffstat (limited to 'toolkit/components/passwordmgr/LoginAutoComplete.sys.mjs')
-rw-r--r-- | toolkit/components/passwordmgr/LoginAutoComplete.sys.mjs | 254 |
1 files changed, 3 insertions, 251 deletions
diff --git a/toolkit/components/passwordmgr/LoginAutoComplete.sys.mjs b/toolkit/components/passwordmgr/LoginAutoComplete.sys.mjs index 187caf9d62..a7ea1bfa2c 100644 --- a/toolkit/components/passwordmgr/LoginAutoComplete.sys.mjs +++ b/toolkit/components/passwordmgr/LoginAutoComplete.sys.mjs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** - * nsIAutoCompleteResult and nsILoginAutoCompleteSearch implementations for saved logins. + * nsIAutoCompleteResult implementations for saved logins. */ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; @@ -15,11 +15,7 @@ import { const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { - InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.sys.mjs", - LoginFormFactory: "resource://gre/modules/LoginFormFactory.sys.mjs", LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs", - LoginManagerChild: "resource://gre/modules/LoginManagerChild.sys.mjs", - NewPasswordModel: "resource://gre/modules/NewPasswordModel.sys.mjs", }); XPCOMUtils.defineLazyServiceGetter( lazy, @@ -335,6 +331,8 @@ export class LoginAutoCompleteResult { // The footer comes last if it's enabled if (isFooterEnabled()) { + // TODO: This would be removed once autofill is triggered from the parent. + gAutoCompleteListener.init(); if (autocompleteItems) { this.#rows.push( ...autocompleteItems.map( @@ -472,252 +470,6 @@ export class LoginAutoCompleteResult { } } -export class LoginAutoComplete { - // HTMLInputElement to number, the element's new-password heuristic confidence score - #cachedNewPasswordScore = new WeakMap(); - #autoCompleteLookupPromise = null; - classID = Components.ID("{2bdac17c-53f1-4896-a521-682ccdeef3a8}"); - QueryInterface = ChromeUtils.generateQI(["nsILoginAutoCompleteSearch"]); - - /** - * Yuck. This is called directly by satchel: - * nsFormFillController::StartSearch() - * [toolkit/components/satchel/nsFormFillController.cpp] - * - * We really ought to have a simple way for code to register an - * auto-complete provider, and not have satchel calling pwmgr directly. - * - * @param {string} aSearchString The value typed in the field. - * @param {nsIAutoCompleteResult} aPreviousResult - * @param {HTMLInputElement} aElement - * @param {nsIFormFillCompleteObserver} aCallback - */ - startSearch(aSearchString, aPreviousResult, aElement, aCallback) { - let { isNullPrincipal } = aElement.nodePrincipal; - if ( - aElement.nodePrincipal.schemeIs("about") || - aElement.nodePrincipal.isSystemPrincipal - ) { - // Don't show autocomplete results for about: pages. - // XXX: Don't we need to call the callback here? - return; - } - - let searchStartTimeMS = Services.telemetry.msSystemNow(); - - // Show the insecure login warning in the passwords field on null principal documents. - // Avoid loading InsecurePasswordUtils.sys.mjs in a sandboxed document (e.g. an ad. frame) if we - // already know it has a null principal and will therefore get the insecure autocomplete - // treatment. - // InsecurePasswordUtils doesn't handle the null principal case as not secure because we don't - // want the same treatment: - // * The web console warnings will be confusing (as they're primarily about http:) and not very - // useful if the developer intentionally sandboxed the document. - // * The site identity insecure field warning would require LoginManagerChild being loaded and - // listening to some of the DOM events we're ignoring in null principal documents. For memory - // reasons it's better to not load LMC at all for these sandboxed frames. Also, if the top- - // document is sandboxing a document, it probably doesn't want that sandboxed document to be - // able to affect the identity icon in the address bar by adding a password field. - let form = lazy.LoginFormFactory.createFromField(aElement); - let isSecure = - !isNullPrincipal && lazy.InsecurePasswordUtils.isFormSecure(form); - let { hasBeenTypePassword } = aElement; - let hostname = aElement.ownerDocument.documentURIObject.host; - let formOrigin = lazy.LoginHelper.getLoginOrigin( - aElement.ownerDocument.documentURI - ); - let loginManagerActor = lazy.LoginManagerChild.forWindow( - aElement.ownerGlobal - ); - let completeSearch = async autoCompleteLookupPromise => { - // Assign to the member synchronously before awaiting the Promise. - this.#autoCompleteLookupPromise = autoCompleteLookupPromise; - - let { - generatedPassword, - importable, - logins, - autocompleteItems, - willAutoSaveGeneratedPassword, - } = await autoCompleteLookupPromise; - - // If the search was canceled before we got our - // results, don't bother reporting them. - // N.B. This check must occur after the `await` above for it to be - // effective. - if (this.#autoCompleteLookupPromise !== autoCompleteLookupPromise) { - lazy.log.debug("Ignoring result from previous search."); - return; - } - - let telemetryEventData = { - acFieldName: aElement.getAutocompleteInfo().fieldName, - hadPrevious: !!aPreviousResult, - typeWasPassword: aElement.hasBeenTypePassword, - fieldType: aElement.type, - searchStartTimeMS, - stringLength: aSearchString.length, - }; - - this.#autoCompleteLookupPromise = null; - let results = new LoginAutoCompleteResult( - aSearchString, - logins, - autocompleteItems, - formOrigin, - { - generatedPassword, - willAutoSaveGeneratedPassword, - importable, - actor: loginManagerActor, - isSecure, - hasBeenTypePassword, - hostname, - telemetryEventData, - } - ); - aCallback.onSearchCompletion(results); - }; - - if (isNullPrincipal) { - // Don't search login storage when the field has a null principal as we don't want to fill - // logins for the `location` in this case. - completeSearch(Promise.resolve({ logins: [] })); - return; - } - - if ( - hasBeenTypePassword && - aSearchString && - !loginManagerActor.isPasswordGenerationForcedOn(aElement) - ) { - // Return empty result on password fields with password already filled, - // unless password generation was forced. - completeSearch(Promise.resolve({ logins: [] })); - return; - } - - if (!lazy.LoginHelper.enabled) { - completeSearch(Promise.resolve({ logins: [] })); - return; - } - - let previousResult; - if (aPreviousResult) { - previousResult = { - searchString: aPreviousResult.searchString, - logins: lazy.LoginHelper.loginsToVanillaObjects( - aPreviousResult.wrappedJSObject.logins - ), - }; - } else { - previousResult = null; - } - - let acLookupPromise = this.#requestAutoCompleteResultsFromParent({ - searchString: aSearchString, - previousResult, - inputElement: aElement, - form, - hasBeenTypePassword, - }); - completeSearch(acLookupPromise).catch(lazy.log.error.bind(lazy.log)); - } - - stopSearch() { - this.#autoCompleteLookupPromise = null; - } - - async #requestAutoCompleteResultsFromParent({ - searchString, - previousResult, - inputElement, - form, - hasBeenTypePassword, - }) { - let actionOrigin = lazy.LoginHelper.getFormActionOrigin(form); - let autocompleteInfo = inputElement.getAutocompleteInfo(); - - let loginManagerActor = lazy.LoginManagerChild.forWindow( - inputElement.ownerGlobal - ); - let forcePasswordGeneration = false; - let isProbablyANewPasswordField = false; - if (hasBeenTypePassword) { - forcePasswordGeneration = - loginManagerActor.isPasswordGenerationForcedOn(inputElement); - // Run the Fathom model only if the password field does not have the - // autocomplete="new-password" attribute. - isProbablyANewPasswordField = - autocompleteInfo.fieldName == "new-password" || - this.isProbablyANewPasswordField(inputElement); - } - const scenario = loginManagerActor.getScenario(inputElement); - - if (lazy.LoginHelper.showAutoCompleteFooter) { - gAutoCompleteListener.init(); - } - - lazy.log.debug("LoginAutoComplete search:", { - forcePasswordGeneration, - hasBeenTypePassword, - isProbablyANewPasswordField, - searchStringLength: searchString.length, - }); - - const result = await loginManagerActor.sendQuery( - "PasswordManager:autoCompleteLogins", - { - actionOrigin, - searchString, - previousResult, - forcePasswordGeneration, - hasBeenTypePassword, - isProbablyANewPasswordField, - scenarioName: scenario?.constructor.name, - inputMaxLength: inputElement.maxLength, - isWebAuthn: this.#isWebAuthnCredentials(autocompleteInfo), - } - ); - - return { - generatedPassword: result.generatedPassword, - importable: result.importable, - autocompleteItems: result.autocompleteItems, - logins: lazy.LoginHelper.vanillaObjectsToLogins(result.logins), - willAutoSaveGeneratedPassword: result.willAutoSaveGeneratedPassword, - }; - } - - isProbablyANewPasswordField(inputElement) { - const threshold = lazy.LoginHelper.generationConfidenceThreshold; - if (threshold == -1) { - // Fathom is disabled - return false; - } - - let score = this.#cachedNewPasswordScore.get(inputElement); - if (score) { - return score >= threshold; - } - - const { rules, type } = lazy.NewPasswordModel; - const results = rules.against(inputElement); - score = results.get(inputElement).scoreFor(type); - this.#cachedNewPasswordScore.set(inputElement, score); - return score >= threshold; - } - - /** - * @param {string} autocompleteInfo - * @returns whether the non-autofill credential type (https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#non-autofill-credential-type) - * of the input field is "webauthn" - */ - #isWebAuthnCredentials(autocompleteInfo) { - return autocompleteInfo.credentialType == "webauthn"; - } -} - let gAutoCompleteListener = { added: false, |