1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-29 15:53:45 +00:00

fix scroll top in firefox

This commit is contained in:
jaasen-livefront
2026-01-22 15:21:12 -08:00
parent a9d8edc52c
commit 92db709808
3 changed files with 50 additions and 2 deletions

View File

@@ -266,6 +266,7 @@ export class ViewV2Component {
}
this.popupScrollPositionService.stop(true);
this.popupScrollPositionService.forceTopOnNextVaultStart();
await this.popupRouterCacheService.back();
this.toastService.showToast({

View File

@@ -108,6 +108,28 @@ describe("VaultPopupScrollPositionService", () => {
});
expect((scrollElement as any).scrollTop).toBe(500);
}));
it("forces scroll to top on next start when requested", fakeAsync(() => {
service["scrollPosition"] = 500;
service.forceTopOnNextVaultStart();
service.start(scrollElement);
tick();
expect((scrollElement as any).scrollTo).toHaveBeenCalledWith({
behavior: "instant",
top: 0,
});
expect((scrollElement as any).scrollTop).toBe(0);
// A follow-up scroll is scheduled to defeat Firefox scroll restoration.
((scrollElement as any).scrollTo as jest.Mock).mockClear();
tick(50);
expect((scrollElement as any).scrollTo).toHaveBeenCalledWith({
behavior: "instant",
top: 0,
});
}));
});
describe("scroll listener", () => {

View File

@@ -18,6 +18,9 @@ export class VaultPopupScrollPositionService {
/** Subscription associated with the virtual scroll element. */
private scrollSubscription: Subscription | null = null;
/** When true, the next call to `start()` will force scroll position back to the top. */
private forceTopOnNextStart = false;
constructor() {
this.router.events
.pipe(
@@ -31,8 +34,22 @@ export class VaultPopupScrollPositionService {
/** Scrolls the user to the stored scroll position and starts tracking scroll of the page. */
start(scrollElement: HTMLElement) {
if (this.hasScrollPosition()) {
// Use `setTimeout` to scroll after rendering is complete
// Use `setTimeout` to scroll after rendering is complete.
// Firefox can sometimes restore scroll position on history navigation (back/forward)
// after our initial scroll call. When we explicitly want to reset to the top (e.g. after
// deleting an item), we schedule an extra follow-up scroll to ensure the final position is 0.
if (this.forceTopOnNextStart) {
this.forceTopOnNextStart = false;
this.scrollPosition = 0;
// try to set scroll to top immediately
setTimeout(() => {
scrollElement.scrollTo({ top: 0, behavior: "instant" });
}, 0);
// wait for FF to possibly restore scroll position after UI settles, then enforce top=0
setTimeout(() => {
scrollElement.scrollTo({ top: 0, behavior: "instant" });
}, 250);
} else if (this.hasScrollPosition()) {
setTimeout(() => {
scrollElement.scrollTo({ top: this.scrollPosition!, behavior: "instant" });
});
@@ -66,6 +83,14 @@ export class VaultPopupScrollPositionService {
return this.scrollPosition !== null;
}
/**
* Forces the next vault list render to start at scroll position 0.
* Useful for flows where we don't want browser back/forward scroll restoration (e.g. after delete).
*/
forceTopOnNextVaultStart() {
this.forceTopOnNextStart = true;
}
/** Conditionally resets the scroll listeners based on the ending path of the navigation */
private resetListenerForNavigation(event: NavigationEnd): void {
// The vault page is the target of the scroll listener, return early