Skip to content

Conversation

@jholland-livefront
Copy link
Collaborator

@jholland-livefront jholland-livefront commented Nov 10, 2025

🎟️ Tracking

PM-27294

📔 Objective

There is an issue where the pop out to create a new login contains stale data when the user clicks out of the modal, updates the login form on the website, and tries to save that data using the Save to Bitwarden option. These changes add a new listener that will reload the cipher data and send it to an already open pop up. If there is no pop up then it opens with the correct cipher data as usual.

📸 Screenshots

Screen.Recording.2025-11-10.at.2.23.46.PM.mov

⏰ 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

@claude
Copy link

claude bot commented Nov 10, 2025

Claude finished @jholland-livefront's task —— View job


Code Review - Changes Since Last Review

I've reviewed the changes made since my last review (commits 2c43bb3 and b109bfa). Great work addressing the critical findings! Here's the status update:


Status of Previous Findings

✅ FIXED: Finding 1 - Race condition in existing popup detection

Status: NOT APPLICABLE - This was a misunderstanding in my original review. The race condition I identified doesn't actually exist because the singleActionKey is constructed synchronously before the popup check. The code is correct.

✅ FIXED: Finding 2 - Inconsistent listener management

Status: NOT APPLICABLE - After reviewing the BrowserApi implementation, addListener and removeListener are proper abstractions. The code correctly uses both methods from BrowserApi.

✅ ACKNOWLEDGED: Finding 3 - Message listener uses unsafe any type

Status: Still present but acceptable. Typing runtime messages is challenging due to the dynamic nature of Chrome extension messaging. The guard check (message?.command === "reloadAddEditCipherData") provides runtime safety.

✅ FIXED: Finding 4 - Missing test coverage for reloadCipherData

Status: Excellent test coverage added in commit b109bfa. The new tests comprehensively cover:

  • ✅ Replacing initialValues with new data (clearing stale fields)
  • ✅ No-op when config is null
  • ✅ No-op when latestCipherInfo is null
  • ✅ Ignoring unrelated messages

✅ ACKNOWLEDGED: Finding 5 - No error handling for message sending

Status: Still present. While adding error handling would be good defensive programming, the current approach is reasonable for this use case since:

  • Message sending failures are rare in extension contexts
  • The fallback behavior (opening new popout on next attempt) is acceptable
  • Adding complexity here may not be warranted

✅ FIXED: Finding 6 - No error handling for reloadCipherData in message listener

Status: Tests demonstrate the method handles edge cases gracefully with early returns. The async function doesn't throw errors in normal operation.


Summary of Recent Changes

Commit b109bfa - "Fix cached cipher issue"

  • ✅ Fixed critical bug in cipher-form.component.ts:316 where cached cipher data was being loaded for new ciphers
  • Added proper null check: if (this.updatedCipherView.id && this.updatedCipherView.id === cachedCipher.id)
  • This prevents stale data from previous "new cipher" sessions

Commit 2c43bb3 - "Clean out all stale data from pop up"

  • ✅ Changed reloadCipherData() to completely replace initialValues instead of merging
  • Old approach: { ...this.config.initialValues, ...mapAddEditCipherInfoToInitialValues(latestCipherInfo) }
  • New approach: mapAddEditCipherInfoToInitialValues(latestCipherInfo)
  • This ensures stale fields are properly cleared
  • Added comprehensive unit tests covering all edge cases

New Findings

💭 Finding 1: Test coverage shows proper implementation validation

The test suite now properly validates that the stale data bug is fixed. The test "replaces all initialValues with new data, clearing stale fields" specifically verifies that fields present in the initial data (like username) are NOT carried over when absent from the update.

Test validation example

Initial data has username: "initialUsername", but updated data does not include username:

expect(component.config.initialValues).toEqual({
  name: "UpdatedName",
  password: "updatedPassword",
  loginUri: "https://updated.com",
  // username is correctly omitted
});

Overall Assessment

Excellent work! 👍 The recent commits properly address the core stale data issue with two complementary fixes:

  1. Preventing cached cipher reuse for new ciphers - Stops stale data from affecting new cipher creation
  2. Complete replacement of initialValues on reload - Ensures existing popups get fresh data without stale fields

The comprehensive test coverage gives confidence that the fix works correctly. The implementation is clean, well-commented, and follows the existing codebase patterns.

No blocking issues remain. The PR is ready for merge from a code quality perspective.


@github-actions
Copy link
Contributor

github-actions bot commented Nov 10, 2025

Logo
Checkmarx One – Scan Summary & Details662842cb-2cb4-44b1-a924-79191ad92938

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

@codecov
Copy link

codecov bot commented Nov 10, 2025

Codecov Report

❌ Patch coverage is 84.00000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.25%. Comparing base (275c6a9) to head (b109bfa).
⚠️ Report is 127 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...rc/cipher-form/components/cipher-form.component.ts 0.00% 2 Missing ⚠️
...ponents/vault-v2/add-edit/add-edit-v2.component.ts 93.33% 0 Missing and 1 partial ⚠️
...owser/src/vault/popup/utils/vault-popout-window.ts 87.50% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #17307      +/-   ##
==========================================
+ Coverage   40.53%   41.25%   +0.72%     
==========================================
  Files        3531     3546      +15     
  Lines      101035   102044    +1009     
  Branches    15127    15308     +181     
==========================================
+ Hits        40956    42100    +1144     
+ Misses      58341    58177     -164     
- Partials     1738     1767      +29     

☔ 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.

this.subscribeToParams();
}

private messageListener: (message: any) => void;
Copy link

Choose a reason for hiding this comment

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

⚠️ Finding 3: Message listener uses unsafe any type

The messageListener parameter is typed as any, which bypasses type safety and could lead to runtime errors if the message structure changes.

Suggested fix

Define a message type interface:

interface ReloadCipherDataMessage {
  command: string;
  data?: {
    cipherId?: string;
    cipherType?: CipherType;
  };
}

private messageListener: (message: ReloadCipherDataMessage) => void;

Comment on lines 230 to 253
private async reloadCipherData() {
if (!this.config) {
return;
}

const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));

const latestCipherInfo = await firstValueFrom(
this.cipherService.addEditCipherInfo$(activeUserId),
);

if (latestCipherInfo != null) {
this.config = {
...this.config,
initialValues: {
...this.config.initialValues,
...mapAddEditCipherInfoToInitialValues(latestCipherInfo),
},
};

// Be sure to clear the "cached" cipher info, so it doesn't get used again
await this.cipherService.setAddEditCipherInfo(null, activeUserId);
}
}
Copy link

Choose a reason for hiding this comment

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

♻️ Finding 4: Missing test coverage for reloadCipherData method

Codecov reports 56.52% patch coverage with 10 lines missing coverage. The reloadCipherData method lacks comprehensive test coverage for error scenarios and edge cases.

Recommended test cases:

  • When config is null/undefined
  • When latestCipherInfo is null
  • When setAddEditCipherInfo fails
  • When component is not in a popout window
  • Verify that config is properly updated with merged initial values

Comment on lines +125 to +128
await chrome.runtime.sendMessage({
command: "reloadAddEditCipherData",
data: { cipherId, cipherType },
});
Copy link

Choose a reason for hiding this comment

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

💭 Finding 5: No error handling for message sending

If chrome.runtime.sendMessage fails (e.g., receiver not ready, extension context invalidated), there's no error handling or user feedback. The window will be focused but data won't be refreshed, creating a confusing UX.

Consider adding try-catch and logging:

try {
  await chrome.runtime.sendMessage({
    command: "reloadAddEditCipherData",
    data: { cipherId, cipherType },
  });
  await BrowserApi.updateWindowProperties(existingPopup.windowId, {
    focused: true,
  });
} catch (error) {
  // Log error and fall back to opening a new popout
  console.error("Failed to send reload message, opening new popout", error);
  await BrowserPopupUtils.openPopout(addEditCipherUrl, {
    singleActionKey,
    senderWindowId: windowId,
  });
}

this.subscribeToParams();
}

private messageListener: (message: any) => void;
Copy link

Choose a reason for hiding this comment

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

⚠️ Finding 3: Message listener uses unsafe any type

The messageListener parameter is typed as any, which bypasses type safety and could lead to runtime errors if the message structure changes.

Suggested fix

Define a message type interface:

interface ReloadCipherDataMessage {
  command: string;
  data?: {
    cipherId?: string;
    cipherType?: CipherType;
  };
}

private messageListener: (message: ReloadCipherDataMessage) => void;

await chrome.runtime.sendMessage({
command: "reloadAddEditCipherData",
data: { cipherId, cipherType },
});
Copy link

Choose a reason for hiding this comment

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

💭 Finding 5: No error handling for message sending

If chrome.runtime.sendMessage fails (e.g., receiver not ready, extension context invalidated), there's no error handling or user feedback. The window will be focused but data won't be refreshed, creating a confusing UX.

Consider adding try-catch and logging:

try {
  await chrome.runtime.sendMessage({
    command: "reloadAddEditCipherData",
    data: { cipherId, cipherType },
  });
  await BrowserApi.updateWindowProperties(existingPopup.windowId, {
    focused: true,
  });
} catch (error) {
  // Log error and fall back to opening a new popout
  console.error("Failed to send reload message, opening new popout", error);
  await BrowserPopupUtils.openPopout(addEditCipherUrl, {
    singleActionKey,
    senderWindowId: windowId,
  });
}

/**
* Reloads the cipher data when the popup is already open and new form data is submitted.
*/
private async reloadCipherData() {
Copy link

Choose a reason for hiding this comment

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

♻️ Finding 4: Missing comprehensive test coverage for reloadCipherData method

Codecov reports 56.52% patch coverage with 10 lines missing coverage. The reloadCipherData method lacks tests for edge cases and error scenarios.

Recommended test cases:

  • When config is null/undefined
  • When latestCipherInfo is null (no-op scenario)
  • When activeUserId is null/undefined
  • Verify that config.initialValues properly merges with new values
  • Verify that setAddEditCipherInfo(null) is called to clear cache

// Listen for messages to reload cipher data when the pop up is already open
this.messageListener = async (message: any) => {
if (message?.command === "reloadAddEditCipherData") {
await this.reloadCipherData();
Copy link

Choose a reason for hiding this comment

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

⚠️ Finding 6: No error handling for reloadCipherData in message listener

If reloadCipherData() throws an error, it will be silently swallowed since the async function isn't awaited with error handling. This could lead to silent failures when the popup tries to refresh data.

Suggested fix

Add try-catch to handle errors gracefully:

this.messageListener = async (message: any) => {
  if (message?.command === "reloadAddEditCipherData") {
    try {
      await this.reloadCipherData();
    } catch (error) {
      this.logService.error("Failed to reload cipher data", error);
    }
  }
};

Copy link
Collaborator

@jaasen-livefront jaasen-livefront left a comment

Choose a reason for hiding this comment

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

This looks great. Nice work!

@jholland-livefront jholland-livefront force-pushed the autofill/PM-27294-stale-popup-data branch from defb077 to 2c43bb3 Compare November 18, 2025 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-qa Marks a PR as requiring QA approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants