mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Implement element.textContent
Summary: This implements the `textContent` property in `ReadOnlyElement` 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: D44464637 fbshipit-source-id: de7e4539b4ef70f3586f802fd07888d22678c239
This commit is contained in:
committed by
Facebook GitHub Bot
parent
528edd82bb
commit
245c405bd6
@@ -124,7 +124,13 @@ export default class ReadOnlyElement extends ReadOnlyNode {
|
||||
}
|
||||
|
||||
get textContent(): string | null {
|
||||
throw new TypeError('Unimplemented');
|
||||
const shadowNode = getShadowNode(this);
|
||||
|
||||
if (shadowNode != null) {
|
||||
return nullthrows(getFabricUIManager()).getTextContent(shadowNode);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
getBoundingClientRect(): DOMRect {
|
||||
|
||||
@@ -70,6 +70,7 @@ export type Spec = {|
|
||||
+getChildNodes: (node: Node) => $ReadOnlyArray<InternalInstanceHandle>,
|
||||
+isConnected: (node: Node) => boolean,
|
||||
+compareDocumentPosition: (node: Node, otherNode: Node) => number,
|
||||
+getTextContent: (node: Node) => string,
|
||||
+getBoundingClientRect: (
|
||||
node: Node,
|
||||
) => ?[
|
||||
|
||||
@@ -124,6 +124,18 @@ function getNodeInCurrentTree(node: Node): ?Node {
|
||||
return nodeInCurrentTree;
|
||||
}
|
||||
|
||||
function* dfs(node: ?Node): Iterator<Node> {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
yield node;
|
||||
|
||||
for (const child of fromNode(node).children) {
|
||||
yield* dfs(child);
|
||||
}
|
||||
}
|
||||
|
||||
const FabricUIManagerMock: FabricUIManager = {
|
||||
createNode: jest.fn(
|
||||
(
|
||||
@@ -288,6 +300,27 @@ const FabricUIManagerMock: FabricUIManager = {
|
||||
isConnected: jest.fn((node: Node): boolean => {
|
||||
return getNodeInCurrentTree(node) != null;
|
||||
}),
|
||||
getTextContent: jest.fn((node: Node): string => {
|
||||
const nodeInCurrentTree = getNodeInCurrentTree(node);
|
||||
|
||||
let result = '';
|
||||
|
||||
if (nodeInCurrentTree == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const childNode of dfs(nodeInCurrentTree)) {
|
||||
if (fromNode(childNode).viewName === 'RCTRawText') {
|
||||
const props = fromNode(childNode).props;
|
||||
// $FlowExpectedError[prop-missing]
|
||||
const maybeString: ?string = props.text;
|
||||
if (typeof maybeString === 'string') {
|
||||
result += maybeString;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}),
|
||||
compareDocumentPosition: jest.fn((node: Node, otherNode: Node): number => {
|
||||
/* eslint-disable no-bitwise */
|
||||
const ReadOnlyNode = require('../../DOM/Nodes/ReadOnlyNode').default;
|
||||
|
||||
@@ -64,7 +64,6 @@ rn_xplat_cxx_library(
|
||||
react_native_xplat_target("react/renderer/graphics:graphics"),
|
||||
react_native_xplat_target("react/renderer/textlayoutmanager:textlayoutmanager"),
|
||||
react_native_xplat_target("react/renderer/components/view:view"),
|
||||
react_native_xplat_target("react/renderer/uimanager:uimanager"),
|
||||
react_native_xplat_target("react/renderer/mounting:mounting"),
|
||||
react_native_xplat_target("react/renderer/componentregistry:componentregistry"),
|
||||
react_native_xplat_target("react/utils:utils"),
|
||||
|
||||
@@ -125,7 +125,6 @@ rn_xplat_cxx_library(
|
||||
react_native_xplat_target("react/utils:utils"),
|
||||
react_native_xplat_target("react/renderer/debug:debug"),
|
||||
react_native_xplat_target("react/renderer/graphics:graphics"),
|
||||
react_native_xplat_target("react/renderer/uimanager:uimanager"),
|
||||
react_native_xplat_target("react/renderer/mounting:mounting"),
|
||||
react_native_xplat_target("react/renderer/componentregistry:componentregistry"),
|
||||
],
|
||||
|
||||
@@ -56,6 +56,7 @@ rn_xplat_cxx_library(
|
||||
"//xplat/jsi:jsi",
|
||||
react_native_xplat_target("react/config:config"),
|
||||
react_native_xplat_target("react/debug:debug"),
|
||||
react_native_xplat_target("react/renderer/components/text:text"),
|
||||
react_native_xplat_target("react/renderer/components/view:view"),
|
||||
react_native_xplat_target("react/renderer/mounting:mounting"),
|
||||
react_native_xplat_target("react/renderer/core:core"),
|
||||
|
||||
@@ -296,6 +296,14 @@ ShadowNode::Shared UIManager::getNewestParentOfShadowNode(
|
||||
parentOfParentPair.second);
|
||||
}
|
||||
|
||||
std::string UIManager::getTextContentInNewestCloneOfShadowNode(
|
||||
ShadowNode const &shadowNode) const {
|
||||
auto newestCloneOfShadowNode = getNewestCloneOfShadowNode(shadowNode);
|
||||
std::string result;
|
||||
getTextContentInShadowNode(*newestCloneOfShadowNode, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
int UIManager::compareDocumentPosition(
|
||||
ShadowNode const &shadowNode,
|
||||
ShadowNode const &otherShadowNode) const {
|
||||
|
||||
@@ -88,6 +88,9 @@ class UIManager final : public ShadowTreeDelegate {
|
||||
ShadowNode::Shared getNewestParentOfShadowNode(
|
||||
ShadowNode const &shadowNode) const;
|
||||
|
||||
std::string getTextContentInNewestCloneOfShadowNode(
|
||||
ShadowNode const &shadowNode) const;
|
||||
|
||||
int compareDocumentPosition(
|
||||
ShadowNode const &shadowNode,
|
||||
ShadowNode const &otherShadowNode) const;
|
||||
|
||||
@@ -892,6 +892,37 @@ jsi::Value UIManagerBinding::get(
|
||||
});
|
||||
}
|
||||
|
||||
if (methodName == "getTextContent") {
|
||||
// This is a React Native implementation of
|
||||
// `Element.prototype.textContent` (see
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/textContent).
|
||||
|
||||
// It uses the version of the shadow node that is present in the current
|
||||
// revision of the shadow tree.
|
||||
// If the version is present, is traverses all its children in DFS and
|
||||
// concatenates all the text contents. Otherwise, it returns an empty
|
||||
// string.
|
||||
|
||||
// getTextContent(shadowNode: ShadowNode): string
|
||||
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 textContent =
|
||||
uiManager->getTextContentInNewestCloneOfShadowNode(*shadowNode);
|
||||
|
||||
return jsi::Value(
|
||||
runtime, jsi::String::createFromUtf8(runtime, textContent));
|
||||
});
|
||||
}
|
||||
|
||||
return jsi::Value::undefined();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
#include <jsi/JSIDynamic.h>
|
||||
#include <jsi/jsi.h>
|
||||
#include <react/debug/react_native_assert.h>
|
||||
#include <react/renderer/components/text/RawTextShadowNode.h>
|
||||
#include <react/renderer/core/CoreFeatures.h>
|
||||
#include <react/renderer/core/EventHandler.h>
|
||||
#include <react/renderer/core/ShadowNode.h>
|
||||
#include <react/renderer/core/TraitCast.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
@@ -219,4 +221,17 @@ inline static jsi::Value getArrayOfInstanceHandlesFromShadowNodes(
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline static void getTextContentInShadowNode(
|
||||
ShadowNode const &shadowNode,
|
||||
std::string &result) {
|
||||
auto rawTextShadowNode = traitCast<RawTextShadowNode const *>(&shadowNode);
|
||||
if (rawTextShadowNode != nullptr) {
|
||||
result.append(rawTextShadowNode->getConcreteProps().text);
|
||||
}
|
||||
|
||||
for (auto const &childNode : shadowNode.getChildren()) {
|
||||
getTextContentInShadowNode(*childNode.get(), result);
|
||||
}
|
||||
}
|
||||
} // namespace facebook::react
|
||||
|
||||
Reference in New Issue
Block a user