Commit 97c7d3f6 authored by Dimi's avatar Dimi
Browse files

Bug 1833618 - P2. Replace checking hidden and display:none in formautofill...

Bug 1833618 - P2. Replace checking hidden and display:none in formautofill visibility check with element.checkVisibility API r=credential-management-reviewers,sgalich

This patch includes a preference setting that triggers the execution of element.checkVisibility
only when a form contains fewer than 200 eligible address and credit card fields.
This measure has been implemented to avoid potential performance impact that could occur due
to the visibility check running on a large number of elements.

Differential Revision: https://phabricator.services.mozilla.com/D178308
parent 89faefc3
Loading
Loading
Loading
Loading
+114 −0
Original line number Diff line number Diff line
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */

/* global add_heuristic_tests */

"use strict";

add_heuristic_tests([
  {
    description: "all fields are visible",
    fixtureData: `
        <html>
        <body>
          <form>
            <input type="text" id="name" autocomplete="name" />
            <input type="text" id="tel" autocomplete="tel" />
            <input type="text" id="email" autocomplete="email" />
            <input type="text" id="country" autocomplete="country"/>
            <input type="text" id="postal-code" autocomplete="postal-code" />
            <input type="text" id="address-line1" autocomplete="address-line1" />
            <div>
              <input type="text" id="address-line2" autocomplete="address-line2" />
            </div>
          </form>
          </form>
        </body>
        </html>
      `,
    expectedResult: [
      {
        default: {
          reason: "autocomplete",
        },
        fields: [
          { fieldName: "name" },
          { fieldName: "tel" },
          { fieldName: "email" },
          { fieldName: "country" },
          { fieldName: "postal-code" },
          { fieldName: "address-line1" },
          { fieldName: "address-line2" },
        ],
      },
    ],
  },
  {
    description: "some fields are invisible because of css style",
    fixtureData: `
        <html>
        <body>
          <form>
            <input type="text" id="name" autocomplete="name" />
            <input type="text" id="tel" autocomplete="tel" />
            <input type="text" id="email" autocomplete="email" />
            <input type="text" id="country" autocomplete="country" hidden />
            <input type="text" id="postal-code" autocomplete="postal-code" style="display:none" />
            <input type="text" id="address-line1" autocomplete="address-line1" style="opacity:0" />
            <div style="content-visibility:hidden">
              <input type="text" id="address-line2" autocomplete="address-line2" />
            </div>
          </form>
        </body>
        </html>
      `,
    expectedResult: [
      {
        default: {
          reason: "autocomplete",
        },
        fields: [
          { fieldName: "name" },
          { fieldName: "tel" },
          { fieldName: "email" },
        ],
      },
    ],
  },
  {
    // hidden and style="display:none" are always considered regardless what visibility check we use
    description: "invisible fields are identified because number of elemenent in the form exceed the threshold",
    prefs: [["extensions.formautofill.heuristics.visibilityCheckThreshold", 1]],
    fixtureData: `
        <html>
        <body>
          <form>
            <input type="text" id="name" autocomplete="name" />
            <input type="text" id="tel" autocomplete="tel" />
            <input type="text" id="email" autocomplete="email" />
            <input type="text" id="country" autocomplete="country" hidden />
            <input type="text" id="postal-code" autocomplete="postal-code" style="display:none" />
            <input type="text" id="address-line1" autocomplete="address-line1" style="opacity:0" />
            <div style="content-visibility:hidden">
              <input type="text" id="address-line2" autocomplete="address-line2" />
            </div>
          </form>
        </body>
        </html>
      `,
    expectedResult: [
      {
        default: {
          reason: "autocomplete",
        },
        fields: [
          { fieldName: "name" },
          { fieldName: "tel" },
          { fieldName: "email" },
          { fieldName: "address-line1" },
          { fieldName: "address-line2" },
        ],
      },
    ],
  },
]);
+2 −0
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@ firefox-appdir = browser
head = head.js
support-files =
  ../fixtures/**
prefs =
  extensions.formautofill.heuristics.visibilityCheckThreshold=0

[test_activeStatus.js]
[test_addressComponent_city.js]
+21 −5
Original line number Diff line number Diff line
@@ -15,6 +15,10 @@ ChromeUtils.defineESModuleGetters(lazy, {
  LabelUtils: "resource://gre/modules/shared/LabelUtils.sys.mjs",
});

XPCOMUtils.defineLazyGetter(lazy, "log", () =>
  FormAutofill.defineLogGetter(lazy, "FormAutofillHeuristics")
);

/**
 * To help us classify sections, we want to know what fields can appear
 * multiple times in a row.
@@ -556,15 +560,27 @@ export const FormAutofillHeuristics = {
   *        all sections within its field details in the form.
   */
  getFormInfo(form) {
    const eligibleFields = Array.from(form.elements).filter(elem =>
      lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(elem)
    let elements = Array.from(form.elements).filter(element =>
      lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element)
    );

    if (eligibleFields.length <= 0) {
      return [];
    // Due to potential performance impact while running visibility check on
    // a large amount of elements, a comprehensive visibility check
    // (considering opacity and CSS visibility) is only applied when the number
    // of eligible elements is below a certain threshold.
    const runVisiblityCheck =
      elements.length < lazy.FormAutofillUtils.visibilityCheckThreshold;
    if (!runVisiblityCheck) {
      lazy.log.debug(
        `Skip running visibility check, because of too many elements (${elements.length})`
      );
    }

    let fieldScanner = new lazy.FieldScanner(eligibleFields);
    elements = elements.filter(element =>
      lazy.FormAutofillUtils.isFieldVisible(element, runVisiblityCheck)
    );

    let fieldScanner = new lazy.FieldScanner(elements);
    while (!fieldScanner.parsingFinished) {
      let parsedPhoneFields = this._parsePhoneFields(fieldScanner);
      let parsedAddressFields = this._parseAddressFields(fieldScanner);
+22 −26
Original line number Diff line number Diff line
@@ -436,10 +436,6 @@ FormAutofillUtils = {
    }
  },

  autofillFieldSelector(doc) {
    return doc.querySelectorAll("input, select");
  },

  /**
   * Determines if an element can be autofilled or not.
   *
@@ -453,21 +449,21 @@ FormAutofillUtils = {
  /**
   * Determines if an element is visually hidden or not.
   *
   * NOTE: this does not encompass every possible way of hiding an element.
   * Instead, we check some of the more common methods of hiding for performance reasons.
   * See Bug 1727832 for follow up.
   *
   * @param {HTMLElement} element
   * @param {boolean} visibilityCheck true to run visiblity check against
   *                  element.checkVisibility API. Otherwise, test by only checking
   *                  `hidden` and `display` attributes
   * @returns {boolean} true if the element is visible
   */
  isFieldVisible(element) {
    if (element.hidden) {
      return false;
    }
    if (element.style.display == "none") {
      return false;
  isFieldVisible(element, visibilityCheck = true) {
    if (visibilityCheck) {
      return element.checkVisibility({
        checkOpacity: true,
        checkVisibilityCSS: true,
      });
    }
    return true;

    return !element.hidden && element.style.display != "none";
  },

  /**
@@ -480,20 +476,13 @@ FormAutofillUtils = {
    if (!element) {
      return false;
    }

    if (HTMLInputElement.isInstance(element)) {
      // `element.type` can be recognized as `text`, if it's missing or invalid.
      if (!ELIGIBLE_INPUT_TYPES.includes(element.type)) {
        return false;
      }
      // If the field is visually invisible, we do not want to autofill into it.
      if (!this.isFieldVisible(element)) {
        return false;
      }
    } else if (!HTMLSelectElement.isInstance(element)) {
      return false;
      return ELIGIBLE_INPUT_TYPES.includes(element.type);
    }

    return true;
    return HTMLSelectElement.isInstance(element);
  },

  loadDataFromScript(url, sandbox = {}) {
@@ -1248,6 +1237,13 @@ XPCOMUtils.defineLazyPreferenceGetter(
  pref => parseFloat(pref)
);

XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofillUtils,
  "visibilityCheckThreshold",
  "extensions.formautofill.heuristics.visibilityCheckThreshold",
  200
);

// This is only used in iOS
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofillUtils,