-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor - auto update credential provider script (#20139)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
a357831
commit 42b9be1
Showing
1 changed file
with
188 additions
and
0 deletions.
There are no files selected for viewing
188 changes: 188 additions & 0 deletions
188
firefox-ios/Client/Assets/CC_Script/addressFormLayout.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
ChromeUtils.defineESModuleGetters(this, { | ||
FormAutofill: "resource://autofill/FormAutofill.sys.mjs", | ||
FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs", | ||
}); | ||
|
||
/* exported canSubmitForm, getCurrentFormData */ | ||
|
||
// Defines template descriptors for generating elements in convertLayoutToUI. | ||
const fieldTemplates = { | ||
commonAttributes(item) { | ||
return { | ||
id: item.fieldId, | ||
name: item.fieldId, | ||
required: item.required, | ||
value: item.value ?? "", | ||
}; | ||
}, | ||
input(item) { | ||
return { | ||
tag: "input", | ||
type: item.type ?? "text", | ||
...this.commonAttributes(item), | ||
}; | ||
}, | ||
textarea(item) { | ||
return { | ||
tag: "textarea", | ||
...this.commonAttributes(item), | ||
}; | ||
}, | ||
select(item) { | ||
return { | ||
tag: "select", | ||
children: item.options.map(({ value, text }) => ({ | ||
tag: "option", | ||
selected: value === item.value, | ||
value, | ||
text, | ||
})), | ||
...this.commonAttributes(item), | ||
}; | ||
}, | ||
}; | ||
|
||
/** | ||
* Creates an HTML element with specified attributes and children. | ||
* | ||
* @param {string} tag - Tag name for the element to create. | ||
* @param {object} options - Options object containing attributes and children. | ||
* @param {object} options.attributes - Element's Attributes/Props (id, class, etc.) | ||
* @param {Array} options.children - Element's children (array of objects with tag and options). | ||
* @returns {HTMLElement} The newly created element. | ||
*/ | ||
const createElement = (tag, { children = [], ...attributes }) => { | ||
const element = document.createElement(tag); | ||
|
||
for (let [attributeName, attributeValue] of Object.entries(attributes)) { | ||
if (attributeName in element) { | ||
element[attributeName] = attributeValue; | ||
} else { | ||
element.setAttribute(attributeName, attributeValue); | ||
} | ||
} | ||
|
||
for (let { tag: childTag, ...childRest } of children) { | ||
element.appendChild(createElement(childTag, childRest)); | ||
} | ||
|
||
return element; | ||
}; | ||
|
||
/** | ||
* Generator that creates UI elements from `fields` object, using localization from `l10nStrings`. | ||
* | ||
* @param {Array} fields - Array of objects as returned from `FormAutofillUtils.getFormLayout`. | ||
* @param {object} l10nStrings - Key-value pairs for field label localization. | ||
* @yields {HTMLElement} - A localized label element with constructed from a field. | ||
*/ | ||
function* convertLayoutToUI(fields, l10nStrings) { | ||
for (const item of fields) { | ||
// eslint-disable-next-line no-nested-ternary | ||
const fieldTag = item.options | ||
? "select" | ||
: item.multiline | ||
? "textarea" | ||
: "input"; | ||
|
||
const fieldUI = { | ||
label: { | ||
id: `${item.fieldId}-container`, | ||
class: `container ${item.newLine ? "new-line" : ""}`, | ||
}, | ||
field: fieldTemplates[fieldTag](item), | ||
span: { | ||
class: "label-text", | ||
textContent: l10nStrings[item.l10nId] ?? "", | ||
}, | ||
}; | ||
|
||
const label = createElement("label", fieldUI.label); | ||
const { tag, ...rest } = fieldUI.field; | ||
const field = createElement(tag, rest); | ||
label.appendChild(field); | ||
const span = createElement("span", fieldUI.span); | ||
label.appendChild(span); | ||
|
||
yield label; | ||
} | ||
} | ||
|
||
/** | ||
* Retrieves the current form data from the current form element on the page. | ||
* | ||
* @returns {object} An object containing key-value pairs of form data. | ||
*/ | ||
const getCurrentFormData = () => { | ||
const formElement = document.querySelector("form"); | ||
const formData = new FormData(formElement); | ||
return Object.fromEntries(formData.entries()); | ||
}; | ||
|
||
/** | ||
* Checks if the form can be submitted based on the number of non-empty values. | ||
* TODO(Bug 1891734): Add address validation. Right now we don't do any validation. (2 fields mimics the old behaviour ). | ||
* | ||
* @returns {boolean} True if the form can be submitted | ||
*/ | ||
const canSubmitForm = () => { | ||
const formData = getCurrentFormData(); | ||
const validValues = Object.values(formData).filter(Boolean); | ||
return validValues.length >= 2; | ||
}; | ||
|
||
/** | ||
* Generates a form layout based on record data and localization strings. | ||
* | ||
* @param {HTMLFormElement} formElement - Target form element. | ||
* @param {object} record - Address record, includes at least country code defaulted to FormAutofill.DEFAULT_REGION. | ||
* @param {object} l10nStrings - Localization strings map. | ||
*/ | ||
const createFormLayoutFromRecord = ( | ||
formElement, | ||
record = { country: FormAutofill.DEFAULT_REGION }, | ||
l10nStrings = {} | ||
) => { | ||
// Always clear select values because they are not persisted between countries. | ||
// For example from US with state NY, we don't want the address-level1 to be NY | ||
// when changing to another country that doesn't have state options | ||
const selects = formElement.querySelectorAll("select:not(#country)"); | ||
for (const select of selects) { | ||
select.value = ""; | ||
} | ||
|
||
// Get old data to persist before clearing form | ||
const formData = getCurrentFormData(); | ||
record = { | ||
...record, | ||
...formData, | ||
}; | ||
|
||
formElement.innerHTML = ""; | ||
const fields = FormAutofillUtils.getFormLayout(record); | ||
|
||
const layoutGenerator = convertLayoutToUI(fields, l10nStrings); | ||
|
||
for (const fieldElement of layoutGenerator) { | ||
formElement.appendChild(fieldElement); | ||
} | ||
|
||
document.querySelector("#country").addEventListener( | ||
"change", | ||
ev => | ||
// Allow some time for the user to type | ||
// before we set the new country and re-render | ||
setTimeout(() => { | ||
record.country = ev.target.value; | ||
createFormLayoutFromRecord(formElement, record, l10nStrings); | ||
}, 300), | ||
{ once: true } | ||
); | ||
|
||
// Used to notify tests that the form has been updated and is ready | ||
window.dispatchEvent(new CustomEvent("FormReadyForTests")); | ||
}; |