mirror of
https://github.com/bitwarden/browser
synced 2026-02-25 00:53:22 +00:00
[WIP] First generated pass.
This commit is contained in:
1
.env.development
Normal file
1
.env.development
Normal file
@@ -0,0 +1 @@
|
||||
DEV_FLAGS={"autofillDebugMode": true}
|
||||
311
apps/browser/src/autofill/AUTOFILL_DEBUG.md
Normal file
311
apps/browser/src/autofill/AUTOFILL_DEBUG.md
Normal file
@@ -0,0 +1,311 @@
|
||||
# Autofill Debug Mode
|
||||
|
||||
This document describes how to use the autofill debug mode for troubleshooting field qualification issues.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
3. Load the extension in your browser (or reload if already loaded)
|
||||
|
||||
## 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
|
||||
|
||||
#### `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');
|
||||
console.log(JSON.parse(data));
|
||||
|
||||
// Export as human-readable summary
|
||||
console.log(__BITWARDEN_AUTOFILL_DEBUG__.exportSession('summary'));
|
||||
|
||||
// Output to console with pretty formatting
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.exportSession('console');
|
||||
```
|
||||
|
||||
#### `exportSummary()`
|
||||
Returns a human-readable summary of the most recent session.
|
||||
|
||||
```javascript
|
||||
console.log(__BITWARDEN_AUTOFILL_DEBUG__.exportSummary());
|
||||
```
|
||||
|
||||
#### `setTracingDepth(depth: number)`
|
||||
Configures how deep to trace precondition qualifiers.
|
||||
|
||||
```javascript
|
||||
// Don't trace preconditions (faster, less data)
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.setTracingDepth(0);
|
||||
|
||||
// Trace immediate preconditions only (default)
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.setTracingDepth(1);
|
||||
|
||||
// Trace full precondition chain (more detail)
|
||||
__BITWARDEN_AUTOFILL_DEBUG__.setTracingDepth(2);
|
||||
```
|
||||
|
||||
#### `getTracingDepth()`
|
||||
Returns the current tracing depth.
|
||||
|
||||
```javascript
|
||||
const depth = __BITWARDEN_AUTOFILL_DEBUG__.getTracingDepth();
|
||||
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);
|
||||
```
|
||||
|
||||
## Enhanced Console Logging
|
||||
|
||||
When debug mode is enabled, the console automatically displays enhanced qualification messages:
|
||||
|
||||
### Field Qualified
|
||||
```
|
||||
✅ Field Qualified: opid_12345
|
||||
Field: <input type="text" ...>
|
||||
Passed conditions: ["notCurrentlyInSandboxedIframe", "isVisibleFormField", ...]
|
||||
All responses: [...]
|
||||
```
|
||||
|
||||
### Field Rejected
|
||||
```
|
||||
❌ Field Rejected: opid_12345
|
||||
Field: <input type="text" ...>
|
||||
Blocking condition failed: isNotDisabledField
|
||||
Message: field is disabled
|
||||
All responses: [...]
|
||||
```
|
||||
|
||||
## Understanding Debug Output
|
||||
|
||||
### JSON Export Structure
|
||||
```json
|
||||
{
|
||||
"sessionId": "session_1234567890_abc123",
|
||||
"startTime": 1234567890000,
|
||||
"endTime": 1234567891000,
|
||||
"url": "https://example.com/login",
|
||||
"qualifications": [
|
||||
{
|
||||
"fieldId": "opid_12345",
|
||||
"elementSelector": "input[type='text']#username",
|
||||
"attempts": [
|
||||
{
|
||||
"attemptId": "attempt_1234567890_xyz789",
|
||||
"timestamp": 1234567890500,
|
||||
"vector": "inline-menu",
|
||||
"result": {
|
||||
"result": true,
|
||||
"conditions": {
|
||||
"pass": [
|
||||
{
|
||||
"name": "isUsernameField",
|
||||
"functionSource": "function isUsernameField(field) { ... }"
|
||||
}
|
||||
],
|
||||
"fail": []
|
||||
},
|
||||
"meta": {
|
||||
"timestamp": 1234567890500,
|
||||
"vector": "inline-menu",
|
||||
"fieldSnapshot": {
|
||||
"opid": "opid_12345",
|
||||
"type": "text",
|
||||
"value": "[REDACTED]"
|
||||
},
|
||||
"tracingDepth": 0
|
||||
}
|
||||
},
|
||||
"triggeredBy": "page-load"
|
||||
}
|
||||
],
|
||||
"finalDecision": { ... }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Summary Export Structure
|
||||
```
|
||||
================================================================================
|
||||
Bitwarden Autofill Debug Summary
|
||||
================================================================================
|
||||
Session ID: session_1234567890_abc123
|
||||
URL: https://example.com/login
|
||||
Start Time: 2026-02-06T12:00:00.000Z
|
||||
End Time: 2026-02-06T12:00:01.000Z
|
||||
Duration: 1.00s
|
||||
|
||||
Total Fields Qualified: 2
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Field ID: opid_12345
|
||||
Selector: input[type='text']#username
|
||||
Attempts: 1
|
||||
Final Decision: ✅ QUALIFIED
|
||||
|
||||
Passed Conditions:
|
||||
✓ isUsernameField
|
||||
✓ notCurrentlyInSandboxedIframe
|
||||
|
||||
Vector: inline-menu
|
||||
Timestamp: 2026-02-06T12:00:00.500Z
|
||||
|
||||
================================================================================
|
||||
⚠️ WARNING: This debug data may contain sensitive information.
|
||||
Do not share this data publicly or with untrusted parties.
|
||||
================================================================================
|
||||
```
|
||||
|
||||
## Autofill Vectors
|
||||
|
||||
Debug data tracks which autofill vector triggered the qualification:
|
||||
|
||||
- **inline-menu**: Inline menu (autofill button) shown on field focus
|
||||
- **popup-autofill**: Autofill triggered from popup UI
|
||||
- **context-menu**: Autofill triggered from browser context menu
|
||||
- **keyboard-shortcut**: Autofill triggered by keyboard shortcut (Ctrl+Shift+L)
|
||||
- **page-load**: Autofill triggered automatically on page load
|
||||
|
||||
## 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)
|
||||
|
||||
## Performance Impact
|
||||
|
||||
- **Debug Disabled**: Zero performance overhead
|
||||
- **Debug Enabled (depth 0)**: ~1-2ms per field qualification
|
||||
- **Debug Enabled (depth 1)**: ~2-3ms per field qualification
|
||||
- **Debug Enabled (depth 2+)**: ~3-5ms per field qualification
|
||||
|
||||
## Data Retention
|
||||
|
||||
- Sessions are stored **in-memory only** (never persisted to disk)
|
||||
- Sessions automatically expire after **5 minutes**
|
||||
- Maximum **100 qualifications per session** to prevent memory issues
|
||||
- Field values are **always redacted** (`[REDACTED]`) to prevent PII leakage
|
||||
|
||||
## Security Considerations
|
||||
|
||||
⚠️ **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
|
||||
|
||||
## Troubleshooting Common Issues
|
||||
|
||||
### Debug API Not Available
|
||||
```javascript
|
||||
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 []
|
||||
```
|
||||
|
||||
**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);
|
||||
```
|
||||
|
||||
## Example Workflow
|
||||
|
||||
1. Enable debug mode and rebuild extension
|
||||
2. Navigate to the problematic login page
|
||||
3. Open browser DevTools console
|
||||
4. Focus the form field in question
|
||||
5. Check console for qualification messages
|
||||
6. Export detailed data:
|
||||
```javascript
|
||||
const summary = __BITWARDEN_AUTOFILL_DEBUG__.exportSummary();
|
||||
console.log(summary);
|
||||
```
|
||||
7. If needed, increase tracing depth for more detail:
|
||||
```javascript
|
||||
__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');
|
||||
// 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
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AutofillInlineMenuContentService } from "../overlay/inline-menu/content/autofill-inline-menu-content.service";
|
||||
import { AutofillDebugService } from "../services/autofill-debug.service";
|
||||
import { AutofillOverlayContentService } from "../services/autofill-overlay-content.service";
|
||||
import DomElementVisibilityService from "../services/dom-element-visibility.service";
|
||||
import { DomQueryService } from "../services/dom-query.service";
|
||||
@@ -16,12 +17,21 @@ import AutofillInit from "./autofill-init";
|
||||
|
||||
const domQueryService = new DomQueryService();
|
||||
const domElementVisibilityService = new DomElementVisibilityService(inlineMenuContentService);
|
||||
const inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
|
||||
|
||||
const debugService = new AutofillDebugService();
|
||||
const inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService(
|
||||
debugService.isDebugEnabled() ? debugService : undefined,
|
||||
);
|
||||
if (debugService.isDebugEnabled()) {
|
||||
inlineMenuFieldQualificationService.setDebugService(debugService);
|
||||
}
|
||||
|
||||
const autofillOverlayContentService = new AutofillOverlayContentService(
|
||||
domQueryService,
|
||||
domElementVisibilityService,
|
||||
inlineMenuFieldQualificationService,
|
||||
inlineMenuContentService,
|
||||
debugService.isDebugEnabled() ? debugService : undefined,
|
||||
);
|
||||
|
||||
windowContext.bitwardenAutofillInit = new AutofillInit(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { OverlayNotificationsContentService } from "../overlay/notifications/content/overlay-notifications-content.service";
|
||||
import { AutofillDebugService } from "../services/autofill-debug.service";
|
||||
import { AutofillOverlayContentService } from "../services/autofill-overlay-content.service";
|
||||
import DomElementVisibilityService from "../services/dom-element-visibility.service";
|
||||
import { DomQueryService } from "../services/dom-query.service";
|
||||
@@ -11,11 +12,21 @@ import AutofillInit from "./autofill-init";
|
||||
if (!windowContext.bitwardenAutofillInit) {
|
||||
const domQueryService = new DomQueryService();
|
||||
const domElementVisibilityService = new DomElementVisibilityService();
|
||||
const inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
|
||||
|
||||
const debugService = new AutofillDebugService();
|
||||
const inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService(
|
||||
debugService.isDebugEnabled() ? debugService : undefined,
|
||||
);
|
||||
if (debugService.isDebugEnabled()) {
|
||||
inlineMenuFieldQualificationService.setDebugService(debugService);
|
||||
}
|
||||
|
||||
const autofillOverlayContentService = new AutofillOverlayContentService(
|
||||
domQueryService,
|
||||
domElementVisibilityService,
|
||||
inlineMenuFieldQualificationService,
|
||||
undefined,
|
||||
debugService.isDebugEnabled() ? debugService : undefined,
|
||||
);
|
||||
|
||||
let overlayNotificationsContentService: undefined | OverlayNotificationsContentService;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
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";
|
||||
import { AutofillDebugService } from "../services/autofill-debug.service";
|
||||
import { AutofillOverlayContentService } from "../services/autofill-overlay-content.service";
|
||||
import DomElementVisibilityService from "../services/dom-element-visibility.service";
|
||||
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";
|
||||
|
||||
@@ -19,12 +22,21 @@ import AutofillInit from "./autofill-init";
|
||||
|
||||
const domQueryService = new DomQueryService();
|
||||
const domElementVisibilityService = new DomElementVisibilityService(inlineMenuContentService);
|
||||
const inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
|
||||
|
||||
const debugService = new AutofillDebugService();
|
||||
const inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService(
|
||||
debugService.isDebugEnabled() ? debugService : undefined,
|
||||
);
|
||||
if (debugService.isDebugEnabled()) {
|
||||
inlineMenuFieldQualificationService.setDebugService(debugService);
|
||||
}
|
||||
|
||||
const autofillOverlayContentService = new AutofillOverlayContentService(
|
||||
domQueryService,
|
||||
domElementVisibilityService,
|
||||
inlineMenuFieldQualificationService,
|
||||
inlineMenuContentService,
|
||||
debugService.isDebugEnabled() ? debugService : undefined,
|
||||
);
|
||||
|
||||
windowContext.bitwardenAutofillInit = new AutofillInit(
|
||||
@@ -37,5 +49,39 @@ import AutofillInit from "./autofill-init";
|
||||
setupAutofillInitDisconnectAction(windowContext);
|
||||
|
||||
windowContext.bitwardenAutofillInit.init();
|
||||
|
||||
if (devFlagEnabled("autofillDebugMode")) {
|
||||
(windowContext as any).__BITWARDEN_AUTOFILL_DEBUG__ = {
|
||||
exportSession: (format: DebugExportFormat = "json") => {
|
||||
return debugService.exportCurrentSession(format);
|
||||
},
|
||||
exportSummary: () => {
|
||||
return debugService.generateSummary(
|
||||
Array.from(debugService.sessionStore.keys())[0] || "",
|
||||
);
|
||||
},
|
||||
setTracingDepth: (depth: number) => {
|
||||
debugService.setTracingDepth(depth);
|
||||
console.log(`[Bitwarden Debug] Precondition tracing depth set to ${depth}`);
|
||||
},
|
||||
getTracingDepth: () => {
|
||||
return debugService.getTracingDepth();
|
||||
},
|
||||
getSessions: () => {
|
||||
return Array.from(debugService.sessionStore.keys());
|
||||
},
|
||||
};
|
||||
|
||||
console.log(
|
||||
"%c[Bitwarden Debug] Autofill debug mode enabled. Use window.__BITWARDEN_AUTOFILL_DEBUG__",
|
||||
"color: #175DDC; font-weight: bold; font-size: 12px",
|
||||
);
|
||||
console.log("Available methods:");
|
||||
console.log(" - exportSession(format?: 'json' | 'summary' | 'console')");
|
||||
console.log(" - exportSummary()");
|
||||
console.log(" - setTracingDepth(depth: number)");
|
||||
console.log(" - getTracingDepth()");
|
||||
console.log(" - getSessions()");
|
||||
}
|
||||
}
|
||||
})(window);
|
||||
|
||||
26
apps/browser/src/autofill/models/autofill-debug-data.ts
Normal file
26
apps/browser/src/autofill/models/autofill-debug-data.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { AutofillVector, QualificationResult } from "../services/abstractions/inline-menu-field-qualifications.service";
|
||||
|
||||
export type AutofillDebugSession = {
|
||||
sessionId: string;
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
url: string;
|
||||
qualifications: FieldQualificationRecord[];
|
||||
};
|
||||
|
||||
export type FieldQualificationRecord = {
|
||||
fieldId: string;
|
||||
elementSelector: string;
|
||||
attempts: QualificationAttempt[];
|
||||
finalDecision?: QualificationResult;
|
||||
};
|
||||
|
||||
export type QualificationAttempt = {
|
||||
attemptId: string;
|
||||
timestamp: number;
|
||||
vector: AutofillVector;
|
||||
result: QualificationResult;
|
||||
triggeredBy?: string;
|
||||
};
|
||||
|
||||
export type DebugExportFormat = "json" | "summary" | "console";
|
||||
@@ -11,7 +11,38 @@ export type AutofillKeywordsMap = WeakMap<
|
||||
|
||||
export type SubmitButtonKeywordsMap = WeakMap<HTMLElement, string>;
|
||||
|
||||
export type AutofillVector =
|
||||
| "inline-menu"
|
||||
| "popup-autofill"
|
||||
| "context-menu"
|
||||
| "keyboard-shortcut"
|
||||
| "page-load";
|
||||
|
||||
export type QualificationCondition = {
|
||||
name: string;
|
||||
functionSource?: string;
|
||||
};
|
||||
|
||||
export type QualificationMeta = {
|
||||
timestamp: number;
|
||||
vector: AutofillVector;
|
||||
fieldSnapshot: AutofillField;
|
||||
pageSnapshot?: Partial<AutofillPageDetails>;
|
||||
preconditions?: QualificationResult[];
|
||||
tracingDepth?: number;
|
||||
};
|
||||
|
||||
export type QualificationResult = {
|
||||
result: boolean;
|
||||
conditions: {
|
||||
pass: QualificationCondition[];
|
||||
fail: QualificationCondition[];
|
||||
};
|
||||
meta?: QualificationMeta;
|
||||
};
|
||||
|
||||
export interface InlineMenuFieldQualificationService {
|
||||
setCurrentVector(vector: AutofillVector): void;
|
||||
isUsernameField(field: AutofillField): boolean;
|
||||
isCurrentPasswordField(field: AutofillField): boolean;
|
||||
isUpdateCurrentPasswordField(field: AutofillField): boolean;
|
||||
|
||||
295
apps/browser/src/autofill/services/autofill-debug.service.ts
Normal file
295
apps/browser/src/autofill/services/autofill-debug.service.ts
Normal file
@@ -0,0 +1,295 @@
|
||||
import AutofillField from "../models/autofill-field";
|
||||
import AutofillPageDetails from "../models/autofill-page-details";
|
||||
import {
|
||||
AutofillDebugSession,
|
||||
DebugExportFormat,
|
||||
FieldQualificationRecord,
|
||||
QualificationAttempt,
|
||||
} from "../models/autofill-debug-data";
|
||||
import { devFlagEnabled } from "../../platform/flags";
|
||||
import {
|
||||
AutofillVector,
|
||||
QualificationResult,
|
||||
} from "./abstractions/inline-menu-field-qualifications.service";
|
||||
|
||||
export class AutofillDebugService {
|
||||
private tracingDepth = 1;
|
||||
private currentSession: AutofillDebugSession | null = null;
|
||||
readonly sessionStore: Map<string, AutofillDebugSession> = new Map();
|
||||
private readonly maxQualificationsPerSession = 100;
|
||||
private readonly sessionTimeoutMs = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
isDebugEnabled(): boolean {
|
||||
return devFlagEnabled("autofillDebugMode");
|
||||
}
|
||||
|
||||
hasCurrentSession(): boolean {
|
||||
return this.currentSession !== null;
|
||||
}
|
||||
|
||||
getTracingDepth(): number {
|
||||
return this.tracingDepth;
|
||||
}
|
||||
|
||||
setTracingDepth(depth: number): void {
|
||||
if (depth < 0) {
|
||||
console.warn("[Bitwarden Debug] Tracing depth must be >= 0. Setting to 0.");
|
||||
this.tracingDepth = 0;
|
||||
return;
|
||||
}
|
||||
this.tracingDepth = depth;
|
||||
}
|
||||
|
||||
startSession(url: string): string {
|
||||
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
||||
this.currentSession = {
|
||||
sessionId,
|
||||
startTime: Date.now(),
|
||||
url,
|
||||
qualifications: [],
|
||||
};
|
||||
this.sessionStore.set(sessionId, this.currentSession);
|
||||
|
||||
setTimeout(() => this.cleanupSession(sessionId), this.sessionTimeoutMs);
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
endSession(): void {
|
||||
if (this.currentSession) {
|
||||
this.currentSession.endTime = Date.now();
|
||||
this.currentSession = null;
|
||||
}
|
||||
}
|
||||
|
||||
recordQualification(
|
||||
fieldId: string,
|
||||
elementSelector: string,
|
||||
vector: AutofillVector,
|
||||
result: QualificationResult,
|
||||
triggeredBy?: string,
|
||||
): void {
|
||||
if (!this.currentSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.currentSession.qualifications.length >= this.maxQualificationsPerSession) {
|
||||
console.warn(
|
||||
`[Bitwarden Debug] Max qualifications (${this.maxQualificationsPerSession}) reached for session`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let fieldRecord = this.currentSession.qualifications.find((q) => q.fieldId === fieldId);
|
||||
|
||||
if (!fieldRecord) {
|
||||
fieldRecord = {
|
||||
fieldId,
|
||||
elementSelector,
|
||||
attempts: [],
|
||||
};
|
||||
this.currentSession.qualifications.push(fieldRecord);
|
||||
}
|
||||
|
||||
const attempt: QualificationAttempt = {
|
||||
attemptId: `attempt_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
||||
timestamp: Date.now(),
|
||||
vector,
|
||||
result,
|
||||
triggeredBy,
|
||||
};
|
||||
|
||||
fieldRecord.attempts.push(attempt);
|
||||
fieldRecord.finalDecision = result;
|
||||
}
|
||||
|
||||
exportCurrentSession(format: DebugExportFormat = "json"): string {
|
||||
if (!this.currentSession) {
|
||||
return format === "json" ? JSON.stringify({ error: "No active session" }) : "No active session";
|
||||
}
|
||||
|
||||
return this.exportSession(this.currentSession.sessionId, format);
|
||||
}
|
||||
|
||||
exportSession(sessionId: string, format: DebugExportFormat = "json"): string {
|
||||
const session = this.sessionStore.get(sessionId);
|
||||
|
||||
if (!session) {
|
||||
return format === "json" ? JSON.stringify({ error: "Session not found" }) : "Session not found";
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case "json":
|
||||
return JSON.stringify(session, null, 2);
|
||||
case "summary":
|
||||
return this.generateSummary(sessionId);
|
||||
case "console":
|
||||
this.generateConsoleOutput(sessionId);
|
||||
return "Output logged to console";
|
||||
default:
|
||||
return JSON.stringify(session, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
generateSummary(sessionId: string): string {
|
||||
const session = this.sessionStore.get(sessionId);
|
||||
|
||||
if (!session) {
|
||||
return "Session not found";
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
lines.push("=".repeat(80));
|
||||
lines.push("Bitwarden Autofill Debug Summary");
|
||||
lines.push("=".repeat(80));
|
||||
lines.push(`Session ID: ${session.sessionId}`);
|
||||
lines.push(`URL: ${session.url}`);
|
||||
lines.push(`Start Time: ${new Date(session.startTime).toISOString()}`);
|
||||
if (session.endTime) {
|
||||
lines.push(`End Time: ${new Date(session.endTime).toISOString()}`);
|
||||
lines.push(`Duration: ${((session.endTime - session.startTime) / 1000).toFixed(2)}s`);
|
||||
}
|
||||
lines.push("");
|
||||
lines.push(`Total Fields Qualified: ${session.qualifications.length}`);
|
||||
lines.push("");
|
||||
|
||||
for (const fieldRecord of session.qualifications) {
|
||||
lines.push("-".repeat(80));
|
||||
lines.push(`Field ID: ${fieldRecord.fieldId}`);
|
||||
lines.push(`Selector: ${fieldRecord.elementSelector}`);
|
||||
lines.push(`Attempts: ${fieldRecord.attempts.length}`);
|
||||
|
||||
if (fieldRecord.finalDecision) {
|
||||
lines.push(
|
||||
`Final Decision: ${fieldRecord.finalDecision.result ? "✅ QUALIFIED" : "❌ REJECTED"}`,
|
||||
);
|
||||
|
||||
if (fieldRecord.finalDecision.conditions.pass.length > 0) {
|
||||
lines.push("");
|
||||
lines.push("Passed Conditions:");
|
||||
for (const condition of fieldRecord.finalDecision.conditions.pass) {
|
||||
lines.push(` ✓ ${condition.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldRecord.finalDecision.conditions.fail.length > 0) {
|
||||
lines.push("");
|
||||
lines.push("Failed Conditions:");
|
||||
for (const condition of fieldRecord.finalDecision.conditions.fail) {
|
||||
lines.push(` ✗ ${condition.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
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("");
|
||||
}
|
||||
|
||||
lines.push("=".repeat(80));
|
||||
lines.push("⚠️ WARNING: This debug data may contain sensitive information.");
|
||||
lines.push("Do not share this data publicly or with untrusted parties.");
|
||||
lines.push("=".repeat(80));
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
generateConsoleOutput(sessionId: string): void {
|
||||
const session = this.sessionStore.get(sessionId);
|
||||
|
||||
if (!session) {
|
||||
console.warn("[Bitwarden Debug] Session not found");
|
||||
return;
|
||||
}
|
||||
|
||||
console.group(
|
||||
`%c[Bitwarden Debug] Session: ${session.sessionId}`,
|
||||
"color: #175DDC; font-weight: bold; font-size: 14px",
|
||||
);
|
||||
console.log(`URL: ${session.url}`);
|
||||
console.log(`Start Time: ${new Date(session.startTime).toISOString()}`);
|
||||
if (session.endTime) {
|
||||
console.log(`End Time: ${new Date(session.endTime).toISOString()}`);
|
||||
console.log(`Duration: ${((session.endTime - session.startTime) / 1000).toFixed(2)}s`);
|
||||
}
|
||||
console.log(`Total Fields: ${session.qualifications.length}`);
|
||||
|
||||
for (const fieldRecord of session.qualifications) {
|
||||
const icon = fieldRecord.finalDecision?.result ? "✅" : "❌";
|
||||
const status = fieldRecord.finalDecision?.result ? "QUALIFIED" : "REJECTED";
|
||||
|
||||
console.group(`${icon} Field: ${fieldRecord.fieldId} (${status})`);
|
||||
console.log(`Selector: ${fieldRecord.elementSelector}`);
|
||||
console.log(`Attempts: ${fieldRecord.attempts.length}`);
|
||||
|
||||
if (fieldRecord.finalDecision) {
|
||||
if (fieldRecord.finalDecision.conditions.pass.length > 0) {
|
||||
console.group("✓ Passed Conditions");
|
||||
for (const condition of fieldRecord.finalDecision.conditions.pass) {
|
||||
console.log(`${condition.name}`);
|
||||
if (condition.functionSource) {
|
||||
console.log(`Function:`, condition.functionSource);
|
||||
}
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
if (fieldRecord.finalDecision.conditions.fail.length > 0) {
|
||||
console.group("✗ Failed Conditions");
|
||||
for (const condition of fieldRecord.finalDecision.conditions.fail) {
|
||||
console.log(`${condition.name}`);
|
||||
if (condition.functionSource) {
|
||||
console.log(`Function:`, condition.functionSource);
|
||||
}
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
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(`Field Snapshot:`, fieldRecord.finalDecision.meta.fieldSnapshot);
|
||||
if (fieldRecord.finalDecision.meta.pageSnapshot) {
|
||||
console.log(`Page Snapshot:`, fieldRecord.finalDecision.meta.pageSnapshot);
|
||||
}
|
||||
if (fieldRecord.finalDecision.meta.preconditions) {
|
||||
console.log(`Preconditions:`, fieldRecord.finalDecision.meta.preconditions);
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
captureFieldSnapshot(field: AutofillField): AutofillField {
|
||||
// Return a shallow copy to avoid capturing field values
|
||||
return { ...field, value: "[REDACTED]" };
|
||||
}
|
||||
|
||||
capturePageSnapshot(pageDetails: AutofillPageDetails): Partial<AutofillPageDetails> {
|
||||
// Return only non-sensitive page information
|
||||
return {
|
||||
title: pageDetails.title,
|
||||
url: pageDetails.url,
|
||||
documentUrl: pageDetails.documentUrl,
|
||||
forms: pageDetails.forms,
|
||||
// Exclude fields array to avoid capturing field values
|
||||
};
|
||||
}
|
||||
|
||||
private cleanupSession(sessionId: string): void {
|
||||
if (this.currentSession?.sessionId === sessionId) {
|
||||
this.endSession();
|
||||
}
|
||||
this.sessionStore.delete(sessionId);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@ import { DomElementVisibilityService } from "./abstractions/dom-element-visibili
|
||||
import { DomQueryService } from "./abstractions/dom-query.service";
|
||||
import { InlineMenuFieldQualificationService } from "./abstractions/inline-menu-field-qualifications.service";
|
||||
import { AutoFillConstants } from "./autofill-constants";
|
||||
import { AutofillDebugService } from "./autofill-debug.service";
|
||||
|
||||
export type QualificationCriteria = {
|
||||
formFieldElement: ElementWithOpId<FormFieldElement>;
|
||||
@@ -186,6 +187,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
private domElementVisibilityService: DomElementVisibilityService,
|
||||
private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService,
|
||||
private inlineMenuContentService?: AutofillInlineMenuContentService,
|
||||
private debugService?: AutofillDebugService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -225,11 +227,17 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
autofillFieldData: AutofillField,
|
||||
pageDetails: AutofillPageDetails,
|
||||
) {
|
||||
if (this.debugService?.isDebugEnabled() && !this.debugService.hasCurrentSession()) {
|
||||
this.debugService.startSession(globalThis.location.href);
|
||||
this.inlineMenuFieldQualificationService.setCurrentVector("inline-menu");
|
||||
}
|
||||
|
||||
const qualification = this.isQualifiedField({
|
||||
formFieldElement,
|
||||
autofillFieldData,
|
||||
pageDetails,
|
||||
});
|
||||
|
||||
if (!qualification) {
|
||||
return;
|
||||
}
|
||||
@@ -314,19 +322,51 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
}
|
||||
|
||||
isQualifiedField(criteria: QualificationCriteria) {
|
||||
const debugEnabled = this.debugService?.isDebugEnabled() ?? false;
|
||||
const responses: QualificationResponse[] = [];
|
||||
|
||||
for (const definition of this.qualifiers) {
|
||||
const response = this.qualify(definition, criteria);
|
||||
responses.push(response);
|
||||
|
||||
if (response.result === false && definition?.blocking !== false) {
|
||||
console.log({ element: criteria.formFieldElement, responses });
|
||||
if (debugEnabled) {
|
||||
console.group(
|
||||
`%c❌ Field Rejected: ${criteria.autofillFieldData.opid}`,
|
||||
"color: #ef4444; font-weight: bold",
|
||||
);
|
||||
console.log("Field:", criteria.formFieldElement);
|
||||
console.log("Blocking condition failed:", response.alias);
|
||||
console.log("Message:", response.message);
|
||||
console.log("All responses:", responses);
|
||||
console.groupEnd();
|
||||
} else {
|
||||
console.log({ element: criteria.formFieldElement, responses });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.result === true && definition.effect) {
|
||||
void definition.effect(criteria);
|
||||
}
|
||||
}
|
||||
console.log({ element: criteria.formFieldElement, responses });
|
||||
|
||||
if (debugEnabled) {
|
||||
console.group(
|
||||
`%c✅ Field Qualified: ${criteria.autofillFieldData.opid}`,
|
||||
"color: #10b981; font-weight: bold",
|
||||
);
|
||||
console.log("Field:", criteria.formFieldElement);
|
||||
console.log(
|
||||
"Passed conditions:",
|
||||
responses.filter((r) => r.result).map((r) => r.alias),
|
||||
);
|
||||
console.log("All responses:", responses);
|
||||
console.groupEnd();
|
||||
} else {
|
||||
console.log({ element: criteria.formFieldElement, responses });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ import { getSubmitButtonKeywordsSet, sendExtensionMessage } from "../utils";
|
||||
|
||||
import {
|
||||
AutofillKeywordsMap,
|
||||
AutofillVector,
|
||||
InlineMenuFieldQualificationService as InlineMenuFieldQualificationServiceInterface,
|
||||
QualificationResult,
|
||||
SubmitButtonKeywordsMap,
|
||||
} from "./abstractions/inline-menu-field-qualifications.service";
|
||||
import {
|
||||
@@ -15,6 +17,7 @@ import {
|
||||
SubmitLoginButtonNames,
|
||||
} from "./autofill-constants";
|
||||
import AutofillService from "./autofill.service";
|
||||
import { AutofillDebugService } from "./autofill-debug.service";
|
||||
|
||||
export class InlineMenuFieldQualificationService implements InlineMenuFieldQualificationServiceInterface {
|
||||
private searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames);
|
||||
@@ -149,6 +152,66 @@ export class InlineMenuFieldQualificationService implements InlineMenuFieldQuali
|
||||
]);
|
||||
private totpFieldAutocompleteValue = "one-time-code";
|
||||
private premiumEnabled = false;
|
||||
private currentVector: AutofillVector = "inline-menu";
|
||||
private debugService?: AutofillDebugService;
|
||||
|
||||
private get debugEnabled(): boolean {
|
||||
return this.debugService?.isDebugEnabled() ?? false;
|
||||
}
|
||||
|
||||
setCurrentVector(vector: AutofillVector): void {
|
||||
this.currentVector = vector;
|
||||
}
|
||||
|
||||
setDebugService(debugService: AutofillDebugService): void {
|
||||
this.debugService = debugService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a qualification function to return QualificationResult with debug metadata.
|
||||
*
|
||||
* @param name - Human-readable name for the condition
|
||||
* @param fn - The qualification function to wrap
|
||||
* @param field - The field being qualified
|
||||
* @param pageDetails - Optional page details
|
||||
* @param currentDepth - Current depth for precondition tracing
|
||||
*/
|
||||
private wrapQualifier(
|
||||
name: string,
|
||||
fn: (field: AutofillField, pageDetails?: AutofillPageDetails) => boolean,
|
||||
field: AutofillField,
|
||||
pageDetails?: AutofillPageDetails,
|
||||
currentDepth = 0,
|
||||
): QualificationResult {
|
||||
const result = fn.call(this, field, pageDetails);
|
||||
|
||||
if (!this.debugEnabled) {
|
||||
return { result, conditions: { pass: [], fail: [] } };
|
||||
}
|
||||
|
||||
const maxDepth = this.debugService?.getTracingDepth() ?? 1;
|
||||
const shouldTrace = currentDepth < maxDepth;
|
||||
|
||||
const condition = {
|
||||
name,
|
||||
functionSource: shouldTrace ? fn.toString() : undefined,
|
||||
};
|
||||
|
||||
return {
|
||||
result,
|
||||
conditions: {
|
||||
pass: result ? [condition] : [],
|
||||
fail: result ? [] : [condition],
|
||||
},
|
||||
meta: {
|
||||
timestamp: Date.now(),
|
||||
vector: this.currentVector,
|
||||
fieldSnapshot: this.debugService?.captureFieldSnapshot(field) ?? field,
|
||||
pageSnapshot: pageDetails ? this.debugService?.capturePageSnapshot(pageDetails) : undefined,
|
||||
tracingDepth: currentDepth,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided field to indicate if the field is a new email field used for account creation/registration.
|
||||
@@ -207,7 +270,8 @@ export class InlineMenuFieldQualificationService implements InlineMenuFieldQuali
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
constructor(debugService?: AutofillDebugService) {
|
||||
this.debugService = debugService;
|
||||
void sendExtensionMessage("getUserPremiumStatus").then((premiumStatus) => {
|
||||
this.premiumEnabled = !!premiumStatus?.result;
|
||||
});
|
||||
@@ -938,6 +1002,16 @@ export class InlineMenuFieldQualificationService implements InlineMenuFieldQuali
|
||||
* @param field - The field to validate
|
||||
*/
|
||||
isUsernameField = (field: AutofillField): boolean => {
|
||||
return this.isUsernameFieldWithResult(field).result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the provided field as a username field with debug metadata.
|
||||
*
|
||||
* @param field - The field to validate
|
||||
* @param depth - Current depth for precondition tracing
|
||||
*/
|
||||
isUsernameFieldWithResult(field: AutofillField, depth = 0): QualificationResult {
|
||||
const fieldType = field.type;
|
||||
if (
|
||||
!fieldType ||
|
||||
@@ -946,11 +1020,12 @@ export class InlineMenuFieldQualificationService implements InlineMenuFieldQuali
|
||||
this.fieldHasDisqualifyingAttributeValue(field) ||
|
||||
this.isTotpField(field)
|
||||
) {
|
||||
return false;
|
||||
return this.wrapQualifier("isUsernameField", () => false, field, undefined, depth);
|
||||
}
|
||||
|
||||
return this.keywordsFoundInFieldData(field, AutoFillConstants.UsernameFieldNames);
|
||||
};
|
||||
const result = this.keywordsFoundInFieldData(field, AutoFillConstants.UsernameFieldNames);
|
||||
return this.wrapQualifier("isUsernameField", () => result, field, undefined, depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided field as an email field.
|
||||
@@ -974,15 +1049,44 @@ export class InlineMenuFieldQualificationService implements InlineMenuFieldQuali
|
||||
* @param field - The field to validate
|
||||
*/
|
||||
isCurrentPasswordField = (field: AutofillField): boolean => {
|
||||
return this.isCurrentPasswordFieldWithResult(field).result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the provided field as a current password field with debug metadata.
|
||||
*
|
||||
* @param field - The field to validate
|
||||
* @param depth - Current depth for precondition tracing
|
||||
*/
|
||||
isCurrentPasswordFieldWithResult(field: AutofillField, depth = 0): QualificationResult {
|
||||
const maxDepth = this.debugService?.getTracingDepth() ?? 1;
|
||||
|
||||
if (
|
||||
this.fieldContainsAutocompleteValues(field, this.newPasswordAutoCompleteValue) ||
|
||||
this.keywordsFoundInFieldData(field, this.accountCreationFieldKeywords)
|
||||
) {
|
||||
return false;
|
||||
return this.wrapQualifier("isCurrentPasswordField", () => false, field, undefined, depth);
|
||||
}
|
||||
|
||||
return this.isPasswordField(field);
|
||||
};
|
||||
const passwordCheck =
|
||||
depth < maxDepth
|
||||
? this.isPasswordFieldWithResult(field, depth + 1)
|
||||
: this.wrapQualifier("isPasswordField", this.isPasswordField, field, undefined, depth);
|
||||
|
||||
if (!passwordCheck.result) {
|
||||
return {
|
||||
result: false,
|
||||
conditions: passwordCheck.conditions,
|
||||
meta: {
|
||||
...passwordCheck.meta,
|
||||
preconditions: [passwordCheck],
|
||||
tracingDepth: depth,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return this.wrapQualifier("isCurrentPasswordField", () => true, field, undefined, depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided field as a current password field for an update password form.
|
||||
@@ -1006,15 +1110,46 @@ export class InlineMenuFieldQualificationService implements InlineMenuFieldQuali
|
||||
* @param field - The field to validate
|
||||
*/
|
||||
isNewPasswordField = (field: AutofillField): boolean => {
|
||||
return this.isNewPasswordFieldWithResult(field).result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the provided field as a new password field with debug metadata.
|
||||
*
|
||||
* @param field - The field to validate
|
||||
* @param depth - Current depth for precondition tracing
|
||||
*/
|
||||
isNewPasswordFieldWithResult(field: AutofillField, depth = 0): QualificationResult {
|
||||
const maxDepth = this.debugService?.getTracingDepth() ?? 1;
|
||||
|
||||
if (this.fieldContainsAutocompleteValues(field, this.currentPasswordAutocompleteValue)) {
|
||||
return false;
|
||||
return this.wrapQualifier("isNewPasswordField", () => false, field, undefined, depth);
|
||||
}
|
||||
|
||||
return (
|
||||
this.isPasswordField(field) &&
|
||||
this.keywordsFoundInFieldData(field, this.accountCreationFieldKeywords)
|
||||
);
|
||||
};
|
||||
const passwordCheck =
|
||||
depth < maxDepth
|
||||
? this.isPasswordFieldWithResult(field, depth + 1)
|
||||
: this.wrapQualifier("isPasswordField", this.isPasswordField, field, undefined, depth);
|
||||
|
||||
if (!passwordCheck.result) {
|
||||
return {
|
||||
result: false,
|
||||
conditions: passwordCheck.conditions,
|
||||
meta: {
|
||||
...passwordCheck.meta,
|
||||
preconditions: [passwordCheck],
|
||||
tracingDepth: depth,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const keywordCheck = this.keywordsFoundInFieldData(field, this.accountCreationFieldKeywords);
|
||||
if (!keywordCheck) {
|
||||
return this.wrapQualifier("isNewPasswordField", () => false, field, undefined, depth);
|
||||
}
|
||||
|
||||
return this.wrapQualifier("isNewPasswordField", () => true, field, undefined, depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided field as a password field.
|
||||
@@ -1022,6 +1157,16 @@ export class InlineMenuFieldQualificationService implements InlineMenuFieldQuali
|
||||
* @param field - The field to validate
|
||||
*/
|
||||
private isPasswordField = (field: AutofillField): boolean => {
|
||||
return this.isPasswordFieldWithResult(field).result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the provided field as a password field with debug metadata.
|
||||
*
|
||||
* @param field - The field to validate
|
||||
* @param depth - Current depth for precondition tracing
|
||||
*/
|
||||
private isPasswordFieldWithResult(field: AutofillField, depth = 0): QualificationResult {
|
||||
const isInputPasswordType = field.type === "password";
|
||||
if (
|
||||
(!isInputPasswordType &&
|
||||
@@ -1029,11 +1174,12 @@ export class InlineMenuFieldQualificationService implements InlineMenuFieldQuali
|
||||
this.fieldHasDisqualifyingAttributeValue(field) ||
|
||||
this.isTotpField(field)
|
||||
) {
|
||||
return false;
|
||||
return this.wrapQualifier("isPasswordField", () => false, field, undefined, depth);
|
||||
}
|
||||
|
||||
return isInputPasswordType || this.isLikePasswordField(field);
|
||||
};
|
||||
const result = isInputPasswordType || this.isLikePasswordField(field);
|
||||
return this.wrapQualifier("isPasswordField", () => result, field, undefined, depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided field as a field to indicate if the
|
||||
|
||||
@@ -14,6 +14,7 @@ export type Flags = SharedFlags;
|
||||
// required to avoid linting errors when there are no flags
|
||||
export type DevFlags = {
|
||||
managedEnvironment?: GroupPolicyEnvironment;
|
||||
autofillDebugMode?: boolean;
|
||||
} & SharedDevFlags;
|
||||
|
||||
export function flagEnabled(flag: keyof Flags): boolean {
|
||||
|
||||
Reference in New Issue
Block a user