mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-24 21:41:33 +01:00
[PM-5189] Implementing a methodology for triggering subframe updates from layout-shift
This commit is contained in:
parent
a7fa57ce72
commit
2329445d45
@ -64,7 +64,6 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
private inlineMenuPageTranslations: Record<string, string>;
|
||||
private inlineMenuFadeInTimeout: number | NodeJS.Timeout;
|
||||
private updateInlineMenuPositionTimeout: number | NodeJS.Timeout;
|
||||
private isReflowUpdatingSubFrames: boolean = false;
|
||||
private delayedCloseTimeout: number | NodeJS.Timeout;
|
||||
private focusedFieldData: FocusedFieldData;
|
||||
private isFieldCurrentlyFocused: boolean = false;
|
||||
@ -414,9 +413,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.updateInlineMenuPositionTimeout) {
|
||||
clearTimeout(this.updateInlineMenuPositionTimeout);
|
||||
}
|
||||
this.clearUpdateInlineMenuPositionTimeout();
|
||||
|
||||
await this.rebuildSubFrameOffsets(sender);
|
||||
|
||||
@ -583,6 +580,12 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private clearUpdateInlineMenuPositionTimeout() {
|
||||
if (this.updateInlineMenuPositionTimeout) {
|
||||
clearTimeout(this.updateInlineMenuPositionTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles cleanup when an overlay element is closed. Disconnects
|
||||
* the list and button ports and sets them to null.
|
||||
|
@ -17,7 +17,12 @@ import {
|
||||
} from "../enums/autofill-overlay.enum";
|
||||
import AutofillField from "../models/autofill-field";
|
||||
import { ElementWithOpId, FillableFormFieldElement, FormFieldElement } from "../types";
|
||||
import { elementIsFillableFormField, getAttributeBoolean, sendExtensionMessage } from "../utils";
|
||||
import {
|
||||
elementIsFillableFormField,
|
||||
getAttributeBoolean,
|
||||
sendExtensionMessage,
|
||||
throttle,
|
||||
} from "../utils";
|
||||
|
||||
import {
|
||||
AutofillOverlayContentExtensionMessageHandlers,
|
||||
@ -43,7 +48,8 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
private focusedFieldData: FocusedFieldData;
|
||||
private userInteractionEventTimeout: number | NodeJS.Timeout;
|
||||
private recalculateSubFrameOffsetsTimeout: number | NodeJS.Timeout;
|
||||
private performanceObserver: PerformanceObserver;
|
||||
private reflowPerformanceObserver: PerformanceObserver;
|
||||
private reflowMutationObserver: MutationObserver;
|
||||
private autofillFieldKeywordsMap: WeakMap<AutofillField, string> = new WeakMap();
|
||||
private eventHandlersMemo: { [key: string]: EventListener } = {};
|
||||
private readonly extensionMessageHandlers: AutofillOverlayContentExtensionMessageHandlers = {
|
||||
@ -778,6 +784,45 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
this.inlineMenuVisibility = inlineMenuVisibility || AutofillOverlayVisibility.OnFieldFocus;
|
||||
}
|
||||
|
||||
private setupPageReflowEventListeners() {
|
||||
if ("PerformanceObserver" in window && "LayoutShift" in window) {
|
||||
this.reflowPerformanceObserver = new PerformanceObserver(
|
||||
throttle(this.updateSubFrameOffsetsFromLayoutShiftEvent.bind(this), 10),
|
||||
);
|
||||
this.reflowPerformanceObserver.observe({ type: "layout-shift", buffered: true });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.reflowMutationObserver = new MutationObserver(
|
||||
throttle(this.updateSubFrameOffsetsFromDomMutationEvent.bind(this), 10),
|
||||
);
|
||||
this.reflowMutationObserver.observe(globalThis.document.documentElement, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
});
|
||||
}
|
||||
|
||||
private updateSubFrameOffsetsFromLayoutShiftEvent = (list: any) => {
|
||||
const entries: any[] = list.getEntries();
|
||||
for (let index = 0; index < entries.length; index++) {
|
||||
const entry = entries[index];
|
||||
if (entry.sources?.length) {
|
||||
this.clearUserInteractionEventTimeout();
|
||||
this.clearRecalculateSubFrameOffsetsTimeout();
|
||||
void this.sendExtensionMessage("updateSubFrameOffsetsForReflowEvent");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private updateSubFrameOffsetsFromDomMutationEvent = async () => {
|
||||
this.clearUserInteractionEventTimeout();
|
||||
this.clearRecalculateSubFrameOffsetsTimeout();
|
||||
void this.sendExtensionMessage("updateSubFrameOffsetsForReflowEvent");
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up event listeners that facilitate repositioning
|
||||
* the overlay elements on scroll or resize.
|
||||
@ -787,17 +832,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
capture: true,
|
||||
});
|
||||
globalThis.addEventListener(EVENTS.RESIZE, this.handleOverlayRepositionEvent);
|
||||
this.performanceObserver = new PerformanceObserver((list) => {
|
||||
const entries: any = list.getEntries();
|
||||
for (let index = 0; index < entries.length; index++) {
|
||||
const entry = entries[index];
|
||||
if (entry.sources?.length > 0) {
|
||||
void this.sendExtensionMessage("updateSubFrameOffsetsForReflowEvent");
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.performanceObserver.observe({ type: "layout-shift", buffered: true });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -941,6 +975,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
globalThis.addEventListener(EVENTS.MESSAGE, this.handleWindowMessageEvent);
|
||||
globalThis.document.addEventListener(EVENTS.VISIBILITYCHANGE, this.handleVisibilityChangeEvent);
|
||||
globalThis.addEventListener(EVENTS.FOCUSOUT, this.handleFormFieldBlurEvent);
|
||||
this.setupPageReflowEventListeners();
|
||||
this.setOverlayRepositionEventListeners();
|
||||
};
|
||||
|
||||
@ -1173,7 +1208,8 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
|
||||
this.handleVisibilityChangeEvent,
|
||||
);
|
||||
globalThis.removeEventListener(EVENTS.FOCUSOUT, this.handleFormFieldBlurEvent);
|
||||
this.performanceObserver?.disconnect();
|
||||
this.reflowPerformanceObserver?.disconnect();
|
||||
this.reflowMutationObserver?.disconnect();
|
||||
this.removeOverlayRepositionEventListeners();
|
||||
}
|
||||
}
|
||||
|
@ -300,3 +300,14 @@ export function getPropertyOrAttribute(element: HTMLElement, attributeName: stri
|
||||
|
||||
return element.getAttribute(attributeName);
|
||||
}
|
||||
|
||||
export function throttle(callback: () => void, limit: number) {
|
||||
let waitingDelay = false;
|
||||
return function (...args: unknown[]) {
|
||||
if (!waitingDelay) {
|
||||
callback.apply(this, args);
|
||||
waitingDelay = true;
|
||||
globalThis.setTimeout(() => (waitingDelay = false), limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user