732 lines
20 KiB
JavaScript
732 lines
20 KiB
JavaScript
/* 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/. */
|
|
|
|
import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
|
|
GeckoViewPrompter: "resource://gre/modules/GeckoViewPrompter.sys.mjs",
|
|
});
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "LoginInfo", () =>
|
|
Components.Constructor(
|
|
"@mozilla.org/login-manager/loginInfo;1",
|
|
"nsILoginInfo",
|
|
"init"
|
|
)
|
|
);
|
|
|
|
export class LoginEntry {
|
|
constructor({
|
|
origin,
|
|
formActionOrigin,
|
|
httpRealm,
|
|
username,
|
|
password,
|
|
guid,
|
|
timeCreated,
|
|
timeLastUsed,
|
|
timePasswordChanged,
|
|
timesUsed,
|
|
}) {
|
|
this.origin = origin ?? "";
|
|
this.formActionOrigin = formActionOrigin ?? null;
|
|
this.httpRealm = httpRealm ?? null;
|
|
this.username = username ?? "";
|
|
this.password = password ?? "";
|
|
|
|
// Metadata.
|
|
this.guid = guid ?? null;
|
|
// TODO: Not supported by GV.
|
|
this.timeCreated = timeCreated ?? null;
|
|
this.timeLastUsed = timeLastUsed ?? null;
|
|
this.timePasswordChanged = timePasswordChanged ?? null;
|
|
this.timesUsed = timesUsed ?? null;
|
|
}
|
|
|
|
toLoginInfo() {
|
|
const info = new lazy.LoginInfo(
|
|
this.origin,
|
|
this.formActionOrigin,
|
|
this.httpRealm,
|
|
this.username,
|
|
this.password
|
|
);
|
|
|
|
// Metadata.
|
|
info.QueryInterface(Ci.nsILoginMetaInfo);
|
|
info.guid = this.guid;
|
|
info.timeCreated = this.timeCreated;
|
|
info.timeLastUsed = this.timeLastUsed;
|
|
info.timePasswordChanged = this.timePasswordChanged;
|
|
info.timesUsed = this.timesUsed;
|
|
|
|
return info;
|
|
}
|
|
|
|
static parse(aObj) {
|
|
const entry = new LoginEntry({});
|
|
Object.assign(entry, aObj);
|
|
|
|
return entry;
|
|
}
|
|
|
|
static fromLoginInfo(aInfo) {
|
|
const entry = new LoginEntry({});
|
|
entry.origin = aInfo.origin;
|
|
entry.formActionOrigin = aInfo.formActionOrigin;
|
|
entry.httpRealm = aInfo.httpRealm;
|
|
entry.username = aInfo.username;
|
|
entry.password = aInfo.password;
|
|
|
|
// Metadata.
|
|
aInfo.QueryInterface(Ci.nsILoginMetaInfo);
|
|
entry.guid = aInfo.guid;
|
|
entry.timeCreated = aInfo.timeCreated;
|
|
entry.timeLastUsed = aInfo.timeLastUsed;
|
|
entry.timePasswordChanged = aInfo.timePasswordChanged;
|
|
entry.timesUsed = aInfo.timesUsed;
|
|
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
export class Address {
|
|
constructor({
|
|
name,
|
|
givenName,
|
|
additionalName,
|
|
familyName,
|
|
organization,
|
|
streetAddress,
|
|
addressLevel1,
|
|
addressLevel2,
|
|
addressLevel3,
|
|
postalCode,
|
|
country,
|
|
tel,
|
|
email,
|
|
guid,
|
|
timeCreated,
|
|
timeLastUsed,
|
|
timeLastModified,
|
|
timesUsed,
|
|
version,
|
|
}) {
|
|
this.name = name ?? "";
|
|
this.givenName = givenName ?? "";
|
|
this.additionalName = additionalName ?? "";
|
|
this.familyName = familyName ?? "";
|
|
this.organization = organization ?? "";
|
|
this.streetAddress = streetAddress ?? "";
|
|
this.addressLevel1 = addressLevel1 ?? "";
|
|
this.addressLevel2 = addressLevel2 ?? "";
|
|
this.addressLevel3 = addressLevel3 ?? "";
|
|
this.postalCode = postalCode ?? "";
|
|
this.country = country ?? "";
|
|
this.tel = tel ?? "";
|
|
this.email = email ?? "";
|
|
|
|
// Metadata.
|
|
this.guid = guid ?? null;
|
|
// TODO: Not supported by GV.
|
|
this.timeCreated = timeCreated ?? null;
|
|
this.timeLastUsed = timeLastUsed ?? null;
|
|
this.timeLastModified = timeLastModified ?? null;
|
|
this.timesUsed = timesUsed ?? null;
|
|
this.version = version ?? null;
|
|
}
|
|
|
|
isValid() {
|
|
return (
|
|
(this.name ?? this.givenName ?? this.familyName) !== "" &&
|
|
this.streetAddress !== "" &&
|
|
this.postalCode !== ""
|
|
);
|
|
}
|
|
|
|
static fromGecko(aObj) {
|
|
return new Address({
|
|
version: aObj.version,
|
|
name: aObj.name,
|
|
givenName: aObj["given-name"],
|
|
additionalName: aObj["additional-name"],
|
|
familyName: aObj["family-name"],
|
|
organization: aObj.organization,
|
|
streetAddress: aObj["street-address"],
|
|
addressLevel1: aObj["address-level1"],
|
|
addressLevel2: aObj["address-level2"],
|
|
addressLevel3: aObj["address-level3"],
|
|
postalCode: aObj["postal-code"],
|
|
country: aObj.country,
|
|
tel: aObj.tel,
|
|
email: aObj.email,
|
|
guid: aObj.guid,
|
|
timeCreated: aObj.timeCreated,
|
|
timeLastUsed: aObj.timeLastUsed,
|
|
timeLastModified: aObj.timeLastModified,
|
|
timesUsed: aObj.timesUsed,
|
|
});
|
|
}
|
|
|
|
static parse(aObj) {
|
|
const entry = new Address({});
|
|
Object.assign(entry, aObj);
|
|
|
|
return entry;
|
|
}
|
|
|
|
toGecko() {
|
|
return {
|
|
version: this.version,
|
|
name: this.name,
|
|
"given-name": this.givenName,
|
|
"additional-name": this.additionalName,
|
|
"family-name": this.familyName,
|
|
organization: this.organization,
|
|
"street-address": this.streetAddress,
|
|
"address-level1": this.addressLevel1,
|
|
"address-level2": this.addressLevel2,
|
|
"address-level3": this.addressLevel3,
|
|
"postal-code": this.postalCode,
|
|
country: this.country,
|
|
tel: this.tel,
|
|
email: this.email,
|
|
guid: this.guid,
|
|
};
|
|
}
|
|
}
|
|
|
|
export class CreditCard {
|
|
constructor({
|
|
name,
|
|
number,
|
|
expMonth,
|
|
expYear,
|
|
type,
|
|
guid,
|
|
timeCreated,
|
|
timeLastUsed,
|
|
timeLastModified,
|
|
timesUsed,
|
|
version,
|
|
}) {
|
|
this.name = name ?? "";
|
|
this.number = number ?? "";
|
|
this.expMonth = expMonth ?? "";
|
|
this.expYear = expYear ?? "";
|
|
this.type = type ?? "";
|
|
|
|
// Metadata.
|
|
this.guid = guid ?? null;
|
|
// TODO: Not supported by GV.
|
|
this.timeCreated = timeCreated ?? null;
|
|
this.timeLastUsed = timeLastUsed ?? null;
|
|
this.timeLastModified = timeLastModified ?? null;
|
|
this.timesUsed = timesUsed ?? null;
|
|
this.version = version ?? null;
|
|
}
|
|
|
|
isValid() {
|
|
return this.number !== "";
|
|
}
|
|
|
|
static fromGecko(aObj) {
|
|
return new CreditCard({
|
|
version: aObj.version,
|
|
name: aObj["cc-name"],
|
|
number: aObj["cc-number"],
|
|
expMonth: aObj["cc-exp-month"]?.toString(),
|
|
expYear: aObj["cc-exp-year"]?.toString(),
|
|
type: aObj["cc-type"],
|
|
guid: aObj.guid,
|
|
timeCreated: aObj.timeCreated,
|
|
timeLastUsed: aObj.timeLastUsed,
|
|
timeLastModified: aObj.timeLastModified,
|
|
timesUsed: aObj.timesUsed,
|
|
});
|
|
}
|
|
|
|
static parse(aObj) {
|
|
const entry = new CreditCard({});
|
|
Object.assign(entry, aObj);
|
|
|
|
return entry;
|
|
}
|
|
|
|
toGecko() {
|
|
return {
|
|
version: this.version,
|
|
"cc-name": this.name,
|
|
"cc-number": this.number,
|
|
"cc-exp-month": this.expMonth,
|
|
"cc-exp-year": this.expYear,
|
|
"cc-type": this.type,
|
|
guid: this.guid,
|
|
};
|
|
}
|
|
}
|
|
|
|
export class SelectOption {
|
|
// Sync with Autocomplete.SelectOption.Hint in Autocomplete.java.
|
|
static Hint = {
|
|
NONE: 0,
|
|
GENERATED: 1 << 0,
|
|
INSECURE_FORM: 1 << 1,
|
|
DUPLICATE_USERNAME: 1 << 2,
|
|
MATCHING_ORIGIN: 1 << 3,
|
|
};
|
|
|
|
constructor({ value, hint }) {
|
|
this.value = value ?? null;
|
|
this.hint = hint ?? SelectOption.Hint.NONE;
|
|
}
|
|
}
|
|
|
|
// Sync with Autocomplete.UsedField in Autocomplete.java.
|
|
const UsedField = { PASSWORD: 1 };
|
|
|
|
export const GeckoViewAutocomplete = {
|
|
/** current opened prompt */
|
|
_prompt: null,
|
|
|
|
/**
|
|
* Delegates login entry fetching for the given domain to the attached
|
|
* LoginStorage GeckoView delegate.
|
|
*
|
|
* @param aDomain
|
|
* The domain string to fetch login entries for. If null, all logins
|
|
* will be fetched.
|
|
* @return {Promise}
|
|
* Resolves with an array of login objects or null.
|
|
* Rejected if no delegate is attached.
|
|
* Login object string properties:
|
|
* { guid, origin, formActionOrigin, httpRealm, username, password }
|
|
*/
|
|
fetchLogins(aDomain = null) {
|
|
debug`fetchLogins for ${aDomain ?? "All domains"}`;
|
|
|
|
return lazy.EventDispatcher.instance.sendRequestForResult({
|
|
type: "GeckoView:Autocomplete:Fetch:Login",
|
|
domain: aDomain,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delegates credit card entry fetching to the attached LoginStorage
|
|
* GeckoView delegate.
|
|
*
|
|
* @return {Promise}
|
|
* Resolves with an array of credit card objects or null.
|
|
* Rejected if no delegate is attached.
|
|
* Login object string properties:
|
|
* { guid, name, number, expMonth, expYear, type }
|
|
*/
|
|
fetchCreditCards() {
|
|
debug`fetchCreditCards`;
|
|
|
|
return lazy.EventDispatcher.instance.sendRequestForResult({
|
|
type: "GeckoView:Autocomplete:Fetch:CreditCard",
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delegates address entry fetching to the attached LoginStorage
|
|
* GeckoView delegate.
|
|
*
|
|
* @return {Promise}
|
|
* Resolves with an array of address objects or null.
|
|
* Rejected if no delegate is attached.
|
|
* Login object string properties:
|
|
* { guid, name, givenName, additionalName, familyName,
|
|
* organization, streetAddress, addressLevel1, addressLevel2,
|
|
* addressLevel3, postalCode, country, tel, email }
|
|
*/
|
|
fetchAddresses() {
|
|
debug`fetchAddresses`;
|
|
|
|
return lazy.EventDispatcher.instance.sendRequestForResult({
|
|
type: "GeckoView:Autocomplete:Fetch:Address",
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delegates credit card entry saving to the attached LoginStorage GeckoView delegate.
|
|
* Call this when a new or modified credit card entry has been submitted.
|
|
*
|
|
* @param aCreditCard The {CreditCard} to be saved.
|
|
*/
|
|
onCreditCardSave(aCreditCard) {
|
|
debug`onCreditCardSave ${aCreditCard}`;
|
|
|
|
lazy.EventDispatcher.instance.sendRequest({
|
|
type: "GeckoView:Autocomplete:Save:CreditCard",
|
|
creditCard: aCreditCard,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delegates address entry saving to the attached LoginStorage GeckoView delegate.
|
|
* Call this when a new or modified address entry has been submitted.
|
|
*
|
|
* @param aAddress The {Address} to be saved.
|
|
*/
|
|
onAddressSave(aAddress) {
|
|
debug`onAddressSave ${aAddress}`;
|
|
|
|
lazy.EventDispatcher.instance.sendRequest({
|
|
type: "GeckoView:Autocomplete:Save:Address",
|
|
address: aAddress,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delegates login entry saving to the attached LoginStorage GeckoView delegate.
|
|
* Call this when a new login entry or a new password for an existing login
|
|
* entry has been submitted.
|
|
*
|
|
* @param aLogin The {LoginEntry} to be saved.
|
|
*/
|
|
onLoginSave(aLogin) {
|
|
debug`onLoginSave ${aLogin}`;
|
|
|
|
lazy.EventDispatcher.instance.sendRequest({
|
|
type: "GeckoView:Autocomplete:Save:Login",
|
|
login: aLogin,
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delegates login entry password usage to the attached LoginStorage GeckoView
|
|
* delegate.
|
|
* Call this when the password of an existing login entry, as returned by
|
|
* fetchLogins, has been used for autofill.
|
|
*
|
|
* @param aLogin The {LoginEntry} whose password was used.
|
|
*/
|
|
onLoginPasswordUsed(aLogin) {
|
|
debug`onLoginUsed ${aLogin}`;
|
|
|
|
lazy.EventDispatcher.instance.sendRequest({
|
|
type: "GeckoView:Autocomplete:Used:Login",
|
|
usedFields: UsedField.PASSWORD,
|
|
login: aLogin,
|
|
});
|
|
},
|
|
|
|
_numActiveSelections: 0,
|
|
|
|
/**
|
|
* Delegates login entry selection.
|
|
* Call this when there are multiple login entry option for a form to delegate
|
|
* the selection.
|
|
*
|
|
* @param aBrowser The browser instance the triggered the selection.
|
|
* @param aOptions The list of {SelectOption} depicting viable options.
|
|
*/
|
|
onLoginSelect(aBrowser, aOptions) {
|
|
debug`onLoginSelect ${aOptions}`;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
if (!aBrowser || !aOptions) {
|
|
debug`onLoginSelect Rejecting - no browser or options provided`;
|
|
reject();
|
|
return;
|
|
}
|
|
|
|
const prompt = new lazy.GeckoViewPrompter(aBrowser.ownerGlobal);
|
|
prompt.asyncShowPrompt(
|
|
{
|
|
type: "Autocomplete:Select:Login",
|
|
options: aOptions,
|
|
},
|
|
result => {
|
|
if (!result || !result.selection) {
|
|
reject();
|
|
return;
|
|
}
|
|
|
|
const option = new SelectOption({
|
|
value: LoginEntry.parse(result.selection.value),
|
|
hint: result.selection.hint,
|
|
});
|
|
resolve(option);
|
|
}
|
|
);
|
|
this._prompt = prompt;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delegates credit card entry selection.
|
|
* Call this when there are multiple credit card entry option for a form to delegate
|
|
* the selection.
|
|
*
|
|
* @param aBrowser The browser instance the triggered the selection.
|
|
* @param aOptions The list of {SelectOption} depicting viable options.
|
|
*/
|
|
onCreditCardSelect(aBrowser, aOptions) {
|
|
debug`onCreditCardSelect ${aOptions}`;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
if (!aBrowser || !aOptions) {
|
|
debug`onCreditCardSelect Rejecting - no browser or options provided`;
|
|
reject();
|
|
return;
|
|
}
|
|
|
|
const prompt = new lazy.GeckoViewPrompter(aBrowser.ownerGlobal);
|
|
prompt.asyncShowPrompt(
|
|
{
|
|
type: "Autocomplete:Select:CreditCard",
|
|
options: aOptions,
|
|
},
|
|
result => {
|
|
if (!result || !result.selection) {
|
|
reject();
|
|
return;
|
|
}
|
|
|
|
const option = new SelectOption({
|
|
value: CreditCard.parse(result.selection.value),
|
|
hint: result.selection.hint,
|
|
});
|
|
resolve(option);
|
|
}
|
|
);
|
|
this._prompt = prompt;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delegates address entry selection.
|
|
* Call this when there are multiple address entry option for a form to delegate
|
|
* the selection.
|
|
*
|
|
* @param aBrowser The browser instance the triggered the selection.
|
|
* @param aOptions The list of {SelectOption} depicting viable options.
|
|
*/
|
|
onAddressSelect(aBrowser, aOptions) {
|
|
debug`onAddressSelect ${aOptions}`;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
if (!aBrowser || !aOptions) {
|
|
debug`onAddressSelect Rejecting - no browser or options provided`;
|
|
reject();
|
|
return;
|
|
}
|
|
|
|
const prompt = new lazy.GeckoViewPrompter(aBrowser.ownerGlobal);
|
|
prompt.asyncShowPrompt(
|
|
{
|
|
type: "Autocomplete:Select:Address",
|
|
options: aOptions,
|
|
},
|
|
result => {
|
|
if (!result || !result.selection) {
|
|
reject();
|
|
return;
|
|
}
|
|
|
|
const option = new SelectOption({
|
|
value: Address.parse(result.selection.value),
|
|
hint: result.selection.hint,
|
|
});
|
|
resolve(option);
|
|
}
|
|
);
|
|
this._prompt = prompt;
|
|
});
|
|
},
|
|
|
|
async delegateSelection({
|
|
browsingContext,
|
|
options,
|
|
inputElementIdentifier,
|
|
formOrigin,
|
|
}) {
|
|
debug`delegateSelection ${options}`;
|
|
|
|
if (!options.length) {
|
|
return;
|
|
}
|
|
|
|
let insecureHint = SelectOption.Hint.NONE;
|
|
let loginStyle = null;
|
|
|
|
// TODO: Replace this string with more robust mechanics.
|
|
let selectionType = null;
|
|
const selectOptions = [];
|
|
|
|
for (const option of options) {
|
|
switch (option.style) {
|
|
case "insecureWarning": {
|
|
// We depend on the insecure warning to be the first option.
|
|
insecureHint = SelectOption.Hint.INSECURE_FORM;
|
|
break;
|
|
}
|
|
case "generatedPassword": {
|
|
selectionType = "login";
|
|
const comment = JSON.parse(option.comment);
|
|
selectOptions.push(
|
|
new SelectOption({
|
|
value: new LoginEntry({
|
|
password: comment.generatedPassword,
|
|
}),
|
|
hint: SelectOption.Hint.GENERATED | insecureHint,
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
case "login":
|
|
// Fallthrough.
|
|
case "loginWithOrigin": {
|
|
selectionType = "login";
|
|
loginStyle = option.style;
|
|
const comment = JSON.parse(option.comment);
|
|
|
|
let hint = SelectOption.Hint.NONE | insecureHint;
|
|
if (comment.isDuplicateUsername) {
|
|
hint |= SelectOption.Hint.DUPLICATE_USERNAME;
|
|
}
|
|
if (comment.isOriginMatched) {
|
|
hint |= SelectOption.Hint.MATCHING_ORIGIN;
|
|
}
|
|
|
|
selectOptions.push(
|
|
new SelectOption({
|
|
value: LoginEntry.parse(comment.fillMessageData),
|
|
hint,
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
case "autofill": {
|
|
const { fillMessageData } = JSON.parse(option.comment);
|
|
const profile = fillMessageData.profile;
|
|
debug`delegateSelection - autofill profile ${profile}`;
|
|
const creditCard = CreditCard.fromGecko(profile);
|
|
const address = Address.fromGecko(profile);
|
|
if (creditCard.isValid()) {
|
|
selectionType = "creditCard";
|
|
selectOptions.push(
|
|
new SelectOption({
|
|
value: creditCard,
|
|
hint: insecureHint,
|
|
})
|
|
);
|
|
} else if (address.isValid()) {
|
|
selectionType = "address";
|
|
selectOptions.push(
|
|
new SelectOption({
|
|
value: address,
|
|
hint: insecureHint,
|
|
})
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
debug`delegateSelection - ignoring unknown option style ${option.style}`;
|
|
}
|
|
}
|
|
|
|
if (selectOptions.length < 1) {
|
|
debug`Abort delegateSelection - no valid options provided`;
|
|
return;
|
|
}
|
|
|
|
if (this._numActiveSelections > 0) {
|
|
debug`Abort delegateSelection - there is already one delegation active`;
|
|
return;
|
|
}
|
|
|
|
++this._numActiveSelections;
|
|
|
|
let selectedOption = null;
|
|
const browser = browsingContext.top.embedderElement;
|
|
if (selectionType === "login") {
|
|
selectedOption = await this.onLoginSelect(browser, selectOptions).catch(
|
|
_ => {
|
|
debug`No GV delegate attached`;
|
|
}
|
|
);
|
|
} else if (selectionType === "creditCard") {
|
|
selectedOption = await this.onCreditCardSelect(
|
|
browser,
|
|
selectOptions
|
|
).catch(_ => {
|
|
debug`No GV delegate attached`;
|
|
});
|
|
} else if (selectionType === "address") {
|
|
selectedOption = await this.onAddressSelect(browser, selectOptions).catch(
|
|
_ => {
|
|
debug`No GV delegate attached`;
|
|
}
|
|
);
|
|
}
|
|
|
|
// prompt is closed now.
|
|
this._prompt = null;
|
|
|
|
--this._numActiveSelections;
|
|
|
|
debug`delegateSelection selected option: ${selectedOption}`;
|
|
|
|
if (selectionType === "login") {
|
|
const selectedLogin = selectedOption?.value?.toLoginInfo();
|
|
|
|
if (!selectedLogin) {
|
|
debug`Abort delegateSelection - no login entry selected`;
|
|
return;
|
|
}
|
|
|
|
debug`delegateSelection - filling form`;
|
|
|
|
if (selectedOption.hint & SelectOption.Hint.GENERATED) {
|
|
this.onLoginSave(selectedLogin);
|
|
}
|
|
|
|
const actor =
|
|
browsingContext.currentWindowGlobal.getActor("LoginManager");
|
|
|
|
await actor.fillForm({
|
|
browser,
|
|
inputElementIdentifier,
|
|
loginFormOrigin: formOrigin,
|
|
login: selectedLogin,
|
|
style:
|
|
selectedOption.hint & SelectOption.Hint.GENERATED
|
|
? "generatedPassword"
|
|
: loginStyle,
|
|
});
|
|
} else if (selectionType === "creditCard") {
|
|
const actor =
|
|
browsingContext.currentWindowGlobal.getActor("FormAutofill");
|
|
const elementId = JSON.stringify(inputElementIdentifier);
|
|
const selectedCreditCard = selectedOption?.value?.toGecko();
|
|
|
|
actor.autofillFields(elementId, selectedCreditCard);
|
|
} else if (selectionType === "address") {
|
|
const actor =
|
|
browsingContext.currentWindowGlobal.getActor("FormAutofill");
|
|
const elementId = JSON.stringify(inputElementIdentifier);
|
|
const selectedAddress = selectedOption?.value?.toGecko();
|
|
|
|
actor.autofillFields(elementId, selectedAddress);
|
|
}
|
|
|
|
debug`delegateSelection - form filled`;
|
|
},
|
|
|
|
delegateDismiss() {
|
|
debug`delegateDismiss`;
|
|
|
|
this._prompt?.dismiss();
|
|
},
|
|
};
|
|
|
|
const { debug } = GeckoViewUtils.initLogging("GeckoViewAutocomplete");
|