mirror of
https://github.com/dream-num/univer.git
synced 2026-10-02 04:24:34 +08:00
fix(ui): scope ResizeObserver to hook to prevent detached DOM retention (#7345)
This commit is contained in:
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
type ResizeObserverTestCallback = (entries: ResizeObserverEntry[], observer: ResizeObserver) => void;
|
||||
|
||||
describe('helper/resize-observer', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('should use a shared ResizeObserver and dispatch callbacks', async () => {
|
||||
const observe = vi.fn();
|
||||
const unobserve = vi.fn();
|
||||
let ctorCalls = 0;
|
||||
const observerCallbackRef: { current?: ResizeObserverTestCallback } = {};
|
||||
|
||||
class MockResizeObserver {
|
||||
constructor(callback: ResizeObserverCallback) {
|
||||
ctorCalls += 1;
|
||||
observerCallbackRef.current = callback;
|
||||
}
|
||||
|
||||
observe = observe;
|
||||
unobserve = unobserve;
|
||||
}
|
||||
|
||||
vi.stubGlobal('ResizeObserver', MockResizeObserver as unknown as typeof ResizeObserver);
|
||||
|
||||
const { resizeObserverCtor } = await import('../resize-observer');
|
||||
|
||||
const cb1 = vi.fn();
|
||||
const cb2 = vi.fn();
|
||||
const targetA = document.createElement('div');
|
||||
const targetB = document.createElement('div');
|
||||
|
||||
const observerA = resizeObserverCtor(cb1);
|
||||
const observerB = resizeObserverCtor(cb2);
|
||||
|
||||
observerA.observe(targetA);
|
||||
observerB.observe(targetB);
|
||||
|
||||
expect(ctorCalls).toBe(1);
|
||||
expect(observe).toHaveBeenCalledWith(targetA, undefined);
|
||||
expect(observe).toHaveBeenCalledWith(targetB, undefined);
|
||||
|
||||
const entries = [{ target: targetA }] as unknown as ResizeObserverEntry[];
|
||||
const observerCallback = observerCallbackRef.current;
|
||||
if (!observerCallback) {
|
||||
throw new Error('ResizeObserver callback should be initialized');
|
||||
}
|
||||
observerCallback(entries, {} as ResizeObserver);
|
||||
|
||||
expect(cb1).toHaveBeenCalledTimes(1);
|
||||
expect(cb2).toHaveBeenCalledTimes(1);
|
||||
|
||||
observerA.unobserve(targetA);
|
||||
observerCallback(entries, {} as ResizeObserver);
|
||||
|
||||
expect(cb1).toHaveBeenCalledTimes(1);
|
||||
expect(cb2).toHaveBeenCalledTimes(2);
|
||||
expect(unobserve).toHaveBeenCalledWith(targetA);
|
||||
});
|
||||
});
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* All elements are observed by a single ResizeObserver is got greater performance than each element observed by separate ResizeObserver
|
||||
* See issue https://github.com/WICG/resize-observer/issues/59#issuecomment-408098151
|
||||
*/
|
||||
const _resizeObserverCallbacks: Set<ResizeObserverCallback> = new Set();
|
||||
let _resizeObserver: ResizeObserver;
|
||||
|
||||
export function resizeObserverCtor(callback: ResizeObserverCallback) {
|
||||
if (!_resizeObserver) {
|
||||
_resizeObserver = new ResizeObserver((...args) => {
|
||||
_resizeObserverCallbacks.forEach((callback) => callback(...args));
|
||||
});
|
||||
}
|
||||
return {
|
||||
observe(target: Element, options?: ResizeObserverOptions | undefined) {
|
||||
_resizeObserverCallbacks.add(callback);
|
||||
_resizeObserver.observe(target, options);
|
||||
},
|
||||
unobserve(target: Element) {
|
||||
_resizeObserverCallbacks.delete(callback);
|
||||
_resizeObserver.unobserve(target);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -130,5 +130,4 @@ export {
|
||||
export { clsx } from './helper/clsx';
|
||||
export { isBrowser } from './helper/is-browser';
|
||||
export { render, unmount } from './helper/react-dom';
|
||||
export { resizeObserverCtor } from './helper/resize-observer';
|
||||
export { cva } from 'class-variance-authority';
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { useScrollYOverContainer } from '../layout';
|
||||
|
||||
describe('useScrollYOverContainer', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('should disconnect the ResizeObserver when unmounted', () => {
|
||||
const disconnect = vi.fn();
|
||||
|
||||
class MockResizeObserver {
|
||||
disconnect = disconnect;
|
||||
observe = vi.fn();
|
||||
takeRecords = vi.fn(() => []);
|
||||
unobserve = vi.fn();
|
||||
}
|
||||
|
||||
vi.stubGlobal('ResizeObserver', MockResizeObserver as unknown as typeof ResizeObserver);
|
||||
|
||||
const element = document.createElement('div');
|
||||
const container = document.createElement('div');
|
||||
const { unmount } = renderHook(() => useScrollYOverContainer(element, container));
|
||||
|
||||
unmount();
|
||||
|
||||
expect(disconnect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import type { Nullable } from '@univerjs/core';
|
||||
import { IConfigService } from '@univerjs/core';
|
||||
import { isBrowser, resizeObserverCtor } from '@univerjs/design';
|
||||
import { isBrowser } from '@univerjs/design';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useDependency, useObservable } from '../../utils/di';
|
||||
import { useEvent } from './event';
|
||||
@@ -65,10 +65,10 @@ export function useScrollYOverContainer(element: Nullable<HTMLElement>, containe
|
||||
|
||||
updater();
|
||||
|
||||
const resizeObserver = resizeObserverCtor(updater);
|
||||
const resizeObserver = new ResizeObserver(updater);
|
||||
resizeObserver.observe(element);
|
||||
return () => {
|
||||
resizeObserver.unobserve(element);
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [element, container]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user