Skip to content

Commit cb2a2f9

Browse files
authored
fix(new arch): change measure function to measure native view hierarchy on UI thread (facebook#66)
* fix(new arch): change measure function to measure native view hierarchy on UI thread * fix ios compile errors * add second parameter `measureOnUI` to .measure function which we only set to true for pressability
1 parent a1044cf commit cb2a2f9

19 files changed

Lines changed: 475 additions & 306 deletions

File tree

packages/react-native/Libraries/Pressability/Pressability.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,7 @@ export default class Pressability {
805805
if (typeof this._responderID === 'number') {
806806
UIManager.measure(this._responderID, this._measureCallback);
807807
} else {
808-
this._responderID.measure(this._measureCallback);
808+
this._responderID.measure(this._measureCallback, true /* measureOnUI - will measure native view hierarchy */);
809809
}
810810
}
811811

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export interface Spec {
4040
+appendChild: (parentNode: Node, child: Node) => Node;
4141
+appendChildToSet: (childSet: NodeSet, child: Node) => void;
4242
+completeRoot: (rootTag: RootTag, childSet: NodeSet) => void;
43-
+measure: (node: Node, callback: MeasureOnSuccessCallback) => void;
43+
+measure: (node: Node, callback: MeasureOnSuccessCallback, measureOnUI: Boolean) => void;
4444
+measureInWindow: (
4545
node: Node,
4646
callback: MeasureInWindowOnSuccessCallback,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ export default class ReactFabricHostComponent implements INativeMethods {
6565
TextInputState.focusTextInput(this);
6666
}
6767

68-
measure(callback: MeasureOnSuccessCallback) {
68+
measure(callback: MeasureOnSuccessCallback, measureOnUI = false) {
6969
const node = getNodeFromInternalInstanceHandle(
7070
this.__internalInstanceHandle,
7171
);
7272
if (node != null) {
73-
fabricMeasure(node, callback);
73+
fabricMeasure(node, callback, measureOnUI);
7474
}
7575
}
7676

packages/react-native/React/Fabric/RCTScheduler.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ NS_ASSUME_NONNULL_BEGIN
4242
blockNativeResponder:(BOOL)blockNativeResponder
4343
forShadowView:(const facebook::react::ShadowView &)shadowView;
4444

45+
46+
- (void)schedulerMeasure:(const facebook::react::ShadowView &)shadowView
47+
jsCallback:(std::function<void(folly::dynamic)>)jsCallback;
48+
4549
@end
4650

4751
/**

packages/react-native/React/Fabric/RCTScheduler.mm

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ void schedulerDidSendAccessibilityEvent(const ShadowView &shadowView, const std:
6767
RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_;
6868
[scheduler.delegate schedulerDidSendAccessibilityEvent:shadowView eventType:eventType];
6969
}
70+
71+
72+
void schedulerMeasure(const ShadowView& shadowView, std::function<void(folly::dynamic)> jsCallback) override {
73+
RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_;
74+
[scheduler.delegate schedulerMeasure:shadowView jsCallback:jsCallback];
75+
}
7076

7177
private:
7278
void *scheduler_;

packages/react-native/React/Fabric/RCTSurfacePresenter.mm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,15 @@ - (void)removeObserver:(id<RCTSurfacePresenterObserver>)observer
342342
}
343343
}
344344

345+
- (void)schedulerMeasure:(const facebook::react::ShadowView &)shadowView jsCallback:(std::function<void (folly::dynamic)>)jsCallback {
346+
// TODO: do we need to implement this on iOS? It seems to _just work_
347+
// dispatch_async(dispatch_get_main_queue(), ^{
348+
// ReactTag tag = shadowView.tag;
349+
// UIView<RCTComponentViewProtocol> *componentView =
350+
// [self->_mountingManager.componentViewRegistry findComponentViewWithTag:tag];
351+
// });
352+
}
353+
345354
#pragma mark - RCTMountingManagerDelegate
346355

347356
- (void)mountingManager:(RCTMountingManager *)mountingManager willMountComponentsWithRootTag:(ReactTag)rootTag

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.facebook.common.logging.FLog;
3333
import com.facebook.infer.annotation.ThreadConfined;
3434
import com.facebook.proguard.annotations.DoNotStripAny;
35+
import com.facebook.react.bridge.Callback;
3536
import com.facebook.react.bridge.ColorPropConverter;
3637
import com.facebook.react.bridge.GuardedRunnable;
3738
import com.facebook.react.bridge.LifecycleEventListener;
@@ -1183,6 +1184,27 @@ public String toString() {
11831184
});
11841185
}
11851186

1187+
public void measure(int surfaceId, int reactTag, final Callback callback) {
1188+
mMountItemDispatcher.addMountItem(
1189+
new MountItem() {
1190+
@Override
1191+
public void execute(@NonNull MountingManager mountingManager) {
1192+
mMountingManager.measure(surfaceId, reactTag, callback);
1193+
}
1194+
1195+
@Override
1196+
public int getSurfaceId() {
1197+
return surfaceId;
1198+
}
1199+
1200+
@NonNull
1201+
@Override
1202+
public String toString() {
1203+
return "MEASURE_VIEW";
1204+
}
1205+
});
1206+
}
1207+
11861208
@Override
11871209
public void profileNextBatch() {
11881210
// TODO T31905686: Remove this method and add support for multi-threading performance counters

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@
1010
import static com.facebook.infer.annotation.ThreadConfined.ANY;
1111
import static com.facebook.infer.annotation.ThreadConfined.UI;
1212

13+
import android.graphics.Matrix;
14+
import android.graphics.RectF;
1315
import android.view.View;
16+
import android.view.ViewParent;
17+
1418
import androidx.annotation.AnyThread;
1519
import androidx.annotation.NonNull;
1620
import androidx.annotation.Nullable;
1721
import androidx.annotation.UiThread;
1822
import com.facebook.common.logging.FLog;
1923
import com.facebook.infer.annotation.ThreadConfined;
24+
import com.facebook.react.bridge.Callback;
2025
import com.facebook.react.bridge.ReactContext;
2126
import com.facebook.react.bridge.ReactSoftExceptionLogger;
2227
import com.facebook.react.bridge.ReadableArray;
@@ -30,12 +35,15 @@
3035
import com.facebook.react.fabric.events.EventEmitterWrapper;
3136
import com.facebook.react.fabric.mounting.mountitems.MountItem;
3237
import com.facebook.react.touch.JSResponderHandler;
38+
import com.facebook.react.uimanager.PixelUtil;
3339
import com.facebook.react.uimanager.RootViewManager;
40+
import com.facebook.react.uimanager.RootViewUtil;
3441
import com.facebook.react.uimanager.ThemedReactContext;
3542
import com.facebook.react.uimanager.ViewManagerRegistry;
3643
import com.facebook.react.uimanager.common.ViewUtil;
3744
import com.facebook.react.uimanager.events.EventCategoryDef;
3845
import com.facebook.yoga.YogaMeasureMode;
46+
3947
import java.util.Map;
4048
import java.util.Queue;
4149
import java.util.concurrent.ConcurrentHashMap;
@@ -472,4 +480,64 @@ public void enqueuePendingEvent(
472480
? getSurfaceManagerForView(reactTag)
473481
: getSurfaceManager(surfaceId));
474482
}
483+
484+
public void measure(int surfaceId, int reactTag, final Callback callback) {
485+
UiThreadUtil.assertOnUiThread();
486+
SurfaceMountingManager smm = getSurfaceMountingManager(surfaceId, reactTag);
487+
View view = smm.getView(reactTag);
488+
int[] mMeasureBuffer = new int[4];
489+
measure(view, mMeasureBuffer);
490+
491+
float x = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
492+
float y = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
493+
float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
494+
float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
495+
callback.invoke(0, 0, width, height, x, y);
496+
}
497+
498+
public synchronized void measure(View v, int[] outputBuffer) {
499+
View rootView = (View) RootViewUtil.getRootView(v);
500+
computeBoundingBox(rootView, outputBuffer);
501+
int rootX = outputBuffer[0];
502+
int rootY = outputBuffer[1];
503+
computeBoundingBox(v, outputBuffer);
504+
outputBuffer[0] -= rootX;
505+
outputBuffer[1] -= rootY;
506+
}
507+
508+
private void computeBoundingBox(View view, int[] outputBuffer) {
509+
RectF mBoundingBox = new RectF();
510+
mBoundingBox.set(0, 0, view.getWidth(), view.getHeight());
511+
mapRectFromViewToWindowCoords(view, mBoundingBox);
512+
513+
outputBuffer[0] = Math.round(mBoundingBox.left);
514+
outputBuffer[1] = Math.round(mBoundingBox.top);
515+
outputBuffer[2] = Math.round(mBoundingBox.right - mBoundingBox.left);
516+
outputBuffer[3] = Math.round(mBoundingBox.bottom - mBoundingBox.top);
517+
}
518+
519+
private void mapRectFromViewToWindowCoords(View view, RectF rect) {
520+
Matrix matrix = view.getMatrix();
521+
if (!matrix.isIdentity()) {
522+
matrix.mapRect(rect);
523+
}
524+
525+
rect.offset(view.getLeft(), view.getTop());
526+
527+
ViewParent parent = view.getParent();
528+
while (parent instanceof View) {
529+
View parentView = (View) parent;
530+
531+
rect.offset(-parentView.getScrollX(), -parentView.getScrollY());
532+
533+
matrix = parentView.getMatrix();
534+
if (!matrix.isIdentity()) {
535+
matrix.mapRect(rect);
536+
}
537+
538+
rect.offset(parentView.getLeft(), parentView.getTop());
539+
540+
parent = parentView.getParent();
541+
}
542+
}
475543
}

packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include <react/renderer/mounting/ShadowView.h>
2222
#include <react/renderer/mounting/ShadowViewMutation.h>
2323

24+
#include <react/jni/JCallback.h>
25+
2426
#include <fbjni/fbjni.h>
2527
#include <glog/logging.h>
2628

@@ -1074,4 +1076,14 @@ void FabricMountingManager::onAllAnimationsComplete() {
10741076
allAnimationsCompleteJNI(javaUIManager_);
10751077
}
10761078

1079+
void FabricMountingManager::measure(const facebook::react::ShadowView& shadowView, std::function<void(folly::dynamic)> jsCallback) {
1080+
static auto measureJNI =
1081+
JFabricUIManager::javaClassStatic()->getMethod<void(jint, jint, jni::alias_ref<JCallback>)>(
1082+
"measure");
1083+
1084+
auto javaCallback = JCxxCallbackImpl::newObjectCxxArgs(jsCallback);
1085+
1086+
measureJNI(javaUIManager_, shadowView.surfaceId, shadowView.tag, javaCallback);
1087+
}
1088+
10771089
} // namespace facebook::react

packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ class FabricMountingManager final {
6060

6161
void onAllAnimationsComplete();
6262

63+
void measure(const ShadowView& shadowView, std::function<void(folly::dynamic)> callback);
64+
6365
private:
6466
bool isOnMainThread();
6567

0 commit comments

Comments
 (0)