Move Java files to OSS folders (#36822)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/36822

Move Bridgeless Java/Kotlin files from

> fbandroid/java/com/facebook/venice

to

> xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless

Changelog:
[Android][Changed] - Move Bridgeless Java/Kotlin code to OSS folders

Reviewed By: fkgozali

Differential Revision: D44626152

fbshipit-source-id: db6a6f51b3a2b09d81a1bf5c82d5baed903b4e4f
This commit is contained in:
Lulu Wu
2023-04-19 10:54:01 -07:00
committed by Facebook GitHub Bot
parent ef2951ea0c
commit 9789aa640f
24 changed files with 3756 additions and 10 deletions
@@ -0,0 +1,91 @@
---
AccessModifierOffset: -1
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: true
AlignOperands: false
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ForEachMacros: [ FOR_EACH_RANGE, FOR_EACH, ]
IncludeCategories:
- Regex: '^<.*\.h(pp)?>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IndentCaseLabels: true
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 8
UseTab: Never
---
Language: ObjC
ColumnLimit: 120
BreakBeforeBraces: WebKit
...
@@ -0,0 +1,22 @@
---
# NOTE there must be no spaces before the '-', so put the comma last.
InheritParentConfig: true
Checks: '
-,
-cert-err60-cpp,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-pro-type-const-cast,
-fuchsia-default-arguments-calls,
-fuchsia-multiple-inheritance,
-google-readability-casting,
-google-runtime-int,
-google-runtime-references,
-hicpp-special-member-functions,
-llvm-header-guard,
-misc-non-private-member-variables-in-classes,
-misc-unused-parameters,
-modernize-use-trailing-return-type,
-performance-unnecessary-value-param,
'
...
@@ -0,0 +1,130 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import static com.facebook.infer.annotation.Assertions.assertNotNull;
import android.annotation.SuppressLint;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Nullsafe;
@Nullsafe(Nullsafe.Mode.LOCAL)
public class BridgelessAtomicRef<T> {
interface Provider<T> {
T get();
}
@Nullable volatile T mValue;
@Nullable T mInitialValue;
enum State {
Init,
Creating,
Success,
Failure
}
volatile State state;
volatile String failureMessage;
public BridgelessAtomicRef(@Nullable T initialValue) {
mValue = initialValue;
mInitialValue = initialValue;
state = State.Init;
failureMessage = "";
}
public BridgelessAtomicRef() {
this(null);
}
@SuppressLint("CatchGeneralException")
public T getOrCreate(BridgelessAtomicRef.Provider<T> provider) {
boolean shouldCreate = false;
synchronized (this) {
if (state == State.Success) {
return get();
}
if (state == State.Failure) {
throw new RuntimeException(
"BridgelessAtomicRef: Failed to create object. Reason: " + failureMessage);
}
if (state != State.Creating) {
state = State.Creating;
shouldCreate = true;
}
}
if (shouldCreate) {
try {
// Call provider with lock on `this` released to mitigate deadlock hazard
mValue = provider.get();
synchronized (this) {
state = State.Success;
notifyAll();
return get();
}
} catch (RuntimeException ex) {
synchronized (this) {
state = State.Failure;
String message = ex.getMessage();
failureMessage = message != null ? message : "null";
notifyAll();
}
throw new RuntimeException("BridgelessAtomicRef: Failed to create object.", ex);
}
}
synchronized (this) {
boolean wasInterrupted = false;
while (state == State.Creating) {
try {
wait();
} catch (InterruptedException ex) {
wasInterrupted = true;
}
}
if (wasInterrupted) {
Thread.currentThread().interrupt();
}
if (state == State.Failure) {
throw new RuntimeException(
"BridgelessAtomicRef: Failed to create object. Reason: " + failureMessage);
}
return get();
}
}
public synchronized T getAndReset() {
T value = get();
reset();
return value;
}
public synchronized void reset() {
mValue = mInitialValue;
state = State.Init;
failureMessage = "";
}
public synchronized T get() {
return assertNotNull(mValue);
}
public synchronized @Nullable T getNullable() {
return mValue;
}
}
@@ -0,0 +1,152 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import android.app.Activity;
import android.content.Context;
import android.view.View;
import bolts.Continuation;
import bolts.Task;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JavaJSExecutor;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.devsupport.DevSupportManagerBase;
import com.facebook.react.devsupport.HMRClient;
import com.facebook.react.devsupport.ReactInstanceDevHelper;
import com.facebook.react.devsupport.interfaces.DevSplitBundleCallback;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import javax.annotation.Nullable;
/**
* An implementation of {@link com.facebook.react.devsupport.interfaces.DevSupportManager} that
* extends the functionality in {@link DevSupportManagerBase} with some additional, more flexible
* APIs for asynchronously loading the JS bundle.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
public class BridgelessDevSupportManager extends DevSupportManagerBase {
private final ReactHost mReactHost;
public BridgelessDevSupportManager(
ReactHost host, Context context, @Nullable String packagerPathForJSBundleName) {
super(
context.getApplicationContext(),
createInstanceDevHelper(host),
packagerPathForJSBundleName,
true /* enableOnCreate */,
null /* redBoxHandler */,
null /* devBundleDownloadListener */,
2 /* minNumShakes */,
null /* customPackagerCommandHandlers */,
null /* surfaceDelegateFactory */,
null /* devLoadingViewManager */);
mReactHost = host;
}
@Override
protected String getUniqueTag() {
return "Bridgeless";
}
@Override
public void loadSplitBundleFromServer(
final String bundlePath, final DevSplitBundleCallback callback) {
fetchSplitBundleAndCreateBundleLoader(
bundlePath,
new CallbackWithBundleLoader() {
@Override
public void onSuccess(final JSBundleLoader bundleLoader) {
mReactHost
.loadBundle(bundleLoader)
.onSuccess(
new Continuation<Boolean, Void>() {
@Override
public Void then(Task<Boolean> task) {
if (task.getResult().equals(Boolean.TRUE)) {
String bundleURL =
getDevServerHelper().getDevServerSplitBundleURL(bundlePath);
ReactContext reactContext = mReactHost.getCurrentReactContext();
if (reactContext != null) {
reactContext.getJSModule(HMRClient.class).registerBundle(bundleURL);
}
callback.onSuccess();
}
return null;
}
});
}
@Override
public void onError(String url, Throwable cause) {
callback.onError(url, cause);
}
});
}
@Override
public void handleReloadJS() {
hideRedboxDialog();
mReactHost.reload("BridgelessDevSupportManager.handleReloadJS()");
}
private static ReactInstanceDevHelper createInstanceDevHelper(final ReactHost reactHost) {
return new ReactInstanceDevHelper() {
@Override
public void onReloadWithJSDebugger(JavaJSExecutor.Factory proxyExecutorFactory) {
// Not implemented
}
@Override
public void onJSBundleLoadedFromServer() {
throw new IllegalStateException("Not implemented for bridgeless mode");
}
@Override
public void toggleElementInspector() {
ReactContext reactContext = reactHost.getCurrentReactContext();
if (reactContext != null) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("toggleElementInspector", null);
}
}
@androidx.annotation.Nullable
@Override
public Activity getCurrentActivity() {
return reactHost.getCurrentActivity();
}
@Override
public JavaScriptExecutorFactory getJavaScriptExecutorFactory() {
throw new IllegalStateException("Not implemented for bridgeless mode");
}
@androidx.annotation.Nullable
@Override
public View createRootView(String appKey) {
Activity currentActivity = getCurrentActivity();
if (currentActivity != null && !reactHost.isSurfaceWithModuleNameAttached(appKey)) {
ReactSurface reactSurface = ReactSurface.createWithView(currentActivity, appKey, null);
reactSurface.attach(reactHost);
reactSurface.start();
return reactSurface.getView();
}
return null;
}
@Override
public void destroyRootView(View rootView) {
// Not implemented
}
};
}
}
@@ -0,0 +1,160 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import android.content.Context;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.JSIModule;
import com.facebook.react.bridge.JSIModuleType;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.JavaScriptModuleRegistry;
import com.facebook.react.bridge.NativeArray;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactNoCrashBridgeNotAllowedSoftException;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.EventDispatcherProvider;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
/**
* This class is used instead of {@link ReactApplicationContext} when React Native is operating in
* bridgeless mode. The purpose of this class is to override some methods on {@link
* com.facebook.react.bridge.ReactContext} that use the {@link
* com.facebook.react.bridge.CatalystInstance}, which doesn't exist in bridgeless mode.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
public class BridgelessReactContext extends ReactApplicationContext
implements EventDispatcherProvider {
private final ReactHost mReactHost;
private final AtomicReference<String> mSourceURL = new AtomicReference<>();
private final String TAG = this.getClass().getSimpleName();
BridgelessReactContext(Context context, ReactHost host) {
super(context);
mReactHost = host;
}
@Override
public boolean isBridgeless() {
return true;
}
@Override
public EventDispatcher getEventDispatcher() {
return mReactHost.getEventDispatcher();
}
public void setSourceURL(String sourceURL) {
mSourceURL.set(sourceURL);
}
@Override
public @Nullable String getSourceURL() {
return mSourceURL.get();
}
@Override
public @Nullable JSIModule getJSIModule(JSIModuleType moduleType) {
if (moduleType == JSIModuleType.UIManager) {
return mReactHost.getUIManager();
}
throw new UnsupportedOperationException(
"getJSIModule is not implemented for bridgeless mode. Trying to get module: "
+ moduleType.name());
}
@Override
public CatalystInstance getCatalystInstance() {
ReactSoftExceptionLogger.logSoftExceptionVerbose(
TAG,
new ReactNoCrashBridgeNotAllowedSoftException(
"getCatalystInstance() cannot be called when the bridge is disabled"));
throw new UnsupportedOperationException("There is no Catalyst instance in bridgeless mode.");
}
@Override
public boolean hasActiveReactInstance() {
return mReactHost.isInstanceInitialized();
}
public DevSupportManager getDevSupportManager() {
return mReactHost.getDevSupportManager();
}
@Override
public void registerSegment(int segmentId, String path, Callback callback) {
mReactHost.registerSegment(segmentId, path, callback);
}
private static class BridgelessJSModuleInvocationHandler implements InvocationHandler {
private final ReactHost mReactHost;
private final Class<? extends JavaScriptModule> mJSModuleInterface;
public BridgelessJSModuleInvocationHandler(
ReactHost reactHost, Class<? extends JavaScriptModule> jsModuleInterface) {
mReactHost = reactHost;
mJSModuleInterface = jsModuleInterface;
}
@Override
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray();
mReactHost.callFunctionOnModule(
JavaScriptModuleRegistry.getJSModuleName(mJSModuleInterface), method.getName(), jsArgs);
return null;
}
}
@Override
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
JavaScriptModule interfaceProxy =
(JavaScriptModule)
Proxy.newProxyInstance(
jsInterface.getClassLoader(),
new Class[] {jsInterface},
new BridgelessJSModuleInvocationHandler(mReactHost, jsInterface));
return (T) interfaceProxy;
}
@Override
public <T extends NativeModule> boolean hasNativeModule(Class<T> nativeModuleInterface) {
return mReactHost.hasNativeModule(nativeModuleInterface);
}
@Override
public Collection<NativeModule> getNativeModules() {
return mReactHost.getNativeModules();
}
@Override
public @Nullable <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
return mReactHost.getNativeModule(nativeModuleInterface);
}
@Override
public void handleException(Exception e) {
mReactHost.handleException(e);
}
public DefaultHardwareBackBtnHandler getDefaultHardwareBackBtnHandler() {
return mReactHost.getDefaultBackButtonHandler();
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Nullsafe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Nullsafe(Nullsafe.Mode.LOCAL)
public class BridgelessReactStateTracker {
final List<String> mStates = Collections.synchronizedList(new ArrayList<String>());
final boolean mShouldTrackStates;
BridgelessReactStateTracker(boolean shouldTrackStates) {
mShouldTrackStates = shouldTrackStates;
}
public void enterState(String state) {
FLog.w("BridgelessReact", state);
if (mShouldTrackStates) {
mStates.add(state);
}
}
public void assertStateOrder(String... expectedStates) {
// TODO: Implement
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.soloader.SoLoader;
@Nullsafe(Nullsafe.Mode.LOCAL)
public abstract class JSEngineInstance {
static {
SoLoader.loadLibrary("rninstance");
}
@DoNotStrip private HybridData mHybridData;
protected JSEngineInstance(HybridData hybridData) {
mHybridData = hybridData;
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.jni.HybridData;
import com.facebook.jni.annotations.DoNotStrip;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.modules.core.JavaScriptTimerExecutor;
import com.facebook.soloader.SoLoader;
import com.facebook.soloader.annotation.SoLoaderLibrary;
@Nullsafe(Nullsafe.Mode.LOCAL)
@SoLoaderLibrary("rninstance")
public class JSTimerExecutor implements JavaScriptTimerExecutor {
static {
SoLoader.loadLibrary("rninstance");
}
@DoNotStrip private HybridData mHybridData;
public JSTimerExecutor(HybridData hybridData) {
mHybridData = hybridData;
}
@Override
public void callTimers(WritableArray timerIDs) {
callTimers((WritableNativeArray) timerIDs);
}
private native void callTimers(WritableNativeArray timerIDs);
@Override
public void callIdleCallbacks(double frameTime) {
// TODO T52558331
}
@Override
public void emitTimeDriftWarning(String warningMessage) {
// TODO T52558331
}
}
@@ -0,0 +1,493 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import android.content.res.AssetManager;
import android.view.View;
import com.facebook.common.logging.FLog;
import com.facebook.fbreact.fabric.components.CatalystRegistry;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.infer.annotation.ThreadConfined;
import com.facebook.infer.annotation.ThreadSafe;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.ReactPackage;
import com.facebook.react.ViewManagerOnDemandReactPackage;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JSBundleLoaderDelegate;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.NativeArray;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.RuntimeExecutor;
import com.facebook.react.bridge.RuntimeScheduler;
import com.facebook.react.bridge.queue.MessageQueueThread;
import com.facebook.react.bridge.queue.MessageQueueThreadSpec;
import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl;
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
import com.facebook.react.bridgeless.exceptionmanager.ReactJsExceptionHandler;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.fabric.Binding;
import com.facebook.react.fabric.BindingImpl;
import com.facebook.react.fabric.ComponentFactory;
import com.facebook.react.fabric.FabricUIManager;
import com.facebook.react.fabric.ReactNativeConfig;
import com.facebook.react.fabric.events.EventBeatManager;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.JavaTimerManager;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;
import com.facebook.react.turbomodule.core.TurboModuleManager;
import com.facebook.react.turbomodule.core.TurboModuleManagerDelegate;
import com.facebook.react.uimanager.ComponentNameResolver;
import com.facebook.react.uimanager.ComponentNameResolverManager;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.uimanager.ViewManagerRegistry;
import com.facebook.react.uimanager.ViewManagerResolver;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.soloader.SoLoader;
import com.facebook.soloader.annotation.SoLoaderLibrary;
import com.facebook.systrace.Systrace;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
/**
* An experimental replacement for {@link com.facebook.react.ReactInstanceManager} responsible for
* creating and managing a React Native instance
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
@ThreadSafe
@SoLoaderLibrary({"rninstance"})
public final class ReactInstance {
private static final String TAG = ReactInstance.class.getSimpleName();
@DoNotStrip private HybridData mHybridData;
private final ReactInstanceDelegate mDelegate;
private final BridgelessReactContext mBridgelessReactContext;
private final ReactQueueConfiguration mQueueConfiguration;
private final TurboModuleManager mTurboModuleManager;
private final FabricUIManager mFabricUIManager;
private final JavaTimerManager mJavaTimerManager;
@DoNotStrip @Nullable private ComponentNameResolverManager mComponentNameResolverManager;
private static volatile boolean sIsLibraryLoaded;
/* package */ ReactInstance(
BridgelessReactContext bridgelessReactContext,
ReactInstanceDelegate delegate,
DevSupportManager devSupportManager,
QueueThreadExceptionHandler exceptionHandler,
ReactJsExceptionHandler reactExceptionManager,
boolean useDevSupport) {
mBridgelessReactContext = bridgelessReactContext;
mDelegate = delegate;
loadLibraryIfNeeded();
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactInstance.initialize");
/**
* Prepare the ReactInstance by installing JSI bindings, initializing Fabric + TurboModules, and
* loading the JS bundle.
*/
MessageQueueThreadSpec nativeModulesSpec =
MessageQueueThreadSpec.newBackgroundThreadSpec("v_native");
ReactQueueConfigurationSpec spec =
ReactQueueConfigurationSpec.builder()
.setJSQueueThreadSpec(MessageQueueThreadSpec.newBackgroundThreadSpec("v_js"))
.setNativeModulesQueueThreadSpec(nativeModulesSpec)
.build();
mQueueConfiguration = ReactQueueConfigurationImpl.create(spec, exceptionHandler);
FLog.d(TAG, "Calling initializeMessageQueueThreads()");
mBridgelessReactContext.initializeMessageQueueThreads(mQueueConfiguration);
MessageQueueThread jsMessageQueueThread = mQueueConfiguration.getJSQueueThread();
MessageQueueThread nativeModulesMessageQueueThread =
mQueueConfiguration.getNativeModulesQueueThread();
ReactChoreographer.initialize();
if (useDevSupport) {
devSupportManager.startInspector();
}
JSTimerExecutor jsTimerExecutor = createJSTimerExecutor();
mJavaTimerManager =
new JavaTimerManager(
mBridgelessReactContext,
jsTimerExecutor,
ReactChoreographer.getInstance(),
devSupportManager);
mBridgelessReactContext.addLifecycleEventListener(
new LifecycleEventListener() {
@Override
public void onHostResume() {
mJavaTimerManager.onHostResume();
}
@Override
public void onHostPause() {
mJavaTimerManager.onHostPause();
}
@Override
public void onHostDestroy() {
mJavaTimerManager.onHostDestroy();
}
});
JSEngineInstance jsEngineInstance = mDelegate.getJSEngineInstance(mBridgelessReactContext);
// Notify JS if profiling is enabled
boolean isProfiling =
Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS | Systrace.TRACE_TAG_REACT_JS_VM_CALLS);
mHybridData =
initHybrid(
jsEngineInstance,
jsMessageQueueThread,
nativeModulesMessageQueueThread,
mJavaTimerManager,
jsTimerExecutor,
reactExceptionManager,
isProfiling);
RuntimeExecutor unbufferedRuntimeExecutor = getUnbufferedRuntimeExecutor();
// Initialize function for JS's UIManager.hasViewManagerConfig()
mComponentNameResolverManager =
new ComponentNameResolverManager(
// Use unbuffered RuntimeExecutor to install binding
unbufferedRuntimeExecutor,
new ComponentNameResolver() {
@Override
public String[] getComponentNames() {
Collection<String> viewManagerNames = getViewManagerNames();
if (viewManagerNames == null) {
FLog.e(TAG, "No ViewManager names found");
return new String[0];
}
return viewManagerNames.toArray(new String[0]);
}
});
// Set up TurboModules
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactInstance.initialize#initTurboModules");
TurboModuleManagerDelegate turboModuleManagerDelegate =
mDelegate.getTurboModuleManagerDelegate(mBridgelessReactContext);
mTurboModuleManager =
new TurboModuleManager(
// Use unbuffered RuntimeExecutor to install binding
unbufferedRuntimeExecutor,
turboModuleManagerDelegate,
getJSCallInvokerHolder(),
getNativeCallInvokerHolder());
// Eagerly initialize TurboModules
for (String moduleName : mTurboModuleManager.getEagerInitModuleNames()) {
mTurboModuleManager.getNativeModule(moduleName);
}
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
// Set up Fabric
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactInstance.initialize#initFabric");
ViewManagerRegistry viewManagerRegistry =
new ViewManagerRegistry(
new ViewManagerResolver() {
@Override
public @Nullable ViewManager getViewManager(String viewManagerName) {
return createViewManager(viewManagerName);
}
@Override
public Collection<String> getViewManagerNames() {
return ReactInstance.this.getViewManagerNames();
}
});
EventBeatManager eventBeatManager = new EventBeatManager(mBridgelessReactContext);
mFabricUIManager =
new FabricUIManager(mBridgelessReactContext, viewManagerRegistry, eventBeatManager);
ReactNativeConfig config = mDelegate.getReactNativeConfig(mTurboModuleManager);
ComponentFactory componentFactory = new ComponentFactory();
// Using Catalyst for the defaults for now. This should be passed in from the Application.
CatalystRegistry.register(componentFactory);
// Misc initialization that needs to be done before Fabric init
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(mBridgelessReactContext);
Binding binding = new BindingImpl();
binding.register(
getBufferedRuntimeExecutor(),
getRuntimeScheduler(),
mFabricUIManager,
eventBeatManager,
componentFactory,
config);
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
// Initialize the FabricUIManager
mFabricUIManager.initialize();
}
private static synchronized void loadLibraryIfNeeded() {
if (!sIsLibraryLoaded) {
SoLoader.loadLibrary("rninstance");
sIsLibraryLoaded = true;
}
}
public ReactQueueConfiguration getReactQueueConfiguration() {
return mQueueConfiguration;
}
public void loadJSBundle(JSBundleLoader bundleLoader) {
// Load the JS bundle
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactInstance.loadJSBundle");
bundleLoader.loadScript(
new JSBundleLoaderDelegate() {
@Override
public void loadScriptFromFile(
String fileName, String sourceURL, boolean loadSynchronously) {
mBridgelessReactContext.setSourceURL(sourceURL);
loadJSBundleFromFile(fileName, sourceURL);
}
@Override
public void loadSplitBundleFromFile(String fileName, String sourceURL) {
loadJSBundleFromFile(fileName, sourceURL);
}
@Override
public void loadScriptFromAssets(
AssetManager assetManager, String assetURL, boolean loadSynchronously) {
mBridgelessReactContext.setSourceURL(assetURL);
loadJSBundleFromAssets(assetManager, assetURL);
}
@Override
public void setSourceURLs(String deviceURL, String remoteURL) {
mBridgelessReactContext.setSourceURL(deviceURL);
}
});
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
public <T extends NativeModule> boolean hasNativeModule(Class<T> nativeModuleInterface) {
ReactModule annotation = nativeModuleInterface.getAnnotation(ReactModule.class);
if (annotation != null) {
return mTurboModuleManager.hasNativeModule(annotation.name());
}
return false;
}
public Collection<NativeModule> getNativeModules() {
Collection<NativeModule> nativeModules = new ArrayList<>();
for (NativeModule module : mTurboModuleManager.getNativeModules()) {
nativeModules.add(module);
}
return nativeModules;
}
public @Nullable <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
ReactModule annotation = nativeModuleInterface.getAnnotation(ReactModule.class);
if (annotation != null) {
return (T) getNativeModule(annotation.name());
}
return null;
}
public @Nullable NativeModule getNativeModule(String nativeModuleName) {
synchronized (mTurboModuleManager) {
return mTurboModuleManager.getNativeModule(nativeModuleName);
}
}
/* package */ void prerenderSurface(ReactSurface surface) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactInstance.prerenderSurface");
FLog.d(TAG, "call prerenderSurface with surface: " + surface.getModuleName());
mFabricUIManager.startSurface(surface.getSurfaceHandler(), surface.getContext(), null);
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
/**
* Renders a React Native surface.
*
* @param surface The {@link ReactSurface} to render.
*/
@ThreadConfined("ReactHost")
/* package */ void startSurface(ReactSurface surface) {
FLog.d(TAG, "startSurface() is called with surface: " + surface.getSurfaceID());
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactInstance.startSurface");
View view = surface.getView();
if (view == null) {
throw new IllegalStateException(
"Starting surface without a view is not supported, use prerenderSurface instead.");
}
/**
* This is a temporary mitigation for 646912b2590a6d5e760316cc064d1e27,
*
* <p>TODO T83828172 investigate why surface.getView() has id NOT equal to View.NO_ID
*/
if (view.getId() != View.NO_ID) {
ReactSoftExceptionLogger.logSoftException(
TAG,
new IllegalViewOperationException(
"surfaceView's is NOT equal to View.NO_ID before calling startSurface."));
view.setId(View.NO_ID);
}
if (surface.isRunning()) {
// surface was initialized beforehand, only attaching view
mFabricUIManager.attachRootView(surface.getSurfaceHandler(), view);
} else {
mFabricUIManager.startSurface(surface.getSurfaceHandler(), surface.getContext(), view);
}
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
@ThreadConfined("ReactHost")
/* package */ void stopSurface(ReactSurface surface) {
FLog.d(TAG, "stopSurface() is called with surface: " + surface.getSurfaceID());
mFabricUIManager.stopSurface(surface.getSurfaceHandler());
}
/* --- Lifecycle methods --- */
@ThreadConfined("ReactHost")
/* package */ void destroy() {
FLog.d(TAG, "ReactInstance.destroy() is called.");
mQueueConfiguration.destroy();
mTurboModuleManager.onCatalystInstanceDestroy();
mFabricUIManager.onCatalystInstanceDestroy();
mHybridData.resetNative();
mComponentNameResolverManager = null;
}
/* --- Native methods --- */
@DoNotStrip
private native HybridData initHybrid(
JSEngineInstance jsEngineInstance,
MessageQueueThread jsMessageQueueThread,
MessageQueueThread nativeModulesMessageQueueThread,
JavaTimerManager timerManager,
JSTimerExecutor jsTimerExecutor,
ReactJsExceptionHandler jReactExceptionsManager,
boolean isProfiling);
@DoNotStrip
private static native JSTimerExecutor createJSTimerExecutor();
@DoNotStrip
private native void installGlobals(boolean isProfiling);
private native void loadJSBundleFromFile(String fileName, String sourceURL);
private native void loadJSBundleFromAssets(AssetManager assetManager, String assetURL);
private native CallInvokerHolderImpl getJSCallInvokerHolder();
private native CallInvokerHolderImpl getNativeCallInvokerHolder();
private native RuntimeExecutor getUnbufferedRuntimeExecutor();
private native RuntimeExecutor getBufferedRuntimeExecutor();
private native RuntimeScheduler getRuntimeScheduler();
/* package */ native void callFunctionOnModule(
String moduleName, String methodName, NativeArray args);
private native void registerSegmentNative(int segmentId, String segmentPath);
private native void handleMemoryPressureJs(int pressureLevel);
public void handleMemoryPressure(int level) {
try {
handleMemoryPressureJs(level);
} catch (NullPointerException e) {
ReactSoftExceptionLogger.logSoftException(
TAG,
new IllegalViewOperationException(
"Native method handleMemoryPressureJs is called earlier than librninstance.so got ready."));
}
}
/**
* @return The {@link EventDispatcher} used by {@link FabricUIManager} to emit UI events to JS.
*/
/* protected */ EventDispatcher getEventDispatcher() {
return mFabricUIManager.getEventDispatcher();
}
/** @return The {@link FabricUIManager} if it's been initialized. */
/* protected */ FabricUIManager getUIManager() {
return mFabricUIManager;
}
public void registerSegment(int segmentId, String path) {
registerSegmentNative(segmentId, path);
}
private @Nullable ViewManager createViewManager(String viewManagerName) {
if (mDelegate != null) {
List<ReactPackage> packages = mDelegate.getReactPackages();
if (packages != null) {
synchronized (packages) {
for (ReactPackage reactPackage : packages) {
if (reactPackage instanceof ViewManagerOnDemandReactPackage) {
ViewManager viewManager =
((ViewManagerOnDemandReactPackage) reactPackage)
.createViewManager(mBridgelessReactContext, viewManagerName);
if (viewManager != null) {
return viewManager;
}
}
}
}
}
}
return null;
}
private Collection<String> getViewManagerNames() {
Set<String> uniqueNames = new HashSet<>();
if (mDelegate != null) {
List<ReactPackage> packages = mDelegate.getReactPackages();
if (packages != null) {
synchronized (packages) {
for (ReactPackage reactPackage : packages) {
if (reactPackage instanceof ViewManagerOnDemandReactPackage) {
Collection<String> names =
((ViewManagerOnDemandReactPackage) reactPackage)
.getViewManagerNames(mBridgelessReactContext);
if (names != null) {
uniqueNames.addAll(names);
}
}
}
}
}
}
return uniqueNames;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import android.content.Context;
import com.facebook.infer.annotation.ThreadSafe;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.fabric.ReactNativeConfig;
import com.facebook.react.turbomodule.core.TurboModuleManager;
import com.facebook.react.turbomodule.core.TurboModuleManagerDelegate;
import com.facebook.react.uimanager.ViewManager;
import java.util.List;
@ThreadSafe
public interface ReactInstanceDelegate {
String getJSMainModulePath();
JSBundleLoader getJSBundleLoader(Context context);
TurboModuleManagerDelegate getTurboModuleManagerDelegate(ReactApplicationContext context);
List<ViewManager> getViewManagers(ReactApplicationContext context);
JSEngineInstance getJSEngineInstance(ReactApplicationContext context);
void handleException(Exception e);
ReactNativeConfig getReactNativeConfig(TurboModuleManager turboModuleManager);
List<ReactPackage> getReactPackages();
}
@@ -0,0 +1,90 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import static com.facebook.infer.annotation.ThreadConfined.UI;
import android.app.Activity;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.infer.annotation.ThreadConfined;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.common.LifecycleState;
@Nullsafe(Nullsafe.Mode.LOCAL)
class ReactLifecycleStateManager {
LifecycleState mState = LifecycleState.BEFORE_CREATE;
private final BridgelessReactStateTracker mBridgelessReactStateTracker;
ReactLifecycleStateManager(BridgelessReactStateTracker bridgelessReactStateTracker) {
mBridgelessReactStateTracker = bridgelessReactStateTracker;
}
public LifecycleState getLifecycleState() {
return mState;
}
@ThreadConfined(UI)
public void resumeReactContextIfHostResumed(
final ReactContext currentContext, final @Nullable Activity activity) {
if (mState == LifecycleState.RESUMED) {
mBridgelessReactStateTracker.enterState("ReactContext.onHostResume()");
currentContext.onHostResume(activity);
}
}
@ThreadConfined(UI)
public void moveToOnHostResume(
final @Nullable ReactContext currentContext, final @Nullable Activity activity) {
if (mState == LifecycleState.RESUMED) {
return;
}
if (currentContext != null) {
mBridgelessReactStateTracker.enterState("ReactContext.onHostResume()");
currentContext.onHostResume(activity);
}
mState = LifecycleState.RESUMED;
}
@ThreadConfined(UI)
public void moveToOnHostPause(
final @Nullable ReactContext currentContext, final @Nullable Activity activity) {
if (currentContext != null) {
if (mState == LifecycleState.BEFORE_CREATE) {
// TODO: Investigate if we can remove this transition.
mBridgelessReactStateTracker.enterState("ReactContext.onHostResume()");
currentContext.onHostResume(activity);
mBridgelessReactStateTracker.enterState("ReactContext.onHostPause()");
currentContext.onHostPause();
} else if (mState == LifecycleState.RESUMED) {
mBridgelessReactStateTracker.enterState("ReactContext.onHostPause()");
currentContext.onHostPause();
}
}
mState = LifecycleState.BEFORE_RESUME;
}
@ThreadConfined(UI)
public void moveToOnHostDestroy(final @Nullable ReactContext currentContext) {
if (currentContext != null) {
if (mState == LifecycleState.BEFORE_RESUME) {
mBridgelessReactStateTracker.enterState("ReactContext.onHostDestroy()");
currentContext.onHostDestroy();
} else if (mState == LifecycleState.RESUMED) {
mBridgelessReactStateTracker.enterState("ReactContext.onHostPause()");
currentContext.onHostPause();
mBridgelessReactStateTracker.enterState("ReactContext.onHostDestroy()");
currentContext.onHostDestroy();
}
}
mState = LifecycleState.BEFORE_CREATE;
}
}
@@ -0,0 +1,241 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import android.content.Context;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.View.MeasureSpec;
import androidx.annotation.UiThread;
import bolts.Task;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.infer.annotation.ThreadSafe;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.NativeMap;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.fabric.SurfaceHandler;
import com.facebook.react.fabric.SurfaceHandlerBinding;
import com.facebook.react.modules.i18nmanager.I18nUtil;
import com.facebook.react.uimanager.events.EventDispatcher;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
/** A class responsible for creating and rendering a full-screen React surface. */
@Nullsafe(Nullsafe.Mode.LOCAL)
@ThreadSafe
public class ReactSurface {
private static final String TAG = "ReactSurface";
private final AtomicReference<ReactSurfaceView> mSurfaceView = new AtomicReference<>(null);
private final AtomicReference<ReactHost> mReactHost = new AtomicReference<>(null);
private final SurfaceHandler mSurfaceHandler;
/**
* Surface can hold two types of context: one used to init the surface without view (e.g. during
* prerendering), and themed view context later, whenever it is available. The assumption is that
* custom theming is applied to both the same way, so changing them SHOULD NOT change the visual
* output.
*/
private Context mContext;
public static ReactSurface createWithView(
Context context, String moduleName, @Nullable Bundle initialProps) {
ReactSurface surface = new ReactSurface(context, moduleName, initialProps);
surface.attachView(new ReactSurfaceView(context, surface));
return surface;
}
/**
* @param context The Android Context to use to render the ReactSurfaceView
* @param moduleName The string key used to register this surface in JS with SurfaceRegistry
* @param initialProps A Bundle of properties to be passed to the root React component
*/
public ReactSurface(Context context, String moduleName, @Nullable Bundle initialProps) {
this(new SurfaceHandlerBinding(moduleName), context);
NativeMap nativeProps =
initialProps == null
? new WritableNativeMap()
: (NativeMap) Arguments.fromBundle(initialProps);
mSurfaceHandler.setProps(nativeProps);
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
mSurfaceHandler.setLayoutConstraints(
MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels, MeasureSpec.AT_MOST),
0,
0,
doRTLSwap(context),
isRTL(context),
getPixelDensity(context));
}
@VisibleForTesting
ReactSurface(SurfaceHandler surfaceHandler, Context context) {
mSurfaceHandler = surfaceHandler;
mContext = context;
}
/**
* Attach a ReactHost to the ReactSurface.
*
* @param host The ReactHost to attach.
*/
public void attach(ReactHost host) {
if (!mReactHost.compareAndSet(null, host)) {
throw new IllegalStateException("This surface is already attached to a host!");
}
}
/**
* Attaches a view to the surface. View can be attached only once and should not be detached
* after.
*/
public void attachView(ReactSurfaceView view) {
if (!mSurfaceView.compareAndSet(null, view)) {
throw new IllegalStateException(
"Trying to call ReactSurface.attachView(), but the view is already attached.");
}
// re: thread safety. Context can be changed only once, during view init, so view atomic above
// already guards the context update.
mContext = view.getContext();
}
public void updateInitProps(Bundle newProps) {
mSurfaceHandler.setProps((NativeMap) Arguments.fromBundle(newProps));
}
@VisibleForTesting
ReactHost getReactHost() {
// NULLSAFE_FIXME[Return Not Nullable]
return mReactHost.get();
}
/** Detach the ReactSurface from its ReactHost. */
public void detach() {
mReactHost.set(null);
}
public SurfaceHandler getSurfaceHandler() {
return mSurfaceHandler;
}
public @Nullable ReactSurfaceView getView() {
return mSurfaceView.get();
}
public Task<Void> prerender() {
ReactHost host = mReactHost.get();
if (host == null) {
return Task.forError(
new IllegalStateException(
"Trying to call ReactSurface.prerender(), but no ReactHost is attached."));
}
return host.prerenderSurface(this);
}
public Task<Void> start() {
if (mSurfaceView.get() == null) {
return Task.forError(
new IllegalStateException(
"Trying to call ReactSurface.start(), but view is not created."));
}
ReactHost host = mReactHost.get();
if (host == null) {
return Task.forError(
new IllegalStateException(
"Trying to call ReactSurface.start(), but no ReactHost is attached."));
}
return host.startSurface(this);
}
public Task<Void> stop() {
ReactHost host = mReactHost.get();
if (host == null) {
return Task.forError(
new IllegalStateException(
"Trying to call ReactSurface.stop(), but no ReactHost is attached."));
}
return host.stopSurface(this).makeVoid();
}
public int getSurfaceID() {
return mSurfaceHandler.getSurfaceId();
}
public String getModuleName() {
return mSurfaceHandler.getModuleName();
}
public void clear() {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
ReactSurfaceView view = getView();
if (view != null) {
view.removeAllViews();
view.setId(View.NO_ID);
}
}
});
}
@UiThread
/* package */ synchronized void updateLayoutSpecs(
int widthMeasureSpec, int heightMeasureSpec, int offsetX, int offsetY) {
mSurfaceHandler.setLayoutConstraints(
widthMeasureSpec,
heightMeasureSpec,
offsetX,
offsetY,
doRTLSwap(mContext),
isRTL(mContext),
getPixelDensity(mContext));
}
/* package */ @Nullable
EventDispatcher getEventDispatcher() {
ReactHost host = mReactHost.get();
if (host == null) {
return null;
}
return host.getEventDispatcher();
}
@VisibleForTesting
boolean isAttached() {
return mReactHost.get() != null;
}
public boolean isRunning() {
return mSurfaceHandler.isRunning();
}
public Context getContext() {
return mContext;
}
private static float getPixelDensity(Context context) {
return context.getResources().getDisplayMetrics().density;
}
private static boolean isRTL(Context context) {
return I18nUtil.getInstance().isRTL(context);
}
private static boolean doRTLSwap(Context context) {
return I18nUtil.getInstance().doLeftAndRightSwapInRTL(context);
}
}
@@ -0,0 +1,239 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.JSPointerDispatcher;
import com.facebook.react.uimanager.JSTouchDispatcher;
import com.facebook.react.uimanager.common.UIManagerType;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.systrace.Systrace;
/** A view created by {@link ReactSurface} that's responsible for rendering a React component. */
@Nullsafe(Nullsafe.Mode.LOCAL)
public class ReactSurfaceView extends ReactRootView {
private static final String TAG = "ReactSurfaceView";
private ReactSurface mSurface;
private final JSTouchDispatcher mJSTouchDispatcher;
private @Nullable JSPointerDispatcher mJSPointerDispatcher;
private boolean mWasMeasured = false;
private int mWidthMeasureSpec = 0;
private int mHeightMeasureSpec = 0;
public ReactSurfaceView(Context context, ReactSurface surface) {
super(context);
mSurface = surface;
mJSTouchDispatcher = new JSTouchDispatcher(this);
if (ReactFeatureFlags.dispatchPointerEvents) {
mJSPointerDispatcher = new JSPointerDispatcher(this);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactSurfaceView.onMeasure");
int width = 0;
int height = 0;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int childSize =
child.getLeft()
+ child.getMeasuredWidth()
+ child.getPaddingLeft()
+ child.getPaddingRight();
width = Math.max(width, childSize);
}
} else {
width = MeasureSpec.getSize(widthMeasureSpec);
}
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int childSize =
child.getTop()
+ child.getMeasuredHeight()
+ child.getPaddingTop()
+ child.getPaddingBottom();
height = Math.max(height, childSize);
}
} else {
height = MeasureSpec.getSize(heightMeasureSpec);
}
setMeasuredDimension(width, height);
mWasMeasured = true;
mWidthMeasureSpec = widthMeasureSpec;
mHeightMeasureSpec = heightMeasureSpec;
Point viewportOffset = getViewportOffset();
mSurface.updateLayoutSpecs(
mWidthMeasureSpec, mHeightMeasureSpec, viewportOffset.x, viewportOffset.y);
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// Call updateLayoutSpecs to update locationOnScreen offsets, in case they've changed
if (mWasMeasured && changed) {
Point viewportOffset = getViewportOffset();
mSurface.updateLayoutSpecs(
mWidthMeasureSpec, mHeightMeasureSpec, viewportOffset.x, viewportOffset.y);
}
}
private Point getViewportOffset() {
int[] locationOnScreen = new int[2];
getLocationOnScreen(locationOnScreen);
// we need to subtract visibleWindowCoords - to subtract possible window insets, split
// screen or multi window
Rect visibleWindowFrame = new Rect();
getWindowVisibleDisplayFrame(visibleWindowFrame);
locationOnScreen[0] -= visibleWindowFrame.left;
locationOnScreen[1] -= visibleWindowFrame.top;
return new Point(locationOnScreen[0], locationOnScreen[1]);
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
// Override in order to still receive events to onInterceptTouchEvent even when some other
// views disallow that, but propagate it up the tree if possible.
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
/**
* Called when a child starts a native gesture (e.g. a scroll in a ScrollView). Should be called
* from the child's onTouchIntercepted implementation.
*/
@Override
public void onChildStartedNativeGesture(MotionEvent ev) {
if (mJSTouchDispatcher != null && mSurface.getEventDispatcher() != null) {
mJSTouchDispatcher.onChildStartedNativeGesture(ev, mSurface.getEventDispatcher());
}
}
/**
* Called when a child starts a native gesture (e.g. a scroll in a ScrollView). Should be called
* from the child's onTouchIntercepted implementation.
*/
@Override
public void onChildStartedNativeGesture(View childView, MotionEvent ev) {
onChildStartedNativeGesture(ev);
}
@Override
public void onChildEndedNativeGesture(View childView, MotionEvent ev) {
if (mJSTouchDispatcher != null && mSurface.getEventDispatcher() != null) {
mJSTouchDispatcher.onChildEndedNativeGesture(ev, mSurface.getEventDispatcher());
}
}
@Override
public void handleException(Throwable t) {
if (mSurface.getReactHost() != null) {
String errorMessage = t.getMessage() == null ? "" : t.getMessage();
Exception e = new IllegalViewOperationException(errorMessage, this, t);
mSurface.getReactHost().handleException(e);
}
}
@Override
public void setIsFabric(boolean isFabric) {
// This surface view is always on Fabric regardless.
super.setIsFabric(true);
}
@Override
public @UIManagerType int getUIManagerType() {
// This surface view is always on Fabric.
return UIManagerType.FABRIC;
}
@Override
protected void dispatchJSTouchEvent(MotionEvent event) {
if (mJSTouchDispatcher == null) {
FLog.w(TAG, "Unable to dispatch touch events to JS before the dispatcher is available");
return;
}
EventDispatcher eventDispatcher = mSurface.getEventDispatcher();
if (eventDispatcher != null) {
mJSTouchDispatcher.handleTouchEvent(event, eventDispatcher);
} else {
FLog.w(
TAG, "Unable to dispatch touch events to JS as the React instance has not been attached");
}
}
@Override
protected void dispatchJSPointerEvent(MotionEvent event, boolean isCapture) {
if (mJSPointerDispatcher == null) {
if (!ReactFeatureFlags.dispatchPointerEvents) {
return;
}
FLog.w(TAG, "Unable to dispatch pointer events to JS before the dispatcher is available");
return;
}
EventDispatcher eventDispatcher = mSurface.getEventDispatcher();
if (eventDispatcher != null) {
mJSPointerDispatcher.handleMotionEvent(event, eventDispatcher, isCapture);
} else {
FLog.w(
TAG,
"Unable to dispatch pointer events to JS as the React instance has not been attached");
}
}
@Override
public boolean hasActiveReactContext() {
return mSurface.isAttached() && mSurface.getReactHost().getCurrentReactContext() != null;
}
@Override
public boolean hasActiveReactInstance() {
return mSurface.isAttached() && mSurface.getReactHost().isInstanceInitialized();
}
@Override
public @Nullable ReactContext getCurrentReactContext() {
if (mSurface.isAttached()) {
return mSurface.getReactHost().getCurrentReactContext();
}
return null;
}
@Override
public boolean isViewAttachedToReactInstance() {
return mSurface.isAttached();
}
}
@@ -0,0 +1,17 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless.exceptionmanager;
import com.facebook.proguard.annotations.DoNotStripAny;
import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
@DoNotStripAny
public interface ReactJsExceptionHandler {
public void reportJsException(ReadableMapBuffer errorMap);
}
@@ -0,0 +1,34 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless.hermes
import com.facebook.common.process.ProcessName
import com.facebook.jni.HybridData
import com.facebook.jni.annotations.DoNotStrip
import com.facebook.jsi.mdcd.HermesCodeCoverage
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridgeless.JSEngineInstance
import com.facebook.soloader.SoLoader
import kotlin.jvm.JvmStatic
class HermesInstance(context: ReactApplicationContext) : JSEngineInstance(initHybrid()!!) {
companion object {
@JvmStatic @DoNotStrip protected external fun initHybrid(): HybridData?
init {
SoLoader.loadLibrary("hermesinstancejni")
}
}
init {
// Initialize Code Coverage tracing for select usecases, like E2E coverage and Hermes MDCD
val processName = ProcessName.current().fullName
HermesCodeCoverage.initialize(context, processName.orEmpty())
}
}
@@ -1,4 +1,9 @@
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
@@ -19,7 +24,7 @@ class JHermesInstance
: public jni::HybridClass<JHermesInstance, JJSEngineInstance> {
public:
static constexpr auto kJavaDescriptor =
"Lcom/facebook/venice/hermes/HermesInstance;";
"Lcom/facebook/react/bridgeless/hermes/HermesInstance;";
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject>);
@@ -1,4 +1,9 @@
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
@@ -14,7 +19,7 @@ class JJSEngineInstance : public jni::HybridClass<JJSEngineInstance>,
public JSEngineInstance {
public:
static auto constexpr kJavaDescriptor =
"Lcom/facebook/venice/JSEngineInstance;";
"Lcom/facebook/react/bridgeless/JSEngineInstance;";
private:
friend HybridBase;
@@ -1,4 +1,9 @@
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
@@ -16,7 +21,7 @@ class JJSTimerExecutor : public jni::HybridClass<JJSTimerExecutor> {
JJSTimerExecutor() = default;
constexpr static auto kJavaDescriptor =
"Lcom/facebook/venice/JSTimerExecutor;";
"Lcom/facebook/react/bridgeless/JSTimerExecutor;";
static void registerNatives();
@@ -1,4 +1,9 @@
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
@@ -13,7 +18,7 @@ class JReactExceptionManager
: public facebook::jni::JavaClass<JReactExceptionManager> {
public:
static auto constexpr kJavaDescriptor =
"Lcom/facebook/venice/exceptionmanager/ReactJsExceptionHandler;";
"Lcom/facebook/react/bridgeless/exceptionmanager/ReactJsExceptionHandler;";
void reportJsException(const JReadableMapBuffer::javaobject errorMapBuffer);
};
@@ -1,4 +1,9 @@
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
@@ -26,7 +31,8 @@ namespace react {
class JReactInstance : public jni::HybridClass<JReactInstance> {
public:
constexpr static auto kJavaDescriptor = "Lcom/facebook/venice/ReactInstance;";
constexpr static auto kJavaDescriptor =
"Lcom/facebook/react/bridgeless/ReactInstance;";
static jni::local_ref<jhybriddata> initHybrid(
jni::alias_ref<jhybridobject>,
@@ -0,0 +1,71 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
import android.app.Activity;
import android.content.Context;
import com.facebook.react.bridge.JSIModuleType;
import com.facebook.react.fabric.FabricUIManager;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.testing.robolectric.v4.WithTestDefaultsRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.robolectric.Robolectric;
/** Tests {@link BridgelessReactContext} */
@RunWith(WithTestDefaultsRunner.class)
public class BridgelessReactContextTest {
private Context mContext;
private ReactHost mReactHost;
private BridgelessReactContext mBridgelessReactContext;
@Before
public void setUp() {
mContext = Robolectric.buildActivity(Activity.class).create().get();
mReactHost = Mockito.mock(ReactHost.class);
mBridgelessReactContext = new BridgelessReactContext(mContext, mReactHost);
}
@Test
public void getNativeModuleTest() {
UIManagerModule mUiManagerModule = Mockito.mock(UIManagerModule.class);
doReturn(mUiManagerModule)
.when(mReactHost)
.getNativeModule(ArgumentMatchers.<Class<UIManagerModule>>any());
UIManagerModule uiManagerModule =
mBridgelessReactContext.getNativeModule(UIManagerModule.class);
assertThat(uiManagerModule).isEqualTo(mUiManagerModule);
}
@Test(expected = UnsupportedOperationException.class)
public void getJSIModule_throwsException() {
mBridgelessReactContext.getJSIModule(JSIModuleType.TurboModuleManager);
}
@Test
public void getJSIModuleTest() {
FabricUIManager fabricUiManager = Mockito.mock(FabricUIManager.class);
doReturn(fabricUiManager).when(mReactHost).getUIManager();
assertThat(mBridgelessReactContext.getJSIModule(JSIModuleType.UIManager))
.isEqualTo(fabricUiManager);
}
@Test(expected = UnsupportedOperationException.class)
public void getCatalystInstance_throwsException() {
mBridgelessReactContext.getCatalystInstance();
}
}
@@ -0,0 +1,131 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.powermock.api.mockito.PowerMockito.whenNew;
import android.app.Activity;
import bolts.TaskCompletionSource;
import com.facebook.base.applicationholder.ApplicationHolder;
import com.facebook.react.MemoryPressureRouter;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.MemoryPressureListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.devsupport.interfaces.PackagerStatusCallback;
import com.facebook.react.uimanager.events.BlackHoleEventDispatcher;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.testing.robolectric.RobolectricTestUtil;
import com.facebook.testing.robolectric.v4.WithTestDefaultsRunner;
import com.facebook.ultralight.testing.MockitoWithUltralightAutoMockSupport;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.robolectric.Robolectric;
import org.robolectric.android.controller.ActivityController;
/** Tests {@linkcom.facebook.react.bridgeless.ReactHost} */
@RunWith(WithTestDefaultsRunner.class)
@PowerMockIgnore({
"org.mockito.*",
"org.robolectric.*",
"android.*",
"androidx.*",
"javax.net.ssl.*"
})
@PrepareForTest({ReactHost.class})
public class ReactHostTest extends MockitoWithUltralightAutoMockSupport {
private ReactInstanceDelegate mReactInstanceDelegate;
private ReactInstance mReactInstance;
private MemoryPressureRouter mMemoryPressureRouter;
private BridgelessDevSupportManager mDevSupportManager;
private JSBundleLoader mJSBundleLoader;
private ReactHost mReactHost;
private ActivityController<Activity> mActivityController;
@Rule public PowerMockRule rule = new PowerMockRule();
@Before
public void setUp() throws Exception {
initMocks(this);
try {
ApplicationHolder.set(RobolectricTestUtil.getInstance().getApplication());
} catch (IllegalStateException e) {
// Do nothing, we're already initialized.
}
mActivityController = Robolectric.buildActivity(Activity.class).create().start().resume();
initializeNiceMockInjector(mActivityController.get());
mReactInstanceDelegate = mock(ReactInstanceDelegate.class);
mReactInstance = mock(ReactInstance.class);
mMemoryPressureRouter = mock(MemoryPressureRouter.class);
mDevSupportManager = mock(BridgelessDevSupportManager.class);
mJSBundleLoader = mock(JSBundleLoader.class);
whenNew(ReactInstance.class).withAnyArguments().thenReturn(mReactInstance);
whenNew(MemoryPressureRouter.class).withAnyArguments().thenReturn(mMemoryPressureRouter);
whenNew(BridgelessDevSupportManager.class).withAnyArguments().thenReturn(mDevSupportManager);
doReturn(mJSBundleLoader)
.when(mReactInstanceDelegate)
.getJSBundleLoader(ArgumentMatchers.<ReactApplicationContext>any());
mReactHost =
new ReactHost(
mActivityController.get().getApplication(), mReactInstanceDelegate, false, null, false);
}
@Test
public void getEventDispatcher_returnsBlackHoleEventDispatcher() {
EventDispatcher eventDispatcher = mReactHost.getEventDispatcher();
assertThat(eventDispatcher).isInstanceOf(BlackHoleEventDispatcher.class);
}
@Test
public void getUIManager_returnsNullIfNoInstance() {
UIManager uiManager = mReactHost.getUIManager();
assertThat(uiManager).isNull();
}
@Ignore("FIXME")
public void testGetDevSupportManager() {
assertThat(mReactHost.getDevSupportManager()).isEqualTo(mDevSupportManager);
}
@Ignore("waitForCompletion is locking the test thread making the entire venice tests to timeout")
public void testPreload() throws Exception {
TaskCompletionSource<Boolean> taskCompletionSource = new TaskCompletionSource<>();
taskCompletionSource.setResult(true);
whenNew(TaskCompletionSource.class).withAnyArguments().thenReturn(taskCompletionSource);
doNothing().when(mDevSupportManager).isPackagerRunning(any(PackagerStatusCallback.class));
assertThat(mReactHost.isInstanceInitialized()).isFalse();
mReactHost.preload().waitForCompletion();
assertThat(mReactHost.isInstanceInitialized()).isTrue();
assertThat(mReactHost.getCurrentReactContext()).isNotNull();
verify(mMemoryPressureRouter).addMemoryPressureListener((MemoryPressureListener) any());
}
}
@@ -0,0 +1,251 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridgeless;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.robolectric.shadows.ShadowInstrumentation.getInstrumentation;
import android.app.Activity;
import android.content.Context;
import android.view.View;
import bolts.Task;
import com.facebook.react.bridge.NativeMap;
import com.facebook.react.fabric.SurfaceHandler;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.testing.robolectric.v4.WithTestDefaultsRunner;
import java.util.concurrent.Callable;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.Robolectric;
@RunWith(WithTestDefaultsRunner.class)
public class ReactSurfaceTest {
@Mock ReactInstanceDelegate mReactInstanceDelegate;
@Mock EventDispatcher mEventDispatcher;
private ReactHost mReactHost;
private Context mContext;
private ReactSurface mReactSurface;
private TestSurfaceHandler mSurfaceHandler;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = Robolectric.buildActivity(Activity.class).create().get();
mReactHost = spy(new ReactHost(mContext, mReactInstanceDelegate, false, null, false));
doAnswer(mockedStartSurface()).when(mReactHost).startSurface(any(ReactSurface.class));
doAnswer(mockedStartSurface()).when(mReactHost).prerenderSurface(any(ReactSurface.class));
doAnswer(mockedStopSurface()).when(mReactHost).stopSurface(any(ReactSurface.class));
doReturn(mEventDispatcher).when(mReactHost).getEventDispatcher();
mSurfaceHandler = new TestSurfaceHandler();
mReactSurface = new ReactSurface(mSurfaceHandler, mContext);
mReactSurface.attachView(new ReactSurfaceView(mContext, mReactSurface));
}
@Test
public void testAttach() {
assertThat(mReactSurface.getReactHost()).isNull();
mReactSurface.attach(mReactHost);
assertThat(mReactSurface.getReactHost()).isEqualTo(mReactHost);
assertThat(mReactSurface.isAttached()).isTrue();
}
@Test(expected = IllegalStateException.class)
public void testAttachThrowExeption() {
mReactSurface.attach(mReactHost);
mReactSurface.attach(mReactHost);
}
@Test
public void testPrerender() throws InterruptedException {
mReactSurface.attach(mReactHost);
Task<Void> task = mReactSurface.prerender();
task.waitForCompletion();
verify(mReactHost).prerenderSurface(mReactSurface);
assertThat(mSurfaceHandler.isRunning).isTrue();
}
@Test
public void testStart() throws InterruptedException {
mReactSurface.attach(mReactHost);
assertThat(mReactHost.isSurfaceAttached(mReactSurface)).isFalse();
Task<Void> task = mReactSurface.start();
task.waitForCompletion();
verify(mReactHost).startSurface(mReactSurface);
assertThat(mSurfaceHandler.isRunning).isTrue();
}
@Test
public void testStop() throws InterruptedException {
mReactSurface.attach(mReactHost);
Task<Void> task = mReactSurface.start();
task.waitForCompletion();
task = mReactSurface.stop();
task.waitForCompletion();
verify(mReactHost).stopSurface(mReactSurface);
}
@Test
public void testClear() {
mReactSurface.getView().addView(new View(mContext));
mReactSurface.clear();
getInstrumentation().waitForIdleSync();
assertThat(mReactSurface.getView().getId()).isEqualTo(View.NO_ID);
assertThat(mReactSurface.getView().getChildCount()).isEqualTo(0);
}
@Test
public void testGetLayoutSpecs() {
int measureSpecWidth = Integer.MAX_VALUE;
int measureSpecHeight = Integer.MIN_VALUE;
assertThat(mSurfaceHandler.mWidthMeasureSpec).isNotEqualTo(measureSpecWidth);
assertThat(mSurfaceHandler.mHeightMeasureSpec).isNotEqualTo(measureSpecHeight);
mReactSurface.attach(mReactHost);
mReactSurface.updateLayoutSpecs(measureSpecWidth, measureSpecHeight, 2, 3);
assertThat(mSurfaceHandler.mWidthMeasureSpec).isEqualTo(measureSpecWidth);
assertThat(mSurfaceHandler.mHeightMeasureSpec).isEqualTo(measureSpecHeight);
}
@Test
public void testGetEventDispatcher() {
mReactSurface.attach(mReactHost);
assertThat(mReactSurface.getEventDispatcher()).isEqualTo(mEventDispatcher);
}
@Test
public void testStartStopHandlerCalls() throws InterruptedException {
mReactSurface.attach(mReactHost);
assertThat(mReactSurface.isRunning()).isFalse();
mReactSurface.start().waitForCompletion();
assertThat(mReactSurface.isRunning()).isTrue();
mReactSurface.stop().waitForCompletion();
assertThat(mReactSurface.isRunning()).isFalse();
}
private Answer<Task<Void>> mockedStartSurface() {
return new Answer<Task<Void>>() {
@Override
public Task<Void> answer(InvocationOnMock invocation) {
return Task.call(
new Callable<Void>() {
@Override
public Void call() {
mSurfaceHandler.start();
return null;
}
});
}
};
}
private Answer<Task<Boolean>> mockedStopSurface() {
return new Answer<Task<Boolean>>() {
@Override
public Task<Boolean> answer(InvocationOnMock invocation) {
return Task.call(
new Callable<Boolean>() {
@Override
public Boolean call() {
mSurfaceHandler.stop();
return true;
}
});
}
};
}
static class TestSurfaceHandler implements SurfaceHandler {
private boolean isRunning = false;
private int mSurfaceId = 0;
private int mHeightMeasureSpec = 0;
private int mWidthMeasureSpec = 0;
@Override
public void start() {
isRunning = true;
}
@Override
public void stop() {
isRunning = false;
}
@Override
public int getSurfaceId() {
return mSurfaceId;
}
@Override
public void setSurfaceId(int surfaceId) {
mSurfaceId = surfaceId;
}
@Override
public boolean isRunning() {
return isRunning;
}
@Override
public String getModuleName() {
return null;
}
@Override
public void setMountable(boolean mountable) {
// no-op
}
@Override
public void setLayoutConstraints(
int widthMeasureSpec,
int heightMeasureSpec,
int offsetX,
int offsetY,
boolean doLeftAndRightSwapInRTL,
boolean isRTL,
float pixelDensity) {
mWidthMeasureSpec = widthMeasureSpec;
mHeightMeasureSpec = heightMeasureSpec;
}
@Override
public void setProps(NativeMap props) {
// no-op
}
}
}