UNPKG

chrome-devtools-frontend

Version:
130 lines (114 loc) 5 kB
// Copyright 2023 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Common from '../../core/common/common.js'; import * as Host from '../../core/host/host.js'; import * as Platform from '../../core/platform/platform.js'; import * as SDK from '../../core/sdk/sdk.js'; import type * as Protocol from '../../generated/protocol.js'; import * as UI from '../../ui/legacy/legacy.js'; let autofillManagerInstance: AutofillManager; export class AutofillManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes> { #autoOpenViewSetting: Common.Settings.Setting<boolean>; #address = ''; #filledFields: Protocol.Autofill.FilledField[] = []; #matches: Match[] = []; #autofillModel: SDK.AutofillModel.AutofillModel|null = null; private constructor() { super(); SDK.TargetManager.TargetManager.instance().addModelListener( SDK.AutofillModel.AutofillModel, SDK.AutofillModel.Events.ADDRESS_FORM_FILLED, this.#addressFormFilled, this, {scoped: true}); this.#autoOpenViewSetting = Common.Settings.Settings.instance().createSetting('auto-open-autofill-view-on-event', true); } static instance(opts: {forceNew: boolean|null} = {forceNew: null}): AutofillManager { const {forceNew} = opts; if (!autofillManagerInstance || forceNew) { autofillManagerInstance = new AutofillManager(); } return autofillManagerInstance; } onShowAutofillTestAddressesSettingsChanged(): void { for (const autofillModel of SDK.TargetManager.TargetManager.instance().models(SDK.AutofillModel.AutofillModel)) { autofillModel.setTestAddresses(); } } async #addressFormFilled({data}: Common.EventTarget.EventTargetEvent< SDK.AutofillModel.EventTypes[SDK.AutofillModel.Events.ADDRESS_FORM_FILLED]>): Promise<void> { if (this.#autoOpenViewSetting.get()) { await UI.ViewManager.ViewManager.instance().showView('autofill-view'); Host.userMetrics.actionTaken(Host.UserMetrics.Action.AutofillReceivedAndTabAutoOpened); } else { Host.userMetrics.actionTaken(Host.UserMetrics.Action.AutofillReceived); } this.#autofillModel = data.autofillModel; this.#processAddressFormFilledData(data.event); if (this.#address) { this.dispatchEventToListeners(Events.ADDRESS_FORM_FILLED, { address: this.#address, filledFields: this.#filledFields, matches: this.#matches, autofillModel: this.#autofillModel, }); } } getLastFilledAddressForm(): AddressFormFilledEvent|null { if (!this.#address || !this.#autofillModel) { return null; } return { address: this.#address, filledFields: this.#filledFields, matches: this.#matches, autofillModel: this.#autofillModel, }; } #processAddressFormFilledData({addressUi, filledFields}: Protocol.Autofill.AddressFormFilledEvent): void { // Transform addressUi into a single (multi-line) string. const concatAddressFields = (addressFields: Protocol.Autofill.AddressFields): string => addressFields.fields.filter(field => field.value.length).map(field => field.value).join(' '); this.#address = addressUi.addressFields.map(addressFields => concatAddressFields(addressFields)) .filter(str => str.length) .join('\n'); this.#filledFields = filledFields; this.#matches = []; // Populate a list of matches by searching in the address string for // occurences of filled field values. for (let i = 0; i < this.#filledFields.length; i++) { if (this.#filledFields[i].value === '') { continue; } // 1) Replace multiple whitespaces with a single space. // 2) Escape special characters. // 3) For ',' or '.' before whitespace, insert the '?' quantifier. const needle = Platform.StringUtilities.escapeForRegExp(this.#filledFields[i].value.replaceAll(/\s/g, ' ')) .replaceAll(/([.,]+)\s/g, '$1? '); const matches = this.#address.replaceAll(/\s/g, ' ').matchAll(new RegExp(needle, 'g')); for (const match of matches) { if (typeof match.index !== 'undefined') { this.#matches.push({startIndex: match.index, endIndex: match.index + match[0].length, filledFieldIndex: i}); } } } } } // A Match describes how the value of a filled field corresponds to a substring // of address from startIndex to endIndex. export interface Match { startIndex: number; endIndex: number; filledFieldIndex: number; } export const enum Events { ADDRESS_FORM_FILLED = 'AddressFormFilled', } export interface AddressFormFilledEvent { address: string; filledFields: Protocol.Autofill.FilledField[]; matches: Match[]; autofillModel: SDK.AutofillModel.AutofillModel; } export interface EventTypes { [Events.ADDRESS_FORM_FILLED]: AddressFormFilledEvent; }