1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-31 22:51:28 +01:00

[PM-15065] Vault Loading on empty tabs (#12059)

* catch errors from `tabSendMessage`

- Removes the need for a timeout when fetching page details

* Add parameter to reject on error

- allows each implementation to decide if they want to handle the error or not
This commit is contained in:
Nick Krantz 2024-11-25 08:37:01 -06:00 committed by GitHub
parent da6a0cb8e9
commit d52da5869b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 57 additions and 17 deletions

View File

@ -143,7 +143,7 @@ describe("AutofillService", () => {
}); });
describe("collectPageDetailsFromTab$", () => { describe("collectPageDetailsFromTab$", () => {
const tab = mock<chrome.tabs.Tab>({ id: 1 }); const tab = mock<chrome.tabs.Tab>({ id: 1, url: "https://www.example.com" });
const messages = new Subject<CollectPageDetailsResponseMessage>(); const messages = new Subject<CollectPageDetailsResponseMessage>();
function mockCollectPageDetailsResponseMessage( function mockCollectPageDetailsResponseMessage(
@ -165,11 +165,16 @@ describe("AutofillService", () => {
it("sends a `collectPageDetails` message to the passed tab", () => { it("sends a `collectPageDetails` message to the passed tab", () => {
autofillService.collectPageDetailsFromTab$(tab); autofillService.collectPageDetailsFromTab$(tab);
expect(BrowserApi.tabSendMessage).toHaveBeenCalledWith(tab, { expect(BrowserApi.tabSendMessage).toHaveBeenCalledWith(
command: AutofillMessageCommand.collectPageDetails,
sender: AutofillMessageSender.collectPageDetailsFromTabObservable,
tab, tab,
}); {
command: AutofillMessageCommand.collectPageDetails,
sender: AutofillMessageSender.collectPageDetailsFromTabObservable,
tab,
},
null,
true,
);
}); });
it("builds an array of page details from received `collectPageDetailsResponse` messages", async () => { it("builds an array of page details from received `collectPageDetailsResponse` messages", async () => {
@ -218,6 +223,24 @@ describe("AutofillService", () => {
expect(tracker.emissions[1]).toBeUndefined(); expect(tracker.emissions[1]).toBeUndefined();
}); });
it("returns an empty array when the tab.url is empty", async () => {
const tracker = subscribeTo(autofillService.collectPageDetailsFromTab$({ ...tab, url: "" }));
await tracker.pauseUntilReceived(1);
expect(tracker.emissions[0]).toEqual([]);
});
it("returns an empty array when the `BrowserApi.tabSendMessage` throws an error", async () => {
(BrowserApi.tabSendMessage as jest.Mock).mockRejectedValueOnce(undefined);
const tracker = subscribeTo(autofillService.collectPageDetailsFromTab$(tab));
await tracker.pauseUntilReceived(1);
expect(tracker.emissions[0]).toEqual([]);
});
}); });
describe("loadAutofillScriptsOnInstall", () => { describe("loadAutofillScriptsOnInstall", () => {

View File

@ -1,4 +1,4 @@
import { filter, firstValueFrom, Observable, scan, startWith } from "rxjs"; import { filter, firstValueFrom, merge, Observable, ReplaySubject, scan, startWith } from "rxjs";
import { pairwise } from "rxjs/operators"; import { pairwise } from "rxjs/operators";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
@ -91,6 +91,9 @@ export default class AutofillService implements AutofillServiceInterface {
* @param tab The tab to collect page details from * @param tab The tab to collect page details from
*/ */
collectPageDetailsFromTab$(tab: chrome.tabs.Tab): Observable<PageDetail[]> { collectPageDetailsFromTab$(tab: chrome.tabs.Tab): Observable<PageDetail[]> {
/** Replay Subject that can be utilized when `messages$` may not emit the page details. */
const pageDetailsFallback$ = new ReplaySubject<[]>(1);
const pageDetailsFromTab$ = this.messageListener const pageDetailsFromTab$ = this.messageListener
.messages$(COLLECT_PAGE_DETAILS_RESPONSE_COMMAND) .messages$(COLLECT_PAGE_DETAILS_RESPONSE_COMMAND)
.pipe( .pipe(
@ -112,13 +115,28 @@ export default class AutofillService implements AutofillServiceInterface {
), ),
); );
void BrowserApi.tabSendMessage(tab, { void BrowserApi.tabSendMessage(
tab: tab, tab,
command: AutofillMessageCommand.collectPageDetails, {
sender: AutofillMessageSender.collectPageDetailsFromTabObservable, tab: tab,
command: AutofillMessageCommand.collectPageDetails,
sender: AutofillMessageSender.collectPageDetailsFromTabObservable,
},
null,
true,
).catch(() => {
// When `tabSendMessage` throws an error the `pageDetailsFromTab$` will not emit,
// fallback to an empty array
pageDetailsFallback$.next([]);
}); });
return pageDetailsFromTab$; // Empty/New tabs do not have a URL.
// In Safari, `tabSendMessage` doesn't throw an error for this case. Fallback to the empty array to handle.
if (!tab.url) {
pageDetailsFallback$.next([]);
}
return merge(pageDetailsFromTab$, pageDetailsFallback$);
} }
/** /**

View File

@ -200,15 +200,17 @@ export class BrowserApi {
tab: chrome.tabs.Tab, tab: chrome.tabs.Tab,
obj: T, obj: T,
options: chrome.tabs.MessageSendOptions = null, options: chrome.tabs.MessageSendOptions = null,
rejectOnError = false,
): Promise<TResponse> { ): Promise<TResponse> {
if (!tab || !tab.id) { if (!tab || !tab.id) {
return; return;
} }
return new Promise<TResponse>((resolve) => { return new Promise<TResponse>((resolve, reject) => {
chrome.tabs.sendMessage(tab.id, obj, options, (response) => { chrome.tabs.sendMessage(tab.id, obj, options, (response) => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError && rejectOnError) {
// Some error happened // Some error happened
reject();
} }
resolve(response); resolve(response);
}); });

View File

@ -10,7 +10,6 @@ import {
startWith, startWith,
Subject, Subject,
switchMap, switchMap,
timeout,
} from "rxjs"; } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@ -75,9 +74,7 @@ export class VaultPopupAutofillService {
if (!tab) { if (!tab) {
return of([]); return of([]);
} }
return this.autofillService return this.autofillService.collectPageDetailsFromTab$(tab);
.collectPageDetailsFromTab$(tab)
.pipe(timeout({ first: 1500, with: () => of([]) }));
}), }),
shareReplay({ refCount: false, bufferSize: 1 }), shareReplay({ refCount: false, bufferSize: 1 }),
); );