From 9b1845c3a09266fa4f28c4ec2c1eecb846895609 Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Tue, 10 Dec 2019 02:28:06 -0800 Subject: [PATCH 001/985] Register LogBox by default in dev Summary: This diff stubs out registering LogBox as a renderable component. In later diffs, we'll render this component on the native side. Changelog: [Internal] Reviewed By: motiz88 Differential Revision: D18750005 fbshipit-source-id: 4db082ca2104731641d2d10de1ba83a318ab44fb --- Libraries/ReactNative/AppRegistry.js | 5 +++++ Libraries/ReactNative/renderApplication.js | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Libraries/ReactNative/AppRegistry.js b/Libraries/ReactNative/AppRegistry.js index ca553cbd0c3085..81bef371de450a 100644 --- a/Libraries/ReactNative/AppRegistry.js +++ b/Libraries/ReactNative/AppRegistry.js @@ -125,6 +125,7 @@ const AppRegistry = { appParameters.fabric, showArchitectureIndicator, scopedPerformanceLogger, + appKey === 'LogBox', ); }, }; @@ -292,4 +293,8 @@ const AppRegistry = { BatchedBridge.registerCallableModule('AppRegistry', AppRegistry); +if (__DEV__) { + AppRegistry.registerComponent('LogBox', () => () => null); +} + module.exports = AppRegistry; diff --git a/Libraries/ReactNative/renderApplication.js b/Libraries/ReactNative/renderApplication.js index d2aff2d7c32eb6..c5bd0e024cdd85 100644 --- a/Libraries/ReactNative/renderApplication.js +++ b/Libraries/ReactNative/renderApplication.js @@ -29,6 +29,7 @@ function renderApplication( fabric?: boolean, showArchitectureIndicator?: boolean, scopedPerformanceLogger?: IPerformanceLogger, + isLogBox?: boolean, ) { invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag); @@ -39,7 +40,8 @@ function renderApplication( rootTag={rootTag} fabric={fabric} showArchitectureIndicator={showArchitectureIndicator} - WrapperComponent={WrapperComponent}> + WrapperComponent={WrapperComponent} + internal_excludeLogBox={isLogBox}> From 6b22a4e80276a0f3a3e0b3e3c688976e6ad78f37 Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Tue, 10 Dec 2019 02:28:06 -0800 Subject: [PATCH 002/985] Add NativeLogBox module on iOS Summary: This diff adds a NativeLogBox module implementation on iOS to manage rendering LogBox the way we render RedBox, except rendering a React Native component instead of a native view. The strategy here is: - initialize: will create a hidden window (the way redbox does) and render the LogBox to it - show: will show the window - hide: will hide the window Most of this is copied from the way RedBox works, the difference here is that we eagerly initialize the window with the `initialize` function so that it's warm by the time LogBox needs to render. Changelog: [Internal] Reviewed By: RSNara Differential Revision: D18750008 fbshipit-source-id: 013e55ded55c8572bb08e0219ff4cd0060ebe0da --- .../FBReactNativeSpec-generated.mm | 27 +++ .../FBReactNativeSpec/FBReactNativeSpec.h | 19 ++ Libraries/NativeModules/specs/NativeLogBox.js | 21 +++ React/CoreModules/BUCK | 3 + React/CoreModules/CoreModulesPlugins.h | 1 + React/CoreModules/CoreModulesPlugins.mm | 1 + React/CoreModules/RCTLogBox.h | 21 +++ React/CoreModules/RCTLogBox.mm | 162 ++++++++++++++++++ .../fbreact/specs/NativeLogBoxSpec.java | 31 ++++ 9 files changed, 286 insertions(+) create mode 100644 Libraries/NativeModules/specs/NativeLogBox.js create mode 100644 React/CoreModules/RCTLogBox.h create mode 100644 React/CoreModules/RCTLogBox.mm create mode 100644 ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeLogBoxSpec.java diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm index 087773c243430f..bce1dea35f5dce 100644 --- a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm +++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm @@ -1671,6 +1671,33 @@ + (RCTManagedPointer *)JS_NativeLinking_SpecSendIntentExtrasElement:(id)json + } + + } // namespace react +} // namespace facebook +namespace facebook { + namespace react { + + + static facebook::jsi::Value __hostFunction_NativeLogBoxSpecJSI_show(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "show", @selector(show), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeLogBoxSpecJSI_hide(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "hide", @selector(hide), args, count); + } + + + NativeLogBoxSpecJSI::NativeLogBoxSpecJSI(id instance, std::shared_ptr jsInvoker) + : ObjCTurboModule("LogBox", instance, jsInvoker) { + + methodMap_["show"] = MethodMetadata {0, __hostFunction_NativeLogBoxSpecJSI_show}; + + + methodMap_["hide"] = MethodMetadata {0, __hostFunction_NativeLogBoxSpecJSI_hide}; + + + } } // namespace react diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h index 0d39cefcab45ab..bb1c8d2d99a67f 100644 --- a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h +++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h @@ -1771,6 +1771,25 @@ namespace facebook { }; } // namespace react } // namespace facebook +@protocol NativeLogBoxSpec + +- (void)show; +- (void)hide; + +@end +namespace facebook { + namespace react { + /** + * ObjC++ class for module 'LogBox' + */ + + class JSI_EXPORT NativeLogBoxSpecJSI : public ObjCTurboModule { + public: + NativeLogBoxSpecJSI(id instance, std::shared_ptr jsInvoker); + + }; + } // namespace react +} // namespace facebook @protocol NativeModalManagerSpec - (void)addListener:(NSString *)eventName; diff --git a/Libraries/NativeModules/specs/NativeLogBox.js b/Libraries/NativeModules/specs/NativeLogBox.js new file mode 100644 index 00000000000000..6d89481c390a03 --- /dev/null +++ b/Libraries/NativeModules/specs/NativeLogBox.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; +import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; + +export interface Spec extends TurboModule { + +show: () => void; + +hide: () => void; +} + +export default (TurboModuleRegistry.get('LogBox'): ?Spec); diff --git a/React/CoreModules/BUCK b/React/CoreModules/BUCK index 21bd66f4258d57..f42fcca46f2485 100644 --- a/React/CoreModules/BUCK +++ b/React/CoreModules/BUCK @@ -96,6 +96,9 @@ rn_apple_library( ) + react_module_plugin_providers( name = "RedBox", native_class_func = "RCTRedBoxCls", + ) + react_module_plugin_providers( + name = "LogBox", + native_class_func = "RCTLogBoxCls", ) + react_module_plugin_providers( name = "TVNavigationEventEmitter", native_class_func = "RCTTVNavigationEventEmitterCls", diff --git a/React/CoreModules/CoreModulesPlugins.h b/React/CoreModules/CoreModulesPlugins.h index 660c86a281dd6d..b158cb2aa3214f 100644 --- a/React/CoreModules/CoreModulesPlugins.h +++ b/React/CoreModules/CoreModulesPlugins.h @@ -48,6 +48,7 @@ Class RCTPerfMonitorCls(void) __attribute__((used)); Class RCTDevMenuCls(void) __attribute__((used)); Class RCTDevSettingsCls(void) __attribute__((used)); Class RCTRedBoxCls(void) __attribute__((used)); +Class RCTLogBoxCls(void) __attribute__((used)); Class RCTTVNavigationEventEmitterCls(void) __attribute__((used)); Class RCTWebSocketExecutorCls(void) __attribute__((used)); Class RCTWebSocketModuleCls(void) __attribute__((used)); diff --git a/React/CoreModules/CoreModulesPlugins.mm b/React/CoreModules/CoreModulesPlugins.mm index d1a6937511606a..dfd937c5389930 100644 --- a/React/CoreModules/CoreModulesPlugins.mm +++ b/React/CoreModules/CoreModulesPlugins.mm @@ -37,6 +37,7 @@ Class RCTCoreModulesClassProvider(const char *name) { {"DevMenu", RCTDevMenuCls}, {"DevSettings", RCTDevSettingsCls}, {"RedBox", RCTRedBoxCls}, + {"LogBox", RCTLogBoxCls}, {"TVNavigationEventEmitter", RCTTVNavigationEventEmitterCls}, {"WebSocketExecutor", RCTWebSocketExecutorCls}, {"WebSocketModule", RCTWebSocketModuleCls}, diff --git a/React/CoreModules/RCTLogBox.h b/React/CoreModules/RCTLogBox.h new file mode 100644 index 00000000000000..ec2a4861e319a1 --- /dev/null +++ b/React/CoreModules/RCTLogBox.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#import +#import +#import + +@class RCTJSStackFrame; + +@interface RCTLogBox : NSObject + +- (void)show; +- (void)hide; + +@end diff --git a/React/CoreModules/RCTLogBox.mm b/React/CoreModules/RCTLogBox.mm new file mode 100644 index 00000000000000..cda46ca31f7645 --- /dev/null +++ b/React/CoreModules/RCTLogBox.mm @@ -0,0 +1,162 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTLogBox.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import + +#import "CoreModulesPlugins.h" + +#if RCT_DEV_MENU + +@class RCTLogBoxWindow; + +@protocol RCTLogBoxWindowActionDelegate + +- (void)logBoxWindow:(RCTLogBoxWindow *)logBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame; +- (void)reloadFromlogBoxWindow:(RCTLogBoxWindow *)logBoxWindow; +- (void)loadExtraDataViewController; + +@end + +@interface RCTLogBoxWindow : UIWindow +@property (nonatomic, weak) id actionDelegate; +@end + +@implementation RCTLogBoxWindow +{ + UITableView *_stackTraceTableView; + NSString *_lastErrorMessage; + NSArray *_lastStackTrace; + int _lastErrorCookie; +} + +- (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge +{ + _lastErrorCookie = -1; + if ((self = [super initWithFrame:frame])) { +#if TARGET_OS_TV + self.windowLevel = UIWindowLevelAlert + 1000; +#else + self.windowLevel = UIWindowLevelStatusBar - 1; +#endif + self.backgroundColor = [UIColor clearColor]; + self.hidden = YES; + + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"LogBox" initialProperties:nil]; + + UIViewController *rootViewController = [UIViewController new]; + rootViewController.view = rootView; + self.rootViewController = rootViewController; + } + return self; +} + + +- (void)show +{ + [self becomeFirstResponder]; + [self makeKeyAndVisible]; +} + +- (void)dismiss +{ + self.hidden = YES; + [self resignFirstResponder]; + [RCTSharedApplication().delegate.window makeKeyWindow]; +} + +@end + +@interface RCTLogBox () +@end + +@implementation RCTLogBox +{ + RCTLogBoxWindow *_window; +} + +@synthesize bridge = _bridge; + +RCT_EXPORT_MODULE() + ++ (BOOL)requiresMainQueueSetup +{ + return YES; +} + +- (void)setBridge:(RCTBridge *)bridge +{ + _bridge = bridge; + dispatch_async(dispatch_get_main_queue(), ^{ + self->_window = [[RCTLogBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds bridge: self->_bridge]; + }); +} + +RCT_EXPORT_METHOD(show) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_window show]; + }); +} + +RCT_EXPORT_METHOD(hide) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_window dismiss]; + }); +} + +- (std::shared_ptr)getTurboModuleWithJsInvoker:(std::shared_ptr)jsInvoker +{ + return std::make_shared(self, jsInvoker); +} + +@end + +#else // Disabled + +@interface RCTLogBox() +@end + +@implementation RCTLogBox + ++ (NSString *)moduleName +{ + return nil; +} + +- (void)show { + // noop +} + +- (void)hide { + // noop +} + +- (std::shared_ptr)getTurboModuleWithJsInvoker:(std::shared_ptr)jsInvoker +{ + return std::make_shared(self, jsInvoker); +} +@end + +#endif + +Class RCTLogBoxCls(void) { + return RCTLogBox.class; +} diff --git a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeLogBoxSpec.java b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeLogBoxSpec.java new file mode 100644 index 00000000000000..085f0559be5f50 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeLogBoxSpec.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + * + *

Generated by an internal genrule from Flow types. + * + * @generated + * @nolint + */ + +package com.facebook.fbreact.specs; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReactModuleWithSpec; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; + +public abstract class NativeLogBoxSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule { + public NativeLogBoxSpec(ReactApplicationContext reactContext) { + super(reactContext); + } + + @ReactMethod + public abstract void hide(); + + @ReactMethod + public abstract void show(); +} From e2720895245366aaf25d63e10bfa9995253bad00 Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Tue, 10 Dec 2019 02:28:06 -0800 Subject: [PATCH 003/985] Add NativeLogBox module on Android Summary: This diff adds a NativeLogBox module implementation on Android to manage rendering LogBox the way we render RedBox, except rendering a React Native component instead of a native view. The strategy here is: - initialize: will create a React rootview and render it. - show: will add the rootview to a dialog and display the dialog. - hide: will remove the rootview from it's parent, dismiss the dialog, and release the reference to the activity to prevent leaks. Most of this is copied from the way RedBox works, the difference here is that we eagerly initialize the rootview with the `initialize` function so that it's warm by the time the dialog needs to render. Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D18768517 fbshipit-source-id: 2510d6c186ccf73153ef9372c736c9e0c71bbc7d --- .../facebook/react/CoreModulesPackage.java | 5 + .../facebook/react/ReactInstanceManager.java | 21 ++++ .../devsupport/DevSupportManagerImpl.java | 9 ++ .../devsupport/DisabledDevSupportManager.java | 9 ++ .../react/devsupport/LogBoxDialog.java | 24 ++++ .../react/devsupport/LogBoxModule.java | 110 ++++++++++++++++++ .../ReactInstanceManagerDevHelper.java | 6 + .../interfaces/DevSupportManager.java | 6 + .../src/main/res/devsupport/values/colors.xml | 1 + .../src/main/res/devsupport/values/styles.xml | 12 ++ 10 files changed, 203 insertions(+) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxDialog.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxModule.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java index 6dd813b6f62b2e..a17763207b53f3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java @@ -16,6 +16,7 @@ import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactMarker; +import com.facebook.react.devsupport.LogBoxModule; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.module.annotations.ReactModuleList; import com.facebook.react.module.model.ReactModuleInfo; @@ -49,6 +50,7 @@ DeviceInfoModule.class, DevSettingsModule.class, ExceptionsManagerModule.class, + LogBoxModule.class, HeadlessJsTaskSupportModule.class, SourceCodeModule.class, TimingModule.class, @@ -94,6 +96,7 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() { DeviceInfoModule.class, DevSettingsModule.class, ExceptionsManagerModule.class, + LogBoxModule.class, HeadlessJsTaskSupportModule.class, SourceCodeModule.class, TimingModule.class, @@ -142,6 +145,8 @@ public NativeModule getModule(String name, ReactApplicationContext reactContext) return new DevSettingsModule(reactContext, mReactInstanceManager.getDevSupportManager()); case ExceptionsManagerModule.NAME: return new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager()); + case LogBoxModule.NAME: + return new LogBoxModule(reactContext, mReactInstanceManager.getDevSupportManager()); case HeadlessJsTaskSupportModule.NAME: return new HeadlessJsTaskSupportModule(reactContext); case SourceCodeModule.NAME: diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 8d56f3418f5607..2d79c4d3fb205f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -304,6 +304,27 @@ public void toggleElementInspector() { public JavaScriptExecutorFactory getJavaScriptExecutorFactory() { return ReactInstanceManager.this.getJSExecutorFactory(); } + + @Override + public @Nullable View createRootView(String appKey) { + Activity currentActivity = getCurrentActivity(); + if (currentActivity != null) { + ReactRootView rootView = new ReactRootView(currentActivity); + + rootView.startReactApplication(ReactInstanceManager.this, appKey, null); + + return rootView; + } + + return null; + } + + @Override + public void destroyRootView(View rootView) { + if (rootView instanceof ReactRootView) { + ((ReactRootView) rootView).unmountReactApplication(); + } + } }; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index 2e3f651fb16e15..5dd361f1d83f91 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -19,6 +19,7 @@ import android.content.pm.PackageManager; import android.hardware.SensorManager; import android.util.Pair; +import android.view.View; import android.widget.EditText; import android.widget.Toast; import androidx.annotation.Nullable; @@ -377,6 +378,14 @@ public void hideRedboxDialog() { } } + public @Nullable View createRootView(String appKey) { + return mReactInstanceManagerHelper.createRootView(appKey); + } + + public void destroyRootView(View rootView) { + mReactInstanceManagerHelper.destroyRootView(rootView); + } + private void hideDevOptionsDialog() { if (mDevOptionsDialog != null) { mDevOptionsDialog.dismiss(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java index 4f510fe18895c8..267a455e6ebe4b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java @@ -7,6 +7,7 @@ package com.facebook.react.devsupport; +import android.view.View; import androidx.annotation.Nullable; import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler; import com.facebook.react.bridge.ReactContext; @@ -40,6 +41,14 @@ public void addCustomDevOption(String optionName, DevOptionHandler optionHandler @Override public void showNewJSError(String message, ReadableArray details, int errorCookie) {} + @Override + public @Nullable View createRootView(String appKey) { + return null; + } + + @Override + public void destroyRootView(View rootView) {} + @Override public void updateJSError(String message, ReadableArray details, int errorCookie) {} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxDialog.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxDialog.java new file mode 100644 index 00000000000000..c6a5b80e8ddebb --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxDialog.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its 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.devsupport; + +import android.app.Activity; +import android.app.Dialog; +import android.view.View; +import android.view.Window; +import com.facebook.react.R; + +/** Dialog for displaying JS errors in LogBox. */ +public class LogBoxDialog extends Dialog { + public LogBoxDialog(Activity context, View reactRootView) { + super(context, R.style.Theme_Catalyst_LogBox); + + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(reactRootView); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxModule.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxModule.java new file mode 100644 index 00000000000000..05e70b069d33fc --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxModule.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) Facebook, Inc. and its 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.devsupport; + +import android.app.Activity; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.Nullable; +import com.facebook.common.logging.FLog; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.common.ReactConstants; +import com.facebook.react.devsupport.interfaces.DevSupportManager; +import com.facebook.react.module.annotations.ReactModule; + +@ReactModule(name = LogBoxModule.NAME) +public class LogBoxModule extends ReactContextBaseJavaModule { + + public static final String NAME = "LogBox"; + + private final DevSupportManager mDevSupportManager; + private @Nullable View mReactRootView; + private @Nullable LogBoxDialog mLogBoxDialog; + + public LogBoxModule(ReactApplicationContext reactContext, DevSupportManager devSupportManager) { + super(reactContext); + + mDevSupportManager = devSupportManager; + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (mReactRootView == null) { + mReactRootView = mDevSupportManager.createRootView("LogBox"); + if (mReactRootView == null) { + FLog.e( + ReactConstants.TAG, + "Unable to launch logbox because react was unable to create the root view"); + } + } + } + }); + } + + @Override + public String getName() { + return NAME; + } + + @ReactMethod + public void show() { + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (mLogBoxDialog == null) { + Activity context = getCurrentActivity(); + if (context == null || context.isFinishing()) { + FLog.e( + ReactConstants.TAG, + "Unable to launch logbox because react activity " + + "is not available, here is the error that logbox would've displayed: "); + return; + } + mLogBoxDialog = new LogBoxDialog(context, mReactRootView); + mLogBoxDialog.setCancelable(false); + mLogBoxDialog.show(); + } + } + }); + } + + @ReactMethod + public void hide() { + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (mLogBoxDialog != null) { + if (mReactRootView.getParent() != null) { + ((ViewGroup) mReactRootView.getParent()).removeView(mReactRootView); + } + mLogBoxDialog.dismiss(); + mLogBoxDialog = null; + } + } + }); + } + + @Override + public void onCatalystInstanceDestroy() { + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (mReactRootView != null) { + mDevSupportManager.destroyRootView(mReactRootView); + mReactRootView = null; + } + } + }); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java index 60d45b1ba5b087..02483586b3416f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java @@ -8,6 +8,7 @@ package com.facebook.react.devsupport; import android.app.Activity; +import android.view.View; import androidx.annotation.Nullable; import com.facebook.react.bridge.JavaJSExecutor; import com.facebook.react.bridge.JavaScriptExecutorFactory; @@ -32,4 +33,9 @@ public interface ReactInstanceManagerDevHelper { Activity getCurrentActivity(); JavaScriptExecutorFactory getJavaScriptExecutorFactory(); + + @Nullable + View createRootView(String appKey); + + void destroyRootView(View rootView); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java index ac04bc6a6dd4e8..43a477e3faddee 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java @@ -7,6 +7,7 @@ package com.facebook.react.devsupport.interfaces; +import android.view.View; import androidx.annotation.Nullable; import com.facebook.react.bridge.NativeModuleCallExceptionHandler; import com.facebook.react.bridge.ReactContext; @@ -25,6 +26,11 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler { void addCustomDevOption(String optionName, DevOptionHandler optionHandler); + @Nullable + View createRootView(String appKey); + + void destroyRootView(View rootView); + void showNewJSError(String message, ReadableArray details, int errorCookie); void updateJSError(final String message, final ReadableArray details, final int errorCookie); diff --git a/ReactAndroid/src/main/res/devsupport/values/colors.xml b/ReactAndroid/src/main/res/devsupport/values/colors.xml index d15ee8caf7bdf1..8ac1a132df0ff9 100644 --- a/ReactAndroid/src/main/res/devsupport/values/colors.xml +++ b/ReactAndroid/src/main/res/devsupport/values/colors.xml @@ -1,4 +1,5 @@ #eecc0000 + #ffffffff diff --git a/ReactAndroid/src/main/res/devsupport/values/styles.xml b/ReactAndroid/src/main/res/devsupport/values/styles.xml index e9f462c5359d56..cb88e438c9179a 100644 --- a/ReactAndroid/src/main/res/devsupport/values/styles.xml +++ b/ReactAndroid/src/main/res/devsupport/values/styles.xml @@ -9,10 +9,22 @@ @android:anim/fade_out @android:color/white + +