Files
Rubén Norte 8a4ddab2c2 Move MutationObserver to react-native/src/private (#45791)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/45791

Changelog: [internal]

These files are safe to move because they haven't been enabled in OSS and people shouldn't be importing them directly.

Reviewed By: rshest

Differential Revision: D60377869

fbshipit-source-id: 02bc0335385859c0589a494de9b59b0c2ebc06f6
2024-07-30 10:08:22 -07:00

131 lines
4.2 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type DOMRectReadOnly from 'react-native/src/private/webapis/dom/geometry/DOMRectReadOnly';
import type ReadOnlyNode from 'react-native/src/private/webapis/dom/nodes/ReadOnlyNode';
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
import IntersectionObserver from 'react-native/src/private/webapis/intersectionobserver/IntersectionObserver';
import MutationObserver from 'react-native/src/private/webapis/mutationobserver/MutationObserver';
export type VisualElement = {
time: number,
rect: DOMRectReadOnly,
};
function debug(...args: $ReadOnlyArray<mixed>): void {
console.debug('VCTrackerExample', args);
}
export default class VCTracker {
_navigationStartTime: number;
_intersectionObserver: IntersectionObserver;
_mutationObserver: MutationObserver;
_registeredCallback: ($ReadOnlyArray<VisualElement>) => void;
_visualElements: Map<ReadOnlyNode, VisualElement> = new Map();
_pendingMutations: WeakSet<ReadOnlyNode> = new WeakSet();
constructor(navigationStartTime: number) {
this._navigationStartTime = navigationStartTime;
// This should be guaranteed to run before painting RootView in native.
this._intersectionObserver = new IntersectionObserver(
(entries, observer) => {
// This will be executed after mount/paint.
for (const entry of entries) {
if (this._pendingMutations.has(entry.target)) {
this._registerVisualElement(entry.target, {
time: entry.time,
rect: entry.boundingClientRect,
});
this._pendingMutations.delete(entry.target);
this._intersectionObserver.unobserve(entry.target);
}
}
},
);
this._mutationObserver = new MutationObserver((entries, observer) => {
// This will be executed after layout effects, and before mount/paint.
for (const entry of entries) {
if (entry.addedNodes) {
for (const addedNode of entry.addedNodes) {
// To measure paint time for added nodes
this._pendingMutations.add(addedNode);
if (addedNode instanceof ReactNativeElement) {
this._intersectionObserver.observe(addedNode);
}
}
for (const removedNode of entry.removedNodes) {
// To measure paint time for added nodes
this._pendingMutations.delete(removedNode);
if (removedNode instanceof ReactNativeElement) {
this._unregisterVisualElement(removedNode);
}
}
}
}
});
}
_registerVisualElement(
target: ReadOnlyNode,
visualElement: VisualElement,
): void {
debug(
'registerVisualElement',
(target instanceof ReactNativeElement && target.id) ||
'<target-without-id>',
'. Painted in',
(visualElement.time - this._navigationStartTime).toFixed(2),
'ms (at ',
visualElement.time,
'), rect:',
visualElement.rect.toJSON(),
);
this._visualElements.set(target, visualElement);
this._registeredCallback?.([...this._visualElements.values()]);
}
_unregisterVisualElement(target: ReadOnlyNode): void {
this._visualElements.delete(target);
this._registeredCallback?.([...this._visualElements.values()]);
}
onUpdateVisualElements(
callback: ($ReadOnlyArray<VisualElement>) => void,
): void {
this._registeredCallback = callback;
}
addMutationRoot(rootNode: ReactNativeElement): void {
debug('addMutationRoot', rootNode.id);
// To observe new nodes added.
this._mutationObserver.observe(rootNode, {
subtree: true,
childList: true,
});
this._pendingMutations.add(rootNode);
// To measure initial paint.
this._intersectionObserver.observe(rootNode);
}
getVisualElements(): $ReadOnlyArray<VisualElement> {
return [...this._visualElements.values()];
}
disconnect(): void {
this._mutationObserver.disconnect();
this._intersectionObserver.disconnect();
}
}