Skip to content

Commit 016e4af

Browse files
rubennortefacebook-github-bot
authored andcommitted
Consider transforms correctly in some of the new DOM layout methods (#39349)
Summary: Pull Request resolved: #39349 This fixes these methods to ignore transforms, as per the spec: * `offsetLeft` * `offsetTop` * `offsetWidth` * `offsetHeight` * `clientLeft` * `clientTop` * `clientWidth` * `clientHeight` `scrollWidth` and `scrollHeight` are the last methods we need to fix, as their fix is more complex than in these cases (in scroll views, the scrollable area is the overflow of all its children with transforms applied, which is an expensive computation we don't currently do, even in host platforms where this behavior doesn't work correctly). Changelog: [internal] Reviewed By: NickGerleman Differential Revision: D49069517 fbshipit-source-id: 3c4b897c904e33514cbeefa8ee317d3c2e4a1280
1 parent 836511e commit 016e4af

File tree

6 files changed

+51
-24
lines changed

6 files changed

+51
-24
lines changed

packages/react-native/Libraries/DOM/Nodes/ReactNativeElement.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import TextInputState from '../../Components/TextInput/TextInputState';
2525
import {getFabricUIManager} from '../../ReactNative/FabricUIManager';
2626
import {create as createAttributePayload} from '../../ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload';
2727
import warnForStyleProps from '../../ReactNative/ReactFabricPublicInstance/warnForStyleProps';
28-
import ReadOnlyElement from './ReadOnlyElement';
28+
import ReadOnlyElement, {getBoundingClientRect} from './ReadOnlyElement';
2929
import ReadOnlyNode from './ReadOnlyNode';
3030
import {
3131
getPublicInstanceFromInternalInstanceHandle,
@@ -58,7 +58,9 @@ export default class ReactNativeElement
5858
}
5959

6060
get offsetHeight(): number {
61-
return Math.round(this.getBoundingClientRect().height);
61+
return Math.round(
62+
getBoundingClientRect(this, {includeTransform: false}).height,
63+
);
6264
}
6365

6466
get offsetLeft(): number {
@@ -110,7 +112,9 @@ export default class ReactNativeElement
110112
}
111113

112114
get offsetWidth(): number {
113-
return Math.round(this.getBoundingClientRect().width);
115+
return Math.round(
116+
getBoundingClientRect(this, {includeTransform: false}).width,
117+
);
114118
}
115119

116120
/**

packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -211,20 +211,7 @@ export default class ReadOnlyElement extends ReadOnlyNode {
211211
}
212212

213213
getBoundingClientRect(): DOMRect {
214-
const shadowNode = getShadowNode(this);
215-
216-
if (shadowNode != null) {
217-
const rect = nullthrows(getFabricUIManager()).getBoundingClientRect(
218-
shadowNode,
219-
);
220-
221-
if (rect) {
222-
return new DOMRect(rect[0], rect[1], rect[2], rect[3]);
223-
}
224-
}
225-
226-
// Empty rect if any of the above failed
227-
return new DOMRect(0, 0, 0, 0);
214+
return getBoundingClientRect(this, {includeTransform: true});
228215
}
229216

230217
/**
@@ -262,3 +249,29 @@ function getChildElements(node: ReadOnlyNode): $ReadOnlyArray<ReadOnlyElement> {
262249
childNode => childNode instanceof ReadOnlyElement,
263250
);
264251
}
252+
253+
/**
254+
* The public API for `getBoundingClientRect` always includes transform,
255+
* so we use this internal version to get the data without transform to
256+
* implement methods like `offsetWidth` and `offsetHeight`.
257+
*/
258+
export function getBoundingClientRect(
259+
node: ReadOnlyElement,
260+
{includeTransform}: {includeTransform: boolean},
261+
): DOMRect {
262+
const shadowNode = getShadowNode(node);
263+
264+
if (shadowNode != null) {
265+
const rect = nullthrows(getFabricUIManager()).getBoundingClientRect(
266+
shadowNode,
267+
includeTransform,
268+
);
269+
270+
if (rect) {
271+
return new DOMRect(rect[0], rect[1], rect[2], rect[3]);
272+
}
273+
}
274+
275+
// Empty rect if any of the above failed
276+
return new DOMRect(0, 0, 0, 0);
277+
}

packages/react-native/Libraries/ReactNative/FabricUIManager.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export interface Spec {
7575
+getTextContent: (node: Node) => string;
7676
+getBoundingClientRect: (
7777
node: Node,
78+
includeTransform: boolean,
7879
) => ?[
7980
/* x: */ number,
8081
/* y: */ number,

packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export default class ReactFabricHostComponent implements INativeMethods {
124124
this.__internalInstanceHandle,
125125
);
126126
if (node != null) {
127-
const rect = fabricGetBoundingClientRect(node);
127+
const rect = fabricGetBoundingClientRect(node, true);
128128

129129
if (rect) {
130130
return new DOMRect(rect[0], rect[1], rect[2], rect[3]);

packages/react-native/Libraries/ReactNative/__mocks__/FabricUIManager.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
295295
getBoundingClientRect: jest.fn(
296296
(
297297
node: Node,
298+
includeTransform: boolean,
298299
): ?[
299300
/* x:*/ number,
300301
/* y:*/ number,

packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -841,14 +841,20 @@ jsi::Value UIManagerBinding::get(
841841
// This is similar to `measureInWindow`, except it's explicitly synchronous
842842
// (returns the result instead of passing it to a callback).
843843

844-
// getBoundingClientRect(shadowNode: ShadowNode):
844+
// It allows indicating whether to include transforms so it can also be used
845+
// to implement methods like
846+
// [`offsetWidth`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetWidth)
847+
// and
848+
// [`offsetHeight`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetHeight).
849+
850+
// getBoundingClientRect(shadowNode: ShadowNode, includeTransform: boolean):
845851
// [
846852
// /* x: */ number,
847853
// /* y: */ number,
848854
// /* width: */ number,
849855
// /* height: */ number
850856
// ]
851-
auto paramCount = 1;
857+
auto paramCount = 2;
852858
return jsi::Function::createFromHostFunction(
853859
runtime,
854860
name,
@@ -860,10 +866,12 @@ jsi::Value UIManagerBinding::get(
860866
size_t count) -> jsi::Value {
861867
validateArgumentCount(runtime, methodName, paramCount, count);
862868

869+
bool includeTransform = arguments[1].getBool();
870+
863871
auto layoutMetrics = uiManager->getRelativeLayoutMetrics(
864872
*shadowNodeFromValue(runtime, arguments[0]),
865873
nullptr,
866-
{/* .includeTransform = */ true,
874+
{/* .includeTransform = */ includeTransform,
867875
/* .includeViewportOffset = */ true});
868876

869877
if (layoutMetrics == EmptyLayoutMetrics) {
@@ -1101,7 +1109,7 @@ jsi::Value UIManagerBinding::get(
11011109
// If the node is not displayed (itself or any of its ancestors has
11021110
// "display: none"), this returns an empty layout metrics object.
11031111
auto layoutMetrics = uiManager->getRelativeLayoutMetrics(
1104-
*shadowNode, nullptr, {/* .includeTransform = */ true});
1112+
*shadowNode, nullptr, {/* .includeTransform = */ false});
11051113

11061114
if (layoutMetrics == EmptyLayoutMetrics) {
11071115
return jsi::Value::undefined();
@@ -1313,7 +1321,7 @@ jsi::Value UIManagerBinding::get(
13131321
// If the node is not displayed (itself or any of its ancestors has
13141322
// "display: none"), this returns an empty layout metrics object.
13151323
auto layoutMetrics = uiManager->getRelativeLayoutMetrics(
1316-
*shadowNode, nullptr, {/* .includeTransform = */ true});
1324+
*shadowNode, nullptr, {/* .includeTransform = */ false});
13171325

13181326
if (layoutMetrics == EmptyLayoutMetrics ||
13191327
layoutMetrics.displayType == DisplayType::Inline) {
@@ -1367,7 +1375,7 @@ jsi::Value UIManagerBinding::get(
13671375
// If the node is not displayed (itself or any of its ancestors has
13681376
// "display: none"), this returns an empty layout metrics object.
13691377
auto layoutMetrics = uiManager->getRelativeLayoutMetrics(
1370-
*shadowNode, nullptr, {/* .includeTransform = */ true});
1378+
*shadowNode, nullptr, {/* .includeTransform = */ false});
13711379

13721380
if (layoutMetrics == EmptyLayoutMetrics ||
13731381
layoutMetrics.displayType == DisplayType::Inline) {

0 commit comments

Comments
 (0)