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