diff --git a/apps/browser/src/autofill/AUTOFILL_DEBUG.md b/apps/browser/src/autofill/AUTOFILL_DEBUG.md
index ad27ccdab80..34ed9648e58 100644
--- a/apps/browser/src/autofill/AUTOFILL_DEBUG.md
+++ b/apps/browser/src/autofill/AUTOFILL_DEBUG.md
@@ -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:
@@ -90,6 +127,7 @@ When debug mode is enabled, the console automatically displays enhanced qualific
```
### Field Rejected
+
```
❌ Field Rejected: opid_12345
Field:
@@ -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
diff --git a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts
index aa01ada0838..22e52f65707 100644
--- a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts
+++ b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts
@@ -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 {
+ const tabId = tab.id!;
+ return new Promise((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.",
+ );
+ },
+ );
+ });
+ }
}
diff --git a/apps/browser/src/autofill/browser/main-context-menu-handler.ts b/apps/browser/src/autofill/browser/main-context-menu-handler.ts
index 5a47975684c..d5566f309ce 100644
--- a/apps/browser/src/autofill/browser/main-context-menu-handler.ts
+++ b/apps/browser/src/autofill/browser/main-context-menu-handler.ts
@@ -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);
diff --git a/apps/browser/src/autofill/content/bootstrap-autofill-overlay.ts b/apps/browser/src/autofill/content/bootstrap-autofill-overlay.ts
index 839f61d551a..02002971678 100644
--- a/apps/browser/src/autofill/content/bootstrap-autofill-overlay.ts
+++ b/apps/browser/src/autofill/content/bootstrap-autofill-overlay.ts
@@ -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);
diff --git a/apps/browser/src/autofill/enums/autofill-message.enums.ts b/apps/browser/src/autofill/enums/autofill-message.enums.ts
index 4fdae2d9146..de17358ea50 100644
--- a/apps/browser/src/autofill/enums/autofill-message.enums.ts
+++ b/apps/browser/src/autofill/enums/autofill-message.enums.ts
@@ -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];
diff --git a/apps/browser/src/autofill/services/abstractions/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/abstractions/autofill-overlay-content.service.ts
index e1d24159664..2cf70bfce7f 100644
--- a/apps/browser/src/autofill/services/abstractions/autofill-overlay-content.service.ts
+++ b/apps/browser/src/autofill/services/abstractions/autofill-overlay-content.service.ts
@@ -27,6 +27,8 @@ export type AutofillOverlayContentExtensionMessageHandlers = {
getInlineMenuFormFieldData: ({
message,
}: AutofillExtensionMessageParam) => Promise;
+ generatedPasswordModifyLogin: () => Promise;
+ getAutofillDebugExport: () => string;
};
export interface AutofillOverlayContentService {
diff --git a/apps/browser/src/autofill/services/abstractions/inline-menu-field-qualifications.service.ts b/apps/browser/src/autofill/services/abstractions/inline-menu-field-qualifications.service.ts
index 8f99f994b61..d2428f566f3 100644
--- a/apps/browser/src/autofill/services/abstractions/inline-menu-field-qualifications.service.ts
+++ b/apps/browser/src/autofill/services/abstractions/inline-menu-field-qualifications.service.ts
@@ -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;
diff --git a/apps/browser/src/autofill/services/autofill-debug.service.ts b/apps/browser/src/autofill/services/autofill-debug.service.ts
index b205e0d1bdd..23b987517a6 100644
--- a/apps/browser/src/autofill/services/autofill-debug.service.ts
+++ b/apps/browser/src/autofill/services/autofill-debug.service.ts
@@ -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);
diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts
index ad0c4b742b6..b63c06bafce 100644
--- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts
+++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts
@@ -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 = {
[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 });
}
diff --git a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts
index c2bf2dd805f..579e0b26678 100644
--- a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts
+++ b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts
@@ -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.
diff --git a/apps/browser/src/types/tab-messages.ts b/apps/browser/src/types/tab-messages.ts
index dbedb3c4a55..e573b142fd7 100644
--- a/apps/browser/src/types/tab-messages.ts
+++ b/apps/browser/src/types/tab-messages.ts
@@ -1,7 +1,8 @@
export type TabMessage =
| CopyTextTabMessage
| ClearClipboardTabMessage
- | GetClickedElementTabMessage;
+ | GetClickedElementTabMessage
+ | GetAutofillDebugExportTabMessage;
export type TabMessageBase = {
command: T;
@@ -14,3 +15,5 @@ type CopyTextTabMessage = TabMessageBase<"copyText"> & {
type ClearClipboardTabMessage = TabMessageBase<"clearClipboard">;
type GetClickedElementTabMessage = TabMessageBase<"getClickedElement">;
+
+type GetAutofillDebugExportTabMessage = TabMessageBase<"getAutofillDebugExport">;