Repository navigation
feat(ios): make CoreSVG decoder opt-in #606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
03a416d
488412e
2bdd3f4
35ca730
e2abebe
923a7b6
0bad533
da9795f
749b8ac
4c3c08b
817c093
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "react-native-bottom-tabs": minor | ||
| --- | ||
|
|
||
| Add the `$RNBottomTabsEnableSVG` Podfile flag, the `enableSVG` Expo plugin option, and the `RN_BOTTOM_TABS_ENABLE_SVG` environment variable for Swift Package Manager. Set the flag to `false` to remove the private CoreSVG code from Apple builds. When the flag is not set, SVG decoding stays enabled and `pod install` shows a deprecation warning. The next major version disables SVG decoding by default. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,13 +1,60 @@ | ||||||||||||||||||||||
| #import "SvgDecoder.h" | ||||||||||||||||||||||
| #import <React/RCTLog.h> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| #if RN_BOTTOM_TABS_ENABLE_SVG | ||||||||||||||||||||||
| #import "CoreSVG.h" | ||||||||||||||||||||||
| #endif | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| #if !RN_BOTTOM_TABS_ENABLE_SVG && RCT_DEV | ||||||||||||||||||||||
| static BOOL RNBottomTabsLooksLikeSVGData(NSData *data) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| if (!data) { | ||||||||||||||||||||||
| return NO; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| NSData *tag = [@"</svg>" dataUsingEncoding:NSUTF8StringEncoding]; | ||||||||||||||||||||||
| NSUInteger length = MIN(100, data.length); | ||||||||||||||||||||||
| return [data rangeOfData:tag options:NSDataSearchBackwards range:NSMakeRange(data.length - length, length)].location != NSNotFound; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| #endif | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @implementation SvgDecoder | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| RCT_EXPORT_MODULE() | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 Nit · SvgDecoder.mm adds
Suggested change
Suggested change Import the headers you use directly ( Detailspersona: Reply Useful? Reply
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @apex-sentinel-staging pivot RCTImageDataDecoder.h imports RCTBridge.h, and the protocol is declared as RCTImageDataDecoder . RCTBridgeModule.h declares the callableJSModules property in all builds and tells modules to add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 Nit ·
Suggested change
Suggested change Move Detailspersona: Reply Useful? Reply
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @apex-sentinel-staging pivot The property comes from RCTBridgeModule and is declared in all builds, not only in dev builds. Because SvgDecoder adopts the protocol, it must synthesize the property in Release too. If it does not, the compiler warns about a missing property implementation. |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @synthesize callableJSModules = _callableJSModules; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| #if RCT_DEV | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Minor · The dev warning is sent by invoking the JS module named "RCTLog" with method "logToConsole", but RCTLog is not a call… The dev warning is sent by invoking the JS module named "RCTLog" with method "logToConsole", but RCTLog is not a callable JS module in React Native, so RCTCallableJSModules.invokeModule would drop the call and neither the DevTools warning nor the LogBox entry the PR description reports would appear; only NSLog would. DetailsNoted in the reviewer's candidate ledger and not refuted before the budget ran out. persona: Reply Useful? Reply
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @apex-sentinel-staging pivot RCTLog is a callable JS module. Libraries/Core/setUpBatchedBridge.js registers it with registerModule('RCTLog', ...). I tested this with Metro: the warning shows in the React Native DevTools console. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 Nit ·
Detailspersona: Reply Useful? Reply
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @apex-sentinel-staging pivot logDevWarning: and all of its callers are compiled only when RCT_DEV is set. In a Release build there is no dev warning to lose, and the pod install warning covers that case. In dev builds, React Native sets callableJSModules on the module, and the DevTools test showed the warning. |
||||||||||||||||||||||
| // RCTLogWarn alone does not show the message in React Native DevTools, so the | ||||||||||||||||||||||
| // warning goes through the JS console, which also shows it in LogBox. | ||||||||||||||||||||||
| - (void)logDevWarning:(NSString *)message | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| RCTCallableJSModules *jsModules = self.callableJSModules; | ||||||||||||||||||||||
| if (jsModules) { | ||||||||||||||||||||||
| NSLog(@"%@", message); | ||||||||||||||||||||||
| [jsModules invokeModule:@"RCTLog" method:@"logToConsole" withArgs:@[ @"warn", message ]]; | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| RCTLogWarn(@"%@", message); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| #endif | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - (BOOL)canDecodeImageData:(NSData *)imageData | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| #if RN_BOTTOM_TABS_ENABLE_SVG | ||||||||||||||||||||||
| return [CoreSVGWrapper isSVGData:imageData]; | ||||||||||||||||||||||
| #else | ||||||||||||||||||||||
| #if RCT_DEV | ||||||||||||||||||||||
| if (RNBottomTabsLooksLikeSVGData(imageData)) { | ||||||||||||||||||||||
| static dispatch_once_t onceToken; | ||||||||||||||||||||||
| dispatch_once(&onceToken, ^{ | ||||||||||||||||||||||
| [self logDevWarning:@"[react-native-bottom-tabs] SVG decoding is disabled, so SVG images are not shown. " | ||||||||||||||||||||||
| @"To enable it, set `$RNBottomTabsEnableSVG = true` in your Podfile, " | ||||||||||||||||||||||
| @"or `RN_BOTTOM_TABS_ENABLE_SVG=1` for Swift Package Manager."]; | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| #endif | ||||||||||||||||||||||
| return NO; | ||||||||||||||||||||||
| #endif | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData | ||||||||||||||||||||||
|
|
@@ -16,7 +63,20 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData | |||||||||||||||||||||
| resizeMode:(RCTResizeMode)resizeMode | ||||||||||||||||||||||
| completionHandler:(RCTImageLoaderCompletionBlock)completionHandler | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| #if RN_BOTTOM_TABS_ENABLE_SVG | ||||||||||||||||||||||
| #if RCT_DEV && RN_BOTTOM_TABS_SVG_DEFAULTED | ||||||||||||||||||||||
| static dispatch_once_t onceToken; | ||||||||||||||||||||||
| dispatch_once(&onceToken, ^{ | ||||||||||||||||||||||
| [self logDevWarning:@"[react-native-bottom-tabs] SVG decoding will be disabled by default in the next major version. " | ||||||||||||||||||||||
| @"To keep it, set `$RNBottomTabsEnableSVG = true` in your Podfile, " | ||||||||||||||||||||||
| @"or `RN_BOTTOM_TABS_ENABLE_SVG=1` for Swift Package Manager. " | ||||||||||||||||||||||
| @"Set it to `false` (or `0`) to remove the private CoreSVG code now."]; | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| #endif | ||||||||||||||||||||||
| UIImage *image = [CoreSVGWrapper.shared imageFromSVGData:imageData]; | ||||||||||||||||||||||
| #else | ||||||||||||||||||||||
| UIImage *image = nil; | ||||||||||||||||||||||
| #endif | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (image) { | ||||||||||||||||||||||
| completionHandler(nil, image); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| require "minitest/autorun" | ||
| require_relative "../svg_configuration" | ||
|
|
||
| class SVGConfigurationTest < Minitest::Test | ||
| Config = RNBottomTabs::SVGConfiguration | ||
| BASE = { "DEFINES_MODULE" => "YES" }.freeze | ||
|
|
||
| def test_unset_is_enabled_and_marked_as_defaulted | ||
| assert_nil Config.setting(global: nil, env: nil) | ||
| assert_nil Config.setting(global: nil, env: " ") | ||
| assert Config.enabled?(nil) | ||
| assert_equal( | ||
| "$(inherited) RN_BOTTOM_TABS_ENABLE_SVG=1 RN_BOTTOM_TABS_SVG_DEFAULTED=1", | ||
| Config.xcconfig(BASE, value: nil)["GCC_PREPROCESSOR_DEFINITIONS"] | ||
| ) | ||
| end | ||
|
|
||
| def test_true_values_enable_svg | ||
| [true, "true", "1", "YES"].each do |value| | ||
| assert_equal true, Config.setting(global: value, env: nil), value.inspect | ||
| end | ||
| assert_equal true, Config.setting(global: nil, env: "1") | ||
| assert_equal( | ||
| "$(inherited) RN_BOTTOM_TABS_ENABLE_SVG=1", | ||
| Config.xcconfig(BASE, value: true)["GCC_PREPROCESSOR_DEFINITIONS"] | ||
| ) | ||
| end | ||
|
|
||
| def test_false_values_disable_svg | ||
| [false, "false", "0", "no"].each do |value| | ||
| assert_equal false, Config.setting(global: value, env: nil), value.inspect | ||
| end | ||
| assert_equal false, Config.setting(global: nil, env: "0") | ||
| refute Config.enabled?(false) | ||
| assert_equal BASE, Config.xcconfig(BASE, value: false) | ||
| end | ||
|
|
||
| def test_podfile_value_wins_over_environment | ||
| assert_equal false, Config.setting(global: false, env: "1") | ||
| assert_equal true, Config.setting(global: true, env: "0") | ||
| end | ||
|
|
||
| def test_unsupported_value_raises | ||
| assert_raises(ArgumentError) { Config.setting(global: "maybe", env: nil) } | ||
| end | ||
|
|
||
| def test_warns_only_when_unset | ||
| Config.instance_variable_set(:@warned, false) | ||
| _, err = capture_io do | ||
| Config.warn_if_unset(nil) | ||
| Config.warn_if_unset(nil) | ||
| end | ||
| assert_equal 1, err.scan(/next major version/).size | ||
|
|
||
| _, err = capture_io do | ||
| Config.warn_if_unset(true) | ||
| Config.warn_if_unset(false) | ||
| end | ||
| assert_empty err | ||
| end | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔵 Nit · Package.swift comment documents a different set of accepted values than the code it sits above
The new comment lists
unset / 1 / true / 0 / false, but enableSVG also treats "no" as disabled, matching FALSE_VALUES in svg_configuration.rb whose own comment table lists its values. This is a third hand-copied list of accepted values that already disagrees with the code, and the next value added to TRUE_VALUES/FALSE_VALUES has to be remembered in all three places. Document "no" (and "yes" on the true side) or drop it, so the SwiftPM table matches the Ruby one.Details
persona:
maintainabilityReply
@apex-sentinel-staging note ...to add context, or@apex-sentinel-staging pivot ...if this is wrong — I will re-check the cited code.Useful? Reply
@apex-sentinel-staging like, or@apex-sentinel-staging dislike <why>if it is not.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@apex-sentinel-staging like
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed