- 37개 파일 IP → zioinfo.co.kr 치환 (소스/매뉴얼/설정/하네스) - Manager DrConsole/NetworkConsole/CsapConsole 빌드 + /var/www/manager/ 배포 - 테스트: Manager HTTP 200, ITSM 신규 API 7개 전체 200 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
328 lines
8.9 KiB
JavaScript
328 lines
8.9 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
|
|
*/
|
|
|
|
/**
|
|
* This is a mock of `NativeMutationObserver` implementing the same logic as the
|
|
* native module and integrating with the existing mock for `FabricUIManager`.
|
|
* This allows us to test all the JavaScript code for IntersectionObserver in
|
|
* JavaScript as an integration test using only public APIs.
|
|
*/
|
|
|
|
import type {NodeSet} from '../../ReactNative/FabricUIManager';
|
|
import type {RootTag} from '../../ReactNative/RootTag';
|
|
import type {
|
|
InternalInstanceHandle,
|
|
Node,
|
|
} from '../../Renderer/shims/ReactNativeTypes';
|
|
import type {
|
|
MutationObserverId,
|
|
NativeMutationObserverObserveOptions,
|
|
NativeMutationRecord,
|
|
Spec,
|
|
} from '../NativeMutationObserver';
|
|
|
|
import ReadOnlyNode from '../../../src/private/webapis/dom/nodes/ReadOnlyNode';
|
|
import {
|
|
type NodeMock,
|
|
type UIManagerCommitHook,
|
|
fromNode,
|
|
getFabricUIManager,
|
|
getNodeInChildSet,
|
|
} from '../../ReactNative/__mocks__/FabricUIManager';
|
|
import invariant from 'invariant';
|
|
import nullthrows from 'nullthrows';
|
|
|
|
let pendingRecords: Array<NativeMutationRecord> = [];
|
|
let callback: ?() => void;
|
|
let getPublicInstance: ?(instanceHandle: InternalInstanceHandle) => mixed;
|
|
let observersByRootTag: Map<
|
|
RootTag,
|
|
Map<MutationObserverId, {deep: Set<Node>, shallow: Set<Node>}>,
|
|
> = new Map();
|
|
|
|
const FabricUIManagerMock = nullthrows(getFabricUIManager());
|
|
|
|
function getMockDataFromShadowNode(node: mixed): NodeMock {
|
|
// $FlowExpectedError[incompatible-call]
|
|
return fromNode(node);
|
|
}
|
|
|
|
function castToNode(node: mixed): Node {
|
|
// $FlowExpectedError[incompatible-return]
|
|
return node;
|
|
}
|
|
|
|
const NativeMutationMock = {
|
|
observe: (options: NativeMutationObserverObserveOptions): void => {
|
|
const targetShadowNode = castToNode(options.targetShadowNode);
|
|
const rootTag = getMockDataFromShadowNode(options.targetShadowNode).rootTag;
|
|
|
|
let observers = observersByRootTag.get(rootTag);
|
|
if (observers == null) {
|
|
observers = new Map();
|
|
observersByRootTag.set(rootTag, observers);
|
|
}
|
|
let observations = observers.get(options.mutationObserverId);
|
|
if (observations == null) {
|
|
observations = {deep: new Set(), shallow: new Set()};
|
|
observers.set(options.mutationObserverId, observations);
|
|
}
|
|
|
|
const isTargetBeingObserved =
|
|
observations.deep.has(targetShadowNode) ||
|
|
observations.shallow.has(targetShadowNode);
|
|
invariant(!isTargetBeingObserved, 'unexpected duplicate call to observe');
|
|
|
|
if (options.subtree) {
|
|
observations.deep.add(targetShadowNode);
|
|
} else {
|
|
observations.shallow.add(targetShadowNode);
|
|
}
|
|
},
|
|
unobserve: (mutationObserverId: number, target: mixed): void => {
|
|
const targetShadowNode = castToNode(target);
|
|
|
|
const observers = observersByRootTag.get(
|
|
getMockDataFromShadowNode(targetShadowNode).rootTag,
|
|
);
|
|
const observations = observers?.get(mutationObserverId);
|
|
invariant(observations != null, 'unexpected call to unobserve');
|
|
|
|
const isTargetBeingObserved =
|
|
observations.deep.has(targetShadowNode) ||
|
|
observations.shallow.has(targetShadowNode);
|
|
invariant(isTargetBeingObserved, 'unexpected call to unobserve');
|
|
|
|
observations.deep.delete(targetShadowNode);
|
|
observations.shallow.delete(targetShadowNode);
|
|
},
|
|
connect: (
|
|
notifyMutationObserversCallback: () => void,
|
|
getPublicInstanceFromInstanceHandle: (
|
|
instanceHandle: InternalInstanceHandle,
|
|
) => mixed,
|
|
): void => {
|
|
invariant(callback == null, 'unexpected call to connect');
|
|
callback = notifyMutationObserversCallback;
|
|
getPublicInstance = getPublicInstanceFromInstanceHandle;
|
|
FabricUIManagerMock.__addCommitHook(NativeMutationObserverCommitHook);
|
|
},
|
|
disconnect: (): void => {
|
|
invariant(callback != null, 'unexpected call to disconnect');
|
|
callback = null;
|
|
FabricUIManagerMock.__removeCommitHook(NativeMutationObserverCommitHook);
|
|
},
|
|
takeRecords: (): $ReadOnlyArray<NativeMutationRecord> => {
|
|
const currentRecords = pendingRecords;
|
|
pendingRecords = [];
|
|
return currentRecords;
|
|
},
|
|
};
|
|
|
|
(NativeMutationMock: Spec);
|
|
|
|
export default NativeMutationMock;
|
|
|
|
const NativeMutationObserverCommitHook: UIManagerCommitHook = {
|
|
shadowTreeWillCommit: (rootTag, oldChildSet, newChildSet) => {
|
|
runMutationObservations(rootTag, oldChildSet, newChildSet);
|
|
},
|
|
};
|
|
|
|
function runMutationObservations(
|
|
rootTag: RootTag,
|
|
oldChildSet: ?NodeSet,
|
|
newChildSet: NodeSet,
|
|
): void {
|
|
const observers = observersByRootTag.get(rootTag);
|
|
if (!observers) {
|
|
return;
|
|
}
|
|
|
|
const newRecords: Array<NativeMutationRecord> = [];
|
|
|
|
for (const [mutationObserverId, observations] of observers) {
|
|
const processedNodes: Set<Node> = new Set();
|
|
for (const targetShadowNode of observations.deep) {
|
|
runMutationObservation({
|
|
mutationObserverId,
|
|
targetShadowNode,
|
|
subtree: true,
|
|
oldChildSet,
|
|
newChildSet,
|
|
newRecords,
|
|
processedNodes,
|
|
});
|
|
}
|
|
for (const targetShadowNode of observations.shallow) {
|
|
runMutationObservation({
|
|
mutationObserverId,
|
|
targetShadowNode,
|
|
subtree: false,
|
|
oldChildSet,
|
|
newChildSet,
|
|
newRecords,
|
|
processedNodes,
|
|
});
|
|
}
|
|
}
|
|
|
|
for (const record of newRecords) {
|
|
pendingRecords.push(record);
|
|
}
|
|
|
|
notifyObserversIfNecessary();
|
|
}
|
|
|
|
function findNodeOfSameFamily(list: NodeSet, node: Node): ?Node {
|
|
for (const current of list) {
|
|
if (fromNode(current).reactTag === fromNode(node).reactTag) {
|
|
return current;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
function recordMutations({
|
|
mutationObserverId,
|
|
targetShadowNode,
|
|
subtree,
|
|
oldNode,
|
|
newNode,
|
|
newRecords,
|
|
processedNodes,
|
|
}: {
|
|
mutationObserverId: MutationObserverId,
|
|
targetShadowNode: Node,
|
|
subtree: boolean,
|
|
oldNode: Node,
|
|
newNode: Node,
|
|
newRecords: Array<NativeMutationRecord>,
|
|
processedNodes: Set<Node>,
|
|
}): void {
|
|
// If the nodes are referentially equal, their children are also the same.
|
|
if (oldNode === newNode || processedNodes.has(newNode)) {
|
|
return;
|
|
}
|
|
|
|
processedNodes.add(newNode);
|
|
|
|
const oldChildren = fromNode(oldNode).children;
|
|
const newChildren = fromNode(newNode).children;
|
|
|
|
const addedNodes = [];
|
|
const removedNodes = [];
|
|
|
|
// Check for removed nodes (and equal nodes for further inspection later)
|
|
for (const oldChild of oldChildren) {
|
|
const newChild = findNodeOfSameFamily(newChildren, oldChild);
|
|
if (newChild == null) {
|
|
removedNodes.push(oldChild);
|
|
} else if (subtree) {
|
|
recordMutations({
|
|
mutationObserverId,
|
|
targetShadowNode,
|
|
subtree,
|
|
oldNode: oldChild,
|
|
newNode: newChild,
|
|
newRecords,
|
|
processedNodes,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check for added nodes
|
|
for (const newChild of newChildren) {
|
|
const oldChild = findNodeOfSameFamily(oldChildren, newChild);
|
|
if (oldChild == null) {
|
|
addedNodes.push(newChild);
|
|
}
|
|
}
|
|
|
|
if (addedNodes.length > 0 || removedNodes.length > 0) {
|
|
newRecords.push({
|
|
mutationObserverId: mutationObserverId,
|
|
target: nullthrows(getPublicInstance)(
|
|
getMockDataFromShadowNode(targetShadowNode).instanceHandle,
|
|
),
|
|
addedNodes: addedNodes.map(node => {
|
|
const readOnlyNode = nullthrows(getPublicInstance)(
|
|
fromNode(node).instanceHandle,
|
|
);
|
|
invariant(
|
|
readOnlyNode instanceof ReadOnlyNode,
|
|
'expected instance of ReadOnlyNode',
|
|
);
|
|
return readOnlyNode;
|
|
}),
|
|
removedNodes: removedNodes.map(node => {
|
|
const readOnlyNode = nullthrows(getPublicInstance)(
|
|
fromNode(node).instanceHandle,
|
|
);
|
|
invariant(
|
|
readOnlyNode instanceof ReadOnlyNode,
|
|
'expected instance of ReadOnlyNode',
|
|
);
|
|
return readOnlyNode;
|
|
}),
|
|
});
|
|
}
|
|
}
|
|
|
|
function runMutationObservation({
|
|
mutationObserverId,
|
|
targetShadowNode,
|
|
subtree,
|
|
oldChildSet,
|
|
newChildSet,
|
|
newRecords,
|
|
processedNodes,
|
|
}: {
|
|
mutationObserverId: MutationObserverId,
|
|
targetShadowNode: Node,
|
|
subtree: boolean,
|
|
oldChildSet: ?NodeSet,
|
|
newChildSet: NodeSet,
|
|
newRecords: Array<NativeMutationRecord>,
|
|
processedNodes: Set<Node>,
|
|
}): void {
|
|
if (!oldChildSet) {
|
|
return;
|
|
}
|
|
|
|
const oldTargetShadowNode = getNodeInChildSet(targetShadowNode, oldChildSet);
|
|
if (oldTargetShadowNode == null) {
|
|
return;
|
|
}
|
|
|
|
const newTargetShadowNode = getNodeInChildSet(targetShadowNode, newChildSet);
|
|
if (newTargetShadowNode == null) {
|
|
return;
|
|
}
|
|
|
|
recordMutations({
|
|
mutationObserverId,
|
|
targetShadowNode,
|
|
subtree,
|
|
oldNode: oldTargetShadowNode,
|
|
newNode: newTargetShadowNode,
|
|
newRecords,
|
|
processedNodes,
|
|
});
|
|
}
|
|
|
|
function notifyObserversIfNecessary(): void {
|
|
if (pendingRecords.length > 0) {
|
|
// We schedule these using regular tasks in native because microtasks are
|
|
// still not properly supported.
|
|
setTimeout(() => callback?.(), 0);
|
|
}
|
|
}
|