mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Implement node.compareDocumentPosition and node.contains
Summary: This implements the `compareDocumentPosition` and `contains` methods (contains is implemented in JS) as defined in https://github.com/react-native-community/discussions-and-proposals/pull/607. This requires a new method in Fabric because we can't derive that from the existing methods. Changelog: [internal] bypass-github-export-checks Reviewed By: rshest Differential Revision: D44370433 fbshipit-source-id: a7c12a35955e9ccbf8eb19f394125bf05cb995ce
This commit is contained in:
committed by
Facebook GitHub Bot
parent
03430ca660
commit
528edd82bb
+21
-2
@@ -158,13 +158,32 @@ export default class ReadOnlyNode {
|
||||
}
|
||||
|
||||
compareDocumentPosition(otherNode: ReadOnlyNode): number {
|
||||
throw new TypeError('Unimplemented');
|
||||
// Quick check to avoid having to call into Fabric if the nodes are the same.
|
||||
if (otherNode === this) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const shadowNode = getShadowNode(this);
|
||||
const otherShadowNode = getShadowNode(otherNode);
|
||||
|
||||
if (shadowNode == null || otherShadowNode == null) {
|
||||
return ReadOnlyNode.DOCUMENT_POSITION_DISCONNECTED;
|
||||
}
|
||||
|
||||
return nullthrows(getFabricUIManager()).compareDocumentPosition(
|
||||
shadowNode,
|
||||
otherShadowNode,
|
||||
);
|
||||
}
|
||||
|
||||
contains(otherNode: ReadOnlyNode): boolean {
|
||||
if (otherNode === this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const position = this.compareDocumentPosition(otherNode);
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return (position & ReadOnlyNode.DOCUMENT_POSITION_CONTAINS) !== 0;
|
||||
return (position & ReadOnlyNode.DOCUMENT_POSITION_CONTAINED_BY) !== 0;
|
||||
}
|
||||
|
||||
getRootNode(): ReadOnlyNode {
|
||||
|
||||
@@ -56,6 +56,20 @@ export type Spec = {|
|
||||
) => void,
|
||||
+sendAccessibilityEvent: (node: Node, eventType: string) => void,
|
||||
+findShadowNodeByTag_DEPRECATED: (reactTag: number) => ?Node,
|
||||
+setNativeProps: (node: Node, newProps: NodeProps) => void,
|
||||
+dispatchCommand: (
|
||||
node: Node,
|
||||
commandName: string,
|
||||
args: Array<mixed>,
|
||||
) => void,
|
||||
|
||||
/**
|
||||
* Support methods for the DOM-compatible APIs.
|
||||
*/
|
||||
+getParentNode: (node: Node) => ?InternalInstanceHandle,
|
||||
+getChildNodes: (node: Node) => $ReadOnlyArray<InternalInstanceHandle>,
|
||||
+isConnected: (node: Node) => boolean,
|
||||
+compareDocumentPosition: (node: Node, otherNode: Node) => number,
|
||||
+getBoundingClientRect: (
|
||||
node: Node,
|
||||
) => ?[
|
||||
@@ -64,15 +78,6 @@ export type Spec = {|
|
||||
/* width:*/ number,
|
||||
/* height:*/ number,
|
||||
],
|
||||
+setNativeProps: (node: Node, newProps: NodeProps) => void,
|
||||
+dispatchCommand: (
|
||||
node: Node,
|
||||
commandName: string,
|
||||
args: Array<mixed>,
|
||||
) => void,
|
||||
+getParentNode: (node: Node) => ?InternalInstanceHandle,
|
||||
+getChildNodes: (node: Node) => $ReadOnlyArray<InternalInstanceHandle>,
|
||||
+isConnected: (node: Node) => boolean,
|
||||
|};
|
||||
|
||||
// This is exposed as a getter because apps using the legacy renderer AND
|
||||
|
||||
@@ -288,6 +288,59 @@ const FabricUIManagerMock: FabricUIManager = {
|
||||
isConnected: jest.fn((node: Node): boolean => {
|
||||
return getNodeInCurrentTree(node) != null;
|
||||
}),
|
||||
compareDocumentPosition: jest.fn((node: Node, otherNode: Node): number => {
|
||||
/* eslint-disable no-bitwise */
|
||||
const ReadOnlyNode = require('../../DOM/Nodes/ReadOnlyNode').default;
|
||||
|
||||
// Quick check for node vs. itself
|
||||
if (fromNode(node).reactTag === fromNode(otherNode).reactTag) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fromNode(node).rootTag !== fromNode(otherNode).rootTag) {
|
||||
return ReadOnlyNode.DOCUMENT_POSITION_DISCONNECTED;
|
||||
}
|
||||
|
||||
const ancestors = getAncestorsInCurrentTree(node);
|
||||
if (ancestors == null) {
|
||||
return ReadOnlyNode.DOCUMENT_POSITION_DISCONNECTED;
|
||||
}
|
||||
|
||||
const otherAncestors = getAncestorsInCurrentTree(otherNode);
|
||||
if (otherAncestors == null) {
|
||||
return ReadOnlyNode.DOCUMENT_POSITION_DISCONNECTED;
|
||||
}
|
||||
|
||||
// Consume all common ancestors
|
||||
let i = 0;
|
||||
while (
|
||||
i < ancestors.length &&
|
||||
i < otherAncestors.length &&
|
||||
ancestors[i][1] === otherAncestors[i][1]
|
||||
) {
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i === ancestors.length) {
|
||||
return (
|
||||
ReadOnlyNode.DOCUMENT_POSITION_CONTAINED_BY |
|
||||
ReadOnlyNode.DOCUMENT_POSITION_FOLLOWING
|
||||
);
|
||||
}
|
||||
|
||||
if (i === otherAncestors.length) {
|
||||
return (
|
||||
ReadOnlyNode.DOCUMENT_POSITION_CONTAINS |
|
||||
ReadOnlyNode.DOCUMENT_POSITION_PRECEDING
|
||||
);
|
||||
}
|
||||
|
||||
if (ancestors[i][1] > otherAncestors[i][1]) {
|
||||
return ReadOnlyNode.DOCUMENT_POSITION_PRECEDING;
|
||||
}
|
||||
|
||||
return ReadOnlyNode.DOCUMENT_POSITION_FOLLOWING;
|
||||
}),
|
||||
};
|
||||
|
||||
global.nativeFabricUIManager = FabricUIManagerMock;
|
||||
|
||||
@@ -21,6 +21,14 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
constexpr int DOCUMENT_POSITION_DISCONNECTED = 1;
|
||||
constexpr int DOCUMENT_POSITION_PRECEDING = 2;
|
||||
constexpr int DOCUMENT_POSITION_FOLLOWING = 4;
|
||||
constexpr int DOCUMENT_POSITION_CONTAINS = 8;
|
||||
constexpr int DOCUMENT_POSITION_CONTAINED_BY = 16;
|
||||
} // namespace
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
// Explicitly define destructors here, as they to exist in order to act as a
|
||||
@@ -288,6 +296,60 @@ ShadowNode::Shared UIManager::getNewestParentOfShadowNode(
|
||||
parentOfParentPair.second);
|
||||
}
|
||||
|
||||
int UIManager::compareDocumentPosition(
|
||||
ShadowNode const &shadowNode,
|
||||
ShadowNode const &otherShadowNode) const {
|
||||
// Quick check for node vs. itself
|
||||
if (&shadowNode == &otherShadowNode) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (shadowNode.getSurfaceId() != otherShadowNode.getSurfaceId()) {
|
||||
return DOCUMENT_POSITION_DISCONNECTED;
|
||||
}
|
||||
|
||||
auto ancestorShadowNode = ShadowNode::Shared{};
|
||||
shadowTreeRegistry_.visit(
|
||||
shadowNode.getSurfaceId(), [&](ShadowTree const &shadowTree) {
|
||||
ancestorShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
|
||||
});
|
||||
if (!ancestorShadowNode) {
|
||||
return DOCUMENT_POSITION_DISCONNECTED;
|
||||
}
|
||||
|
||||
auto ancestors = shadowNode.getFamily().getAncestors(*ancestorShadowNode);
|
||||
if (ancestors.empty()) {
|
||||
return DOCUMENT_POSITION_DISCONNECTED;
|
||||
}
|
||||
|
||||
auto otherAncestors =
|
||||
otherShadowNode.getFamily().getAncestors(*ancestorShadowNode);
|
||||
if (ancestors.empty()) {
|
||||
return DOCUMENT_POSITION_DISCONNECTED;
|
||||
}
|
||||
|
||||
// Consume all common ancestors
|
||||
size_t i = 0;
|
||||
while (i < ancestors.size() && i < otherAncestors.size() &&
|
||||
ancestors[i].second == otherAncestors[i].second) {
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i == ancestors.size()) {
|
||||
return (DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING);
|
||||
}
|
||||
|
||||
if (i == otherAncestors.size()) {
|
||||
return (DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING);
|
||||
}
|
||||
|
||||
if (ancestors[i].second > otherAncestors[i].second) {
|
||||
return DOCUMENT_POSITION_PRECEDING;
|
||||
}
|
||||
|
||||
return DOCUMENT_POSITION_FOLLOWING;
|
||||
}
|
||||
|
||||
ShadowNode::Shared UIManager::findNodeAtPoint(
|
||||
ShadowNode::Shared const &node,
|
||||
Point point) const {
|
||||
|
||||
@@ -88,6 +88,10 @@ class UIManager final : public ShadowTreeDelegate {
|
||||
ShadowNode::Shared getNewestParentOfShadowNode(
|
||||
ShadowNode const &shadowNode) const;
|
||||
|
||||
int compareDocumentPosition(
|
||||
ShadowNode const &shadowNode,
|
||||
ShadowNode const &otherShadowNode) const;
|
||||
|
||||
#pragma mark - Surface Start & Stop
|
||||
|
||||
void startSurface(
|
||||
|
||||
@@ -862,6 +862,36 @@ jsi::Value UIManagerBinding::get(
|
||||
});
|
||||
}
|
||||
|
||||
if (methodName == "compareDocumentPosition") {
|
||||
// This is a React Native implementation of
|
||||
// `Node.prototype.compareDocumentPosition` (see
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition).
|
||||
|
||||
// It uses the version of the shadow nodes that are present in the current
|
||||
// revision of the shadow tree (if any). If any of the nodes is not present,
|
||||
// it just indicates they are disconnected.
|
||||
|
||||
// compareDocumentPosition(shadowNode: ShadowNode, otherShadowNode:
|
||||
// ShadowNode): number
|
||||
return jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
name,
|
||||
1,
|
||||
[uiManager](
|
||||
jsi::Runtime &runtime,
|
||||
jsi::Value const & /*thisValue*/,
|
||||
jsi::Value const *arguments,
|
||||
size_t /*count*/) noexcept -> jsi::Value {
|
||||
auto shadowNode = shadowNodeFromValue(runtime, arguments[0]);
|
||||
auto otherShadowNode = shadowNodeFromValue(runtime, arguments[1]);
|
||||
|
||||
auto documentPosition =
|
||||
uiManager->compareDocumentPosition(*shadowNode, *otherShadowNode);
|
||||
|
||||
return jsi::Value(documentPosition);
|
||||
});
|
||||
}
|
||||
|
||||
return jsi::Value::undefined();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user