react-native 0.83.6 → 0.83.7

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 (52) hide show
  1. package/Libraries/Core/ReactNativeVersion.js +1 -1
  2. package/React/Base/RCTVersion.m +1 -1
  3. package/React/CoreModules/RCTJscSafeUrl+Internal.h +23 -0
  4. package/React/CoreModules/RCTJscSafeUrl.mm +38 -0
  5. package/React/CoreModules/RCTRedBox+Internal.h +42 -0
  6. package/React/CoreModules/RCTRedBox.mm +30 -450
  7. package/React/CoreModules/RCTRedBox2AnsiParser+Internal.h +22 -0
  8. package/React/CoreModules/RCTRedBox2AnsiParser.mm +55 -0
  9. package/React/CoreModules/RCTRedBox2Controller+Internal.h +34 -0
  10. package/React/CoreModules/RCTRedBox2Controller.mm +764 -0
  11. package/React/CoreModules/RCTRedBox2ErrorParser+Internal.h +46 -0
  12. package/React/CoreModules/RCTRedBox2ErrorParser.mm +57 -0
  13. package/React/CoreModules/RCTRedBoxController+Internal.h +31 -0
  14. package/React/CoreModules/RCTRedBoxController.mm +447 -0
  15. package/React/CoreModules/RCTRedBoxHMRClient+Internal.h +26 -0
  16. package/React/CoreModules/RCTRedBoxHMRClient.mm +125 -0
  17. package/React/CoreModules/React-CoreModules.podspec +1 -0
  18. package/React/FBReactNativeSpec/FBReactNativeSpecJSI.h +16 -0
  19. package/ReactAndroid/gradle.properties +1 -1
  20. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +13 -1
  21. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +21 -1
  22. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +5 -1
  23. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +5 -1
  24. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +23 -1
  25. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +5 -1
  26. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
  27. package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +29 -1
  28. package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +7 -1
  29. package/ReactCommon/cxxreact/ReactNativeVersion.h +2 -2
  30. package/ReactCommon/react/debug/CMakeLists.txt +2 -1
  31. package/ReactCommon/react/debug/React-debug.podspec +7 -1
  32. package/ReactCommon/react/debug/redbox/AnsiParser.cpp +139 -0
  33. package/ReactCommon/react/debug/redbox/AnsiParser.h +35 -0
  34. package/ReactCommon/react/debug/redbox/JscSafeUrl.cpp +179 -0
  35. package/ReactCommon/react/debug/redbox/JscSafeUrl.h +27 -0
  36. package/ReactCommon/react/debug/redbox/RedBoxErrorParser.cpp +171 -0
  37. package/ReactCommon/react/debug/redbox/RedBoxErrorParser.h +40 -0
  38. package/ReactCommon/react/debug/redbox/tests/AnsiParserTest.cpp +97 -0
  39. package/ReactCommon/react/debug/redbox/tests/JscSafeUrlTest.cpp +173 -0
  40. package/ReactCommon/react/debug/redbox/tests/RedBoxErrorParserTest.cpp +107 -0
  41. package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +9 -1
  42. package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +11 -1
  43. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +58 -22
  44. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +6 -2
  45. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +9 -1
  46. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +19 -1
  47. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +3 -1
  48. package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +11 -1
  49. package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +5 -1
  50. package/package.json +8 -8
  51. package/src/private/featureflags/ReactNativeFeatureFlags.js +11 -1
  52. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +3 -1
@@ -0,0 +1,179 @@
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
+ #include "JscSafeUrl.h"
9
+
10
+ #include <cassert>
11
+ #include <regex> // NOLINT(facebook-hte-BadInclude-regex)
12
+ #include <stdexcept>
13
+ #include <string_view>
14
+
15
+ // @lint-ignore-every CLANGTIDY facebook-hte-StdRegexIsAwful
16
+
17
+ namespace facebook::react::unstable_redbox {
18
+
19
+ namespace {
20
+
21
+ // We use regex-based URL parsing as defined in RFC 3986 because it's easier to
22
+ // determine whether the input is a complete URI, a path-absolute or a
23
+ // path-rootless (as defined in the spec), and be as faithful to the input as
24
+ // possible. This will match any string, and does not imply validity.
25
+ //
26
+ // https://www.rfc-editor.org/rfc/rfc3986#appendix-B
27
+ const std::regex& uriRegex() {
28
+ static const std::regex re(
29
+ R"(^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)");
30
+ return re;
31
+ }
32
+
33
+ struct ParsedUri {
34
+ std::string_view schemeAndAuthority;
35
+ std::string_view path;
36
+ bool hasQueryPart = false;
37
+ std::string_view queryWithoutQuestionMark;
38
+ std::string_view fragmentWithHash;
39
+ };
40
+
41
+ ParsedUri rfc3986Parse(std::string_view url) {
42
+ std::cmatch match;
43
+ if (!std::regex_match(
44
+ url.data(), url.data() + url.size(), match, uriRegex())) {
45
+ throw std::runtime_error("Unexpected error - failed to regex-match URL");
46
+ }
47
+
48
+ // match[1] = scheme with colon (e.g. "http:")
49
+ // match[3] = authority with slashes (e.g. "//host")
50
+ // match[5] = path
51
+ // match[6] = query with question mark (e.g. "?key=val")
52
+ // match[7] = query without question mark
53
+ // match[8] = fragment with hash (e.g. "#frag")
54
+
55
+ auto viewOf = [&](int group) -> std::string_view {
56
+ if (!match[group].matched) {
57
+ return {};
58
+ }
59
+ return {match[group].first, static_cast<size_t>(match[group].length())};
60
+ };
61
+
62
+ // schemeAndAuthority = (match[1] || "") + (match[3] || "")
63
+ // These are contiguous when both present, but may be individually absent.
64
+ std::string_view schemeAndAuthority;
65
+ if (match[1].matched && match[3].matched) {
66
+ assert(match[1].second == match[3].first);
67
+ schemeAndAuthority = {
68
+ match[1].first, static_cast<size_t>(match[3].second - match[1].first)};
69
+ } else if (match[1].matched) {
70
+ schemeAndAuthority = viewOf(1);
71
+ } else if (match[3].matched) {
72
+ schemeAndAuthority = viewOf(3);
73
+ }
74
+
75
+ return ParsedUri{
76
+ .schemeAndAuthority = schemeAndAuthority,
77
+ .path = viewOf(5),
78
+ .hasQueryPart = match[6].matched,
79
+ .queryWithoutQuestionMark = viewOf(7),
80
+ .fragmentWithHash = viewOf(8),
81
+ };
82
+ }
83
+
84
+ } // namespace
85
+
86
+ bool isJscSafeUrl(std::string_view url) {
87
+ return !rfc3986Parse(url).hasQueryPart;
88
+ }
89
+
90
+ std::string toNormalUrl(std::string url) {
91
+ auto parsed = rfc3986Parse(url);
92
+ auto markerPos = parsed.path.find("//&");
93
+ if (markerPos == std::string_view::npos) {
94
+ return url;
95
+ }
96
+
97
+ // path before //&, then ?, then path after //&
98
+ std::string_view pathBefore = parsed.path.substr(0, markerPos);
99
+ std::string_view pathAfter = parsed.path.substr(markerPos + 3);
100
+
101
+ // We don't expect JSC urls to also have query strings, but interpret
102
+ // liberally and append them.
103
+ bool hasExistingQuery = !parsed.queryWithoutQuestionMark.empty();
104
+
105
+ // Likewise, JSC URLs will usually have their fragments stripped, but
106
+ // preserve if we find one.
107
+ size_t totalSize = parsed.schemeAndAuthority.size() + pathBefore.size() +
108
+ 1 /* ? */ + pathAfter.size() +
109
+ (hasExistingQuery ? 1 + parsed.queryWithoutQuestionMark.size() : 0) +
110
+ parsed.fragmentWithHash.size();
111
+
112
+ std::string result;
113
+ result.reserve(totalSize);
114
+ result += parsed.schemeAndAuthority;
115
+ result += pathBefore;
116
+ result += '?';
117
+ result += pathAfter;
118
+ if (hasExistingQuery) {
119
+ result += '&';
120
+ result += parsed.queryWithoutQuestionMark;
121
+ }
122
+ result += parsed.fragmentWithHash;
123
+ assert(result.size() == totalSize);
124
+ return result;
125
+ }
126
+
127
+ std::string toJscSafeUrl(std::string url) {
128
+ if (!rfc3986Parse(url).hasQueryPart) {
129
+ return url;
130
+ }
131
+ url = toNormalUrl(std::move(url));
132
+ auto parsed = rfc3986Parse(url);
133
+ if (!parsed.queryWithoutQuestionMark.empty() &&
134
+ (parsed.path.empty() || parsed.path == "/")) {
135
+ throw std::invalid_argument(
136
+ "The given URL \"" + url +
137
+ "\" has an empty path and cannot be converted to a JSC-safe format.");
138
+ }
139
+
140
+ // Query strings may contain '?' (e.g. in key or value names) - these
141
+ // must be percent-encoded to form a valid path, and not be stripped.
142
+ // Count them first so we can preallocate exactly.
143
+ bool hasQuery = !parsed.queryWithoutQuestionMark.empty();
144
+ size_t questionMarks = 0;
145
+ if (hasQuery) {
146
+ for (char c : parsed.queryWithoutQuestionMark) {
147
+ if (c == '?') {
148
+ questionMarks++;
149
+ }
150
+ }
151
+ }
152
+
153
+ // Each '?' becomes "%3F" (+2 bytes), plus "//&" delimiter (+3 bytes)
154
+ size_t totalSize = parsed.schemeAndAuthority.size() + parsed.path.size() +
155
+ (hasQuery ? 3 + parsed.queryWithoutQuestionMark.size() + questionMarks * 2
156
+ : 0) +
157
+ // We expect JSC to strip this - we don't handle fragments for now.
158
+ parsed.fragmentWithHash.size();
159
+
160
+ std::string result;
161
+ result.reserve(totalSize);
162
+ result += parsed.schemeAndAuthority;
163
+ result += parsed.path;
164
+ if (hasQuery) {
165
+ result += "//&";
166
+ for (char c : parsed.queryWithoutQuestionMark) {
167
+ if (c == '?') {
168
+ result += "%3F";
169
+ } else {
170
+ result += c;
171
+ }
172
+ }
173
+ }
174
+ result += parsed.fragmentWithHash;
175
+ assert(result.size() == totalSize);
176
+ return result;
177
+ }
178
+
179
+ } // namespace facebook::react::unstable_redbox
@@ -0,0 +1,27 @@
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
+ #pragma once
9
+
10
+ #include <string>
11
+ #include <string_view>
12
+
13
+ namespace facebook::react::unstable_redbox {
14
+
15
+ /**
16
+ * These functions are for handling of query-string free URLs, necessitated
17
+ * by query string stripping of URLs in JavaScriptCore stack traces
18
+ * introduced in iOS 16.4. This is a direct port of https://www.npmjs.com/package/jsc-safe-url.
19
+ *
20
+ * See https://github.com/facebook/react-native/issues/36794 for context.
21
+ */
22
+
23
+ bool isJscSafeUrl(std::string_view url);
24
+ std::string toNormalUrl(std::string url);
25
+ std::string toJscSafeUrl(std::string url);
26
+
27
+ } // namespace facebook::react::unstable_redbox
@@ -0,0 +1,171 @@
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
+ #include "RedBoxErrorParser.h"
9
+
10
+ #include <regex> // NOLINT(facebook-hte-BadInclude-regex)
11
+ #include <unordered_set>
12
+
13
+ // @lint-ignore-every CLANGTIDY facebook-hte-StdRegexIsAwful
14
+ namespace facebook::react::unstable_redbox {
15
+
16
+ namespace {
17
+
18
+ const std::regex& metroErrorRegex() {
19
+ static const std::regex re(
20
+ R"(^(?:InternalError Metro has encountered an error:) (.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+))");
21
+ return re;
22
+ }
23
+
24
+ const std::regex& babelTransformErrorRegex() {
25
+ static const std::regex re(
26
+ R"(^(?:TransformError )?(?:SyntaxError: |ReferenceError: )(.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+))");
27
+ return re;
28
+ }
29
+
30
+ const std::regex& bundleLoadErrorRegex() {
31
+ static const std::regex re(R"(^(\w+) in (\S+): (.+) \((\d+):(\d+)\))");
32
+ return re;
33
+ }
34
+
35
+ const std::regex& babelCodeFrameErrorRegex() {
36
+ static const std::regex re(
37
+ R"(^(?:TransformError )?(?:.*):? (?:.*?)(\/.*): ([\s\S]+?)\n([ >]{2}[\d\s]+ \|[\s\S]+|\x1b[\s\S]+))");
38
+ return re;
39
+ }
40
+
41
+ bool startsWithTransformError(const std::string& msg) {
42
+ return msg.rfind("TransformError ", 0) == 0;
43
+ }
44
+
45
+ const std::unordered_set<std::string>& knownBundleLoadErrorTypes() {
46
+ static const std::unordered_set<std::string> types{
47
+ "SyntaxError", "ReferenceError", "TypeError", "UnableToResolveError"};
48
+ return types;
49
+ }
50
+
51
+ } // namespace
52
+
53
+ ParsedError parseErrorMessage(
54
+ const std::string& message,
55
+ const std::string& name,
56
+ const std::string& componentStack,
57
+ bool isFatal) {
58
+ std::smatch match;
59
+
60
+ if (message.empty()) {
61
+ return ParsedError{
62
+ .title = isFatal ? "Uncaught Error" : "Error",
63
+ .message = "",
64
+ .codeFrame = std::nullopt,
65
+ .isCompileError = false,
66
+ };
67
+ }
68
+
69
+ // 1. Metro internal error
70
+ if (std::regex_search(message, match, metroErrorRegex())) {
71
+ return ParsedError{
72
+ .title = match[1].str().empty() ? "Metro Error" : match[1].str(),
73
+ .message = match[2].str(),
74
+ .codeFrame =
75
+ CodeFrame{
76
+ .content = match[5].str(),
77
+ .fileName = "",
78
+ .row = std::stoi(match[3].str()),
79
+ .column = std::stoi(match[4].str()),
80
+ },
81
+ .isCompileError = true,
82
+ };
83
+ }
84
+
85
+ // 2. Babel transform error
86
+ if (std::regex_search(message, match, babelTransformErrorRegex())) {
87
+ return ParsedError{
88
+ .title = "Syntax Error",
89
+ .message = match[2].str(),
90
+ .codeFrame =
91
+ CodeFrame{
92
+ .content = match[5].str(),
93
+ .fileName = match[1].str(),
94
+ .row = std::stoi(match[3].str()),
95
+ .column = std::stoi(match[4].str()),
96
+ },
97
+ .isCompileError = true,
98
+ };
99
+ }
100
+
101
+ // 3. Bundle loading error: "ErrorType in /path: message (line:col)"
102
+ if (std::regex_search(message, match, bundleLoadErrorRegex())) {
103
+ const auto& errorType = match[1].str();
104
+ if (knownBundleLoadErrorTypes().count(errorType) > 0) {
105
+ std::string title = errorType == "UnableToResolveError"
106
+ ? "Module Not Found"
107
+ : "Syntax Error";
108
+ std::optional<std::string> codeFrameContent;
109
+ auto newlinePos = message.find('\n');
110
+ if (newlinePos != std::string::npos) {
111
+ codeFrameContent = message.substr(newlinePos + 1);
112
+ }
113
+ return ParsedError{
114
+ .title = title,
115
+ .message = match[3].str(),
116
+ .codeFrame =
117
+ CodeFrame{
118
+ .content = codeFrameContent.value_or(""),
119
+ .fileName = match[2].str(),
120
+ .row = std::stoi(match[4].str()),
121
+ .column = std::stoi(match[5].str()),
122
+ },
123
+ .isCompileError = true,
124
+ };
125
+ }
126
+ }
127
+
128
+ // 4. Babel code frame error
129
+ if (std::regex_search(message, match, babelCodeFrameErrorRegex())) {
130
+ return ParsedError{
131
+ .title = "Syntax Error",
132
+ .message = match[2].str(),
133
+ .codeFrame =
134
+ CodeFrame{
135
+ .content = match[3].str(),
136
+ .fileName = match[1].str(),
137
+ },
138
+ .isCompileError = true,
139
+ };
140
+ }
141
+
142
+ // 5. Generic transform error (no code frame)
143
+ if (startsWithTransformError(message)) {
144
+ return ParsedError{
145
+ .title = "Syntax Error",
146
+ .message = message,
147
+ .codeFrame = std::nullopt,
148
+ .isCompileError = true,
149
+ };
150
+ }
151
+
152
+ // 6. Determine title from context (matching LogBoxInspectorHeader title map)
153
+ std::string title;
154
+ if (!name.empty()) {
155
+ title = name;
156
+ } else if (!componentStack.empty()) {
157
+ title = "Render Error";
158
+ } else if (isFatal) {
159
+ title = "Uncaught Error";
160
+ } else {
161
+ title = "Error";
162
+ }
163
+ return ParsedError{
164
+ .title = title,
165
+ .message = message,
166
+ .codeFrame = std::nullopt,
167
+ .isCompileError = false,
168
+ };
169
+ }
170
+
171
+ } // namespace facebook::react::unstable_redbox
@@ -0,0 +1,40 @@
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
+ #pragma once
9
+
10
+ #include <optional>
11
+ #include <string>
12
+
13
+ namespace facebook::react::unstable_redbox {
14
+
15
+ struct CodeFrame {
16
+ std::string content;
17
+ std::string fileName;
18
+ int row = 0;
19
+ int column = 0;
20
+ };
21
+
22
+ struct ParsedError {
23
+ std::string title;
24
+ std::string message;
25
+ std::optional<CodeFrame> codeFrame;
26
+ bool isCompileError = false;
27
+ bool isRetryable = true;
28
+ };
29
+
30
+ /**
31
+ * Parse a raw error message into structured components.
32
+ * C++ port of parseLogBoxException from parseLogBoxLog.js.
33
+ */
34
+ ParsedError parseErrorMessage(
35
+ const std::string &message,
36
+ const std::string &name = "",
37
+ const std::string &componentStack = "",
38
+ bool isFatal = true);
39
+
40
+ } // namespace facebook::react::unstable_redbox
@@ -0,0 +1,97 @@
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
+ #include <gtest/gtest.h>
9
+ #include <react/debug/redbox/AnsiParser.h>
10
+
11
+ using namespace facebook::react::unstable_redbox;
12
+
13
+ TEST(AnsiParserTest, ParsesPlainText) {
14
+ auto spans = parseAnsi("hello world");
15
+ ASSERT_EQ(spans.size(), 1);
16
+ EXPECT_EQ(spans[0].text, "hello world");
17
+ EXPECT_FALSE(spans[0].foregroundColor.has_value());
18
+ EXPECT_FALSE(spans[0].backgroundColor.has_value());
19
+ }
20
+
21
+ TEST(AnsiParserTest, ParsesRedForeground) {
22
+ auto spans = parseAnsi("\x1b[31mred text\x1b[0m normal");
23
+ ASSERT_EQ(spans.size(), 2);
24
+ EXPECT_EQ(spans[0].text, "red text");
25
+ ASSERT_TRUE(spans[0].foregroundColor.has_value());
26
+ EXPECT_EQ(spans[0].foregroundColor->r, 187);
27
+ EXPECT_EQ(spans[0].foregroundColor->g, 86);
28
+ EXPECT_EQ(spans[0].foregroundColor->b, 83);
29
+
30
+ EXPECT_EQ(spans[1].text, " normal");
31
+ EXPECT_FALSE(spans[1].foregroundColor.has_value());
32
+ }
33
+
34
+ TEST(AnsiParserTest, ParsesMultipleColors) {
35
+ auto spans = parseAnsi("\x1b[32mgreen\x1b[34mblue\x1b[0m");
36
+ ASSERT_EQ(spans.size(), 2);
37
+ EXPECT_EQ(spans[0].text, "green");
38
+ EXPECT_EQ(spans[0].foregroundColor->r, 144);
39
+ EXPECT_EQ(spans[1].text, "blue");
40
+ EXPECT_EQ(spans[1].foregroundColor->r, 125);
41
+ }
42
+
43
+ TEST(AnsiParserTest, ParsesBrightColors) {
44
+ auto spans = parseAnsi("\x1b[93myellow\x1b[0m");
45
+ ASSERT_EQ(spans.size(), 1);
46
+ EXPECT_EQ(spans[0].text, "yellow");
47
+ ASSERT_TRUE(spans[0].foregroundColor.has_value());
48
+ EXPECT_EQ(spans[0].foregroundColor->r, 234);
49
+ }
50
+
51
+ TEST(AnsiParserTest, ParsesBackgroundColor) {
52
+ auto spans = parseAnsi("\x1b[41mred bg\x1b[0m");
53
+ ASSERT_EQ(spans.size(), 1);
54
+ EXPECT_EQ(spans[0].text, "red bg");
55
+ EXPECT_FALSE(spans[0].foregroundColor.has_value());
56
+ ASSERT_TRUE(spans[0].backgroundColor.has_value());
57
+ EXPECT_EQ(spans[0].backgroundColor->r, 187);
58
+ EXPECT_EQ(spans[0].backgroundColor->g, 86);
59
+ EXPECT_EQ(spans[0].backgroundColor->b, 83);
60
+ }
61
+
62
+ TEST(AnsiParserTest, ResetClearsBackground) {
63
+ auto spans = parseAnsi("\x1b[31;42mcolored\x1b[49mfg only\x1b[0m");
64
+ ASSERT_EQ(spans.size(), 2);
65
+ ASSERT_TRUE(spans[0].foregroundColor.has_value());
66
+ ASSERT_TRUE(spans[0].backgroundColor.has_value());
67
+ ASSERT_TRUE(spans[1].foregroundColor.has_value());
68
+ EXPECT_FALSE(spans[1].backgroundColor.has_value());
69
+ }
70
+
71
+ TEST(AnsiParserTest, ResetShorthandClearsColors) {
72
+ auto spans = parseAnsi("\x1b[31mred\x1b[mplain");
73
+ ASSERT_EQ(spans.size(), 2);
74
+ EXPECT_EQ(spans[0].text, "red");
75
+ ASSERT_TRUE(spans[0].foregroundColor.has_value());
76
+ EXPECT_EQ(spans[1].text, "plain");
77
+ EXPECT_FALSE(spans[1].foregroundColor.has_value());
78
+ EXPECT_FALSE(spans[1].backgroundColor.has_value());
79
+ }
80
+
81
+ TEST(AnsiParserTest, StripsAnsi) {
82
+ auto result = stripAnsi("\x1b[31mred\x1b[0m and \x1b[32mgreen\x1b[0m");
83
+ EXPECT_EQ(result, "red and green");
84
+ }
85
+
86
+ TEST(AnsiParserTest, HandlesEmptyString) {
87
+ auto spans = parseAnsi("");
88
+ EXPECT_TRUE(spans.empty());
89
+ }
90
+
91
+ TEST(AnsiParserTest, HandlesSemicolonSeparatedCodes) {
92
+ auto spans = parseAnsi("\x1b[1;31mtext\x1b[0m");
93
+ ASSERT_EQ(spans.size(), 1);
94
+ EXPECT_EQ(spans[0].text, "text");
95
+ ASSERT_TRUE(spans[0].foregroundColor.has_value());
96
+ EXPECT_EQ(spans[0].foregroundColor->r, 187);
97
+ }
@@ -0,0 +1,173 @@
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
+ #include <gtest/gtest.h>
9
+ #include <react/debug/redbox/JscSafeUrl.h>
10
+
11
+ using namespace facebook::react::unstable_redbox;
12
+
13
+ // --- toNormalUrl ---
14
+
15
+ // Rewrites urls treating //& in paths as ?
16
+ TEST(JscSafeUrlTest, ToNormalUrl_RewritesMarkerInAbsolutePath) {
17
+ EXPECT_EQ(
18
+ toNormalUrl("/path1/path2//&foo=bar?bar=baz#frag?"),
19
+ "/path1/path2?foo=bar&bar=baz#frag?");
20
+ // Idempotent
21
+ EXPECT_EQ(
22
+ toNormalUrl("/path1/path2?foo=bar&bar=baz#frag?"),
23
+ "/path1/path2?foo=bar&bar=baz#frag?");
24
+ }
25
+
26
+ TEST(JscSafeUrlTest, ToNormalUrl_RewritesMarkerInRelativePathWithEncoding) {
27
+ EXPECT_EQ(
28
+ toNormalUrl("relative/path/with%3B%26/encoded//&foo=bar?bar=baz#frag?"),
29
+ "relative/path/with%3B%26/encoded?foo=bar&bar=baz#frag?");
30
+ EXPECT_EQ(
31
+ toNormalUrl("relative/path/with%3B%26/encoded?foo=bar&bar=baz#frag?"),
32
+ "relative/path/with%3B%26/encoded?foo=bar&bar=baz#frag?");
33
+ }
34
+
35
+ TEST(JscSafeUrlTest, ToNormalUrl_RewritesMarkerInFullUrl) {
36
+ EXPECT_EQ(
37
+ toNormalUrl(
38
+ "https://user:password@mydomain.com:8080/path1/path2//&foo=bar?bar=baz#frag?"),
39
+ "https://user:password@mydomain.com:8080/path1/path2?foo=bar&bar=baz#frag?");
40
+ EXPECT_EQ(
41
+ toNormalUrl(
42
+ "https://user:password@mydomain.com:8080/path1/path2?foo=bar&bar=baz#frag?"),
43
+ "https://user:password@mydomain.com:8080/path1/path2?foo=bar&bar=baz#frag?");
44
+ }
45
+
46
+ TEST(JscSafeUrlTest, ToNormalUrl_RewritesMarkerWithoutFragment) {
47
+ EXPECT_EQ(
48
+ toNormalUrl("http://127.0.0.1/path1/path2//&foo=bar&bar=baz"),
49
+ "http://127.0.0.1/path1/path2?foo=bar&bar=baz");
50
+ EXPECT_EQ(
51
+ toNormalUrl("http://127.0.0.1/path1/path2?foo=bar&bar=baz"),
52
+ "http://127.0.0.1/path1/path2?foo=bar&bar=baz");
53
+ }
54
+
55
+ // Returns other strings exactly as given
56
+ TEST(JscSafeUrlTest, ToNormalUrl_PassthroughForQueryWithMarkerAfter) {
57
+ auto url = "http://user:password/@mydomain.com/foo?bar=zoo?baz=quux//&";
58
+ EXPECT_EQ(toNormalUrl(url), url);
59
+ }
60
+
61
+ TEST(JscSafeUrlTest, ToNormalUrl_PassthroughForSimpleQuery) {
62
+ auto url = "/foo?bar=zoo?baz=quux";
63
+ EXPECT_EQ(toNormalUrl(url), url);
64
+ }
65
+
66
+ TEST(JscSafeUrlTest, ToNormalUrl_PassthroughForOpaqueUri) {
67
+ auto url = "proto:arbitrary_bad_url";
68
+ EXPECT_EQ(toNormalUrl(url), url);
69
+ }
70
+
71
+ TEST(JscSafeUrlTest, ToNormalUrl_PassthroughForStar) {
72
+ EXPECT_EQ(toNormalUrl("*"), "*");
73
+ }
74
+
75
+ TEST(JscSafeUrlTest, ToNormalUrl_PassthroughForRelativePath) {
76
+ auto url = "relative/path";
77
+ EXPECT_EQ(toNormalUrl(url), url);
78
+ }
79
+
80
+ // --- toJscSafeUrl ---
81
+
82
+ // Replaces the first ? with a JSC-friendly delimiter, url-encodes subsequent
83
+ // ?, and is idempotent
84
+ TEST(JscSafeUrlTest, ToJscSafeUrl_FullUrlWithEncodedQuestionMark) {
85
+ auto input =
86
+ "https://user:password@mydomain.com:8080/path1/path2?foo=bar&bar=question?#frag?";
87
+ auto output =
88
+ "https://user:password@mydomain.com:8080/path1/path2//&foo=bar&bar=question%3F#frag?";
89
+ EXPECT_EQ(toJscSafeUrl(input), output);
90
+ EXPECT_EQ(toJscSafeUrl(output), output);
91
+ }
92
+
93
+ TEST(JscSafeUrlTest, ToJscSafeUrl_SimpleUrl) {
94
+ auto input = "http://127.0.0.1/path1/path2?foo=bar";
95
+ auto output = "http://127.0.0.1/path1/path2//&foo=bar";
96
+ EXPECT_EQ(toJscSafeUrl(input), output);
97
+ EXPECT_EQ(toJscSafeUrl(output), output);
98
+ }
99
+
100
+ TEST(JscSafeUrlTest, ToJscSafeUrl_PassthroughForStar) {
101
+ EXPECT_EQ(toJscSafeUrl("*"), "*");
102
+ EXPECT_EQ(toJscSafeUrl("*"), "*");
103
+ }
104
+
105
+ TEST(JscSafeUrlTest, ToJscSafeUrl_PassthroughForAbsolutePath) {
106
+ EXPECT_EQ(toJscSafeUrl("/absolute/path"), "/absolute/path");
107
+ EXPECT_EQ(toJscSafeUrl("/absolute/path"), "/absolute/path");
108
+ }
109
+
110
+ TEST(JscSafeUrlTest, ToJscSafeUrl_PassthroughForRelativePath) {
111
+ EXPECT_EQ(toJscSafeUrl("relative/path"), "relative/path");
112
+ EXPECT_EQ(toJscSafeUrl("relative/path"), "relative/path");
113
+ }
114
+
115
+ TEST(JscSafeUrlTest, ToJscSafeUrl_EmptyQueryDropped) {
116
+ EXPECT_EQ(toJscSafeUrl("/?"), "/");
117
+ EXPECT_EQ(toJscSafeUrl("/"), "/");
118
+ }
119
+
120
+ TEST(JscSafeUrlTest, ToJscSafeUrl_PassthroughForUrlWithoutQuery) {
121
+ auto url = "http://127.0.0.1/path1/path";
122
+ EXPECT_EQ(toJscSafeUrl(url), url);
123
+ EXPECT_EQ(toJscSafeUrl(url), url);
124
+ }
125
+
126
+ TEST(JscSafeUrlTest, ToJscSafeUrl_AbsolutePathWithEncodedQuestionMark) {
127
+ auto input = "/path1/path2?foo=bar&bar=question?#frag?";
128
+ auto output = "/path1/path2//&foo=bar&bar=question%3F#frag?";
129
+ EXPECT_EQ(toJscSafeUrl(input), output);
130
+ EXPECT_EQ(toJscSafeUrl(output), output);
131
+ }
132
+
133
+ TEST(JscSafeUrlTest, ToJscSafeUrl_RelativePathWithEncodedQuestionMark) {
134
+ auto input = "relative/path?foo=bar&bar=question?#frag?";
135
+ auto output = "relative/path//&foo=bar&bar=question%3F#frag?";
136
+ EXPECT_EQ(toJscSafeUrl(input), output);
137
+ EXPECT_EQ(toJscSafeUrl(output), output);
138
+ }
139
+
140
+ TEST(JscSafeUrlTest, ToJscSafeUrl_AlreadySafeWithExtraQuery) {
141
+ auto input = "/path1/path2//&foo=bar&bar=question%3F?extra=query#frag?";
142
+ auto output = "/path1/path2//&foo=bar&bar=question%3F&extra=query#frag?";
143
+ EXPECT_EQ(toJscSafeUrl(input), output);
144
+ EXPECT_EQ(toJscSafeUrl(output), output);
145
+ }
146
+
147
+ // Throws on a URL with an empty path and a query string
148
+ TEST(JscSafeUrlTest, ToJscSafeUrl_ThrowsForEmptyPathWithQuery) {
149
+ EXPECT_THROW(toJscSafeUrl("http://127.0.0.1?foo=bar"), std::invalid_argument);
150
+ EXPECT_THROW(toJscSafeUrl("http://127.0.0.1?q#hash"), std::invalid_argument);
151
+ EXPECT_THROW(toJscSafeUrl("?foo=bar"), std::invalid_argument);
152
+ EXPECT_THROW(toJscSafeUrl("?foo=/bar#hash"), std::invalid_argument);
153
+ EXPECT_THROW(toJscSafeUrl("/?bar=baz/"), std::invalid_argument);
154
+ }
155
+
156
+ // --- isJscSafeUrl ---
157
+
158
+ TEST(JscSafeUrlTest, IsJscSafeUrl_TrueForSafeUrls) {
159
+ EXPECT_TRUE(isJscSafeUrl("http://example.com//&foo=bar//#frag=//"));
160
+ EXPECT_TRUE(isJscSafeUrl("http://example.com/with/path//&foo=bar//#frag=//"));
161
+ EXPECT_TRUE(isJscSafeUrl("//&foo=bar//#frag=//"));
162
+ EXPECT_TRUE(isJscSafeUrl("relative/path///&foo=bar//&#frag=//&"));
163
+ EXPECT_TRUE(isJscSafeUrl("/absolute/path//&foo=bar//&#frag=//&"));
164
+ }
165
+
166
+ TEST(JscSafeUrlTest, IsJscSafeUrl_FalseForNormalUrls) {
167
+ EXPECT_FALSE(isJscSafeUrl("http://example.com?foo=bar//&#frag=//"));
168
+ EXPECT_FALSE(
169
+ isJscSafeUrl("http://example.com/with/path/?foo=bar//&#frag=//"));
170
+ EXPECT_FALSE(isJscSafeUrl("?foo=bar//&#frag=//&"));
171
+ EXPECT_FALSE(isJscSafeUrl("relative/path/?foo=bar//#frag=//"));
172
+ EXPECT_FALSE(isJscSafeUrl("/absolute/path/?foo=bar//&#frag=//"));
173
+ }