From 1b3b9de33fb84f0ca39262467f35c38e641eafb0 Mon Sep 17 00:00:00 2001 From: David Vacca Date: Fri, 29 Mar 2019 00:39:43 -0700 Subject: [PATCH] Rebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose collapsable as React Prop in LayoutShadowNode Summary: This diff exposes the collapsable prop as part of LayoutShadowNode, fixing the bug of the collapsable prop being filtered by JS in Fabric Reviewed By: shergin Differential Revision: D14594522 fbshipit-source-id: a892ba8228e76f11232acc6fe5c8d75e991d8fc6 Add logs in mounting layer Summary: Adds debug logging in FabricUIManager and the mounting layer of Fabric. Reviewed By: shergin Differential Revision: D14594521 fbshipit-source-id: a5c0ee39e1bac8a340849ca3e044694cbee5427e Refactor mapping of FabricComponentNames Summary: Simple diff that refactors the usage of the sComponentNames to not require components to be part of the mapping when the name in JS is the same as the name in Native side. Reviewed By: shergin Differential Revision: D14594659 fbshipit-source-id: d1948b27e04686fefbf9b6e2b06d4f9317b97062 Fix scrolling of Android Horizontal Scroll View Summary: This diff fixes the scrolling of Android Horizontal Scroll View, the root cause was that Binding was mounting a ScrollView instead of an AndroidHorizontalScrollView component. This will be automatically fixed when all the View components are autogenerated. Reviewed By: shergin Differential Revision: D14594622 fbshipit-source-id: 7c477ca167188ea9c473f61145461d3cf1696e17 Small changes to State objects to support Android Summary: Small changes to State objects to support Android. See following diffs. Reviewed By: mdvacca Differential Revision: D14663470 fbshipit-source-id: 878f4dc39265991a7b8ff54ca80bdb862f1dd3de TM iOS: force flush message queue when calling into JS from native Summary: When calling into JS (e.g. promise resolve/reject, callback) in TurboModule, we bypass the bridge's message queue. At times this causes race condition, where there are a bunch of pending UI operations (in RCTUImanager) waiting to be flushed, but nothing adds calls to the message queue. Usually tapping the screen will trigger the flush because we're sending down touch events to JS. Reviewed By: JoshuaGross Differential Revision: D14656466 fbshipit-source-id: cb3a174e97542bf80f0a37b4170b6a8e6780fa35 Fix invalid CGContext when invalidate layer (#24195) Summary: If size is zero, the `CGContext` would invalid, so we need to filter zero things. cc. shergin [iOS] [Fixed] - Fix invalid CGContext when invalidate layer Pull Request resolved: https://github.com/facebook/react-native/pull/24195 Differential Revision: D14683396 Pulled By: shergin fbshipit-source-id: b5b45fb86ca3158284281460cf1d95d1b22eab0d use shared mutex in RCTSurfacePresenter Summary: Otherwise reloading from metro deadlocks like this: `[RCTSurfaceRegistry _stopAllSurfaces]` is calling `[RCTSurfaceRegistry enumerateWithBlock]` which locks `_mutex` and then we call `surfaceForRootTag` which then deadlocks on the same mutex: https://pxl.cl/tmm1 Reviewed By: shergin Differential Revision: D14679843 fbshipit-source-id: 9f4ecdfa7a79fcf7f3fc2ce437d4399b00910f26 TM Android: temporarily disable callback support to unbreak build Summary: With the recent change, JSCallInvoker now expects `Instance` to be passed in, not just the `MessageQueueThread`. There needs to be more plumbing to get the instance of `Instance` from the Java side, but this commit is just to unbreak the build temporarily. Reviewed By: JoshuaGross Differential Revision: D14687622 fbshipit-source-id: 42e0173ee8336fc5660fe8188a1e1f8517611521 Move WebView JS files to FB internal Summary: This moves all the JS files and updates the BUCK files. The next step is to move the Android and iOS native files. Reviewed By: TheSavior Differential Revision: D14618890 fbshipit-source-id: 42984acdf5920e712f272d5742c778943be37ac5 Back out "[react-native][PR] There is a small gap in the SynchronizedWeakHashSet implementation. T?" Summary: Original commit changeset: 2998bffb06e2 Reviewed By: JoshuaGross Differential Revision: D14689422 fbshipit-source-id: 2638bed8005859cc95972ba5f78a9e9cace878ef Back out "[react-native][PR][Microsoft] This change fixes currently broken ReactContext listeners mechanism." Summary: Original commit changeset: d506e5035a7f Reviewed By: JoshuaGross Differential Revision: D14689559 fbshipit-source-id: 9a8c8be0d2b7b9dd9be1ec038d7c3b356a5e3adf Move requireNativeComponent calls to standalone files Summary: Moves a number of requireNativeComponent calls to standalone files to support codegen Reviewed By: TheSavior Differential Revision: D14654018 fbshipit-source-id: 349b975cd3a99a9373b2b9b1a19aa311d7c36399 Fabric: Fixed a bug in Diffing algorithm Summary: Before the fix, the algorithm compares ShadowViews to make a decision should it recursively call itself or not; that didn't work properly because a ShadowView doesn't fully represent the state of the subtree tree (e.g. they don't have information about children nodes). After the fix, we compare pointers to ShadowNodes (by comparing pairs). Reviewed By: mdvacca Differential Revision: D14696996 fbshipit-source-id: 560d623b15a272f13b08a11745dec6be39a5dbdd Fabric: Removed log leftover Summary: Trivial. Reviewed By: mdvacca Differential Revision: D14696997 fbshipit-source-id: 5f093ae5f343f2a2a2ee060f0574a6acf4c186d4 Disable view pooling Summary: Temporarily disable View Pooling in Fabric Naming of classes will change in the near future Reviewed By: JoshuaGross Differential Revision: D14685009 fbshipit-source-id: 83573dd09af0202a67d0d0aa11e37c1660c3991f Implement auto-conversions from primitives to Objects Summary: If a NativeModule method requires an optional boolean argument, our codegen translates those optional booleans into `NSNumber*` instead of `BOOL`. The reason why is probably because this is the closest object analogue to `BOOL` in Objective C. The same boxing occurs with numbers. If the type of a number argument in JavaScript is optional, we'll map it to the `NSNumber*` Objective C type instead of `double`. Our existing TurboModules argument conversion code would not take this behaviour into account. Therefore, we'd try to insert a `BOOL` where the `NSInvocation` would expect a `NSNumber*`. This, in turn, would cause the app to crash. (Why would it crash at the point of NSInvocation retainArguments, I'm still not sure). Our flow typechecking ensures that if the type of a method argument is a boolean, we pass in a boolean. Therefore, on the Native side, if we detect a boolean, we can check the type of the Native argument to see whether we should box the primitive. If the native argument type is an object, then we know it has to be an `NSNumber*` in both cases, so we simply wrap the `BOOL` or `double` in a `NSNumber*`. Reviewed By: shergin Differential Revision: D14679590 fbshipit-source-id: c394a878492aab8e98c71d74ec8740a94fc3a6c5 Use constructor attribute instead of +load objc method (#24155) Summary: Xcode 10.2 forbids creating categories for swift class that uses `+load` method. In react-native categories like this are used to register swift classes as modules (macro `RCT_EXTERN_MODULE`) This PR changes it to use `__attribute__((constructor))` instead of objc `+load` method. I introduced new macro for this purpose, `RCT_EXPORT_MODULE_NO_LOAD`, it expands in something like: ``` void RCTRegisterModule(Class); + (NSString *)moduleName { return @"jsNameFoo"; } __attribute__((constructor)) static void initialize_ObjcClassFoo{ RCTRegisterModule([ObjcClassFoo class]); } ``` Functions marked with `__attribute__((constructor))` are run before main and after all `+load` methods, so it seems like correct thing to do. Fixes https://github.com/facebook/react-native/issues/24139 Doc about loading order https://developer.apple.com/documentation/objectivec/nsobject/1418815-load?language=objc [iOS] [Fixed] - Fix runtime crash in xcode 10.2 when using RCT_EXTERN_MODULE for swift classes. Pull Request resolved: https://github.com/facebook/react-native/pull/24155 Reviewed By: javache Differential Revision: D14668235 Pulled By: shergin fbshipit-source-id: 0c19e69ce2a68327387809773848d4ecd36d7461 Move Android code for Geolocation out Reviewed By: cpojer Differential Revision: D14692916 fbshipit-source-id: 6bcfda8aa8c7d22dce36828dae9f57068815ce20 moved all the properties used for layout outputs to YogaNodeJNI Summary: Moved layout outputs transfer logic from YogaNodeJNIBase to YogaNodeJNI. This change set is for adding experiment for layout outputs batching using a float array Reviewed By: davidaurelio Differential Revision: D14642995 fbshipit-source-id: 5d0bc7fa18c1985be7e216d7351f5eab2e03861d added test for reset method in YogaNodeJNI to verify all layout outputs are reset properly Summary: This diff adds a test for reset method in YogaNodeJNI to verify all layout outputs are reset properly Reviewed By: davidaurelio Differential Revision: D14643926 fbshipit-source-id: fffbcd07ccb6d2df83fc0bf187d992ef194f3bd0 Added implementation for YogaNodeJNIBatching and logic for passing array from c++ to java Summary: This diff adds the logic to transfer layout outputs using a float array Reviewed By: davidaurelio Differential Revision: D14368120 fbshipit-source-id: d1f22283bcea051d15657f42c15b90edaa0a8a7a added flag for useBatchingForLayoutOutputs experiment Summary: Using a config flag to switch between different implementations of transferring layout outputs - YogaNodeJNI uses multiple access of java fields to pass all properties like width, height, margin etc... - YogaNodeJNIBatching uses a float array to pass all the data in one java field access Reviewed By: davidaurelio Differential Revision: D14378301 fbshipit-source-id: 0da5b28e6a67ad8fd60eb7efe622d9b2deaf177f Turn on react-hooks/exhaustive-deps in FBSource Summary: Looks like there are already a bunch of issues in the codebase because this wasn't on. Reviewed By: cpojer Differential Revision: D14701084 fbshipit-source-id: 09ff8e0d905b81fbe08c41d4f58758479b38187b fix typo in DatePickerAndroidTypes.js (#24234) Summary: fix typo in DatePickerAndroidTypes.js, fixes https://github.com/facebook/react-native/issues/24137 [Android] [Changed] - fix typo in DatePickerAndroidTypes.js Pull Request resolved: https://github.com/facebook/react-native/pull/24234 Differential Revision: D14705691 Pulled By: cpojer fbshipit-source-id: 0d4aee045f7ec36b0cfcd5b4ce5a2cd47cee9ec5 Remove navigator.geolocation, use Geolocation Summary: This is the first diff in an effort to remove Geolocation from React Native. This diff removes the globally injected navigator.geolocation feature and instead requires explicit importing of `Geolocation`. When using Web APIs, people will need to patch `navigator.geolocation` on their own from now on. Reviewed By: sahrens Differential Revision: D14692386 fbshipit-source-id: c57b290b49728101250d726d67b1956ff23a9a92 Fabric `ShadowTree` (and co) was moved to `mounting` module Summary: Because it's kinda more logical and we will rely on this in comming diffs. Reviewed By: mdvacca Differential Revision: D14587124 fbshipit-source-id: 94ae9410b4ffeffd0fcb4da4a0518f0bb0d2ba63 Fabric: Introspection (self testing in debug mode) in ShadowTree Summary: We suspect that we have some error in diffing algorithm that cause some crashes in mounting layer, so we decided to write a comprehensive unit tests for that. Writing them we realized that it would be cool to also enable that for normal app run in the debug more, so we can catch the problem in real environment when/if it happens. Reviewed By: mdvacca Differential Revision: D14587123 fbshipit-source-id: 6dcdf451b39489dec751cd6787de33f3b8ffb3fd Add the ability to customize webview response to client cert requests Summary: Add the ability to set a custom handler on ReactWebViewManager to handle client certificate challenges during TLS authentication. [Android][Added] - Public method setCustomClientCertRequestHandler to the native com.facebook.react.views.webview.ReactWebViewManager, that allows using a custom response for client certificate challenges. Reviewed By: mjhu Differential Revision: D14609697 fbshipit-source-id: 567c95458af638d1f8233fc3ca0d9cefc061c2bf Harmonize spacing after colons (#24186) Summary: Harmonizes the spacing after colons to have a more consistent coding style. Pull Request resolved: https://github.com/facebook/react-native/pull/24186 Differential Revision: D14668167 Pulled By: cpojer fbshipit-source-id: 8f1ba71eec186b3a2221d1f4fac851e51afc52cc Fix setting the contentOffset value when refresh ends (#24191) Summary: It closes #24170. I opened the issue yesterday, and I think I found the way how to resolve it. So, I'm opening this PR directly without any comment on the issue. In the `endRefreshProgrammatically` of `RCTRefreshControl.m`, the comment says that " The contentOffset of the scrollview MUST be greater than 0". However, if the `contentInset` prop is set for the `FlatList`, I think `contentOffset` can be a negative value after refreshing and should be greater than `-contentInset.top`. As I reported the bug in that issue, If I am using `contentInset` prop to the `FlatList`, making `contentOffset` value be always 0 when refreshing ends causes something wrong situation. [iOS] [Fixed] - Fix setting the contnetOffset when refreshing ends Pull Request resolved: https://github.com/facebook/react-native/pull/24191 Differential Revision: D14710987 Pulled By: sahrens fbshipit-source-id: 03f06df9a93a2a46a7cc0b56269091778805917e Revert D14423742: Use of TraceCompat Summary: D14423742 introduced a regression on OSS test_android due to androidx.core.os not being available. I spent some time investigating a fix forward this morning, with no success. Reverting. Changelog: [Android] [Changed] - Revert 92f019c666c4933b8553b60038803819f4204e5d Reviewed By: cpojer Differential Revision: D14668728 fbshipit-source-id: c7542992805551dc4302626e1536759794efaa82 Fix retain cycle in RCTDevMenu Summary: A retain-cycle regression was added by D14162776 and detected here: T41208740. Fix should be trivial. Reviewed By: shergin Differential Revision: D14709784 fbshipit-source-id: bad6ab31a5170e9320c4432cbd20d02ec6164f38 Move Geolocation JS code to FB internal Summary: This removes the JS parts of Geolocation from React Native open source. Reviewed By: yungsters Differential Revision: D14693179 fbshipit-source-id: 1da5b7ec0e3e9d21d2019b7ee43e5f85661795b4 Improvement: Adjust template to match new init command (#24138) Summary: Adjusting template to new init command (https://github.com/react-native-community/react-native-cli/pull/241). PR with new init command needs to be merged before we land this. [General] [Changed] - Adjust template to match new init command Pull Request resolved: https://github.com/facebook/react-native/pull/24138 Differential Revision: D14617568 Pulled By: cpojer fbshipit-source-id: f4b90920d47d3e0d2b08470e0eaad1abd733e4cc Android: Enable views to be nested within (#23195) Summary: Potential breaking change: The signature of ReactShadowNode's onBeforeLayout method was changed - Before: public void onBeforeLayout() - After: public void onBeforeLayout(NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) Implements same feature as this iOS PR: https://github.com/facebook/react-native/pull/7304 Previously, only Text and Image could be nested within Text. Now, any view can be nested within Text. One restriction of this feature is that developers must give inline views a width and a height via the style prop. Previously, inline Images were supported via FrescoBasedReactTextInlineImageSpan. To get support for nesting views within Text, we create one special kind of span per inline view. This span is called TextInlineViewPlaceholderSpan. It is the same size as the inline view. Its job is just to occupy space -- it doesn't render any visual. After the text is rendered, we query the Android Layout object associated with the TextView to find out where it has positioned each TextInlineViewPlaceholderSpan. We then position the views to be at those locations. One tricky aspect of the implementation is that the Text component needs to be able to render native children (the inline views) but the Android TextView cannot have children. This is solved by having the native parent of the ReactTextView also host the inline views. Implementation-wise, this was accomplished by extending the NativeViewHierarchyOptimizer to handle this case. The optimizer now handles these cases: - Node is not in the native tree. An ancestor must host its children. - Node is in the native tree and it can host its own children. - (new) Node is in the native tree but it cannot host its own children. An ancestor must host both this node and its children. I added the `onInlineViewLayout` event which is useful for writing tests for verifying that the inline views are positioned properly. Limitation: Clipping ---------- If Text's height/width is small such that an inline view doesn't completely fit, the inline view may still be fully visible due to hoisting (the inline view isn't actually parented to the Text which has the limited size. It is parented to an ancestor which may have a different clipping rectangle.). Prior to this change, layout-only views had a similar limitation. Pull Request resolved: https://github.com/facebook/react-native/pull/23195 Differential Revision: D14014668 Pulled By: shergin fbshipit-source-id: d46130f3d19cc83ac7ddf423adcc9e23988245d3 update url for apk-splits user-guide (#24253) Summary: Changed apk-splits user-guide url. [ANDROID] [CHANGED] - Apk tools url. Pull Request resolved: https://github.com/facebook/react-native/pull/24253 Differential Revision: D14724834 Pulled By: cpojer fbshipit-source-id: 8e9e21ebdcb0d585f1f262640e259ccf71112adb Bump react-native version in template in release script (#24262) Summary: Since template has a fixed version in `template/package.json`, we want to automate this process. [General] [Added] - Bump react-native in `template/package.json` Pull Request resolved: https://github.com/facebook/react-native/pull/24262 Differential Revision: D14724831 Pulled By: cpojer fbshipit-source-id: 164d13001a889941398f3db3b9b96eb9d5114cc3 add ram bundle to android template (#24264) Summary: I plan to do this for a long time :) Still a little surprised so much people not know it https://twitter.com/geekykaran/status/1112756026611257344 [Android] [Feature] - add ram bundle to android template Pull Request resolved: https://github.com/facebook/react-native/pull/24264 Differential Revision: D14725396 Pulled By: cpojer fbshipit-source-id: b8e424f0be81978465259c82024f206d232acb55 Prevent crash when setting underlineColorAndroid (#24183) Summary: Try to prevent the crash described in https://github.com/facebook/react-native/issues/17530 There seems to be a bug in `Drawable.mutate()` in some devices/android os versions even after you check the constant state. This error is hard to reproduce and to fix, so just try to catch the exception to prevent crash. [Android][Fixed] Prevent random crash when setting underlineColorAndroid Pull Request resolved: https://github.com/facebook/react-native/pull/24183 Differential Revision: D14710484 Pulled By: cpojer fbshipit-source-id: 3af20a5cb0ecd40839beaf85118c0f5aa6905414 Move WebView Android files to FB internal Summary: This moves the Java files to FB internal and updates all the buck files Reviewed By: TheSavior Differential Revision: D14622521 fbshipit-source-id: a8d293e9f9e08868cca3ed2986a08d0db16dec15 bump gradle to 5.2.1 (#23879) Summary: Bump Gradle to 5.2.1, includes many bug fixes and improvements, especially improved Kotlin DSL support. [Android] [Changed] - bump Gradle to 5.2.1 Pull Request resolved: https://github.com/facebook/react-native/pull/23879 Reviewed By: hramos Differential Revision: D14486430 Pulled By: cpojer fbshipit-source-id: 3476516352ff2c947d65103cff6f37e27d888c2d @allow-large-files xplat flow 0.96 upgrade Summary: bypass-lint allow_many_files Reviewed By: panagosg7 Differential Revision: D14727932 fbshipit-source-id: 66b2fce7753450204c5daa131ffe43dcd4736539 Move iOS Geolocation code out from the repo Summary: @public This resolves the iOS side of #20879. Reviewed By: natestedman, cpojer Differential Revision: D14712066 fbshipit-source-id: 88dd0ff80d3467b314cacb9349029dadca4ddf19 Make it possible to run JSBigString tests Summary: Add a target for JSBigString tests that can be run with a normal `buck test` invocation. Also fix an issue in the test when `getenv` returns null by defaulting to `/tmp`. Reviewed By: ridiculousfish Differential Revision: D14716270 fbshipit-source-id: f2eb6d3aab93c32a4b41f5786aedd04a70468d75 TM iOS: Use weak_ptr to pass around Instance to avoid unreleased refs Summary: There is a timing issue when reloading the bridge (in dev mode) and the tear down of the TurboModules. This causes `Instance` to never get freed, hence the "bridge" isn't cleaning up properly. The side effect can be bogus error saying that it's unable to find a module. To address this, JSCallInvoker should just take in weak_ptr. Reviewed By: RSNara Differential Revision: D14739181 fbshipit-source-id: f9f2a55486debaeb28d3d293df3cf1d3f6b9a031 Guard against content view being null in onOverScrolled Summary: It seems that the content view can sometimes be null when onOverScrolled is called (presumably when the scrollview gets unmounted while it's in the middle of scrolling?). Changelog: [Android][fixed] - Guard against content view being null in onOverScrolled. Reviewed By: mdvacca Differential Revision: D14737534 fbshipit-source-id: e88ec6f585e50517b734a8809fc3843c0b22df10 Log uri for guessContentTypeFromName exceptions Summary: Add additional logging around the exception to figure out what kind of uris are causing the exception for `getContentTypeForFileName` Reviewed By: PeteTheHeat Differential Revision: D14715917 fbshipit-source-id: 46299d2ff3f1f06991d7800784a025a85815ae8c Remove legacy AnimationManagerModule Summary: This code was shipped as part of the initial open-source release but was never used. Reviewed By: sahrens Differential Revision: D14649389 fbshipit-source-id: 0c068ca69b91d275008f4a7af77a23a4f1470c18 Implement completion callback for LayoutAnimation on Android Summary: All animations are scheduled by the UIManager while it processes a batch of changes, so we can just wait to see what the longest animation is and cancel+reschedule the callback. Reviewed By: mdvacca Differential Revision: D14656733 fbshipit-source-id: 4cbbb7e741219cd43f511f2ce750c53c30e2b2ca Remove experimental gating for LayoutAnimation on Android Reviewed By: sahrens Differential Revision: D14658087 fbshipit-source-id: 378ef4a5c5336d428b5045772d094a297b2767c7 Removing unused and very old versions of the support library resources Summary: TSIA Differential Revision: D14727969 fbshipit-source-id: 1c73534dea7225f2d952ef0f20ea602894d78f04 Change default Inspector Proxy port to 8081 Summary: Change default Inspector Proxy port to 8081 Reviewed By: cwdick Differential Revision: D14745974 fbshipit-source-id: f4b3b158f55c6f5f1b3d9cc2528c5ddb59774a8b Move `YGSetUsedCachedEntries` to internal header Summary: @public In order to get out of pre-releases again, we move `YGSetUsedCachedEntries` from `Yoga.h` to `Yoga-internal.h`. This way, it’s obvious that the function is not public, and we can remove it from future versions without breaking semver contracts. Reviewed By: SidharthGuglani Differential Revision: D14726029 fbshipit-source-id: 8d7e747e843bf5c4b5e1d4cfbfa37ca9d240dffb Make Jest transform @react-native-community packages by default (#24294) Summary: Currently, `react-native` Jest preset will not automatically transpile extracted Lean Core modules (all under `react-native-community` org), so maintainers will likely need to update their docs on how to do that (it's a common pain in RN testing story). We can make it easier for users and maintainers by adding RNC modules to the `transformIgnorePatterns` whitelist we have in Jest preset. Some of them are not necessary to transpile, but I'd say it's a small sacrifice to make (having first test run possibly slower) for having less friction around migrating to extracted modules. [General] [Added] - make Jest transform react-native-community/ packages by default Pull Request resolved: https://github.com/facebook/react-native/pull/24294 Differential Revision: D14748962 Pulled By: cpojer fbshipit-source-id: 36300ee021f03b8c13275a6e0cf28d988ee5beba fix gradle wrapper in template (#24275) Summary: Fix Gradle wrapper in template [Android] [Changed] - fix Gradle wrapper in template Pull Request resolved: https://github.com/facebook/react-native/pull/24275 Differential Revision: D14750280 Pulled By: cpojer fbshipit-source-id: 9aadb1a674503f3fbbdf6cc03a8f8e3d9d2692aa bump fresco to 1.13 (#24274) Summary: Bump Fresco to 1.13, which uses libc++ and many improvements. [Android] [Changed] - Bump Fresco to 1.13 Pull Request resolved: https://github.com/facebook/react-native/pull/24274 Differential Revision: D14750275 Pulled By: cpojer fbshipit-source-id: 3c040fccd4cb484e31d9c6e849ec285666f140c7 Rename ios project name with new template (#24292) Summary: With new `init` implementation we replace all occurrences of the project name (`HelloWorld`) inside `ios` and `android` directories. For some reason, a product name field in `ios` was named wrongly. This shouldn't impact initialization using `global-cli` [iOS] [Fixed] - Change productName in iOS in new init. Pull Request resolved: https://github.com/facebook/react-native/pull/24292 Differential Revision: D14749249 Pulled By: cpojer fbshipit-source-id: aaf4294ab23180770aac3075789d3ffb4d5a4f3f Avoid failing Obj-C tests when xcpretty is not installed (#24173) Summary: Fixes an issue where `scripts/objc-test-ios.sh` would fail if `xcpretty` is not installed. As this tool is not bundled with macOS, and it's not explicitly called out in our docs on testing, the script should not fail if it's not present. In a related change, JUnit reports are written to a more sensible location when the script is run outside of Circle CI. [iOS] [Fixed] - Fixed test script failure when xcpretty is not present Pull Request resolved: https://github.com/facebook/react-native/pull/24173 Differential Revision: D14726101 Pulled By: hramos fbshipit-source-id: 9f3081a75a4a262f55aef8498241fe7d1e04b931 fixed touchable longpress (#24238) Summary: This diff fixes a bug in TouchableNativeFeedback where a long press is not registered. cause of the bug is _touchableHandleResponderMove_ being invoked **regardless** of a moving gesture ( even when movedDistance is 0) in some devices ( including OnePlus5t ), which was eventually clearing out the long-press timeout. fix is to prevent _touchableHandleResponderMove_ from Implementing if the state of touchable is RESPONDER_INACTIVE_PRESS_IN. [General] [Fixed] - Touchable onLongPress fix. Pull Request resolved: https://github.com/facebook/react-native/pull/24238 Reviewed By: cpojer Differential Revision: D14712986 Pulled By: rickhanlonii fbshipit-source-id: e85a66a7e8b61e0a33146b2472e2e055726a0e93 Add YogaNode.cloneWithoutChildren Summary: Adding flat clone method back to YogaNode for reconciliation. Reviewed By: davidaurelio Differential Revision: D14683019 fbshipit-source-id: 08ed2ffbb16052cb11aa464f67a7ba9a64297985 TM Android: fixed up JSCallInvoker creation Summary: A previous commit changed the signature of the Instance (the arg to JSCallInvoker) to be a weak ref, so this callsite needs updating. Reviewed By: mmmulani Differential Revision: D14757188 fbshipit-source-id: 1a8663e56a42b26c6202881c57a7caafa71da2ab Add scrollToOverflowEnabled prop to ScrollView (#24296) Summary: ScrollView's scrollTo behavior on iOS was recently changed to limit the offset to the content size plus any content inset (see #23427). This departure from the old behavior created UI issues for anyone that is using the over-scroll ability for the purpose of positioning elements at specific coordinates on the screen. Examples include using this behavior to position TextInputs above the virtual keyboard programmatically when focused or moving drop down elements positioned near the bottom of the content toward the top of the screen when selected to show a larger absolutely positioned item list. Default behavior does not change and this is an "opt-in" type of prop to re-enable the old behavior. [iOS] [Added] - Added scrollToOverflowEnabled prop to ScrollView Pull Request resolved: https://github.com/facebook/react-native/pull/24296 Differential Revision: D14762619 Pulled By: cpojer fbshipit-source-id: d2a552b5cb321d52e8ea4116327bf9ec647a3aae Pre-allocate Fabric views even when React is running in the UI Thread Summary: Before D14297477, the pre-allocation of views was ONLY necessary when react was running in the JS Thread, this is because the batch of mount items used to contain mount items for creation of views. After D14297477, views are only created during pre-allocation, that means that pre-allocation of views need to be trated the same way independently the thread where it is running. Reviewed By: shergin Differential Revision: D14714933 fbshipit-source-id: 7bd19cd33b624a5b0daaafabb476bb06707eea17 Implement AxialGradientView in Fabric Android Summary: This diff integreates AxialGradientView in Fabric Android Reviewed By: shergin Differential Revision: D14631690 fbshipit-source-id: 54785466ab4cd7251c6667c8dc12d69d9d194832 Fix crash in RCTText in open source RNTester Summary: See https://github.com/facebook/react-native/issues/24288 Reviewed By: sahrens Differential Revision: D14765625 fbshipit-source-id: f7275f3735691e1f0c87e682f6dd675e5ba5a7c0 Android - Add a ReactFragment (#12199) Summary: React Native on Android has currently been focused and targeted at using an [Activity](https://developer.android.com/reference/android/app/Activity.html) for its main form of instantiation. While this has probably worked for most companies and developers, you lose some of the modularity of a more cohesive application when working in a "brown-field" project that is currently native. This hurts more companies that are looking to adopt React Native and slowly implement it in a fully native application. A lot of developers follow Android's guidelines of using Fragments in their projects, even if it is a debated subject in the Android community, and this addition will allow others to embrace React Native more freely. (I even assume it could help with managing navigation state in applications that contain a decent amount of Native code and would be appreciated in those projects. Such as sharing the Toolbar, TabBar, ViewPager, etc in Native Android) Even with this addition, a developer will still need to host the fragment in an activity, but now that activity can contain native logic like a Drawer, Tabs, ViewPager, etc. **Test plan (required)** * We have been using this class at Hudl for over a couple of months and have found it valuable. * If the community agrees on the addition, I can add documentation to the Android sections to include notes about the potential of this Fragment. * If the community agrees on the addition, I can update one or more of the examples in the `/Examples` folder and make use of the Fragment, or even create a new example that uses a native layout manager like Drawer, Tabs, Viewpager, etc) Make sure tests pass on both Travis and Circle CI. _To Note:_ * There is also talk of using React Native inside Android Fragment's without any legit documentation, this could help remedy some of that with more documentation included in this PR https://facebook.github.io/react-native/releases/0.26/docs/embedded-app-android.html#sharing-a-reactinstance-across-multiple-activities-fragments-in-your-app * Others have also requested something similar and have a half-baked solution as well http://stackoverflow.com/questions/35221447/react-native-inside-a-fragment [ANDROID][FEATURE][ReactAndroid/src/main/java/com/facebook/react/ReactFragment.java] - Adds support for Android's Fragment system. This allows for a more hybrid application. Pull Request resolved: https://github.com/facebook/react-native/pull/12199 Differential Revision: D14590665 Pulled By: mdvacca fbshipit-source-id: b50b708cde458f9634e0c14b3952fa32f9d82048 Back out "[react-native] Remove experimental gating for LayoutAnimation on Android" Summary: We've identified a couple of remaining issues that need to be re-tested before we can ship this more broadly. Reviewed By: fred2028 Differential Revision: D14775730 fbshipit-source-id: 22402149066c5fbe72c36fcf7f547d63feaf5241 Do not overwrite Object.freeze Summary: Now that React Native ships with a newer version of JSC, we do not need this code to wrap `Object.freeze` any longer. Reviewed By: mmmulani Differential Revision: D14779239 fbshipit-source-id: 1a6e1a9c7f4312572bd08ba604fa8c9d6b1927e1 Adding Flow types for FabricUIManager measure calls Summary: These are the same types as the existing measure calls on Paper's UIManager except ReactTag has been replaced with Node Reviewed By: fkgozali Differential Revision: D14732142 fbshipit-source-id: b757e0d1f8d168232d8ba58938cdcd3b30e006ff Fabric: Improvements in DebugStringConvertible Summary: * Small improvements in pretty-printing algorirhm (adding spaces and new-line caracters). Now it's even more pretty. * The `depth` parameter was integrated into `DebugStringConvertibleOptions` which simplifies evething a bit and reduce amount of arguments that `getDebugDescription` requires. Reviewed By: sahrens Differential Revision: D14715082 fbshipit-source-id: 3ea0b8db3c1816c5cb43f40ccec9cdc1943f33a5 Fabric: `toString` methods were moved into `DebugStringConvertible` with an implementation in .cpp file Summary: They need to be in DebugStringConvertible because it depends on they (and because `debugStringConvertibleUtils` depends on `DebugStringConvertible`). We also moved they implementation to cpp file to avoid leaking Folly's features to consumer namespace. Reviewed By: mdvacca Differential Revision: D14715080 fbshipit-source-id: 7277e17b39a14a2d7ba7e5a9b44a70178feb1045 Fabric: *Informal* `DebugStringConvertible` interface Summary: Informal `DebugStringConvertible` interface serves the same purpose as `DebugStringConvertible` abstract class (providing universal pretty-printing interface) but relies on C++ static overloading instead of virtual dispatch. This approach has some advantages and disadvantages: Pros: * It's more clear and scoped. It's simpler to implement debug-printing functionality aside of normal production code. * It's more composable. * It allows print types that are not classes. * For some classes using `DebugStringConvertible` means that we have to use virtual inheritance (which might be undesirable and affect *production* performance (if a compiler isn't smart enough to remove empty base class). * For some highly lean classes (that designed to be as small as possible) adding base (even empty-in-prod) classes kinda... smells. Cons: The particular implementations cannot rely on dynamic dispatch which can complicate printing classes with meaningful differences between sampling classes (e.g. ShadowNode subclasses). That's why we don't remove `DebugStringConvertible` class yet. Reviewed By: mdvacca Differential Revision: D14715081 fbshipit-source-id: 1d397dbf81dc6d1dff0cc3f64ad09f10afe0085d Fabric: `getDebugDescription` implementation for `shared_ptr`, `weak_ptr` and `unique_ptr` Summary: Trivial. Reviewed By: mdvacca Differential Revision: D14715083 fbshipit-source-id: 92031cbbf166ea00569a6076d41575a9fd741043 Fabric: `getDebugDescription` for `ShadowView` and `ShadowViewMutation` Summary: Trivial. Now we can print actual list of mutations in case of some failure in the diffing algorithm. Reviewed By: mdvacca Differential Revision: D14715079 fbshipit-source-id: d0af7c756287643892d7120c199bc8028a6b3431 Fix crash if set text and set selection at the same time. (#22723) Summary: Since text and selection has dependency, handle text selection in updateExtraData as well. The root cause is due to setText is handled on extra data update but setSelection is handled on set property. And extra data update will be handled after all properties are handled. Since selection and text has dependency, move selection to extra data update as well. Changelog: ---------- [Android] [Fixed] - Fix crash when set text and selection on textinput at the same time Pull Request resolved: https://github.com/facebook/react-native/pull/22723 Differential Revision: D14783791 Pulled By: cpojer fbshipit-source-id: a4065f3e151d23f4813d76e91d68362cfd4daaf4 Use node package dependency to manage JSC version (#24276) Summary: In origin approach, we packed libjsc.so inside react-native.aar and it is difficult for user to choose different JSC variants. E.g., [the Intl supported version](https://github.com/react-native-community/jsc-android-buildscripts#international-variant). This change list allows application to determine JSC versions or variants by npm/yarn package. There is a |useIntlJsc| flag in build.gradle, it will use the same JSC version but with Intl support. `yarn add jsc-android@canary` [Android] [Changed] - Allow application to select different JSC variants **MIGRATION** Note that there are some changes in build.gradle. Existing application needs to change their android/build.gradle and android/app/build.gradle. Hopefully, the rn-diff-purge should handle the case well. Pull Request resolved: https://github.com/facebook/react-native/pull/24276 Differential Revision: D14752359 Pulled By: cpojer fbshipit-source-id: a4bfb135ad8e328f404a2d1a062412f40ebf4622 - VirtualizedSectionList/SectionList: replace enableVirtualization prop annotation by correct underlying disableVirtualisation of VirtualizedList (#24312) Summary: It seems (I used git history to confirm) that FlatList/VirtualizedList have ([since the begining](https://github.com/facebook/react-native/blame/c13f5d48cfe3e7c0f6c6d0b745b50a089d6993ef/Libraries/Lists/VirtualizedList.js#L79)) a `disableVirtualization` prop. SectionList ([since it's begining](https://github.com/facebook/react-native/blame/abe737fe746406533798f9670e8e243cb18d5634/Libraries/Lists/VirtualizedSectionList.js#L98)) have a `enableVirtualization` prop, but since SectionList is VirtualizedSectionList which use VirtualizedList, this prop probably never did something. This fix just rename the prop properly so it can have an effect on the underlying VirtualizedList when you use a SectionList. Since props are spread it's kind of working already, but the flow annotation are wrong (so it tells you it won't work/ you can't use it) which sucks. (NB: I am doing this since I was trying to use a SectionList with react-native-web & server side rendering to get the all list, you can laugh). [General] [Fixed] - VirtualizedSectionList/SectionList: replace enableVirtualization prop annotation by correct underlying disableVirtualisation of VirtualizedList Pull Request resolved: https://github.com/facebook/react-native/pull/24312 Differential Revision: D14779449 Pulled By: cpojer fbshipit-source-id: e51e1d639d2bb265b5b286786010d01ffd9d90e0 Remove Polyfills from RN Open Source Summary: We don't need these polyfills in RN Open Source any longer because JSC supports these features natively. We also don't need these internally at FB, but I want the removal to be a step by step process and separate from the open source work. Reviewed By: rickhanlonii Differential Revision: D14762827 fbshipit-source-id: 114626bd18516c42ced43c3f7aa29d42d1d95335 Move iOS WebView files to FB internal Summary: This moves the iOS WebView files to be fb internal which completes the removal of WebView from React Native open source as part of the Lean Core effort. Reviewed By: TheSavior Differential Revision: D14630076 fbshipit-source-id: 7bc11d6c1470bb748c823c86cbb8b5ee94b508ff Fix #23755 ("RCTImagePickerManager requires main queue setup" warning) (#24314) Summary: Fix #23755 - Which is to remove the warning: ``` Module RCTImagePickerManager requires main queue setup since it overrides `init` but doesn't implement `requiresMainQueueSetup`. In a future release React Native will default to initializing all native modules on a background thread unless explicitly opted-out of. ``` General Fixed - Warning "RCTImagePickerManager requires main queue setup Pull Request resolved: https://github.com/facebook/react-native/pull/24314 Differential Revision: D14788772 Pulled By: cpojer fbshipit-source-id: e2017136008367d36468debb258afa698b5402bc Fix duplicate definition Flow error in HMRLoadingView Summary: Fix duplicate definition Flow error in HMRLoadingView, and match latest changes made to Facebook's internal .flowconfig Reviewed By: cpojer Differential Revision: D13110965 fbshipit-source-id: 8c22cab8232e1f539e77014b21e258de8b08d2b3 Back out "[react-native][PR] Android - Add a ReactFragment" Summary: Original commit changeset: b50b708cde45 Reviewed By: mdvacca Differential Revision: D14792438 fbshipit-source-id: c5e0b5f7663fe70110f73ae94a6fa99388f87ae3 Support experimental re-mmap wrappers Summary: Add experimental support for reordering the pages of a file that is mmap:ed by JSBigFileString. The wrapper is auto-detected (by checking file size and magic header) and transparently reorders the pages. Reviewed By: ridiculousfish Differential Revision: D14721397 fbshipit-source-id: 34e095350a9eeb9b07105bed6f3379f2fe472ae6 add flow check to windows ci (#21401) Summary: add flow check to windows ci Pull Request resolved: https://github.com/facebook/react-native/pull/21401 Differential Revision: D14807935 Pulled By: hramos fbshipit-source-id: 38354aa498fe7783966e8bf1895286c690eed90a Set scroll view throttle by default Summary: Setting the scroll throttle for every animated scrollview is a pain, and if you forget it's super janky and can be confusing and frustrating. Enables setting default props in createAnimatedComponent and uses it for scrollview. Reviewed By: TheSavior Differential Revision: D14790093 fbshipit-source-id: dd8f6f6540813245e87d696351f09ebb2e6ed5f2 Cleanup native anim example Summary: Makes things a little more clear. Reviewed By: TheSavior, yungsters Differential Revision: D14790256 fbshipit-source-id: 42e47487adfd48b8de5e987ac0e73a128a200824 Upgrade Jest to 24.7.1 Summary: Update Jest to 24.7.1, which has performance improvements I've built over the last few weeks. Reviewed By: rubennorte Differential Revision: D14766905 fbshipit-source-id: 9ebb3b6f2a9ed656ca0a41a27099fe1ea6e92710 Android plumbing for State and LocalData update mount items Summary: Android plumbing for State and LocalData update mount items. See other commits in stack for usage Reviewed By: mdvacca Differential Revision: D14663522 fbshipit-source-id: 5604a6a9af292805e9ce46c68e5ce7472eef0218 Fabric: Fixing const correctness in ShadowNodeFragment (and some prettification) Summary: Previously, all placeholders methods have return type `SomeType &` which is not correct because it allows the called to modify enclosed `static` value of the placeholders; the type should be `SomeType const &`. Besides that this diff migrates some type aliases to the new style (which makes evething much prettier and readable). Reviewed By: mdvacca Differential Revision: D14819076 fbshipit-source-id: 87e68d546f16d7a9ad1fb65e1b238859f9381eb7 fix typo (#24343) Summary: "Built from source" is past tense - if anything, it should be "Build from source". However, all other heading end with "ing" so it makes sense here too. Pull Request resolved: https://github.com/facebook/react-native/pull/24343 Differential Revision: D14822098 Pulled By: cpojer fbshipit-source-id: 3d07f2e6f8ed4a21e0311ef4f675f2d41553b619 change jest native method mocks to jest functions (#24337) Summary: Currently calling native methods on internal react native components throw a warning. I believe this is problematic because _users_ aren't calling native methods on internal components, the _component_ is making the call. So for instance, if I unmount a component that has a form with a few uses of `TextInput`, which is a perfectly valid test case, my test output will be full of warnings that I can't call `.blur()` in the test renderer environment. That's very misleading, because I didn't, the internal component did. In fact, as far as I can tell, there's not really even anything I can do to stop that call or use the output from it, its all internal. `TextInput` is a black box, and 99% of users writing tests probably won't even know it calls `.blur()` under the hood on unmount. I want to change these to `jest.fn()` because I think this eliminates a lot of chatter in test output, but also doesn't send users down a rabbit hole of trying to find workarounds that may involve filtering console output, which could potentially lead them to inadvertently filter out real warnings that they should see. So I'm willing to change the implementation of how I did this, but I don't think its right to warn users that they called a native method when they didn't. If they build a component that calls these methods, I believe it's on them to do something similar to this, and maybe we can make this exposed as a helper that can be used for third party component mocks? [General] [Changes] - Changed MockNativeMethods for core components to `jest.fn()` instead of function that warns about calling native methods. Pull Request resolved: https://github.com/facebook/react-native/pull/24337 Differential Revision: D14822126 Pulled By: cpojer fbshipit-source-id: 2199b8c8da8e289d38823bdcd2c43c82f3f635c9 useAndroidX and enableJetifier config in template gradle.properties (#24319) Summary: enable useAndroidX and enableJetifier that transforms non-Androidx third-party libraries to use AndroidX. [Android] [Changed] - useAndroidX and enableJetifier config in template gradle.properties Pull Request resolved: https://github.com/facebook/react-native/pull/24319 Differential Revision: D14822125 Pulled By: cpojer fbshipit-source-id: 596a92f26fb06b077da507583b72af2b5abf712a Fix bugs around `align-content` Summary: @public Regenerating the “golden master” tests with chrome surfaced different bugs around `align-content`: - a misunderstanding that values in `align-content` only applied *if there is only one line.* In fact, it applies *every time* a container is set to `flex-wrap: wrap`. Chrome had this wrong, and as such our tests were generated with incorrect parameters. - empty children growing to the cross axis size of the container, even when `align-content` is different from `stretch`. This was implemented incorrectly in Chrome as well. Here, we fix it with an extra check. Reviewed By: SidharthGuglani Differential Revision: D14725402 fbshipit-source-id: a45bebdadb9c694dc0eb7e27cb52b3d247f81c50 Support callbacks in synchronous native functions Summary: We need to move animated native module calls to synchronous so we can properly thread them in with mounting instructions in Fabric. Some of them take callbacks so we need to add support for that when switching to synchronous. A side benefit is that we can unify codepaths a little more with async callbacks. Reviewed By: shergin Differential Revision: D14790898 fbshipit-source-id: dc222b9e74375e046e8a9b1b19d72f652dc6722c Add some native module method test cases Summary: Just a little more rigorous Reviewed By: shergin Differential Revision: D14790912 fbshipit-source-id: 0a4c9b6ea68466efb060c9c90572ff8987fdbd26 More iOS animation fixes Summary: Main change is to the property diffing - we now use the last known props set on the view rather than the default props to compute the diff. This requires exposing a `getProps` method on all view components which should be fine I think. I also realized that in more complex animations with multiple nodes, the node that the animation starts on might not be connected to a view, so we don't know if it's fabric just based on that, so we have to do a recursive search through the children to find if there are any that are associated with a fabric view to decide we should start the animation immediately. Unfortunately there can still be a timing gap here since the animated API is async and the uimanager API is sync - I'll need to change the animated API to be sync to completely fix this. Reviewed By: shergin Differential Revision: D14732028 fbshipit-source-id: 882c056b0b63aa576f8e42439be405cf7fb3147a ignore dropView method when view is null (#24347) Summary: In #20288, we solved `com.facebook.react.uimanager.NativeViewHierarchyManager.dropView (NativeViewHierarchyManager.java:536)` by adding codes that prevent to run method on `view` object when `view.getId()` is null. But if `view` is null, `view.getId()` can make error about NullPointerException. So, I think `dropView` method needs to check if `view` is null. If you need more information, Please read #24346. [Android] [Fixed] - Ignore dropView method when view is null. Pull Request resolved: https://github.com/facebook/react-native/pull/24347 Differential Revision: D14833822 Pulled By: cpojer fbshipit-source-id: 88b6a05090ea8e8d6737c1f10b40e93649fab151 Remove wrapper around ListEmptyComponent (#24339) Summary: This pull request fixes #24257. The wrapper around ListEmptyComponent doesn't allow to use flex on the ListEmptyComponent. The wrapper was removed in this [commit](https://github.com/facebook/react-native/commit/db061ea8c7b78d7e9df4a450c9e7a24d9b2382b4), and then put back in this [commit](https://github.com/facebook/react-native/commit/e94d3444dcface90bd20234d13143462690ff23c) but I think the relevant part was removing the condition on `itemCount !== 0` to apply the inversionStyle on the ScrollView and everything is still working without the wrapper. [GENERAL] [FIXED] - Remove wrapper around ListEmptyComponent Pull Request resolved: https://github.com/facebook/react-native/pull/24339 Differential Revision: D14822221 Pulled By: cpojer fbshipit-source-id: 623e1ab3f228e9b75b92cdcd568683232a403c1a Make FlatList's renderItem return React.Node Summary: Flow typing can be annoying because the `renderItem` prop for FlatList always has to specifically be of type `React.Element`. Since really we just want to return something renderable, it should be fine to type this as `React.Node` instead. I'm not sure if this is valid, but it seems like since we just need to implant the `key` property, we should be able to accomplish this with a `React.Fragment` wrapper instead of needing to call `cloneElement`. Looking for feedback on if this is a sensible fix. Changelog: [General][Changed] Updated FlatList's renderItem Flow type from React.Element to React.Node Reviewed By: sahrens Differential Revision: D14814805 fbshipit-source-id: ce6793dea5a92619babe048dcfddee0e4519979c Set up .flowconfig to support Haste and path-based imports (#24318) Summary: See https://github.com/facebook/react-native/issues/24316 for the motivation. By adding the .android.js and .ios.js extensions to the respective .flowconfig files, Flow is able to find files that either: - Are required using Haste - Are required using standard paths and have a .js extension - Are required using standard paths and have a .platform.js subextension [General] [Changed] - Adjusted .flowconfig to support both Haste and standard path-based requires Pull Request resolved: https://github.com/facebook/react-native/pull/24318 Differential Revision: D14822356 Pulled By: cpojer fbshipit-source-id: dde0c83692d6170f4a44cd3fb8ede162054157e9 Export JNativeRunnable from react/jni Summary: Adding JNativeRunnable to exported headers in react/jni/BUCK so I can use it outside of CatalystInstance. Reviewed By: axe-fb Differential Revision: D14817655 fbshipit-source-id: 15aa794e50f273778da337956c887c235a5aec3d Fix Docker Android tests container issue related to the JSC (#24360) Summary: [Fixes an issue where the Docker Android tests container cannot be built.](https://github.com/facebook/react-native/pull/24276#issuecomment-480915232) The JSC is now pulled from the npm registry, so we need to run `yarn` prior to pulling the Gradle dependencies. [Android] [Fixed] - Fixed React Native Android tests Docker container issue related to the JSC Pull Request resolved: https://github.com/facebook/react-native/pull/24360 Differential Revision: D14842534 Pulled By: hramos fbshipit-source-id: 3a1a714879e9c52a812b1077dce449470c30bddd Fix tests with JavaOnlyMap Summary: Need to force the double thing in more places. Reviewed By: cpojer Differential Revision: D14835792 fbshipit-source-id: fb7a5435675b322d5fbbe9858e08804e9abe65db --- .appveyor/config.yml | 11 +- .circleci/Dockerfiles/Dockerfile.android | 19 +- .flowconfig | 6 +- .flowconfig.android | 6 +- IntegrationTests/SyncMethodTest.js | 20 +- .../src/__tests__/AnimatedMock-test.js | 35 +- .../src/components/AnimatedScrollView.js | 2 +- .../Animated/src/createAnimatedComponent.js | 3 +- Libraries/BatchedBridge/MessageQueue.js | 50 +- Libraries/BatchedBridge/NativeModules.js | 37 +- Libraries/CameraRoll/RCTImagePickerManager.m | 7 +- .../DatePickerAndroidTypes.js | 2 +- Libraries/Components/ScrollView/ScrollView.js | 6 + .../AndroidTextInputNativeComponent.js | 19 + .../TextInput/__tests__/TextInput-test.js | 14 +- Libraries/Components/Touchable/Touchable.js | 8 +- Libraries/Components/View/View.js | 15 +- .../Components/WebView/WebView.android.js | 510 ------------ Libraries/Components/WebView/WebView.ios.js | 774 ------------------ Libraries/Components/WebView/WebViewShared.js | 26 - .../WebView/__tests__/WebViewShared-test.js | 62 -- Libraries/Core/InitializeCore.js | 2 +- .../Core/__tests__/MapAndSetPolyfills-test.js | 2 - Libraries/Core/polyfillES6Collections.js | 2 - ...{setUpGeolocation.js => setUpNavigator.js} | 5 - Libraries/Geolocation/Geolocation.js | 182 ---- .../RCTGeolocation.xcodeproj/project.pbxproj | 257 ------ Libraries/Geolocation/RCTLocationObserver.h | 12 - Libraries/Geolocation/RCTLocationObserver.m | 403 --------- .../Geolocation/React-RCTGeolocation.podspec | 35 - .../Geolocation/__tests__/Geolocation-test.js | 102 --- Libraries/LayoutAnimation/LayoutAnimation.js | 4 +- Libraries/Lists/FlatList.js | 8 +- Libraries/Lists/VirtualizedList.js | 28 +- Libraries/Lists/VirtualizedSectionList.js | 8 +- .../VirtualizedList-test.js.snap | 6 +- .../NativeAnimation/Nodes/RCTAnimatedNode.h | 2 + .../NativeAnimation/Nodes/RCTAnimatedNode.m | 10 + .../Nodes/RCTPropsAnimatedNode.m | 5 + .../NativeAnimation/RCTNativeAnimatedModule.m | 6 +- .../RCTNativeAnimatedNodesManager.h | 2 + .../RCTNativeAnimatedNodesManager.m | 6 + Libraries/ReactNative/FabricUIManager.js | 19 +- Libraries/Text/Text.js | 4 + Libraries/polyfills/Array.es6.js | 81 -- Libraries/polyfills/Array.prototype.es6.js | 95 --- Libraries/polyfills/Number.es6.js | 38 - Libraries/polyfills/Object.es6.js | 39 - Libraries/polyfills/String.prototype.es6.js | 160 ---- Libraries/polyfills/babelHelpers.js | 608 -------------- Libraries/vendor/core/Map.js | 3 - .../core/_wrapObjectFreezeAndFriends.js | 39 - RNTester/Podfile | 1 - RNTester/Podfile.lock | 7 - RNTester/README.md | 2 +- RNTester/RNTester.xcodeproj/project.pbxproj | 32 - .../RNTesterTestModule.m | 6 + .../RNTesterUnitTests/RCTModuleMethodTests.mm | 37 +- RNTester/android/app/BUCK | 2 +- RNTester/android/app/build.gradle | 20 + RNTester/js/GeolocationExample.js | 77 -- RNTester/js/LayoutAnimationExample.js | 21 +- RNTester/js/NativeAnimationsExample.js | 19 +- RNTester/js/RNTesterList.android.js | 4 - RNTester/js/RNTesterList.ios.js | 5 - RNTester/js/SectionListExample.js | 2 +- React.podspec | 1 - React/Base/RCTBridgeModule.h | 13 +- React/CxxBridge/RCTCxxBridge.mm | 5 + React/DevSupport/RCTDevMenu.m | 12 +- .../DevSupport/RCTInspectorDevServerHelper.mm | 2 +- .../View/RCTViewComponentView.mm | 14 +- .../Mounting/RCTComponentViewProtocol.h | 5 + React/Fabric/Mounting/RCTMountingManager.h | 6 +- React/Fabric/Mounting/RCTMountingManager.mm | 11 +- .../Mounting/UIView+ComponentViewProtocol.h | 2 + .../Mounting/UIView+ComponentViewProtocol.mm | 6 + React/Fabric/RCTSurfacePresenter.mm | 18 +- React/Fabric/RCTSurfaceRegistry.mm | 14 +- React/React-Core.podspec | 2 - React/React.xcodeproj/project.pbxproj | 40 - React/Views/RCTRefreshControl.m | 6 +- React/Views/RCTWKWebView.h | 47 -- React/Views/RCTWKWebView.m | 435 ---------- React/Views/RCTWKWebViewManager.h | 11 - React/Views/RCTWKWebViewManager.m | 170 ---- React/Views/RCTWebView.h | 46 -- React/Views/RCTWebView.m | 351 -------- React/Views/RCTWebViewManager.h | 12 - React/Views/RCTWebViewManager.m | 158 ---- React/Views/ScrollView/RCTScrollView.h | 1 + React/Views/ScrollView/RCTScrollView.m | 2 +- React/Views/ScrollView/RCTScrollViewManager.m | 1 + ReactAndroid/build.gradle | 18 +- ReactAndroid/gradle.properties | 3 +- .../java/com/facebook/react/testing/rule/BUCK | 2 +- .../java/com/facebook/react/tests/BUCK | 2 +- .../com/facebook/catalyst/appcompat/BUCK | 2 +- .../src/main/java/com/facebook/react/BUCK | 2 +- .../AbstractFloatPairPropertyUpdater.java | 62 -- .../AbstractSingleFloatProperyUpdater.java | 52 -- .../facebook/react/animation/Animation.java | 105 --- .../react/animation/AnimationListener.java | 25 - .../animation/AnimationPropertyUpdater.java | 44 - .../react/animation/AnimationRegistry.java | 41 - .../java/com/facebook/react/animation/BUCK | 14 - .../react/animation/ImmediateAnimation.java | 26 - .../NoopAnimationPropertyUpdater.java | 28 - .../OpacityAnimationPropertyUpdater.java | 34 - .../PositionAnimationPairPropertyUpdater.java | 40 - .../RotationAnimationPropertyUpdater.java | 30 - .../ScaleXAnimationPropertyUpdater.java | 34 - .../ScaleXYAnimationPairPropertyUpdater.java | 40 - .../ScaleYAnimationPropertyUpdater.java | 34 - .../facebook/react/bridge/JavaOnlyArray.java | 2 +- .../facebook/react/bridge/JavaOnlyMap.java | 2 +- .../facebook/react/bridge/ReactContext.java | 111 ++- .../react/bridge/SynchronizedWeakHashSet.java | 130 --- .../react/fabric/FabricJSIModuleProvider.java | 3 + .../react/fabric/FabricUIManager.java | 33 +- .../react/fabric/jsi/StateWrapperImpl.java | 41 + .../com/facebook/react/fabric/jsi/jni/BUCK | 1 + .../facebook/react/fabric/jsi/jni/Binding.cpp | 56 +- .../react/fabric/jsi/jni/NodeStateWrapper.cpp | 41 + .../react/fabric/jsi/jni/NodeStateWrapper.h | 32 + .../facebook/react/fabric/jsi/jni/OnLoad.cpp | 2 + .../react/fabric/jsi/jni/StateWrapperImpl.cpp | 45 + .../react/fabric/jsi/jni/StateWrapperImpl.h | 34 + .../fabric/mounting/ContextBasedViewPool.java | 14 +- .../fabric/mounting/MountingManager.java | 32 +- .../react/fabric/mounting/ViewFactory.java | 18 + .../fabric/mounting/ViewManagerFactory.java | 34 + .../mounting/mountitems/BatchMountItem.java | 10 +- .../mountitems/PreAllocateViewMountItem.java | 25 +- .../mountitems/UpdateLocalDataMountItem.java | 1 - .../mountitems/UpdateStateMountItem.java | 31 + .../modules/camera/CameraRollManager.java | 8 +- .../com/facebook/react/modules/location/BUCK | 20 - .../modules/location/LocationModule.java | 377 --------- .../react/modules/location/PositionError.java | 51 -- .../systeminfo/AndroidInfoHelpers.java | 2 +- .../main/java/com/facebook/react/shell/BUCK | 2 - .../react/shell/MainReactPackage.java | 12 - .../facebook/react/turbomodule/core/jni/BUCK | 1 + .../core/jni/TurboModuleManager.cpp | 6 +- .../java/com/facebook/react/uimanager/BUCK | 1 - .../uimanager/IViewManagerWithChildren.java | 22 + .../react/uimanager/LayoutShadowNode.java | 6 + .../facebook/react/uimanager/NativeKind.java | 25 + .../uimanager/NativeViewHierarchyManager.java | 68 +- .../NativeViewHierarchyOptimizer.java | 107 ++- .../react/uimanager/ReactShadowNode.java | 29 +- .../react/uimanager/ReactShadowNodeImpl.java | 89 +- .../react/uimanager/StateWrapper.java | 29 + .../react/uimanager/UIImplementation.java | 74 +- .../react/uimanager/UIManagerModule.java | 28 +- .../react/uimanager/UIViewOperationQueue.java | 93 +-- .../react/uimanager/ViewGroupManager.java | 4 +- .../facebook/react/uimanager/ViewManager.java | 25 +- .../uimanager/ViewManagersPropertyCache.java | 6 +- .../LayoutAnimationController.java | 56 +- .../com/facebook/react/views/checkbox/BUCK | 2 +- .../java/com/facebook/react/views/picker/BUCK | 2 +- .../react/views/scroll/ReactScrollView.java | 2 +- .../java/com/facebook/react/views/slider/BUCK | 2 +- .../com/facebook/react/views/switchview/BUCK | 2 +- .../java/com/facebook/react/views/text/BUCK | 2 +- .../views/text/ReactBaseTextShadowNode.java | 99 ++- .../text/ReactTextAnchorViewManager.java | 5 + .../react/views/text/ReactTextShadowNode.java | 44 +- .../react/views/text/ReactTextUpdate.java | 43 +- .../react/views/text/ReactTextView.java | 202 ++++- .../views/text/ReactTextViewManager.java | 13 +- .../react/views/text/TextAttributes.java | 20 +- .../text/TextInlineViewPlaceholderSpan.java | 61 ++ .../com/facebook/react/views/textinput/BUCK | 1 + .../textinput/ReactTextInputManager.java | 27 +- .../textinput/ReactTextInputShadowNode.java | 29 +- .../com/facebook/react/views/toolbar/BUCK | 2 +- .../com/facebook/react/views/webview/BUCK | 18 - .../views/webview/ReactWebViewManager.java | 737 ----------------- .../react/views/webview/WebViewConfig.java | 19 - .../webview/events/TopLoadingErrorEvent.java | 47 -- .../webview/events/TopLoadingFinishEvent.java | 47 -- .../webview/events/TopLoadingStartEvent.java | 47 -- .../views/webview/events/TopMessageEvent.java | 50 -- .../src/main/java/com/facebook/systrace/BUCK | 1 - .../java/com/facebook/systrace/Systrace.java | 11 +- .../java/com/facebook/yoga/YogaConfig.java | 1 + .../java/com/facebook/yoga/YogaNative.java | 5 +- .../main/java/com/facebook/yoga/YogaNode.java | 8 +- .../java/com/facebook/yoga/YogaNodeJNI.java | 165 ++++ .../com/facebook/yoga/YogaNodeJNIBase.java | 179 +--- .../facebook/yoga/YogaNodeJNIBatching.java | 177 ++++ .../jni/first-party/yogajni/jni/YGJNI.cpp | 242 ++++-- ReactAndroid/src/main/jni/react/jni/BUCK | 1 + .../libraries/fresco/fresco-react-native/BUCK | 28 +- .../v7/{appcompat-orig => appcompat}/BUCK | 0 .../aar-unpacker.py | 0 .../src/test/java/com/facebook/react/BUCK | 4 +- .../test/java/com/facebook/react/modules/BUCK | 4 +- .../modules/timing/TimingModuleTest.java | 24 +- .../java/com/facebook/react/uimanager/BUCK | 1 - ReactCommon/cxxreact/BUCK | 3 + ReactCommon/cxxreact/Instance.cpp | 7 + ReactCommon/cxxreact/Instance.h | 2 + ReactCommon/cxxreact/JSBigString.cpp | 67 ++ ReactCommon/cxxreact/JSBigString.h | 2 +- ReactCommon/cxxreact/JSExecutor.h | 2 + ReactCommon/cxxreact/NativeToJsBridge.h | 3 +- ReactCommon/cxxreact/tests/BUCK | 14 + ReactCommon/cxxreact/tests/jsbigstring.cpp | 48 +- .../view/accessibility/AccessibilityProps.cpp | 3 - ReactCommon/fabric/core/events/EventEmitter.h | 2 + .../fabric/core/shadownode/LocalData.h | 2 + ReactCommon/fabric/core/shadownode/Props.h | 12 +- .../fabric/core/shadownode/ShadowNode.h | 10 +- .../core/shadownode/ShadowNodeFragment.cpp | 23 +- .../core/shadownode/ShadowNodeFragment.h | 33 +- ReactCommon/fabric/core/state/ConcreteState.h | 11 + ReactCommon/fabric/core/state/State.cpp | 19 + ReactCommon/fabric/core/state/State.h | 6 + ReactCommon/fabric/core/state/StateData.cpp | 30 + ReactCommon/fabric/core/state/StateData.h | 23 +- .../fabric/debug/DebugStringConvertible.cpp | 85 +- .../fabric/debug/DebugStringConvertible.h | 276 ++++++- .../debug/debugStringConvertibleUtils.h | 18 - .../tests/DebugStringConvertibleTest.cpp | 10 +- ReactCommon/fabric/mounting/BUCK | 4 + .../fabric/mounting/Differentiator.cpp | 2 +- .../{uimanager => mounting}/ShadowTree.cpp | 25 +- .../{uimanager => mounting}/ShadowTree.h | 14 +- .../ShadowTreeDelegate.h | 0 .../ShadowTreeRegistry.cpp | 0 .../ShadowTreeRegistry.h | 2 +- ReactCommon/fabric/mounting/ShadowView.cpp | 21 + ReactCommon/fabric/mounting/ShadowView.h | 9 + .../fabric/mounting/ShadowViewMutation.cpp | 49 ++ .../fabric/mounting/ShadowViewMutation.h | 9 + .../fabric/mounting/stubs/StubView.cpp | 31 + ReactCommon/fabric/mounting/stubs/StubView.h | 41 + .../fabric/mounting/stubs/StubViewTree.cpp | 104 +++ .../fabric/mounting/stubs/StubViewTree.h | 31 + ReactCommon/fabric/mounting/stubs/stubs.cpp | 30 + ReactCommon/fabric/mounting/stubs/stubs.h | 18 + ReactCommon/fabric/uimanager/BUCK | 2 +- ReactCommon/fabric/uimanager/Scheduler.cpp | 2 +- ReactCommon/fabric/uimanager/Scheduler.h | 6 +- ReactCommon/fabric/uimanager/UIManager.cpp | 2 +- ReactCommon/fabric/uimanager/UIManager.h | 2 +- .../jsiexecutor/jsireact/JSIExecutor.h | 3 +- .../turbomodule/core/JSCallInvoker.cpp | 16 +- ReactCommon/turbomodule/core/JSCallInvoker.h | 11 +- .../core/platform/ios/RCTTurboModule.h | 3 + .../core/platform/ios/RCTTurboModule.mm | 109 ++- .../platform/ios/RCTTurboModuleManager.mm | 2 +- .../{fabric/uimanager => utils}/TimeUtils.h | 0 ReactCommon/yoga/yoga/Yoga-internal.h | 2 + ReactCommon/yoga/yoga/Yoga.cpp | 17 +- ReactCommon/yoga/yoga/Yoga.h | 2 - build.gradle | 4 + gradle/wrapper/gradle-wrapper.jar | Bin 56177 -> 55741 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- jest-preset.js | 4 +- jest/MockNativeMethods.js | 29 +- jest/setup.js | 8 +- package.json | 13 +- .../index.js | 1 + rn-get-polyfills.js | 5 - scripts/bump-oss-version.js | 17 +- scripts/circleci/gradle_download_deps.sh | 2 +- scripts/objc-test-ios.sh | 3 +- scripts/objc-test-tvos.sh | 1 + scripts/objc-test.sh | 130 +-- template.config.js | 4 + template/_flowconfig | 53 +- template/android/app/build.gradle | 30 +- template/android/build.gradle | 11 +- template/android/gradle.properties | 3 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 56177 -> 55741 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../ios/HelloWorld.xcodeproj/project.pbxproj | 2 +- template/ios/Podfile | 1 - template/package.json | 25 + yarn.lock | 615 ++++++++------ 285 files changed, 4077 insertions(+), 8450 deletions(-) create mode 100644 Libraries/Components/TextInput/AndroidTextInputNativeComponent.js delete mode 100644 Libraries/Components/WebView/WebView.android.js delete mode 100644 Libraries/Components/WebView/WebView.ios.js delete mode 100644 Libraries/Components/WebView/WebViewShared.js delete mode 100644 Libraries/Components/WebView/__tests__/WebViewShared-test.js rename Libraries/Core/{setUpGeolocation.js => setUpNavigator.js} (74%) delete mode 100644 Libraries/Geolocation/Geolocation.js delete mode 100644 Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj delete mode 100644 Libraries/Geolocation/RCTLocationObserver.h delete mode 100644 Libraries/Geolocation/RCTLocationObserver.m delete mode 100644 Libraries/Geolocation/React-RCTGeolocation.podspec delete mode 100644 Libraries/Geolocation/__tests__/Geolocation-test.js delete mode 100644 Libraries/polyfills/Array.es6.js delete mode 100644 Libraries/polyfills/Array.prototype.es6.js delete mode 100644 Libraries/polyfills/Number.es6.js delete mode 100644 Libraries/polyfills/Object.es6.js delete mode 100644 Libraries/polyfills/String.prototype.es6.js delete mode 100644 Libraries/polyfills/babelHelpers.js delete mode 100644 Libraries/vendor/core/_wrapObjectFreezeAndFriends.js delete mode 100644 RNTester/js/GeolocationExample.js delete mode 100644 React/Views/RCTWKWebView.h delete mode 100644 React/Views/RCTWKWebView.m delete mode 100644 React/Views/RCTWKWebViewManager.h delete mode 100644 React/Views/RCTWKWebViewManager.m delete mode 100644 React/Views/RCTWebView.h delete mode 100644 React/Views/RCTWebView.m delete mode 100644 React/Views/RCTWebViewManager.h delete mode 100644 React/Views/RCTWebViewManager.m delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/AbstractFloatPairPropertyUpdater.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/AbstractSingleFloatProperyUpdater.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/Animation.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/AnimationListener.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/AnimationPropertyUpdater.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/AnimationRegistry.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/ImmediateAnimation.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/NoopAnimationPropertyUpdater.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/OpacityAnimationPropertyUpdater.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/PositionAnimationPairPropertyUpdater.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/RotationAnimationPropertyUpdater.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/ScaleXAnimationPropertyUpdater.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/ScaleXYAnimationPairPropertyUpdater.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/animation/ScaleYAnimationPropertyUpdater.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/bridge/SynchronizedWeakHashSet.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/StateWrapperImpl.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/NodeStateWrapper.cpp create mode 100644 ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/NodeStateWrapper.h create mode 100644 ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/StateWrapperImpl.cpp create mode 100644 ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/StateWrapperImpl.h create mode 100644 ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewFactory.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewManagerFactory.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateStateMountItem.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/location/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/location/PositionError.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/IViewManagerWithChildren.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeKind.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineViewPlaceholderSpan.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/webview/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/webview/WebViewConfig.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingErrorEvent.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingFinishEvent.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingStartEvent.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopMessageEvent.java create mode 100644 ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNIBatching.java rename ReactAndroid/src/main/third-party/android/support/v7/{appcompat-orig => appcompat}/BUCK (100%) rename ReactAndroid/src/main/third-party/android/support/v7/{appcompat-orig => appcompat}/aar-unpacker.py (100%) create mode 100644 ReactCommon/fabric/core/state/StateData.cpp rename ReactCommon/fabric/{uimanager => mounting}/ShadowTree.cpp (90%) rename ReactCommon/fabric/{uimanager => mounting}/ShadowTree.h (90%) rename ReactCommon/fabric/{uimanager => mounting}/ShadowTreeDelegate.h (100%) rename ReactCommon/fabric/{uimanager => mounting}/ShadowTreeRegistry.cpp (100%) rename ReactCommon/fabric/{uimanager => mounting}/ShadowTreeRegistry.h (97%) create mode 100644 ReactCommon/fabric/mounting/stubs/StubView.cpp create mode 100644 ReactCommon/fabric/mounting/stubs/StubView.h create mode 100644 ReactCommon/fabric/mounting/stubs/StubViewTree.cpp create mode 100644 ReactCommon/fabric/mounting/stubs/StubViewTree.h create mode 100644 ReactCommon/fabric/mounting/stubs/stubs.cpp create mode 100644 ReactCommon/fabric/mounting/stubs/stubs.h rename ReactCommon/{fabric/uimanager => utils}/TimeUtils.h (100%) create mode 100644 template.config.js create mode 100644 template/package.json diff --git a/.appveyor/config.yml b/.appveyor/config.yml index cf4098f1dccf2d..545f955abeeaa2 100644 --- a/.appveyor/config.yml +++ b/.appveyor/config.yml @@ -30,17 +30,16 @@ install: - appveyor DownloadFile "%NDK_TOOLS_URL%" -FileName "%TMP%/ndk.zip" - 7z x "%TMP%/ndk.zip" -o"%ANDROID_HOME%" > nul - - ps: Install-Product node $env:nodejs_version - - node --version - - yarn --version + - ps: Install-Product node $env:nodejs_version x64 + - npx envinfo@latest - appveyor-retry yarn install build_script: + - yarn run flow-check-android + - yarn run flow-check-ios + - yarn run test - gradlew.bat RNTester:android:app:assembleRelease -test_script: - - npm test - cache: - node_modules - "%LOCALAPPDATA%/Yarn" diff --git a/.circleci/Dockerfiles/Dockerfile.android b/.circleci/Dockerfiles/Dockerfile.android index 4b3a95d8a83f24..982f158fc023d6 100644 --- a/.circleci/Dockerfiles/Dockerfile.android +++ b/.circleci/Dockerfiles/Dockerfile.android @@ -8,10 +8,10 @@ # # The base image is expected to remain relatively stable, and only # needs to be updated when major dependencies such as the Android -# SDK or NDK are updated. +# SDK or NDK are updated. # -# In this Android Test image, we download the latest dependencies -# and build a Android application that can be used to run the +# In this Android Test image, we download the latest dependencies +# and build a Android application that can be used to run the # tests specified in the scripts/ directory. # FROM reactnativecommunity/react-native-android @@ -42,16 +42,11 @@ RUN buck fetch ReactAndroid/src/androidTest/... RUN buck build ReactAndroid/src/main/java/com/facebook/react RUN buck build ReactAndroid/src/main/java/com/facebook/react/shell -ADD gradle /app/gradle -ADD gradlew /app/gradlew -ADD settings.gradle /app/settings.gradle -ADD build.gradle /app/build.gradle -ADD react.gradle /app/react.gradle +ADD . /app -RUN ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog :ReactAndroid:downloadJSC +RUN yarn -RUN ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=1 +RUN ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog -ADD . /app +RUN ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=1 -RUN yarn diff --git a/.flowconfig b/.flowconfig index 7c62e71d1d894f..797e4cb47f3262 100644 --- a/.flowconfig +++ b/.flowconfig @@ -44,6 +44,10 @@ emoji=true esproposal.optional_chaining=enable esproposal.nullish_coalescing=enable +module.file_ext=.js +module.file_ext=.json +module.file_ext=.ios.js + module.system=haste module.system.haste.use_name_reducers=true # keep the following in sync with server/haste/hasteImpl.js @@ -99,4 +103,4 @@ untyped-import untyped-type-import [version] -^0.95.0 +^0.96.0 diff --git a/.flowconfig.android b/.flowconfig.android index 5336ab989fa0f1..037cdfeac7ee48 100644 --- a/.flowconfig.android +++ b/.flowconfig.android @@ -44,6 +44,10 @@ emoji=true esproposal.optional_chaining=enable esproposal.nullish_coalescing=enable +module.file_ext=.js +module.file_ext=.json +module.file_ext=.android.js + module.system=haste module.system.haste.use_name_reducers=true # keep the following in sync with server/haste/hasteImpl.js @@ -99,4 +103,4 @@ untyped-import untyped-type-import [version] -^0.95.0 +^0.96.0 diff --git a/IntegrationTests/SyncMethodTest.js b/IntegrationTests/SyncMethodTest.js index d618ced4761585..8dbb5b94ad78f2 100644 --- a/IntegrationTests/SyncMethodTest.js +++ b/IntegrationTests/SyncMethodTest.js @@ -21,12 +21,26 @@ class SyncMethodTest extends React.Component<{}> { if ( RNTesterTestModule.echoString('test string value') !== 'test string value' ) { - throw new Error('Something wrong with sync method export'); + throw new Error('Something wrong with echoString sync method'); } if (RNTesterTestModule.methodThatReturnsNil() != null) { - throw new Error('Something wrong with sync method export'); + throw new Error('Something wrong with methodThatReturnsNil sync method'); } - TestModule.markTestCompleted(); + let response; + RNTesterTestModule.methodThatCallsCallbackWithString('test', echo => { + response = echo; + }); + requestAnimationFrame(() => { + if (response === 'test') { + TestModule.markTestCompleted(); + } else { + throw new Error( + 'Something wrong with methodThatCallsCallbackWithString sync method, ' + + 'got response ' + + JSON.stringify(response), + ); + } + }); } render(): React.Node { diff --git a/Libraries/Animated/src/__tests__/AnimatedMock-test.js b/Libraries/Animated/src/__tests__/AnimatedMock-test.js index ac0bc6b20b36cd..767093cf9dfdf6 100644 --- a/Libraries/Animated/src/__tests__/AnimatedMock-test.js +++ b/Libraries/Animated/src/__tests__/AnimatedMock-test.js @@ -19,11 +19,34 @@ describe('Animated Mock', () => { Object.keys(AnimatedImplementation), ); }); - it('matches implementation params', () => { - Object.keys(AnimatedImplementation).forEach(key => - expect(AnimatedImplementation[key].length).toEqual( - AnimatedMock[key].length, - ), - ); + it('matches implementation params', done => { + Object.keys(AnimatedImplementation).forEach(key => { + if (AnimatedImplementation[key].length !== AnimatedMock[key].length) { + done( + new Error( + 'key ' + + key + + ' had different lengths: ' + + JSON.stringify( + { + impl: { + len: AnimatedImplementation[key].length, + type: typeof AnimatedImplementation[key], + val: AnimatedImplementation[key].toString(), + }, + mock: { + len: AnimatedMock[key].length, + type: typeof AnimatedMock[key], + val: AnimatedMock[key].toString(), + }, + }, + null, + 2, + ), + ), + ); + } + }); + done(); }); }); diff --git a/Libraries/Animated/src/components/AnimatedScrollView.js b/Libraries/Animated/src/components/AnimatedScrollView.js index 195f17b7005ad7..aae27469be677e 100644 --- a/Libraries/Animated/src/components/AnimatedScrollView.js +++ b/Libraries/Animated/src/components/AnimatedScrollView.js @@ -14,4 +14,4 @@ const ScrollView = require('ScrollView'); const createAnimatedComponent = require('createAnimatedComponent'); -module.exports = createAnimatedComponent(ScrollView); +module.exports = createAnimatedComponent(ScrollView, {scrollEventThrottle: 16}); diff --git a/Libraries/Animated/src/createAnimatedComponent.js b/Libraries/Animated/src/createAnimatedComponent.js index 327070de59da40..840df7485e0509 100644 --- a/Libraries/Animated/src/createAnimatedComponent.js +++ b/Libraries/Animated/src/createAnimatedComponent.js @@ -16,7 +16,7 @@ const DeprecatedViewStylePropTypes = require('DeprecatedViewStylePropTypes'); const invariant = require('invariant'); -function createAnimatedComponent(Component: any): any { +function createAnimatedComponent(Component: any, defaultProps: any): any { invariant( typeof Component !== 'function' || (Component.prototype && Component.prototype.isReactComponent), @@ -149,6 +149,7 @@ function createAnimatedComponent(Component: any): any { const props = this._propsAnimated.__getValue(); return ( '}`) + + `. Args: '${stringifySafe(args)}'`, + ); const profileName = debug ? '' : cbID; diff --git a/Libraries/BatchedBridge/NativeModules.js b/Libraries/BatchedBridge/NativeModules.js index 27db8b9ad3e3c3..c8487e8884ead5 100644 --- a/Libraries/BatchedBridge/NativeModules.js +++ b/Libraries/BatchedBridge/NativeModules.js @@ -105,19 +105,6 @@ function genMethod(moduleID: number, methodID: number, type: MethodType) { ); }); }; - } else if (type === 'sync') { - fn = function(...args: Array) { - if (__DEV__) { - invariant( - global.nativeCallSyncHook, - 'Calling synchronous methods on native ' + - 'modules is not supported in Chrome.\n\n Consider providing alternative ' + - 'methods to expose this method in debug mode, e.g. by exposing constants ' + - 'ahead-of-time.', - ); - } - return global.nativeCallSyncHook(moduleID, methodID, args); - }; } else { fn = function(...args: Array) { const lastArg = args.length > 0 ? args[args.length - 1] : null; @@ -133,13 +120,23 @@ function genMethod(moduleID: number, methodID: number, type: MethodType) { const onFail = hasErrorCallback ? secondLastArg : null; const callbackCount = hasSuccessCallback + hasErrorCallback; args = args.slice(0, args.length - callbackCount); - BatchedBridge.enqueueNativeCall( - moduleID, - methodID, - args, - onFail, - onSuccess, - ); + if (type === 'sync') { + return BatchedBridge.callNativeSyncHook( + moduleID, + methodID, + args, + onFail, + onSuccess, + ); + } else { + BatchedBridge.enqueueNativeCall( + moduleID, + methodID, + args, + onFail, + onSuccess, + ); + } }; } fn.type = type; diff --git a/Libraries/CameraRoll/RCTImagePickerManager.m b/Libraries/CameraRoll/RCTImagePickerManager.m index ac6db9b8070b4e..d7b57c7b7a2a11 100644 --- a/Libraries/CameraRoll/RCTImagePickerManager.m +++ b/Libraries/CameraRoll/RCTImagePickerManager.m @@ -52,9 +52,14 @@ - (id)init return self; } ++ (BOOL)requiresMainQueueSetup +{ + return NO; +} + - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self name:@"AVCaptureDeviceDidStartRunningNotification" object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:@"AVCaptureDeviceDidStartRunningNotification" object:nil]; } - (dispatch_queue_t)methodQueue diff --git a/Libraries/Components/DatePickerAndroid/DatePickerAndroidTypes.js b/Libraries/Components/DatePickerAndroid/DatePickerAndroidTypes.js index eedc99d229aa68..096d7b5e8d9abb 100644 --- a/Libraries/Components/DatePickerAndroid/DatePickerAndroidTypes.js +++ b/Libraries/Components/DatePickerAndroid/DatePickerAndroidTypes.js @@ -12,7 +12,7 @@ export type Options = $ReadOnly<{| date?: ?(Date | number), minDate?: ?(Date | number), maxDate?: ?(Date | number), - mode?: ?('calender' | 'spinner' | 'default'), + mode?: ?('calendar' | 'spinner' | 'default'), |}>; export type DatePickerOpenAction = diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index e0c0a7a5faea6b..ed1ba1b01699e6 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -221,6 +221,12 @@ type IOSProps = $ReadOnly<{| * @platform ios */ scrollIndicatorInsets?: ?EdgeInsetsProp, + /** + * When true, the scroll view can be programmatically scrolled beyond its + * content size. The default value is false. + * @platform ios + */ + scrollToOverflowEnabled?: ?boolean, /** * When true, the scroll view scrolls to top when the status bar is tapped. * The default value is true. diff --git a/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js new file mode 100644 index 00000000000000..aa6b88ea8a7a55 --- /dev/null +++ b/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js @@ -0,0 +1,19 @@ +/** + * 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 {requireNativeComponent} from 'react-native'; + +const AndroidTextInputNativeComponent = requireNativeComponent( + 'AndroidTextInput', +); + +export default AndroidTextInputNativeComponent; diff --git a/Libraries/Components/TextInput/__tests__/TextInput-test.js b/Libraries/Components/TextInput/__tests__/TextInput-test.js index 78d6884db03395..431b65b1104971 100644 --- a/Libraries/Components/TextInput/__tests__/TextInput-test.js +++ b/Libraries/Components/TextInput/__tests__/TextInput-test.js @@ -50,12 +50,14 @@ describe('TextInput tests', () => { it('has expected instance functions', () => { expect(input.instance.isFocused).toBeInstanceOf(Function); // Would have prevented S168585 expect(input.instance.clear).toBeInstanceOf(Function); - expect(input.instance.focus).toBeInstanceOf(Function); - expect(input.instance.blur).toBeInstanceOf(Function); - expect(input.instance.setNativeProps).toBeInstanceOf(Function); - expect(input.instance.measure).toBeInstanceOf(Function); - expect(input.instance.measureInWindow).toBeInstanceOf(Function); - expect(input.instance.measureLayout).toBeInstanceOf(Function); + expect(input.instance.focus).toBeInstanceOf(jest.fn().constructor); + expect(input.instance.blur).toBeInstanceOf(jest.fn().constructor); + expect(input.instance.setNativeProps).toBeInstanceOf(jest.fn().constructor); + expect(input.instance.measure).toBeInstanceOf(jest.fn().constructor); + expect(input.instance.measureInWindow).toBeInstanceOf( + jest.fn().constructor, + ); + expect(input.instance.measureLayout).toBeInstanceOf(jest.fn().constructor); }); it('calls onChange callbacks', () => { expect(input.props.value).toBe(initialValue); diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js index 72f7e4c35033f5..03aeeb9261b444 100644 --- a/Libraries/Components/Touchable/Touchable.js +++ b/Libraries/Components/Touchable/Touchable.js @@ -484,6 +484,7 @@ const TouchableMixin = { * Place as callback for a DOM element's `onResponderRelease` event. */ touchableHandleResponderRelease: function(e: PressEvent) { + this.pressInLocation = null; this._receiveSignal(Signals.RESPONDER_RELEASE, e); }, @@ -491,6 +492,7 @@ const TouchableMixin = { * Place as callback for a DOM element's `onResponderTerminate` event. */ touchableHandleResponderTerminate: function(e: PressEvent) { + this.pressInLocation = null; this._receiveSignal(Signals.RESPONDER_TERMINATED, e); }, @@ -558,9 +560,13 @@ const TouchableMixin = { dimensionsOnActivate.height + pressExpandBottom; if (isTouchWithinActive) { + const prevState = this.state.touchable.touchState; this._receiveSignal(Signals.ENTER_PRESS_RECT, e); const curState = this.state.touchable.touchState; - if (curState === States.RESPONDER_INACTIVE_PRESS_IN) { + if ( + curState === States.RESPONDER_INACTIVE_PRESS_IN && + prevState !== States.RESPONDER_INACTIVE_PRESS_IN + ) { // fix for t7967420 this._cancelLongPressDelayTimeout(); } diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 3e9d7c10a1a7b6..8e75dcac461fd6 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -11,11 +11,8 @@ 'use strict'; const React = require('React'); -const TextAncestor = require('TextAncestor'); const ViewNativeComponent = require('ViewNativeComponent'); -const invariant = require('invariant'); - import type {ViewProps} from 'ViewPropTypes'; export type Props = ViewProps; @@ -35,17 +32,7 @@ if (__DEV__) { props: Props, forwardedRef: React.Ref, ) => { - return ( - - {hasTextAncestor => { - invariant( - !hasTextAncestor, - 'Nesting of within is not currently supported.', - ); - return ; - }} - - ); + return ; }; ViewToExport = React.forwardRef(View); ViewToExport.displayName = 'View'; diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js deleted file mode 100644 index 6ca4844b1be9d4..00000000000000 --- a/Libraries/Components/WebView/WebView.android.js +++ /dev/null @@ -1,510 +0,0 @@ -/** - * 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'; - -const ActivityIndicator = require('ActivityIndicator'); -const DeprecatedViewPropTypes = require('DeprecatedViewPropTypes'); -const DeprecatedEdgeInsetsPropType = require('DeprecatedEdgeInsetsPropType'); -const PropTypes = require('prop-types'); -const React = require('React'); -const ReactNative = require('ReactNative'); -const StyleSheet = require('StyleSheet'); -const UIManager = require('UIManager'); -const View = require('View'); -const WebViewShared = require('WebViewShared'); - -const deprecatedPropType = require('deprecatedPropType'); -const keyMirror = require('fbjs/lib/keyMirror'); -const requireNativeComponent = require('requireNativeComponent'); -const resolveAssetSource = require('resolveAssetSource'); - -const RCT_WEBVIEW_REF = 'webview'; - -const WebViewState = keyMirror({ - IDLE: null, - LOADING: null, - ERROR: null, -}); - -const defaultRenderLoading = () => ( - - - -); - -type Props = any; -type State = any; - -/** - * Renders a native WebView. - */ -class WebView extends React.Component { - static propTypes = { - ...DeprecatedViewPropTypes, - renderError: PropTypes.func, - renderLoading: PropTypes.func, - onLoad: PropTypes.func, - onLoadEnd: PropTypes.func, - onLoadStart: PropTypes.func, - onError: PropTypes.func, - automaticallyAdjustContentInsets: PropTypes.bool, - contentInset: DeprecatedEdgeInsetsPropType, - onNavigationStateChange: PropTypes.func, - onMessage: PropTypes.func, - onContentSizeChange: PropTypes.func, - startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load - style: DeprecatedViewPropTypes.style, - - html: deprecatedPropType( - PropTypes.string, - 'Use the `source` prop instead.', - ), - - url: deprecatedPropType(PropTypes.string, 'Use the `source` prop instead.'), - - /** - * Loads static html or a uri (with optional headers) in the WebView. - */ - source: PropTypes.oneOfType([ - PropTypes.shape({ - /* - * The URI to load in the WebView. Can be a local or remote file. - */ - uri: PropTypes.string, - /* - * The HTTP Method to use. Defaults to GET if not specified. - * NOTE: On Android, only GET and POST are supported. - */ - method: PropTypes.oneOf(['GET', 'POST']), - /* - * Additional HTTP headers to send with the request. - * NOTE: On Android, this can only be used with GET requests. - */ - headers: PropTypes.object, - /* - * The HTTP body to send with the request. This must be a valid - * UTF-8 string, and will be sent exactly as specified, with no - * additional encoding (e.g. URL-escaping or base64) applied. - * NOTE: On Android, this can only be used with POST requests. - */ - body: PropTypes.string, - }), - PropTypes.shape({ - /* - * A static HTML page to display in the WebView. - */ - html: PropTypes.string, - /* - * The base URL to be used for any relative links in the HTML. - */ - baseUrl: PropTypes.string, - }), - /* - * Used internally by packager. - */ - PropTypes.number, - ]), - - /** - * If true, use WKWebView instead of UIWebView. - * @platform ios - */ - useWebKit: PropTypes.bool, - - /** - * Used on Android only to disable Hardware Acceleration if needed - * Hardware acceleration can not be enabled at view level but it can be - * disabled see: - * https://developer.android.com/guide/topics/graphics/hardware-accel - * - * @platform android - */ - hardwareAccelerationEnabledExperimental: PropTypes.bool, - - /** - * Used on Android only, JS is enabled by default for WebView on iOS - * @platform android - */ - javaScriptEnabled: PropTypes.bool, - - /** - * Used on Android Lollipop and above only, third party cookies are enabled - * by default for WebView on Android Kitkat and below and on iOS - * @platform android - */ - thirdPartyCookiesEnabled: PropTypes.bool, - - /** - * Used on Android only, controls whether DOM Storage is enabled or not - * @platform android - */ - domStorageEnabled: PropTypes.bool, - - /** - * Sets whether Geolocation is enabled. The default is false. - * @platform android - */ - geolocationEnabled: PropTypes.bool, - - /** - * Sets the JS to be injected when the webpage loads. - */ - injectedJavaScript: PropTypes.string, - - /** - * Sets whether the webpage scales to fit the view and the user can change the scale. - */ - scalesPageToFit: PropTypes.bool, - - /** - * Sets whether the webview allow access to file system. - * @platform android - */ - allowFileAccess: PropTypes.bool, - - /** - * Sets the user-agent for this WebView. The user-agent can also be set in native using - * WebViewConfig. This prop will overwrite that config. - */ - userAgent: PropTypes.string, - - /** - * Used to locate this view in end-to-end tests. - */ - testID: PropTypes.string, - - /** - * Determines whether HTML5 audio & videos require the user to tap before they can - * start playing. The default value is `false`. - */ - mediaPlaybackRequiresUserAction: PropTypes.bool, - - /** - * Boolean that sets whether JavaScript running in the context of a file - * scheme URL should be allowed to access content from any origin. - * Including accessing content from other file scheme URLs - * @platform android - */ - allowUniversalAccessFromFileURLs: PropTypes.bool, - - /** - * List of origin strings to allow being navigated to. The strings allow - * wildcards and get matched against *just* the origin (not the full URL). - * If the user taps to navigate to a new page but the new page is not in - * this whitelist, the URL will be opened by the Android OS. - * The default whitelisted origins are "http://*" and "https://*". - */ - originWhitelist: PropTypes.arrayOf(PropTypes.string), - - /** - * Function that accepts a string that will be passed to the WebView and - * executed immediately as JavaScript. - */ - injectJavaScript: PropTypes.func, - - /** - * Specifies the mixed content mode. i.e WebView will allow a secure origin to load content from any other origin. - * - * Possible values for `mixedContentMode` are: - * - * - `'never'` (default) - WebView will not allow a secure origin to load content from an insecure origin. - * - `'always'` - WebView will allow a secure origin to load content from any other origin, even if that origin is insecure. - * - `'compatibility'` - WebView will attempt to be compatible with the approach of a modern web browser with regard to mixed content. - * @platform android - */ - mixedContentMode: PropTypes.oneOf(['never', 'always', 'compatibility']), - - /** - * Used on Android only, controls whether form autocomplete data should be saved - * @platform android - */ - saveFormDataDisabled: PropTypes.bool, - - /** - * Override the native component used to render the WebView. Enables a custom native - * WebView which uses the same JavaScript as the original WebView. - */ - nativeConfig: PropTypes.shape({ - /* - * The native component used to render the WebView. - */ - component: PropTypes.any, - /* - * Set props directly on the native component WebView. Enables custom props which the - * original WebView doesn't pass through. - */ - props: PropTypes.object, - /* - * Set the ViewManager to use for communication with the native side. - * @platform ios - */ - viewManager: PropTypes.object, - }), - /* - * Used on Android only, controls whether the given list of URL prefixes should - * make {@link com.facebook.react.views.webview.ReactWebViewClient} to launch a - * default activity intent for those URL instead of loading it within the webview. - * Use this to list URLs that WebView cannot handle, e.g. a PDF url. - * @platform android - */ - urlPrefixesForDefaultIntent: PropTypes.arrayOf(PropTypes.string), - }; - - static defaultProps = { - javaScriptEnabled: true, - thirdPartyCookiesEnabled: true, - scalesPageToFit: true, - hardwareAccelerationEnabledExperimental: true, - saveFormDataDisabled: false, - originWhitelist: WebViewShared.defaultOriginWhitelist, - }; - - state = { - viewState: WebViewState.IDLE, - lastErrorEvent: null, - startInLoadingState: true, - }; - - UNSAFE_componentWillMount() { - if (this.props.startInLoadingState) { - this.setState({viewState: WebViewState.LOADING}); - } - } - - render() { - let otherView = null; - - if (this.state.viewState === WebViewState.LOADING) { - otherView = (this.props.renderLoading || defaultRenderLoading)(); - } else if (this.state.viewState === WebViewState.ERROR) { - const errorEvent = this.state.lastErrorEvent; - otherView = - this.props.renderError && - this.props.renderError( - errorEvent.domain, - errorEvent.code, - errorEvent.description, - ); - } else if (this.state.viewState !== WebViewState.IDLE) { - console.error( - 'RCTWebView invalid state encountered: ' + this.state.loading, - ); - } - - const webViewStyles = [styles.container, this.props.style]; - if ( - this.state.viewState === WebViewState.LOADING || - this.state.viewState === WebViewState.ERROR - ) { - // if we're in either LOADING or ERROR states, don't show the webView - webViewStyles.push(styles.hidden); - } - - const source = this.props.source || {}; - if (this.props.html) { - source.html = this.props.html; - } else if (this.props.url) { - source.uri = this.props.url; - } - - if (source.method === 'POST' && source.headers) { - console.warn( - 'WebView: `source.headers` is not supported when using POST.', - ); - } else if (source.method === 'GET' && source.body) { - console.warn('WebView: `source.body` is not supported when using GET.'); - } - - const nativeConfig = this.props.nativeConfig || {}; - - const originWhitelist = (this.props.originWhitelist || []).map( - WebViewShared.originWhitelistToRegex, - ); - - let NativeWebView = nativeConfig.component || RCTWebView; - - const webView = ( - - ); - - return ( - - {webView} - {otherView} - - ); - } - - goForward = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.getViewManagerConfig('RCTWebView').Commands.goForward, - null, - ); - }; - - goBack = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.getViewManagerConfig('RCTWebView').Commands.goBack, - null, - ); - }; - - reload = () => { - this.setState({ - viewState: WebViewState.LOADING, - }); - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.getViewManagerConfig('RCTWebView').Commands.reload, - null, - ); - }; - - stopLoading = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.getViewManagerConfig('RCTWebView').Commands.stopLoading, - null, - ); - }; - - postMessage = (data: string) => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.getViewManagerConfig('RCTWebView').Commands.postMessage, - [String(data)], - ); - }; - - /** - * Injects a javascript string into the referenced WebView. Deliberately does not - * return a response because using eval() to return a response breaks this method - * on pages with a Content Security Policy that disallows eval(). If you need that - * functionality, look into postMessage/onMessage. - */ - injectJavaScript = (data: string) => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.getViewManagerConfig('RCTWebView').Commands.injectJavaScript, - [data], - ); - }; - - /** - * We return an event with a bunch of fields including: - * url, title, loading, canGoBack, canGoForward - */ - updateNavigationState = (event: any) => { - if (this.props.onNavigationStateChange) { - this.props.onNavigationStateChange(event.nativeEvent); - } - }; - - getWebViewHandle = () => { - return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]); - }; - - onLoadingStart = (event: any) => { - const onLoadStart = this.props.onLoadStart; - onLoadStart && onLoadStart(event); - this.updateNavigationState(event); - }; - - onLoadingError = (event: any) => { - event.persist(); // persist this event because we need to store it - const {onError, onLoadEnd} = this.props; - onError && onError(event); - onLoadEnd && onLoadEnd(event); - console.warn('Encountered an error loading page', event.nativeEvent); - - this.setState({ - lastErrorEvent: event.nativeEvent, - viewState: WebViewState.ERROR, - }); - }; - - onLoadingFinish = (event: any) => { - const {onLoad, onLoadEnd} = this.props; - onLoad && onLoad(event); - onLoadEnd && onLoadEnd(event); - this.setState({ - viewState: WebViewState.IDLE, - }); - this.updateNavigationState(event); - }; - - onMessage = (event: any) => { - const {onMessage} = this.props; - onMessage && onMessage(event); - }; -} - -const RCTWebView = requireNativeComponent('RCTWebView'); - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - hidden: { - height: 0, - flex: 0, // disable 'flex:1' when hiding a View - }, - loadingView: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - loadingProgressBar: { - height: 20, - }, -}); - -module.exports = WebView; diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js deleted file mode 100644 index c1956a2d0529ad..00000000000000 --- a/Libraries/Components/WebView/WebView.ios.js +++ /dev/null @@ -1,774 +0,0 @@ -/** - * 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'; - -const ActivityIndicator = require('ActivityIndicator'); -const DeprecatedViewPropTypes = require('DeprecatedViewPropTypes'); -const DeprecatedEdgeInsetsPropType = require('DeprecatedEdgeInsetsPropType'); -const Linking = require('Linking'); -const PropTypes = require('prop-types'); -const React = require('React'); -const ReactNative = require('ReactNative'); -const StyleSheet = require('StyleSheet'); -const Text = require('Text'); -const UIManager = require('UIManager'); -const View = require('View'); -const WebViewShared = require('WebViewShared'); - -const deprecatedPropType = require('deprecatedPropType'); -const invariant = require('invariant'); -const keyMirror = require('fbjs/lib/keyMirror'); -const processDecelerationRate = require('processDecelerationRate'); -const requireNativeComponent = require('requireNativeComponent'); -const resolveAssetSource = require('resolveAssetSource'); - -const RCTWebViewManager = require('NativeModules').WebViewManager; -const RCTWKWebViewManager = require('NativeModules').WKWebViewManager; - -const BGWASH = 'rgba(255,255,255,0.8)'; -const RCT_WEBVIEW_REF = 'webview'; - -const WebViewState = keyMirror({ - IDLE: null, - LOADING: null, - ERROR: null, -}); - -const NavigationType = keyMirror({ - click: true, - formsubmit: true, - backforward: true, - reload: true, - formresubmit: true, - other: true, -}); - -const JSNavigationScheme = 'react-js-navigation'; - -type ErrorEvent = { - domain: any, - code: any, - description: any, -}; - -type Event = Object; - -const DataDetectorTypes = [ - 'phoneNumber', - 'link', - 'address', - 'calendarEvent', - 'trackingNumber', - 'flightNumber', - 'lookupSuggestion', - 'none', - 'all', -]; - -const defaultRenderLoading = () => ( - - - -); -const defaultRenderError = (errorDomain, errorCode, errorDesc) => ( - - Error loading page - {'Domain: ' + errorDomain} - {'Error Code: ' + errorCode} - {'Description: ' + errorDesc} - -); - -type Props = any; -type State = any; - -/** - * `WebView` renders web content in a native view. - * - *``` - * import React, { Component } from 'react'; - * import { WebView } from 'react-native'; - * - * class MyWeb extends Component { - * render() { - * return ( - * - * ); - * } - * } - *``` - * - * You can use this component to navigate back and forth in the web view's - * history and configure various properties for the web content. - */ -class WebView extends React.Component { - static JSNavigationScheme = JSNavigationScheme; - static NavigationType = NavigationType; - static propTypes = { - ...DeprecatedViewPropTypes, - - html: deprecatedPropType( - PropTypes.string, - 'Use the `source` prop instead.', - ), - - url: deprecatedPropType(PropTypes.string, 'Use the `source` prop instead.'), - - /** - * Loads static html or a uri (with optional headers) in the WebView. - */ - source: PropTypes.oneOfType([ - PropTypes.shape({ - /* - * The URI to load in the `WebView`. Can be a local or remote file. - */ - uri: PropTypes.string, - /* - * The HTTP Method to use. Defaults to GET if not specified. - * NOTE: On Android, only GET and POST are supported. - */ - method: PropTypes.string, - /* - * Additional HTTP headers to send with the request. - * NOTE: On Android, this can only be used with GET requests. - */ - headers: PropTypes.object, - /* - * The HTTP body to send with the request. This must be a valid - * UTF-8 string, and will be sent exactly as specified, with no - * additional encoding (e.g. URL-escaping or base64) applied. - * NOTE: On Android, this can only be used with POST requests. - */ - body: PropTypes.string, - }), - PropTypes.shape({ - /* - * A static HTML page to display in the WebView. - */ - html: PropTypes.string, - /* - * The base URL to be used for any relative links in the HTML. - */ - baseUrl: PropTypes.string, - }), - /* - * Used internally by packager. - */ - PropTypes.number, - ]), - - /** - * If true, use WKWebView instead of UIWebView. - * @platform ios - */ - useWebKit: PropTypes.bool, - - /** - * Function that returns a view to show if there's an error. - */ - renderError: PropTypes.func, // view to show if there's an error - /** - * Function that returns a loading indicator. - */ - renderLoading: PropTypes.func, - /** - * Function that is invoked when the `WebView` has finished loading. - */ - onLoad: PropTypes.func, - /** - * Function that is invoked when the `WebView` load succeeds or fails. - */ - onLoadEnd: PropTypes.func, - /** - * Function that is invoked when the `WebView` starts loading. - */ - onLoadStart: PropTypes.func, - /** - * Function that is invoked when the `WebView` load fails. - */ - onError: PropTypes.func, - /** - * Boolean value that determines whether the web view bounces - * when it reaches the edge of the content. The default value is `true`. - * @platform ios - */ - bounces: PropTypes.bool, - /** - * A floating-point number that determines how quickly the scroll view - * decelerates after the user lifts their finger. You may also use the - * string shortcuts `"normal"` and `"fast"` which match the underlying iOS - * settings for `UIScrollViewDecelerationRateNormal` and - * `UIScrollViewDecelerationRateFast` respectively: - * - * - normal: 0.998 - * - fast: 0.99 (the default for iOS web view) - * @platform ios - */ - decelerationRate: PropTypes.oneOfType([ - PropTypes.oneOf(['fast', 'normal']), - PropTypes.number, - ]), - /** - * Boolean value that determines whether scrolling is enabled in the - * `WebView`. The default value is `true`. - * @platform ios - */ - scrollEnabled: PropTypes.bool, - /** - * Controls whether to adjust the content inset for web views that are - * placed behind a navigation bar, tab bar, or toolbar. The default value - * is `true`. - */ - automaticallyAdjustContentInsets: PropTypes.bool, - /** - * The amount by which the web view content is inset from the edges of - * the scroll view. Defaults to {top: 0, left: 0, bottom: 0, right: 0}. - * @platform ios - */ - contentInset: DeprecatedEdgeInsetsPropType, - /** - * Function that is invoked when the `WebView` loading starts or ends. - */ - onNavigationStateChange: PropTypes.func, - /** - * A function that is invoked when the webview calls `window.postMessage`. - * Setting this property will inject a `postMessage` global into your - * webview, but will still call pre-existing values of `postMessage`. - * - * `window.postMessage` accepts one argument, `data`, which will be - * available on the event object, `event.nativeEvent.data`. `data` - * must be a string. - */ - onMessage: PropTypes.func, - /** - * Boolean value that forces the `WebView` to show the loading view - * on the first load. - */ - startInLoadingState: PropTypes.bool, - /** - * The style to apply to the `WebView`. - */ - style: DeprecatedViewPropTypes.style, - - /** - * Determines the types of data converted to clickable URLs in the web view's content. - * By default only phone numbers are detected. - * - * You can provide one type or an array of many types. - * - * Possible values for `dataDetectorTypes` are: - * - * - `'phoneNumber'` - * - `'link'` - * - `'address'` - * - `'calendarEvent'` - * - `'none'` - * - `'all'` - * - * With the new WebKit implementation, we have three new values: - * - `'trackingNumber'`, - * - `'flightNumber'`, - * - `'lookupSuggestion'`, - * - * @platform ios - */ - dataDetectorTypes: PropTypes.oneOfType([ - PropTypes.oneOf(DataDetectorTypes), - PropTypes.arrayOf(PropTypes.oneOf(DataDetectorTypes)), - ]), - - /** - * Used on Android only to disable Hardware Acceleration if needed - * Hardware acceleration can not be enabled at view level but it can be - * disabled see: - * https://developer.android.com/guide/topics/graphics/hardware-accel - * - * @platform android - */ - hardwareAccelerationEnabledExperimental: PropTypes.bool, - - /** - * Boolean value to enable JavaScript in the `WebView`. Used on Android only - * as JavaScript is enabled by default on iOS. The default value is `true`. - * @platform android - */ - javaScriptEnabled: PropTypes.bool, - - /** - * Boolean value to enable third party cookies in the `WebView`. Used on - * Android Lollipop and above only as third party cookies are enabled by - * default on Android Kitkat and below and on iOS. The default value is `true`. - * @platform android - */ - thirdPartyCookiesEnabled: PropTypes.bool, - - /** - * Boolean value to control whether DOM Storage is enabled. Used only in - * Android. - * @platform android - */ - domStorageEnabled: PropTypes.bool, - - /** - * Set this to provide JavaScript that will be injected into the web page - * when the view loads. - */ - injectedJavaScript: PropTypes.string, - - /** - * Sets the user-agent for the `WebView`. - * @platform android - */ - userAgent: PropTypes.string, - - /** - * Boolean that controls whether the web content is scaled to fit - * the view and enables the user to change the scale. The default value - * is `true`. - * - * On iOS, when `useWebKit=true`, this prop will not work. - */ - scalesPageToFit: PropTypes.bool, - - /** - * Function that allows custom handling of any web view requests. Return - * `true` from the function to continue loading the request and `false` - * to stop loading. - * @platform ios - */ - onShouldStartLoadWithRequest: PropTypes.func, - - /** - * Boolean that determines whether HTML5 videos play inline or use the - * native full-screen controller. The default value is `false`. - * - * **NOTE** : In order for video to play inline, not only does this - * property need to be set to `true`, but the video element in the HTML - * document must also include the `webkit-playsinline` attribute. - * @platform ios - */ - allowsInlineMediaPlayback: PropTypes.bool, - - /** - * Boolean that determines whether HTML5 audio and video requires the user - * to tap them before they start playing. The default value is `true`. - */ - mediaPlaybackRequiresUserAction: PropTypes.bool, - - /** - * List of origin strings to allow being navigated to. The strings allow - * wildcards and get matched against *just* the origin (not the full URL). - * If the user taps to navigate to a new page but the new page is not in - * this whitelist, we will open the URL in Safari. - * The default whitelisted origins are "http://*" and "https://*". - */ - originWhitelist: PropTypes.arrayOf(PropTypes.string), - - /** - * Function that accepts a string that will be passed to the WebView and - * executed immediately as JavaScript. - */ - injectJavaScript: PropTypes.func, - - /** - * Specifies the mixed content mode. i.e WebView will allow a secure origin to load content from any other origin. - * - * Possible values for `mixedContentMode` are: - * - * - `'never'` (default) - WebView will not allow a secure origin to load content from an insecure origin. - * - `'always'` - WebView will allow a secure origin to load content from any other origin, even if that origin is insecure. - * - `'compatibility'` - WebView will attempt to be compatible with the approach of a modern web browser with regard to mixed content. - * @platform android - */ - mixedContentMode: PropTypes.oneOf(['never', 'always', 'compatibility']), - - /** - * Override the native component used to render the WebView. Enables a custom native - * WebView which uses the same JavaScript as the original WebView. - */ - nativeConfig: PropTypes.shape({ - /* - * The native component used to render the WebView. - */ - component: PropTypes.any, - /* - * Set props directly on the native component WebView. Enables custom props which the - * original WebView doesn't pass through. - */ - props: PropTypes.object, - /* - * Set the ViewManager to use for communication with the native side. - * @platform ios - */ - viewManager: PropTypes.object, - }), - }; - - static defaultProps = { - originWhitelist: WebViewShared.defaultOriginWhitelist, - }; - - state = { - viewState: WebViewState.IDLE, - lastErrorEvent: (null: ?ErrorEvent), - startInLoadingState: true, - }; - - UNSAFE_componentWillMount() { - if (this.props.startInLoadingState) { - this.setState({viewState: WebViewState.LOADING}); - } - - if ( - this.props.useWebKit === true && - this.props.scalesPageToFit !== undefined - ) { - console.warn( - 'The scalesPageToFit property is not supported when useWebKit = true', - ); - } - } - - render() { - let otherView = null; - - let scalesPageToFit; - - if (this.props.useWebKit) { - ({scalesPageToFit} = this.props); - } else { - ({scalesPageToFit = true} = this.props); - } - - if (this.state.viewState === WebViewState.LOADING) { - otherView = (this.props.renderLoading || defaultRenderLoading)(); - } else if (this.state.viewState === WebViewState.ERROR) { - const errorEvent = this.state.lastErrorEvent; - invariant(errorEvent != null, 'lastErrorEvent expected to be non-null'); - otherView = (this.props.renderError || defaultRenderError)( - errorEvent.domain, - errorEvent.code, - errorEvent.description, - ); - } else if (this.state.viewState !== WebViewState.IDLE) { - console.error( - 'RCTWebView invalid state encountered: ' + this.state.loading, - ); - } - - const webViewStyles = [styles.container, styles.webView, this.props.style]; - if ( - this.state.viewState === WebViewState.LOADING || - this.state.viewState === WebViewState.ERROR - ) { - // if we're in either LOADING or ERROR states, don't show the webView - webViewStyles.push(styles.hidden); - } - - const nativeConfig = this.props.nativeConfig || {}; - - let viewManager = nativeConfig.viewManager; - - if (this.props.useWebKit) { - viewManager = viewManager || RCTWKWebViewManager; - } else { - viewManager = viewManager || RCTWebViewManager; - } - - const compiledWhitelist = [ - 'about:blank', - ...(this.props.originWhitelist || []), - ].map(WebViewShared.originWhitelistToRegex); - const onShouldStartLoadWithRequest = (event: Event) => { - let shouldStart = true; - const {url} = event.nativeEvent; - const origin = WebViewShared.extractOrigin(url); - const passesWhitelist = compiledWhitelist.some( - x => origin && new RegExp(x).test(origin), - ); - shouldStart = shouldStart && passesWhitelist; - if (!passesWhitelist) { - Linking.openURL(url); - } - if (this.props.onShouldStartLoadWithRequest) { - shouldStart = - shouldStart && - this.props.onShouldStartLoadWithRequest(event.nativeEvent); - } - viewManager.startLoadWithResult( - !!shouldStart, - event.nativeEvent.lockIdentifier, - ); - }; - - const decelerationRate = processDecelerationRate( - this.props.decelerationRate, - ); - - const source = this.props.source || {}; - if (this.props.html) { - source.html = this.props.html; - } else if (this.props.url) { - source.uri = this.props.url; - } - - const messagingEnabled = typeof this.props.onMessage === 'function'; - - let NativeWebView = nativeConfig.component; - - if (this.props.useWebKit) { - NativeWebView = NativeWebView || RCTWKWebView; - } else { - NativeWebView = NativeWebView || RCTWebView; - } - - const webView = ( - - ); - - return ( - - {webView} - {otherView} - - ); - } - - _getCommands() { - if (!this.props.useWebKit) { - return UIManager.getViewManagerConfig('RCTWebView').Commands; - } - - return UIManager.getViewManagerConfig('RCTWKWebView').Commands; - } - - /** - * Go forward one page in the web view's history. - */ - goForward = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - this._getCommands().goForward, - null, - ); - }; - - /** - * Go back one page in the web view's history. - */ - goBack = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - this._getCommands().goBack, - null, - ); - }; - - /** - * Reloads the current page. - */ - reload = () => { - this.setState({viewState: WebViewState.LOADING}); - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - this._getCommands().reload, - null, - ); - }; - - /** - * Stop loading the current page. - */ - stopLoading = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - this._getCommands().stopLoading, - null, - ); - }; - - /** - * Posts a message to the web view, which will emit a `message` event. - * Accepts one argument, `data`, which must be a string. - * - * In your webview, you'll need to something like the following. - * - * ```js - * document.addEventListener('message', e => { document.title = e.data; }); - * ``` - */ - postMessage = (data: string) => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - this._getCommands().postMessage, - [String(data)], - ); - }; - - /** - * Injects a javascript string into the referenced WebView. Deliberately does not - * return a response because using eval() to return a response breaks this method - * on pages with a Content Security Policy that disallows eval(). If you need that - * functionality, look into postMessage/onMessage. - */ - injectJavaScript = (data: string) => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - this._getCommands().injectJavaScript, - [data], - ); - }; - - /** - * We return an event with a bunch of fields including: - * url, title, loading, canGoBack, canGoForward - */ - _updateNavigationState = (event: Event) => { - if (this.props.onNavigationStateChange) { - this.props.onNavigationStateChange(event.nativeEvent); - } - }; - - /** - * Returns the native `WebView` node. - */ - getWebViewHandle = (): any => { - return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]); - }; - - _onLoadingStart = (event: Event) => { - const onLoadStart = this.props.onLoadStart; - onLoadStart && onLoadStart(event); - this._updateNavigationState(event); - }; - - _onLoadingError = (event: Event) => { - event.persist(); // persist this event because we need to store it - const {onError, onLoadEnd} = this.props; - onError && onError(event); - onLoadEnd && onLoadEnd(event); - console.warn('Encountered an error loading page', event.nativeEvent); - - this.setState({ - lastErrorEvent: event.nativeEvent, - viewState: WebViewState.ERROR, - }); - }; - - _onLoadingFinish = (event: Event) => { - const {onLoad, onLoadEnd} = this.props; - onLoad && onLoad(event); - onLoadEnd && onLoadEnd(event); - this.setState({ - viewState: WebViewState.IDLE, - }); - this._updateNavigationState(event); - }; - - _onMessage = (event: Event) => { - const {onMessage} = this.props; - onMessage && onMessage(event); - }; - - componentDidUpdate(prevProps: Props) { - if (!(prevProps.useWebKit && this.props.useWebKit)) { - return; - } - - this._showRedboxOnPropChanges(prevProps, 'allowsInlineMediaPlayback'); - this._showRedboxOnPropChanges(prevProps, 'mediaPlaybackRequiresUserAction'); - this._showRedboxOnPropChanges(prevProps, 'dataDetectorTypes'); - - if (this.props.scalesPageToFit !== undefined) { - console.warn( - 'The scalesPageToFit property is not supported when useWebKit = true', - ); - } - } - - _showRedboxOnPropChanges(prevProps, propName: string) { - if (this.props[propName] !== prevProps[propName]) { - console.error( - `Changes to property ${propName} do nothing after the initial render.`, - ); - } - } -} - -const RCTWebView = requireNativeComponent('RCTWebView'); -const RCTWKWebView = requireNativeComponent('RCTWKWebView'); - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - errorContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: BGWASH, - }, - errorText: { - fontSize: 14, - textAlign: 'center', - marginBottom: 2, - }, - errorTextTitle: { - fontSize: 15, - fontWeight: '500', - marginBottom: 10, - }, - hidden: { - height: 0, - flex: 0, // disable 'flex:1' when hiding a View - }, - loadingView: { - backgroundColor: BGWASH, - flex: 1, - justifyContent: 'center', - alignItems: 'center', - height: 100, - }, - webView: { - backgroundColor: '#ffffff', - }, -}); - -module.exports = WebView; diff --git a/Libraries/Components/WebView/WebViewShared.js b/Libraries/Components/WebView/WebViewShared.js deleted file mode 100644 index af2f7e9a80baaa..00000000000000 --- a/Libraries/Components/WebView/WebViewShared.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 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. - * - * @format - * @flow - */ - -'use strict'; - -const escapeStringRegexp = require('escape-string-regexp'); - -const WebViewShared = { - defaultOriginWhitelist: ['http://*', 'https://*'], - extractOrigin: (url: string): ?string => { - const result = /^[A-Za-z0-9]+:(\/\/)?[^/]*/.exec(url); - return result === null ? null : result[0]; - }, - originWhitelistToRegex: (originWhitelist: string): string => { - return escapeStringRegexp(originWhitelist).replace(/\\\*/g, '.*'); - }, -}; - -module.exports = WebViewShared; diff --git a/Libraries/Components/WebView/__tests__/WebViewShared-test.js b/Libraries/Components/WebView/__tests__/WebViewShared-test.js deleted file mode 100644 index 2a6dee2e2758de..00000000000000 --- a/Libraries/Components/WebView/__tests__/WebViewShared-test.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * 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. - * - * @format - * @emails oncall+react_native - */ - -'use strict'; - -const WebViewShared = require('WebViewShared'); - -describe('WebViewShared', () => { - it('extracts the origin correctly', () => { - expect(WebViewShared.extractOrigin('http://facebook.com')).toBe( - 'http://facebook.com', - ); - expect(WebViewShared.extractOrigin('https://facebook.com')).toBe( - 'https://facebook.com', - ); - expect(WebViewShared.extractOrigin('http://facebook.com:8081')).toBe( - 'http://facebook.com:8081', - ); - expect(WebViewShared.extractOrigin('ftp://facebook.com')).toBe( - 'ftp://facebook.com', - ); - expect(WebViewShared.extractOrigin('myweirdscheme://')).toBe( - 'myweirdscheme://', - ); - expect(WebViewShared.extractOrigin('http://facebook.com/')).toBe( - 'http://facebook.com', - ); - expect(WebViewShared.extractOrigin('http://facebook.com/longerurl')).toBe( - 'http://facebook.com', - ); - expect( - WebViewShared.extractOrigin('http://facebook.com/http://facebook.com'), - ).toBe('http://facebook.com'); - expect( - WebViewShared.extractOrigin('http://facebook.com//http://facebook.com'), - ).toBe('http://facebook.com'); - expect( - WebViewShared.extractOrigin('http://facebook.com//http://facebook.com//'), - ).toBe('http://facebook.com'); - expect(WebViewShared.extractOrigin('about:blank')).toBe('about:blank'); - }); - - it('rejects bad urls', () => { - expect(WebViewShared.extractOrigin('a/b')).toBeNull(); - expect(WebViewShared.extractOrigin('a//b')).toBeNull(); - }); - - it('creates a whitelist regex correctly', () => { - expect(WebViewShared.originWhitelistToRegex('http://*')).toBe('http://.*'); - expect(WebViewShared.originWhitelistToRegex('*')).toBe('.*'); - expect(WebViewShared.originWhitelistToRegex('*//test')).toBe('.*//test'); - expect(WebViewShared.originWhitelistToRegex('*/*')).toBe('.*/.*'); - expect(WebViewShared.originWhitelistToRegex('*.com')).toBe('.*\\.com'); - }); -}); diff --git a/Libraries/Core/InitializeCore.js b/Libraries/Core/InitializeCore.js index 37660a44dd0634..ad11b26b52e9a4 100644 --- a/Libraries/Core/InitializeCore.js +++ b/Libraries/Core/InitializeCore.js @@ -37,7 +37,7 @@ require('setUpRegeneratorRuntime'); require('setUpTimers'); require('setUpXHR'); require('setUpAlert'); -require('setUpGeolocation'); +require('setUpNavigator'); require('setUpBatchedBridge'); require('setUpSegmentFetcher'); if (__DEV__) { diff --git a/Libraries/Core/__tests__/MapAndSetPolyfills-test.js b/Libraries/Core/__tests__/MapAndSetPolyfills-test.js index 79a73a968789cc..cfda89e12d9a48 100644 --- a/Libraries/Core/__tests__/MapAndSetPolyfills-test.js +++ b/Libraries/Core/__tests__/MapAndSetPolyfills-test.js @@ -14,8 +14,6 @@ const {freeze, seal, preventExtensions} = Object; function setup() { jest.setMock('../../vendor/core/_shouldPolyfillES6Collection', () => true); - jest.unmock('_wrapObjectFreezeAndFriends'); - require('_wrapObjectFreezeAndFriends'); } function cleanup() { diff --git a/Libraries/Core/polyfillES6Collections.js b/Libraries/Core/polyfillES6Collections.js index afbb24ab23ba4e..ca1ee798c73f4c 100644 --- a/Libraries/Core/polyfillES6Collections.js +++ b/Libraries/Core/polyfillES6Collections.js @@ -18,10 +18,8 @@ const {polyfillGlobal} = require('PolyfillFunctions'); */ const _shouldPolyfillCollection = require('_shouldPolyfillES6Collection'); if (_shouldPolyfillCollection('Map')) { - require('_wrapObjectFreezeAndFriends'); polyfillGlobal('Map', () => require('Map')); } if (_shouldPolyfillCollection('Set')) { - require('_wrapObjectFreezeAndFriends'); polyfillGlobal('Set', () => require('Set')); } diff --git a/Libraries/Core/setUpGeolocation.js b/Libraries/Core/setUpNavigator.js similarity index 74% rename from Libraries/Core/setUpGeolocation.js rename to Libraries/Core/setUpNavigator.js index fd68182bce6bfb..796d0b411877d1 100644 --- a/Libraries/Core/setUpGeolocation.js +++ b/Libraries/Core/setUpNavigator.js @@ -11,10 +11,6 @@ const {polyfillObjectProperty} = require('PolyfillFunctions'); -/** - * Set up Geolocation. - * You can use this module directly, or just require InitializeCore. - */ let navigator = global.navigator; if (navigator === undefined) { global.navigator = navigator = {}; @@ -22,4 +18,3 @@ if (navigator === undefined) { // see https://github.com/facebook/react-native/issues/10881 polyfillObjectProperty(navigator, 'product', () => 'ReactNative'); -polyfillObjectProperty(navigator, 'geolocation', () => require('Geolocation')); diff --git a/Libraries/Geolocation/Geolocation.js b/Libraries/Geolocation/Geolocation.js deleted file mode 100644 index 9158d62d1c7f52..00000000000000 --- a/Libraries/Geolocation/Geolocation.js +++ /dev/null @@ -1,182 +0,0 @@ -/** - * 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. - * - * @format - * @flow - */ - -'use strict'; - -const NativeEventEmitter = require('NativeEventEmitter'); -const RCTLocationObserver = require('NativeModules').LocationObserver; - -const invariant = require('invariant'); -const logError = require('logError'); -const warning = require('fbjs/lib/warning'); - -const LocationEventEmitter = new NativeEventEmitter(RCTLocationObserver); - -const Platform = require('Platform'); -const PermissionsAndroid = require('PermissionsAndroid'); - -let subscriptions = []; -let updatesEnabled = false; - -type GeoConfiguration = { - skipPermissionRequests: boolean, -}; - -type GeoOptions = { - timeout?: number, - maximumAge?: number, - enableHighAccuracy?: boolean, - distanceFilter: number, - useSignificantChanges?: boolean, -}; - -/** - * The Geolocation API extends the web spec: - * https://developer.mozilla.org/en-US/docs/Web/API/Geolocation - * - * See https://facebook.github.io/react-native/docs/geolocation.html - */ -const Geolocation = { - /* - * Sets configuration options that will be used in all location requests. - * - * See https://facebook.github.io/react-native/docs/geolocation.html#setrnconfiguration - * - */ - setRNConfiguration: function(config: GeoConfiguration) { - if (RCTLocationObserver.setConfiguration) { - RCTLocationObserver.setConfiguration(config); - } - }, - - /* - * Requests Location permissions based on the key configured on pList. - * - * See https://facebook.github.io/react-native/docs/geolocation.html#requestauthorization - */ - requestAuthorization: function() { - RCTLocationObserver.requestAuthorization(); - }, - - /* - * Invokes the success callback once with the latest location info. - * - * See https://facebook.github.io/react-native/docs/geolocation.html#getcurrentposition - */ - getCurrentPosition: async function( - geo_success: Function, - geo_error?: Function, - geo_options?: GeoOptions, - ) { - invariant( - typeof geo_success === 'function', - 'Must provide a valid geo_success callback.', - ); - let hasPermission = true; - // Supports Android's new permission model. For Android older devices, - // it's always on. - if (Platform.OS === 'android' && Platform.Version >= 23) { - hasPermission = await PermissionsAndroid.check( - PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, - ); - if (!hasPermission) { - const status = await PermissionsAndroid.request( - PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, - ); - hasPermission = status === PermissionsAndroid.RESULTS.GRANTED; - } - } - if (hasPermission) { - RCTLocationObserver.getCurrentPosition( - geo_options || {}, - geo_success, - geo_error || logError, - ); - } - }, - - /* - * Invokes the success callback whenever the location changes. - * - * See https://facebook.github.io/react-native/docs/geolocation.html#watchposition - */ - watchPosition: function( - success: Function, - error?: Function, - options?: GeoOptions, - ): number { - if (!updatesEnabled) { - RCTLocationObserver.startObserving(options || {}); - updatesEnabled = true; - } - const watchID = subscriptions.length; - subscriptions.push([ - LocationEventEmitter.addListener('geolocationDidChange', success), - error - ? LocationEventEmitter.addListener('geolocationError', error) - : null, - ]); - return watchID; - }, - - /* - * Unsubscribes the watcher with the given watchID. - * - * See https://facebook.github.io/react-native/docs/geolocation.html#clearwatch - */ - clearWatch: function(watchID: number) { - const sub = subscriptions[watchID]; - if (!sub) { - // Silently exit when the watchID is invalid or already cleared - // This is consistent with timers - return; - } - - sub[0].remove(); - // array element refinements not yet enabled in Flow - const sub1 = sub[1]; - sub1 && sub1.remove(); - subscriptions[watchID] = undefined; - let noWatchers = true; - for (let ii = 0; ii < subscriptions.length; ii++) { - if (subscriptions[ii]) { - noWatchers = false; // still valid subscriptions - } - } - if (noWatchers) { - Geolocation.stopObserving(); - } - }, - - /* - * Stops observing for device location changes and removes all registered listeners. - * - * See https://facebook.github.io/react-native/docs/geolocation.html#stopobserving - */ - stopObserving: function() { - if (updatesEnabled) { - RCTLocationObserver.stopObserving(); - updatesEnabled = false; - for (let ii = 0; ii < subscriptions.length; ii++) { - const sub = subscriptions[ii]; - if (sub) { - warning(false, 'Called stopObserving with existing subscriptions.'); - sub[0].remove(); - // array element refinements not yet enabled in Flow - const sub1 = sub[1]; - sub1 && sub1.remove(); - } - } - subscriptions = []; - } - }, -}; - -module.exports = Geolocation; diff --git a/Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj b/Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj deleted file mode 100644 index c0754118087ab4..00000000000000 --- a/Libraries/Geolocation/RCTGeolocation.xcodeproj/project.pbxproj +++ /dev/null @@ -1,257 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 134814061AA4E45400B7C361 /* RCTLocationObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 134814051AA4E45400B7C361 /* RCTLocationObserver.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 134814041AA4E45400B7C361 /* RCTLocationObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTLocationObserver.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - 134814051AA4E45400B7C361 /* RCTLocationObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLocationObserver.m; sourceTree = ""; }; - 134814201AA4EA6300B7C361 /* libRCTGeolocation.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTGeolocation.a; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXGroup section */ - 134814211AA4EA7D00B7C361 /* Products */ = { - isa = PBXGroup; - children = ( - 134814201AA4EA6300B7C361 /* libRCTGeolocation.a */, - ); - name = Products; - sourceTree = ""; - }; - 58B511D21A9E6C8500147676 = { - isa = PBXGroup; - children = ( - 134814041AA4E45400B7C361 /* RCTLocationObserver.h */, - 134814051AA4E45400B7C361 /* RCTLocationObserver.m */, - 134814211AA4EA7D00B7C361 /* Products */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - usesTabs = 0; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 58B511DA1A9E6C8500147676 /* RCTGeolocation */ = { - isa = PBXNativeTarget; - buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTGeolocation" */; - buildPhases = ( - 58B511D71A9E6C8500147676 /* Sources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = RCTGeolocation; - productName = RCTDataManager; - productReference = 134814201AA4EA6300B7C361 /* libRCTGeolocation.a */; - productType = "com.apple.product-type.library.static"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 58B511D31A9E6C8500147676 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0940; - ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 58B511DA1A9E6C8500147676 = { - CreatedOnToolsVersion = 6.1.1; - }; - }; - }; - buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTGeolocation" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 58B511D21A9E6C8500147676; - productRefGroup = 58B511D21A9E6C8500147676; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 58B511DA1A9E6C8500147676 /* RCTGeolocation */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 58B511D71A9E6C8500147676 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 134814061AA4E45400B7C361 /* RCTLocationObserver.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 58B511ED1A9E6C8500147676 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - WARNING_CFLAGS = ( - "-Werror", - "-Wall", - ); - }; - name = Debug; - }; - 58B511EE1A9E6C8500147676 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - VALIDATE_PRODUCT = YES; - WARNING_CFLAGS = ( - "-Werror", - "-Wall", - ); - }; - name = Release; - }; - 58B511F01A9E6C8500147676 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_STATIC_ANALYZER_MODE = deep; - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - RUN_CLANG_STATIC_ANALYZER = YES; - }; - name = Debug; - }; - 58B511F11A9E6C8500147676 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_STATIC_ANALYZER_MODE = deep; - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTGeolocation" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 58B511ED1A9E6C8500147676 /* Debug */, - 58B511EE1A9E6C8500147676 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTGeolocation" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 58B511F01A9E6C8500147676 /* Debug */, - 58B511F11A9E6C8500147676 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 58B511D31A9E6C8500147676 /* Project object */; -} diff --git a/Libraries/Geolocation/RCTLocationObserver.h b/Libraries/Geolocation/RCTLocationObserver.h deleted file mode 100644 index f3f9e19b8f2902..00000000000000 --- a/Libraries/Geolocation/RCTLocationObserver.h +++ /dev/null @@ -1,12 +0,0 @@ -/** - * 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 - -@interface RCTLocationObserver : RCTEventEmitter - -@end diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m deleted file mode 100644 index f3b2209a39afc9..00000000000000 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ /dev/null @@ -1,403 +0,0 @@ -/** - * 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 "RCTLocationObserver.h" - -#import -#import -#import - -#import -#import -#import -#import -#import - -typedef NS_ENUM(NSInteger, RCTPositionErrorCode) { - RCTPositionErrorDenied = 1, - RCTPositionErrorUnavailable, - RCTPositionErrorTimeout, -}; - -#define RCT_DEFAULT_LOCATION_ACCURACY kCLLocationAccuracyHundredMeters - -typedef struct { - BOOL skipPermissionRequests; -} RCTLocationConfiguration; - -typedef struct { - double timeout; - double maximumAge; - double accuracy; - double distanceFilter; - BOOL useSignificantChanges; -} RCTLocationOptions; - -@implementation RCTConvert (RCTLocationOptions) - -+ (RCTLocationConfiguration)RCTLocationConfiguration:(id)json -{ - NSDictionary *options = [RCTConvert NSDictionary:json]; - - return (RCTLocationConfiguration) { - .skipPermissionRequests = [RCTConvert BOOL:options[@"skipPermissionRequests"]] - }; -} - -+ (RCTLocationOptions)RCTLocationOptions:(id)json -{ - NSDictionary *options = [RCTConvert NSDictionary:json]; - - double distanceFilter = options[@"distanceFilter"] == NULL ? RCT_DEFAULT_LOCATION_ACCURACY - : [RCTConvert double:options[@"distanceFilter"]] ?: kCLDistanceFilterNone; - - return (RCTLocationOptions){ - .timeout = [RCTConvert NSTimeInterval:options[@"timeout"]] ?: INFINITY, - .maximumAge = [RCTConvert NSTimeInterval:options[@"maximumAge"]] ?: INFINITY, - .accuracy = [RCTConvert BOOL:options[@"enableHighAccuracy"]] ? kCLLocationAccuracyBest : RCT_DEFAULT_LOCATION_ACCURACY, - .distanceFilter = distanceFilter, - .useSignificantChanges = [RCTConvert BOOL:options[@"useSignificantChanges"]] ?: NO, - }; -} - -@end - -static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /* nil for default */) -{ - if (!msg) { - switch (code) { - case RCTPositionErrorDenied: - msg = @"User denied access to location services."; - break; - case RCTPositionErrorUnavailable: - msg = @"Unable to retrieve location."; - break; - case RCTPositionErrorTimeout: - msg = @"The location request timed out."; - break; - } - } - - return @{ - @"code": @(code), - @"message": msg, - @"PERMISSION_DENIED": @(RCTPositionErrorDenied), - @"POSITION_UNAVAILABLE": @(RCTPositionErrorUnavailable), - @"TIMEOUT": @(RCTPositionErrorTimeout) - }; -} - -@interface RCTLocationRequest : NSObject - -@property (nonatomic, copy) RCTResponseSenderBlock successBlock; -@property (nonatomic, copy) RCTResponseSenderBlock errorBlock; -@property (nonatomic, assign) RCTLocationOptions options; -@property (nonatomic, strong) NSTimer *timeoutTimer; - -@end - -@implementation RCTLocationRequest - -- (void)dealloc -{ - if (_timeoutTimer.valid) { - [_timeoutTimer invalidate]; - } -} - -@end - -@interface RCTLocationObserver () - -@end - -@implementation RCTLocationObserver -{ - CLLocationManager *_locationManager; - NSDictionary *_lastLocationEvent; - NSMutableArray *_pendingRequests; - BOOL _observingLocation; - BOOL _usingSignificantChanges; - RCTLocationConfiguration _locationConfiguration; - RCTLocationOptions _observerOptions; -} - -RCT_EXPORT_MODULE() - -#pragma mark - Lifecycle - -- (void)dealloc -{ - _usingSignificantChanges ? - [_locationManager stopMonitoringSignificantLocationChanges] : - [_locationManager stopUpdatingLocation]; - - _locationManager.delegate = nil; -} - -- (dispatch_queue_t)methodQueue -{ - return dispatch_get_main_queue(); -} - -- (NSArray *)supportedEvents -{ - return @[@"geolocationDidChange", @"geolocationError"]; -} - -#pragma mark - Private API - -- (void)_beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy - distanceFilter:(CLLocationDistance)distanceFilter - useSignificantChanges:(BOOL)useSignificantChanges -{ - if (!_locationConfiguration.skipPermissionRequests) { - [self requestAuthorization]; - } - - if (!_locationManager) { - _locationManager = [CLLocationManager new]; - _locationManager.delegate = self; - } - - _locationManager.distanceFilter = distanceFilter; - _locationManager.desiredAccuracy = desiredAccuracy; - _usingSignificantChanges = useSignificantChanges; - - // Start observing location - _usingSignificantChanges ? - [_locationManager startMonitoringSignificantLocationChanges] : - [_locationManager startUpdatingLocation]; -} - -- (void)_stopUpdatingIfIdle { - if (_pendingRequests.count == 0 && !_observingLocation) { - _usingSignificantChanges ? - [_locationManager stopMonitoringSignificantLocationChanges] : - [_locationManager stopUpdatingLocation]; - } -} - -#pragma mark - Static Helpers - -static BOOL locationEventValid(NSDictionary *event, RCTLocationOptions options) { - return [NSDate date].timeIntervalSince1970 - [RCTConvert NSTimeInterval:event[@"timestamp"]] < options.maximumAge && - [event[@"coords"][@"accuracy"] doubleValue] <= options.accuracy; -} - -static void checkLocationConfig() -{ -#if RCT_DEV - if (!([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] || - [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"] || - [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysAndWhenInUseUsageDescription"])) { - RCTLogError(@"Either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription or NSLocationAlwaysAndWhenInUseUsageDescription key must be present in Info.plist to use geolocation."); - } -#endif -} - -#pragma mark - Timeout handler - -- (void)timeout:(NSTimer *)timer -{ - RCTLocationRequest *request = timer.userInfo; - NSString *message = [NSString stringWithFormat: @"Unable to fetch location within %.1fs.", request.options.timeout]; - request.errorBlock(@[RCTPositionError(RCTPositionErrorTimeout, message)]); - [_pendingRequests removeObject:request]; - - // Stop updating if not observing and no pending requests - [self _stopUpdatingIfIdle]; -} - -#pragma mark - Public API - -RCT_EXPORT_METHOD(setConfiguration:(RCTLocationConfiguration)config) -{ - _locationConfiguration = config; -} - -RCT_EXPORT_METHOD(requestAuthorization) -{ - if (!_locationManager) { - _locationManager = [CLLocationManager new]; - _locationManager.delegate = self; - } - // On iOS 9+ we also need to enable background updates - NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"]; - if (backgroundModes && [backgroundModes containsObject:@"location"]) { - if ([_locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { - [_locationManager setAllowsBackgroundLocationUpdates:YES]; - } - } - // Request location access permission - if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"] && - [_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) { - [_locationManager requestAlwaysAuthorization]; - } else if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] && - [_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { - [_locationManager requestWhenInUseAuthorization]; - } -} - -RCT_EXPORT_METHOD(startObserving:(RCTLocationOptions)options) -{ - checkLocationConfig(); - - // Select best options - _observerOptions = options; - for (RCTLocationRequest *request in _pendingRequests) { - _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy); - } - - [self _beginLocationUpdatesWithDesiredAccuracy:_observerOptions.accuracy - distanceFilter:_observerOptions.distanceFilter - useSignificantChanges:_observerOptions.useSignificantChanges]; - _observingLocation = YES; -} - -RCT_EXPORT_METHOD(stopObserving) -{ - // Stop observing - _observingLocation = NO; - - // Stop updating if no pending requests - [self _stopUpdatingIfIdle]; - -} - -RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options - withSuccessCallback:(RCTResponseSenderBlock)successBlock - errorCallback:(RCTResponseSenderBlock)errorBlock) -{ - checkLocationConfig(); - - if (!successBlock) { - RCTLogError(@"%@.getCurrentPosition called with nil success parameter.", [self class]); - return; - } - - if (![CLLocationManager locationServicesEnabled]) { - if (errorBlock) { - errorBlock(@[ - RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.") - ]); - return; - } - } - - if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { - if (errorBlock) { - errorBlock(@[ - RCTPositionError(RCTPositionErrorDenied, nil) - ]); - return; - } - } - - // Check if previous recorded location exists and is good enough - if (_lastLocationEvent && locationEventValid(_lastLocationEvent, options)) { - - // Call success block with most recent known location - successBlock(@[_lastLocationEvent]); - return; - } - - // Create request - RCTLocationRequest *request = [RCTLocationRequest new]; - request.successBlock = successBlock; - request.errorBlock = errorBlock ?: ^(NSArray *args){}; - request.options = options; - request.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:options.timeout - target:self - selector:@selector(timeout:) - userInfo:request - repeats:NO]; - if (!_pendingRequests) { - _pendingRequests = [NSMutableArray new]; - } - [_pendingRequests addObject:request]; - - // Configure location manager and begin updating location - CLLocationAccuracy accuracy = options.accuracy; - if (_locationManager) { - accuracy = MIN(_locationManager.desiredAccuracy, accuracy); - } - [self _beginLocationUpdatesWithDesiredAccuracy:accuracy - distanceFilter:options.distanceFilter - useSignificantChanges:options.useSignificantChanges]; -} - -#pragma mark - CLLocationManagerDelegate - -- (void)locationManager:(CLLocationManager *)manager - didUpdateLocations:(NSArray *)locations -{ - // Create event - CLLocation *location = locations.lastObject; - _lastLocationEvent = @{ - @"coords": @{ - @"latitude": @(location.coordinate.latitude), - @"longitude": @(location.coordinate.longitude), - @"altitude": @(location.altitude), - @"accuracy": @(location.horizontalAccuracy), - @"altitudeAccuracy": @(location.verticalAccuracy), - @"heading": @(location.course), - @"speed": @(location.speed), - }, - @"timestamp": @([location.timestamp timeIntervalSince1970] * 1000) // in ms - }; - - // Send event - if (_observingLocation) { - [self sendEventWithName:@"geolocationDidChange" body:_lastLocationEvent]; - } - - // Fire off queued callbacks that pass maximumAge and accuracy filters - for (RCTLocationRequest *request in [_pendingRequests copy]) { - if (locationEventValid(_lastLocationEvent, request.options)) { - request.successBlock(@[_lastLocationEvent]); - [request.timeoutTimer invalidate]; - [_pendingRequests removeObject:request]; - } - } - - // Stop updating if not observing and no pending requests - [self _stopUpdatingIfIdle]; -} - -- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error -{ - // Check error type - NSDictionary *jsError = nil; - switch (error.code) { - case kCLErrorDenied: - jsError = RCTPositionError(RCTPositionErrorDenied, nil); - break; - case kCLErrorNetwork: - jsError = RCTPositionError(RCTPositionErrorUnavailable, @"Unable to retrieve location due to a network failure"); - break; - case kCLErrorLocationUnknown: - default: - jsError = RCTPositionError(RCTPositionErrorUnavailable, nil); - break; - } - - // Send event - if (_observingLocation) { - [self sendEventWithName:@"geolocationError" body:jsError]; - } - - // Fire all queued error callbacks - for (RCTLocationRequest *request in _pendingRequests) { - request.errorBlock(@[jsError]); - [request.timeoutTimer invalidate]; - } - [_pendingRequests removeAllObjects]; - -} - -@end diff --git a/Libraries/Geolocation/React-RCTGeolocation.podspec b/Libraries/Geolocation/React-RCTGeolocation.podspec deleted file mode 100644 index ebf10933a48f07..00000000000000 --- a/Libraries/Geolocation/React-RCTGeolocation.podspec +++ /dev/null @@ -1,35 +0,0 @@ -# coding: utf-8 -# 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. - -require "json" - -package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json"))) -version = package['version'] - -source = { :git => 'https://github.com/facebook/react-native.git' } -if version == '1000.0.0' - # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. - source[:commit] = `git rev-parse HEAD`.strip -else - source[:tag] = "v#{version}" -end - -Pod::Spec.new do |s| - s.name = "React-RCTGeolocation" - s.version = version - s.summary = "A geolocation API for React Native." - s.homepage = "http://facebook.github.io/react-native/" - s.documentation_url = "https://facebook.github.io/react-native/docs/geolocation" - s.license = package["license"] - s.author = "Facebook, Inc. and its affiliates" - s.platforms = { :ios => "9.0", :tvos => "9.2" } - s.source = source - s.source_files = "*.{h,m}" - s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs" - s.header_dir = "React" - - s.dependency "React-Core", version -end diff --git a/Libraries/Geolocation/__tests__/Geolocation-test.js b/Libraries/Geolocation/__tests__/Geolocation-test.js deleted file mode 100644 index ebf0daf08b7fd5..00000000000000 --- a/Libraries/Geolocation/__tests__/Geolocation-test.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * 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. - * - * @format - * @emails oncall+react_native - */ - -'use strict'; - -describe('Geolocation', () => { - let Geolocation; - const NativeModules = require('NativeModules'); - - beforeEach(() => { - jest.resetModules(); - Geolocation = jest.requireActual('Geolocation'); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should set the location observer configuration', () => { - Geolocation.setRNConfiguration({skipPermissionRequests: true}); - expect( - NativeModules.LocationObserver.setConfiguration.mock.calls.length, - ).toEqual(1); - }); - - it('should request authorization for location requests', () => { - Geolocation.requestAuthorization(); - expect( - NativeModules.LocationObserver.requestAuthorization.mock.calls.length, - ).toEqual(1); - }); - - it('should get the current position and pass it to the given callback', () => { - const callback = () => {}; - Geolocation.getCurrentPosition(callback); - expect( - NativeModules.LocationObserver.getCurrentPosition.mock.calls.length, - ).toEqual(1); - expect( - NativeModules.LocationObserver.getCurrentPosition.mock.calls[0][1], - ).toBe(callback); - }); - - it('should add a success listener to the geolocation', () => { - const watchID = Geolocation.watchPosition(() => {}); - expect(watchID).toEqual(0); - expect(NativeModules.LocationObserver.addListener.mock.calls[0][0]).toBe( - 'geolocationDidChange', - ); - }); - - it('should add an error listener to the geolocation', () => { - const watchID = Geolocation.watchPosition(() => {}, () => {}); - expect(watchID).toEqual(0); - expect(NativeModules.LocationObserver.addListener.mock.calls[1][0]).toBe( - 'geolocationError', - ); - }); - - it('should clear the listeners associated with a watchID', () => { - const watchID = Geolocation.watchPosition(() => {}, () => {}); - Geolocation.clearWatch(watchID); - expect(NativeModules.LocationObserver.stopObserving.mock.calls.length).toBe( - 1, - ); - }); - - it('should correctly assess if all listeners have been cleared', () => { - const watchID = Geolocation.watchPosition(() => {}, () => {}); - Geolocation.watchPosition(() => {}, () => {}); - Geolocation.clearWatch(watchID); - expect(NativeModules.LocationObserver.stopObserving.mock.calls.length).toBe( - 0, - ); - }); - - it('should not fail if the watchID one wants to clear does not exist', () => { - Geolocation.watchPosition(() => {}, () => {}); - Geolocation.clearWatch(42); - expect(NativeModules.LocationObserver.stopObserving.mock.calls.length).toBe( - 0, - ); - }); - - it('should stop observing and warn about removing existing subscriptions', () => { - const warningCallback = jest.fn(); - jest.mock('fbjs/lib/warning', () => warningCallback); - Geolocation.watchPosition(() => {}, () => {}); - Geolocation.stopObserving(); - expect(NativeModules.LocationObserver.stopObserving.mock.calls.length).toBe( - 1, - ); - expect(warningCallback.mock.calls.length).toBeGreaterThanOrEqual(1); - }); -}); diff --git a/Libraries/LayoutAnimation/LayoutAnimation.js b/Libraries/LayoutAnimation/LayoutAnimation.js index 0b84dd5fe1cf20..ef92d99cec6319 100644 --- a/Libraries/LayoutAnimation/LayoutAnimation.js +++ b/Libraries/LayoutAnimation/LayoutAnimation.js @@ -47,9 +47,7 @@ function configureNext( UIManager.configureNextLayoutAnimation( config, onAnimationDidEnd ?? function() {}, - function() { - /* unused */ - }, + function() {} /* unused onError */, ); } } diff --git a/Libraries/Lists/FlatList.js b/Libraries/Lists/FlatList.js index c00c0cef4c1b34..2c7280694ddcfa 100644 --- a/Libraries/Lists/FlatList.js +++ b/Libraries/Lists/FlatList.js @@ -63,7 +63,7 @@ type RequiredProps = { item: ItemT, index: number, separators: SeparatorsObj, - }) => ?React.Element, + }) => ?React.Node, /** * For simplicity, data is just a plain array. If you want to use something else, like an * immutable list, use the underlying `VirtualizedList` directly. @@ -598,7 +598,7 @@ class FlatList extends React.PureComponent, void> { }; } - _renderItem = (info: Object) => { + _renderItem = (info: Object): ?React.Node => { const {renderItem, numColumns, columnWrapperStyle} = this.props; if (numColumns > 1) { const {item, index} = info; @@ -618,7 +618,9 @@ class FlatList extends React.PureComponent, void> { index: index * numColumns + kk, separators: info.separators, }); - return element && React.cloneElement(element, {key: kk}); + return element != null ? ( + {element} + ) : null; })} ); diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index c3a72ab7a93157..b64058851f9287 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -76,7 +76,7 @@ type OptionalProps = { * unmounts react instances that are outside of the render window. You should only need to disable * this for debugging purposes. */ - disableVirtualization: boolean, + disableVirtualization?: ?boolean, /** * A marker property for telling the list to re-render (since it implements `PureComponent`). If * any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the @@ -705,7 +705,7 @@ class VirtualizedList extends React.PureComponent { }; _isVirtualizationDisabled(): boolean { - return this.props.disableVirtualization; + return this.props.disableVirtualization || false; } _isNestedWithSameOrientation(): boolean { @@ -873,17 +873,19 @@ class VirtualizedList extends React.PureComponent { )): any); cells.push( - - {React.cloneElement(element, { - onLayout: event => { - this._onLayoutEmpty(event); - if (element.props.onLayout) { - element.props.onLayout(event); - } - }, - style: element.props.style, - })} - , + React.cloneElement(element, { + key: '$empty', + onLayout: event => { + this._onLayoutEmpty(event); + if (element.props.onLayout) { + element.props.onLayout(event); + } + }, + style: StyleSheet.compose( + inversionStyle, + element.props.style, + ), + }), ); } if (ListFooterComponent) { diff --git a/Libraries/Lists/VirtualizedSectionList.js b/Libraries/Lists/VirtualizedSectionList.js index c1e76e1228f494..c52395954482ca 100644 --- a/Libraries/Lists/VirtualizedSectionList.js +++ b/Libraries/Lists/VirtualizedSectionList.js @@ -91,11 +91,11 @@ type OptionalProps = { */ ItemSeparatorComponent?: ?React.ComponentType, /** - * Warning: Virtualization can drastically improve memory consumption for long lists, but trashes - * the state of items when they scroll out of the render window, so make sure all relavent data is - * stored outside of the recursive `renderItem` instance tree. + * DEPRECATED: Virtualization provides significant performance and memory optimizations, but fully + * unmounts react instances that are outside of the render window. You should only need to disable + * this for debugging purposes. */ - enableVirtualization?: ?boolean, + disableVirtualization?: ?boolean, keyExtractor: (item: Item, index: number) => string, onEndReached?: ?({distanceFromEnd: number}) => void, /** diff --git a/Libraries/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap b/Libraries/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap index 32ec715a10ba71..2ae233201f72ba 100644 --- a/Libraries/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap +++ b/Libraries/Lists/__tests__/__snapshots__/VirtualizedList-test.js.snap @@ -668,11 +668,7 @@ exports[`VirtualizedList renders empty list with empty component 1`] = ` >
- - - + diff --git a/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h index 81b5b8b903b29e..376f9e602683f0 100644 --- a/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h +++ b/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h @@ -23,6 +23,8 @@ @property (nonatomic, readonly) BOOL needsUpdate; +-(BOOL)isManagedByFabric; + /** * Marks a node and its children as needing update. */ diff --git a/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m index 7d9a2fc6f7e4fb..6f61e084a472a5 100644 --- a/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m @@ -115,4 +115,14 @@ - (void)performUpdate // during the current update loop } +- (BOOL)isManagedByFabric +{ + for (RCTAnimatedNode *child in _childNodes.objectEnumerator) { + if ([child isManagedByFabric]) { + return YES; + } + } + return NO; +} + @end diff --git a/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m index 1b8e3e16f588a0..0a9a33434fb461 100644 --- a/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m @@ -36,6 +36,11 @@ - (instancetype)initWithTag:(NSNumber *)tag return self; } +- (BOOL)isManagedByFabric +{ + return _managedByFabric; +} + - (void)connectToView:(NSNumber *)viewTag viewName:(NSString *)viewName bridge:(RCTBridge *)bridge diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m index 5b8e165b4b6daf..69bf078f804a6a 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m @@ -19,7 +19,6 @@ @implementation RCTNativeAnimatedModule // Operations called before views have been updated. NSMutableArray *_preOperations; NSMutableDictionary *_animIdIsManagedByFabric; - NSMutableDictionary *_animatedNodeIsManagedByFabric; } RCT_EXPORT_MODULE(); @@ -88,7 +87,7 @@ - (void)setBridge:(RCTBridge *)bridge [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager startAnimatingNode:animationId nodeTag:nodeTag config:config endCallback:callBack]; }]; - if ([_animatedNodeIsManagedByFabric[nodeTag] boolValue]) { + if ([_nodesManager isNodeManagedByFabric:nodeTag]) { _animIdIsManagedByFabric[animationId] = @YES; [self flushOperationQueues]; } @@ -138,9 +137,6 @@ - (void)setBridge:(RCTBridge *)bridge viewTag:(nonnull NSNumber *)viewTag) { NSString *viewName = [self.bridge.uiManager viewNameForReactTag:viewTag]; - if (RCTUIManagerTypeForTagIsFabric(nodeTag)) { - _animatedNodeIsManagedByFabric[nodeTag] = @YES; - } [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager connectAnimatedNodeToView:nodeTag viewTag:viewTag viewName:viewName]; }]; diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h index 1222050583c4ef..79e954daca37be 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h +++ b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h @@ -19,6 +19,8 @@ - (void)stepAnimations:(nonnull CADisplayLink *)displaylink; +- (BOOL)isNodeManagedByFabric:(nonnull NSNumber *)tag; + // graph - (void)createAnimatedNode:(nonnull NSNumber *)tag diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m index 03bded86af55da..1a47f1b7abd2ef 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m @@ -50,6 +50,12 @@ - (instancetype)initWithBridge:(nonnull RCTBridge *)bridge return self; } +- (BOOL)isNodeManagedByFabric:(nonnull NSNumber *)tag +{ + RCTAnimatedNode *node = _animationNodes[tag]; + return [node isManagedByFabric]; +} + #pragma mark -- Graph - (void)createAnimatedNode:(nonnull NSNumber *)tag diff --git a/Libraries/ReactNative/FabricUIManager.js b/Libraries/ReactNative/FabricUIManager.js index 927d93d8fb1613..a1a4ad68f5ad5b 100644 --- a/Libraries/ReactNative/FabricUIManager.js +++ b/Libraries/ReactNative/FabricUIManager.js @@ -4,11 +4,17 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local * @format */ 'use strict'; +import type { + MeasureOnSuccessCallback, + MeasureInWindowOnSuccessCallback, + MeasureLayoutOnSuccessCallback, +} from 'ReactNativeTypes'; + // TODO: type these properly. type Node = {}; type NodeSet = Array; @@ -31,6 +37,17 @@ type Spec = {| +appendChildToSet: (childSet: NodeSet, child: Node) => void, +completeRoot: (rootTag: number, childSet: NodeSet) => void, +setNativeProps: (node: Node, nativeProps: NodeProps) => void, + +measure: (node: Node, callback: MeasureOnSuccessCallback) => void, + +measureInWindow: ( + node: Node, + callback: MeasureInWindowOnSuccessCallback, + ) => void, + +measureLayout: ( + node: Node, + relativeNode: Node, + onFail: () => void, + onSuccess: MeasureLayoutOnSuccessCallback, + ) => void, |}; const FabricUIManager: ?Spec = global.nativeFabricUIManager; diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index dd1ccffc7e1901..d14aa513444bf5 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -66,12 +66,16 @@ const viewConfig = { minimumFontScale: true, textBreakStrategy: true, onTextLayout: true, + onInlineViewLayout: true, dataDetectorType: true, }, directEventTypes: { topTextLayout: { registrationName: 'onTextLayout', }, + topInlineViewLayout: { + registrationName: 'onInlineViewLayout', + }, }, uiViewClassName: 'RCTText', }; diff --git a/Libraries/polyfills/Array.es6.js b/Libraries/polyfills/Array.es6.js deleted file mode 100644 index 64c012c7d6d15e..00000000000000 --- a/Libraries/polyfills/Array.es6.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * 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. - * - * @format - * @polyfill - * @nolint - */ - -/* eslint-disable consistent-this */ - -/** - * Creates an array from array like objects. - * - * https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from - */ -if (!Array.from) { - Array.from = function(arrayLike /*, mapFn, thisArg */) { - if (arrayLike == null) { - throw new TypeError('Object is null or undefined'); - } - - // Optional args. - var mapFn = arguments[1]; - var thisArg = arguments[2]; - - var C = this; - var items = Object(arrayLike); - var symbolIterator = - typeof Symbol === 'function' ? Symbol.iterator : '@@iterator'; - var mapping = typeof mapFn === 'function'; - var usingIterator = typeof items[symbolIterator] === 'function'; - var key = 0; - var ret; - var value; - - if (usingIterator) { - ret = typeof C === 'function' ? new C() : []; - var it = items[symbolIterator](); - var next; - - while (!(next = it.next()).done) { - value = next.value; - - if (mapping) { - value = mapFn.call(thisArg, value, key); - } - - ret[key] = value; - key += 1; - } - - ret.length = key; - return ret; - } - - var len = items.length; - if (isNaN(len) || len < 0) { - len = 0; - } - - ret = typeof C === 'function' ? new C(len) : new Array(len); - - while (key < len) { - value = items[key]; - - if (mapping) { - value = mapFn.call(thisArg, value, key); - } - - ret[key] = value; - - key += 1; - } - - ret.length = key; - return ret; - }; -} diff --git a/Libraries/polyfills/Array.prototype.es6.js b/Libraries/polyfills/Array.prototype.es6.js deleted file mode 100644 index 1b2395d064db4b..00000000000000 --- a/Libraries/polyfills/Array.prototype.es6.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * 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. - * - * @format - * @polyfill - * @nolint - */ - -/* eslint-disable no-bitwise, no-extend-native, radix, no-self-compare */ - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex -function findIndex(predicate, context) { - if (this == null) { - throw new TypeError( - 'Array.prototype.findIndex called on null or undefined', - ); - } - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - var list = Object(this); - var length = list.length >>> 0; - for (var i = 0; i < length; i++) { - if (predicate.call(context, list[i], i, list)) { - return i; - } - } - return -1; -} - -if (!Array.prototype.findIndex) { - Object.defineProperty(Array.prototype, 'findIndex', { - enumerable: false, - writable: true, - configurable: true, - value: findIndex, - }); -} - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find -if (!Array.prototype.find) { - Object.defineProperty(Array.prototype, 'find', { - enumerable: false, - writable: true, - configurable: true, - value: function(predicate, context) { - if (this == null) { - throw new TypeError('Array.prototype.find called on null or undefined'); - } - var index = findIndex.call(this, predicate, context); - return index === -1 ? undefined : this[index]; - }, - }); -} - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes -if (!Array.prototype.includes) { - Object.defineProperty(Array.prototype, 'includes', { - enumerable: false, - writable: true, - configurable: true, - value: function(searchElement) { - var O = Object(this); - var len = parseInt(O.length) || 0; - if (len === 0) { - return false; - } - var n = parseInt(arguments[1]) || 0; - var k; - if (n >= 0) { - k = n; - } else { - k = len + n; - if (k < 0) { - k = 0; - } - } - var currentElement; - while (k < len) { - currentElement = O[k]; - if ( - searchElement === currentElement || - (searchElement !== searchElement && currentElement !== currentElement) - ) { - return true; - } - k++; - } - return false; - }, - }); -} diff --git a/Libraries/polyfills/Number.es6.js b/Libraries/polyfills/Number.es6.js deleted file mode 100644 index 8fd538f425d0a9..00000000000000 --- a/Libraries/polyfills/Number.es6.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * 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. - * - * @format - * @polyfill - * @nolint - */ - -if (Number.EPSILON === undefined) { - Object.defineProperty(Number, 'EPSILON', { - value: Math.pow(2, -52), - }); -} -if (Number.MAX_SAFE_INTEGER === undefined) { - Object.defineProperty(Number, 'MAX_SAFE_INTEGER', { - value: Math.pow(2, 53) - 1, - }); -} -if (Number.MIN_SAFE_INTEGER === undefined) { - Object.defineProperty(Number, 'MIN_SAFE_INTEGER', { - value: -(Math.pow(2, 53) - 1), - }); -} -if (!Number.isNaN) { - // https://github.com/dherman/tc39-codex-wiki/blob/master/data/es6/number/index.md#polyfill-for-numberisnan - const globalIsNaN = global.isNaN; - Object.defineProperty(Number, 'isNaN', { - configurable: true, - enumerable: false, - value: function isNaN(value) { - return typeof value === 'number' && globalIsNaN(value); - }, - writable: true, - }); -} diff --git a/Libraries/polyfills/Object.es6.js b/Libraries/polyfills/Object.es6.js deleted file mode 100644 index b6c09479f6a307..00000000000000 --- a/Libraries/polyfills/Object.es6.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * 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. - * - * @format - * @polyfill - */ - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign - -if (typeof Object.assign !== 'function') { - Object.defineProperty(Object, 'assign', { - value: function assign(target, varArgs) { - 'use strict'; - if (target == null) { - throw new TypeError('Cannot convert undefined or null to object'); - } - - let to = Object(target); - - for (let index = 1; index < arguments.length; index++) { - let nextSource = arguments[index]; - - if (nextSource != null) { - for (let nextKey in nextSource) { - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - return to; - }, - writable: true, - configurable: true, - }); -} diff --git a/Libraries/polyfills/String.prototype.es6.js b/Libraries/polyfills/String.prototype.es6.js deleted file mode 100644 index 0c312c06d0de2c..00000000000000 --- a/Libraries/polyfills/String.prototype.es6.js +++ /dev/null @@ -1,160 +0,0 @@ -/** - * 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. - * - * @format - * @polyfill - * @nolint - */ - -/* eslint-disable no-extend-native, no-bitwise */ - -/* - * NOTE: We use (Number(x) || 0) to replace NaN values with zero. - */ - -if (!String.prototype.startsWith) { - String.prototype.startsWith = function(search) { - 'use strict'; - if (this == null) { - throw TypeError(); - } - var string = String(this); - var pos = arguments.length > 1 ? Number(arguments[1]) || 0 : 0; - var start = Math.min(Math.max(pos, 0), string.length); - return string.indexOf(String(search), pos) === start; - }; -} - -if (!String.prototype.endsWith) { - String.prototype.endsWith = function(search) { - 'use strict'; - if (this == null) { - throw TypeError(); - } - var string = String(this); - var stringLength = string.length; - var searchString = String(search); - var pos = arguments.length > 1 ? Number(arguments[1]) || 0 : stringLength; - var end = Math.min(Math.max(pos, 0), stringLength); - var start = end - searchString.length; - if (start < 0) { - return false; - } - return string.lastIndexOf(searchString, start) === start; - }; -} - -if (!String.prototype.repeat) { - String.prototype.repeat = function(count) { - 'use strict'; - if (this == null) { - throw TypeError(); - } - var string = String(this); - count = Number(count) || 0; - if (count < 0 || count === Infinity) { - throw RangeError(); - } - if (count === 1) { - return string; - } - var result = ''; - while (count) { - if (count & 1) { - result += string; - } - if ((count >>= 1)) { - string += string; - } - } - return result; - }; -} - -if (!String.prototype.includes) { - String.prototype.includes = function(search, start) { - 'use strict'; - if (typeof start !== 'number') { - start = 0; - } - - if (start + search.length > this.length) { - return false; - } else { - return this.indexOf(search, start) !== -1; - } - }; -} - -if (!String.prototype.codePointAt) { - String.prototype.codePointAt = function(position) { - if (this == null) { - throw TypeError(); - } - var string = String(this); - var size = string.length; - // `ToInteger` - var index = position ? Number(position) : 0; - if (Number.isNaN(index)) { - index = 0; - } - // Account for out-of-bounds indices: - if (index < 0 || index >= size) { - return undefined; - } - // Get the first code unit - var first = string.charCodeAt(index); - var second; - if ( - // check if it’s the start of a surrogate pair - first >= 0xd800 && - first <= 0xdbff && // high surrogate - size > index + 1 // there is a next code unit - ) { - second = string.charCodeAt(index + 1); - if (second >= 0xdc00 && second <= 0xdfff) { - // low surrogate - // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - return (first - 0xd800) * 0x400 + second - 0xdc00 + 0x10000; - } - } - return first; - }; -} - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd -if (!String.prototype.padEnd) { - String.prototype.padEnd = function padEnd(targetLength, padString) { - targetLength = targetLength >> 0; //floor if number or convert non-number to 0; - padString = String(typeof padString !== 'undefined' ? padString : ' '); - if (this.length > targetLength) { - return String(this); - } else { - targetLength = targetLength - this.length; - if (targetLength > padString.length) { - padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed - } - return String(this) + padString.slice(0, targetLength); - } - }; -} - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart -if (!String.prototype.padStart) { - String.prototype.padStart = function padStart(targetLength, padString) { - targetLength = targetLength >> 0; //truncate if number or convert non-number to 0; - padString = String(typeof padString !== 'undefined' ? padString : ' '); - if (this.length > targetLength) { - return String(this); - } else { - targetLength = targetLength - this.length; - if (targetLength > padString.length) { - padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed - } - return padString.slice(0, targetLength) + String(this); - } - }; -} diff --git a/Libraries/polyfills/babelHelpers.js b/Libraries/polyfills/babelHelpers.js deleted file mode 100644 index 605f6daaaa85d2..00000000000000 --- a/Libraries/polyfills/babelHelpers.js +++ /dev/null @@ -1,608 +0,0 @@ -/** - * 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. - * - * Regenerate through - * `js1 upgrade babel-helpers` + manual tweaks - * - * Components used for this file; - * - arrayWithHoles - * - arrayWithoutHoles - * - assertThisInitialized - * - classCallCheck - * - construct - * - createClass - * - defineProperty - * - extends - * - get - * - getPrototypeOf - * - inherits - * - interopRequireDefault - * - interopRequireWildcard - * - iterableToArray - * - iterableToArrayLimit - * - nonIterableRest - * - nonIterableSpread - * - objectSpread - * - objectWithoutProperties - * - possibleConstructorReturn - * - setPrototypeOf - * - slicedToArray - * - superPropBase - * - taggedTemplateLiteral - * - taggedTemplateLiteralLoose - * - toArray - * - toConsumableArray - * - wrapNativeSuper - * - * @flow - * @generated (with babel 7.0.0-beta.47) - * @format - * @nolint - * @polyfill - */ - -/* eslint-disable no-func-assign, no-shadow, no-proto, no-void, no-undef-init */ - -'use strict'; - -var babelHelpers = (global.babelHelpers = {}); - -// ### classCallCheck ### - -function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError('Cannot call a class as a function'); - } -} - -babelHelpers.classCallCheck = _classCallCheck; - -// ### createClass ### - -function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ('value' in descriptor) { - descriptor.writable = true; - } - Object.defineProperty(target, descriptor.key, descriptor); - } -} - -function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) { - _defineProperties(Constructor.prototype, protoProps); - } - if (staticProps) { - _defineProperties(Constructor, staticProps); - } - return Constructor; -} - -babelHelpers.createClass = _createClass; - -// ### defineProperty ### - -function _defineProperty(obj, key, value) { - if (key in obj) { - Object.defineProperty(obj, key, { - value: value, - enumerable: true, - configurable: true, - writable: true, - }); - } else { - obj[key] = value; - } - - return obj; -} - -babelHelpers.defineProperty = _defineProperty; - -// ### extends ### - -function _extends() { - babelHelpers.extends = _extends = - Object.assign || - function(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - - return target; - }; - - return _extends.apply(this, arguments); -} - -babelHelpers.extends = _extends; - -// ### setPrototypeOf ### - -function _setPrototypeOf(o, p) { - babelHelpers.setPrototypeOf = _setPrototypeOf = - Object.setPrototypeOf || - function _setPrototypeOf(o, p) { - o.__proto__ = p; - return o; - }; - - return _setPrototypeOf(o, p); -} - -babelHelpers.setPrototypeOf = _setPrototypeOf; - -// ### superPropBase ### - -function _superPropBase(object, property) { - while (!Object.prototype.hasOwnProperty.call(object, property)) { - object = babelHelpers.getPrototypeOf(object); - if (object === null) { - break; - } - } - - return object; -} - -babelHelpers.superPropBase = _superPropBase; - -// ### get ### - -// FB: -// TODO: prepack does not like Reflect (and we can use the fallback just fine) -// function _get(target, property, receiver) { -// if (typeof Reflect !== 'undefined' && Reflect.get) { -// babelHelpers.get = _get = Reflect.get; -// } else { -// babelHelpers.get = _get = function _get(target, property, receiver) { -// var base = babelHelpers.superPropBase(target, property); -// if (!base) { -// return; -// } -// var desc = Object.getOwnPropertyDescriptor(base, property); -// -// if (desc.get) { -// return desc.get.call(receiver); -// } -// -// return desc.value; -// }; -// } -// -// return _get(target, property, receiver || target); -// } -// -// babelHelpers.get = _get; - -babelHelpers.get = function _get(target, property, receiver = target) { - var base = babelHelpers.superPropBase(target, property); - if (!base) { - return; - } - var desc = Object.getOwnPropertyDescriptor(base, property); - - if (desc.get) { - return desc.get.call(receiver); - } - - return desc.value; -}; - -// ### inherits ### - -function _inherits(subClass, superClass) { - if (typeof superClass !== 'function' && superClass !== null) { - throw new TypeError('Super expression must either be null or a function'); - } - - babelHelpers.setPrototypeOf( - subClass.prototype, - superClass && superClass.prototype, - ); - if (superClass) { - babelHelpers.setPrototypeOf(subClass, superClass); - } -} - -babelHelpers.inherits = _inherits; - -// ### construct ### - -function _construct(Parent, args, Class) { - // FB: - // TODO: prepack does not like this line (and we can use the fallback just fine) - // if (typeof Reflect !== 'undefined' && Reflect.construct) { - // babelHelpers.construct = _construct = Reflect.construct; - // } else { - babelHelpers.construct = _construct = function _construct( - Parent, - args, - Class, - ) { - var a = [null]; - a.push.apply(a, args); - var Constructor = Parent.bind.apply(Parent, a); - var instance = new Constructor(); - if (Class) { - babelHelpers.setPrototypeOf(instance, Class.prototype); - } - return instance; - }; - // } - - return _construct.apply(null, arguments); -} - -babelHelpers.construct = _construct; - -// ### getPrototypeOf ### - -function _getPrototypeOf(o) { - babelHelpers.getPrototypeOf = _getPrototypeOf = - Object.getPrototypeOf || - function _getPrototypeOf(o) { - return o.__proto__; - }; - - return _getPrototypeOf(o); -} - -babelHelpers.getPrototypeOf = _getPrototypeOf; - -// ### assertThisInitialized ### - -function _assertThisInitialized(self) { - if (self === void 0) { - throw new ReferenceError( - "this hasn't been initialised - super() hasn't been called", - ); - } - - return self; -} - -babelHelpers.assertThisInitialized = _assertThisInitialized; - -// ### wrapNativeSuper ### - -function _wrapNativeSuper(Class) { - var _cache = typeof Map === 'function' ? new Map() : undefined; - - babelHelpers.wrapNativeSuper = _wrapNativeSuper = function _wrapNativeSuper( - Class, - ) { - if (typeof Class !== 'function') { - throw new TypeError('Super expression must either be null or a function'); - } - - if (typeof _cache !== 'undefined') { - if (_cache.has(Class)) { - return _cache.get(Class); - } - - _cache.set(Class, Wrapper); - } - - function Wrapper() { - // FB: - // this is a temporary fix for a babel bug (it's invoking the wrong func - // when you do `super()`) - return _construct(Class, arguments, _getPrototypeOf(this).constructor); - } - - Wrapper.prototype = Object.create(Class.prototype, { - constructor: { - value: Wrapper, - enumerable: false, - writable: true, - configurable: true, - }, - }); - return babelHelpers.setPrototypeOf( - Wrapper, - babelHelpers.setPrototypeOf(function Super() { - return babelHelpers.construct( - Class, - arguments, - babelHelpers.getPrototypeOf(this).constructor, - ); - }, Class), - ); - }; - - return _wrapNativeSuper(Class); -} - -babelHelpers.wrapNativeSuper = _wrapNativeSuper; - -// ### interopRequireDefault ### - -function _interopRequireDefault(obj) { - return obj && obj.__esModule - ? obj - : { - default: obj, - }; -} - -babelHelpers.interopRequireDefault = _interopRequireDefault; - -// ### interopRequireWildcard ### - -function _interopRequireWildcard(obj) { - if (obj && obj.__esModule) { - return obj; - } else { - var newObj = {}; - - if (obj != null) { - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - var desc = - Object.defineProperty && Object.getOwnPropertyDescriptor - ? Object.getOwnPropertyDescriptor(obj, key) - : {}; - - if (desc.get || desc.set) { - Object.defineProperty(newObj, key, desc); - } else { - newObj[key] = obj[key]; - } - } - } - } - - newObj.default = obj; - return newObj; - } -} - -babelHelpers.interopRequireWildcard = _interopRequireWildcard; - -// ### objectWithoutProperties ### - -function _objectWithoutProperties(source, excluded) { - if (source == null) { - return {}; - } - var target = {}; - var sourceKeys = Object.keys(source); - var key, i; - - for (i = 0; i < sourceKeys.length; i++) { - key = sourceKeys[i]; - if (excluded.indexOf(key) >= 0) { - continue; - } - target[key] = source[key]; - } - - if (Object.getOwnPropertySymbols) { - var sourceSymbolKeys = Object.getOwnPropertySymbols(source); - - for (i = 0; i < sourceSymbolKeys.length; i++) { - key = sourceSymbolKeys[i]; - if (excluded.indexOf(key) >= 0) { - continue; - } - if (!Object.prototype.propertyIsEnumerable.call(source, key)) { - continue; - } - target[key] = source[key]; - } - } - - return target; -} - -babelHelpers.objectWithoutProperties = _objectWithoutProperties; - -// ### possibleConstructorReturn ### - -function _possibleConstructorReturn(self, call) { - if (call && (typeof call === 'object' || typeof call === 'function')) { - return call; - } - - return babelHelpers.assertThisInitialized(self); -} - -babelHelpers.possibleConstructorReturn = _possibleConstructorReturn; - -// ### arrayWithHoles ### - -function _arrayWithHoles(arr) { - if (Array.isArray(arr)) { - return arr; - } -} - -babelHelpers.arrayWithHoles = _arrayWithHoles; - -// ### arrayWithoutHoles ### - -function _arrayWithoutHoles(arr) { - if (Array.isArray(arr)) { - for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { - arr2[i] = arr[i]; - } - - return arr2; - } -} - -babelHelpers.arrayWithoutHoles = _arrayWithoutHoles; - -// ### iterableToArrayLimit ### - -function _iterableToArrayLimit(arr, i) { - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; - - try { - for ( - var _i = arr[Symbol.iterator](), _s; - !(_n = (_s = _i.next()).done); - _n = true - ) { - _arr.push(_s.value); - - if (i && _arr.length === i) { - break; - } - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i.return != null) { - _i.return(); - } - } finally { - if (_d) { - throw _e; - } - } - } - - return _arr; -} - -babelHelpers.iterableToArrayLimit = _iterableToArrayLimit; - -// ### nonIterableRest ### - -function _nonIterableRest() { - throw new TypeError('Invalid attempt to destructure non-iterable instance'); -} - -babelHelpers.nonIterableRest = _nonIterableRest; - -// ### nonIterableSpread ### - -function _nonIterableSpread() { - throw new TypeError('Invalid attempt to spread non-iterable instance'); -} - -babelHelpers.nonIterableSpread = _nonIterableSpread; - -// ### slicedToArray ### - -function _slicedToArray(arr, i) { - return ( - babelHelpers.arrayWithHoles(arr) || - babelHelpers.iterableToArrayLimit(arr, i) || - babelHelpers.nonIterableRest() - ); -} - -babelHelpers.slicedToArray = _slicedToArray; - -// ### taggedTemplateLiteral ### - -function _taggedTemplateLiteral(strings, raw) { - if (!raw) { - raw = strings.slice(0); - } - - return Object.freeze( - Object.defineProperties(strings, { - raw: { - value: Object.freeze(raw), - }, - }), - ); -} - -babelHelpers.taggedTemplateLiteral = _taggedTemplateLiteral; - -// ### toArray ### - -function _toArray(arr) { - return ( - babelHelpers.arrayWithHoles(arr) || - babelHelpers.iterableToArray(arr) || - babelHelpers.nonIterableRest() - ); -} - -babelHelpers.toArray = _toArray; - -// ### toConsumableArray ### - -function _toConsumableArray(arr) { - return ( - babelHelpers.arrayWithoutHoles(arr) || - babelHelpers.iterableToArray(arr) || - babelHelpers.nonIterableSpread() - ); -} - -babelHelpers.toConsumableArray = _toConsumableArray; - -// ### taggedTemplateLiteralLoose ### - -function _taggedTemplateLiteralLoose(strings, raw) { - if (!raw) { - raw = strings.slice(0); - } - - strings.raw = raw; - return strings; -} - -babelHelpers.taggedTemplateLiteralLoose = _taggedTemplateLiteralLoose; - -// ### objectSpread ### - -function _objectSpread(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i] != null ? arguments[i] : {}; - var ownKeys = Object.keys(source); - - if (typeof Object.getOwnPropertySymbols === 'function') { - ownKeys = ownKeys.concat( - Object.getOwnPropertySymbols(source).filter(function(sym) { - return Object.getOwnPropertyDescriptor(source, sym).enumerable; - }), - ); - } - - ownKeys.forEach(function(key) { - babelHelpers.defineProperty(target, key, source[key]); - }); - } - - return target; -} - -babelHelpers.objectSpread = _objectSpread; - -// ### iterableToArray ### - -function _iterableToArray(iter) { - if ( - Symbol.iterator in Object(iter) || - Object.prototype.toString.call(iter) === '[object Arguments]' - ) { - return Array.from(iter); - } -} - -babelHelpers.iterableToArray = _iterableToArray; diff --git a/Libraries/vendor/core/Map.js b/Libraries/vendor/core/Map.js index 009a5258dcb9e5..76f27534ac651c 100644 --- a/Libraries/vendor/core/Map.js +++ b/Libraries/vendor/core/Map.js @@ -26,9 +26,6 @@ module.exports = (function(global, undefined) { return global.Map; } - // In case this module has not already been evaluated, import it now. - require('./_wrapObjectFreezeAndFriends'); - const hasOwn = Object.prototype.hasOwnProperty; /** diff --git a/Libraries/vendor/core/_wrapObjectFreezeAndFriends.js b/Libraries/vendor/core/_wrapObjectFreezeAndFriends.js deleted file mode 100644 index 9bfadc9adf00ba..00000000000000 --- a/Libraries/vendor/core/_wrapObjectFreezeAndFriends.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * 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. - * - * @author Ben Newman (@benjamn) - * @flow - * @format - */ - -'use strict'; - -let testMap; // Initialized lazily. -function getTestMap() { - return testMap || (testMap = new (require('./Map'))()); -} - -// Wrap Object.{freeze,seal,preventExtensions} so each function adds its -// argument to a Map first, which gives our ./Map.js polyfill a chance to -// tag the object before it becomes non-extensible. -['freeze', 'seal', 'preventExtensions'].forEach(name => { - const method = Object[name]; - if (typeof method === 'function') { - (Object: any)[name] = function(obj) { - try { - // If .set succeeds, also call .delete to avoid leaking memory. - getTestMap() - .set(obj, obj) - .delete(obj); - } finally { - // If .set fails, the exception will be silently swallowed - // by this return-from-finally statement, and the method will - // behave exactly as it did before it was wrapped. - return method.call(Object, obj); - } - }; - } -}); diff --git a/RNTester/Podfile b/RNTester/Podfile index 229ecb58a21898..28157998bdc7e1 100644 --- a/RNTester/Podfile +++ b/RNTester/Podfile @@ -15,7 +15,6 @@ target 'RNTester' do pod 'React-RCTAnimation', :path => '../Libraries/NativeAnimation' pod 'React-RCTBlob', :path => '../Libraries/Blob' pod 'React-RCTCameraRoll', :path => '../Libraries/CameraRoll' - pod 'React-RCTGeolocation', :path => '../Libraries/Geolocation' pod 'React-RCTImage', :path => '../Libraries/Image' pod 'React-RCTLinking', :path => '../Libraries/LinkingIOS' pod 'React-RCTNetwork', :path => '../Libraries/Network' diff --git a/RNTester/Podfile.lock b/RNTester/Podfile.lock index 85558e3680abe7..c6ef6ac2907c35 100644 --- a/RNTester/Podfile.lock +++ b/RNTester/Podfile.lock @@ -17,7 +17,6 @@ PODS: - React-RCTActionSheet (= 1000.0.0) - React-RCTAnimation (= 1000.0.0) - React-RCTBlob (= 1000.0.0) - - React-RCTGeolocation (= 1000.0.0) - React-RCTImage (= 1000.0.0) - React-RCTLinking (= 1000.0.0) - React-RCTNetwork (= 1000.0.0) @@ -71,8 +70,6 @@ PODS: - React-RCTCameraRoll (1000.0.0): - React-Core (= 1000.0.0) - React-RCTImage (= 1000.0.0) - - React-RCTGeolocation (1000.0.0): - - React-Core (= 1000.0.0) - React-RCTImage (1000.0.0): - React-Core (= 1000.0.0) - React-RCTNetwork (= 1000.0.0) @@ -110,7 +107,6 @@ DEPENDENCIES: - React-RCTAnimation (from `../Libraries/NativeAnimation`) - React-RCTBlob (from `../Libraries/Blob`) - React-RCTCameraRoll (from `../Libraries/CameraRoll`) - - React-RCTGeolocation (from `../Libraries/Geolocation`) - React-RCTImage (from `../Libraries/Image`) - React-RCTLinking (from `../Libraries/LinkingIOS`) - React-RCTNetwork (from `../Libraries/Network`) @@ -158,8 +154,6 @@ EXTERNAL SOURCES: :path: "../Libraries/Blob" React-RCTCameraRoll: :path: "../Libraries/CameraRoll" - React-RCTGeolocation: - :path: "../Libraries/Geolocation" React-RCTImage: :path: "../Libraries/Image" React-RCTLinking: @@ -197,7 +191,6 @@ SPEC CHECKSUMS: React-RCTAnimation: b324c6eb699637c735650c6180e13d003eeb0e56 React-RCTBlob: 069290c8db758bb1d77523a06d117dd668b6cef3 React-RCTCameraRoll: 353af870a0acd5ebb0bcf6a8187ed78d94c4c65e - React-RCTGeolocation: 4bbdba9893dc3f22b22553904e54ae46dcedf48b React-RCTImage: 012d845d919177e2726743ad06052dda66592760 React-RCTLinking: c6fe7b82bed97ce72203b2ce2f4aac87b1e2647f React-RCTNetwork: 2a2b22a17cd965de53ba21c5ca392d0da84ef322 diff --git a/RNTester/README.md b/RNTester/README.md index cb4b7162a2258a..f7d35ebe1396ff 100644 --- a/RNTester/README.md +++ b/RNTester/README.md @@ -81,7 +81,7 @@ When developing E2E tests, you may want to run in development mode, so that chan You will also need to have Metro Bundler running in another terminal. Note that if you've previously run the E2E tests in release mode, you may need to delete the `RNTester/build` folder before rerunning `detox build`. -## Built from source +## Building from source Building the app on both iOS and Android means building the React Native framework from source. This way you're running the latest native and JS code the way you see it in your clone of the github repo. diff --git a/RNTester/RNTester.xcodeproj/project.pbxproj b/RNTester/RNTester.xcodeproj/project.pbxproj index fb957fdccdf025..308e9bf4230a17 100644 --- a/RNTester/RNTester.xcodeproj/project.pbxproj +++ b/RNTester/RNTester.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; }; 134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; }; 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; - 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; }; 134CB92A1C85A38800265FA6 /* RCTModuleInitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */; }; 138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; }; 138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */; }; @@ -37,7 +36,6 @@ 1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */; }; 14D6D7111B220EB3001FB087 /* libOCMock.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14D6D7101B220EB3001FB087 /* libOCMock.a */; }; 14D6D71E1B2222EF001FB087 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; }; - 14D6D7201B2222EF001FB087 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; }; 14D6D7211B2222EF001FB087 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; }; 14D6D7221B2222EF001FB087 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; 14D6D7231B2222EF001FB087 /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; }; @@ -161,13 +159,6 @@ remoteGlobalIDString = 58B511DB1A9E6C8500147676; remoteInfo = RCTNetwork; }; - 134A8A241AACED6A00945AAE /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = RCTGeolocation; - }; 138DEE081B9EDDDB007F4EA5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */; @@ -492,7 +483,6 @@ 13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; - 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; 134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleInitTests.m; sourceTree = ""; }; 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = ""; }; 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCameraRoll.xcodeproj; path = ../Libraries/CameraRoll/RCTCameraRoll.xcodeproj; sourceTree = ""; }; @@ -586,7 +576,6 @@ 19BA89031F8439A700741C5A /* libRCTBlob.a in Frameworks */, 192F69DA1E8240E2008692C7 /* libRCTAnimation.a in Frameworks */, 14D6D71E1B2222EF001FB087 /* libRCTActionSheet.a in Frameworks */, - 14D6D7201B2222EF001FB087 /* libRCTGeolocation.a in Frameworks */, 14D6D7211B2222EF001FB087 /* libRCTImage.a in Frameworks */, 14D6D7221B2222EF001FB087 /* libRCTNetwork.a in Frameworks */, 14D6D7231B2222EF001FB087 /* libRCTPushNotification.a in Frameworks */, @@ -611,7 +600,6 @@ 147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */, 13E501F11D07A84A005F35D8 /* libRCTAnimation.a in Frameworks */, 138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */, - 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */, 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */, 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */, 3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */, @@ -696,7 +684,6 @@ 14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */, 13E5019C1D07A502005F35D8 /* RCTAnimation.xcodeproj */, 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */, - 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */, 13417FE31AA91428003F314A /* RCTImage.xcodeproj */, 357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */, 134180261AA91779003F314A /* RCTNetwork.xcodeproj */, @@ -746,14 +733,6 @@ name = Products; sourceTree = ""; }; - 134A8A211AACED6A00945AAE /* Products */ = { - isa = PBXGroup; - children = ( - 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */, - ); - name = Products; - sourceTree = ""; - }; 138DEE031B9EDDDB007F4EA5 /* Products */ = { isa = PBXGroup; children = ( @@ -1249,10 +1228,6 @@ ProductGroup = 138DEE031B9EDDDB007F4EA5 /* Products */; ProjectRef = 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */; }, - { - ProductGroup = 134A8A211AACED6A00945AAE /* Products */; - ProjectRef = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */; - }, { ProductGroup = 13417FE41AA91428003F314A /* Products */; ProjectRef = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */; @@ -1329,13 +1304,6 @@ remoteRef = 1341802A1AA91779003F314A /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTGeolocation.a; - remoteRef = 134A8A241AACED6A00945AAE /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; diff --git a/RNTester/RNTesterIntegrationTests/RNTesterTestModule.m b/RNTester/RNTesterIntegrationTests/RNTesterTestModule.m index d5a4346c4155d1..db62c89856a987 100644 --- a/RNTester/RNTesterIntegrationTests/RNTesterTestModule.m +++ b/RNTester/RNTesterIntegrationTests/RNTesterTestModule.m @@ -27,4 +27,10 @@ @implementation RNTesterTestModule return nil; } +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(methodThatCallsCallbackWithString:(NSString *)input callback:(RCTResponseSenderBlock)callback) +{ + callback(@[input]); + return nil; +} + @end diff --git a/RNTester/RNTesterUnitTests/RCTModuleMethodTests.mm b/RNTester/RNTesterUnitTests/RCTModuleMethodTests.mm index 3efc6905343d9d..c319d26492ba6b 100644 --- a/RNTester/RNTesterUnitTests/RCTModuleMethodTests.mm +++ b/RNTester/RNTesterUnitTests/RCTModuleMethodTests.mm @@ -57,6 +57,9 @@ - (void)doFooWithBar:(__unused NSString *)bar { } - (id)echoString:(NSString *)input { return input; } - (id)methodThatReturnsNil { return nil; } +- (void)openURL:(NSURL *)URL resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {} +- (void)openURL:(NSURL *)URL callback:(RCTResponseSenderBlock)callback {} +- (id)methodThatCallsCallbackWithArg:(NSString *)input callback:(RCTResponseSenderBlock)callback { callback(@[input]); return nil; } - (void)testNonnull { @@ -147,18 +150,50 @@ - (void)testFunctionType const char *methodSignature = "doFoo"; RCTModuleMethod *method = buildDefaultMethodWithMethodSignature(methodSignature); XCTAssertTrue(method.functionType == RCTFunctionTypeNormal); + XCTAssertFalse(RCTLogsError(^{ + // Invoke method to trigger parsing + __unused SEL selector = method.selector; + }), @"Unexpected error when parsing normal function"); + } + + { + const char *methodSignature = "openURL:(NSURL *)URL callback:(RCTResponseSenderBlock)callBack"; + RCTModuleMethod *method = buildDefaultMethodWithMethodSignature(methodSignature); + XCTAssertTrue(method.functionType == RCTFunctionTypeNormal); + XCTAssertFalse(RCTLogsError(^{ + // Invoke method to trigger parsing + __unused SEL selector = method.selector; + }), @"Unexpected error when parsing normal function with callback"); } { const char *methodSignature = "openURL:(NSURL *)URL resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject"; RCTModuleMethod *method = buildDefaultMethodWithMethodSignature(methodSignature); XCTAssertTrue(method.functionType == RCTFunctionTypePromise); + XCTAssertFalse(RCTLogsError(^{ + // Invoke method to trigger parsing + __unused SEL selector = method.selector; + }), @"Unexpected error when parsing promise function"); } - + { const char *methodSignature = "echoString:(NSString *)input"; RCTModuleMethod *method = buildSyncMethodWithMethodSignature(methodSignature); XCTAssertTrue(method.functionType == RCTFunctionTypeSync); + XCTAssertFalse(RCTLogsError(^{ + // Invoke method to trigger parsing + __unused SEL selector = method.selector; + }), @"Unexpected error when parsing sync function"); + } + + { + const char *methodSignature = "methodThatCallsCallbackWithArg:(NSString *)input callback:(RCTResponseSenderBlock)callback"; + RCTModuleMethod *method = buildSyncMethodWithMethodSignature(methodSignature); + XCTAssertTrue(method.functionType == RCTFunctionTypeSync); + XCTAssertFalse(RCTLogsError(^{ + // Invoke method to trigger parsing + __unused SEL selector = method.selector; + }), @"Unexpected error when parsing sync function with callback"); } } diff --git a/RNTester/android/app/BUCK b/RNTester/android/app/BUCK index 6af3bb0e5ec974..95e5318015a817 100644 --- a/RNTester/android/app/BUCK +++ b/RNTester/android/app/BUCK @@ -16,7 +16,7 @@ rn_android_library( deps = [ ":res", react_native_dep("third-party/android/support/v4:lib-support-v4"), - react_native_dep("third-party/android/support/v7/appcompat-orig:appcompat"), + react_native_dep("third-party/android/support/v7/appcompat:appcompat"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_target("java/com/facebook/react:react"), react_native_target("java/com/facebook/react/common:build_config"), diff --git a/RNTester/android/app/build.gradle b/RNTester/android/app/build.gradle index 4a6a9aad2c5057..3ef02339fa931a 100644 --- a/RNTester/android/app/build.gradle +++ b/RNTester/android/app/build.gradle @@ -91,6 +91,14 @@ def enableSeparateBuildPerCPUArchitecture = true */ def enableProguardInReleaseBuilds = true +/** + * Use the international variant of JavaScriptCore + * This variant includes the ICU i18n library to make APIs like `Date.toLocaleString` + * and `String.localeCompare` work when using with locales other than en-US. + * Note that this variant is about 6MiB larger per architecture than the default. + */ +def useIntlJsc = false + android { compileSdkVersion 28 @@ -132,6 +140,12 @@ android { signingConfig signingConfigs.release } } + packagingOptions { + pickFirst '**/armeabi-v7a/libc++_shared.so' + pickFirst '**/x86/libc++_shared.so' + pickFirst '**/x86_64/libc++_shared.so' + pickFirst '**/arm64-v8a/libc++_shared.so' + } } dependencies { @@ -139,4 +153,10 @@ dependencies { // Build React Native from source implementation project(':ReactAndroid') + + if (useIntlJsc) { + implementation 'org.webkit:android-jsc-intl:+' + } else { + implementation 'org.webkit:android-jsc:+' + } } diff --git a/RNTester/js/GeolocationExample.js b/RNTester/js/GeolocationExample.js deleted file mode 100644 index 808f6c074c5cf9..00000000000000 --- a/RNTester/js/GeolocationExample.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * 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. - * - * @format - * @flow - */ - -'use strict'; - -const React = require('react'); -const ReactNative = require('react-native'); -const {StyleSheet, Text, View, Alert} = ReactNative; - -class GeolocationExample extends React.Component<{}, $FlowFixMeState> { - state = { - initialPosition: 'unknown', - lastPosition: 'unknown', - }; - - watchID: ?number = null; - - componentDidMount() { - navigator.geolocation.getCurrentPosition( - position => { - const initialPosition = JSON.stringify(position); - this.setState({initialPosition}); - }, - error => Alert.alert('Error', JSON.stringify(error)), - {enableHighAccuracy: true, timeout: 20000, maximumAge: 1000}, - ); - this.watchID = navigator.geolocation.watchPosition(position => { - const lastPosition = JSON.stringify(position); - this.setState({lastPosition}); - }); - } - - componentWillUnmount() { - this.watchID != null && navigator.geolocation.clearWatch(this.watchID); - } - - render() { - return ( - - - Initial position: - {this.state.initialPosition} - - - Current position: - {this.state.lastPosition} - - - ); - } -} - -const styles = StyleSheet.create({ - title: { - fontWeight: '500', - }, -}); - -exports.framework = 'React'; -exports.title = 'Geolocation'; -exports.description = 'Examples of using the Geolocation API.'; - -exports.examples = [ - { - title: 'navigator.geolocation', - render: function(): React.Element { - return ; - }, - }, -]; diff --git a/RNTester/js/LayoutAnimationExample.js b/RNTester/js/LayoutAnimationExample.js index 184c3d5b29cfaa..fb8aac62264561 100644 --- a/RNTester/js/LayoutAnimationExample.js +++ b/RNTester/js/LayoutAnimationExample.js @@ -20,7 +20,9 @@ class AddRemoveExample extends React.Component<{}, $FlowFixMeState> { }; UNSAFE_componentWillUpdate() { - LayoutAnimation.easeInEaseOut(); + LayoutAnimation.easeInEaseOut(args => + console.log('AddRemoveExample completed', args), + ); } _onPressAddView = () => { @@ -73,7 +75,9 @@ class CrossFadeExample extends React.Component<{}, $FlowFixMeState> { }; _onPressToggle = () => { - LayoutAnimation.easeInEaseOut(); + LayoutAnimation.easeInEaseOut(args => + console.log('CrossFadeExample completed', args), + ); this.setState(state => ({toggled: !state.toggled})); }; @@ -116,12 +120,15 @@ class LayoutUpdateExample extends React.Component<{}, $FlowFixMeState> { this._clearTimeout(); this.setState({width: 150}); - LayoutAnimation.configureNext({ - duration: 1000, - update: { - type: LayoutAnimation.Types.linear, + LayoutAnimation.configureNext( + { + duration: 1000, + update: { + type: LayoutAnimation.Types.linear, + }, }, - }); + args => console.log('LayoutUpdateExample completed', args), + ); this.timeout = setTimeout(() => this.setState({width: 100}), 500); }; diff --git a/RNTester/js/NativeAnimationsExample.js b/RNTester/js/NativeAnimationsExample.js index 30f47ae22c2501..1bb71002b8920d 100644 --- a/RNTester/js/NativeAnimationsExample.js +++ b/RNTester/js/NativeAnimationsExample.js @@ -220,24 +220,26 @@ class EventExample extends React.Component<{}, $FlowFixMeState> { }; render() { - const opacity = this.state.scrollX.interpolate({ - inputRange: [0, 200], - outputRange: [1, 0], - }); return ( { width: 600, backgroundColor: '#eee', justifyContent: 'center', + paddingLeft: 100, }}> - Scroll me! + Scroll me sideways! @@ -637,7 +640,7 @@ exports.examples = [ }, }, { - title: 'Drive custom property', + title: 'Drive custom property (tap to animate)', render: function() { return ( diff --git a/RNTester/js/RNTesterList.android.js b/RNTester/js/RNTesterList.android.js index 95ff31b10cc87a..3eb3c8a5b26ff7 100644 --- a/RNTester/js/RNTesterList.android.js +++ b/RNTester/js/RNTesterList.android.js @@ -156,10 +156,6 @@ const APIExamples: Array = [ key: 'Dimensions', module: require('./DimensionsExample'), }, - { - key: 'GeolocationExample', - module: require('./GeolocationExample'), - }, { key: 'ImageEditingExample', module: require('./ImageEditingExample'), diff --git a/RNTester/js/RNTesterList.ios.js b/RNTester/js/RNTesterList.ios.js index 418936778fe590..e2922e4aa83dd9 100644 --- a/RNTester/js/RNTesterList.ios.js +++ b/RNTester/js/RNTesterList.ios.js @@ -231,11 +231,6 @@ const APIExamples: Array = [ module: require('./DimensionsExample'), supportsTVOS: true, }, - { - key: 'GeolocationExample', - module: require('./GeolocationExample'), - supportsTVOS: false, - }, { key: 'ImageEditingExample', module: require('./ImageEditingExample'), diff --git a/RNTester/js/SectionListExample.js b/RNTester/js/SectionListExample.js index be4179f7e6c120..455b1d36efcc0b 100644 --- a/RNTester/js/SectionListExample.js +++ b/RNTester/js/SectionListExample.js @@ -149,7 +149,7 @@ class SectionListExample extends React.PureComponent<{}, $FlowFixMeState> { )} debug={this.state.debug} inverted={this.state.inverted} - enableVirtualization={this.state.virtualized} + disableVirtualization={!this.state.virtualized} onRefresh={() => Alert.alert('onRefresh: nothing to refresh :P')} onScroll={this._scrollSinkY} onViewableItemsChanged={this._onViewableItemsChanged} diff --git a/React.podspec b/React.podspec index 508283c0778c4e..935b00d2ffba5d 100644 --- a/React.podspec +++ b/React.podspec @@ -47,7 +47,6 @@ Pod::Spec.new do |s| s.dependency "React-RCTActionSheet", version s.dependency "React-RCTAnimation", version s.dependency "React-RCTBlob", version - s.dependency "React-RCTGeolocation", version s.dependency "React-RCTImage", version s.dependency "React-RCTLinking", version s.dependency "React-RCTNetwork", version diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index f6b12bf00c0881..54a27802cbd0dc 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -73,6 +73,17 @@ RCT_EXTERN void RCTRegisterModule(Class); \ + (NSString *)moduleName { return @#js_name; } \ + (void)load { RCTRegisterModule(self); } +/** + * Same as RCT_EXPORT_MODULE, but uses __attribute__((constructor)) for module + * registration. Useful for registering swift classes that forbids use of load + * Used in RCT_EXTERN_REMAP_MODULE + */ +#define RCT_EXPORT_MODULE_NO_LOAD(js_name, objc_name) \ +RCT_EXTERN void RCTRegisterModule(Class); \ ++ (NSString *)moduleName { return @#js_name; } \ +__attribute__((constructor)) static void \ +RCT_CONCAT(initialize_, objc_name)() { RCTRegisterModule([objc_name class]); } + /** * To improve startup performance users may want to generate their module lists * at build time and hook the delegate to merge with the runtime list. This @@ -250,7 +261,7 @@ RCT_EXTERN void RCTRegisterModule(Class); \ @interface objc_name (RCTExternModule) \ @end \ @implementation objc_name (RCTExternModule) \ - RCT_EXPORT_MODULE(js_name) + RCT_EXPORT_MODULE_NO_LOAD(js_name, objc_name) /** * Use this macro in accordance with RCT_EXTERN_MODULE to export methods diff --git a/React/CxxBridge/RCTCxxBridge.mm b/React/CxxBridge/RCTCxxBridge.mm index 6f0aeed7cb6ac4..163fe44fd1de9f 100644 --- a/React/CxxBridge/RCTCxxBridge.mm +++ b/React/CxxBridge/RCTCxxBridge.mm @@ -210,6 +210,11 @@ - (void) setRCTTurboModuleLookupDelegate:(id)turbo return _jsMessageThread; } +- (std::weak_ptr)reactInstance +{ + return _reactInstance; +} + - (BOOL)isInspectable { return _reactInstance ? _reactInstance->isInspectable() : NO; diff --git a/React/DevSupport/RCTDevMenu.m b/React/DevSupport/RCTDevMenu.m index d631fa5dc7525c..140be91dd98fa5 100644 --- a/React/DevSupport/RCTDevMenu.m +++ b/React/DevSupport/RCTDevMenu.m @@ -204,6 +204,7 @@ - (void)setDefaultJSBundle { // Add built-in items __weak RCTBridge *bridge = _bridge; __weak RCTDevSettings *devSettings = _bridge.devSettings; + __weak RCTDevMenu *weakSelf = self; [items addObject:[RCTDevMenuItem buttonItemWithTitle:@"Reload" handler:^{ [bridge reload]; @@ -296,7 +297,7 @@ - (void)setDefaultJSBundle { textField.placeholder = @"index"; }]; [alertController addAction:[UIAlertAction actionWithTitle:@"Use bundled JS" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { - [self setDefaultJSBundle]; + [weakSelf setDefaultJSBundle]; }]]; [alertController addAction:[UIAlertAction actionWithTitle:@"Use packager location" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { NSArray * textfields = alertController.textFields; @@ -308,7 +309,7 @@ - (void)setDefaultJSBundle { bundleRoot = @"index"; } if(ipTextField.text.length == 0 && portTextField.text.length == 0) { - [self setDefaultJSBundle]; + [weakSelf setDefaultJSBundle]; return; } NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; @@ -319,8 +320,11 @@ - (void)setDefaultJSBundle { } [RCTBundleURLProvider sharedSettings].jsLocation = [NSString stringWithFormat:@"%@:%d", ipTextField.text, portNumber.intValue]; - self->_bridge.bundleURL = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:bundleRoot fallbackResource:nil]; - [self->_bridge reload]; + __strong RCTBridge *strongBridge = bridge; + if (strongBridge) { + strongBridge.bundleURL = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:bundleRoot fallbackResource:nil]; + [strongBridge reload]; + } }]]; [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(__unused UIAlertAction *action) { return; diff --git a/React/DevSupport/RCTInspectorDevServerHelper.mm b/React/DevSupport/RCTInspectorDevServerHelper.mm index aff1cb6fb81a88..0f1ada9347ad60 100644 --- a/React/DevSupport/RCTInspectorDevServerHelper.mm +++ b/React/DevSupport/RCTInspectorDevServerHelper.mm @@ -32,7 +32,7 @@ static NSURL *getInspectorDeviceUrl(NSURL *bundleURL) { - NSNumber *inspectorProxyPort = @8082; + NSNumber *inspectorProxyPort = @8081; NSString *escapedDeviceName = [[[UIDevice currentDevice] name] stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]; NSString *escapedAppName = [[[NSBundle mainBundle] bundleIdentifier] stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]; return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/inspector/device?name=%@&app=%@", diff --git a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index fa0a7d2b06649e..f01a8dfce708a2 100644 --- a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -29,10 +29,14 @@ - (instancetype)initWithFrame:(CGRect)frame static const auto defaultProps = std::make_shared(); _props = defaultProps; } - return self; } +- (facebook::react::SharedProps)props +{ + return _props; +} + - (void)setContentView:(UIView *)contentView { if (_contentView) { @@ -356,11 +360,15 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) - (void)invalidateLayer { + CALayer *layer = self.layer; + + if (CGSizeEqualToSize(layer.bounds.size, CGSizeZero)) { + return; + } + const auto borderMetrics = _props->resolveBorderMetrics(_layoutMetrics.layoutDirection == LayoutDirection::RightToLeft); - CALayer *layer = self.layer; - // Stage 1. Shadow Path BOOL layerHasShadow = layer.shadowOpacity > 0 && CGColorGetAlpha(layer.shadowColor) > 0; if (layerHasShadow) { diff --git a/React/Fabric/Mounting/RCTComponentViewProtocol.h b/React/Fabric/Mounting/RCTComponentViewProtocol.h index f049b8f33a66c6..fe23ee305f6ce8 100644 --- a/React/Fabric/Mounting/RCTComponentViewProtocol.h +++ b/React/Fabric/Mounting/RCTComponentViewProtocol.h @@ -85,6 +85,11 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)prepareForRecycle; +/** + * Read the last props used to update the view. + */ +- (facebook::react::SharedProps)props; + @end NS_ASSUME_NONNULL_END diff --git a/React/Fabric/Mounting/RCTMountingManager.h b/React/Fabric/Mounting/RCTMountingManager.h index 21e3a8c191d504..19d945abb67aae 100644 --- a/React/Fabric/Mounting/RCTMountingManager.h +++ b/React/Fabric/Mounting/RCTMountingManager.h @@ -9,6 +9,7 @@ #import #import +#import #import #import #import @@ -40,8 +41,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)optimisticallyCreateComponentViewWithComponentHandle:(facebook::react::ComponentHandle)componentHandle; - (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag - oldProps:(facebook::react::SharedProps)oldProps - newProps:(facebook::react::SharedProps)newProps; + changedProps:(NSDictionary *)props + componentDescriptor:(const facebook::react::ComponentDescriptor &)componentDescriptor; + @end NS_ASSUME_NONNULL_END diff --git a/React/Fabric/Mounting/RCTMountingManager.mm b/React/Fabric/Mounting/RCTMountingManager.mm index 8bcc71cc454d8d..6ae40799291d48 100644 --- a/React/Fabric/Mounting/RCTMountingManager.mm +++ b/React/Fabric/Mounting/RCTMountingManager.mm @@ -8,8 +8,10 @@ #import "RCTMountingManager.h" #import +#import #import #import +#import #import #import "RCTComponentViewProtocol.h" @@ -40,7 +42,7 @@ - (instancetype)init return self; } -- (void)performTransactionWithMutations:(facebook::react::ShadowViewMutationList)mutations rootTag:(ReactTag)rootTag +- (void)performTransactionWithMutations:(ShadowViewMutationList)mutations rootTag:(ReactTag)rootTag { NSMutableArray *mountItems; @@ -193,9 +195,12 @@ - (void)_performMountItems:(NSArray *)mountItems rootTag:( } - (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag - oldProps:(SharedProps)oldProps - newProps:(SharedProps)newProps + changedProps:(NSDictionary *)props + componentDescriptor:(const ComponentDescriptor &)componentDescriptor { + UIView *componentView = [self->_componentViewRegistry componentViewByTag:reactTag]; + SharedProps oldProps = [componentView props]; + SharedProps newProps = componentDescriptor.cloneProps(oldProps, RawProps(convertIdToFollyDynamic(props))); RCTUpdatePropsMountItem *mountItem = [[RCTUpdatePropsMountItem alloc] initWithTag:reactTag oldProps:oldProps newProps:newProps]; diff --git a/React/Fabric/Mounting/UIView+ComponentViewProtocol.h b/React/Fabric/Mounting/UIView+ComponentViewProtocol.h index 7ea8dc5d7d7f5e..d18e83366ab0ca 100644 --- a/React/Fabric/Mounting/UIView+ComponentViewProtocol.h +++ b/React/Fabric/Mounting/UIView+ComponentViewProtocol.h @@ -34,6 +34,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)prepareForRecycle; +- (facebook::react::SharedProps)props; + @end NS_ASSUME_NONNULL_END diff --git a/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm b/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm index 428a7844cd8c19..54fb66d9c81050 100644 --- a/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm +++ b/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm @@ -87,4 +87,10 @@ - (void)prepareForRecycle // Default implementation does nothing. } +- (facebook::react::SharedProps)props +{ + RCTAssert(NO, @"props access should be implemented by RCTViewComponentView."); + return nullptr; +} + @end diff --git a/React/Fabric/RCTSurfacePresenter.mm b/React/Fabric/RCTSurfacePresenter.mm index 3002e3ad729fbe..56847863d642de 100644 --- a/React/Fabric/RCTSurfacePresenter.mm +++ b/React/Fabric/RCTSurfacePresenter.mm @@ -75,7 +75,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge config:(std::shared_ptr(); } - + _observers = [NSMutableArray array]; _isFirstJavaScriptLoaded = YES; @@ -179,17 +179,9 @@ - (BOOL)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag props:(NSDictiona } ComponentHandle handle = [[componentView class] componentHandle]; const facebook::react::ComponentDescriptor &componentDescriptor = [self._scheduler getComponentDescriptor:handle]; - - // Note: we use an empty object for `oldProps` to rely on the diffing algorithm internal to the - // RCTComponentViewProtocol::updateProps method. If there is a bug in that diffing, some props - // could get reset. One way around this would be to require all RCTComponentViewProtocol - // implementations to expose their current props so we could clone them, but that could be - // problematic for threading and other reasons. - facebook::react::SharedProps newProps = - componentDescriptor.cloneProps(nullptr, RawProps(convertIdToFollyDynamic(props))); - facebook::react::SharedProps oldProps = componentDescriptor.cloneProps(nullptr, RawProps(folly::dynamic::object())); - - [self->_mountingManager synchronouslyUpdateViewOnUIThread:tag oldProps:oldProps newProps:newProps]; + [self->_mountingManager synchronouslyUpdateViewOnUIThread:tag + changedProps:props + componentDescriptor:componentDescriptor]; return YES; } @@ -228,7 +220,7 @@ - (SharedContextContainer)contextContainer // Make sure initializeBridge completed messageQueueThread->runOnQueueSync([] {}); } - + auto runtime = (facebook::jsi::Runtime *)((RCTCxxBridge *)_batchedBridge).runtime; RuntimeExecutor runtimeExecutor = diff --git a/React/Fabric/RCTSurfaceRegistry.mm b/React/Fabric/RCTSurfaceRegistry.mm index 76bbf8f86fc67f..0eae535f40bae6 100644 --- a/React/Fabric/RCTSurfaceRegistry.mm +++ b/React/Fabric/RCTSurfaceRegistry.mm @@ -8,11 +8,15 @@ #import "RCTSurfaceRegistry.h" #import +#import +#import #import +using namespace facebook; + @implementation RCTSurfaceRegistry { - std::mutex _mutex; + better::shared_mutex _mutex; NSMapTable *_registry; } @@ -28,13 +32,13 @@ - (instancetype)init - (void)enumerateWithBlock:(RCTSurfaceEnumeratorBlock)block { - std::lock_guard lock(_mutex); + std::shared_lock lock(_mutex); block([_registry objectEnumerator]); } - (void)registerSurface:(RCTFabricSurface *)surface { - std::lock_guard lock(_mutex); + std::unique_lock lock(_mutex); ReactTag rootTag = surface.rootViewTag.integerValue; [_registry setObject:surface forKey:(__bridge id)(void *)rootTag]; @@ -42,7 +46,7 @@ - (void)registerSurface:(RCTFabricSurface *)surface - (void)unregisterSurface:(RCTFabricSurface *)surface { - std::lock_guard lock(_mutex); + std::unique_lock lock(_mutex); ReactTag rootTag = surface.rootViewTag.integerValue; [_registry removeObjectForKey:(__bridge id)(void *)rootTag]; @@ -50,7 +54,7 @@ - (void)unregisterSurface:(RCTFabricSurface *)surface - (RCTFabricSurface *)surfaceForRootTag:(ReactTag)rootTag { - std::lock_guard lock(_mutex); + std::shared_lock lock(_mutex); return [_registry objectForKey:(__bridge id)(void *)rootTag]; } diff --git a/React/React-Core.podspec b/React/React-Core.podspec index 0643e53af48e22..07b250db4c6b72 100644 --- a/React/React-Core.podspec +++ b/React/React-Core.podspec @@ -41,8 +41,6 @@ Pod::Spec.new do |s| "Views/RCTRefreshControl*", "Views/RCTSlider*", "Views/RCTSwitch*", - "Views/RCTWebView*", - "Views/RCTWKWebView*" s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags s.header_dir = "React" s.framework = "JavaScriptCore" diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 6c71576abda507..18f194eaf790aa 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -106,8 +106,6 @@ 13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */; }; 13BB3D021BECD54500932C10 /* RCTImageSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BB3D011BECD54500932C10 /* RCTImageSource.m */; }; 13BCE8091C99CB9D00DD7AAD /* RCTRootShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BCE8081C99CB9D00DD7AAD /* RCTRootShadowView.m */; }; - 13C156051AB1A2840079392D /* RCTWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156021AB1A2840079392D /* RCTWebView.m */; }; - 13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156041AB1A2840079392D /* RCTWebViewManager.m */; }; 13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */ = {isa = PBXBuildFile; fileRef = 13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */; }; 13D033631C1837FE0021DC29 /* RCTClipboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D033621C1837FE0021DC29 /* RCTClipboard.m */; }; 13D9FEEB1CDCCECF00158BD7 /* RCTEventEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D9FEEA1CDCCECF00158BD7 /* RCTEventEmitter.m */; }; @@ -546,8 +544,6 @@ 3D80D9921DF6FA890028D040 /* RCTTextDecorationLineType.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = E3BBC8EB1ADE6F47001BBD81 /* RCTTextDecorationLineType.h */; }; 3D80D9931DF6FA890028D040 /* RCTView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13E0674F1A70F44B002CDEE1 /* RCTView.h */; }; 3D80D9951DF6FA890028D040 /* RCTViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13E0674D1A70F44B002CDEE1 /* RCTViewManager.h */; }; - 3D80D9961DF6FA890028D040 /* RCTWebView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13C156011AB1A2840079392D /* RCTWebView.h */; }; - 3D80D9971DF6FA890028D040 /* RCTWebViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13C156031AB1A2840079392D /* RCTWebViewManager.h */; }; 3D80D9981DF6FA890028D040 /* RCTWrapperViewController.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13B080231A694A8400A75B9A /* RCTWrapperViewController.h */; }; 3D80D99A1DF6FA890028D040 /* UIView+React.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13E067531A70F44B002CDEE1 /* UIView+React.h */; }; 3D80DA191DF820620028D040 /* RCTImageLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D1FA0831DE4F3A000E03CC6 /* RCTImageLoader.h */; }; @@ -644,8 +640,6 @@ 3D80DA8C1DF820620028D040 /* RCTTextDecorationLineType.h in Headers */ = {isa = PBXBuildFile; fileRef = E3BBC8EB1ADE6F47001BBD81 /* RCTTextDecorationLineType.h */; }; 3D80DA8D1DF820620028D040 /* RCTView.h in Headers */ = {isa = PBXBuildFile; fileRef = 13E0674F1A70F44B002CDEE1 /* RCTView.h */; }; 3D80DA8F1DF820620028D040 /* RCTViewManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 13E0674D1A70F44B002CDEE1 /* RCTViewManager.h */; }; - 3D80DA901DF820620028D040 /* RCTWebView.h in Headers */ = {isa = PBXBuildFile; fileRef = 13C156011AB1A2840079392D /* RCTWebView.h */; }; - 3D80DA911DF820620028D040 /* RCTWebViewManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 13C156031AB1A2840079392D /* RCTWebViewManager.h */; }; 3D80DA921DF820620028D040 /* RCTWrapperViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 13B080231A694A8400A75B9A /* RCTWrapperViewController.h */; }; 3D80DA931DF820620028D040 /* UIView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F15A171B7CC46900F10295 /* UIView+Private.h */; }; 3D80DA941DF820620028D040 /* UIView+React.h in Headers */ = {isa = PBXBuildFile; fileRef = 13E067531A70F44B002CDEE1 /* UIView+React.h */; }; @@ -757,8 +751,6 @@ 3DA982301E5B0F7F004F2374 /* RCTTVView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 130443D61E401AD800D93A67 /* RCTTVView.h */; }; 3DA982311E5B0F7F004F2374 /* RCTView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13E0674F1A70F44B002CDEE1 /* RCTView.h */; }; 3DA982331E5B0F7F004F2374 /* RCTViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13E0674D1A70F44B002CDEE1 /* RCTViewManager.h */; }; - 3DA982341E5B0F7F004F2374 /* RCTWebView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13C156011AB1A2840079392D /* RCTWebView.h */; }; - 3DA982351E5B0F7F004F2374 /* RCTWebViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13C156031AB1A2840079392D /* RCTWebViewManager.h */; }; 3DA982361E5B0F7F004F2374 /* RCTWrapperViewController.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13B080231A694A8400A75B9A /* RCTWrapperViewController.h */; }; 3DA982381E5B0F7F004F2374 /* UIView+React.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13E067531A70F44B002CDEE1 /* UIView+React.h */; }; 3DA982391E5B0F8A004F2374 /* UIView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F15A171B7CC46900F10295 /* UIView+Private.h */; }; @@ -804,10 +796,6 @@ 4F56C93822167A4800DB9F3F /* jsilib.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F56C93722167A4800DB9F3F /* jsilib.h */; }; 4F56C93922167A4D00DB9F3F /* jsilib.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 4F56C93722167A4800DB9F3F /* jsilib.h */; }; 4F56C93A2216A3B700DB9F3F /* jsilib.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 4F56C93722167A4800DB9F3F /* jsilib.h */; }; - 50E98FEA21460B0D00CD9289 /* RCTWKWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 50E98FE621460B0D00CD9289 /* RCTWKWebViewManager.m */; }; - 50E98FEB21460B0D00CD9289 /* RCTWKWebView.h in Headers */ = {isa = PBXBuildFile; fileRef = 50E98FE721460B0D00CD9289 /* RCTWKWebView.h */; }; - 50E98FEC21460B0D00CD9289 /* RCTWKWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 50E98FE821460B0D00CD9289 /* RCTWKWebView.m */; }; - 50E98FED21460B0D00CD9289 /* RCTWKWebViewManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 50E98FE921460B0D00CD9289 /* RCTWKWebViewManager.h */; }; 5335D5411FE81A4700883D58 /* RCTShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5335D5401FE81A4700883D58 /* RCTShadowView.m */; }; 53438962203905BB008E0CB3 /* YGLayout.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5343895E203905B6008E0CB3 /* YGLayout.cpp */; }; 53438963203905BC008E0CB3 /* YGLayout.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5343895E203905B6008E0CB3 /* YGLayout.cpp */; }; @@ -1436,8 +1424,6 @@ 3DA982301E5B0F7F004F2374 /* RCTTVView.h in Copy Headers */, 3DA982311E5B0F7F004F2374 /* RCTView.h in Copy Headers */, 3DA982331E5B0F7F004F2374 /* RCTViewManager.h in Copy Headers */, - 3DA982341E5B0F7F004F2374 /* RCTWebView.h in Copy Headers */, - 3DA982351E5B0F7F004F2374 /* RCTWebViewManager.h in Copy Headers */, 3DA982361E5B0F7F004F2374 /* RCTWrapperViewController.h in Copy Headers */, 3DA982381E5B0F7F004F2374 /* UIView+React.h in Copy Headers */, 3DA981BF1E5B0F29004F2374 /* RCTAssert.h in Copy Headers */, @@ -1669,8 +1655,6 @@ 3D80D9921DF6FA890028D040 /* RCTTextDecorationLineType.h in Copy Headers */, 3D80D9931DF6FA890028D040 /* RCTView.h in Copy Headers */, 3D80D9951DF6FA890028D040 /* RCTViewManager.h in Copy Headers */, - 3D80D9961DF6FA890028D040 /* RCTWebView.h in Copy Headers */, - 3D80D9971DF6FA890028D040 /* RCTWebViewManager.h in Copy Headers */, 3D80D9981DF6FA890028D040 /* RCTWrapperViewController.h in Copy Headers */, 3D80D99A1DF6FA890028D040 /* UIView+React.h in Copy Headers */, ); @@ -1936,10 +1920,6 @@ 13BB3D011BECD54500932C10 /* RCTImageSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageSource.m; sourceTree = ""; }; 13BCE8071C99CB9D00DD7AAD /* RCTRootShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootShadowView.h; sourceTree = ""; }; 13BCE8081C99CB9D00DD7AAD /* RCTRootShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootShadowView.m; sourceTree = ""; }; - 13C156011AB1A2840079392D /* RCTWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebView.h; sourceTree = ""; }; - 13C156021AB1A2840079392D /* RCTWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebView.m; sourceTree = ""; }; - 13C156031AB1A2840079392D /* RCTWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewManager.h; sourceTree = ""; }; - 13C156041AB1A2840079392D /* RCTWebViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewManager.m; sourceTree = ""; }; 13C325261AA63B6A0048765F /* RCTAutoInsetsProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAutoInsetsProtocol.h; sourceTree = ""; }; 13C325281AA63B6A0048765F /* RCTComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTComponent.h; sourceTree = ""; }; 13CC8A801B17642100940AE7 /* RCTBorderDrawing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBorderDrawing.h; sourceTree = ""; }; @@ -2067,10 +2047,6 @@ 3EDCA8A31D3591E700450C31 /* RCTErrorInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTErrorInfo.h; sourceTree = ""; }; 3EDCA8A41D3591E700450C31 /* RCTErrorInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTErrorInfo.m; sourceTree = ""; }; 4F56C93722167A4800DB9F3F /* jsilib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jsilib.h; sourceTree = ""; }; - 50E98FE621460B0D00CD9289 /* RCTWKWebViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWKWebViewManager.m; sourceTree = ""; }; - 50E98FE721460B0D00CD9289 /* RCTWKWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWKWebView.h; sourceTree = ""; }; - 50E98FE821460B0D00CD9289 /* RCTWKWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWKWebView.m; sourceTree = ""; }; - 50E98FE921460B0D00CD9289 /* RCTWKWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWKWebViewManager.h; sourceTree = ""; }; 5335D5401FE81A4700883D58 /* RCTShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowView.m; sourceTree = ""; }; 5343895E203905B6008E0CB3 /* YGLayout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = YGLayout.cpp; sourceTree = ""; }; 5343895F203905B6008E0CB3 /* YGLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YGLayout.h; sourceTree = ""; }; @@ -2552,10 +2528,6 @@ 13B07FF31A6947C200A75B9A /* Views */ = { isa = PBXGroup; children = ( - 50E98FE721460B0D00CD9289 /* RCTWKWebView.h */, - 50E98FE821460B0D00CD9289 /* RCTWKWebView.m */, - 50E98FE921460B0D00CD9289 /* RCTWKWebViewManager.h */, - 50E98FE621460B0D00CD9289 /* RCTWKWebViewManager.m */, B95154301D1B34B200FE7B80 /* RCTActivityIndicatorView.h */, B95154311D1B34B200FE7B80 /* RCTActivityIndicatorView.m */, 13B080181A69489C00A75B9A /* RCTActivityIndicatorViewManager.h */, @@ -2630,10 +2602,6 @@ 13E067501A70F44B002CDEE1 /* RCTView.m */, 13E0674D1A70F44B002CDEE1 /* RCTViewManager.h */, 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */, - 13C156011AB1A2840079392D /* RCTWebView.h */, - 13C156021AB1A2840079392D /* RCTWebView.m */, - 13C156031AB1A2840079392D /* RCTWebViewManager.h */, - 13C156041AB1A2840079392D /* RCTWebViewManager.m */, 13B080231A694A8400A75B9A /* RCTWrapperViewController.h */, 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */, 59D031E41F8353D3008361F0 /* SafeAreaView */, @@ -3421,7 +3389,6 @@ 3D80DA2A1DF820620028D040 /* RCTErrorCustomizer.h in Headers */, 3D80DA2B1DF820620028D040 /* RCTErrorInfo.h in Headers */, 1384E2081E806D4E00545659 /* RCTNativeModule.h in Headers */, - 50E98FED21460B0D00CD9289 /* RCTWKWebViewManager.h in Headers */, 3D7BFD2D1EA8E3FA008DFB7A /* RCTReconnectingWebSocket.h in Headers */, 3D80DA2C1DF820620028D040 /* RCTEventDispatcher.h in Headers */, 3D80DA2D1DF820620028D040 /* RCTFrameUpdate.h in Headers */, @@ -3431,7 +3398,6 @@ 599FAA3A1FB274980058CCF6 /* RCTSurfaceDelegate.h in Headers */, 3D80DA301DF820620028D040 /* RCTJavaScriptExecutor.h in Headers */, 3DCE53281FEAB23100613583 /* RCTDatePicker.h in Headers */, - 50E98FEB21460B0D00CD9289 /* RCTWKWebView.h in Headers */, 3D80DA311DF820620028D040 /* RCTJavaScriptLoader.h in Headers */, 130E3D881E6A082100ACE484 /* RCTDevSettings.h in Headers */, 3D80DA321DF820620028D040 /* RCTJSStackFrame.h in Headers */, @@ -3551,8 +3517,6 @@ 3D80DA8F1DF820620028D040 /* RCTViewManager.h in Headers */, 13134CA01E296B2A00B9F3CB /* RCTCxxUtils.h in Headers */, 599FAA4A1FB274980058CCF6 /* RCTSurfaceView.h in Headers */, - 3D80DA901DF820620028D040 /* RCTWebView.h in Headers */, - 3D80DA911DF820620028D040 /* RCTWebViewManager.h in Headers */, 3D80DA921DF820620028D040 /* RCTWrapperViewController.h in Headers */, 3D80DA931DF820620028D040 /* UIView+Private.h in Headers */, 3D80DA941DF820620028D040 /* UIView+React.h in Headers */, @@ -4471,7 +4435,6 @@ C60128AD1F3D1258009DF9FF /* RCTCxxConvert.m in Sources */, 3DCE53291FEAB23100613583 /* RCTDatePicker.m in Sources */, 0EEEA8DF2239002200A8C82D /* RCTSurfacePresenterStub.m in Sources */, - 50E98FEA21460B0D00CD9289 /* RCTWKWebViewManager.m in Sources */, 83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */, 59EB6DBD1EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.mm in Sources */, 59E604A21FE9CCE300BD90C5 /* RCTScrollContentViewManager.m in Sources */, @@ -4546,13 +4509,10 @@ 130443C61E401A8C00D93A67 /* RCTConvert+Transform.m in Sources */, 191E3EC11C29DC3800C180A6 /* RCTRefreshControl.m in Sources */, 3DCE532B1FEAB23100613583 /* RCTDatePickerManager.m in Sources */, - 13C156051AB1A2840079392D /* RCTWebView.m in Sources */, 83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */, - 50E98FEC21460B0D00CD9289 /* RCTWKWebView.m in Sources */, 590D7BFF1EBD458B00D8A370 /* RCTShadowView+Layout.m in Sources */, 5335D5411FE81A4700883D58 /* RCTShadowView.m in Sources */, 66CD94B31F1045E700CB3C7C /* RCTMaskedView.m in Sources */, - 13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */, 58114A161AAE854800E7D092 /* RCTPicker.m in Sources */, 83A1FE8C1B62640A00BE0E65 /* RCTModalHostView.m in Sources */, 5925356A20084D0600DD584B /* RCTSurfaceSizeMeasureMode.mm in Sources */, diff --git a/React/Views/RCTRefreshControl.m b/React/Views/RCTRefreshControl.m index 24090c83e41011..26856be07094e1 100644 --- a/React/Views/RCTRefreshControl.m +++ b/React/Views/RCTRefreshControl.m @@ -76,12 +76,12 @@ - (void)beginRefreshingProgrammatically - (void)endRefreshingProgrammatically { - // The contentOffset of the scrollview MUST be greater than 0 before calling + // The contentOffset of the scrollview MUST be greater than the contentInset before calling // endRefreshing otherwise the next pull to refresh will not work properly. UIScrollView *scrollView = (UIScrollView *)self.superview; - if (_refreshingProgrammatically && scrollView.contentOffset.y < 0) { + if (_refreshingProgrammatically && scrollView.contentOffset.y < -scrollView.contentInset.top) { UInt64 endRefreshingTimestamp = _currentRefreshingStateTimestamp; - CGPoint offset = {scrollView.contentOffset.x, 0}; + CGPoint offset = {scrollView.contentOffset.x, -scrollView.contentInset.top}; [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState diff --git a/React/Views/RCTWKWebView.h b/React/Views/RCTWKWebView.h deleted file mode 100644 index 04b6e4e4cc50c1..00000000000000 --- a/React/Views/RCTWKWebView.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - * 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 - -@class RCTWKWebView; - -@protocol RCTWKWebViewDelegate - -- (BOOL)webView:(RCTWKWebView *)webView -shouldStartLoadForRequest:(NSMutableDictionary *)request - withCallback:(RCTDirectEventBlock)callback; - -@end - -@interface RCTWKWebView : RCTView - -@property (nonatomic, weak) id delegate; -@property (nonatomic, copy) NSDictionary *source; -@property (nonatomic, assign) BOOL messagingEnabled; -@property (nonatomic, copy) NSString *injectedJavaScript; -@property (nonatomic, assign) BOOL scrollEnabled; -@property (nonatomic, assign) CGFloat decelerationRate; -@property (nonatomic, assign) BOOL allowsInlineMediaPlayback; -@property (nonatomic, assign) BOOL bounces; -@property (nonatomic, assign) BOOL mediaPlaybackRequiresUserAction; -#if WEBKIT_IOS_10_APIS_AVAILABLE -@property (nonatomic, assign) WKDataDetectorTypes dataDetectorTypes; -#endif -@property (nonatomic, assign) UIEdgeInsets contentInset; -@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; - -+ (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential; -- (void)postMessage:(NSString *)message; -- (void)injectJavaScript:(NSString *)script; -- (void)goForward; -- (void)goBack; -- (void)reload; -- (void)stopLoading; - -@end diff --git a/React/Views/RCTWKWebView.m b/React/Views/RCTWKWebView.m deleted file mode 100644 index 2171d42fb14d5b..00000000000000 --- a/React/Views/RCTWKWebView.m +++ /dev/null @@ -1,435 +0,0 @@ -/** - * 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 "RCTWKWebView.h" -#import -#import "RCTAutoInsetsProtocol.h" - -static NSString *const MessageHanderName = @"ReactNative"; -static NSURLCredential* clientAuthenticationCredential; - - -@interface RCTWKWebView () -@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart; -@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish; -@property (nonatomic, copy) RCTDirectEventBlock onLoadingError; -@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest; -@property (nonatomic, copy) RCTDirectEventBlock onMessage; -@property (nonatomic, copy) WKWebView *webView; -@end - -@implementation RCTWKWebView -{ - UIColor * _savedBackgroundColor; -} - -- (void)dealloc -{ - -} - -/** - * See https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/DisplayWebContent/Tasks/WebKitAvail.html. - */ -+ (BOOL)dynamicallyLoadWebKitIfAvailable -{ - static BOOL _webkitAvailable=NO; - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - NSBundle *webKitBundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/WebKit.framework"]; - if (webKitBundle) { - _webkitAvailable = [webKitBundle load]; - } - }); - - return _webkitAvailable; -} - - -- (instancetype)initWithFrame:(CGRect)frame -{ - if ((self = [super initWithFrame:frame])) { - super.backgroundColor = [UIColor clearColor]; - _bounces = YES; - _scrollEnabled = YES; - _automaticallyAdjustContentInsets = YES; - _contentInset = UIEdgeInsetsZero; - } - return self; -} - -- (void)didMoveToWindow -{ - if (self.window != nil) { - if (![[self class] dynamicallyLoadWebKitIfAvailable]) { - return; - }; - - WKWebViewConfiguration *wkWebViewConfig = [WKWebViewConfiguration new]; - wkWebViewConfig.userContentController = [WKUserContentController new]; - [wkWebViewConfig.userContentController addScriptMessageHandler: self name: MessageHanderName]; - wkWebViewConfig.allowsInlineMediaPlayback = _allowsInlineMediaPlayback; -#if WEBKIT_IOS_10_APIS_AVAILABLE - wkWebViewConfig.mediaTypesRequiringUserActionForPlayback = _mediaPlaybackRequiresUserAction - ? WKAudiovisualMediaTypeAll - : WKAudiovisualMediaTypeNone; - wkWebViewConfig.dataDetectorTypes = _dataDetectorTypes; -#else - wkWebViewConfig.mediaPlaybackRequiresUserAction = _mediaPlaybackRequiresUserAction; -#endif - - _webView = [[WKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig]; - _webView.scrollView.delegate = self; - _webView.UIDelegate = self; - _webView.navigationDelegate = self; - _webView.scrollView.scrollEnabled = _scrollEnabled; - _webView.scrollView.bounces = _bounces; - -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ - if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) { - _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; - } -#endif - - [self addSubview:_webView]; - - [self visitSource]; - } -} - -- (void)setBackgroundColor:(UIColor *)backgroundColor -{ - _savedBackgroundColor = backgroundColor; - if (_webView == nil) { - return; - } - - CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); - self.opaque = _webView.opaque = (alpha == 1.0); - _webView.scrollView.backgroundColor = backgroundColor; - _webView.backgroundColor = backgroundColor; -} - -/** - * This method is called whenever JavaScript running within the web view calls: - * - window.webkit.messageHandlers.[MessageHanderName].postMessage - */ -- (void)userContentController:(__unused WKUserContentController *)userContentController - didReceiveScriptMessage:(WKScriptMessage *)message -{ - if (_onMessage != nil) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{@"data": message.body}]; - _onMessage(event); - } -} - -- (void)setSource:(NSDictionary *)source -{ - if (![_source isEqualToDictionary:source]) { - _source = [source copy]; - - if (_webView != nil) { - [self visitSource]; - } - } -} - -- (void)setContentInset:(UIEdgeInsets)contentInset -{ - _contentInset = contentInset; - [RCTView autoAdjustInsetsForView:self - withScrollView:_webView.scrollView - updateOffset:NO]; -} - -- (void)refreshContentInset -{ - [RCTView autoAdjustInsetsForView:self - withScrollView:_webView.scrollView - updateOffset:YES]; -} - -- (void)visitSource -{ - // Check for a static html source first - NSString *html = [RCTConvert NSString:_source[@"html"]]; - if (html) { - NSURL *baseURL = [RCTConvert NSURL:_source[@"baseUrl"]]; - if (!baseURL) { - baseURL = [NSURL URLWithString:@"about:blank"]; - } - [_webView loadHTMLString:html baseURL:baseURL]; - return; - } - - NSURLRequest *request = [RCTConvert NSURLRequest:_source]; - // Because of the way React works, as pages redirect, we actually end up - // passing the redirect urls back here, so we ignore them if trying to load - // the same url. We'll expose a call to 'reload' to allow a user to load - // the existing page. - if ([request.URL isEqual:_webView.URL]) { - return; - } - if (!request.URL) { - // Clear the webview - [_webView loadHTMLString:@"" baseURL:nil]; - return; - } - [_webView loadRequest:request]; -} - - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - scrollView.decelerationRate = _decelerationRate; -} - -- (void)setScrollEnabled:(BOOL)scrollEnabled -{ - _scrollEnabled = scrollEnabled; - _webView.scrollView.scrollEnabled = scrollEnabled; -} - -- (void)postMessage:(NSString *)message -{ - NSDictionary *eventInitDict = @{@"data": message}; - NSString *source = [NSString - stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));", - RCTJSONStringify(eventInitDict, NULL) - ]; - [self evaluateJS: source thenCall: nil]; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - // Ensure webview takes the position and dimensions of RCTWKWebView - _webView.frame = self.bounds; -} - -- (NSMutableDictionary *)baseEvent -{ - NSDictionary *event = @{ - @"url": _webView.URL.absoluteString ?: @"", - @"title": _webView.title, - @"loading" : @(_webView.loading), - @"canGoBack": @(_webView.canGoBack), - @"canGoForward" : @(_webView.canGoForward) - }; - return [[NSMutableDictionary alloc] initWithDictionary: event]; -} - -#pragma mark - WKNavigationDelegate methods - -/** - * Decides whether to allow or cancel a navigation. - * @see https://fburl.com/42r9fxob - */ -- (void) webView:(__unused WKWebView *)webView - decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction - decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler -{ - static NSDictionary *navigationTypes; - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - navigationTypes = @{ - @(WKNavigationTypeLinkActivated): @"click", - @(WKNavigationTypeFormSubmitted): @"formsubmit", - @(WKNavigationTypeBackForward): @"backforward", - @(WKNavigationTypeReload): @"reload", - @(WKNavigationTypeFormResubmitted): @"formresubmit", - @(WKNavigationTypeOther): @"other", - }; - }); - - WKNavigationType navigationType = navigationAction.navigationType; - NSURLRequest *request = navigationAction.request; - - if (_onShouldStartLoadWithRequest) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": navigationTypes[@(navigationType)] - }]; - if (![self.delegate webView:self - shouldStartLoadForRequest:event - withCallback:_onShouldStartLoadWithRequest]) { - decisionHandler(WKNavigationActionPolicyCancel); - return; - } - } - - if (_onLoadingStart) { - // We have this check to filter out iframe requests and whatnot - BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; - if (isTopFrame) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": navigationTypes[@(navigationType)] - }]; - _onLoadingStart(event); - } - } - - // Allow all navigation by default - decisionHandler(WKNavigationActionPolicyAllow); -} - -/** - * Called when an error occurs while the web view is loading content. - * @see https://fburl.com/km6vqenw - */ -- (void) webView:(__unused WKWebView *)webView - didFailProvisionalNavigation:(__unused WKNavigation *)navigation - withError:(NSError *)error -{ - if (_onLoadingError) { - if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { - // NSURLErrorCancelled is reported when a page has a redirect OR if you load - // a new URL in the WebView before the previous one came back. We can just - // ignore these since they aren't real errors. - // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os - return; - } - - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary:@{ - @"didFailProvisionalNavigation": @YES, - @"domain": error.domain, - @"code": @(error.code), - @"description": error.localizedDescription, - }]; - _onLoadingError(event); - } - - [self setBackgroundColor: _savedBackgroundColor]; -} - -+ (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential { - clientAuthenticationCredential = credential; -} - -- (void) webView:(__unused WKWebView *)webView - didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge - completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable))completionHandler -{ - if (!clientAuthenticationCredential) { - completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); - return; - } - if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) { - completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential); - } else { - completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); -} -} - -- (void)evaluateJS:(NSString *)js - thenCall: (void (^)(NSString*)) callback -{ - [self.webView evaluateJavaScript: js completionHandler: ^(id result, NSError *error) { - if (error == nil && callback != nil) { - callback([NSString stringWithFormat:@"%@", result]); - } - }]; -} - - -/** - * Called when the navigation is complete. - * @see https://fburl.com/rtys6jlb - */ -- (void) webView:(__unused WKWebView *)webView - didFinishNavigation:(__unused WKNavigation *)navigation -{ - if (_messagingEnabled) { - #if RCT_DEV - - // Implementation inspired by Lodash.isNative. - NSString *isPostMessageNative = @"String(String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage'))"; - [self evaluateJS: isPostMessageNative thenCall: ^(NSString *result) { - if (! [result isEqualToString:@"true"]) { - RCTLogError(@"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined"); - } - }]; - #endif - - NSString *source = [NSString stringWithFormat: - @"(function() {" - "window.originalPostMessage = window.postMessage;" - - "window.postMessage = function(data) {" - "window.webkit.messageHandlers.%@.postMessage(String(data));" - "};" - "})();", - MessageHanderName - ]; - [self evaluateJS: source thenCall: nil]; - } - - if (_injectedJavaScript) { - [self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) { - NSMutableDictionary *event = [self baseEvent]; - event[@"jsEvaluationValue"] = jsEvaluationValue; - if (self.onLoadingFinish) { - self.onLoadingFinish(event); - } - }]; - } else if (_onLoadingFinish) { - _onLoadingFinish([self baseEvent]); - } - - [self setBackgroundColor: _savedBackgroundColor]; -} - -- (void)injectJavaScript:(NSString *)script -{ - [self evaluateJS: script thenCall: nil]; -} - -- (void)goForward -{ - [_webView goForward]; -} - -- (void)goBack -{ - [_webView goBack]; -} - -- (void)reload -{ - /** - * When the initial load fails due to network connectivity issues, - * [_webView reload] doesn't reload the webpage. Therefore, we must - * manually call [_webView loadRequest:request]. - */ - NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; - if (request.URL && !_webView.URL.absoluteString.length) { - [_webView loadRequest:request]; - } - else { - [_webView reload]; - } -} - -- (void)stopLoading -{ - [_webView stopLoading]; -} - -- (void)setBounces:(BOOL)bounces -{ - _bounces = bounces; - _webView.scrollView.bounces = bounces; -} -@end diff --git a/React/Views/RCTWKWebViewManager.h b/React/Views/RCTWKWebViewManager.h deleted file mode 100644 index cf44faf240cbd0..00000000000000 --- a/React/Views/RCTWKWebViewManager.h +++ /dev/null @@ -1,11 +0,0 @@ -/** - * 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 - -@interface RCTWKWebViewManager : RCTViewManager -@end diff --git a/React/Views/RCTWKWebViewManager.m b/React/Views/RCTWKWebViewManager.m deleted file mode 100644 index 39a6cfd8def77d..00000000000000 --- a/React/Views/RCTWKWebViewManager.m +++ /dev/null @@ -1,170 +0,0 @@ -/** - * 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 "RCTWKWebViewManager.h" - -#import "RCTUIManager.h" -#import "RCTWKWebView.h" -#import - -@interface RCTWKWebViewManager () -@end - -@implementation RCTWKWebViewManager -{ - NSConditionLock *_shouldStartLoadLock; - BOOL _shouldStartLoad; -} - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - RCTWKWebView *webView = [RCTWKWebView new]; - webView.delegate = self; - return webView; -} - -RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary) -RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString) -RCT_EXPORT_VIEW_PROPERTY(allowsInlineMediaPlayback, BOOL) -RCT_EXPORT_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, BOOL) -#if WEBKIT_IOS_10_APIS_AVAILABLE -RCT_EXPORT_VIEW_PROPERTY(dataDetectorTypes, WKDataDetectorTypes) -#endif -RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) -RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL) - -/** - * Expose methods to enable messaging the webview. - */ -RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL) -RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock) - -RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWKWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWKWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view postMessage:message]; - } - }]; -} - -RCT_CUSTOM_VIEW_PROPERTY(bounces, BOOL, __unused RCTWKWebView) { - view.bounces = json == nil ? true : [RCTConvert BOOL: json]; -} - -RCT_CUSTOM_VIEW_PROPERTY(scrollEnabled, BOOL, __unused RCTWKWebView) { - view.scrollEnabled = json == nil ? true : [RCTConvert BOOL: json]; -} - -RCT_CUSTOM_VIEW_PROPERTY(decelerationRate, CGFloat, __unused RCTWKWebView) { - view.decelerationRate = json == nil ? UIScrollViewDecelerationRateNormal : [RCTConvert CGFloat: json]; -} - -RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWKWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWKWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view injectJavaScript:script]; - } - }]; -} - -RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWKWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWKWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view goBack]; - } - }]; -} - -RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWKWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWKWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view goForward]; - } - }]; -} - -RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWKWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWKWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view reload]; - } - }]; -} - -RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWKWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWKWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view stopLoading]; - } - }]; -} - -#pragma mark - Exported synchronous methods - -- (BOOL) webView:(__unused RCTWKWebView *)webView -shouldStartLoadForRequest:(NSMutableDictionary *)request - withCallback:(RCTDirectEventBlock)callback -{ - _shouldStartLoadLock = [[NSConditionLock alloc] initWithCondition:arc4random()]; - _shouldStartLoad = YES; - request[@"lockIdentifier"] = @(_shouldStartLoadLock.condition); - callback(request); - - // Block the main thread for a maximum of 250ms until the JS thread returns - if ([_shouldStartLoadLock lockWhenCondition:0 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.25]]) { - BOOL returnValue = _shouldStartLoad; - [_shouldStartLoadLock unlock]; - _shouldStartLoadLock = nil; - return returnValue; - } else { - RCTLogWarn(@"Did not receive response to shouldStartLoad in time, defaulting to YES"); - return YES; - } -} - -RCT_EXPORT_METHOD(startLoadWithResult:(BOOL)result lockIdentifier:(NSInteger)lockIdentifier) -{ - if ([_shouldStartLoadLock tryLockWhenCondition:lockIdentifier]) { - _shouldStartLoad = result; - [_shouldStartLoadLock unlockWithCondition:0]; - } else { - RCTLogWarn(@"startLoadWithResult invoked with invalid lockIdentifier: " - "got %lld, expected %lld", (long long)lockIdentifier, (long long)_shouldStartLoadLock.condition); - } -} - -@end diff --git a/React/Views/RCTWebView.h b/React/Views/RCTWebView.h deleted file mode 100644 index 4fa9d62d8a831d..00000000000000 --- a/React/Views/RCTWebView.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * 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 - -@class RCTWebView; - -/** - * Special scheme used to pass messages to the injectedJavaScript - * code without triggering a page load. Usage: - * - * window.location.href = RCTJSNavigationScheme + '://hello' - */ -extern NSString *const RCTJSNavigationScheme; - -@protocol RCTWebViewDelegate - -- (BOOL)webView:(RCTWebView *)webView -shouldStartLoadForRequest:(NSMutableDictionary *)request - withCallback:(RCTDirectEventBlock)callback; - -@end - -@interface RCTWebView : RCTView - -@property (nonatomic, weak) id delegate; - -@property (nonatomic, copy) NSDictionary *source; -@property (nonatomic, assign) UIEdgeInsets contentInset; -@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; -@property (nonatomic, assign) BOOL messagingEnabled; -@property (nonatomic, copy) NSString *injectedJavaScript; -@property (nonatomic, assign) BOOL scalesPageToFit; - -- (void)goForward; -- (void)goBack; -- (void)reload; -- (void)stopLoading; -- (void)postMessage:(NSString *)message; -- (void)injectJavaScript:(NSString *)script; - -@end diff --git a/React/Views/RCTWebView.m b/React/Views/RCTWebView.m deleted file mode 100644 index 8f61e38041439e..00000000000000 --- a/React/Views/RCTWebView.m +++ /dev/null @@ -1,351 +0,0 @@ -/** - * 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 "RCTWebView.h" - -#import - -#import "RCTAutoInsetsProtocol.h" -#import "RCTConvert.h" -#import "RCTEventDispatcher.h" -#import "RCTLog.h" -#import "RCTUtils.h" -#import "RCTView.h" -#import "UIView+React.h" - -NSString *const RCTJSNavigationScheme = @"react-js-navigation"; - -static NSString *const kPostMessageHost = @"postMessage"; - -@interface RCTWebView () - -@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart; -@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish; -@property (nonatomic, copy) RCTDirectEventBlock onLoadingError; -@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest; -@property (nonatomic, copy) RCTDirectEventBlock onMessage; - -@end - -@implementation RCTWebView -{ - UIWebView *_webView; - NSString *_injectedJavaScript; -} - -- (void)dealloc -{ - _webView.delegate = nil; -} - -- (instancetype)initWithFrame:(CGRect)frame -{ - if ((self = [super initWithFrame:frame])) { - super.backgroundColor = [UIColor clearColor]; - _automaticallyAdjustContentInsets = YES; - _contentInset = UIEdgeInsetsZero; - _webView = [[UIWebView alloc] initWithFrame:self.bounds]; - _webView.delegate = self; -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ - if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) { - _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; - } -#endif - [self addSubview:_webView]; - } - return self; -} - -RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) - -- (void)goForward -{ - [_webView goForward]; -} - -- (void)goBack -{ - [_webView goBack]; -} - -- (void)reload -{ - NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; - if (request.URL && !_webView.request.URL.absoluteString.length) { - [_webView loadRequest:request]; - } - else { - [_webView reload]; - } -} - -- (void)stopLoading -{ - [_webView stopLoading]; -} - -- (void)postMessage:(NSString *)message -{ - NSDictionary *eventInitDict = @{ - @"data": message, - }; - NSString *source = [NSString - stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));", - RCTJSONStringify(eventInitDict, NULL) - ]; - [_webView stringByEvaluatingJavaScriptFromString:source]; -} - -- (void)injectJavaScript:(NSString *)script -{ - [_webView stringByEvaluatingJavaScriptFromString:script]; -} - -- (void)setSource:(NSDictionary *)source -{ - if (![_source isEqualToDictionary:source]) { - _source = [source copy]; - - // Check for a static html source first - NSString *html = [RCTConvert NSString:source[@"html"]]; - if (html) { - NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]]; - if (!baseURL) { - baseURL = [NSURL URLWithString:@"about:blank"]; - } - [_webView loadHTMLString:html baseURL:baseURL]; - return; - } - - NSURLRequest *request = [RCTConvert NSURLRequest:source]; - // Because of the way React works, as pages redirect, we actually end up - // passing the redirect urls back here, so we ignore them if trying to load - // the same url. We'll expose a call to 'reload' to allow a user to load - // the existing page. - if ([request.URL isEqual:_webView.request.URL]) { - return; - } - if (!request.URL) { - // Clear the webview - [_webView loadHTMLString:@"" baseURL:nil]; - return; - } - [_webView loadRequest:request]; - } -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - _webView.frame = self.bounds; -} - -- (void)setContentInset:(UIEdgeInsets)contentInset -{ - _contentInset = contentInset; - [RCTView autoAdjustInsetsForView:self - withScrollView:_webView.scrollView - updateOffset:NO]; -} - -- (void)setScalesPageToFit:(BOOL)scalesPageToFit -{ - if (_webView.scalesPageToFit != scalesPageToFit) { - _webView.scalesPageToFit = scalesPageToFit; - [_webView reload]; - } -} - -- (BOOL)scalesPageToFit -{ - return _webView.scalesPageToFit; -} - -- (void)setBackgroundColor:(UIColor *)backgroundColor -{ - CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); - self.opaque = _webView.opaque = (alpha == 1.0); - _webView.backgroundColor = backgroundColor; -} - -- (UIColor *)backgroundColor -{ - return _webView.backgroundColor; -} - -- (NSMutableDictionary *)baseEvent -{ - NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"url": _webView.request.URL.absoluteString ?: @"", - @"loading" : @(_webView.loading), - @"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"], - @"canGoBack": @(_webView.canGoBack), - @"canGoForward" : @(_webView.canGoForward), - }]; - - return event; -} - -- (void)refreshContentInset -{ - [RCTView autoAdjustInsetsForView:self - withScrollView:_webView.scrollView - updateOffset:YES]; -} - -#pragma mark - UIWebViewDelegate methods - -- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request - navigationType:(UIWebViewNavigationType)navigationType -{ - BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; - - static NSDictionary *navigationTypes; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - navigationTypes = @{ - @(UIWebViewNavigationTypeLinkClicked): @"click", - @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit", - @(UIWebViewNavigationTypeBackForward): @"backforward", - @(UIWebViewNavigationTypeReload): @"reload", - @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit", - @(UIWebViewNavigationTypeOther): @"other", - }; - }); - - // skip this for the JS Navigation handler - if (!isJSNavigation && _onShouldStartLoadWithRequest) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": navigationTypes[@(navigationType)] - }]; - if (![self.delegate webView:self - shouldStartLoadForRequest:event - withCallback:_onShouldStartLoadWithRequest]) { - return NO; - } - } - - if (_onLoadingStart) { - // We have this check to filter out iframe requests and whatnot - BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; - if (isTopFrame) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": navigationTypes[@(navigationType)] - }]; - _onLoadingStart(event); - } - } - - if (isJSNavigation && [request.URL.host isEqualToString:kPostMessageHost]) { - NSString *data = request.URL.query; - data = [data stringByReplacingOccurrencesOfString:@"+" withString:@" "]; - data = [data stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]; - - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"data": data, - }]; - - NSString *source = @"document.dispatchEvent(new MessageEvent('message:received'));"; - - [_webView stringByEvaluatingJavaScriptFromString:source]; - - _onMessage(event); - } - - // JS Navigation handler - return !isJSNavigation; -} - -- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error -{ - if (_onLoadingError) { - if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { - // NSURLErrorCancelled is reported when a page has a redirect OR if you load - // a new URL in the WebView before the previous one came back. We can just - // ignore these since they aren't real errors. - // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os - return; - } - - if ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102) { - // Error code 102 "Frame load interrupted" is raised by the UIWebView if - // its delegate returns FALSE from webView:shouldStartLoadWithRequest:navigationType - // when the URL is from an http redirect. This is a common pattern when - // implementing OAuth with a WebView. - return; - } - - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary:@{ - @"domain": error.domain, - @"code": @(error.code), - @"description": error.localizedDescription, - }]; - _onLoadingError(event); - } -} - -- (void)webViewDidFinishLoad:(UIWebView *)webView -{ - if (_messagingEnabled) { - #if RCT_DEV - // See isNative in lodash - NSString *testPostMessageNative = @"String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')"; - BOOL postMessageIsNative = [ - [webView stringByEvaluatingJavaScriptFromString:testPostMessageNative] - isEqualToString:@"true" - ]; - if (!postMessageIsNative) { - RCTLogError(@"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined"); - } - #endif - NSString *source = [NSString stringWithFormat: - @"(function() {" - "window.originalPostMessage = window.postMessage;" - - "var messageQueue = [];" - "var messagePending = false;" - - "function processQueue() {" - "if (!messageQueue.length || messagePending) return;" - "messagePending = true;" - "window.location = '%@://%@?' + encodeURIComponent(messageQueue.shift());" - "}" - - "window.postMessage = function(data) {" - "messageQueue.push(String(data));" - "processQueue();" - "};" - - "document.addEventListener('message:received', function(e) {" - "messagePending = false;" - "processQueue();" - "});" - "})();", RCTJSNavigationScheme, kPostMessageHost - ]; - [webView stringByEvaluatingJavaScriptFromString:source]; - } - if (_injectedJavaScript != nil) { - NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; - - NSMutableDictionary *event = [self baseEvent]; - event[@"jsEvaluationValue"] = jsEvaluationValue; - - _onLoadingFinish(event); - } - // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. - else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) { - _onLoadingFinish([self baseEvent]); - } -} - -@end diff --git a/React/Views/RCTWebViewManager.h b/React/Views/RCTWebViewManager.h deleted file mode 100644 index d06ea1b806e470..00000000000000 --- a/React/Views/RCTWebViewManager.h +++ /dev/null @@ -1,12 +0,0 @@ -/** - * 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 - -@interface RCTWebViewManager : RCTViewManager - -@end diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m deleted file mode 100644 index fc39f7cb1382a2..00000000000000 --- a/React/Views/RCTWebViewManager.m +++ /dev/null @@ -1,158 +0,0 @@ -/** - * 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 "RCTWebViewManager.h" - -#import "RCTBridge.h" -#import "RCTUIManager.h" -#import "RCTWebView.h" -#import "UIView+React.h" - -@interface RCTWebViewManager () - -@end - -@implementation RCTWebViewManager -{ - NSConditionLock *_shouldStartLoadLock; - BOOL _shouldStartLoad; -} - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - RCTWebView *webView = [RCTWebView new]; - webView.delegate = self; - return webView; -} - -RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary) -RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL) -RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL) -RCT_REMAP_VIEW_PROPERTY(decelerationRate, _webView.scrollView.decelerationRate, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(scalesPageToFit, BOOL) -RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL) -RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString) -RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) -RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL) -RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock) -RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL) -RCT_REMAP_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, _webView.mediaPlaybackRequiresUserAction, BOOL) -RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, _webView.dataDetectorTypes, UIDataDetectorTypes) - -RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view goBack]; - } - }]; -} - -RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view goForward]; - } - }]; -} - -RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view reload]; - } - }]; -} - -RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view stopLoading]; - } - }]; -} - -RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view postMessage:message]; - } - }]; -} - -RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view injectJavaScript:script]; - } - }]; -} - -#pragma mark - Exported synchronous methods - -- (BOOL)webView:(__unused RCTWebView *)webView -shouldStartLoadForRequest:(NSMutableDictionary *)request - withCallback:(RCTDirectEventBlock)callback -{ - _shouldStartLoadLock = [[NSConditionLock alloc] initWithCondition:arc4random()]; - _shouldStartLoad = YES; - request[@"lockIdentifier"] = @(_shouldStartLoadLock.condition); - callback(request); - - // Block the main thread for a maximum of 250ms until the JS thread returns - if ([_shouldStartLoadLock lockWhenCondition:0 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.25]]) { - BOOL returnValue = _shouldStartLoad; - [_shouldStartLoadLock unlock]; - _shouldStartLoadLock = nil; - return returnValue; - } else { - RCTLogWarn(@"Did not receive response to shouldStartLoad in time, defaulting to YES"); - return YES; - } -} - -RCT_EXPORT_METHOD(startLoadWithResult:(BOOL)result lockIdentifier:(NSInteger)lockIdentifier) -{ - if ([_shouldStartLoadLock tryLockWhenCondition:lockIdentifier]) { - _shouldStartLoad = result; - [_shouldStartLoadLock unlockWithCondition:0]; - } else { - RCTLogWarn(@"startLoadWithResult invoked with invalid lockIdentifier: " - "got %lld, expected %lld", (long long)lockIdentifier, (long long)_shouldStartLoadLock.condition); - } -} - -@end diff --git a/React/Views/ScrollView/RCTScrollView.h b/React/Views/ScrollView/RCTScrollView.h index 3404422c7b00da..6ef40fa840857d 100644 --- a/React/Views/ScrollView/RCTScrollView.h +++ b/React/Views/ScrollView/RCTScrollView.h @@ -44,6 +44,7 @@ @property (nonatomic, assign) NSTimeInterval scrollEventThrottle; @property (nonatomic, assign) BOOL centerContent; @property (nonatomic, copy) NSDictionary *maintainVisibleContentPosition; +@property (nonatomic, assign) BOOL scrollToOverflowEnabled; @property (nonatomic, assign) int snapToInterval; @property (nonatomic, copy) NSArray *snapToOffsets; @property (nonatomic, assign) BOOL snapToStart; diff --git a/React/Views/ScrollView/RCTScrollView.m b/React/Views/ScrollView/RCTScrollView.m index 75da9bb0841cf0..27347c05036d32 100644 --- a/React/Views/ScrollView/RCTScrollView.m +++ b/React/Views/ScrollView/RCTScrollView.m @@ -615,7 +615,7 @@ - (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated fmax(_scrollView.contentSize.height - _scrollView.bounds.size.height + _scrollView.contentInset.bottom + fmax(_scrollView.contentInset.top, 0), 0.01)); // Make width and height greater than 0 // Ensure at least one scroll event will fire _allowNextScrollNoMatterWhat = YES; - if (!CGRectContainsPoint(maxRect, offset)) { + if (!CGRectContainsPoint(maxRect, offset) && !self.scrollToOverflowEnabled) { CGFloat x = fmax(offset.x, CGRectGetMinX(maxRect)); x = fmin(x, CGRectGetMaxX(maxRect)); CGFloat y = fmax(offset.y, CGRectGetMinY(maxRect)); diff --git a/React/Views/ScrollView/RCTScrollViewManager.m b/React/Views/ScrollView/RCTScrollViewManager.m index fb477c85f5df9b..1b4c52e4110b9a 100644 --- a/React/Views/ScrollView/RCTScrollViewManager.m +++ b/React/Views/ScrollView/RCTScrollViewManager.m @@ -83,6 +83,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat) RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets) +RCT_EXPORT_VIEW_PROPERTY(scrollToOverflowEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int) RCT_EXPORT_VIEW_PROPERTY(snapToOffsets, NSArray) RCT_EXPORT_VIEW_PROPERTY(snapToStart, BOOL) diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index 060af22adbaaea..d79a9b9bafd626 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -133,21 +133,14 @@ task prepareGlog(dependsOn: dependenciesPath ? [] : [downloadGlog], type: Copy) } } -task downloadJSC(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://registry.npmjs.org/jsc-android/-/jsc-android-${JSC_VERSION}.tgz") - onlyIfNewer(true) - overwrite(false) - dest(new File(downloadsDir, "jsc-${JSC_VERSION}.tar.gz")) -} - // Create Android.mk library module based on jsc from npm -task prepareJSC(dependsOn: downloadJSC) { +task prepareJSC { doLast { - def jscTar = tarTree(downloadJSC.dest) - def jscAAR = jscTar.matching({ it.include "**/android-jsc/**/*.aar" }).singleFile + def jscPackageRoot = fileTree("$projectDir/../node_modules/jsc-android/dist") + def jscAAR = jscPackageRoot.matching({ it.include "**/android-jsc/**/*.aar" }).singleFile def soFiles = zipTree(jscAAR).matching({ it.include "**/*.so" }) - def headerFiles = jscTar.matching({ it.include "**/include/*.h" }) + def headerFiles = jscPackageRoot.matching({ it.include "**/include/*.h" }) copy { from(soFiles) @@ -168,7 +161,6 @@ task downloadNdkBuildDependencies { dependsOn(downloadDoubleConversion) dependsOn(downloadFolly) dependsOn(downloadGlog) - dependsOn(downloadJSC) } def getNdkBuildName() { @@ -255,8 +247,8 @@ task cleanReactNdkLib(type: Exec) { task packageReactNdkLibs(dependsOn: buildReactNdkLib, type: Copy) { from("$buildDir/react-ndk/all") - from("$thirdPartyNdkDir/jsc/jni") into("$buildDir/react-ndk/exported") + exclude("**/libjsc.so") } task packageReactNdkLibsForBuck(dependsOn: packageReactNdkLibs, type: Copy) { diff --git a/ReactAndroid/gradle.properties b/ReactAndroid/gradle.properties index c7d08bf4cd53d0..00b3dcb0d10ff8 100644 --- a/ReactAndroid/gradle.properties +++ b/ReactAndroid/gradle.properties @@ -12,7 +12,7 @@ JUNIT_VERSION=4.12 FEST_ASSERT_CORE_VERSION=2.0M10 ANDROID_SUPPORT_TEST_VERSION=1.0.2 -FRESCO_VERSION=1.11.0 +FRESCO_VERSION=1.13.0 OKHTTP_VERSION=3.12.1 SO_LOADER_VERSION=0.6.0 @@ -20,7 +20,6 @@ BOOST_VERSION=1_63_0 DOUBLE_CONVERSION_VERSION=1.1.6 FOLLY_VERSION=2018.10.22.00 GLOG_VERSION=0.3.5 -JSC_VERSION=236355.1.1 android.useAndroidX=true android.enableJetifier=true diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/rule/BUCK b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/rule/BUCK index 833d7246599e07..d1963d369eaccd 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/rule/BUCK +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/rule/BUCK @@ -21,7 +21,7 @@ rn_android_library( react_native_dep("third-party/java/junit:junit"), react_native_dep("third-party/java/testing-support-lib:testing-support-lib"), react_native_dep("third-party/android/support/v4:lib-support-v4"), - react_native_dep("third-party/android/support/v7/appcompat-orig:appcompat"), + react_native_dep("third-party/android/support/v7/appcompat:appcompat"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_integration_tests_target("java/com/facebook/react/testing:testing"), react_native_integration_tests_target("java/com/facebook/react/testing/idledetection:idledetection"), diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK index e7e4481dd7a4a3..72d5eb328ca217 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK @@ -8,7 +8,7 @@ rn_android_library( "PUBLIC", ], deps = [ - react_native_dep("third-party/android/support/v7/appcompat-orig:appcompat"), + react_native_dep("third-party/android/support/v7/appcompat:appcompat"), react_native_dep("third-party/android/support/v4:lib-support-v4"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/junit:junit"), diff --git a/ReactAndroid/src/main/android_res/com/facebook/catalyst/appcompat/BUCK b/ReactAndroid/src/main/android_res/com/facebook/catalyst/appcompat/BUCK index d0d6a03da817d1..895f28a83f5a7b 100644 --- a/ReactAndroid/src/main/android_res/com/facebook/catalyst/appcompat/BUCK +++ b/ReactAndroid/src/main/android_res/com/facebook/catalyst/appcompat/BUCK @@ -5,6 +5,6 @@ load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_resou rn_android_resource( name = "appcompat", package = "com.facebook.react", - res = react_native_dep("third-party/android/support/v7/appcompat-orig:res-unpacker-cmd"), + res = react_native_dep("third-party/android/support/v7/appcompat:res-unpacker-cmd"), visibility = ["//ReactAndroid/..."], ) diff --git a/ReactAndroid/src/main/java/com/facebook/react/BUCK b/ReactAndroid/src/main/java/com/facebook/react/BUCK index f869e109cf75c2..04b76cf812bbda 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/BUCK @@ -5,7 +5,7 @@ rn_android_library( srcs = glob(["*.java"]), is_androidx = True, provided_deps = [ - react_native_dep("third-party/android/support/v7/appcompat-orig:appcompat"), + react_native_dep("third-party/android/support/v7/appcompat:appcompat"), react_native_dep("third-party/android/support/v4:lib-support-v4"), ], visibility = [ diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/AbstractFloatPairPropertyUpdater.java b/ReactAndroid/src/main/java/com/facebook/react/animation/AbstractFloatPairPropertyUpdater.java deleted file mode 100644 index b086f8fdcc91f9..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/AbstractFloatPairPropertyUpdater.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * 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.animation; - -import android.view.View; - -/** - * Base class for {@link AnimationPropertyUpdater} subclasses that updates a pair of float property - * values. It helps to handle conversion from animation progress to the actual values as - * well as the quite common case when no starting value is provided. - */ -public abstract class AbstractFloatPairPropertyUpdater implements AnimationPropertyUpdater { - - private final float[] mFromValues = new float[2]; - private final float[] mToValues = new float[2]; - private final float[] mUpdateValues = new float[2]; - private boolean mFromSource; - - protected AbstractFloatPairPropertyUpdater(float toFirst, float toSecond) { - mToValues[0] = toFirst; - mToValues[1] = toSecond; - mFromSource = true; - } - - protected AbstractFloatPairPropertyUpdater( - float fromFirst, - float fromSecond, - float toFirst, - float toSecond) { - this(toFirst, toSecond); - mFromValues[0] = fromFirst; - mFromValues[1] = fromSecond; - mFromSource = false; - } - - protected abstract void getProperty(View view, float[] returnValues); - protected abstract void setProperty(View view, float[] propertyValues); - - @Override - public void prepare(View view) { - if (mFromSource) { - getProperty(view, mFromValues); - } - } - - @Override - public void onUpdate(View view, float progress) { - mUpdateValues[0] = mFromValues[0] + (mToValues[0] - mFromValues[0]) * progress; - mUpdateValues[1] = mFromValues[1] + (mToValues[1] - mFromValues[1]) * progress; - setProperty(view, mUpdateValues); - } - - @Override - public void onFinish(View view) { - setProperty(view, mToValues); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/AbstractSingleFloatProperyUpdater.java b/ReactAndroid/src/main/java/com/facebook/react/animation/AbstractSingleFloatProperyUpdater.java deleted file mode 100644 index cfa24177eca220..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/AbstractSingleFloatProperyUpdater.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 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.animation; - -import android.view.View; - -/** - * Base class for {@link AnimationPropertyUpdater} subclasses that updates a single float property - * value. It helps to handle conversion from animation progress to the actual value as well as the - * quite common case when no starting value is provided. - */ -public abstract class AbstractSingleFloatProperyUpdater implements AnimationPropertyUpdater { - - private float mFromValue, mToValue; - private boolean mFromSource; - - protected AbstractSingleFloatProperyUpdater(float toValue) { - mToValue = toValue; - mFromSource = true; - } - - protected AbstractSingleFloatProperyUpdater(float fromValue, float toValue) { - this(toValue); - mFromValue = fromValue; - mFromSource = false; - } - - protected abstract float getProperty(View view); - protected abstract void setProperty(View view, float propertyValue); - - @Override - public final void prepare(View view) { - if (mFromSource) { - mFromValue = getProperty(view); - } - } - - @Override - public final void onUpdate(View view, float progress) { - setProperty(view, mFromValue + (mToValue - mFromValue) * progress); - } - - @Override - public void onFinish(View view) { - setProperty(view, mToValue); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/Animation.java b/ReactAndroid/src/main/java/com/facebook/react/animation/Animation.java deleted file mode 100644 index af29fdeaae0918..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/Animation.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * 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.animation; - -import javax.annotation.Nullable; - -import android.view.View; - -import com.facebook.infer.annotation.Assertions; - -/** - * Base class for various catalyst animation engines. Subclasses of this class should implement - * {@link #run} method which should bootstrap the animation. Then in each animation frame we expect - * animation engine to call {@link #onUpdate} with a float progress which then will be transferred - * to the underlying {@link AnimationPropertyUpdater} instance. - * - * Animation engine should support animation cancelling by monitoring the returned value of - * {@link #onUpdate}. In case of returning false, animation should be considered cancelled and - * engine should not attempt to call {@link #onUpdate} again. - */ -public abstract class Animation { - - private final int mAnimationID; - private final AnimationPropertyUpdater mPropertyUpdater; - private volatile boolean mCancelled = false; - private volatile boolean mIsFinished = false; - private @Nullable AnimationListener mAnimationListener; - private @Nullable View mAnimatedView; - - public Animation(int animationID, AnimationPropertyUpdater propertyUpdater) { - mAnimationID = animationID; - mPropertyUpdater = propertyUpdater; - } - - public void setAnimationListener(AnimationListener animationListener) { - mAnimationListener = animationListener; - } - - public final void start(View view) { - mAnimatedView = view; - mPropertyUpdater.prepare(view); - run(); - } - - public abstract void run(); - - /** - * Animation engine should call this method for every animation frame passing animation progress - * value as a parameter. Animation progress should be within the range 0..1 (the exception here - * would be a spring animation engine which may slightly exceed start and end progress values). - * - * This method will return false if the animation has been cancelled. In that case animation - * engine should not attempt to call this method again. Otherwise this method will return true - */ - protected final boolean onUpdate(float value) { - Assertions.assertCondition(!mIsFinished, "Animation must not already be finished!"); - if (!mCancelled) { - mPropertyUpdater.onUpdate(Assertions.assertNotNull(mAnimatedView), value); - } - return !mCancelled; - } - - /** - * Animation engine should call this method when the animation is finished. Should be called only - * once - */ - protected final void finish() { - Assertions.assertCondition(!mIsFinished, "Animation must not already be finished!"); - mIsFinished = true; - if (!mCancelled) { - if (mAnimatedView != null) { - mPropertyUpdater.onFinish(mAnimatedView); - } - if (mAnimationListener != null) { - mAnimationListener.onFinished(); - } - } - } - - /** - * Cancels the animation. - * - * It is possible for this to be called after finish() and should handle that gracefully. - */ - public final void cancel() { - if (mIsFinished || mCancelled) { - // If we were already finished, ignore - return; - } - - mCancelled = true; - if (mAnimationListener != null) { - mAnimationListener.onCancel(); - } - } - - public int getAnimationID() { - return mAnimationID; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/AnimationListener.java b/ReactAndroid/src/main/java/com/facebook/react/animation/AnimationListener.java deleted file mode 100644 index bc4b9e2916858d..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/AnimationListener.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * 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.animation; - -/** - * Interface for getting animation lifecycle updates. It is guaranteed that for a given animation, - * only one of onFinished and onCancel will be called, and it will be called exactly once. - */ -public interface AnimationListener { - - /** - * Called once animation is finished - */ - public void onFinished(); - - /** - * Called in case when animation was cancelled - */ - public void onCancel(); -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/AnimationPropertyUpdater.java b/ReactAndroid/src/main/java/com/facebook/react/animation/AnimationPropertyUpdater.java deleted file mode 100644 index b815210be58018..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/AnimationPropertyUpdater.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * 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.animation; - -import android.view.View; - -/** - * Interface used to update particular property types during animation. While animation is in - * progress {@link Animation} instance will call {@link #onUpdate} several times with a value - * representing animation progress. Normally value will be from 0..1 range, but for spring animation - * it can slightly exceed that limit due to bounce effect at the start/end of animation. - */ -public interface AnimationPropertyUpdater { - - /** - * This method will be called before animation starts. - * - * @param view view that will be animated - */ - public void prepare(View view); - - /** - * This method will be called for each animation frame - * - * @param view view to update property - * @param progress animation progress from 0..1 range (may slightly exceed that limit in case of - * spring engine) retrieved from {@link Animation} engine. - */ - public void onUpdate(View view, float progress); - - /** - * This method will be called at the end of animation. It should be used to set the final values - * for animated properties in order to avoid floating point inaccuracy calculated in - * {@link #onUpdate} by passing value close to 1.0 or in a case some frames got dropped. - * - * @param view view to update property - */ - public void onFinish(View view); -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/AnimationRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/animation/AnimationRegistry.java deleted file mode 100644 index 58b9518801cc8a..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/AnimationRegistry.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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.animation; - -import android.util.SparseArray; - -import com.facebook.react.bridge.UiThreadUtil; - -/** - * Coordinates catalyst animations driven by {@link UIManagerModule} and - * {@link AnimationManagerModule} - */ -public class AnimationRegistry { - - private final SparseArray mRegistry = new SparseArray(); - - public void registerAnimation(Animation animation) { - UiThreadUtil.assertOnUiThread(); - mRegistry.put(animation.getAnimationID(), animation); - } - - public Animation getAnimation(int animationID) { - UiThreadUtil.assertOnUiThread(); - return mRegistry.get(animationID); - } - - public Animation removeAnimation(int animationID) { - UiThreadUtil.assertOnUiThread(); - Animation animation = mRegistry.get(animationID); - if (animation != null) { - mRegistry.delete(animationID); - } - return animation; - } - -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/BUCK b/ReactAndroid/src/main/java/com/facebook/react/animation/BUCK deleted file mode 100644 index 1c2781c23b47b6..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/BUCK +++ /dev/null @@ -1,14 +0,0 @@ -load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library") - -rn_android_library( - name = "animation", - srcs = glob(["**/*.java"]), - visibility = [ - "PUBLIC", - ], - deps = [ - react_native_dep("third-party/java/infer-annotations:infer-annotations"), - react_native_dep("third-party/java/jsr-305:jsr-305"), - react_native_target("java/com/facebook/react/bridge:bridge"), - ], -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/ImmediateAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/animation/ImmediateAnimation.java deleted file mode 100644 index 4949d68dfe20b1..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/ImmediateAnimation.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 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.animation; - -/** - * Ignores duration and immediately jump to the end of animation. This is a temporal solution for - * the lack of real animation engines implemented. - */ -public class ImmediateAnimation extends Animation { - - public ImmediateAnimation(int animationID, AnimationPropertyUpdater propertyUpdater) { - super(animationID, propertyUpdater); - } - - @Override - public void run() { - onUpdate(1.0f); - finish(); - } - -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/NoopAnimationPropertyUpdater.java b/ReactAndroid/src/main/java/com/facebook/react/animation/NoopAnimationPropertyUpdater.java deleted file mode 100644 index 04a5ee9ee4b149..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/NoopAnimationPropertyUpdater.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 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.animation; - -import android.view.View; - -/** - * Empty {@link AnimationPropertyUpdater} that can be used as a stub for unsupported property types - */ -public class NoopAnimationPropertyUpdater implements AnimationPropertyUpdater { - - @Override - public void prepare(View view) { - } - - @Override - public void onUpdate(View view, float value) { - } - - @Override - public void onFinish(View view) { - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/OpacityAnimationPropertyUpdater.java b/ReactAndroid/src/main/java/com/facebook/react/animation/OpacityAnimationPropertyUpdater.java deleted file mode 100644 index 7ca1af8a30a6df..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/OpacityAnimationPropertyUpdater.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 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.animation; - -import android.view.View; - -/** - * Subclass of {@link AnimationPropertyUpdater} for animating view's opacity - */ -public class OpacityAnimationPropertyUpdater extends AbstractSingleFloatProperyUpdater { - - public OpacityAnimationPropertyUpdater(float toOpacity) { - super(toOpacity); - } - - public OpacityAnimationPropertyUpdater(float fromOpacity, float toOpacity) { - super(fromOpacity, toOpacity); - } - - @Override - protected float getProperty(View view) { - return view.getAlpha(); - } - - @Override - protected void setProperty(View view, float propertyValue) { - view.setAlpha(propertyValue); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/PositionAnimationPairPropertyUpdater.java b/ReactAndroid/src/main/java/com/facebook/react/animation/PositionAnimationPairPropertyUpdater.java deleted file mode 100644 index 9d2e9737106c46..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/PositionAnimationPairPropertyUpdater.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * 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.animation; - -import android.view.View; - -/** - * Subclass of {@link AnimationPropertyUpdater} for animating center position of a view - */ -public class PositionAnimationPairPropertyUpdater extends AbstractFloatPairPropertyUpdater { - - public PositionAnimationPairPropertyUpdater(float toFirst, float toSecond) { - super(toFirst, toSecond); - } - - public PositionAnimationPairPropertyUpdater( - float fromFirst, - float fromSecond, - float toFirst, - float toSecond) { - super(fromFirst, fromSecond, toFirst, toSecond); - } - - @Override - protected void getProperty(View view, float[] returnValues) { - returnValues[0] = view.getX() + 0.5f * view.getWidth(); - returnValues[1] = view.getY() + 0.5f * view.getHeight(); - } - - @Override - protected void setProperty(View view, float[] propertyValues) { - view.setX(propertyValues[0] - 0.5f * view.getWidth()); - view.setY(propertyValues[1] - 0.5f * view.getHeight()); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/RotationAnimationPropertyUpdater.java b/ReactAndroid/src/main/java/com/facebook/react/animation/RotationAnimationPropertyUpdater.java deleted file mode 100644 index 772fa09a236f99..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/RotationAnimationPropertyUpdater.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * 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.animation; - -import android.view.View; - -/** - * Subclass of {@link AnimationPropertyUpdater} for animating view's rotation - */ -public class RotationAnimationPropertyUpdater extends AbstractSingleFloatProperyUpdater { - - public RotationAnimationPropertyUpdater(float toValue) { - super(toValue); - } - - @Override - protected float getProperty(View view) { - return view.getRotation(); - } - - @Override - protected void setProperty(View view, float propertyValue) { - view.setRotation((float) Math.toDegrees(propertyValue)); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/ScaleXAnimationPropertyUpdater.java b/ReactAndroid/src/main/java/com/facebook/react/animation/ScaleXAnimationPropertyUpdater.java deleted file mode 100644 index dd8bb4c24c458f..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/ScaleXAnimationPropertyUpdater.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 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.animation; - -import android.view.View; - -/** - * Subclass of {@link AnimationPropertyUpdater} for animating view's X scale - */ -public class ScaleXAnimationPropertyUpdater extends AbstractSingleFloatProperyUpdater { - - public ScaleXAnimationPropertyUpdater(float toValue) { - super(toValue); - } - - public ScaleXAnimationPropertyUpdater(float fromValue, float toValue) { - super(fromValue, toValue); - } - - @Override - protected float getProperty(View view) { - return view.getScaleX(); - } - - @Override - protected void setProperty(View view, float propertyValue) { - view.setScaleX(propertyValue); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/ScaleXYAnimationPairPropertyUpdater.java b/ReactAndroid/src/main/java/com/facebook/react/animation/ScaleXYAnimationPairPropertyUpdater.java deleted file mode 100644 index 779527b1a86159..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/ScaleXYAnimationPairPropertyUpdater.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * 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.animation; - -import android.view.View; - -/** - * Subclass of {@link AnimationPropertyUpdater} for animating view's X and Y scale - */ -public class ScaleXYAnimationPairPropertyUpdater extends AbstractFloatPairPropertyUpdater { - - public ScaleXYAnimationPairPropertyUpdater(float toFirst, float toSecond) { - super(toFirst, toSecond); - } - - public ScaleXYAnimationPairPropertyUpdater( - float fromFirst, - float fromSecond, - float toFirst, - float toSecond) { - super(fromFirst, fromSecond, toFirst, toSecond); - } - - @Override - protected void getProperty(View view, float[] returnValues) { - returnValues[0] = view.getScaleX(); - returnValues[1] = view.getScaleY(); - } - - @Override - protected void setProperty(View view, float[] propertyValues) { - view.setScaleX(propertyValues[0]); - view.setScaleY(propertyValues[1]); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animation/ScaleYAnimationPropertyUpdater.java b/ReactAndroid/src/main/java/com/facebook/react/animation/ScaleYAnimationPropertyUpdater.java deleted file mode 100644 index e3bf36cfb6c087..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/animation/ScaleYAnimationPropertyUpdater.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 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.animation; - -import android.view.View; - -/** - * Subclass of {@link AnimationPropertyUpdater} for animating view's Y scale - */ -public class ScaleYAnimationPropertyUpdater extends AbstractSingleFloatProperyUpdater { - - public ScaleYAnimationPropertyUpdater(float toValue) { - super(toValue); - } - - public ScaleYAnimationPropertyUpdater(float fromValue, float toValue) { - super(fromValue, toValue); - } - - @Override - protected float getProperty(View view) { - return view.getScaleY(); - } - - @Override - protected void setProperty(View view, float propertyValue) { - view.setScaleY(propertyValue); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java index 0c98a91d74a529..1b3774d7f875d8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java @@ -156,7 +156,7 @@ public void pushDouble(double value) { @Override public void pushInt(int value) { - mBackingList.add(value); + mBackingList.add(new Double(value)); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java index 119890a2088a81..139f824611338a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java @@ -183,7 +183,7 @@ public void putDouble(@Nonnull String key, double value) { @Override public void putInt(@Nonnull String key, int value) { - mBackingMap.put(key, value); + mBackingMap.put(key, new Double(value)); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java index a0b3d5102a7cb8..d07d4ae28a28dd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -18,6 +18,7 @@ import com.facebook.react.bridge.queue.ReactQueueConfiguration; import com.facebook.react.common.LifecycleState; import java.lang.ref.WeakReference; +import java.util.concurrent.CopyOnWriteArraySet; import javax.annotation.Nullable; /** @@ -31,35 +32,10 @@ public class ReactContext extends ContextWrapper { "ReactContext#getJSModule should only happen once initialize() has been called on your " + "native module."; - private final SynchronizedWeakHashSet mLifecycleEventListeners = - new SynchronizedWeakHashSet<>(); - private final SynchronizedWeakHashSet mActivityEventListeners = - new SynchronizedWeakHashSet<>(); - - - private final GuardedIteration mResumeIteration = - new GuardedIteration() { - @Override - public void onIterate(LifecycleEventListener listener) { - listener.onHostResume(); - } - }; - - private final GuardedIteration mPauseIteration = - new GuardedIteration() { - @Override - public void onIterate(LifecycleEventListener listener) { - listener.onHostPause(); - } - }; - - private final GuardedIteration mDestroyIteration = - new GuardedIteration() { - @Override - public void onIterate(LifecycleEventListener listener) { - listener.onHostDestroy(); - } - }; + private final CopyOnWriteArraySet mLifecycleEventListeners = + new CopyOnWriteArraySet<>(); + private final CopyOnWriteArraySet mActivityEventListeners = + new CopyOnWriteArraySet<>(); private LifecycleState mLifecycleState = LifecycleState.BEFORE_CREATE; @@ -176,17 +152,14 @@ public void addLifecycleEventListener(final LifecycleEventListener listener) { new Runnable() { @Override public void run() { - - mLifecycleEventListeners.doIfContains(listener, new Runnable() { - @Override - public void run() { - try { - listener.onHostResume(); - } catch (RuntimeException e) { - handleException(e); - } - } - }); + if (!mLifecycleEventListeners.contains(listener)) { + return; + } + try { + listener.onHostResume(); + } catch (RuntimeException e) { + handleException(e); + } } }); break; @@ -215,19 +188,26 @@ public void onHostResume(@Nullable Activity activity) { mLifecycleState = LifecycleState.RESUMED; mCurrentActivity = new WeakReference(activity); ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_RESUME_START); - mLifecycleEventListeners.iterate(mResumeIteration); + for (LifecycleEventListener listener : mLifecycleEventListeners) { + try { + listener.onHostResume(); + } catch (RuntimeException e) { + handleException(e); + } + } ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_RESUME_END); } - public void onNewIntent(@Nullable Activity activity, final Intent intent) { + public void onNewIntent(@Nullable Activity activity, Intent intent) { UiThreadUtil.assertOnUiThread(); mCurrentActivity = new WeakReference(activity); - mActivityEventListeners.iterate(new GuardedIteration() { - @Override - public void onIterate(ActivityEventListener listener) { + for (ActivityEventListener listener : mActivityEventListeners) { + try { listener.onNewIntent(intent); + } catch (RuntimeException e) { + handleException(e); } - }); + } } /** @@ -236,7 +216,13 @@ public void onIterate(ActivityEventListener listener) { public void onHostPause() { mLifecycleState = LifecycleState.BEFORE_RESUME; ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_PAUSE_START); - mLifecycleEventListeners.iterate(mPauseIteration); + for (LifecycleEventListener listener : mLifecycleEventListeners) { + try { + listener.onHostPause(); + } catch (RuntimeException e) { + handleException(e); + } + } ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_PAUSE_END); } @@ -246,7 +232,13 @@ public void onHostPause() { public void onHostDestroy() { UiThreadUtil.assertOnUiThread(); mLifecycleState = LifecycleState.BEFORE_CREATE; - mLifecycleEventListeners.iterate(mDestroyIteration); + for (LifecycleEventListener listener : mLifecycleEventListeners) { + try { + listener.onHostDestroy(); + } catch (RuntimeException e) { + handleException(e); + } + } mCurrentActivity = null; } @@ -264,13 +256,14 @@ public void destroy() { /** * Should be called by the hosting Fragment in {@link Fragment#onActivityResult} */ - public void onActivityResult(final Activity activity, final int requestCode, final int resultCode, final Intent data) { - mActivityEventListeners.iterate(new GuardedIteration() { - @Override - public void onIterate(ActivityEventListener listener) { + public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { + for (ActivityEventListener listener : mActivityEventListeners) { + try { listener.onActivityResult(activity, requestCode, resultCode, data); + } catch (RuntimeException e) { + handleException(e); } - }); + } } public void assertOnUiQueueThread() { @@ -366,16 +359,4 @@ public JavaScriptContextHolder getJavaScriptContextHolder() { return mCatalystInstance.getJavaScriptContextHolder(); } - private abstract class GuardedIteration implements SynchronizedWeakHashSet.Iteration { - @Override - public void iterate(T listener) { - try { - onIterate(listener); - } catch (RuntimeException e) { - handleException(e); - } - } - - public abstract void onIterate(T listener); - } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/SynchronizedWeakHashSet.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/SynchronizedWeakHashSet.java deleted file mode 100644 index b20af011512b0d..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/SynchronizedWeakHashSet.java +++ /dev/null @@ -1,130 +0,0 @@ -// 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.bridge; - -import android.util.Pair; - -import java.util.ArrayDeque; -import java.util.Queue; -import java.util.WeakHashMap; - -/** - * Thread-safe Set based on the WeakHashMap. - * - * Doesn't implement the `iterator` method because it's tricky to support modifications - * to the collection while somebody is using an `Iterator` to iterate over it. - * - * Instead, it provides an `iterate` method for traversing the collection. Any add/remove operations - * that occur during iteration are postponed until the iteration has completed. - */ -public class SynchronizedWeakHashSet { - private WeakHashMap mMap = new WeakHashMap<>(); - private Queue> mPendingOperations = new ArrayDeque<>(); - private boolean mIterating; - - public void doIfContains(T item, Runnable runnable) { - synchronized (mMap) { - if (mIterating) { - mPendingOperations.add(new Pair<>(item, Command.newDoIfContains(runnable))); - } else { - if (mMap.containsKey(item)) { - runnable.run(); - } - } - } - } - - public void add(T item) { - synchronized (mMap) { - if (mIterating) { - mPendingOperations.add(new Pair<>(item, Command.ADD)); - } else { - mMap.put(item, null); - } - } - } - - public void remove(T item) { - synchronized (mMap) { - if (mIterating) { - mPendingOperations.add(new Pair<>(item, Command.REMOVE)); - } else { - mMap.remove(item); - } - } - } - - public void iterate(Iteration iterated) { - synchronized (mMap) { - // Protection from modification during iteration on the same thread - mIterating = true; - for (T listener: mMap.keySet()) { - iterated.iterate(listener); - } - mIterating = false; - - while (!mPendingOperations.isEmpty()) { - Pair pair = mPendingOperations.poll(); - Command command = pair.second; - switch (command.getType()) { - case ADD: - mMap.put(pair.first, null); - break; - case REMOVE: - mMap.remove(pair.first); - break; - case DO_IF_CONTAINS: - if (mMap.containsKey(pair.first)) { - command.execute(); - } - break; - default: - throw new AssertionException("Unsupported command" + pair.second); - } - } - } - } - - public interface Iteration { - void iterate(T item); - } - - private enum CommandType { - ADD, - REMOVE, - DO_IF_CONTAINS - } - - private static class Command { - public static final Command ADD = new Command(CommandType.ADD); - public static final Command REMOVE = new Command(CommandType.REMOVE); - - private CommandType mType; - private Runnable mRunnable; - - public static Command newDoIfContains(Runnable runnable) { - return new Command(CommandType.DO_IF_CONTAINS, runnable); - } - - private Command(CommandType type) { - this(type, null); - } - - private Command(CommandType type, Runnable runnable) { - mType = type; - mRunnable = runnable; - } - - public CommandType getType() { - return mType; - } - - public void execute() { - if (mRunnable != null) { - mRunnable.run(); - } - } - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java index f095c3187308dc..889f98d60bd356 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricJSIModuleProvider.java @@ -12,6 +12,7 @@ import com.facebook.react.fabric.jsi.EventBeatManager; import com.facebook.react.fabric.jsi.EventEmitterWrapper; import com.facebook.react.fabric.jsi.FabricSoLoader; +import com.facebook.react.fabric.jsi.StateWrapperImpl; import com.facebook.react.fabric.mounting.ContextBasedViewPool; import com.facebook.react.fabric.mounting.LayoutMetricsConversions; import com.facebook.react.fabric.mounting.MountingManager; @@ -27,6 +28,7 @@ import com.facebook.react.fabric.mounting.mountitems.UpdateLayoutMountItem; import com.facebook.react.fabric.mounting.mountitems.UpdateLocalDataMountItem; import com.facebook.react.fabric.mounting.mountitems.UpdatePropsMountItem; +import com.facebook.react.uimanager.StateWrapper; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.systrace.Systrace; @@ -111,6 +113,7 @@ private static void loadClasses() { ComponentRegistry.class.getClass(); EventBeatManager.class.getClass(); EventEmitterWrapper.class.getClass(); + StateWrapperImpl.class.getClass(); FabricSoLoader.class.getClass(); PreAllocateViewMountItem.class.getClass(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index b714850e9f340c..433f2012f65019 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -20,6 +20,8 @@ import androidx.annotation.UiThread; import android.view.View; import com.facebook.common.logging.FLog; +import com.facebook.debug.holder.PrinterHolder; +import com.facebook.debug.tags.ReactDebugOverlayTags; import com.facebook.infer.annotation.ThreadConfined; import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.LifecycleEventListener; @@ -36,6 +38,7 @@ import com.facebook.react.fabric.jsi.EventBeatManager; import com.facebook.react.fabric.jsi.EventEmitterWrapper; import com.facebook.react.fabric.jsi.FabricSoLoader; +import com.facebook.react.fabric.jsi.StateWrapperImpl; import com.facebook.react.fabric.mounting.MountingManager; import com.facebook.react.fabric.mounting.mountitems.BatchMountItem; import com.facebook.react.fabric.mounting.mountitems.DeleteMountItem; @@ -48,8 +51,10 @@ import com.facebook.react.fabric.mounting.mountitems.UpdateLayoutMountItem; import com.facebook.react.fabric.mounting.mountitems.UpdateLocalDataMountItem; import com.facebook.react.fabric.mounting.mountitems.UpdatePropsMountItem; +import com.facebook.react.fabric.mounting.mountitems.UpdateStateMountItem; import com.facebook.react.modules.core.ReactChoreographer; import com.facebook.react.uimanager.ReactRootViewTagGenerator; +import com.facebook.react.uimanager.StateWrapper; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewManagerPropertyUpdater; import com.facebook.react.uimanager.ViewManagerRegistry; @@ -65,7 +70,9 @@ @SuppressLint("MissingNativeLoadLibrary") public class FabricUIManager implements UIManager, LifecycleEventListener { - private static final String TAG = FabricUIManager.class.getSimpleName(); + public static final String TAG = FabricUIManager.class.getSimpleName(); + public static final boolean DEBUG = + PrinterHolder.getPrinter().shouldDisplayLogMessage(ReactDebugOverlayTags.FABRIC_UI_MANAGER); private static final Map sComponentNames = new HashMap<>(); private static final int FRAME_TIME_MS = 16; @@ -80,14 +87,13 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { sComponentNames.put("Image", "RCTImageView"); sComponentNames.put("ScrollView", "RCTScrollView"); sComponentNames.put("Slider", "RCTSlider"); - sComponentNames.put("ReactPerformanceLoggerFlag", "ReactPerformanceLoggerFlag"); - sComponentNames.put("ReactTTRCRenderFlag", "ReactTTRCRenderFlag"); sComponentNames.put("Paragraph", "RCTText"); sComponentNames.put("Text", "RCText"); sComponentNames.put("RawText", "RCTRawText"); sComponentNames.put("ActivityIndicatorView", "AndroidProgressBar"); sComponentNames.put("ShimmeringView", "RKShimmeringView"); sComponentNames.put("TemplateView", "RCTTemplateView"); + sComponentNames.put("AxialGradientView", "RCTAxialGradientView"); } private Binding mBinding; @@ -161,7 +167,7 @@ public void removeRootView(int reactRootTag) { mMountingManager.removeRootView(reactRootTag); mReactContextForRootTag.remove(reactRootTag); } - + @Override public void initialize() { mEventDispatcher.registerEventEmitter(FABRIC, new FabricEventEmitter(this)); @@ -183,16 +189,17 @@ private void preallocateView( final String componentName, ReadableMap props, boolean isLayoutable) { - if (UiThreadUtil.isOnUiThread()) { - // There is no reason to allocate views ahead of time on the main thread. - return; - } - ThemedReactContext context = mReactContextForRootTag.get(rootTag); String component = sComponentNames.get(componentName); synchronized (mPreMountItemsLock) { mPreMountItems.add( - new PreAllocateViewMountItem(context, rootTag, reactTag, component, props, isLayoutable)); + new PreAllocateViewMountItem( + context, + rootTag, + reactTag, + component != null ? component : componentName, + props, + isLayoutable)); } } @@ -232,6 +239,12 @@ private MountItem updateLocalDataMountItem(int reactTag, ReadableMap newLocalDat return new UpdateLocalDataMountItem(reactTag, newLocalData); } + @DoNotStrip + @SuppressWarnings("unused") + private MountItem updateStateMountItem(int reactTag, Object stateWrapper) { + return new UpdateStateMountItem(reactTag, (StateWrapper) stateWrapper); + } + @DoNotStrip @SuppressWarnings("unused") private MountItem updateEventEmitterMountItem(int reactTag, Object eventEmitter) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/StateWrapperImpl.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/StateWrapperImpl.java new file mode 100644 index 00000000000000..b5f459f797da65 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/StateWrapperImpl.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

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.fabric.jsi; + +import android.annotation.SuppressLint; +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.NativeMap; +import com.facebook.react.bridge.ReadableNativeMap; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.StateWrapper; + +/** + * This class holds reference to the C++ EventEmitter object. Instances of this class are created on + * the Bindings.cpp, where the pointer to the C++ event emitter is set. + */ +@SuppressLint("MissingNativeLoadLibrary") +public class StateWrapperImpl implements StateWrapper { + static { + FabricSoLoader.staticInit(); + } + + @DoNotStrip private final HybridData mHybridData; + + private static native HybridData initHybrid(); + + private StateWrapperImpl() { + mHybridData = initHybrid(); + } + + @Override public native ReadableNativeMap getState(); + public native void updateStateImpl(NativeMap map); + + @Override public void updateState(WritableMap map) { + updateStateImpl((NativeMap)map); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/BUCK index 590d06c63c62f7..75b75835b3bd69 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/BUCK @@ -29,6 +29,7 @@ rn_xplat_cxx_library( react_native_xplat_target("config:config"), react_native_xplat_target("fabric/uimanager:uimanager"), react_native_xplat_target("fabric/components/scrollview:scrollview"), + react_native_xplat_target("utils:utils"), react_native_target("jni/react/jni:jni"), "fbsource//xplat/fbsystrace:fbsystrace", "fbsource//xplat/folly:molly", diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.cpp index 81a4ceb0572092..6dfc081c875d4b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/Binding.cpp @@ -6,6 +6,7 @@ #include "AsyncEventBeat.h" #include "EventEmitterWrapper.h" #include "ReactNativeConfigHolder.h" +#include "StateWrapperImpl.h" #include #include @@ -18,8 +19,8 @@ #include #include #include -#include #include +#include using namespace facebook::jni; using namespace facebook::jsi; @@ -276,6 +277,32 @@ local_ref createUpdateLocalData( castReadableMap(readableNativeMap).get()); } +local_ref createUpdateStateMountItem( + const jni::global_ref& javaUIManager, + const ShadowViewMutation& mutation) { + static auto updateStateInstruction = + jni::findClassStatic(UIManagerJavaDescriptor) + ->getMethod(jint, jobject)>( + "updateStateMountItem"); + + auto state = mutation.newChildShadowView.state; + + // We use state.get() to pass a raw pointer through the JNI + // We don't need to access the state ptr in Java, but we need to be able to + // pass a state object back through the JNI for state updates + + // Do not hold onto Java object from C + auto javaStateWrapper = StateWrapperImpl::newObjectJavaArgs(); + StateWrapperImpl* cStateWrapper = cthis(javaStateWrapper); + cStateWrapper->state_ = state.get(); + + return updateStateInstruction( + javaUIManager, + mutation.newChildShadowView.tag, + javaStateWrapper.get()); +} + + local_ref createRemoveMountItem( const jni::global_ref& javaUIManager, const ShadowViewMutation& mutation) { @@ -351,6 +378,11 @@ void Binding::schedulerDidFinishTransaction( mountItems[position++] = createUpdateLocalData(javaUIManager_, mutation); } + if (mutation.oldChildShadowView.state != + mutation.newChildShadowView.state) { + mountItems[position++] = + createUpdateStateMountItem(javaUIManager_, mutation); + } auto updateLayoutMountItem = createUpdateLayoutMountItem(javaUIManager_, mutation); @@ -371,31 +403,42 @@ void Binding::schedulerDidFinishTransaction( } case ShadowViewMutation::Insert: { if (!isVirtual) { - mountItems[position++] = - createInsertMountItem(javaUIManager_, mutation); + // Insert item + mountItems[position++] = createInsertMountItem(javaUIManager_, mutation); + // Props if (mutation.newChildShadowView.props->revision > 1) { mountItems[position++] = createUpdatePropsMountItem(javaUIManager_, mutation); } + // LocalData + if (mutation.newChildShadowView.localData) { + mountItems[position++] = + createUpdateLocalData(javaUIManager_, mutation); + } + + // Layout auto updateLayoutMountItem = createUpdateLayoutMountItem(javaUIManager_, mutation); if (updateLayoutMountItem) { mountItems[position++] = updateLayoutMountItem; } - if (mutation.newChildShadowView.localData) { + // State + if (mutation.newChildShadowView.state) { mountItems[position++] = - createUpdateLocalData(javaUIManager_, mutation); + createUpdateStateMountItem(javaUIManager_, mutation); } } + // EventEmitter auto updateEventEmitterMountItem = createUpdateEventEmitterMountItem(javaUIManager_, mutation); if (updateEventEmitterMountItem) { mountItems[position++] = updateEventEmitterMountItem; } + break; } default: { @@ -445,8 +488,9 @@ void Binding::schedulerDidRequestPreliminaryViewAllocation( local_ref readableMap = castReadableMap(ReadableNativeMap::newObjectCxxArgs(shadowView.props->rawProps)); + auto component = getPlatformComponentName(shadowView); preallocateView( - javaUIManager_, surfaceId, shadowView.tag, make_jstring(shadowView.componentName).get(), readableMap.get(), isLayoutableShadowNode); + javaUIManager_, surfaceId, shadowView.tag, component.get(), readableMap.get(), isLayoutableShadowNode); } void Binding::registerNatives() { diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/NodeStateWrapper.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/NodeStateWrapper.cpp new file mode 100644 index 00000000000000..b75952fdc19462 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/NodeStateWrapper.cpp @@ -0,0 +1,41 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#include "NodeStateWrapper.h" +#include +#include + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +jni::local_ref +NodeStateWrapper::initHybrid(jni::alias_ref) { + return makeCxxInstance(); +} + +jni::local_ref NodeStateWrapper::getState() { + folly::dynamic map = state_->getDynamic(); + local_ref readableNativeMap = + ReadableNativeMap::newObjectCxxArgs(map); + return readableNativeMap; +} + +void NodeStateWrapper::updateState(ReadableNativeMap* map) { + // Get folly::dynamic from map + auto dynamicMap = map->consume(); + // Set state + state_->updateState(dynamicMap); +} + +void NodeStateWrapper::registerNatives() { + registerHybrid({ + makeNativeMethod("getState", NodeStateWrapper::getState), + makeNativeMethod("updateState", NodeStateWrapper::updateState), + }); +} + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/NodeStateWrapper.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/NodeStateWrapper.h new file mode 100644 index 00000000000000..afa6af0031f847 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/NodeStateWrapper.h @@ -0,0 +1,32 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#pragma once + +#include +#include +#include + +namespace facebook { +namespace react { + +class NodeStateWrapper : public jni::HybridClass { + public: + constexpr static const char* const kJavaDescriptor = + "Lcom/facebook/react/fabric/NodeStateWrapper;"; + + NodeStateWrapper() {} + + static void registerNatives(); + + jni::local_ref getState(); + void updateState(ReadableNativeMap* map); + + const State* state_; + private: + static jni::local_ref initHybrid(jni::alias_ref); +}; + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/OnLoad.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/OnLoad.cpp index 42c10f45f9ba06..be20e3feeed567 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/OnLoad.cpp @@ -9,12 +9,14 @@ #include "Binding.h" #include "EventBeatManager.h" #include "EventEmitterWrapper.h" +#include "StateWrapperImpl.h" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { return facebook::xplat::initialize(vm, [] { facebook::react::Binding::registerNatives(); facebook::react::EventBeatManager::registerNatives(); facebook::react::EventEmitterWrapper::registerNatives(); + facebook::react::StateWrapperImpl::registerNatives(); facebook::react::ComponentFactoryDelegate::registerNatives(); }); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/StateWrapperImpl.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/StateWrapperImpl.cpp new file mode 100644 index 00000000000000..1e5a2db6ed89bd --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/StateWrapperImpl.cpp @@ -0,0 +1,45 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#include "StateWrapperImpl.h" +#include +#include + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +/** + * Called from Java constructor through the JNI. + */ +jni::local_ref +StateWrapperImpl::initHybrid(jni::alias_ref) { + return makeCxxInstance(); +} + +jni::local_ref StateWrapperImpl::getState() { + folly::dynamic map = state_->getDynamic(); + local_ref readableNativeMap = + ReadableNativeMap::newObjectCxxArgs(map); + return readableNativeMap; +} + +void StateWrapperImpl::updateStateImpl(NativeMap* map) { + // Get folly::dynamic from map + auto dynamicMap = map->consume(); + // Set state + state_->updateState(dynamicMap); +} + +void StateWrapperImpl::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", StateWrapperImpl::initHybrid), + makeNativeMethod("getState", StateWrapperImpl::getState), + makeNativeMethod("updateStateImpl", StateWrapperImpl::updateStateImpl), + }); +} + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/StateWrapperImpl.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/StateWrapperImpl.h new file mode 100644 index 00000000000000..0796f5ec836a6b --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsi/jni/StateWrapperImpl.h @@ -0,0 +1,34 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +#pragma once + +#include +#include +#include + +namespace facebook { +namespace react { + +class Instance; + +class StateWrapperImpl : public jni::HybridClass { + public: + constexpr static const char* const kJavaDescriptor = + "Lcom/facebook/react/fabric/jsi/StateWrapperImpl;"; + + static void registerNatives(); + + jni::local_ref getState(); + void updateStateImpl(NativeMap *map); + + const State* state_; + private: + jni::alias_ref jhybridobject_; + + static jni::local_ref initHybrid(jni::alias_ref); +}; + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ContextBasedViewPool.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ContextBasedViewPool.java index d0cd0fafeb56f3..88324df35ac014 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ContextBasedViewPool.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ContextBasedViewPool.java @@ -6,15 +6,14 @@ */ package com.facebook.react.fabric.mounting; -import androidx.annotation.UiThread; import android.view.View; -import com.facebook.react.bridge.UiThreadUtil; +import androidx.annotation.UiThread; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewManagerRegistry; import java.util.WeakHashMap; /** Class that provides pool for views based on {@link ThemedReactContext}. */ -public final class ContextBasedViewPool { +public final class ContextBasedViewPool implements ViewFactory { private final WeakHashMap mContextViewPoolHashMap = new WeakHashMap<>(); private final ViewManagerRegistry mViewManagerRegistry; @@ -25,19 +24,18 @@ public final class ContextBasedViewPool { @UiThread void createView(ThemedReactContext context, String componentName) { - UiThreadUtil.assertOnUiThread(); getViewPool(context).createView(componentName, context); } @UiThread - View getOrCreateView(String componentName, ThemedReactContext context) { - UiThreadUtil.assertOnUiThread(); + @Override + public View getOrCreateView(String componentName, ThemedReactContext context) { return getViewPool(context).getOrCreateView(componentName, context); } @UiThread - void returnToPool(ThemedReactContext context, String componentName, View view) { - UiThreadUtil.assertOnUiThread(); + @Override + public void recycle(ThemedReactContext context, String componentName, View view) { getViewPool(context).returnToPool(componentName, view); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java index d589e7ab5dde5f..dc143386b8ee59 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java @@ -17,6 +17,7 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableNativeMap; import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.fabric.FabricUIManager; @@ -26,11 +27,13 @@ import com.facebook.react.uimanager.ReactStylesDiffMap; import com.facebook.react.uimanager.RootView; import com.facebook.react.uimanager.RootViewManager; +import com.facebook.react.uimanager.StateWrapper; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.ViewManager; import com.facebook.react.uimanager.ViewManagerRegistry; import com.facebook.yoga.YogaMeasureMode; +import android.util.Log; import java.util.concurrent.ConcurrentHashMap; /** @@ -42,12 +45,12 @@ public class MountingManager { private final ConcurrentHashMap mTagToViewState; private final ViewManagerRegistry mViewManagerRegistry; private final RootViewManager mRootViewManager = new RootViewManager(); - private final ContextBasedViewPool mViewPool; + private final ViewFactory mViewFactory; public MountingManager(ViewManagerRegistry viewManagerRegistry) { mTagToViewState = new ConcurrentHashMap<>(); mViewManagerRegistry = viewManagerRegistry; - mViewPool = new ContextBasedViewPool(viewManagerRegistry); + mViewFactory = new ViewManagerFactory(viewManagerRegistry); } @UiThread @@ -91,7 +94,7 @@ private void dropView(View view) { mTagToViewState.remove(reactTag); Context context = view.getContext(); - mViewPool.returnToPool( + mViewFactory.recycle( (ThemedReactContext) context, Assertions.assertNotNull(viewManager).getName(), view); } @@ -174,7 +177,7 @@ public void createView( if (isLayoutable) { viewManager = mViewManagerRegistry.get(componentName); - view = mViewPool.getOrCreateView(componentName, themedReactContext); + view = mViewFactory.getOrCreateView(componentName, themedReactContext); view.setId(reactTag); } @@ -270,6 +273,26 @@ public void updateLocalData(int reactTag, ReadableMap newLocalData) { } } + @UiThread + public void updateState(final int reactTag, StateWrapper stateWrapper) { + UiThreadUtil.assertOnUiThread(); + ViewState viewState = getViewState(reactTag); + ReadableNativeMap newState = stateWrapper.getState(); + if (viewState.mCurrentState != null && viewState.mCurrentState.equals(newState)) { + return; + } + viewState.mCurrentState = newState; + + ViewManager viewManager = viewState.mViewManager; + + if (viewManager == null) { + throw new IllegalStateException("Unable to find ViewManager for tag: " + reactTag); + } + viewManager.updateState( + viewState.mView, + stateWrapper); + } + @UiThread public void preallocateView( ThemedReactContext reactContext, @@ -326,6 +349,7 @@ private static class ViewState { @Nullable final ViewManager mViewManager; public ReactStylesDiffMap mCurrentProps; public ReadableMap mCurrentLocalData; + public ReadableMap mCurrentState; public EventEmitterWrapper mEventEmitter; private ViewState(int reactTag, @Nullable View view, @Nullable ViewManager viewManager) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewFactory.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewFactory.java new file mode 100644 index 00000000000000..408119dc553291 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewFactory.java @@ -0,0 +1,18 @@ +/** + * 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.fabric.mounting; + +import android.view.View; +import com.facebook.react.uimanager.ThemedReactContext; + +public interface ViewFactory { + + View getOrCreateView(String componentName, ThemedReactContext context); + + void recycle(ThemedReactContext context, String componentName, View view); + +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewManagerFactory.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewManagerFactory.java new file mode 100644 index 00000000000000..46a053c994b9c0 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewManagerFactory.java @@ -0,0 +1,34 @@ +/** + * 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.fabric.mounting; + +import androidx.annotation.UiThread; +import android.view.View; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewManagerRegistry; + +public class ViewManagerFactory implements ViewFactory { + + private ViewManagerRegistry mViewManagerRegistry; + + ViewManagerFactory(ViewManagerRegistry viewManagerRegistry) { + mViewManagerRegistry = viewManagerRegistry; + } + + @UiThread + @Override + public View getOrCreateView( + String componentName, ThemedReactContext context) { + return mViewManagerRegistry.get(componentName).createView(context, null); + } + + @UiThread + @Override + public void recycle(ThemedReactContext context, String componentName, View view) { + // do nothing + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java index f204054e8ac9a8..7a0000b260cb2f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/BatchMountItem.java @@ -6,10 +6,15 @@ */ package com.facebook.react.fabric.mounting.mountitems; +import static com.facebook.react.fabric.FabricUIManager.DEBUG; +import static com.facebook.react.fabric.FabricUIManager.TAG; + import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.fabric.mounting.MountingManager; import com.facebook.systrace.Systrace; +import com.facebook.common.logging.FLog; + /** * This class represents a batch of {@link MountItem}s * @@ -40,10 +45,13 @@ public BatchMountItem(MountItem[] items, int size) { @Override public void execute(MountingManager mountingManager) { Systrace.beginSection( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "FabricUIManager::mountViews (" + mSize + " items)"); + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "FabricUIManager::mountViews - " + mSize + " items"); for (int mountItemIndex = 0; mountItemIndex < mSize; mountItemIndex++) { MountItem mountItem = mMountItems[mountItemIndex]; + if (DEBUG) { + FLog.d(TAG, "Executing mountItem: " + mountItem); + } mountItem.execute(mountingManager); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PreAllocateViewMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PreAllocateViewMountItem.java index fc01ad8ef47f1c..bdc7c7b68eb50f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PreAllocateViewMountItem.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/PreAllocateViewMountItem.java @@ -6,6 +6,10 @@ */ package com.facebook.react.fabric.mounting.mountitems; +import static com.facebook.react.fabric.FabricUIManager.DEBUG; +import static com.facebook.react.fabric.FabricUIManager.TAG; + +import com.facebook.common.logging.FLog; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.fabric.mounting.MountingManager; import com.facebook.react.uimanager.ThemedReactContext; @@ -21,7 +25,12 @@ public class PreAllocateViewMountItem implements MountItem { private final boolean mIsLayoutable; public PreAllocateViewMountItem( - ThemedReactContext context, int rootTag, int reactTag, String component, ReadableMap props, boolean isLayoutable) { + ThemedReactContext context, + int rootTag, + int reactTag, + String component, + ReadableMap props, + boolean isLayoutable) { mContext = context; mComponent = component; mRootTag = rootTag; @@ -32,11 +41,23 @@ public PreAllocateViewMountItem( @Override public void execute(MountingManager mountingManager) { + if (DEBUG) { + FLog.d(TAG, "Executing pre-allocation of: " + toString()); + } mountingManager.preallocateView(mContext, mComponent, mReactTag, mProps, mIsLayoutable); } @Override public String toString() { - return "[" + mRootTag + "] - Preallocate " + mComponent; + return "PreAllocateViewMountItem [" + + mReactTag + + "] - component: " + + mComponent + + " rootTag: " + + mRootTag + + " mIsLayoutable: " + + mIsLayoutable + + " props: " + + mProps; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLocalDataMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLocalDataMountItem.java index b6f334a9153480..0786e8f6138674 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLocalDataMountItem.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateLocalDataMountItem.java @@ -8,7 +8,6 @@ import com.facebook.react.fabric.mounting.MountingManager; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableNativeMap; public class UpdateLocalDataMountItem implements MountItem { diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateStateMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateStateMountItem.java new file mode 100644 index 00000000000000..e1dee17a1dbd78 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/UpdateStateMountItem.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

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.fabric.mounting.mountitems; + +import com.facebook.react.fabric.mounting.MountingManager; +import com.facebook.react.uimanager.StateWrapper; + +public class UpdateStateMountItem implements MountItem { + + private final int mReactTag; + private final StateWrapper mStateWrapper; + + public UpdateStateMountItem(int reactTag, StateWrapper stateWrapper) { + mReactTag = reactTag; + mStateWrapper = stateWrapper; + } + + @Override + public void execute(MountingManager mountingManager) { + mountingManager.updateState(mReactTag, mStateWrapper); + } + + @Override + public String toString() { + return "UpdateStateMountItem [" + mReactTag + "] - stateWrapper: " + mStateWrapper; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/camera/CameraRollManager.java b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/CameraRollManager.java index ceb838df72b430..73a7f4ae88f6f1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/camera/CameraRollManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/CameraRollManager.java @@ -417,7 +417,13 @@ private static boolean putImageInfo( float width = media.getInt(widthIndex); float height = media.getInt(heightIndex); - String mimeType = URLConnection.guessContentTypeFromName(photoUri.toString()); + String mimeType; + try { + mimeType = URLConnection.guessContentTypeFromName(photoUri.toString()); + } catch (StringIndexOutOfBoundsException e) { + FLog.e(ReactConstants.TAG, "Unable to guess content type from " + photoUri.toString(), e); + throw e; + } if (mimeType != null && mimeType.startsWith("video")) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/location/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/location/BUCK deleted file mode 100644 index f2c28f62e7b7cf..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/location/BUCK +++ /dev/null @@ -1,20 +0,0 @@ -load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library") - -rn_android_library( - name = "location", - srcs = glob(["**/*.java"]), - is_androidx = True, - visibility = [ - "PUBLIC", - ], - deps = [ - react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"), - react_native_dep("third-party/android/support/v4:lib-support-v4"), - react_native_dep("third-party/java/infer-annotations:infer-annotations"), - react_native_dep("third-party/java/jsr-305:jsr-305"), - react_native_target("java/com/facebook/react/bridge:bridge"), - react_native_target("java/com/facebook/react/common:common"), - react_native_target("java/com/facebook/react/module/annotations:annotations"), - react_native_target("java/com/facebook/react/modules/core:core"), - ], -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java deleted file mode 100644 index 46df8e28c291dc..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java +++ /dev/null @@ -1,377 +0,0 @@ -/** - * 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.modules.location; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.pm.PackageManager; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.location.LocationProvider; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import androidx.core.content.ContextCompat; -import com.facebook.common.logging.FLog; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.common.ReactConstants; -import com.facebook.react.common.SystemClock; -import com.facebook.react.module.annotations.ReactModule; -import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; -import javax.annotation.Nullable; - -/** - * Native module that exposes Geolocation to JS. - */ -@SuppressLint("MissingPermission") -@ReactModule(name = LocationModule.NAME) -public class LocationModule extends ReactContextBaseJavaModule { - - public static final String NAME = "LocationObserver"; - private @Nullable String mWatchedProvider; - private static final float RCT_DEFAULT_LOCATION_ACCURACY = 100; - - private final LocationListener mLocationListener = new LocationListener() { - @Override - public void onLocationChanged(Location location) { - getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class) - .emit("geolocationDidChange", locationToMap(location)); - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - if (status == LocationProvider.OUT_OF_SERVICE) { - emitError(PositionError.POSITION_UNAVAILABLE, "Provider " + provider + " is out of service."); - } else if (status == LocationProvider.TEMPORARILY_UNAVAILABLE) { - emitError(PositionError.TIMEOUT, "Provider " + provider + " is temporarily unavailable."); - } - } - - @Override - public void onProviderEnabled(String provider) { } - - @Override - public void onProviderDisabled(String provider) { } - }; - - public LocationModule(ReactApplicationContext reactContext) { - super(reactContext); - } - - @Override - public String getName() { - return NAME; - } - - private static class LocationOptions { - private final long timeout; - private final double maximumAge; - private final boolean highAccuracy; - private final float distanceFilter; - - private LocationOptions( - long timeout, - double maximumAge, - boolean highAccuracy, - float distanceFilter) { - this.timeout = timeout; - this.maximumAge = maximumAge; - this.highAccuracy = highAccuracy; - this.distanceFilter = distanceFilter; - } - - private static LocationOptions fromReactMap(ReadableMap map) { - // precision might be dropped on timeout (double -> int conversion), but that's OK - long timeout = - map.hasKey("timeout") ? (long) map.getDouble("timeout") : Long.MAX_VALUE; - double maximumAge = - map.hasKey("maximumAge") ? map.getDouble("maximumAge") : Double.POSITIVE_INFINITY; - boolean highAccuracy = - map.hasKey("enableHighAccuracy") && map.getBoolean("enableHighAccuracy"); - float distanceFilter = map.hasKey("distanceFilter") ? - (float) map.getDouble("distanceFilter") : - RCT_DEFAULT_LOCATION_ACCURACY; - - return new LocationOptions(timeout, maximumAge, highAccuracy, distanceFilter); - } - } - - /** - * Get the current position. This can return almost immediately if the location is cached or - * request an update, which might take a while. - * - * @param options map containing optional arguments: timeout (millis), maximumAge (millis) and - * highAccuracy (boolean) - */ - @ReactMethod - public void getCurrentPosition( - ReadableMap options, - final Callback success, - Callback error) { - LocationOptions locationOptions = LocationOptions.fromReactMap(options); - - try { - LocationManager locationManager = - (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE); - String provider = getValidProvider(locationManager, locationOptions.highAccuracy); - if (provider == null) { - error.invoke( - PositionError.buildError( - PositionError.POSITION_UNAVAILABLE, "No location provider available.")); - return; - } - Location location = locationManager.getLastKnownLocation(provider); - if (location != null && (SystemClock.currentTimeMillis() - location.getTime()) < locationOptions.maximumAge) { - success.invoke(locationToMap(location)); - return; - } - - new SingleUpdateRequest(locationManager, provider, locationOptions.timeout, success, error) - .invoke(location); - } catch (SecurityException e) { - throwLocationPermissionMissing(e); - } - } - - /** - * Start listening for location updates. These will be emitted via the - * {@link RCTDeviceEventEmitter} as {@code geolocationDidChange} events. - * - * @param options map containing optional arguments: highAccuracy (boolean) - */ - @ReactMethod - public void startObserving(ReadableMap options) { - if (LocationManager.GPS_PROVIDER.equals(mWatchedProvider)) { - return; - } - LocationOptions locationOptions = LocationOptions.fromReactMap(options); - - try { - LocationManager locationManager = - (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE); - String provider = getValidProvider(locationManager, locationOptions.highAccuracy); - if (provider == null) { - emitError(PositionError.POSITION_UNAVAILABLE, "No location provider available."); - return; - } - if (!provider.equals(mWatchedProvider)) { - locationManager.removeUpdates(mLocationListener); - locationManager.requestLocationUpdates( - provider, - 1000, - locationOptions.distanceFilter, - mLocationListener); - } - mWatchedProvider = provider; - } catch (SecurityException e) { - throwLocationPermissionMissing(e); - } - } - - /** - * Stop listening for location updates. - * - * NB: this is not balanced with {@link #startObserving}: any number of calls to that method will - * be canceled by just one call to this one. - */ - @ReactMethod - public void stopObserving() { - LocationManager locationManager = - (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE); - locationManager.removeUpdates(mLocationListener); - mWatchedProvider = null; - } - - @Nullable - private String getValidProvider(LocationManager locationManager, boolean highAccuracy) { - String provider = - highAccuracy ? LocationManager.GPS_PROVIDER : LocationManager.NETWORK_PROVIDER; - if (!locationManager.isProviderEnabled(provider)) { - provider = provider.equals(LocationManager.GPS_PROVIDER) - ? LocationManager.NETWORK_PROVIDER - : LocationManager.GPS_PROVIDER; - if (!locationManager.isProviderEnabled(provider)) { - return null; - } - } - // If it's an enabled provider, but we don't have permissions, ignore it - int finePermission = ContextCompat.checkSelfPermission(getReactApplicationContext(), android.Manifest.permission.ACCESS_FINE_LOCATION); - if (provider.equals(LocationManager.GPS_PROVIDER) && finePermission != PackageManager.PERMISSION_GRANTED) { - return null; - } - return provider; - } - - private static WritableMap locationToMap(Location location) { - WritableMap map = Arguments.createMap(); - WritableMap coords = Arguments.createMap(); - coords.putDouble("latitude", location.getLatitude()); - coords.putDouble("longitude", location.getLongitude()); - coords.putDouble("altitude", location.getAltitude()); - coords.putDouble("accuracy", location.getAccuracy()); - coords.putDouble("heading", location.getBearing()); - coords.putDouble("speed", location.getSpeed()); - map.putMap("coords", coords); - map.putDouble("timestamp", location.getTime()); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - map.putBoolean("mocked", location.isFromMockProvider()); - } - - return map; - } - - private void emitError(int code, String message) { - getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class) - .emit("geolocationError", PositionError.buildError(code, message)); - } - - /** - * Provides a clearer exception message than the default one. - */ - private static void throwLocationPermissionMissing(SecurityException e) { - throw new SecurityException( - "Looks like the app doesn't have the permission to access location.\n" + - "Add the following line to your app's AndroidManifest.xml:\n" + - "", e); - } - - private static class SingleUpdateRequest { - - private final Callback mSuccess; - private final Callback mError; - private final LocationManager mLocationManager; - private final String mProvider; - private final long mTimeout; - private Location mOldLocation; - private final Handler mHandler = new Handler(); - private final Runnable mTimeoutRunnable = new Runnable() { - @Override - public void run() { - synchronized (SingleUpdateRequest.this) { - if (!mTriggered) { - mError.invoke(PositionError.buildError(PositionError.TIMEOUT, "Location request timed out")); - mLocationManager.removeUpdates(mLocationListener); - FLog.i(ReactConstants.TAG, "LocationModule: Location request timed out"); - mTriggered = true; - } - } - } - }; - private final LocationListener mLocationListener = new LocationListener() { - @Override - public void onLocationChanged(Location location) { - synchronized (SingleUpdateRequest.this) { - if (!mTriggered && isBetterLocation(location, mOldLocation)) { - mSuccess.invoke(locationToMap(location)); - mHandler.removeCallbacks(mTimeoutRunnable); - mTriggered = true; - mLocationManager.removeUpdates(mLocationListener); - } - - mOldLocation = location; - } - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) {} - - @Override - public void onProviderEnabled(String provider) {} - - @Override - public void onProviderDisabled(String provider) {} - }; - private boolean mTriggered; - - private SingleUpdateRequest( - LocationManager locationManager, - String provider, - long timeout, - Callback success, - Callback error) { - mLocationManager = locationManager; - mProvider = provider; - mTimeout = timeout; - mSuccess = success; - mError = error; - } - - public void invoke(Location location) { - mOldLocation = location; - mLocationManager.requestLocationUpdates(mProvider, 100, 1, mLocationListener); - mHandler.postDelayed(mTimeoutRunnable, mTimeout); - } - - private static final int TWO_MINUTES = 1000 * 60 * 2; - - /** Determines whether one Location reading is better than the current Location fix - * taken from Android Examples https://developer.android.com/guide/topics/location/strategies.html - * - * @param location The new Location that you want to evaluate - * @param currentBestLocation The current Location fix, to which you want to compare the new one - */ - private boolean isBetterLocation(Location location, Location currentBestLocation) { - if (currentBestLocation == null) { - // A new location is always better than no location - return true; - } - - // Check whether the new location fix is newer or older - long timeDelta = location.getTime() - currentBestLocation.getTime(); - boolean isSignificantlyNewer = timeDelta > TWO_MINUTES; - boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES; - boolean isNewer = timeDelta > 0; - - // If it's been more than two minutes since the current location, use the new location - // because the user has likely moved - if (isSignificantlyNewer) { - return true; - // If the new location is more than two minutes older, it must be worse - } else if (isSignificantlyOlder) { - return false; - } - - // Check whether the new location fix is more or less accurate - int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy()); - boolean isLessAccurate = accuracyDelta > 0; - boolean isMoreAccurate = accuracyDelta < 0; - boolean isSignificantlyLessAccurate = accuracyDelta > 200; - - // Check if the old and new location are from the same provider - boolean isFromSameProvider = isSameProvider(location.getProvider(), - currentBestLocation.getProvider()); - - // Determine location quality using a combination of timeliness and accuracy - if (isMoreAccurate) { - return true; - } else if (isNewer && !isLessAccurate) { - return true; - } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { - return true; - } - - return false; - } - - /** Checks whether two providers are the same */ - private boolean isSameProvider(String provider1, String provider2) { - if (provider1 == null) { - return provider2 == null; - } - return provider1.equals(provider2); - } - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/location/PositionError.java b/ReactAndroid/src/main/java/com/facebook/react/modules/location/PositionError.java deleted file mode 100644 index b643382c831e7a..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/location/PositionError.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * 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.modules.location; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; - -/** - * @see {https://developer.mozilla.org/en-US/docs/Web/API/PositionError} - */ -public class PositionError { - /** - * The acquisition of the geolocation information failed because - * the page didn't have the permission to do it. - */ - public static int PERMISSION_DENIED = 1; - - /** - * The acquisition of the geolocation failed because at least one - * internal source of position returned an internal error. - */ - public static int POSITION_UNAVAILABLE = 2; - - /** - * The time allowed to acquire the geolocation, defined by - * PositionOptions.timeout information was reached before the information was obtained. - */ - public static int TIMEOUT = 3; - - public static WritableMap buildError(int code, String message) { - WritableMap error = Arguments.createMap(); - error.putInt("code", code); - - if (message != null) { - error.putString("message", message); - } - - /** - * Provide error types in error message. Feature parity with iOS - */ - error.putInt("PERMISSION_DENIED", PERMISSION_DENIED); - error.putInt("POSITION_UNAVAILABLE", POSITION_UNAVAILABLE); - error.putInt("TIMEOUT", TIMEOUT); - return error; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoHelpers.java b/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoHelpers.java index 01d3c6abbff6d6..c336a7b5d6ada8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoHelpers.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoHelpers.java @@ -24,7 +24,7 @@ public class AndroidInfoHelpers { public static final String METRO_HOST_PROP_NAME = "metro.host"; private static final int DEBUG_SERVER_HOST_PORT = 8081; - private static final int INSPECTOR_PROXY_PORT = 8082; + private static final int INSPECTOR_PROXY_PORT = 8081; private static final String TAG = AndroidInfoHelpers.class.getSimpleName(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK index c136b1beb84f5b..6e16ebfe191d6d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK @@ -34,7 +34,6 @@ rn_android_library( react_native_target("java/com/facebook/react/modules/i18nmanager:i18nmanager"), react_native_target("java/com/facebook/react/modules/image:image"), react_native_target("java/com/facebook/react/modules/intent:intent"), - react_native_target("java/com/facebook/react/modules/location:location"), react_native_target("java/com/facebook/react/modules/netinfo:netinfo"), react_native_target("java/com/facebook/react/modules/network:network"), react_native_target("java/com/facebook/react/modules/permissions:permissions"), @@ -63,7 +62,6 @@ rn_android_library( react_native_target("java/com/facebook/react/views/toolbar:toolbar"), react_native_target("java/com/facebook/react/views/view:view"), react_native_target("java/com/facebook/react/views/viewpager:viewpager"), - react_native_target("java/com/facebook/react/views/webview:webview"), react_native_target("java/com/facebook/react/module/annotations:annotations"), react_native_target("res:shell"), ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index 31a871d5efaca3..22348ac1dc63c0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -28,7 +28,6 @@ import com.facebook.react.modules.i18nmanager.I18nManagerModule; import com.facebook.react.modules.image.ImageLoaderModule; import com.facebook.react.modules.intent.IntentModule; -import com.facebook.react.modules.location.LocationModule; import com.facebook.react.modules.netinfo.NetInfoModule; import com.facebook.react.modules.network.NetworkingModule; import com.facebook.react.modules.permissions.PermissionsModule; @@ -63,7 +62,6 @@ import com.facebook.react.views.toolbar.ReactToolbarManager; import com.facebook.react.views.view.ReactViewManager; import com.facebook.react.views.viewpager.ReactViewPagerManager; -import com.facebook.react.views.webview.ReactWebViewManager; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -88,7 +86,6 @@ ImageLoaderModule.class, ImageStoreManager.class, IntentModule.class, - LocationModule.class, NativeAnimatedModule.class, NetworkingModule.class, NetInfoModule.class, @@ -239,14 +236,6 @@ public NativeModule get() { return new IntentModule(context); } }), - ModuleSpec.nativeModuleSpec( - LocationModule.class, - new Provider() { - @Override - public NativeModule get() { - return new LocationModule(context); - } - }), ModuleSpec.nativeModuleSpec( NativeAnimatedModule.class, new Provider() { @@ -347,7 +336,6 @@ public List createViewManagers(ReactApplicationContext reactContext viewManagers.add(new ReactSliderManager()); viewManagers.add(new ReactSwitchManager()); viewManagers.add(new ReactToolbarManager()); - viewManagers.add(new ReactWebViewManager()); viewManagers.add(new SwipeRefreshLayoutManager()); // Native equivalents diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/BUCK index 5803c6741e28d7..aab5a3f426835d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/BUCK @@ -33,6 +33,7 @@ rn_xplat_cxx_library( ], exported_deps = [ "fbsource//xplat/jsi:jsi", + react_native_xplat_target("cxxreact:bridge"), react_native_xplat_target("turbomodule/core:core"), ], ) diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/TurboModuleManager.cpp b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/TurboModuleManager.cpp index d5cd4feae69fd0..7fbf35eb87298b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/TurboModuleManager.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/TurboModuleManager.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -55,7 +56,10 @@ void TurboModuleManager::installJSIBindings() { TurboModuleBinding::install(*runtime_, std::make_shared( [this](const std::string &name) { const auto moduleInstance = getJavaModule(name); - const auto jsInvoker = std::make_shared(jsMessageQueueThread_); + // TODO: Pass in react Instance to JSCallInvoker instead. + std::shared_ptr instance = nullptr; + std::weak_ptr weakInstance = instance; + const auto jsInvoker = std::make_shared(weakInstance); return moduleProvider_(name, moduleInstance, jsInvoker); }) ); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK index 63f3e09cd5ab33..0b91cc607293da 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK @@ -29,7 +29,6 @@ rn_android_library( react_native_dep("third-party/java/infer-annotations:infer-annotations"), react_native_target("java/com/facebook/debug/tags:tags"), react_native_target("java/com/facebook/debug/holder:holder"), - react_native_target("java/com/facebook/react/animation:animation"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), react_native_target("java/com/facebook/react/config:config"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/IViewManagerWithChildren.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/IViewManagerWithChildren.java new file mode 100644 index 00000000000000..c07021107a0490 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/IViewManagerWithChildren.java @@ -0,0 +1,22 @@ +/** + * 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.uimanager; + +public interface IViewManagerWithChildren { + /** + * Returns whether this View type needs to handle laying out its own children instead of + * deferring to the standard css-layout algorithm. + * Returns true for the layout to *not* be automatically invoked. Instead onLayout will be + * invoked as normal and it is the View instance's responsibility to properly call layout on its + * children. + * Returns false for the default behavior of automatically laying out children without going + * through the ViewGroup's onLayout method. In that case, onLayout for this View type must *not* + * call layout on its children. + */ + public boolean needsCustomLayoutForChildren(); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java index 1b89228e59d253..3936518cf47cfd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java @@ -118,6 +118,12 @@ public void setMinWidth(Dynamic minWidth) { minWidth.recycle(); } + boolean mCollapsable; + @ReactProp(name = "collapsable") + public void setCollapsable(boolean collapsable) { + mCollapsable = collapsable; + } + @ReactProp(name = ViewProps.MAX_WIDTH) public void setMaxWidth(Dynamic maxWidth) { if (isVirtual()) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeKind.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeKind.java new file mode 100644 index 00000000000000..44ca85d02876ce --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeKind.java @@ -0,0 +1,25 @@ +/** + * 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.uimanager; + +// Common conditionals: +// - `kind == PARENT` checks whether the node can host children in the native tree. +// - `kind != NONE` checks whether the node appears in the native tree. + +public enum NativeKind { + // Node is in the native hierarchy and the HierarchyOptimizer should assume it can host children + // (e.g. because it's a ViewGroup). Note that it's okay if the node doesn't support children. When + // the HierarchyOptimizer generates children manipulation commands for that node, the + // HierarchyManager will catch this case and throw an exception. + PARENT, + // Node is in the native hierarchy, it may have children, but it cannot host them itself (e.g. + // because it isn't a ViewGroup). Consequently, its children need to be hosted by an ancestor. + LEAF, + // Node is not in the native hierarchy. + NONE +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index e846052e09f4e7..d42e865913baaf 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -21,9 +21,6 @@ import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; import com.facebook.react.R; -import com.facebook.react.animation.Animation; -import com.facebook.react.animation.AnimationListener; -import com.facebook.react.animation.AnimationRegistry; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReactContext; @@ -68,7 +65,6 @@ public class NativeViewHierarchyManager { private static final String TAG = NativeViewHierarchyManager.class.getSimpleName(); - private final AnimationRegistry mAnimationRegistry; private final SparseArray mTagsToViews; private final SparseArray mTagsToViewManagers; private final SparseBooleanArray mRootTags; @@ -86,7 +82,6 @@ public NativeViewHierarchyManager(ViewManagerRegistry viewManagers) { } public NativeViewHierarchyManager(ViewManagerRegistry viewManagers, RootViewManager manager) { - mAnimationRegistry = new AnimationRegistry(); mViewManagers = viewManagers; mTagsToViews = new SparseArray<>(); mTagsToViewManagers = new SparseArray<>(); @@ -111,10 +106,6 @@ public synchronized final ViewManager resolveViewManager(int tag) { return viewManager; } - public AnimationRegistry getAnimationRegistry() { - return mAnimationRegistry; - } - public void setLayoutAnimationEnabled(boolean enabled) { mLayoutAnimationEnabled = enabled; } @@ -195,16 +186,16 @@ public synchronized void updateLayout( // Check if the parent of the view has to layout the view, or the child has to lay itself out. if (!mRootTags.get(parentTag)) { ViewManager parentViewManager = mTagsToViewManagers.get(parentTag); - ViewGroupManager parentViewGroupManager; - if (parentViewManager instanceof ViewGroupManager) { - parentViewGroupManager = (ViewGroupManager) parentViewManager; + IViewManagerWithChildren parentViewManagerWithChildren; + if (parentViewManager instanceof IViewManagerWithChildren) { + parentViewManagerWithChildren = (IViewManagerWithChildren) parentViewManager; } else { throw new IllegalViewOperationException( "Trying to use view with tag " + parentTag + - " as a parent, but its Manager doesn't extends ViewGroupManager"); + " as a parent, but its Manager doesn't implement IViewManagerWithChildren"); } - if (parentViewGroupManager != null - && !parentViewGroupManager.needsCustomLayoutForChildren()) { + if (parentViewManagerWithChildren != null + && !parentViewManagerWithChildren.needsCustomLayoutForChildren()) { updateLayout(viewToUpdate, x, y, width, height); } } else { @@ -601,6 +592,10 @@ protected synchronized final void addRootViewGroup(int tag, View view) { */ protected synchronized void dropView(View view) { UiThreadUtil.assertOnUiThread(); + if (view == null) { + // Ignore this drop operation when view is null. + return; + } if (mTagsToViewManagers.get(view.getId()) == null) { // This view has already been dropped (likely due to a threading issue caused by async js // execution). Ignore this drop operation. @@ -738,53 +733,14 @@ public void clearJSResponder() { mJSResponderHandler.clearJSResponder(); } - void configureLayoutAnimation(final ReadableMap config) { - mLayoutAnimator.initializeFromConfig(config); + void configureLayoutAnimation(final ReadableMap config, final Callback onAnimationComplete) { + mLayoutAnimator.initializeFromConfig(config, onAnimationComplete); } void clearLayoutAnimation() { mLayoutAnimator.reset(); } - /* package */ synchronized void startAnimationForNativeView( - int reactTag, - Animation animation, - @Nullable final Callback animationCallback) { - UiThreadUtil.assertOnUiThread(); - View view = mTagsToViews.get(reactTag); - final int animationId = animation.getAnimationID(); - if (view != null) { - animation.setAnimationListener(new AnimationListener() { - @Override - public void onFinished() { - Animation removedAnimation = mAnimationRegistry.removeAnimation(animationId); - - // There's a chance that there was already a removeAnimation call enqueued on the main - // thread when this callback got enqueued on the main thread, but the Animation class - // should handle only calling one of onFinished and onCancel exactly once. - Assertions.assertNotNull(removedAnimation, "Animation was already removed somehow!"); - if (animationCallback != null) { - animationCallback.invoke(true); - } - } - - @Override - public void onCancel() { - Animation removedAnimation = mAnimationRegistry.removeAnimation(animationId); - - Assertions.assertNotNull(removedAnimation, "Animation was already removed somehow!"); - if (animationCallback != null) { - animationCallback.invoke(false); - } - } - }); - animation.start(view); - } else { - // TODO(5712813): cleanup callback in JS callbacks table in case of an error - throw new IllegalViewOperationException("View with tag " + reactTag + " not found"); - } - } - public synchronized void dispatchCommand( int reactTag, int commandId, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java index f371f7ed0a9a8c..a22e8b90501afd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java @@ -64,6 +64,15 @@ private static class NodeIndexPair { private final ShadowNodeRegistry mShadowNodeRegistry; private final SparseBooleanArray mTagsWithLayoutVisited = new SparseBooleanArray(); + public static void assertNodeSupportedWithoutOptimizer(ReactShadowNode node) { + // NativeKind.LEAF nodes require the optimizer. They are not ViewGroups so they cannot host + // their native children themselves. Their native children need to be hoisted by the optimizer + // to an ancestor which is a ViewGroup. + Assertions.assertCondition( + node.getNativeKind() != NativeKind.LEAF, + "Nodes with NativeKind.LEAF are not supported when the optimizer is disabled"); + } + public NativeViewHierarchyOptimizer( UIViewOperationQueue uiViewOperationQueue, ShadowNodeRegistry shadowNodeRegistry) { @@ -79,6 +88,7 @@ public void handleCreateView( ThemedReactContext themedContext, @Nullable ReactStylesDiffMap initialProps) { if (!ENABLED) { + assertNodeSupportedWithoutOptimizer(node); int tag = node.getReactTag(); mUIViewOperationQueue.enqueueCreateView( themedContext, @@ -92,7 +102,7 @@ public void handleCreateView( isLayoutOnlyAndCollapsable(initialProps); node.setIsLayoutOnly(isLayoutOnly); - if (!isLayoutOnly) { + if (node.getNativeKind() != NativeKind.NONE) { mUIViewOperationQueue.enqueueCreateView( themedContext, node.getReactTag(), @@ -118,6 +128,7 @@ public void handleUpdateView( String className, ReactStylesDiffMap props) { if (!ENABLED) { + assertNodeSupportedWithoutOptimizer(node); mUIViewOperationQueue.enqueueUpdateProperties(node.getReactTag(), className, props); return; } @@ -148,6 +159,7 @@ public void handleManageChildren( int[] tagsToDelete, int[] indicesToDelete) { if (!ENABLED) { + assertNodeSupportedWithoutOptimizer(nodeToManage); mUIViewOperationQueue.enqueueManageChildren( nodeToManage.getReactTag(), indicesToRemove, @@ -189,6 +201,7 @@ public void handleSetChildren( ReadableArray childrenTags ) { if (!ENABLED) { + assertNodeSupportedWithoutOptimizer(nodeToManage); mUIViewOperationQueue.enqueueSetChildren( nodeToManage.getReactTag(), childrenTags); @@ -208,8 +221,9 @@ public void handleSetChildren( */ public void handleUpdateLayout(ReactShadowNode node) { if (!ENABLED) { + assertNodeSupportedWithoutOptimizer(node); mUIViewOperationQueue.enqueueUpdateLayout( - Assertions.assertNotNull(node.getParent()).getReactTag(), + Assertions.assertNotNull(node.getLayoutParent()).getReactTag(), node.getReactTag(), node.getScreenX(), node.getScreenY(), @@ -221,6 +235,12 @@ public void handleUpdateLayout(ReactShadowNode node) { applyLayoutBase(node); } + public void handleForceViewToBeNonLayoutOnly(ReactShadowNode node) { + if (node.isLayoutOnly()) { + transitionLayoutOnlyViewToNativeView(node, null); + } + } + /** * Processes the shadow hierarchy to dispatch all necessary updateLayout calls to the native * hierarchy. Should be called after all updateLayout calls for a batch have been handled. @@ -229,16 +249,18 @@ public void onBatchComplete() { mTagsWithLayoutVisited.clear(); } - private NodeIndexPair walkUpUntilNonLayoutOnly( + private NodeIndexPair walkUpUntilNativeKindIsParent( ReactShadowNode node, int indexInNativeChildren) { - while (node.isLayoutOnly()) { + while (node.getNativeKind() != NativeKind.PARENT) { ReactShadowNode parent = node.getParent(); if (parent == null) { return null; } - indexInNativeChildren = indexInNativeChildren + parent.getNativeOffsetForChild(node); + indexInNativeChildren = indexInNativeChildren + + (node.getNativeKind() == NativeKind.LEAF ? 1 : 0) + + parent.getNativeOffsetForChild(node); node = parent; } @@ -247,8 +269,8 @@ private NodeIndexPair walkUpUntilNonLayoutOnly( private void addNodeToNode(ReactShadowNode parent, ReactShadowNode child, int index) { int indexInNativeChildren = parent.getNativeOffsetForChild(parent.getChildAt(index)); - if (parent.isLayoutOnly()) { - NodeIndexPair result = walkUpUntilNonLayoutOnly(parent, indexInNativeChildren); + if (parent.getNativeKind() != NativeKind.PARENT) { + NodeIndexPair result = walkUpUntilNativeKindIsParent(parent, indexInNativeChildren); if (result == null) { // If the parent hasn't been attached to its native parent yet, don't issue commands to the // native hierarchy. We'll do that when the parent node actually gets attached somewhere. @@ -258,20 +280,26 @@ private void addNodeToNode(ReactShadowNode parent, ReactShadowNode child, int in indexInNativeChildren = result.index; } - if (!child.isLayoutOnly()) { - addNonLayoutNode(parent, child, indexInNativeChildren); + if (child.getNativeKind() != NativeKind.NONE) { + addNativeChild(parent, child, indexInNativeChildren); } else { - addLayoutOnlyNode(parent, child, indexInNativeChildren); + addNonNativeChild(parent, child, indexInNativeChildren); } } /** - * For handling node removal from manageChildren. In the case of removing a layout-only node, we - * need to instead recursively remove all its children from their native parents. + * For handling node removal from manageChildren. In the case of removing a node which isn't + * hosting its own children (e.g. layout-only or NativeKind.LEAF), we need to recursively remove + * all its children from their native parents. */ private void removeNodeFromParent(ReactShadowNode nodeToRemove, boolean shouldDelete) { - ReactShadowNode nativeNodeToRemoveFrom = nodeToRemove.getNativeParent(); + if (nodeToRemove.getNativeKind() != NativeKind.PARENT) { + for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) { + removeNodeFromParent(nodeToRemove.getChildAt(i), shouldDelete); + } + } + ReactShadowNode nativeNodeToRemoveFrom = nodeToRemove.getNativeParent(); if (nativeNodeToRemoveFrom != null) { int index = nativeNodeToRemoveFrom.indexOfNativeChild(nodeToRemove); nativeNodeToRemoveFrom.removeNativeChildAt(index); @@ -282,21 +310,17 @@ private void removeNodeFromParent(ReactShadowNode nodeToRemove, boolean shouldDe null, shouldDelete ? new int[] {nodeToRemove.getReactTag()} : null, shouldDelete ? new int[] {index} : null); - } else { - for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) { - removeNodeFromParent(nodeToRemove.getChildAt(i), shouldDelete); - } } } - private void addLayoutOnlyNode( - ReactShadowNode nonLayoutOnlyNode, - ReactShadowNode layoutOnlyNode, + private void addNonNativeChild( + ReactShadowNode nativeParent, + ReactShadowNode nonNativeChild, int index) { - addGrandchildren(nonLayoutOnlyNode, layoutOnlyNode, index); + addGrandchildren(nativeParent, nonNativeChild, index); } - private void addNonLayoutNode( + private void addNativeChild( ReactShadowNode parent, ReactShadowNode child, int index) { @@ -307,13 +331,17 @@ private void addNonLayoutNode( new ViewAtIndex[] {new ViewAtIndex(child.getReactTag(), index)}, null, null); + + if (child.getNativeKind() != NativeKind.PARENT) { + addGrandchildren(parent, child, index + 1); + } } private void addGrandchildren( ReactShadowNode nativeParent, ReactShadowNode child, int index) { - Assertions.assertCondition(!nativeParent.isLayoutOnly()); + Assertions.assertCondition(child.getNativeKind() != NativeKind.PARENT); // `child` can't hold native children. Add all of `child`'s children to `parent`. int currentIndex = index; @@ -321,16 +349,15 @@ private void addGrandchildren( ReactShadowNode grandchild = child.getChildAt(i); Assertions.assertCondition(grandchild.getNativeParent() == null); - if (grandchild.isLayoutOnly()) { - // Adding this child could result in adding multiple native views - int grandchildCountBefore = nativeParent.getNativeChildCount(); - addLayoutOnlyNode(nativeParent, grandchild, currentIndex); - int grandchildCountAfter = nativeParent.getNativeChildCount(); - currentIndex += grandchildCountAfter - grandchildCountBefore; + // Adding this child could result in adding multiple native views + int grandchildCountBefore = nativeParent.getNativeChildCount(); + if (grandchild.getNativeKind() == NativeKind.NONE) { + addNonNativeChild(nativeParent, grandchild, currentIndex); } else { - addNonLayoutNode(nativeParent, grandchild, currentIndex); - currentIndex++; + addNativeChild(nativeParent, grandchild, currentIndex); } + int grandchildCountAfter = nativeParent.getNativeChildCount(); + currentIndex += grandchildCountAfter - grandchildCountBefore; } } @@ -349,10 +376,16 @@ private void applyLayoutBase(ReactShadowNode node) { int x = node.getScreenX(); int y = node.getScreenY(); - while (parent != null && parent.isLayoutOnly()) { - // TODO(7854667): handle and test proper clipping - x += Math.round(parent.getLayoutX()); - y += Math.round(parent.getLayoutY()); + while (parent != null && parent.getNativeKind() != NativeKind.PARENT) { + if (!parent.isVirtual()) { + // Skip these additions for virtual nodes. This has the same effect as `getLayout*` + // returning `0`. Virtual nodes aren't in the Yoga tree so we can't call `getLayout*` on + // them. + + // TODO(7854667): handle and test proper clipping + x += Math.round(parent.getLayoutX()); + y += Math.round(parent.getLayoutY()); + } parent = parent.getParent(); } @@ -361,10 +394,10 @@ private void applyLayoutBase(ReactShadowNode node) { } private void applyLayoutRecursive(ReactShadowNode toUpdate, int x, int y) { - if (!toUpdate.isLayoutOnly() && toUpdate.getNativeParent() != null) { + if (toUpdate.getNativeKind() != NativeKind.NONE && toUpdate.getNativeParent() != null) { int tag = toUpdate.getReactTag(); mUIViewOperationQueue.enqueueUpdateLayout( - toUpdate.getNativeParent().getReactTag(), + toUpdate.getLayoutParent().getReactTag(), tag, x, y, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java index 7d3e43aac40657..50c7140466bbd6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java @@ -48,15 +48,16 @@ public interface ReactShadowNode { /** * Nodes that return {@code true} will be treated as "virtual" nodes. That is, nodes that are not - * mapped into native views (e.g. nested text node). By default this method returns {@code false}. + * mapped into native views or Yoga nodes (e.g. nested text node). By default this method returns + * {@code false}. */ boolean isVirtual(); /** * Nodes that return {@code true} will be treated as a root view for the virtual nodes tree. It - * means that {@link NativeViewHierarchyManager} will not try to perform {@code manageChildren} - * operation on such views. Good example is {@code InputText} view that may have children {@code - * Text} nodes but this whole hierarchy will be mapped to a single android {@link EditText} view. + * means that all of its descendants will be "virtual" nodes. Good example is {@code InputText} + * view that may have children {@code Text} nodes but this whole hierarchy will be mapped to a + * single android {@link EditText} view. */ boolean isVirtualAnchor(); @@ -68,6 +69,14 @@ public interface ReactShadowNode { */ boolean isYogaLeafNode(); + /** + * When constructing the native tree, nodes that return {@code true} will be treated as leaves. + * Instead of adding this view's native children as subviews of it, they will be added as subviews + * of an ancestor. In other words, this view wants to support native children but it cannot host + * them itself (e.g. it isn't a ViewGroup). + */ + boolean hoistNativeChildren(); + String getViewClass(); boolean hasUpdates(); @@ -99,7 +108,7 @@ public interface ReactShadowNode { * layout. Will be only called for nodes that are marked as updated with {@link #markUpdated()} or * require layouting (marked with {@link #dirty()}). */ - void onBeforeLayout(); + void onBeforeLayout(NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer); void updateProperties(ReactStylesDiffMap props); @@ -135,6 +144,12 @@ public interface ReactShadowNode { @Nullable T getParent(); + // Returns the node that is responsible for laying out this node. + @Nullable + T getLayoutParent(); + + void setLayoutParent(@Nullable T layoutParent); + /** * Get the {@link ThemedReactContext} associated with this {@link ReactShadowNode}. This will * never change during the lifetime of a {@link ReactShadowNode} instance, but different instances @@ -179,6 +194,8 @@ public interface ReactShadowNode { boolean isLayoutOnly(); + NativeKind getNativeKind(); + int getTotalNativeChildren(); boolean isDescendantOf(T ancestorNode); @@ -354,4 +371,6 @@ public interface ReactShadowNode { Integer getWidthMeasureSpec(); Integer getHeightMeasureSpec(); + + Iterable calculateLayoutOnChildren(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java index 5c44f6c4d97d9a..47a2a7157e4735 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java @@ -67,6 +67,7 @@ public class ReactShadowNodeImpl implements ReactShadowNode private boolean mNodeUpdated = true; private @Nullable ArrayList mChildren; private @Nullable ReactShadowNodeImpl mParent; + private @Nullable ReactShadowNodeImpl mLayoutParent; // layout-only nodes private boolean mIsLayoutOnly; @@ -98,7 +99,8 @@ public ReactShadowNodeImpl() { /** * Nodes that return {@code true} will be treated as "virtual" nodes. That is, nodes that are not - * mapped into native views (e.g. nested text node). By default this method returns {@code false}. + * mapped into native views or Yoga nodes (e.g. nested text node). By default this method returns + * {@code false}. */ @Override public boolean isVirtual() { @@ -107,9 +109,9 @@ public boolean isVirtual() { /** * Nodes that return {@code true} will be treated as a root view for the virtual nodes tree. It - * means that {@link NativeViewHierarchyManager} will not try to perform {@code manageChildren} - * operation on such views. Good example is {@code InputText} view that may have children {@code - * Text} nodes but this whole hierarchy will be mapped to a single android {@link EditText} view. + * means that all of its descendants will be "virtual" nodes. Good example is {@code InputText} + * view that may have children {@code Text} nodes but this whole hierarchy will be mapped to a + * single android {@link EditText} view. */ @Override public boolean isVirtualAnchor() { @@ -127,6 +129,17 @@ public boolean isYogaLeafNode() { return isMeasureDefined(); } + /** + * When constructing the native tree, nodes that return {@code true} will be treated as leaves. + * Instead of adding this view's native children as subviews of it, they will be added as subviews + * of an ancestor. In other words, this view wants to support native children but it cannot host + * them itself (e.g. it isn't a ViewGroup). + */ + @Override + public boolean hoistNativeChildren() { + return false; + } + @Override public final String getViewClass() { return Assertions.assertNotNull(mViewClassName); @@ -166,6 +179,18 @@ public final boolean hasUnseenUpdates() { public void dirty() { if (!isVirtual()) { mYogaNode.dirty(); + } else if (getParent() != null) { + // Virtual nodes aren't involved in layout but they need to have the dirty signal + // propagated to their ancestors. + // + // TODO: There are some edge cases that currently aren't supported. For example, if the size + // of your inline image/view changes, its size on-screen is not be updated. Similarly, + // if the size of a view inside of an inline view changes, its size on-screen is not + // updated. The problem may be that dirty propagation stops at inline views because the + // parent of each inline view is null. A possible fix would be to implement an `onDirty` + // handler in Yoga that will propagate the dirty signal to the ancestors of the inline view. + // + getParent().dirty(); } } @@ -199,7 +224,7 @@ public void addChildAt(ReactShadowNodeImpl child, int i) { } markUpdated(); - int increase = child.isLayoutOnly() ? child.getTotalNativeChildren() : 1; + int increase = child.getTotalNativeNodeContributionToParent(); mTotalNativeChildren += increase; updateNativeChildrenCountInParent(increase); @@ -219,7 +244,7 @@ public ReactShadowNodeImpl removeChildAt(int i) { } markUpdated(); - int decrease = removed.isLayoutOnly() ? removed.getTotalNativeChildren() : 1; + int decrease = removed.getTotalNativeNodeContributionToParent(); mTotalNativeChildren -= decrease; updateNativeChildrenCountInParent(-decrease); return removed; @@ -257,9 +282,8 @@ public void removeAndDisposeAllChildren() { } ReactShadowNodeImpl toRemove = getChildAt(i); toRemove.mParent = null; + decrease += toRemove.getTotalNativeNodeContributionToParent(); toRemove.dispose(); - - decrease += toRemove.isLayoutOnly() ? toRemove.getTotalNativeChildren() : 1; } Assertions.assertNotNull(mChildren).clear(); markUpdated(); @@ -269,11 +293,11 @@ public void removeAndDisposeAllChildren() { } private void updateNativeChildrenCountInParent(int delta) { - if (mIsLayoutOnly) { + if (getNativeKind() != NativeKind.PARENT) { ReactShadowNodeImpl parent = getParent(); while (parent != null) { parent.mTotalNativeChildren += delta; - if (!parent.isLayoutOnly()) { + if (parent.getNativeKind() == NativeKind.PARENT) { break; } parent = parent.getParent(); @@ -287,7 +311,8 @@ private void updateNativeChildrenCountInParent(int delta) { * require layouting (marked with {@link #dirty()}). */ @Override - public void onBeforeLayout() {} + public void onBeforeLayout(NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) { + } @Override public final void updateProperties(ReactStylesDiffMap props) { @@ -397,6 +422,17 @@ public final void setViewClassName(String viewClassName) { return mParent; } + // Returns the node that is responsible for laying out this node. + @Override + public final @Nullable ReactShadowNodeImpl getLayoutParent() { + return mLayoutParent != null ? mLayoutParent : getNativeParent(); + } + + @Override + public final void setLayoutParent(@Nullable ReactShadowNodeImpl layoutParent) { + mLayoutParent = layoutParent; + } + /** * Get the {@link ThemedReactContext} associated with this {@link ReactShadowNodeImpl}. This will * never change during the lifetime of a {@link ReactShadowNodeImpl} instance, but different @@ -446,8 +482,8 @@ public final void markLayoutSeen() { */ @Override public final void addNativeChildAt(ReactShadowNodeImpl child, int nativeIndex) { - Assertions.assertCondition(!mIsLayoutOnly); - Assertions.assertCondition(!child.mIsLayoutOnly); + Assertions.assertCondition(getNativeKind() == NativeKind.PARENT); + Assertions.assertCondition(child.getNativeKind() != NativeKind.NONE); if (mNativeChildren == null) { mNativeChildren = new ArrayList<>(4); @@ -508,6 +544,14 @@ public final boolean isLayoutOnly() { return mIsLayoutOnly; } + @Override + public NativeKind getNativeKind() { + return + isVirtual() || isLayoutOnly() ? NativeKind.NONE : + hoistNativeChildren() ? NativeKind.LEAF : + NativeKind.PARENT; + } + @Override public final int getTotalNativeChildren() { return mTotalNativeChildren; @@ -531,6 +575,14 @@ public boolean isDescendantOf(ReactShadowNodeImpl ancestorNode) { return isDescendant; } + private int getTotalNativeNodeContributionToParent() { + NativeKind kind = getNativeKind(); + return + kind == NativeKind.NONE ? mTotalNativeChildren : + kind == NativeKind.LEAF ? 1 + mTotalNativeChildren : + 1; // kind == NativeKind.PARENT + } + @Override public String toString() { return "[" + mViewClassName + " " + getReactTag() + "]"; @@ -585,7 +637,7 @@ public final int getNativeOffsetForChild(ReactShadowNodeImpl child) { found = true; break; } - index += (current.isLayoutOnly() ? current.getTotalNativeChildren() : 1); + index += current.getTotalNativeNodeContributionToParent(); } if (!found) { throw new RuntimeException( @@ -978,4 +1030,13 @@ public Integer getWidthMeasureSpec() { public Integer getHeightMeasureSpec() { return mHeightMeasureSpec; } + + @Override + public Iterable calculateLayoutOnChildren() { + return isVirtualAnchor() ? + // All of the descendants are virtual so none of them are involved in layout. + null : + // Just return the children. Flexbox calculations have already been run on them. + mChildren; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.java new file mode 100644 index 00000000000000..2f6ba96d84730d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + *

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.uimanager; + +import com.facebook.react.bridge.ReadableNativeMap; +import com.facebook.react.bridge.WritableMap; + +/** + * This is a wrapper that can be used for passing State objects from Fabric C++ + * core to platform-specific components in Java. + * State allows you to break out of uni-directional dataflow by calling updateState, + * which communicates state back to the C++ layer. + */ +public interface StateWrapper { + /** + * Get a ReadableNativeMap object from the C++ layer, which is a K/V map of + * string keys to values. + */ + ReadableNativeMap getState(); + + /** + * Pass a map of values back to the C++ layer. + */ + void updateState(WritableMap map); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index decf97488273d4..dfa310b76d4e26 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -11,7 +11,6 @@ import android.view.View.MeasureSpec; import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; -import com.facebook.react.animation.Animation; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; @@ -429,15 +428,13 @@ public void manageChildren( cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex); } - if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) { - mNativeViewHierarchyOptimizer.handleManageChildren( - cssNodeToManage, - indicesToRemove, - tagsToRemove, - viewsToAdd, - tagsToDelete, - indicesToDelete); - } + mNativeViewHierarchyOptimizer.handleManageChildren( + cssNodeToManage, + indicesToRemove, + tagsToRemove, + viewsToAdd, + tagsToDelete, + indicesToDelete); for (int i = 0; i < tagsToDelete.length; i++) { removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i])); @@ -467,11 +464,9 @@ public void setChildren( cssNodeToManage.addChildAt(cssNodeToAdd, i); } - if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) { - mNativeViewHierarchyOptimizer.handleSetChildren( - cssNodeToManage, - childrenTags); - } + mNativeViewHierarchyOptimizer.handleSetChildren( + cssNodeToManage, + childrenTags); } } @@ -698,29 +693,6 @@ protected void updateViewHierarchy() { } } - /** - * Registers a new Animation that can then be added to a View using {@link #addAnimation}. - */ - public void registerAnimation(Animation animation) { - mOperationsQueue.enqueueRegisterAnimation(animation); - } - - /** - * Adds an Animation previously registered with {@link #registerAnimation} to a View and starts it - */ - public void addAnimation(int reactTag, int animationID, Callback onSuccess) { - assertViewExists(reactTag, "addAnimation"); - mOperationsQueue.enqueueAddAnimation(reactTag, animationID, onSuccess); - } - - /** - * Removes an existing Animation, canceling it if it was in progress. - */ - public void removeAnimation(int reactTag, int animationID) { - assertViewExists(reactTag, "removeAnimation"); - mOperationsQueue.enqueueRemoveAnimation(animationID); - } - /** * LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled * explicitly in order to avoid regression in existing application written for iOS using this API. @@ -748,11 +720,8 @@ public void setLayoutAnimationEnabledExperimental(boolean enabled) { * interrupted. In this case, callback parameter will be false. * @param error will be called if there was an error processing the animation */ - public void configureNextLayoutAnimation( - ReadableMap config, - Callback success, - Callback error) { - mOperationsQueue.enqueueConfigureLayoutAnimation(config, success, error); + public void configureNextLayoutAnimation(ReadableMap config, Callback success) { + mOperationsQueue.enqueueConfigureLayoutAnimation(config, success); } public void setJSResponder(int reactTag, boolean blockNativeResponder) { @@ -764,7 +733,7 @@ public void setJSResponder(int reactTag, boolean blockNativeResponder) { return; } - while (node.isVirtual() || node.isLayoutOnly()) { + while (node.getNativeKind() == NativeKind.NONE) { node = node.getParent(); } mOperationsQueue.enqueueSetJSResponder(node.getReactTag(), reactTag, blockNativeResponder); @@ -903,14 +872,14 @@ private void assertViewExists(int reactTag, String operationNameForExceptionMess private void assertNodeDoesNotNeedCustomLayoutForChildren(ReactShadowNode node) { ViewManager viewManager = Assertions.assertNotNull(mViewManagers.get(node.getViewClass())); - ViewGroupManager viewGroupManager; - if (viewManager instanceof ViewGroupManager) { - viewGroupManager = (ViewGroupManager) viewManager; + IViewManagerWithChildren viewManagerWithChildren; + if (viewManager instanceof IViewManagerWithChildren) { + viewManagerWithChildren = (IViewManagerWithChildren) viewManager; } else { throw new IllegalViewOperationException("Trying to use view " + node.getViewClass() + " as a parent, but its Manager doesn't extends ViewGroupManager"); } - if (viewGroupManager != null && viewGroupManager.needsCustomLayoutForChildren()) { + if (viewManagerWithChildren != null && viewManagerWithChildren.needsCustomLayoutForChildren()) { throw new IllegalViewOperationException( "Trying to measure a view using measureLayout/measureLayoutRelativeToParent relative to" + " an ancestor that requires custom layout for it's children (" + node.getViewClass() + @@ -925,7 +894,7 @@ private void notifyOnBeforeLayoutRecursive(ReactShadowNode cssNode) { for (int i = 0; i < cssNode.getChildCount(); i++) { notifyOnBeforeLayoutRecursive(cssNode.getChildAt(i)); } - cssNode.onBeforeLayout(); + cssNode.onBeforeLayout(mNativeViewHierarchyOptimizer); } protected void calculateRootLayout(ReactShadowNode cssRoot) { @@ -957,10 +926,11 @@ protected void applyUpdatesRecursive( return; } - if (!cssNode.isVirtualAnchor()) { - for (int i = 0; i < cssNode.getChildCount(); i++) { + Iterable cssChildren = cssNode.calculateLayoutOnChildren(); + if (cssChildren != null) { + for (ReactShadowNode cssChild : cssChildren) { applyUpdatesRecursive( - cssNode.getChildAt(i), + cssChild, absoluteX + cssNode.getLayoutX(), absoluteY + cssNode.getLayoutY()); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 1d2e4fbb6a7749..51a229f73ae72e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -20,7 +20,6 @@ import com.facebook.common.logging.FLog; import com.facebook.debug.holder.PrinterHolder; import com.facebook.debug.tags.ReactDebugOverlayTags; -import com.facebook.react.animation.Animation; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.GuardedRunnable; @@ -635,23 +634,6 @@ public void viewIsDescendantOf( mUIImplementation.viewIsDescendantOf(reactTag, ancestorReactTag, callback); } - /** Registers a new Animation that can then be added to a View using {@link #addAnimation}. */ - public void registerAnimation(Animation animation) { - mUIImplementation.registerAnimation(animation); - } - - /** - * Adds an Animation previously registered with {@link #registerAnimation} to a View and starts it - */ - public void addAnimation(int reactTag, int animationID, Callback onSuccess) { - mUIImplementation.addAnimation(reactTag, animationID, onSuccess); - } - - /** Removes an existing Animation, canceling it if it was in progress. */ - public void removeAnimation(int reactTag, int animationID) { - mUIImplementation.removeAnimation(reactTag, animationID); - } - @Override @ReactMethod public void setJSResponder(int reactTag, boolean blockNativeResponder) { @@ -727,8 +709,7 @@ public void setLayoutAnimationEnabledExperimental(boolean enabled) { * Configure an animation to be used for the native layout changes, and native views creation. The * animation will only apply during the current batch operations. * - *

TODO(7728153) : animating view deletion is currently not supported. TODO(7613721) : - * callbacks are not supported, this feature will likely be killed. + *

TODO(7728153) : animating view deletion is currently not supported. * * @param config the configuration of the animation for view addition/removal/update. * @param success will be called when the animation completes, or when the animation get @@ -737,7 +718,7 @@ public void setLayoutAnimationEnabledExperimental(boolean enabled) { */ @ReactMethod public void configureNextLayoutAnimation(ReadableMap config, Callback success, Callback error) { - mUIImplementation.configureNextLayoutAnimation(config, success, error); + mUIImplementation.configureNextLayoutAnimation(config, success); } /** @@ -878,4 +859,9 @@ public void onConfigurationChanged(Configuration newConfig) {} @Override public void onLowMemory() {} } + + public View resolveView(int tag) { + UiThreadUtil.assertOnUiThread(); + return mUIImplementation.getUIViewOperationQueue().getNativeViewHierarchyManager().resolveView(tag); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index 4d84d7545c03ef..cf66d2a5c50f40 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -10,8 +10,6 @@ import android.os.SystemClock; import android.view.View; import com.facebook.common.logging.FLog; -import com.facebook.react.animation.Animation; -import com.facebook.react.animation.AnimationRegistry; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.GuardedRunnable; import com.facebook.react.bridge.ReactApplicationContext; @@ -33,9 +31,9 @@ import javax.annotation.concurrent.GuardedBy; /** - * This class acts as a buffer for command executed on {@link NativeViewHierarchyManager} or on - * {@link AnimationRegistry}. It expose similar methods as mentioned classes but instead of - * executing commands immediately it enqueues those operations in a queue that is then flushed from + * This class acts as a buffer for command executed on {@link NativeViewHierarchyManager}. + * It expose similar methods as mentioned classes but instead of executing commands + * immediately it enqueues those operations in a queue that is then flushed from * {@link UIManagerModule} once JS batch of ui operations is finished. This is to make sure that we * execute all the JS operation coming from a single batch a single loop of the main (UI) android * looper. @@ -356,63 +354,6 @@ public AnimationOperation(int animationID) { } } - private class RegisterAnimationOperation extends AnimationOperation { - - private final Animation mAnimation; - - private RegisterAnimationOperation(Animation animation) { - super(animation.getAnimationID()); - mAnimation = animation; - } - - @Override - public void execute() { - mAnimationRegistry.registerAnimation(mAnimation); - } - } - - private class AddAnimationOperation extends AnimationOperation { - private final int mReactTag; - private final Callback mSuccessCallback; - - private AddAnimationOperation(int reactTag, int animationID, Callback successCallback) { - super(animationID); - mReactTag = reactTag; - mSuccessCallback = successCallback; - } - - @Override - public void execute() { - Animation animation = mAnimationRegistry.getAnimation(mAnimationID); - if (animation != null) { - mNativeViewHierarchyManager.startAnimationForNativeView( - mReactTag, - animation, - mSuccessCallback); - } else { - // node or animation not found - // TODO(5712813): cleanup callback in JS callbacks table in case of an error - throw new IllegalViewOperationException("Animation with id " + mAnimationID - + " was not found"); - } - } - } - - private final class RemoveAnimationOperation extends AnimationOperation { - - private RemoveAnimationOperation(int animationID) { - super(animationID); - } - - @Override - public void execute() { - Animation animation = mAnimationRegistry.getAnimation(mAnimationID); - if (animation != null) { - animation.cancel(); - } - } - } - private class SetLayoutAnimationEnabledOperation implements UIOperation { private final boolean mEnabled; @@ -428,14 +369,16 @@ public void execute() { private class ConfigureLayoutAnimationOperation implements UIOperation { private final ReadableMap mConfig; + private final Callback mAnimationComplete; - private ConfigureLayoutAnimationOperation(final ReadableMap config) { + private ConfigureLayoutAnimationOperation(final ReadableMap config, final Callback animationComplete) { mConfig = config; + mAnimationComplete = animationComplete; } @Override public void execute() { - mNativeViewHierarchyManager.configureLayoutAnimation(mConfig); + mNativeViewHierarchyManager.configureLayoutAnimation(mConfig, mAnimationComplete); } } @@ -604,7 +547,6 @@ public void execute() { } private final NativeViewHierarchyManager mNativeViewHierarchyManager; - private final AnimationRegistry mAnimationRegistry; private final Object mDispatchRunnablesLock = new Object(); private final Object mNonBatchedOperationsLock = new Object(); private final DispatchUIFrameCallback mDispatchUIFrameCallback; @@ -637,7 +579,6 @@ public UIViewOperationQueue( NativeViewHierarchyManager nativeViewHierarchyManager, int minTimeLeftInFrameForNonBatchedOperationMs) { mNativeViewHierarchyManager = nativeViewHierarchyManager; - mAnimationRegistry = nativeViewHierarchyManager.getAnimationRegistry(); mDispatchUIFrameCallback = new DispatchUIFrameCallback( reactContext, @@ -795,21 +736,6 @@ public void enqueueSetChildren( new SetChildrenOperation(reactTag, childrenTags)); } - public void enqueueRegisterAnimation(Animation animation) { - mOperations.add(new RegisterAnimationOperation(animation)); - } - - public void enqueueAddAnimation( - final int reactTag, - final int animationID, - final Callback onSuccess) { - mOperations.add(new AddAnimationOperation(reactTag, animationID, onSuccess)); - } - - public void enqueueRemoveAnimation(int animationID) { - mOperations.add(new RemoveAnimationOperation(animationID)); - } - public void enqueueSetLayoutAnimationEnabled( final boolean enabled) { mOperations.add(new SetLayoutAnimationEnabledOperation(enabled)); @@ -817,9 +743,8 @@ public void enqueueSetLayoutAnimationEnabled( public void enqueueConfigureLayoutAnimation( final ReadableMap config, - final Callback onSuccess, - final Callback onError) { - mOperations.add(new ConfigureLayoutAnimationOperation(config)); + final Callback onAnimationComplete) { + mOperations.add(new ConfigureLayoutAnimationOperation(config, onAnimationComplete)); } public void enqueueMeasure( diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java index d210d63e2d161b..253c3ef0a94c15 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java @@ -17,7 +17,8 @@ * Class providing children management API for view managers of classes extending ViewGroup. */ public abstract class ViewGroupManager - extends BaseViewManager { + extends BaseViewManager + implements IViewManagerWithChildren { private static WeakHashMap mZIndexHash = new WeakHashMap<>(); @@ -97,6 +98,7 @@ public void removeAllViews(T parent) { * through the ViewGroup's onLayout method. In that case, onLayout for this View type must *not* * call layout on its children. */ + @Override public boolean needsCustomLayoutForChildren() { return false; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java index cf852a64500bef..d3724a5e154687 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java @@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.StateWrapper; import com.facebook.react.touch.JSResponderHandler; import com.facebook.react.touch.ReactInterceptingViewGroup; import com.facebook.react.uimanager.annotations.ReactProp; @@ -129,7 +130,7 @@ protected void onAfterUpdateTransaction(@Nonnull T view) { /** * Subclasses may use this method to receive events/commands directly from JS through the * {@link UIManager}. Good example of such a command would be {@code scrollTo} request with - * coordinates for a {@link ScrollView} or {@code goBack} request for a {@link WebView} instance. + * coordinates for a {@link ScrollView} instance. * * @param root View instance that should receive the command * @param commandId code of the command @@ -144,18 +145,6 @@ public void receiveCommand(@Nonnull T root, int commandId, @Nullable ReadableArr * map between names of the commands and IDs that are then used in {@link #receiveCommand} method * whenever the command is dispatched for this particular {@link ViewManager}. * - * As an example we may consider {@link ReactWebViewManager} that expose the following commands: - * goBack, goForward, reload. In this case the map returned from {@link #getCommandsMap} from - * {@link ReactWebViewManager} will look as follows: - * { - * "goBack": 1, - * "goForward": 2, - * "reload": 3, - * } - * - * Now assuming that "reload" command is dispatched through {@link UIManagerModule} we trigger - * {@link ReactWebViewManager#receiveCommand} passing "3" as {@code commandId} argument. - * * @return map of string to int mapping of the expected commands */ public @Nullable Map getCommandsMap() { @@ -208,11 +197,15 @@ public Map getNativeProps() { return ViewManagerPropertyUpdater.getNativeProps(getClass(), getShadowNodeClass()); } + public @Nullable Object updateLocalData( @Nonnull T view, ReactStylesDiffMap props, ReactStylesDiffMap localData) { + return null; + } + /** - * + * Subclasses can implement this method to receive state updates shared between all instances + * of this component type. */ - public @Nullable Object updateLocalData(@Nonnull T view, ReactStylesDiffMap props, ReactStylesDiffMap localData) { - return null; + public void updateState(@Nonnull T view, StateWrapper stateWrapper) { } public long measure( diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java index f987fc2ef5b55c..20e48f919020b5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java @@ -288,7 +288,11 @@ public BoxedIntPropSetter(ReactPropGroup prop, Method setter, int index) { @Override protected @Nullable Object getValueOrDefault(Object value) { if (value != null) { - return (Integer) value; + if (value instanceof Double) { + return ((Double) value).intValue(); + } else { + return (Integer) value; + } } return null; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java index 2f08da20ecae2b..232cb01f9071e5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java @@ -8,11 +8,14 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; +import android.os.Handler; +import android.os.Looper; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; +import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.UiThreadUtil; @@ -20,25 +23,22 @@ * Class responsible for animation layout changes, if a valid layout animation config has been * supplied. If not animation is available, layout change is applied immediately instead of * performing an animation. - * - * TODO(7613721): Invoke success callback at the end of animation and when animation gets cancelled. */ @NotThreadSafe public class LayoutAnimationController { - private static final boolean ENABLED = true; - private final AbstractLayoutAnimation mLayoutCreateAnimation = new LayoutCreateAnimation(); private final AbstractLayoutAnimation mLayoutUpdateAnimation = new LayoutUpdateAnimation(); private final AbstractLayoutAnimation mLayoutDeleteAnimation = new LayoutDeleteAnimation(); private final SparseArray mLayoutHandlers = new SparseArray<>(0); + private boolean mShouldAnimateLayout; + private long mMaxAnimationDuration = -1; + @Nullable private Runnable mCompletionRunnable; - public void initializeFromConfig(final @Nullable ReadableMap config) { - if (!ENABLED) { - return; - } + @Nullable private static Handler sCompletionHandler; + public void initializeFromConfig(final @Nullable ReadableMap config, final Callback completionCallback) { if (config == null) { reset(); return; @@ -61,13 +61,24 @@ public void initializeFromConfig(final @Nullable ReadableMap config) { config.getMap(LayoutAnimationType.toString(LayoutAnimationType.DELETE)), globalDuration); mShouldAnimateLayout = true; } + + if (mShouldAnimateLayout && completionCallback != null) { + mCompletionRunnable = new Runnable() { + @Override + public void run() { + completionCallback.invoke(Boolean.TRUE); + } + }; + } } public void reset() { mLayoutCreateAnimation.reset(); mLayoutUpdateAnimation.reset(); mLayoutDeleteAnimation.reset(); + mCompletionRunnable = null; mShouldAnimateLayout = false; + mMaxAnimationDuration = -1; } public boolean shouldAnimateLayout(View viewToAnimate) { @@ -94,10 +105,10 @@ public void applyLayoutUpdate(View view, int x, int y, int width, int height) { UiThreadUtil.assertOnUiThread(); final int reactTag = view.getId(); - LayoutHandlingAnimation existingAnimation = mLayoutHandlers.get(reactTag); // Update an ongoing animation if possible, otherwise the layout update would be ignored as // the existing animation would still animate to the old layout. + LayoutHandlingAnimation existingAnimation = mLayoutHandlers.get(reactTag); if (existingAnimation != null) { existingAnimation.onLayoutUpdate(x, y, width, height); return; @@ -132,6 +143,12 @@ public void onAnimationRepeat(Animation animation) {} } if (animation != null) { + long animationDuration = animation.getDuration(); + if (animationDuration > mMaxAnimationDuration) { + mMaxAnimationDuration = animationDuration; + scheduleCompletionCallback(animationDuration); + } + view.startAnimation(animation); } } @@ -146,9 +163,7 @@ public void onAnimationRepeat(Animation animation) {} public void deleteView(final View view, final LayoutAnimationListener listener) { UiThreadUtil.assertOnUiThread(); - AbstractLayoutAnimation layoutAnimation = mLayoutDeleteAnimation; - - Animation animation = layoutAnimation.createAnimation( + Animation animation = mLayoutDeleteAnimation.createAnimation( view, view.getLeft(), view.getTop(), view.getWidth(), view.getHeight()); if (animation != null) { @@ -167,6 +182,12 @@ public void onAnimationEnd(Animation anim) { } }); + long animationDuration = animation.getDuration(); + if (animationDuration > mMaxAnimationDuration) { + scheduleCompletionCallback(animationDuration); + mMaxAnimationDuration = animationDuration; + } + view.startAnimation(animation); } else { listener.onAnimationEnd(); @@ -185,4 +206,15 @@ private void disableUserInteractions(View view) { } } } + + private void scheduleCompletionCallback(long delayMillis) { + if (sCompletionHandler == null) { + sCompletionHandler = new Handler(Looper.getMainLooper()); + } + + if (mCompletionRunnable != null) { + sCompletionHandler.removeCallbacks(mCompletionRunnable); + sCompletionHandler.postDelayed(mCompletionRunnable, delayMillis); + } + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/checkbox/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/checkbox/BUCK index 68cb8eb4e7d021..ed9aecafe2d957 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/checkbox/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/checkbox/BUCK @@ -6,7 +6,7 @@ rn_android_library( is_androidx = True, provided_deps = [ react_native_dep("third-party/android/support/v4:lib-support-v4"), - react_native_dep("third-party/android/support/v7/appcompat-orig:appcompat"), + react_native_dep("third-party/android/support/v7/appcompat:appcompat"), ], visibility = [ "PUBLIC", diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/picker/BUCK index 8b35c9144e2c71..1291385c0c3fab 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/picker/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/BUCK @@ -6,7 +6,7 @@ rn_android_library( is_androidx = True, provided_deps = [ react_native_dep("third-party/android/support/v4:lib-support-v4"), - react_native_dep("third-party/android/support/v7/appcompat-orig:appcompat"), + react_native_dep("third-party/android/support/v7/appcompat:appcompat"), ], visibility = [ "PUBLIC", diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java index e6ef83137fbc54..fffdf27704b3d7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -665,7 +665,7 @@ public void setEndFillColor(int color) { @Override protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { - if (mScroller != null) { + if (mScroller != null && mContentView != null) { // FB SCROLLVIEW CHANGE // This is part two of the reimplementation of fling to fix the bounce-back bug. See #fling() for diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/slider/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/slider/BUCK index ed8109be801dc7..b717203f6b8b68 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/slider/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/slider/BUCK @@ -9,7 +9,7 @@ rn_android_library( ], deps = [ YOGA_TARGET, - react_native_dep("third-party/android/support/v7/appcompat-orig:appcompat"), + react_native_dep("third-party/android/support/v7/appcompat:appcompat"), react_native_dep("third-party/android/support/v4:lib-support-v4"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_target("java/com/facebook/react/bridge:bridge"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK index 3535a85c992bd5..b86d46e7f1d941 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK @@ -6,7 +6,7 @@ rn_android_library( is_androidx = True, provided_deps = [ react_native_dep("third-party/android/support/v4:lib-support-v4"), - react_native_dep("third-party/android/support/v7/appcompat-orig:appcompat"), + react_native_dep("third-party/android/support/v7/appcompat:appcompat"), ], visibility = [ "PUBLIC", diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK index 6c23775eae7667..0a44dbe90049a3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK @@ -11,7 +11,7 @@ rn_android_library( deps = [ YOGA_TARGET, react_native_dep("third-party/android/support/v4:lib-support-v4"), - react_native_dep("third-party/android/support/v7/appcompat-orig:appcompat"), + react_native_dep("third-party/android/support/v7/appcompat:appcompat"), react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"), react_native_dep("third-party/java/infer-annotations:infer-annotations"), react_native_dep("third-party/java/jsr-305:jsr-305"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java index e5c94e0084f971..5a420bbb977f73 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java @@ -14,18 +14,26 @@ import android.text.Spannable; import android.text.SpannableStringBuilder; import android.view.Gravity; + +import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.LayoutShadowNode; +import com.facebook.react.uimanager.NativeViewHierarchyOptimizer; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.yoga.YogaConstants; import com.facebook.yoga.YogaDirection; +import com.facebook.yoga.YogaUnit; +import com.facebook.yoga.YogaValue; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.annotation.Nullable; /** @@ -41,7 +49,10 @@ @TargetApi(Build.VERSION_CODES.M) public abstract class ReactBaseTextShadowNode extends LayoutShadowNode { - private static final String INLINE_IMAGE_PLACEHOLDER = "I"; + // Use a direction weak character so the placeholder doesn't change the direction of the previous + // character. + // https://en.wikipedia.org/wiki/Bi-directional_text#weak_characters + private static final String INLINE_VIEW_PLACEHOLDER = "0"; public static final int UNSET = -1; public static final String PROP_SHADOW_OFFSET = "textShadowOffset"; @@ -84,6 +95,8 @@ private static void buildSpannedFromShadowNode( SpannableStringBuilder sb, List ops, TextAttributes parentTextAttributes, + boolean supportsInlineViews, + Map inlineViews, int start) { TextAttributes textAttributes; @@ -102,19 +115,39 @@ private static void buildSpannedFromShadowNode( ((ReactRawTextShadowNode) child).getText(), textAttributes.getTextTransform())); } else if (child instanceof ReactBaseTextShadowNode) { - buildSpannedFromShadowNode((ReactBaseTextShadowNode) child, sb, ops, textAttributes, sb.length()); + buildSpannedFromShadowNode((ReactBaseTextShadowNode) child, sb, ops, textAttributes, supportsInlineViews, inlineViews, sb.length()); } else if (child instanceof ReactTextInlineImageShadowNode) { // We make the image take up 1 character in the span and put a corresponding character into // the text so that the image doesn't run over any following text. - sb.append(INLINE_IMAGE_PLACEHOLDER); + sb.append(INLINE_VIEW_PLACEHOLDER); ops.add( new SetSpanOperation( - sb.length() - INLINE_IMAGE_PLACEHOLDER.length(), + sb.length() - INLINE_VIEW_PLACEHOLDER.length(), sb.length(), ((ReactTextInlineImageShadowNode) child).buildInlineImageSpan())); + } else if (supportsInlineViews) { + int reactTag = child.getReactTag(); + YogaValue widthValue = child.getStyleWidth(); + YogaValue heightValue = child.getStyleHeight(); + + if (widthValue.unit != YogaUnit.POINT || heightValue.unit != YogaUnit.POINT) { + throw new IllegalViewOperationException("Views nested within a must have a width and height"); + } + float width = widthValue.value; + float height = heightValue.value; + + // We make the inline view take up 1 character in the span and put a corresponding character into + // the text so that the inline view doesn't run over any following text. + sb.append(INLINE_VIEW_PLACEHOLDER); + ops.add( + new SetSpanOperation( + sb.length() - INLINE_VIEW_PLACEHOLDER.length(), + sb.length(), + new TextInlineViewPlaceholderSpan(reactTag, (int) width, (int) height))); + inlineViews.put(reactTag, child); } else { throw new IllegalViewOperationException( - "Unexpected view type nested under text node: " + child.getClass()); + "Unexpected view type nested under a or node: " + child.getClass()); } child.markUpdateSeen(); } @@ -192,8 +225,15 @@ private static void buildSpannedFromShadowNode( } } + // `nativeViewHierarchyOptimizer` can be `null` as long as `supportsInlineViews` is `false`. protected static Spannable spannedFromShadowNode( - ReactBaseTextShadowNode textShadowNode, String text) { + ReactBaseTextShadowNode textShadowNode, + String text, + boolean supportsInlineViews, + NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) { + Assertions.assertCondition( + !supportsInlineViews || nativeViewHierarchyOptimizer != null, + "nativeViewHierarchyOptimizer is required when inline views are supported"); SpannableStringBuilder sb = new SpannableStringBuilder(); // TODO(5837930): Investigate whether it's worth optimizing this part and do it if so @@ -202,6 +242,7 @@ protected static Spannable spannedFromShadowNode( // up-to-bottom, otherwise all the spannables that are withing the region for which one may set // a new spannable will be wiped out List ops = new ArrayList<>(); + Map inlineViews = supportsInlineViews ? new HashMap() : null; if (text != null) { // Handle text that is provided via a prop (e.g. the `value` and `defaultValue` props on @@ -209,20 +250,37 @@ protected static Spannable spannedFromShadowNode( sb.append(TextTransform.apply(text, textShadowNode.mTextAttributes.getTextTransform())); } - buildSpannedFromShadowNode(textShadowNode, sb, ops, null, 0); + buildSpannedFromShadowNode(textShadowNode, sb, ops, null, supportsInlineViews, inlineViews, 0); textShadowNode.mContainsImages = false; - float heightOfTallestInlineImage = Float.NaN; + textShadowNode.mInlineViews = inlineViews; + float heightOfTallestInlineViewOrImage = Float.NaN; - // While setting the Spans on the final text, we also check whether any of them are images. + // While setting the Spans on the final text, we also check whether any of them are inline views + // or images. int priority = 0; for (SetSpanOperation op : ops) { - if (op.what instanceof TextInlineImageSpan) { - int height = ((TextInlineImageSpan) op.what).getHeight(); - textShadowNode.mContainsImages = true; - if (Float.isNaN(heightOfTallestInlineImage) - || height > heightOfTallestInlineImage) { - heightOfTallestInlineImage = height; + boolean isInlineImage = op.what instanceof TextInlineImageSpan; + if (isInlineImage || op.what instanceof TextInlineViewPlaceholderSpan) { + int height; + if (isInlineImage) { + height = ((TextInlineImageSpan)op.what).getHeight(); + textShadowNode.mContainsImages = true; + } else { + TextInlineViewPlaceholderSpan placeholder = (TextInlineViewPlaceholderSpan) op.what; + height = placeholder.getHeight(); + + // Inline views cannot be layout-only because the ReactTextView needs to be able to grab + // ahold of them on the UI thread to size and position them. + ReactShadowNode childNode = inlineViews.get(placeholder.getReactTag()); + nativeViewHierarchyOptimizer.handleForceViewToBeNonLayoutOnly(childNode); + + // The ReactTextView is responsible for laying out the inline views. + childNode.setLayoutParent(textShadowNode); + } + + if (Float.isNaN(heightOfTallestInlineViewOrImage) || height > heightOfTallestInlineViewOrImage) { + heightOfTallestInlineViewOrImage = height; } } @@ -232,7 +290,7 @@ protected static Spannable spannedFromShadowNode( priority++; } - textShadowNode.mTextAttributes.setHeightOfTallestInlineImage(heightOfTallestInlineImage); + textShadowNode.mTextAttributes.setHeightOfTallestInlineViewOrImage(heightOfTallestInlineViewOrImage); return sb; } @@ -305,7 +363,7 @@ private static int parseNumericFontWeight(String fontWeightString) { protected @Nullable String mFontFamily = null; protected boolean mContainsImages = false; - protected float mHeightOfTallestInlineImage = Float.NaN; + protected Map mInlineViews; public ReactBaseTextShadowNode() { mTextAttributes = new TextAttributes(); @@ -403,8 +461,11 @@ public void setColor(@Nullable Integer color) { @ReactProp(name = ViewProps.BACKGROUND_COLOR) public void setBackgroundColor(Integer color) { - // Don't apply background color to anchor TextView since it will be applied on the View directly - if (!isVirtualAnchor()) { + // Background color needs to be handled here for virtual nodes so it can be incorporated into + // the span. However, it doesn't need to be applied to non-virtual nodes because non-virtual + // nodes get mapped to native views and native views get their background colors get set via + // {@link BaseViewManager}. + if (isVirtual()) { mIsBackgroundColorSet = (color != null); if (mIsBackgroundColorSet) { mBackgroundColor = color; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java index 73ec452920590c..2cedc5afbe9341 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java @@ -183,4 +183,9 @@ public void setDataDetectorType(ReactTextView view, @Nullable String type) { break; } } + + @ReactProp(name = "onInlineViewLayout") + public void setNotifyOnInlineViewLayout(ReactTextView view, boolean notifyOnInlineViewLayout) { + view.setNotifyOnInlineViewLayout(notifyOnInlineViewLayout); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java index e5eaf49ff01df9..0a572581eefa04 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java @@ -20,6 +20,8 @@ import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.NativeViewHierarchyOptimizer; +import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.Spacing; import com.facebook.react.uimanager.UIViewOperationQueue; import com.facebook.react.uimanager.annotations.ReactProp; @@ -30,6 +32,9 @@ import com.facebook.yoga.YogaMeasureMode; import com.facebook.yoga.YogaMeasureOutput; import com.facebook.yoga.YogaNode; + +import java.util.ArrayList; + import javax.annotation.Nullable; /** @@ -189,13 +194,25 @@ private int getTextAlign() { } @Override - public void onBeforeLayout() { - mPreparedSpannableText = spannedFromShadowNode(this, null); + public void onBeforeLayout(NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer) { + mPreparedSpannableText = spannedFromShadowNode( + this, + /* text (e.g. from `value` prop): */ null, + /* supportsInlineViews: */ true, + nativeViewHierarchyOptimizer); markUpdated(); } @Override public boolean isVirtualAnchor() { + // Text's descendants aren't necessarily all virtual nodes. Text can contain a combination of + // virtual and non-virtual (e.g. inline views) nodes. Therefore it's not a virtual anchor + // by the doc comment on {@link ReactShadowNode#isVirtualAnchor}. + return false; + } + + @Override + public boolean hoistNativeChildren() { return true; } @@ -231,4 +248,27 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { public void setShouldNotifyOnTextLayout(boolean shouldNotifyOnTextLayout) { mShouldNotifyOnTextLayout = shouldNotifyOnTextLayout; } + + @Override + public Iterable calculateLayoutOnChildren() { + // Run flexbox on and return the descendants which are inline views. + + if (mInlineViews == null || mInlineViews.isEmpty()) { + return null; + } + + Spanned text = Assertions.assertNotNull( + this.mPreparedSpannableText, + "Spannable element has not been prepared in onBeforeLayout"); + TextInlineViewPlaceholderSpan[] placeholders = text.getSpans(0, text.length(), TextInlineViewPlaceholderSpan.class); + ArrayList shadowNodes = new ArrayList(placeholders.length); + + for (TextInlineViewPlaceholderSpan placeholder : placeholders) { + ReactShadowNode child = mInlineViews.get(placeholder.getReactTag()); + child.calculateLayout(); + shadowNodes.add(child); + } + + return shadowNodes; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java index fd1344f0fb714d..26957a58f19399 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java @@ -26,6 +26,8 @@ public class ReactTextUpdate { private final float mPaddingBottom; private final int mTextAlign; private final int mTextBreakStrategy; + private final int mSelectionStart; + private final int mSelectionEnd; private final int mJustificationMode; /** @@ -51,7 +53,9 @@ public ReactTextUpdate( paddingBottom, textAlign, Layout.BREAK_STRATEGY_HIGH_QUALITY, - Layout.JUSTIFICATION_MODE_NONE); + Layout.JUSTIFICATION_MODE_NONE, + -1, + -1); } public ReactTextUpdate( @@ -65,6 +69,33 @@ public ReactTextUpdate( int textAlign, int textBreakStrategy, int justificationMode) { + this(text, + jsEventCounter, + containsImages, + paddingStart, + paddingTop, + paddingEnd, + paddingBottom, + textAlign, + textBreakStrategy, + justificationMode, + -1, + -1); + } + + public ReactTextUpdate( + Spannable text, + int jsEventCounter, + boolean containsImages, + float paddingStart, + float paddingTop, + float paddingEnd, + float paddingBottom, + int textAlign, + int textBreakStrategy, + int justificationMode, + int selectionStart, + int selectionEnd) { mText = text; mJsEventCounter = jsEventCounter; mContainsImages = containsImages; @@ -74,6 +105,8 @@ public ReactTextUpdate( mPaddingBottom = paddingBottom; mTextAlign = textAlign; mTextBreakStrategy = textBreakStrategy; + mSelectionStart = selectionStart; + mSelectionEnd = selectionEnd; mJustificationMode = justificationMode; } @@ -116,4 +149,12 @@ public int getTextBreakStrategy() { public int getJustificationMode() { return mJustificationMode; } + + public int getSelectionStart() { + return mSelectionStart; + } + + public int getSelectionEnd() { + return mSelectionEnd; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index f6f5201ecae34d..b14e49e8bf9afd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -11,22 +11,35 @@ import android.graphics.drawable.Drawable; import android.os.Build; import androidx.appcompat.widget.AppCompatTextView; +import androidx.appcompat.widget.TintContextWrapper; import android.text.Layout; import android.text.Spannable; import android.text.Spanned; -import android.text.Spannable; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.util.Linkify; import android.view.Gravity; +import android.view.View; import android.view.ViewGroup; + import com.facebook.common.logging.FLog; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.ReactConstants; +import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.ReactCompoundView; +import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewDefaults; +import com.facebook.react.uimanager.events.RCTEventEmitter; import com.facebook.react.views.view.ReactViewBackgroundManager; import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + public class ReactTextView extends AppCompatTextView implements ReactCompoundView { private static final ViewGroup.LayoutParams EMPTY_LAYOUT_PARAMS = @@ -39,6 +52,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie private int mNumberOfLines = ViewDefaults.NUMBER_OF_LINES; private TextUtils.TruncateAt mEllipsizeLocation = TextUtils.TruncateAt.END; private int mLinkifyMaskType = 0; + private boolean mNotifyOnInlineViewLayout; private ReactViewBackgroundManager mReactBackgroundManager; private Spannable mSpanned; @@ -51,6 +65,185 @@ public ReactTextView(Context context) { mDefaultGravityVertical = getGravity() & Gravity.VERTICAL_GRAVITY_MASK; } + private WritableMap inlineViewJson(int visibility, int index, int left, int top, int right, int bottom) { + WritableMap json = Arguments.createMap(); + if (visibility == View.GONE) { + json.putString("visibility", "gone"); + json.putInt("index", index); + } else if (visibility == View.VISIBLE) { + json.putString("visibility", "visible"); + json.putInt("index", index); + json.putDouble("left", PixelUtil.toDIPFromPixel(left)); + json.putDouble("top", PixelUtil.toDIPFromPixel(top)); + json.putDouble("right", PixelUtil.toDIPFromPixel(right)); + json.putDouble("bottom", PixelUtil.toDIPFromPixel(bottom)); + } else { + json.putString("visibility", "unknown"); + json.putInt("index", index); + } + return json; + } + + private ReactContext getReactContext() { + Context context = getContext(); + return (context instanceof TintContextWrapper) + ? (ReactContext)((TintContextWrapper)context).getBaseContext() + : (ReactContext)context; + } + + @Override + protected void onLayout(boolean changed, + int textViewLeft, + int textViewTop, + int textViewRight, + int textViewBottom) { + if (!(getText() instanceof Spanned)) { + /** + * In general, {@link #setText} is called via {@link ReactTextViewManager#updateExtraData} + * before we are laid out. This ordering is a requirement because we utilize the data from + * setText in onLayout. + * + * However, it's possible for us to get an extra layout before we've received our setText + * call. If this happens before the initial setText call, then getText() will have its default + * value which isn't a Spanned and we need to bail out. That's fine because we'll get a + * setText followed by a layout later. + * + * The cause for the extra early layout is that an ancestor gets transitioned from a + * layout-only node to a non layout-only node. + */ + return; + } + + UIManagerModule uiManager = getReactContext().getNativeModule(UIManagerModule.class); + + Spanned text = (Spanned) getText(); + Layout layout = getLayout(); + TextInlineViewPlaceholderSpan[] placeholders = text.getSpans(0, text.length(), TextInlineViewPlaceholderSpan.class); + ArrayList inlineViewInfoArray = mNotifyOnInlineViewLayout ? new ArrayList(placeholders.length) : null; + int textViewWidth = textViewRight - textViewLeft; + int textViewHeight = textViewBottom - textViewTop; + + for (TextInlineViewPlaceholderSpan placeholder : placeholders) { + View child = uiManager.resolveView(placeholder.getReactTag()); + + int start = text.getSpanStart(placeholder); + int line = layout.getLineForOffset(start); + boolean isLineTruncated = layout.getEllipsisCount(line) > 0; + + if (// This truncation check works well on recent versions of Android (tested on 5.1.1 and + // 6.0.1) but not on Android 4.4.4. The reason is that getEllipsisCount is buggy on + // Android 4.4.4. Specifically, it incorrectly returns 0 if an inline view is the first + // thing to be truncated. + (isLineTruncated && start >= layout.getLineStart(line) + layout.getEllipsisStart(line)) || + + // This truncation check works well on Android 4.4.4 but not on others (e.g. 6.0.1). + // On Android 4.4.4, getLineEnd returns the first truncated character whereas on 6.0.1, + // it appears to return the position after the last character on the line even if that + // character is truncated. + line >= mNumberOfLines || start >= layout.getLineEnd(line)) { + // On some versions of Android (e.g. 4.4.4, 5.1.1), getPrimaryHorizontal can infinite + // loop when called on a character that appears after the ellipsis. Avoid this bug by + // special casing the character truncation case. + child.setVisibility(View.GONE); + if (mNotifyOnInlineViewLayout) { + inlineViewInfoArray.add(inlineViewJson(View.GONE, start, -1, -1, -1, -1)); + } + } else { + int width = placeholder.getWidth(); + int height = placeholder.getHeight(); + + // Calculate if the direction of the placeholder character is Right-To-Left. + boolean isRtlChar = layout.isRtlCharAt(start); + + boolean isRtlParagraph = layout.getParagraphDirection(line) == Layout.DIR_RIGHT_TO_LEFT; + + int placeholderHorizontalPosition; + // There's a bug on Samsung devices where calling getPrimaryHorizontal on + // the last offset in the layout will result in an endless loop. Work around + // this bug by avoiding getPrimaryHorizontal in that case. + if (start == text.length() - 1) { + placeholderHorizontalPosition = isRtlParagraph + // Equivalent to `layout.getLineLeft(line)` but `getLineLeft` returns incorrect + // values when the paragraph is RTL and `setSingleLine(true)`. + ? textViewWidth - (int)layout.getLineWidth(line) + : (int) layout.getLineRight(line) - width; + } else { + // The direction of the paragraph may not be exactly the direction the string is heading in at the + // position of the placeholder. So, if the direction of the character is the same as the paragraph + // use primary, secondary otherwise. + boolean characterAndParagraphDirectionMatch = isRtlParagraph == isRtlChar; + + placeholderHorizontalPosition = characterAndParagraphDirectionMatch + ? (int) layout.getPrimaryHorizontal(start) + : (int) layout.getSecondaryHorizontal(start); + + if (isRtlParagraph) { + // Adjust `placeholderHorizontalPosition` to work around an Android bug. + // The bug is when the paragraph is RTL and `setSingleLine(true)`, some layout + // methods such as `getPrimaryHorizontal`, `getSecondaryHorizontal`, and + // `getLineRight` return incorrect values. Their return values seem to be off + // by the same number of pixels so subtracting these values cancels out the error. + // + // The result is equivalent to bugless versions of `getPrimaryHorizontal`/`getSecondaryHorizontal`. + placeholderHorizontalPosition = textViewWidth - ((int)layout.getLineRight(line) - placeholderHorizontalPosition); + } + + if (isRtlChar) { + placeholderHorizontalPosition -= width; + } + } + + int leftRelativeToTextView = isRtlChar + ? placeholderHorizontalPosition + getTotalPaddingRight() + : placeholderHorizontalPosition + getTotalPaddingLeft(); + + int left = textViewLeft + leftRelativeToTextView; + + // Vertically align the inline view to the baseline of the line of text. + int topRelativeToTextView = getTotalPaddingTop() + layout.getLineBaseline(line) - height; + int top = textViewTop + topRelativeToTextView; + + boolean isFullyClipped = textViewWidth <= leftRelativeToTextView || textViewHeight <= topRelativeToTextView; + int layoutVisibility = isFullyClipped ? View.GONE : View.VISIBLE; + int layoutLeft = left; + int layoutTop = top; + int layoutRight = left + width; + int layoutBottom = top + height; + + // Keep these parameters in sync with what goes into `inlineViewInfoArray`. + child.setVisibility(layoutVisibility); + child.layout(layoutLeft, layoutTop, layoutRight, layoutBottom); + if (mNotifyOnInlineViewLayout) { + inlineViewInfoArray.add( + inlineViewJson(layoutVisibility, start, layoutLeft, layoutTop, layoutRight, layoutBottom)); + } + } + } + + if (mNotifyOnInlineViewLayout) { + Collections.sort(inlineViewInfoArray, new Comparator() { + @Override + public int compare(Object o1, Object o2) { + WritableMap m1 = (WritableMap)o1; + WritableMap m2 = (WritableMap)o2; + return m1.getInt("index") - m2.getInt("index"); + } + }); + WritableArray inlineViewInfoArray2 = Arguments.createArray(); + for (Object item : inlineViewInfoArray) { + inlineViewInfoArray2.pushMap((WritableMap)item); + } + + WritableMap event = Arguments.createMap(); + event.putArray("inlineViews", inlineViewInfoArray2); + getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent( + getId(), + "topInlineViewLayout", + event + ); + } + } + public void setText(ReactTextUpdate update) { mContainsImages = update.containsImages(); // Android's TextView crashes when it tries to relayout if LayoutParams are @@ -86,6 +279,9 @@ public void setText(ReactTextUpdate update) { setJustificationMode(update.getJustificationMode()); } } + + // Ensure onLayout is called so the inline views can be repositioned. + requestLayout(); } @Override @@ -248,6 +444,10 @@ public void setEllipsizeLocation(TextUtils.TruncateAt ellipsizeLocation) { mEllipsizeLocation = ellipsizeLocation; } + public void setNotifyOnInlineViewLayout(boolean notifyOnInlineViewLayout) { + mNotifyOnInlineViewLayout = notifyOnInlineViewLayout; + } + public void updateView() { @Nullable TextUtils.TruncateAt ellipsizeLocation = mNumberOfLines == ViewDefaults.NUMBER_OF_LINES ? null : mEllipsizeLocation; setEllipsize(ellipsizeLocation); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java index edcaf4a22cd59b..7e20919bfc7069 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java @@ -13,10 +13,12 @@ import com.facebook.react.common.MapBuilder; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.uimanager.IViewManagerWithChildren; import com.facebook.react.uimanager.ReactStylesDiffMap; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.yoga.YogaMeasureMode; import java.util.Map; + import javax.annotation.Nullable; /** @@ -25,7 +27,8 @@ */ @ReactModule(name = ReactTextViewManager.REACT_CLASS) public class ReactTextViewManager - extends ReactTextAnchorViewManager { + extends ReactTextAnchorViewManager + implements IViewManagerWithChildren { @VisibleForTesting public static final String REACT_CLASS = "RCTText"; @@ -65,6 +68,10 @@ protected void onAfterUpdateTransaction(ReactTextView view) { view.updateView(); } + public boolean needsCustomLayoutForChildren() { + return true; + } + @Override public Object updateLocalData( ReactTextView view, ReactStylesDiffMap props, ReactStylesDiffMap localData) { @@ -98,7 +105,9 @@ public Object updateLocalData( @Override public @Nullable Map getExportedCustomDirectEventTypeConstants() { - return MapBuilder.of("topTextLayout", MapBuilder.of("registrationName", "onTextLayout")); + return MapBuilder.of( + "topTextLayout", MapBuilder.of("registrationName", "onTextLayout"), + "topInlineViewLayout", MapBuilder.of("registrationName", "onInlineViewLayout")); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributes.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributes.java index 020cfdc35211f1..9ebf1d83d1e56f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributes.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributes.java @@ -27,7 +27,7 @@ public class TextAttributes { private float mLineHeight = Float.NaN; private float mLetterSpacing = Float.NaN; private float mMaxFontSizeMultiplier = Float.NaN; - private float mHeightOfTallestInlineImage = Float.NaN; + private float mHeightOfTallestInlineViewOrImage = Float.NaN; private TextTransform mTextTransform = TextTransform.UNSET; public TextAttributes() { @@ -44,7 +44,7 @@ public TextAttributes applyChild(TextAttributes child) { result.mLineHeight = !Float.isNaN(child.mLineHeight) ? child.mLineHeight : mLineHeight; result.mLetterSpacing = !Float.isNaN(child.mLetterSpacing) ? child.mLetterSpacing : mLetterSpacing; result.mMaxFontSizeMultiplier = !Float.isNaN(child.mMaxFontSizeMultiplier) ? child.mMaxFontSizeMultiplier : mMaxFontSizeMultiplier; - result.mHeightOfTallestInlineImage = !Float.isNaN(child.mHeightOfTallestInlineImage) ? child.mHeightOfTallestInlineImage : mHeightOfTallestInlineImage; + result.mHeightOfTallestInlineViewOrImage = !Float.isNaN(child.mHeightOfTallestInlineViewOrImage) ? child.mHeightOfTallestInlineViewOrImage : mHeightOfTallestInlineViewOrImage; result.mTextTransform = child.mTextTransform != TextTransform.UNSET ? child.mTextTransform : mTextTransform; return result; @@ -96,12 +96,12 @@ public void setMaxFontSizeMultiplier(float maxFontSizeMultiplier) { mMaxFontSizeMultiplier = maxFontSizeMultiplier; } - public float getHeightOfTallestInlineImage() { - return mHeightOfTallestInlineImage; + public float getHeightOfTallestInlineViewOrImage() { + return mHeightOfTallestInlineViewOrImage; } - public void setHeightOfTallestInlineImage(float value) { - mHeightOfTallestInlineImage = value; + public void setHeightOfTallestInlineViewOrImage(float value) { + mHeightOfTallestInlineViewOrImage = value; } public TextTransform getTextTransform() { @@ -137,9 +137,9 @@ public float getEffectiveLineHeight() { // Take into account the requested line height // and the height of the inline images. boolean useInlineViewHeight = - !Float.isNaN(mHeightOfTallestInlineImage) - && mHeightOfTallestInlineImage > lineHeight; - return useInlineViewHeight ? mHeightOfTallestInlineImage : lineHeight; + !Float.isNaN(mHeightOfTallestInlineViewOrImage) + && mHeightOfTallestInlineViewOrImage > lineHeight; + return useInlineViewHeight ? mHeightOfTallestInlineViewOrImage : lineHeight; } public float getEffectiveLetterSpacing() { @@ -169,7 +169,7 @@ public String toString() { + "\n getAllowFontScaling(): " + getAllowFontScaling() + "\n getFontSize(): " + getFontSize() + "\n getEffectiveFontSize(): " + getEffectiveFontSize() - + "\n getHeightOfTallestInlineImage(): " + getHeightOfTallestInlineImage() + + "\n getHeightOfTallestInlineViewOrImage(): " + getHeightOfTallestInlineViewOrImage() + "\n getLetterSpacing(): " + getLetterSpacing() + "\n getEffectiveLetterSpacing(): " + getEffectiveLetterSpacing() + "\n getLineHeight(): " + getLineHeight() diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineViewPlaceholderSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineViewPlaceholderSpan.java new file mode 100644 index 00000000000000..9e6726efe3d563 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineViewPlaceholderSpan.java @@ -0,0 +1,61 @@ +/** + * 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.views.text; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.text.style.ReplacementSpan; + +/** + * TextInlineViewPlaceholderSpan is a span for inlined views that are inside . It computes + * its size based on the input size. It contains no draw logic, just positioning logic. + */ +public class TextInlineViewPlaceholderSpan extends ReplacementSpan implements ReactSpan { + private int mReactTag; + private int mWidth; + private int mHeight; + + public TextInlineViewPlaceholderSpan(int reactTag, int width, int height) { + mReactTag = reactTag; + mWidth = width; + mHeight = height; + } + + public int getReactTag() { + return mReactTag; + } + + public int getWidth() { + return mWidth; + } + + public int getHeight() { + return mHeight; + } + + @Override + public int getSize( + Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { + // NOTE: This getSize code is copied from DynamicDrawableSpan and modified to not use a Drawable + + if (fm != null) { + fm.ascent = -mHeight; + fm.descent = 0; + + fm.top = fm.ascent; + fm.bottom = 0; + } + + return mWidth; + } + + @Override + public void draw( + Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK index dae8c212ad0913..2ce3100e90fc45 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK @@ -13,6 +13,7 @@ rn_android_library( ], deps = [ YOGA_TARGET, + react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"), react_native_dep("third-party/java/infer-annotations:infer-annotations"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_target("java/com/facebook/react/bridge:bridge"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index 5788161bd28512..84b6349547324d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -27,6 +27,7 @@ import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.TextView; +import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReactContext; @@ -64,7 +65,7 @@ */ @ReactModule(name = ReactTextInputManager.REACT_CLASS) public class ReactTextInputManager extends BaseViewManager { - + public static final String TAG = ReactTextInputManager.class.getSimpleName(); protected static final String REACT_CLASS = "AndroidTextInput"; private static final int[] SPACING_TYPES = { @@ -201,6 +202,8 @@ public void updateExtraData(ReactEditText view, Object extraData) { TextInlineImageSpan.possiblyUpdateInlineImageSpans(spannable, view); } view.maybeSetText(update); + if (update.getSelectionStart() != UNSET && update.getSelectionEnd() != UNSET) + view.setSelection(update.getSelectionStart(), update.getSelectionEnd()); } } @@ -273,17 +276,6 @@ public void setFontStyle(ReactEditText view, @Nullable String fontStyleString) { } } - @ReactProp(name = "selection") - public void setSelection(ReactEditText view, @Nullable ReadableMap selection) { - if (selection == null) { - return; - } - - if (selection.hasKey("start") && selection.hasKey("end")) { - view.setSelection(selection.getInt("start"), selection.getInt("end")); - } - } - @ReactProp(name = "importantForAutofill") public void setImportantForAutofill(ReactEditText view, @Nullable String value) { int mode = View.IMPORTANT_FOR_AUTOFILL_AUTO; @@ -464,9 +456,14 @@ public void setUnderlineColor(ReactEditText view, @Nullable Integer underlineCol // Drawable.mutate() can sometimes crash due to an AOSP bug: // See https://code.google.com/p/android/issues/detail?id=191754 for more info Drawable background = view.getBackground(); - Drawable drawableToMutate = background.getConstantState() != null ? - background.mutate() : - background; + Drawable drawableToMutate = background; + if (background.getConstantState() != null) { + try { + drawableToMutate = background.mutate(); + } catch (NullPointerException e) { + FLog.e(TAG, "NullPointerException when setting underlineColorAndroid for TextInput", e); + } + } if (underlineColor == null) { drawableToMutate.clearColorFilter(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java index 3189164288ed1c..3f00b8718b3ad6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java @@ -16,8 +16,10 @@ import android.widget.EditText; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.uimanager.LayoutShadowNode; +import com.facebook.react.uimanager.NativeViewHierarchyOptimizer; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.ReactShadowNodeImpl; import com.facebook.react.uimanager.Spacing; @@ -45,10 +47,13 @@ public class ReactTextInputShadowNode extends ReactBaseTextShadowNode @VisibleForTesting public static final String PROP_TEXT = "text"; @VisibleForTesting public static final String PROP_PLACEHOLDER = "placeholder"; + @VisibleForTesting public static final String PROP_SELECTION = "selection"; // Represents the {@code text} property only, not possible nested content. private @Nullable String mText = null; private @Nullable String mPlaceholder = null; + private int mSelectionStart = UNSET; + private int mSelectionEnd = UNSET; public ReactTextInputShadowNode() { mTextBreakStrategy = (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? @@ -172,6 +177,19 @@ public void setPlaceholder(@Nullable String placeholder) { return mPlaceholder; } + @ReactProp(name = PROP_SELECTION) + public void setSelection(@Nullable ReadableMap selection) { + mSelectionStart = mSelectionEnd = UNSET; + if (selection == null) + return; + + if (selection.hasKey("start") && selection.hasKey("end")) { + mSelectionStart = selection.getInt("start"); + mSelectionEnd = selection.getInt("end"); + markUpdated(); + } + } + @Override public void setTextBreakStrategy(@Nullable String textBreakStrategy) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { @@ -196,7 +214,12 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { if (mMostRecentEventCount != UNSET) { ReactTextUpdate reactTextUpdate = new ReactTextUpdate( - spannedFromShadowNode(this, getText()), + spannedFromShadowNode( + this, + getText(), + /* supportsInlineViews: */ false, + /* nativeViewHierarchyOptimizer: */ null // only needed to support inline views + ), mMostRecentEventCount, mContainsImages, getPadding(Spacing.LEFT), @@ -205,7 +228,9 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { getPadding(Spacing.BOTTOM), mTextAlign, mTextBreakStrategy, - mJustificationMode); + mJustificationMode, + mSelectionStart, + mSelectionEnd); uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK index ac2b9fd7fec8a6..37c0c69d008bd3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK @@ -6,7 +6,7 @@ rn_android_library( is_androidx = True, provided_deps = [ react_native_dep("third-party/android/support/v4:lib-support-v4"), - react_native_dep("third-party/android/support/v7/appcompat-orig:appcompat"), + react_native_dep("third-party/android/support/v7/appcompat:appcompat"), ], visibility = [ "PUBLIC", diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/webview/BUCK deleted file mode 100644 index 5e8a0cc8333e64..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/BUCK +++ /dev/null @@ -1,18 +0,0 @@ -load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library") - -rn_android_library( - name = "webview", - srcs = glob(["**/*.java"]), - visibility = [ - "PUBLIC", - ], - deps = [ - react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"), - react_native_dep("third-party/java/jsr-305:jsr-305"), - react_native_target("java/com/facebook/react/bridge:bridge"), - react_native_target("java/com/facebook/react/common:common"), - react_native_target("java/com/facebook/react/module/annotations:annotations"), - react_native_target("java/com/facebook/react/uimanager:uimanager"), - react_native_target("java/com/facebook/react/uimanager/annotations:annotations"), - ], -) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java deleted file mode 100644 index a5f54d0ca6f205..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java +++ /dev/null @@ -1,737 +0,0 @@ -/** - * 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.views.webview; - -import android.annotation.TargetApi; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.Bitmap; -import android.graphics.Picture; -import android.net.Uri; -import android.os.Build; -import android.text.TextUtils; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.webkit.ConsoleMessage; -import android.webkit.CookieManager; -import android.webkit.GeolocationPermissions; -import android.webkit.JavascriptInterface; -import android.webkit.ValueCallback; -import android.webkit.WebChromeClient; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import com.facebook.common.logging.FLog; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.LifecycleEventListener; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableMapKeySetIterator; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.common.ReactConstants; -import com.facebook.react.common.build.ReactBuildConfig; -import com.facebook.react.module.annotations.ReactModule; -import com.facebook.react.uimanager.SimpleViewManager; -import com.facebook.react.uimanager.ThemedReactContext; -import com.facebook.react.uimanager.UIManagerModule; -import com.facebook.react.uimanager.annotations.ReactProp; -import com.facebook.react.uimanager.events.ContentSizeChangeEvent; -import com.facebook.react.uimanager.events.Event; -import com.facebook.react.uimanager.events.EventDispatcher; -import com.facebook.react.views.webview.events.TopLoadingErrorEvent; -import com.facebook.react.views.webview.events.TopLoadingFinishEvent; -import com.facebook.react.views.webview.events.TopLoadingStartEvent; -import com.facebook.react.views.webview.events.TopMessageEvent; -import java.io.UnsupportedEncodingException; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Pattern; -import javax.annotation.Nullable; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Manages instances of {@link WebView} - * - *

Can accept following commands: - GO_BACK - GO_FORWARD - RELOAD - * - *

{@link WebView} instances could emit following direct events: - topLoadingFinish - - * topLoadingStart - topLoadingError - * - *

Each event will carry the following properties: - target - view's react tag - url - url set - * for the webview - loading - whether webview is in a loading state - title - title of the current - * page - canGoBack - boolean, whether there is anything on a history stack to go back - - * canGoForward - boolean, whether it is possible to request GO_FORWARD command - */ -@ReactModule(name = ReactWebViewManager.REACT_CLASS) -public class ReactWebViewManager extends SimpleViewManager { - - public static final String REACT_CLASS = "RCTWebView"; - - protected static final String HTML_ENCODING = "UTF-8"; - protected static final String HTML_MIME_TYPE = "text/html"; - protected static final String BRIDGE_NAME = "__REACT_WEB_VIEW_BRIDGE"; - - protected static final String HTTP_METHOD_POST = "POST"; - - public static final int COMMAND_GO_BACK = 1; - public static final int COMMAND_GO_FORWARD = 2; - public static final int COMMAND_RELOAD = 3; - public static final int COMMAND_STOP_LOADING = 4; - public static final int COMMAND_POST_MESSAGE = 5; - public static final int COMMAND_INJECT_JAVASCRIPT = 6; - - // Use `webView.loadUrl("about:blank")` to reliably reset the view - // state and release page resources (including any running JavaScript). - protected static final String BLANK_URL = "about:blank"; - - // Intent urls are a type of deeplinks which start with: intent:// - private static final String INTENT_URL_PREFIX = "intent://"; - - protected WebViewConfig mWebViewConfig; - protected @Nullable WebView.PictureListener mPictureListener; - - protected static class ReactWebViewClient extends WebViewClient { - - protected boolean mLastLoadFailed = false; - protected @Nullable ReadableArray mUrlPrefixesForDefaultIntent; - protected @Nullable List mOriginWhitelist; - - @Override - public void onPageFinished(WebView webView, String url) { - super.onPageFinished(webView, url); - - if (!mLastLoadFailed) { - ReactWebView reactWebView = (ReactWebView) webView; - reactWebView.callInjectedJavaScript(); - reactWebView.linkBridge(); - emitFinishEvent(webView, url); - } - } - - @Override - public void onPageStarted(WebView webView, String url, Bitmap favicon) { - super.onPageStarted(webView, url, favicon); - mLastLoadFailed = false; - - dispatchEvent( - webView, new TopLoadingStartEvent(webView.getId(), createWebViewEvent(webView, url))); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (url.equals(BLANK_URL)) return false; - - // url blacklisting - if (mUrlPrefixesForDefaultIntent != null && mUrlPrefixesForDefaultIntent.size() > 0) { - ArrayList urlPrefixesForDefaultIntent = mUrlPrefixesForDefaultIntent.toArrayList(); - for (Object urlPrefix : urlPrefixesForDefaultIntent) { - if (url.startsWith((String) urlPrefix)) { - launchIntent(view.getContext(), url); - return true; - } - } - } - - if (mOriginWhitelist != null && shouldHandleURL(mOriginWhitelist, url)) { - return false; - } - - launchIntent(view.getContext(), url); - return true; - } - - private void launchIntent(Context context, String url) { - Intent intent = null; - - // URLs starting with 'intent://' require special handling. - if (url.startsWith(INTENT_URL_PREFIX)) { - try { - intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); - } catch (URISyntaxException e) { - FLog.e(ReactConstants.TAG, "Can't parse intent:// URI", e); - } - } - - if (intent != null) { - // This is needed to prevent security issue where non-exported activities from the same process can be started with intent:// URIs. - // See: T10607927/S136245 - intent.addCategory(Intent.CATEGORY_BROWSABLE); - intent.setComponent(null); - intent.setSelector(null); - - PackageManager packageManager = context.getPackageManager(); - ResolveInfo info = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); - if (info != null) { - // App is installed. - context.startActivity(intent); - } else { - String fallbackUrl = intent.getStringExtra("browser_fallback_url"); - intent = new Intent(Intent.ACTION_VIEW, Uri.parse(fallbackUrl)); - } - } else { - intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - } - - try { - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addCategory(Intent.CATEGORY_BROWSABLE); - context.startActivity(intent); - } catch (ActivityNotFoundException e) { - FLog.w(ReactConstants.TAG, "activity not found to handle uri scheme for: " + url, e); - } - } - - private boolean shouldHandleURL(List originWhitelist, String url) { - Uri uri = Uri.parse(url); - String scheme = uri.getScheme() != null ? uri.getScheme() : ""; - String authority = uri.getAuthority() != null ? uri.getAuthority() : ""; - String urlToCheck = scheme + "://" + authority; - for (Pattern pattern : originWhitelist) { - if (pattern.matcher(urlToCheck).matches()) { - return true; - } - } - return false; - } - - @Override - public void onReceivedError( - WebView webView, int errorCode, String description, String failingUrl) { - super.onReceivedError(webView, errorCode, description, failingUrl); - mLastLoadFailed = true; - - // In case of an error JS side expect to get a finish event first, and then get an error event - // Android WebView does it in the opposite way, so we need to simulate that behavior - emitFinishEvent(webView, failingUrl); - - WritableMap eventData = createWebViewEvent(webView, failingUrl); - eventData.putDouble("code", errorCode); - eventData.putString("description", description); - - dispatchEvent(webView, new TopLoadingErrorEvent(webView.getId(), eventData)); - } - - protected void emitFinishEvent(WebView webView, String url) { - dispatchEvent( - webView, new TopLoadingFinishEvent(webView.getId(), createWebViewEvent(webView, url))); - } - - protected WritableMap createWebViewEvent(WebView webView, String url) { - WritableMap event = Arguments.createMap(); - event.putDouble("target", webView.getId()); - // Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks - // like onPageFinished - event.putString("url", url); - event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100); - event.putString("title", webView.getTitle()); - event.putBoolean("canGoBack", webView.canGoBack()); - event.putBoolean("canGoForward", webView.canGoForward()); - return event; - } - - public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) { - mUrlPrefixesForDefaultIntent = specialUrls; - } - - public void setOriginWhitelist(List originWhitelist) { - mOriginWhitelist = originWhitelist; - } - } - - /** - * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order - * to call {@link WebView#destroy} on activity destroy event and also to clear the client - */ - protected static class ReactWebView extends WebView implements LifecycleEventListener { - protected @Nullable String injectedJS; - protected boolean messagingEnabled = false; - protected @Nullable ReactWebViewClient mReactWebViewClient; - - protected class ReactWebViewBridge { - ReactWebView mContext; - - ReactWebViewBridge(ReactWebView c) { - mContext = c; - } - - @JavascriptInterface - public void postMessage(String message) { - mContext.onMessage(message); - } - } - - /** - * WebView must be created with an context of the current activity - * - *

Activity Context is required for creation of dialogs internally by WebView Reactive Native - * needed for access to ReactNative internal system functionality - */ - public ReactWebView(ThemedReactContext reactContext) { - super(reactContext); - } - - @Override - public void onHostResume() { - // do nothing - } - - @Override - public void onHostPause() { - // do nothing - } - - @Override - public void onHostDestroy() { - cleanupCallbacksAndDestroy(); - } - - @Override - public void setWebViewClient(WebViewClient client) { - super.setWebViewClient(client); - mReactWebViewClient = (ReactWebViewClient) client; - } - - public @Nullable ReactWebViewClient getReactWebViewClient() { - return mReactWebViewClient; - } - - public void setInjectedJavaScript(@Nullable String js) { - injectedJS = js; - } - - protected ReactWebViewBridge createReactWebViewBridge(ReactWebView webView) { - return new ReactWebViewBridge(webView); - } - - public void setMessagingEnabled(boolean enabled) { - if (messagingEnabled == enabled) { - return; - } - - messagingEnabled = enabled; - if (enabled) { - addJavascriptInterface(createReactWebViewBridge(this), BRIDGE_NAME); - linkBridge(); - } else { - removeJavascriptInterface(BRIDGE_NAME); - } - } - - protected void evaluateJavascriptWithFallback(String script) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - evaluateJavascript(script, null); - return; - } - - try { - loadUrl("javascript:" + URLEncoder.encode(script, "UTF-8")); - } catch (UnsupportedEncodingException e) { - // UTF-8 should always be supported - throw new RuntimeException(e); - } - } - - public void callInjectedJavaScript() { - if (getSettings().getJavaScriptEnabled() - && injectedJS != null - && !TextUtils.isEmpty(injectedJS)) { - evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();"); - } - } - - public void linkBridge() { - if (messagingEnabled) { - if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - // See isNative in lodash - String testPostMessageNative = - "String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')"; - evaluateJavascript( - testPostMessageNative, - new ValueCallback() { - @Override - public void onReceiveValue(String value) { - if (value.equals("true")) { - FLog.w( - ReactConstants.TAG, - "Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined"); - } - } - }); - } - - evaluateJavascriptWithFallback( - "(" - + "window.originalPostMessage = window.postMessage," - + "window.postMessage = function(data) {" - + BRIDGE_NAME - + ".postMessage(String(data));" - + "}" - + ")"); - } - } - - public void onMessage(String message) { - dispatchEvent(this, new TopMessageEvent(this.getId(), message)); - } - - protected void cleanupCallbacksAndDestroy() { - setWebViewClient(null); - destroy(); - } - } - - public ReactWebViewManager() { - mWebViewConfig = - new WebViewConfig() { - public void configWebView(WebView webView) {} - }; - } - - public ReactWebViewManager(WebViewConfig webViewConfig) { - mWebViewConfig = webViewConfig; - } - - @Override - public String getName() { - return REACT_CLASS; - } - - protected ReactWebView createReactWebViewInstance(ThemedReactContext reactContext) { - return new ReactWebView(reactContext); - } - - @Override - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - protected WebView createViewInstance(ThemedReactContext reactContext) { - ReactWebView webView = createReactWebViewInstance(reactContext); - webView.setWebChromeClient( - new WebChromeClient() { - @Override - public boolean onConsoleMessage(ConsoleMessage message) { - if (ReactBuildConfig.DEBUG) { - return super.onConsoleMessage(message); - } - // Ignore console logs in non debug builds. - return true; - } - - @Override - public void onGeolocationPermissionsShowPrompt( - String origin, GeolocationPermissions.Callback callback) { - callback.invoke(origin, true, false); - } - }); - reactContext.addLifecycleEventListener(webView); - mWebViewConfig.configWebView(webView); - WebSettings settings = webView.getSettings(); - settings.setBuiltInZoomControls(true); - settings.setDisplayZoomControls(false); - settings.setDomStorageEnabled(true); - - settings.setAllowFileAccess(false); - settings.setAllowContentAccess(false); - settings.setAllowFileAccessFromFileURLs(false); - setAllowUniversalAccessFromFileURLs(webView, false); - setMixedContentMode(webView, "never"); - - // Fixes broken full-screen modals/galleries due to body height being 0. - webView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - - setGeolocationEnabled(webView, false); - if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - WebView.setWebContentsDebuggingEnabled(true); - } - return webView; - } - - @ReactProp(name = "hardwareAccelerationEnabledExperimental", defaultBoolean = true) - public void sethardwareAccelerationEnabledExperimental(WebView view, boolean enabled) { - // Hardware acceleration can not be enabled at view level but it can be disabled - // see: https://developer.android.com/guide/topics/graphics/hardware-accel - // - // Disabling hardware acceleration is sometimes required to workaround chromium bugs: - // https://bugs.chromium.org/p/chromium/issues/detail?id=501901 - // - if (!enabled) { - view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - } - - @ReactProp(name = "javaScriptEnabled") - public void setJavaScriptEnabled(WebView view, boolean enabled) { - view.getSettings().setJavaScriptEnabled(enabled); - } - - @ReactProp(name = "thirdPartyCookiesEnabled") - public void setThirdPartyCookiesEnabled(WebView view, boolean enabled) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - CookieManager.getInstance().setAcceptThirdPartyCookies(view, enabled); - } - } - - @ReactProp(name = "scalesPageToFit") - public void setScalesPageToFit(WebView view, boolean enabled) { - view.getSettings().setUseWideViewPort(!enabled); - } - - @ReactProp(name = "domStorageEnabled") - public void setDomStorageEnabled(WebView view, boolean enabled) { - view.getSettings().setDomStorageEnabled(enabled); - } - - @ReactProp(name = "userAgent") - public void setUserAgent(WebView view, @Nullable String userAgent) { - if (userAgent != null) { - // TODO(8496850): Fix incorrect behavior when property is unset (uA == null) - view.getSettings().setUserAgentString(userAgent); - } - } - - @ReactProp(name = "mediaPlaybackRequiresUserAction") - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - public void setMediaPlaybackRequiresUserAction(WebView view, boolean requires) { - view.getSettings().setMediaPlaybackRequiresUserGesture(requires); - } - - @ReactProp(name = "allowUniversalAccessFromFileURLs") - public void setAllowUniversalAccessFromFileURLs(WebView view, boolean allow) { - view.getSettings().setAllowUniversalAccessFromFileURLs(allow); - } - - @ReactProp(name = "saveFormDataDisabled") - public void setSaveFormDataDisabled(WebView view, boolean disable) { - view.getSettings().setSaveFormData(!disable); - } - - @ReactProp(name = "injectedJavaScript") - public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) { - ((ReactWebView) view).setInjectedJavaScript(injectedJavaScript); - } - - @ReactProp(name = "messagingEnabled") - public void setMessagingEnabled(WebView view, boolean enabled) { - ((ReactWebView) view).setMessagingEnabled(enabled); - } - - @ReactProp(name = "source") - public void setSource(WebView view, @Nullable ReadableMap source) { - if (source != null) { - if (source.hasKey("html")) { - String html = source.getString("html"); - if (source.hasKey("baseUrl")) { - view.loadDataWithBaseURL( - source.getString("baseUrl"), html, HTML_MIME_TYPE, HTML_ENCODING, null); - } else { - view.loadData(html, HTML_MIME_TYPE, HTML_ENCODING); - } - return; - } - if (source.hasKey("uri")) { - String url = source.getString("uri"); - String previousUrl = view.getUrl(); - if (previousUrl != null && previousUrl.equals(url)) { - return; - } - if (source.hasKey("method")) { - String method = source.getString("method"); - if (method.equalsIgnoreCase(HTTP_METHOD_POST)) { - byte[] postData = null; - if (source.hasKey("body")) { - String body = source.getString("body"); - try { - postData = body.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - postData = body.getBytes(); - } - } - if (postData == null) { - postData = new byte[0]; - } - view.postUrl(url, postData); - return; - } - } - HashMap headerMap = new HashMap<>(); - if (source.hasKey("headers")) { - ReadableMap headers = source.getMap("headers"); - ReadableMapKeySetIterator iter = headers.keySetIterator(); - while (iter.hasNextKey()) { - String key = iter.nextKey(); - if ("user-agent".equals(key.toLowerCase(Locale.ENGLISH))) { - if (view.getSettings() != null) { - view.getSettings().setUserAgentString(headers.getString(key)); - } - } else { - headerMap.put(key, headers.getString(key)); - } - } - } - view.loadUrl(url, headerMap); - return; - } - } - view.loadUrl(BLANK_URL); - } - - @ReactProp(name = "onContentSizeChange") - public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) { - if (sendContentSizeChangeEvents) { - view.setPictureListener(getPictureListener()); - } else { - view.setPictureListener(null); - } - } - - @ReactProp(name = "mixedContentMode") - public void setMixedContentMode(WebView view, @Nullable String mixedContentMode) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (mixedContentMode == null || "never".equals(mixedContentMode)) { - view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW); - } else if ("always".equals(mixedContentMode)) { - view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); - } else if ("compatibility".equals(mixedContentMode)) { - view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); - } - } - } - - @ReactProp(name = "urlPrefixesForDefaultIntent") - public void setUrlPrefixesForDefaultIntent( - WebView view, @Nullable ReadableArray urlPrefixesForDefaultIntent) { - ReactWebViewClient client = ((ReactWebView) view).getReactWebViewClient(); - if (client != null && urlPrefixesForDefaultIntent != null) { - client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent); - } - } - - @ReactProp(name = "allowFileAccess") - public void setAllowFileAccess(WebView view, @Nullable Boolean allowFileAccess) { - view.getSettings().setAllowFileAccess(allowFileAccess != null && allowFileAccess); - } - - @ReactProp(name = "geolocationEnabled") - public void setGeolocationEnabled(WebView view, @Nullable Boolean isGeolocationEnabled) { - view.getSettings().setGeolocationEnabled(isGeolocationEnabled != null && isGeolocationEnabled); - } - - @ReactProp(name = "originWhitelist") - public void setOriginWhitelist(WebView view, @Nullable ReadableArray originWhitelist) { - ReactWebViewClient client = ((ReactWebView) view).getReactWebViewClient(); - if (client != null && originWhitelist != null) { - List whiteList = new LinkedList<>(); - for (int i = 0; i < originWhitelist.size(); i++) { - whiteList.add(Pattern.compile(originWhitelist.getString(i))); - } - client.setOriginWhitelist(whiteList); - } - } - - @Override - protected void addEventEmitters(ThemedReactContext reactContext, WebView view) { - // Do not register default touch emitter and let WebView implementation handle touches - view.setWebViewClient(new ReactWebViewClient()); - } - - @Override - public @Nullable Map getCommandsMap() { - return MapBuilder.of( - "goBack", COMMAND_GO_BACK, - "goForward", COMMAND_GO_FORWARD, - "reload", COMMAND_RELOAD, - "stopLoading", COMMAND_STOP_LOADING, - "postMessage", COMMAND_POST_MESSAGE, - "injectJavaScript", COMMAND_INJECT_JAVASCRIPT); - } - - @Override - public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) { - switch (commandId) { - case COMMAND_GO_BACK: - root.goBack(); - break; - case COMMAND_GO_FORWARD: - root.goForward(); - break; - case COMMAND_RELOAD: - root.reload(); - break; - case COMMAND_STOP_LOADING: - root.stopLoading(); - break; - case COMMAND_POST_MESSAGE: - try { - ReactWebView reactWebView = (ReactWebView) root; - JSONObject eventInitDict = new JSONObject(); - eventInitDict.put("data", args.getString(0)); - reactWebView.evaluateJavascriptWithFallback( - "(function () {" - + "var event;" - + "var data = " - + eventInitDict.toString() - + ";" - + "try {" - + "event = new MessageEvent('message', data);" - + "} catch (e) {" - + "event = document.createEvent('MessageEvent');" - + "event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" - + "}" - + "document.dispatchEvent(event);" - + "})();"); - } catch (JSONException e) { - throw new RuntimeException(e); - } - break; - case COMMAND_INJECT_JAVASCRIPT: - ReactWebView reactWebView = (ReactWebView) root; - reactWebView.evaluateJavascriptWithFallback(args.getString(0)); - break; - } - } - - @Override - public void onDropViewInstance(WebView webView) { - super.onDropViewInstance(webView); - ((ThemedReactContext) webView.getContext()) - .removeLifecycleEventListener((ReactWebView) webView); - ((ReactWebView) webView).cleanupCallbacksAndDestroy(); - } - - protected WebView.PictureListener getPictureListener() { - if (mPictureListener == null) { - mPictureListener = - new WebView.PictureListener() { - @Override - public void onNewPicture(WebView webView, Picture picture) { - dispatchEvent( - webView, - new ContentSizeChangeEvent( - webView.getId(), webView.getWidth(), webView.getContentHeight())); - } - }; - } - return mPictureListener; - } - - protected static void dispatchEvent(WebView webView, Event event) { - ReactContext reactContext = (ReactContext) webView.getContext(); - EventDispatcher eventDispatcher = - reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); - eventDispatcher.dispatchEvent(event); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/WebViewConfig.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/WebViewConfig.java deleted file mode 100644 index 198fad85fb8938..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/WebViewConfig.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * 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.views.webview; - -import android.webkit.WebView; - -/** - * Implement this interface in order to config your {@link WebView}. An instance of that - * implementation will have to be given as a constructor argument to {@link ReactWebViewManager}. - */ -public interface WebViewConfig { - - void configWebView(WebView webView); -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingErrorEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingErrorEvent.java deleted file mode 100644 index 86e1aa058935a1..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingErrorEvent.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * 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.views.webview.events; - -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.uimanager.events.Event; -import com.facebook.react.uimanager.events.RCTEventEmitter; - -/** - * Event emitted when there is an error in loading. - */ -public class TopLoadingErrorEvent extends Event { - - public static final String EVENT_NAME = "topLoadingError"; - private WritableMap mEventData; - - public TopLoadingErrorEvent(int viewId, WritableMap eventData) { - super(viewId); - mEventData = eventData; - } - - @Override - public String getEventName() { - return EVENT_NAME; - } - - @Override - public boolean canCoalesce() { - return false; - } - - @Override - public short getCoalescingKey() { - // All events for a given view can be coalesced. - return 0; - } - - @Override - public void dispatch(RCTEventEmitter rctEventEmitter) { - rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingFinishEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingFinishEvent.java deleted file mode 100644 index dd12aa877b8096..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingFinishEvent.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * 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.views.webview.events; - -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.uimanager.events.Event; -import com.facebook.react.uimanager.events.RCTEventEmitter; - -/** - * Event emitted when loading is completed. - */ -public class TopLoadingFinishEvent extends Event { - - public static final String EVENT_NAME = "topLoadingFinish"; - private WritableMap mEventData; - - public TopLoadingFinishEvent(int viewId, WritableMap eventData) { - super(viewId); - mEventData = eventData; - } - - @Override - public String getEventName() { - return EVENT_NAME; - } - - @Override - public boolean canCoalesce() { - return false; - } - - @Override - public short getCoalescingKey() { - // All events for a given view can be coalesced. - return 0; - } - - @Override - public void dispatch(RCTEventEmitter rctEventEmitter) { - rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingStartEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingStartEvent.java deleted file mode 100644 index 28aae57e5989fd..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingStartEvent.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * 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.views.webview.events; - -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.uimanager.events.Event; -import com.facebook.react.uimanager.events.RCTEventEmitter; - -/** - * Event emitted when loading has started - */ -public class TopLoadingStartEvent extends Event { - - public static final String EVENT_NAME = "topLoadingStart"; - private WritableMap mEventData; - - public TopLoadingStartEvent(int viewId, WritableMap eventData) { - super(viewId); - mEventData = eventData; - } - - @Override - public String getEventName() { - return EVENT_NAME; - } - - @Override - public boolean canCoalesce() { - return false; - } - - @Override - public short getCoalescingKey() { - // All events for a given view can be coalesced. - return 0; - } - - @Override - public void dispatch(RCTEventEmitter rctEventEmitter) { - rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopMessageEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopMessageEvent.java deleted file mode 100644 index c2f43673b4e01e..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopMessageEvent.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * 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.views.webview.events; - -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.uimanager.events.Event; -import com.facebook.react.uimanager.events.RCTEventEmitter; - -/** - * Event emitted when there is an error in loading. - */ -public class TopMessageEvent extends Event { - - public static final String EVENT_NAME = "topMessage"; - private final String mData; - - public TopMessageEvent(int viewId, String data) { - super(viewId); - mData = data; - } - - @Override - public String getEventName() { - return EVENT_NAME; - } - - @Override - public boolean canCoalesce() { - return false; - } - - @Override - public short getCoalescingKey() { - // All events for a given view can be coalesced. - return 0; - } - - @Override - public void dispatch(RCTEventEmitter rctEventEmitter) { - WritableMap data = Arguments.createMap(); - data.putString("data", mData); - rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data); - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/systrace/BUCK b/ReactAndroid/src/main/java/com/facebook/systrace/BUCK index a5ad996259e0a7..21006f1842c39a 100644 --- a/ReactAndroid/src/main/java/com/facebook/systrace/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/systrace/BUCK @@ -3,7 +3,6 @@ load("//tools/build_defs/oss:rn_defs.bzl", "rn_android_library") rn_android_library( name = "systrace", srcs = glob(["*.java"]), - is_androidx = True, visibility = [ "PUBLIC", ], diff --git a/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java b/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java index 2d7aa429e3f7ff..fd9a575ae15150 100644 --- a/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java +++ b/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java @@ -7,7 +7,8 @@ package com.facebook.systrace; -import androidx.core.os.TraceCompat; +import android.os.Build; +import android.os.Trace; /** * Systrace stub that mostly does nothing but delegates to Trace for beginning/ending sections. @@ -54,11 +55,15 @@ public static void traceInstant( } public static void beginSection(long tag, final String sectionName) { - TraceCompat.beginSection(sectionName); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + Trace.beginSection(sectionName); + } } public static void endSection(long tag) { - TraceCompat.endSection(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + Trace.endSection(); + } } public static void beginAsyncSection( diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaConfig.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaConfig.java index bc2538da2712f4..2121d643016998 100644 --- a/ReactAndroid/src/main/java/com/facebook/yoga/YogaConfig.java +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaConfig.java @@ -11,6 +11,7 @@ public class YogaConfig { public static int SPACING_TYPE = 1; + public static boolean useBatchingForLayoutOutputs = false; long mNativePointer; private YogaLogger mLogger; diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNative.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNative.java index ef0b44ee5d2c65..8b81e9295ea013 100644 --- a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNative.java +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNative.java @@ -29,8 +29,8 @@ public class YogaNative { // YGNode related static native int jni_YGNodeGetInstanceCount(); - static native long jni_YGNodeNew(); - static native long jni_YGNodeNewWithConfig(long configPointer); + static native long jni_YGNodeNew(boolean useBatchingForLayoutOutputs); + static native long jni_YGNodeNewWithConfig(long configPointer, boolean useBatchingForLayoutOutputs); static native void jni_YGNodeFree(long nativePointer); static native void jni_YGNodeReset(long nativePointer); static native void jni_YGNodeInsertChild(long nativePointer, long childPointer, int index); @@ -111,4 +111,5 @@ public class YogaNative { static native void jni_YGNodeSetHasBaselineFunc(long nativePointer, boolean hasMeasureFunc); static native void jni_YGNodePrint(long nativePointer); static native void jni_YGNodeSetStyleInputs(long nativePointer, float[] styleInputsArray, int size); + static native long jni_YGNodeClone(long nativePointer); } diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNode.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNode.java index c6db5d4506ac24..a01a0bdde49ddb 100644 --- a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNode.java +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNode.java @@ -10,11 +10,11 @@ public abstract class YogaNode { public static YogaNode create() { - return new YogaNodeJNI(); + return YogaConfig.useBatchingForLayoutOutputs ? new YogaNodeJNIBatching() : new YogaNodeJNI(); } public static YogaNode create(YogaConfig config) { - return new YogaNodeJNI(config); + return YogaConfig.useBatchingForLayoutOutputs ? new YogaNodeJNIBatching(config) : new YogaNodeJNI(config); } public abstract void reset(); @@ -211,6 +211,8 @@ public static YogaNode create(YogaConfig config) { public abstract boolean isMeasureDefined(); + public abstract boolean isBaselineDefined(); + public abstract void setData(Object data); @Nullable @@ -219,4 +221,6 @@ public static YogaNode create(YogaConfig config) { public abstract void print(); public abstract void setStyleInputs(float[] styleInputs, int size); + + public abstract YogaNode cloneWithoutChildren(); } diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNI.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNI.java index ff5d11490f625a..d2a9357bb3dd0b 100644 --- a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNI.java +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNI.java @@ -11,6 +11,45 @@ @DoNotStrip public class YogaNodeJNI extends YogaNodeJNIBase { + @DoNotStrip + private float mWidth = YogaConstants.UNDEFINED; + @DoNotStrip + private float mHeight = YogaConstants.UNDEFINED; + @DoNotStrip + private float mTop = YogaConstants.UNDEFINED; + @DoNotStrip + private float mLeft = YogaConstants.UNDEFINED; + @DoNotStrip + private float mMarginLeft = 0; + @DoNotStrip + private float mMarginTop = 0; + @DoNotStrip + private float mMarginRight = 0; + @DoNotStrip + private float mMarginBottom = 0; + @DoNotStrip + private float mPaddingLeft = 0; + @DoNotStrip + private float mPaddingTop = 0; + @DoNotStrip + private float mPaddingRight = 0; + @DoNotStrip + private float mPaddingBottom = 0; + @DoNotStrip + private float mBorderLeft = 0; + @DoNotStrip + private float mBorderTop = 0; + @DoNotStrip + private float mBorderRight = 0; + @DoNotStrip + private float mBorderBottom = 0; + @DoNotStrip + private int mLayoutDirection = 0; + @DoNotStrip + private boolean mHasNewLayout = true; + @DoNotStrip + private boolean mDoesLegacyStretchFlagAffectsLayout = false; + public YogaNodeJNI() { super(); } @@ -18,4 +57,130 @@ public YogaNodeJNI() { public YogaNodeJNI(YogaConfig config) { super(config); } + + @Override + public void reset() { + super.reset(); + mHasNewLayout = true; + + mWidth = YogaConstants.UNDEFINED; + mHeight = YogaConstants.UNDEFINED; + mTop = YogaConstants.UNDEFINED; + mLeft = YogaConstants.UNDEFINED; + mMarginLeft = 0; + mMarginTop = 0; + mMarginRight = 0; + mMarginBottom = 0; + mPaddingLeft = 0; + mPaddingTop = 0; + mPaddingRight = 0; + mPaddingBottom = 0; + mBorderLeft = 0; + mBorderTop = 0; + mBorderRight = 0; + mBorderBottom = 0; + mLayoutDirection = 0; + + mDoesLegacyStretchFlagAffectsLayout = false; + } + + @Override + public boolean hasNewLayout() { + return mHasNewLayout; + } + + @Override + public void markLayoutSeen() { + mHasNewLayout = false; + } + + @Override + public float getLayoutX() { + return mLeft; + } + + @Override + public float getLayoutY() { + return mTop; + } + + @Override + public float getLayoutWidth() { + return mWidth; + } + + @Override + public float getLayoutHeight() { + return mHeight; + } + + @Override + public boolean getDoesLegacyStretchFlagAffectsLayout() { + return mDoesLegacyStretchFlagAffectsLayout; + } + + @Override + public float getLayoutMargin(YogaEdge edge) { + switch (edge) { + case LEFT: + return mMarginLeft; + case TOP: + return mMarginTop; + case RIGHT: + return mMarginRight; + case BOTTOM: + return mMarginBottom; + case START: + return getLayoutDirection() == YogaDirection.RTL ? mMarginRight : mMarginLeft; + case END: + return getLayoutDirection() == YogaDirection.RTL ? mMarginLeft : mMarginRight; + default: + throw new IllegalArgumentException("Cannot get layout margins of multi-edge shorthands"); + } + } + + @Override + public float getLayoutPadding(YogaEdge edge) { + switch (edge) { + case LEFT: + return mPaddingLeft; + case TOP: + return mPaddingTop; + case RIGHT: + return mPaddingRight; + case BOTTOM: + return mPaddingBottom; + case START: + return getLayoutDirection() == YogaDirection.RTL ? mPaddingRight : mPaddingLeft; + case END: + return getLayoutDirection() == YogaDirection.RTL ? mPaddingLeft : mPaddingRight; + default: + throw new IllegalArgumentException("Cannot get layout paddings of multi-edge shorthands"); + } + } + + @Override + public float getLayoutBorder(YogaEdge edge) { + switch (edge) { + case LEFT: + return mBorderLeft; + case TOP: + return mBorderTop; + case RIGHT: + return mBorderRight; + case BOTTOM: + return mBorderBottom; + case START: + return getLayoutDirection() == YogaDirection.RTL ? mBorderRight : mBorderLeft; + case END: + return getLayoutDirection() == YogaDirection.RTL ? mBorderLeft : mBorderRight; + default: + throw new IllegalArgumentException("Cannot get layout border of multi-edge shorthands"); + } + } + + @Override + public YogaDirection getLayoutDirection() { + return YogaDirection.fromInt(mLayoutDirection); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNIBase.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNIBase.java index 7ddfa228ac02e2..c48162f4a470d6 100644 --- a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNIBase.java +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNIBase.java @@ -12,7 +12,7 @@ import javax.annotation.Nullable; @DoNotStrip -public abstract class YogaNodeJNIBase extends YogaNode { +public abstract class YogaNodeJNIBase extends YogaNode implements Cloneable { @Nullable private YogaNodeJNIBase mOwner; @Nullable private List mChildren; @@ -21,58 +21,15 @@ public abstract class YogaNodeJNIBase extends YogaNode { private long mNativePointer; @Nullable private Object mData; - /* Those flags needs be in sync with YGJNI.cpp */ - private static final int MARGIN = 1; - private static final int PADDING = 2; - private static final int BORDER = 4; - - @DoNotStrip - private float mWidth = YogaConstants.UNDEFINED; - @DoNotStrip - private float mHeight = YogaConstants.UNDEFINED; - @DoNotStrip - private float mTop = YogaConstants.UNDEFINED; - @DoNotStrip - private float mLeft = YogaConstants.UNDEFINED; - @DoNotStrip - private float mMarginLeft = 0; - @DoNotStrip - private float mMarginTop = 0; - @DoNotStrip - private float mMarginRight = 0; - @DoNotStrip - private float mMarginBottom = 0; - @DoNotStrip - private float mPaddingLeft = 0; - @DoNotStrip - private float mPaddingTop = 0; - @DoNotStrip - private float mPaddingRight = 0; - @DoNotStrip - private float mPaddingBottom = 0; - @DoNotStrip - private float mBorderLeft = 0; - @DoNotStrip - private float mBorderTop = 0; - @DoNotStrip - private float mBorderRight = 0; - @DoNotStrip - private float mBorderBottom = 0; - @DoNotStrip - private int mLayoutDirection = 0; - @DoNotStrip - private boolean mHasNewLayout = true; - @DoNotStrip private boolean mDoesLegacyStretchFlagAffectsLayout = false; - public YogaNodeJNIBase() { - mNativePointer = YogaNative.jni_YGNodeNew(); + mNativePointer = YogaNative.jni_YGNodeNew(YogaConfig.useBatchingForLayoutOutputs); if (mNativePointer == 0) { throw new IllegalStateException("Failed to allocate native memory"); } } public YogaNodeJNIBase(YogaConfig config) { - mNativePointer = YogaNative.jni_YGNodeNewWithConfig(config.mNativePointer); + mNativePointer = YogaNative.jni_YGNodeNewWithConfig(config.mNativePointer, YogaConfig.useBatchingForLayoutOutputs); if (mNativePointer == 0) { throw new IllegalStateException("Failed to allocate native memory"); } @@ -96,30 +53,9 @@ public void freeNatives() { } } public void reset() { - mHasNewLayout = true; - - mWidth = YogaConstants.UNDEFINED; - mHeight = YogaConstants.UNDEFINED; - mTop = YogaConstants.UNDEFINED; - mLeft = YogaConstants.UNDEFINED; - mMarginLeft = 0; - mMarginTop = 0; - mMarginRight = 0; - mMarginBottom = 0; - mPaddingLeft = 0; - mPaddingTop = 0; - mPaddingRight = 0; - mPaddingBottom = 0; - mBorderLeft = 0; - mBorderTop = 0; - mBorderRight = 0; - mBorderBottom = 0; - mLayoutDirection = 0; - mMeasureFunction = null; mBaselineFunction = null; mData = null; - mDoesLegacyStretchFlagAffectsLayout = false; YogaNative.jni_YGNodeReset(mNativePointer); } @@ -157,6 +93,21 @@ public boolean isReferenceBaseline() { return YogaNative.jni_YGNodeIsReferenceBaseline(mNativePointer); } + @Override + public YogaNodeJNIBase cloneWithoutChildren() { + try { + YogaNodeJNIBase clonedYogaNode = (YogaNodeJNIBase) super.clone(); + long clonedNativePointer = YogaNative.jni_YGNodeClone(mNativePointer); + clonedYogaNode.mOwner = null; + clonedYogaNode.mNativePointer = clonedNativePointer; + clonedYogaNode.clearChildren(); + return clonedYogaNode; + } catch (CloneNotSupportedException ex) { + // This class implements Cloneable, this should not happen + throw new RuntimeException(ex); + } + } + private void clearChildren() { mChildren = null; YogaNative.jni_YGNodeClearChildren(mNativePointer); @@ -219,10 +170,6 @@ public void calculateLayout(float width, float height) { YogaNative.jni_YGNodeCalculateLayout(mNativePointer, width, height, nativePointers, nodes); } - public boolean hasNewLayout() { - return mHasNewLayout; - } - public void dirty() { YogaNative.jni_YGNodeMarkDirty(mNativePointer); } @@ -240,10 +187,6 @@ public void copyStyle(YogaNode srcNode) { YogaNative.jni_YGNodeCopyStyle(mNativePointer, ((YogaNodeJNIBase) srcNode).mNativePointer); } - public void markLayoutSeen() { - mHasNewLayout = false; - } - public YogaDirection getStyleDirection() { return YogaDirection.fromInt(YogaNative.jni_YGNodeStyleGetDirection(mNativePointer)); } @@ -500,86 +443,7 @@ public void setAspectRatio(float aspectRatio) { YogaNative.jni_YGNodeStyleSetAspectRatio(mNativePointer, aspectRatio); } - public float getLayoutX() { - return mLeft; - } - - public float getLayoutY() { - return mTop; - } - - public float getLayoutWidth() { - return mWidth; - } - - public float getLayoutHeight() { - return mHeight; - } - - public boolean getDoesLegacyStretchFlagAffectsLayout() { - return mDoesLegacyStretchFlagAffectsLayout; - } - - public float getLayoutMargin(YogaEdge edge) { - switch (edge) { - case LEFT: - return mMarginLeft; - case TOP: - return mMarginTop; - case RIGHT: - return mMarginRight; - case BOTTOM: - return mMarginBottom; - case START: - return getLayoutDirection() == YogaDirection.RTL ? mMarginRight : mMarginLeft; - case END: - return getLayoutDirection() == YogaDirection.RTL ? mMarginLeft : mMarginRight; - default: - throw new IllegalArgumentException("Cannot get layout margins of multi-edge shorthands"); - } - } - - public float getLayoutPadding(YogaEdge edge) { - switch (edge) { - case LEFT: - return mPaddingLeft; - case TOP: - return mPaddingTop; - case RIGHT: - return mPaddingRight; - case BOTTOM: - return mPaddingBottom; - case START: - return getLayoutDirection() == YogaDirection.RTL ? mPaddingRight : mPaddingLeft; - case END: - return getLayoutDirection() == YogaDirection.RTL ? mPaddingLeft : mPaddingRight; - default: - throw new IllegalArgumentException("Cannot get layout paddings of multi-edge shorthands"); - } - } - - public float getLayoutBorder(YogaEdge edge) { - switch (edge) { - case LEFT: - return mBorderLeft; - case TOP: - return mBorderTop; - case RIGHT: - return mBorderRight; - case BOTTOM: - return mBorderBottom; - case START: - return getLayoutDirection() == YogaDirection.RTL ? mBorderRight : mBorderLeft; - case END: - return getLayoutDirection() == YogaDirection.RTL ? mBorderLeft : mBorderRight; - default: - throw new IllegalArgumentException("Cannot get layout border of multi-edge shorthands"); - } - } - - public YogaDirection getLayoutDirection() { - return YogaDirection.fromInt(mLayoutDirection); - } + public abstract boolean getDoesLegacyStretchFlagAffectsLayout(); public void setMeasureFunction(YogaMeasureFunction measureFunction) { mMeasureFunction = measureFunction; @@ -619,6 +483,11 @@ public boolean isMeasureDefined() { return mMeasureFunction != null; } + @Override + public boolean isBaselineDefined() { + return mBaselineFunction != null; + } + public void setData(Object data) { mData = data; } diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNIBatching.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNIBatching.java new file mode 100644 index 00000000000000..7760fa80875f6b --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNodeJNIBatching.java @@ -0,0 +1,177 @@ +/** + * 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.yoga; + +import javax.annotation.Nullable; + +import com.facebook.proguard.annotations.DoNotStrip; + +@DoNotStrip +public class YogaNodeJNIBatching extends YogaNodeJNIBase { + + /* Those flags needs be in sync with YGJNI.cpp */ + private static final byte MARGIN = 1; + private static final byte PADDING = 2; + private static final byte BORDER = 4; + private static final byte DOES_LEGACY_STRETCH_BEHAVIOUR = 8; + private static final byte HAS_NEW_LAYOUT = 16; + + private static final byte LAYOUT_EDGE_SET_FLAG_INDEX = 0; + private static final byte LAYOUT_WIDTH_INDEX = 1; + private static final byte LAYOUT_HEIGHT_INDEX = 2; + private static final byte LAYOUT_LEFT_INDEX = 3; + private static final byte LAYOUT_TOP_INDEX = 4; + private static final byte LAYOUT_DIRECTION_INDEX = 5; + private static final byte LAYOUT_MARGIN_START_INDEX = 6; + private static final byte LAYOUT_PADDING_START_INDEX = 10; + private static final byte LAYOUT_BORDER_START_INDEX = 14; + + @DoNotStrip + private @Nullable float[] arr = null; + + @DoNotStrip + private int mLayoutDirection = 0; + + private boolean mHasNewLayout = true; + + public YogaNodeJNIBatching() { + super(); + } + + public YogaNodeJNIBatching(YogaConfig config) { + super(config); + } + + @Override + public void reset() { + super.reset(); + arr = null; + mHasNewLayout = true; + mLayoutDirection = 0; + } + + @Override + public float getLayoutX() { + return arr != null ? arr[LAYOUT_LEFT_INDEX] : 0; + } + + @Override + public float getLayoutY() { + return arr != null ? arr[LAYOUT_TOP_INDEX] : 0; + } + + @Override + public float getLayoutWidth() { + return arr != null ? arr[LAYOUT_WIDTH_INDEX] : 0; + } + + @Override + public float getLayoutHeight() { + return arr != null ? arr[LAYOUT_HEIGHT_INDEX] : 0; + } + + @Override + public boolean getDoesLegacyStretchFlagAffectsLayout() { + return arr != null && (((int) arr[LAYOUT_EDGE_SET_FLAG_INDEX] & DOES_LEGACY_STRETCH_BEHAVIOUR) == DOES_LEGACY_STRETCH_BEHAVIOUR); + } + + @Override + public float getLayoutMargin(YogaEdge edge) { + if (arr != null && ((int) arr[LAYOUT_EDGE_SET_FLAG_INDEX] & MARGIN) == MARGIN) { + switch (edge) { + case LEFT: + return arr[LAYOUT_MARGIN_START_INDEX]; + case TOP: + return arr[LAYOUT_MARGIN_START_INDEX + 1]; + case RIGHT: + return arr[LAYOUT_MARGIN_START_INDEX + 2]; + case BOTTOM: + return arr[LAYOUT_MARGIN_START_INDEX + 3]; + case START: + return getLayoutDirection() == YogaDirection.RTL ? arr[LAYOUT_MARGIN_START_INDEX + 2] : arr[LAYOUT_MARGIN_START_INDEX]; + case END: + return getLayoutDirection() == YogaDirection.RTL ? arr[LAYOUT_MARGIN_START_INDEX] : arr[LAYOUT_MARGIN_START_INDEX + 2]; + default: + throw new IllegalArgumentException("Cannot get layout margins of multi-edge shorthands"); + } + } else { + return 0; + } + } + + @Override + public float getLayoutPadding(YogaEdge edge) { + if (arr != null && ((int) arr[LAYOUT_EDGE_SET_FLAG_INDEX] & PADDING) == PADDING) { + int paddingStartIndex = LAYOUT_PADDING_START_INDEX - ((((int)arr[LAYOUT_EDGE_SET_FLAG_INDEX] & MARGIN) == MARGIN) ? 0 : 4); + switch (edge) { + case LEFT: + return arr[paddingStartIndex]; + case TOP: + return arr[paddingStartIndex + 1]; + case RIGHT: + return arr[paddingStartIndex + 2]; + case BOTTOM: + return arr[paddingStartIndex + 3]; + case START: + return getLayoutDirection() == YogaDirection.RTL ? arr[paddingStartIndex + 2] : arr[paddingStartIndex]; + case END: + return getLayoutDirection() == YogaDirection.RTL ? arr[paddingStartIndex] : arr[paddingStartIndex + 2]; + default: + throw new IllegalArgumentException("Cannot get layout paddings of multi-edge shorthands"); + } + } else { + return 0; + } + } + + @Override + public float getLayoutBorder(YogaEdge edge) { + if (arr != null && ((int) arr[LAYOUT_EDGE_SET_FLAG_INDEX] & BORDER) == BORDER) { + int borderStartIndex = LAYOUT_BORDER_START_INDEX - ((((int) arr[LAYOUT_EDGE_SET_FLAG_INDEX] & MARGIN) == MARGIN) ? 0 : 4) - ((((int) arr[LAYOUT_EDGE_SET_FLAG_INDEX] & PADDING) == PADDING) ? 0 : 4); + switch (edge) { + case LEFT: + return arr[borderStartIndex]; + case TOP: + return arr[borderStartIndex + 1]; + case RIGHT: + return arr[borderStartIndex + 2]; + case BOTTOM: + return arr[borderStartIndex + 3]; + case START: + return getLayoutDirection() == YogaDirection.RTL ? arr[borderStartIndex + 2] : arr[borderStartIndex]; + case END: + return getLayoutDirection() == YogaDirection.RTL ? arr[borderStartIndex] : arr[borderStartIndex + 2]; + default: + throw new IllegalArgumentException("Cannot get layout border of multi-edge shorthands"); + } + } else { + return 0; + } + } + + @Override + public YogaDirection getLayoutDirection() { + return YogaDirection.fromInt(arr != null ? (int) arr[LAYOUT_DIRECTION_INDEX] : mLayoutDirection); + } + + @Override + public boolean hasNewLayout() { + if (arr != null) { + return (((int) arr[LAYOUT_EDGE_SET_FLAG_INDEX]) & HAS_NEW_LAYOUT) == HAS_NEW_LAYOUT; + } else { + return mHasNewLayout; + } + } + + @Override + public void markLayoutSeen() { + if (arr != null) { + arr[LAYOUT_EDGE_SET_FLAG_INDEX] = ((int) arr[LAYOUT_EDGE_SET_FLAG_INDEX]) & ~(HAS_NEW_LAYOUT); + } + mHasNewLayout = false; + } +} diff --git a/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNI.cpp b/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNI.cpp index 7f703c89c84568..b8725c7e87bd37 100644 --- a/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNI.cpp +++ b/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNI.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,18 @@ enum YGStyleInput { IsReferenceBaseline, }; +const short int LAYOUT_EDGE_SET_FLAG_INDEX = 0; +const short int LAYOUT_WIDTH_INDEX = 1; +const short int LAYOUT_HEIGHT_INDEX = 2; +const short int LAYOUT_LEFT_INDEX = 3; +const short int LAYOUT_TOP_INDEX = 4; +const short int LAYOUT_DIRECTION_INDEX = 5; +const short int LAYOUT_MARGIN_START_INDEX = 6; +const short int LAYOUT_PADDING_START_INDEX = 10; +const short int LAYOUT_BORDER_START_INDEX = 14; + +bool useBatchingForLayoutOutputs; + class PtrJNodeMap { using JNodeArray = JArrayClass; std::map ptrsToIdxs_; @@ -97,6 +110,9 @@ union YGNodeContext { void* asVoidPtr; }; +const int DOES_LEGACY_STRETCH_BEHAVIOUR = 8; +const int HAS_NEW_LAYOUT = 16; + class YGNodeEdges { uintptr_t edges_; @@ -127,6 +143,10 @@ class YGNodeEdges { edges_ |= edge; return *this; } + + int get() { + return edges_; + } }; struct YogaValue { @@ -177,80 +197,140 @@ static void YGTransferLayoutOutputsRecursive( auto edgesSet = YGNodeEdges{root}; - static auto widthField = obj->getClass()->getField("mWidth"); - static auto heightField = obj->getClass()->getField("mHeight"); - static auto leftField = obj->getClass()->getField("mLeft"); - static auto topField = obj->getClass()->getField("mTop"); - - static auto marginLeftField = - obj->getClass()->getField("mMarginLeft"); - static auto marginTopField = obj->getClass()->getField("mMarginTop"); - static auto marginRightField = - obj->getClass()->getField("mMarginRight"); - static auto marginBottomField = - obj->getClass()->getField("mMarginBottom"); - - static auto paddingLeftField = - obj->getClass()->getField("mPaddingLeft"); - static auto paddingTopField = - obj->getClass()->getField("mPaddingTop"); - static auto paddingRightField = - obj->getClass()->getField("mPaddingRight"); - static auto paddingBottomField = - obj->getClass()->getField("mPaddingBottom"); - - static auto borderLeftField = - obj->getClass()->getField("mBorderLeft"); - static auto borderTopField = obj->getClass()->getField("mBorderTop"); - static auto borderRightField = - obj->getClass()->getField("mBorderRight"); - static auto borderBottomField = - obj->getClass()->getField("mBorderBottom"); - - static auto hasNewLayoutField = - obj->getClass()->getField("mHasNewLayout"); - static auto doesLegacyStretchBehaviour = obj->getClass()->getField( - "mDoesLegacyStretchFlagAffectsLayout"); - - obj->setFieldValue(widthField, YGNodeLayoutGetWidth(root)); - obj->setFieldValue(heightField, YGNodeLayoutGetHeight(root)); - obj->setFieldValue(leftField, YGNodeLayoutGetLeft(root)); - obj->setFieldValue(topField, YGNodeLayoutGetTop(root)); - obj->setFieldValue( - doesLegacyStretchBehaviour, - YGNodeLayoutGetDidLegacyStretchFlagAffectLayout(root)); - obj->setFieldValue(hasNewLayoutField, true); - YGTransferLayoutDirection(root, obj); - - if (edgesSet.has(YGNodeEdges::MARGIN)) { - obj->setFieldValue( - marginLeftField, YGNodeLayoutGetMargin(root, YGEdgeLeft)); - obj->setFieldValue(marginTopField, YGNodeLayoutGetMargin(root, YGEdgeTop)); - obj->setFieldValue( - marginRightField, YGNodeLayoutGetMargin(root, YGEdgeRight)); - obj->setFieldValue( - marginBottomField, YGNodeLayoutGetMargin(root, YGEdgeBottom)); - } + if (useBatchingForLayoutOutputs) { + bool marginFieldSet = edgesSet.has(YGNodeEdges::MARGIN); + bool paddingFieldSet = edgesSet.has(YGNodeEdges::PADDING); + bool borderFieldSet = edgesSet.has(YGNodeEdges::BORDER); - if (edgesSet.has(YGNodeEdges::PADDING)) { - obj->setFieldValue( - paddingLeftField, YGNodeLayoutGetPadding(root, YGEdgeLeft)); - obj->setFieldValue( - paddingTopField, YGNodeLayoutGetPadding(root, YGEdgeTop)); - obj->setFieldValue( - paddingRightField, YGNodeLayoutGetPadding(root, YGEdgeRight)); - obj->setFieldValue( - paddingBottomField, YGNodeLayoutGetPadding(root, YGEdgeBottom)); - } + int fieldFlags = edgesSet.get(); + fieldFlags |= HAS_NEW_LAYOUT; + if (YGNodeLayoutGetDidLegacyStretchFlagAffectLayout(root)) { + fieldFlags |= DOES_LEGACY_STRETCH_BEHAVIOUR; + } + + const int arrSize = 6 + (marginFieldSet ? 4 : 0) + + (paddingFieldSet ? 4 : 0) + (borderFieldSet ? 4 : 0); + float arr[18]; + arr[LAYOUT_EDGE_SET_FLAG_INDEX] = fieldFlags; + arr[LAYOUT_WIDTH_INDEX] = YGNodeLayoutGetWidth(root); + arr[LAYOUT_HEIGHT_INDEX] = YGNodeLayoutGetHeight(root); + arr[LAYOUT_LEFT_INDEX] = YGNodeLayoutGetLeft(root); + arr[LAYOUT_TOP_INDEX] = YGNodeLayoutGetTop(root); + arr[LAYOUT_DIRECTION_INDEX] = + static_cast(YGNodeLayoutGetDirection(root)); + if (marginFieldSet) { + arr[LAYOUT_MARGIN_START_INDEX] = YGNodeLayoutGetMargin(root, YGEdgeLeft); + arr[LAYOUT_MARGIN_START_INDEX + 1] = + YGNodeLayoutGetMargin(root, YGEdgeTop); + arr[LAYOUT_MARGIN_START_INDEX + 2] = + YGNodeLayoutGetMargin(root, YGEdgeRight); + arr[LAYOUT_MARGIN_START_INDEX + 3] = + YGNodeLayoutGetMargin(root, YGEdgeBottom); + } + if (paddingFieldSet) { + int paddingStartIndex = + LAYOUT_PADDING_START_INDEX - (marginFieldSet ? 0 : 4); + arr[paddingStartIndex] = YGNodeLayoutGetPadding(root, YGEdgeLeft); + arr[paddingStartIndex + 1] = YGNodeLayoutGetPadding(root, YGEdgeTop); + arr[paddingStartIndex + 2] = YGNodeLayoutGetPadding(root, YGEdgeRight); + arr[paddingStartIndex + 3] = YGNodeLayoutGetPadding(root, YGEdgeBottom); + } + + if (borderFieldSet) { + int borderStartIndex = LAYOUT_BORDER_START_INDEX - + (marginFieldSet ? 0 : 4) - (paddingFieldSet ? 0 : 4); + arr[borderStartIndex] = YGNodeLayoutGetBorder(root, YGEdgeLeft); + arr[borderStartIndex + 1] = YGNodeLayoutGetBorder(root, YGEdgeTop); + arr[borderStartIndex + 2] = YGNodeLayoutGetBorder(root, YGEdgeRight); + arr[borderStartIndex + 3] = YGNodeLayoutGetBorder(root, YGEdgeBottom); + } + + static auto arrField = obj->getClass()->getField("arr"); + local_ref arrFinal = make_float_array(arrSize); + arrFinal->setRegion(0, arrSize, arr); + obj->setFieldValue(arrField, arrFinal.get()); + + } else { + static auto widthField = obj->getClass()->getField("mWidth"); + static auto heightField = obj->getClass()->getField("mHeight"); + static auto leftField = obj->getClass()->getField("mLeft"); + static auto topField = obj->getClass()->getField("mTop"); + + static auto marginLeftField = + obj->getClass()->getField("mMarginLeft"); + static auto marginTopField = + obj->getClass()->getField("mMarginTop"); + static auto marginRightField = + obj->getClass()->getField("mMarginRight"); + static auto marginBottomField = + obj->getClass()->getField("mMarginBottom"); + + static auto paddingLeftField = + obj->getClass()->getField("mPaddingLeft"); + static auto paddingTopField = + obj->getClass()->getField("mPaddingTop"); + static auto paddingRightField = + obj->getClass()->getField("mPaddingRight"); + static auto paddingBottomField = + obj->getClass()->getField("mPaddingBottom"); + + static auto borderLeftField = + obj->getClass()->getField("mBorderLeft"); + static auto borderTopField = + obj->getClass()->getField("mBorderTop"); + static auto borderRightField = + obj->getClass()->getField("mBorderRight"); + static auto borderBottomField = + obj->getClass()->getField("mBorderBottom"); + + static auto hasNewLayoutField = + obj->getClass()->getField("mHasNewLayout"); + static auto doesLegacyStretchBehaviour = + obj->getClass()->getField( + "mDoesLegacyStretchFlagAffectsLayout"); + + obj->setFieldValue(widthField, YGNodeLayoutGetWidth(root)); + obj->setFieldValue(heightField, YGNodeLayoutGetHeight(root)); + obj->setFieldValue(leftField, YGNodeLayoutGetLeft(root)); + obj->setFieldValue(topField, YGNodeLayoutGetTop(root)); + obj->setFieldValue( + doesLegacyStretchBehaviour, + YGNodeLayoutGetDidLegacyStretchFlagAffectLayout(root)); + obj->setFieldValue(hasNewLayoutField, true); + YGTransferLayoutDirection(root, obj); + + if (edgesSet.has(YGNodeEdges::MARGIN)) { + obj->setFieldValue( + marginLeftField, YGNodeLayoutGetMargin(root, YGEdgeLeft)); + obj->setFieldValue( + marginTopField, YGNodeLayoutGetMargin(root, YGEdgeTop)); + obj->setFieldValue( + marginRightField, YGNodeLayoutGetMargin(root, YGEdgeRight)); + obj->setFieldValue( + marginBottomField, YGNodeLayoutGetMargin(root, YGEdgeBottom)); + } + + if (edgesSet.has(YGNodeEdges::PADDING)) { + obj->setFieldValue( + paddingLeftField, YGNodeLayoutGetPadding(root, YGEdgeLeft)); + obj->setFieldValue( + paddingTopField, YGNodeLayoutGetPadding(root, YGEdgeTop)); + obj->setFieldValue( + paddingRightField, YGNodeLayoutGetPadding(root, YGEdgeRight)); + obj->setFieldValue( + paddingBottomField, YGNodeLayoutGetPadding(root, YGEdgeBottom)); + } - if (edgesSet.has(YGNodeEdges::BORDER)) { - obj->setFieldValue( - borderLeftField, YGNodeLayoutGetBorder(root, YGEdgeLeft)); - obj->setFieldValue(borderTopField, YGNodeLayoutGetBorder(root, YGEdgeTop)); - obj->setFieldValue( - borderRightField, YGNodeLayoutGetBorder(root, YGEdgeRight)); - obj->setFieldValue( - borderBottomField, YGNodeLayoutGetBorder(root, YGEdgeBottom)); + if (edgesSet.has(YGNodeEdges::BORDER)) { + obj->setFieldValue( + borderLeftField, YGNodeLayoutGetBorder(root, YGEdgeLeft)); + obj->setFieldValue( + borderTopField, YGNodeLayoutGetBorder(root, YGEdgeTop)); + obj->setFieldValue( + borderRightField, YGNodeLayoutGetBorder(root, YGEdgeRight)); + obj->setFieldValue( + borderBottomField, YGNodeLayoutGetBorder(root, YGEdgeBottom)); + } } root->setHasNewLayout(false); @@ -292,6 +372,14 @@ static inline YGConfigRef _jlong2YGConfigRef(jlong addr) { return reinterpret_cast(static_cast(addr)); } +jlong jni_YGNodeClone(alias_ref thiz, jlong nativePointer) { + auto node = _jlong2YGNodeRef(nativePointer); + const YGNodeRef clonedYogaNode = YGNodeClone(node); + clonedYogaNode->setContext(node->getContext()); + + return reinterpret_cast(clonedYogaNode); +} + static YGSize YGJNIMeasureFunc( YGNodeRef node, float width, @@ -354,16 +442,21 @@ static int YGJNILogFunc( return result; } -jlong jni_YGNodeNew(alias_ref) { +jlong jni_YGNodeNew(alias_ref thiz, jboolean useBatching) { const YGNodeRef node = YGNodeNew(); node->setContext(YGNodeContext{}.asVoidPtr); node->setPrintFunc(YGPrint); + useBatchingForLayoutOutputs = useBatching; return reinterpret_cast(node); } -jlong jni_YGNodeNewWithConfig(alias_ref, jlong configPointer) { +jlong jni_YGNodeNewWithConfig( + alias_ref, + jlong configPointer, + jboolean useBatching) { const YGNodeRef node = YGNodeNewWithConfig(_jlong2YGConfigRef(configPointer)); node->setContext(YGNodeContext{}.asVoidPtr); + useBatchingForLayoutOutputs = useBatching; return reinterpret_cast(node); } @@ -1018,6 +1111,7 @@ jint JNI_OnLoad(JavaVM* vm, void*) { YGMakeCriticalNativeMethod(jni_YGNodeStyleSetAspectRatio), YGMakeCriticalNativeMethod(jni_YGNodeGetInstanceCount), YGMakeCriticalNativeMethod(jni_YGNodePrint), + YGMakeNativeMethod(jni_YGNodeClone), YGMakeNativeMethod(jni_YGNodeSetStyleInputs), YGMakeNativeMethod(jni_YGConfigNew), YGMakeNativeMethod(jni_YGConfigFree), diff --git a/ReactAndroid/src/main/jni/react/jni/BUCK b/ReactAndroid/src/main/jni/react/jni/BUCK index 71e59501f802cc..44737e39397856 100644 --- a/ReactAndroid/src/main/jni/react/jni/BUCK +++ b/ReactAndroid/src/main/jni/react/jni/BUCK @@ -8,6 +8,7 @@ EXPORTED_HEADERS = [ "JavaScriptExecutorHolder.h", "JCallback.h", "JMessageQueueThread.h", + "JNativeRunnable.h", "JReactMarker.h", "JSLoader.h", "JSLogging.h", diff --git a/ReactAndroid/src/main/libraries/fresco/fresco-react-native/BUCK b/ReactAndroid/src/main/libraries/fresco/fresco-react-native/BUCK index bba0fbf1da0e2b..2a9bfe8660874a 100644 --- a/ReactAndroid/src/main/libraries/fresco/fresco-react-native/BUCK +++ b/ReactAndroid/src/main/libraries/fresco/fresco-react-native/BUCK @@ -9,8 +9,8 @@ rn_android_prebuilt_aar( fb_native.remote_file( name = "fresco-binary-aar", - sha1 = "afc009601a89423ee3d3aec637a13f58e042b903", - url = "mvn:com.facebook.fresco:fresco:aar:1.11.0", + sha1 = "0369d4ac5a48cbd748854ea9043c88b807940fb3", + url = "mvn:com.facebook.fresco:fresco:aar:1.13.0", ) rn_android_prebuilt_aar( @@ -21,8 +21,8 @@ rn_android_prebuilt_aar( fb_native.remote_file( name = "drawee-binary-aar", - sha1 = "8e7f42127a6a4d646d1af7a26e884b3be2d38480", - url = "mvn:com.facebook.fresco:drawee:aar:1.11.0", + sha1 = "b846ceec4b708b630693fedb79c85aabd1dbdeed", + url = "mvn:com.facebook.fresco:drawee:aar:1.13.0", ) rn_android_library( @@ -44,8 +44,8 @@ rn_android_prebuilt_aar( fb_native.remote_file( name = "imagepipeline-base-aar", - sha1 = "a8d47c704314323b1046157c515f5c5c073018b8", - url = "mvn:com.facebook.fresco:imagepipeline-base:aar:1.11.0", + sha1 = "3c4b6613a59825951d3c2b3a5accdbdfd667d9cb", + url = "mvn:com.facebook.fresco:imagepipeline-base:aar:1.13.0", ) rn_android_prebuilt_aar( @@ -56,8 +56,8 @@ rn_android_prebuilt_aar( fb_native.remote_file( name = "imagepipeline-aar", - sha1 = "2d144ee1ca0d5139e19c90397f15b5b872daac61", - url = "mvn:com.facebook.fresco:imagepipeline:aar:1.11.0", + sha1 = "405fa064f139b495e0e857661a5706cfb22eafdf", + url = "mvn:com.facebook.fresco:imagepipeline:aar:1.13.0", ) rn_android_prebuilt_aar( @@ -68,8 +68,8 @@ rn_android_prebuilt_aar( remote_file( name = "nativeimagefilters-aar", - sha1 = "cea24f4b5afe89acd28c304eb63366de9d7cb2c5", - url = "mvn:com.facebook.fresco:nativeimagefilters:aar:1.11.0", + sha1 = "f49525db580abc4d2fb0a74fac771fc6c69f2adb", + url = "mvn:com.facebook.fresco:nativeimagefilters:aar:1.13.0", ) rn_prebuilt_jar( @@ -92,8 +92,8 @@ rn_android_prebuilt_aar( fb_native.remote_file( name = "fbcore-aar", - sha1 = "68026f8e56d556bf563314185d79708e1dad03fe", - url = "mvn:com.facebook.fresco:fbcore:aar:1.11.0", + sha1 = "f8dd8ba9d7ea60dc54b5fba4ed5f6feacc5e596f", + url = "mvn:com.facebook.fresco:fbcore:aar:1.13.0", ) rn_android_prebuilt_aar( @@ -104,6 +104,6 @@ rn_android_prebuilt_aar( fb_native.remote_file( name = "imagepipeline-okhttp3-binary-aar", - sha1 = "7a7b03cecd00526c7d6e42457879501411d291c9", - url = "mvn:com.facebook.fresco:imagepipeline-okhttp3:aar:1.11.0", + sha1 = "bc1212ca66cd09678b416894ea8bd04102d26c5f", + url = "mvn:com.facebook.fresco:imagepipeline-okhttp3:aar:1.13.0", ) diff --git a/ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/BUCK b/ReactAndroid/src/main/third-party/android/support/v7/appcompat/BUCK similarity index 100% rename from ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/BUCK rename to ReactAndroid/src/main/third-party/android/support/v7/appcompat/BUCK diff --git a/ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/aar-unpacker.py b/ReactAndroid/src/main/third-party/android/support/v7/appcompat/aar-unpacker.py similarity index 100% rename from ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/aar-unpacker.py rename to ReactAndroid/src/main/third-party/android/support/v7/appcompat/aar-unpacker.py diff --git a/ReactAndroid/src/test/java/com/facebook/react/BUCK b/ReactAndroid/src/test/java/com/facebook/react/BUCK index 3eecaae58b7f51..8cdeff023e29fb 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/BUCK @@ -3,8 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "YOGA_TARGET", "react_native_dep", "r rn_robolectric_test( name = "react", srcs = glob(["*.java"]), - # Please change the contact to the oncall of your team - contacts = ["oncall+fbandroid_sheriff@xmail.facebook.com"], + contacts = ["oncall+react_native@xmail.facebook.com"], deps = [ YOGA_TARGET, react_native_dep("libraries/fbcore/src/test/java/com/facebook/powermock:powermock"), @@ -17,7 +16,6 @@ rn_robolectric_test( react_native_dep("third-party/java/okio:okio"), react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react:react"), - react_native_target("java/com/facebook/react/animation:animation"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), react_native_target("java/com/facebook/react/touch:touch"), diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK b/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK index 2206284f930891..8319a30de312d4 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK @@ -3,8 +3,7 @@ load("//tools/build_defs/oss:rn_defs.bzl", "YOGA_TARGET", "react_native_dep", "r rn_robolectric_test( name = "modules", srcs = glob(["**/*.java"]), - # Please change the contact to the oncall of your team - contacts = ["oncall+fbandroid_sheriff@xmail.facebook.com"], + contacts = ["oncall+react_native@xmail.facebook.com"], visibility = [ "PUBLIC", ], @@ -20,7 +19,6 @@ rn_robolectric_test( react_native_dep("third-party/java/okio:okio"), react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react:react"), - react_native_target("java/com/facebook/react/animation:animation"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), react_native_target("java/com/facebook/react/common/network:network"), diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java index a7a18cde6c9135..ef4c60fa0eaa2b 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java @@ -129,7 +129,7 @@ public void testSimpleTimer() { mTiming.onHostResume(); mTiming.createTimer(1, 1, 0, false); stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(1)); + verify(mJSTimersMock).callTimers(JavaOnlyArray.of(1.0)); reset(mJSTimersMock); stepChoreographerFrame(); verifyNoMoreInteractions(mJSTimersMock); @@ -140,11 +140,11 @@ public void testSimpleRecurringTimer() { mTiming.createTimer(100, 1, 0, true); mTiming.onHostResume(); stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100)); + verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100.0)); reset(mJSTimersMock); stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100)); + verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100.0)); } @Test @@ -153,7 +153,7 @@ public void testCancelRecurringTimer() { mTiming.createTimer(105, 1, 0, true); stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(105)); + verify(mJSTimersMock).callTimers(JavaOnlyArray.of(105.0)); reset(mJSTimersMock); mTiming.deleteTimer(105); @@ -167,7 +167,7 @@ public void testPausingAndResuming() { mTiming.createTimer(41, 1, 0, true); stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41)); + verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); reset(mJSTimersMock); mTiming.onHostPause(); @@ -177,7 +177,7 @@ public void testPausingAndResuming() { reset(mJSTimersMock); mTiming.onHostResume(); stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41)); + verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); } @Test @@ -187,7 +187,7 @@ public void testHeadlessJsTaskInBackground() { mTiming.createTimer(41, 1, 0, true); stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41)); + verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); reset(mJSTimersMock); mTiming.onHeadlessJsTaskFinish(42); @@ -202,12 +202,12 @@ public void testHeadlessJsTaskInForeground() { mTiming.createTimer(41, 1, 0, true); stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41)); + verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); reset(mJSTimersMock); mTiming.onHeadlessJsTaskFinish(42); stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41)); + verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); reset(mJSTimersMock); mTiming.onHostPause(); @@ -222,13 +222,13 @@ public void testHeadlessJsTaskIntertwine() { mTiming.onHostPause(); stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41)); + verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); reset(mJSTimersMock); mTiming.onHostResume(); mTiming.onHeadlessJsTaskFinish(42); stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41)); + verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); reset(mJSTimersMock); mTiming.onHostPause(); @@ -239,7 +239,7 @@ public void testHeadlessJsTaskIntertwine() { @Test public void testSetTimeoutZero() { mTiming.createTimer(100, 0, 0, false); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100)); + verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100.0)); } @Test diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK b/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK index 8d49aa5e259cd9..b9df6bf62a3d73 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK @@ -27,7 +27,6 @@ rn_robolectric_test( react_native_dep("third-party/java/okio:okio"), react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react:react"), - react_native_target("java/com/facebook/react/animation:animation"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), react_native_target("java/com/facebook/react/touch:touch"), diff --git a/ReactCommon/cxxreact/BUCK b/ReactCommon/cxxreact/BUCK index 7a1c7aa654b30c..0f6f1076357c1c 100644 --- a/ReactCommon/cxxreact/BUCK +++ b/ReactCommon/cxxreact/BUCK @@ -44,6 +44,9 @@ rn_xplat_cxx_library( ], fbobjc_compiler_flags = get_apple_compiler_flags(), force_static = True, + preprocessor_flags = [ + "-DWITH_FBREMAP=1", + ], visibility = [ "PUBLIC", ], diff --git a/ReactCommon/cxxreact/Instance.cpp b/ReactCommon/cxxreact/Instance.cpp index a9b6bd5623a53c..ad5069238d6e32 100644 --- a/ReactCommon/cxxreact/Instance.cpp +++ b/ReactCommon/cxxreact/Instance.cpp @@ -180,5 +180,12 @@ void Instance::handleMemoryPressure(int pressureLevel) { nativeToJsBridge_->handleMemoryPressure(pressureLevel); } +void Instance::invokeAsync(std::function&& func) { + nativeToJsBridge_->runOnExecutorQueue([func=std::move(func)](JSExecutor *executor) { + func(); + executor->flush(); + }); +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/cxxreact/Instance.h b/ReactCommon/cxxreact/Instance.h index 72a5a7ee836071..b72729660ff601 100644 --- a/ReactCommon/cxxreact/Instance.h +++ b/ReactCommon/cxxreact/Instance.h @@ -71,6 +71,8 @@ class RN_EXPORT Instance { void handleMemoryPressure(int pressureLevel); + void invokeAsync(std::function&& func); + private: void callNativeModules(folly::dynamic &&calls, bool isEndOfBatch); void loadApplication(std::unique_ptr bundleRegistry, diff --git a/ReactCommon/cxxreact/JSBigString.cpp b/ReactCommon/cxxreact/JSBigString.cpp index fb6540d143c7d6..dc997d1cd5b728 100644 --- a/ReactCommon/cxxreact/JSBigString.cpp +++ b/ReactCommon/cxxreact/JSBigString.cpp @@ -48,6 +48,65 @@ JSBigFileString::~JSBigFileString() { close(m_fd); } +#ifdef WITH_FBREMAP +// Read and advance the pointer. +static uint16_t read16(char *&data) { + uint16_t result; + ::memcpy(&result, data, sizeof(result)); + data += sizeof(result); + return result; +} + +// If the given file has a remapping table header, remap its pages accordingly +// and return the byte offset from the beginning to the unwrapped payload. +static off_t maybeRemap(char *data, size_t size, int fd) { + // A remapped file's size must be a multiple of its page size, so quickly + // filter out files with incorrect size, without touching any pages. + static const size_t kMinPageSize = 4096; + if (size < kMinPageSize || size % kMinPageSize != 0) { + return 0; + } + const auto begin = data; + static const uint8_t kRemapMagic[] = { + 0xc6, 0x1f, 0xbc, 0x03, 0xc1, 0x03, 0x19, 0x1f, 0xa1, 0xd0, 0xeb, 0x73 + }; + if (::memcmp(data, kRemapMagic, sizeof(kRemapMagic)) != 0) { + return 0; + } + data += sizeof(kRemapMagic); + const size_t filePS = static_cast(1) << read16(data); + if (size & (filePS - 1)) { + return 0; + } + { + // System page size must be at least as granular as the remapping. + // TODO: Consider fallback that reads entire file into memory. + const size_t systemPS = getpagesize(); + CHECK(filePS >= systemPS) + << "filePS: " << filePS + << "systemPS: " << systemPS; + } + const off_t headerPages = read16(data); + uint16_t numMappings = read16(data); + size_t curFilePage = headerPages; + while (numMappings--) { + auto memPage = read16(data) + headerPages; + auto numPages = read16(data); + if (mmap(begin + memPage * filePS, numPages * filePS, + PROT_READ, MAP_FILE | MAP_PRIVATE | MAP_FIXED, + fd, curFilePage * filePS) == MAP_FAILED) { + CHECK(false) + << " memPage: " << memPage + << " numPages: " << numPages + << " curFilePage: " << curFilePage + << " size: " << size + << " error: " << std::strerror(errno); + } + curFilePage += numPages; + } + return headerPages * filePS; +} +#endif // WITH_FBREMAP const char *JSBigFileString::c_str() const { if (!m_data) { @@ -58,11 +117,19 @@ const char *JSBigFileString::c_str() const { << " size: " << m_size << " offset: " << m_mapOff << " error: " << std::strerror(errno); +#ifdef WITH_FBREMAP + // Remapping is only attempted when the entire file was requested. + if (m_mapOff == 0 && m_pageOff == 0) { + m_pageOff = maybeRemap(const_cast(m_data), m_size, m_fd); + } +#endif // WITH_FBREMAP } return m_data + m_pageOff; } size_t JSBigFileString::size() const { + // Ensure mapping has been initialized. + c_str(); return m_size - m_pageOff; } diff --git a/ReactCommon/cxxreact/JSBigString.h b/ReactCommon/cxxreact/JSBigString.h index 8e0f2128a1fde8..c4bf86fd71990e 100644 --- a/ReactCommon/cxxreact/JSBigString.h +++ b/ReactCommon/cxxreact/JSBigString.h @@ -133,7 +133,7 @@ class RN_EXPORT JSBigFileString : public JSBigString { private: int m_fd; // The file descriptor being mmaped size_t m_size; // The size of the mmaped region - off_t m_pageOff; // The offset in the mmaped region to the data. + mutable off_t m_pageOff; // The offset in the mmaped region to the data. off_t m_mapOff; // The offset in the file to the mmaped region. mutable const char *m_data; // Pointer to the mmaped region. }; diff --git a/ReactCommon/cxxreact/JSExecutor.h b/ReactCommon/cxxreact/JSExecutor.h index f7cfd9752642de..bc2eb7d4e40d42 100644 --- a/ReactCommon/cxxreact/JSExecutor.h +++ b/ReactCommon/cxxreact/JSExecutor.h @@ -108,6 +108,8 @@ class RN_EXPORT JSExecutor { virtual void destroy() {} virtual ~JSExecutor() {} + virtual void flush() {} + static std::string getSyntheticBundlePath( uint32_t bundleId, const std::string& bundlePath); diff --git a/ReactCommon/cxxreact/NativeToJsBridge.h b/ReactCommon/cxxreact/NativeToJsBridge.h index cc30054acd5bf4..c80114230e8bbe 100644 --- a/ReactCommon/cxxreact/NativeToJsBridge.h +++ b/ReactCommon/cxxreact/NativeToJsBridge.h @@ -83,9 +83,10 @@ class NativeToJsBridge { * Synchronously tears down the bridge and the main executor. */ void destroy(); -private: + void runOnExecutorQueue(std::function task); +private: // This is used to avoid a race condition where a proxyCallback gets queued // after ~NativeToJsBridge(), on the same thread. In that case, the callback // will try to run the task on m_callback which will have been destroyed diff --git a/ReactCommon/cxxreact/tests/BUCK b/ReactCommon/cxxreact/tests/BUCK index e9ae81aa21a2fe..b123414e910c8e 100644 --- a/ReactCommon/cxxreact/tests/BUCK +++ b/ReactCommon/cxxreact/tests/BUCK @@ -55,3 +55,17 @@ fb_xplat_cxx_test( react_native_xplat_target("cxxreact:jsbigstring"), ], ) + +fb_xplat_cxx_test( + name = "jsbigstring_test", + srcs = ["jsbigstring.cpp"], + compiler_flags = [ + "-fexceptions", + "-frtti", + ], + deps = [ + "fbsource//xplat/folly:molly", + "fbsource//xplat/third-party/gmock:gtest", + react_native_xplat_target("cxxreact:jsbigstring"), + ], +) diff --git a/ReactCommon/cxxreact/tests/jsbigstring.cpp b/ReactCommon/cxxreact/tests/jsbigstring.cpp index 69e265c96f0891..dc1bd3bd3717b5 100644 --- a/ReactCommon/cxxreact/tests/jsbigstring.cpp +++ b/ReactCommon/cxxreact/tests/jsbigstring.cpp @@ -15,7 +15,10 @@ using namespace facebook::react; namespace { int tempFileFromString(std::string contents) { - std::string tmp {getenv("TMPDIR")}; + const char *tmpDir = getenv("TMPDIR"); + if (tmpDir == nullptr) + tmpDir = "/tmp"; + std::string tmp {tmpDir}; tmp += "/temp.XXXXXX"; std::vector tmpBuf {tmp.begin(), tmp.end()}; @@ -57,3 +60,46 @@ TEST(JSBigFileString, MapPartTest) { ASSERT_EQ(needle[i], bigStr.c_str()[i]); } } + +TEST(JSBigFileString, RemapTest) { + static const uint8_t kRemapMagic[] = { + 0xc6, 0x1f, 0xbc, 0x03, 0xc1, 0x03, 0x19, 0x1f, 0xa1, 0xd0, 0xeb, 0x73 + }; + std::string data(std::begin(kRemapMagic), std::end(kRemapMagic)); + auto app = [&data](uint16_t v) { + data.append(reinterpret_cast(&v), sizeof(v)); + }; + size_t pageSizeLog2 = 16; + app(pageSizeLog2); + size_t pageSize = 1 << pageSizeLog2; + app(1); // header pages + app(2); // num mappings + // file page 0 -> memory page 1 + app(1); // memory page + app(1); // num pages + // file page 1 -> memory page 0 + app(0); // memory page + app(1); // num pages + while (data.size() < pageSize) { + app(0); + } + while (data.size() < pageSize * 2) { + app(0x1111); + } + while (data.size() < pageSize * 3) { + app(0x2222); + } + + int fd = tempFileFromString(data); + JSBigFileString bigStr {fd, data.size()}; + + ASSERT_EQ(pageSize * 2, bigStr.size()); + auto remapped = bigStr.c_str(); + size_t i = 0; + for (; i < pageSize; ++i) { + ASSERT_EQ(0x22, remapped[i]); + } + for (; i < pageSize * 2; ++i) { + ASSERT_EQ(0x11, remapped[i]); + } +} diff --git a/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp b/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp index a900be33ca1989..d464abe7e303d3 100644 --- a/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp +++ b/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp @@ -7,8 +7,6 @@ #include "AccessibilityProps.h" -#include - #include #include #include @@ -57,7 +55,6 @@ AccessibilityProps::AccessibilityProps( #if RN_DEBUG_STRING_CONVERTIBLE SharedDebugStringConvertibleList AccessibilityProps::getDebugProps() const { const auto &defaultProps = AccessibilityProps(); - LOG(INFO) << "Call AccessibilityProps::getDebugProps with testId " << testId; return SharedDebugStringConvertibleList{ debugStringConvertibleItem("testId", testId, defaultProps.testId), }; diff --git a/ReactCommon/fabric/core/events/EventEmitter.h b/ReactCommon/fabric/core/events/EventEmitter.h index f3ce76ca4f1f0e..f819e56b024cd4 100644 --- a/ReactCommon/fabric/core/events/EventEmitter.h +++ b/ReactCommon/fabric/core/events/EventEmitter.h @@ -35,6 +35,8 @@ class EventEmitter { using Tag = int32_t; public: + using Shared = std::shared_ptr; + static std::mutex &DispatchMutex(); static ValueFactory defaultPayloadFactory(); diff --git a/ReactCommon/fabric/core/shadownode/LocalData.h b/ReactCommon/fabric/core/shadownode/LocalData.h index d62b3d29128768..83680c3853b4c6 100644 --- a/ReactCommon/fabric/core/shadownode/LocalData.h +++ b/ReactCommon/fabric/core/shadownode/LocalData.h @@ -28,6 +28,8 @@ using SharedLocalData = std::shared_ptr; */ class LocalData : public Sealable, public DebugStringConvertible { public: + using Shared = std::shared_ptr; + virtual ~LocalData() = default; virtual folly::dynamic getDynamic() const { diff --git a/ReactCommon/fabric/core/shadownode/Props.h b/ReactCommon/fabric/core/shadownode/Props.h index 56362dddf87d5d..3c07d2b4c9eb08 100644 --- a/ReactCommon/fabric/core/shadownode/Props.h +++ b/ReactCommon/fabric/core/shadownode/Props.h @@ -18,18 +18,20 @@ namespace react { class Props; -using SharedProps = std::shared_ptr; +using SharedProps = std::shared_ptr; /* * Represents the most generic props object. */ class Props : public virtual Sealable, public virtual DebugStringConvertible { public: + using Shared = std::shared_ptr; + Props() = default; - Props(const Props &sourceProps, const RawProps &rawProps); + Props(Props const &sourceProps, RawProps const &rawProps); virtual ~Props() = default; - const std::string nativeId; + std::string const nativeId; /* * Special value that represents generation number of `Props` object, which @@ -38,10 +40,10 @@ class Props : public virtual Sealable, public virtual DebugStringConvertible { * revision equals `0`. * The value might be used for optimization purposes. */ - const int revision{0}; + int const revision{0}; #ifdef ANDROID - const folly::dynamic rawProps = folly::dynamic::object(); + folly::dynamic const rawProps = folly::dynamic::object(); #endif }; diff --git a/ReactCommon/fabric/core/shadownode/ShadowNode.h b/ReactCommon/fabric/core/shadownode/ShadowNode.h index 2d34ba43f7fd92..773406b4df1ece 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNode.h +++ b/ReactCommon/fabric/core/shadownode/ShadowNode.h @@ -43,8 +43,14 @@ class ShadowNode : public virtual Sealable, public virtual DebugStringConvertible, public std::enable_shared_from_this { public: - using Shared = std::shared_ptr; - using Weak = std::weak_ptr; + using Shared = std::shared_ptr; + using Weak = std::weak_ptr; + using Unshared = std::shared_ptr; + using ListOfShared = + better::small_vector; + using SharedListOfShared = std::shared_ptr; + using UnsharedListOfShared = std::shared_ptr; + using AncestorList = better::small_vector< std::pair< std::reference_wrapper /* parentNode */, diff --git a/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.cpp b/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.cpp index 18bce2d4558a37..4e31b42b2d2364 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.cpp +++ b/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.cpp @@ -10,35 +10,36 @@ namespace facebook { namespace react { -Tag ShadowNodeFragment::tagPlaceholder() { +Tag const ShadowNodeFragment::tagPlaceholder() { return 0; } -Tag ShadowNodeFragment::surfaceIdPlaceholder() { +SurfaceId const ShadowNodeFragment::surfaceIdPlaceholder() { return 0; } -SharedProps &ShadowNodeFragment::propsPlaceholder() { - static auto &instance = *new SharedProps(); +Props::Shared const &ShadowNodeFragment::propsPlaceholder() { + static auto &instance = *new Props::Shared(); return instance; } -SharedEventEmitter &ShadowNodeFragment::eventEmitterPlaceholder() { - static auto &instance = *new SharedEventEmitter(); +EventEmitter::Shared const &ShadowNodeFragment::eventEmitterPlaceholder() { + static auto &instance = *new EventEmitter::Shared(); return instance; } -SharedShadowNodeSharedList &ShadowNodeFragment::childrenPlaceholder() { - static auto &instance = *new SharedShadowNodeSharedList(); +ShadowNode::SharedListOfShared const & +ShadowNodeFragment::childrenPlaceholder() { + static auto &instance = *new ShadowNode::SharedListOfShared(); return instance; } -SharedLocalData &ShadowNodeFragment::localDataPlaceholder() { - static auto &instance = *new SharedLocalData(); +LocalData::Shared const &ShadowNodeFragment::localDataPlaceholder() { + static auto &instance = *new LocalData::Shared(); return instance; } -State::Shared &ShadowNodeFragment::statePlaceholder() { +State::Shared const &ShadowNodeFragment::statePlaceholder() { static auto &instance = *new State::Shared(); return instance; } diff --git a/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.h b/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.h index 1c77b09572cb2c..754e0553189755 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.h +++ b/ReactCommon/fabric/core/shadownode/ShadowNodeFragment.h @@ -25,21 +25,26 @@ namespace react { * retain ownership of them. */ struct ShadowNodeFragment { - Tag tag = 0; - Tag rootTag = 0; - const SharedProps &props = propsPlaceholder(); - const SharedEventEmitter &eventEmitter = eventEmitterPlaceholder(); - const SharedShadowNodeSharedList &children = childrenPlaceholder(); - const SharedLocalData &localData = localDataPlaceholder(); - const State::Shared &state = statePlaceholder(); + Tag const tag = tagPlaceholder(); + SurfaceId const rootTag = surfaceIdPlaceholder(); + Props::Shared const &props = propsPlaceholder(); + EventEmitter::Shared const &eventEmitter = eventEmitterPlaceholder(); + ShadowNode::SharedListOfShared const &children = childrenPlaceholder(); + LocalData::Shared const &localData = localDataPlaceholder(); + State::Shared const &state = statePlaceholder(); - static Tag tagPlaceholder(); - static Tag surfaceIdPlaceholder(); - static SharedProps &propsPlaceholder(); - static SharedEventEmitter &eventEmitterPlaceholder(); - static SharedShadowNodeSharedList &childrenPlaceholder(); - static SharedLocalData &localDataPlaceholder(); - static State::Shared &statePlaceholder(); + /* + * Placeholders. + * Use as default arguments as an indication that the field does not need to + * be changed. + */ + static Tag const tagPlaceholder(); + static SurfaceId const surfaceIdPlaceholder(); + static Props::Shared const &propsPlaceholder(); + static EventEmitter::Shared const &eventEmitterPlaceholder(); + static ShadowNode::SharedListOfShared const &childrenPlaceholder(); + static LocalData::Shared const &localDataPlaceholder(); + static State::Shared const &statePlaceholder(); }; } // namespace react diff --git a/ReactCommon/fabric/core/state/ConcreteState.h b/ReactCommon/fabric/core/state/ConcreteState.h index 3cb378835bccfe..6e64e04affad89 100644 --- a/ReactCommon/fabric/core/state/ConcreteState.h +++ b/ReactCommon/fabric/core/state/ConcreteState.h @@ -27,6 +27,8 @@ class ConcreteState : public State { using Shared = std::shared_ptr; using Data = DataT; + virtual ~ConcreteState() = default; + ConcreteState(Data &&data, StateCoordinator::Shared stateCoordinator) : State(std::move(stateCoordinator)), data_(std::move(data)) {} @@ -80,6 +82,15 @@ class ConcreteState : public State { priority); } +#ifdef ANDROID + const folly::dynamic getDynamic() const override { + return data_.getDynamic(); + } + void updateState(folly::dynamic data) const override { + updateState(std::move(Data(data))); + } +#endif + private: DataT data_; }; diff --git a/ReactCommon/fabric/core/state/State.cpp b/ReactCommon/fabric/core/state/State.cpp index fb9740bfed1e1d..9aa4b248112423 100644 --- a/ReactCommon/fabric/core/state/State.cpp +++ b/ReactCommon/fabric/core/state/State.cpp @@ -7,11 +7,16 @@ #include "State.h" +#include #include #include #include #include +#ifdef ANDROID +#include +#endif + namespace facebook { namespace react { @@ -26,5 +31,19 @@ const State::Shared &State::getCommitedState() const { return stateCoordinator_->getTarget().getShadowNode().getState(); } +#ifdef ANDROID +const folly::dynamic State::getDynamic() const { + LOG(FATAL) + << "State::getDynamic should never be called (some virtual method of a concrete implementation should be called instead)"; + abort(); + return folly::dynamic::object(); +} +void State::updateState(folly::dynamic data) const { + LOG(FATAL) + << "State::updateState should never be called (some virtual method of a concrete implementation should be called instead)."; + abort(); +} +#endif + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/core/state/State.h b/ReactCommon/fabric/core/state/State.h index 73769c020302b0..729645850d689b 100644 --- a/ReactCommon/fabric/core/state/State.h +++ b/ReactCommon/fabric/core/state/State.h @@ -7,6 +7,7 @@ #pragma once +#include #include namespace facebook { @@ -26,6 +27,11 @@ class State { State(StateCoordinator::Shared stateCoordinator); virtual ~State() = default; +#ifdef ANDROID + virtual const folly::dynamic getDynamic() const; + virtual void updateState(folly::dynamic data) const; +#endif + protected: StateCoordinator::Shared stateCoordinator_; diff --git a/ReactCommon/fabric/core/state/StateData.cpp b/ReactCommon/fabric/core/state/StateData.cpp new file mode 100644 index 00000000000000..44f2744e10d834 --- /dev/null +++ b/ReactCommon/fabric/core/state/StateData.cpp @@ -0,0 +1,30 @@ +/** + * 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. + */ + +#include "StateData.h" + +#ifdef ANDROID +#include +#endif + +namespace facebook { +namespace react { + +#ifdef ANDROID +StateData::~StateData() { + // This needs to be here or the linker will complain: + // https://gcc.gnu.org/wiki/VerboseDiagnostics#missing_vtable +} +const folly::dynamic StateData::getDynamic() const { + assert(false); // TODO: get rid of this? + return folly::dynamic::object(); +} + +#endif + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/core/state/StateData.h b/ReactCommon/fabric/core/state/StateData.h index f9ef2523dd142b..32b026cfd18b34 100644 --- a/ReactCommon/fabric/core/state/StateData.h +++ b/ReactCommon/fabric/core/state/StateData.h @@ -9,15 +9,32 @@ #include +#ifdef ANDROID +#include +#endif + namespace facebook { namespace react { /* - * Dummy type that is used as a placeholder for state data for nodes that - * don't have a state. + * Base class for state data. + * Must be used to provide getDynamic for Android. */ -struct StateData { +class StateData { + public: using Shared = std::shared_ptr; + + StateData() {} + +#ifdef ANDROID + StateData(folly::dynamic data) {} + + // Destructor must either be virtual or protected if we have any + // virtual methods + virtual ~StateData(); + + virtual const folly::dynamic getDynamic() const; +#endif }; } // namespace react diff --git a/ReactCommon/fabric/debug/DebugStringConvertible.cpp b/ReactCommon/fabric/debug/DebugStringConvertible.cpp index 20c182c483369d..b8a2e30eea3df7 100644 --- a/ReactCommon/fabric/debug/DebugStringConvertible.cpp +++ b/ReactCommon/fabric/debug/DebugStringConvertible.cpp @@ -7,39 +7,50 @@ #include "DebugStringConvertible.h" +#include +#include + namespace facebook { namespace react { #if RN_DEBUG_STRING_CONVERTIBLE std::string DebugStringConvertible::getDebugChildrenDescription( - DebugStringConvertibleOptions options, - int depth) const { - if (depth >= options.maximumDepth) { + DebugStringConvertibleOptions options) const { + if (options.depth >= options.maximumDepth) { return ""; } - std::string childrenString = ""; + options.depth++; + + auto trailing = options.format ? std::string{"\n"} : std::string{""}; + auto childrenString = std::string{""}; for (auto child : getDebugChildren()) { if (!child) { continue; } - childrenString += child->getDebugDescription(options, depth + 1); + childrenString += child->getDebugDescription(options) + trailing; + } + + if (!childrenString.empty() && !trailing.empty()) { + // Removing trailing fragment. + childrenString.erase(childrenString.end() - 1); } return childrenString; } std::string DebugStringConvertible::getDebugPropsDescription( - DebugStringConvertibleOptions options, - int depth) const { - if (depth >= options.maximumDepth) { + DebugStringConvertibleOptions options) const { + if (options.depth >= options.maximumDepth) { return ""; } - std::string propsString = ""; + options.depth++; + + auto propsString = std::string{""}; for (auto prop : getDebugProps()) { if (!prop) { @@ -48,7 +59,7 @@ std::string DebugStringConvertible::getDebugPropsDescription( auto name = prop->getDebugName(); auto value = prop->getDebugValue(); - auto children = prop->getDebugPropsDescription(options, depth + 1); + auto children = prop->getDebugPropsDescription(options); auto valueAndChildren = value + (children.empty() ? "" : "(" + children + ")"); propsString += @@ -64,22 +75,35 @@ std::string DebugStringConvertible::getDebugPropsDescription( } std::string DebugStringConvertible::getDebugDescription( - DebugStringConvertibleOptions options, - int depth) const { + DebugStringConvertibleOptions options) const { auto nameString = getDebugName(); auto valueString = getDebugValue(); - auto childrenString = getDebugChildrenDescription(options, depth); - auto propsString = getDebugPropsDescription(options, depth); - auto leading = options.format ? std::string(depth * 2, ' ') : std::string{""}; + // Convention: + // If `name` and `value` are empty, `description` is also empty. + if (nameString.empty() && valueString.empty()) { + return ""; + } + + // Convention: + // If `name` is empty and `value` isn't empty, `description` equals `value`. + if (nameString.empty()) { + return valueString; + } + + auto childrenString = getDebugChildrenDescription(options); + auto propsString = getDebugPropsDescription(options); + + auto leading = + options.format ? std::string(options.depth * 2, ' ') : std::string{""}; auto trailing = options.format ? std::string{"\n"} : std::string{""}; return leading + "<" + nameString + (valueString.empty() ? "" : "=" + valueString) + (propsString.empty() ? "" : " " + propsString) + - (childrenString.empty() ? "/>" + trailing - : ">" + trailing + childrenString + leading + - "" + trailing); + (childrenString.empty() ? "/>" + : ">" + trailing + childrenString + trailing + + leading + ""); } std::string DebugStringConvertible::getDebugName() const { @@ -99,6 +123,31 @@ SharedDebugStringConvertibleList DebugStringConvertible::getDebugProps() const { return SharedDebugStringConvertibleList(); } +/* + * `toString`-family implementation. + */ +std::string toString(std::string const &value) { + return value; +} +std::string toString(int const &value) { + return folly::to(value); +} +std::string toString(bool const &value) { + return folly::to(value); +} +std::string toString(float const &value) { + return folly::to(value); +} +std::string toString(double const &value) { + return folly::to(value); +} +std::string toString(void const *value) { + if (value == nullptr) { + return "null"; + } + return folly::sformat("0x{0:016x}", reinterpret_cast(value)); +} + #endif } // namespace react diff --git a/ReactCommon/fabric/debug/DebugStringConvertible.h b/ReactCommon/fabric/debug/DebugStringConvertible.h index 2c38093f1772fb..2767f92d2e9e88 100644 --- a/ReactCommon/fabric/debug/DebugStringConvertible.h +++ b/ReactCommon/fabric/debug/DebugStringConvertible.h @@ -30,13 +30,20 @@ using SharedDebugStringConvertibleList = struct DebugStringConvertibleOptions { bool format{true}; + int depth{0}; int maximumDepth{INT_MAX}; }; -// Abstract class describes conformance to DebugStringConvertible concept -// and implements basic recursive debug string assembly algorithm. -// Use this as a base class for providing a debugging textual representation -// of your class. +/* + * Abstract class describes conformance to DebugStringConvertible concept + * and implements basic recursive debug string assembly algorithm. + * Use this as a base class for providing a debugging textual representation + * of your class. + * + * The `DebugStringConvertible` *class* is obsolete. Whenever possible prefer + * implementing standalone functions that conform to the informal + * `DebugStringConvertible`-like interface instead of extending this class. + */ class DebugStringConvertible { public: virtual ~DebugStringConvertible() = default; @@ -63,17 +70,14 @@ class DebugStringConvertible { // Default implementation returns a description of the subtree // rooted at this node, represented in XML-like format. virtual std::string getDebugDescription( - DebugStringConvertibleOptions options = {}, - int depth = 0) const; + DebugStringConvertibleOptions options = {}) const; // Do same as `getDebugDescription` but return only *children* and // *properties* parts (which are used in `getDebugDescription`). virtual std::string getDebugPropsDescription( - DebugStringConvertibleOptions options = {}, - int depth = 0) const; + DebugStringConvertibleOptions options = {}) const; virtual std::string getDebugChildrenDescription( - DebugStringConvertibleOptions options = {}, - int depth = 0) const; + DebugStringConvertibleOptions options = {}) const; }; #else @@ -82,5 +86,257 @@ class DebugStringConvertible {}; #endif +#if RN_DEBUG_STRING_CONVERTIBLE + +/* + * Set of particular-format-opinionated functions that convert base types to + * `std::string`; practically incapsulate `folly:to<>` and `folly::format`. + */ +std::string toString(std::string const &value); +std::string toString(int const &value); +std::string toString(bool const &value); +std::string toString(float const &value); +std::string toString(double const &value); +std::string toString(void const *value); + +/* + * *Informal* `DebugStringConvertible` interface. + * + * The interface consts of several functions which are designed to be composable + * and reusable relying on C++ overloading mechanism. Implement appropriate + * versions of those functions for your custom type to enable conformance to the + * interface: + * + * - `getDebugName`: Returns a name of the object. Default implementation + * returns "Node". + * + * - `getDebugValue`: Returns a value assosiate with the object. Default + * implementation returns an empty string. + * + * - `getDebugChildren`: Returns a list of `DebugStringConvertible`-compatible + * objects which can be considered as *children* of the object. Default + * implementation returns an empty list. + * + * - `getDebugProps`: Returns a list of `DebugStringConvertible` objects which + * can be considered as *properties* of the object. Default implementation + * returns an empty list. + * + * - `getDebugDescription`: Returns a string which represents the object in a + * human-readable way. Default implementation returns a description of the + * subtree rooted at this node, represented in XML-like format using functions + * above to form the tree. + */ + +/* + * Universal implementation of `getDebugDescription`-family functions for all + * types. + */ +template +std::string getDebugName(T const &object) { + return "Node"; +} + +template +std::string getDebugValue(T const &object) { + return ""; +} + +template +std::vector getDebugChildren(T const &object) { + return {}; +} + +template +std::vector getDebugProps(T const &object) { + return {}; +} + +template +std::string getDebugPropsDescription( + T const &object, + DebugStringConvertibleOptions options) { + if (options.depth >= options.maximumDepth) { + return ""; + } + + std::string propsString = ""; + + options.depth++; + + for (auto prop : getDebugProps(object)) { + auto name = getDebugName(prop); + auto value = getDebugValue(prop); + auto children = getDebugPropsDescription(prop, options); + auto valueAndChildren = + value + (children.empty() ? "" : "(" + children + ")"); + propsString += + " " + name + (valueAndChildren.empty() ? "" : "=" + valueAndChildren); + } + + if (!propsString.empty()) { + // Removing leading space character. + propsString.erase(propsString.begin()); + } + + return propsString; +} + +template +std::string getDebugChildrenDescription( + T const &object, + DebugStringConvertibleOptions options) { + if (options.depth >= options.maximumDepth) { + return ""; + } + + auto trailing = options.format ? std::string{"\n"} : std::string{""}; + auto childrenString = std::string{""}; + options.depth++; + + for (auto child : getDebugChildren(object)) { + childrenString += getDebugDescription(child, options) + trailing; + } + + if (!childrenString.empty() && !trailing.empty()) { + // Removing trailing fragment. + childrenString.erase(childrenString.end() - 1); + } + + return childrenString; +} + +template +std::string getDebugDescription( + T const &object, + DebugStringConvertibleOptions options = {}) { + auto nameString = getDebugName(object); + auto valueString = getDebugValue(object); + + // Convention: + // If `name` and `value` are empty, `description` is also empty. + if (nameString.empty() && valueString.empty()) { + return ""; + } + + // Convention: + // If `name` is empty and `value` isn't empty, `description` equals `value`. + if (nameString.empty()) { + return valueString; + } + + auto childrenString = getDebugChildrenDescription(object, options); + auto propsString = getDebugPropsDescription(object, options); + + auto leading = + options.format ? std::string(options.depth * 2, ' ') : std::string{""}; + auto trailing = options.format ? std::string{"\n"} : std::string{""}; + + return leading + "<" + nameString + + (valueString.empty() ? "" : "=" + valueString) + + (propsString.empty() ? "" : " " + propsString) + + (childrenString.empty() ? "/>" + : ">" + trailing + childrenString + trailing + + leading + ""); +} + +/* + * Functions of `getDebugDescription`-family for primitive types. + */ +// `int` +inline std::string getDebugDescription( + int number, + DebugStringConvertibleOptions options = {}) { + return toString(number); +} + +// `float` +inline std::string getDebugDescription( + float number, + DebugStringConvertibleOptions options = {}) { + return toString(number); +} + +// `double` +inline std::string getDebugDescription( + double number, + DebugStringConvertibleOptions options = {}) { + return toString(number); +} + +// `bool` +inline std::string getDebugDescription( + bool boolean, + DebugStringConvertibleOptions options = {}) { + return toString(boolean); +} + +// `void *` +inline std::string getDebugDescription( + void *pointer, + DebugStringConvertibleOptions options = {}) { + return toString(pointer); +} + +// `std::string` +inline std::string getDebugDescription( + std::string const &string, + DebugStringConvertibleOptions options = {}) { + return string; +} + +// `std::vector` +template +std::string getDebugName(std::vector const &vector) { + return "List"; +} + +template +std::vector getDebugChildren(std::vector const &vector) { + return vector; +} + +// `std::shared_ptr` +template +inline std::string getDebugDescription( + std::shared_ptr const &pointer, + DebugStringConvertibleOptions options = {}) { + return getDebugDescription((void *)pointer.get()) + "(shared)"; +} + +// `std::weak_ptr` +template +inline std::string getDebugDescription( + std::weak_ptr const &pointer, + DebugStringConvertibleOptions options = {}) { + return getDebugDescription((void *)pointer.lock().get()) + "(weak)"; +} + +// `std::unique_ptr` +template +inline std::string getDebugDescription( + std::unique_ptr const &pointer, + DebugStringConvertibleOptions options = {}) { + return getDebugDescription((void *)pointer.get()) + "(unique)"; +} + +/* + * Trivial container for `name` and `value` pair that supports + * static `DebugStringConvertible` informal interface. + */ +struct DebugStringConvertibleObject { + std::string name; + std::string value; +}; + +inline std::string getDebugName(DebugStringConvertibleObject const &object) { + return object.name; +} + +inline std::string getDebugValue(DebugStringConvertibleObject const &object) { + return object.value; +} + +#endif + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/debug/debugStringConvertibleUtils.h b/ReactCommon/fabric/debug/debugStringConvertibleUtils.h index 51bf698b4be0b9..cf5fb18cb01f58 100644 --- a/ReactCommon/fabric/debug/debugStringConvertibleUtils.h +++ b/ReactCommon/fabric/debug/debugStringConvertibleUtils.h @@ -12,8 +12,6 @@ #include #include -#include -#include #include #include @@ -22,22 +20,6 @@ namespace react { #if RN_DEBUG_STRING_CONVERTIBLE -inline std::string toString(const std::string &value) { - return value; -} -inline std::string toString(const int &value) { - return folly::to(value); -} -inline std::string toString(const bool &value) { - return folly::to(value); -} -inline std::string toString(const float &value) { - return folly::to(value); -} -inline std::string toString(const double &value) { - return folly::to(value); -} - template inline SharedDebugStringConvertible debugStringConvertibleItem(std::string name, T value, T defaultValue = {}) { diff --git a/ReactCommon/fabric/debug/tests/DebugStringConvertibleTest.cpp b/ReactCommon/fabric/debug/tests/DebugStringConvertibleTest.cpp index 99a1e43a365113..edeafdb174b001 100644 --- a/ReactCommon/fabric/debug/tests/DebugStringConvertibleTest.cpp +++ b/ReactCommon/fabric/debug/tests/DebugStringConvertibleTest.cpp @@ -19,7 +19,7 @@ TEST(DebugStringConvertibleTest, handleSimpleNode) { ASSERT_STREQ(item->getDebugName().c_str(), "View"); ASSERT_STREQ(item->getDebugValue().c_str(), "hello"); - ASSERT_STREQ(item->getDebugDescription().c_str(), "\n"); + ASSERT_STREQ(item->getDebugDescription().c_str(), ""); } TEST(DebugStringConvertibleTest, handleSimpleNodeWithProps) { @@ -31,7 +31,7 @@ TEST(DebugStringConvertibleTest, handleSimpleNodeWithProps) { ASSERT_STREQ(item->getDebugName().c_str(), "View"); ASSERT_STREQ(item->getDebugValue().c_str(), "hello"); - ASSERT_STREQ(item->getDebugDescription().c_str(), "\n"); + ASSERT_STREQ(item->getDebugDescription().c_str(), ""); } TEST(DebugStringConvertibleTest, handleSimpleNodeWithChildren) { @@ -45,7 +45,7 @@ TEST(DebugStringConvertibleTest, handleSimpleNodeWithChildren) { ASSERT_STREQ(item->getDebugValue().c_str(), "hello"); ASSERT_STREQ( item->getDebugDescription().c_str(), - "\n \n\n"); + "\n \n"); } TEST(DebugStringConvertibleTest, handleNestedNode) { @@ -61,7 +61,7 @@ TEST(DebugStringConvertibleTest, handleNestedNode) { ASSERT_STREQ(item->getDebugValue().c_str(), "hello"); ASSERT_STREQ( item->getDebugDescription().c_str(), - "\n \n\n"); + "\n \n"); } TEST(DebugStringConvertibleTest, handleNodeWithComplexProps) { @@ -80,5 +80,5 @@ TEST(DebugStringConvertibleTest, handleNodeWithComplexProps) { ASSERT_STREQ(item->getDebugValue().c_str(), "hello"); ASSERT_STREQ( item->getDebugDescription().c_str(), - "\n"); + ""); } diff --git a/ReactCommon/fabric/mounting/BUCK b/ReactCommon/fabric/mounting/BUCK index 674ad5b5611acb..268c698fb424bc 100644 --- a/ReactCommon/fabric/mounting/BUCK +++ b/ReactCommon/fabric/mounting/BUCK @@ -27,6 +27,7 @@ rn_xplat_cxx_library( exported_headers = subdir_glob( [ ("", "*.h"), + ("stubs", "*.h"), ], prefix = "react/mounting", ), @@ -55,8 +56,11 @@ rn_xplat_cxx_library( "fbsource//xplat/folly:molly", "fbsource//xplat/third-party/glog:glog", react_native_xplat_target("better:better"), + react_native_xplat_target("fabric/components/root:root"), + react_native_xplat_target("fabric/components/view:view"), react_native_xplat_target("fabric/core:core"), react_native_xplat_target("fabric/debug:debug"), + react_native_xplat_target("utils:utils"), ], ) diff --git a/ReactCommon/fabric/mounting/Differentiator.cpp b/ReactCommon/fabric/mounting/Differentiator.cpp index 7c7b7f766a34ed..33610348a45602 100644 --- a/ReactCommon/fabric/mounting/Differentiator.cpp +++ b/ReactCommon/fabric/mounting/Differentiator.cpp @@ -152,7 +152,7 @@ static void calculateShadowViewMutations( // We have to call the algorithm recursively if the inserted view // is *not* the same as removed one. const auto &newChildPair = it->second; - if (newChildPair.shadowView != oldChildPair.shadowView) { + if (newChildPair != oldChildPair) { const auto oldGrandChildPairs = sliceChildShadowNodeViewPairs(*oldChildPair.shadowNode); const auto newGrandChildPairs = diff --git a/ReactCommon/fabric/uimanager/ShadowTree.cpp b/ReactCommon/fabric/mounting/ShadowTree.cpp similarity index 90% rename from ReactCommon/fabric/uimanager/ShadowTree.cpp rename to ReactCommon/fabric/mounting/ShadowTree.cpp index b008825bbf17d9..9bc002cb68282c 100644 --- a/ReactCommon/fabric/uimanager/ShadowTree.cpp +++ b/ReactCommon/fabric/mounting/ShadowTree.cpp @@ -5,13 +5,15 @@ #include "ShadowTree.h" +#include + #include #include #include #include #include #include -#include +#include #include "ShadowTreeDelegate.h" @@ -96,6 +98,10 @@ ShadowTree::ShadowTree( /* .props = */ props, /* .eventEmitter = */ noopEventEmitter, })); + +#ifdef RN_SHADOW_TREE_INTROSPECTION + stubViewTree_ = stubViewTreeFromShadowNode(*rootShadowNode_); +#endif } ShadowTree::~ShadowTree() { @@ -190,6 +196,23 @@ bool ShadowTree::tryCommit( if (revision) { *revision = revision_; } + +#ifdef RN_SHADOW_TREE_INTROSPECTION + stubViewTree_.mutate(mutations); + auto stubViewTree = stubViewTreeFromShadowNode(*rootShadowNode_); + if (stubViewTree_ != stubViewTree) { + LOG(ERROR) << "Old tree:" + << "\n" + << oldRootShadowNode->getDebugDescription() << "\n"; + LOG(ERROR) << "New tree:" + << "\n" + << newRootShadowNode->getDebugDescription() << "\n"; + LOG(ERROR) << "Mutations:" + << "\n" + << getDebugDescription(mutations); + assert(false); + } +#endif } emitLayoutEvents(mutations); diff --git a/ReactCommon/fabric/uimanager/ShadowTree.h b/ReactCommon/fabric/mounting/ShadowTree.h similarity index 90% rename from ReactCommon/fabric/uimanager/ShadowTree.h rename to ReactCommon/fabric/mounting/ShadowTree.h index 32811919764d94..5d5b83edb4e70f 100644 --- a/ReactCommon/fabric/uimanager/ShadowTree.h +++ b/ReactCommon/fabric/mounting/ShadowTree.h @@ -5,6 +5,10 @@ #pragma once +#ifdef DEBUG +#define RN_SHADOW_TREE_INTROSPECTION +#endif + #include #include @@ -13,8 +17,12 @@ #include #include #include +#include #include -#include + +#ifdef RN_SHADOW_TREE_INTROSPECTION +#include +#endif namespace facebook { namespace react { @@ -87,6 +95,10 @@ class ShadowTree final { mutable SharedRootShadowNode rootShadowNode_; // Protected by `commitMutex_`. mutable int revision_{1}; // Protected by `commitMutex_`. ShadowTreeDelegate const *delegate_; + +#ifdef RN_SHADOW_TREE_INTROSPECTION + mutable StubViewTree stubViewTree_; // Protected by `commitMutex_`. +#endif }; } // namespace react diff --git a/ReactCommon/fabric/uimanager/ShadowTreeDelegate.h b/ReactCommon/fabric/mounting/ShadowTreeDelegate.h similarity index 100% rename from ReactCommon/fabric/uimanager/ShadowTreeDelegate.h rename to ReactCommon/fabric/mounting/ShadowTreeDelegate.h diff --git a/ReactCommon/fabric/uimanager/ShadowTreeRegistry.cpp b/ReactCommon/fabric/mounting/ShadowTreeRegistry.cpp similarity index 100% rename from ReactCommon/fabric/uimanager/ShadowTreeRegistry.cpp rename to ReactCommon/fabric/mounting/ShadowTreeRegistry.cpp diff --git a/ReactCommon/fabric/uimanager/ShadowTreeRegistry.h b/ReactCommon/fabric/mounting/ShadowTreeRegistry.h similarity index 97% rename from ReactCommon/fabric/uimanager/ShadowTreeRegistry.h rename to ReactCommon/fabric/mounting/ShadowTreeRegistry.h index 06549953c34a5b..6f808d9aee19bc 100644 --- a/ReactCommon/fabric/uimanager/ShadowTreeRegistry.h +++ b/ReactCommon/fabric/mounting/ShadowTreeRegistry.h @@ -8,7 +8,7 @@ #include #include -#include +#include namespace facebook { namespace react { diff --git a/ReactCommon/fabric/mounting/ShadowView.cpp b/ReactCommon/fabric/mounting/ShadowView.cpp index 0cd8eee83101e5..f98a7d0d102479 100644 --- a/ReactCommon/fabric/mounting/ShadowView.cpp +++ b/ReactCommon/fabric/mounting/ShadowView.cpp @@ -50,6 +50,27 @@ bool ShadowView::operator!=(const ShadowView &rhs) const { return !(*this == rhs); } +#if RN_DEBUG_STRING_CONVERTIBLE + +std::string getDebugName(ShadowView const &object) { + return object.componentHandle == 0 ? object.componentName : "Empty"; +} + +std::vector getDebugProps( + ShadowView const &object, + DebugStringConvertibleOptions options) { + return { + {"tag", getDebugDescription(object.tag, options)}, + {"props", getDebugDescription(object.props, options)}, + {"eventEmitter", getDebugDescription(object.eventEmitter, options)}, + {"layoutMetrics", getDebugDescription(object.layoutMetrics, options)}, + {"localData", getDebugDescription(object.localData, options)}, + {"state", getDebugDescription(object.state, options)}, + }; +} + +#endif + bool ShadowViewNodePair::operator==(const ShadowViewNodePair &rhs) const { return this->shadowNode == rhs.shadowNode; } diff --git a/ReactCommon/fabric/mounting/ShadowView.h b/ReactCommon/fabric/mounting/ShadowView.h index b8b57a0f82a116..a854421abf6cdd 100644 --- a/ReactCommon/fabric/mounting/ShadowView.h +++ b/ReactCommon/fabric/mounting/ShadowView.h @@ -46,6 +46,15 @@ struct ShadowView final { State::Shared state = {}; }; +#if RN_DEBUG_STRING_CONVERTIBLE + +std::string getDebugName(ShadowView const &object); +std::vector getDebugProps( + ShadowView const &object, + DebugStringConvertibleOptions options = {}); + +#endif + /* * Describes pair of a `ShadowView` and a `ShadowNode`. */ diff --git a/ReactCommon/fabric/mounting/ShadowViewMutation.cpp b/ReactCommon/fabric/mounting/ShadowViewMutation.cpp index e73e05ceea8cce..12f940c780fd5e 100644 --- a/ReactCommon/fabric/mounting/ShadowViewMutation.cpp +++ b/ReactCommon/fabric/mounting/ShadowViewMutation.cpp @@ -70,5 +70,54 @@ ShadowViewMutation ShadowViewMutation::UpdateMutation( }; } +#if RN_DEBUG_STRING_CONVERTIBLE + +std::string getDebugName(ShadowViewMutation const &mutation) { + switch (mutation.type) { + case ShadowViewMutation::Create: + return "Create"; + case ShadowViewMutation::Delete: + return "Delete"; + case ShadowViewMutation::Insert: + return "Insert"; + case ShadowViewMutation::Remove: + return "Remove"; + case ShadowViewMutation::Update: + return "Update"; + } +} + +std::vector getDebugProps( + ShadowViewMutation const &mutation, + DebugStringConvertibleOptions options) { + return { + mutation.oldChildShadowView.componentHandle + ? DebugStringConvertibleObject{"oldChild", + getDebugDescription( + mutation.oldChildShadowView, + options)} + : DebugStringConvertibleObject{}, + mutation.newChildShadowView.componentHandle + ? DebugStringConvertibleObject{"newChild", + getDebugDescription( + mutation.newChildShadowView, + options)} + : DebugStringConvertibleObject{}, + mutation.parentShadowView.componentHandle + ? DebugStringConvertibleObject{"parent", + getDebugDescription( + mutation.parentShadowView, + options)} + : DebugStringConvertibleObject{}, + mutation.index != -1 + ? DebugStringConvertibleObject{"index", + getDebugDescription( + mutation.index, options)} + : DebugStringConvertibleObject{}, + }; +} + +#endif + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/mounting/ShadowViewMutation.h b/ReactCommon/fabric/mounting/ShadowViewMutation.h index 2de6e06aad63e1..13eab78da42a58 100644 --- a/ReactCommon/fabric/mounting/ShadowViewMutation.h +++ b/ReactCommon/fabric/mounting/ShadowViewMutation.h @@ -73,5 +73,14 @@ struct ShadowViewMutation final { using ShadowViewMutationList = std::vector; +#if RN_DEBUG_STRING_CONVERTIBLE + +std::string getDebugName(ShadowViewMutation const &object); +std::vector getDebugProps( + ShadowViewMutation const &object, + DebugStringConvertibleOptions options = {}); + +#endif + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/mounting/stubs/StubView.cpp b/ReactCommon/fabric/mounting/stubs/StubView.cpp new file mode 100644 index 00000000000000..5f0adaf016fb6b --- /dev/null +++ b/ReactCommon/fabric/mounting/stubs/StubView.cpp @@ -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. + +#include "StubView.h" + +namespace facebook { +namespace react { + +void StubView::update(ShadowView const &shadowView) { + componentName = shadowView.componentName; + componentHandle = shadowView.componentHandle; + tag = shadowView.tag; + props = shadowView.props; + eventEmitter = shadowView.eventEmitter; + layoutMetrics = shadowView.layoutMetrics; + state = shadowView.state; +} + +bool operator==(StubView const &lhs, StubView const &rhs) { + return std::tie(lhs.props, lhs.layoutMetrics) == + std::tie(rhs.props, rhs.layoutMetrics); +} + +bool operator!=(StubView const &lhs, StubView const &rhs) { + return !(lhs == rhs); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/mounting/stubs/StubView.h b/ReactCommon/fabric/mounting/stubs/StubView.h new file mode 100644 index 00000000000000..a69598251cf7b8 --- /dev/null +++ b/ReactCommon/fabric/mounting/stubs/StubView.h @@ -0,0 +1,41 @@ +// 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. + +#pragma once + +#include +#include + +#include +#include +#include + +namespace facebook { +namespace react { + +class StubView final { + public: + using Shared = std::shared_ptr; + + StubView() = default; + StubView(StubView const &stubView) = default; + + void update(ShadowView const &shadowView); + + ComponentName componentName; + ComponentHandle componentHandle; + Tag tag; + SharedProps props; + SharedEventEmitter eventEmitter; + LayoutMetrics layoutMetrics; + State::Shared state; + std::vector children; +}; + +bool operator==(StubView const &lhs, StubView const &rhs); +bool operator!=(StubView const &lhs, StubView const &rhs); + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/mounting/stubs/StubViewTree.cpp b/ReactCommon/fabric/mounting/stubs/StubViewTree.cpp new file mode 100644 index 00000000000000..a6152c0840e529 --- /dev/null +++ b/ReactCommon/fabric/mounting/stubs/StubViewTree.cpp @@ -0,0 +1,104 @@ +// 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. + +#include "StubViewTree.h" + +namespace facebook { +namespace react { + +StubViewTree::StubViewTree(ShadowView const &shadowView) { + auto view = std::make_shared(); + view->update(shadowView); + registry[shadowView.tag] = view; +} + +void StubViewTree::mutate(ShadowViewMutationList const &mutations) { + for (auto const &mutation : mutations) { + switch (mutation.type) { + case ShadowViewMutation::Create: { + assert(mutation.parentShadowView == ShadowView{}); + assert(mutation.oldChildShadowView == ShadowView{}); + auto stubView = std::make_shared(); + auto tag = mutation.newChildShadowView.tag; + assert(registry.find(tag) == registry.end()); + registry[tag] = stubView; + break; + } + + case ShadowViewMutation::Delete: { + assert(mutation.parentShadowView == ShadowView{}); + assert(mutation.newChildShadowView == ShadowView{}); + auto tag = mutation.oldChildShadowView.tag; + assert(registry.find(tag) != registry.end()); + registry.erase(tag); + break; + } + + case ShadowViewMutation::Insert: { + assert(mutation.oldChildShadowView == ShadowView{}); + auto parentTag = mutation.parentShadowView.tag; + assert(registry.find(parentTag) != registry.end()); + auto parentStubView = registry[parentTag]; + auto childTag = mutation.newChildShadowView.tag; + assert(registry.find(childTag) != registry.end()); + auto childStubView = registry[childTag]; + childStubView->update(mutation.newChildShadowView); + parentStubView->children.insert( + parentStubView->children.begin() + mutation.index, childStubView); + break; + } + + case ShadowViewMutation::Remove: { + assert(mutation.newChildShadowView == ShadowView{}); + auto parentTag = mutation.parentShadowView.tag; + assert(registry.find(parentTag) != registry.end()); + auto parentStubView = registry[parentTag]; + auto childTag = mutation.oldChildShadowView.tag; + assert(registry.find(childTag) != registry.end()); + auto childStubView = registry[childTag]; + assert( + parentStubView->children[mutation.index]->tag == + childStubView->tag); + parentStubView->children.erase( + parentStubView->children.begin() + mutation.index); + break; + } + + case ShadowViewMutation::Update: { + assert( + mutation.newChildShadowView.tag == mutation.oldChildShadowView.tag); + assert( + registry.find(mutation.newChildShadowView.tag) != registry.end()); + auto stubView = registry[mutation.newChildShadowView.tag]; + stubView->update(mutation.newChildShadowView); + break; + } + } + } +} + +bool operator==(StubViewTree const &lhs, StubViewTree const &rhs) { + if (lhs.registry.size() != rhs.registry.size()) { + return false; + } + + for (auto const &pair : lhs.registry) { + auto &lhsStubView = *lhs.registry.at(pair.first); + auto &rhsStubView = *rhs.registry.at(pair.first); + + if (lhsStubView != rhsStubView) { + return false; + } + } + + return true; +} + +bool operator!=(StubViewTree const &lhs, StubViewTree const &rhs) { + return !(lhs == rhs); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/mounting/stubs/StubViewTree.h b/ReactCommon/fabric/mounting/stubs/StubViewTree.h new file mode 100644 index 00000000000000..59f112b895430f --- /dev/null +++ b/ReactCommon/fabric/mounting/stubs/StubViewTree.h @@ -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. + +#pragma once + +#include +#include + +#include +#include + +namespace facebook { +namespace react { + +class StubViewTree { + public: + StubViewTree() = default; + StubViewTree(ShadowView const &shadowView); + + void mutate(ShadowViewMutationList const &mutations); + + std::unordered_map registry{}; +}; + +bool operator==(StubViewTree const &lhs, StubViewTree const &rhs); +bool operator!=(StubViewTree const &lhs, StubViewTree const &rhs); + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/mounting/stubs/stubs.cpp b/ReactCommon/fabric/mounting/stubs/stubs.cpp new file mode 100644 index 00000000000000..48c609c32bb3f1 --- /dev/null +++ b/ReactCommon/fabric/mounting/stubs/stubs.cpp @@ -0,0 +1,30 @@ +// 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. + +#include "stubs.h" + +#include +#include +#include + +namespace facebook { +namespace react { + +StubViewTree stubViewTreeFromShadowNode(ShadowNode const &rootShadowNode) { + auto emptyRootShadowNode = rootShadowNode.clone( + ShadowNodeFragment{ShadowNodeFragment::tagPlaceholder(), + ShadowNodeFragment::surfaceIdPlaceholder(), + ShadowNodeFragment::propsPlaceholder(), + ShadowNodeFragment::eventEmitterPlaceholder(), + ShadowNode::emptySharedShadowNodeSharedList()}); + + auto stubViewTree = StubViewTree(ShadowView(*emptyRootShadowNode)); + stubViewTree.mutate( + calculateShadowViewMutations(*emptyRootShadowNode, rootShadowNode)); + return stubViewTree; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/mounting/stubs/stubs.h b/ReactCommon/fabric/mounting/stubs/stubs.h new file mode 100644 index 00000000000000..f02c5a2e60615e --- /dev/null +++ b/ReactCommon/fabric/mounting/stubs/stubs.h @@ -0,0 +1,18 @@ +// 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. + +#pragma once + +#include +#include "StubView.h" +#include "StubViewTree.h" + +namespace facebook { +namespace react { + +StubViewTree stubViewTreeFromShadowNode(ShadowNode const &rootShadowNode); + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/uimanager/BUCK b/ReactCommon/fabric/uimanager/BUCK index e92abaaa1c83b9..359f3b9e2b45a2 100644 --- a/ReactCommon/fabric/uimanager/BUCK +++ b/ReactCommon/fabric/uimanager/BUCK @@ -57,11 +57,11 @@ rn_xplat_cxx_library( "fbsource//xplat/jsi:jsi", "fbsource//xplat/third-party/glog:glog", react_native_xplat_target("config:config"), - react_native_xplat_target("fabric/components/root:root"), react_native_xplat_target("fabric/components/view:view"), react_native_xplat_target("fabric/mounting:mounting"), react_native_xplat_target("fabric/core:core"), react_native_xplat_target("fabric/debug:debug"), + react_native_xplat_target("utils:utils"), ], ) diff --git a/ReactCommon/fabric/uimanager/Scheduler.cpp b/ReactCommon/fabric/uimanager/Scheduler.cpp index 37f2151cbcbe99..728bedcfa87981 100644 --- a/ReactCommon/fabric/uimanager/Scheduler.cpp +++ b/ReactCommon/fabric/uimanager/Scheduler.cpp @@ -11,10 +11,10 @@ #include #include #include -#include #include #include #include +#include namespace facebook { namespace react { diff --git a/ReactCommon/fabric/uimanager/Scheduler.h b/ReactCommon/fabric/uimanager/Scheduler.h index 6bad79fc14400f..4426eda9a666e2 100644 --- a/ReactCommon/fabric/uimanager/Scheduler.h +++ b/ReactCommon/fabric/uimanager/Scheduler.h @@ -12,13 +12,13 @@ #include #include #include +#include +#include +#include #include #include #include #include -#include -#include -#include #include #include #include diff --git a/ReactCommon/fabric/uimanager/UIManager.cpp b/ReactCommon/fabric/uimanager/UIManager.cpp index 6c6c9552dfd9fb..dc2df34ab85881 100644 --- a/ReactCommon/fabric/uimanager/UIManager.cpp +++ b/ReactCommon/fabric/uimanager/UIManager.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace facebook { namespace react { diff --git a/ReactCommon/fabric/uimanager/UIManager.h b/ReactCommon/fabric/uimanager/UIManager.h index 02fd1eda2f31a6..f8ccb65dd5b0d5 100644 --- a/ReactCommon/fabric/uimanager/UIManager.h +++ b/ReactCommon/fabric/uimanager/UIManager.h @@ -8,8 +8,8 @@ #include #include +#include #include -#include #include namespace facebook { diff --git a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h index 1eea5d5bce3b2f..39c692f979af6f 100644 --- a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h +++ b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h @@ -103,10 +103,11 @@ class JSIExecutor : public JSExecutor { invokee(); } + void flush() override; + private: class NativeModuleProxy; - void flush(); void bindBridge(); void callNativeModules(const jsi::Value &queue, bool isEndOfBatch); jsi::Value nativeCallSyncHook(const jsi::Value *args, size_t count); diff --git a/ReactCommon/turbomodule/core/JSCallInvoker.cpp b/ReactCommon/turbomodule/core/JSCallInvoker.cpp index ec28ce1c3c746f..f58adb91f304f7 100644 --- a/ReactCommon/turbomodule/core/JSCallInvoker.cpp +++ b/ReactCommon/turbomodule/core/JSCallInvoker.cpp @@ -7,20 +7,20 @@ #include "JSCallInvoker.h" -#include +#include namespace facebook { namespace react { -JSCallInvoker::JSCallInvoker(std::shared_ptr jsThread) - : jsThread_(jsThread) {} +JSCallInvoker::JSCallInvoker(std::weak_ptr reactInstance) + : reactInstance_(reactInstance) {} void JSCallInvoker::invokeAsync(std::function&& func) { - jsThread_->runOnQueue(std::move(func)); -} - -void JSCallInvoker::invokeSync(std::function&& func) { - jsThread_->runOnQueueSync(std::move(func)); + auto instance = reactInstance_.lock(); + if (instance == nullptr) { + return; + } + instance->invokeAsync(std::move(func)); } } // namespace react diff --git a/ReactCommon/turbomodule/core/JSCallInvoker.h b/ReactCommon/turbomodule/core/JSCallInvoker.h index 1f84bbc020d45d..770cdf2545d39a 100644 --- a/ReactCommon/turbomodule/core/JSCallInvoker.h +++ b/ReactCommon/turbomodule/core/JSCallInvoker.h @@ -13,25 +13,26 @@ namespace facebook { namespace react { -class MessageQueueThread; +class Instance; /** * A generic native-to-JS call invoker. It guarantees that any calls from any * thread are queued on the right JS thread. * - * For now, this is a thin-wrapper around existing MessageQueueThread. Eventually, + * For now, this is a thin-wrapper around existing bridge (`Instance`). Eventually, * it should be consolidated with Fabric implementation so there's only one * API to call JS from native, whether synchronously or asynchronously. + * Also, this class should not depend on `Instance` in the future. */ class JSCallInvoker { public: - JSCallInvoker(std::shared_ptr jsThread); + JSCallInvoker(std::weak_ptr reactInstance); void invokeAsync(std::function&& func); - void invokeSync(std::function&& func); + // TODO: add sync support private: - std::shared_ptr jsThread_; + std::weak_ptr reactInstance_; }; } // namespace react diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h index da78e21e11c444..dbd31c8f4dfef4 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h @@ -24,6 +24,8 @@ namespace facebook { namespace react { +class Instance; + /** * ObjC++ specific TurboModule base class. */ @@ -89,5 +91,6 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule { @interface RCTBridge () - (std::shared_ptr)jsMessageThread; +- (std::weak_ptr)reactInstance; @end diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm index ed2d68dd3ab3b4..bf52917bd59f29 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.mm @@ -474,29 +474,60 @@ SEL resolveMethodSelector( NSMutableArray *retainedObjectsForInvocation) { NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[module class] instanceMethodSignatureForSelector:selector]]; [inv setSelector:selector]; + + NSMethodSignature *methodSignature = [[module class] instanceMethodSignatureForSelector:selector]; + for (size_t i = 0; i < count; i++) { const jsi::Value *arg = &args[i]; + const char *objCArgType = [methodSignature getArgumentTypeAtIndex:i + 2]; + if (arg->isBool()) { bool v = arg->getBool(); - [inv setArgument:(void *)&v atIndex:i + 2]; - } else if (arg->isNumber()) { + + /** + * JS type checking ensures the Objective C argument here is either a BOOL or NSNumber*. + */ + if (objCArgType[0] == _C_ID) { + id objCArg = [NSNumber numberWithBool:v]; + [inv setArgument:(void *)&objCArg atIndex:i + 2]; + [retainedObjectsForInvocation addObject:objCArg]; + } else { + [inv setArgument:(void *)&v atIndex:i + 2]; + } + + continue; + } + + if (arg->isNumber()) { double v = arg->getNumber(); - [inv setArgument:(void *)&v atIndex:i + 2]; - } else { - id v = convertJSIValueToObjCObject(runtime, *arg, jsInvoker); - NSString *methodNameObjc = @(methodName.c_str()); - - NSMethodSignature *methodSignature = [[module class] instanceMethodSignatureForSelector:selector]; - const char *objcType = [methodSignature getArgumentTypeAtIndex:i]; - - if (objcType[0] == _C_ID) { - NSString* argumentType = getArgumentTypeName(methodNameObjc, i); - - /** - * When argumentType is nil, it means that the method hasn't been wrapped with - * an RCT_EXPORT_METHOD macro. Therefore, we do not support converting the method - * arguments using RCTConvert. - */ + + /** + * JS type checking ensures the Objective C argument here is either a double or NSNumber*. + */ + if (objCArgType[0] == _C_ID) { + id objCArg = [NSNumber numberWithDouble:v]; + [inv setArgument:(void *)&objCArg atIndex:i + 2]; + [retainedObjectsForInvocation addObject:objCArg]; + } else { + [inv setArgument:(void *)&v atIndex:i + 2]; + } + + continue; + } + + /** + * Convert arg to ObjC objects. + */ + id objCArg = convertJSIValueToObjCObject(runtime, *arg, jsInvoker); + + if (objCArg) { + NSString *methodNameNSString = @(methodName.c_str()); + + /** + * Convert objects using RCTConvert. + */ + if (objCArgType[0] == _C_ID) { + NSString* argumentType = getArgumentTypeName(methodNameNSString, i); if (argumentType != nil) { NSString *rctConvertMethodName = [NSString stringWithFormat:@"%@:", argumentType]; SEL rctConvertSelector = NSSelectorFromString(rctConvertMethodName); @@ -504,34 +535,50 @@ SEL resolveMethodSelector( if ([RCTConvert respondsToSelector: rctConvertSelector]) { // Message dispatch logic from old infra id (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend; - v = convert([RCTConvert class], rctConvertSelector, v); - - /** - * TODO(ramanpreet): - * Investigate whether we can avoid inserting to retainedObjectsForInvocation. - * Otherwise, NSInvocation raises a BAD_ACCESS when we invoke the retainArguments method. - **/ - [retainedObjectsForInvocation addObject:v]; + id convertedObjCArg = convert([RCTConvert class], rctConvertSelector, objCArg); + + [inv setArgument:(void *)&convertedObjCArg atIndex:i + 2]; + if (convertedObjCArg) { + [retainedObjectsForInvocation addObject:convertedObjCArg]; + } + continue; } } } - if ([v isKindOfClass:[NSDictionary class]] && hasMethodArgConversionSelector(methodNameObjc, i)) { - SEL methodArgConversionSelector = getMethodArgConversionSelector(methodNameObjc, i); + /** + * Convert objects using RCTCxxConvert to structs. + */ + if ([objCArg isKindOfClass:[NSDictionary class]] && hasMethodArgConversionSelector(methodNameNSString, i)) { + SEL methodArgConversionSelector = getMethodArgConversionSelector(methodNameNSString, i); // Message dispatch logic from old infra (link: https://git.io/fjf3U) RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend; - RCTManagedPointer *box = convert([RCTCxxConvert class], methodArgConversionSelector, v); + RCTManagedPointer *box = convert([RCTCxxConvert class], methodArgConversionSelector, objCArg); void *pointer = box.voidPointer; [inv setArgument:&pointer atIndex:i + 2]; [retainedObjectsForInvocation addObject:box]; - } else { - [inv setArgument:(void *)&v atIndex:i + 2]; + continue; } } + + /** + * Insert converted args unmodified. + */ + [inv setArgument:(void *)&objCArg atIndex:i + 2]; + if (objCArg) { + [retainedObjectsForInvocation addObject:objCArg]; + } } + + /** + * TODO(rsnara): + * If you remove this call, then synchronous calls that return NSDictionary's break. + * Investigate why. + */ [inv retainArguments]; + return inv; } diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm index e6111517b8920f..1a5e001ae5fae9 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm @@ -51,7 +51,7 @@ - (instancetype)initWithRuntime:(jsi::Runtime *)runtime { if (self = [super init]) { _runtime = runtime; - _jsInvoker = std::make_shared(bridge.jsMessageThread); + _jsInvoker = std::make_shared(bridge.reactInstance); _delegate = delegate; _bridge = bridge; diff --git a/ReactCommon/fabric/uimanager/TimeUtils.h b/ReactCommon/utils/TimeUtils.h similarity index 100% rename from ReactCommon/fabric/uimanager/TimeUtils.h rename to ReactCommon/utils/TimeUtils.h diff --git a/ReactCommon/yoga/yoga/Yoga-internal.h b/ReactCommon/yoga/yoga/Yoga-internal.h index f5cd6136afd427..be815921e41f80 100644 --- a/ReactCommon/yoga/yoga/Yoga-internal.h +++ b/ReactCommon/yoga/yoga/Yoga-internal.h @@ -29,6 +29,8 @@ void YGNodeCalculateLayoutWithContext( YGDirection ownerDirection, void* layoutContext); +void YGSetUsedCachedEntries(size_t); + YG_EXTERN_C_END namespace facebook { diff --git a/ReactCommon/yoga/yoga/Yoga.cpp b/ReactCommon/yoga/yoga/Yoga.cpp index 8190399125daaf..431a59a888a03e 100644 --- a/ReactCommon/yoga/yoga/Yoga.cpp +++ b/ReactCommon/yoga/yoga/Yoga.cpp @@ -3089,12 +3089,19 @@ static void YGNodelayoutImpl( const float childHeight = !isMainAxisRow ? childMainSize : childCrossSize; + auto alignContent = node->getStyle().alignContent; + auto crossAxisDoesNotGrow = + alignContent != YGAlignStretch && isNodeFlexWrap; const YGMeasureMode childWidthMeasureMode = - YGFloatIsUndefined(childWidth) ? YGMeasureModeUndefined - : YGMeasureModeExactly; + YGFloatIsUndefined(childWidth) || + (!isMainAxisRow && crossAxisDoesNotGrow) + ? YGMeasureModeUndefined + : YGMeasureModeExactly; const YGMeasureMode childHeightMeasureMode = - YGFloatIsUndefined(childHeight) ? YGMeasureModeUndefined - : YGMeasureModeExactly; + YGFloatIsUndefined(childHeight) || + (isMainAxisRow && crossAxisDoesNotGrow) + ? YGMeasureModeUndefined + : YGMeasureModeExactly; YGLayoutNodeInternal( child, @@ -3148,7 +3155,7 @@ static void YGNodelayoutImpl( // STEP 8: MULTI-LINE CONTENT ALIGNMENT // currentLead stores the size of the cross dim - if (performLayout && (lineCount > 1 || YGIsBaselineLayout(node))) { + if (performLayout && (isNodeFlexWrap || YGIsBaselineLayout(node))) { float crossDimLead = 0; float currentLead = leadingPaddingAndBorderCross; if (!YGFloatIsUndefined(availableInnerCrossDim)) { diff --git a/ReactCommon/yoga/yoga/Yoga.h b/ReactCommon/yoga/yoga/Yoga.h index 7132a934f4ee48..753a2f9f83266e 100644 --- a/ReactCommon/yoga/yoga/Yoga.h +++ b/ReactCommon/yoga/yoga/Yoga.h @@ -411,8 +411,6 @@ WIN_EXPORT float YGRoundValueToPixelGrid( const bool forceCeil, const bool forceFloor); -void YGSetUsedCachedEntries(size_t); - YG_EXTERN_C_END #ifdef __cplusplus diff --git a/build.gradle b/build.gradle index e645fb920aebdd..9198857c2b19ae 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,10 @@ buildscript { allprojects { repositories { mavenLocal() + maven { + url("$rootDir/node_modules/jsc-android/dist") + } + google() jcenter() diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 94336fcae912db8a11d55634156fa011f4686124..457aad0d98108420a977756b7145c93c8910b076 100644 GIT binary patch delta 46476 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

d1ZzV`PJaf;LURr$w8USfDLW+{$cK=5Uq0( z=^nf5=Jw|RukO3MmScw+w^w%A7N4pm}Es< zrajhU6$ZI};S6NOzL7rURYXx(xH;{hg{BdGzdVpU;bJ(tW#3MlqF@7i3jRS2bD^mO zzude5c&NMp#yJTzz$p-cGM9Lzk9ZSVC>!DJ8FFPA=uu+CEB2*-UNpI9saewnEHh|T zUJ(KPya!6}QrhCNJXVAfNr5+je`!!Wmx6~)<{ z;zHrzVvy>5V2_X0K*lLGe!R56V>!e)J5B{j6_bc9X-WGXVI}G8z!hYhj1_y2GoZ6j z_2tk$CL|-gjk&c=+$~}U@X}omFed|nS`KKeSNpm9xPVHiu@VAks$wZ4f5|eA_Rt?9 zLW<|2)GP!R3`j{eN+?K{Mg>O{`LAGdNe9!zo9s=1{)|RnX>`47=;k?9w!K|^-wLEO zd&|%%MWNoqCF46N ze}|fT5$_!ydC&tuj`O4rLgA^j--H3<@m>lx{SI*J37dYYAvMf82hbV{4WYsGCYq;h zVF&ObY8%F)UUc-%q%hDJwstg|v%@t^650!5XfB6arW-o64JCG1&l|U-)n*Ci?U=ph z#mz*R82HRTV#_?M;ItdD|1)oae%ktHa@e{Gw!TJIv`Y-Y0EZIQKCvr2NNXCTM-MY` zY4D~|Sf~&rJm)A<#-EKtoox>;P7@)Tre89dUc{+4snu+*q^Xy8fw=p7z`TMq^OH{^ z(b_=hjaOrm?o#_#aJi;nDxn$|*HEFV-D6h_7Db=Nmzz|S7i@qDMA?^+7lRM&{E}xYP>Jf0=sAu*a-I(pkN^!P1tAl&pkR|r(Q%l5Un>W zLy@F1jfT+~@ksBI!t9HV3<1@@NLGlPRMMKav9p#Zl8j{6edcHtj|t#OZ<#=GOFT8K&!UlzYwP~X6G z{rPqR`??4gPDrf8uR*THZ$Tzgtje+cd|7}e#uS4*LHDN950^cF7%E>XgBY*~_lX1? zyp?c`o1TCOg$+mw3z1@=F%o&<$C%zAhGBj7(uCQysOKL|S0|G0*;u{a4~POtz?S0rWh z&4i*X<}{tq2J>941445_tj30%$_)+LL7kZ;)NqJ-BkA8oY4mA!2-mJ3zdK%vI&Sc3 z{glbuv!WRIQ!K3`xx<1eI<*6d{aUg*Cv zEk*E>HSB-cLZ9ZLZ-wo^(~1CYplBlml4`L9FflOFBVGH==r+fo%tgdKAXNJ@tHXm3 z`pVcx!+Md0bT+z3!KFk6f@uU;@0)Cnpb$BWs!Em@|IRK&Eo7R+NSbBM*K;h=YNQ8l zCKVeN7Umw+m2#}lIj+w3cjT6+G_{l+Uc73$>#QBjW3nv>`cY<-KMw&A_e_ZMQRb4b z*og=!Ij^_rA7mplGP{Z&Yr>pb1lB5sWQeiEQV$j&4uKmGRn&>XqgI+ z8`OtG_ifqp4{2yB*EB>0$5a2!U-ly!*k`{=Xx1+n3t?UA#=>K>F{O*6=$nL>kEgTH zO$GLwXt1FEl|r+0tsew9^+T>vaWZwBugWSP4cF7;w$B00pJSFDTLzppOs-nIoi(Jp zR*jL5wa^l@>VwAl*{RfqM~Z0QQ6cP@JV$qcI~2{NSBB!(HbESc+$v%)EBB|B-z zE%)5RQdIKtVLWgd;@d4gJ9>1Z8eKCm56bHu@vxMQL31X`SI-0BTkuvNGR|w{=9s9D z|Lw@VQ>;W#h)zZXes^=mDae9Vbz@hIXsd-0!8m8=r?-7UnnAucJ-5D%F2X4M$Htr; zo;UC3pli=@daf0uDwl-i+LBC%WwB}25$-{VLv;J?cR1rQMXh0eSwO2%)-Gx(pF_D4 zgGKDU+GpNaBS-~+YBCIo%z(1dZ}!(VI2ncoyWVZ`e`d(v0r4Wwt#5;28GV+51NO1N`f7`CV}Ja5A>hD4QhEW^sl~Y z^ylh(m@+La>APJ$q|!+L60zKle3T)BGM#6o389BR|HTP-gD#R)0X?41<{=;`@C@HX zkyiVyS8B}YA=)lc-kZs!{G>HXGAoPHSZvH9B&2<-p)IiM*JeH2qAt{=zGkqDp+QSE zGQL7e@-R63eQp4~saJhNaZ~TO!3@(zNKW3d>sf1|vwXw--O_w}o&%=POIgFK1=fNG zKXA9?`9cIh!cIFp!bZuwpF;W|$@-MNx*P^t4$%7aBL(Himf#Lfbo1|d9X9g_)!P$e z<`b537n7|bAlCQz#?&C$7*v7MhwLU~On8Cp^|O%T1_8lDTn zb26mz)(MjTa7tV#VbLKTmGz*HjL;$coe@a)fr<$L#y01Io!QnOgF2p@f+!3~Y=?In zi%mCr7-c8EkAMImR3P7!|HT|k#WD$}vhP%Xc?-yob*Q6cedg3jyvfjBCy8BdaYYx? zie+Q}%|0GhARgsw?x5E<<-$ithFx+MpNy7Mi3~9~9hEy#!xA-<$V9U)+vyfgeCyY%#)uM<^y2i&gj* zjQ<&hn}%2R>o&U8X7fmrzNz_|)_{5Go~92lI+YbO)1HbAZ^LsF=~_>y$1UJ53V}r; z4o5)@Lneu@6Xl`ZFPKhT4jkLETb#Wxh?cJ&KCGu4Ah7^oI_vG1$^vm(=`?V zpY{cwixOcSd!S&rOdm}_78H+A1L+p=TI(iv6u0zgG32DouSGa?+?im?!~WHi!=t+_ zg~{1N-QakseM8yXy>QAn?#`n+RO>Sw{f}-O$FY5?s{nHfE5oloF=yy(83NL!nk0j? z&dVvoR5HivIKku?Nrvg>)c0a}AN2t;**;J8t-OXE@rZBW{|k5fxD2l@{K=af{=DP~ zv$4bpJ06ID#-G(hZK0L{I=SQ!;r?Wym-3o(pz&wBT5dpaBt(c=SY%gir924$20^?pQ;H3_zyQ(UAk^)dOHB$+~pD7 z3yd>g;3!F~fR{ud;605%+ZFbOCM_FS*0@e z5Ymo->0&SxF*ZYvT%CM0Ui6}+LkmTk!VK3^OO%nxl#Pgie1WlfQa zh3TcxR-LT%*P<>fSiC+nJRrvoXUf4g!;J%^kBOt7{qgRNm!WFbHxHg-7@9eliKal* z2z;z#YNzauLjqHuCPSrGn!guH%A|Fjrm7kJU2SzFvPXGyGLF&2%b|>_Q8mDIy3WN( z$X}ONF*$?6$6fJ+9oM_C0qY*G@d{YM%P zWZNM#r+p0A5h_%p{DXWW9YrcS_gb1qMMgS$a3a~NEX0sgHkQ|WUjDLObv8_7hg>0R zG&~PABF>Lk+1wT-YWA?Btz*PB1aEMgCpA5(=X;p`iAgt`ev{@XDHyhBhFmky^Gw z@A+Si&$^3R3nHqo6}XHQ>LhgtL`L&w7-#U)=I9RU+UOsFP0}$T0WySMoW4{1D!dCe zLhMEYzg2Yyb}>c2{!5se0GI^>C~{UElkn?|y%Rz^6D0+%-~QnQHpeaNLnpn)Yht5T zAUs1FVU7V$8N86LV8SDEY>Fo#Zb60g!goNIa0FYn6p?S-sYN{J319zJE($S(^Yeb3 zF46>9059Nw%&l)vnob9rh7x>URZu@FJKu1^A>kVWVicKJQ8)d%22+mCtF+797laSm zu2^A`*#Wqn5XLE;X@|LZ5X1C~sfnpC6BAdP_xt5$JRlSMb>C!IGyE_K z5TzO?>GM?mk-r@tSrHba2(10`qX{hKr0$E2m4NVbHMv%+mBm8uY_Q-395MDU?clgt zx93|%A&1d&MDAi3s`W=1P2T+sT6NjP4k*`gElx?eC<(y0Ila+!=Y#d@LDaRIVClHv zio@t(RIp5$Gra?`No*i?^i^@iz4MpPyv}3mLxyPZSM40tSF$E88jIy(N^}WLq%A#h z3!tL(U=0<+gBwBMB-QxDa%{4s;Y8`@8fpZ`U=VF~NagrC=+%_Fb}x)?(dcVTuU5(W z_BJohNy|`;w-0C7(A1xI#-(VHUz^Pp zq6^C22L_Lm$0;2nD#Z^CK$z^{__btaDDIxn+U<3Th&KiUBit zg2!emIvRV15lMttD-jSvmJB~^U0C!0b~HxZdD!si4Oimjh$BoD!OB_qgiFf%*5qL0EtjaI!sTNFlm*uLgpw3N%6<9Qkh$dvJ2I<1OKaYwH%B2nR z)NF!aREaQ3H?mW}fDIQFP@%&zVsb&c=oDrEOSh3_4e{P_3f-?3ygf77#~t|tAIv}e zHZuSovi-l^u3&h0=hx50Oa^qqR4iG7r9TefTyb3#`D+Qaf+%93B{i_A0G8#KSMNr= zUIsB(x~9FI23Z4X=cu4PiWLXZxQtG2%uMqSDw2R=zTlg4zWHRkONfhyibKyJK$?{4 zGk=rk{WQ~-FN=p0@b!KU4dm#CZwL`Vb`lqd&`T&L8yrZ2p{@nH2UX?ZMT|O*O$7{i zs)fN=X0Q5f7qDLxsre#21hm9{$JnQKOm4TKf70B|gjtIbTu_#`> zWXZC)F@{y-Bi4x7mPGUruaEhJ5{#9wb+e2R?Oy&TC1Xu5R^?oiv!6m=&30+5%~_R3 zbrf-GnWeEUS0h-hIWj(H9A14EZ-EBz*{08OVNkzuH=47-D6^?iSr!#6TO%SDUt1M@ zQXpWBwm51}O2VE-Eimmq4=~r>AO_ z9ZzI?gxwzKt=2#x&<^2yb8-GHN7#zw8ju_WGVWCE_lynKB%y3J$WED|6$uZBRI^-f zUKE=eJ|t(f>Q3soasSRaa)Nw$<^_|Asi4h=91nenMZABMDM1P zpwqhJnqS6({Bhle_bFA=@~YpPfhJ%(xHDXwgG1!BGQ4_e&Z13oZNp7trw!GwEDm2d zFN{}~na7G+*@Mxpdd0oEPsA!W$691Y0Z~AQMK38+cFNfG;*w!g(&+@O+pL+!BESrJ z>eq}4c!0x|ybG}@Zwa27)2(z`V0vUhCOJw@rc2ftwcT_88WcSXx#kYF5w5S<$op}> zA+PcJzDaEE*mV#JuP6m&Frb5k-HX%kcoi;tZ)huR=GNr$5#>IBb-Cj<{JzOF-fwVu zVo`J*Uufa_D7E-LiaQ0n?k943jV)fZ%J4 zw}k0l?-v}u3TDDjKlNp=J+VMnf)vCCm?6k&?#)4d3NOXCKgE(?e{V4axzN2@BkUwP z;xOZM2)5nNuq9`Bpk?0vqqPf<><4qZcMt*V`PGKN3BsBaMP^4r z@EM^*61-v(|B#DHon^RwKYj*ahYTQ~L-`43TH`eHtA&YPoeV!co})#tW+Jg)2|fWk zS|rMG`EeZKfDKyxLd&Fcn!UA$#G7dmw18D;_DZH^z#Jx-l`)OWDRLKWfda#CJ5>(V z;fPodjF@>TJ?JM?W%i*N{cqd5fwEjf=BLeF|I`Z6|EOZxMG`=vlJ4zHbnENLit46e#p2s5v%R#(A)$NIR|aO$-NzC<(d?y-=)?7{?d2 z1)C6@Nc8gx0bZ|OGusIsVi)e26wUt8H!eXcN13Gcvs5UEM_7c3GJJ_XC4|nz>v1YR z)d&x8L8uBjkh1g7g*I-4FeGT)=3s5dPYov?bC+&o##Xv7+VhWvGr0>%2#L=An_+U? zLvs%2{MeAoNIugtv5&jbjiW5R`USv%{u2z~Ol{jbKwZ_^HW~af@_O7!hYu;N((G|t z=L%PBRtaxrDXaBHOfONP%|g`uQiikOq3%rJZPVE+Atw5*CLrAeo?`m*aAm1x$GV8J zA;9lyq-V`GWZ`xxwzR<*`1{)l;Ct2aA`HK8K_na^B7c)m zy6D;nKxX#z!7nwRP4C^C!9Zv90VAV^E;4Pi@)2Z@0&rAU8zST7@N(cl9M-zzsc`M6 zl|Nvyjeh`3ala-8lFpfkiwrWaHE~KdbT@tkO}lJu9+||+@P10IPfIPef`349=M?el z>dnPtu%s!KR-d%NWU?=2th$@p!@$<+cRot9k|E_h+?zAJ0uWt(`(86*M>HnMja1%U zgTC%}hmw6$hu z140+|3HpDV6Zi=uBshYhAvkbnH} zAa}A`vVA>qVH2;tGJyAaIQ8sv{p{R5-TuC~4EV(Im6Z;e0d0&-{v-%QNPveU0E%J& z#N5X37>A-|rGQ8SYSRi^J}k6wSWC?a7g0{g8xf}_X(i^&3~3ABY%0h{1iA*b z&KdDf21hx8qRUw1z?ZYEsTiAQ6?fJ;WNPf~F)){tA_8=Il56YYGNCNa?D={VFu0dV zuvvB9@-@i=o$66Je4>dbE{gteeqMG z@2}`YYC5(&P1Z9nW6u_1$(DYqVX*09rkM?;6~B+lo%INFTcxmt8WXmkCzfOXV#CT` z$TAG~q}DyqWO)4xy=>0CZ5xVVfM8>03F}oX1u#xj#8NABF30*`F3*I$p`zcL;|Ry% ziiUJaEfms8dYnS(jq`Lo?gM{JJu(vXIA)*f1^Y4(x8iS_dcM2h)YVfxIx|{D_HjBY zjtdh~{&ZL!9J11WgN5d{JY-N%6j5oq$7}IBI^FhvyF}bmftOjrx-{4Wqx|(E816O|p-v`Xf4qh|qsn#uL!<@V= z8@atwJxae5vKUVZMOkhEQfJhf0qd;m6<^NaNZs}x4y&!;5CS&J%7!)h+D)hk8bA=H#Ajv!1A|b2O9m z*`$z!HqH9|uRwJ2+Mpy-iJfMb2jhA_GLmnnBogi>HX@EpI>1u< zZvecRy0kNNg{GQjesWf2osL-j-{A%`Nwg{pzu-RWnQMZDOmmt-HMsJHa<;=XPEVqH z=x}g_fBK3^wg$B~1t!Fq1gffZlOe6uh=S#}FWgrRnCVza%(86>5{}eN3T7XYZJj~q z&u!+1n@W1uz?pGefM~R{9tRfGd8>At&mq{eAs*cxW__j^yQ#HDv~8s=)gV>(SXQKzn9Fo`1oe!#%I|H!Op?yMTEsi7M@ zXQn!f{xh9Ygd~RDpVED-RTtCgD#ax0y^sUKuN{K6ZxaQDn_GK|@L!?RozNQ2&w5JV zWV?If@IC{~!QBW57}?(ir-+=8KQ&R8O1f+-dbs%(st(eFe=FUaF81A(Zs0j z%_YWgp-Qm>=5=4*6-cHtB)t69FEgD2jm12U0pv}9+!U*ISvw#R%Vqcu5QObD??9x7 zN;2@Zh&8n|CSaJ zVlG7i9@;Xyq-ZN>>{%|o=}z)XM{r{>+UVXiOf6}PDhPHFJ;g07Ya5(FZA{xA%ScLm zLUE&Wo3TZfp>+bnYi>sw-|*;ToQ(-Ku=bEt1IA;+auj9Ry9&1)Lj!0Jf4_f|4y3S? zeg#SGZNCV51p&gTs=E6Q5}GNXT@^AT?8CSLuHD(Cs$O_5yUHKvpFXG+MH6k|IEXH~ zB58p?8*tXjW3_jRph&m&L&o!J#Ia-T9HlH}a*B*F_dYseI}g)qCiBOMvm&$QU!l-2 zAE3pD0=m>0pWeWT_X(PP(LK;(S`NQT%JuQqv{Dik3tuF=&i1I?Nib5L0>eq9k@lSMFQEOhw)x;hJ}IGSef1A!pH zT^4tDw*bN2-GT>qXVJxVad!(&g1fuB2KQhI5Fq5+H}`(`d3oj>&Y83Px_hOjr>Cpx zU!MCk{rQxrWJ~R2ZSSAX0>&^H<5tj81lYFWH(f4?4^0BUPzorJL<%hCPF4@H4_}qm zM&94iKj~xqQc+9e=-+oX!2h9HM$J1poVu1YOUc7oN$?hwpp~}%)-9WZEuQ@i4ikP$ zg#6p@CPHzNpkFVk5vGq+zb%02!rpHPXndMOOi%Iq5eakZHh#m+{kCyjEk#lv%k|QC z>&B@rr56Gzuv(cPU!)Y@6AH_IBBZj3C`X(fWwTM1iJk#6Oy8l8;ssE&Nqh>O5Ia=8 z+317vMaz-RE@+(X0a(cfv(hsaZS%*3%?zSf9=vOh@VC9?YC~(Jc@#z#U#h(HY?nj3$VETC+*aqljY3appEMmgH`I-jH!j%H{!VB^Q zT@;!MMm^#P3{X^YjL}q+_s;%Y%%=!G1;aj_?#1-5^^}OIx8RDCBw27-uaK-v6_{rd z<9P7c%{g`?)$DK)~%e3#-kW3ONdQ(L2`2j zI{7e1DrVRiJeM_mVk$<=48r0N;lK>o3XF8+5oTIqs=e-@&>gNU;6_af)4KUcDnONW z0j`#NB6zkANx9W&a_y9K0#jna*QT{KgX9?1;3+@h*kZh?Ll7D5w)e{9*EN!a`C^4? zgY3zQHTUOIuc6NFcP}m75}tBfAD=RCQb_)LSAhGd5QSq?^{I=~r%A0@DRZjsiN%gY?Si`K|8` zl~>e3qri&l;C5uQUf0|#`)TJ17qqr)L_)t)!rDcWj%(Q3E2;74adXYKinfJ{oeegH z<;ls(8DW*Jd*&6;(>R3s-?#RfkIoeZ-ZZ=QcHYl-KC|B#7a)%^d6pzhdgs|!{N69Ds zS;>a#*qaBlFg=bD)_LzeP;w`uui*04sF@PY^P}Trw%E+C@ zKSSllu}4iPN<8!|hSTHzQ?B2IkA4LF{`u=&pky+&IU8j`UiTv#<42`nD9t_?ISO1! zCRrI*!B|=fEj~ZZ0d)*piX{Rc9tGg~4_<;KIE#2OIzjli{neE=M1s|aT{~T~_sloQ zS~+bF7bi8N7bEl!mkROZ?Y{e2afTk5(rurg;>O9e?be;fQ@?w^B~Pu4TUQ~f2{0k} zw8^2_$K(DKSms^bp1Y%Dd@zGQ;A5g%s0~@l^vN)j6lg@uBmThb*YNA1_Dcj#t#NrT znEX@`UDA|qCi*n#sn}i+W(zxtw}$I^j~3fj>+X{rg}@9oyi{C#olCXF%BHXlJ2|7z zN!0Yc*=x4)&zj;mFY{0365ic+^|A|UlQymHdnKcSqHBo(x$5I1AKwqX&5g{@(Cai^ z?iX*Gp0>V+o65twHWuBJ$3$HaG+8|!*3V}6QoJQMLuSRN*{d9K z-a{_=L-M;D71txqv_=2SI%;n?SZ|r_nGMlD`VCx(J@O||t)XvfLYQf>aJR*5u6P^O zq)1Aq%0<(b|2M7@SB_dg;n}jq6IBMcdW++>BKatm_t1zUF6JmnB}wK~Ky^+l89yrO zAV{?`C)y{F>D&Q&AjSsys8@91H?$9_6s;V-QTzO%&F9D!%EzsNE$Zh^)$fjET-!Yz z6!^m2Tr#RjWhq|b$nV~G-*Jw#0XU>}TRrgZgLA6BiP#8ngu|!juLu#_C2^6rE4KUM zUpS;_Mc`#*tiJn!a{@&ArMI6+Y1wD4`?61w#GMpbE>h*|FD21)proEYM5(8|>l`>; zgf0WuEUTnC$w8;Kz<2UvymVB3Kxow_X&nC#%IleX-%=vI3?j8q^jB)(KOWQn%-o90 z7{HXIZ8;(_?4j;P-Nv?3!arP#O-tp_McB)7j&hKU`;xHyFd3GPEUJEihK!We$Oj~U zKrJlmBb0yn9A{QwL>N~)-gZXg_Alb8?BBnScJKlz7sRpaF#3k20w{yRRa{mXp8$!P7EeeunN>$f3B)3<|Af}uqx_wVUMjb?T4 zTj`9s6EL>c>{`^|q_GMft>KHmL@pBbpKM=^jj#nD%?cPDSvb1<(RVBqH!6e!6K#f025C_-GozKCTbFv=n z2UU#dFVf?x19@bGwhA!_G<0wO=+~sVcy-!-M=grq#d=_+f{CON3A`+J_RvSS1B-*F zzE4c~-A+z?|NVITn>LVN=tNRfA!p!A99NV$a|v6~9w8G-qW~9n7~MPev_AVC!P=b~ zkS{VK$r66UIB@-$vRUZ+a@AKpx8G|p63R0Xn>}Tdg=g*8kcp*hvvyNW$4Z4!Y|#{7 z$HU*sqO9vA)bXM8C_*7N<-?QDE(rz(H#|s~f`?AoJl}xONBm ziFM0#LR83MJqR9|by~yIGr(e%=wQAI-GclO-^D7@?0B!~;)809kf{7l$}@laY`v2| z^9rKeZ3DzcDUNLLe&{`Jh`10JTOcw!YYH-xNuudKaHCAHM%!KdO?;jCH-RC1 ze17BJbRcLLxy1F_a^$(+cyq(!{2}>d#J12E?WE>N^64ktp(&u#b&nZTfC^^rC}W%^ zLK&{b+*CrfFV&h{n7elPz@{|Ks;I18;GxE9lZL}w%1v({k{>O%_SObb4}L;|YU0Gd z+|rrUP8ozc%7$a|!zVr1DD{121wPG_7bh}h3lMoEN2Y&YiAv2{dEUAtvKW;qb35M> z41fG9xSn)|2Q?A%6YDAgGyF{V#~;V}mkYftgS1p-Bte5*qy5&qO!s-;M2 zXueBE2TFc+Qpkj7^N9a!OF=-Jkfc}~Q~oT)=#_F)3A38bLL$B@NF2A#v0>K+;Qi45 z;j5PbapE9-OlgUQsV8PM1|xOghfVmC*dSPmv%r| z%1X?+AsJ%RUU2_^eTljP0On{+|7QpL#nNLv%;FtR0fXNAsu?wNda}@DbQ!E98cl^y zGCz2d6?F|fM!%;%)b@;Kr5Sa+FN~aplv8EzTCJhOt-hY|7Cp?m@49!l?Dq5hgV95w z<@Uo&Dx3um#T#PH8WW=ji6HM*#ifP1{DOf5gJwg)4HK1{FvGWBfx8#_$P(P^Uvrz{ z?wJ0xuDH^18r%~0t>Sbs@9|ZD;5~X0R{2rwr>n!eE_fKXX6vsckAERcExI#z??#Z- zAv9~#bdNV$4Xk0Oo&s`XiIkj&azWX6P&MC_hUibSH zcBR`;bY%+F6HCv*9grdt9pDup!*_Qh{eD$L7&}&5dB9odB4tR>H(ZO7$ z<*2TtAvwF-$YkXAyUc=a2VO%Ah+psw_;DV|e)ySw%jf=_NCUD^V(vDzUw8SUpxy56 zJrWp{97N4z>orlg3O193)mlVh;;oZ-8aVC3z!FAp*eecF1)Af}b>CX*{GNX2R-z-# z1WG(x_?UkBTX}}snW>q8djQzzfEEW|X>r~-*4ti(Ej0?b62mEH*jltvv! zua?c&N1EFwB8GA>31tG`41|kH6bRL63WXPT1(kM%RoYGbCaf-BRX;8|CBW>+5e9#! z$Ncp4W&!UU1{g_E0Jdi+{17{Q(g>Z~L#9kI&aH4OvfmnqN!o*{EE$?AsAwiW*B-Wx zG0MC_9*@;Z9kau-I6)EayI-*%q(mF5c!%@$tjfnHdWax?SeLp(oQGTg8P+~7;ON-) zk0e+lTwHGm?3~EmAcUK2$ZB2sre!~`$6sN#D;OR_geyEeF=t4z!7TYw8oGa9QO2t+ zzLY*cqWqrn!t~dVDkP!Kz|rRW&f;nNx7d zziGmO;ZiI}<^EH{J6t6*g7r-rB@VHFOvii7C8AEwYgxXo1hXU;BiLDCGzHlY+HQFhHXLOayFh14NH`*SLU>RI`jgL=ko+Zm0{sXPe?y)8 ziVf4D_tLDiy`e3O znkVdp*19hMC{r7zANX_vE<)FKcWSuglB-Isro{ z!nGu)ojpZn_u3E-*Ir^f|hGP0l<#&0F+@)rd*lB7$4QW0N zkg_e#y4*veeTq$4BUf4WRp~t1c6*dO>&z&u2mii#J5B1KY(i|s7UDS({c9`w#bm&7 zH7r$hVb3JlKDPG==+|Kpbg~GcX=t$Z33GD{NY&!Hb?sE@&TbqMiZo-a53DT3QPL^z zM0-+*jNkD(Y#g)dcqo^OhpLdXY`AT&H&35*l{wN}0=xa;O^}+o2ND)%K=nL&v5lkk z8Q(%=W7SHFS_lWZp}(HXPDkv5-ai2;Pf}4lQ{KQlzFFtlpq5V^m(n%yTXl|ZEo6L` znmDd+=`c#4Qf#QhV_y_SR1-3d7+S24EyE-VW;LHm@?dX1Z7nq{F1mY$)-pL}Yn)au z-=mc=-}<8HDAprGY+>w5lXO%P?lK7*ac*6)MTYF28mvYpnqMPRzo?YS(lZ)^!ROO{97t`rWW z3{z@bEr7%8xWVatH%iIB=O+y$7CyFcRhP!w6=+MGDJW1MMt@tFKF=PnuR4>J`}?Sj zoNW<%9cp2dEY)7p{Be;7PtJtOea;k_#}^icxIv|*{h-xFQ4eSBp4;a$N_kxJ0GU7B z`msELcrd*9XW00WdoGAX9-WYg3eH-JH6G1&8pe|fM^QV1MMFD+)sP3Uz+pZpm}-?u zcwFl>8$V zoagzz@jbr*MeZv3R}b_xphR-EPfs~}^h2b__2-%ruap#;xsaVe+Z=2ve~j@M%i%ln zCN%e-y~EQQuAdACo%m@g1lw+vxi*9=^wmPC^9zsRG$V1Y>vyT=K#OIcXi zb+hv(k!!-gioTvN&GoVta4{h@Cg>rNS&%?ZNAO|-DI~GS&eYX4N7u&0U@-ATE;EDI z$EV3+wL#d#Zs*NeDtlAifw~A3fjm!YeInX~j!%*l$#g^pMiZZSOCLrYdYlOsOpr-9 zY;ddjyP#FYmOjjp@0cr-4W_Fh!htB{c2L8^0l&AhW+#pbBA-<}l|41xHQg7N7nN&? zeeci%b%8e;Bm&p(~|9U_XSuWUnN9_He z)^7?4BtP;4^v?R753?@IP!hr#V*=)bi1k)8xZu7w_j*Wk$*= zM_(rU39*7#zLnE}-4|r)Y06I18P&NmDo449Z!Oy5^!OE3r)G*j41e?ylXG9FNFjuT z`WOtmP+!?+?5835NHQ$Sr5IN(vT$m;wy`wsBXeg~k7oN-r4n{$SBx5PXO}fq{T%lz zxd*~@Gih|=*)-@#bHa^d0uE$Oy<_M7CR` z4FyM9D}ddDN5_Mm8$~dADX+#)j+sep8Kt@FR8@D%t&8-y`83F`5dVa=R5my!`^ z6cm?Shh3HHn#9=FEFNLRR3+$0j+X8|8CGrT6*9ZqBXU|!4{_Vz#(cG$LK@tSEd6f3 zU@OfwCkHJ**H?~>a+1?%h&oE(?Q@8 zxBJ;rwaz*nLe(j<5oXnQrO>`b_QuT(%}$#1e67S^!K$kIt0ls+1VwY@M8Sr|qqaz2 zrGq2C<1-s1wzl=W5THt}eYS_b)s{57SiGn=v0bThKn$-WUe^kvD6Kw!mQ3asz((^z2A)dT+Gk%F+!Hn<7@ip%qm5?ev*{t^sQPHnxkO9+RAm6$z6h!5)n4=D0!EY zvO30|L_s)&EHj4dKn3q7HPwt=;ru9(``r`@1ESfcf+@C}1>>a)g~QuZ+#;~MrbJen z%}UEe;PHvv+pw%*LvaJRD+DCl)hX;c9qv|@x=$4wV(j!qSiV16v94N-p>`e0)f{sloZvY_NaBRE8LLJb zOzCP}pCl<9pzd|z<^7qN_(9>fk?Fl;Mgsq2rk26%_s}RwZIleB4l)lNAYTssZq+_y zsboo~c+g?`80!B$?i)+*f}Loa)Xf*B5T*A#+mpz=&z=XloIg5u`pQNnGhw+`r8>UH zfbgB($<4tsM}zt;qj8`d*e_tzj0(F1F5F=CJFCIs*ju{M*9#fum%W^kSruyi&DHJI zio{c)VnobewH7?=PlKmBfI;xEEOhF*y5|mwdVo?@W5E*jj0$*ty$LVBwd@Q|xOsVy zRFIm{EPuNts?(HawiR-?tBP(SHoJ1<6)Rbmf7Y4e?021awv=Hbdv9ec)m)G_qV$nB z0*O#Is-(7iFFuq9F;5{53AV)2FoV8C*?jYDQC1PcK1WUJ{lZ?X2wZCoaVrNk8%!oF z$J)A=wFq*rI>_jF;A1A1lN-9QV^BX`Ax9$ivz0Z>C4{=YV+h}1@4V6=G8A8_uG(0>?sohP@Dw-*m5$RBV>u%s~}5K~9*m7osxRdPslk!v#)Iug317$|yuSz2t+8?~|gH9XY z1?;z;kah4$!lCy9w879!a6?SZ@U-!Euz|dJ+*4PlwWl~L7LQ}f-Ee}-yC!%(To)Hb zh&I{wQe`p+v_b7j8|zlb*|2Hn4g=BCQrKCI$Cv1$y*E!+1L8%)`C@Z$QZYoKbc6ue z%$p5Z1Ic~&jU>8Q@2y?(f_M+$K^u94xiGd8Bq}?H866~d8l`}r35{HC=tJCFrmU!#D>?`k1n$55WA5|;$-Gg z6vBw1j`9Ry5)X1RSZNSE;MOefmw|*B;%#m?Q&3fVOHJjN;-vb72bpJODDb0HW^3Y1 zLJtSTnFpDPh3auZI@#A1cMWaToRwW1CKvt7`_2+~Kt@TLR68TTUxzj0r+vx3gQW}Q zE@!C?Y7)5#v?mMXW|7qQN#v0rKapu7wSh>Zx&BDcd(S45rDb3aGJL4=l~f+55EJna zb5aKpE71)&9OENz4nWVvHzF4clYEvjJwlO= zxUhKI(y!8l^bNahW88Sum72O7Cjdi&0%uskkega&`$EW?1o(8(d8s|CRR5bqA}=4I2p>q1>GPc0QYq4~sZf_opkmx?TToZwG=|3m ziPDNhWmGr`e6}uTCzK^I36jjKedP5EW2=SVZNgl4REiZ zeKEiTho8W1do&JnboZ0ipNoD5L`kFNR2Jw#Vzkf8YNa<`u#zn%UsB{wOl& z^_aN4f!5i2ZnOV`+}H_f2s+04czEQn0f|3QHTJ-t?%TUQtg9cUUrd@iYNWz~yZh4h z94&g*qwCub>I0kW%JKF*itTRmwRKa z-oSz|9mP*BwM(%8(b8KwCEp+>s>+}Ajc>X{CPoU{hv;=htfb`yI}S!_7MbKG3py$r zNU5*qLCJrtAIb@pF7JpV*3I`ZCEsW6+g9A&gz4;lgkXWev=)}F}$dG z;4GhJP`FDM=VIzEdfDfm3%=}%FqN_-)zmb0q0NWS$zz->B~IFHc-mQ7uqxHAI7&uD zmC>s9-U%s~U@Ayroz|f;I*Y9TMELloX%FT{K7sZ}%6StkGkZ>wAzYwP?7Qpbl_=vq ziVpd)`#O}oBDmpM21=7LuP>wY)@dKzWatGB6ztMyLM^a7WM~WL!dO~i@WvG*MvVpz zR9^z#3LfzP7#VNT*c#y7moT6Q_Hf@2)PP0zeE2~`t}f}R40oi1)~5&kXilQ*?Q8^) z%npEXjOtRh)FiTRN*i#+jnFeI82%dFEz*oV;6t3yr>~QGKV7$ltTdvu=}*XA{En|4 zZC1-x#`#H4_jS+rMm{&tXZQmyhT=1vvGys2g7esT%f_A_=(#5c%|_jt%L9gH_p)^Z zgU#rc6Uufzp*+Q##DUlc8?9nQgE8ac3UjMLhSKG;rc448*3Ur9vO@qUzW)nTbI>Vb zT;6~>DqwOaMjWYn;uhIu{LZRirds2ri3@uUz!}O|Lt&l}AxO8YgomNc&XZd&8Ijgx zo4sCjbqHn2oy@C6QhB|< z?k#ytts7gp6gf~`l67js^Esp;NGG>X9p?8A?V;js#iY4$e+ysyQAf~GSE5zvj~vBG z+JYJW$=iozd1OefCv}}R0(fSopPX2aS4_-Gxdp=W(**7y^&S2xl^-_83s>2oqgABM zYq(Nk)y5|@Gl~q<9k_Ms)@difzPjg1QrE0;G!qi%=2W1UPo+J|9<9>ARF%%x6G2V((ZkyG-5 z@rLe1I|3duJZ8ioxc677@i1QaF%U9c&{QNAk_hY8E~gOtWZsaD)jgRn1o7Jw zd>!8+HIR+QM%0dzhrJNl=H&^f8|z4I)APg*twMn(U%><}ysrBJL!0D7tBwT5e`1cV zpmT?Zv-u}X_(6HmamMKK#>p@pRE^gSOudW^$hC5U0rY~`nH!+x1L_X)+FeEO952tZ zlod*PU;Yc~G3R0Fh`DJFcu%$Vygj^_Ei^V1Ru}Gg-<;qyrZ(>4)p5X;GikrLOl-SB zL>e$MBok@L+{o#Ne8-T=6d3QC*DFUCJ)!KH@#g4b>P9rLj(~Uh^{p3J_vmYb)InDD zXsvqYGc)_-*3KoL)%daU=!371y2UaIzBk~jl1FEWX-$erx2ax`R|m}FH#ijTr3d@y zV+34G3A87L!0TY9>z4(6HfsLw|f%?6uQVtOoU5|Dmnn z01MGf0uCY~q3AK=pjxMc$_5cTx!NqY&5Eo~on_}@MsWQ$w;&{UJV(`{4aujt?*4~X zC#e2PY`&QQ(XHKFNA+1x1lH~Y=HJQ#jh>NK-TjzbP4d^QU;g~Y&eg0Ve3qW`PB8)& z`!9>vr^I{?XPVHH>$+zWOij2={UJD72+Zred4Tnb`}hT9$qzH>0+Z8pCx2?X&GARh zZ#x?3dL=y2sGiHGv8Cc{t?Ce4HsU%KvZAZHpP_m7_5J2qvT{&>ojps(h^(;VycjH+ z2~ji$94qta&!zL9w5AM>Uw;<*$4kru6BS*n!#xLMf0x^6E#5$f5;8EY$af3SXqLVo zFgrP@koh?6tHqsc>)j2K6uu%K+NIp0A#GIoBP@K6Xq&G*3liORsWFTdWgi!@iUW%6 z|4Q=q<#R_en>F2JFsg~GyOG&s2v=(!BR|q=O|h!DVfGHU9{$$M5=U8X8K@)zjHxj0 z@x_+Z;^L$#!+p5Ed_X(0tg);`RchtCpRH={&Hf^|I&OC~W&Wc4oc7LD>8`&o=U2C( zXR7J>#K=?L0+vnWR~T3%LEriuydW!5p;l$nPo@uK9zt-gSNau!(l;&CEW(}Q^i%g01c4h6dn0AA++gI+r+-^K}Ii}*Rfc#I4 zE5ML5%2nghm#p*JoBgXVg3c%>wH83)6D=%yqw9m;|Jq|Bj6M(Lf9U7u%R;Y@s(paJ^ zO2leTW>K+TEwHm$<$Z&gfSqLGqSMv8Be?xAl$W2KoqptRMJ2z~!Y+L@v2j1;pLf4? z#KQSF+$s7(&;$L<66}%AaD(w+0$d`GjAp1p2%ah=V`+-|2wv>u>6yQW~Nc+e0w>KixhDBN8{X!aKf#`TjLw8e1iL*8rLeQ9Mg!n@}U z9XuMGVqMsaZlOvw#)LA~x=vy1A{4{x(u%L$Q#8FrasE}>(owOik3=}x0nA}{4Wne_ zY)Tm|smSQaXFYo(DF`M`pS$fxI<@3D$#%eFmgpTlEu);`_?lQDq}`=d*-lDSTTGQ(hqM019umW|JpC^dk^7WsaP z0U;)fUczhosx(^u2>FcuT0zWh&S0jou`LC|R$KSTX^JvbY%&AsOUM2MqLi3ld-9z2 z{AQ!)EeY#cU}rkL*UHkjc`a`Fg(k21{QPQxg%m&fjee3l%6m^`6=1j7&=Ov*S&l3n z&bHPyF)Df->Q_RMSR={nii?W-eM2LdW)#!sX1g z14SkF=G{{zzx|3duVS)wg%5+3oM)^{G?d42Me>L2#YRc9caOR?y%tB_#250RqGzVQ ze{TMW_AO>R>j~6;wuJG{pI;t{f#h7rm=Vn0*>hh#h0PP9?jZpYH_1*~jC9PSZI6dv zGT{Ng2)E+j5Tfn`2E+5@#_6VZQ6MZ`LHnun>wD?|~zTKhK$vyGug{fMP5lsmf*Oc)TS+2zob zj)&wonhrAlmIeUg2@{sGRhuFoaXlkxYZ}b6#WxZL#BMu%(N=)JlhK<_?GW(s?sf8B zIeEZ7J7M@S;8IE~PjE!w|EYTf$LzTpbkAmJ=DZ6adlR}arLrRr1@W8R#Z#M^%_aM zBeK`S_r8AnN~nw`exztCOFNV=-7oTci;Un^!;fkb zb_5vx4okW>%I^f)I(fXqR#k+rTJjp@_;Tf9EgG@2=Z5>AIqZ+ZU7=KG&s5lYQqV5< zGMSBxNcSISfTi5}9UU*|r{1D5Vq+449H^oipSEN+KuIlcwx8`E94X$j%dop)or3U7 zb)?x{uzvd6&R{E0FV{)*;$#=kr;3dvqjk1Hoe>!wCl07%Irj)mhLb zKM2DR7sT}t>itgFH~h1F`Y6b2p3r|I8a!l6t6lxv1#~Q0n3}%MLH;H7g0&Jxa1M$U z_QFBi;U2gWkR{xSlXylmErO2!h)u3X++*gIO^5Y?xF6%~gcExlEfhWRAp6{{=@{W< z+#z2HR`NZ0ozm1ZLlOgx#>-`ou-j*AjoE=k;LLAh%o7#CQQ>UbjwGGzomH4>)-~Km zthJ-xh(M{&TSHSBzXH+EO7O8i>O=tF54$5$ihDMBj5-0z9u30%KaTLX_P2M0dx`qP z=a@hDR`isEQwjpVY25hB$gphpZaR`D#e8oI+jJd0ChjqS3Z*km;poEvlQ5m09bmu1TNq<6&GMPx`~w3D3>QD}SeLxeQPE>rFWMes7; zR%DGh!D5S+;TMn3OA@x^ej2K@MKS$du^8}|P>vy!p#DW+zxo?NgCAAOZMYy8JYhRN z5nZyV{_VhhEQBbdwGIB&s42v@D6u+WIk(o zXq({|-NYcXv_$apR{K`!Wa18{C-2pzN&j$sSSO=L->V!+Y+{<0?8Hb?Oj1V$QB?&g)qJG&4|HQ_pD8@{A}16? zaRVOV?3$Yh3v%;^q^3cEzl{OF_+yNSLXFq6!jKmWhCNOBC5(2sp_Ah-%!1?9)Nw81SK>82+mtC@3Y$|ApRyzfB6lM6-YkcyYm~ ztf-JTgAQDYKu@%g0C9F*+BpT@T46TSbmNHrpXC>odE!Y=fq$XT>hR4&5;6LDW<(@Ugi21 zlAQ+tUdiOVf^NLQLGuEDe}riM{U$bjAiYpRWN8S&+Y9J`|5MO_{JyWPD>UFQ=mEt1 z``iAbRR9Gg^lC`YAaKEg65v&m@+)92>~Bvji`;-$Nu{r#kchuf;UW{@|FUQxR{wga z-T1!{$`T{sRmQ@ro_3NT5IrP$fd=p@{`(blnDQ4oULpg$ilBW3-K9ey0dUtMI^dt6 z%zuARx0!!?!p}wqcQ2y@UPZdS>g_V;f51FkFazXbc@C)Zo(J#AJKmIHKjyB~;HF$YUxr zlX~90b06J)2Q7?#_J(*m3g_pxAGGg`@8CHp)qA+vCr8y9tWB&BeyqIrhKPU<$R3FQ zPm*9(;VTK9k?iORG-9`7lwS~sn%(AGKh0IYD^lfD!g(i9ds@( zXn?lFQTauN%}16p8;KDO`YhcfxPOgnEO5iA3#&`GHY%DiCM>49nabI&7;Z^t>fi$IcsS581)UFTwrSMM&La<}RKEo_hm|Ocg3?m7c7wC;1~-n%p%Ee^I^HmIsJ$Xl8X#P; zN>wi;We0)274M|lPrs#c8>;oZCy(tw?TLFs<%xNN%wf|Uth*X)0G7jb+!dvV))j~N z^EVX&vF(9oe^~qA+?STG`y0-3q^J5VvOuYfk6f{9(-g!vj6i>WnsW@vAKE{ybPFG9 z{g$tK{g|(bAe~+@K3*}D5)7!csX%lcns1Clxgvq2K{-~d#=+&V8CM1it+#+py1QCg zY=hU5!M-x4?$QG`zn>j(C3Y@XoicXwapWx${*Eqz0@W+VZp2cLCDy;ZwnXEPr1!L6 zJErRTjsBZm^+I3s_))8dh3n%Z7c(n#i}b11jaSx{ajS&;ByFs1lN^lf27u{b??K`~`E%3(<@=1iTI9(iAuGrBC-{mvk~v3`YA&(yVAR>1te*WX9{_h_Vptuk_W5FvQglXGW2rx=gAan!>`9p)VsW zI~;a~`tBh=v;P_$n)$1|hwXLMH@AG(0PF+yL>mC=-c#12aiT zG7X3PE9pNaOl^mm_trvQ)#20#A+kOxl!$C0HD~*_)%F*26Fj$g-@!*arR#HgJK~lzo zxdl{E=bW=Y_+-mNW`Q8j*lL93c1O3_q`we}k3sx}Mvc*fEPu@9xW1EEV#hoa9#s`q z;24$3-Y1rG5E7!a* z1H!*gc~L&j=LKIHXmKxI+W&+Fu#0^=+R&bzmn?xxD}(YY)N8&h{zK~DIW0yd^5Tp| zH4^+Il{Ek@V-K6d7h!#mRQi`ooL9QIO<@x|jP69}Z{Q8_0E-3I<{hJ#ak?AM=_8;d z3kHq`0`l|ce?wOoM1!DgxadE$oCH9!gX+*GwL%gCUQ<4N>2C%%qo{p2&|s$F(0A)Q zdK51qslj(E91^+-jIvIc!Q(S#8}9?3i5_gLw$=dSi{{o61exL2}ZW9G#6kxK52);7e;r%MNKi>oUzsyko z54BZivmQiH5D+175D>N`VFipNUnnxb0Yw;%A1ry;+}cUKenrOKR==`wsm_Xt0^HO@ zL@vn&b+L?E&S=^_3@#|y(Ci?oXH5bX_$@f$3000Fqq7~>;qjOIWv0W$68QK24A$qz z*P0;F9~u-Gl7jYYLu*(E=8~EY=Or};eybHZ%+h#q06+Aoi3sM=CA3|0%UlXrGE-%u zTMClu`ip-B`xJT;hURqH*gvRW3)(~oh585I6?#+S-cjlLyfxf)Zcm2>@>CrSS{>G_ zjW%4hR(APbHvWu!82o0WS>bGX4+l( zqk55^y6Ya&=r}73EwFuPxJR~1WDcu~Apa7;y@GE*{#-X!E74%A5$sYR(&oJD}UfVQ`A`X-;q6)ZQinYBDZ5TR3ohNal= zC|WbrF=9c5T9qZEx0~=@7u3;om!w*^=p{`(uD~y^%B`{p=Qm|;P}X)1hL>bJyXuhX zl|)?R5uNC^;LE}0W8!a0NQNZ0aoM79*?OlGy2gs#|0|mRqnS(S6T21!5RkZ}S!gc6 zi5^P}ZcU`DAIr`Oi;N+FEEq!=eB_xZrZ{A|mX9|oK8K4UnL`rSn@mAOl~-Yl$37da z%s52em(1EJv-R(Gv%Gh)z4xE)kVi|ICU25RK^n6@gwT(iku{7ne zB<)b1!3oz@5&TwbQ@Z|BjkJ0ZqE) zVtG!Yk?1#=itWU}Z}5_tWbsfs4OLk=ZJP-eup3S?wD{x3fn?pK3R-w=^D!IMfzoQ` zBMsFR$T`MKIcBp6AeM z^g4Wfyf_IB+-G~}ZG4;BVJX6AUU*ZsJuq{pXYk|WS%L)774PhZn=D>I*iDio_wlOT zV=A^J+u2-FSl#El$Is`3Alx-~O&4LwWr9#PI+}~mTwHSi-x}ZPx&TODJsVLq zm?ewfL&=OBM}AeXqkY6T+sls==gkXo{qzvdu9?`uiW>{$62;8hc((kR9&*3j^Nj|^wzOXl4n|NW5#f21{ov5Q_o|m4j04a5L2Fu zA8H%05Bp$)_{<{m9NHvg*}CxY9_Aj7Tw{gEb~I?0xjX5~(*Wn~os!vh=?iyds)K_x zCKC#+s=1yv)w9ZtnyMZ;lb6m1q&rl88Bqnt z!%54oi<`_Jc3$%pLN+gCgS9G#YVn=ArYPDr?<7#!F782jRYYSAM2~Me=Z$fZ0B_@4`ToXGEyd z0jKRG>owhZ;xAe6qk8gg=U{Cs={zwwZX4-dJfP{Q9p%c&3zr{BU1+~_gx_*3{)eo> zn6$9IKpWyIm;%y-HW8mOxiQ0 zU+AG3*6UefKl^zo8mg~1$=^>|gf%zhDG}hzkO3AXL9xqR>&sT^ql-L^k zV1Q)Yb11u9aFi2CHrHvpF|UGk(hLz}#4Q|JaJLv&*tqd8n48sq2#9wu&P8Rak)`BY zxGq0YY;;*B&|u2$;YjJDxrZ6{B?xB<7Z%o$cXK00TxH%_52!E6=zL*jby#YfeWmnd z69aGj#&dQfB7O753^rNGV&_bz&P7084eMK$2czySyq zl|l=cPAQJ!=Uz>N=G<@d(tF8~XG_$kx@hAyH5Snc z(2v25T3AGjHIjp6{dkTnwU05QU7^%nr5t>^Oxk?xg@@3WWf<#}VnL)p)|9DspP{SE zMx5g~No0xQ1;0_z6oglOPoxKr_5^--8JBvXhs?|?b22AX3~Qhb2%E9|T|b2lG;$h` zs^(&$)MiQ4MNbNzlbi06_RE>BYGi=3c+7b6{z-=K5XWj)W+FF*4#8=PoFh=A)6k-? zU?HcO)%jv^A@serUQnL3w0s6#nw~cIJf=F6><@c+x{_D$z7UeGQ&sTiP6Uvo%X70( z*2gT?@S72(#;9Qp{nIVBtomoEwxCq}pT17MjWK$9MLbVrl7XMWW*R=SPppnLK-R z(lLHW7O#XoXQ3S~p=+c~**1`=Gt5(B{rMwTD4D^7|L*OeB+(3+Q2EcwG|e&sff|dZ zT)T4I539M>oE)3d>{+cD#1Rx}b>Vr+gnnhog82S_0b&c2kKWL6>$x?0=8Ya?$5g=@ z(k-(sk|VD2oc~fy%>T-rBDTq&@0H14YdNP@H4ZmUl%BMywOHO5s}BI)Z?;P370Tin zu7g*ZUb++qQAU((X>__ei#-#n>xV1ff+ELQRC!0a5S=nt+}xIm`_wK3IFmiQIM-M) zd9v9hmp9bLqevnj{~(}+`-QMQm0Ki(Q*Wn0ic?{!8)q)$jG z-hc-?O706u2;WZ)4mKIp2ecz@zp25@`e=JBigm#O1JO_Ep>pn@BTpfz4|m3H{IVHDFil-oIv*N-%IZPn@frB1YH2 z=vv5e8*QbxtJvG=nB2$qDUs%@Mn|RPXIqTA{~k0OhcCi$|2rE8-fGg!3eC)`14+3+ zOcYpJs+wRNq+q6~#oNII8%|z^f8LJh2YVRJuK_sq;`s$%RQPG@W`UpkWK=5L=;`Xf?V<^(>wz}_oz!I=2Y4!2ju6;O9b))S!m zO7XW04eXWUbf-^xUsagKu~)0Uk01KxDGK?;iD1ESrmq0L{GG zt}yVyM*cKQPR1v%npKEi?!|n2XHB7dot?5ph*FMMR zDRcBte5ktR$LuOg=vuJq1eJxl<$!clbp$@(udvbwX7;+RuWX3mZcaF3+$~PvFw7m9 z6}VB@mbu{1O{hi5rX06~r?UFba=hTyGe+ZcUd4IRB}~q~6Gw(&aq1>_gwXw(sp^Jw zQ0@xy0;D67D)MO4tYc%@o?SU1UP>Dn+j~F7@uZ5H%BVe+Vi4cB2&n2rHG`Y%djJb! zVGkUBdvs#M^1!NXu;ZBiLF>oF{8Mzij)@xNmZ!7o8sfZU5{HLvp>Ltv^uW49MpdaK z=+)6)WRrIYe^w2fe0!l9dB``GP%m0^TT-28<^~eE3RbyNSh=z&Idb!c533;a;#W3- zK~$ZJe~eA1hf^$i!!D2e*gD*-9zcTsHy!HMXj1E;%N6xdPj=h0xxF?9-jNTy^+dVd zR(PW|d2Xx%#b7eb#pR+^0HfeAH2)<1>|w|ix?2e8@k~sA+g;=Z0z&<@^`D64p$_!* zaAJ_ZmG-iSVlEUWwJbdf2GgTcD1M6m@eID%jX-$BqjBI?N}$$ydYXSxEMS2_m;4&q zGfOWre)`a$0^x-!ud`Fu{z_snG4aOEqFmrb<4H(<1(Un5uv1qIFVNRIjp?i8M-$lk z{u)$fD}QZd@tcul@34%7rH?BD?p5>(n;0=q#K!{)oCLl-hkY5S@$K58)P2ePTVC}(EdlwIOL4trp zz=43U{%;`C^&cONO&45E>~DPSg^hb2_pPCt%uOC?-SLQ;W(XBC)Pr`XcvD{Mqx3(} z(yI#*-FG&c)f!GyHA+&@ZJ;F~9LcS73j$0z)|TaodjYrLtbBvFn73<~2I&DmUOe2% zN85rcBn5nCJbFG}K6E(S z&fVzA8m+!GlvkFX#6Hct7OW-1kA%S*Pe{YnTX^_co7YmI(X)tx!_zRABFWiPMMT8Y zT(mR1ZY#img|+p3%Iv9<_vew zBSS_j$p{qP=shCzkV=Q1w+a!@5eD^|fDN~EmriEtPB6EEGc)@w;8t0pnz3%9&^c z^5@<&{eb2mI{d8lYpYP8TeA5sNqu-(7yDtl|8h}6LqNpUyA9sE))O4Rv`Yym>p~up z-YDIsM!tPvPYFc zfx(NuhlF)r;_7@@70+%;=A5QP*&*+1ZC|%7@14|gW7NXp9lx*PDB>;O-I68d ziR-5l^7}0*zTgNfkAGn*<{hZ8lkR9mWzmK^_YEji=vJjZ8{R4>OfH+Bb+UYlWD&3i zlIhg{1P38_2>Qk1+J%PmUJs*8ltVub|OD%*%k0~9LqAW!{N*6NMVbl%40B!8=M?#cu*N?cv(`-p76+&5$CK(%xF*h(`1+B$829 zuG*BR*5)TdsoPml!WH$yM(kbS>2shN*qaT)1a>Aj?bxtu3a)Vo zG-l=N^40k_>k533@m1Z{XQt6eJS;_@b9G5@QP;Ud*B2gi#cI=`*ntFl>DEu|pvaYy z?pGQF29rxQ2g_Brt*jVwQ}(U27|KI-GO&@7Uh+duM}TD{geJ3gtFP4~^r9?wl7mOF!1H`Ck|U2Q*zBLAqjZzut@ z>nY>Twd!1|$Z#pBQ^4;+8bQ78RErh~rV|vIDSCmeP&3HsOS3JrKzXuR7)tEc*@3+{hmpRgY{5fB+ZQc6L$G^@G zLtyXcMA+VAx~hnL1v{Q@Z!W$PfdrqZ8{WT6>iT%YV|x^xlcVj~Hqpv2Md+;7sb=E+ zV>vC?;}q+Zc4MV|a}`sd;S-qQ6BL+yjZKzlrhh%SSnV4^0jB?=CXF4hu^%7>aIm!l5K_gPunu-77jw5=$6EhDMSb`ZOn=gMkiX|s+H?Chxi^n? zjU1y(bn`~Q!e)?5S#7lJ=Xfyg)jGw$y&Q`0nmjQK8L!ZD)YF*O7{X5EVhs|m5q>QlD=}VFz%HPg zbs=KDx|Yp4i(#Qu{*Ps5ZG3aeHW&1{T-M+1QlFdd&FFu-Ib^nYdxn74&0oKp%}c*g z)LFhqs^2XpQIR$6vco!E3Ji5=liM(Vm6d7wOFV-DIlb>F703NPEw`qc{H!Um*rUH7CIt8c(7AAOl~iK!Iu# zXn{ha5)*C;HM)`Fz_>~+>W;0rFf=)@yZT5TzQP0L>?DV{@ncixw|30D+k+&UuGi!( zK&StZMj&Xm(i1dK_0G#z7#PO9BZn~5xJ5_Lx>ZLv99->w4K02v#oSf7wSRlZ_j)bn z`>Zv4EXLeTitf1?MnwBm+Hg+O^FJECzJD`i^e;Rpd(8;U^SOhS{**=U9jH6L$3^cQ zzES%m0F-Ve(ZODmgsz{vuX74&ML*sn>Fni_Nufgd;ayF(`Q5xr%NfV2ITGtlb)B|Eu=?3h^dvN!C=ue- zHK62A@Ij!t2rlUh7GWgJMV#F3YvG7zVMU>83vie7!u$jYA8d1`F%l@CRiYNYWLzk# zVMP+RW_pB#oCp2az_WXA)8#xVGtM#gp{kMpSHDS;{kgYq@MoodEW{GqX^yvE{zQLx zdcS=_dH-VNUHiBwtqB>Km{3Gk=`uF)ul05UMJ!+Kgepre9BB7+Q&Vp{Eu7BH5#plz zdTd@{i9{n&V&U)eVDsqfnaBy_xddWT#9_QPlIXlBDcu=I00DpglniEVK|Iz^nid6K zVX!0zq0UduJ&ap|ce3zgEL_b!K^INrnfTnIj99-c+Hrv;qIUrv00dH9eO4>#6PU|% z2niS-1Ns`@Kdp{#%#73}ql@kqKp#T~v}WXOlW0v9{E#y_Cz6vR%@>I+wEF#gI=v{U zETswRzg?$*1x53i1o9P!^LUf44c9!^0?$$xW(Cf%PVz@h2U5Bbl^#WcI`p=cI83kt+7h0t+S%kh2K@MU*;@ECv9#4;$*_R+P@4tjpAcNHmXluR0z zd9h#s>`%rVFsDbwp_qpp-pa|Ro%Es&x@{7hsL-a-5jbg{m3lPb7kcq^CWo?GTVt_!Ft;JJjnM)F!PIC*d8Lx6FlwnUxVZTO$T8@Pqh<)Jh4mBqtGX ziM6Tx<`kF#Bp6l$j2JW~2^1Jn2Jfp4LP5~;~Yk%;+MFsUTTh$9gSFXA^w zE2@0;aLd&`!**!GB~w!IrL`CR!kSy8R$T48$XNUc))zS*FpxsS*fjZGVa%4%B-Pec%q`Cy~~so)jcJb<=t`l z>huG{F9Y`w6u*XXy30gOv3XQ7(NtpUPr8kEkMju^3byo!^AgzqEoN@LHMLC~m}h4xS)J=fu?k-FCD$TUPG7Qd2mwx7I0 zW#_(_HDhYJfelfT)r{QB zc-7`6MD3(gt=k?!<+$S*$J{hpi#B%>oGuXKaGpn_o%kvw&-1-HUT55Z%@xPrKdbMU za{@N64!(jI*nTBsor>6E#^f-sl%wW#i$C}V;ck0b_{OpB(U*Jhr!CNw`dnN4i{|Bd z^xT*1ms5ZEABHS4?v93FYAOd=V0k*3GcapH&41|XrF3E4T(P%#Tcm$B7TGR*-|Tbj z{hIv%N60v7IO5_r0P>9iq{#*h-5;KNqeRc5dp3V93bH>6vb!g~0gWfhT6#owGHJu&{lqenb-VLsh zVbOp0)O7n7x;zo)s6XY5-{N<91JF_1&0uK9t<_LzZ_7d^izBW71#!6kCxRTLJW3L9~tt*R3}r_}@r3;rLx)Z_L~> zK&mMya?kj0zOmT}pAk;7zq$^5XJ&r;biZaIUd*Xkn5G9}KXv#LBmXprn|XM?Sr8j9^Yo`wd} zwGc<&b(~|f{0)J2ONiX47r!M&<@m)(XD`&%Ks{bR>A|CwsPm6YW7WVj?ASpmUDPw1 zK~0S#jP0xKbIWTWwH{BvB+UBLQGL~P<6)am<~Eu><C8ss=7;Veh|}q<2W;-BK77!NZ`bea)O>DuA7UVd>Rj`- zuuQ3TZw1738qsVQ&HcH0 z+~_J1%huZq%|>98W|H>kC4196@2L^$F^BZU+^Xos9GvHh(vh6DDC~y!y3F)ShX>b} zwoE(a3ZZ4H(R^w#W$KB7M_>%e&O)Y{xy*wl%Y!+o1Pq6|AVS)e7oQYYi`O794W%b~ zE{z!&gkYvUOXgjuwF*3?UZU}QThyxELx(YVtA->0u_a99x!m-Kc(hrCeFq-ZAMqNm z|1(;qsE#jW+oNbi+V9tMalNL5Bgz*`oQ$*ccA<7q*KPkc8SA`F%25Z**%zqp+nu&3#N`)oE+`sRbhR^sxnQNQlQSn2Y(|--!CdL&iz7x^ zP1q(>QX7*~Taa208taUy+2|V%*ycb(Kud&R&BF}eFu9;~_Iy|q=_VtkZ@GIMc6?s8 zpE_Uo`9E$bbHVG80eul?xkPDcI_Xlf26Dj3WGy%_l5-R(q^b~06b^#>?nsk5^3a)& zSW`j9StilvzakdStBXCfZU0(%i&k@oSaSz^xbrB%W7Lswhg9>g!!Vdqv%D;lEY-8t5uJM4}-ZEE1! zg_-Bp3*AuZP8{73#Y(VNS3fmrzGl#3pJR~)yjF&AG47IjQF{E%JwRhTi9KrP_c@H7 zeon`U*f=Y`&s2*|nBAakObp!p9LspB(^Zj0?a@V|Vm|M6dxzT0a#Lyo&;CV7;D4=t zP(yES%IRT@jl{$I$h5A6tSTse01F`7=2D@|vc^pi?&;OE)?#3!&PpHTKEFsWdw6XlTMb@+NG>QT6VlD_H; zZB*o=3JB;%zP)Nt31$?eX072+MlJlKZUGZTCI9?(`ffU>dbzn^a*FgX=1u^-78(oN zkCy!m`*FZ&oMEh`i^9}#cP9zENNHThPR4^0o)c-=0#~AyYcIQ=UUK$+&$zIxoRA$7 zXTHcwf!H38la3_1r+JlHAxTr`h9{P?-o#%{0lm`c+>Wc!v?r;Ps?dIEku^!wyI*kN zUH#4+-B9^XJrz#A?oCp zO}nr>D_Nk>7?#LM%wi4ooFy`!$)_$sF^M$QLZEr;mXiJr&wQa{C=x<~9g#zMGK)Pp&Lz|p? zaO12!@phLT6%w&KG)BD8n{XKRpaUEoggyA-F^`{LZvAX(O0x!dONJ=b`AYVoUJF7A z_pQ+dS8h@HiuV7rNTGR$+7?2CjJJ4;`TJ=qGj&YD*>G5?`_jZiq+plrWmJDxJq`Zr ziu{9iS-fxzhHlvYGf)5#fXas=^`$UA;l2^Os@-_Q44VV4Qg-Ty)D1e1EXv5^P6+X> z{O6hSQ+v>`7&?eWs|tgbYU0;aci)A0lA*kGi9@~ROc))lzj3!Z|HIRgm-}`}3~4D# zZGn$}2DF{+#z`heIYbU|ay)(|y`|@e+qvv-m6Qi#zX_}A1fWBM&U~}_HCHStx6P;m zRD>YfTcMuJ(X>!|*4{3+#=A4!;Nva?<^x)`G~$D z_eJbI@&iESJTYl$jHvI}DVylwJjt|Mig;U<^f;Wx9+x@8by@ipzrvwty1Y@!X}d9g z{8>LED_WvV52S{^l7}#lqz+!uIi#)yxllMHQ{1#WCqY#qZ~8~PXGK}D>3(z-5EnnF zl3qumbcHja_+q)O?y{Ze95rKZ8m`WQ{8@@&c5S;#jXOXA2>#{SMSHDF#7oZf_V_1FCd;+ zaB+KTe2NUB%F>%-4U0i_hR*S&bU2{B#1PxqJeG%b+|t4J+I=Uj(*urbF5|jV-@r(Q zzPHx~7u)V_1$FYB^^){B!4kT>AnC_*v4azx%pql12y%tFu;ZqP68R9Hn4Mem^VkL+ z`n=)d0h@m>I9J2h@eYDJibsWyN?69!`dyyL^P=M}R@A2B;zI~IlD15X6IPe)k;Ty^ zr*Dl1Hs#Ugi(1{a(XrOmdx7y${Uggw?I(7QjfckN6WCM*IoY&LS^`}*I7v|#+RBTI zWkV?}wfJcZmUodbwvyR!{N{R+r7RkdSyjcaK)dsgD$#J-fac55DvRlZPyrw<&E1fT zpy#(HjGSrNr+3QUAjt-%wURSw$`I)zd3|Xi6+wph>TAX~pLzlw!(k~)lZfBv>B+`~ zgls;C6QsvW2(B0_fm3vN88c1p;Ey{{g!eVx z3BuAc>PrL@!-8VBr9UBOpS&JnOwml_z*dOgkeAj$ec*hsC%8wV;$Apc1adS=owjn{ zj}81n`R_igW7?%wxWgmZ!zc7?D4($MOD4@RZdLx?OOJGCw4M4NnO9E&X|)rEoq^5D2R+e%D*K$gAsNEgBjW`tePdylr#%4%oT;C zQjN@IP~#Y(*Vr({v0uDYsO4KI=&-{Dhj@#gG*0KLuuVSSRwN%l zfg^8n7d^G-bpN4P(2(k_X9Ux8JQ!>KGoR4m0Jh|<^#sW20CcnSh9J>t8U@ znl>k5R>MRaJ7mGv%Itv(lSr?5>i49p@7KfnnQ8v?WI@-u_7gXs#e?akgrF&}8ABB!#C~R8|kD*TzR&&X_-j#1IEd@Fs0v2!}l4Wj=4t z1e@C`k4_io>?bOJS&@_)K?-ShHF#4~fbJ1rEFiHjBqF zj=HMfSeIglCEvdal>OF5oq`3OM^oPByM%eW5Q_2@O|3_55|15b>jAatH}T{9c~^?e z$52O^{3*9{XvLwM!S#Dr#;Q>{U>2sITw-(6)WVMsr+z~QV-UkQ92OLix0p&yiYa{D zLW`Ynl-&Z=Tl{I9LJaAl-_@Wj$8S6B>#3`c+w7nPsmc+*fyM-XBf9MCCWEGLiv4tn ziAz5*gT#om$=;)KXlUaKRmcTOQC&OUJTU-IJ8hNUK(C)aNrH^Ek!&DxMjIsX%ucrr zf|1_v-}8r8s+x$AAZ7uuYg8@7R5gfXrqJidsMgeChgY9#{#wdbuQ3VD{a47W&)~FF z&a9_P)55_FFF$AjGEqk(dNW(tMvjY`c4Re-1pKI>qNmcv4}S~WC~b!fc$;C=W_iS| zH`Xk9o;C$i9DGDeokd;OnSAUK5e=g*i8wGr`Fv_St-JDdO=O_E{}~xhj?_X2y{2AJ z)~sVGIux`aeT}cHma{n}kUf=!a%$7UjDwCf29nozq&ie;OJ==Vq?-u^9y_&!gDdE# z$L~dzu(=$H)sp#e@PoHIp}Ei@dbyyFU-9ToVX}16Z|7Ud)a9rt#`)1BxC7-w=Ly4NP#t5|jJU@qZOef>sN0 zeIxEvK6O85rm*{3`S1bF`fo5_uZygHo2B4!M1v$|n?7VV@3?Vv#60}pY$Fz~O|IAN zQ}yWfveZ)0gp#sIi!^59fq2bHk;Dh8(nPWgC*%q+<-Y+m*ujP0>cNM^tmtR>k#v?p z)aw$wyx3e}Ps6oTORqM`+*HK;J)6KusR3X$)%(BE%nEgs^iplb=LC@Mrp^^XTCWb6{2O+zVt|h z%KbE9wTtH>?*$5i`SLi1br0~LwmA+*S!_Mp0>6OyH;tXj2m3Dch|fK_m^_-kU#o8j zJ*afWE0IdWx$$>HFCv6RF<3mhZ^W)L4pdGsacZi+tBXFiC~e`!Ey z=Nk;00>$%r5up-bFg9G?BAz1c7#x{SierG)11B!79FSuj`jv`olTiG37pp##Qt@Zi zy8zC;?sRC#$fM$fR=47Pn;0pVRm&UkIY+;_u8z~d0tMwtf2*O55kLgFFBT$6RVp0& z8bc)|E5^DOE|SLe*CAR$#DCC1{E#^zP2W-;W90Lky@IervjPyY6XC+f_)_7TNCfzE z5gS_J}shw@(CORDU4KnwOPrUAm zhnv?Io^N4^Sy9W!P_4!`1|$W~{PbB^sTC;NK<{+@#$=CW&M*=xHM}OvnWeWl-z1^8 zq#59(H(z7^t0G2a-c^p&oqzHO z2J>~XS>Z4>ulpad1bg01Bs^uktaw z3vYR!tux;T9`&vBwePO}X}gPWe*Zwktwwkx%M@Qlmw0#pvdVAHZ_0{zfv5BtLeYRv zL*{7ZZnxHA$pQ{O{+&|Bh(7|z*M5~n^=3;NJiy56#V5CeO@i2hWUKl0SK+Jw8n zrdi%J7o=@0|A40CtYay!ofJwi2BqO{B;sOv5~FNS(9ZBNGVy|0*zb}ElT_;iEINPT6Fdb z!HQz~DfPdA{(onUuc0<2xRUR=(OhiF#1@x{BSGy7CEmtS>yyim=kkY=uPY`O_@*MoOA?Hq~OUZIp31(HHn%Fe` zm`GHd?Zvv-&#i1qd`X-W;${k5BF;NsCkk(rWA(}a=9z@fmJ26Z4gT7(Gbqy=i$6cF z<8Ivqh`8qC7|-&r0u}C8P9AZ^6kX$cg5ELF{u0&XTS0ZeMtaQszJ?hbwvvp;z2?e( zTtwo5WQ#6iX8TbT@n~9&w#IZ$`KU_5j*_Vr6I@Bhj`9aD{R{gGk5yz^;XX0c-@up` zzXfX==(`B~um*t)lF1RUzetg?22i3$@PcQJ|H5#F}z0VLzs0V5fE)i`BE@E#qY|YI7JN zN5eYn1g|xT;3+NOh_A7xOl3$&i$5;&XcAz!@}QD5b2JJKD*ElwrLw4KCfj$ky8it( z-8Nn)S3hB<>qIh>EwbJ3Qh(Y}(vuF-$(f@9hn^5M<~vaD%=wbCzBKF+9lY(TH$ z@vX@tlf={oNK?;HEMlz$vt~7`(u7s&LZxm!t6fnfqmC@XIa|L7 zKOHN;*$PKj?1i1baEBS>L)N|;9E}SgawyvQq2LJ)RAg;yubZl#+rrx_n)8Uew1~ST z$GhW1>i<#n_5!A`Xgz|x*b||+{)^niObIeW+X?`#zE{DtErE{vhhSRTTt=A28cJ()HYkg`k!&aCs44vX#uy-V1*Q{In#xD$m<;j#fz}?|r#7CKJ zYav$!l;p2xMxe%?WAXv(KO3y7fud$dHkBMqRM3I3?v(5pxi@w}zEG%phBBuSSI4+2 zx(zP;PDcyCirJBG%PbuzF0C2=SxT!2Z)cm6kzuHIPFM2_@Rr@L^XuF&lN5`0p$%l| zp!9Q!r(_u+&>8(9L^XmW_ZdYlKCCty)=B3FP>b&S3HVEW{u`q=jk^SiH~y!`JO!4$8Z+?6t7TX=1KGCFwb9s=v3v}gzAv;z!J%pW&1SI76cW=hABTl3zkBWGj zdKv7sA&T0QOF8bQ)xO-ROMI@!VpF_FKxH=fuKdyvy1&-#D05%Bg$)AZ-(rd89CaP~ z9cs^6T0(V77c165J%fs<_q4?{pT{|tytKx}M~J+#(i}yDFdE7|3!Mi|Jlhb1coSjY z)&@MPj0Ej!z&u?qA7fcdj%LhIfn6Er#XX_UBrjpe#V#YZB>;7){2!Zc5=-Y2kOSj_ z5#_TQWz9j_mP)o10R-a5>(T+%=6X88?kp?v*zSIP46m!^2=Xufy(L)|_ko;%jrcS!zs*ILyCpxa zMi?*PLOl<_-j=;Vu%Wu0T{P;CM9dg$_*^PjQJ!!k-2?MfG z%_l?Qie3;F@?-1<6KVDo2ubt-9cVtbLxI-zDno*8)!qb>ADfSBf##M8w@yPu?Ir1r zv^}@~tyJXe8jGQMMEl1N6j$` zayAURHY_?g1Bjpf20L@;^?|W@H~8TL)9#O4C-g)96D3jT$H!d~+AZ6tnLv)@r(`Q& zZ&CX-;~#`aY+*zYIR!54E)-V$wrE3O3>OJ@$43jwYHyfk5A93A_ zr@i2GbElO|nXK>rE&QqI@R#I26*{J&L9)*Gqo!3mJIzYy@;9{Aue#;R1rbZ&_< zM)6im6(`;MNvLIO|MZ)t=r=cJugnG(nrb*Ztgb%@A-a*0!+VUJaiF%d;6};EMQ(P2 zDSdEvG9Ta@f`k_VxXjzDd%OOQyjz48A%`9`UL9RGS$6T`P|@Y6r41rGNPq^N;G~5- z6f_WmZfXdBrY+5L|6{UMny1Ao)I8lW7lQ(sWKAL##s{>Y9b&2V&P%`8v+X%zYqEl2%gq#BSq+2`R7TAJKZxCki4(=u#C^vROgu*qg)( z4yF9sw$R2(#qyL!8d~rGN2(7qD#H}HQMj(&OG4-l7MI08r?Hwcbshiq4zigWO@-mH zr>e^HTUIz2aV|t4(+|`Ga0muyoB?Yv;v%6mxGd(UQOBxO@#3jxyK2gF-U4OTN(N-J z0kltCqEB|{&|yfa0#>LL+2O*AYpR>*8Mx_y{-B8`_xoQk(T;oKc7hm%4@8@GHT)S( z1eTJlYB4H3*LZNU`n2r$W~@WR_U26l__%-~&XBck%}H*_8$qch=I z)gOt2E$KwWXVa#GZ!kz1TA=$2OQsD>&qaTX74m!SF(?eN-#i8{+!au zAEFd-^AlDmW=7%~QHXgSTis9Pcmp+iu#rumY+sJ9n_yUrEOM{X#7`lmhiQO&aF$!* zRTF0??d64~8-3W5{F$hmd6zl#CWOT>1;kwbTo6r6%fn}&y>JJz9IKyNYA^$Nsz?GK zC)^qnPi@F|D?xz@)ZYoDUV6lrf!8zv6VQ=a(wv z7u|f!276Fg@w^?>qdA$MvpSXjUYP_5s4@YWTW?F~EmivL->cVru>-h9J9S&`((mW_ zD2Hc3mv({k*UlT^Ds*SOX7iJ{lu{yjW$J9p<1J_QC2YP%YQnAUN74W;146C**^wGS z%`&c)*h1nw<*_i5{?4M>tJh1%dD4X_;L-LZ_>C&h4IJ>88_?7{i%CQ~4koj*K>r5J)O*muW!RCYvBw`X zNwcax{Zl!~s5xTyMUaTU)KTGOmY_UqF9;=D?Jus6qtv_WNq6~Lt+UDmNyqR$gkgBX zQpj!E1rbWzEFtcs!qGb{M);S;!LI16$NX$FofG3Gef#!_8uXl{;3D z*_Rsr$dAhsGkcY9g40rWr~&;B-csDYM>2u7Gd?<;?$Y`>%S#`#H9o{aWdMl3YVz+R zVB}oeG6sJvvn9I6?m2?O7{jT7jPmZptg0qMCpxtnUvm#@Y;cMbnblfQFFfPh#)yJ+NlQ;*B_d(N} zy|c-lajTO8@&Eb{SeEOouC9QeaVU=ga7|qWD$ci`92B)`#aqZbcx~Cdn|EUTy4pU$ zD~k{}A~tpfuB?^(9N4i&gxHb=RS8(pWnDQnUhO89XmRbn{#GViOq|c2RH6!`e*L}m^e!54}NEXg0=QiFtvYcH>-ikX7*kpo%Yxn zbXMmmWO6)1z>>yyW;;o4qF!E_XA&)iNVksy!E-3$N0D)p%X&svzsSlbH%%TGbPbx? z_6lqsNghal*j+=v(BxyEGyM@R^mc=4fIXP*!LIV|1b?ko?N&M;fisX@ufwmDWeH zmHCY8(*^v!1;OMR?%Bwfe~7lq@VCm&WyK!E3J3}G6y0Gu#5CL;S2oW?BF(aE5cgrp zeIR6#iTbI+4OW=Ei|uN*hA{>PboY8rC5{WD${bbC2?kstu!J>!oZw`_h2NrvlSuPB zc%`UDX1?>h^wGFt?g}K53>?F^SMUg_Jq?lz|4rHvO6+Bq-_%Lh>u*#!;M%=a*0&i~ zC=B;abZO%p;~td)hMgQ9O5YC>f;crvfi-q77;68B)+9j zUgb}eP@s*m<8Aouj*D%qJq&=LZ;DP9m2CK29-wWovyrn&QQu`w-By3-K>XGP_!-9a z&uxr3Ea44NDSC=%_^f*}vDk7~l(;XVCE^uANy8aP_`xIi%^z`IIF86;wv6VZ+^Vu{ zI3I@ID;h_{UBr`K=HLj8X^JTwi{?{OeW+q~?9bdnzimM%cvoGzo=M&h?2;3`_jKG+BbzS7W;BVgsNk246 zMwP?)?V+cYO|mK0Bh}J(#|HVe@PGd&cU`;@QSW{kN!7uDfwBCjNU2MU7^tVFriJm3 zONNJ(K>Wv|wtBQ?KVv<1C8e|;rY#MWEKOwBsu&NI)Bx|XQKFqDnm!vdB~4o0tL!Y3FIbz#+FLiWCc@d2@ zW^GBA>})?EF9EHqsAoAnCvRmqaR>++MjhbDRsmNkcQWktUDqvb0RrY(9=rl9WPYDC zmBcdT=*Td#*RF-@r>7WFI~+oNqh{pClv!JMl}xBw*X63*NE-B+p3}0lk3krjb+h6G zS!R*C7rU1&(>b%KBd*ku9Y{(hJYuGMkBo|!HZeTCAc0n9n=yloF}W-)?-b|6GmMRM zEPU;K>=}Pq5e~4~0PpmdSI(px)m|{S+$(r94cm*U2@COJ2Ov7=rW1JEt9jE91Eg9A z8U-?=|I|Te%0&Vs8NYtAB#EBO`4;9eBUjeYSei2qvwjMwN-dxBK{L&zs!fCk?;A z%0q*ph|!8?AS`jCkE=y+KfI924Xu#c4R5#d4Q03L4QIE>rMh3t71S&7#}?e*s%rOT zaSci6MOnNn(9vav+{Uu)FLZk#wmrGz8E;)&wiUdcom2~!Oj7SQquNnNDy$Kk*^#+Y zGB;}!A-n_guWpA5RiQ%8=XtjPob4-Z2}}g~CPHa(ATS4(>U?&8cxUu7d6v-oV|+wZ zqbb@B`+$4=@S-MIivyps`e^?ZZ_aRmu%d{8yDF!d^>(R#Y(vqp&w1WH;=#*26OX<9 zXEK;2f?XGaLno5n_Px%C1JW8i zltZmipofTeXeO*w-`oliVc!#bUT*D_E(BJkLIUh{cBUQ<5{Du>Yslyt4eq@qDtt)q zTK*Ax_XD*%DC^5sV2-C5&LS!Id zaZC%udW2)a!J{#F2iGhu3h{ccW6(Lzz!*^w(8Ot0uXp=v<*ml7uG6b3u*Sq_c4STI zlmPZse<47Z+qpL+4F8Gin`6J2kPoWmt_cBPge2Y!XR0&4(~p2K`to(DdUcI=hiXflsR$%=qu`Wx|p4!29PR(MzDJ=p4+KE91&dR`BiFADzdou(Za z$v?iv`Io#Q;(pyL?K2_Q5)qH8^$w@HIBora;NuyW`kFuub?`}ma~vZ!C0VU0=g@xf zBjKngh6{RT=YZ4@n%TKxR|2|>1S7i}@{<{WK1|Y=uineIFHco|;OBZrU++M=sX#(^5|ct#s7G>uX4txx`L`Cg z?b1h%55V_xX*sJR9Mv2re5M=aGt~>PVbprmO>I|lpe~%i37GvcQ!A@wz~EBpVo&%? z6Z}aU2u0>cCV?8@yW7}*5;}um1pCf{b3)CpSSC2me!K9B%pyp9MpkiRW-u7SgE~0o z6!gxv<}x@IPbR67$)+Q($`J-PZo5Y^E}vx88eF-L79vnP^l&BZFG!q*5{%&Tt}hU0 zC)z2xg}Plm0|X?s|B!)MBeQY*hCV#N#2O@}d#xFM-dX?_HBQ1Vn@AR?6)0|z$Yyg7 zPVzo*9pgkRvd=c!iFVk6N5K@rkX*_JMnV6>GKEBpZ8RC}Fdolb`kS|WCN<;_R|s=W zxa35UTzU~%+r&`B2HDJJa1w8DRn1@ui^K{vajg&v3))15`A#A*RTL7ppyNxU|K6(F zv*!}bUm2V4t5}KVKUte$1{0X7^#WWt#{HzLCzpx@`wosKY9|V2#t%iKTr;19BH2P< znEeC2y(GGt0%F9~0#~}jk)FT3KC4>K#?u3)1D{J;yD=@N4)El^^K?Uf-+19oK{_GH z@))vn?!A5GCS3cz*^?K5)r<8mH)to($rLI@p}zM(woO7Bxqt@MnFLhYrr4h%?UWeQ zqwGX-N>`3yN5k4}iu9D$4qa#-d{HDKI@&YvyV(*D7+!276_~z5_q!!B?BWi6x_J*E z5<=;R@eW8HBi>(%5POE5AwJsEhb2CEIS}v@9i$d}#-P8pvHXk;<-f+LwL9` z%`HY3C3B z*75cx8S%|W67)s%)dcmKfbZoNjk}ZxRvuEVtCbcUQF$O}ul_oLFHL4grd=zf%m0O# zZ)Q|65u&Upe~B+}N?JohUi$qKf2+aa&DyqHtM@kP^3dA04hYPzM4J=)6$rWw&(%Uw zQ$v7ATdTlUVBE-cpON`!M_U1BlzUxS%g)@M_T4X+Id5}u5bX^o+RCubV@Z+MbPzV7 z@orxUSk2{U%q=X)$jmL0dpKwpagDFAaqF?1EyrJgXpx-ga|&Mts48He;RQkSJs{e& zluJ$g@*n!ex&*{gAJ$%RPRP}4h`D~^E7sA6$98CRbl@k#A;tF=r> zTbS&ESk|&WyJ|;@fP!_e?gM>w6fD?o-~8y!UL3ut{%u-l5Tx#%vWHRGS#XRgwpEKs zGd3KJ$YX8|fhEPg+V5JiCd4s2)kk|OL2dxQ!3l368-J^-QL5iK3bWPbH`kN^_gG2RO|=g%VJ6Dig_rE(u9^k|u|M}oZ6m(q z3ajHr;dA(^dnfkiczwds--fW=F*!iCTg#*aV?Z5=-X44?gT(IK-#ig56Nxte#S#LV zf$*-cM+BJqTpJa4Zq*z7M-djp;q*o zU7Y<1BWc{TXc*$dKe8qNa9eZnAJ;r$(XF!H5^dG^mbIERD0X9pv=)>N2AOj7@aE(( zD6_Xca#2;+%Qb=4&^=uaZY}t7TS~(u-HL@D(K$eyom~^@VYg?hhnwUn$f4|C3cnnG z!AbM*NyzRdxu{Y#1UQ)ZQxq+5m1UtmP-x6Z(q*A(e&TR5-GXAB_BI%<`4{6I>jTX@ zVVTohz?f59P`22Hb+*z?)Q3H91d~BG>UI&T%3q?kaH0zu^7u2U9NmF5-Ena&G3!Xe z&sE?qWhoV=PnPNsEv|4qD^c=|nk9&M_Ldet)K=~pR}E+=Sr-KC3pHXZ*e(Mmqw_zQ za+&c)cc>qRCMY5KkKdk2J}%apm)oD$;y!afSI6$${CUDDF-)bkE78&P(jBIiH=4jM zbJB(KQSahCJOeVEAhUgO2P)(#H&%+#3_wYVcZp%68*Wr!`VI2CT5q?Ipy`p{VW_2| zWc;rMk81>P&gwWjTdKk%KZW)*suXPcuIQ3dw^G;ngFK(SNB!f8J{d7#lOB7@?EvKd z{qc6py5XlIdOLzep~@nGW$hQex)$|)&ih$K+EU=gHJ2{`$Opf2h|vPMP7Hji6L2co z`o(ud{{fR3&58umeNju@zb~N;E!geB$MDTv0=iZYU7T(+kz%eImC-2%1iu}pP~S%w z-4(UfiQbXa(y1)R^6S2MJfTlCK=o9P%xZmV_9KwSe~YvAFD*a!f_qRu4hP`YZfx$7 z8owt`Kgrx;@d-bOH~6e-nu~Mm83;{@zcA>eo5!oUzWFDjAHektknX`wzo*Mc15|hK zU}J`xa(;Mf|M*O@or~;h&GKN*H^AhY3&^wT$$liv{g7Sv^m+U}7tmw1Bl^ZP6TsNN z*WZH!ik4cN1BD?3&oGkL-VSvu<3*P&q{Ri!km!WgA-}H#Bf)N+M z0m<4QS%m9Aqysk+n=(Ytu^{eL#;D#J!31?`PQ?|$2Vp4Q8^dfW!{CCtr~OX$=b(5> zCp(Io$;9R_gv#+OON_-b8?ZCGOs6VC?0_NjZ)57CP9vaYT&QD8s3S)J10#%Lc zw(_+8j}M0RQ}Zu}C5CmY{gTA+>QVQ3;lqzzr6{cAye3Mvv?48(-v>f8#n^V}!Pf%1 z;<2O(9bh?@k$M2mHsA_h2xhRe*8&5VQ5Xh-Q|3V0z+J1^s;*IZ!HhcqAx8p6_Di&Newb0>dTP$fQ7Ky zMMFqZ_|?bR)&*h8-egp9#2~fVFK+~abOZsSL}R*b=W-MI_G5ekJ$FLnsUg1epq2p_ z} z5zbY+7w{wH6nNitgi|*%wyMm)1XXTL?=b4i#(F4=xCdSdVw(^7J`XNh(UOD~AYDow zxk7&P=cuWofrZ$WC9l|Ewt{-xlrDFw10pVnzT5_JlA?wrBSQy()m+a*`4Fotdx+H! zQ6O!8MAnate#_RcAEc$q))jqPCJX=WOf01?`Y0k^d9HBdgc%t2um2oz{21(=D8p~d13a&`qYC5yx1$O_Ol^d5~m z{i3UJ9r%c^!q1ny-A~Qk;x`0>{*S`*MaykjiB$H*v?GUy0h2iiJCmC~vvxjTj<~_D z_VnPyS}9Fc`biw`Ol0~A{V4XB=KbDSjkZ9EeGQ?QRgcC2pBRV*B2?WZj|~VlfTZfP@jt(7$;CV3Awt%BS!(ert4a*f6gR||;6s}dKGtn76G$-IdWX+( zS+&@n0phkQZ0yg&8YB{n>Osa?`PH;(3awrw!IYj;EH|zJ`&{RxiU&2M?tb1iEe+G&CO))x#n<}FDvN^2 z&JU5#$>X8Q3>xay;d+B+(Y%Qe+ze!dy~!p7I!<974Tc$4sdARmj)C|2A99CQ(RCvs}h)T9V1 zv*0}YA`;Vxyi+m{i@yalrbgl5AXlcM&{}itUGC3PL>P;2qddJUY;-(_1KjRfy6>r| zBNzsq>;QV#H%u}Gi<4@`+Gwgd=t|}76~rO74;djTj5h3^WWdnAE3l)vbyXl zvexY4YHf*?VT4K(SdMspB!T*n~>2>VW(AlS8!zlGbu&WoePQNL7kbTKII@?+BqzphQ73{ml?>peRQi zls_nxDVn}j+!vg5vk0T};Pv;?wG+bC{Eqx_4HJ4TvKGGgZVGOpzS|x7dst%RIgp%k z-~SON{9kbqJ`+c{&SYCb*ILgHmSdZt0k^PN7q@wJvX`$G67k&JWRG9p|AZ+T8JxpB z&*otNgZ}T6ouJD?SND>9|AtKerNJ$Qh;_xv|w2}?L7Bn9=(^OT}xr!W$`59p%utx?zn?^~g z2-Lt`o$tGd4S!7}kxq2;mYcCaKwBSXs$5w#pPP?6OtD0gwf;HJuCqj!nHhSKY7`+W zCpKl7td{o6ialiDURj-8O@Far`F=_uN7Gj^4#&2jm)s6m`4qn2&z`*uPpkHOOgeJ0 zUk>(cP2I(?xiy(&D#>Vg67#OHy_uw-C(~$pl7hW7ldH*md=^CBHYCBBMYPbkUQ9T6 zj4x~FttR&?EiDZNXTGtWlYtcPE|j>LLoJ89sc#z>?q8C=E_w;sq_~TVA$!>nw+K(V zY8>v;;?O#94e>DB-Onc>t95GjTeB+_?Q=DB&eG1Kdw*)Po43qq72@|z z&YLldXaydcb0D6|3~6R&X&99dE(L|>EM|hF`cPlQRf+M>cr=Dddjt}>=aybbsWx+j zTh5zJcGMqlu{#14cQhI$qV%HNHO*6>LV7ed*7#eJf=5X=Rud!Ow@lcMC>|fTZ;aGw;TIswNBe#_W9m0ID)I z{C{VSW$`kwe=hr`;RU^FRDZVT&N{MG2;240MbwJRYyvRhjm10ss% zFp__z(mS|3A~I&7qFFi?p>YjL{+nR^ftQW54QCh^e~1{O9*P2uZ%7c+W{1~u3ll&N z{EUGmA!~wujG!le@)tF{Y>xn{2Rk%w`ALP|ZeF?kqx}2fad{z1ywcJ33aM~?_o4cZ zTKsJGJS`-;0Qr0%T=)VGaYV>lADSO}V<2ap<;(2sxv zW^af7nD}D24!={9Pzj|T*TU3UBJ+Hxr4%tuS7f0AWD-XLwn zs7Nizx}B`R^e#FQ6ltlZ^ex9UkMT%$d~;(w`1GD<;)my+Q~L>(n(8?&P-$VEaPXtW z)xSCd>QLQZ`p@p$w?uZO5c$@2JZF-l)=zLUPTHBdlWZ6T4~^zVA#o|BK?za)3Ix^* zs$;^F(XI*?#T8sDxY|X)Wx0o8`2hpG`W0DHB{N#lo&=lS4stCYNoG-2IoKXjM?6d@ zu2H{Fro~IkEz%+FR^?(M``Mi8;t>`gfOI@PW;LoKeL_dqtp)IZ3Bz%eX7kdoaWiMlJFr0`W*v>COcQ&ZlLNU3CIvpQ41T}*0O)6 zn)WlWE06=~YdP|Y0ru(5opSx|MQ#@QB@>XpwbVj0P>zSw3Y!{~8C*4@@=k`L!9+V3 zG95~b-TuPLL}1ANQ6*sJ{GwYvq-0fR&i45InV+$YciDSq-&UOtoyzUTPXBkv4c7mk zzth0rf2RTo26q0%a`OM@gKOuZL2~n$z#+|ZMRXOEe~PpW_6QiE(Vm0Rvj|;Lff7yX zZPEqCsZgH|8_i59U-qHVwYzT55E<6WGO?fXei(UuI46IxXj*1PrQh9-vw!mL=zCZ` zzQ5l+;0MCalkuTA7EzKFqMM+qYyylio}gmka88on$e;$HifzT8< zy0I&=bLgfx(zZTA87n3FBHt|$WLXLv9A#~pewE)g6k}TmO0I*VD{#~h8^s=$K9^Y5 z+TgoZujxVr3y#dg6{of794yU0+EQpy%J{!PZ+}HG{ky*9dRqsg;Eh&z)eMc#oOC*y zh4i7>Q+qhpKa*z z3H)eC-qKEKnN%`aUO|M!;hHYN`Xfcabw2dSHJHv~M246vdC3}SQ(bHYJi-mbPU?`g z3bmSya@2S34`TvvRGc(1>{X-2N;E&MO(zMDYLD+!=2mymt5(7EABoETBHK6XF;d+O zAQ47-I}*^L@EF)DW6S&K5;m(Xx~o`XbAK)1MU6Fl6&Sg{Sj&&Vk(D_S{t0~5a5qKO zmQ}Y><)X)q$-P0*uxSbc4&$_BHk;Fgkvw2sHIgIR{^_)5LDZa%Q@1pj#(VRm*MZ2L zyXQk&@6bg*Ay}_>z2{i(L_kvLk(>+2F}cpx-=qGO4zu?uoAb$0ep*Hhq!vWYm>7b= zyT~*?xQjamAm<#PZi+9SJ@^Mto!?y*oibU-8IrQUF zIMS~=c|mQuxX3#J*Y+@Ec;mkumSRGG09S2@P;IO(8aIlKD zio0h{ddz~V5_ndkw(DYW$l$f;w<1j;-WjomAd~qcg`j{#5;v=k17vfQ9naYR4Lcu? zj4X=3v@qJQvNrawnBnAV#cbtjVs38%`ksajs+~szR;M~BEhuA*eE!BiKmCSJ6FGwr z`~xASpfHjZ)em+|9Niq%FhjqEuVSnEEP7w+y5m_CEma&{IFNYU%TY^eOA>yV<4o{m z(+zl%?K|V}zhkEa(m2X9788fnnPu9eZIEOpZCO~$3d>D%N!47=0ZbK?dfJFZlq;1# zIe`gUgcDUfEiWm)NR{obr;US&?PdrYJrgWKxK~0NmeMt?8o?*UCJw*59lU3rTg1}( zlJ)i!fBLp8ikZ#t)Z-(r`7--Y_}&ixHXvQnAZ2hG+KUU9s8}Q7MPrxl$B(HgtGWaH z3)s+oYCGYMLlgs*XuUIX6EfKzw_mLlwg5SrhZ1)U6{uEN$7WBWVMT{H!-GmUV`V`? zOLa?Oo0OVr{QzmN!sMNpOGF#^c89brw6Z_b$0-FBUP5gmR6PSj+`L%;jPjEsUOg* zzLIKy*zI9#ETvSJbj_<}rYHKP#J2r!lWx@7UBFs)()J`N2GnCf{i((q#XmU0Z%B$N z`^Mq;Xn&NsshSuEc23WGG0_48zcI~e=cB%Gd_W>i{&++B@10%dTTKP?#qJb?;)5iB z7pfbQ7=rW&;^s*>*bB31iv?7H!Yn)#eK^SwQ(7)8)u1M-k7SD>mAaBx25#>#A<-SO z9Gi$@dC|D-{)-Rn~BbDV0p6I=}9AuxFM^F%897MR%k-$ z%I2q@L^yG)34*!?Ozir3x1PYQ z0Zy&;ch?0^+(FLDLiX(a@Hoq^s*T@Eu5k=|oh84TJ1QKmT_$K;TM?5qKDsS{=PQUE zdl*?qn)2U{N^Sl;>MA&JxV!3IL*t&~MS9>ZJ{oi+F0^5Xi%c-^Ra{^Y@rGN-whpiy zK6L4GoGDjT|9TgD@Z~g6o3d>`T@BZn0_c{06nG9&A%ZW}xc=eWpIg(Z5l%V;bi(0b zeMemAqS{6fSd`Q#NIjt8^cKnnvWQkY?5GmAMaX4Rp~{gc1e&~6!%hO3-D+UNs2kte zM}%?1wVBUx4hxr>IMI-`j-?}J5QU+j%UCOnp{`_~gGm7lt}pw{m??FXd%3~2*PLRD z3EI#*yL#bOPNn?EEjEH&9n$yrCTj50rrAYw>=fUX>P)q=09)=gLUZ@PG?M8SQAs^h znWa(YKeViTymJ}ZCa@QkEy@AqcJWXOBG7y@UU_@TS82=0YqQc%a75`8z?-QGapu6z z6#4`WJz?0>x~R#UTEQ(YU>gxF!Az7%SK0jHc}cQ{!UBHWYCnTP&7I)pi4C*X0;{b} z1$?I{0gJMC<)7~ZZ5|oGTvfwr2kr~Xe26{MzE3Wp3kfRh52d` z;m7V$?mxq;xzlfPkc)ITgUl%VQHS3lgy*z--|hv+PFB6|>%F0XYtVi_T2M2|fw3y~ zt%4OT=4Q{ZC%P)1{a~-MMsLx5$XHR>RD!q5~B`&1xfqvMoq)Hl6uH&=N%u09g^ zmjNYL#Mzo(I0LIKwfMg(L&;#n8C+XbJTRc95hRd<*$hPUPPVv>1??wQO*j4t{@;Ou z-@mh0^(zJ5egzB3{~%DG1xjFvnu6294;24%zgD>KNH+NS%DS=zFNKQZ;05ZKC~&l@ z@Y270b|^OLhMFsz)o!$&k=vYU(O?FD0z+wt8(TEgRbkO!t|q6uz1f{Q-(KD#62Q1_ zND$CH8#CtQ;Ggar91DvFEYLO3InkeR=k?}O`?$mk)wj*0TRMY`GcJJAS^~s`kD=>F zf)Qk4@hd0OL|Up>2gYn(l>XsISWglg4`JsMKZs%+^NacIy(6UbZ+g`bH8%iZw!E89 z(3AofUbfcAn8ZB&m;Ccoe@Q)I+W*Ma_!TUdDs$mZ2DBD&Ez9LV2246cbcpeba#52X zaUFR{1>J9kFSCR5@NWYtEFjx@W_oF;SCG6uAHEAmOXO8P7J8Driny_m3TH@FcFfp-1ATtE*a$h*GOz5bFouFeXWLD)&EA`7BCd$iqAJD zij~f=TCQguxIX!*XkqgGj>_62;q&Pv*DL{wb<(If(gg6@gmVnsuWUGPj;JXMt(mEF z?rWY)r*I3m)cz@%z$aLa`#Oc|;~|6NquA7FZ3Qa94^%%AjP*Rk8J){q7I}nDANx%P zW~ma`P0!KKql3yP)1q9RLsH*$kM*mh{t;vrO(j|yELm6kRd{E<#;yI;Hl!QHW~O<; zn2)1Lw@c^!?ZFthhgX9>W4bs;OL>x~{DJTC5#gRjl2xfLOsY|I1$A~~Bz{=v>OBkP zI7E>!igyFGI)qLm8Q5s3racUwL+#8zSi7%iz2Z#ycPHOPt9+cyF^QMfmsFq1S?R0c5@ThyHeFGqj_ZUX<{h~j;&CMgWiq|c># zI=>nWI=Xhcp0?88U!Smzz^zBdQ1DQg3q%k|V>>7*L}ByuX!(${yjT0hl7SK6$J`Vd zB8alB)S(E*VPA#3sp#Sr7-BeL(c0e}<^R5gWbHk||nqk^?bfEorzIK~?zBES)icZ;- zW)RK&ngdSeQtH>Cq7hn`99Wuat)Oiy@T++^*~c!Omn9$XCeqQGyF9I0hZA(fj7{^% z0Gkc`rlJ(yDrbg1NI+H-^-LjxM%zHRG)M85%w{IckP5cx@Ckq>+`(5&rRF!@Flq^q zc2MB;Y2Yl}_)+;TFl-QX)lo zuf&~hR-mh>zVRoK9th#^(AdJuUBFwZ;k!=}_JH)eErNWkuXvaSvyGNPIe!I~AtR-1 zE8g-~{Mb)TTfUFX=4EC%?^z_kD?9y516$t=4qUCwDx*mP7}URaVG4(U%MwRZY3l>ODj21(7i=ievfMUWpLoU|$oY?RBv_Lb;n%yPCpci!TizWsD z{*F**v(j*hHfa%RHQRcfdTDo){IIJ&SrLp%(D1@r%5<5Ao>^ud)WrM6F~tS_HgwJ6 z(mFtPbJ2c?g>Xpaw{^5F%^9C{fu398_hrZRs-e-w51!u3E~Wz{0u1dn8=Cdr{6|qPWhE5(-uyC}B&R<2%VVFM#rxmbgnA>`g!)A)FmZ_w&~c@BaR>!}6&I-uSvGBq z*IC-IF9Q+tR4>M!V_BaG$kpne#-)a7286;7(1xK?{D4Cg2MF0Ht%t&f4Gn#GhPhsCM2D~DZOh`DN@pz zg~S$})X1;VrDbhzRdzJR9=pn{%+bX%grp@ezfAXaM9y^AKfF0(#k|=z{EJ1DTZTg# ze#1?-(?HrflW)f6Pk(y)4;;A9}k4!qEwy|3nwB$J;vGcxd{bZ#-tITPrg7Uc&DWUYejp_ujdIVF5}~ zE_-*gN9Ir3Q@uA9^eod-Pyw8Th*8q+NC-8vYAEBuKYX(e7ywBBA+Ty3&KwR1B$jHn z0;mk4^G9rJ7xAnZ*dr3iiq-KDrckzpuqnbwDlPC#n>AuK@fhvm>!Uc>6G=?%0smbu zF2<{wQ$0^B-YrS2k(yQuzt3DXAu0r}D^f@BM7`|GjgL6dRP5|C-4KQkJ+0CP`-Kw_BUyJ~CwuQobm4UTUv_PRJm zE?c@6Hx_6r!{6v^_G%oTVq}L2160OZ7ntdAJ=-LPD&v&5=#4>kq~TcDLZzm3ofp=U zu-J2cE`gukYw>oG2b!5GSc9UOS1_M#jfDJ=1;GD<=#m51X8woNc=`rP2%-k*EaHO% z5|MxaHf0pzpHLRaqg>)YexMIU;815y8VA=hRsH1LV%)|myoqzVU-)7#!UnMez=Tt7 zHd`ja<%ZL!E_i>Ryic303;(~e&H}2fX6yU7Q{02QyIXO0rxbU0hZJ`WTHGmG914Zv z?(SBsIK{1Bp5FJp_vLX4`#O>mS?A!aLQSM}z;rninvbMP^>HTiC;=or4W81432Yy_1C)lruw@)EV zUUX<^wD?e>Rkr-I(6A|#;9e>B6O%>?j)LxnaCn%3=%;4^3H$jk8X_Sh&JJa5HX}Oz zv!$TziiZAE4;JQ26$b^A}Nl;KB)`oJ-^RX=C~nr|&3Ccm(I z5eaj?uJsbnyaYL|mLh3P+;p&g+lde?F@+*@XkSQ$Vd8;9=aG=6n%a0y^AmD%AHxOiU8@Wy_F$u`YeOB-L019wy1p)~@f_&0euRk)$CBBz}h*i3)XYIh^d zu~r3F7OPBQW%h}_T^N3^uDkpSHsBGk7g!klmxKW{JckRc*7~~<7kH+g z&2Dc0!&p8M&*)PmxiVL#Uhw8wvjF1<1xLZ@ydBny7*Z|_?&N#uXSD%0h3=$r>?kAO zGya3NNjKlgw$n+YO&|oFDR(&E0M}tFxy~-3Fj%@EaW-+VIGP@n0g1tqt4~$aMWdfN z+Q(&%Oted!~V+-$fkgTaaVF=Zt0sZ=)56KK0R7$ z@y&~X#4=>N`w9yO&SJbqqYjZ^$-bo`+DH&~vl?QCK5OUAi3?a#O%?dH`&%c`ua zKJ||< zH;*B_U_t5)dm>bc@BG8BBUT; zRwVlsC7dp;5O7NFhjF2;PAQq4&UV7dn#`(}Wblfdf+pjSF;q9=c_A6^P0x@f6FZc9UeWiS83ICo;!;G>oD}5|Gd*V)~)8GT?ec+txNYNQr!;R8Y}9 zM&_H8(ValGR_Fl97g;RxIYxmHWOFx7>Fih0gH&{4dy+-$2fPup&*<#+4OAR5xWk7| zZw;H%X6LCJY}nPHmsC;U|Jw7yv>Fe7?TEja%Lq~|$9wc>gbC|3CSwkXok0#qOS#45 zX3AF-AcD~|lr(0q)t@-EF2c!=MA`Q0ip?8tlRgt}6X5OT+vD0vBM7FguwX7r?CTgA zQcH_5`b4(c{76`W7>+b-s(z6u3j#){SIL2jSZ)FViAmr$o2*wj38C6E4q0leJWdo% z#VjXYBEPi&vlG2yf(kzF-W6^GjT4EIpIw~7;bRj8P6D38HUPq!Z9x`^3j5VHX1(wO z7jY(SKugX1N3+cx{c#TN9v&1wqfj4*RuV6_bbdPCwuKe7e4PhI1Sl0@!h*D@S({<0 z@L}r+_3wx3 zu|1~KmY#gV*F2_BzH2o&5(R3^RbSPHe(tof0Y-l(`51#*s9m8vTYZ^cYW=D!bI&i9 zBMuirFVr6%YlXtNXz#B&dW%7L?1=u%Pmo1#zs(OE-`MkHvzHEMA|PR1C20|!{ut4 z+Sml&A;C#6MdLkQ*=1S%0pt3~I=r%uAnorNSR^N%GHGVB+zrzg_Yk;4Vmfjf>3C0x z##w7%$!J*LHN7dRT%<`xuCVxUr%C8fO^6?Kj^YLqwrYpZK|`30ER)$}fpAC6f>JwJ zz7yn5lb+=Z#hg_Aoh@uEIq&&9BNr0*Fo1(>PeJsz4LBb3#_x)dj&L1YIP?aHlS2=n z*ZN@|l&8DTlBg`2>Dr%6JQ>Z&4U5aHCZ>4}6#ifp$Jg?i3LvCmbQ#Fj%iA$kp&c9M z)j>EX*+8LPMxIcx22DyhRL{1G9aGIS#Xh{Ujk8}u5EXa=a}WxMR>u!|xqcVdbWcvSTf^kpv#oIvJF%1dJ- zC&l-8&-?)l*WILB37F^hP+}B)CqmhRUX#MBe@8o>k3t~kX_6s zJPu7_$o~{inwWWYUTdg{Y8P`8B3a;fa;-qWM!;j}m`t(jSA1>zYRZniPoz>>VZLA6 zh@oXD1MERS11}$zY{nKvY*&={cq345(!YLDqa``Yy=OR8ui(sv7BqoW81gOW1h94@ zh0n~DjGn!}wjO$sGB92GEQ%!IKr&oCjpb?5#RL$On`;Stca;AyG@_Mbwd|J;9#7vMl5S@1xO(|>%@ z!d|QO7JBl`Mul`R(T&I!^o939(Q_I27>cSWukH4{7B20pCoIyW{j?XXR8UbgBA?ES zUC!2njk6#`J_uc1ntg~aJb*c4P6SW;}TCsk#H*F>hJ0c z_oIfJ_t&nV2s6f8)t)W1oNS?Px;m&0x3so*L(42FS#hj*io*pc;l`Q_`EUDps=D~# zuegI9S7PH;upPfvO7{dT`)e25ba1E*f0H$aJRkYnl4&#)St&0$KL8K349f%p!c5zx z+ZQPv1HAi*lH;S{QI3WZYPD1$@C^;iZ+_FK?RlrDG>E5!Wta^aQ6&__d`Ke>f4F*P zD$zko2gt5&GO>Pcu&OwaOSzY<22vf%B4bXH zJj{JXxTDGM*Lx71HB3f{lr1a^AEH!YN~TJnOd!gA=ttao1nyx{9en z*Z3{NyKn&vDN!v9jx+s3h>##w zMYG~JIo2a3l;M;MO*{C}aeVmE3zW8SF_*b$)T~XUbIzK=L@aq&3UIJXxY(%fil!>? zoN)+G&4V(^{#*L`(QhFu9cQ3^g7;VCEu zQhW#@h>2W}qu)GSa+9Tqpnv9{dokn1&n#BIOIS>$*v>~@g59=LNteS|wDyC+mkIt& ze!&)_&hLK^j4>(&9+PGZMkOQ+CDbCKWv((7jYsT~kqPAqaS4)-o9!ZqP~=&W<(BOWvkTL?d!l%L?|j1T3?=y5ds<4?T`IPNh}q0N<0V$zjtiXH$D_U6nfx() zMMBg(K91J%Q6Mn&RL=`_+3*C^o$Ffm)+zOnAbJ_$`q4c9xGJli)U=!;Op=naa%+-s zp-_}*RpOLRYZrcC==WA@PB!0!ZS6<{N+7%w5~Tw8J@k?}RggpvRMPIW&Yq_B2>L~6 zQ!;t^U1^~zphR8>KQX87%{?5$h93dNIIkB^OBs980R4}72uA|0J1l=Jtz7Fs;V>q%FeXUgu41#l{i%x)q~!rUih437Qui(0v=ClgJDZCQ7>y$F z=(T}T`KL`nC%M1Z6u-?;A4TK)AH)@`LD#Hpv1?cB<+Ho!ZYW{loLkdRBo#<3@CplL zdNgwS1XM_PTj#Rd_=TMGIR;{%-6`R=;H1gYV+LjsUs1xIe`sFqQ#4g0;8$d-=uqWm`Y22sk}_6!Z>SKVD5gcW-lxP}0eOE2 z1tA;^{rDJ91i4dOM;WRX=Ljz4pJ{C2?J1lwHfxkh)Tr1wTs+G<_)H=<*SJlb8#Y#{ z7f8dJ1^0+6fR~Bs#n(|qb@s5vMp{9+1=*{K_sciX<9(kLUJ(_yA=erEc(ry&O=*%F zTIF10pI_*VGa3<-W$)B@VaH5P1m8~0ZSYiI;lcLX4))e?{Porl7mxs9TfVrdt*IK@ zYu;J1eU{~7Ctt-JBf0eq3iqII*>C226n)rVsVr=&q~*M}xHpnZCQB#-2R|GPH9D-+DU9bQXQ{ci;nOg|B|Up(fPoVb}!aOx70 z!*ETUGH+YkfzSfK^87+FoWDN*L|l!@YS(63hr46Lz1&hX+8UvNY3Kp!ynC!OKE$}d zbgbK}{~1Tt{scV$40L{gAkEUdiK;*zyT!{M*q8*Sqzjq0W6SQiTVgRH7R01X39kVQZM`%uInVj*nO zw!s$S2==Uq{9{kqWcsB?N~9J#tvDnF(Id#LEH{uh{0`ZnG8E{5FOaJUVl~-1rhT2 zquIl2d1tTQ&@w~U#emYPwZrysb&mkp?S|VD zxAytqbF-MtWe<6p$vs`eq(;>)t*m)>qmomSmsDS&C@%A}n9!F^gTikIb%bVfq-mgbK&j z@rZ_MpDVn_tGAMiZ7HA8!8VIRS^eqA!cMuL`S4D0`gN`jn2mTTjr08w%hFJa7n8v^ zQL`Xlp`Q(|NLSSbD@JY2G~?;u$asjip9kWGE7D>WBQvmdJj@hdsbwX$z*6GJ)No9H z`)cm!Vw+Y$0Ir`y!D`Af)f0$%zQYCxQQ~5?@(D~vL zB$b0b8aZ=2FybpuC%jL@JF4QLQnJ}tVCa~Lm`UFBcxQe>KtH{}Yy}Md4}u>?QOKEH zm^$*W^4M5~hkDt_H518rdg}Kcr@4`lU!6p1^@UNr2`0?g-tR*dSDjK`?IYa#5$o0j zu_kh8T>yNJs4mb*vny!k45@Yt|4`5j2rEwO6Xg@sTHE+5HCpFz{wfdc@r})FC!NNK-ofD?1mx7cg zV^*Vupl3Fzos2L-+&>u&d`en|6bYgDS$YO&jKKd>w)LMYVTf9ywFDCaLX80e3M^O# znMMg@F|#*ubIZ~5F;1Cp`jazurPS$f9|Tdvgn>CM4?!-j3oQvN2N7bd+eiK&3m2Zr z$BD5Mukg{fV4(!o&d!E=-u74;qG*YsW^$o(v2$@xE1Du-EDOm~=+zH>ootqv%eeh zG#TuGaKfuJdllx>D*e@KuN^OjZEyU5apZPNT=d=P!))=zNiz9kw#k?h3lBeCEf#&T zK3Iehe%uDp?B&Fj+9!{f+9xpv)N9%F{B9jd`DFiFu0h|Py$|=jaWz83>0!Mp8E%7F za9!ov^(lGuz4sKs%h+ zE};%iz%sEX?jlSfyWk2wTAzc&O6zWkl^!yZv_<9yIbAv0fa%(bUUgjaFX|Qbc40@E zoaGF1L0GfvF{y5Arj4b#INwvG&<}LRU%m@tHa(3KVaoI8dwP-CTAj2jB&M@t?1vw1 zV8$ALw_J~&NBux#dwkqn^W|ep4e+cUE(kau|ARZIBHo)#6Q}9Jsj!wEl#4A+E-OBU zOUlQU%oKKtxNjm;%AeN;jP3ZqRs3dpzKzYpPE4wjt?BO}WOOlLI#k=n!Rd>8YPr$r zrhawMVzU<ilk8v|35JaYqeS1%h=eSM&eO5EPOJna#!n%KoK13;)olMMOgWjaIbgsgs*qN29w z!jpQ_SedldldJ@N%6!Ty*3eWuToBr^-(SB70n>|n-skMR&l6lAjG2wiuS{jB$vKxlZNR zf~d<;vmkAfX3>{O+tt-fMCDryt14Nz{rD)!4=2_f%h`G*Mt*Y}HIr2QoE%DBTHP7y z%T2RqR2>Fb2p-NF3BEuAIOqfW^;{y2gQhvA@zb`ga2Xo_tDqBwT(ZhflzU*lN*{^;V&ZeFf zx|fc}OPwEcX6_-0nCBU7+fOSbhdO$nU|8rhN^2|-X?3&LS)@25XGPpb%3SABxOi9V zbf>ckIAQv!Ly9EnYag?PIUo5B7$%Uzd-(H(dy>AzG^<6YBqdEl+Z%Y{E~*RVP4Klj+r%$-ePTSgUNVWJabIQ zp}k4oCix{LY{>&eWKMkivnNx1J3-bR&R%RA+I-cnEAGf%TgnoK^*>uX?1%2Oq)%|{ z2@CsxJ+gdrdP@MrLFjHa90w=lP#a~gC4;540W3Sq$bS4hTh?`l9G({q=mma%i9q9F zM)@Q}>MT5?uC|wNE9iS0U+K9k*1nZg%_ZyX!Q=>7Gu4F~anMcYpI`cfMDwlA`qN$J zzHGB(+$`q_N(AjR2t6L4pen=rL*v_u-2K4>l9k^xIejkJ3fR{Py=s`XlKcJ~lpxUg ziQ^Y|w|?1n9)$zdrI!p8iEO_L$0~&}im=MvIO%kLkDGrmJteoA>33cU zo#xGJ>&dxu%?UvKJI6h;;|1O?#qS?p-!to;!)S<*W|X_3a=Yx}R^z4>R7_w&-mooZ8yY>UJZX|{;`YcC=A47&?$%R^<0=(FQw{iUv ze0|z&W{^Cx8SAqwZ7-&6=yX4yT8kWS0DYoX={&#q#`{7GNJJ6oS>%&@M1wYsxRK*oCjE0eAm#>g4<#X*_uG0|m$)?{ zX^YXhf7XTTp=88wkAidJoWPzC2-iovNW^8#7S=SORSDv{=AZr3lRh1g5%`~@bgnxl zi|s7OEMY}+zkV6*$-t7c(efL>y9Hv~OWhndt4wjE`NoB+2(H;%-4B`6dS&ceB^l(U z@KPv!emqAcFY1qnE%H?O1zCdG5rQnuS)|bwB1&D>@C<}FFo9=*-sFhp(7&ccPwzfBk$B% z9l~vEz#w+WWgGIf9zY@#$=a|v+Hwb}&gg@@eDHOWnNbv}h^VtnBe>)97iDDpV{~em z@@X-lzMERt6U#qrf9~@S7O;Ok@{f-d;d`OEX89WPqzWfwn+;(Bf;?b)fBCU>e5ndP zPs2dn8)QqaMQGU!Kq-QO|49tw9>Mb`EwU3z4OHPB03?<@=ROBJ#tMH*62@OF)*a+^ zAMVY*_1evb=8H%PM2ng{9S9|?-CT`}qH4{B0daM^cUk@Haa63i6-a?CrqgT;-@N#p z{=_iuN0P}xGmAa$o?GhShgpn$eCMs<4$AAF3!x+P!1#hjQ48NM6#pA&M1ve<+c9F_ zb%$TNH??V&%H(7!Q%xNBIq{>JwLGO*TT4AWHMZtwqlGwEDT9qTq7I*U8vD{yXpk<)hSzWcIT_m78+L4n$r*BUrC?W^@FyiRqNV3diM4lJn~yPmz@7Br z97HRu!Q1i4;A*cIMDb7 zBG1|G21cDOsB%q#Y09Z+mmmETGVk3vF1hD90m7r1u=qZ3dvLj&7ac*pWo(1*y|zzm zc9jc4i)MAZ7k(sZ%$-u<=9n7<2LED8J$ebareI&!x=JGTKn}=~y|f zxW)EHT8qe-gU!|B-iS8$G)$DhvOF;Ebs(|)@q{zTCNrga^aEo+i)N}^-$YZ2m*wQw zOL-M*McGBi`>y*#keuWXf|E+0XgdOGBJW>B1hDd(<3g8mu~Y{JO8Y$ulB#&y-(Uvi z4hHD3e}|$?1ACf^y)!?2L!89A`O@APO7-YyDMELB!|r9+U3sl|Rh5&8m91tajB{of zeM9Iy{^J;K!*N#Q^9iKH|$QzXn&@Q^{~gnXz+c5z=7R8rk!Wt<9;;c+2(NEYRq+U&aN|s{n~H zWoa>~%z`vDsVf=F5m>V+6t3_wIuMRhW?il_Wn5mcHkz<{or+uD+6vnn8(W)LDxfLC zrC65`4p`q>^{XT>r18*!SqPu2I;5g_86xrnqKSsMF*>A`y4_PiQtoB6}qZ z9S8Q*o|C^kkhmRF4jcpB_Ngd;qZndf)nW#KYhB59&@>QeD_n##^=8dPx0v&8q`i2{ z81^RoRO(7Cdm^sKZ*& zJDY&RH*PCW%fsoPG2nIeIx07&cIZ%1I}42JIY;sHg=O@c>YG~8JvY>Rh3j}=)JY}o zExnFL>5q!88sNMnb7jQ$%;ME7{}32M*oNzI^BU{oX+cV(tV1SF`gCXkdL>rwiY?q^ zIIJVM*5ck6#N60b_~Jq=GFMj@xuW@X;u+zOvdR;`@|tHA#w+xnFD_2huQ9aMs*XVV zz_?B8m^3z#+mcPs&#yrD?~n_GXS4VgTP?0g;hym8ckL?WNp8TGTwWPGrw<4Ft?6au z{;nMzojdmwI!*0AgfG!Ob+B8l=Dn{yw&hZ+UrFp8M!Fc1s&WU45CGjgxPeHCpFzjW zFN=9$)z8h}iK^kTxEx+=9Nm87eyaq+&ns0>hz1)6gu#tmE7+)8AbGCovwBHkR*OvG zq$}h}aFe3tmkmtmXdITc-7^c{2`=aXe|SE#)*P%b-($VpSrzsS_Eo549MNjr&Eq#7 zJ&j1HtXG{^0^*&f?owmQoU1!)*M1V0x=oGqzE}IzDUkcEHlFsZGC(LGwQCr-I?M;1 z*O{zb5KS)pKvf!uGHHW-cyEQSLVQ&88K^i1r*$$9Sy|*PynLR+L0ipmvGr$e!u%DB znP@0`_D>Qh=V7->a1`CM)Ja56c-i&_$$7&15ERxP7WFp8qSS@sQsswT@l$i zC$%0~t?@56*`$N7aJRhmT~Wuq%bR0?tWO`-_*+w79E!VgUJhhi^gHf9IGz3ZS2pp# zw|*O#&X8Zg+rDO?^;TBku@OLrAkdO^tX>g8l_iMgh-R;zNnwg=1%f_xsxK76MuLcH z{xCF$3B)33?u9F1r}Uv(|J+Ndda=Sg>hm0o{N=3@F!1g}*G--(@Buc@&C+wpA_A^! ztm9zpN#r0P@6OfpkI(sO7X)9VsMK^YEKXi5#iuY^Di^JOQ*0BU)n&G~Q0#2 z>)h6Ct9A(9WwLii)ea|v2$2*qZsAgtj_n1%fyU(np+97;^aZ$5di2c6s2bM}BSR;l z6cH}P{bz5WaIjP49dFn`OXyyU-374$*62CXr}HoOfiR0jhd=DN?;#EG1VphDNTUXi z83L<}%ebAgw0VF)dzYDs%w;aG0hVKAU!!gw&Vb#==ltMba-2Eyr{WW;&flJo)vPk7 z8;(Tajh)9vN}gu;g;UudEjcVVv3yyailwXC_=U3wIU5EDe0H|blk5)Mq&J3ECbT=B zFXVblM|$@JMd0Q0v)Qef@#)dZ7g}u(uD1=ovB~&5&ZTYvV>5jq*E?Ul-N^gM zUYy^S9p3){q`;B!zfRcL6i!Mb1~rTPX0lTiuglPSmh4x_NmFM#Os{efiwTvsuUtZl zyL9XV*ytdoIiOf+6s6H$Z5a^-rfK=$>3M{9$SQT`vMwR%Gor-fj46(wXX=bUBXXvf zrMVhXZLkXELG3SXNvb)!ui|zZ>P-H6FrDV^XXO3{JknUNI>X)9DZ(`Dl@#NvHoWR$%RTq*?my-Z%Cy*$$tMo&9*jsKbr5AdJ8 zu%TN}J-R=tT3Vy{MC{8zjWKzY8C(YomN2^r{{8*)qf`&;{ozIz;%kC4x78KyU8=xF@nR17o@skflDx9B@tzSs~X zlBIir?9ro`U}IF$AMpINTpdAqZo1a@uz_Dp5WCdT+w(TZmTq;YFYKPB`<<52v)XiA zBzn&;{9q&2GWpSuhiJ|AT2dQy0!yO4bNt}M1F|+Gatno(aX&-y`iMHP3wjq&nGrf} zE5*mNpjxqiF@Y%!7u{)&79XP@}t0 zZ$`h1S)@XYPB2>Gtdg=50QY)O#!IwW7Jn35F zXR&Q@o>2v97gS|(9)ncRIS92cKLrOl+bzw+OfVPd(ch3nVji1?}g05GK zkzo&IFnEtg(i?FvW1bw@%O~BTwv({s68?&in_>hV3;;pXv#zY;yBFqjMDZrCUym|&VxWX(NqKfd!q^bN2Nx_~B z+qi6@LEa#z;+!-?=z`3;CKtIk8Y+WGY2tl*wZMLFKEkKVMUD0yx}-`IuEEcLL+V(CeK%vCEP=340( z(93<@Z>#K1K10f31hSYeC|zuDq|r#~(d;dq>|=Zr5>eFR(zGn7brV93&Fyz0& zrjL`&vs0r*#BApOx!$npJb89b|IWI$bBFpyO2=0x3BkEtdyX4V7?Qp8kH_d}hKBv5 z3#O=wm*JL{TU`2ROs=0FUoB~BKknWV9qxy&1J^G1#^T08Cba5MI_9wM=q==bEfYYO zi-e$`tz`eGX*Cdcx-EqZ_#1fT8gJ1CoRM zM$iGri2L?UV12LzVh9NGH{c%PU$A3@4{90zZ*vTv1VlfI4xslf?(G0alt-cbOY7l3 z-G&MN)rWu#6g`R#n{zu1;|t0g#erHN17VC2fu={Ph-kJ4!KFpPF>&}15Dfq63T|bA z(8qM3)>yz};DUf-BmluxS|K2K8ODna0YUKw9bg4RRG{@Sbii+m8+#V;*KN)S0YUZ# z3gZPs0FWv#9Efq80qRoW?}xJ=2LS$EM++<%!}q37j`ClKbOIGLFir<}Q>p0%g!OgOx0$(sh2^I^&1H4h>c>~4yfgw7uI1e@8 zzjZ_Y!|?*a5H~0td^Z1o-tb_+Q9(mK*TjnX>=k zB%=O8XR}P;to8p*`}yy;Yfk(N>CG_$-lSIiXBcp+5d_MgQvv)tL>+vk@V)`M(!c;c zh%pTv1dh-L{5#b5|1-w6?*ev;L0sub@&m5b^>#;7wfKTR8WBU>d-i z;HNiGf5G1~Brd=JZO)+s-o(JX0RhE-LFz>o!2bykfS`E;>6d{a3J~QeF5usPslagr zZy||qpz|LTpuI&lsL&p8ljWaF0Hn1<2vyz>a#^y3@*e~(FIhmT41?Y;vjhI`3=5t@ T{?8+S7! { - let warned = false; - return function() { - if (warned) { - return; - } - warned = true; - console.warn( - 'Calling .' + - methodName + - '() in the test renderer environment is not supported. Instead, mock ' + - 'out your components that use findNodeHandle with replacements that ' + - "don't rely on the native environment.", - ); - }; -}; - const MockNativeMethods = { - measure: mockNativeFunction('measure'), - measureInWindow: mockNativeFunction('measureInWindow'), - measureLayout: mockNativeFunction('measureLayout'), - setNativeProps: mockNativeFunction('setNativeProps'), - focus: mockNativeFunction('focus'), - blur: mockNativeFunction('blur'), + measure: jest.fn(), + measureInWindow: jest.fn(), + measureLayout: jest.fn(), + setNativeProps: jest.fn(), + focus: jest.fn(), + blur: jest.fn(), }; module.exports = MockNativeMethods; diff --git a/jest/setup.js b/jest/setup.js index 17aa9ccc5f9369..e40da36599bfb0 100644 --- a/jest/setup.js +++ b/jest/setup.js @@ -12,7 +12,6 @@ const MockNativeMethods = jest.requireActual('./MockNativeMethods'); const mockComponent = jest.requireActual('./mockComponent'); -jest.requireActual('../Libraries/polyfills/babelHelpers.js'); jest.requireActual('../Libraries/polyfills/Object.es7.js'); jest.requireActual('../Libraries/polyfills/error-guard'); @@ -46,8 +45,11 @@ jest .mock('AnimatedImplementation', () => { const AnimatedImplementation = jest.requireActual('AnimatedImplementation'); const oldCreate = AnimatedImplementation.createAnimatedComponent; - AnimatedImplementation.createAnimatedComponent = function(Component) { - const Wrapped = oldCreate(Component); + AnimatedImplementation.createAnimatedComponent = function( + Component, + defaultProps, + ) { + const Wrapped = oldCreate(Component, defaultProps); Wrapped.__skipSetNativeProps_FOR_TESTS_ONLY = true; return Wrapped; }; diff --git a/package.json b/package.json index 56fbe4d00eba36..ce6eff3fe853f7 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,11 @@ "README.md", "third-party-podspecs", "template", - "local-cli" + "local-cli", + "template.config.js", + "!template/node_modules", + "!template/yarn.lock", + "!template/package-lock.json" ], "scripts": { "start": "react-native start --reactNativePath .", @@ -92,10 +96,11 @@ "fbjs": "^1.0.0", "fbjs-scripts": "^1.1.0", "invariant": "^2.2.4", + "jsc-android": "^236355.1.1", "metro-babel-register": "0.52.0", "metro-react-native-babel-transformer": "0.52.0", "nullthrows": "^1.1.0", - "pretty-format": "^24.5.0", + "pretty-format": "^24.7.0", "promise": "^7.1.1", "prop-types": "^15.7.2", "react-devtools-core": "^3.6.0", @@ -125,9 +130,9 @@ "eslint-plugin-react-hooks": "^1.5.1", "eslint-plugin-react-native": "3.6.0", "eslint-plugin-relay": "1.3.0", - "flow-bin": "^0.95.0", + "flow-bin": "^0.96.0", "flow-remove-types": "1.2.3", - "jest": "^24.5.0", + "jest": "^24.7.1", "jest-junit": "^6.3.0", "jscodeshift": "^0.6.2", "mkdirp": "^0.5.1", diff --git a/packages/eslint-config-react-native-community/index.js b/packages/eslint-config-react-native-community/index.js index 19b3e3abbb61b4..3c40a09a5dd9b0 100644 --- a/packages/eslint-config-react-native-community/index.js +++ b/packages/eslint-config-react-native-community/index.js @@ -290,6 +290,7 @@ module.exports = { // React-Hooks Plugin // The following rules are made available via `eslint-plugin-react-hooks` 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'error', // React-Native Plugin // The following rules are made available via `eslint-plugin-react-native` diff --git a/rn-get-polyfills.js b/rn-get-polyfills.js index 121fa00ab66543..a0041d6ef17410 100644 --- a/rn-get-polyfills.js +++ b/rn-get-polyfills.js @@ -10,12 +10,7 @@ 'use strict'; module.exports = () => [ - require.resolve('./Libraries/polyfills/Object.es6.js'), require.resolve('./Libraries/polyfills/console.js'), require.resolve('./Libraries/polyfills/error-guard.js'), - require.resolve('./Libraries/polyfills/Number.es6.js'), - require.resolve('./Libraries/polyfills/String.prototype.es6.js'), - require.resolve('./Libraries/polyfills/Array.prototype.es6.js'), - require.resolve('./Libraries/polyfills/Array.es6.js'), require.resolve('./Libraries/polyfills/Object.es7.js'), ]; diff --git a/scripts/bump-oss-version.js b/scripts/bump-oss-version.js index 8ee5d10c7acd04..a9acfc52f83e62 100755 --- a/scripts/bump-oss-version.js +++ b/scripts/bump-oss-version.js @@ -24,7 +24,7 @@ let argv = yargs.option('r', { default: 'origin', }).argv; -// - check we are in release branch, e.g. 0.33-stable +// Check we are in release branch, e.g. 0.33-stable let branch = exec('git symbolic-ref --short HEAD', { silent: true, }).stdout.trim(); @@ -100,7 +100,7 @@ let packageJson = JSON.parse(cat('package.json')); packageJson.version = version; fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2), 'utf-8'); -// - change ReactAndroid/gradle.properties +// Change ReactAndroid/gradle.properties if ( sed( '-i', @@ -113,12 +113,17 @@ if ( exit(1); } -// verify that files changed, we just do a git diff and check how many times version is added across files +// Change react-native version in the template's package.json +let templatePackageJson = JSON.parse(cat('template/package.json')); +templatePackageJson.dependencies['react-native'] = version; +fs.writeFileSync('./template/package.json', JSON.stringify(templatePackageJson, null, 2) + '\n', 'utf-8'); + +// Verify that files changed, we just do a git diff and check how many times version is added across files let numberOfChangedLinesWithNewVersion = exec( `git diff -U0 | grep '^[+]' | grep -c ${version} `, {silent: true}, ).stdout.trim(); -if (+numberOfChangedLinesWithNewVersion !== 2) { +if (+numberOfChangedLinesWithNewVersion !== 3) { echo( 'Failed to update all the files. package.json and gradle.properties must have versions in them', ); @@ -127,13 +132,13 @@ if (+numberOfChangedLinesWithNewVersion !== 2) { exit(1); } -// - make commit [0.21.0-rc] Bump version numbers +// Make commit [0.21.0-rc] Bump version numbers if (exec(`git commit -a -m "[${version}] Bump version numbers"`).code) { echo('failed to commit'); exit(1); } -// - add tag v0.21.0-rc +// Add tag v0.21.0-rc if (exec(`git tag v${version}`).code) { echo( `failed to tag the commit with v${version}, are you sure this release wasn't made earlier?`, diff --git a/scripts/circleci/gradle_download_deps.sh b/scripts/circleci/gradle_download_deps.sh index d1877c89d5956a..dd3d781dc926e9 100755 --- a/scripts/circleci/gradle_download_deps.sh +++ b/scripts/circleci/gradle_download_deps.sh @@ -6,4 +6,4 @@ set -e -./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog :ReactAndroid:downloadJSC +./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog diff --git a/scripts/objc-test-ios.sh b/scripts/objc-test-ios.sh index 10d0f00a81e54f..48100cc9eecb05 100755 --- a/scripts/objc-test-ios.sh +++ b/scripts/objc-test-ios.sh @@ -11,7 +11,7 @@ # also run the RNTester integration test (needs JS and packager): # ./objc-test-ios.sh test -set -ex +set -e SCRIPTS=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) ROOT=$(dirname "$SCRIPTS") @@ -25,6 +25,7 @@ export TEST_NAME="iOS" export SCHEME="RNTester" export SDK="iphonesimulator" export DESTINATION="platform=iOS Simulator,name=${IOS_DEVICE},OS=${IOS_TARGET_OS}" +export USE_MODERN_BUILD_SYSTEM="NO" # If there's a "test" argument, pass it to the test script. ./scripts/objc-test.sh $1 diff --git a/scripts/objc-test-tvos.sh b/scripts/objc-test-tvos.sh index e58ac222f5a0c2..281dd46728d15d 100755 --- a/scripts/objc-test-tvos.sh +++ b/scripts/objc-test-tvos.sh @@ -25,6 +25,7 @@ export TEST_NAME="tvOS" export SCHEME="RNTester-tvOS" export SDK="appletvsimulator" export DESTINATION="platform=tvOS Simulator,name=${TVOS_DEVICE},OS=${IOS_TARGET_OS}" +export USE_MODERN_BUILD_SYSTEM="NO" # If there's a "test" argument, pass it to the test script. ./scripts/objc-test.sh $1 diff --git a/scripts/objc-test.sh b/scripts/objc-test.sh index 08ef7205ed2fdc..daa03b7ae1046e 100755 --- a/scripts/objc-test.sh +++ b/scripts/objc-test.sh @@ -12,15 +12,11 @@ # also run the RNTester integration test (needs JS and packager). # ./objc-test.sh test -set -ex - SCRIPTS=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) ROOT=$(dirname "$SCRIPTS") -cd "$ROOT" - # Create cleanup handler -function cleanup { +cleanup() { EXIT=$? set +e @@ -34,14 +30,13 @@ function cleanup { # kill whatever is occupying port 5555 (web socket server) lsof -i tcp:5555 | awk 'NR!=1 {print $2}' | xargs kill } -trap cleanup EXIT # Wait for the package to start -function waitForPackager { +waitForPackager() { local -i max_attempts=60 local -i attempt_num=1 - until $(curl -s http://localhost:8081/status | grep "packager-status:running" -q); do + until curl -s http://localhost:8081/status | grep "packager-status:running" -q; do if (( attempt_num == max_attempts )); then echo "Packager did not respond in time. No more attempts left." exit 1 @@ -55,47 +50,78 @@ function waitForPackager { echo "Packager is ready!" } -# If first argument is "test", actually start the packager and run tests. -# Otherwise, just build RNTester for tvOS and exit - -if [ "$1" = "test" ]; then - -# Start the packager -yarn start --max-workers=1 || echo "Can't start packager automatically" & -# Start the WebSocket test server -open "./IntegrationTests/launchWebSocketServer.command" || echo "Can't start web socket server automatically" - -waitForPackager - -# Preload the RNTesterApp bundle for better performance in integration tests -curl 'http://localhost:8081/RNTester/js/RNTesterApp.ios.bundle?platform=ios&dev=true' -o temp.bundle -rm temp.bundle -curl 'http://localhost:8081/RNTester/js/RNTesterApp.ios.bundle?platform=ios&dev=true&minify=false' -o temp.bundle -rm temp.bundle -curl 'http://localhost:8081/IntegrationTests/IntegrationTestsApp.bundle?platform=ios&dev=true' -o temp.bundle -rm temp.bundle -curl 'http://localhost:8081/IntegrationTests/RCTRootViewIntegrationTestApp.bundle?platform=ios&dev=true' -o temp.bundle -rm temp.bundle - -# Run tests -xcodebuild \ - -project "RNTester/RNTester.xcodeproj" \ - -scheme "$SCHEME" \ - -sdk "$SDK" \ - -destination "$DESTINATION" \ - -UseModernBuildSystem=NO \ - build test \ - | xcpretty --report junit --output "$HOME/react-native/reports/junit/$TEST_NAME/results.xml" \ - && exit "${PIPESTATUS[0]}" - -else - -# Don't run tests. No need to pass -destination to xcodebuild. -xcodebuild \ - -project "RNTester/RNTester.xcodeproj" \ - -scheme "$SCHEME" \ - -sdk "$SDK" \ - -UseModernBuildSystem=NO \ - build - -fi +runTests() { + xcodebuild \ + -project "RNTester/RNTester.xcodeproj" \ + -scheme "$SCHEME" \ + -sdk "$SDK" \ + -destination "$DESTINATION" \ + -UseModernBuildSystem="$USE_MODERN_BUILD_SYSTEM" \ + build test +} + +buildProject() { + xcodebuild \ + -project "RNTester/RNTester.xcodeproj" \ + -scheme "$SCHEME" \ + -sdk "$SDK" \ + -UseModernBuildSystem="$USE_MODERN_BUILD_SYSTEM" \ + build +} + +xcprettyFormat() { + if [ "$CI" ]; then + # Circle CI expects JUnit reports to be available here + REPORTS_DIR="$HOME/react-native/reports" + else + THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd) + + # Write reports to the react-native root dir + REPORTS_DIR="$THIS_DIR/../build/reports" + fi + + xcpretty --report junit --output "$REPORTS_DIR/junit/$TEST_NAME/results.xml" +} + +preloadBundles() { + # Preload the RNTesterApp bundle for better performance in integration tests + curl -s 'http://localhost:8081/RNTester/js/RNTesterApp.ios.bundle?platform=ios&dev=true' -o /dev/null + curl -s 'http://localhost:8081/RNTester/js/RNTesterApp.ios.bundle?platform=ios&dev=true&minify=false' -o /dev/null + curl -s 'http://localhost:8081/IntegrationTests/IntegrationTestsApp.bundle?platform=ios&dev=true' -o /dev/null + curl -s 'http://localhost:8081/IntegrationTests/RCTRootViewIntegrationTestApp.bundle?platform=ios&dev=true' -o /dev/null +} + +main() { + cd "$ROOT" || exit + + # If first argument is "test", actually start the packager and run tests. + # Otherwise, just build RNTester and exit + if [ "$1" = "test" ]; then + + # Start the packager + yarn start --max-workers=1 || echo "Can't start packager automatically" & + # Start the WebSocket test server + open "./IntegrationTests/launchWebSocketServer.command" || echo "Can't start web socket server automatically" + + waitForPackager + preloadBundles + + # Build and run tests. + if [ -x "$(command -v xcpretty)" ]; then + runTests | xcprettyFormat && exit "${PIPESTATUS[0]}" + else + echo 'Warning: xcpretty is not installed. Install xcpretty to generate JUnit reports.' + runTests + fi + else + # Build without running tests. + if [ -x "$(command -v xcpretty)" ]; then + buildProject | xcprettyFormat && exit "${PIPESTATUS[0]}" + else + buildProject + fi + fi +} + +trap cleanup EXIT +main "$@" diff --git a/template.config.js b/template.config.js new file mode 100644 index 00000000000000..7e251cd178c112 --- /dev/null +++ b/template.config.js @@ -0,0 +1,4 @@ +module.exports = { + placeholderName: 'HelloWorld', + templateDir: './template', +}; diff --git a/template/_flowconfig b/template/_flowconfig index 24b99424cc6930..e8f6ec18d45ae7 100644 --- a/template/_flowconfig +++ b/template/_flowconfig @@ -11,13 +11,20 @@ ; Ignore duplicate module providers ; For RN Apps installed via npm, "Libraries" folder is inside ; "node_modules/react-native" but in the source repo it is in the root -.*/Libraries/react-native/React.js +node_modules/react-native/Libraries/react-native/React.js ; Ignore polyfills -.*/Libraries/polyfills/.* +node_modules/react-native/Libraries/polyfills/.* -; Ignore metro -.*/node_modules/metro/.* +; These should not be required directly +; require from fbjs/lib instead: require('fbjs/lib/warning') +node_modules/warning/.* + +; Flow doesn't support platforms +.*/Libraries/Utilities/HMRLoadingView.js + +[untyped] +.*/node_modules/@react-native-community/cli/.*/.* [include] @@ -43,26 +50,46 @@ module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' module.system.haste.paths.blacklist=.*/__tests__/.* module.system.haste.paths.blacklist=.*/__mocks__/.* -module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* +module.system.haste.paths.whitelist=/node_modules/react-native/RNTester/.* +module.system.haste.paths.whitelist=/node_modules/react-native/IntegrationTests/.* +module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/react-native/react-native-implementation.js +module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* munge_underscores=true module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' -module.file_ext=.js -module.file_ext=.jsx -module.file_ext=.json -module.file_ext=.native.js - suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError +[lints] +sketchy-null-number=warn +sketchy-null-mixed=warn +sketchy-number=warn +untyped-type-import=warn +nonstrict-import=warn +deprecated-type=warn +unsafe-getters-setters=warn +inexact-spread=warn +unnecessary-invariant=warn +signature-verification-failure=warn +deprecated-utility=error + +[strict] +deprecated-type +nonstrict-import +sketchy-null +unclear-type +unsafe-getters-setters +untyped-import +untyped-type-import + [version] -^0.95.0 +^0.96.0 diff --git a/template/android/app/build.gradle b/template/android/app/build.gradle index 1ddcb478006e36..51342de4d06cba 100644 --- a/template/android/app/build.gradle +++ b/template/android/app/build.gradle @@ -18,6 +18,9 @@ import com.android.build.OutputFile * // the entry file for bundle generation * entryFile: "index.android.js", * + * // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format + * bundleCommand: "ram-bundle", + * * // whether to bundle JS and assets in debug mode * bundleInDebug: false, * @@ -93,6 +96,15 @@ def enableSeparateBuildPerCPUArchitecture = false */ def enableProguardInReleaseBuilds = false +/** + * Use international variant JavaScriptCore + * International variant includes ICU i18n library and necessary data allowing to use + * e.g. Date.toLocaleString and String.localeCompare that give correct results + * when using with locales other than en-US. + * Note that this variant is about 6MiB larger per architecture than default. + */ +def useIntlJsc = false + android { compileSdkVersion rootProject.ext.compileSdkVersion @@ -140,8 +152,8 @@ android { applicationVariants.all { variant -> variant.outputs.each { output -> // For each separate APK per architecture, set a unique version code as described here: - // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits - def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3, "x86_64": 4] + // https://developer.android.com/studio/build/configure-apk-splits.html + def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] def abi = output.getFilter(OutputFile.ABI) if (abi != null) { // null for the universal-debug, universal-release variants output.versionCodeOverride = @@ -149,11 +161,25 @@ android { } } } + + packagingOptions { + pickFirst '**/armeabi-v7a/libc++_shared.so' + pickFirst '**/x86/libc++_shared.so' + pickFirst '**/arm64-v8a/libc++_shared.so' + pickFirst '**/x86_64/libc++_shared.so' + } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "com.facebook.react:react-native:+" // From node_modules + + // JSC from node_modules + if (useIntlJsc) { + implementation 'org.webkit:android-jsc-intl:+' + } else { + implementation 'org.webkit:android-jsc:+' + } } // Run this once to be able to run the application with BUCK diff --git a/template/android/build.gradle b/template/android/build.gradle index e7078ff33e250d..6b738db44078c0 100644 --- a/template/android/build.gradle +++ b/template/android/build.gradle @@ -23,11 +23,16 @@ buildscript { allprojects { repositories { mavenLocal() - google() - jcenter() maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - url "$rootDir/../node_modules/react-native/android" + url("$rootDir/../node_modules/react-native/android") + } + maven { + // Android JSC is installed from npm + url("$rootDir/../node_modules/jsc-android/dist") } + + google() + jcenter() } } diff --git a/template/android/gradle.properties b/template/android/gradle.properties index 89e0d99e2173fc..027ef9db8a3001 100644 --- a/template/android/gradle.properties +++ b/template/android/gradle.properties @@ -16,3 +16,6 @@ # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + +android.useAndroidX=true +android.enableJetifier=true diff --git a/template/android/gradle/wrapper/gradle-wrapper.jar b/template/android/gradle/wrapper/gradle-wrapper.jar index 94336fcae912db8a11d55634156fa011f4686124..457aad0d98108420a977756b7145c93c8910b076 100644 GIT binary patch delta 46476 zcmZ6xb8IeN)HK@DQ`@%f_UWl@+qUiYIkjz{+O}=mwrzdCyx+~6dvCILvj3c9PiCz( zv+kOIcQyQu13>dM+|BWVfPkpP3n+mHG~PS?itojZZ*hYuUo%_%4Gscw4xM;Sgq^7H z3m>SCs*d&@lWt;w2W~777!e3SVF+(pR;z84>LU6@|I0>X17VCfO3rM4Y*6|J)B6ju z`?*M7x55{?v3h-J5sWa2zMWn6d5LVp=5`^ ze07xJ-I|qUI`_kSy<~#L@btxT{x#Nq7emsfYC(V8Fu}s{32~~R23#C^g(y<(AHzWPVt@uRw|XX z{9tVfaWf@K-q0JIyZRNVuzp~`fI3hn$9>@&+wz$M^`#as4%6Dz+s-23(H&X+t%KET zO*yensE>hf^r7B*RZUbZzP!OmBRER0>Aqs~?)HgNtJIoYSc^HRLDeaxsX{IX_K)Ih zwG_2s#XeL_IcMk!_OtB(YT&v@et$1Mw!3N?)mW{{Dpkd+0G;^mXlbc~qAJi4@rBy- zJUvN2KS^^Wq5ZNeQb&cSS9o0eRwhilROybG&**d;O<%iPxUu3DD_3C%^f9qmcP~I3 zJ$K(C&ORIC+;c$#qs_N?-`OW`U)o31;;T?Buux1{xFrl_{3kx5<_>$tfCT zhYD$6NV3BUgKff%J(1?dX$q)&lz}J%>tm%pGh-Qo9tI1s)T>bqgSftXH*O#`)bc7< z_{=-7`_ril_tA18+%A|Y4nO8b-^3xc+Jt#r9JVe}f9E9=4Z!p+uTEdfPbZ$rNpS+d zLZNj6eAMpWD?Nz9@ic0Aw4`;cxQ1%8u_p9QLXp@SlS7CLTTj+-=>REV+>R<@9MC#P zGWy?@;(dZU5^aY?_4mQ|xMbH?PKNFVmkpc14e+%TasV5vbhdSp-4?SVa2GkgsqODJ z1&lv>Y;Wa}3EHA6i=C~^aN^n!Qn(@nR#rNW48w1 z?q?BkBROt7+hqS}n}Nbo^a2}k%Mhnxht1QNXNf!lJ7#y7ps1vA%f^o>#gql+9v1MI z>g$)ij#{KS4*YVnVM=<_Y7R^5$^**+uFv z;;c_Kgiq2M5y%;qYFQaXmvG#ogAevj?dGq?gY&o6aGs+?_kqY_0Tv8wGB@7#gGD_^ z-gJ|G!ia$qa`y?`{&HF$RIk7K9gzke=lG-jboveH1O{yTwf>>Tf1>mEdEFZUqL2}h zMyQm)$b@=u=Nn<;hrqK2rO}Sqs!DEI{4x0C576hiluP>gy_2W)NG17Kona^@u;S+E z3syc5$4(Q@UVyMn$EH(Ea`C5`%I06@<1|7uXOyQ?@Zv93hCukc^!O6gJXWPxms4>( zg0pxz=me|NNP^P6U1O2`J$(Yn%aW5Hp1wStM*dwZ{LqzuUuVf`vkTH?&X*(_DHN(bA3Dt9mrlGX;Au7gjYg7j}NG!PId zsQ;8x5X3_9+c*Ee*ZhB%H1UoIBhg_JDsc@06|{*a@fv~wAJ; zEoiO}>x@IogOQimwxSh7>~5hbNmDlM+fw3$$mKTMP6G@x57RHmZH0bEcJ5E(EK5VrqC*#(IQlt&dr<87d~(QVdHw-r4U7TC;pi`1W6OpC)o zNwktsKp9w=m0M?@kYml(dXJQuh4e+v59jYCH0@M|;h06+3e8UUp84~#*_N)~)AIx7 zKp2ZJW3cQWsfp`MJ;~h~9*l;etiiIOjgaEFAqd(XjgIS%HJlslSena9_W6kc6t1VU z4%jmyj~cpozK&6_f0YwKt|6)5S>LB&JMm*G!!?X_!j-yqU0H7@Ho{kHONr?7#U2q# zCf&Az?nk3lS_*96Z;}ARAIg-?3&5;L6;D`6nTAQiDn1voN9rO~wI#wFPcxjGQQv+* zctyg;}HcmP;ZpeEDJAqzjLZG@#z#YOX8e(7^T@* zY|*jF_SkC+t+k%y|NYBwXTP5|0g_0!uHkCgjwll!%0a-4_I<;GT~9eIM{Xv@F%@@) zOiHj4IAtdtsT%^#ib)P#)&V!+X9|+9inSvB)<7IWDg5_GVk#>p9-4jv2w%ONv~NOk zcG5Bd*G5dQIh$JFcuazSfLXL8EF4eK{Zp7!<# za_f-1OjbYoG*&}p<th#D#7FT%#Y!eAAGjr^`iexi66?DwW@hAGX7CTT`+D;#E*FyY>|GnMIpY}R}#Ic)e_3xVv zltK@2FZV8*>l5~yJxCh(3PAm-Hv1ONew1Z@kp2BrcKF7)xDs$A zrfkPz8c2;R+op-{5J@Z>VuPZ=Tb6X-N_pk7E(zQJmE-Mar9MpPuH;&s~WJ(e7puEl`J{PGMAU*(i5-I+azYVxJ1Jb~3U9nAT->uwM%cN^ktWymE(9X{nStF-mLUJk4yY~$AIxAJ z`%IS=4iISJ78=8RqEM34H``A-z;BW#Z(_Kv&Bm+m3c>-NQ2ze*2q@O;h#N(l(^aS^ z6xjk)3}+Yu*fX#uJf=6PX3c8sltiAGcL?~LM{_2vjbjeiVC*c$vooDOWBUAacv6El z>HzUBY>)#4*oyhg#p9CLT%>aA@`d-EIO8uYAkSHZZWK!-004%^bD__O^qM40>Rp1f z+|^NYhzxN3&Mo@S8&lDNys4PD>Yz&YX+BTc0_4`)x zR&H}~X2M!823vUA_8CBHc^ops(W*n)O!u|3!CM`mUHNJC zn-tYZa7d(ayAE~}CHpxFpWK?I`NlGDnPK)X06;aYQtEA!^Z2z*cAI#F(A{!EmE0wV zHG`}rW)tU(UK|zEB6@R{Ry=Z`M9w+!zCEMktF~q2IRRRO9H(Aiabd0O4Cg( z=@K}5x>|vGnmiRSBy+weGyk03mrrh2c^T)_)O4hTb37?Y;5GeJD%*O=iC+~^B#SN& zRn|sK$)5kWjR7z^u)n`nw68NOd!o>^ESoBKeIJ5-n3tA$7lNN`6m=zJ&{E+{mo>DZ z`cMmwnTSCUU`||xLx>G~(>uVn(|RM{EDxlqRY<_CA4=G`wG{jSy=^r7Mi=I*Uy9qH zU!&NdZxg<5EL3*W@Ibq%zeVFS8`E_H{3?&5)a44#j-R(N!bvd_J4@gKmRMca9qO%_ z?pg8}@6qTLLMQFot{q%`c5E%^;3WnyqxEXLyS8V>1E zFnbdYoCXt6$Klm1cQu^|^%@Tgt*+Ib`2KkzmJgc^ILm8#{89&FANRJD4hd%V*jugk z@6PG=o$F)!P2c`=I`>^F7H#pzMg+Lk5QEvIoWyYj_fQ@-O(MMLUI1?IZ}5^ z$7efMI`1SiOfNZV84$I;A=f%q0b(I9yK@nNHe0zRC9eY*|UzGT;+i3YJ zlMMYQb8)d@aYu%D>zwa;xBgfh_OBrn{&rqUhLhYphi)*V-VJ=Du;2vc`xP`szOaCL zCAOF~F_FO(E8NL0;uV>f$pt7eLMup{C7u54GhVv653a(umOH|G)EY%@u8LtrWX+!$ z%1+BaKCqO(>0snG?67e42MxYXox|4JK)Z{1tBx7pc0Fx7@5h$rOv_1b8daLc#AD{# zh`zy%7;Hmlw2fyOsfu>%My2aMSt|mv4CQ`a7iUBzFys9$ZGLzcs zsX1g_!Sh_KftjFX6lpL7f4@P&+yyqDg8kNst6z0!fIun5EZaQu5O5(%~zmT z*TgG#EVH7~dTJKgwL6#|AC$UGe&R?Ge`Dp1Ms0r4F(msCIVquhWF=k2uCS$2MyfXT zr=ijVR=MKWNgf>Sf+kRSODS)-Oh+D|u)y+DO{Lh>xHz#Symf?5<$A(OyL)!uSbQdu zio=l-EYj^X7ZT&D!=ym!KZpE z%PtdtR8^GOF=ESDY?F!OHXc!=o1rztRb=`0Yx_@9wZq44^ghsh-XtDJxt$?hITN4r zpGozf6LKGje}Ws&kEQ8W-Ku~1vs@O+P4eZ91`cBSu>-%p?AB+V-l6}#@(+yB{J8?9`V$Vq>YnhZSBj&X7n4%&7LALv*(oy zB3RCf+tBZ0bOwQ`eR3vzn%!~5zR4x`BetLXK{QMXeAGOk*HyJAgPpuZC6nH2m7jjr zjm9Ld%e=xXJDPnllr(A0^THz7mDc0xJ(`+&lEP0AwtAdm5u>@@`2bhOLt1p-2Kj{D z(hJ$uZ#;Awjq(5(PnyaRoJYZ3qA*p7UA7N%5EbIFI}zX*XK<|-buFPf4h>T@FO|xQ}$&cD|hI7y>ai{E0bF~^P%9e6v zxr;n=#WLM?j`H+vP`-XW<6rsiR#RuzE3YAT;E zGQ@a_zG^{(t-|tbIam~cT+6fvJ2fADrg;d0N#m-i4CMd zWa-!$(WxPCagV#PEVhW+yHE7sKOep0arNduFB_|Pwe%4vs#wJA5AX8ftV@M0p(z_M zekCdw4R)~;k4}3<;**&u?ezn1V&!Sw?#20?3D0Ab@3-V#D6W{B}H z&(+*{g6;a!+8f>fq(7VfFnK6Oy4D3`X9&a&e#aM{`(UA!(#Ia7BLjLs_Y-I~H#M0x zl#j!^g-qL{8)euI^9f?(snp+rXNfz>5!p6|8eBio-TUrpC zf|0x`K+=8S%tS580`GK0GF8VN$s4LQ;N7r_xvl@@`Do(!uZG=6A=>*l-blqjs8QA> zTqE?#M?L4JCMvXuRqHBdjU5pB#oRFnEL4BE*YujF+zlb+CJL+&#npTlCuD!m7^ZXk z9z~Qb9ZdXm@mKU&U1xNLCANZb;;-%mnkH1r8xtvfLnU~o=;3q;MAmQu!*?q0EpgaN zvZ0w>b59{|`|Mm}#@_T~8MC84$8}D?3sl#4V(P5;KE;)y zS*@^N**!%MI_AQxbPgoi5v@pZ{~M`(XQVe>6BiCMnYfpjR15IwNqPQ7Zu=_Oob6v* z#mByd!ey#k^m=f1nXlD+F2FYD$7p=w3U0D)L z(tHGew(^+f=p59GOJcO1?ula76;d@+!LFDKdcPTR4En}QT*LZ?E(i?WtcCyq@k!jp z<_4;7IOD2g|KMxRuUEMGSeC0z$;!b<$k`O_C6HtyjWj90CA8KZER&PzN>e@$SLoJh z4Kcr<3Bd+CSo?>74#4^vq~)=yNZ!Kjfv4?ZhxGg%>AvlUgG<|4OUBzlDJJ;1{Im0R zy>s*7_I2{}{p=1BvuDo57j&}m?*P1M(h}&QD2|pp?;t3SH&ZcCvUHsvB3VPu?T}pQ>H=uQ`&d-QaA|O>{ts)&^g4C`Uw7{8vo7qpQkox|)ZSeiOPM=uCdG zF`T+9-GLU|dC9@z7?|3vCVcL~t*8HVc*DR!su(OPX=?FIYz&4T(rHesksiBq^BIut zu-JFT2?GLbJyf^kJTr#hdYzqm_E_8WZ25Dn{4W*MWqE6}u7l5+F1xL06rxtlQ7%%L zDNOKXrGU#Sy<)9#ztds5mZzQ<>v1Q2GH;8;dPA{!nR6i46DGaYm~88|kX;|Ebn_&= zw^tMVs&N{b_oI&iSKE$zjUCt8g>rvMn%s!%5s# zYhc55VS@w<0r61Qj$uryNHc|!@cLSH{m9S+28&B?Q5wZ9W|QYzC<)F~ydauF0gn(nx(xCp~qb zSvz@ViSBGe#`L~BdY1$Yj2XD0We3_gFBq_!Vkp92k;mkks1u9u~6;R7LuDHzQ#c-{Y~NLEgVK;^(V;ZszwCT?-bvejoXPjqf(k6p91 z9>Czsg{O_#^lUTCfn?(FcjG32AzZBnW}?k)czA|~LE&Bv8MtYw=8goZZZS1;o!-v+ zEHznoXR&;V`ir|eMZLQ?RiD(BU~dT*ryP6J8Y;c;$8|6laRULSx*3!5l~&T4hBYR` zNl#tr{R8!=M?Q@$6uw_wd7kqrW~z29Y+XuErs?6XG9#+9$4u)SN~hvX&a=OtuzxiY z1;f=NogPsKfVD4y>~(8TA@LP-UWmtBDCQiGF68SycC`?Dy>v!O+-$qg-OCYust{ou ztq(jAtvf(<+ycq?wf?$Lg&-b~?xhMDh=FmNQuTj{-!VJQ$s8hv<>4dA*N||zx5li} z>^ryQIc{i)Yr6+X;}6?5=AIqc@?}IU*Ii)bD9ywG;uU75wNEugS9FA%J2Hh1{R*i& zK5|A!MEIr^2(G^1NdS`v8KW&l>)%1dqS971J;4VR62R#vSAI9#*c0P24g4}69`u@n z@V|~%HD#~(my$!R3ZfIy+^ZkY>o>^}-O=6-M>9Fttwpqh-4c zRXp&~DHn~&l+hj*`=K4bw9xymQ8@)a@d~Hq6M)MBQKyA~z^K{5ozn&C{IZ}P1FV_b z1J|Ja;r2r+a`{9S_$z+SGUD+aU+dLq4tOsI9}Y4r2`j2wW{6MRl6QLe1M5oyTX8<7 zRYw`N_mjXhIGjVcpVx7#$N9Ql1dFb~zz)aK&x$9C2*}3=L6)ev66DtEZA19CNFT8P zJ=ssvb?=+~-=OxSTz3d#iY(0wHq!qX!Zl{wKN2IRKoXCxTma~>mo>+2=UO+?D_C=^3TN<|Bv4{C{rLfxMGx~2Zgat zAJ{$$3XD)%(w$DFzQ}9T2 z0{=if6${M0($CrFmPNRyDc^zM2=83QK9^aoiC!~{RIgWfAhOV20LRu1&>vnUd}~_l zuJCUIc0iWe*@Y<4a|xLnQ(Jqk+DvBo4Y6W(x;Vql+{;R^q&v;tkLB z=r81gS$DdUKA5&mIC5qO!scVkyfQr4y>;aqlYgzS#XWRC)qriwXu2^xaF3T0V2^GH2 zO_E_Ryov+;=29aK=pOHqsv=4CjMeZKr zCQJUO}rK-@@SK?EjsOA^jJQMKtV?Kf!>2v|@pP(EJw};3e)( zAp^5DAbn6*uz&dJJ`NuYans2`W73*UZ73^|gh7mP9gsLYkVDB1;}ErpE}~qF*eXb6 zx#kOtSkLU%i!B#Y3ac)MB_+()U1D8sx?b$Wes1Iv*b#1PdUoBtc0QiHb|w;M_&%}z zvKs|P&~sMhP9TJls#raXiB$k_7IkpY_G+py24QXPWC2~9?VmwjWJ&ezsWAW&$C z@{SrkcNmq1FL0*Z10`4a))QP`p$=JJ#gVrAa440$PrdWCJfLPQS3fmWer&*Af-F+7l#_x0}mh3K8^%IOgd16N86R$_%PWn@- zuSWIp000W>_T20m#D$3P==w~s30!I@c`%sS{;tlk`=x&x=BHyHeqI-!!beLG9&Lk? zwq-Yrl0wh`@nkWN1cesw?(*E?8qCFA?_)Jj^#Re&_0cdph2S~X^c zT^t;Ub()rRZQWcxpT=WF-$&iiGWC25)QvT}@bU@yu``T-T4g*Wd+Gd}?+Z(#=O$4M zEJ;3aObk^Ul#mGfivp0XAQjrIcjV1sdNtOp&e^jlEZV}uT`(S)U+v;Rz)lXGT6JVe z0K0&NewQPsGq$l;Q$YxP2Et)`QXRLV8-D7DtUHA`pN8pWQyEBO{&^;^l4vx=VYqVf zfLB=4J;u7Ci_9P;Ra%SO%txw6lvGa+amiupmz*H`h5>wjy=lx}Wm67_4QJu_dbG|$ zf?}?mh?4>-e^+oB;YC}+&f>xt|Dmjst&Tc>CtEZq1!_Tfb$C!vnM)GX z-l$B@%bP{VlP{Z}?G<=GI&x>Cf2R1%CqPDf^qcVjNSpt0#d1T*g)4Nl#xo_})y}05 z#pK08PfnC?pySGGA*2dY99H(;0`n~O(Hxxn2%XjW zz$NkY7&JWNu&qlo)!2(&h*6DQbTc{1FqShX!yS(kHF~MD;P!M*T5W55x5@j)a0Cl6 z`}7n^#)Sfhe2ePXaz&VvY-zp_D$Z|4Gi7$g@Qu}wYD2X_btsj?gfJRK`m?m^x04e4?e30`CozrIcw`DGvM=lt`$W)y`n)(hguOZmE8pV0W zgHz+lEHbH$rBS*D!%)@ipcM7=juED_@@CmF&xCq7kPD)618$YdpG}HrfvQx9g+qqg zc?+kD;#@S4A|PMU;J0VthAffUOQwL)CX_gWc+{Q%nH$3QRE9>h^;(_oI;?fomP`WG z4lj0SVKBS2bS9dh9Q{6uT3V|LvNc;BMhmr3w#-}RnZI*0vSb%uqS{%l+2WXegs0SV z?C=ySaN0$2fQDHfZZKw81C2MG)GK&JYRh1K@lw0dCJQ{&O(I`rYOK8NigLXme4 zgHSRCoB2Yh-1Kc6`oO`247((nHqy(rj`<#xzR5f-UF(3$u!O|_inUeOskK(#&WdVvBA(8&X^h|$Siv}0R5!uElcu1#eEZ%1r|(SnA_5`` zTbAoB1zjg%NY9cF0oIgragJ?t!8% zP>$se43T4bhAPSqRex(z!qRVvtx;ExGbXxmdLO+XVb+kV=AUW0l{AyvU6^Zm5mKm; z^-S#Et6=w9R(SMG7fJO($zjW_j`^kOA=ib=YgG-Gel!b`u7 zxhZ%16+U)J_id>mziyPVB(D_Y)WncG@OoVqULDn+ov)bHA+^Kf=Pzuu_Th2c?7FD2 zc@+EkU`dO`UwelF?h%C>N3-VOr|J_Wts$2h-XY9~HqR4UeVmw#`WwiZT)#IyhR)Uq z9JYbpNLuIcHmu0LVmDx!ZyYbJQ|M_!gM_>20nOEyu141n2`Qj;^dueMYJQBrF%s?Qg!Roxv)iJaCuPfdq= z?@d|j14>(5n~#2pBz$sX~jT`yh=ly6Z6rD zD++nO98l#Cu0x_4ht0jLJn$##jF0Yt#1JC{vuto?#Pmy-UA*E*Cu~@ryW>$}r6xAx z`SX5GooI1w8s29N+2iv9xq{#G{?!mS@TKpT zR%&$twT4-{8Z&Gmnuw`+Ve1Gz<0^Act>Fo7qOO!S^o7B7+X-C*=26?=S!MMenN#O4 zwNM97y%^+yy;tAzM9>S9_jOC!WcM~VSn94*)P6ff1mi|^%SZWJ3d9`=M(+-M zgB84YsvFJAl!$VUvZ0KO@_-(KB3zWN?5wi9NqSDP1k4C|NTZdO-pp7_<_PV5tScd( zf=MB*5O7!vTOrec>QNN^RQ~fTag*T{+CgCN$rus@1Uhkxkq!vNGsXAYzGPmQ3M%%8 zNNq+cnw?59@t$2ShNPFIjhEF*pvcRkO58d#%NU#F;@x`r1^9Ei%CHAs#AGM*g! z-zj@-Re2MEd)Z5LI#Ql`W#c0SzfN74;W%L8TZwY?(c{opxD##rEW+xJsu^@BaY^Y@ zH+}dU@Sm&D9s#N9BRm8!J{@d19BwIUx*7EN8;ZtL2{aJvBL%G*akZ{GGI6oqoV)?h z3-3leGJmz^uw*A?-5^<{uxK{yKBZUpCMTy`%c1cu)QRg~i8p4n`J&mafe2tG}>HC+@! zZE#YG1bm{H>R*Qkt-$p>Yk|$N(^Rr6vCQ1I^Z!7VhGyst5^EUOWPDSIRCJGl3G&XS zv-kB}Z;bBSe+=3qn5Q(sTc2m4D`>YK_w;0@lc)aow81f@?UiTX;B|#Uso4=9im-rE zu$XaLiD);OIjt^CI6e*q8>6On^p1GRoz8rJ1gsq_c6A?j__=|x@vV_?2v!Bv6z^U28YeD%@%FDON)D*06fS& z7f{(nci>s6t5f5w`d7|st+ETNn1j03AG|H4sJ(4-bGi}%6gaHy!bH{E!jPMNZydJ+ z40!EY)d7yRPQnfLM}yFi?A0Pe#BO=*rfRlNkS|d6mBfqXd`nY!^#Q~?Qh512wt6hIR9KV2ED-SUa{zPm3kp`G)3!n$W46I1|L;Hp0Yw#vtq^4IDSjI4S;ZQ<= zGtRMt-T-Xtzuiz$DJ$JT;`t$V&7!0H-0aOA zBsg&K7U#Qg&-GJtK>t=920%oVzHot7WfOYkfiL=@$GRhU?BapIJxBi!0o$vu4(M(1 z1n-#-xC6igQ@`yK-5b5i=<=@K<6tW_S&ow>l4yTb>+t_Qg;-qAWo)JimW-KJIb_TZdNDIgXKM4A+m&!-K?bFz9$lO(n z1kyBHluh!|l;@z*LF?pNsEA%n01X>7Pb=aELMJj%*kON79}Tv-=kIEW&Ty7`Cw5}- zCNjfRe&A}n{pZ=v;o=#8SF5ozap+&7X`YJdBrXB!ZUs*(fv^RdW8TIDS7PDKj##u7 zxZTN?l&KFju7X(OPnKm&gN#;D?lym-rpLwn@v}oaC55&<-{L-K0nG;_z&8M}z^a+6 zv-(;JGem4(X-tphIc`8cn#)LXT*bitd#Cmy36o#8TvK-IS>(!(yokC$<~&&NfUJSl z=0o>CSws<7IRYVicTG*(5foeOZf3Xt75E|ZXSokFzBh;d`N%t?r!v(8Ckx0 z!+Bs74C~=tmA=ycgCmZL_6(p{nGqth&k$9vx5mgb;o*sNU?E5sCBu%LCduG|Jo+QW zk(X+l^xg~cSZ8~JLvKLH2l7gK7G9I)Q`RcZFKbUq@)g#?Ai$dikr2T*;Vc3vs1`V%-44b8Se;E%!=?O=gIz*&Ra?VX=7nOR$kdq^keMTk=0abL@{M5&$lC#~|YZ$GSDk2iPrRwid7(hBW zh3k3YU*lndK%KG0gb|zNkJM!(5p{c`xoRF7t@@oi1E$#y%%+n~%oHD;p!@SjQjJeS zj!#4_hg?~YTz#8J{9AC!eU@_BAXg*wizIwQgwYODm{VfHk~!qp9SFow11C|=NSp`w zz`iRUioMaI`5rP|!It)|pqXG=%JP9*O#Wg=7ca%KZFqbL9WG;)<@T1w&8v-*UGNEeHrv;vOS@Viy1k2<@Y?w8T%@&^dOy z3Q7!j5e8Nd*-!B+9w|U1@o&(O1hO6lgh zR)ltws&$o`ZB>)s`m^?9MaK7xH*2CK3)E=r=F7J3H1EU@-OF@?J^_!22>NGyX7huC zrI-Y(QikGu)U;S4kaP;`4ez2@Xj;`HEBZBAeDm;_2OF0$dSnXKBcCmClPA5T^^7Zr zCR%+Db(1ZnWb6|aJ?DDg2e385ik5R$46U{!xtFvERmUVHg#Ne!qo;q?vc~~U^)y6m^w@>1m;E39<@oV>e3klDLbfRQ(IrrJQ=2rY4nbd zt@N&kwWIrKHyc5`4KGzJ)ufbZ(d1BJN;+EQyw1_>6g-k6)jM9{&NL2+i#DmIv;bY> z7ifAlw-BDZ3$jg{hsDW0DgC%xDvXQj_W+$vNxtcnVxhe$JF7!_7;8H_vs^ZMJ1t7y z_RI{>|p`dw|3n>V}nAjY^E*uyI!<|%L(V5VdheDI@% zt3c$?BUYlh=Ttg6e}5-IX%;hFs>EV(+x zL7WO{Su3sBBtxTvhnb;6gsq?ph7Kb0w~(h+F?hwoiNPFL*8*nP$mhl*`uAVGqldFP zjV=t}yv+7Et77S&eb6{(hUd`FVU*$q*lwZfnv}y+8_a6+di79km*RNW@3?IwM2?HO zCLD+%P{UjdjT+If`|Q(3+$%o~JEh_5Ar60#R`-wmcC*uoFXli?LosL$4SB4_r5%h| zs}O!XEfovwEkXv&>@aGdvjdsn5g8biCW8-PY0n>qos0Zm#mE?Madt^H+RBmCZD-0j z8%+_O54Z<{#dE)q|7(N(_3sF}Z|&L@w}~&^KN45eHjr>LzKAQ!JWW()Wab{ljsjeu zvrdsUF_b+ZCj$iac|e|unR%)Vh?4tdwvDYWe_FhKP@@4t;4Tct2fl`93`%hfLA)zG28sx z6uKR=y4>5zbGNM)b_%ylb*7}f0vNCo)qs@XfBIAAgH3c<37*a5s!`4f^2cJz0f|^tFN+!OO&e+7hh$ioT^WkTqB&taSJb(HU~RV@T{(%LRf!ARPr# z2r7IrgkwFJ-huKL2F+s%d;7AH=s?;WN(?SFREBm9lz3oJsa5hquk}?}t zHabUF%}v5uD#swp$ysQYd_;lQ@}v!VDa|z^zq^e&%pSe|YUQ=0^isq;%}ImgIdQ27_;F6-o}dhs+)pX>9;W8^x* zMaBZc%v`6>c1*i3f%T`YQ`y%Xl1i{K+FF{M7Q=S1?^Z^7=`zY^l3r3p?q|HoUT1>I z+yD4WAE|hY=S3g!lNk!_D_r<_|IHWqfOGIwD=A}q2^8JTlha%gnQRf3f0A}99aX$q z1=qyKTC09D>zCZizj75GkixS2HY076-9P#K7LiCrvfFB{2bQBPMU3+w;qGmm6;k|ydk z$j{9rao$gIK(c*-RChNkJ#}oU@b52m_S`_bWZ>9KM9#IcC*saf<5`#t*>j9xWG&$J ziBrLoai?Z}$|Z6f@s=;SBM;pUP(j@@1J}FlPys+itiq95a1!#P;$H-_Fv~(+Z122U z=h)h)ydbX011C*r?!xyg=%V8{!8n}zw_>pZ$JXu+G#hsEE(V~$8|Td(1s8XiFv1!X zLH|uAA0}>Ak5H@Ohd2@u6~ihPP4Q@=(vSvY1{vIBsN3AYfrsqMSP2_8&S$KUph4~H zJOeMnbX6__@TU+;i3!)J__CO|matt*FWM&;=LrmJn_?IWL#SLM1J-NqfAN(%)QlrW z>=Et*g-4ga=IUv1mfFco%0HR9s_#iZOUGl!HV$gByC@{w03X88BdD)D{+B+5Eao7W z2~8~nD+F)_Xm#{y0Qn>RK4F0)$>iMq5>-F3I8FxVw_szhkSMF@vQN4nEhzkyPhnDFA#l;9J&%7w zE=G&_4lyRo=h2Vq1>4IGjq|jM2Q4r^a!2ZKd}RAezjPy^vpStt(N_X(_cXrMP_6K_ zMv+P)jbnQ0_OLFj_HLv&)i8w6OYW^d+xvUe-{8Lk$L;S4nU@XGeQ0`y>?=Mg{r;Pv z%!g6Lix6LRlXpI#XQv=KSy@S5M&;ThK|DZ9kFCb=+OUEv0pBPZDm))i zX203OM&X~;fT$6ln>o6^#y5{@lkVj&v1y{_sRbFSPQQXyW=7Ygx&!M}{ohS)ll`XE zu-RJsKB~!gg(msnW7xNYbrV!I5YC`Mzfu|0nnV8%5dEVT9EF57H|M;;BsjVU4k27N zL3ML5&{l%G-$_K8Nk|4Gz&cH*1lg~`!W_MuKK5c+=?NmR_S1gY8#%#Ru^NqplPYA+ z*%@g)qhyc$ax)Tqntdyq$V?!WPPS~HfTDa;_^it?80^(tShG5YeZXT2S{=^Im>Tu7 zqYyicVLjeqh=I|Di{~|^F7aak?)%svB_Fyu3AG>`ryQuUkM#Su{@0$t$+YU0vg~Ga z99BQ#1~2Uv|LE{g2m=<8<7@uvh@NHpE&NOIzcUqEvuPVarpv&j4i+}rWCGfF*1!IS z)q=Hg-=C`)tsSBoRjCT@Cr%3fC0*(Wj?I8JsQ}3-E-PD1TmM5NdN%tBgt+JLuMhuj zgvv<80EO5liJ9?nO8pvsL?o{cDqJD=Q#9ZHpyOkhglpqXAX3TgRFGyRH|jj|8bGbi z`6R&dsM;2FDoLc%m^|2FZWFoJ8+RT&=-QMH+Ncz$=Tf9%o#Lc;k4)It2v^*1+)Svl zmig*V;HVviLvh_h5RqI2@_%_3&WyE&2K0;F=q!1GK~}QgoETvkuxu-O!pzIE-nvvR|t^Md!MI#V7G*f>Qgx8)Jjw<&$* zZNWIO68z6##Mw&`(G>G3p@Z6d)xxVibkUcsv($r-T9o`_R8yrrR_v15{grPW`cQ-$ zp0Rg6=`~wLb49^pW%t3h*%|3zH)q z(P3%Ao1q9ViZ}}e?2M{z^uF9mQa+0tNt2>{5B1LKVrwoNJ$U4lijelEYRMet{m++% z-)}%-!SHv2_Mk5cSAm!|Pxq;Abd$bo3#u-x=#3A=m|t>%FKxAC(1rb4uWb(Vli?;_ z6B+g7-TJG!V1e?5W-r%{Tydr0iMqV|YCSe^k~b3_AL%>{yrGo$Wt6p`xy}oRZ+?58 zD>xNYFCtyZab=aIdE#uhocHmr{wnU4m*PRtW(dmathnh)u**<_rY_-lifd7-n{q#D z=lMTeol}rz(Xy?pOI_-+ZQHhuF59;Gmu<7lwr$(CZTr^QaU)Kg9kJHidYdaUN6azu z%PFDe*^1ePWBa4Fqs;DX*^-GTBiDK6cN*r2{K)i088Vw2@;Mgoeps~YGtaGjC#9~E zL6b6LuqaVLJET>%43RlgPAwSef*B>ty!4%b0*w=5qX3Q%XVO$IO9KiM@C(#jD{>L- zgEqf{QcxwlyvjBdiSfKSbBs-XcAQk8Z0Vyx8;+#ErQRZr6ImiteA6s#8}!G4eB{*- z6qgz3$*~jMLK|rN)lde~G|2<>8_9$d-AQ76KHNMY6UthZTrT$=d95Gnk|h0(Bx)!^ zQ0dh%Jy(9wl-bKy=BoHNBKkW^C?qFVxE_?4(CM%AUi$MFo%Gd2B|ShWRE8Bv)H@`s z!Uw&m6uhjBEKCY4ZWB&U9krz1)W6_X*oG|ldT91q6y#csos0ymnp@UNrxA}=wT5X$87POQ0$l=3iK)bmhfA8;seXW zPxm{J;iee(@*H?*@tk}a`UdZH;=n5$Ms(=%FJ`w>rlau<$RtKE+?7oa5Hl7!&3$!Q z_Y$Mkx?sIaKH{XL%)&Kgqgj)%2pe|yMKzrb#(`}a)+G5{$}m9OfCSQoJKy97+pz$k zHtB{wD*@dVfI;sFHfQ{cxm23_OJa4}=T)nxGUl%`{p%t0FVg5z2r1Y>02I%ZeBXf_ z!3~nh%Nxqqzk=cl`YaY~EaUuddOcT0gPDIiW0sUt>T^QH3ywl`G}aEh!pbsDna1>H zvMdvj<+QdJxtLToRs0))0&<%YR37F4Vh3$UmWCh~i7k7kLt>tBr0VMq6^{?dAbanX5n}D1@w3oeQAhriWG+ zMYa%qHz!3hn@&wIM^;1QxZTwg0T zWkOtPA>kDu5u03|P-#yDW!#=O9r?51CJ&%t9r^W`9&ex{c8Gc&2n)DhY#}>eF{z@F zK(SamsdDp7Gnc5u)uxj(GqMS%^gG9WHkKzG9=JnQ1@ot1^X0Vt8!0DjC+(;v-eEed zyI$;IzWqG~KzcAPUzL!@S~vHAcH~UUk;Ak8uBw8=+iuC+La5HN8OfltFy3N{lkPd{$f_B;&|>a^eAbbI{MN2TA=^5s1?LpaAMu3jEr0AM!qc-?6{JeE!&t zS!FX#nwI~DlVDufRQxE4uPNLrNwu~d?D`cB>n*Ev*5>paGm~oQ5uD-vG5p_uAv8L^ z37w(b57^55o_%{-Rw*&+wsUv{0}z4AqfD$7X#%#h>qS7&iY{vW0LDvvU>LG>G-knE zSGPM;{CE;RYvQ|mrWGx~##xz;MZ&~}P460=`Y!e;luw71F+ZsuhrAuxVVE7s9fH5x zCUUTD$A%OF`(mpM$dCv5nh--GC2qSD2CgaG;9zglW!`Rbx%E9c-1S+zDC=dcY|87A zJx|APK^wvJDF1eHjHCHKIZ|M*kY&wM;3W5K^lzk5{*>JELrxxm-(#LBvcUyXr{3~! zKn=XYXJ1_8-l0|x2xi`q>|c=N-jQX;ct!Ec{CM-H(r-+kVCidt{yW}pRMb3BuNNS1 zST2E8ijrMWi4_yhC?`-W)dMLk(|=4qLcgFDLm`Px&qxAN(AF73eTM)c~=Ram$5;gw} z7LwPeAFbQwAdnQA#4Wa#UlRegxZDB+x$u!wba&JTl&7>&JO#YjbFxVvRbkQD@5S@hs$xdQ}6_RBg~rSfFjkU#gK|1o7TL-tr}< zr3D#OAiPtVj-RrwGx|T?KKIFdy{#tlL(xdw)R?xU40T@J_VWdA)-7TIs@{Eq`D9Zf z6?HG-BzK<$P9@B}6%-UiA}H5@IIO=aj%<++H$Q;&Hz`EtNqIK(17Yy`Q`(5h+=WT z6AUdsWDGE933%$T77(5z?-JzucXiyN8bz2m*X^;1T$6)fu8E3z8p|K$FAUu8G+@De zzs?y5;4x#OSrxYgw+R=&d|K=luWI3SB6%lhvNeoc_{-=W9mlQSyexeWWVthjGvsk` z`6FrOA%ifu3RCtGkKo1Hf3x~(s<%dxeVF&wZQ-+jpi}|o+!+flPV_dZk)+(-@E2RZ zb$NkYZYI`_E3hhJTkg`gg#rId*!T||0Y`FW5B0~x)%*b@kpBN8{gbdW4Gu_BF*n3g zMd2}&!c@l{jy51`G(;O@sH9#+g=FU60PkQ?uw3ocw4YnT(L{1bbT5b&4f|tE5u5By zAy1xIC$y?=W*Rt{C>sZ>i#^*_kBOa2b4LuO@!_6GZMoC zjI>OYXfqOtPJ4B5dh^_trejR+8xn~<{y+zOwghmv)E2ZzHp4d&;uR1uhEd16M zZX0+jgV2pK1g-DI<$t}??*DCoz74(q?(ialLPx>QsjW7DpI|atJDG5-AORpsIX1yd zOIIt&;*76hf=B&dH$;}n)T1LA6BvMjaJ8Wr-javk-21> z%Rq&@yG?)c|IRrM6{ImqM*H*1l^RtfSeD0hj6D_VfIcxa|;&tyzGrBJ_#8*QuY#*OOhyTV-PIMM z2Z6x~+$FpH?A3cAn4~v#!Na$K!7+v^LYVU%>z*{0F9S9&*`pT7e9Je`aR8q%Rpd5 zI|EDVB@FYF$yuqYltc#a(@rUUx;JgXTqR~D;q9)Vv`;Qqlqc*fOaPXRL0xq`AEpCs zn)>f3BN@#>EKC<-f&3S&ab?PVugFC-CaKyw=YGl!!tLlbruy>bC>GQ0r4HS_=kEUZ z@&4Q105FdW{(UT0%POX=ni)9Ch;L##c@>r38?Lq|pD+m|N2qq*Ox~FUEOd>#EBN)) z;aOcb>>*5-^Uthq1VG;w8~-{MCK%bb9M#>L@S6AC(#YY&?mxV>@xD3u4j0r=I7uPs zqzLuR+*ZAhvleTEH&YZJoAWS!ShBDD^IK zcl^{{L>pJ|XIy+Y7kdbx+6VIb2? z2+-A_7`gdBMBZ?QDFeL?wi|jD5jH|_#8ED@GEELajU88IUuMmjYYomT^Y>(7L=8a$ z;DoNpZ$ybhF926uMNVrtKPkgs5j#?b%nCv$-Wm{R^)D#Z*}8xc2b4-JbCd>=7`qc# zyONyF@aX$q?almmr=LgLyu^pRA;54v>`KqIBz)>Jor?#;K5{-upn=E_De??7xF$@0 zxls!DG~Ke9VeqC#@|xe5V5X8Dey=!2(0HD(ii*uG)z7?!j>d!#sw=7X32Tj*ra*emj- zGIARG$o+}=5RAHQD{^5aMsL*k8~qSIQ8|fS=C0tqqWZodQOA_gqs`wh#6wqXMmz##t3?6D${o%fLYuks&e04{eJiUH_s zQ1OQ3oLg(4RQ-4%*jP7$axOHEPy36LP7Vg-h7lvDn!p}OAX0U{ITwaZTRChi)@*J& zdX<5ZX5Dd$GV`F4gM;O{cYDg-M8@yXALtjE_?|?OMUY6rb?asV%U;6B5~2vLSldhY zF6t>uk=9t^HEA{S`pox#wXY}k)3ELzHk2DA5YSJ|_=jReHiH9*QrT4c!Tx<~$JY>0 z6XF|?LnTq}GOaCHgvpl&$D;z1AP@MgkW`EstjMro#rdpWi)C|W)xvq7&!owkUPxh& zHFQ18)yZC7+X&(lXy7?qwH|*=wH|*>b+WxbcLP4ac0|0;_~G)PNP$a)n0@U#a`hO2 zvYCf~Kt7(gPF)-Kz}EsUbv#vdJKqYcfygS9O44~ z%Y8bqdVZLH;cx{lq_cRe1~BRV#(ql!iUz!#-qtQQCdq`H2U9m)^7Y*T(W%*G6YB`1;Tgw-bcyCk#5Kh> zrAw3(Sd?*=ikrZ=S)$KD^DPF(U2~do_HKO#thOdn_==vP39BuNHV7o@iq4CeNnu-( z#|lF=Qg6UNtT%8Za~I0)q6}FQF{bfyk2!)f{wA#yX*nTdbKoeF0dT4WE9Df-k*n3! zU7o8=9I=9Y|M{p3*lJTsq<`DnBcI9ks~ds&B)0T@lll0idLqA)&Vm0x5*b^_Zkc?Y z%(wa+Jpw~g*^)gHN@&ZSyoACi-vY}*G+b0Ml^z1-w4_9!%>9HK$q`T71T%~|g$5~Y zN=8NotXbY@W?D5LwYHohS-fNF^F5@{?nO;Rn!!Bso2*xP&doP3M%C7!ym{LHT((YW z4E?R>P3%*Q8x*U<*A#(`_U{PKP*b&sHb=pFQPbO5{YkT`R9#>w+=PY8-KK?4+iMP@ zy(j_(RbMm)*Lo7h%)TYgwoNIW2^E9rv}cNUKckhIZ^eV2 zK*G>V6ng*u(b#x9f7Ky&60-qZB5mmKUrQD=4LaVX2F z`2>EKTOJti*IRS7iuCV#n^zAKt^duQolFF+v1HJB)AHd^4Y!3Gv_RaG7~PZmf}1@Y zUas#!*iT3eEu6c(@2Sru=oUFu>QP9M&y?M z5}qAq_m1&LI1_DH!iOM7v`e07iw~&wKvteh2o8r#Ow4TcO~f(D62BmZdvLxU9^br2 z@*jvj&&>XEVxNYE_&hqn%Z~TT13(}um|;xXTDe|k8E|%=aghHqZXZBFz;Ky}nY`r& zjue-1{@kXWj^jmnn~CX{LDYJnkh35Ogi` z8KJs|ew<baaI}$v#sJ3u&QoV~L=OTP`(S8Ztt}n4M&p($X2^k9MvtVz4LYiL>++7AKHgpIUenO7R(@_e zU##q0&aefPgB$6k_NEH9?Y{l8ZA34aVthz?1vk=P@#RhfP9qS8Wa9w0^aDEpJj$N%lZ zOHB)kw&&+~YmJV!hjMY1Z{zre`~Z#2Ga;LEB+H(Q?i>b*Jli=lgtTIhLQEPUO(}5I z$Acm+4Nj3ibn#0NP6$hmQh*u5NEFgR_fOs^zGF%01JEc`ECtC(4h&;8C`G9NxN)q= z0`;Rb&?sbV9U?J70Jo}tszq!dOkH?-BQC!CFUCqxi7|X-k`WLVaYgV|{>cnne6U}P zNERmF{}o?~uUC#n{ORE62?Tl!fM)>GF~;{ln%9GUHAZkNX~M`~1gq!}@G9{JhT)oU zi**FS4M)^jmmuoJnX-DtGm*(fMA{Pb!A`{iQv(fp^DU5BGew@)n|wssP1E;V1X%+D zr*Iz2_Z{1&eAlN*fbJJY-^!PiNHHUB%sxY?vKti)TP64bbuh%ODYBSxfSVY^b>b~8 zSn;h5SmRG<3pg|B78fkSzA2e~d%XYM&5cj@0|-uz@v#*C2V6Vvbw{A%%^qm##yi|! zPv8}J9K-l(*zx8Dk-hVp!0kmDch4t*eL9JyK40dp1ts z6|@NWhoKY8(0_j~?ISn@00ZN?Q6>e*2we;CYwj8&(C|XXNt=>KrSYxc-@*twRq_1{V8ea-&6y->nQG=YU zq+HzLCn6!egTr}*QjLd>2E2QPEJ8QMnad$hOMHruRDp;FKj)BI4PpKLEfHxCy><9a`P8qN8xtXwtU34 zwXQH?=xT(RuN7n`pD(V^PS!a~4N&B$?;BZX#lR@`+2NBgZ%XC_8t3!Auu(wkDwFfW zt_wKETj}rvt{ejd8f$qy@*S`~ACx!4v##dRjfl!r5@ycM;fXy&h6U0_ z{cRt?;%f!gOASPC$jZ~Clo^OB-{h{8DBFY2YHP|Cyv^!7lsX~HD5QAVe%?Il8_^Qg#cZzJ-Z3H{0)u$B zWGH|mY4l+Q41mI|>W9qGw(Vvu|BHYv9S2>(M7G+K6UKm6u*B|Bw#baZ#CDDAWD#pn|S~vpXN) zND^Xd7Rx8c*l8b>OD>*wF0=0KdfOuJ6w_{4eVgS5SjS;hrboMUwD=3$2Sii6 zOHKtLsvLdXxf|}XV8h)r4$vb6?0-U&*asNDmgVyMTW}#Um5&j(@N+B~pskLrw5S(e0UYCyB}zS%@gy3Q5yX zdYu9#pXY;svL*(OYC?U{kZ$6o)G#eO1MR4NAf4^!jET6 z7pqrf3iVTCX4NZCsVA^&US@6rb~866aKk4Iq8wK)u&YoaX{J^=kF7e{*X?kly!-xO z;jj&?{;_rp)o$f~6T4ylC$Qkm|2ui6C9OhT>Km+%fIJfI)|K>?Tzc6}r-y5KhIMM0HCol#Pk9`;osdLc>X~$Ox

d1ZzV`PJaf;LURr$w8USfDLW+{$cK=5Uq0( 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 T{?8+S7! '../node_modules/react-native/Libraries/ActionSheetIOS' pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation' pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob' - pod 'React-RCTGeolocation', :path => '../node_modules/react-native/Libraries/Geolocation' pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image' pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS' pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network' diff --git a/template/package.json b/template/package.json new file mode 100644 index 00000000000000..c7edd0fbe2c48d --- /dev/null +++ b/template/package.json @@ -0,0 +1,25 @@ +{ + "name": "HelloWorld", + "version": "0.0.1", + "private": true, + "scripts": { + "start": "react-native start", + "test": "jest" + }, + "dependencies": { + "react": "16.8.1", + "react-native": "1000.0.0" + }, + "devDependencies": { + "@babel/core": "^7.3.3", + "@babel/runtime": "^7.3.1", + "@react-native-community/eslint-config": "^0.0.3", + "babel-jest": "^24.1.0", + "jest": "^24.1.0", + "metro-react-native-babel-preset": "^0.51.1", + "react-test-renderer": "16.8.1" + }, + "jest": { + "preset": "react-native" + } +} diff --git a/yarn.lock b/yarn.lock index 4b628a5d57801e..9e209568fbe4d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -893,32 +893,41 @@ chalk "^2.0.1" slash "^2.0.0" -"@jest/core@^24.5.0": - version "24.5.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.5.0.tgz#2cefc6a69e9ebcae1da8f7c75f8a257152ba1ec0" - integrity sha512-RDZArRzAs51YS7dXG1pbXbWGxK53rvUu8mCDYsgqqqQ6uSOaTjcVyBl2Jce0exT2rSLk38ca7az7t2f3b0/oYQ== +"@jest/console@^24.7.1": + version "24.7.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.7.1.tgz#32a9e42535a97aedfe037e725bd67e954b459545" + integrity sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg== dependencies: - "@jest/console" "^24.3.0" - "@jest/reporters" "^24.5.0" - "@jest/test-result" "^24.5.0" - "@jest/transform" "^24.5.0" - "@jest/types" "^24.5.0" + "@jest/source-map" "^24.3.0" + chalk "^2.0.1" + slash "^2.0.0" + +"@jest/core@^24.7.1": + version "24.7.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.7.1.tgz#6707f50db238d0c5988860680e2e414df0032024" + integrity sha512-ivlZ8HX/FOASfHcb5DJpSPFps8ydfUYzLZfgFFqjkLijYysnIEOieg72YRhO4ZUB32xu40hsSMmaw+IGYeKONA== + dependencies: + "@jest/console" "^24.7.1" + "@jest/reporters" "^24.7.1" + "@jest/test-result" "^24.7.1" + "@jest/transform" "^24.7.1" + "@jest/types" "^24.7.0" ansi-escapes "^3.0.0" chalk "^2.0.1" exit "^0.1.2" graceful-fs "^4.1.15" - jest-changed-files "^24.5.0" - jest-config "^24.5.0" - jest-haste-map "^24.5.0" - jest-message-util "^24.5.0" + jest-changed-files "^24.7.0" + jest-config "^24.7.1" + jest-haste-map "^24.7.1" + jest-message-util "^24.7.1" jest-regex-util "^24.3.0" - jest-resolve-dependencies "^24.5.0" - jest-runner "^24.5.0" - jest-runtime "^24.5.0" - jest-snapshot "^24.5.0" - jest-util "^24.5.0" - jest-validate "^24.5.0" - jest-watcher "^24.5.0" + jest-resolve-dependencies "^24.7.1" + jest-runner "^24.7.1" + jest-runtime "^24.7.1" + jest-snapshot "^24.7.1" + jest-util "^24.7.1" + jest-validate "^24.7.0" + jest-watcher "^24.7.1" micromatch "^3.1.10" p-each-series "^1.0.0" pirates "^4.0.1" @@ -926,16 +935,15 @@ rimraf "^2.5.4" strip-ansi "^5.0.0" -"@jest/environment@^24.5.0": - version "24.5.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.5.0.tgz#a2557f7808767abea3f9e4cc43a172122a63aca8" - integrity sha512-tzUHR9SHjMXwM8QmfHb/EJNbF0fjbH4ieefJBvtwO8YErLTrecc1ROj0uo2VnIT6SlpEGZnvdCK6VgKYBo8LsA== +"@jest/environment@^24.7.1": + version "24.7.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.7.1.tgz#9b9196bc737561f67ac07817d4c5ece772e33135" + integrity sha512-wmcTTYc4/KqA+U5h1zQd5FXXynfa7VGP2NfF+c6QeGJ7c+2nStgh65RQWNX62SC716dTtqheTRrZl0j+54oGHw== dependencies: - "@jest/fake-timers" "^24.5.0" - "@jest/transform" "^24.5.0" - "@jest/types" "^24.5.0" - "@types/node" "*" - jest-mock "^24.5.0" + "@jest/fake-timers" "^24.7.1" + "@jest/transform" "^24.7.1" + "@jest/types" "^24.7.0" + jest-mock "^24.7.0" "@jest/fake-timers@^24.5.0": version "24.5.0" @@ -947,15 +955,24 @@ jest-message-util "^24.5.0" jest-mock "^24.5.0" -"@jest/reporters@^24.5.0": - version "24.5.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.5.0.tgz#9363a210d0daa74696886d9cb294eb8b3ad9b4d9" - integrity sha512-vfpceiaKtGgnuC3ss5czWOihKOUSyjJA4M4udm6nH8xgqsuQYcyDCi4nMMcBKsHXWgz9/V5G7iisnZGfOh1w6Q== +"@jest/fake-timers@^24.7.1": + version "24.7.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.7.1.tgz#56e5d09bdec09ee81050eaff2794b26c71d19db2" + integrity sha512-4vSQJDKfR2jScOe12L9282uiwuwQv9Lk7mgrCSZHA9evB9efB/qx8i0KJxsAKtp8fgJYBJdYY7ZU6u3F4/pyjA== dependencies: - "@jest/environment" "^24.5.0" - "@jest/test-result" "^24.5.0" - "@jest/transform" "^24.5.0" - "@jest/types" "^24.5.0" + "@jest/types" "^24.7.0" + jest-message-util "^24.7.1" + jest-mock "^24.7.0" + +"@jest/reporters@^24.7.1": + version "24.7.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.7.1.tgz#38ac0b096cd691bbbe3051ddc25988d42e37773a" + integrity sha512-bO+WYNwHLNhrjB9EbPL4kX/mCCG4ZhhfWmO3m4FSpbgr7N83MFejayz30kKjgqr7smLyeaRFCBQMbXpUgnhAJw== + dependencies: + "@jest/environment" "^24.7.1" + "@jest/test-result" "^24.7.1" + "@jest/transform" "^24.7.1" + "@jest/types" "^24.7.0" chalk "^2.0.1" exit "^0.1.2" glob "^7.1.2" @@ -963,11 +980,11 @@ istanbul-lib-coverage "^2.0.2" istanbul-lib-instrument "^3.0.1" istanbul-lib-source-maps "^3.0.1" - jest-haste-map "^24.5.0" - jest-resolve "^24.5.0" - jest-runtime "^24.5.0" - jest-util "^24.5.0" - jest-worker "^24.4.0" + jest-haste-map "^24.7.1" + jest-resolve "^24.7.1" + jest-runtime "^24.7.1" + jest-util "^24.7.1" + jest-worker "^24.6.0" node-notifier "^5.2.1" slash "^2.0.0" source-map "^0.6.0" @@ -991,21 +1008,40 @@ "@jest/types" "^24.5.0" "@types/istanbul-lib-coverage" "^1.1.0" -"@jest/transform@^24.5.0": - version "24.5.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.5.0.tgz#6709fc26db918e6af63a985f2cc3c464b4cf99d9" - integrity sha512-XSsDz1gdR/QMmB8UCKlweAReQsZrD/DK7FuDlNo/pE8EcKMrfi2kqLRk8h8Gy/PDzgqJj64jNEzOce9pR8oj1w== +"@jest/test-result@^24.7.1": + version "24.7.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.7.1.tgz#19eacdb29a114300aed24db651e5d975f08b6bbe" + integrity sha512-3U7wITxstdEc2HMfBX7Yx3JZgiNBubwDqQMh+BXmZXHa3G13YWF3p6cK+5g0hGkN3iufg/vGPl3hLxQXD74Npg== + dependencies: + "@jest/console" "^24.7.1" + "@jest/types" "^24.7.0" + "@types/istanbul-lib-coverage" "^2.0.0" + +"@jest/test-sequencer@^24.7.1": + version "24.7.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.7.1.tgz#9c18e428e1ad945fa74f6233a9d35745ca0e63e0" + integrity sha512-84HQkCpVZI/G1zq53gHJvSmhUer4aMYp9tTaffW28Ih5OxfCg8hGr3nTSbL1OhVDRrFZwvF+/R9gY6JRkDUpUA== + dependencies: + "@jest/test-result" "^24.7.1" + jest-haste-map "^24.7.1" + jest-runner "^24.7.1" + jest-runtime "^24.7.1" + +"@jest/transform@^24.7.1": + version "24.7.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.7.1.tgz#872318f125bcfab2de11f53b465ab1aa780789c2" + integrity sha512-EsOUqP9ULuJ66IkZQhI5LufCHlTbi7hrcllRMUEV/tOgqBVQi93+9qEvkX0n8mYpVXQ8VjwmICeRgg58mrtIEw== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^24.5.0" + "@jest/types" "^24.7.0" babel-plugin-istanbul "^5.1.0" chalk "^2.0.1" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.1.15" - jest-haste-map "^24.5.0" + jest-haste-map "^24.7.1" jest-regex-util "^24.3.0" - jest-util "^24.5.0" + jest-util "^24.7.1" micromatch "^3.1.10" realpath-native "^1.1.0" slash "^2.0.0" @@ -1020,6 +1056,14 @@ "@types/istanbul-lib-coverage" "^1.1.0" "@types/yargs" "^12.0.9" +"@jest/types@^24.7.0": + version "24.7.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.7.0.tgz#c4ec8d1828cdf23234d9b4ee31f5482a3f04f48b" + integrity sha512-ipJUa2rFWiKoBqMKP63Myb6h9+iT3FHRTF2M8OR6irxWzItisa8i4dcSg14IbvmXUnBlHBlUQPYUHWyX3UPpYA== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/yargs" "^12.0.9" + "@react-native-community/cli@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-2.0.0.tgz#7ccf28545b7d6357a7c4b8eea7baa3f463f753fb" @@ -1102,6 +1146,11 @@ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.0.tgz#2cc2ca41051498382b43157c8227fea60363f94a" integrity sha512-ohkhb9LehJy+PA40rDtGAji61NCgdtKLAlFoYp4cnuuQEswwdK3vz9SOIkkyc3wrk8dzjphQApNs56yyXLStaQ== +"@types/istanbul-lib-coverage@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz#1eb8c033e98cf4e1a4cedcaf8bcafe8cb7591e85" + integrity sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg== + "@types/node@*": version "11.11.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.2.tgz#873d2c3f3824212cc16130074699e1bcb38c0231" @@ -1496,16 +1545,16 @@ babel-eslint@10.0.1: eslint-scope "3.7.1" eslint-visitor-keys "^1.0.0" -babel-jest@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.5.0.tgz#0ea042789810c2bec9065f7c8ab4dc18e1d28559" - integrity sha512-0fKCXyRwxFTJL0UXDJiT2xYxO9Lu2vBd9n+cC+eDjESzcVG3s2DRGAxbzJX21fceB1WYoBjAh8pQ83dKcl003g== +babel-jest@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.7.1.tgz#73902c9ff15a7dfbdc9994b0b17fcefd96042178" + integrity sha512-GPnLqfk8Mtt0i4OemjWkChi73A3ALs4w2/QbG64uAj8b5mmwzxc7jbJVRZt8NJkxi6FopVHog9S3xX6UJKb2qg== dependencies: - "@jest/transform" "^24.5.0" - "@jest/types" "^24.5.0" + "@jest/transform" "^24.7.1" + "@jest/types" "^24.7.0" "@types/babel__core" "^7.1.0" babel-plugin-istanbul "^5.1.0" - babel-preset-jest "^24.3.0" + babel-preset-jest "^24.6.0" chalk "^2.4.2" slash "^2.0.0" @@ -1518,10 +1567,10 @@ babel-plugin-istanbul@^5.1.0: istanbul-lib-instrument "^3.0.0" test-exclude "^5.0.0" -babel-plugin-jest-hoist@^24.3.0: - version "24.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.3.0.tgz#f2e82952946f6e40bb0a75d266a3790d854c8b5b" - integrity sha512-nWh4N1mVH55Tzhx2isvUN5ebM5CDUvIpXPZYMRazQughie/EqGnbR+czzoQlhUmJG9pPJmYDRhvocotb2THl1w== +babel-plugin-jest-hoist@^24.6.0: + version "24.6.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz#f7f7f7ad150ee96d7a5e8e2c5da8319579e78019" + integrity sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w== dependencies: "@types/babel__traverse" "^7.0.6" @@ -1597,13 +1646,13 @@ babel-preset-fbjs@^3.1.2: "@babel/plugin-transform-template-literals" "^7.0.0" babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0" -babel-preset-jest@^24.3.0: - version "24.3.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.3.0.tgz#db88497e18869f15b24d9c0e547d8e0ab950796d" - integrity sha512-VGTV2QYBa/Kn3WCOKdfS31j9qomaXSgJqi65B6o05/1GsJyj9LVhSljM9ro4S+IBGj/ENhNBuH9bpqzztKAQSw== +babel-preset-jest@^24.6.0: + version "24.6.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz#66f06136eefce87797539c0d63f1769cc3915984" + integrity sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw== dependencies: "@babel/plugin-syntax-object-rest-spread" "^7.0.0" - babel-plugin-jest-hoist "^24.3.0" + babel-plugin-jest-hoist "^24.6.0" babylon@^6.15.0: version "6.18.0" @@ -2775,16 +2824,16 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expect@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-24.5.0.tgz#492fb0df8378d8474cc84b827776b069f46294ed" - integrity sha512-p2Gmc0CLxOgkyA93ySWmHFYHUPFIHG6XZ06l7WArWAsrqYVaVEkOU5NtT5i68KUyGKbkQgDCkiT65bWmdoL6Bw== +expect@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.7.1.tgz#d91defbab4e627470a152feaf35b3c31aa1c7c14" + integrity sha512-mGfvMTPduksV3xoI0xur56pQsg2vJjNf5+a+bXOjqCkiCBbmCayrBbHS/75y9K430cfqyocPr2ZjiNiRx4SRKw== dependencies: - "@jest/types" "^24.5.0" + "@jest/types" "^24.7.0" ansi-styles "^3.2.0" jest-get-type "^24.3.0" - jest-matcher-utils "^24.5.0" - jest-message-util "^24.5.0" + jest-matcher-utils "^24.7.0" + jest-message-util "^24.7.1" jest-regex-util "^24.3.0" extend-shallow@^1.1.2: @@ -3010,10 +3059,10 @@ flat-cache@^1.2.1: rimraf "~2.6.2" write "^0.2.1" -flow-bin@^0.95.0: - version "0.95.1" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.95.1.tgz#633113831ccff4b7ee70a2730f63fc43b69ba85f" - integrity sha512-06IOC/pqPMNRYtC6AMZEWYR9Fi6UdBC7gImGinPuNUpPZFnP5E9/0cBCl3DWrH4zz/gSM2HdDilU7vPGpYIr2w== +flow-bin@^0.96.0: + version "0.96.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.96.0.tgz#3b0379d97304dc1879ae6db627cd2d6819998661" + integrity sha512-OSxERs0EdhVxEVCst/HmlT/RcnXsQQIRqcfK9J9wC8/93JQj+xQz4RtlsmYe1PSRYaozuDLyPS5pIA81Zwzaww== flow-parser@0.*: version "0.89.0" @@ -3106,6 +3155,14 @@ fsevents@^1.2.3: nan "^2.9.2" node-pre-gyp "^0.10.0" +fsevents@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" + integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw== + dependencies: + nan "^2.9.2" + node-pre-gyp "^0.10.0" + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -3748,65 +3805,66 @@ iterall@^1.2.2: resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA== -jest-changed-files@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.5.0.tgz#4075269ee115d87194fd5822e642af22133cf705" - integrity sha512-Ikl29dosYnTsH9pYa1Tv9POkILBhN/TLZ37xbzgNsZ1D2+2n+8oEZS2yP1BrHn/T4Rs4Ggwwbp/x8CKOS5YJOg== +jest-changed-files@^24.7.0: + version "24.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.7.0.tgz#39d723a11b16ed7b373ac83adc76a69464b0c4fa" + integrity sha512-33BgewurnwSfJrW7T5/ZAXGE44o7swLslwh8aUckzq2e17/2Os1V0QU506ZNik3hjs8MgnEMKNkcud442NCDTw== dependencies: - "@jest/types" "^24.5.0" + "@jest/types" "^24.7.0" execa "^1.0.0" throat "^4.0.0" -jest-cli@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.5.0.tgz#598139d3446d1942fb7dc93944b9ba766d756d4b" - integrity sha512-P+Jp0SLO4KWN0cGlNtC7JV0dW1eSFR7eRpoOucP2UM0sqlzp/bVHeo71Omonvigrj9AvCKy7NtQANtqJ7FXz8g== +jest-cli@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.7.1.tgz#6093a539073b6f4953145abeeb9709cd621044f1" + integrity sha512-32OBoSCVPzcTslGFl6yVCMzB2SqX3IrWwZCY5mZYkb0D2WsogmU3eV2o8z7+gRQa4o4sZPX/k7GU+II7CxM6WQ== dependencies: - "@jest/core" "^24.5.0" - "@jest/test-result" "^24.5.0" - "@jest/types" "^24.5.0" + "@jest/core" "^24.7.1" + "@jest/test-result" "^24.7.1" + "@jest/types" "^24.7.0" chalk "^2.0.1" exit "^0.1.2" import-local "^2.0.0" is-ci "^2.0.0" - jest-config "^24.5.0" - jest-util "^24.5.0" - jest-validate "^24.5.0" + jest-config "^24.7.1" + jest-util "^24.7.1" + jest-validate "^24.7.0" prompts "^2.0.1" realpath-native "^1.1.0" yargs "^12.0.2" -jest-config@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.5.0.tgz#404d1bc6bb81aed6bd1890d07e2dca9fbba2e121" - integrity sha512-t2UTh0Z2uZhGBNVseF8wA2DS2SuBiLOL6qpLq18+OZGfFUxTM7BzUVKyHFN/vuN+s/aslY1COW95j1Rw81huOQ== +jest-config@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.7.1.tgz#6c1dd4db82a89710a3cf66bdba97827c9a1cf052" + integrity sha512-8FlJNLI+X+MU37j7j8RE4DnJkvAghXmBWdArVzypW6WxfGuxiL/CCkzBg0gHtXhD2rxla3IMOSUAHylSKYJ83g== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^24.5.0" - babel-jest "^24.5.0" + "@jest/test-sequencer" "^24.7.1" + "@jest/types" "^24.7.0" + babel-jest "^24.7.1" chalk "^2.0.1" glob "^7.1.1" - jest-environment-jsdom "^24.5.0" - jest-environment-node "^24.5.0" + jest-environment-jsdom "^24.7.1" + jest-environment-node "^24.7.1" jest-get-type "^24.3.0" - jest-jasmine2 "^24.5.0" + jest-jasmine2 "^24.7.1" jest-regex-util "^24.3.0" - jest-resolve "^24.5.0" - jest-util "^24.5.0" - jest-validate "^24.5.0" + jest-resolve "^24.7.1" + jest-util "^24.7.1" + jest-validate "^24.7.0" micromatch "^3.1.10" - pretty-format "^24.5.0" + pretty-format "^24.7.0" realpath-native "^1.1.0" -jest-diff@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.5.0.tgz#a2d8627964bb06a91893c0fbcb28ab228c257652" - integrity sha512-mCILZd9r7zqL9Uh6yNoXjwGQx0/J43OD2vvWVKwOEOLZliQOsojXwqboubAQ+Tszrb6DHGmNU7m4whGeB9YOqw== +jest-diff@^24.7.0: + version "24.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.7.0.tgz#5d862899be46249754806f66e5729c07fcb3580f" + integrity sha512-ULQZ5B1lWpH70O4xsANC4tf4Ko6RrpwhE3PtG6ERjMg1TiYTC2Wp4IntJVGro6a8HG9luYHhhmF4grF0Pltckg== dependencies: chalk "^2.0.1" diff-sequences "^24.3.0" jest-get-type "^24.3.0" - pretty-format "^24.5.0" + pretty-format "^24.7.0" jest-docblock@^21.0.0: version "21.2.0" @@ -3820,39 +3878,39 @@ jest-docblock@^24.3.0: dependencies: detect-newline "^2.1.0" -jest-each@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.5.0.tgz#da14d017a1b7d0f01fb458d338314cafe7f72318" - integrity sha512-6gy3Kh37PwIT5sNvNY2VchtIFOOBh8UCYnBlxXMb5sr5wpJUDPTUATX2Axq1Vfk+HWTMpsYPeVYp4TXx5uqUBw== +jest-each@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.7.1.tgz#fcc7dda4147c28430ad9fb6dc7211cd17ab54e74" + integrity sha512-4fsS8fEfLa3lfnI1Jw6NxjhyRTgfpuOVTeUZZFyVYqeTa4hPhr2YkToUhouuLTrL2eMGOfpbdMyRx0GQ/VooKA== dependencies: - "@jest/types" "^24.5.0" + "@jest/types" "^24.7.0" chalk "^2.0.1" jest-get-type "^24.3.0" - jest-util "^24.5.0" - pretty-format "^24.5.0" - -jest-environment-jsdom@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.5.0.tgz#1c3143063e1374100f8c2723a8b6aad23b6db7eb" - integrity sha512-62Ih5HbdAWcsqBx2ktUnor/mABBo1U111AvZWcLKeWN/n/gc5ZvDBKe4Og44fQdHKiXClrNGC6G0mBo6wrPeGQ== - dependencies: - "@jest/environment" "^24.5.0" - "@jest/fake-timers" "^24.5.0" - "@jest/types" "^24.5.0" - jest-mock "^24.5.0" - jest-util "^24.5.0" + jest-util "^24.7.1" + pretty-format "^24.7.0" + +jest-environment-jsdom@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.7.1.tgz#a40e004b4458ebeb8a98082df135fd501b9fbbd6" + integrity sha512-Gnhb+RqE2JuQGb3kJsLF8vfqjt3PHKSstq4Xc8ic+ax7QKo4Z0RWGucU3YV+DwKR3T9SYc+3YCUQEJs8r7+Jxg== + dependencies: + "@jest/environment" "^24.7.1" + "@jest/fake-timers" "^24.7.1" + "@jest/types" "^24.7.0" + jest-mock "^24.7.0" + jest-util "^24.7.1" jsdom "^11.5.1" -jest-environment-node@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.5.0.tgz#763eebdf529f75b60aa600c6cf8cb09873caa6ab" - integrity sha512-du6FuyWr/GbKLsmAbzNF9mpr2Iu2zWSaq/BNHzX+vgOcts9f2ayXBweS7RAhr+6bLp6qRpMB6utAMF5Ygktxnw== +jest-environment-node@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.7.1.tgz#fa2c047a31522a48038d26ee4f7c8fd9c1ecfe12" + integrity sha512-GJJQt1p9/C6aj6yNZMvovZuxTUd+BEJprETdvTKSb4kHcw4mFj8777USQV0FJoJ4V3djpOwA5eWyPwfq//PFBA== dependencies: - "@jest/environment" "^24.5.0" - "@jest/fake-timers" "^24.5.0" - "@jest/types" "^24.5.0" - jest-mock "^24.5.0" - jest-util "^24.5.0" + "@jest/environment" "^24.7.1" + "@jest/fake-timers" "^24.7.1" + "@jest/types" "^24.7.0" + jest-mock "^24.7.0" + jest-util "^24.7.1" jest-get-type@^24.0.0: version "24.0.0" @@ -3878,41 +3936,45 @@ jest-haste-map@24.0.0: micromatch "^3.1.10" sane "^3.0.0" -jest-haste-map@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.5.0.tgz#3f17d0c548b99c0c96ed2893f9c0ccecb2eb9066" - integrity sha512-mb4Yrcjw9vBgSvobDwH8QUovxApdimGcOkp+V1ucGGw4Uvr3VzZQBJhNm1UY3dXYm4XXyTW2G7IBEZ9pM2ggRQ== +jest-haste-map@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.7.1.tgz#772e215cd84080d4bbcb759cfb668ad649a21471" + integrity sha512-g0tWkzjpHD2qa03mTKhlydbmmYiA2KdcJe762SbfFo/7NIMgBWAA0XqQlApPwkWOF7Cxoi/gUqL0i6DIoLpMBw== dependencies: - "@jest/types" "^24.5.0" + "@jest/types" "^24.7.0" + anymatch "^2.0.0" fb-watchman "^2.0.0" graceful-fs "^4.1.15" invariant "^2.2.4" jest-serializer "^24.4.0" - jest-util "^24.5.0" - jest-worker "^24.4.0" + jest-util "^24.7.1" + jest-worker "^24.6.0" micromatch "^3.1.10" sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^1.2.7" -jest-jasmine2@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.5.0.tgz#e6af4d7f73dc527d007cca5a5b177c0bcc29d111" - integrity sha512-sfVrxVcx1rNUbBeyIyhkqZ4q+seNKyAG6iM0S2TYBdQsXjoFDdqWFfsUxb6uXSsbimbXX/NMkJIwUZ1uT9+/Aw== +jest-jasmine2@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.7.1.tgz#01398686dabe46553716303993f3be62e5d9d818" + integrity sha512-Y/9AOJDV1XS44wNwCaThq4Pw3gBPiOv/s6NcbOAkVRRUEPu+36L2xoPsqQXsDrxoBerqeyslpn2TpCI8Zr6J2w== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^24.5.0" - "@jest/test-result" "^24.5.0" - "@jest/types" "^24.5.0" + "@jest/environment" "^24.7.1" + "@jest/test-result" "^24.7.1" + "@jest/types" "^24.7.0" chalk "^2.0.1" co "^4.6.0" - expect "^24.5.0" + expect "^24.7.1" is-generator-fn "^2.0.0" - jest-each "^24.5.0" - jest-matcher-utils "^24.5.0" - jest-message-util "^24.5.0" - jest-runtime "^24.5.0" - jest-snapshot "^24.5.0" - jest-util "^24.5.0" - pretty-format "^24.5.0" + jest-each "^24.7.1" + jest-matcher-utils "^24.7.0" + jest-message-util "^24.7.1" + jest-runtime "^24.7.1" + jest-snapshot "^24.7.1" + jest-util "^24.7.1" + pretty-format "^24.7.0" throat "^4.0.0" jest-junit@^6.3.0: @@ -3925,22 +3987,22 @@ jest-junit@^6.3.0: strip-ansi "^4.0.0" xml "^1.0.1" -jest-leak-detector@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.5.0.tgz#21ae2b3b0da252c1171cd494f75696d65fb6fa89" - integrity sha512-LZKBjGovFRx3cRBkqmIg+BZnxbrLqhQl09IziMk3oeh1OV81Hg30RUIx885mq8qBv1PA0comB9bjKcuyNO1bCQ== +jest-leak-detector@^24.7.0: + version "24.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.7.0.tgz#323ff93ed69be12e898f5b040952f08a94288ff9" + integrity sha512-zV0qHKZGXtmPVVzT99CVEcHE9XDf+8LwiE0Ob7jjezERiGVljmqKFWpV2IkG+rkFIEUHFEkMiICu7wnoPM/RoQ== dependencies: - pretty-format "^24.5.0" + pretty-format "^24.7.0" -jest-matcher-utils@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.5.0.tgz#5995549dcf09fa94406e89526e877b094dad8770" - integrity sha512-QM1nmLROjLj8GMGzg5VBra3I9hLpjMPtF1YqzQS3rvWn2ltGZLrGAO1KQ9zUCVi5aCvrkbS5Ndm2evIP9yZg1Q== +jest-matcher-utils@^24.7.0: + version "24.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.7.0.tgz#bbee1ff37bc8b2e4afcaabc91617c1526af4bcd4" + integrity sha512-158ieSgk3LNXeUhbVJYRXyTPSCqNgVXOp/GT7O94mYd3pk/8+odKTyR1JLtNOQSPzNi8NFYVONtvSWA/e1RDXg== dependencies: chalk "^2.0.1" - jest-diff "^24.5.0" + jest-diff "^24.7.0" jest-get-type "^24.3.0" - pretty-format "^24.5.0" + pretty-format "^24.7.0" jest-message-util@^24.5.0: version "24.5.0" @@ -3956,6 +4018,20 @@ jest-message-util@^24.5.0: slash "^2.0.0" stack-utils "^1.0.1" +jest-message-util@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.7.1.tgz#f1dc3a6c195647096a99d0f1dadbc447ae547018" + integrity sha512-dk0gqVtyqezCHbcbk60CdIf+8UHgD+lmRHifeH3JRcnAqh4nEyPytSc9/L1+cQyxC+ceaeP696N4ATe7L+omcg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/test-result" "^24.7.1" + "@jest/types" "^24.7.0" + "@types/stack-utils" "^1.0.1" + chalk "^2.0.1" + micromatch "^3.1.10" + slash "^2.0.0" + stack-utils "^1.0.1" + jest-mock@^24.5.0: version "24.5.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.5.0.tgz#976912c99a93f2a1c67497a9414aa4d9da4c7b76" @@ -3963,6 +4039,13 @@ jest-mock@^24.5.0: dependencies: "@jest/types" "^24.5.0" +jest-mock@^24.7.0: + version "24.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.7.0.tgz#e49ce7262c12d7f5897b0d8af77f6db8e538023b" + integrity sha512-6taW4B4WUcEiT2V9BbOmwyGuwuAFT2G8yghF7nyNW1/2gq5+6aTqSPcS9lS6ArvEkX55vbPAS/Jarx5LSm4Fng== + dependencies: + "@jest/types" "^24.7.0" + jest-pnp-resolver@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" @@ -3973,75 +4056,75 @@ jest-regex-util@^24.3.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.3.0.tgz#d5a65f60be1ae3e310d5214a0307581995227b36" integrity sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg== -jest-resolve-dependencies@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.5.0.tgz#1a0dae9cdd41349ca4a84148b3e78da2ba33fd4b" - integrity sha512-dRVM1D+gWrFfrq2vlL5P9P/i8kB4BOYqYf3S7xczZ+A6PC3SgXYSErX/ScW/469pWMboM1uAhgLF+39nXlirCQ== +jest-resolve-dependencies@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.7.1.tgz#cf93bbef26999488a96a2b2012f9fe7375aa378f" + integrity sha512-2Eyh5LJB2liNzfk4eo7bD1ZyBbqEJIyyrFtZG555cSWW9xVHxII2NuOkSl1yUYTAYCAmM2f2aIT5A7HzNmubyg== dependencies: - "@jest/types" "^24.5.0" + "@jest/types" "^24.7.0" jest-regex-util "^24.3.0" - jest-snapshot "^24.5.0" + jest-snapshot "^24.7.1" -jest-resolve@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.5.0.tgz#8c16ba08f60a1616c3b1cd7afb24574f50a24d04" - integrity sha512-ZIfGqLX1Rg8xJpQqNjdoO8MuxHV1q/i2OO1hLXjgCWFWs5bsedS8UrOdgjUqqNae6DXA+pCyRmdcB7lQEEbXew== +jest-resolve@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.7.1.tgz#e4150198299298380a75a9fd55043fa3b9b17fde" + integrity sha512-Bgrc+/UUZpGJ4323sQyj85hV9d+ANyPNu6XfRDUcyFNX1QrZpSoM0kE4Mb2vZMAYTJZsBFzYe8X1UaOkOELSbw== dependencies: - "@jest/types" "^24.5.0" + "@jest/types" "^24.7.0" browser-resolve "^1.11.3" chalk "^2.0.1" jest-pnp-resolver "^1.2.1" realpath-native "^1.1.0" -jest-runner@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.5.0.tgz#9be26ece4fd4ab3dfb528b887523144b7c5ffca8" - integrity sha512-oqsiS9TkIZV5dVkD+GmbNfWBRPIvxqmlTQ+AQUJUQ07n+4xTSDc40r+aKBynHw9/tLzafC00DIbJjB2cOZdvMA== +jest-runner@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.7.1.tgz#41c8a02a06aa23ea82d8bffd69d7fa98d32f85bf" + integrity sha512-aNFc9liWU/xt+G9pobdKZ4qTeG/wnJrJna3VqunziDNsWT3EBpmxXZRBMKCsNMyfy+A/XHiV+tsMLufdsNdgCw== dependencies: - "@jest/console" "^24.3.0" - "@jest/environment" "^24.5.0" - "@jest/test-result" "^24.5.0" - "@jest/types" "^24.5.0" + "@jest/console" "^24.7.1" + "@jest/environment" "^24.7.1" + "@jest/test-result" "^24.7.1" + "@jest/types" "^24.7.0" chalk "^2.4.2" exit "^0.1.2" graceful-fs "^4.1.15" - jest-config "^24.5.0" + jest-config "^24.7.1" jest-docblock "^24.3.0" - jest-haste-map "^24.5.0" - jest-jasmine2 "^24.5.0" - jest-leak-detector "^24.5.0" - jest-message-util "^24.5.0" - jest-resolve "^24.5.0" - jest-runtime "^24.5.0" - jest-util "^24.5.0" - jest-worker "^24.4.0" + jest-haste-map "^24.7.1" + jest-jasmine2 "^24.7.1" + jest-leak-detector "^24.7.0" + jest-message-util "^24.7.1" + jest-resolve "^24.7.1" + jest-runtime "^24.7.1" + jest-util "^24.7.1" + jest-worker "^24.6.0" source-map-support "^0.5.6" throat "^4.0.0" -jest-runtime@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.5.0.tgz#3a76e0bfef4db3896d5116e9e518be47ba771aa2" - integrity sha512-GTFHzfLdwpaeoDPilNpBrorlPoNZuZrwKKzKJs09vWwHo+9TOsIIuszK8cWOuKC7ss07aN1922Ge8fsGdsqCuw== +jest-runtime@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.7.1.tgz#2ffd70b22dd03a5988c0ab9465c85cdf5d25c597" + integrity sha512-0VAbyBy7tll3R+82IPJpf6QZkokzXPIS71aDeqh+WzPRXRCNz6StQ45otFariPdJ4FmXpDiArdhZrzNAC3sj6A== dependencies: - "@jest/console" "^24.3.0" - "@jest/environment" "^24.5.0" + "@jest/console" "^24.7.1" + "@jest/environment" "^24.7.1" "@jest/source-map" "^24.3.0" - "@jest/transform" "^24.5.0" - "@jest/types" "^24.5.0" + "@jest/transform" "^24.7.1" + "@jest/types" "^24.7.0" "@types/yargs" "^12.0.2" chalk "^2.0.1" exit "^0.1.2" glob "^7.1.3" graceful-fs "^4.1.15" - jest-config "^24.5.0" - jest-haste-map "^24.5.0" - jest-message-util "^24.5.0" - jest-mock "^24.5.0" + jest-config "^24.7.1" + jest-haste-map "^24.7.1" + jest-message-util "^24.7.1" + jest-mock "^24.7.0" jest-regex-util "^24.3.0" - jest-resolve "^24.5.0" - jest-snapshot "^24.5.0" - jest-util "^24.5.0" - jest-validate "^24.5.0" + jest-resolve "^24.7.1" + jest-snapshot "^24.7.1" + jest-util "^24.7.1" + jest-validate "^24.7.0" realpath-native "^1.1.0" slash "^2.0.0" strip-bom "^3.0.0" @@ -4057,25 +4140,25 @@ jest-serializer@^24.0.0, jest-serializer@^24.4.0: resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.4.0.tgz#f70c5918c8ea9235ccb1276d232e459080588db3" integrity sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q== -jest-snapshot@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.5.0.tgz#e5d224468a759fd19e36f01217aac912f500f779" - integrity sha512-eBEeJb5ROk0NcpodmSKnCVgMOo+Qsu5z9EDl3tGffwPzK1yV37mjGWF2YeIz1NkntgTzP+fUL4s09a0+0dpVWA== +jest-snapshot@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.7.1.tgz#bd5a35f74aedff070975e9e9c90024f082099568" + integrity sha512-8Xk5O4p+JsZZn4RCNUS3pxA+ORKpEKepE+a5ejIKrId9CwrVN0NY+vkqEkXqlstA5NMBkNahXkR/4qEBy0t5yA== dependencies: "@babel/types" "^7.0.0" - "@jest/types" "^24.5.0" + "@jest/types" "^24.7.0" chalk "^2.0.1" - expect "^24.5.0" - jest-diff "^24.5.0" - jest-matcher-utils "^24.5.0" - jest-message-util "^24.5.0" - jest-resolve "^24.5.0" + expect "^24.7.1" + jest-diff "^24.7.0" + jest-matcher-utils "^24.7.0" + jest-message-util "^24.7.1" + jest-resolve "^24.7.1" mkdirp "^0.5.1" natural-compare "^1.4.0" - pretty-format "^24.5.0" + pretty-format "^24.7.0" semver "^5.5.0" -jest-util@^24.0.0, jest-util@^24.5.0: +jest-util@^24.0.0: version "24.5.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.5.0.tgz#9d9cb06d9dcccc8e7cc76df91b1635025d7baa84" integrity sha512-Xy8JsD0jvBz85K7VsTIQDuY44s+hYJyppAhcsHsOsGisVtdhar6fajf2UOf2mEVEgh15ZSdA0zkCuheN8cbr1Q== @@ -4094,6 +4177,24 @@ jest-util@^24.0.0, jest-util@^24.5.0: slash "^2.0.0" source-map "^0.6.0" +jest-util@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.7.1.tgz#b4043df57b32a23be27c75a2763d8faf242038ff" + integrity sha512-/KilOue2n2rZ5AnEBYoxOXkeTu6vi7cjgQ8MXEkih0oeAXT6JkS3fr7/j8+engCjciOU1Nq5loMSKe0A1oeX0A== + dependencies: + "@jest/console" "^24.7.1" + "@jest/fake-timers" "^24.7.1" + "@jest/source-map" "^24.3.0" + "@jest/test-result" "^24.7.1" + "@jest/types" "^24.7.0" + callsites "^3.0.0" + chalk "^2.0.1" + graceful-fs "^4.1.15" + is-ci "^2.0.0" + mkdirp "^0.5.1" + slash "^2.0.0" + source-map "^0.6.0" + jest-validate@^24.0.0: version "24.0.0" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.0.0.tgz#aa8571a46983a6538328fef20406b4a496b6c020" @@ -4105,30 +4206,29 @@ jest-validate@^24.0.0: leven "^2.1.0" pretty-format "^24.0.0" -jest-validate@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.5.0.tgz#62fd93d81214c070bb2d7a55f329a79d8057c7de" - integrity sha512-gg0dYszxjgK2o11unSIJhkOFZqNRQbWOAB2/LOUdsd2LfD9oXiMeuee8XsT0iRy5EvSccBgB4h/9HRbIo3MHgQ== +jest-validate@^24.7.0: + version "24.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.7.0.tgz#70007076f338528ee1b1c8a8258b1b0bb982508d" + integrity sha512-cgai/gts9B2chz1rqVdmLhzYxQbgQurh1PEQSvSgPZ8KGa1AqXsqC45W5wKEwzxKrWqypuQrQxnF4+G9VejJJA== dependencies: - "@jest/types" "^24.5.0" + "@jest/types" "^24.7.0" camelcase "^5.0.0" chalk "^2.0.1" jest-get-type "^24.3.0" leven "^2.1.0" - pretty-format "^24.5.0" + pretty-format "^24.7.0" -jest-watcher@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.5.0.tgz#da7bd9cb5967e274889b42078c8f501ae1c47761" - integrity sha512-/hCpgR6bg0nKvD3nv4KasdTxuhwfViVMHUATJlnGCD0r1QrmIssimPbmc5KfAQblAVxkD8xrzuij9vfPUk1/rA== +jest-watcher@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.7.1.tgz#e161363d7f3f4e1ef3d389b7b3a0aad247b673f5" + integrity sha512-Wd6TepHLRHVKLNPacEsBwlp9raeBIO+01xrN24Dek4ggTS8HHnOzYSFnvp+6MtkkJ3KfMzy220KTi95e2rRkrw== dependencies: - "@jest/test-result" "^24.5.0" - "@jest/types" "^24.5.0" - "@types/node" "*" + "@jest/test-result" "^24.7.1" + "@jest/types" "^24.7.0" "@types/yargs" "^12.0.9" ansi-escapes "^3.0.0" chalk "^2.0.1" - jest-util "^24.5.0" + jest-util "^24.7.1" string-length "^2.0.0" jest-worker@24.0.0: @@ -4139,7 +4239,7 @@ jest-worker@24.0.0: merge-stream "^1.0.1" supports-color "^6.1.0" -jest-worker@^24.0.0, jest-worker@^24.4.0: +jest-worker@^24.0.0: version "24.4.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.4.0.tgz#fbc452b0120bb5c2a70cdc88fa132b48eeb11dd0" integrity sha512-BH9X/klG9vxwoO99ZBUbZFfV8qO0XNZ5SIiCyYK2zOuJBl6YJVAeNIQjcoOVNu4HGEHeYEKsUWws8kSlSbZ9YQ== @@ -4148,13 +4248,21 @@ jest-worker@^24.0.0, jest-worker@^24.4.0: merge-stream "^1.0.1" supports-color "^6.1.0" -jest@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-24.5.0.tgz#38f11ae2c2baa2f86c2bc4d8a91d2b51612cd19a" - integrity sha512-lxL+Fq5/RH7inxxmfS2aZLCf8MsS+YCUBfeiNO6BWz/MmjhDGaIEA/2bzEf9q4Q0X+mtFHiinHFvQ0u+RvW/qQ== +jest-worker@^24.6.0: + version "24.6.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.6.0.tgz#7f81ceae34b7cde0c9827a6980c35b7cdc0161b3" + integrity sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ== + dependencies: + merge-stream "^1.0.1" + supports-color "^6.1.0" + +jest@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-24.7.1.tgz#0d94331cf510c75893ee32f87d7321d5bf8f2501" + integrity sha512-AbvRar5r++izmqo5gdbAjTeA6uNRGoNRuj5vHB0OnDXo2DXWZJVuaObiGgtlvhKb+cWy2oYbQSfxv7Q7GjnAtA== dependencies: import-local "^2.0.0" - jest-cli "^24.5.0" + jest-cli "^24.7.1" js-levenshtein@^1.1.3: version "1.1.4" @@ -4192,6 +4300,11 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= +jsc-android@^236355.1.1: + version "236355.1.1" + resolved "https://registry.yarnpkg.com/jsc-android/-/jsc-android-236355.1.1.tgz#43e153b722e3c60dd0595be4e7430baf65e67c9c" + integrity sha512-2py4f0McZIl/oH6AzPj1Ebutc58fyeLvwq6gyVYp1RsWr4qeLNHAPfW3kmfeVMz44oUBJMQ0lECZg9n4KBhHbQ== + jscodeshift@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.6.2.tgz#bb648e6bce717a597d165781158b0d73b7fa99c3" @@ -5594,12 +5707,12 @@ pretty-format@^24.0.0: ansi-regex "^4.0.0" ansi-styles "^3.2.0" -pretty-format@^24.5.0: - version "24.5.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.5.0.tgz#cc69a0281a62cd7242633fc135d6930cd889822d" - integrity sha512-/3RuSghukCf8Riu5Ncve0iI+BzVkbRU5EeUoArKARZobREycuH5O4waxvaNIloEXdb0qwgmEAed5vTpX1HNROQ== +pretty-format@^24.7.0: + version "24.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.7.0.tgz#d23106bc2edcd776079c2daa5da02bcb12ed0c10" + integrity sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA== dependencies: - "@jest/types" "^24.5.0" + "@jest/types" "^24.7.0" ansi-regex "^4.0.0" ansi-styles "^3.2.0" react-is "^16.8.4" @@ -6966,7 +7079,7 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^0.1.2" -walker@~1.0.5: +walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=