mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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:
committed by
Facebook GitHub Bot
parent
ef2951ea0c
commit
9789aa640f
+91
@@ -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
|
||||
...
|
||||
+22
@@ -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,
|
||||
'
|
||||
...
|
||||
+130
@@ -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;
|
||||
}
|
||||
}
|
||||
+152
@@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+160
@@ -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();
|
||||
}
|
||||
}
|
||||
+35
@@ -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
|
||||
}
|
||||
}
|
||||
+26
@@ -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;
|
||||
}
|
||||
}
|
||||
+49
@@ -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
|
||||
}
|
||||
}
|
||||
+1450
File diff suppressed because it is too large
Load Diff
+493
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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();
|
||||
}
|
||||
+90
@@ -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;
|
||||
}
|
||||
}
|
||||
+241
@@ -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);
|
||||
}
|
||||
}
|
||||
+239
@@ -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();
|
||||
}
|
||||
}
|
||||
+17
@@ -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);
|
||||
}
|
||||
+34
@@ -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())
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -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>);
|
||||
|
||||
|
||||
+7
-2
@@ -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;
|
||||
|
||||
+7
-2
@@ -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();
|
||||
|
||||
|
||||
+7
-2
@@ -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);
|
||||
};
|
||||
|
||||
+8
-2
@@ -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>,
|
||||
|
||||
+71
@@ -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();
|
||||
}
|
||||
}
|
||||
+131
@@ -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());
|
||||
}
|
||||
}
|
||||
+251
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user