Skip to content

Commit a902267

Browse files
rubennortefacebook-github-bot
authored andcommitted
Implement ReactNativeDocument.prototype.getElementById (#53270)
Summary: Pull Request resolved: #53270 Changelog: [internal] (Marked as internal because the DOM APIs haven't been released yet). This implements `getElementById` in the DOM document API. Reviewed By: rshest Differential Revision: D69307133 fbshipit-source-id: 0c1454ae42fad92cddc705877b26052e887185bd
1 parent d55bbfe commit a902267

File tree

8 files changed

+178
-2
lines changed

8 files changed

+178
-2
lines changed

packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,23 @@ std::vector<jsi::Value> NativeDOM::getChildNodes(
172172
return getArrayOfInstanceHandlesFromShadowNodes(childNodes, rt);
173173
}
174174

175+
jsi::Value NativeDOM::getElementById(
176+
jsi::Runtime& rt,
177+
SurfaceId surfaceId,
178+
const std::string& id) {
179+
auto currentRevision = getCurrentShadowTreeRevision(rt, surfaceId);
180+
if (currentRevision == nullptr) {
181+
return jsi::Value::undefined();
182+
}
183+
184+
auto elementById = dom::getElementById(currentRevision, id);
185+
if (elementById == nullptr) {
186+
return jsi::Value::undefined();
187+
}
188+
189+
return elementById->getInstanceHandle(rt);
190+
}
191+
175192
jsi::Value NativeDOM::getParentNode(
176193
jsi::Runtime& rt,
177194
jsi::Value nativeNodeReference) {

packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ class NativeDOM : public NativeDOMCxxSpec<NativeDOM> {
4545
jsi::Runtime& rt,
4646
jsi::Value nativeNodeReference);
4747

48+
jsi::Value
49+
getElementById(jsi::Runtime& rt, SurfaceId surfaceId, const std::string& id);
50+
4851
jsi::Value getParentNode(jsi::Runtime& rt, jsi::Value nativeNodeReference);
4952

5053
bool isConnected(jsi::Runtime& rt, jsi::Value nativeNodeReference);

packages/react-native/ReactCommon/react/renderer/dom/DOM.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,23 @@ Rect getScrollableContentBounds(
156156

157157
} // namespace
158158

159+
std::shared_ptr<const ShadowNode> getElementById(
160+
const std::shared_ptr<const ShadowNode>& shadowNode,
161+
const std::string& id) {
162+
if (shadowNode->getProps()->nativeId == id) {
163+
return shadowNode;
164+
}
165+
166+
for (const auto& childNode : shadowNode->getChildren()) {
167+
auto result = getElementById(childNode, id);
168+
if (result != nullptr) {
169+
return result;
170+
}
171+
}
172+
173+
return nullptr;
174+
}
175+
159176
std::shared_ptr<const ShadowNode> getParentNode(
160177
const RootShadowNode::Shared& currentRevision,
161178
const ShadowNode& shadowNode) {

packages/react-native/ReactCommon/react/renderer/dom/DOM.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ std::shared_ptr<const ShadowNode> getParentNode(
6464
const RootShadowNode::Shared& currentRevision,
6565
const ShadowNode& shadowNode);
6666

67+
std::shared_ptr<const ShadowNode> getElementById(
68+
const std::shared_ptr<const ShadowNode>& currentRevision,
69+
const std::string& id);
70+
6771
std::vector<std::shared_ptr<const ShadowNode>> getChildNodes(
6872
const RootShadowNode::Shared& currentRevision,
6973
const ShadowNode& shadowNode);

packages/react-native/ReactNativeApi.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<41ecf4d1ea640f43ef6f21ae5670d30f>>
7+
* @generated SignedSource<<8ba6e82a1f08006665d4552574a773d4>>
88
*
99
* This file was generated by scripts/js-api/build-types/index.js.
1010
*/
@@ -3979,6 +3979,7 @@ declare class ReactNativeDocument_default extends ReadOnlyNode_default {
39793979
)
39803980
get documentElement(): ReactNativeElement_default
39813981
get firstElementChild(): null | ReadOnlyElement_default
3982+
getElementById(id: string): null | ReadOnlyElement_default
39823983
get lastElementChild(): null | ReadOnlyElement_default
39833984
get nodeName(): string
39843985
get nodeType(): number

packages/react-native/src/private/webapis/dom/nodes/ReactNativeDocument.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,30 @@ import type {RootTag} from '../../../../../Libraries/ReactNative/RootTag';
1414
import type {ViewConfig} from '../../../../../Libraries/Renderer/shims/ReactNativeTypes';
1515
import type HTMLCollection from '../oldstylecollections/HTMLCollection';
1616
import type {ReactNativeDocumentInstanceHandle} from './internals/ReactNativeDocumentInstanceHandle';
17-
import type ReadOnlyElement from './ReadOnlyElement';
1817

1918
import {createHTMLCollection} from '../oldstylecollections/HTMLCollection';
19+
import {getPublicInstanceFromInstanceHandle} from './internals/NodeInternals';
2020
import {
2121
createReactNativeDocumentElementInstanceHandle,
2222
setNativeElementReferenceForReactNativeDocumentElementInstanceHandle,
2323
setPublicInstanceForReactNativeDocumentElementInstanceHandle,
2424
} from './internals/ReactNativeDocumentElementInstanceHandle';
2525
import {createReactNativeDocumentInstanceHandle} from './internals/ReactNativeDocumentInstanceHandle';
2626
import ReactNativeElement from './ReactNativeElement';
27+
import ReadOnlyElement from './ReadOnlyElement';
2728
import ReadOnlyNode from './ReadOnlyNode';
2829
import NativeDOM from './specs/NativeDOM';
2930

3031
export default class ReactNativeDocument extends ReadOnlyNode {
32+
_rootTag: RootTag;
3133
_documentElement: ReactNativeElement;
3234

3335
constructor(
3436
rootTag: RootTag,
3537
instanceHandle: ReactNativeDocumentInstanceHandle,
3638
) {
3739
super(instanceHandle, null);
40+
this._rootTag = rootTag;
3841
this._documentElement = createDocumentElement(rootTag, this);
3942
}
4043

@@ -75,6 +78,23 @@ export default class ReactNativeDocument extends ReadOnlyNode {
7578
get textContent(): null {
7679
return null;
7780
}
81+
82+
getElementById(id: string): ReadOnlyElement | null {
83+
const elementByIdInstanceHandle = NativeDOM.getElementById(
84+
this._rootTag,
85+
id,
86+
);
87+
88+
if (elementByIdInstanceHandle == null) {
89+
return null;
90+
}
91+
92+
const elementById = getPublicInstanceFromInstanceHandle(
93+
elementByIdInstanceHandle,
94+
);
95+
96+
return elementById instanceof ReadOnlyElement ? elementById : null;
97+
}
7898
}
7999

80100
function createDocumentElement(

packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeDocument-itest.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,96 @@ describe('ReactNativeDocument', () => {
216216
expect(isUnreachable(weakDocument)).toBe(true);
217217
expect(isUnreachable(weakNode)).toBe(true);
218218
});
219+
220+
describe('getElementById', () => {
221+
it('returns the first element with the given ID, doing a depth-first search', () => {
222+
let lastNode;
223+
let fooNode;
224+
let barFirstNode;
225+
let barLastNode;
226+
let bazNode;
227+
228+
const root = Fantom.createRoot();
229+
Fantom.runTask(() => {
230+
root.render(
231+
<View
232+
ref={node => {
233+
lastNode = node;
234+
}}>
235+
<View
236+
id="foo"
237+
key="foo"
238+
ref={node => {
239+
fooNode = node;
240+
}}
241+
/>
242+
<View
243+
id="bar"
244+
key="bar"
245+
ref={node => {
246+
barFirstNode = node;
247+
}}
248+
/>
249+
<View key="parent">
250+
<View id="bar" />
251+
<View
252+
id="baz"
253+
ref={node => {
254+
bazNode = node;
255+
}}
256+
/>
257+
</View>
258+
</View>,
259+
);
260+
});
261+
262+
const element = ensureInstance(lastNode, ReactNativeElement);
263+
const document = ensureInstance(
264+
element.ownerDocument,
265+
ReactNativeDocument,
266+
);
267+
268+
expect(document.getElementById('foo')).toBe(fooNode);
269+
expect(document.getElementById('bar')).toBe(barFirstNode);
270+
expect(document.getElementById('baz')).toBe(bazNode);
271+
expect(document.getElementById('foobar')).toBe(null);
272+
273+
// Remove foo and first bar.
274+
Fantom.runTask(() => {
275+
root.render(
276+
<View
277+
ref={node => {
278+
lastNode = node;
279+
}}>
280+
<View key="parent">
281+
<View
282+
id="bar"
283+
ref={node => {
284+
barLastNode = node;
285+
}}
286+
/>
287+
<View
288+
id="baz"
289+
ref={node => {
290+
bazNode = node;
291+
}}
292+
/>
293+
</View>
294+
</View>,
295+
);
296+
});
297+
298+
expect(document.getElementById('foo')).toBe(null);
299+
expect(document.getElementById('bar')).toBe(barLastNode);
300+
expect(document.getElementById('baz')).toBe(bazNode);
301+
302+
Fantom.runTask(() => {
303+
root.destroy();
304+
});
305+
306+
expect(document.getElementById('foo')).toBe(null);
307+
expect(document.getElementById('bar')).toBe(null);
308+
expect(document.getElementById('baz')).toBe(null);
309+
});
310+
});
219311
});

packages/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ export interface Spec extends TurboModule {
6262
nativeNodeReference: mixed /* NativeNodeReference */,
6363
) => $ReadOnlyArray<mixed> /* $ReadOnlyArray<InstanceHandle> */;
6464

65+
+getElementById?: (
66+
nativeNodeReference: mixed /* NativeNodeReference */,
67+
id: string,
68+
) => mixed /* ?InstanceHandle */;
69+
6570
+getParentNode: (
6671
nativeNodeReference: mixed /* NativeNodeReference */,
6772
) => mixed /* ?InstanceHandle */;
@@ -204,6 +209,15 @@ export interface RefinedSpec {
204209
nativeNodeReference: NativeNodeReference,
205210
) => $ReadOnlyArray<InstanceHandle>;
206211

212+
/**
213+
* This is a React Native implementation of `Document.prototype.getElementById`
214+
* (see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById).
215+
*
216+
* If the document is active and contains an element with the given ID, it
217+
* returns the instance handle of that element. Otherwise, it returns `null`.
218+
*/
219+
+getElementById: (rootTag: RootTag, id: string) => ?InstanceHandle;
220+
207221
/**
208222
* This is a React Native implementation of `Node.prototype.parentNode`
209223
* (see https://developer.mozilla.org/en-US/docs/Web/API/Node/parentNode).
@@ -451,6 +465,14 @@ const NativeDOM: RefinedSpec = {
451465
): $ReadOnlyArray<InstanceHandle>);
452466
},
453467

468+
getElementById(rootTag, id) {
469+
// $FlowExpectedError[incompatible-cast]
470+
return (nullthrows(RawNativeDOM?.getElementById)(
471+
rootTag,
472+
id,
473+
): ?InstanceHandle);
474+
},
475+
454476
getParentNode(nativeNodeReference) {
455477
// $FlowExpectedError[incompatible-cast]
456478
return (nullthrows(RawNativeDOM).getParentNode(

0 commit comments

Comments
 (0)