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/** 6 * Form Autofill content process module. 7 */ 8 9/* eslint-disable no-use-before-define */ 10 11"use strict"; 12 13var EXPORTED_SYMBOLS = ["FormAutofillContent"]; 14 15const Cm = Components.manager; 16 17const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); 18const { XPCOMUtils } = ChromeUtils.import( 19 "resource://gre/modules/XPCOMUtils.jsm" 20); 21 22ChromeUtils.defineModuleGetter( 23 this, 24 "AddressResult", 25 "resource://formautofill/ProfileAutoCompleteResult.jsm" 26); 27ChromeUtils.defineModuleGetter( 28 this, 29 "CreditCardResult", 30 "resource://formautofill/ProfileAutoCompleteResult.jsm" 31); 32ChromeUtils.defineModuleGetter( 33 this, 34 "FormAutofill", 35 "resource://formautofill/FormAutofill.jsm" 36); 37ChromeUtils.defineModuleGetter( 38 this, 39 "FormAutofillHandler", 40 "resource://formautofill/FormAutofillHandler.jsm" 41); 42ChromeUtils.defineModuleGetter( 43 this, 44 "FormAutofillUtils", 45 "resource://formautofill/FormAutofillUtils.jsm" 46); 47ChromeUtils.defineModuleGetter( 48 this, 49 "FormLikeFactory", 50 "resource://gre/modules/FormLikeFactory.jsm" 51); 52ChromeUtils.defineModuleGetter( 53 this, 54 "InsecurePasswordUtils", 55 "resource://gre/modules/InsecurePasswordUtils.jsm" 56); 57ChromeUtils.defineModuleGetter( 58 this, 59 "PrivateBrowsingUtils", 60 "resource://gre/modules/PrivateBrowsingUtils.jsm" 61); 62 63const formFillController = Cc[ 64 "@mozilla.org/satchel/form-fill-controller;1" 65].getService(Ci.nsIFormFillController); 66const autocompleteController = Cc[ 67 "@mozilla.org/autocomplete/controller;1" 68].getService(Ci.nsIAutoCompleteController); 69 70XPCOMUtils.defineLazyGetter( 71 this, 72 "ADDRESSES_COLLECTION_NAME", 73 () => FormAutofillUtils.ADDRESSES_COLLECTION_NAME 74); 75XPCOMUtils.defineLazyGetter( 76 this, 77 "CREDITCARDS_COLLECTION_NAME", 78 () => FormAutofillUtils.CREDITCARDS_COLLECTION_NAME 79); 80XPCOMUtils.defineLazyGetter( 81 this, 82 "FIELD_STATES", 83 () => FormAutofillUtils.FIELD_STATES 84); 85 86function getActorFromWindow(contentWindow, name = "FormAutofill") { 87 // In unit tests, contentWindow isn't a real window. 88 if (!contentWindow) { 89 return null; 90 } 91 92 return contentWindow.windowGlobalChild 93 ? contentWindow.windowGlobalChild.getActor(name) 94 : null; 95} 96 97// Register/unregister a constructor as a factory. 98function AutocompleteFactory() {} 99AutocompleteFactory.prototype = { 100 register(targetConstructor) { 101 let proto = targetConstructor.prototype; 102 this._classID = proto.classID; 103 104 let factory = XPCOMUtils._getFactory(targetConstructor); 105 this._factory = factory; 106 107 let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); 108 registrar.registerFactory( 109 proto.classID, 110 proto.classDescription, 111 proto.contractID, 112 factory 113 ); 114 115 if (proto.classID2) { 116 this._classID2 = proto.classID2; 117 registrar.registerFactory( 118 proto.classID2, 119 proto.classDescription, 120 proto.contractID2, 121 factory 122 ); 123 } 124 }, 125 126 unregister() { 127 let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); 128 registrar.unregisterFactory(this._classID, this._factory); 129 if (this._classID2) { 130 registrar.unregisterFactory(this._classID2, this._factory); 131 } 132 this._factory = null; 133 }, 134}; 135 136/** 137 * @constructor 138 * 139 * @implements {nsIAutoCompleteSearch} 140 */ 141function AutofillProfileAutoCompleteSearch() { 142 FormAutofill.defineLazyLogGetter(this, "AutofillProfileAutoCompleteSearch"); 143} 144AutofillProfileAutoCompleteSearch.prototype = { 145 classID: Components.ID("4f9f1e4c-7f2c-439e-9c9e-566b68bc187d"), 146 contractID: "@mozilla.org/autocomplete/search;1?name=autofill-profiles", 147 classDescription: "AutofillProfileAutoCompleteSearch", 148 QueryInterface: ChromeUtils.generateQI([Ci.nsIAutoCompleteSearch]), 149 150 // Begin nsIAutoCompleteSearch implementation 151 152 /** 153 * Searches for a given string and notifies a listener (either synchronously 154 * or asynchronously) of the result 155 * 156 * @param {string} searchString the string to search for 157 * @param {string} searchParam 158 * @param {Object} previousResult a previous result to use for faster searchinig 159 * @param {Object} listener the listener to notify when the search is complete 160 */ 161 startSearch(searchString, searchParam, previousResult, listener) { 162 let { 163 activeInput, 164 activeSection, 165 activeFieldDetail, 166 savedFieldNames, 167 } = FormAutofillContent; 168 this.forceStop = false; 169 170 this.debug("startSearch: for", searchString, "with input", activeInput); 171 172 let isAddressField = FormAutofillUtils.isAddressField( 173 activeFieldDetail.fieldName 174 ); 175 let isInputAutofilled = activeFieldDetail.state == FIELD_STATES.AUTO_FILLED; 176 let allFieldNames = activeSection.allFieldNames; 177 let filledRecordGUID = activeSection.filledRecordGUID; 178 let searchPermitted = isAddressField 179 ? FormAutofill.isAutofillAddressesEnabled 180 : FormAutofill.isAutofillCreditCardsEnabled; 181 let AutocompleteResult = isAddressField ? AddressResult : CreditCardResult; 182 let isFormAutofillSearch = true; 183 let pendingSearchResult = null; 184 185 ProfileAutocomplete.lastProfileAutoCompleteFocusedInput = activeInput; 186 // Fallback to form-history if ... 187 // - specified autofill feature is pref off. 188 // - no profile can fill the currently-focused input. 189 // - the current form has already been populated. 190 // - (address only) less than 3 inputs are covered by all saved fields in the storage. 191 if ( 192 !searchPermitted || 193 !savedFieldNames.has(activeFieldDetail.fieldName) || 194 (!isInputAutofilled && filledRecordGUID) || 195 (isAddressField && 196 allFieldNames.filter(field => savedFieldNames.has(field)).length < 197 FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD) 198 ) { 199 isFormAutofillSearch = false; 200 if (activeInput.autocomplete == "off") { 201 // Create a dummy result as an empty search result. 202 pendingSearchResult = new AutocompleteResult("", "", [], [], {}); 203 } else { 204 pendingSearchResult = new Promise(resolve => { 205 let formHistory = Cc[ 206 "@mozilla.org/autocomplete/search;1?name=form-history" 207 ].createInstance(Ci.nsIAutoCompleteSearch); 208 formHistory.startSearch(searchString, searchParam, previousResult, { 209 onSearchResult: (_, result) => resolve(result), 210 }); 211 }); 212 } 213 } else if (isInputAutofilled) { 214 pendingSearchResult = new AutocompleteResult(searchString, "", [], [], { 215 isInputAutofilled, 216 }); 217 } else { 218 let infoWithoutElement = { ...activeFieldDetail }; 219 delete infoWithoutElement.elementWeakRef; 220 221 let data = { 222 collectionName: isAddressField 223 ? ADDRESSES_COLLECTION_NAME 224 : CREDITCARDS_COLLECTION_NAME, 225 info: infoWithoutElement, 226 searchString, 227 }; 228 229 pendingSearchResult = this._getRecords(activeInput, data).then( 230 records => { 231 if (this.forceStop) { 232 return null; 233 } 234 // Sort addresses by timeLastUsed for showing the lastest used address at top. 235 records.sort((a, b) => b.timeLastUsed - a.timeLastUsed); 236 237 let adaptedRecords = activeSection.getAdaptedProfiles(records); 238 let handler = FormAutofillContent.activeHandler; 239 let isSecure = InsecurePasswordUtils.isFormSecure(handler.form); 240 241 return new AutocompleteResult( 242 searchString, 243 activeFieldDetail.fieldName, 244 allFieldNames, 245 adaptedRecords, 246 { isSecure, isInputAutofilled } 247 ); 248 } 249 ); 250 } 251 252 Promise.resolve(pendingSearchResult).then(result => { 253 listener.onSearchResult(this, result); 254 // Don't save cache results or reset state when returning non-autofill results such as the 255 // form history fallback above. 256 if (isFormAutofillSearch) { 257 ProfileAutocomplete.lastProfileAutoCompleteResult = result; 258 // Reset AutoCompleteController's state at the end of startSearch to ensure that 259 // none of form autofill result will be cached in other places and make the 260 // result out of sync. 261 autocompleteController.resetInternalState(); 262 } else { 263 // Clear the cache so that we don't try to autofill from it after falling 264 // back to form history. 265 ProfileAutocomplete.lastProfileAutoCompleteResult = null; 266 } 267 }); 268 }, 269 270 /** 271 * Stops an asynchronous search that is in progress 272 */ 273 stopSearch() { 274 ProfileAutocomplete.lastProfileAutoCompleteResult = null; 275 this.forceStop = true; 276 }, 277 278 /** 279 * Get the records from parent process for AutoComplete result. 280 * 281 * @private 282 * @param {Object} input 283 * Input element for autocomplete. 284 * @param {Object} data 285 * Parameters for querying the corresponding result. 286 * @param {string} data.collectionName 287 * The name used to specify which collection to retrieve records. 288 * @param {string} data.searchString 289 * The typed string for filtering out the matched records. 290 * @param {string} data.info 291 * The input autocomplete property's information. 292 * @returns {Promise} 293 * Promise that resolves when addresses returned from parent process. 294 */ 295 _getRecords(input, data) { 296 this.debug("_getRecords with data:", data); 297 if (!input) { 298 return []; 299 } 300 301 let actor = getActorFromWindow(input.ownerGlobal); 302 return actor.sendQuery("FormAutofill:GetRecords", data); 303 }, 304}; 305 306let ProfileAutocomplete = { 307 QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]), 308 309 lastProfileAutoCompleteResult: null, 310 lastProfileAutoCompleteFocusedInput: null, 311 _registered: false, 312 _factory: null, 313 314 ensureRegistered() { 315 if (this._registered) { 316 return; 317 } 318 319 FormAutofill.defineLazyLogGetter(this, "ProfileAutocomplete"); 320 this.debug("ensureRegistered"); 321 this._factory = new AutocompleteFactory(); 322 this._factory.register(AutofillProfileAutoCompleteSearch); 323 this._registered = true; 324 325 Services.obs.addObserver(this, "autocomplete-will-enter-text"); 326 327 this.debug( 328 "ensureRegistered. Finished with _registered:", 329 this._registered 330 ); 331 }, 332 333 ensureUnregistered() { 334 if (!this._registered) { 335 return; 336 } 337 338 this.debug("ensureUnregistered"); 339 this._factory.unregister(); 340 this._factory = null; 341 this._registered = false; 342 this._lastAutoCompleteResult = null; 343 344 Services.obs.removeObserver(this, "autocomplete-will-enter-text"); 345 }, 346 347 observe(subject, topic, data) { 348 switch (topic) { 349 case "autocomplete-will-enter-text": { 350 if (!FormAutofillContent.activeInput) { 351 // The observer notification is for autocomplete in a different process. 352 break; 353 } 354 this._fillFromAutocompleteRow(FormAutofillContent.activeInput); 355 break; 356 } 357 } 358 }, 359 360 _getSelectedIndex(contentWindow) { 361 let actor = getActorFromWindow(contentWindow, "AutoComplete"); 362 if (!actor) { 363 throw new Error("Invalid autocomplete selectedIndex"); 364 } 365 366 return actor.selectedIndex; 367 }, 368 369 _fillFromAutocompleteRow(focusedInput) { 370 this.debug("_fillFromAutocompleteRow:", focusedInput); 371 let formDetails = FormAutofillContent.activeFormDetails; 372 if (!formDetails) { 373 // The observer notification is for a different frame. 374 return; 375 } 376 377 let selectedIndex = this._getSelectedIndex(focusedInput.ownerGlobal); 378 if ( 379 selectedIndex == -1 || 380 !this.lastProfileAutoCompleteResult || 381 this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != 382 "autofill-profile" 383 ) { 384 return; 385 } 386 387 let profile = JSON.parse( 388 this.lastProfileAutoCompleteResult.getCommentAt(selectedIndex) 389 ); 390 391 FormAutofillContent.activeHandler.autofillFormFields(profile); 392 }, 393 394 _clearProfilePreview() { 395 if ( 396 !this.lastProfileAutoCompleteFocusedInput || 397 !FormAutofillContent.activeSection 398 ) { 399 return; 400 } 401 402 FormAutofillContent.activeSection.clearPreviewedFormFields(); 403 }, 404 405 _previewSelectedProfile(selectedIndex) { 406 if ( 407 !FormAutofillContent.activeInput || 408 !FormAutofillContent.activeFormDetails 409 ) { 410 // The observer notification is for a different process/frame. 411 return; 412 } 413 414 if ( 415 !this.lastProfileAutoCompleteResult || 416 this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != 417 "autofill-profile" 418 ) { 419 return; 420 } 421 422 let profile = JSON.parse( 423 this.lastProfileAutoCompleteResult.getCommentAt(selectedIndex) 424 ); 425 FormAutofillContent.activeSection.previewFormFields(profile); 426 }, 427}; 428 429/** 430 * Handles content's interactions for the process. 431 * 432 * NOTE: Declares it by "var" to make it accessible in unit tests. 433 */ 434var FormAutofillContent = { 435 /** 436 * @type {WeakMap} mapping FormLike root HTML elements to FormAutofillHandler objects. 437 */ 438 _formsDetails: new WeakMap(), 439 440 /** 441 * @type {Set} Set of the fields with usable values in any saved profile. 442 */ 443 get savedFieldNames() { 444 return Services.cpmm.sharedData.get("FormAutofill:savedFieldNames"); 445 }, 446 447 /** 448 * @type {Object} The object where to store the active items, e.g. element, 449 * handler, section, and field detail. 450 */ 451 _activeItems: {}, 452 453 init() { 454 FormAutofill.defineLazyLogGetter(this, "FormAutofillContent"); 455 this.debug("init"); 456 457 // eslint-disable-next-line mozilla/balanced-listeners 458 Services.cpmm.sharedData.addEventListener("change", this); 459 460 let autofillEnabled = Services.cpmm.sharedData.get("FormAutofill:enabled"); 461 // If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure 462 // autocomplete is registered before the focusin so register it in this case as long as the 463 // pref is true. 464 let shouldEnableAutofill = 465 autofillEnabled === undefined && 466 (FormAutofill.isAutofillAddressesEnabled || 467 FormAutofill.isAutofillCreditCardsEnabled); 468 if (autofillEnabled || shouldEnableAutofill) { 469 ProfileAutocomplete.ensureRegistered(); 470 } 471 }, 472 473 /** 474 * Send the profile to parent for doorhanger and storage saving/updating. 475 * 476 * @param {Object} profile Submitted form's address/creditcard guid and record. 477 * @param {Object} domWin Current content window. 478 * @param {int} timeStartedFillingMS Time of form filling started. 479 */ 480 _onFormSubmit(profile, domWin, timeStartedFillingMS) { 481 let actor = getActorFromWindow(domWin); 482 actor.sendAsyncMessage("FormAutofill:OnFormSubmit", { 483 profile, 484 timeStartedFillingMS, 485 }); 486 }, 487 488 /** 489 * Handle a form submission and early return when: 490 * 1. In private browsing mode. 491 * 2. Could not map any autofill handler by form element. 492 * 3. Number of filled fields is less than autofill threshold 493 * 494 * @param {HTMLElement} formElement Root element which receives submit event. 495 * @param {Window} domWin Content window only passed for unit tests 496 */ 497 formSubmitted(formElement, domWin = formElement.ownerGlobal) { 498 this.debug("Handling form submission"); 499 500 if (!FormAutofill.isAutofillEnabled) { 501 this.debug("Form Autofill is disabled"); 502 return; 503 } 504 505 // The `domWin` truthiness test is used by unit tests to bypass this check. 506 if (domWin && PrivateBrowsingUtils.isContentWindowPrivate(domWin)) { 507 this.debug("Ignoring submission in a private window"); 508 return; 509 } 510 511 let handler = this._formsDetails.get(formElement); 512 if (!handler) { 513 this.debug("Form element could not map to an existing handler"); 514 return; 515 } 516 517 let records = handler.createRecords(); 518 if (!Object.values(records).some(typeRecords => typeRecords.length)) { 519 return; 520 } 521 522 this._onFormSubmit(records, domWin, handler.timeStartedFillingMS); 523 }, 524 525 handleEvent(evt) { 526 switch (evt.type) { 527 case "change": { 528 if (!evt.changedKeys.includes("FormAutofill:enabled")) { 529 return; 530 } 531 if (Services.cpmm.sharedData.get("FormAutofill:enabled")) { 532 ProfileAutocomplete.ensureRegistered(); 533 } else { 534 ProfileAutocomplete.ensureUnregistered(); 535 } 536 break; 537 } 538 } 539 }, 540 541 /** 542 * Get the form's handler from cache which is created after page identified. 543 * 544 * @param {HTMLInputElement} element Focused input which triggered profile searching 545 * @returns {Array<Object>|null} 546 * Return target form's handler from content cache 547 * (or return null if the information is not found in the cache). 548 * 549 */ 550 _getFormHandler(element) { 551 if (!element) { 552 return null; 553 } 554 let rootElement = FormLikeFactory.findRootForField(element); 555 return this._formsDetails.get(rootElement); 556 }, 557 558 /** 559 * Get the active form's information from cache which is created after page 560 * identified. 561 * 562 * @returns {Array<Object>|null} 563 * Return target form's information from content cache 564 * (or return null if the information is not found in the cache). 565 * 566 */ 567 get activeFormDetails() { 568 let formHandler = this.activeHandler; 569 return formHandler ? formHandler.fieldDetails : null; 570 }, 571 572 /** 573 * All active items should be updated according the active element of 574 * `formFillController.focusedInput`. All of them including element, 575 * handler, section, and field detail, can be retrieved by their own getters. 576 * 577 * @param {HTMLElement|null} element The active item should be updated based 578 * on this or `formFillController.focusedInput` will be taken. 579 */ 580 updateActiveInput(element) { 581 element = element || formFillController.focusedInput; 582 if (!element) { 583 this._activeItems = {}; 584 return; 585 } 586 this._activeItems = { 587 elementWeakRef: Cu.getWeakReference(element), 588 fieldDetail: null, 589 }; 590 }, 591 592 get activeInput() { 593 let elementWeakRef = this._activeItems.elementWeakRef; 594 return elementWeakRef ? elementWeakRef.get() : null; 595 }, 596 597 get activeHandler() { 598 const activeInput = this.activeInput; 599 if (!activeInput) { 600 return null; 601 } 602 603 // XXX: We are recomputing the activeHandler every time to avoid keeping a 604 // reference on the active element. This might be called quite frequently 605 // so if _getFormHandler/findRootForField become more costly, we should 606 // look into caching this result (eg by adding a weakmap). 607 let handler = this._getFormHandler(activeInput); 608 if (handler) { 609 handler.focusedInput = activeInput; 610 } 611 return handler; 612 }, 613 614 get activeSection() { 615 let formHandler = this.activeHandler; 616 return formHandler ? formHandler.activeSection : null; 617 }, 618 619 /** 620 * Get the active input's information from cache which is created after page 621 * identified. 622 * 623 * @returns {Object|null} 624 * Return the active input's information that cloned from content cache 625 * (or return null if the information is not found in the cache). 626 */ 627 get activeFieldDetail() { 628 if (!this._activeItems.fieldDetail) { 629 let formDetails = this.activeFormDetails; 630 if (!formDetails) { 631 return null; 632 } 633 for (let detail of formDetails) { 634 let detailElement = detail.elementWeakRef.get(); 635 if (detailElement && this.activeInput == detailElement) { 636 this._activeItems.fieldDetail = detail; 637 break; 638 } 639 } 640 } 641 return this._activeItems.fieldDetail; 642 }, 643 644 identifyAutofillFields(element) { 645 this.debug( 646 "identifyAutofillFields:", 647 String(element.ownerDocument.location) 648 ); 649 650 if (!this.savedFieldNames) { 651 this.debug("identifyAutofillFields: savedFieldNames are not known yet"); 652 let actor = getActorFromWindow(element.ownerGlobal); 653 if (actor) { 654 actor.sendAsyncMessage("FormAutofill:InitStorage"); 655 } 656 } 657 658 let formHandler = this._getFormHandler(element); 659 if (!formHandler) { 660 let formLike = FormLikeFactory.createFromField(element); 661 formHandler = new FormAutofillHandler(formLike); 662 } else if (!formHandler.updateFormIfNeeded(element)) { 663 this.debug("No control is removed or inserted since last collection."); 664 return; 665 } 666 667 let validDetails = formHandler.collectFormFields(); 668 669 this._formsDetails.set(formHandler.form.rootElement, formHandler); 670 this.debug("Adding form handler to _formsDetails:", formHandler); 671 672 validDetails.forEach(detail => 673 this._markAsAutofillField(detail.elementWeakRef.get()) 674 ); 675 }, 676 677 clearForm() { 678 let focusedInput = 679 this.activeInput || ProfileAutocomplete._lastAutoCompleteFocusedInput; 680 if (!focusedInput) { 681 return; 682 } 683 684 this.activeSection.clearPopulatedForm(); 685 }, 686 687 previewProfile(doc) { 688 let docWin = doc.ownerGlobal; 689 let selectedIndex = ProfileAutocomplete._getSelectedIndex(docWin); 690 let lastAutoCompleteResult = 691 ProfileAutocomplete.lastProfileAutoCompleteResult; 692 let focusedInput = this.activeInput; 693 let actor = getActorFromWindow(docWin); 694 695 if ( 696 selectedIndex === -1 || 697 !focusedInput || 698 !lastAutoCompleteResult || 699 lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile" 700 ) { 701 actor.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {}); 702 703 ProfileAutocomplete._clearProfilePreview(); 704 } else { 705 let focusedInputDetails = this.activeFieldDetail; 706 let profile = JSON.parse( 707 lastAutoCompleteResult.getCommentAt(selectedIndex) 708 ); 709 let allFieldNames = FormAutofillContent.activeSection.allFieldNames; 710 let profileFields = allFieldNames.filter( 711 fieldName => !!profile[fieldName] 712 ); 713 714 let focusedCategory = FormAutofillUtils.getCategoryFromFieldName( 715 focusedInputDetails.fieldName 716 ); 717 let categories = FormAutofillUtils.getCategoriesFromFieldNames( 718 profileFields 719 ); 720 actor.sendAsyncMessage("FormAutofill:UpdateWarningMessage", { 721 focusedCategory, 722 categories, 723 }); 724 725 ProfileAutocomplete._previewSelectedProfile(selectedIndex); 726 } 727 }, 728 729 onPopupClosed(selectedRowStyle) { 730 ProfileAutocomplete._clearProfilePreview(); 731 732 let lastAutoCompleteResult = 733 ProfileAutocomplete.lastProfileAutoCompleteResult; 734 let focusedInput = FormAutofillContent.activeInput; 735 if ( 736 lastAutoCompleteResult && 737 FormAutofillContent._keyDownEnterForInput && 738 focusedInput === FormAutofillContent._keyDownEnterForInput && 739 focusedInput === ProfileAutocomplete.lastProfileAutoCompleteFocusedInput 740 ) { 741 if (selectedRowStyle == "autofill-footer") { 742 let actor = getActorFromWindow(focusedInput.ownerGlobal); 743 actor.sendAsyncMessage("FormAutofill:OpenPreferences"); 744 } else if (selectedRowStyle == "autofill-clear-button") { 745 FormAutofillContent.clearForm(); 746 } 747 } 748 }, 749 750 _markAsAutofillField(field) { 751 // Since Form Autofill popup is only for input element, any non-Input 752 // element should be excluded here. 753 if (!field || ChromeUtils.getClassName(field) !== "HTMLInputElement") { 754 return; 755 } 756 757 formFillController.markAsAutofillField(field); 758 }, 759 760 _onKeyDown(e) { 761 delete FormAutofillContent._keyDownEnterForInput; 762 let lastAutoCompleteResult = 763 ProfileAutocomplete.lastProfileAutoCompleteResult; 764 let focusedInput = FormAutofillContent.activeInput; 765 if ( 766 e.keyCode != e.DOM_VK_RETURN || 767 !lastAutoCompleteResult || 768 !focusedInput || 769 focusedInput != ProfileAutocomplete.lastProfileAutoCompleteFocusedInput 770 ) { 771 return; 772 } 773 FormAutofillContent._keyDownEnterForInput = focusedInput; 774 }, 775}; 776 777FormAutofillContent.init(); 778