From 380583804c3f8888315968812694749a860f031d Mon Sep 17 00:00:00 2001 From: TheCymaera <69355340+TheCymaera@users.noreply.github.com> Date: Fri, 11 Nov 2022 00:29:17 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Use=20RO=20singleton=20ins?= =?UTF-8?q?tead=20of=20iframe=20if=20supported?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/ResizeObserverSingleton.ts | 46 +++++++++++++++++++ src/runtime/internal/dom.ts | 28 ++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/runtime/internal/ResizeObserverSingleton.ts diff --git a/src/runtime/internal/ResizeObserverSingleton.ts b/src/runtime/internal/ResizeObserverSingleton.ts new file mode 100644 index 0000000000..5492dfe07e --- /dev/null +++ b/src/runtime/internal/ResizeObserverSingleton.ts @@ -0,0 +1,46 @@ +const MapImplementation = "WeakMap" in window ? WeakMap : Map; + +/** + * Resize observer singleton + * https://groups.google.com/a/chromium.org/g/blink-dev/c/z6ienONUb5A/m/F5-VcUZtBAAJ + */ +export class ResizeObserverSingleton { + constructor(readonly options?: ResizeObserverOptions) {} + + addListener(element: Element, callback: (element: Element)=>any) { + if (!this._callbacks.has(element)) { + this._callbacks.set(element, new Set([callback])); + this._getObserver().observe(element, this.options); + } else { + this._callbacks.get(element)!.add(callback); + callback(element); + } + } + + removeListener(element: Element, callback: (element: Element)=>any) { + const list = this._callbacks.get(element); + if (!list) return; + if (list.size > 1) { + list.delete(callback); + } else { + this._callbacks.delete(element); + this._getObserver().unobserve(element); + } + } + + // TODO: consider using Array instead of Set? + // If we can guarantee one listener per element, we can simplify even more. + private readonly _callbacks: WeakMapany>> = new MapImplementation(); + private _observer: ResizeObserver|undefined = undefined; + private _getObserver() { + return this._observer ?? (this._observer = new ResizeObserver((entries)=>{ + for (const entry of entries) { + const element = entry.target; + const callbacks = this._callbacks.get(element); + if (callbacks) { + for (const callback of callbacks) callback(element); + } + } + })); + } +} \ No newline at end of file diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index ca76b11ab3..02ae20b4d4 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -1,3 +1,4 @@ +import { ResizeObserverSingleton } from './ResizeObserverSingleton.js'; import { has_prop } from './utils'; // Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM @@ -596,7 +597,7 @@ export function is_crossorigin() { return crossorigin; } -export function add_resize_listener(node: HTMLElement, fn: () => void) { +function add_iframe_resize_listener(node: HTMLElement, fn: () => void) { const computed_style = getComputedStyle(node); if (computed_style.position === 'static') { @@ -640,6 +641,31 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { }; } + +export const resize_observer_content_box = new ResizeObserverSingleton({ box: "content-box" }); +// export const resize_observer_border_box = new SoleResizeObserver({ box: "border-box" }); +// export const resize_observer_device_pixel_content_box = new SoleResizeObserver({ box: "device-pixel-content-box" }); + +export function add_content_box_observer(node: HTMLElement, fn: () => void) { + resize_observer_content_box.addListener(node, fn); + return ()=> resize_observer_content_box.removeListener(node, fn); +} + +// export function add_border_box_observer(node: HTMLElement, fn: () => void) { +// resize_observer_border_box.addListener(node, fn); +// return ()=> resize_observer_border_box.removeListener(node, fn); +// } +// +// export function add_device_pixel_content_box_observer(node: HTMLElement, fn: () => void) { +// resize_observer_device_pixel_content_box.addListener(node, fn); +// return ()=> resize_observer_device_pixel_content_box.removeListener(node, fn); +// } + + +export const add_resize_listener = "ResizeObserver" in window ? +add_content_box_observer : +add_iframe_resize_listener; + export function toggle_class(element, name, toggle) { element.classList[toggle ? 'add' : 'remove'](name); }