mirror of
https://github.com/bitwarden/browser
synced 2026-02-25 00:53:22 +00:00
[WIP] Fix qualification pipeline, add debug capture, human-readable conditions
This commit is contained in:
@@ -5,11 +5,13 @@ This document describes how to use the autofill debug mode for troubleshooting f
|
||||
## Enabling Debug Mode
|
||||
|
||||
1. Set the dev flag in your `.env.development` file:
|
||||
|
||||
```
|
||||
DEV_FLAGS={"autofillDebugMode": true}
|
||||
```
|
||||
|
||||
2. Rebuild the browser extension:
|
||||
|
||||
```bash
|
||||
npm run build:watch
|
||||
```
|
||||
@@ -19,35 +21,67 @@ This document describes how to use the autofill debug mode for troubleshooting f
|
||||
## Using Debug Mode
|
||||
|
||||
When debug mode is enabled, the browser console will display:
|
||||
|
||||
```
|
||||
[Bitwarden Debug] Autofill debug mode enabled. Use window.__BITWARDEN_AUTOFILL_DEBUG__
|
||||
```
|
||||
|
||||
### Available Methods
|
||||
### Right-Click Context Menu (Easiest Path)
|
||||
|
||||
With debug mode enabled, right-clicking any element on the page reveals a **[Debug] Copy autofill debug info** item in the Bitwarden context menu. Clicking it copies a plain-text summary of all field qualification decisions for the current page to your clipboard.
|
||||
|
||||
This summary includes:
|
||||
|
||||
- All fields that were evaluated (both qualified and rejected)
|
||||
- Why each field passed or failed each condition
|
||||
- Human-readable explanations and fix suggestions for failures
|
||||
|
||||
Paste the copied text directly into a support ticket or bug report. Field values are never captured.
|
||||
|
||||
### Console API
|
||||
|
||||
#### `exportSession(format?: 'json' | 'summary' | 'console')`
|
||||
|
||||
Exports the current debug session in the specified format.
|
||||
|
||||
```javascript
|
||||
// Export as JSON (default)
|
||||
const data = __BITWARDEN_AUTOFILL_DEBUG__.exportSession('json');
|
||||
const data = __BITWARDEN_AUTOFILL_DEBUG__.exportSession("json");
|
||||
console.log(JSON.parse(data));
|
||||
|
||||
// Export as human-readable summary
|
||||
console.log(__BITWARDEN_AUTOFILL_DEBUG__.exportSession('summary'));
|
||||
console.log(__BITWARDEN_AUTOFILL_DEBUG__.exportSession("summary"));
|
||||
|
||||
// Output to console with pretty formatting
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.exportSession('console');
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.exportSession("console");
|
||||
```
|
||||
|
||||
#### `exportSummary()`
|
||||
|
||||
Returns a human-readable summary of the most recent session.
|
||||
|
||||
```javascript
|
||||
console.log(__BITWARDEN_AUTOFILL_DEBUG__.exportSummary());
|
||||
```
|
||||
|
||||
#### `startSession(name?: string)`
|
||||
|
||||
Starts a new debug session, optionally with a stable name for diffing.
|
||||
|
||||
Named sessions produce deterministic, timestamp-prefixed IDs — useful for comparing the same page across different deploys or configurations:
|
||||
|
||||
```javascript
|
||||
// Named session: session_2026-02-19T12-00-00-000Z_login-form-test
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.startSession("login-form-test");
|
||||
|
||||
// Anonymous session: session_2026-02-19T12-00-00-000Z_ab3f2
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.startSession();
|
||||
```
|
||||
|
||||
The ISO timestamp prefix means sessions sort naturally by date, enabling meaningful diffs between weekly deploys.
|
||||
|
||||
#### `setTracingDepth(depth: number)`
|
||||
|
||||
Configures how deep to trace precondition qualifiers.
|
||||
|
||||
```javascript
|
||||
@@ -62,6 +96,7 @@ __BITWARDEN_AUTOFILL_DEBUG__.setTracingDepth(2);
|
||||
```
|
||||
|
||||
#### `getTracingDepth()`
|
||||
|
||||
Returns the current tracing depth.
|
||||
|
||||
```javascript
|
||||
@@ -70,11 +105,12 @@ console.log(`Current tracing depth: ${depth}`);
|
||||
```
|
||||
|
||||
#### `getSessions()`
|
||||
|
||||
Returns an array of all session IDs in memory.
|
||||
|
||||
```javascript
|
||||
const sessions = __BITWARDEN_AUTOFILL_DEBUG__.getSessions();
|
||||
console.log('Active sessions:', sessions);
|
||||
console.log("Active sessions:", sessions);
|
||||
```
|
||||
|
||||
## Enhanced Console Logging
|
||||
@@ -82,6 +118,7 @@ console.log('Active sessions:', sessions);
|
||||
When debug mode is enabled, the console automatically displays enhanced qualification messages:
|
||||
|
||||
### Field Qualified
|
||||
|
||||
```
|
||||
✅ Field Qualified: opid_12345
|
||||
Field: <input type="text" ...>
|
||||
@@ -90,6 +127,7 @@ When debug mode is enabled, the console automatically displays enhanced qualific
|
||||
```
|
||||
|
||||
### Field Rejected
|
||||
|
||||
```
|
||||
❌ Field Rejected: opid_12345
|
||||
Field: <input type="text" ...>
|
||||
@@ -101,19 +139,20 @@ When debug mode is enabled, the console automatically displays enhanced qualific
|
||||
## Understanding Debug Output
|
||||
|
||||
### JSON Export Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionId": "session_1234567890_abc123",
|
||||
"sessionId": "session_2026-02-19T12-00-00-000Z_abc12",
|
||||
"startTime": 1234567890000,
|
||||
"endTime": 1234567891000,
|
||||
"url": "https://example.com/login",
|
||||
"qualifications": [
|
||||
{
|
||||
"fieldId": "opid_12345",
|
||||
"elementSelector": "input[type='text']#username",
|
||||
"elementSelector": "#username",
|
||||
"attempts": [
|
||||
{
|
||||
"attemptId": "attempt_1234567890_xyz789",
|
||||
"attemptId": "attempt_2026-02-19T12-00-00-500Z",
|
||||
"timestamp": 1234567890500,
|
||||
"vector": "inline-menu",
|
||||
"result": {
|
||||
@@ -122,6 +161,7 @@ When debug mode is enabled, the console automatically displays enhanced qualific
|
||||
"pass": [
|
||||
{
|
||||
"name": "isUsernameField",
|
||||
"description": "Field is recognized as a username or email input",
|
||||
"functionSource": "function isUsernameField(field) { ... }"
|
||||
}
|
||||
],
|
||||
@@ -138,7 +178,7 @@ When debug mode is enabled, the console automatically displays enhanced qualific
|
||||
"tracingDepth": 0
|
||||
}
|
||||
},
|
||||
"triggeredBy": "page-load"
|
||||
"triggeredBy": "setupOverlayListeners"
|
||||
}
|
||||
],
|
||||
"finalDecision": { ... }
|
||||
@@ -148,30 +188,40 @@ When debug mode is enabled, the console automatically displays enhanced qualific
|
||||
```
|
||||
|
||||
### Summary Export Structure
|
||||
|
||||
```
|
||||
================================================================================
|
||||
Bitwarden Autofill Debug Summary
|
||||
================================================================================
|
||||
Session ID: session_1234567890_abc123
|
||||
Session ID: session_2026-02-19T12-00-00-000Z_abc12
|
||||
URL: https://example.com/login
|
||||
Start Time: 2026-02-06T12:00:00.000Z
|
||||
End Time: 2026-02-06T12:00:01.000Z
|
||||
Start Time: 2026-02-19T12:00:00.000Z
|
||||
End Time: 2026-02-19T12:00:01.000Z
|
||||
Duration: 1.00s
|
||||
|
||||
Total Fields Qualified: 2
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Field ID: opid_12345
|
||||
Selector: input[type='text']#username
|
||||
Selector: #username
|
||||
Attempts: 1
|
||||
Final Decision: ✅ QUALIFIED
|
||||
|
||||
Passed Conditions:
|
||||
✓ isUsernameField
|
||||
✓ notCurrentlyInSandboxedIframe
|
||||
✓ notCurrentlyInSandboxedIframe — Field is not in a sandboxed iframe
|
||||
✓ isUsernameField — Field is recognized as a username or email input
|
||||
|
||||
Vector: inline-menu
|
||||
Timestamp: 2026-02-06T12:00:00.500Z
|
||||
--------------------------------------------------------------------------------
|
||||
Field ID: opid_67890
|
||||
Selector: [name="search"]
|
||||
Attempts: 1
|
||||
Final Decision: ❌ REJECTED
|
||||
|
||||
Passed Conditions:
|
||||
✓ notCurrentlyInSandboxedIframe
|
||||
Failed Conditions:
|
||||
✗ fieldIsForLoginForm — Field is part of a login form
|
||||
→ Fix: Add autocomplete="username" or autocomplete="email" to the field
|
||||
|
||||
================================================================================
|
||||
⚠️ WARNING: This debug data may contain sensitive information.
|
||||
@@ -189,32 +239,60 @@ Debug data tracks which autofill vector triggered the qualification:
|
||||
- **keyboard-shortcut**: Autofill triggered by keyboard shortcut (Ctrl+Shift+L)
|
||||
- **page-load**: Autofill triggered automatically on page load
|
||||
|
||||
### Architectural Reality
|
||||
|
||||
All 4 entry points (inline menu, popup, keyboard shortcut, context menu) ultimately call the same fill path in the background service worker: `autofillService.doAutoFill()` → `generateFillScript()`. They use the same core logic and do not bypass each other.
|
||||
|
||||
However, there are **two distinct qualification systems**:
|
||||
|
||||
| System | Used by | Where it runs |
|
||||
| ------------------------------------- | ------------------------ | ------------------------- |
|
||||
| `InlineMenuFieldQualificationService` | Inline menu overlay only | Content script |
|
||||
| `generateFillScript()` field matching | All 4 entry points | Background service worker |
|
||||
|
||||
The `InlineMenuFieldQualificationService` is used exclusively to decide **whether to show the overlay icon on a given field**. The debug session records only this decision. The actual fill logic uses simpler, independent field detection in the background.
|
||||
|
||||
This means:
|
||||
|
||||
- A field _rejected_ by inline menu qualification (no overlay shown) may still be _filled_ by keyboard shortcut or context menu, because the two qualification systems use different criteria.
|
||||
- A field _accepted_ by inline menu qualification may fail to fill if `generateFillScript()` doesn't match it.
|
||||
|
||||
### Why Other Vectors Are Not Tracked
|
||||
|
||||
The debug service lives in the content script inside `AutofillOverlayContentService`. The popup, keyboard shortcut, and context menu flows run entirely in the background service worker and only send the rendered fill script to the content script — they never invoke the content script's qualification service. There is no point in those paths where the current vector can be set.
|
||||
|
||||
To track those vectors would require:
|
||||
|
||||
1. A new message from the background to the content script announcing "autofill was triggered via [vector]"
|
||||
2. The content script debug service recording it against the current session
|
||||
|
||||
This is tracked as future work.
|
||||
|
||||
## Precondition Tracing
|
||||
|
||||
Some qualification functions depend on other qualifiers (preconditions). For example:
|
||||
|
||||
- `isNewPasswordField` depends on `isPasswordField`
|
||||
- `isCurrentPasswordField` depends on `isPasswordField`
|
||||
|
||||
The tracing depth controls how deep to capture these dependencies:
|
||||
|
||||
### Depth = 0 (No Tracing)
|
||||
|
||||
Only captures the top-level condition result. Fastest, minimal data.
|
||||
|
||||
### Depth = 1 (Immediate Preconditions) - Default
|
||||
|
||||
Captures the direct preconditions of the qualification.
|
||||
|
||||
Example: For `isNewPasswordField`, captures:
|
||||
|
||||
- `isNewPasswordField` (top level)
|
||||
- `isPasswordField` (immediate precondition)
|
||||
|
||||
### Depth = 2+ (Full Chain)
|
||||
Captures the entire chain of preconditions recursively.
|
||||
|
||||
Example: For a complex qualification, captures:
|
||||
- `isFieldForLoginForm` (top level)
|
||||
- `isUsernameFieldForLoginForm` (precondition)
|
||||
- `isUsernameField` (precondition of precondition)
|
||||
- ... (and so on)
|
||||
Captures the entire chain of preconditions recursively.
|
||||
|
||||
## Performance Impact
|
||||
|
||||
@@ -235,48 +313,85 @@ Example: For a complex qualification, captures:
|
||||
⚠️ **Never share debug output publicly or with untrusted parties**
|
||||
|
||||
While field values are redacted, debug output still contains:
|
||||
|
||||
- Page URLs
|
||||
- Field IDs, names, and attributes
|
||||
- Form structure
|
||||
- Qualification logic (function source code)
|
||||
|
||||
This information could be used to identify:
|
||||
|
||||
- Internal applications
|
||||
- Custom form fields
|
||||
- Business logic patterns
|
||||
|
||||
## QA / Regression Workflow
|
||||
|
||||
Use named sessions for deterministic, diffable output across deployments:
|
||||
|
||||
```javascript
|
||||
// Before a deploy
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.startSession("checkout-form-baseline");
|
||||
// Focus fields on the page to trigger qualification
|
||||
const before = __BITWARDEN_AUTOFILL_DEBUG__.exportSession("json");
|
||||
|
||||
// After a deploy
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.startSession("checkout-form-after-deploy");
|
||||
// Focus same fields
|
||||
const after = __BITWARDEN_AUTOFILL_DEBUG__.exportSession("json");
|
||||
|
||||
// Diff — attempt IDs and timestamps in conditions are stable; only real changes show up
|
||||
```
|
||||
|
||||
Session IDs use ISO timestamps (`session_2026-02-19T12-00-00-000Z_name`) so they sort naturally and identify when each capture was taken.
|
||||
|
||||
## Troubleshooting Common Issues
|
||||
|
||||
### Debug API Not Available
|
||||
|
||||
```javascript
|
||||
typeof window.__BITWARDEN_AUTOFILL_DEBUG__ === 'undefined'
|
||||
typeof window.__BITWARDEN_AUTOFILL_DEBUG__ === "undefined";
|
||||
```
|
||||
|
||||
**Solution**: Verify that:
|
||||
|
||||
1. Dev flag is set correctly in `.env.development`
|
||||
2. Extension was rebuilt after setting the flag
|
||||
3. You're on a page where autofill is active (not a chrome:// or browser settings page)
|
||||
|
||||
### No Sessions Found
|
||||
|
||||
```javascript
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.getSessions() // returns []
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.getSessions(); // returns []
|
||||
```
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Focus a form field to trigger qualification
|
||||
2. Sessions expire after 5 minutes - check timing
|
||||
3. Verify debug mode is enabled (check console for initialization message)
|
||||
|
||||
### Missing Precondition Data
|
||||
|
||||
```javascript
|
||||
// result.meta.preconditions is undefined
|
||||
```
|
||||
|
||||
**Solution**: Increase tracing depth:
|
||||
|
||||
```javascript
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.setTracingDepth(2);
|
||||
```
|
||||
|
||||
### Empty qualifications Array in Export
|
||||
|
||||
If `exportSession('json')` shows `"qualifications": []`, the session captured no field evaluations.
|
||||
|
||||
**Solution**: The session may have started after fields were already evaluated. Either:
|
||||
|
||||
1. Start a named session before navigating: `startSession('my-test')` — then navigate to the page
|
||||
2. Or refresh the page after enabling debug mode so all fields are re-evaluated
|
||||
|
||||
## Example Workflow
|
||||
|
||||
1. Enable debug mode and rebuild extension
|
||||
@@ -296,16 +411,6 @@ __BITWARDEN_AUTOFILL_DEBUG__.setTracingDepth(2);
|
||||
8. Focus the field again to capture with higher depth
|
||||
9. Export and analyze:
|
||||
```javascript
|
||||
const json = __BITWARDEN_AUTOFILL_DEBUG__.exportSession('json');
|
||||
const json = __BITWARDEN_AUTOFILL_DEBUG__.exportSession("json");
|
||||
// Save to file or analyze
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Planned features (not yet implemented):
|
||||
- Visual debug panel UI in popup/options
|
||||
- Download JSON button
|
||||
- Copy to clipboard functionality
|
||||
- JSON-DSL representation of conditions
|
||||
- Filter by field type or qualification result
|
||||
- Session replay/comparison
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
openAddEditVaultItemPopout,
|
||||
openVaultItemPasswordRepromptPopout,
|
||||
} from "../../vault/popup/utils/vault-popout-window";
|
||||
import { COPY_AUTOFILL_DEBUG_ID } from "../enums/autofill-message.enums";
|
||||
import { LockedVaultPendingNotificationsData } from "../background/abstractions/notification.background";
|
||||
import { AutofillCipherTypeId } from "../types";
|
||||
|
||||
@@ -76,6 +77,13 @@ export class ContextMenuClickedHandler {
|
||||
|
||||
this.copyToClipboard({ text: await this.getIdentifier(tab, info), tab: tab });
|
||||
break;
|
||||
case COPY_AUTOFILL_DEBUG_ID:
|
||||
if (!tab.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.copyToClipboard({ text: await this.getAutofillDebugExport(tab), tab: tab });
|
||||
break;
|
||||
default:
|
||||
await this.cipherAction(info, tab);
|
||||
}
|
||||
@@ -279,4 +287,21 @@ export class ContextMenuClickedHandler {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private async getAutofillDebugExport(tab: chrome.tabs.Tab): Promise<string> {
|
||||
const tabId = tab.id!;
|
||||
return new Promise<string>((resolve) => {
|
||||
BrowserApi.sendTabsMessage(
|
||||
tabId,
|
||||
{ command: "getAutofillDebugExport" },
|
||||
undefined,
|
||||
(debugText: string) => {
|
||||
resolve(
|
||||
debugText ||
|
||||
"No autofill debug data available. Enable debug mode and focus a form field first.",
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
|
||||
|
||||
import { COPY_AUTOFILL_DEBUG_ID } from "../enums/autofill-message.enums";
|
||||
import { devFlagEnabled } from "../../platform/flags";
|
||||
import { InitContextMenuItems } from "./abstractions/main-context-menu-handler";
|
||||
|
||||
export class MainContextMenuHandler {
|
||||
@@ -204,6 +206,14 @@ export class MainContextMenuHandler {
|
||||
|
||||
await MainContextMenuHandler.create({ ...otherOptions, contexts: ["all"] });
|
||||
}
|
||||
if (devFlagEnabled("autofillDebugMode")) {
|
||||
await MainContextMenuHandler.create({
|
||||
id: COPY_AUTOFILL_DEBUG_ID,
|
||||
parentId: ROOT_ID,
|
||||
title: "[Debug] Copy autofill debug info",
|
||||
contexts: ["all"],
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
this.logService.warning(error.message);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { devFlagEnabled } from "../../platform/flags";
|
||||
import { DebugExportFormat } from "../models/autofill-debug-data";
|
||||
import { AutofillInlineMenuContentService } from "../overlay/inline-menu/content/autofill-inline-menu-content.service";
|
||||
import { OverlayNotificationsContentService } from "../overlay/notifications/content/overlay-notifications-content.service";
|
||||
@@ -7,7 +8,6 @@ import DomElementVisibilityService from "../services/dom-element-visibility.serv
|
||||
import { DomQueryService } from "../services/dom-query.service";
|
||||
import { InlineMenuFieldQualificationService } from "../services/inline-menu-field-qualification.service";
|
||||
import { setupAutofillInitDisconnectAction } from "../utils";
|
||||
import { devFlagEnabled } from "../../platform/flags";
|
||||
|
||||
import AutofillInit from "./autofill-init";
|
||||
|
||||
@@ -60,8 +60,15 @@ import AutofillInit from "./autofill-init";
|
||||
Array.from(debugService.sessionStore.keys())[0] || "",
|
||||
);
|
||||
},
|
||||
startSession: (name?: string) => {
|
||||
const sessionId = debugService.startSession(globalThis.location.href, name);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[Bitwarden Debug] Session started: ${sessionId}`);
|
||||
return sessionId;
|
||||
},
|
||||
setTracingDepth: (depth: number) => {
|
||||
debugService.setTracingDepth(depth);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[Bitwarden Debug] Precondition tracing depth set to ${depth}`);
|
||||
},
|
||||
getTracingDepth: () => {
|
||||
@@ -72,6 +79,7 @@ import AutofillInit from "./autofill-init";
|
||||
},
|
||||
};
|
||||
|
||||
/* eslint-disable no-console */
|
||||
console.log(
|
||||
"%c[Bitwarden Debug] Autofill debug mode enabled. Use window.__BITWARDEN_AUTOFILL_DEBUG__",
|
||||
"color: #175DDC; font-weight: bold; font-size: 12px",
|
||||
@@ -79,9 +87,11 @@ import AutofillInit from "./autofill-init";
|
||||
console.log("Available methods:");
|
||||
console.log(" - exportSession(format?: 'json' | 'summary' | 'console')");
|
||||
console.log(" - exportSummary()");
|
||||
console.log(" - startSession(name?: string) — named sessions are diffable across deploys");
|
||||
console.log(" - setTracingDepth(depth: number)");
|
||||
console.log(" - getTracingDepth()");
|
||||
console.log(" - getSessions()");
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
}
|
||||
})(window);
|
||||
|
||||
@@ -3,6 +3,8 @@ export const AutofillMessageCommand = {
|
||||
collectPageDetailsResponse: "collectPageDetailsResponse",
|
||||
} as const;
|
||||
|
||||
export const COPY_AUTOFILL_DEBUG_ID = "copy-autofill-debug";
|
||||
|
||||
export type AutofillMessageCommandType =
|
||||
(typeof AutofillMessageCommand)[keyof typeof AutofillMessageCommand];
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ export type AutofillOverlayContentExtensionMessageHandlers = {
|
||||
getInlineMenuFormFieldData: ({
|
||||
message,
|
||||
}: AutofillExtensionMessageParam) => Promise<ModifyLoginCipherFormData | void>;
|
||||
generatedPasswordModifyLogin: () => Promise<void>;
|
||||
getAutofillDebugExport: () => string;
|
||||
};
|
||||
|
||||
export interface AutofillOverlayContentService {
|
||||
|
||||
@@ -20,6 +20,8 @@ export type AutofillVector =
|
||||
|
||||
export type QualificationCondition = {
|
||||
name: string;
|
||||
description?: string;
|
||||
suggestion?: string;
|
||||
functionSource?: string;
|
||||
};
|
||||
|
||||
@@ -53,11 +55,17 @@ export interface InlineMenuFieldQualificationService {
|
||||
isFieldForAccountCreationForm(field: AutofillField, pageDetails: AutofillPageDetails): boolean;
|
||||
isFieldForIdentityForm(field: AutofillField, pageDetails: AutofillPageDetails): boolean;
|
||||
isFieldForCardholderName(field: AutofillField): boolean;
|
||||
isFieldForCardholderNameWithResult(field: AutofillField): QualificationResult;
|
||||
isFieldForCardNumber(field: AutofillField): boolean;
|
||||
isFieldForCardNumberWithResult(field: AutofillField): QualificationResult;
|
||||
isFieldForCardExpirationDate(field: AutofillField): boolean;
|
||||
isFieldForCardExpirationDateWithResult(field: AutofillField): QualificationResult;
|
||||
isFieldForCardExpirationMonth(field: AutofillField): boolean;
|
||||
isFieldForCardExpirationMonthWithResult(field: AutofillField): QualificationResult;
|
||||
isFieldForCardExpirationYear(field: AutofillField): boolean;
|
||||
isFieldForCardExpirationYearWithResult(field: AutofillField): QualificationResult;
|
||||
isFieldForCardCvv(field: AutofillField): boolean;
|
||||
isFieldForCardCvvWithResult(field: AutofillField): QualificationResult;
|
||||
isFieldForIdentityTitle(field: AutofillField): boolean;
|
||||
isFieldForIdentityFirstName(field: AutofillField): boolean;
|
||||
isFieldForIdentityMiddleName(field: AutofillField): boolean;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import AutofillField from "../models/autofill-field";
|
||||
import AutofillPageDetails from "../models/autofill-page-details";
|
||||
/* eslint-disable no-console */
|
||||
import { devFlagEnabled } from "../../platform/flags";
|
||||
import {
|
||||
AutofillDebugSession,
|
||||
DebugExportFormat,
|
||||
FieldQualificationRecord,
|
||||
QualificationAttempt,
|
||||
} from "../models/autofill-debug-data";
|
||||
import { devFlagEnabled } from "../../platform/flags";
|
||||
import AutofillField from "../models/autofill-field";
|
||||
import AutofillPageDetails from "../models/autofill-page-details";
|
||||
|
||||
import {
|
||||
AutofillVector,
|
||||
QualificationResult,
|
||||
@@ -40,8 +41,11 @@ export class AutofillDebugService {
|
||||
this.tracingDepth = depth;
|
||||
}
|
||||
|
||||
startSession(url: string): string {
|
||||
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
||||
startSession(url: string, sessionName?: string): string {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const sessionId = sessionName
|
||||
? `session_${timestamp}_${sessionName}`
|
||||
: `session_${timestamp}_${Math.random().toString(36).substring(2, 7)}`;
|
||||
this.currentSession = {
|
||||
sessionId,
|
||||
startTime: Date.now(),
|
||||
@@ -92,7 +96,7 @@ export class AutofillDebugService {
|
||||
}
|
||||
|
||||
const attempt: QualificationAttempt = {
|
||||
attemptId: `attempt_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
||||
attemptId: `attempt_${new Date().toISOString().replace(/[:.]/g, "-")}`,
|
||||
timestamp: Date.now(),
|
||||
vector,
|
||||
result,
|
||||
@@ -105,7 +109,9 @@ export class AutofillDebugService {
|
||||
|
||||
exportCurrentSession(format: DebugExportFormat = "json"): string {
|
||||
if (!this.currentSession) {
|
||||
return format === "json" ? JSON.stringify({ error: "No active session" }) : "No active session";
|
||||
return format === "json"
|
||||
? JSON.stringify({ error: "No active session" })
|
||||
: "No active session";
|
||||
}
|
||||
|
||||
return this.exportSession(this.currentSession.sessionId, format);
|
||||
@@ -115,7 +121,9 @@ export class AutofillDebugService {
|
||||
const session = this.sessionStore.get(sessionId);
|
||||
|
||||
if (!session) {
|
||||
return format === "json" ? JSON.stringify({ error: "Session not found" }) : "Session not found";
|
||||
return format === "json"
|
||||
? JSON.stringify({ error: "Session not found" })
|
||||
: "Session not found";
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
@@ -168,7 +176,8 @@ export class AutofillDebugService {
|
||||
lines.push("");
|
||||
lines.push("Passed Conditions:");
|
||||
for (const condition of fieldRecord.finalDecision.conditions.pass) {
|
||||
lines.push(` ✓ ${condition.name}`);
|
||||
const desc = condition.description ? ` — ${condition.description}` : "";
|
||||
lines.push(` ✓ ${condition.name}${desc}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,14 +185,20 @@ export class AutofillDebugService {
|
||||
lines.push("");
|
||||
lines.push("Failed Conditions:");
|
||||
for (const condition of fieldRecord.finalDecision.conditions.fail) {
|
||||
lines.push(` ✗ ${condition.name}`);
|
||||
const desc = condition.description ? ` — ${condition.description}` : "";
|
||||
lines.push(` ✗ ${condition.name}${desc}`);
|
||||
if (condition.suggestion) {
|
||||
lines.push(` → Fix: ${condition.suggestion}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldRecord.finalDecision.meta) {
|
||||
lines.push("");
|
||||
lines.push(`Vector: ${fieldRecord.finalDecision.meta.vector}`);
|
||||
lines.push(`Timestamp: ${new Date(fieldRecord.finalDecision.meta.timestamp).toISOString()}`);
|
||||
lines.push(
|
||||
`Timestamp: ${new Date(fieldRecord.finalDecision.meta.timestamp).toISOString()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +245,9 @@ export class AutofillDebugService {
|
||||
if (fieldRecord.finalDecision.conditions.pass.length > 0) {
|
||||
console.group("✓ Passed Conditions");
|
||||
for (const condition of fieldRecord.finalDecision.conditions.pass) {
|
||||
console.log(`${condition.name}`);
|
||||
console.log(
|
||||
`${condition.name}${condition.description ? ` — ${condition.description}` : ""}`,
|
||||
);
|
||||
if (condition.functionSource) {
|
||||
console.log(`Function:`, condition.functionSource);
|
||||
}
|
||||
@@ -241,7 +258,12 @@ export class AutofillDebugService {
|
||||
if (fieldRecord.finalDecision.conditions.fail.length > 0) {
|
||||
console.group("✗ Failed Conditions");
|
||||
for (const condition of fieldRecord.finalDecision.conditions.fail) {
|
||||
console.log(`${condition.name}`);
|
||||
console.log(
|
||||
`${condition.name}${condition.description ? ` — ${condition.description}` : ""}`,
|
||||
);
|
||||
if (condition.suggestion) {
|
||||
console.log(` → Fix: ${condition.suggestion}`);
|
||||
}
|
||||
if (condition.functionSource) {
|
||||
console.log(`Function:`, condition.functionSource);
|
||||
}
|
||||
@@ -252,7 +274,9 @@ export class AutofillDebugService {
|
||||
if (fieldRecord.finalDecision.meta) {
|
||||
console.group("Metadata");
|
||||
console.log(`Vector: ${fieldRecord.finalDecision.meta.vector}`);
|
||||
console.log(`Timestamp: ${new Date(fieldRecord.finalDecision.meta.timestamp).toISOString()}`);
|
||||
console.log(
|
||||
`Timestamp: ${new Date(fieldRecord.finalDecision.meta.timestamp).toISOString()}`,
|
||||
);
|
||||
console.log(`Field Snapshot:`, fieldRecord.finalDecision.meta.fieldSnapshot);
|
||||
if (fieldRecord.finalDecision.meta.pageSnapshot) {
|
||||
console.log(`Page Snapshot:`, fieldRecord.finalDecision.meta.pageSnapshot);
|
||||
|
||||
@@ -54,7 +54,11 @@ import {
|
||||
} from "./abstractions/autofill-overlay-content.service";
|
||||
import { DomElementVisibilityService } from "./abstractions/dom-element-visibility.service";
|
||||
import { DomQueryService } from "./abstractions/dom-query.service";
|
||||
import { InlineMenuFieldQualificationService } from "./abstractions/inline-menu-field-qualifications.service";
|
||||
import {
|
||||
AutofillVector,
|
||||
InlineMenuFieldQualificationService,
|
||||
QualificationResult,
|
||||
} from "./abstractions/inline-menu-field-qualifications.service";
|
||||
import { AutoFillConstants } from "./autofill-constants";
|
||||
import { AutofillDebugService } from "./autofill-debug.service";
|
||||
|
||||
@@ -123,6 +127,8 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
getInlineMenuFormFieldData: ({ message }) =>
|
||||
this.handleGetInlineMenuFormFieldDataMessage(message),
|
||||
generatedPasswordModifyLogin: () => this.sendGeneratedPasswordModifyLogin(),
|
||||
getAutofillDebugExport: () =>
|
||||
this.debugService?.exportCurrentSession("summary") ?? "Debug mode not enabled",
|
||||
};
|
||||
private readonly loginFieldQualifiers: Record<string, CallableFunction> = {
|
||||
[AutofillFieldQualifier.username]: this.inlineMenuFieldQualificationService.isUsernameField,
|
||||
@@ -263,16 +269,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
failure: "field previously qualified",
|
||||
},
|
||||
},
|
||||
// Function should be entirely deconstructed first.
|
||||
// {
|
||||
// qualifier: ({ autofillFieldData, pageDetails }: QualificationCriteria) =>
|
||||
// !this.isIgnoredField(autofillFieldData, pageDetails),
|
||||
// alias: "isNotIgnoredField",
|
||||
// message: {
|
||||
// success: "field is not ignored",
|
||||
// failure: "field is ignored",
|
||||
// },
|
||||
// },
|
||||
{
|
||||
qualifier: ({ autofillFieldData }: QualificationCriteria) =>
|
||||
!this.ignoredFieldTypes.has(autofillFieldData.type),
|
||||
@@ -297,6 +293,88 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
effect: ({ autofillFieldData }: QualificationCriteria) =>
|
||||
this.setQualifiedLoginFillType(autofillFieldData),
|
||||
},
|
||||
{
|
||||
qualifier: ({ autofillFieldData, pageDetails }: QualificationCriteria) =>
|
||||
this.showInlineMenuCards &&
|
||||
this.inlineMenuFieldQualificationService.isFieldForCreditCardForm(
|
||||
autofillFieldData,
|
||||
pageDetails,
|
||||
),
|
||||
alias: "fieldIsForCreditCardForm",
|
||||
message: {
|
||||
success: "field is for credit card form",
|
||||
failure: "field is not for credit card form",
|
||||
},
|
||||
blocking: false,
|
||||
effect: ({ autofillFieldData }: QualificationCriteria) => {
|
||||
if (!autofillFieldData.inlineMenuFillType) {
|
||||
autofillFieldData.inlineMenuFillType = CipherType.Card;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
qualifier: ({ autofillFieldData, pageDetails }: QualificationCriteria) =>
|
||||
this.inlineMenuFieldQualificationService.isFieldForAccountCreationForm(
|
||||
autofillFieldData,
|
||||
pageDetails,
|
||||
),
|
||||
alias: "fieldIsForAccountCreationForm",
|
||||
message: {
|
||||
success: "field is for account creation form",
|
||||
failure: "field is not for account creation form",
|
||||
},
|
||||
blocking: false,
|
||||
effect: ({ autofillFieldData }: QualificationCriteria) => {
|
||||
if (!autofillFieldData.inlineMenuFillType) {
|
||||
this.setQualifiedAccountCreationFillType(autofillFieldData);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
qualifier: ({ autofillFieldData, pageDetails }: QualificationCriteria) =>
|
||||
this.showInlineMenuIdentities &&
|
||||
this.inlineMenuFieldQualificationService.isFieldForIdentityForm(
|
||||
autofillFieldData,
|
||||
pageDetails,
|
||||
),
|
||||
alias: "fieldIsForIdentityForm",
|
||||
message: {
|
||||
success: "field is for identity form",
|
||||
failure: "field is not for identity form",
|
||||
},
|
||||
blocking: false,
|
||||
effect: ({ autofillFieldData }: QualificationCriteria) => {
|
||||
if (!autofillFieldData.inlineMenuFillType) {
|
||||
autofillFieldData.inlineMenuFillType = CipherType.Identity;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
qualifier: ({ autofillFieldData, pageDetails }: QualificationCriteria) =>
|
||||
this.inlineMenuFieldQualificationService.isFieldForLoginForm(
|
||||
autofillFieldData,
|
||||
pageDetails,
|
||||
) ||
|
||||
this.inlineMenuFieldQualificationService.isFieldForAccountCreationForm(
|
||||
autofillFieldData,
|
||||
pageDetails,
|
||||
) ||
|
||||
(this.showInlineMenuCards &&
|
||||
this.inlineMenuFieldQualificationService.isFieldForCreditCardForm(
|
||||
autofillFieldData,
|
||||
pageDetails,
|
||||
)) ||
|
||||
(this.showInlineMenuIdentities &&
|
||||
this.inlineMenuFieldQualificationService.isFieldForIdentityForm(
|
||||
autofillFieldData,
|
||||
pageDetails,
|
||||
)),
|
||||
alias: "fieldIsForKnownFormType",
|
||||
message: {
|
||||
success: "field is for a recognized form type",
|
||||
failure: "field is not for any recognized form type",
|
||||
},
|
||||
},
|
||||
{
|
||||
qualifier: ({ formFieldElement, autofillFieldData }: QualificationCriteria) =>
|
||||
!this.isHiddenField(formFieldElement, autofillFieldData),
|
||||
@@ -324,6 +402,13 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
isQualifiedField(criteria: QualificationCriteria) {
|
||||
const debugEnabled = this.debugService?.isDebugEnabled() ?? false;
|
||||
const responses: QualificationResponse[] = [];
|
||||
const { autofillFieldData } = criteria;
|
||||
|
||||
const elementSelector = autofillFieldData.htmlID
|
||||
? `#${autofillFieldData.htmlID}`
|
||||
: autofillFieldData.htmlName
|
||||
? `[name="${autofillFieldData.htmlName}"]`
|
||||
: autofillFieldData.opid;
|
||||
|
||||
for (const definition of this.qualifiers) {
|
||||
const response = this.qualify(definition, criteria);
|
||||
@@ -331,8 +416,25 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
|
||||
if (response.result === false && definition?.blocking !== false) {
|
||||
if (debugEnabled) {
|
||||
const qualificationResult: QualificationResult = {
|
||||
result: false,
|
||||
conditions: {
|
||||
pass: responses
|
||||
.filter((r) => r !== response && r.result)
|
||||
.map((r) => ({ name: r.alias })),
|
||||
fail: [{ name: response.alias }],
|
||||
},
|
||||
};
|
||||
this.debugService?.recordQualification(
|
||||
autofillFieldData.opid,
|
||||
elementSelector,
|
||||
"inline-menu" as AutofillVector,
|
||||
qualificationResult,
|
||||
"setupOverlayListeners",
|
||||
);
|
||||
/* eslint-disable no-console */
|
||||
console.group(
|
||||
`%c❌ Field Rejected: ${criteria.autofillFieldData.opid}`,
|
||||
`%c❌ Field Rejected: ${autofillFieldData.opid}`,
|
||||
"color: #ef4444; font-weight: bold",
|
||||
);
|
||||
console.log("Field:", criteria.formFieldElement);
|
||||
@@ -340,7 +442,9 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
console.log("Message:", response.message);
|
||||
console.log("All responses:", responses);
|
||||
console.groupEnd();
|
||||
/* eslint-enable no-console */
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log({ element: criteria.formFieldElement, responses });
|
||||
}
|
||||
return false;
|
||||
@@ -352,8 +456,23 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
}
|
||||
|
||||
if (debugEnabled) {
|
||||
const qualificationResult: QualificationResult = {
|
||||
result: true,
|
||||
conditions: {
|
||||
pass: responses.map((r) => ({ name: r.alias })),
|
||||
fail: [],
|
||||
},
|
||||
};
|
||||
this.debugService?.recordQualification(
|
||||
autofillFieldData.opid,
|
||||
elementSelector,
|
||||
"inline-menu" as AutofillVector,
|
||||
qualificationResult,
|
||||
"setupOverlayListeners",
|
||||
);
|
||||
/* eslint-disable no-console */
|
||||
console.group(
|
||||
`%c✅ Field Qualified: ${criteria.autofillFieldData.opid}`,
|
||||
`%c✅ Field Qualified: ${autofillFieldData.opid}`,
|
||||
"color: #10b981; font-weight: bold",
|
||||
);
|
||||
console.log("Field:", criteria.formFieldElement);
|
||||
@@ -363,7 +482,9 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
);
|
||||
console.log("All responses:", responses);
|
||||
console.groupEnd();
|
||||
/* eslint-enable no-console */
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log({ element: criteria.formFieldElement, responses });
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
SubmitChangePasswordButtonNames,
|
||||
SubmitLoginButtonNames,
|
||||
} from "./autofill-constants";
|
||||
import AutofillService from "./autofill.service";
|
||||
import { AutofillDebugService } from "./autofill-debug.service";
|
||||
import AutofillService from "./autofill.service";
|
||||
|
||||
export class InlineMenuFieldQualificationService implements InlineMenuFieldQualificationServiceInterface {
|
||||
private searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames);
|
||||
@@ -159,6 +159,116 @@ export class InlineMenuFieldQualificationService implements InlineMenuFieldQuali
|
||||
return this.debugService?.isDebugEnabled() ?? false;
|
||||
}
|
||||
|
||||
private static readonly conditionDescriptions: Record<
|
||||
string,
|
||||
{ description?: string; suggestion?: string }
|
||||
> = {
|
||||
isUsernameField: {
|
||||
description: "Field is recognized as a username or email input",
|
||||
suggestion: 'Add autocomplete="username" or autocomplete="email" to the field',
|
||||
},
|
||||
isCurrentPasswordField: {
|
||||
description: "Field is recognized as an existing (login) password field",
|
||||
suggestion: 'Add autocomplete="current-password" to the field',
|
||||
},
|
||||
isNewPasswordField: {
|
||||
description: "Field is recognized as a new password field (account creation)",
|
||||
suggestion: 'Add autocomplete="new-password" to the field',
|
||||
},
|
||||
isPasswordField: {
|
||||
description: "Field has type='password' or matches password heuristics",
|
||||
suggestion: "Set type='password' on the field",
|
||||
},
|
||||
isFieldForCardholderName: {
|
||||
description: "Field is recognized as a cardholder name field",
|
||||
suggestion: 'Add autocomplete="cc-name" to the field',
|
||||
},
|
||||
isFieldForCardNumber: {
|
||||
description: "Field is recognized as a credit card number field",
|
||||
suggestion: 'Add autocomplete="cc-number" to the field',
|
||||
},
|
||||
isFieldForCardExpirationDate: {
|
||||
description: "Field is recognized as a card expiration date field",
|
||||
suggestion: 'Add autocomplete="cc-exp" to the field',
|
||||
},
|
||||
isFieldForCardExpirationMonth: {
|
||||
description: "Field is recognized as a card expiration month field",
|
||||
suggestion: 'Add autocomplete="cc-exp-month" to the field',
|
||||
},
|
||||
isFieldForCardExpirationYear: {
|
||||
description: "Field is recognized as a card expiration year field",
|
||||
suggestion: 'Add autocomplete="cc-exp-year" to the field',
|
||||
},
|
||||
isFieldForCardCvv: {
|
||||
description: "Field is recognized as a card CVV/security code field",
|
||||
suggestion: 'Add autocomplete="cc-csc" to the field',
|
||||
},
|
||||
isFieldForIdentityTitle: {
|
||||
description: "Field is recognized as an honorific title field",
|
||||
suggestion: 'Add autocomplete="honorific-prefix" to the field',
|
||||
},
|
||||
isFieldForIdentityFirstName: {
|
||||
description: "Field is recognized as a first name field",
|
||||
suggestion: 'Add autocomplete="given-name" to the field',
|
||||
},
|
||||
isFieldForIdentityMiddleName: {
|
||||
description: "Field is recognized as a middle name field",
|
||||
suggestion: 'Add autocomplete="additional-name" to the field',
|
||||
},
|
||||
isFieldForIdentityLastName: {
|
||||
description: "Field is recognized as a last name field",
|
||||
suggestion: 'Add autocomplete="family-name" to the field',
|
||||
},
|
||||
isFieldForIdentityFullName: {
|
||||
description: "Field is recognized as a full name field",
|
||||
suggestion: 'Add autocomplete="name" to the field',
|
||||
},
|
||||
isFieldForIdentityAddress1: {
|
||||
description: "Field is recognized as a primary address line field",
|
||||
suggestion: 'Add autocomplete="address-line1" to the field',
|
||||
},
|
||||
isFieldForIdentityAddress2: {
|
||||
description: "Field is recognized as a secondary address line field",
|
||||
suggestion: 'Add autocomplete="address-line2" to the field',
|
||||
},
|
||||
isFieldForIdentityAddress3: {
|
||||
description: "Field is recognized as a third address line field",
|
||||
suggestion: 'Add autocomplete="address-line3" to the field',
|
||||
},
|
||||
isFieldForIdentityCity: {
|
||||
description: "Field is recognized as a city field",
|
||||
suggestion: 'Add autocomplete="address-level2" to the field',
|
||||
},
|
||||
isFieldForIdentityState: {
|
||||
description: "Field is recognized as a state or province field",
|
||||
suggestion: 'Add autocomplete="address-level1" to the field',
|
||||
},
|
||||
isFieldForIdentityPostalCode: {
|
||||
description: "Field is recognized as a postal or ZIP code field",
|
||||
suggestion: 'Add autocomplete="postal-code" to the field',
|
||||
},
|
||||
isFieldForIdentityCountry: {
|
||||
description: "Field is recognized as a country field",
|
||||
suggestion: 'Add autocomplete="country" to the field',
|
||||
},
|
||||
isFieldForIdentityCompany: {
|
||||
description: "Field is recognized as a company or organization field",
|
||||
suggestion: 'Add autocomplete="organization" to the field',
|
||||
},
|
||||
isFieldForIdentityPhone: {
|
||||
description: "Field is recognized as a phone number field",
|
||||
suggestion: 'Add autocomplete="tel" to the field',
|
||||
},
|
||||
isFieldForIdentityEmail: {
|
||||
description: "Field is recognized as an email address field",
|
||||
suggestion: 'Add autocomplete="email" to the field',
|
||||
},
|
||||
isFieldForIdentityUsername: {
|
||||
description: "Field is recognized as a username field for identity",
|
||||
suggestion: 'Add autocomplete="username" to the field',
|
||||
},
|
||||
};
|
||||
|
||||
setCurrentVector(vector: AutofillVector): void {
|
||||
this.currentVector = vector;
|
||||
}
|
||||
@@ -192,8 +302,11 @@ export class InlineMenuFieldQualificationService implements InlineMenuFieldQuali
|
||||
const maxDepth = this.debugService?.getTracingDepth() ?? 1;
|
||||
const shouldTrace = currentDepth < maxDepth;
|
||||
|
||||
const descMeta = InlineMenuFieldQualificationService.conditionDescriptions[name];
|
||||
const condition = {
|
||||
name,
|
||||
description: descMeta?.description,
|
||||
suggestion: result ? undefined : descMeta?.suggestion,
|
||||
functionSource: shouldTrace ? fn.toString() : undefined,
|
||||
};
|
||||
|
||||
@@ -640,104 +753,97 @@ export class InlineMenuFieldQualificationService implements InlineMenuFieldQuali
|
||||
*
|
||||
* @param field - The field to validate
|
||||
*/
|
||||
isFieldForCardholderName = (field: AutofillField): boolean => {
|
||||
if (this.fieldContainsAutocompleteValues(field, this.creditCardNameAutocompleteValues)) {
|
||||
return true;
|
||||
}
|
||||
isFieldForCardholderName = (field: AutofillField): boolean =>
|
||||
this.isFieldForCardholderNameWithResult(field).result;
|
||||
|
||||
return this.keywordsFoundInFieldData(
|
||||
field,
|
||||
CreditCardAutoFillConstants.CardHolderFieldNames,
|
||||
false,
|
||||
);
|
||||
};
|
||||
isFieldForCardholderNameWithResult(field: AutofillField): QualificationResult {
|
||||
const result =
|
||||
this.fieldContainsAutocompleteValues(field, this.creditCardNameAutocompleteValues) ||
|
||||
this.keywordsFoundInFieldData(field, CreditCardAutoFillConstants.CardHolderFieldNames, false);
|
||||
return this.wrapQualifier("isFieldForCardholderName", () => result, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided field as a credit card number field.
|
||||
*
|
||||
* @param field - The field to validate
|
||||
*/
|
||||
isFieldForCardNumber = (field: AutofillField): boolean => {
|
||||
if (this.fieldContainsAutocompleteValues(field, this.creditCardNumberAutocompleteValue)) {
|
||||
return true;
|
||||
}
|
||||
isFieldForCardNumber = (field: AutofillField): boolean =>
|
||||
this.isFieldForCardNumberWithResult(field).result;
|
||||
|
||||
return this.keywordsFoundInFieldData(
|
||||
field,
|
||||
CreditCardAutoFillConstants.CardNumberFieldNames,
|
||||
false,
|
||||
);
|
||||
};
|
||||
isFieldForCardNumberWithResult(field: AutofillField): QualificationResult {
|
||||
const result =
|
||||
this.fieldContainsAutocompleteValues(field, this.creditCardNumberAutocompleteValue) ||
|
||||
this.keywordsFoundInFieldData(field, CreditCardAutoFillConstants.CardNumberFieldNames, false);
|
||||
return this.wrapQualifier("isFieldForCardNumber", () => result, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided field as a credit card expiration date field.
|
||||
*
|
||||
* @param field - The field to validate
|
||||
*/
|
||||
isFieldForCardExpirationDate = (field: AutofillField): boolean => {
|
||||
if (
|
||||
this.fieldContainsAutocompleteValues(field, this.creditCardExpirationDateAutocompleteValue)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
isFieldForCardExpirationDate = (field: AutofillField): boolean =>
|
||||
this.isFieldForCardExpirationDateWithResult(field).result;
|
||||
|
||||
return this.keywordsFoundInFieldData(
|
||||
field,
|
||||
CreditCardAutoFillConstants.CardExpiryFieldNames,
|
||||
false,
|
||||
);
|
||||
};
|
||||
isFieldForCardExpirationDateWithResult(field: AutofillField): QualificationResult {
|
||||
const result =
|
||||
this.fieldContainsAutocompleteValues(field, this.creditCardExpirationDateAutocompleteValue) ||
|
||||
this.keywordsFoundInFieldData(field, CreditCardAutoFillConstants.CardExpiryFieldNames, false);
|
||||
return this.wrapQualifier("isFieldForCardExpirationDate", () => result, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided field as a credit card expiration month field.
|
||||
*
|
||||
* @param field - The field to validate
|
||||
*/
|
||||
isFieldForCardExpirationMonth = (field: AutofillField): boolean => {
|
||||
if (
|
||||
this.fieldContainsAutocompleteValues(field, this.creditCardExpirationMonthAutocompleteValue)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
isFieldForCardExpirationMonth = (field: AutofillField): boolean =>
|
||||
this.isFieldForCardExpirationMonthWithResult(field).result;
|
||||
|
||||
return this.keywordsFoundInFieldData(
|
||||
field,
|
||||
CreditCardAutoFillConstants.ExpiryMonthFieldNames,
|
||||
false,
|
||||
);
|
||||
};
|
||||
isFieldForCardExpirationMonthWithResult(field: AutofillField): QualificationResult {
|
||||
const result =
|
||||
this.fieldContainsAutocompleteValues(
|
||||
field,
|
||||
this.creditCardExpirationMonthAutocompleteValue,
|
||||
) ||
|
||||
this.keywordsFoundInFieldData(
|
||||
field,
|
||||
CreditCardAutoFillConstants.ExpiryMonthFieldNames,
|
||||
false,
|
||||
);
|
||||
return this.wrapQualifier("isFieldForCardExpirationMonth", () => result, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided field as a credit card expiration year field.
|
||||
*
|
||||
* @param field - The field to validate
|
||||
*/
|
||||
isFieldForCardExpirationYear = (field: AutofillField): boolean => {
|
||||
if (
|
||||
this.fieldContainsAutocompleteValues(field, this.creditCardExpirationYearAutocompleteValue)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
isFieldForCardExpirationYear = (field: AutofillField): boolean =>
|
||||
this.isFieldForCardExpirationYearWithResult(field).result;
|
||||
|
||||
return this.keywordsFoundInFieldData(
|
||||
field,
|
||||
CreditCardAutoFillConstants.ExpiryYearFieldNames,
|
||||
false,
|
||||
);
|
||||
};
|
||||
isFieldForCardExpirationYearWithResult(field: AutofillField): QualificationResult {
|
||||
const result =
|
||||
this.fieldContainsAutocompleteValues(field, this.creditCardExpirationYearAutocompleteValue) ||
|
||||
this.keywordsFoundInFieldData(field, CreditCardAutoFillConstants.ExpiryYearFieldNames, false);
|
||||
return this.wrapQualifier("isFieldForCardExpirationYear", () => result, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided field as a credit card CVV field.
|
||||
*
|
||||
* @param field - The field to validate
|
||||
*/
|
||||
isFieldForCardCvv = (field: AutofillField): boolean => {
|
||||
if (this.fieldContainsAutocompleteValues(field, this.creditCardCvvAutocompleteValue)) {
|
||||
return true;
|
||||
}
|
||||
isFieldForCardCvv = (field: AutofillField): boolean =>
|
||||
this.isFieldForCardCvvWithResult(field).result;
|
||||
|
||||
return this.keywordsFoundInFieldData(field, CreditCardAutoFillConstants.CVVFieldNames, false);
|
||||
};
|
||||
isFieldForCardCvvWithResult(field: AutofillField): QualificationResult {
|
||||
const result =
|
||||
this.fieldContainsAutocompleteValues(field, this.creditCardCvvAutocompleteValue) ||
|
||||
this.keywordsFoundInFieldData(field, CreditCardAutoFillConstants.CVVFieldNames, false);
|
||||
return this.wrapQualifier("isFieldForCardCvv", () => result, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided field as an identity title type field.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export type TabMessage =
|
||||
| CopyTextTabMessage
|
||||
| ClearClipboardTabMessage
|
||||
| GetClickedElementTabMessage;
|
||||
| GetClickedElementTabMessage
|
||||
| GetAutofillDebugExportTabMessage;
|
||||
|
||||
export type TabMessageBase<T extends string> = {
|
||||
command: T;
|
||||
@@ -14,3 +15,5 @@ type CopyTextTabMessage = TabMessageBase<"copyText"> & {
|
||||
type ClearClipboardTabMessage = TabMessageBase<"clearClipboard">;
|
||||
|
||||
type GetClickedElementTabMessage = TabMessageBase<"getClickedElement">;
|
||||
|
||||
type GetAutofillDebugExportTabMessage = TabMessageBase<"getAutofillDebugExport">;
|
||||
|
||||
Reference in New Issue
Block a user