diff --git a/.github/workflows/test-browser-interactions.yml b/.github/workflows/test-browser-interactions.yml index 872b4c35264..5f2c78fe5a2 100644 --- a/.github/workflows/test-browser-interactions.yml +++ b/.github/workflows/test-browser-interactions.yml @@ -1,4 +1,5 @@ name: Autofill BIT checks +run-name: Autofill BIT checks on ${{ github.event.workflow_run.head_branch }} build on: workflow_run: @@ -11,10 +12,23 @@ jobs: name: Check files runs-on: ubuntu-22.04 permissions: - actions: read + actions: write contents: read id-token: write steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Check for job requirements + if: ${{ !(github.event.workflow_run.pull_requests && github.event.workflow_run.pull_requests.length > 0 && github.event.workflow_run.head_branch) }} + env: + GH_TOKEN: ${{ github.token }} + run: | + gh run cancel ${{ github.run_id }} + gh run watch ${{ github.run_id }} + - name: Log in to Azure uses: bitwarden/gh-actions/azure-login@main with: @@ -42,11 +56,6 @@ jobs: repositories: browser-interactions-testing permission-actions: write - - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 0 - - name: Get changed files id: changed-files uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts index 5f3bc4dfff9..f0aa9c1c440 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts @@ -1757,6 +1757,54 @@ describe("CollectAutofillContentService", () => { expect(parsedText).toEqual("Hello! This is a test string."); }); + + it("preserves extended Latin letters like Š and ć", () => { + const text = "Šifra ćevapčići korisnika"; + const result = collectAutofillContentService["trimAndRemoveNonPrintableText"](text); + expect(result).toEqual("Šifra ćevapčići korisnika"); + }); + + it("removes zero-width and control characters", () => { + const text = "Hello\u200B\u200C\u200D\u2060World\x00\x1F!"; + const result = collectAutofillContentService["trimAndRemoveNonPrintableText"](text); + expect(result).toEqual("Hello World !"); + }); + + it("removes leading and trailing whitespace", () => { + const text = " padded text with spaces "; + const result = collectAutofillContentService["trimAndRemoveNonPrintableText"](text); + expect(result).toEqual("padded text with spaces"); + }); + + it("replaces multiple whitespaces (tabs, newlines, spaces) with one space", () => { + const text = "one\t\ntwo \n three\t\tfour"; + const result = collectAutofillContentService["trimAndRemoveNonPrintableText"](text); + expect(result).toEqual("one two three four"); + }); + + it("preserves emoji and symbols", () => { + const text = "Text with emoji 🐍🚀 and ©®✓ symbols"; + const result = collectAutofillContentService["trimAndRemoveNonPrintableText"](text); + expect(result).toEqual("Text with emoji 🐍🚀 and ©®✓ symbols"); + }); + + it("handles RTL and LTR marks", () => { + const text = "abc\u200F\u202Edеf"; + const result = collectAutofillContentService["trimAndRemoveNonPrintableText"](text); + expect(result).toEqual("abc dеf"); + }); + + it("handles mathematical unicode letters", () => { + const text = "Unicode math: 𝒜𝒷𝒸𝒹"; + const result = collectAutofillContentService["trimAndRemoveNonPrintableText"](text); + expect(result).toEqual("Unicode math: 𝒜𝒷𝒸𝒹"); + }); + + it("removes only invisible non-printables, keeps Japanese", () => { + const text = "これは\u200Bテストです"; + const result = collectAutofillContentService["trimAndRemoveNonPrintableText"](text); + expect(result).toEqual("これは テストです"); + }); }); describe("recursivelyGetTextFromPreviousSiblings", () => { diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.ts index 0f9c8993014..c6af9739773 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.ts @@ -713,7 +713,7 @@ export class CollectAutofillContentService implements CollectAutofillContentServ */ private trimAndRemoveNonPrintableText(textContent: string): string { return (textContent || "") - .replace(/[^\x20-\x7E]+|\s+/g, " ") // Strip out non-primitive characters and replace multiple spaces with a single space + .replace(/\p{C}+|\s+/gu, " ") // Strip out non-printable characters and replace multiple spaces with a single space .trim(); // Trim leading and trailing whitespace } diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html index 256aec34b50..76a2d466369 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -36,6 +36,7 @@ [ngClass]="{ 'tw-hidden': passwordRevealed }" readonly bitInput + #passwordInput type="password" [value]="cipher.login.password" aria-readonly="true" diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index 5eeb3f9e8f3..9a40caf031b 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -3,12 +3,14 @@ import { CommonModule, DatePipe } from "@angular/common"; import { Component, + ElementRef, EventEmitter, inject, Input, OnChanges, Output, SimpleChanges, + ViewChild, } from "@angular/core"; import { Observable, switchMap } from "rxjs"; @@ -61,6 +63,8 @@ export class LoginCredentialsViewComponent implements OnChanges { @Input() activeUserId: UserId; @Input() hadPendingChangePasswordTask: boolean; @Output() handleChangePassword = new EventEmitter(); + @ViewChild("passwordInput") + private passwordInput!: ElementRef; isPremium$: Observable = this.accountService.activeAccount$.pipe( switchMap((account) => @@ -92,6 +96,10 @@ export class LoginCredentialsViewComponent implements OnChanges { ngOnChanges(changes: SimpleChanges): void { if (changes["cipher"]) { + if (this.passwordInput?.nativeElement) { + // Reset password input type in case it's been toggled + this.passwordInput.nativeElement.type = "password"; + } this.passwordRevealed = false; this.showPasswordCount = false; }