react-native 0.85.2 → 0.85.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/Libraries/Core/ReactNativeVersion.js +1 -1
  2. package/Libraries/Utilities/HMRClient.js +28 -1
  3. package/React/Base/RCTVersion.m +1 -1
  4. package/React/CoreModules/RCTJscSafeUrl+Internal.h +23 -0
  5. package/React/CoreModules/RCTJscSafeUrl.mm +38 -0
  6. package/React/CoreModules/RCTRedBox+Internal.h +42 -0
  7. package/React/CoreModules/RCTRedBox.mm +30 -454
  8. package/React/CoreModules/RCTRedBox2AnsiParser+Internal.h +22 -0
  9. package/React/CoreModules/RCTRedBox2AnsiParser.mm +55 -0
  10. package/React/CoreModules/RCTRedBox2Controller+Internal.h +34 -0
  11. package/React/CoreModules/RCTRedBox2Controller.mm +764 -0
  12. package/React/CoreModules/RCTRedBox2ErrorParser+Internal.h +46 -0
  13. package/React/CoreModules/RCTRedBox2ErrorParser.mm +57 -0
  14. package/React/CoreModules/RCTRedBoxController+Internal.h +31 -0
  15. package/React/CoreModules/RCTRedBoxController.mm +447 -0
  16. package/React/CoreModules/RCTRedBoxHMRClient+Internal.h +26 -0
  17. package/React/CoreModules/RCTRedBoxHMRClient.mm +125 -0
  18. package/React/CoreModules/React-CoreModules.podspec +1 -0
  19. package/React/DevSupport/RCTFrameTimingsObserver.h +24 -0
  20. package/React/DevSupport/RCTFrameTimingsObserver.mm +298 -0
  21. package/React/FBReactNativeSpec/FBReactNativeSpecJSI.h +40 -0
  22. package/ReactAndroid/gradle.properties +1 -1
  23. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorFlags.kt +4 -0
  24. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingSequence.kt +1 -1
  25. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingsObserver.kt +127 -26
  26. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +31 -1
  27. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +51 -1
  28. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +11 -1
  29. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +11 -1
  30. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +56 -1
  31. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +11 -1
  32. package/ReactAndroid/src/main/java/com/facebook/react/internal/tracing/PerformanceTracer.kt +39 -0
  33. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
  34. package/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt +50 -10
  35. package/ReactAndroid/src/main/jni/CMakeLists.txt +7 -0
  36. package/ReactAndroid/src/main/jni/react/devsupport/JInspectorFlags.cpp +22 -0
  37. package/ReactAndroid/src/main/jni/react/devsupport/JInspectorFlags.h +2 -0
  38. package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +71 -1
  39. package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +16 -1
  40. package/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.cpp +14 -0
  41. package/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.h +18 -4
  42. package/ReactCommon/React-Fabric.podspec +6 -0
  43. package/ReactCommon/cxxreact/ReactNativeVersion.h +2 -2
  44. package/ReactCommon/jsinspector-modern/HostAgent.cpp +36 -0
  45. package/ReactCommon/jsinspector-modern/HostTarget.cpp +7 -1
  46. package/ReactCommon/jsinspector-modern/HostTarget.h +25 -0
  47. package/ReactCommon/jsinspector-modern/HostTargetTracing.cpp +1 -1
  48. package/ReactCommon/jsinspector-modern/HostTargetTracing.h +4 -4
  49. package/ReactCommon/jsinspector-modern/InspectorFlags.cpp +12 -0
  50. package/ReactCommon/jsinspector-modern/InspectorFlags.h +12 -0
  51. package/ReactCommon/jsinspector-modern/NetworkIOAgent.cpp +1 -1
  52. package/ReactCommon/jsinspector-modern/RuntimeAgent.cpp +19 -0
  53. package/ReactCommon/jsinspector-modern/RuntimeAgent.h +7 -0
  54. package/ReactCommon/jsinspector-modern/RuntimeTarget.cpp +33 -0
  55. package/ReactCommon/jsinspector-modern/RuntimeTarget.h +6 -0
  56. package/ReactCommon/jsinspector-modern/tests/HostTargetTest.cpp +12 -0
  57. package/ReactCommon/jsinspector-modern/tests/InspectorMocks.h +3 -2
  58. package/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.cpp +1 -0
  59. package/ReactCommon/jsinspector-modern/tests/NetworkReporterTest.cpp +1 -1
  60. package/ReactCommon/jsinspector-modern/tests/TracingTest.cpp +1 -1
  61. package/ReactCommon/jsinspector-modern/tests/utils/InspectorFlagOverridesGuard.cpp +10 -0
  62. package/ReactCommon/jsinspector-modern/tests/utils/InspectorFlagOverridesGuard.h +3 -1
  63. package/ReactCommon/jsinspector-modern/tracing/CMakeLists.txt +1 -0
  64. package/ReactCommon/jsinspector-modern/tracing/FrameTimingSequence.h +7 -3
  65. package/ReactCommon/jsinspector-modern/tracing/HostTracingProfileSerializer.cpp +52 -29
  66. package/ReactCommon/jsinspector-modern/tracing/HostTracingProfileSerializer.h +6 -6
  67. package/ReactCommon/jsinspector-modern/tracing/PerformanceTracerSection.h +113 -0
  68. package/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec +1 -0
  69. package/ReactCommon/jsinspector-modern/tracing/TraceEventGenerator.cpp +12 -5
  70. package/ReactCommon/jsinspector-modern/tracing/TraceEventGenerator.h +3 -1
  71. package/ReactCommon/jsinspector-modern/tracing/TraceEventSerializer.cpp +42 -0
  72. package/ReactCommon/jsinspector-modern/tracing/TraceEventSerializer.h +7 -0
  73. package/ReactCommon/react/debug/CMakeLists.txt +2 -1
  74. package/ReactCommon/react/debug/React-debug.podspec +7 -1
  75. package/ReactCommon/react/debug/redbox/AnsiParser.cpp +139 -0
  76. package/ReactCommon/react/debug/redbox/AnsiParser.h +35 -0
  77. package/ReactCommon/react/debug/redbox/JscSafeUrl.cpp +179 -0
  78. package/ReactCommon/react/debug/redbox/JscSafeUrl.h +27 -0
  79. package/ReactCommon/react/debug/redbox/RedBoxErrorParser.cpp +171 -0
  80. package/ReactCommon/react/debug/redbox/RedBoxErrorParser.h +40 -0
  81. package/ReactCommon/react/debug/redbox/tests/AnsiParserTest.cpp +97 -0
  82. package/ReactCommon/react/debug/redbox/tests/JscSafeUrlTest.cpp +173 -0
  83. package/ReactCommon/react/debug/redbox/tests/RedBoxErrorParserTest.cpp +107 -0
  84. package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +21 -1
  85. package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +26 -1
  86. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +135 -45
  87. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +12 -2
  88. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +21 -1
  89. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +46 -1
  90. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +6 -1
  91. package/ReactCommon/react/nativemodule/defaults/CMakeLists.txt +1 -0
  92. package/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp +7 -0
  93. package/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec +1 -0
  94. package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +26 -1
  95. package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +11 -1
  96. package/ReactCommon/react/nativemodule/mutationobserver/NativeMutationObserver.h +4 -0
  97. package/ReactCommon/react/nativemodule/mutationobserver/React-mutationobservernativemodule.podspec +66 -0
  98. package/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +18 -6
  99. package/ReactCommon/react/performance/timeline/PerformanceObserver.h +2 -0
  100. package/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm +115 -0
  101. package/ReactCommon/{jsinspector-modern → react/utils}/Base64.h +2 -2
  102. package/package.json +11 -11
  103. package/scripts/cocoapods/utils.rb +1 -0
  104. package/scripts/react_native_pods.rb +1 -0
  105. package/scripts/replace-rncore-version.js +72 -15
  106. package/src/private/featureflags/ReactNativeFeatureFlags.js +26 -1
  107. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +6 -1
  108. package/src/private/setup/setUpDefaultReactNativeEnvironment.js +6 -0
@@ -0,0 +1,764 @@
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ #import "RCTRedBox2Controller+Internal.h"
9
+
10
+ #import <React/RCTDefines.h>
11
+ #import <React/RCTJSStackFrame.h>
12
+ #import <React/RCTReloadCommand.h>
13
+ #import <React/RCTUtils.h>
14
+
15
+ #include <array>
16
+
17
+ #import "RCTJscSafeUrl+Internal.h"
18
+ #import "RCTRedBox2AnsiParser+Internal.h"
19
+ #import "RCTRedBox2ErrorParser+Internal.h"
20
+ #import "RCTRedBoxHMRClient+Internal.h"
21
+
22
+ // @lint-ignore-every CLANGTIDY clang-diagnostic-switch-default
23
+ // NOTE: clang-diagnostic-switch-default conflicts with clang-diagnostic-switch-enum
24
+
25
+ #if RCT_DEV_MENU
26
+
27
+ #pragma mark - RCTRedBox2Controller
28
+
29
+ // Color Palette (matching LogBoxStyle.js)
30
+ static UIColor *RCTRedBox2BackgroundColor()
31
+ {
32
+ return [UIColor colorWithRed:51.0 / 255 green:51.0 / 255 blue:51.0 / 255 alpha:1.0];
33
+ }
34
+
35
+ static UIColor *RCTRedBox2ErrorColor()
36
+ {
37
+ return [UIColor colorWithRed:243.0 / 255 green:83.0 / 255 blue:105.0 / 255 alpha:1.0];
38
+ }
39
+
40
+ static UIColor *RCTRedBox2TextColor(CGFloat opacity)
41
+ {
42
+ return [UIColor colorWithWhite:1.0 alpha:opacity];
43
+ }
44
+
45
+ enum class Section : uint8_t { Message, CodeFrame, CallStack, kMaxValue };
46
+ static constexpr size_t kSectionCount = static_cast<size_t>(Section::kMaxValue);
47
+
48
+ struct SectionState {
49
+ bool visible = false;
50
+ };
51
+
52
+ static const NSTimeInterval kAutoRetryInterval = 20.0;
53
+
54
+ @implementation RCTRedBox2Controller {
55
+ UITableView *_stackTraceTableView;
56
+ UILabel *_headerTitleLabel;
57
+ UILabel *_errorCategoryLabel;
58
+ NSString *_lastErrorMessage;
59
+ NSArray<RCTJSStackFrame *> *_lastStackTrace;
60
+ NSArray<NSString *> *_customButtonTitles;
61
+ NSArray<RCTRedBox2ButtonPressHandler> *_customButtonHandlers;
62
+ int _lastErrorCookie;
63
+ RCTRedBox2ErrorData *_errorData;
64
+ std::array<SectionState, kSectionCount> _sectionStates;
65
+ NSTimer *_autoRetryTimer;
66
+ NSInteger _autoRetryCountdown;
67
+ UIButton *_reloadButton;
68
+ NSString *_reloadBaseText;
69
+ RCTRedBoxHMRClient *_hmrClient;
70
+ }
71
+
72
+ - (instancetype)initWithCustomButtonTitles:(NSArray<NSString *> *)customButtonTitles
73
+ customButtonHandlers:(NSArray<RCTRedBox2ButtonPressHandler> *)customButtonHandlers
74
+ {
75
+ self = [super init];
76
+ if (self != nullptr) {
77
+ _lastErrorCookie = -1;
78
+ _customButtonTitles = customButtonTitles;
79
+ _customButtonHandlers = customButtonHandlers;
80
+ self.modalPresentationStyle = UIModalPresentationFullScreen;
81
+ }
82
+ return self;
83
+ }
84
+
85
+ - (void)viewDidLoad
86
+ {
87
+ [super viewDidLoad];
88
+ self.view.backgroundColor = RCTRedBox2BackgroundColor();
89
+
90
+ // Header bar (adds itself to self.view)
91
+ UIView *headerBar = [self createHeaderBar];
92
+
93
+ // Footer button bar
94
+ UIView *footerBar = [self createFooterBar];
95
+
96
+ // Stack trace table
97
+ _stackTraceTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
98
+ _stackTraceTableView.translatesAutoresizingMaskIntoConstraints = NO;
99
+ _stackTraceTableView.delegate = self;
100
+ _stackTraceTableView.dataSource = self;
101
+ _stackTraceTableView.backgroundColor = [UIColor clearColor];
102
+ #if !TARGET_OS_TV
103
+ _stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
104
+ #endif
105
+ _stackTraceTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
106
+ _stackTraceTableView.bounces = NO;
107
+ [self.view addSubview:_stackTraceTableView];
108
+
109
+ [NSLayoutConstraint activateConstraints:@[
110
+ [_stackTraceTableView.topAnchor constraintEqualToAnchor:headerBar.bottomAnchor],
111
+ [_stackTraceTableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
112
+ [_stackTraceTableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
113
+ [_stackTraceTableView.bottomAnchor constraintEqualToAnchor:footerBar.topAnchor],
114
+ ]];
115
+ }
116
+
117
+ #pragma mark - Header Bar
118
+
119
+ - (UIView *)createHeaderBar
120
+ {
121
+ UIView *headerContainer = [[UIView alloc] init];
122
+ headerContainer.translatesAutoresizingMaskIntoConstraints = NO;
123
+ headerContainer.backgroundColor = RCTRedBox2ErrorColor();
124
+
125
+ _headerTitleLabel = [[UILabel alloc] init];
126
+ _headerTitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
127
+ _headerTitleLabel.textColor = [UIColor whiteColor];
128
+ _headerTitleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
129
+ _headerTitleLabel.textAlignment = NSTextAlignmentCenter;
130
+ [headerContainer addSubview:_headerTitleLabel];
131
+
132
+ [self.view addSubview:headerContainer];
133
+
134
+ [NSLayoutConstraint activateConstraints:@[
135
+ [headerContainer.topAnchor constraintEqualToAnchor:self.view.topAnchor],
136
+ [headerContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
137
+ [headerContainer.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
138
+
139
+ [_headerTitleLabel.leadingAnchor constraintEqualToAnchor:headerContainer.leadingAnchor constant:12],
140
+ [_headerTitleLabel.trailingAnchor constraintEqualToAnchor:headerContainer.trailingAnchor constant:-12],
141
+ [_headerTitleLabel.bottomAnchor constraintEqualToAnchor:headerContainer.bottomAnchor constant:-12],
142
+ [_headerTitleLabel.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:12],
143
+ ]];
144
+
145
+ return headerContainer;
146
+ }
147
+
148
+ #pragma mark - Footer Bar
149
+
150
+ - (UIView *)createFooterBar
151
+ {
152
+ const CGFloat buttonHeight = 48;
153
+
154
+ NSString *reloadText = @"Reload";
155
+ NSString *dismissText = @"Dismiss";
156
+ NSString *copyText = @"Copy";
157
+
158
+ UIButton *dismissButton = [self footerButton:dismissText
159
+ accessibilityIdentifier:@"redbox-dismiss"
160
+ selector:@selector(dismiss)];
161
+ _reloadBaseText = reloadText;
162
+ _reloadButton = [self footerButton:reloadText accessibilityIdentifier:@"redbox-reload" selector:@selector(reload)];
163
+ UIButton *copyButton = [self footerButton:copyText
164
+ accessibilityIdentifier:@"redbox-copy"
165
+ selector:@selector(copyStack)];
166
+
167
+ UIStackView *buttonStackView = [[UIStackView alloc] init];
168
+ buttonStackView.translatesAutoresizingMaskIntoConstraints = NO;
169
+ buttonStackView.axis = UILayoutConstraintAxisHorizontal;
170
+ buttonStackView.distribution = UIStackViewDistributionFillEqually;
171
+ buttonStackView.alignment = UIStackViewAlignmentTop;
172
+ buttonStackView.backgroundColor = RCTRedBox2BackgroundColor();
173
+
174
+ [buttonStackView addArrangedSubview:dismissButton];
175
+ [buttonStackView addArrangedSubview:_reloadButton];
176
+ [buttonStackView addArrangedSubview:copyButton];
177
+
178
+ for (NSUInteger i = 0; i < [_customButtonTitles count]; i++) {
179
+ UIButton *button = [self footerButton:_customButtonTitles[i]
180
+ accessibilityIdentifier:@""
181
+ handler:_customButtonHandlers[i]];
182
+ [buttonStackView addArrangedSubview:button];
183
+ }
184
+
185
+ // Shadow layer above footer
186
+ buttonStackView.layer.shadowColor = [UIColor blackColor].CGColor;
187
+ buttonStackView.layer.shadowOffset = CGSizeMake(0, -2);
188
+ buttonStackView.layer.shadowRadius = 2;
189
+ buttonStackView.layer.shadowOpacity = 0.5;
190
+
191
+ [self.view addSubview:buttonStackView];
192
+
193
+ CGFloat bottomInset = [self bottomSafeViewHeight];
194
+
195
+ [NSLayoutConstraint activateConstraints:@[
196
+ [buttonStackView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
197
+ [buttonStackView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
198
+ [buttonStackView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
199
+ [buttonStackView.heightAnchor constraintEqualToConstant:buttonHeight + bottomInset],
200
+ ]];
201
+
202
+ for (UIButton *btn in buttonStackView.arrangedSubviews) {
203
+ [btn.heightAnchor constraintEqualToConstant:buttonHeight].active = YES;
204
+ }
205
+
206
+ return buttonStackView;
207
+ }
208
+
209
+ - (UIButton *)styledButton:(NSString *)title accessibilityIdentifier:(NSString *)accessibilityIdentifier
210
+ {
211
+ UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
212
+ button.accessibilityIdentifier = accessibilityIdentifier;
213
+ button.titleLabel.font = [UIFont systemFontOfSize:14];
214
+ button.titleLabel.textAlignment = NSTextAlignmentCenter;
215
+ button.backgroundColor = RCTRedBox2BackgroundColor();
216
+ [button setTitle:title forState:UIControlStateNormal];
217
+ [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
218
+ [button setTitleColor:RCTRedBox2TextColor(0.5) forState:UIControlStateHighlighted];
219
+ return button;
220
+ }
221
+
222
+ - (UIButton *)footerButton:(NSString *)title
223
+ accessibilityIdentifier:(NSString *)accessibilityIdentifier
224
+ selector:(SEL)selector
225
+ {
226
+ UIButton *button = [self styledButton:title accessibilityIdentifier:accessibilityIdentifier];
227
+ [button addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside];
228
+ return button;
229
+ }
230
+
231
+ - (UIButton *)footerButton:(NSString *)title
232
+ accessibilityIdentifier:(NSString *)accessibilityIdentifier
233
+ handler:(RCTRedBox2ButtonPressHandler)handler
234
+ {
235
+ UIButton *button = [self styledButton:title accessibilityIdentifier:accessibilityIdentifier];
236
+ [button addAction:[UIAction actionWithHandler:^(__unused UIAction *action) {
237
+ handler();
238
+ }]
239
+ forControlEvents:UIControlEventTouchUpInside];
240
+ return button;
241
+ }
242
+
243
+ - (CGFloat)bottomSafeViewHeight
244
+ {
245
+ #if TARGET_OS_MACCATALYST
246
+ return 0;
247
+ #else
248
+ return RCTKeyWindow().safeAreaInsets.bottom;
249
+ #endif
250
+ }
251
+
252
+ #pragma mark - Error Display
253
+
254
+ - (NSString *)stripAnsi:(NSString *)text
255
+ {
256
+ NSError *error = nil;
257
+ NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\x1b\\[[0-9;]*m"
258
+ options:NSRegularExpressionCaseInsensitive
259
+ error:&error];
260
+ return [regex stringByReplacingMatchesInString:text options:0 range:NSMakeRange(0, [text length]) withTemplate:@""];
261
+ }
262
+
263
+ - (void)showErrorMessage:(NSString *)message
264
+ withStack:(NSArray<RCTJSStackFrame *> *)stack
265
+ isUpdate:(BOOL)isUpdate
266
+ errorCookie:(int)errorCookie
267
+ {
268
+ // Remove ANSI color codes from the message
269
+ NSString *messageWithoutAnsi = [self stripAnsi:message];
270
+
271
+ BOOL isRootViewControllerPresented = self.presentingViewController != nil;
272
+ // Show if this is a new message, or if we're updating the previous message
273
+ BOOL isNew = !isRootViewControllerPresented && !isUpdate;
274
+ BOOL isUpdateForSameMessage = !isNew &&
275
+ (isRootViewControllerPresented && isUpdate &&
276
+ ((errorCookie == -1 && [_lastErrorMessage isEqualToString:messageWithoutAnsi]) ||
277
+ (errorCookie == _lastErrorCookie)));
278
+ if (isNew || isUpdateForSameMessage) {
279
+ _lastStackTrace = stack;
280
+ // message is displayed using UILabel, which is unable to render text of
281
+ // unlimited length, so we truncate it
282
+ _lastErrorMessage = [messageWithoutAnsi substringToIndex:MIN((NSUInteger)10000, messageWithoutAnsi.length)];
283
+ _lastErrorCookie = errorCookie;
284
+
285
+ // Parse the message to extract structure (title, code frame, etc.)
286
+ _errorData = [RCTRedBox2ErrorParser parseErrorMessage:message name:nil componentStack:nil isFatal:YES];
287
+ [self updateSectionVisibility];
288
+
289
+ [_stackTraceTableView reloadData];
290
+
291
+ if (!isRootViewControllerPresented) {
292
+ [RCTKeyWindow().rootViewController presentViewController:self animated:NO completion:nil];
293
+ }
294
+
295
+ // Update all UI from _errorData (view is now guaranteed to be loaded)
296
+ _headerTitleLabel.text = _errorData.isCompileError ? @"Failed to compile" : @"Error";
297
+ [_stackTraceTableView reloadData];
298
+ [_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
299
+ atScrollPosition:UITableViewScrollPositionTop
300
+ animated:NO];
301
+
302
+ [self startAutoRetryIfApplicable];
303
+ [self _startHMRClient];
304
+ }
305
+ }
306
+
307
+ - (void)dismiss
308
+ {
309
+ [self stopAutoRetry];
310
+ [self dismissViewControllerAnimated:NO completion:nil];
311
+ }
312
+
313
+ - (void)reload
314
+ {
315
+ [self _stopHMRClient];
316
+ [self stopAutoRetry];
317
+ if (_actionDelegate != nil) {
318
+ [_actionDelegate reloadFromRedBoxController:self];
319
+ } else {
320
+ // In bridgeless mode `RCTRedBox` gets deallocated, we need to notify listeners anyway.
321
+ RCTTriggerReloadCommandListeners(@"Redbox");
322
+ [self dismiss];
323
+ }
324
+ }
325
+
326
+ #pragma mark - Native HMR Connection
327
+
328
+ - (void)_startHMRClient
329
+ {
330
+ [self _stopHMRClient];
331
+ if (!_bundleURL) {
332
+ return;
333
+ }
334
+ __weak __typeof(self) weakSelf = self;
335
+ _hmrClient = [[RCTRedBoxHMRClient alloc] initWithBundleURL:_bundleURL
336
+ onFileChange:^{
337
+ [weakSelf reload];
338
+ }];
339
+ [_hmrClient start];
340
+ }
341
+
342
+ - (void)_stopHMRClient
343
+ {
344
+ [_hmrClient stop];
345
+ _hmrClient = nil;
346
+ }
347
+
348
+ #pragma mark - Auto-Retry
349
+
350
+ - (void)startAutoRetryIfApplicable
351
+ {
352
+ [self stopAutoRetry];
353
+ if (!_errorData.isRetryable) {
354
+ return;
355
+ }
356
+ _autoRetryCountdown = (NSInteger)kAutoRetryInterval;
357
+ [self updateReloadButtonTitle];
358
+ _autoRetryTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
359
+ target:self
360
+ selector:@selector(autoRetryTick)
361
+ userInfo:nil
362
+ repeats:YES];
363
+ }
364
+
365
+ - (void)stopAutoRetry
366
+ {
367
+ [_autoRetryTimer invalidate];
368
+ _autoRetryTimer = nil;
369
+ if (_reloadButton) {
370
+ [_reloadButton setTitle:_reloadBaseText forState:UIControlStateNormal];
371
+ }
372
+ }
373
+
374
+ - (void)autoRetryTick
375
+ {
376
+ _autoRetryCountdown--;
377
+ if (_autoRetryCountdown <= 0) {
378
+ [self stopAutoRetry];
379
+ [self reload];
380
+ } else {
381
+ [self updateReloadButtonTitle];
382
+ }
383
+ }
384
+
385
+ - (void)updateReloadButtonTitle
386
+ {
387
+ NSString *title = [NSString stringWithFormat:@"%@ (%lds)", _reloadBaseText, (long)_autoRetryCountdown];
388
+ [_reloadButton setTitle:title forState:UIControlStateNormal];
389
+ }
390
+
391
+ - (void)copyStack
392
+ {
393
+ NSMutableString *fullStackTrace;
394
+
395
+ if (_lastErrorMessage != nil) {
396
+ fullStackTrace = [_lastErrorMessage mutableCopy];
397
+ [fullStackTrace appendString:@"\n\n"];
398
+ } else {
399
+ fullStackTrace = [NSMutableString string];
400
+ }
401
+
402
+ for (RCTJSStackFrame *stackFrame in _lastStackTrace) {
403
+ [fullStackTrace appendString:[NSString stringWithFormat:@"%@\n", stackFrame.methodName]];
404
+ if (stackFrame.file != nullptr) {
405
+ [fullStackTrace appendFormat:@" %@\n", [self formatFrameSource:stackFrame]];
406
+ }
407
+ }
408
+ #if !TARGET_OS_TV
409
+ UIPasteboard *pb = [UIPasteboard generalPasteboard];
410
+ [pb setString:fullStackTrace];
411
+ #endif
412
+ }
413
+
414
+ - (NSString *)formatFrameSource:(RCTJSStackFrame *)stackFrame
415
+ {
416
+ NSString *file = [RCTJscSafeUrl normalUrlFromJscSafeUrl:stackFrame.file];
417
+ // Strip query string (e.g. ?platform=ios&dev=true) before extracting the filename.
418
+ NSRange queryRange = [file rangeOfString:@"?"];
419
+ if (queryRange.location != NSNotFound) {
420
+ file = [file substringToIndex:queryRange.location];
421
+ }
422
+ NSString *fileName = RCTNilIfNull(file) ? [file lastPathComponent] : @"<unknown file>";
423
+ NSString *lineInfo = [NSString stringWithFormat:@"%@:%lld", fileName, (long long)stackFrame.lineNumber];
424
+
425
+ if (stackFrame.column != 0) {
426
+ lineInfo = [lineInfo stringByAppendingFormat:@":%lld", (long long)stackFrame.column];
427
+ }
428
+ return lineInfo;
429
+ }
430
+
431
+ #pragma mark - Section Helpers
432
+
433
+ - (void)updateSectionVisibility
434
+ {
435
+ _sectionStates = {};
436
+ _sectionStates[static_cast<size_t>(Section::Message)].visible = true;
437
+ _sectionStates[static_cast<size_t>(Section::CodeFrame)].visible = _errorData.codeFrame.length > 0;
438
+ _sectionStates[static_cast<size_t>(Section::CallStack)].visible =
439
+ _lastStackTrace.count > 0 && _errorData.codeFrame.length == 0;
440
+ }
441
+
442
+ - (NSInteger)visibleSectionCount
443
+ {
444
+ NSInteger count = 0;
445
+ for (size_t i = 0; i < kSectionCount; i++) {
446
+ if (_sectionStates[i].visible) {
447
+ count++;
448
+ }
449
+ }
450
+ return count;
451
+ }
452
+
453
+ - (Section)sectionForIndex:(NSInteger)index
454
+ {
455
+ NSInteger visible = 0;
456
+ for (size_t i = 0; i < kSectionCount; i++) {
457
+ if (_sectionStates[i].visible) {
458
+ if (visible == index) {
459
+ return static_cast<Section>(i);
460
+ }
461
+ visible++;
462
+ }
463
+ }
464
+ RCTAssert(NO, @"Invalid section index %ld", (long)index);
465
+ return Section::kMaxValue;
466
+ }
467
+
468
+ - (NSString *)displayMessage
469
+ {
470
+ return _errorData.message.length > 0 ? [self stripAnsi:_errorData.message] : _lastErrorMessage;
471
+ }
472
+
473
+ #pragma mark - TableView DataSource & Delegate
474
+
475
+ - (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView
476
+ {
477
+ return [self visibleSectionCount];
478
+ }
479
+
480
+ - (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:(NSInteger)section
481
+ {
482
+ if ([self sectionForIndex:section] == Section::CallStack) {
483
+ return static_cast<NSInteger>(_lastStackTrace.count);
484
+ }
485
+ return 1;
486
+ }
487
+
488
+ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
489
+ {
490
+ switch ([self sectionForIndex:indexPath.section]) {
491
+ case Section::Message: {
492
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"msg-cell"];
493
+ return [self reuseCell:cell forErrorMessage:[self displayMessage]];
494
+ }
495
+ case Section::CodeFrame: {
496
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"code-cell"];
497
+ return [self reuseCell:cell forCodeFrame:_errorData];
498
+ }
499
+ case Section::CallStack:
500
+ case Section::kMaxValue:
501
+ break;
502
+ }
503
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
504
+ NSUInteger index = indexPath.row;
505
+ RCTJSStackFrame *stackFrame = _lastStackTrace[index];
506
+ return [self reuseCell:cell forStackFrame:stackFrame];
507
+ }
508
+
509
+ - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forErrorMessage:(NSString *)message
510
+ {
511
+ if (cell == nullptr) {
512
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"msg-cell"];
513
+ cell.backgroundColor = RCTRedBox2BackgroundColor();
514
+ cell.selectionStyle = UITableViewCellSelectionStyleNone;
515
+
516
+ // Error category label (e.g. "Syntax Error", "Uncaught Error")
517
+ _errorCategoryLabel = [[UILabel alloc] init];
518
+ _errorCategoryLabel.translatesAutoresizingMaskIntoConstraints = NO;
519
+ _errorCategoryLabel.textColor = RCTRedBox2ErrorColor();
520
+ _errorCategoryLabel.font = [UIFont systemFontOfSize:21 weight:UIFontWeightBold];
521
+ _errorCategoryLabel.numberOfLines = 1;
522
+ [cell.contentView addSubview:_errorCategoryLabel];
523
+
524
+ // Error message label
525
+ UILabel *messageLabel = [[UILabel alloc] init];
526
+ messageLabel.translatesAutoresizingMaskIntoConstraints = NO;
527
+ messageLabel.accessibilityIdentifier = @"redbox-error";
528
+ messageLabel.textColor = [UIColor whiteColor];
529
+ messageLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
530
+ messageLabel.lineBreakMode = NSLineBreakByWordWrapping;
531
+ messageLabel.numberOfLines = 0;
532
+ messageLabel.tag = 100;
533
+ [cell.contentView addSubview:messageLabel];
534
+
535
+ [NSLayoutConstraint activateConstraints:@[
536
+ [_errorCategoryLabel.topAnchor constraintEqualToAnchor:cell.contentView.topAnchor constant:15],
537
+ [_errorCategoryLabel.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:12],
538
+ [_errorCategoryLabel.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-12],
539
+
540
+ [messageLabel.topAnchor constraintEqualToAnchor:_errorCategoryLabel.bottomAnchor constant:10],
541
+ [messageLabel.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:12],
542
+ [messageLabel.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-12],
543
+ [messageLabel.bottomAnchor constraintEqualToAnchor:cell.contentView.bottomAnchor constant:-15],
544
+ ]];
545
+ }
546
+
547
+ _errorCategoryLabel.text = _errorData.title;
548
+ UILabel *messageLabel = [cell.contentView viewWithTag:100];
549
+ messageLabel.text = message;
550
+
551
+ return cell;
552
+ }
553
+
554
+ - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(RCTJSStackFrame *)stackFrame
555
+ {
556
+ if (cell == nullptr) {
557
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
558
+ cell.textLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:14];
559
+ cell.textLabel.lineBreakMode = NSLineBreakByCharWrapping;
560
+ cell.textLabel.numberOfLines = 2;
561
+ cell.detailTextLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightLight];
562
+ cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
563
+ cell.backgroundColor = [UIColor clearColor];
564
+ cell.selectedBackgroundView = [UIView new];
565
+ cell.selectedBackgroundView.backgroundColor = RCTRedBox2BackgroundColor();
566
+ cell.selectedBackgroundView.layer.cornerRadius = 5;
567
+ }
568
+
569
+ cell.textLabel.text = stackFrame.methodName ?: @"(unnamed method)";
570
+ if (stackFrame.file != nullptr) {
571
+ cell.detailTextLabel.text = [self formatFrameSource:stackFrame];
572
+ } else {
573
+ cell.detailTextLabel.text = @"";
574
+ }
575
+
576
+ if (stackFrame.collapse) {
577
+ cell.textLabel.textColor = RCTRedBox2TextColor(0.4);
578
+ cell.detailTextLabel.textColor = RCTRedBox2TextColor(0.3);
579
+ } else {
580
+ cell.textLabel.textColor = [UIColor whiteColor];
581
+ cell.detailTextLabel.textColor = RCTRedBox2TextColor(0.8);
582
+ }
583
+
584
+ return cell;
585
+ }
586
+
587
+ - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forCodeFrame:(RCTRedBox2ErrorData *)errorData
588
+ {
589
+ if (cell == nullptr) {
590
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"code-cell"];
591
+ cell.backgroundColor = [UIColor clearColor];
592
+ cell.selectionStyle = UITableViewCellSelectionStyleNone;
593
+ }
594
+
595
+ // Remove old subviews
596
+ for (UIView *subview in cell.contentView.subviews) {
597
+ [subview removeFromSuperview];
598
+ }
599
+
600
+ // Code frame container with rounded corners
601
+ UIView *container = [[UIView alloc] init];
602
+ container.translatesAutoresizingMaskIntoConstraints = NO;
603
+ container.backgroundColor = RCTRedBox2BackgroundColor();
604
+ container.layer.cornerRadius = 3;
605
+ container.clipsToBounds = YES;
606
+ [cell.contentView addSubview:container];
607
+
608
+ // Render code frame with ANSI syntax highlighting
609
+ UIFont *codeFont = [UIFont fontWithName:@"Menlo-Regular" size:12];
610
+ NSAttributedString *highlighted = [RCTRedBox2AnsiParser attributedStringFromAnsiText:errorData.codeFrame
611
+ baseFont:codeFont
612
+ baseColor:[UIColor whiteColor]];
613
+
614
+ UILabel *codeLabel = [[UILabel alloc] init];
615
+ codeLabel.translatesAutoresizingMaskIntoConstraints = NO;
616
+ codeLabel.attributedText = highlighted;
617
+ codeLabel.numberOfLines = 0;
618
+ codeLabel.lineBreakMode = NSLineBreakByClipping;
619
+
620
+ UIScrollView *codeScrollView = [[UIScrollView alloc] init];
621
+ codeScrollView.translatesAutoresizingMaskIntoConstraints = NO;
622
+ codeScrollView.showsHorizontalScrollIndicator = YES;
623
+ codeScrollView.showsVerticalScrollIndicator = NO;
624
+ codeScrollView.bounces = NO;
625
+ [codeScrollView addSubview:codeLabel];
626
+ [container addSubview:codeScrollView];
627
+
628
+ // File name label below the code frame
629
+ UILabel *fileLabel = [[UILabel alloc] init];
630
+ fileLabel.translatesAutoresizingMaskIntoConstraints = NO;
631
+ NSString *fileName = errorData.codeFrameFileName.lastPathComponent ?: errorData.codeFrameFileName;
632
+ if (errorData.codeFrameRow > 0) {
633
+ fileLabel.text = [NSString
634
+ stringWithFormat:@"%@ (%ld:%ld)", fileName, (long)errorData.codeFrameRow, (long)errorData.codeFrameColumn + 1];
635
+ } else if (fileName.length > 0) {
636
+ fileLabel.text = fileName;
637
+ }
638
+ fileLabel.textColor = RCTRedBox2TextColor(0.5);
639
+ fileLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:12];
640
+ fileLabel.textAlignment = NSTextAlignmentCenter;
641
+ [cell.contentView addSubview:fileLabel];
642
+
643
+ [NSLayoutConstraint activateConstraints:@[
644
+ [container.topAnchor constraintEqualToAnchor:cell.contentView.topAnchor constant:5],
645
+ [container.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:10],
646
+ [container.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-10],
647
+
648
+ [codeScrollView.topAnchor constraintEqualToAnchor:container.topAnchor constant:10],
649
+ [codeScrollView.leadingAnchor constraintEqualToAnchor:container.leadingAnchor constant:10],
650
+ [codeScrollView.trailingAnchor constraintEqualToAnchor:container.trailingAnchor constant:-10],
651
+ [codeScrollView.bottomAnchor constraintEqualToAnchor:container.bottomAnchor constant:-10],
652
+
653
+ [codeLabel.topAnchor constraintEqualToAnchor:codeScrollView.topAnchor],
654
+ [codeLabel.leadingAnchor constraintEqualToAnchor:codeScrollView.leadingAnchor],
655
+ [codeLabel.trailingAnchor constraintEqualToAnchor:codeScrollView.trailingAnchor],
656
+ [codeLabel.bottomAnchor constraintEqualToAnchor:codeScrollView.bottomAnchor],
657
+ [codeLabel.heightAnchor constraintEqualToAnchor:codeScrollView.heightAnchor],
658
+
659
+ [fileLabel.topAnchor constraintEqualToAnchor:container.bottomAnchor constant:10],
660
+ [fileLabel.leadingAnchor constraintEqualToAnchor:cell.contentView.leadingAnchor constant:10],
661
+ [fileLabel.trailingAnchor constraintEqualToAnchor:cell.contentView.trailingAnchor constant:-10],
662
+ [fileLabel.bottomAnchor constraintEqualToAnchor:cell.contentView.bottomAnchor constant:-10],
663
+ ]];
664
+
665
+ return cell;
666
+ }
667
+
668
+ - (CGFloat)tableView:(__unused UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
669
+ {
670
+ auto section = [self sectionForIndex:indexPath.section];
671
+ if (section == Section::Message || section == Section::CodeFrame) {
672
+ return UITableViewAutomaticDimension;
673
+ }
674
+ return 50;
675
+ }
676
+
677
+ - (CGFloat)tableView:(__unused UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
678
+ {
679
+ switch ([self sectionForIndex:indexPath.section]) {
680
+ case Section::Message:
681
+ return 100;
682
+ case Section::CodeFrame:
683
+ return 200;
684
+ case Section::CallStack:
685
+ case Section::kMaxValue:
686
+ return 50;
687
+ }
688
+ }
689
+
690
+ - (UIView *)sectionHeaderViewWithTitle:(NSString *)title
691
+ {
692
+ UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 38)];
693
+ headerView.backgroundColor = [UIColor clearColor];
694
+
695
+ UILabel *label = [[UILabel alloc] init];
696
+ label.translatesAutoresizingMaskIntoConstraints = NO;
697
+ label.text = title;
698
+ label.textColor = [UIColor whiteColor];
699
+ label.font = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold];
700
+ [headerView addSubview:label];
701
+
702
+ [NSLayoutConstraint activateConstraints:@[
703
+ [label.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor constant:12],
704
+ [label.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor constant:-12],
705
+ [label.bottomAnchor constraintEqualToAnchor:headerView.bottomAnchor constant:-10],
706
+ ]];
707
+
708
+ return headerView;
709
+ }
710
+
711
+ - (UIView *)tableView:(__unused UITableView *)tableView viewForHeaderInSection:(NSInteger)section
712
+ {
713
+ switch ([self sectionForIndex:section]) {
714
+ case Section::CodeFrame:
715
+ return [self sectionHeaderViewWithTitle:@"Source"];
716
+ case Section::CallStack:
717
+ return [self sectionHeaderViewWithTitle:@"Call Stack"];
718
+ case Section::Message:
719
+ case Section::kMaxValue:
720
+ return nil;
721
+ }
722
+ }
723
+
724
+ - (CGFloat)tableView:(__unused UITableView *)tableView heightForHeaderInSection:(NSInteger)section
725
+ {
726
+ auto s = [self sectionForIndex:section];
727
+ return (s == Section::CodeFrame || s == Section::CallStack) ? 38 : 0;
728
+ }
729
+
730
+ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
731
+ {
732
+ if ([self sectionForIndex:indexPath.section] == Section::CallStack) {
733
+ NSUInteger row = indexPath.row;
734
+ RCTJSStackFrame *stackFrame = _lastStackTrace[row];
735
+ [_actionDelegate redBoxController:self openStackFrameInEditor:stackFrame];
736
+ }
737
+ [tableView deselectRowAtIndexPath:indexPath animated:YES];
738
+ }
739
+
740
+ #pragma mark - Key Commands
741
+
742
+ - (NSArray<UIKeyCommand *> *)keyCommands
743
+ {
744
+ return @[
745
+ // Dismiss red box
746
+ [UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:0 action:@selector(dismiss)],
747
+ // Reload
748
+ [UIKeyCommand keyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand action:@selector(reload)],
749
+ // Copy = Cmd-Option C since Cmd-C in the simulator copies the pasteboard from
750
+ // the simulator to the desktop pasteboard.
751
+ [UIKeyCommand keyCommandWithInput:@"c"
752
+ modifierFlags:UIKeyModifierCommand | UIKeyModifierAlternate
753
+ action:@selector(copyStack)],
754
+ ];
755
+ }
756
+
757
+ - (BOOL)canBecomeFirstResponder
758
+ {
759
+ return YES;
760
+ }
761
+
762
+ @end
763
+
764
+ #endif