Skip to content

Conversation

@harr1424
Copy link
Contributor

@harr1424 harr1424 commented Oct 18, 2025

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-22836?atlOrigin=eyJpIjoiNTUwZTI2NmZiMzRhNDIzNTlmMTAzMGJkZWZjZDhjOGMiLCJwIjoiaiJ9

📔 Objective

• Determine reason why extension crashes and fix this

Fix:

• Firefox has a known issue that is relevant here
• To avoid the crash it is necessary to only open modal dialogs (file selectors) from within a popout window
• The extension UI involving file type Sends already implemented this logic (mostly)
• File type Send logic was extended to the Import UI
• Both Send and Import UI logic were extended such that a user can't close the popout and still open a file selector in the original extension panel (file selector will NEVER show outside of popout)

📸 Screenshots

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

• added popout dialog for file imports inspired by file type send popout dialog
• TODO: fix bug whereby a user may dismiss popout dialog and attempt to select a file outside of popout in Send and Import
@harr1424 harr1424 changed the title initial attempt at fix PM-22836 Import popout crashes unexpectedly Oct 18, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 18, 2025

Logo
Checkmarx One – Scan Summary & Detailsa779f8a2-11c6-4df7-a1c1-9e98d7c11c71

Great job! No new security vulnerabilities introduced in this pull request

@codecov
Copy link

codecov bot commented Oct 18, 2025

Codecov Report

❌ Patch coverage is 2.32558% with 42 lines in your changes missing coverage. Please review.
✅ Project coverage is 40.68%. Comparing base (338ea95) to head (7b53db2).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...t/import-file-popout-dialog-container.component.ts 0.00% 13 Missing ⚠️
...ings/import/import-file-popout-dialog.component.ts 0.00% 12 Missing ⚠️
...tform/storage/foreground-memory-storage.service.ts 9.09% 10 Missing ⚠️
...popout-dialog/send-file-popout-dialog.component.ts 0.00% 4 Missing ⚠️
apps/browser/src/popup/main.ts 0.00% 2 Missing ⚠️
...pup/settings/import/import-browser-v2.component.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #16928      +/-   ##
==========================================
- Coverage   40.70%   40.68%   -0.03%     
==========================================
  Files        3541     3543       +2     
  Lines      101383   101419      +36     
  Branches    15181    15183       +2     
==========================================
- Hits        41271    41264       -7     
- Misses      58369    58412      +43     
  Partials     1743     1743              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sonarqubecloud
Copy link

@harr1424 harr1424 marked this pull request as ready for review October 21, 2025 15:24
@harr1424 harr1424 requested a review from a team as a code owner October 21, 2025 15:24
@harr1424 harr1424 added the needs-qa Marks a PR as requiring QA approval label Oct 21, 2025
@djsmith85 djsmith85 self-requested a review October 22, 2025 12:21
@harr1424 harr1424 requested a review from itsadrago November 5, 2025 13:44
>
</tools-import>

<import-file-popout-dialog-container></import-file-popout-dialog-container>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Why is this a separate component rather than just living inside the import-browser component? It seems quite weird to have a child component popup a dialog that redirects the parent page.

component: SendAddEditV2Component,
canActivate: [authGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
data: { elevation: 1, doNotSaveUrl: true } satisfies RouteDataProperties,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Why are we not saving the url here?

Copy link
Contributor Author

@harr1424 harr1424 Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying from Claude's comment (below) which correctly notes this logic wasn't applied to the import path:

Without this flag, the import page URL is saved in popup history
If a user navigates away and the popup restores this URL, they could still access the file selector outside the popout
This defeats the purpose of the Firefox crash fix

…en-after-closing-extension-pop-up-and-selecting-choose-file
@claude
Copy link

claude bot commented Nov 7, 2025

Claude finished @harr1424's task —— View job


Code Review Complete

Good work addressing the doNotSaveUrl: true routing issue (apps/browser/src/popup/app-routing.module.ts:249). The dialog cleanup in ngOnDestroy properly handles component lifecycle.

However, several issues remain from previous reviews and new concerns were identified:

Findings

Finding 1: ❌ Wrong i18n key creates confusing dialog title

apps/browser/src/tools/popup/settings/import/import-file-popout-dialog.component.html:7 uses sendFilePopoutDialogText which displays "Pop out extension?" (Send-specific), while line 9 correctly uses importPopoutDialogDesc (Import-specific). This creates inconsistent messaging.

Required: Add dedicated import title key to messages.json and use it:

"importPopoutDialogTitle": {
  "message": "Pop out to import"
}

Finding 2: ❌ Zero test coverage for new components

Codecov reports 0% coverage for all new files. No unit tests exist for:

  • ImportFilePopoutDialogContainerComponent
  • ImportFilePopoutDialogComponent

At minimum, test that the dialog opens conditionally based on filePopoutUtilsService.showFilePopoutMessage() return value.

Finding 3: ⚠️ Components should be standalone

apps/browser/src/tools/popup/settings/import/import-file-popout-dialog-container.component.ts:11 and import-file-popout-dialog.component.ts:10 are not standalone components. Per project modernization efforts, new Angular components should include standalone: true.

Finding 4: ⚠️ Use inject() instead of constructor injection

Multiple files (import-file-popout-dialog-container.component.ts:20-23, import-file-popout-dialog.component.ts:17-20, send-file-popout-dialog.component.ts:18-21) use constructor injection. Per Angular modernization patterns, use inject():

export class ImportFilePopoutDialogComponent {
  private dialogService = inject(DialogService);
  private popupRouterCacheService = inject(PopupRouterCacheService);
  // ... methods
}

Finding 5: ⚠️ Missing return type annotations

Methods in import-file-popout-dialog.component.ts:22 and :26, send-file-popout-dialog.component.ts:23 and :27 lack explicit return types. Add : Promise<void>.

Finding 6: ⚠️ Unnecessary closing tag

apps/browser/src/tools/popup/settings/import/import-browser-v2.component.html:15 has </import-file-popout-dialog-container> but self-closing components don't need closing tags in Angular.

Finding 7: ♻️ Inconsistent navigation patterns create technical debt

Import dialog uses popupRouterCacheService.back() (import-file-popout-dialog.component.ts:29) while Send dialog uses router.navigate(["/tabs/send"]) (send-file-popout-dialog.component.ts:30). Both solve the same problem with different strategies, creating maintenance burden.

Consider standardizing on one approach or extracting shared navigation logic to FilePopoutUtilsService.

Finding 8: 💭 Component architecture rationale unclear

Why is ImportFilePopoutDialogContainerComponent a separate component with empty template rather than logic in ImportBrowserV2Component.ngOnInit()? The container has no visual output but opens a dialog that navigates the parent page. This creates unclear component boundaries.

Alternative: Move dialog-opening logic directly to parent component or implement as a route guard since it checks conditions on route entry.

Finding 9: 💭 Unrelated bootstrap configuration changes

apps/browser/src/popup/main.ts:23-31 adds explicit bootstrap configuration and error handling. While not harmful, this appears unrelated to the Firefox file selector crash fix. Was this intentional or debugging code that should be removed?


Summary: Core fix (doNotSaveUrl + popout dialog) is sound, but code quality issues and missing tests need attention before merge. Most critical: Fix the i18n key and add basic test coverage.

async close() {
this.dialogService.closeAll();
// the current view exposes a file selector, ensure the view is popped to avoid using it outside of a popout
await this.popupRouterCacheService.back();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Finding 1: Navigation logic may fail silently if back() has no history

The popupRouterCacheService.back() method will navigate to the root vault page if history is empty. This could be confusing UX if the user arrived at the import page through a direct link or bookmark rather than navigation.

Consider:

  1. Explicitly navigating to a known safe route (e.g., /vault-settings) instead of relying on back()
  2. Or add error handling if back() returns false or fails
Context from PopupRouterCacheService
async back() {
  // ...
  if (this.hasNavigated && history.length) {
    this.location.back();
    return;
  }
  // if no history is present, fallback to vault page
  await this.router.navigate([""]);
}

This fallback behavior may not match user expectations when canceling an import.


ngOnInit() {
if (this.filePopoutUtilsService.showFilePopoutMessage(window)) {
this.dialogService.open(ImportFilePopoutDialogComponent);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finding 2: Dialog opened in ngOnInit without cleanup handling

Opening a dialog in ngOnInit without storing a reference prevents proper cleanup and can cause issues:

  1. Memory leak: If component is destroyed while dialog is open, no cleanup occurs
  2. Multiple dialogs: If component is re-initialized, multiple dialogs could open
  3. No unsubscribe: Dialog subscription isn't tracked for cleanup
Recommended fix
import { Component, OnDestroy, OnInit } from "@angular/core";
import { DialogRef } from "@bitwarden/components";

export class ImportFilePopoutDialogContainerComponent implements OnInit, OnDestroy {
  private dialogRef: DialogRef | null = null;

  ngOnInit() {
    if (this.filePopoutUtilsService.showFilePopoutMessage(window)) {
      this.dialogRef = this.dialogService.open(ImportFilePopoutDialogComponent);
    }
  }

  ngOnDestroy() {
    this.dialogRef?.close();
  }
}

async close() {
this.dialogService.closeAll();
// the current view exposes a file selector, ensure the view is popped to avoid using it outside of a popout
await this.router.navigate(["/tabs/send"]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Finding 3: Inconsistent navigation patterns create technical debt

The Send dialog uses Router.navigate() to an absolute path while the Import dialog uses popupRouterCacheService.back(). This inconsistency creates confusion and maintenance burden.

Issue: Both components solve the same problem (prevent file selector outside popout) but use different navigation strategies.

Recommendation: Standardize on one approach:

  • Option A: Both use explicit navigation to a known route
  • Option B: Both use the router cache service (requires refactoring Send)
  • Option C: Extract shared navigation logic to FilePopoutUtilsService

>
</tools-import>

<import-file-popout-dialog-container></import-file-popout-dialog-container>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Finding 4: Component placement and responsibilities need clarification

The <import-file-popout-dialog-container> is embedded in the template but has no visual representation (empty template). This creates unclear component boundaries.

Questions for consideration:

  1. Why is this a separate component rather than logic in the parent ImportBrowserV2Component?
  2. The container component opens a dialog that navigates the parent - this violates typical component encapsulation
  3. Should this logic live in a route guard instead, since it's checking conditions on route entry?

Alternative approaches:

  • Move the dialog-opening logic to ImportBrowserV2Component.ngOnInit()
  • Or use a route guard that opens the dialog before route activation
  • Or keep as child component but have it emit events rather than directly navigating

This addresses the reviewer's question about architecture.

component: SendAddEditV2Component,
canActivate: [authGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
data: { elevation: 1, doNotSaveUrl: true } satisfies RouteDataProperties,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Finding 5: Missing route configuration for import path

The add-send route has doNotSaveUrl: true to prevent saving the URL when the popout dialog might be shown. However, there's no corresponding route configuration change for the import page.

Questions:

  1. What is the route path for ImportBrowserV2Component? (Not visible in the diff)
  2. Does that route also need doNotSaveUrl: true for consistency?
  3. Without doNotSaveUrl, users who close the popout dialog will have the import page saved in history, which could lead to the file selector issue recurring

This addresses the reviewer's question about why we're not saving the URL here.

</div>
<ng-container bitDialogContent>
<div bitTypography="h3">
{{ "sendFilePopoutDialogText" | i18n }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Finding 6: Incorrect i18n key reuse creates confusing UX

Line 7 uses "sendFilePopoutDialogText" (Send-related) while line 9 correctly uses "importPopoutDialogDesc" (Import-related). This creates a confusing message that says "Send file" in the title but talks about importing in the description.

Expected behavior: The title should use an import-specific i18n key.

Fix needed:

<div bitTypography="h3">
  {{ "importPopoutDialogTitle" | i18n }}
</div>

Then add to messages.json:

"importPopoutDialogTitle": {
  "message": "Pop out required"
}

"message": "This setting is disabled by your organization's policy.",
"description": "This hint text is displayed when a user setting is disabled due to an organization policy."
},
"importPopoutDialogDesc": {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 Finding 7: Missing i18n key for dialog title

Only the description key was added, but the template reuses the Send dialog's title (sendFilePopoutDialogText). This should have a dedicated import title key.

Suggested addition:

"importPopoutDialogTitle": {
  "message": "Pop out required"
},
"importPopoutDialogDesc": {
  "message": "To import from a file, you need to pop out the extension to a new window."
}

component: SendAddEditV2Component,
canActivate: [authGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
data: { elevation: 1, doNotSaveUrl: true } satisfies RouteDataProperties,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finding 8: Import route missing doNotSaveUrl: true flag

The import route (line 246-249) should also have data: { elevation: 1, doNotSaveUrl: true } for consistency with the Send route change.

Why this matters:

  • Without this flag, the import page URL is saved in popup history
  • If a user navigates away and the popup restores this URL, they could still access the file selector outside the popout
  • This defeats the purpose of the Firefox crash fix

Required change:

{
  path: "import",
  component: ImportBrowserV2Component,
  canActivate: [authGuard],
  data: { elevation: 1, doNotSaveUrl: true } satisfies RouteDataProperties,
}

</div>
<ng-container bitDialogContent>
<div bitTypography="h3">
{{ "sendFilePopoutDialogText" | i18n }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finding 1: Incorrect i18n key creates confusing dialog title

This uses sendFilePopoutDialogText which displays "Pop out extension?" with a description about Send. However, line 9 correctly uses importPopoutDialogDesc for the description, creating inconsistent messaging.

Required fix:

Suggested change
{{ "sendFilePopoutDialogText" | i18n }}
{{ "importPopoutDialogTitle" | i18n }}

Then add to messages.json:

"importPopoutDialogTitle": {
  "message": "Pop out to import"
}

>
</tools-import>

<import-file-popout-dialog-container></import-file-popout-dialog-container>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Finding 2: Component architecture violates encapsulation principles

This container component has no visual output (empty template) but opens a dialog that navigates the parent page, creating unclear boundaries.

Questions:

  1. Why is this a separate component rather than logic in ImportBrowserV2Component.ngOnInit()?
  2. Should this be a route guard instead since it checks conditions on route entry?

Alternative: Move dialog logic directly to parent component or implement as canActivate guard.

async close() {
this.dialogService.closeAll();
// the current view exposes a file selector, ensure the view is popped to avoid using it outside of a popout
await this.popupRouterCacheService.back();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Finding 3: Inconsistent navigation patterns create technical debt

Import uses popupRouterCacheService.back() while Send uses Router.navigate(["/tabs/send"]) (send-file-popout-dialog.component.ts:30). Both solve the same problem with different approaches.

Recommendation: Standardize navigation strategy across both features or extract shared logic to FilePopoutUtilsService.

private dialogService: DialogService,
private router: Router,
) {}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use inject instead of constructor

}

close() {
async close() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return type

>
</tools-import>

<import-file-popout-dialog-container></import-file-popout-dialog-container>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont need closing tag

selector: "import-file-popout-dialog",
templateUrl: "./import-file-popout-dialog.component.html",
imports: [JslibModule, CommonModule, DialogModule, ButtonModule, TypographyModule],
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

standalone component

@djsmith85 djsmith85 removed their request for review November 10, 2025 09:58
@harr1424 harr1424 marked this pull request as draft November 11, 2025 14:09
@harr1424 harr1424 removed the needs-qa Marks a PR as requiring QA approval label Nov 11, 2025
@harr1424 harr1424 marked this pull request as ready for review November 12, 2025 00:47
@harr1424 harr1424 requested review from a team as code owners November 12, 2025 00:47
…en-after-closing-extension-pop-up-and-selecting-choose-file
</div>
<ng-container bitDialogContent>
<div bitTypography="h3">
{{ "sendFilePopoutDialogText" | i18n }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finding 1: Wrong i18n key creates confusing dialog title

This uses sendFilePopoutDialogText which displays "Pop out extension?" (a Send-specific message), but line 9 correctly uses importPopoutDialogDesc for the description. This creates inconsistent messaging.

Required fix:

Suggested change
{{ "sendFilePopoutDialogText" | i18n }}
{{ "importPopoutDialogTitle" | i18n }}

Then add to messages.json:

"importPopoutDialogTitle": {
  "message": "Pop out to import"
}


import { ImportFilePopoutDialogComponent } from "./import-file-popout-dialog.component";

@Component({
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Finding 3: Component should be standalone

New Angular components should include standalone: true per project modernization efforts.

Suggested change
@Component({
@Component({
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,

export class ImportFilePopoutDialogContainerComponent implements OnInit, OnDestroy {
private dialogRef: DialogRef | null = null;

constructor(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Finding 4: Use inject() instead of constructor injection

Per Angular modernization patterns, prefer inject():

Suggested change
constructor(
private dialogService = inject(DialogService);
private filePopoutUtilsService = inject(FilePopoutUtilsService);

import BrowserPopupUtils from "../../../../platform/browser/browser-popup-utils";
import { PopupRouterCacheService } from "../../../../platform/popup/view-cache/popup-router-cache.service";

@Component({
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Finding 3: Component should be standalone

Suggested change
@Component({
@Component({
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,

imports: [JslibModule, CommonModule, DialogModule, ButtonModule, TypographyModule],
})
export class ImportFilePopoutDialogComponent {
constructor(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Finding 4: Use inject() instead of constructor injection

Suggested change
constructor(
private dialogService = inject(DialogService);
private popupRouterCacheService = inject(PopupRouterCacheService);

private popupRouterCacheService: PopupRouterCacheService,
) {}

async popOutWindow() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Finding 5: Missing return type annotation

Suggested change
async popOutWindow() {
async popOutWindow(): Promise<void> {

await BrowserPopupUtils.openCurrentPagePopout(window);
}

async close() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Finding 5: Missing return type annotation

Suggested change
async close() {
async close(): Promise<void> {

>
</tools-import>

<import-file-popout-dialog-container></import-file-popout-dialog-container>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Finding 6: Unnecessary closing tag

Self-closing components don't need closing tags in Angular.

Suggested change
<import-file-popout-dialog-container></import-file-popout-dialog-container>
<import-file-popout-dialog-container />

})
export class SendFilePopoutDialogComponent {
constructor(private dialogService: DialogService) {}
constructor(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Finding 4: Use inject() instead of constructor injection

Suggested change
constructor(
private dialogService = inject(DialogService);
private router = inject(Router);

private router: Router,
) {}

async popOutWindow() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Finding 5: Missing return type annotation

Suggested change
async popOutWindow() {
async popOutWindow(): Promise<void> {

}

close() {
async close() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Finding 5: Missing return type annotation

Suggested change
async close() {
async close(): Promise<void> {


function init() {
void platformBrowserDynamic().bootstrapModule(AppModule);
platformBrowserDynamic()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Finding 9: Unrelated bootstrap configuration changes

This explicit bootstrap configuration and error handling appears unrelated to the Firefox file selector crash fix. Was this intentional or debugging code that should be removed?

@harr1424 harr1424 marked this pull request as draft November 12, 2025 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants