diff --git a/.circleci/Dockerfiles/scripts/run-ci-e2e-tests.sh b/.circleci/Dockerfiles/scripts/run-ci-e2e-tests.sh index e22867fd04163c..4c12de632141fd 100755 --- a/.circleci/Dockerfiles/scripts/run-ci-e2e-tests.sh +++ b/.circleci/Dockerfiles/scripts/run-ci-e2e-tests.sh @@ -199,7 +199,7 @@ function e2e_suite() { return 1 fi - echo "Starting packager server" + echo "Starting Metro" npm start >> /dev/null & SERVER_PID=$! sleep 15 diff --git a/.circleci/config.yml b/.circleci/config.yml index 45d5e039b9a33e..b62ec8a85a6321 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -122,7 +122,7 @@ commands: description: Homebrew package to install type: string steps: - - run: + - run: name: "Brew: Install << parameters.package >>" command: HOMEBREW_NO_AUTO_UPDATE=1 brew install << parameters.package >> >/dev/null @@ -410,7 +410,7 @@ jobs: name: Fetch CocoaPods Specs command: | curl https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf - - run: + - run: name: Setup the CocoaPods environment command: pod setup @@ -429,7 +429,7 @@ jobs: - when: condition: << parameters.run_unit_tests >> steps: - - run: + - run: name: "Run Tests: iOS Unit and Integration Tests" command: yarn test-ios # Runs iOS Detox e2e tests @@ -473,7 +473,7 @@ jobs: parameters: run_disabled_tests: type: boolean - default: false + default: false steps: - restore_cache_checkout: checkout_type: android @@ -606,7 +606,7 @@ jobs: - ANDROID_HOME: "C:\\Android\\android-sdk" - ANDROID_NDK: "C:\\Android\\android-sdk\\ndk\\19.2.5345600" - ANDROID_BUILD_VERSION: 28 - - ANDROID_TOOLS_VERSION: 29.0.2 + - ANDROID_TOOLS_VERSION: 29.0.3 - GRADLE_OPTS: -Dorg.gradle.daemon=false - NDK_VERSION: 19.2.5345600 steps: @@ -783,18 +783,18 @@ workflows: run_unit_tests: true requires: - setup_ios - - test_ios: - name: test_ios_detox - run_disabled_tests: false - run_detox_tests: true - requires: - - setup_ios - - test_ios: - name: test_ios_detox_frameworks - use_frameworks: true - run_detox_tests: true - requires: - - setup_ios + # - test_ios: + # name: test_ios_detox + # run_disabled_tests: false + # run_detox_tests: true + # requires: + # - setup_ios + # - test_ios: + # name: test_ios_detox_frameworks + # use_frameworks: true + # run_detox_tests: true + # requires: + # - setup_ios - test_js: name: test_js_prev_lts executor: nodeprevlts diff --git a/.flowconfig b/.flowconfig index db7ce0a5e6a957..16f997971fe0db 100644 --- a/.flowconfig +++ b/.flowconfig @@ -59,8 +59,8 @@ suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState suppress_type=$FlowFixMeEmpty -experimental.well_formed_exports=true -experimental.types_first=true +well_formed_exports=true +types_first=true experimental.abstract_locations=true [lints] @@ -86,4 +86,4 @@ untyped-import untyped-type-import [version] -^0.127.0 +^0.129.0 diff --git a/.flowconfig.android b/.flowconfig.android index c4e92ddec22200..5fcc18922577d3 100644 --- a/.flowconfig.android +++ b/.flowconfig.android @@ -62,8 +62,8 @@ suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState suppress_type=$FlowFixMeEmpty -experimental.well_formed_exports=true -experimental.types_first=true +well_formed_exports=true +types_first=true experimental.abstract_locations=true [lints] @@ -89,4 +89,4 @@ untyped-import untyped-type-import [version] -^0.127.0 +^0.129.0 diff --git a/.flowconfig.macos b/.flowconfig.macos index 02d1c53e6da1bc..020edb4e834b4c 100644 --- a/.flowconfig.macos +++ b/.flowconfig.macos @@ -59,8 +59,8 @@ suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState suppress_type=$FlowFixMeEmpty -experimental.well_formed_exports=true -experimental.types_first=true +well_formed_exports=true +types_first=true experimental.abstract_locations=true [lints] @@ -85,4 +85,4 @@ untyped-import untyped-type-import [version] -^0.127.0 +^0.129.0 diff --git a/Libraries/Animated/src/NativeAnimatedHelper.js b/Libraries/Animated/src/NativeAnimatedHelper.js index e24ba00097f087..632b543c50ea6f 100644 --- a/Libraries/Animated/src/NativeAnimatedHelper.js +++ b/Libraries/Animated/src/NativeAnimatedHelper.js @@ -168,7 +168,7 @@ const API = { * In general native animated implementation should support any numeric property that doesn't need * to be updated through the shadow view hierarchy (all non-layout properties). */ -const STYLES_WHITELIST = { +const SUPPORTED_STYLES = { opacity: true, transform: true, borderRadius: true, @@ -192,7 +192,7 @@ const STYLES_WHITELIST = { translateY: true, }; -const TRANSFORM_WHITELIST = { +const SUPPORTED_TRANSFORMS = { translateX: true, translateY: true, scale: true, @@ -214,11 +214,11 @@ const SUPPORTED_INTERPOLATION_PARAMS = { }; function addWhitelistedStyleProp(prop: string): void { - STYLES_WHITELIST[prop] = true; + SUPPORTED_STYLES[prop] = true; } function addWhitelistedTransformProp(prop: string): void { - TRANSFORM_WHITELIST[prop] = true; + SUPPORTED_TRANSFORMS[prop] = true; } function addWhitelistedInterpolationParam(param: string): void { @@ -242,7 +242,7 @@ function validateTransform( >, ): void { configs.forEach(config => { - if (!TRANSFORM_WHITELIST.hasOwnProperty(config.property)) { + if (!SUPPORTED_TRANSFORMS.hasOwnProperty(config.property)) { throw new Error( `Property '${config.property}' is not supported by native animated module`, ); @@ -252,7 +252,7 @@ function validateTransform( function validateStyles(styles: {[key: string]: ?number, ...}): void { for (const key in styles) { - if (!STYLES_WHITELIST.hasOwnProperty(key)) { + if (!SUPPORTED_STYLES.hasOwnProperty(key)) { throw new Error( `Style property '${key}' is not supported by native animated module`, ); diff --git a/Libraries/Blob/RCTBlobManager.mm b/Libraries/Blob/RCTBlobManager.mm index 380f3ae22885a6..a75844ed6ae1ed 100755 --- a/Libraries/Blob/RCTBlobManager.mm +++ b/Libraries/Blob/RCTBlobManager.mm @@ -38,7 +38,7 @@ @implementation RCTBlobManager @synthesize bridge = _bridge; @synthesize methodQueue = _methodQueue; -@synthesize turboModuleLookupDelegate = _turboModuleLookupDelegate; +@synthesize turboModuleRegistry = _turboModuleRegistry; - (void)setBridge:(RCTBridge *)bridge { @@ -140,7 +140,7 @@ - (void)remove:(NSString *)blobId RCT_EXPORT_METHOD(addNetworkingHandler) { - RCTNetworking *const networking = _bridge ? _bridge.networking : [_turboModuleLookupDelegate moduleForName:"RCTNetworking"]; + RCTNetworking *const networking = _bridge ? _bridge.networking : [_turboModuleRegistry moduleForName:"RCTNetworking"]; // TODO(T63516227): Why can methodQueue be nil here? // We don't want to do anything when methodQueue is nil. diff --git a/Libraries/Components/Pressable/Pressable.js b/Libraries/Components/Pressable/Pressable.js index 49086e75127037..0e72a979a001e2 100644 --- a/Libraries/Components/Pressable/Pressable.js +++ b/Libraries/Components/Pressable/Pressable.js @@ -240,7 +240,8 @@ function Pressable(props: Props, forwardedRef): React.Node { focusable={focusable !== false} hitSlop={hitSlop} ref={viewRef} - style={typeof style === 'function' ? style({pressed}) : style}> + style={typeof style === 'function' ? style({pressed}) : style} + collapsable={false}> {typeof children === 'function' ? children({pressed}) : children} {__DEV__ ? : null} diff --git a/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap b/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap index 00b9b67f112640..6f244c492b92fb 100644 --- a/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap +++ b/Libraries/Components/Pressable/__tests__/__snapshots__/Pressable-test.js.snap @@ -4,6 +4,7 @@ exports[` should render as expected: should deep render when mocked should render as expected: should deep render when not mo ; type Props = $ReadOnly<{| diff --git a/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js index 27f7abbe7d1c2f..dffccc428ff01a 100644 --- a/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js +++ b/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js @@ -25,7 +25,6 @@ import type {ColorValue} from '../../StyleSheet/StyleSheet'; import requireNativeComponent from '../../ReactNative/requireNativeComponent'; import codegenNativeCommands from '../../Utilities/codegenNativeCommands'; import type {TextInputNativeCommands} from './TextInputNativeCommands'; -import * as React from 'react'; import AndroidTextInputViewConfig from './AndroidTextInputViewConfig'; const ReactNativeViewConfigRegistry = require('../../Renderer/shims/ReactNativeViewConfigRegistry'); diff --git a/Libraries/Components/TextInput/AndroidTextInputViewConfig.js b/Libraries/Components/TextInput/AndroidTextInputViewConfig.js index c4a43a773398cb..0853b60a75dc65 100644 --- a/Libraries/Components/TextInput/AndroidTextInputViewConfig.js +++ b/Libraries/Components/TextInput/AndroidTextInputViewConfig.js @@ -16,6 +16,36 @@ import type {ReactNativeBaseComponentViewConfig} from '../../Renderer/shims/Reac const AndroidTextInputViewConfig = { uiViewClassName: 'AndroidTextInput', bubblingEventTypes: { + topBlur: { + phasedRegistrationNames: { + bubbled: 'onBlur', + captured: 'onBlurCapture', + }, + }, + topEndEditing: { + phasedRegistrationNames: { + bubbled: 'onEndEditing', + captured: 'onEndEditingCapture', + }, + }, + topFocus: { + phasedRegistrationNames: { + bubbled: 'onFocus', + captured: 'onFocusCapture', + }, + }, + topKeyPress: { + phasedRegistrationNames: { + bubbled: 'onKeyPress', + captured: 'onKeyPressCapture', + }, + }, + topSubmitEditing: { + phasedRegistrationNames: { + bubbled: 'onSubmitEditing', + captured: 'onSubmitEditingCapture', + }, + }, topTextInput: { phasedRegistrationNames: { bubbled: 'onTextInput', diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js index a55164413f4bba..8bfa1cb75eeb26 100644 --- a/Libraries/Components/Touchable/Touchable.js +++ b/Libraries/Components/Touchable/Touchable.js @@ -21,7 +21,6 @@ const UIManager = require('../../ReactNative/UIManager'); const View = require('../View/View'); const SoundManager = require('../Sound/SoundManager'); -const keyMirror = require('fbjs/lib/keyMirror'); const normalizeColor = require('../../StyleSheet/normalizeColor'); import type {EdgeInsetsProp} from '../../StyleSheet/EdgeInsetsPropType'; @@ -129,16 +128,16 @@ const extractSingleTouch = nativeEvent => { * Touchable states. */ -const States = keyMirror({ - NOT_RESPONDER: null, // Not the responder - RESPONDER_INACTIVE_PRESS_IN: null, // Responder, inactive, in the `PressRect` - RESPONDER_INACTIVE_PRESS_OUT: null, // Responder, inactive, out of `PressRect` - RESPONDER_ACTIVE_PRESS_IN: null, // Responder, active, in the `PressRect` - RESPONDER_ACTIVE_PRESS_OUT: null, // Responder, active, out of `PressRect` - RESPONDER_ACTIVE_LONG_PRESS_IN: null, // Responder, active, in the `PressRect`, after long press threshold - RESPONDER_ACTIVE_LONG_PRESS_OUT: null, // Responder, active, out of `PressRect`, after long press threshold - ERROR: null, -}); +const States = { + NOT_RESPONDER: 'NOT_RESPONDER', // Not the responder + RESPONDER_INACTIVE_PRESS_IN: 'RESPONDER_INACTIVE_PRESS_IN', // Responder, inactive, in the `PressRect` + RESPONDER_INACTIVE_PRESS_OUT: 'RESPONDER_INACTIVE_PRESS_OUT', // Responder, inactive, out of `PressRect` + RESPONDER_ACTIVE_PRESS_IN: 'RESPONDER_ACTIVE_PRESS_IN', // Responder, active, in the `PressRect` + RESPONDER_ACTIVE_PRESS_OUT: 'RESPONDER_ACTIVE_PRESS_OUT', // Responder, active, out of `PressRect` + RESPONDER_ACTIVE_LONG_PRESS_IN: 'RESPONDER_ACTIVE_LONG_PRESS_IN', // Responder, active, in the `PressRect`, after long press threshold + RESPONDER_ACTIVE_LONG_PRESS_OUT: 'RESPONDER_ACTIVE_LONG_PRESS_OUT', // Responder, active, out of `PressRect`, after long press threshold + ERROR: 'ERROR', +}; type State = | typeof States.NOT_RESPONDER @@ -190,15 +189,15 @@ const IsLongPressingIn = { /** * Inputs to the state machine. */ -const Signals = keyMirror({ - DELAY: null, - RESPONDER_GRANT: null, - RESPONDER_RELEASE: null, - RESPONDER_TERMINATED: null, - ENTER_PRESS_RECT: null, - LEAVE_PRESS_RECT: null, - LONG_PRESS_DETECTED: null, -}); +const Signals = { + DELAY: 'DELAY', + RESPONDER_GRANT: 'RESPONDER_GRANT', + RESPONDER_RELEASE: 'RESPONDER_RELEASE', + RESPONDER_TERMINATED: 'RESPONDER_TERMINATED', + ENTER_PRESS_RECT: 'ENTER_PRESS_RECT', + LEAVE_PRESS_RECT: 'LEAVE_PRESS_RECT', + LONG_PRESS_DETECTED: 'LONG_PRESS_DETECTED', +}; type Signal = | typeof Signals.DELAY diff --git a/Libraries/Core/Timers/JSTimers.js b/Libraries/Core/Timers/JSTimers.js index 4c154db519deb2..00a25d93d9146c 100644 --- a/Libraries/Core/Timers/JSTimers.js +++ b/Libraries/Core/Timers/JSTimers.js @@ -18,14 +18,6 @@ const invariant = require('invariant'); import NativeTiming from './NativeTiming'; -let _performanceNow = null; -function performanceNow() { - if (!_performanceNow) { - _performanceNow = require('fbjs/lib/performanceNow'); - } - return _performanceNow(); -} - /** * JS implementation of timer functions. Must be completely driven by an * external clock signal, all that's stored here is timerID, timer type, and @@ -129,14 +121,17 @@ function _callTimer(timerID: number, frameTime: number, didTimeout: ?boolean) { ) { callback(); } else if (type === 'requestAnimationFrame') { - callback(performanceNow()); + callback(global.performance.now()); } else if (type === 'requestIdleCallback') { callback({ timeRemaining: function() { // TODO: Optimisation: allow running for longer than one frame if // there are no pending JS calls on the bridge from native. This // would require a way to check the bridge queue synchronously. - return Math.max(0, FRAME_DURATION - (performanceNow() - frameTime)); + return Math.max( + 0, + FRAME_DURATION - (global.performance.now() - frameTime), + ); }, didTimeout: !!didTimeout, }); @@ -318,7 +313,7 @@ const JSTimers = { const index = requestIdleCallbacks.indexOf(id); if (index > -1) { requestIdleCallbacks.splice(index, 1); - _callTimer(id, performanceNow(), true); + _callTimer(id, global.performance.now(), true); } delete requestIdleCallbackTimeouts[id]; if (requestIdleCallbacks.length === 0) { @@ -403,7 +398,7 @@ const JSTimers = { callIdleCallbacks: function(frameTime: number) { if ( - FRAME_DURATION - (performanceNow() - frameTime) < + FRAME_DURATION - (global.performance.now() - frameTime) < IDLE_CALLBACK_FRAME_DEADLINE ) { return; diff --git a/Libraries/Image/RCTImageLoader.mm b/Libraries/Image/RCTImageLoader.mm index 90c7a3b5ef7287..e16dcea69efa95 100644 --- a/Libraries/Image/RCTImageLoader.mm +++ b/Libraries/Image/RCTImageLoader.mm @@ -124,7 +124,7 @@ @implementation RCTImageLoader @synthesize maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks; @synthesize maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks; @synthesize maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes; -@synthesize turboModuleLookupDelegate = _turboModuleLookupDelegate; +@synthesize turboModuleRegistry = _turboModuleRegistry; RCT_EXPORT_MODULE() @@ -657,7 +657,7 @@ - (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request { // Check if networking module is available if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)] - && ![_turboModuleLookupDelegate moduleForName:"RCTNetworking"]) { + && ![_turboModuleRegistry moduleForName:"RCTNetworking"]) { RCTLogError(@"No suitable image URL loader found for %@. You may need to " " import the RCTNetwork library in order to load images.", request.URL.absoluteString); @@ -666,7 +666,7 @@ - (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request RCTNetworking *networking = [_bridge networking]; if (!networking) { - networking = [_turboModuleLookupDelegate moduleForName:"RCTNetworking"]; + networking = [_turboModuleRegistry moduleForName:"RCTNetworking"]; } // Check if networking module can load image @@ -1260,7 +1260,7 @@ @implementation RCTBridge (RCTImageLoader) - (RCTImageLoader *)imageLoader { RCTLogWarn(@"Calling bridge.imageLoader is deprecated and will not work in newer versions of RN. Please update to the " - "moduleForClass API or turboModuleLookupDelegate API."); + "moduleForClass API or turboModuleRegistry API."); return [self moduleForClass:[RCTImageLoader class]]; } diff --git a/Libraries/Interaction/InteractionManager.js b/Libraries/Interaction/InteractionManager.js index 1bf5cbf032db4b..c438a67e7a9f1c 100644 --- a/Libraries/Interaction/InteractionManager.js +++ b/Libraries/Interaction/InteractionManager.js @@ -15,7 +15,6 @@ const TaskQueue = require('./TaskQueue'); const infoLog = require('../Utilities/infoLog'); const invariant = require('invariant'); -const keyMirror = require('fbjs/lib/keyMirror'); import EventEmitter from '../vendor/emitter/EventEmitter'; @@ -77,10 +76,10 @@ const DEBUG: false = false; * from executing, making apps more responsive. */ const InteractionManager = { - Events: keyMirror({ - interactionStart: true, - interactionComplete: true, - }), + Events: { + interactionStart: 'interactionStart', + interactionComplete: 'interactionComplete', + }, /** * Schedule a function to run after all interactions have completed. Returns a cancellable diff --git a/Libraries/Interaction/JSEventLoopWatchdog.js b/Libraries/Interaction/JSEventLoopWatchdog.js index c2787f188fd722..9d9f6cc05b0736 100644 --- a/Libraries/Interaction/JSEventLoopWatchdog.js +++ b/Libraries/Interaction/JSEventLoopWatchdog.js @@ -11,7 +11,6 @@ 'use strict'; const infoLog = require('../Utilities/infoLog'); -const performanceNow = require('fbjs/lib/performanceNow'); type Handler = { onIterate?: () => void, @@ -44,7 +43,7 @@ const JSEventLoopWatchdog = { totalStallTime = 0; stallCount = 0; longestStall = 0; - lastInterval = performanceNow(); + lastInterval = global.performance.now(); }, addHandler: function(handler: Handler) { handlers.push(handler); @@ -55,9 +54,9 @@ const JSEventLoopWatchdog = { return; } installed = true; - lastInterval = performanceNow(); + lastInterval = global.performance.now(); function iteration() { - const now = performanceNow(); + const now = global.performance.now(); const busyTime = now - lastInterval; if (busyTime >= thresholdMS) { const stallTime = busyTime - thresholdMS; diff --git a/Libraries/Lists/FillRateHelper.js b/Libraries/Lists/FillRateHelper.js index a1d58669c48da5..e9c579e656e2f2 100644 --- a/Libraries/Lists/FillRateHelper.js +++ b/Libraries/Lists/FillRateHelper.js @@ -10,7 +10,6 @@ 'use strict'; -const performanceNow = require('fbjs/lib/performanceNow'); const warning = require('fbjs/lib/warning'); export type FillRateInfo = Info; @@ -89,7 +88,7 @@ class FillRateHelper { activate() { if (this._enabled && this._samplesStartTime == null) { DEBUG && console.debug('FillRateHelper: activate'); - this._samplesStartTime = performanceNow(); + this._samplesStartTime = global.performance.now(); } } @@ -108,7 +107,7 @@ class FillRateHelper { this._resetData(); return; } - const total_time_spent = performanceNow() - start; + const total_time_spent = global.performance.now() - start; const info: any = { ...this._info, total_time_spent, @@ -172,7 +171,7 @@ class FillRateHelper { const scrollSpeed = Math.round(Math.abs(velocity) * 1000); // px / sec // Whether blank now or not, record the elapsed time blank if we were blank last time. - const now = performanceNow(); + const now = global.performance.now(); if (this._anyBlankStartTime != null) { this._info.any_blank_ms += now - this._anyBlankStartTime; } diff --git a/Libraries/LogBox/Data/__tests__/LogBoxData-test.js b/Libraries/LogBox/Data/__tests__/LogBoxData-test.js index b0dfc3074aa079..2c0df65b9aa622 100644 --- a/Libraries/LogBox/Data/__tests__/LogBoxData-test.js +++ b/Libraries/LogBox/Data/__tests__/LogBoxData-test.js @@ -51,7 +51,7 @@ const observe = () => { }; }; -const addLogs = logs => { +const addLogs = (logs, options) => { logs.forEach(message => { LogBoxData.addLog({ level: 'warn', @@ -62,10 +62,13 @@ const addLogs = logs => { category: message, componentStack: [], }); + if (options == null || options.flush !== false) { + jest.runOnlyPendingTimers(); + } }); }; -const addSoftErrors = errors => { +const addSoftErrors = (errors, options) => { errors.forEach(error => { LogBoxData.addException( Object.assign( @@ -85,10 +88,13 @@ const addSoftErrors = errors => { : error, ), ); + if (options == null || options.flush !== false) { + jest.runOnlyPendingTimers(); + } }); }; -const addFatalErrors = errors => { +const addFatalErrors = (errors, options) => { errors.forEach(error => { LogBoxData.addException( Object.assign( @@ -108,37 +114,43 @@ const addFatalErrors = errors => { : error, ), ); + if (options == null || options.flush !== false) { + // Errors include two timers, the second is for optimistic symbolication. + jest.runOnlyPendingTimers(); + jest.runOnlyPendingTimers(); + } }); }; -const addSyntaxError = () => { - addFatalErrors([ - { - message: ` - +const addSyntaxError = options => { + addFatalErrors( + [ + { + message: ` 197 | }); 198 | > 199 | export default CrashReactApp; | ^ 200 |`, - originalMessage: `TransformError SyntaxError: /path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js: 'import' and 'export' may only appear at the top level (199:0) - + originalMessage: `TransformError SyntaxError: /path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js: 'import' and 'export' may only appear at the top level (199:0) 197 | }); 198 | > 199 | export default CrashReactApp; | ^ 200 |`, - }, - ]); + }, + ], + options, + ); }; beforeEach(() => { jest.resetModules(); }); -const flushLogs = () => { - jest.runAllImmediates(); - jest.runAllTimers(); +const flushToObservers = () => { + // Observer updates are debounced and need to advance timers to flush. + jest.runOnlyPendingTimers(); }; describe('LogBoxData', () => { @@ -147,7 +159,6 @@ describe('LogBoxData', () => { addSoftErrors(['B']); addFatalErrors(['C']); addSyntaxError(); - flushLogs(); expect(registry().length).toBe(4); expect(registry()[0]).toBeDefined(); @@ -169,10 +180,9 @@ describe('LogBoxData', () => { addSoftErrors(['C', 'D']); addFatalErrors(['E', 'F']); addSyntaxError(); - flushLogs(); expect(registry().length).toBe(7); - expect(selectedLogIndex()).toBe(4); // Fatal index. + expect(selectedLogIndex()).toBe(6); // Syntax error index. LogBoxData.clear(); expect(registry().length).toBe(0); @@ -183,14 +193,13 @@ describe('LogBoxData', () => { addLogs(['A', 'B']); addSoftErrors(['C', 'D', 'E']); addSyntaxError(); - flushLogs(); expect(registry().length).toBe(6); - expect(selectedLogIndex()).toBe(5); // Fatal index. + expect(selectedLogIndex()).toBe(5); // Syntax error index. LogBoxData.clearWarnings(); expect(registry().length).toBe(4); - expect(selectedLogIndex()).toBe(3); // New fatal index. + expect(selectedLogIndex()).toBe(3); // New syntax error index. }); it('clears errors and fatals, but not syntax errors.', () => { @@ -198,14 +207,13 @@ describe('LogBoxData', () => { addSoftErrors(['C', 'D', 'E']); addFatalErrors(['F']); addSyntaxError(); - flushLogs(); expect(registry().length).toBe(7); - expect(selectedLogIndex()).toBe(5); // Fatal index. + expect(selectedLogIndex()).toBe(6); // Syntax error index. LogBoxData.clearErrors(); expect(registry().length).toBe(3); - expect(selectedLogIndex()).toBe(2); // New Fatal index. + expect(selectedLogIndex()).toBe(2); // New syntax error index. }); it('clears all types except syntax errors', () => { @@ -213,24 +221,25 @@ describe('LogBoxData', () => { addSoftErrors(['C', 'D', 'E']); addFatalErrors(['F']); addSyntaxError(); - flushLogs(); expect(registry().length).toBe(7); - expect(selectedLogIndex()).toBe(5); // Fatal index. + expect(selectedLogIndex()).toBe(6); // Syntax error index. LogBoxData.clearErrors(); LogBoxData.clearWarnings(); expect(registry().length).toBe(1); - expect(selectedLogIndex()).toBe(0); // New Fatal index. + expect(selectedLogIndex()).toBe(0); // New syntax error index. }); it('keeps logs in chronological order', () => { - addLogs(['A']); - addSoftErrors(['B']); - addFatalErrors(['C']); - flushLogs(); + addLogs(['A'], {flush: false}); + addSoftErrors(['B'], {flush: false}); + addFatalErrors(['C'], {flush: false}); + + // Flush logs manually. + jest.runAllTimers(); + addLogs(['D']); - flushLogs(); let logs = registry(); expect(logs.length).toBe(4); @@ -240,7 +249,6 @@ describe('LogBoxData', () => { expect(logs[3].category).toEqual('D'); addLogs(['A']); - jest.runAllImmediates(); // Expect `A` to be added to the end of the registry. logs = registry(); @@ -256,40 +264,39 @@ describe('LogBoxData', () => { expect(selectedLogIndex()).toBe(-1); LogBoxData.setSelectedLog(1); - flushLogs(); expect(selectedLogIndex()).toBe(1); }); it('does not set the selectedLogIndex for warnings', () => { addLogs(['A']); - flushLogs(); expect(selectedLogIndex()).toBe(-1); }); it('does not set the selectedLogIndex for soft errors', () => { addSoftErrors(['A']); - flushLogs(); expect(selectedLogIndex()).toBe(-1); }); it('sets the selectedLogIndex to the first fatal error (after symbolication)', () => { - addFatalErrors(['A']); + addFatalErrors(['A'], {flush: false}); - // Order maters for symbolication before timeout. - flushLogs(); - jest.runAllTimers(); + // Order maters for testing symbolication before timeout. + jest.runOnlyPendingTimers(); // Trigger appendNewLog. + jest.advanceTimersByTime(10); // Trigger symbolication but not timeout. + jest.runAllTimers(); // Flush remaining. expect(selectedLogIndex()).toBe(0); - addLogs(['B']); - addFatalErrors(['C']); + addLogs(['B'], {flush: false}); + addFatalErrors(['C'], {flush: false}); - // Order maters for symbolication before timeout. - flushLogs(); - jest.runAllTimers(); + // Order maters for testing symbolication before timeout. + jest.runOnlyPendingTimers(); // Trigger appendNewLogs. + jest.advanceTimersByTime(10); // Trigger symbolication. + jest.runAllTimers(); // Flush remaining. // This should still be 0 (the first fatal exception) // becuase it is the most likely source of the error. @@ -299,20 +306,22 @@ describe('LogBoxData', () => { }); it('sets the selectedLogIndex to the first fatal error (hitting timeout limit)', () => { - addFatalErrors(['A']); + addFatalErrors(['A'], {flush: false}); - // Order maters for timeout before symbolication. - jest.runAllTimers(); - flushLogs(); + // Order maters for testing timeout before symbolication. + jest.runOnlyPendingTimers(); // Trigger appendNewLog. + jest.advanceTimersByTime(1001); // Trigger OPTIMISTIC_WAIT_TIME timeout. + jest.runAllTimers(); // Flush remaining. expect(selectedLogIndex()).toBe(0); addLogs(['B']); addFatalErrors(['C']); - // Order maters for timeout before symbolication. - jest.runAllTimers(); - flushLogs(); + // Order maters for testing timeout before symbolication. + jest.runOnlyPendingTimers(); // Trigger appendNewLogs. + jest.advanceTimersByTime(1001); // Trigger OPTIMISTIC_WAIT_TIME timeout. + jest.runAllTimers(); // Flush remaining. // This should still be 0 (the first fatal exception) // becuase it is the most likely source of the error. @@ -323,45 +332,38 @@ describe('LogBoxData', () => { it('sets the selectedLogIndex to the last syntax error', () => { addSyntaxError(); - flushLogs(); expect(selectedLogIndex()).toBe(0); addLogs(['B']); addSyntaxError(); - flushLogs(); expect(selectedLogIndex()).toBe(2); }); it('keeps selectedLogIndex set to the syntax error even when a new fatal is added', () => { addSyntaxError(); - flushLogs(); expect(selectedLogIndex()).toBe(0); addLogs(['B']); addFatalErrors(['C']); - flushLogs(); expect(selectedLogIndex()).toBe(0); }); it('keeps selectedLogIndex set to the syntax error even when explicitly changed', () => { addSyntaxError(); - flushLogs(); expect(selectedLogIndex()).toBe(0); LogBoxData.setSelectedLog(1); - flushLogs(); expect(selectedLogIndex()).toBe(0); }); it('increments the count of previous log with matching category (logs)', () => { addLogs(['A', 'B']); - flushLogs(); let logs = registry(); expect(logs.length).toBe(2); @@ -371,7 +373,6 @@ describe('LogBoxData', () => { expect(logs[1].count).toBe(1); addLogs(['B']); - flushLogs(); // Expect `B` to be rolled into the last log. logs = registry(); @@ -384,7 +385,6 @@ describe('LogBoxData', () => { it('increments the count of previous log with matching category (errors)', () => { addFatalErrors(['A', 'B']); - flushLogs(); let logs = registry(); expect(logs.length).toBe(2); @@ -394,7 +394,6 @@ describe('LogBoxData', () => { expect(logs[1].count).toBe(1); addSoftErrors(['B']); - flushLogs(); // Expect `B` to be rolled into the last log. logs = registry(); @@ -407,14 +406,12 @@ describe('LogBoxData', () => { it('increments the count of previous log with matching category (syntax)', () => { addSyntaxError(); - flushLogs(); let logs = registry(); expect(logs.length).toBe(1); expect(logs[0].count).toBe(1); addSyntaxError(); - flushLogs(); logs = registry(); expect(logs.length).toBe(1); @@ -423,7 +420,6 @@ describe('LogBoxData', () => { it('ignores logs matching patterns (logs)', () => { addLogs(['A!', 'B?', 'C!']); - flushLogs(); expect(filteredRegistry().length).toBe(3); @@ -437,7 +433,6 @@ describe('LogBoxData', () => { it('ignores logs matching patterns (errors)', () => { addSoftErrors(['A!', 'B?']); addFatalErrors(['C!']); - flushLogs(); expect(filteredRegistry().length).toBe(3); @@ -450,7 +445,6 @@ describe('LogBoxData', () => { it('ignores matching regexs or pattern (logs)', () => { addLogs(['There are 4 dogs', 'There are 3 cats', 'There are H cats']); - flushLogs(); expect(filteredRegistry().length).toBe(3); @@ -467,7 +461,6 @@ describe('LogBoxData', () => { it('ignores matching regexs or pattern (errors)', () => { addSoftErrors(['There are 4 dogs', 'There are 3 cats']); addFatalErrors(['There are H cats']); - flushLogs(); expect(filteredRegistry().length).toBe(3); @@ -486,7 +479,6 @@ describe('LogBoxData', () => { addSoftErrors(['B?']); addFatalErrors(['C!']); addSyntaxError(); - flushLogs(); expect(registry().length).toBe(4); expect(disabledState()).toBe(false); @@ -516,7 +508,7 @@ describe('LogBoxData', () => { expect(observer.mock.calls.length).toBe(1); addLogs(['A']); - flushLogs(); + flushToObservers(); expect(observer.mock.calls.length).toBe(2); // We expect observers to recieve the same Set object in sequential updates @@ -541,17 +533,17 @@ describe('LogBoxData', () => { expect(observer.mock.calls.length).toBe(1); addLogs(['A']); - flushLogs(); + flushToObservers(); expect(observer.mock.calls.length).toBe(2); const lastLog = Array.from(observer.mock.calls[1][0].logs)[0]; LogBoxData.dismiss(lastLog); - jest.runAllImmediates(); + flushToObservers(); expect(observer.mock.calls.length).toBe(3); // Does nothing when category does not exist. LogBoxData.dismiss(lastLog); - jest.runAllImmediates(); + flushToObservers(); expect(observer.mock.calls.length).toBe(3); }); @@ -560,16 +552,16 @@ describe('LogBoxData', () => { expect(observer.mock.calls.length).toBe(1); addLogs(['A']); - flushLogs(); + flushToObservers(); expect(observer.mock.calls.length).toBe(2); LogBoxData.clear(); - jest.runAllImmediates(); + flushToObservers(); expect(observer.mock.calls.length).toBe(3); // Does nothing when already empty. LogBoxData.clear(); - jest.runAllImmediates(); + flushToObservers(); expect(observer.mock.calls.length).toBe(3); }); @@ -581,17 +573,16 @@ describe('LogBoxData', () => { addSoftErrors(['B']); addFatalErrors(['C']); addSyntaxError(); - jest.runAllImmediates(); - expect(observer.mock.calls.length).toBe(2); + expect(observer.mock.calls.length).toBe(5); LogBoxData.clearWarnings(); - jest.runAllImmediates(); - expect(observer.mock.calls.length).toBe(3); + flushToObservers(); + expect(observer.mock.calls.length).toBe(6); // Does nothing when already empty. LogBoxData.clearWarnings(); - jest.runAllImmediates(); - expect(observer.mock.calls.length).toBe(3); + flushToObservers(); + expect(observer.mock.calls.length).toBe(6); }); it('updates observers when errors cleared', () => { @@ -602,17 +593,16 @@ describe('LogBoxData', () => { addSoftErrors(['B']); addFatalErrors(['C']); addSyntaxError(); - jest.runAllImmediates(); - expect(observer.mock.calls.length).toBe(2); + expect(observer.mock.calls.length).toBe(5); LogBoxData.clearErrors(); - jest.runAllImmediates(); - expect(observer.mock.calls.length).toBe(3); + flushToObservers(); + expect(observer.mock.calls.length).toBe(6); // Does nothing when already empty. LogBoxData.clearErrors(); - jest.runAllImmediates(); - expect(observer.mock.calls.length).toBe(3); + flushToObservers(); + expect(observer.mock.calls.length).toBe(6); }); it('updates observers when an ignore pattern is added', () => { @@ -620,16 +610,16 @@ describe('LogBoxData', () => { expect(observer.mock.calls.length).toBe(1); LogBoxData.addIgnorePatterns(['?']); - jest.runAllImmediates(); + flushToObservers(); expect(observer.mock.calls.length).toBe(2); LogBoxData.addIgnorePatterns(['!']); - jest.runAllImmediates(); + flushToObservers(); expect(observer.mock.calls.length).toBe(3); // Does nothing for an existing ignore pattern. LogBoxData.addIgnorePatterns(['!']); - jest.runAllImmediates(); + flushToObservers(); expect(observer.mock.calls.length).toBe(3); }); @@ -638,21 +628,21 @@ describe('LogBoxData', () => { expect(observer.mock.calls.length).toBe(1); LogBoxData.setDisabled(true); - jest.runAllImmediates(); + flushToObservers(); expect(observer.mock.calls.length).toBe(2); // Does nothing when already disabled. LogBoxData.setDisabled(true); - jest.runAllImmediates(); + flushToObservers(); expect(observer.mock.calls.length).toBe(2); LogBoxData.setDisabled(false); - jest.runAllImmediates(); + flushToObservers(); expect(observer.mock.calls.length).toBe(3); // Does nothing when already enabled. LogBoxData.setDisabled(false); - jest.runAllImmediates(); + flushToObservers(); expect(observer.mock.calls.length).toBe(3); }); diff --git a/Libraries/NewAppScreen/components/Header.js b/Libraries/NewAppScreen/components/Header.js index b69fa5a109149e..541a51ebf64431 100644 --- a/Libraries/NewAppScreen/components/Header.js +++ b/Libraries/NewAppScreen/components/Header.js @@ -9,10 +9,11 @@ */ 'use strict'; -import Colors from './Colors'; import type {Node} from 'react'; import {ImageBackground, StyleSheet, Text, useColorScheme} from 'react-native'; import React from 'react'; +import Colors from './Colors'; +import HermesBadge from './HermesBadge'; const Header = (): Node => { const isDarkMode = useColorScheme() === 'dark'; @@ -27,6 +28,7 @@ const Header = (): Node => { }, ]} imageStyle={styles.logo}> + { color: isDarkMode ? Colors.white : Colors.black, }, ]}> - Welcome to React + Welcome to + {'\n'} + React Native ); @@ -61,7 +65,7 @@ const styles = StyleSheet.create({ }, text: { fontSize: 40, - fontWeight: '600', + fontWeight: '700', textAlign: 'center', }, }); diff --git a/Libraries/NewAppScreen/components/HermesBadge.js b/Libraries/NewAppScreen/components/HermesBadge.js new file mode 100644 index 00000000000000..aa02f3a9881455 --- /dev/null +++ b/Libraries/NewAppScreen/components/HermesBadge.js @@ -0,0 +1,46 @@ +/** + * 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 strict-local + * @format + */ + +import React from 'react'; +import type {Node} from 'react'; +import {StyleSheet, Text, useColorScheme, View} from 'react-native'; +import Colors from './Colors'; + +const HermesBadge = (): Node => { + const isDarkMode = useColorScheme() === 'dark'; + return global.HermesInternal ? ( + + + Engine: Hermes + + + ) : null; +}; + +const styles = StyleSheet.create({ + badge: { + position: 'absolute', + top: 8, + right: 12, + }, + badgeText: { + fontSize: 14, + fontWeight: '600', + textAlign: 'right', + }, +}); + +export default HermesBadge; diff --git a/Libraries/NewAppScreen/index.js b/Libraries/NewAppScreen/index.js index 4450683d792176..9da6d362b32924 100644 --- a/Libraries/NewAppScreen/index.js +++ b/Libraries/NewAppScreen/index.js @@ -10,10 +10,18 @@ 'use strict'; +import Colors from './components/Colors'; import Header from './components/Header'; +import HermesBadge from './components/HermesBadge'; import LearnMoreLinks from './components/LearnMoreLinks'; -import Colors from './components/Colors'; import DebugInstructions from './components/DebugInstructions'; import ReloadInstructions from './components/ReloadInstructions'; -export {Header, LearnMoreLinks, Colors, DebugInstructions, ReloadInstructions}; +export { + Colors, + Header, + HermesBadge, + LearnMoreLinks, + DebugInstructions, + ReloadInstructions, +}; diff --git a/Libraries/StyleSheet/StyleSheetTypes.js b/Libraries/StyleSheet/StyleSheetTypes.js index ba3ca816f6534b..a44babce2c38cc 100644 --- a/Libraries/StyleSheet/StyleSheetTypes.js +++ b/Libraries/StyleSheet/StyleSheetTypes.js @@ -522,8 +522,9 @@ type ____TransformStyle_Internal = $ReadOnly<{| * Because they are dynamically generated, they may cause performance regressions. Static * shadow image asset may be a better way to go for optimal performance. * - * These properties are iOS only - for similar functionality on Android, use the [`elevation` - * property](docs/viewstyleproptypes.html#elevation). + * Shadow-related properties are not fully supported on Android. + * To add a drop shadow to a view use the [`elevation` property](docs/viewstyleproptypes.html#elevation) (Android 5.0+). + * To customize the color use the [`shadowColor` property](docs/shadow-props.html#shadowColor) (Android 9.0+). */ export type ____ShadowStyle_Internal = $ReadOnly<{| /** diff --git a/Libraries/Text/TextInput/RCTBaseTextInputView.m b/Libraries/Text/TextInput/RCTBaseTextInputView.m index c1e4222f765173..e8e10d329ca4e6 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -381,7 +381,7 @@ - (void)textInputDidBeginEditing [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus reactTag:self.reactTag - text:[self.backedTextInputView.attributedText.string copy] // [TODO(macOS Candidate ISS#2710739) + text:[self.backedTextInputView.attributedText.string copy] key:nil eventCount:_nativeEventCount]; } @@ -395,13 +395,13 @@ - (void)textInputDidEndEditing { [_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd reactTag:self.reactTag - text:[self.backedTextInputView.attributedText.string copy] // [TODO(macOS Candidate ISS#2710739) + text:[self.backedTextInputView.attributedText.string copy] key:nil eventCount:_nativeEventCount]; [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur reactTag:self.reactTag - text:[self.backedTextInputView.attributedText.string copy] // [TODO(macOS Candidate ISS#2710739) + text:[self.backedTextInputView.attributedText.string copy] key:nil eventCount:_nativeEventCount]; } @@ -418,7 +418,7 @@ - (BOOL)textInputShouldReturn #endif // ]TODO(macOS Candidate ISS#2710739) [_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit reactTag:self.reactTag - text:[self.backedTextInputView.attributedText.string copy] // [TODO(macOS Candidate ISS#2710739) + text:[self.backedTextInputView.attributedText.string copy] key:nil eventCount:_nativeEventCount]; #if TARGET_OS_OSX // [TODO(macOS Candidate ISS#2710739) @@ -481,7 +481,7 @@ - (NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range } } - NSString *previousText = [backedTextInputView.attributedText.string copy] ?: @""; // TODO(OSS Candidate ISS#2710739) + NSString *previousText = [backedTextInputView.attributedText.string copy] ?: @""; if (range.location + range.length > backedTextInputView.attributedText.string.length) { _predictedText = backedTextInputView.attributedText.string; @@ -528,7 +528,7 @@ - (void)textInputDidChange if (_onChange) { _onChange(@{ - @"text": [self.attributedText.string copy], // [TODO(macOS Candidate ISS#2710739) + @"text": [self.attributedText.string copy], @"target": self.reactTag, @"eventCount": @(_nativeEventCount), }); diff --git a/Libraries/Utilities/HMRClient.js b/Libraries/Utilities/HMRClient.js index e653ab89ff0ba5..d890f389bac73d 100644 --- a/Libraries/Utilities/HMRClient.js +++ b/Libraries/Utilities/HMRClient.js @@ -172,17 +172,17 @@ const HMRClient: HMRClientNativeInterface = { ); client.on('connection-error', e => { - let error = `Cannot connect to the Metro server. + let error = `Cannot connect to Metro. Try the following to fix the issue: -- Ensure that the Metro server is running and available on the same network`; +- Ensure that Metro is running and available on the same network`; if ( Platform.OS === 'ios' || Platform.OS === 'macos' /* TODO(macOS GH#774) */ ) { error += ` -- Ensure that the Metro server URL is correctly set in AppDelegate`; +- Ensure that the Metro URL is correctly set in AppDelegate`; } else { error += ` - Ensure that your device/emulator is connected to your machine and has USB debugging enabled - run 'adb devices' to see a list of connected devices @@ -225,12 +225,12 @@ Error: ${e.message}`; if (data.type === 'GraphNotFoundError') { client.close(); setHMRUnavailableReason( - 'The Metro server has restarted since the last edit. Reload to reconnect.', + 'Metro has restarted since the last edit. Reload to reconnect.', ); } else if (data.type === 'RevisionNotFoundError') { client.close(); setHMRUnavailableReason( - 'The Metro server and the client are out of sync. Reload to reconnect.', + 'Metro and the client are out of sync. Reload to reconnect.', ); } else { currentCompileErrorMessage = `${data.type} ${data.message}`; @@ -242,7 +242,7 @@ Error: ${e.message}`; client.on('close', data => { LoadingView.hide(); - setHMRUnavailableReason('Disconnected from the Metro server.'); + setHMRUnavailableReason('Disconnected from Metro.'); }); if (isEnabled) { diff --git a/Libraries/Utilities/ReactNativeTestTools.js b/Libraries/Utilities/ReactNativeTestTools.js index f6dadab9b7da93..2240656b434d23 100644 --- a/Libraries/Utilities/ReactNativeTestTools.js +++ b/Libraries/Utilities/ReactNativeTestTools.js @@ -15,7 +15,7 @@ const React = require('react'); const ReactTestRenderer = require('react-test-renderer'); -const ShallowRenderer = require('react-test-renderer/shallow'); +const ShallowRenderer = require('react-shallow-renderer'); /* $FlowFixMe(>=0.125.1 site=react_native_fb) This comment suppresses an error * found when Flow v0.125.1 was deployed. To see the error, delete this comment * and run Flow. */ diff --git a/Libraries/Utilities/buildStyleInterpolator.js b/Libraries/Utilities/buildStyleInterpolator.js index a7ab6d89385f8f..ed86a32df6bc0b 100644 --- a/Libraries/Utilities/buildStyleInterpolator.js +++ b/Libraries/Utilities/buildStyleInterpolator.js @@ -9,11 +9,9 @@ 'use strict'; -const keyOf = require('fbjs/lib/keyOf'); - -const X_DIM = keyOf({x: null}); -const Y_DIM = keyOf({y: null}); -const Z_DIM = keyOf({z: null}); +const X_DIM = 'x'; +const Y_DIM = 'y'; +const Z_DIM = 'z'; const InitialOperationField = { transformTranslate: [0, 0, 0], diff --git a/Libraries/Utilities/createPerformanceLogger.js b/Libraries/Utilities/createPerformanceLogger.js index 2bfdb34258169e..556363a5fa4081 100644 --- a/Libraries/Utilities/createPerformanceLogger.js +++ b/Libraries/Utilities/createPerformanceLogger.js @@ -13,10 +13,7 @@ const Systrace = require('../Performance/Systrace'); const infoLog = require('./infoLog'); -const performanceNow = - global.nativeQPLTimestamp || - global.nativePerformanceNow || - require('fbjs/lib/performanceNow'); +const performanceNow = global.nativeQPLTimestamp ?? global.performance.now; type Timespan = { description?: string, diff --git a/RNTester/Podfile b/RNTester/Podfile index 268ebd64284ced..87c0f00dfc0918 100644 --- a/RNTester/Podfile +++ b/RNTester/Podfile @@ -81,6 +81,31 @@ target 'macOSBuild' do end # ]TODO(macOS GH#774) +def frameworks_pre_install(installer) + static_frameworks = ['FlipperKit', 'Flipper', 'Flipper-Folly', + 'CocoaAsyncSocket', 'ComponentKit', 'Flipper-DoubleConversion', + 'Flipper-Glog', 'Flipper-PeerTalk', 'Flipper-RSocket', + 'CocoaLibEvent', 'OpenSSL-Universal', 'boost-for-react-native'] + + Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {} + installer.pod_targets.each do |pod| + if static_frameworks.include?(pod.name) + def pod.build_type + Pod::Target::BuildType.static_library + end + end + end +end + +def codegen_pre_install(installer) + system("../scripts/generate-native-modules-specs.sh") +end + +pre_install do |installer| + frameworks_pre_install(installer) if ENV['USE_FRAMEWORKS'] == '1' + codegen_pre_install(installer) if ENV['USE_CODEGEN'] == '1' +end + post_install do |installer| flipper_post_install(installer) end diff --git a/RNTester/Podfile.lock b/RNTester/Podfile.lock index 2127bed0cb8f74..372911272cff10 100644 --- a/RNTester/Podfile.lock +++ b/RNTester/Podfile.lock @@ -524,8 +524,8 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845 CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f DoubleConversion: 2b45d0f8e156a5b02354c8a4062de64d41ccb4e0 - FBLazyVector: 25db8fc930c080720dd9f1d1a26d539ce9515858 - FBReactNativeSpec: 357b56d66b8d511a9f812b4c630bd4966a433371 + FBLazyVector: ab4c6d0152ef6fe898f1fe79f43a0eecbc87fbee + FBReactNativeSpec: 1181a07ef463f907e3f65dd10c67832d96a085cc Flipper: be611d4b742d8c87fbae2ca5f44603a02539e365 Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3 @@ -536,36 +536,36 @@ SPEC CHECKSUMS: glog: 789873d01e4b200777d0a09bc23d548446758699 OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355 RCT-Folly: 55d0039b24e192081ec0b2257f7bd9f42e382fb7 - RCTRequired: ce43f9f9167af58d0e1eecf5605634ab016faa81 - RCTTypeSafety: c9b0498fb46d5d1d756e0ce90dd5e60f1b9d5cdd - React: 9a7fd7a316d91eaabfb9b6fbe3456d873e1d92b2 - React-ART: c2a752357958d214b16806c18fd812e3eda5165e - React-callinvoker: 66b62bab1170e0359d46a8af0cb61a9a1a8d18ad - React-Core: 312e62d070297a0cf4c693eb9400145149b1fc70 - React-CoreModules: 68d668bfd30a500e49c83c68e2fc75524f2437f8 - React-cxxreact: 6e2e98c6b87bc02437806a9498c582a03a10c9f5 - React-jsi: 827382a7da620a02cb890accfcf84de279f272a6 - React-jsiexecutor: dbc72f3190c3f3bf7d2ac548ce17e12d357e122b - React-jsinspector: 320d5cb76361b00a6a87a8b5a67280c200712604 - React-perflogger: 0cac631583b97716913aafd5d5c07ac133bab771 - React-RCTActionSheet: c15c33157199c398d405a5cbc2715d9dba17bbf8 - React-RCTAnimation: bffa5450d36cae700b6c97bfdd617986810b6f33 - React-RCTBlob: 169d4c1f011c5fe60f43cbde36f25391c5bd62a7 - React-RCTImage: 3a129461152c25115f67a3052e086b8277c36938 - React-RCTLinking: 2f4d467f6449bfa6f35ef108ea4eada0c110e020 - React-RCTNetwork: 66bdde9026a6fe29b06b6027b847de96153653a7 - React-RCTPushNotification: c2b605a89e385917bb764017d0e8e5c6d4f4a206 - React-RCTSettings: 8d536a656c3d2f34ea155d24244488a0c39ec3b2 - React-RCTTest: 991feac2784e473e8b466091259cefe6d33bc72f - React-RCTText: 6be029a3b38a2b98f875b84a4bd182eebd048218 - React-RCTVibration: 655e32ab1393718662b8a6f75eef9f3d2ceedf96 - React-runtimeexecutor: b529b2ffeac2f15b775a37226471a2523a3f7844 + RCTRequired: 29bf5b171dee98522c32df97f5a9e295b7af0760 + RCTTypeSafety: b4dd5a6eff3e4d36017acc259b86e58dc98b0ed3 + React: 51b24afe583c5ad745fdc163985d4862d14ecd9a + React-ART: fe4e5da330d31f1858d4f065600a1b0b50497a69 + React-callinvoker: 9f3a98834312a1ff6835cc3c3d3b8fd51acecb7b + React-Core: 070959b879b02eb6e66cd7d8282b54626b560721 + React-CoreModules: 139e360cf3e6810390138d9beb98afa5572e10d4 + React-cxxreact: c50bb5c2fb8803fa1819ee3f069e953eb61c6f49 + React-jsi: c93a044d107be7665ac3244354be652f655d617f + React-jsiexecutor: ef3091e4690794539c4e1f98750083e6a360ae16 + React-jsinspector: 53d304bbf2e4a6ead8c236c8ddcb25d2cd4b85d0 + React-perflogger: 57587ce78fd0505730f492544ee14570b759b384 + React-RCTActionSheet: 9ace73e604ee2036c83f9c0e4b3077cf0740c686 + React-RCTAnimation: bd9070b019d8be03b1832ec7eee9cc56fff39d3b + React-RCTBlob: 4f142fd7572e53e7412ac361d50e7e6e091b8313 + React-RCTImage: b066681d362900cf6d4851bd40ce41c445067256 + React-RCTLinking: 419474ef4e57746853b1e689ad723c14f1c8b219 + React-RCTNetwork: 1055bf1e0cb1a8c2911367d2cc3d1aa2c88b2d40 + React-RCTPushNotification: acee780df4dcc58a6588c76517678b077984f3de + React-RCTSettings: 5615ad4051740041234b202b6aea820b4452725b + React-RCTTest: 7490fd4d8a7c704080dfdc54df672b6ea13626a4 + React-RCTText: 943d92f61cfe369d599b818c2c4c34f57eb80384 + React-RCTVibration: 05749088a899bba0191a61447cc20e1391df25f0 + React-runtimeexecutor: 52fa0483f4f6ce77e605f364b40c02c27cd60272 React-TurboModuleCxx-RNW: 18bb71af41fe34c8b12a56bef60aae7ee32b0817 - React-TurboModuleCxx-WinRTPort: 29b59397f335c90defd1c74eaf0982ccd13c52be - ReactCommon: 989cfe7b74513b6bed09cf75e733bc631ec963c7 - Yoga: ba2f886e24a03fa7dad871b727b5de2a7205dd8a + React-TurboModuleCxx-WinRTPort: bda120670acda667c1751bba68639377aa9dc24c + ReactCommon: 73845e097427ff0d225887fe9c291ec576e31e94 + Yoga: 3e98dc4f25d60ca42e409b5e53b3be634946d0d9 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: 7d43a928a9b9ad27329da110adbfadd923a39ba8 +PODFILE CHECKSUM: 2a5ebd1edccf5913eeb0748b1ce38c8dafa87a21 COCOAPODS: 1.10.1 diff --git a/RNTester/RNTester-macOS/AppDelegate.mm b/RNTester/RNTester-macOS/AppDelegate.mm index 002c372f1c2cd4..05e6a53ee52c13 100644 --- a/RNTester/RNTester-macOS/AppDelegate.mm +++ b/RNTester/RNTester-macOS/AppDelegate.mm @@ -98,6 +98,7 @@ - (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge delegate:self jsInvoker:bridge.jsCallInvoker]; + [bridge setRCTTurboModuleRegistry:_turboModuleManager]; #if RCT_DEV /** diff --git a/RNTester/RNTester/AppDelegate.mm b/RNTester/RNTester/AppDelegate.mm index d8bd2807b2fd23..364342afa1945a 100644 --- a/RNTester/RNTester/AppDelegate.mm +++ b/RNTester/RNTester/AppDelegate.mm @@ -161,6 +161,7 @@ - (void)loadSourceForBridge:(RCTBridge *)bridge _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge delegate:self jsInvoker:bridge.jsCallInvoker]; + [bridge setRCTTurboModuleRegistry:_turboModuleManager]; #if RCT_DEV /** diff --git a/RNTester/RNTesterUnitTests/RCTBundleURLProviderTests.m b/RNTester/RNTesterUnitTests/RCTBundleURLProviderTests.m index df71469ac09ccd..7e023f96cda650 100644 --- a/RNTester/RNTesterUnitTests/RCTBundleURLProviderTests.m +++ b/RNTester/RNTesterUnitTests/RCTBundleURLProviderTests.m @@ -10,6 +10,8 @@ #import #import +#import "OCMock/OCMock.h" + static NSString *const testFile = @"test.jsbundle"; static NSString *const mainBundle = @"main.jsbundle"; @@ -96,6 +98,8 @@ - (void)testLocalhostURL - (void)testIPURL { + id classMock = OCMClassMock([RCTBundleURLProvider class]); + [[[classMock stub] andReturnValue:@YES] isPackagerRunning:[OCMArg any]]; RCTBundleURLProvider *settings = [RCTBundleURLProvider sharedSettings]; settings.jsLocation = @"192.168.1.1"; NSURL *URL = [settings jsBundleURLForBundleRoot:testFile fallbackResource:nil]; diff --git a/RNTester/js/examples/Alert/AlertExample.js b/RNTester/js/examples/Alert/AlertExample.js index 4e362a525f91f9..dfafd870cf324b 100644 --- a/RNTester/js/examples/Alert/AlertExample.js +++ b/RNTester/js/examples/Alert/AlertExample.js @@ -18,8 +18,6 @@ const { View, } = require('react-native'); -const RNTesterBlock = require('../../components/RNTesterBlock'); - // corporate ipsum > lorem ipsum const alertMessage = 'Credibly reintermediate next-generation potentialities after goal-oriented ' + @@ -125,22 +123,6 @@ class SimpleAlertExampleBlock extends React.Component { } } -class AlertExample extends React.Component { - static title = 'Alert'; - - static description = - 'Alerts display a concise and informative message ' + - 'and prompt the user to make a decision.'; - - render() { - return ( - - - - ); - } -} - const styles = StyleSheet.create({ wrapper: { borderRadius: 5, @@ -152,7 +134,17 @@ const styles = StyleSheet.create({ }, }); -module.exports = { - AlertExample, - SimpleAlertExampleBlock, -}; +exports.title = 'Alert'; +exports.description = + 'Alerts display a concise and informative message ' + + 'and prompt the user to make a decision.'; +exports.examples = [ + { + title: 'Alerts', + render(): React.Node { + return ; + }, + }, +]; + +exports.SimpleAlertExampleBlock = SimpleAlertExampleBlock; diff --git a/RNTester/js/examples/BoxShadow/BoxShadowExample.js b/RNTester/js/examples/BoxShadow/BoxShadowExample.js index 5801cf3f5ad4ae..429fb0873c504c 100644 --- a/RNTester/js/examples/BoxShadow/BoxShadowExample.js +++ b/RNTester/js/examples/BoxShadow/BoxShadowExample.js @@ -46,6 +46,29 @@ const styles = StyleSheet.create({ margin: 8, backgroundColor: 'red', }, + + elevation1: { + elevation: 1, + }, + elevation2: { + elevation: 3, + }, + elevation3: { + elevation: 10, + }, + shadowColor1: { + shadowColor: 'red', + }, + shadowColor2: { + shadowColor: 'blue', + }, + shadowColor3: { + shadowColor: '#00FF0080', + }, + border: { + borderWidth: 5, + borderColor: '#EEE', + }, }); exports.title = 'Box Shadow'; @@ -97,4 +120,75 @@ exports.examples = [ ); }, }, + + { + title: 'Basic elevation', + description: 'elevation: 1, 3, 6', + platform: 'android', + render() { + return ( + + + + + + ); + }, + }, + { + title: 'Fractional elevation', + description: 'elevation: 0.1, 0.5, 1.5', + platform: 'android', + render() { + return ( + + + + + + ); + }, + }, + { + title: 'Colored shadow', + description: "shadowColor: 'red', 'blue', '#00FF0080'", + platform: 'android', + render() { + return ( + + + + + + ); + }, + }, + { + title: 'Shaped shadow', + description: 'borderRadius: 50', + platform: 'android', + render() { + return ( + + + + + + ); + }, + }, + { + title: 'Borders', + description: 'borderWidth: 5', + platform: 'android', + render() { + return ( + + + + + + ); + }, + }, ]; diff --git a/RNTester/js/examples/Timer/TimerExample.js b/RNTester/js/examples/Timer/TimerExample.js index de13ae9f845f92..e9c9b74c8cf4de 100644 --- a/RNTester/js/examples/Timer/TimerExample.js +++ b/RNTester/js/examples/Timer/TimerExample.js @@ -13,13 +13,11 @@ const RNTesterButton = require('../../components/RNTesterButton'); const React = require('react'); -const performanceNow = require('fbjs/lib/performanceNow'); - const {Alert, Platform, ToastAndroid, Text, View} = require('react-native'); function burnCPU(milliseconds) { - const start = performanceNow(); - while (performanceNow() < start + milliseconds) {} + const start = global.performance.now(); + while (global.performance.now() < start + milliseconds) {} } type RequestIdleCallbackTesterProps = $ReadOnly<{||}>; diff --git a/RNTester/js/examples/View/ViewExample.js b/RNTester/js/examples/View/ViewExample.js index 7f89797ed51570..96636eb2a417e6 100644 --- a/RNTester/js/examples/View/ViewExample.js +++ b/RNTester/js/examples/View/ViewExample.js @@ -441,6 +441,79 @@ exports.examples = [ return ; }, }, + { + title: '`display: none` style', + render(): React.Node { + type Props = $ReadOnly<{||}>; + type State = {| + index: number, + |}; + + class DisplayNoneStyle extends React.Component { + state = { + index: 0, + }; + + render() { + return ( + + + + Press to toggle `display: none` + + + + + + + + + + ); + } + + _handlePress = () => { + this.setState({index: this.state.index + 1}); + }; + } + + return ; + }, + }, { title: 'BackfaceVisibility', render: function(): React.Node { diff --git a/RNTester/js/utils/RNTesterList.android.js b/RNTester/js/utils/RNTesterList.android.js index 66962af515439d..2b1b921efe75f0 100644 --- a/RNTester/js/utils/RNTesterList.android.js +++ b/RNTester/js/utils/RNTesterList.android.js @@ -122,7 +122,7 @@ const APIExamples: Array = [ }, { key: 'AlertExample', - module: require('../examples/Alert/AlertExample').AlertExample, + module: require('../examples/Alert/AlertExample'), }, { key: 'AnimatedExample', @@ -144,6 +144,10 @@ const APIExamples: Array = [ key: 'BorderExample', module: require('../examples/Border/BorderExample'), }, + { + key: 'BoxShadowExample', + module: require('../examples/BoxShadow/BoxShadowExample'), + }, { key: 'ClipboardExample', module: require('../examples/Clipboard/ClipboardExample'), diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index e1e4065f277bd6..c241b0b24a5827 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -158,6 +158,10 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); RCT_EXTERN BOOL RCTTurboModuleEnabled(void); RCT_EXTERN void RCTEnableTurboModule(BOOL enabled); +// Turn on TurboModule eager initialization +RCT_EXTERN BOOL RCTTurboModuleEagerInitEnabled(void); +RCT_EXTERN void RCTEnableTurboModuleEagerInit(BOOL enabled); + /** * Async batched bridge used to communicate with the JavaScript application. */ @@ -221,9 +225,9 @@ RCT_EXTERN void RCTEnableTurboModule(BOOL enabled); /** * When a NativeModule performs a lookup for a TurboModule, we need to query - * the lookupDelegate. + * the TurboModuleRegistry. */ -- (void)setRCTTurboModuleLookupDelegate:(id)turboModuleLookupDelegate; +- (void)setRCTTurboModuleRegistry:(id)turboModuleRegistry; /** * Convenience method for retrieving all modules conforming to a given protocol. diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 64792e726164d6..3241b5b019350e 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -116,6 +116,17 @@ void RCTEnableTurboModule(BOOL enabled) turboModuleEnabled = enabled; } +static BOOL turboModuleEagerInitEnabled = NO; +BOOL RCTTurboModuleEagerInitEnabled(void) +{ + return turboModuleEagerInitEnabled; +} + +void RCTEnableTurboModuleEagerInit(BOOL enabled) +{ + turboModuleEagerInitEnabled = enabled; +} + @interface RCTBridge () @end @@ -191,9 +202,9 @@ - (void)dealloc [self invalidate]; } -- (void)setRCTTurboModuleLookupDelegate:(id)turboModuleLookupDelegate +- (void)setRCTTurboModuleRegistry:(id)turboModuleRegistry { - [self.batchedBridge setRCTTurboModuleLookupDelegate:turboModuleLookupDelegate]; + [self.batchedBridge setRCTTurboModuleRegistry:turboModuleRegistry]; } - (void)didReceiveReloadCommand diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 98b7754ae75507..102f98194bd5f2 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -346,7 +346,7 @@ RCT_EXTERN_C_END * A protocol that allows TurboModules to do lookup on other TurboModules. * Calling these methods may cause a module to be synchronously instantiated. */ -@protocol RCTTurboModuleLookupDelegate +@protocol RCTTurboModuleRegistry - (id)moduleForName:(const char *)moduleName; /** @@ -360,6 +360,9 @@ RCT_EXTERN_C_END */ - (id)moduleForName:(const char *)moduleName warnOnLookupFailure:(BOOL)warnOnLookupFailure; - (BOOL)moduleIsInitialized:(const char *)moduleName; + +- (NSArray *)eagerInitModuleNames; +- (NSArray *)eagerInitMainQueueModuleNames; @end /** diff --git a/React/Base/RCTBundleURLProvider.mm b/React/Base/RCTBundleURLProvider.mm index 64cf9f7b851efe..4be1f1f67fd4fd 100644 --- a/React/Base/RCTBundleURLProvider.mm +++ b/React/Base/RCTBundleURLProvider.mm @@ -131,6 +131,11 @@ + (BOOL)isPackagerRunning:(NSString *)host - (NSString *)packagerServerHost { NSString *location = [self jsLocation]; +#if RCT_DEV_MENU + if (![RCTBundleURLProvider isPackagerRunning:location]) { + location = nil; + } +#endif if (location != nil) { return location; } diff --git a/React/Base/RCTModuleData.mm b/React/Base/RCTModuleData.mm index c9c2ac79490ec9..c81b8a41fafb29 100644 --- a/React/Base/RCTModuleData.mm +++ b/React/Base/RCTModuleData.mm @@ -383,6 +383,11 @@ - (NSString *)name } - (void)gatherConstants +{ + return [self gatherConstantsAndSignalJSRequireEnding:NO]; +} + +- (void)gatherConstantsAndSignalJSRequireEnding:(BOOL)startMarkers { NSString *moduleName = [self name]; @@ -391,15 +396,17 @@ - (void)gatherConstants RCTProfileTagAlways, ([NSString stringWithFormat:@"[RCTModuleData gatherConstants] %@", _moduleClass]), nil); (void)[self instance]; - /** - * Why do we instrument moduleJSRequireEndingStart here? - * - NativeModule requires from JS go through ModuleRegistry::getConfig(). - * - ModuleRegistry::getConfig() calls NativeModule::getConstants() first. - * - This delegates to RCTNativeModule::getConstants(), which calls RCTModuleData gatherConstants(). - * - Therefore, this is the first statement that executes after the NativeModule is created/initialized in a JS - * require. - */ - BridgeNativeModulePerfLogger::moduleJSRequireEndingStart([moduleName UTF8String]); + if (startMarkers) { + /** + * Why do we instrument moduleJSRequireEndingStart here? + * - NativeModule requires from JS go through ModuleRegistry::getConfig(). + * - ModuleRegistry::getConfig() calls NativeModule::getConstants() first. + * - This delegates to RCTNativeModule::getConstants(), which calls RCTModuleData gatherConstants(). + * - Therefore, this is the first statement that executes after the NativeModule is created/initialized in a JS + * require. + */ + BridgeNativeModulePerfLogger::moduleJSRequireEndingStart([moduleName UTF8String]); + } if (!RCTIsMainQueueExecutionOfConstantsToExportDisabled() && _requiresMainQueueSetup) { if (!RCTIsMainQueue()) { @@ -414,7 +421,7 @@ - (void)gatherConstants } RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); - } else { + } else if (startMarkers) { /** * If a NativeModule doesn't have constants, it isn't eagerly loaded until its methods are first invoked. * Therefore, we should immediately start JSRequireEnding @@ -425,7 +432,7 @@ - (void)gatherConstants - (NSDictionary *)exportedConstants { - [self gatherConstants]; + [self gatherConstantsAndSignalJSRequireEnding:YES]; NSDictionary *constants = _constantsToExport; _constantsToExport = nil; // Not needed anymore return constants; diff --git a/React/Base/Surface/RCTSurfaceView+Internal.h b/React/Base/Surface/RCTSurfaceView+Internal.h index a0c7a2a58501f3..5cc04de870fbca 100644 --- a/React/Base/Surface/RCTSurfaceView+Internal.h +++ b/React/Base/Surface/RCTSurfaceView+Internal.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @interface RCTSurfaceView (Internal) -@property (nonatomic, strong) RCTSurfaceRootView *rootView; +@property (nonatomic, nullable, strong) RCTSurfaceRootView *rootView; @property (nonatomic, assign) RCTSurfaceStage stage; @end diff --git a/React/Base/Surface/RCTSurfaceView.mm b/React/Base/Surface/RCTSurfaceView.mm index 1fb7020ef715a3..4774f7a73a9d1d 100644 --- a/React/Base/Surface/RCTSurfaceView.mm +++ b/React/Base/Surface/RCTSurfaceView.mm @@ -40,7 +40,7 @@ - (BOOL)isFlipped #pragma mark - Internal Interface -- (void)setRootView:(RCTSurfaceRootView *)rootView +- (void)setRootView:(RCTSurfaceRootView *_Nullable)rootView { if (_rootView == rootView) { return; @@ -79,7 +79,7 @@ - (RCTSurfaceStage)stage - (void)_updateStage { if (RCTSurfaceStageIsRunning(_stage)) { - if (_rootView.superview != self) { + if (_rootView && _rootView.superview != self) { [self addSubview:_rootView]; } } else { diff --git a/React/CoreModules/RCTExceptionsManager.mm b/React/CoreModules/RCTExceptionsManager.mm index 072e4511f2d813..cd2c2f34013156 100644 --- a/React/CoreModules/RCTExceptionsManager.mm +++ b/React/CoreModules/RCTExceptionsManager.mm @@ -24,7 +24,7 @@ @interface RCTExceptionsManager () @implementation RCTExceptionsManager @synthesize bridge = _bridge; -@synthesize turboModuleLookupDelegate = _turboModuleLookupDelegate; +@synthesize turboModuleRegistry = _turboModuleRegistry; RCT_EXPORT_MODULE() @@ -46,7 +46,7 @@ - (void)reportSoft:(NSString *)message if (_bridge) { [_bridge.redBox showErrorMessage:message withStack:stack errorCookie:((int)exceptionId)]; } else { - RCTRedBox *redbox = [_turboModuleLookupDelegate moduleForName:"RCTRedBox"]; + RCTRedBox *redbox = [_turboModuleRegistry moduleForName:"RCTRedBox"]; [redbox showErrorMessage:message withStack:stack errorCookie:(int)exceptionId]; } } @@ -68,7 +68,7 @@ - (void)reportFatal:(NSString *)message if (_bridge) { [_bridge.redBox showErrorMessage:message withStack:stack errorCookie:((int)exceptionId)]; } else { - RCTRedBox *redbox = [_turboModuleLookupDelegate moduleForName:"RCTRedBox"]; + RCTRedBox *redbox = [_turboModuleRegistry moduleForName:"RCTRedBox"]; [redbox showErrorMessage:message withStack:stack errorCookie:(int)exceptionId]; } } @@ -115,7 +115,7 @@ - (void)reportFatal:(NSString *)message if (_bridge) { [_bridge.redBox updateErrorMessage:message withStack:stack errorCookie:((int)exceptionId)]; } else { - RCTRedBox *redbox = [_turboModuleLookupDelegate moduleForName:"RCTRedBox"]; + RCTRedBox *redbox = [_turboModuleRegistry moduleForName:"RCTRedBox"]; [redbox updateErrorMessage:message withStack:stack errorCookie:(int)exceptionId]; } diff --git a/React/CoreModules/RCTWebSocketExecutor.mm b/React/CoreModules/RCTWebSocketExecutor.mm index 3422cf049f6d38..ac89891f17914c 100644 --- a/React/CoreModules/RCTWebSocketExecutor.mm +++ b/React/CoreModules/RCTWebSocketExecutor.mm @@ -98,7 +98,7 @@ - (void)setUp [self invalidate]; NSString *error = @"Runtime is not ready for debugging.\n " - "- Make sure Packager server is running.\n" + "- Make sure Metro is running.\n" "- Make sure the JavaScript Debugger is running and not paused on a " "breakpoint or exception and try reloading again."; _setupError = RCTErrorWithMessage(error); @@ -165,7 +165,7 @@ - (void)sendMessage:(NSDictionary *)message onReply:(RCTWSMessag dispatch_async(_jsQueue, ^{ if (!self.valid) { - callback(RCTErrorWithMessage(@"Runtime is not ready for debugging. Make sure Packager server is running."), nil); + callback(RCTErrorWithMessage(@"Runtime is not ready for debugging. Make sure Metro is running."), nil); return; } diff --git a/React/CxxBridge/RCTCxxBridge.mm b/React/CxxBridge/RCTCxxBridge.mm index dcb22df6660f7f..8c975b95c0701f 100644 --- a/React/CxxBridge/RCTCxxBridge.mm +++ b/React/CxxBridge/RCTCxxBridge.mm @@ -208,8 +208,8 @@ @implementation RCTCxxBridge { // This is uniquely owned, but weak_ptr is used. std::shared_ptr _reactInstance; - // Necessary for searching in TurboModuleRegistry - id _turboModuleLookupDelegate; + // Necessary for searching in TurboModules in TurboModuleManager + id _turboModuleRegistry; } @synthesize bridgeDescription = _bridgeDescription; @@ -217,9 +217,9 @@ @implementation RCTCxxBridge { @synthesize performanceLogger = _performanceLogger; @synthesize valid = _valid; -- (void)setRCTTurboModuleLookupDelegate:(id)turboModuleLookupDelegate +- (void)setRCTTurboModuleRegistry:(id)turboModuleRegistry { - _turboModuleLookupDelegate = turboModuleLookupDelegate; + _turboModuleRegistry = turboModuleRegistry; } - (std::shared_ptr)jsMessageThread @@ -407,6 +407,27 @@ - (void)start })); } + /** + * id jsExecutorFactory may create and assign an id object to + * RCTCxxBridge If id is assigned by this time, eagerly initialize all TurboModules + */ + if (_turboModuleRegistry && RCTTurboModuleEagerInitEnabled()) { + for (NSString *moduleName in [_turboModuleRegistry eagerInitModuleNames]) { + [_turboModuleRegistry moduleForName:[moduleName UTF8String]]; + } + + for (NSString *moduleName in [_turboModuleRegistry eagerInitMainQueueModuleNames]) { + if (RCTIsMainQueue()) { + [_turboModuleRegistry moduleForName:[moduleName UTF8String]]; + } else { + id turboModuleRegistry = _turboModuleRegistry; + dispatch_group_async(prepareBridge, dispatch_get_main_queue(), ^{ + [turboModuleRegistry moduleForName:[moduleName UTF8String]]; + }); + } + } + } + // Dispatch the instance initialization as soon as the initial module metadata has // been collected (see initModules) dispatch_group_enter(prepareBridge); @@ -523,10 +544,10 @@ - (id)moduleForName:(NSString *)moduleName - (id)moduleForName:(NSString *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad { - if (RCTTurboModuleEnabled() && _turboModuleLookupDelegate) { + if (RCTTurboModuleEnabled() && _turboModuleRegistry) { const char *moduleNameCStr = [moduleName UTF8String]; - if (lazilyLoad || [_turboModuleLookupDelegate moduleIsInitialized:moduleNameCStr]) { - id module = [_turboModuleLookupDelegate moduleForName:moduleNameCStr warnOnLookupFailure:NO]; + if (lazilyLoad || [_turboModuleRegistry moduleIsInitialized:moduleNameCStr]) { + id module = [_turboModuleRegistry moduleForName:moduleNameCStr warnOnLookupFailure:NO]; if (module != nil) { return module; } @@ -580,8 +601,8 @@ - (BOOL)moduleIsInitialized:(Class)moduleClass return YES; } - if (_turboModuleLookupDelegate) { - return [_turboModuleLookupDelegate moduleIsInitialized:[moduleName UTF8String]]; + if (_turboModuleRegistry) { + return [_turboModuleRegistry moduleIsInitialized:[moduleName UTF8String]]; } return NO; diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.h b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.h index cd7497f09c630f..9f83d0d5e8d7f6 100644 --- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.h +++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.h @@ -7,10 +7,18 @@ #import +#import #import +#import #import #import +/* + * Allows to enable or disable on-demand view mounting feature of ScrollView. + * It's an experimental feature that improves performance and memory footprint of huge lists inside ScrollView. + */ +RCT_EXTERN void RCTSetEnableOnDemandViewMounting(BOOL value); + NS_ASSUME_NONNULL_BEGIN /* @@ -21,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN * keyboard-avoiding functionality and so on. All that complexity must be implemented inside those components in order * to keep the complexity of this component manageable. */ -@interface RCTScrollViewComponentView : RCTViewComponentView +@interface RCTScrollViewComponentView : RCTViewComponentView /* * Finds and returns the closet RCTScrollViewComponentView component to the given view diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm index 4868e6b5121576..2d3e8c3802f761 100644 --- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -24,6 +24,8 @@ using namespace facebook::react; +static CGFloat const kClippingLeeway = 44.0; + static void RCTSendPaperScrollEvent_DEPRECATED(UIScrollView *scrollView, NSInteger tag) { static uint16_t coalescingKey = 0; @@ -39,6 +41,13 @@ static void RCTSendPaperScrollEvent_DEPRECATED(UIScrollView *scrollView, NSInteg [[RCTBridge currentBridge].eventDispatcher sendEvent:scrollEvent]; } +static BOOL isOnDemandViewMountingEnabledGlobally = NO; + +void RCTSetEnableOnDemandViewMounting(BOOL value) +{ + isOnDemandViewMountingEnabledGlobally = value; +} + @interface RCTScrollViewComponentView () @end @@ -53,6 +62,10 @@ @implementation RCTScrollViewComponentView { // This helps to only update state from `scrollViewDidScroll` in case // some other part of the system scrolls scroll view. BOOL _isUserTriggeredScrolling; + + BOOL _isOnDemandViewMountingEnabled; + CGPoint _contentOffsetWhenClipped; + NSMutableArray *> *_childComponentViews; } + (RCTScrollViewComponentView *_Nullable)findScrollViewComponentViewForView:(UIView *)view @@ -69,6 +82,9 @@ - (instancetype)initWithFrame:(CGRect)frame static const auto defaultProps = std::make_shared(); _props = defaultProps; + _isOnDemandViewMountingEnabled = isOnDemandViewMountingEnabledGlobally; + _childComponentViews = [[NSMutableArray alloc] init]; + _scrollView = [[RCTEnhancedScrollView alloc] initWithFrame:self.bounds]; _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _scrollView.delaysContentTouches = NO; @@ -98,6 +114,13 @@ - (void)dealloc return ((RCTEnhancedScrollView *)_scrollView).delegateSplitter; } +#pragma mark - RCTMountingTransactionObserving + +- (void)mountingTransactionDidMountWithMetadata:(MountingTransactionMetadata const &)metadata +{ + [self _remountChildren]; +} + #pragma mark - RCTComponentViewProtocol + (ComponentDescriptorProvider)componentDescriptorProvider @@ -178,8 +201,14 @@ - (void)updateState:(State::Shared const &)state oldState:(State::Shared const & { assert(std::dynamic_pointer_cast(state)); _state = std::static_pointer_cast(state); + auto &data = _state->getData(); - CGSize contentSize = RCTCGSizeFromSize(_state->getData().getContentSize()); + auto contentOffset = RCTCGPointFromPoint(data.contentOffset); + if (!oldState && !CGPointEqualToPoint(contentOffset, CGPointZero)) { + _scrollView.contentOffset = contentOffset; + } + + CGSize contentSize = RCTCGSizeFromSize(data.getContentSize()); if (CGSizeEqualToSize(_contentSize, contentSize)) { return; @@ -192,13 +221,27 @@ - (void)updateState:(State::Shared const &)state oldState:(State::Shared const & - (void)mountChildComponentView:(UIView *)childComponentView index:(NSInteger)index { - [_containerView insertSubview:childComponentView atIndex:index]; + if (_isOnDemandViewMountingEnabled) { + [_childComponentViews insertObject:childComponentView atIndex:index]; + } else { + [_containerView insertSubview:childComponentView atIndex:index]; + } } - (void)unmountChildComponentView:(UIView *)childComponentView index:(NSInteger)index { - RCTAssert(childComponentView.superview == _containerView, @"Attempt to unmount improperly mounted component view."); - [childComponentView removeFromSuperview]; + if (_isOnDemandViewMountingEnabled) { + RCTAssert( + [_childComponentViews objectAtIndex:index] == childComponentView, + @"Attempt to unmount improperly mounted component view."); + [_childComponentViews removeObjectAtIndex:index]; + // In addition to removing a view from `_childComponentViews`, + // we have to unmount views immediately to not mess with recycling. + [childComponentView removeFromSuperview]; + } else { + RCTAssert(childComponentView.superview == _containerView, @"Attempt to unmount improperly mounted component view."); + [childComponentView removeFromSuperview]; + } } - (ScrollViewMetrics)_scrollViewMetrics @@ -241,18 +284,19 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView if (!_isUserTriggeredScrolling) { [self _updateStateWithContentOffset]; } - if (!_eventEmitter) { - return; - } NSTimeInterval now = CACurrentMediaTime(); if ((_lastScrollEventDispatchTime == 0) || (now - _lastScrollEventDispatchTime > _scrollEventThrottle)) { _lastScrollEventDispatchTime = now; - std::static_pointer_cast(_eventEmitter)->onScroll([self _scrollViewMetrics]); + if (_eventEmitter) { + std::static_pointer_cast(_eventEmitter)->onScroll([self _scrollViewMetrics]); + } // Once Fabric implements proper NativeAnimationDriver, this should be removed. // This is just a workaround to allow animations based on onScroll event. RCTSendPaperScrollEvent_DEPRECATED(scrollView, self.tag); } + + [self _remountChildrenIfNeeded]; } - (void)scrollViewDidZoom:(UIScrollView *)scrollView @@ -395,6 +439,75 @@ - (void)scrollToEnd:(BOOL)animated [_scrollView setContentOffset:offset animated:animated]; } +#pragma mark - Child views mounting + +- (void)_remountChildrenIfNeeded +{ + if (!_isOnDemandViewMountingEnabled) { + return; + } + + CGPoint contentOffset = _scrollView.contentOffset; + + if (std::abs(_contentOffsetWhenClipped.x - contentOffset.x) < kClippingLeeway && + std::abs(_contentOffsetWhenClipped.y - contentOffset.y) < kClippingLeeway) { + return; + } + + _contentOffsetWhenClipped = contentOffset; + + [self _remountChildren]; +} + +- (void)_remountChildren +{ + if (!_isOnDemandViewMountingEnabled) { + return; + } + + CGRect visibleFrame = CGRect{_scrollView.contentOffset, _scrollView.bounds.size}; + visibleFrame = CGRectInset(visibleFrame, -kClippingLeeway, -kClippingLeeway); + + CGFloat scale = 1.0 / _scrollView.zoomScale; + visibleFrame.origin.x *= scale; + visibleFrame.origin.y *= scale; + visibleFrame.size.width *= scale; + visibleFrame.size.height *= scale; + + NSInteger mountedIndex = 0; + for (UIView *componentView in _childComponentViews) { + BOOL shouldBeMounted = YES; + BOOL isMounted = componentView.superview != nil; + + // If a view is mounted, it must be mounted exactly at `mountedIndex` position. + RCTAssert( + !isMounted || [_containerView.subviews objectAtIndex:mountedIndex] == componentView, + @"Attempt to unmount improperly mounted component view."); + + // It's simpler and faster to not mess with views that are not `RCTViewComponentView` subclasses. + if ([componentView isKindOfClass:[RCTViewComponentView class]]) { + RCTViewComponentView *viewComponentView = (RCTViewComponentView *)componentView; + auto layoutMetrics = viewComponentView->_layoutMetrics; + + if (layoutMetrics.overflowInset == EdgeInsets{}) { + shouldBeMounted = CGRectIntersectsRect(visibleFrame, componentView.frame); + } + } + + if (shouldBeMounted != isMounted) { + if (shouldBeMounted) { + [_containerView insertSubview:componentView atIndex:mountedIndex]; + } else { + [componentView removeFromSuperview]; + } + } + + if (shouldBeMounted) { + mountedIndex++; + } + } +} + #pragma mark - RCTScrollableProtocol - (CGSize)contentSize diff --git a/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.h b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.h new file mode 100644 index 00000000000000..87573f789f5e17 --- /dev/null +++ b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.h @@ -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. + */ + +#import + +#import +#import +#import + +#import "RCTParagraphComponentView.h" + +@interface RCTParagraphComponentAccessibilityProvider : NSObject + +- (instancetype)initWithString:(facebook::react::AttributedString)attributedString + layoutManager:(RCTTextLayoutManager *)layoutManager + paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes + frame:(CGRect)frame + view:(UIView *)view; + +/* + * Returns an array of `UIAccessibilityElement`s to be used for `UIAccessibilityContainer` implementation. + */ +- (NSArray *)accessibilityElements; + +/** + @abstract To make sure the provider is up to date. +*/ +- (BOOL)isUpToDate:(facebook::react::AttributedString)currentAttributedString; + +@end diff --git a/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm new file mode 100644 index 00000000000000..04adf1159ba3b2 --- /dev/null +++ b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm @@ -0,0 +1,153 @@ +/* + * 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 "RCTParagraphComponentAccessibilityProvider.h" + +#import +#import +#import +#import +#import + +#import "RCTConversions.h" +#import "RCTFabricComponentsPlugins.h" + +using namespace facebook::react; + +@implementation RCTParagraphComponentAccessibilityProvider { + NSMutableArray *_accessibilityElements; + AttributedString _attributedString; + RCTTextLayoutManager *_layoutManager; + ParagraphAttributes _paragraphAttributes; + CGRect _frame; + __weak UIView *_view; +} + +- (instancetype)initWithString:(facebook::react::AttributedString)attributedString + layoutManager:(RCTTextLayoutManager *)layoutManager + paragraphAttributes:(ParagraphAttributes)paragraphAttributes + frame:(CGRect)frame + view:(UIView *)view +{ + if (self = [super init]) { + _attributedString = attributedString; + _layoutManager = layoutManager; + _paragraphAttributes = paragraphAttributes; + _frame = frame; + _view = view; + } + return self; +} + +- (NSArray *)accessibilityElements +{ + if (_accessibilityElements) { + return _accessibilityElements; + } + + __block NSInteger numberOfLinks = 0; + __block NSInteger numberOfButtons = 0; + __block NSString *truncatedText; + // build an array of the accessibleElements + NSMutableArray *elements = [NSMutableArray new]; + + NSString *accessibilityLabel = [_view valueForKey:@"accessibilityLabel"]; + if (!accessibilityLabel.length) { + accessibilityLabel = RCTNSStringFromString(_attributedString.getString()); + } + // add first element has the text for the whole textview in order to read out the whole text + UIAccessibilityElement *firstElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:_view]; + firstElement.isAccessibilityElement = YES; + firstElement.accessibilityTraits = UIAccessibilityTraitStaticText; + firstElement.accessibilityLabel = accessibilityLabel; + firstElement.accessibilityFrameInContainerSpace = _view.bounds; + [firstElement setAccessibilityActivationPoint:CGPointMake( + firstElement.accessibilityFrame.origin.x + 1.0, + firstElement.accessibilityFrame.origin.y + 1.0)]; + [elements addObject:firstElement]; + + // add additional elements for those parts of text with embedded link so VoiceOver could specially recognize links + + [_layoutManager getRectWithAttributedString:_attributedString + paragraphAttributes:_paragraphAttributes + enumerateAttribute:RCTTextAttributesAccessibilityRoleAttributeName + frame:_frame + usingBlock:^(CGRect fragmentRect, NSString *_Nonnull fragmentText, NSString *value) { + if (![value isEqualToString:@"button"] && ![value isEqualToString:@"link"]) { + return; + } + if ([value isEqualToString:@"button"] && + ([fragmentText isEqualToString:@"See Less"] || + [fragmentText isEqualToString:@"See More"])) { + truncatedText = fragmentText; + return; + } + UIAccessibilityElement *element = + [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self->_view]; + element.isAccessibilityElement = YES; + if ([value isEqualToString:@"link"]) { + element.accessibilityTraits = UIAccessibilityTraitLink; + numberOfLinks++; + } else if ([value isEqualToString:@"button"]) { + element.accessibilityTraits = UIAccessibilityTraitButton; + numberOfButtons++; + } + element.accessibilityLabel = fragmentText; + element.accessibilityFrameInContainerSpace = fragmentRect; + [elements addObject:element]; + }]; + + if (numberOfLinks > 0 || numberOfButtons > 0) { + __block NSInteger indexOfLink = 1; + __block NSInteger indexOfButton = 1; + [elements enumerateObjectsUsingBlock:^(UIAccessibilityElement *element, NSUInteger idx, BOOL *_Nonnull stop) { + if (idx == 0) { + return; + } + if (element.accessibilityTraits & UIAccessibilityTraitLink) { + element.accessibilityHint = + [NSString stringWithFormat:@"Link %ld of %ld.", (long)indexOfLink++, (long)numberOfLinks]; + } else { + element.accessibilityHint = + [NSString stringWithFormat:@"Button %ld of %ld.", (long)indexOfButton++, (long)numberOfButtons]; + } + }]; + } + + if (numberOfLinks > 0 && numberOfButtons > 0) { + firstElement.accessibilityHint = @"Links and buttons are found, swipe to move to them."; + + } else if (numberOfLinks > 0) { + NSString *firstElementHint = (numberOfLinks == 1) + ? @"One link found, swipe to move to the link." + : [NSString stringWithFormat:@"%ld links found, swipe to move to the first link.", (long)numberOfLinks]; + firstElement.accessibilityHint = firstElementHint; + + } else if (numberOfButtons > 0) { + NSString *firstElementHint = (numberOfButtons == 1) + ? @"One button found, swipe to move to the button." + : [NSString stringWithFormat:@"%ld buttons found, swipe to move to the first button.", (long)numberOfButtons]; + firstElement.accessibilityHint = firstElementHint; + } + + if (truncatedText && truncatedText.length > 0) { + firstElement.accessibilityHint = (numberOfLinks > 0 || numberOfButtons > 0) + ? [NSString stringWithFormat:@"%@ Double tap to %@.", firstElement.accessibilityHint, truncatedText] + : firstElement.accessibilityHint = [NSString stringWithFormat:@"Double tap to %@.", truncatedText]; + } + + // add accessible element for truncation attributed string for automation purposes only + _accessibilityElements = elements; + return _accessibilityElements; +} + +- (BOOL)isUpToDate:(facebook::react::AttributedString)currentAttributedString +{ + return currentAttributedString == _attributedString; +} + +@end diff --git a/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index 42fa2ebe86ac2d..ba5a1f34c73ae3 100644 --- a/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -6,6 +6,7 @@ */ #import "RCTParagraphComponentView.h" +#import "RCTParagraphComponentAccessibilityProvider.h" #import #import @@ -26,6 +27,7 @@ @implementation RCTParagraphComponentView { ParagraphShadowNode::ConcreteState::Shared _state; ParagraphAttributes _paragraphAttributes; + RCTParagraphComponentAccessibilityProvider *_accessibilityProvider; } - (instancetype)initWithFrame:(CGRect)frame @@ -35,7 +37,6 @@ - (instancetype)initWithFrame:(CGRect)frame _props = defaultProps; self.isAccessibilityElement = YES; - self.accessibilityTraits |= UIAccessibilityTraitStaticText; self.opaque = NO; self.contentMode = UIViewContentModeRedraw; } @@ -138,6 +139,29 @@ - (NSString *)accessibilityLabel return RCTNSStringFromString(_state->getData().attributedString.getString()); } +- (NSArray *)accessibilityElements +{ + if (![_accessibilityProvider isUpToDate:_state->getData().attributedString]) { + RCTTextLayoutManager *textLayoutManager = + (RCTTextLayoutManager *)unwrapManagedObject(_state->getData().layoutManager->getNativeTextLayoutManager()); + CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame()); + _accessibilityProvider = + [[RCTParagraphComponentAccessibilityProvider alloc] initWithString:_state->getData().attributedString + layoutManager:textLayoutManager + paragraphAttributes:_state->getData().paragraphAttributes + frame:frame + view:self]; + } + + self.isAccessibilityElement = NO; + return _accessibilityProvider.accessibilityElements; +} + +- (UIAccessibilityTraits)accessibilityTraits +{ + return [super accessibilityTraits] | UIAccessibilityTraitStaticText; +} + - (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point { if (!_state) { diff --git a/React/Fabric/Mounting/RCTComponentViewRegistry.h b/React/Fabric/Mounting/RCTComponentViewRegistry.h index 9199d68ac6871c..44b7773ea73ac9 100644 --- a/React/Fabric/Mounting/RCTComponentViewRegistry.h +++ b/React/Fabric/Mounting/RCTComponentViewRegistry.h @@ -27,8 +27,9 @@ NS_ASSUME_NONNULL_BEGIN * for given `componentHandle` and with given `tag`. * #RefuseSingleUse */ -- (RCTComponentViewDescriptor)dequeueComponentViewWithComponentHandle:(facebook::react::ComponentHandle)componentHandle - tag:(facebook::react::Tag)tag; +- (RCTComponentViewDescriptor const &)dequeueComponentViewWithComponentHandle: + (facebook::react::ComponentHandle)componentHandle + tag:(facebook::react::Tag)tag; /** * Puts a given native component view to the recycle pool. diff --git a/React/Fabric/Mounting/RCTComponentViewRegistry.mm b/React/Fabric/Mounting/RCTComponentViewRegistry.mm index 870f49352cf980..ac6d4d7095a380 100644 --- a/React/Fabric/Mounting/RCTComponentViewRegistry.mm +++ b/React/Fabric/Mounting/RCTComponentViewRegistry.mm @@ -66,7 +66,8 @@ - (void)preallocateViewComponents } } -- (RCTComponentViewDescriptor)dequeueComponentViewWithComponentHandle:(ComponentHandle)componentHandle tag:(Tag)tag +- (RCTComponentViewDescriptor const &)dequeueComponentViewWithComponentHandle:(ComponentHandle)componentHandle + tag:(Tag)tag { RCTAssertMainQueue(); @@ -76,10 +77,8 @@ - (RCTComponentViewDescriptor)dequeueComponentViewWithComponentHandle:(Component auto componentViewDescriptor = [self _dequeueComponentViewWithComponentHandle:componentHandle]; componentViewDescriptor.view.tag = tag; - - _registry.insert({tag, componentViewDescriptor}); - - return componentViewDescriptor; + auto it = _registry.insert({tag, componentViewDescriptor}); + return it.first->second; } - (void)enqueueComponentViewWithComponentHandle:(ComponentHandle)componentHandle @@ -146,6 +145,8 @@ - (void)_enqueueComponentViewWithComponentHandle:(ComponentHandle)componentHandl return; } + RCTAssert( + componentViewDescriptor.view.superview == nil, @"RCTComponentViewRegistry: Attempt to recycle a mounted view."); [componentViewDescriptor.view prepareForRecycle]; recycledViews.push_back(componentViewDescriptor); diff --git a/React/Fabric/Mounting/RCTMountingManager.mm b/React/Fabric/Mounting/RCTMountingManager.mm index a64b1dd79ddbf0..ebc361e849b197 100644 --- a/React/Fabric/Mounting/RCTMountingManager.mm +++ b/React/Fabric/Mounting/RCTMountingManager.mm @@ -15,176 +15,108 @@ #import #import #import +#import #import "RCTComponentViewProtocol.h" #import "RCTComponentViewRegistry.h" #import "RCTConversions.h" #import "RCTMountingTransactionObserverCoordinator.h" -using namespace facebook; using namespace facebook::react; -// `Create` instruction -static void RNCreateMountInstruction( - ShadowViewMutation const &mutation, - RCTComponentViewRegistry *registry, - RCTMountingTransactionObserverCoordinator &observerCoordinator, - SurfaceId surfaceId) -{ - auto componentViewDescriptor = - [registry dequeueComponentViewWithComponentHandle:mutation.newChildShadowView.componentHandle - tag:mutation.newChildShadowView.tag]; - - observerCoordinator.registerViewComponentDescriptor(componentViewDescriptor, surfaceId); -} - -// `Delete` instruction -static void RNDeleteMountInstruction( - ShadowViewMutation const &mutation, - RCTComponentViewRegistry *registry, - RCTMountingTransactionObserverCoordinator &observerCoordinator, - SurfaceId surfaceId) -{ - auto const &oldChildShadowView = mutation.oldChildShadowView; - auto const &componentViewDescriptor = [registry componentViewDescriptorWithTag:oldChildShadowView.tag]; - observerCoordinator.unregisterViewComponentDescriptor(componentViewDescriptor, surfaceId); - [registry enqueueComponentViewWithComponentHandle:oldChildShadowView.componentHandle - tag:oldChildShadowView.tag - componentViewDescriptor:componentViewDescriptor]; -} - -// `Insert` instruction -static void RNInsertMountInstruction(ShadowViewMutation const &mutation, RCTComponentViewRegistry *registry) -{ - auto const &newShadowView = mutation.newChildShadowView; - auto const &parentShadowView = mutation.parentShadowView; - - auto const &childComponentViewDescriptor = [registry componentViewDescriptorWithTag:newShadowView.tag]; - auto const &parentComponentViewDescriptor = [registry componentViewDescriptorWithTag:parentShadowView.tag]; - - [parentComponentViewDescriptor.view mountChildComponentView:childComponentViewDescriptor.view index:mutation.index]; -} - -// `Remove` instruction -static void RNRemoveMountInstruction(ShadowViewMutation const &mutation, RCTComponentViewRegistry *registry) -{ - auto const &oldShadowView = mutation.oldChildShadowView; - auto const &parentShadowView = mutation.parentShadowView; - - auto const &childComponentViewDescriptor = [registry componentViewDescriptorWithTag:oldShadowView.tag]; - auto const &parentComponentViewDescriptor = [registry componentViewDescriptorWithTag:parentShadowView.tag]; - - [parentComponentViewDescriptor.view unmountChildComponentView:childComponentViewDescriptor.view index:mutation.index]; -} - -// `Update Props` instruction -static void RNUpdatePropsMountInstruction(ShadowViewMutation const &mutation, RCTComponentViewRegistry *registry) -{ - auto const &oldShadowView = mutation.oldChildShadowView; - auto const &newShadowView = mutation.newChildShadowView; - auto const &componentViewDescriptor = [registry componentViewDescriptorWithTag:newShadowView.tag]; - [componentViewDescriptor.view updateProps:newShadowView.props oldProps:oldShadowView.props]; -} - -// `Update EventEmitter` instruction -static void RNUpdateEventEmitterMountInstruction(ShadowViewMutation const &mutation, RCTComponentViewRegistry *registry) -{ - auto const &newShadowView = mutation.newChildShadowView; - auto const &componentViewDescriptor = [registry componentViewDescriptorWithTag:newShadowView.tag]; - [componentViewDescriptor.view updateEventEmitter:newShadowView.eventEmitter]; -} - -// `Update LayoutMetrics` instruction -static void RNUpdateLayoutMetricsMountInstruction( - ShadowViewMutation const &mutation, - RCTComponentViewRegistry *registry) -{ - auto const &oldShadowView = mutation.oldChildShadowView; - auto const &newShadowView = mutation.newChildShadowView; - auto const &componentViewDescriptor = [registry componentViewDescriptorWithTag:newShadowView.tag]; - [componentViewDescriptor.view updateLayoutMetrics:newShadowView.layoutMetrics - oldLayoutMetrics:oldShadowView.layoutMetrics]; -} - -// `Update State` instruction -static void RNUpdateStateMountInstruction(ShadowViewMutation const &mutation, RCTComponentViewRegistry *registry) -{ - auto const &oldShadowView = mutation.oldChildShadowView; - auto const &newShadowView = mutation.newChildShadowView; - auto const &componentViewDescriptor = [registry componentViewDescriptorWithTag:newShadowView.tag]; - [componentViewDescriptor.view updateState:newShadowView.state oldState:oldShadowView.state]; -} - -// `Finalize Updates` instruction -static void RNFinalizeUpdatesMountInstruction( - ShadowViewMutation const &mutation, - RNComponentViewUpdateMask mask, - RCTComponentViewRegistry *registry) -{ - auto const &newShadowView = mutation.newChildShadowView; - auto const &componentViewDescriptor = [registry componentViewDescriptorWithTag:newShadowView.tag]; - [componentViewDescriptor.view finalizeUpdates:mask]; -} - -// `Update` instruction -static void RNPerformMountInstructions( +static void RCTPerformMountInstructions( ShadowViewMutationList const &mutations, RCTComponentViewRegistry *registry, RCTMountingTransactionObserverCoordinator &observerCoordinator, SurfaceId surfaceId) { - SystraceSection s("RNPerformMountInstructions"); + SystraceSection s("RCTPerformMountInstructions"); [CATransaction begin]; [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; for (auto const &mutation : mutations) { switch (mutation.type) { case ShadowViewMutation::Create: { - RNCreateMountInstruction(mutation, registry, observerCoordinator, surfaceId); + auto &newChildShadowView = mutation.newChildShadowView; + auto &newChildViewDescriptor = + [registry dequeueComponentViewWithComponentHandle:newChildShadowView.componentHandle + tag:newChildShadowView.tag]; + observerCoordinator.registerViewComponentDescriptor(newChildViewDescriptor, surfaceId); break; } + case ShadowViewMutation::Delete: { - RNDeleteMountInstruction(mutation, registry, observerCoordinator, surfaceId); + auto &oldChildShadowView = mutation.oldChildShadowView; + auto &oldChildViewDescriptor = [registry componentViewDescriptorWithTag:oldChildShadowView.tag]; + + observerCoordinator.unregisterViewComponentDescriptor(oldChildViewDescriptor, surfaceId); + + [registry enqueueComponentViewWithComponentHandle:oldChildShadowView.componentHandle + tag:oldChildShadowView.tag + componentViewDescriptor:oldChildViewDescriptor]; break; } + case ShadowViewMutation::Insert: { - RNUpdatePropsMountInstruction(mutation, registry); - RNUpdateEventEmitterMountInstruction(mutation, registry); - RNUpdateStateMountInstruction(mutation, registry); - RNUpdateLayoutMetricsMountInstruction(mutation, registry); - RNFinalizeUpdatesMountInstruction(mutation, RNComponentViewUpdateMaskAll, registry); - RNInsertMountInstruction(mutation, registry); + auto &oldChildShadowView = mutation.oldChildShadowView; + auto &newChildShadowView = mutation.newChildShadowView; + auto &parentShadowView = mutation.parentShadowView; + auto &newChildViewDescriptor = [registry componentViewDescriptorWithTag:newChildShadowView.tag]; + auto &parentViewDescriptor = [registry componentViewDescriptorWithTag:parentShadowView.tag]; + + UIView *newChildComponentView = newChildViewDescriptor.view; + + [newChildComponentView updateProps:newChildShadowView.props oldProps:oldChildShadowView.props]; + [newChildComponentView updateEventEmitter:newChildShadowView.eventEmitter]; + [newChildComponentView updateState:newChildShadowView.state oldState:oldChildShadowView.state]; + [newChildComponentView updateLayoutMetrics:newChildShadowView.layoutMetrics + oldLayoutMetrics:oldChildShadowView.layoutMetrics]; + [newChildComponentView finalizeUpdates:RNComponentViewUpdateMaskAll]; + + [parentViewDescriptor.view mountChildComponentView:newChildComponentView index:mutation.index]; break; } + case ShadowViewMutation::Remove: { - RNRemoveMountInstruction(mutation, registry); + auto &oldChildShadowView = mutation.oldChildShadowView; + auto &parentShadowView = mutation.parentShadowView; + auto &oldChildViewDescriptor = [registry componentViewDescriptorWithTag:oldChildShadowView.tag]; + auto &parentViewDescriptor = [registry componentViewDescriptorWithTag:parentShadowView.tag]; + [parentViewDescriptor.view unmountChildComponentView:oldChildViewDescriptor.view index:mutation.index]; break; } + case ShadowViewMutation::Update: { - auto const &oldChildShadowView = mutation.oldChildShadowView; - auto const &newChildShadowView = mutation.newChildShadowView; + auto &oldChildShadowView = mutation.oldChildShadowView; + auto &newChildShadowView = mutation.newChildShadowView; + auto &newChildViewDescriptor = [registry componentViewDescriptorWithTag:newChildShadowView.tag]; + UIView *newChildComponentView = newChildViewDescriptor.view; auto mask = RNComponentViewUpdateMask{}; if (oldChildShadowView.props != newChildShadowView.props) { - RNUpdatePropsMountInstruction(mutation, registry); + [newChildComponentView updateProps:newChildShadowView.props oldProps:oldChildShadowView.props]; mask |= RNComponentViewUpdateMaskProps; } + if (oldChildShadowView.eventEmitter != newChildShadowView.eventEmitter) { - RNUpdateEventEmitterMountInstruction(mutation, registry); + [newChildComponentView updateEventEmitter:newChildShadowView.eventEmitter]; mask |= RNComponentViewUpdateMaskEventEmitter; } + if (oldChildShadowView.state != newChildShadowView.state) { - RNUpdateStateMountInstruction(mutation, registry); + [newChildComponentView updateState:newChildShadowView.state oldState:oldChildShadowView.state]; mask |= RNComponentViewUpdateMaskState; } + if (oldChildShadowView.layoutMetrics != newChildShadowView.layoutMetrics) { - RNUpdateLayoutMetricsMountInstruction(mutation, registry); + [newChildComponentView updateLayoutMetrics:newChildShadowView.layoutMetrics + oldLayoutMetrics:oldChildShadowView.layoutMetrics]; mask |= RNComponentViewUpdateMaskLayoutMetrics; } if (mask != RNComponentViewUpdateMaskNone) { - RNFinalizeUpdatesMountInstruction(mutation, mask, registry); + [newChildComponentView finalizeUpdates:mask]; } break; @@ -266,28 +198,20 @@ - (void)performTransaction:(MountingCoordinator::Shared const &)mountingCoordina SystraceSection s("-[RCTMountingManager performTransaction:]"); RCTAssertMainQueue(); - auto transaction = mountingCoordinator->pullTransaction(); - if (!transaction.has_value()) { - return; - } - - auto surfaceId = transaction->getSurfaceId(); - auto &mutations = transaction->getMutations(); - - if (mutations.empty()) { - return; - } - - auto telemetry = transaction->getTelemetry(); - auto number = transaction->getNumber(); - - [self.delegate mountingManager:self willMountComponentsWithRootTag:surfaceId]; - _observerCoordinator.notifyObserversMountingTransactionWillMount({surfaceId, number, telemetry}); - telemetry.willMount(); - RNPerformMountInstructions(mutations, self.componentViewRegistry, _observerCoordinator, surfaceId); - telemetry.didMount(); - _observerCoordinator.notifyObserversMountingTransactionDidMount({surfaceId, number, telemetry}); - [self.delegate mountingManager:self didMountComponentsWithRootTag:surfaceId]; + auto surfaceId = mountingCoordinator->getSurfaceId(); + + mountingCoordinator->getTelemetryController().pullTransaction( + [&](MountingTransactionMetadata metadata) { + [self.delegate mountingManager:self willMountComponentsWithRootTag:surfaceId]; + _observerCoordinator.notifyObserversMountingTransactionWillMount(metadata); + }, + [&](ShadowViewMutationList const &mutations) { + RCTPerformMountInstructions(mutations, _componentViewRegistry, _observerCoordinator, surfaceId); + }, + [&](MountingTransactionMetadata metadata) { + _observerCoordinator.notifyObserversMountingTransactionDidMount(metadata); + [self.delegate mountingManager:self didMountComponentsWithRootTag:surfaceId]; + }); } - (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag diff --git a/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm b/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm index 0eda1bda5ea8e8..82bfa1a5515111 100644 --- a/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm +++ b/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm @@ -30,12 +30,17 @@ + (ComponentDescriptorProvider)componentDescriptorProvider - (void)mountChildComponentView:(UIView *)childComponentView index:(NSInteger)index { + RCTAssert(childComponentView.superview == nil, @"Attempt to mount already mounted component view."); [self insertSubview:childComponentView atIndex:index]; } - (void)unmountChildComponentView:(UIView *)childComponentView index:(NSInteger)index { RCTAssert(childComponentView.superview == self, @"Attempt to unmount improperly mounted component view."); + RCTAssert( + [self.subviews objectAtIndex:index] == childComponentView, + @"Attempt to unmount improperly mounted component view."); + [childComponentView removeFromSuperview]; } diff --git a/React/Fabric/RCTSurfacePresenter.mm b/React/Fabric/RCTSurfacePresenter.mm index 79a0edd2a498b6..1b63e621cd595f 100644 --- a/React/Fabric/RCTSurfacePresenter.mm +++ b/React/Fabric/RCTSurfacePresenter.mm @@ -23,6 +23,8 @@ #import #import +#import + #import #import #import @@ -290,6 +292,10 @@ - (RCTScheduler *)_createScheduler { auto reactNativeConfig = _contextContainer->at>("ReactNativeConfig"); + if (reactNativeConfig && reactNativeConfig->getBool("react_fabric:scrollview_on_demand_mounting_ios")) { + RCTSetEnableOnDemandViewMounting(YES); + } + auto componentRegistryFactory = [factory = wrapManagedObject(_mountingManager.componentViewRegistry.componentViewFactory)]( EventDispatcher::Weak const &eventDispatcher, ContextContainer::Shared const &contextContainer) { @@ -361,6 +367,7 @@ - (void)_stopSurface:(RCTFabricSurface *)surface scheduler:(RCTScheduler *)sched RCTMountingManager *mountingManager = _mountingManager; RCTExecuteOnMainQueue(^{ + surface.view.rootView = nil; RCTComponentViewDescriptor rootViewDescriptor = [mountingManager.componentViewRegistry componentViewDescriptorWithTag:surface.rootTag]; [mountingManager.componentViewRegistry enqueueComponentViewWithComponentHandle:RootShadowNode::Handle() diff --git a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeCameraRollManagerSpec.java b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeCameraRollManagerSpec.java deleted file mode 100644 index 3dd691799c9ebd..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeCameraRollManagerSpec.java +++ /dev/null @@ -1,37 +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. - * - *

Generated by an internal genrule from Flow types. - * - * @generated - * @nolint - */ - -package com.facebook.fbreact.specs; - -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReactModuleWithSpec; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.turbomodule.core.interfaces.TurboModule; - -public abstract class NativeCameraRollManagerSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule { - public NativeCameraRollManagerSpec(ReactApplicationContext reactContext) { - super(reactContext); - } - - @ReactMethod - public abstract void getPhotos(ReadableMap params, Promise promise); - - @ReactMethod - public abstract void deletePhotos(ReadableArray assets, Promise promise); - - @ReactMethod - public abstract void saveToCameraRoll(String uri, String type, Promise promise); -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.java b/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.java index fb72f43732cc52..740182fbc0f7f1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.java @@ -37,7 +37,7 @@ public NativeModule getModule(String name, ReactApplicationContext reactContext) return new JSCHeapCapture(reactContext); default: throw new IllegalArgumentException( - "In CoreModulesPackage, could not find Native module for " + name); + "In DebugCorePackage, could not find Native module for " + name); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 46ce038b4052c1..7807b98c9091ee 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -152,7 +152,7 @@ public interface ReactInstanceEventListener { private final JavaScriptExecutorFactory mJavaScriptExecutorFactory; private final @Nullable JSBundleLoader mBundleLoader; - private final @Nullable String mJSMainModulePath; /* path to JS bundle root on packager server */ + private final @Nullable String mJSMainModulePath; /* path to JS bundle root on Metro */ private final List mPackages; private final DevSupportManager mDevSupportManager; private final boolean mUseDeveloperSupport; @@ -706,6 +706,7 @@ public void destroy() { synchronized (mHasStartedDestroying) { mHasStartedDestroying.notifyAll(); } + FLog.d(ReactConstants.TAG, "ReactInstanceManager has been destroyed"); } private synchronized void moveToResumedLifecycleState(boolean force) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java index e2809cd6580c68..a3b33f6cfa3b71 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java @@ -118,9 +118,9 @@ public ReactInstanceManagerBuilder setJSBundleLoader(JSBundleLoader jsBundleLoad } /** - * Path to your app's main module on the packager server. This is used when reloading JS during - * development. All paths are relative to the root folder the packager is serving files from. - * Examples: {@code "index.android"} or {@code "subdirectory/index.android"} + * Path to your app's main module on Metro. This is used when reloading JS during development. All + * paths are relative to the root folder the packager is serving files from. Examples: {@code + * "index.android"} or {@code "subdirectory/index.android"} */ public ReactInstanceManagerBuilder setJSMainModulePath(String jsMainModulePath) { mJSMainModulePath = jsMainModulePath; diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java index 96dd42ba9ae953..188ae05f361ee1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java @@ -118,9 +118,9 @@ protected UIImplementationProvider getUIImplementationProvider() { } /** - * Returns the name of the main module. Determines the URL used to fetch the JS bundle from the - * packager server. It is only used when dev support is enabled. This is the first file to be - * executed once the {@link ReactInstanceManager} is created. e.g. "index.android" + * Returns the name of the main module. Determines the URL used to fetch the JS bundle from Metro. + * It is only used when dev support is enabled. This is the first file to be executed once the + * {@link ReactInstanceManager} is created. e.g. "index.android" */ protected String getJSMainModuleName() { return "index.android"; @@ -138,7 +138,7 @@ protected String getJSMainModuleName() { /** * Returns the name of the bundle in assets. If this is null, and no file path is specified for * the bundle, the app will only work with {@code getUseDeveloperSupport} enabled and will always - * try to load the JS bundle from the packager server. e.g. "index.android.bundle" + * try to load the JS bundle from Metro. e.g. "index.android.bundle" */ protected @Nullable String getBundleAssetName() { return "index.android.bundle"; diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java index f85a092952b16f..249bf3775899c1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java @@ -32,7 +32,6 @@ import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.common.UIManagerType; import com.facebook.react.uimanager.common.ViewUtil; -import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -87,23 +86,37 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec public static final String NAME = "NativeAnimatedModule"; public static final boolean ANIMATED_MODULE_DEBUG = false; - private interface UIThreadOperation { - void execute(NativeAnimatedNodesManager animatedNodesManager); + private abstract class UIThreadOperation { + abstract void execute(NativeAnimatedNodesManager animatedNodesManager); + + long mFrameNumber = -1; + + public void setFrameNumber(long frameNumber) { + mFrameNumber = frameNumber; + } + + public long getFrameNumber() { + return mFrameNumber; + } } @NonNull private final GuardedFrameCallback mAnimatedFrameCallback; private final ReactChoreographer mReactChoreographer; @NonNull - private ConcurrentLinkedQueue mOperations = new ConcurrentLinkedQueue<>(); + private final ConcurrentLinkedQueue mOperations = + new ConcurrentLinkedQueue<>(); @NonNull - private ConcurrentLinkedQueue mPreOperations = new ConcurrentLinkedQueue<>(); + private final ConcurrentLinkedQueue mPreOperations = + new ConcurrentLinkedQueue<>(); private @Nullable NativeAnimatedNodesManager mNodesManager; - private volatile boolean mFabricBatchCompleted = false; + private volatile long mFrameNumber = 0; + private long mDispatchedFrameNumber = 0; private boolean mInitializedForFabric = false; + private boolean mInitializedForNonFabric = false; private @UIManagerType int mUIManagerType = UIManagerType.DEFAULT; private int mNumFabricAnimations = 0; private int mNumNonFabricAnimations = 0; @@ -145,12 +158,8 @@ protected void doFrameGuarded(final long frameTimeNanos) { public void initialize() { ReactApplicationContext reactApplicationContext = getReactApplicationContextIfActiveOrWarn(); - // TODO T59412313 Implement this API on FabricUIManager to use in bridgeless mode - if (reactApplicationContext != null && !reactApplicationContext.isBridgeless()) { + if (reactApplicationContext != null) { reactApplicationContext.addLifecycleEventListener(this); - UIManagerModule uiManager = - Assertions.assertNotNull(reactApplicationContext.getNativeModule(UIManagerModule.class)); - uiManager.addUIManagerEventListener(this); } } @@ -159,6 +168,16 @@ public void onHostResume() { enqueueFrameCallback(); } + private void addOperation(UIThreadOperation operation) { + operation.setFrameNumber(mFrameNumber); + mOperations.add(operation); + } + + private void addPreOperation(UIThreadOperation operation) { + operation.setFrameNumber(mFrameNumber); + mPreOperations.add(operation); + } + // For FabricUIManager only @Override public void didScheduleMountItems(UIManager uiManager) { @@ -166,7 +185,7 @@ public void didScheduleMountItems(UIManager uiManager) { return; } - mFabricBatchCompleted = true; + mFrameNumber++; } // For FabricUIManager only @@ -177,23 +196,66 @@ public void didDispatchMountItems(UIManager uiManager) { return; } - if (mFabricBatchCompleted) { - // This will execute all operations and preOperations queued - // since the last time this was run, and will race with anything - // being queued from the JS thread. That is, if the JS thread - // is still queuing operations, we might execute some of them - // at the very end until we exhaust the queue faster than the - // JS thread can queue up new items. - executeAllOperations(mPreOperations); - executeAllOperations(mOperations); - mFabricBatchCompleted = false; + // The problem we're trying to solve here: we could be in the middle of queueing + // a batch of related animation operations when Fabric flushes a batch of MountItems. + // It's visually bad if we execute half of the animation ops and then wait another frame + // (or more) to execute the rest. + // See mFrameNumber. If the dispatchedFrameNumber drifts too far - that + // is, if no MountItems are scheduled for a while, which can happen if a tree + // is committed but there are no changes - bring these counts back in sync and + // execute any queued operations. This number is arbitrary, but we want it low + // enough that the user shouldn't be able to see this delay in most cases. + mDispatchedFrameNumber++; + long currentFrameNo = mFrameNumber - 1; + if ((mDispatchedFrameNumber - mFrameNumber) > 2) { + mFrameNumber = mDispatchedFrameNumber; + currentFrameNo = mFrameNumber; } - } - private void executeAllOperations(Queue operationQueue) { + // This will execute all operations and preOperations queued + // since the last time this was run, and will race with anything + // being queued from the JS thread. That is, if the JS thread + // is still queuing operations, we might execute some of them + // at the very end until we exhaust the queue faster than the + // JS thread can queue up new items. + // The reason we increment in scheduleMountItems and subtract 1 here + // is that `scheduleMountItems` happens as close to the JS commit as + // possible, whereas execution of those same items might happen sometime + // later on the UI thread while the JS thread keeps plugging along. + executeAllOperations(mPreOperations, currentFrameNo); + executeAllOperations(mOperations, currentFrameNo); + } + + private void executeAllOperations(Queue operationQueue, long maxFrameNumber) { NativeAnimatedNodesManager nodesManager = getNodesManager(); - while (!operationQueue.isEmpty()) { - operationQueue.poll().execute(nodesManager); + while (true) { + // There is a race condition where `peek` may return a non-null value and isEmpty() is false, + // but `poll` returns a null value - it's not clear why since we only peek and poll on the UI + // thread, but it might be something that happens during teardown or a crash. Regardless, the + // root cause is not currently known so we're extra cautious here. + // It happens equally in Fabric and non-Fabric. + UIThreadOperation peekedOperation = operationQueue.peek(); + + // This is the same as operationQueue.isEmpty() + if (peekedOperation == null) { + return; + } + // The rest of the operations are for the next frame. + if (peekedOperation.getFrameNumber() > maxFrameNumber) { + return; + } + + // Since we apparently can't guarantee that there is still an operation on the queue, + // much less the same operation, we do a poll and another null check. If this isn't + // the same operation as the peeked operation, we can't do anything about it - we still + // need to execute it, we have no mechanism to put it at the front of the queue, and it + // won't cause any errors to execute it earlier than expected (just a bit of UI jank at worst) + // so we just continue happily along. + UIThreadOperation polledOperation = operationQueue.poll(); + if (polledOperation == null) { + return; + } + polledOperation.execute(nodesManager); } } @@ -208,20 +270,13 @@ public void willDispatchViewUpdates(final UIManager uiManager) { return; } - final Queue preOperations = new LinkedList<>(); - final Queue operations = new LinkedList<>(); - while (!mPreOperations.isEmpty()) { - preOperations.add(mPreOperations.poll()); - } - while (!mOperations.isEmpty()) { - operations.add(mOperations.poll()); - } + final long frameNo = mFrameNumber++; UIBlock preOperationsUIBlock = new UIBlock() { @Override public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) { - executeAllOperations(preOperations); + executeAllOperations(mPreOperations, frameNo); } }; @@ -229,8 +284,7 @@ public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) { new UIBlock() { @Override public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) { - NativeAnimatedNodesManager nodesManager = getNodesManager(); - executeAllOperations(operations); + executeAllOperations(mOperations, frameNo); } }; @@ -292,6 +346,77 @@ public void setNodesManager(NativeAnimatedNodesManager nodesManager) { mNodesManager = nodesManager; } + /** + * Given a viewTag, detect if we're running in Fabric or non-Fabric and attach an event listener + * to the correct UIManager, if necessary. This is expected to only be called from the JS thread, + * and not concurrently. + * + * @param viewTag + */ + private void initializeLifecycleEventListenersForViewTag(final int viewTag) { + mUIManagerType = ViewUtil.getUIManagerType(viewTag); + if (mUIManagerType == UIManagerType.FABRIC) { + mNumFabricAnimations++; + } else { + mNumNonFabricAnimations++; + } + + if (mNodesManager != null) { + mNodesManager.initializeEventListenerForUIManagerType(mUIManagerType); + } + + // Subscribe to UIManager (Fabric or non-Fabric) lifecycle events if we haven't yet + if ((mInitializedForFabric && mUIManagerType == UIManagerType.FABRIC) + || (mInitializedForNonFabric && mUIManagerType == UIManagerType.DEFAULT)) { + return; + } + + ReactApplicationContext reactApplicationContext = getReactApplicationContext(); + if (reactApplicationContext != null) { + @Nullable + UIManager uiManager = UIManagerHelper.getUIManager(reactApplicationContext, mUIManagerType); + if (uiManager != null) { + uiManager.addUIManagerEventListener(this); + if (mUIManagerType == UIManagerType.FABRIC) { + mInitializedForFabric = true; + } else { + mInitializedForNonFabric = true; + } + } + } + } + + /** + * Given a viewTag and the knowledge that a "disconnect" or "stop"-type imperative command is + * being executed, decrement the number of inflight animations and possibly switch UIManager + * modes. + * + * @param viewTag + */ + private void decrementInFlightAnimationsForViewTag(final int viewTag) { + @UIManagerType int animationManagerType = ViewUtil.getUIManagerType(viewTag); + if (animationManagerType == UIManagerType.FABRIC) { + mNumFabricAnimations--; + } else { + mNumNonFabricAnimations--; + } + + // Should we switch to a different animation mode? + // This can be useful when navigating between Fabric and non-Fabric screens: + // If there are ongoing Fabric animations from a previous screen, + // and we tear down the current non-Fabric screen, we should expect + // the animation mode to switch back - and vice-versa. + if (mNumNonFabricAnimations == 0 + && mNumFabricAnimations > 0 + && mUIManagerType != UIManagerType.FABRIC) { + mUIManagerType = UIManagerType.FABRIC; + } else if (mNumFabricAnimations == 0 + && mNumNonFabricAnimations > 0 + && mUIManagerType != UIManagerType.DEFAULT) { + mUIManagerType = UIManagerType.DEFAULT; + } + } + @Override public void createAnimatedNode(final double tagDouble, final ReadableMap config) { final int tag = (int) tagDouble; @@ -300,7 +425,7 @@ public void createAnimatedNode(final double tagDouble, final ReadableMap config) NAME, "queue createAnimatedNode: " + tag + " config: " + config.toHashMap().toString()); } - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -341,7 +466,7 @@ public void onValueUpdate(double value) { } }; - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -360,7 +485,7 @@ public void stopListeningToAnimatedNodeValue(final double tagDouble) { FLog.d(NAME, "queue stopListeningToAnimatedNodeValue: " + tag); } - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -379,7 +504,7 @@ public void dropAnimatedNode(final double tagDouble) { FLog.d(NAME, "queue dropAnimatedNode: " + tag); } - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -398,7 +523,7 @@ public void setAnimatedNodeValue(final double tagDouble, final double value) { FLog.d(NAME, "queue setAnimatedNodeValue: " + tag + " value: " + value); } - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -417,7 +542,7 @@ public void setAnimatedNodeOffset(final double tagDouble, final double value) { FLog.d(NAME, "queue setAnimatedNodeOffset: " + tag + " offset: " + value); } - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -436,7 +561,7 @@ public void flattenAnimatedNodeOffset(final double tagDouble) { FLog.d(NAME, "queue flattenAnimatedNodeOffset: " + tag); } - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -455,7 +580,7 @@ public void extractAnimatedNodeOffset(final double tagDouble) { FLog.d(NAME, "queue extractAnimatedNodeOffset: " + tag); } - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -479,7 +604,7 @@ public void startAnimatingNode( FLog.d(NAME, "queue startAnimatingNode: ID: " + animationId + " tag: " + animatedNodeTag); } - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -501,7 +626,7 @@ public void stopAnimation(final double animationIdDouble) { FLog.d(NAME, "queue stopAnimation: ID: " + animationId); } - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -523,7 +648,7 @@ public void connectAnimatedNodes( NAME, "queue connectAnimatedNodes: parent: " + parentNodeTag + " child: " + childNodeTag); } - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -551,7 +676,7 @@ public void disconnectAnimatedNodes( "queue disconnectAnimatedNodes: parent: " + parentNodeTag + " child: " + childNodeTag); } - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -582,7 +707,9 @@ public void connectAnimatedNodeToView( + viewTag); } - mOperations.add( + initializeLifecycleEventListenersForViewTag(viewTag); + + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -613,7 +740,9 @@ public void disconnectAnimatedNodeFromView( + viewTag); } - mOperations.add( + decrementInFlightAnimationsForViewTag(viewTag); + + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -638,7 +767,7 @@ public void restoreDefaultValues(final double animatedNodeTagDouble) { NAME, "queue restoreDefaultValues: disconnectAnimatedNodeFromView: " + animatedNodeTag); } - mPreOperations.add( + addPreOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -668,28 +797,9 @@ public void addAnimatedEventToView( + eventMapping.toHashMap().toString()); } - mUIManagerType = ViewUtil.getUIManagerType(viewTag); - if (mUIManagerType == UIManagerType.FABRIC) { - mNumFabricAnimations++; - } else { - mNumNonFabricAnimations++; - } + initializeLifecycleEventListenersForViewTag(viewTag); - // Subscribe to FabricUIManager lifecycle events if we haven't yet - if (!mInitializedForFabric && mUIManagerType == UIManagerType.FABRIC) { - ReactApplicationContext reactApplicationContext = getReactApplicationContext(); - if (reactApplicationContext != null) { - @Nullable - UIManager uiManager = - UIManagerHelper.getUIManager(reactApplicationContext, UIManagerType.FABRIC); - if (uiManager != null) { - uiManager.addUIManagerEventListener(this); - mInitializedForFabric = true; - } - } - } - - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -724,29 +834,9 @@ public void removeAnimatedEventFromView( + animatedValueTag); } - @UIManagerType int animationManagerType = ViewUtil.getUIManagerType(viewTag); - if (animationManagerType == UIManagerType.FABRIC) { - mNumFabricAnimations--; - } else { - mNumNonFabricAnimations--; - } - - // Should we switch to a different animation mode? - // This can be useful when navigating between Fabric and non-Fabric screens: - // If there are ongoing Fabric animations from a previous screen, - // and we tear down the current non-Fabric screen, we should expect - // the animation mode to switch back - and vice-versa. - if (mNumNonFabricAnimations == 0 - && mNumFabricAnimations > 0 - && mUIManagerType != UIManagerType.FABRIC) { - mUIManagerType = UIManagerType.FABRIC; - } else if (mNumFabricAnimations == 0 - && mNumNonFabricAnimations > 0 - && mUIManagerType != UIManagerType.DEFAULT) { - mUIManagerType = UIManagerType.DEFAULT; - } + decrementInFlightAnimationsForViewTag(viewTag); - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { @@ -778,7 +868,7 @@ public void removeListeners(double count) { @Override public void getValue(final double animatedValueNodeTagDouble, final Callback callback) { final int animatedValueNodeTag = (int) animatedValueNodeTagDouble; - mOperations.add( + addOperation( new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java index f1c76b780d10e4..179a6f0a7664e7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java @@ -9,8 +9,8 @@ import android.util.SparseArray; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import com.facebook.common.logging.FLog; -import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.JSApplicationCausedNativeException; @@ -25,7 +25,7 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.UIManagerHelper; -import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.common.UIManagerType; import com.facebook.react.uimanager.events.Event; import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.events.EventDispatcherListener; @@ -62,23 +62,51 @@ // Mapping of a view tag and an event name to a list of event animation drivers. 99% of the time // there will be only one driver per mapping so all code code should be optimized around that. private final Map> mEventDrivers = new HashMap<>(); - private final UIManagerModule.CustomEventNamesResolver mCustomEventNamesResolver; private final ReactApplicationContext mReactApplicationContext; private int mAnimatedGraphBFSColor = 0; private int mNumInconsistentFrames = 0; // Used to avoid allocating a new array on every frame in `runUpdates` and `onEventDispatch`. private final List mRunUpdateNodeList = new LinkedList<>(); + private boolean mEventListenerInitializedForFabric = false; + private boolean mEventListenerInitializedForNonFabric = false; + public NativeAnimatedNodesManager(ReactApplicationContext reactApplicationContext) { mReactApplicationContext = reactApplicationContext; + } - UIManagerModule uiManager = - Assertions.assertNotNull(reactApplicationContext.getNativeModule(UIManagerModule.class)); + /** + * Initialize event listeners for Fabric UIManager or non-Fabric UIManager, exactly once. Once + * Fabric is the only UIManager, this logic can be simplified. This is only called on the JS + * thread. + * + * @param uiManagerType + */ + @UiThread + public void initializeEventListenerForUIManagerType(@UIManagerType final int uiManagerType) { + if ((uiManagerType == UIManagerType.FABRIC && mEventListenerInitializedForFabric) + || (uiManagerType == UIManagerType.DEFAULT && mEventListenerInitializedForNonFabric)) { + return; + } - uiManager.getEventDispatcher().addListener(this); - // TODO T64216139 Remove dependency of UIManagerModule when the Constants are not in Native - // anymore - mCustomEventNamesResolver = uiManager.getDirectEventNamesResolver(); + final NativeAnimatedNodesManager self = this; + mReactApplicationContext.runOnUiQueueThread( + new Runnable() { + @Override + public void run() { + UIManager uiManager = + UIManagerHelper.getUIManager(mReactApplicationContext, uiManagerType); + if (uiManager != null) { + uiManager.getEventDispatcher().addListener(self); + + if (uiManagerType == UIManagerType.FABRIC) { + mEventListenerInitializedForFabric = true; + } else { + mEventListenerInitializedForNonFabric = true; + } + } + } + }); } /*package*/ @Nullable @@ -90,6 +118,7 @@ public boolean hasActiveAnimations() { return mActiveAnimations.size() > 0 || mUpdatedNodes.size() > 0; } + @UiThread public void createAnimatedNode(int tag, ReadableMap config) { if (mAnimatedNodes.get(tag) != null) { throw new JSApplicationIllegalArgumentException( @@ -129,11 +158,13 @@ public void createAnimatedNode(int tag, ReadableMap config) { mUpdatedNodes.put(tag, node); } + @UiThread public void dropAnimatedNode(int tag) { mAnimatedNodes.remove(tag); mUpdatedNodes.remove(tag); } + @UiThread public void startListeningToAnimatedNodeValue(int tag, AnimatedNodeValueListener listener) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { @@ -143,6 +174,7 @@ public void startListeningToAnimatedNodeValue(int tag, AnimatedNodeValueListener ((ValueAnimatedNode) node).setValueListener(listener); } + @UiThread public void stopListeningToAnimatedNodeValue(int tag) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { @@ -152,6 +184,7 @@ public void stopListeningToAnimatedNodeValue(int tag) { ((ValueAnimatedNode) node).setValueListener(null); } + @UiThread public void setAnimatedNodeValue(int tag, double value) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { @@ -163,6 +196,7 @@ public void setAnimatedNodeValue(int tag, double value) { mUpdatedNodes.put(tag, node); } + @UiThread public void setAnimatedNodeOffset(int tag, double offset) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { @@ -173,6 +207,7 @@ public void setAnimatedNodeOffset(int tag, double offset) { mUpdatedNodes.put(tag, node); } + @UiThread public void flattenAnimatedNodeOffset(int tag) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { @@ -182,6 +217,7 @@ public void flattenAnimatedNodeOffset(int tag) { ((ValueAnimatedNode) node).flattenOffset(); } + @UiThread public void extractAnimatedNodeOffset(int tag) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { @@ -191,6 +227,7 @@ public void extractAnimatedNodeOffset(int tag) { ((ValueAnimatedNode) node).extractOffset(); } + @UiThread public void startAnimatingNode( int animationId, int animatedNodeTag, ReadableMap animationConfig, Callback endCallback) { AnimatedNode node = mAnimatedNodes.get(animatedNodeTag); @@ -228,6 +265,7 @@ public void startAnimatingNode( mActiveAnimations.put(animationId, animation); } + @UiThread private void stopAnimationsForNode(AnimatedNode animatedNode) { // in most of the cases there should never be more than a few active animations running at the // same time. Therefore it does not make much sense to create an animationId -> animation @@ -248,6 +286,7 @@ private void stopAnimationsForNode(AnimatedNode animatedNode) { } } + @UiThread public void stopAnimation(int animationId) { // in most of the cases there should never be more than a few active animations running at the // same time. Therefore it does not make much sense to create an animationId -> animation @@ -272,6 +311,7 @@ public void stopAnimation(int animationId) { // when the animation is already over. } + @UiThread public void connectAnimatedNodes(int parentNodeTag, int childNodeTag) { AnimatedNode parentNode = mAnimatedNodes.get(parentNodeTag); if (parentNode == null) { @@ -302,6 +342,7 @@ public void disconnectAnimatedNodes(int parentNodeTag, int childNodeTag) { mUpdatedNodes.put(childNodeTag, childNode); } + @UiThread public void connectAnimatedNodeToView(int animatedNodeTag, int viewTag) { AnimatedNode node = mAnimatedNodes.get(animatedNodeTag); if (node == null) { @@ -336,6 +377,7 @@ public void connectAnimatedNodeToView(int animatedNodeTag, int viewTag) { mUpdatedNodes.put(animatedNodeTag, node); } + @UiThread public void disconnectAnimatedNodeFromView(int animatedNodeTag, int viewTag) { AnimatedNode node = mAnimatedNodes.get(animatedNodeTag); if (node == null) { @@ -352,6 +394,7 @@ public void disconnectAnimatedNodeFromView(int animatedNodeTag, int viewTag) { propsAnimatedNode.disconnectFromView(viewTag); } + @UiThread public void getValue(int tag, Callback callback) { AnimatedNode node = mAnimatedNodes.get(tag); if (node == null || !(node instanceof ValueAnimatedNode)) { @@ -361,6 +404,7 @@ public void getValue(int tag, Callback callback) { callback.invoke(((ValueAnimatedNode) node).getValue()); } + @UiThread public void restoreDefaultValues(int animatedNodeTag) { AnimatedNode node = mAnimatedNodes.get(animatedNodeTag); // Restoring default values needs to happen before UIManager operations so it is @@ -380,6 +424,7 @@ public void restoreDefaultValues(int animatedNodeTag) { propsAnimatedNode.restoreDefaultValues(); } + @UiThread public void addAnimatedEventToView(int viewTag, String eventName, ReadableMap eventMapping) { int nodeTag = eventMapping.getInt("animatedValueTag"); AnimatedNode node = mAnimatedNodes.get(nodeTag); @@ -411,6 +456,7 @@ public void addAnimatedEventToView(int viewTag, String eventName, ReadableMap ev } } + @UiThread public void removeAnimatedEventFromView(int viewTag, String eventName, int animatedValueTag) { String key = viewTag + eventName; if (mEventDrivers.containsKey(key)) { @@ -429,6 +475,7 @@ public void removeAnimatedEventFromView(int viewTag, String eventName, int anima } } + @UiThread @Override public void onEventDispatch(final Event event) { // Events can be dispatched from any thread so we have to make sure handleEvent is run from the @@ -446,10 +493,25 @@ public void run() { } } + @UiThread private void handleEvent(Event event) { if (!mEventDrivers.isEmpty()) { // If the event has a different name in native convert it to it's JS name. - String eventName = mCustomEventNamesResolver.resolveCustomEventName(event.getEventName()); + // TODO T64216139 Remove dependency of UIManagerModule when the Constants are not in Native + // anymore + if (mReactApplicationContext == null) { + return; + } + UIManager uiManager = + UIManagerHelper.getUIManagerForReactTag(mReactApplicationContext, event.getViewTag()); + if (uiManager == null) { + return; + } + String eventName = uiManager.resolveCustomDirectEventName(event.getEventName()); + if (eventName == null) { + eventName = ""; + } + List driversForKey = mEventDrivers.get(event.getViewTag() + eventName); if (driversForKey != null) { for (EventAnimationDriver driver : driversForKey) { @@ -475,6 +537,7 @@ private void handleEvent(Event event) { * sub-graph of *active* nodes. This is done by adding node to the BFS queue only if all its * "predecessors" have already been visited. */ + @UiThread public void runUpdates(long frameTimeNanos) { UiThreadUtil.assertOnUiThread(); boolean hasFinishedAnimations = false; @@ -517,6 +580,7 @@ public void runUpdates(long frameTimeNanos) { } } + @UiThread private void updateNodes(List nodes) { int activeNodesCount = 0; int updatedNodesCount = 0; diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java index 42cb57a7ec3a8e..e8f77e0dbafdad 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java @@ -13,6 +13,8 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.UIManager; +import com.facebook.react.uimanager.common.UIManagerType; +import com.facebook.react.uimanager.common.ViewUtil; import java.util.HashMap; import java.util.Map; @@ -69,6 +71,14 @@ public void restoreDefaultValues() { if (mConnectedViewTag == -1) { return; } + // Don't restore default values in Fabric. + // In Non-Fabric this had the effect of "restore the value to whatever the value was on the + // ShadowNode instead of in the View hierarchy". However, "synchronouslyUpdateViewOnUIThread" + // will not have that impact on Fabric, because the FabricUIManager doesn't have access to the + // ShadowNode layer. + if (ViewUtil.getUIManagerType(mConnectedViewTag) == UIManagerType.FABRIC) { + return; + } ReadableMapKeySetIterator it = mPropMap.keySetIterator(); while (it.hasNextKey()) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index dae969346aa9be..a61d7f4b108ad1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -339,9 +339,6 @@ public void invokeCallback(final int callbackID, final NativeArrayInterface argu public void destroy() { FLog.d(ReactConstants.TAG, "CatalystInstanceImpl.destroy() start"); UiThreadUtil.assertOnUiThread(); - - UiThreadUtil.assertOnUiThread(); - if (mDestroyed) { return; } 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 27d44870176762..b74f9b4873df1d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -23,7 +23,6 @@ import com.facebook.react.bridge.queue.ReactQueueConfiguration; import com.facebook.react.common.LifecycleState; import com.facebook.react.common.ReactConstants; -import com.facebook.react.config.ReactFeatureFlags; import java.lang.ref.WeakReference; import java.util.concurrent.CopyOnWriteArraySet; @@ -184,7 +183,7 @@ public LifecycleState getLifecycleState() { public void addLifecycleEventListener(final LifecycleEventListener listener) { mLifecycleEventListeners.add(listener); - if (hasActiveCatalystInstance()) { + if (hasActiveCatalystInstance() || isBridgeless()) { switch (mLifecycleState) { case BEFORE_CREATE: case BEFORE_RESUME: @@ -446,7 +445,7 @@ public JavaScriptContextHolder getJavaScriptContextHolder() { return mCatalystInstance.getJavaScriptContextHolder(); } - public JSIModule getJSIModule(JSIModuleType moduleType) { + public @Nullable JSIModule getJSIModule(JSIModuleType moduleType) { if (!hasActiveCatalystInstance()) { throw new IllegalStateException( "Unable to retrieve a JSIModule if CatalystInstance is not active."); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java index 1707c623edbbc8..0bd6a2c649a15e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java @@ -123,4 +123,9 @@ int startSurface( * @param event parameters */ void receiveEvent(int reactTag, String eventName, @Nullable WritableMap event); + + /** Resolves Direct Event name exposed to JS from the one known to the Native side. */ + @Deprecated + @Nullable + String resolveCustomDirectEventName(@Nullable String eventName); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/DebugServerException.java b/ReactAndroid/src/main/java/com/facebook/react/common/DebugServerException.java index 00f52a84c09a0b..1fbd8380b47920 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/DebugServerException.java +++ b/ReactAndroid/src/main/java/com/facebook/react/common/DebugServerException.java @@ -21,7 +21,7 @@ public class DebugServerException extends RuntimeException { private static final String GENERIC_ERROR_MESSAGE = "\n\nTry the following to fix the issue:\n" - + "\u2022 Ensure that the packager server is running\n" + + "\u2022 Ensure that Metro is running\n" + "\u2022 Ensure that your device/emulator is connected to your machine and has USB debugging enabled - run 'adb devices' to see a list of connected devices\n" + "\u2022 Ensure Airplane Mode is disabled\n" + "\u2022 If you're on a physical device connected to the same machine, run 'adb reverse tcp: tcp:' to forward requests from your device\n" diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/LifecycleState.java b/ReactAndroid/src/main/java/com/facebook/react/common/LifecycleState.java index ce15f5f19f8255..cc3238a210309b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/LifecycleState.java +++ b/ReactAndroid/src/main/java/com/facebook/react/common/LifecycleState.java @@ -10,9 +10,16 @@ /** * Lifecycle state for an Activity. The state right after pause and right before resume are the * basically the same so this enum is in terms of the forward lifecycle progression (onResume, etc). - * Eventually, if necessary, it could contain something like: * - *

BEFORE_CREATE, CREATED, VIEW_CREATED, STARTED, RESUMED + *

BEFORE_CREATE is used before a ReactRootView is attached to ReactInstanceManager, or after all + * the ReactRootView has been detached from the ReactInstanceManager. + * + *

BEFORE_RESUME is used after a ReactRootView is attached to ReactInstanceManager but before + * it's activity is resumed, or after its activity has been paused and before the ReactRootView has + * been detached from the ReactInstanceManager. + * + *

RESUMED is used when a ReactRootView is rendered on the screen and the user can interact with + * it. */ public enum LifecycleState { BEFORE_CREATE, diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index 94e6349153074a..acea75b442f4db 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -41,7 +41,7 @@ public class ReactFeatureFlags { * inside view manager will be called instead. */ public static boolean useViewManagerDelegatesForCommands = false; - + /** * This react flag enables a custom algorithm for the getChildVisibleRect() method in the classes * ReactViewGroup, ReactHorizontalScrollView and ReactScrollView. diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java index 2e118ffa1ac93e..427c7f37a1ac16 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java @@ -70,6 +70,12 @@ public abstract class DevSupportManagerBase implements DevSupportManager, PackagerCommandListener, DevInternalSettings.Listener { + public interface CallbackWithBundleLoader { + void onSuccess(JSBundleLoader bundleLoader); + + void onError(String url, Throwable cause); + } + private static final int JAVA_ERROR_COOKIE = -1; private static final int JSEXCEPTION_ERROR_COOKIE = -1; private static final String JS_BUNDLE_FILE_NAME = "ReactNativeDevBundle.js"; @@ -863,7 +869,29 @@ public void handleReloadJS() { } @Override - public void loadSplitBundleFromServer(String bundlePath, final DevSplitBundleCallback callback) { + public void loadSplitBundleFromServer( + final String bundlePath, final DevSplitBundleCallback callback) { + fetchSplitBundleAndCreateBundleLoader( + bundlePath, + new CallbackWithBundleLoader() { + @Override + public void onSuccess(JSBundleLoader bundleLoader) { + bundleLoader.loadScript(mCurrentContext.getCatalystInstance()); + mCurrentContext + .getJSModule(HMRClient.class) + .registerBundle(mDevServerHelper.getDevServerSplitBundleURL(bundlePath)); + callback.onSuccess(); + } + + @Override + public void onError(String url, Throwable cause) { + callback.onError(url, cause); + } + }); + } + + public void fetchSplitBundleAndCreateBundleLoader( + String bundlePath, final CallbackWithBundleLoader callback) { final String bundleUrl = mDevServerHelper.getDevServerSplitBundleURL(bundlePath); // The bundle path may contain the '/' character, which is not allowed in file names. final File bundleFile = @@ -886,16 +914,16 @@ public void run() { }); @Nullable ReactContext context = mCurrentContext; - if (context == null || !context.hasActiveCatalystInstance()) { + if (context == null + || (!context.isBridgeless() && !context.hasActiveCatalystInstance())) { return; } - JSBundleLoader.createCachedSplitBundleFromNetworkLoader( - bundleUrl, bundleFile.getAbsolutePath()) - .loadScript(context.getCatalystInstance()); - context.getJSModule(HMRClient.class).registerBundle(bundleUrl); + JSBundleLoader bundleLoader = + JSBundleLoader.createCachedSplitBundleFromNetworkLoader( + bundleUrl, bundleFile.getAbsolutePath()); - callback.onSuccess(); + callback.onSuccess(bundleLoader); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/HMRClient.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/HMRClient.java index 97469c319d92ba..e10cdb81fd15ae 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/HMRClient.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/HMRClient.java @@ -12,14 +12,14 @@ /** * JS module interface for HMRClient * - *

The HMR(Hot Module Replacement)Client allows for the application to receive updates from the - * packager server (over a web socket), allowing for injection of JavaScript to the running - * application (without a refresh). + *

The HMR(Hot Module Replacement)Client allows for the application to receive updates from Metro + * (over a web socket), allowing for injection of JavaScript to the running application (without a + * refresh). */ public interface HMRClient extends JavaScriptModule { /** - * Enable the HMRClient so that the client will receive updates from the packager server. + * Enable the HMRClient so that the client will receive updates from Metro. * * @param platform The platform in which HMR updates will be enabled. Should be "android". * @param bundleEntry The path to the bundle entry file (e.g. index.ios.bundle). 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 3f3eb338d13b17..603089a28d22d4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -314,7 +314,11 @@ private void preallocateView( @Nullable ReadableMap props, @Nullable Object stateWrapper, boolean isLayoutable) { - ThemedReactContext context = mReactContextForRootTag.get(rootTag); + + // This could be null if teardown/navigation away from a surface on the main thread happens + // while a commit is being processed in a different thread. By contract we expect this to be + // possible at teardown, but this race should *never* happen at startup. + @Nullable ThemedReactContext context = mReactContextForRootTag.get(rootTag); String component = getFabricComponentName(componentName); synchronized (mPreMountItemsLock) { @@ -342,10 +346,12 @@ private MountItem createMountItem( int reactTag, boolean isLayoutable) { String component = getFabricComponentName(componentName); - ThemedReactContext reactContext = mReactContextForRootTag.get(reactRootTag); - if (reactContext == null) { - throw new IllegalArgumentException("Unable to find ReactContext for root: " + reactRootTag); - } + + // This could be null if teardown/navigation away from a surface on the main thread happens + // while a commit is being processed in a different thread. By contract we expect this to be + // possible at teardown, but this race should *never* happen at startup. + @Nullable ThemedReactContext reactContext = mReactContextForRootTag.get(reactRootTag); + return new CreateMountItem( reactContext, reactRootTag, @@ -460,8 +466,19 @@ private long measure( float minHeight, float maxHeight, @Nullable float[] attachmentsPositions) { + + // This could be null if teardown/navigation away from a surface on the main thread happens + // while a commit is being processed in a different thread. By contract we expect this to be + // possible at teardown, but this race should *never* happen at startup. + @Nullable ReactContext context = rootTag < 0 ? mReactApplicationContext : mReactContextForRootTag.get(rootTag); + + // Don't both measuring if we can't get a context. + if (context == null) { + return 0; + } + return mMountingManager.measure( context, componentName, @@ -1050,6 +1067,19 @@ public void profileNextBatch() { // TODO T31905686: Remove this method and add support for multi-threading performance counters } + @Override + @Deprecated + @Nullable + public String resolveCustomDirectEventName(@Nullable String eventName) { + if (eventName == null) { + return null; + } + if (eventName.substring(0, 3).equals("top")) { + return "on" + eventName.substring(3); + } + return eventName; + } + // Called from Binding.cpp @DoNotStrip @AnyThread diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java index 6320eb1d04d242..d96bab3326e086 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/CreateMountItem.java @@ -25,7 +25,7 @@ public class CreateMountItem implements MountItem { private final boolean mIsLayoutable; public CreateMountItem( - @NonNull ThemedReactContext context, + @Nullable ThemedReactContext context, int rootTag, int reactTag, @NonNull String component, @@ -43,6 +43,13 @@ public CreateMountItem( @Override public void execute(@NonNull MountingManager mountingManager) { + if (mContext == null) { + throw new IllegalStateException( + "Cannot execute PreAllocateViewMountItem without Context for ReactTag: " + + mReactTag + + " and rootTag: " + + mRootTag); + } mountingManager.createView( mContext, mComponent, mReactTag, mProps, mStateWrapper, mIsLayoutable); } 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 7f519e9bc6f2f2..0e3c7140eafbf2 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 @@ -31,7 +31,7 @@ public class PreAllocateViewMountItem implements MountItem { private final boolean mIsLayoutable; public PreAllocateViewMountItem( - @NonNull ThemedReactContext context, + @Nullable ThemedReactContext context, int rootTag, int reactTag, @NonNull String component, @@ -56,6 +56,13 @@ public void execute(@NonNull MountingManager mountingManager) { if (ENABLE_FABRIC_LOGS) { FLog.d(TAG, "Executing pre-allocation of: " + toString()); } + if (mContext == null) { + throw new IllegalStateException( + "Cannot execute PreAllocateViewMountItem without Context for ReactTag: " + + mReactTag + + " and rootTag: " + + mRootTag); + } mountingManager.preallocateView( mContext, mComponent, mReactTag, mProps, mStateWrapper, mIsLayoutable); } 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 deleted file mode 100644 index 7220d67a96b952..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/camera/CameraRollManager.java +++ /dev/null @@ -1,545 +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.camera; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.database.Cursor; -import android.graphics.BitmapFactory; -import android.media.MediaMetadataRetriever; -import android.media.MediaScannerConnection; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Environment; -import android.provider.MediaStore; -import android.provider.MediaStore.Images; -import android.text.TextUtils; -import androidx.annotation.Nullable; -import com.facebook.common.logging.FLog; -import com.facebook.fbreact.specs.NativeCameraRollManagerSpec; -import com.facebook.react.bridge.GuardedAsyncTask; -import com.facebook.react.bridge.JSApplicationIllegalArgumentException; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.WritableNativeArray; -import com.facebook.react.bridge.WritableNativeMap; -import com.facebook.react.common.ReactConstants; -import com.facebook.react.module.annotations.ReactModule; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URL; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -// TODO #6015104: rename to something less iOSish -/** - * {@link NativeModule} that allows JS to interact with the photos and videos on the device (i.e. - * {@link MediaStore.Images}). - */ -@ReactModule(name = CameraRollManager.NAME) -public class CameraRollManager extends NativeCameraRollManagerSpec { - - public static final String NAME = "CameraRollManager"; - - private static final String ERROR_UNABLE_TO_LOAD = "E_UNABLE_TO_LOAD"; - private static final String ERROR_UNABLE_TO_LOAD_PERMISSION = "E_UNABLE_TO_LOAD_PERMISSION"; - private static final String ERROR_UNABLE_TO_SAVE = "E_UNABLE_TO_SAVE"; - private static final String ERROR_UNABLE_TO_FILTER = "E_UNABLE_TO_FILTER"; - - private static final String ASSET_TYPE_PHOTOS = "Photos"; - private static final String ASSET_TYPE_VIDEOS = "Videos"; - private static final String ASSET_TYPE_ALL = "All"; - - private static final String SELECTION_BUCKET = Images.Media.BUCKET_DISPLAY_NAME + " = ?"; - private static final String SELECTION_DATE_TAKEN = Images.Media.DATE_TAKEN + " < ?"; - private static final String SELECTION_MEDIA_SIZE = Images.Media.SIZE + " < ?"; - - private static final int IMAGES_MEDIA_LATITUDE_LONGITUDE_DEPRECATED_API_LEVEL = 29; - private static final String[] PROJECTION_LIST; - - static { - ArrayList projection_list = - new ArrayList<>( - Arrays.asList( - Images.Media._ID, - Images.Media.MIME_TYPE, - Images.Media.BUCKET_DISPLAY_NAME, - Images.Media.DATE_TAKEN, - MediaStore.MediaColumns.WIDTH, - MediaStore.MediaColumns.HEIGHT, - MediaStore.MediaColumns.DATA)); - if (Build.VERSION.SDK_INT < IMAGES_MEDIA_LATITUDE_LONGITUDE_DEPRECATED_API_LEVEL) { - projection_list.add(Images.Media.LATITUDE); - projection_list.add(Images.Media.LONGITUDE); - PROJECTION_LIST = projection_list.toArray(new String[0]); - } else { - PROJECTION_LIST = projection_list.toArray(new String[0]); - } - } - - public CameraRollManager(ReactApplicationContext reactContext) { - super(reactContext); - } - - @Override - public String getName() { - return NAME; - } - - /** - * Save an image to the gallery (i.e. {@link MediaStore.Images}). This copies the original file - * from wherever it may be to the external storage pictures directory, so that it can be scanned - * by the MediaScanner. - * - * @param uri the file://, http:// or https:// URI of the image to save - * @param promise to be resolved or rejected - */ - @Override - public void saveToCameraRoll(String uri, String type, Promise promise) { - new SaveToCameraRoll(getReactApplicationContext(), Uri.parse(uri), promise) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private static class SaveToCameraRoll extends GuardedAsyncTask { - - private static final int SAVE_BUFFER_SIZE = 1048576; // 1MB - private final Context mContext; - private final Uri mUri; - private final Promise mPromise; - - public SaveToCameraRoll(ReactContext context, Uri uri, Promise promise) { - super(context); - mContext = context; - mUri = uri; - mPromise = promise; - } - - @Override - protected void doInBackgroundGuarded(Void... params) { - ReadableByteChannel input = null; - FileChannel output = null; - File source = new File(mUri.getPath()); - try { - String scheme = mUri.getScheme(); - if (scheme.equals("http") || scheme.equals("https")) { - input = Channels.newChannel(new URL(mUri.toString()).openStream()); - } else { - input = new FileInputStream(source).getChannel(); - } - File exportDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); - exportDir.mkdirs(); - if (!exportDir.isDirectory()) { - mPromise.reject(ERROR_UNABLE_TO_LOAD, "External media storage directory not available"); - return; - } - File dest = new File(exportDir, source.getName()); - int n = 0; - String fullSourceName = source.getName(); - String sourceName, sourceExt; - if (fullSourceName.indexOf('.') >= 0) { - sourceName = fullSourceName.substring(0, fullSourceName.lastIndexOf('.')); - sourceExt = fullSourceName.substring(fullSourceName.lastIndexOf('.')); - } else { - sourceName = fullSourceName; - sourceExt = ""; - } - while (!dest.createNewFile()) { - dest = new File(exportDir, sourceName + "_" + (n++) + sourceExt); - } - output = new FileOutputStream(dest).getChannel(); - // Performs a buffered copy - final ByteBuffer buffer = ByteBuffer.allocate(SAVE_BUFFER_SIZE); - while (input.read(buffer) > 0) { - buffer.flip(); - output.write(buffer); - buffer.compact(); - } - // Drains the buffer - buffer.flip(); - while (buffer.hasRemaining()) { - output.write(buffer); - } - input.close(); - output.close(); - - MediaScannerConnection.scanFile( - mContext, - new String[] {dest.getAbsolutePath()}, - null, - new MediaScannerConnection.OnScanCompletedListener() { - @Override - public void onScanCompleted(String path, Uri uri) { - if (uri != null) { - mPromise.resolve(uri.toString()); - } else { - mPromise.reject(ERROR_UNABLE_TO_SAVE, "Could not add image to gallery"); - } - } - }); - } catch (IOException e) { - mPromise.reject(e); - } finally { - if (input != null && input.isOpen()) { - try { - input.close(); - } catch (IOException e) { - FLog.e(ReactConstants.TAG, "Could not close input channel", e); - } - } - if (output != null && output.isOpen()) { - try { - output.close(); - } catch (IOException e) { - FLog.e(ReactConstants.TAG, "Could not close output channel", e); - } - } - } - } - } - - /** - * Get photos from {@link MediaStore.Images}, most recent first. - * - * @param params a map containing the following keys: - *

    - *
  • first (mandatory): a number representing the number of photos to fetch - *
  • after (optional): a cursor that matches page_info[end_cursor] returned by a previous - * call to {@link #getPhotos} - *
  • groupName (optional): an album name - *
  • mimeType (optional): restrict returned images to a specific mimetype (e.g. - * image/jpeg) - *
  • assetType (optional): chooses between either photos or videos from the camera roll. - * Valid values are "Photos" or "Videos". Defaults to photos. - *
- * - * @param promise the Promise to be resolved when the photos are loaded; for a format of the - * parameters passed to this callback, see {@code getPhotosReturnChecker} in CameraRoll.js - */ - @Override - public void getPhotos(final ReadableMap params, final Promise promise) { - int first = params.getInt("first"); - String after = params.hasKey("after") ? params.getString("after") : null; - String groupName = params.hasKey("groupName") ? params.getString("groupName") : null; - String assetType = - params.hasKey("assetType") ? params.getString("assetType") : ASSET_TYPE_PHOTOS; - Integer maxSize = params.hasKey("maxSize") ? params.getInt("maxSize") : null; - ReadableArray mimeTypes = params.hasKey("mimeTypes") ? params.getArray("mimeTypes") : null; - if (params.hasKey("groupTypes")) { - throw new JSApplicationIllegalArgumentException("groupTypes is not supported on Android"); - } - - new GetMediaTask( - getReactApplicationContext(), - first, - after, - groupName, - mimeTypes, - assetType, - maxSize, - promise) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private static class GetMediaTask extends GuardedAsyncTask { - private final Context mContext; - private final int mFirst; - private final @Nullable String mAfter; - private final @Nullable String mGroupName; - private final @Nullable ReadableArray mMimeTypes; - private final Promise mPromise; - private final String mAssetType; - private final @Nullable Integer mMaxSize; - - private GetMediaTask( - ReactContext context, - int first, - @Nullable String after, - @Nullable String groupName, - @Nullable ReadableArray mimeTypes, - String assetType, - @Nullable Integer maxSize, - Promise promise) { - super(context); - mContext = context; - mFirst = first; - mAfter = after; - mGroupName = groupName; - mMimeTypes = mimeTypes; - mPromise = promise; - mAssetType = assetType; - mMaxSize = maxSize; - } - - @Override - protected void doInBackgroundGuarded(Void... params) { - StringBuilder selection = new StringBuilder("1"); - List selectionArgs = new ArrayList<>(); - if (!TextUtils.isEmpty(mAfter)) { - selection.append(" AND " + SELECTION_DATE_TAKEN); - selectionArgs.add(mAfter); - } - if (!TextUtils.isEmpty(mGroupName)) { - selection.append(" AND " + SELECTION_BUCKET); - selectionArgs.add(mGroupName); - } - if (mMaxSize != null) { - selection.append(" AND " + SELECTION_MEDIA_SIZE); - selectionArgs.add(mMaxSize.toString()); - } - - switch (mAssetType) { - case ASSET_TYPE_PHOTOS: - selection.append( - " AND " - + MediaStore.Files.FileColumns.MEDIA_TYPE - + " = " - + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE); - break; - case ASSET_TYPE_VIDEOS: - selection.append( - " AND " - + MediaStore.Files.FileColumns.MEDIA_TYPE - + " = " - + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO); - break; - case ASSET_TYPE_ALL: - selection.append( - " AND " - + MediaStore.Files.FileColumns.MEDIA_TYPE - + " IN (" - + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO - + "," - + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE - + ")"); - break; - default: - mPromise.reject( - ERROR_UNABLE_TO_FILTER, - "Invalid filter option: '" - + mAssetType - + "'. Expected one of '" - + ASSET_TYPE_PHOTOS - + "', '" - + ASSET_TYPE_VIDEOS - + "' or '" - + ASSET_TYPE_ALL - + "'."); - return; - } - - if (mMimeTypes != null && mMimeTypes.size() > 0) { - selection.append(" AND " + Images.Media.MIME_TYPE + " IN ("); - for (int i = 0; i < mMimeTypes.size(); i++) { - selection.append("?,"); - selectionArgs.add(mMimeTypes.getString(i)); - } - selection.replace(selection.length() - 1, selection.length(), ")"); - } - WritableMap response = new WritableNativeMap(); - ContentResolver resolver = mContext.getContentResolver(); - // using LIMIT in the sortOrder is not explicitly supported by the SDK (which does not support - // setting a limit at all), but it works because this specific ContentProvider is backed by - // an SQLite DB and forwards parameters to it without doing any parsing / validation. - try { - Cursor media = - resolver.query( - MediaStore.Files.getContentUri("external"), - PROJECTION_LIST, - selection.toString(), - selectionArgs.toArray(new String[selectionArgs.size()]), - Images.Media.DATE_TAKEN - + " DESC, " - + Images.Media.DATE_MODIFIED - + " DESC LIMIT " - + (mFirst - + 1)); // set LIMIT to first + 1 so that we know how to populate page_info - if (media == null) { - mPromise.reject(ERROR_UNABLE_TO_LOAD, "Could not get media"); - } else { - try { - putEdges(resolver, media, response, mFirst); - putPageInfo(media, response, mFirst); - } finally { - media.close(); - mPromise.resolve(response); - } - } - } catch (SecurityException e) { - mPromise.reject( - ERROR_UNABLE_TO_LOAD_PERMISSION, - "Could not get media: need READ_EXTERNAL_STORAGE permission", - e); - } - } - } - - private static void putPageInfo(Cursor media, WritableMap response, int limit) { - WritableMap pageInfo = new WritableNativeMap(); - pageInfo.putBoolean("has_next_page", limit < media.getCount()); - if (limit < media.getCount()) { - media.moveToPosition(limit - 1); - pageInfo.putString( - "end_cursor", media.getString(media.getColumnIndex(Images.Media.DATE_TAKEN))); - } - response.putMap("page_info", pageInfo); - } - - private static void putEdges( - ContentResolver resolver, Cursor media, WritableMap response, int limit) { - WritableArray edges = new WritableNativeArray(); - media.moveToFirst(); - int idIndex = media.getColumnIndex(Images.Media._ID); - int mimeTypeIndex = media.getColumnIndex(Images.Media.MIME_TYPE); - int groupNameIndex = media.getColumnIndex(Images.Media.BUCKET_DISPLAY_NAME); - int dateTakenIndex = media.getColumnIndex(Images.Media.DATE_TAKEN); - int widthIndex = media.getColumnIndex(MediaStore.MediaColumns.WIDTH); - int heightIndex = media.getColumnIndex(MediaStore.MediaColumns.HEIGHT); - int longitudeIndex = media.getColumnIndex(Images.Media.LONGITUDE); - int latitudeIndex = media.getColumnIndex(Images.Media.LATITUDE); - int dataIndex = media.getColumnIndex(MediaStore.MediaColumns.DATA); - - for (int i = 0; i < limit && !media.isAfterLast(); i++) { - WritableMap edge = new WritableNativeMap(); - WritableMap node = new WritableNativeMap(); - boolean imageInfoSuccess = - putImageInfo( - resolver, media, node, idIndex, widthIndex, heightIndex, dataIndex, mimeTypeIndex); - if (imageInfoSuccess) { - putBasicNodeInfo(media, node, mimeTypeIndex, groupNameIndex, dateTakenIndex); - if (Build.VERSION.SDK_INT < IMAGES_MEDIA_LATITUDE_LONGITUDE_DEPRECATED_API_LEVEL) { - putLocationInfo(media, node, longitudeIndex, latitudeIndex); - } - - edge.putMap("node", node); - edges.pushMap(edge); - } else { - // we skipped an image because we couldn't get its details (e.g. width/height), so we - // decrement i in order to correctly reach the limit, if the cursor has enough rows - i--; - } - media.moveToNext(); - } - response.putArray("edges", edges); - } - - private static void putBasicNodeInfo( - Cursor media, WritableMap node, int mimeTypeIndex, int groupNameIndex, int dateTakenIndex) { - node.putString("type", media.getString(mimeTypeIndex)); - node.putString("group_name", media.getString(groupNameIndex)); - node.putDouble("timestamp", media.getLong(dateTakenIndex) / 1000d); - } - - private static boolean putImageInfo( - ContentResolver resolver, - Cursor media, - WritableMap node, - int idIndex, - int widthIndex, - int heightIndex, - int dataIndex, - int mimeTypeIndex) { - WritableMap image = new WritableNativeMap(); - Uri photoUri = Uri.parse("file://" + media.getString(dataIndex)); - image.putString("uri", photoUri.toString()); - float width = media.getInt(widthIndex); - float height = media.getInt(heightIndex); - - String mimeType = media.getString(mimeTypeIndex); - - if (mimeType != null && mimeType.startsWith("video")) { - try { - AssetFileDescriptor photoDescriptor = resolver.openAssetFileDescriptor(photoUri, "r"); - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - retriever.setDataSource(photoDescriptor.getFileDescriptor()); - - try { - if (width <= 0 || height <= 0) { - width = - Integer.parseInt( - retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)); - height = - Integer.parseInt( - retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)); - } - int timeInMillisec = - Integer.parseInt( - retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); - int playableDuration = timeInMillisec / 1000; - image.putInt("playableDuration", playableDuration); - } catch (NumberFormatException e) { - FLog.e( - ReactConstants.TAG, - "Number format exception occurred while trying to fetch video metadata for " - + photoUri.toString(), - e); - return false; - } finally { - retriever.release(); - photoDescriptor.close(); - } - } catch (Exception e) { - FLog.e(ReactConstants.TAG, "Could not get video metadata for " + photoUri.toString(), e); - return false; - } - } - - if (width <= 0 || height <= 0) { - try { - AssetFileDescriptor photoDescriptor = resolver.openAssetFileDescriptor(photoUri, "r"); - BitmapFactory.Options options = new BitmapFactory.Options(); - // Set inJustDecodeBounds to true so we don't actually load the Bitmap, but only get its - // dimensions instead. - options.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(photoDescriptor.getFileDescriptor(), null, options); - width = options.outWidth; - height = options.outHeight; - photoDescriptor.close(); - } catch (IOException e) { - FLog.e(ReactConstants.TAG, "Could not get width/height for " + photoUri.toString(), e); - return false; - } - } - image.putDouble("width", width); - image.putDouble("height", height); - node.putMap("image", image); - - return true; - } - - private static void putLocationInfo( - Cursor media, WritableMap node, int longitudeIndex, int latitudeIndex) { - double longitude = media.getDouble(longitudeIndex); - double latitude = media.getDouble(latitudeIndex); - if (longitude > 0 || latitude > 0) { - WritableMap location = new WritableNativeMap(); - location.putDouble("longitude", longitude); - location.putDouble("latitude", latitude); - node.putMap("location", location); - } - } - - @Override - public void deletePhotos(ReadableArray assets, Promise promise) { - // iOS only - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/BUCK b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/BUCK new file mode 100644 index 00000000000000..d8d99e703d2b02 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/BUCK @@ -0,0 +1,22 @@ +load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library") + +rn_android_library( + name = "reactperflogger", + srcs = glob( + [ + "*.java", + ], + ), + labels = [ + "supermodule:xplat/default/public.react_native.infra", + ], + required_for_source_only_abi = True, + visibility = [ + "PUBLIC", + ], + deps = [ + react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"), + react_native_dep("libraries/fbjni:java"), + react_native_target("java/com/facebook/react/reactperflogger/jni:jni"), + ], +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/NativeModulePerfLogger.java b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/NativeModulePerfLogger.java new file mode 100644 index 00000000000000..56f9adfbbfb8c3 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/NativeModulePerfLogger.java @@ -0,0 +1,56 @@ +/* + * 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.perflogger; + +import com.facebook.jni.HybridData; +import com.facebook.soloader.SoLoader; + +public abstract class NativeModulePerfLogger { + private final HybridData mHybridData; + + private static volatile boolean sIsSoLibraryLoaded; + + protected abstract HybridData initHybrid(); + + protected NativeModulePerfLogger() { + maybeLoadOtherSoLibraries(); + maybeLoadSoLibrary(); + mHybridData = initHybrid(); + } + + public abstract void moduleDataCreateStart(String moduleName, int id); + + public abstract void moduleDataCreateEnd(String moduleName, int id); + + public abstract void moduleCreateStart(String moduleName, int id); + + public abstract void moduleCreateCacheHit(String moduleName, int id); + + public abstract void moduleCreateConstructStart(String moduleName, int id); + + public abstract void moduleCreateConstructEnd(String moduleName, int id); + + public abstract void moduleCreateSetUpStart(String moduleName, int id); + + public abstract void moduleCreateSetUpEnd(String moduleName, int id); + + public abstract void moduleCreateEnd(String moduleName, int id); + + public abstract void moduleCreateFail(String moduleName, int id); + + // Prevents issues with initializer interruptions. See T38996825 and D13793825 for more context. + private static synchronized void maybeLoadSoLibrary() { + if (!sIsSoLibraryLoaded) { + SoLoader.loadLibrary("reactperfloggerjni"); + sIsSoLibraryLoaded = true; + } + } + + /** Subclasses will override this method to load their own SO libraries. */ + protected synchronized void maybeLoadOtherSoLibraries() {} +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/Android.mk b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/Android.mk new file mode 100644 index 00000000000000..c44496918d50fc --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/Android.mk @@ -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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +# Header search path for all source files in this module. +LOCAL_C_INCLUDES := $(LOCAL_PATH)/reactperflogger + +# Header search path for modules that depend on this module +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) + +LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall + +LOCAL_LDLIBS += -landroid + +LOCAL_STATIC_LIBRARIES = libreactperflogger + +LOCAL_SHARED_LIBRARIES = libfb libfbjni + +# Name of this module. +LOCAL_MODULE := reactperfloggerjni + +# Compile all local c++ files +LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/reactperflogger/*.cpp) + +# Build the files in this directory as a shared library +include $(BUILD_SHARED_LIBRARY) diff --git a/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/BUCK new file mode 100644 index 00000000000000..481682cb97c8c3 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/BUCK @@ -0,0 +1,37 @@ +load("@fbsource//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "FBJNI_TARGET", "react_native_xplat_target", "rn_xplat_cxx_library") + +rn_xplat_cxx_library( + name = "jni", + srcs = [ + "reactperflogger/OnLoad.cpp", + ], + header_namespace = "", + exported_headers = { + "reactperflogger/JNativeModulePerfLogger.h": "reactperflogger/JNativeModulePerfLogger.h", + }, + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + ], + fbandroid_allow_jni_merging = True, + fbandroid_labels = [ + "supermodule:xplat/default/public.react_native.infra", + ], + platforms = ANDROID, + preprocessor_flags = [ + "-DLOG_TAG=\"ReactNative\"", + "-DWITH_FBSYSTRACE=1", + ], + soname = "libreactperfloggerjni.$(ext)", + visibility = [ + "PUBLIC", + ], + deps = [ + FBJNI_TARGET, + ], + exported_deps = [ + react_native_xplat_target("reactperflogger:reactperflogger"), + ], +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/reactperflogger/.clang-tidy b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/reactperflogger/.clang-tidy new file mode 100644 index 00000000000000..c98fd78ff64baa --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/reactperflogger/.clang-tidy @@ -0,0 +1,5 @@ +--- +Checks: '> +clang-diagnostic-*, +' +... diff --git a/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/reactperflogger/JNativeModulePerfLogger.h b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/reactperflogger/JNativeModulePerfLogger.h new file mode 100644 index 00000000000000..018d8de782deaf --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/reactperflogger/JNativeModulePerfLogger.h @@ -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. + */ + +#pragma once + +#include +#include +#include + +namespace facebook { +namespace react { + +class JNativeModulePerfLogger + : public jni::HybridClass { + public: + static auto constexpr kJavaDescriptor = + "Lcom/facebook/react/perflogger/NativeModulePerfLogger;"; + + virtual std::unique_ptr get() = 0; + + private: + friend HybridBase; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTElement.cpp b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/reactperflogger/OnLoad.cpp similarity index 53% rename from ReactCommon/fabric/components/art/state/ARTElement.cpp rename to ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/reactperflogger/OnLoad.cpp index 5b1ed74cb61c3a..4ac3d476441645 100644 --- a/ReactCommon/fabric/components/art/state/ARTElement.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/reactperflogger/jni/reactperflogger/OnLoad.cpp @@ -5,8 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -#include +#include -namespace facebook { -namespace react {} // namespace react -} // namespace facebook +#include "JNativeModulePerfLogger.h" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { + return facebook::jni::initialize(vm, [] {}); +} 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 669718e63cc353..35ce74f69b5f58 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -21,7 +21,6 @@ import com.facebook.react.modules.appstate.AppStateModule; import com.facebook.react.modules.blob.BlobModule; import com.facebook.react.modules.blob.FileReaderModule; -import com.facebook.react.modules.camera.CameraRollManager; import com.facebook.react.modules.camera.ImageStoreManager; import com.facebook.react.modules.clipboard.ClipboardModule; import com.facebook.react.modules.datepicker.DatePickerDialogModule; @@ -73,7 +72,6 @@ BlobModule.class, FileReaderModule.class, AsyncStorageModule.class, - CameraRollManager.class, ClipboardModule.class, DatePickerDialogModule.class, DialogModule.class, @@ -118,8 +116,6 @@ public MainReactPackage(MainPackageConfig config) { return new FileReaderModule(context); case AsyncStorageModule.NAME: return new AsyncStorageModule(context); - case CameraRollManager.NAME: - return new CameraRollManager(context); case ClipboardModule.NAME: return new ClipboardModule(context); case DatePickerDialogModule.FRAGMENT_TAG: @@ -203,7 +199,6 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() { BlobModule.class, FileReaderModule.class, AsyncStorageModule.class, - CameraRollManager.class, ClipboardModule.class, DatePickerDialogModule.class, DialogModule.class, diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/BUCK b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/BUCK index 50e716a39a8cb6..721fe18d0cc7f1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/BUCK @@ -22,6 +22,7 @@ rn_android_library( 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/common:common"), + react_native_target("java/com/facebook/react/reactperflogger:reactperflogger"), react_native_target("java/com/facebook/react/turbomodule/core/jni:jni"), react_native_target("java/com/facebook/debug/holder:holder"), react_native_target("java/com/facebook/react/bridge:interfaces"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/TurboModulePerfLogger.java b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/TurboModulePerfLogger.java new file mode 100644 index 00000000000000..19d84a41a5a963 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/TurboModulePerfLogger.java @@ -0,0 +1,83 @@ +/* + * 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.turbomodule.core; + +import com.facebook.react.perflogger.NativeModulePerfLogger; +import com.facebook.soloader.SoLoader; +import javax.annotation.Nullable; + +public class TurboModulePerfLogger { + @Nullable private static NativeModulePerfLogger sNativeModulePerfLogger = null; + + static { + SoLoader.loadLibrary("turbomodulejsijni"); + } + + public static void moduleDataCreateStart(String moduleName, int id) { + if (sNativeModulePerfLogger != null) { + sNativeModulePerfLogger.moduleDataCreateStart(moduleName, id); + } + } + + public static void moduleDataCreateEnd(String moduleName, int id) { + if (sNativeModulePerfLogger != null) { + sNativeModulePerfLogger.moduleDataCreateEnd(moduleName, id); + } + } + + public static void moduleCreateStart(String moduleName, int id) { + if (sNativeModulePerfLogger != null) { + sNativeModulePerfLogger.moduleCreateStart(moduleName, id); + } + } + + public static void moduleCreateCacheHit(String moduleName, int id) { + if (sNativeModulePerfLogger != null) { + sNativeModulePerfLogger.moduleCreateCacheHit(moduleName, id); + } + } + + public static void moduleCreateConstructStart(String moduleName, int id) { + if (sNativeModulePerfLogger != null) { + sNativeModulePerfLogger.moduleCreateConstructStart(moduleName, id); + } + } + + public static void moduleCreateConstructEnd(String moduleName, int id) { + if (sNativeModulePerfLogger != null) { + sNativeModulePerfLogger.moduleCreateConstructEnd(moduleName, id); + } + } + + public static void moduleCreateSetUpStart(String moduleName, int id) { + if (sNativeModulePerfLogger != null) { + sNativeModulePerfLogger.moduleCreateSetUpStart(moduleName, id); + } + } + + public static void moduleCreateSetUpEnd(String moduleName, int id) { + if (sNativeModulePerfLogger != null) { + sNativeModulePerfLogger.moduleCreateSetUpEnd(moduleName, id); + } + } + + public static void moduleCreateEnd(String moduleName, int id) { + if (sNativeModulePerfLogger != null) { + sNativeModulePerfLogger.moduleCreateEnd(moduleName, id); + } + } + + private static native void jniEnableCppLogging(NativeModulePerfLogger perfLogger); + + public static void enableLogging(NativeModulePerfLogger perfLogger) { + if (perfLogger != null) { + sNativeModulePerfLogger = perfLogger; + jniEnableCppLogging(perfLogger); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/Android.mk b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/Android.mk index 1633f5ad878a53..80ba4096da6480 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/Android.mk +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/Android.mk @@ -15,7 +15,7 @@ LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) LOCAL_CFLAGS += -fexceptions -frtti -std=c++14 -Wall -LOCAL_STATIC_LIBRARIES = libcallinvoker +LOCAL_STATIC_LIBRARIES = libcallinvoker libreactperfloggerjni LOCAL_SHARED_LIBRARIES = libfb libfbjni 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 9a7eb46af4bf50..84258474072b91 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 @@ -35,6 +35,7 @@ rn_xplat_cxx_library( ":callinvokerholder", "//xplat/jsi:jsi", react_native_xplat_target("turbomodule/core:core"), + react_native_target("java/com/facebook/react/reactperflogger/jni:jni"), ], ) diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/OnLoad.cpp b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/OnLoad.cpp index 65dc87dd688c10..9cc583c7231849 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/OnLoad.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/OnLoad.cpp @@ -5,15 +5,29 @@ * LICENSE file in the root directory of this source tree. */ +#include #include #include +#include #include "TurboModuleManager.h" +void jniEnableCppLogging( + jni::alias_ref cls, + jni::alias_ref + perfLogger) { + facebook::react::TurboModulePerfLogger::enableLogging( + perfLogger->cthis()->get()); +} + JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { return facebook::xplat::initialize(vm, [] { // TODO: dvacca ramanpreet unify this with the way // "ComponentDescriptorFactory" is defined in Fabric facebook::react::TurboModuleManager::registerNatives(); + + facebook::jni::registerNatives( + "com/facebook/react/turbomodule/core/TurboModulePerfLogger", + {makeNativeMethod("jniEnableCppLogging", jniEnableCppLogging)}); }); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp index ac3a74c20660c9..c2a3b76df36d3c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include "TurboModuleManager.h" @@ -81,11 +82,19 @@ void TurboModuleManager::installJSIBindings() { return nullptr; } + const char *moduleName = name.c_str(); + + TurboModulePerfLogger::moduleJSRequireBeginningStart(moduleName); + auto turboModuleLookup = turboModuleCache->find(name); if (turboModuleLookup != turboModuleCache->end()) { + TurboModulePerfLogger::moduleJSRequireBeginningCacheHit(moduleName); + TurboModulePerfLogger::moduleJSRequireBeginningEnd(moduleName); return turboModuleLookup->second; } + TurboModulePerfLogger::moduleJSRequireBeginningEnd(moduleName); + auto cxxModule = delegate->cthis()->getTurboModule(name, jsCallInvoker); if (cxxModule) { turboModuleCache->insert({name, cxxModule}); @@ -99,9 +108,13 @@ void TurboModuleManager::installJSIBindings() { auto legacyCxxModule = getLegacyCxxModule(javaPart.get(), name); if (legacyCxxModule) { + TurboModulePerfLogger::moduleJSRequireEndingStart(moduleName); + auto turboModule = std::make_shared( legacyCxxModule->cthis()->getModule(), jsCallInvoker); turboModuleCache->insert({name, turboModule}); + + TurboModulePerfLogger::moduleJSRequireEndingEnd(moduleName); return turboModule; } @@ -112,6 +125,7 @@ void TurboModuleManager::installJSIBindings() { auto moduleInstance = getJavaModule(javaPart.get(), name); if (moduleInstance) { + TurboModulePerfLogger::moduleJSRequireEndingStart(moduleName); JavaTurboModule::InitParams params = {.moduleName = name, .instance = moduleInstance, .jsInvoker = jsCallInvoker, @@ -119,6 +133,7 @@ void TurboModuleManager::installJSIBindings() { auto turboModule = delegate->cthis()->getTurboModule(name, params); turboModuleCache->insert({name, turboModule}); + TurboModulePerfLogger::moduleJSRequireEndingEnd(moduleName); return turboModule; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index ef39975d4936ba..2028bdb6082536 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -95,6 +95,15 @@ public void setElevation(@NonNull T view, float elevation) { ViewCompat.setElevation(view, PixelUtil.toPixelFromDIP(elevation)); } + @Override + @ReactProp(name = ViewProps.SHADOW_COLOR, defaultInt = Color.BLACK, customType = "Color") + public void setShadowColor(@NonNull T view, int shadowColor) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + view.setOutlineAmbientShadowColor(shadowColor); + view.setOutlineSpotShadowColor(shadowColor); + } + } + @Override @ReactProp(name = ViewProps.Z_INDEX) public void setZIndex(@NonNull T view, float zIndex) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerAdapter.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerAdapter.java index 6af559a15c90f2..c0e21dece66f2a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerAdapter.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerAdapter.java @@ -54,6 +54,9 @@ public void setBorderTopRightRadius(@NonNull T view, float borderRadius) {} @Override public void setElevation(@NonNull T view, float elevation) {} + @Override + public void setShadowColor(@NonNull T view, int shadowColor) {} + @Override public void setImportantForAccessibility( @NonNull T view, @Nullable String importantForAccessibility) {} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java index f430398bacf208..1598b578877b2f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerDelegate.java @@ -74,6 +74,10 @@ public void setProperty(T view, String propName, @Nullable Object value) { case ViewProps.ELEVATION: mViewManager.setElevation(view, value == null ? 0.0f : ((Double) value).floatValue()); break; + case ViewProps.SHADOW_COLOR: + mViewManager.setShadowColor( + view, value == null ? 0 : ColorPropConverter.getColor(value, view.getContext())); + break; case ViewProps.IMPORTANT_FOR_ACCESSIBILITY: mViewManager.setImportantForAccessibility(view, (String) value); break; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java index fb194e213699f2..5423eeedd91a71 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java @@ -43,6 +43,8 @@ public interface BaseViewManagerInterface { void setElevation(T view, float elevation); + void setShadowColor(T view, int shadowColor); + void setImportantForAccessibility(T view, @Nullable String importantForAccessibility); void setNativeId(T view, @Nullable String nativeId); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java index 039798dcaf321f..834208a962e676 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java @@ -10,6 +10,8 @@ import android.app.Activity; import android.content.Context; import androidx.annotation.Nullable; +import com.facebook.react.bridge.JSIModule; +import com.facebook.react.bridge.JSIModuleType; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; @@ -77,4 +79,12 @@ public ReactApplicationContext getReactApplicationContext() { public boolean isBridgeless() { return mReactApplicationContext.isBridgeless(); } + + @Override + public JSIModule getJSIModule(JSIModuleType moduleType) { + if (isBridgeless()) { + return mReactApplicationContext.getJSIModule(moduleType); + } + return super.getJSIModule(moduleType); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.java index 30133a5388815b..954dfe95edfb3c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.java @@ -51,6 +51,22 @@ private static UIManager getUIManager( ReactContext context, @UIManagerType int uiManagerType, boolean returnNullIfCatalystIsInactive) { + if (context.isBridgeless()) { + @Nullable + UIManager uiManager = + context.getJSIModule(JSIModuleType.UIManager) != null + ? (UIManager) context.getJSIModule(JSIModuleType.UIManager) + : null; + if (uiManager == null) { + ReactSoftException.logSoftException( + "UIManagerHelper", + new ReactNoCrashSoftException( + "Cannot get UIManager because the instance hasn't been initialized yet.")); + return null; + } + return uiManager; + } + if (!context.hasCatalystInstance()) { ReactSoftException.logSoftException( "UIManagerHelper", 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 52750844ed8d26..2336cf82953f57 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -51,6 +51,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; /** * Native module to allow JS to create and update native Views. @@ -119,7 +120,8 @@ public interface CustomEventNamesResolver { private final UIImplementation mUIImplementation; private final MemoryTrimCallback mMemoryTrimCallback = new MemoryTrimCallback(); private final List mListeners = new ArrayList<>(); - private final List mUIManagerListeners = new ArrayList<>(); + private final CopyOnWriteArrayList mUIManagerListeners = + new CopyOnWriteArrayList<>(); private @Nullable Map mViewManagerConstantsCache; private volatile int mViewManagerConstantsCacheSize; @@ -355,20 +357,30 @@ public WritableMap getDefaultEventTypes() { } /** Resolves Direct Event name exposed to JS from the one known to the Native side. */ + @Deprecated public CustomEventNamesResolver getDirectEventNamesResolver() { return new CustomEventNamesResolver() { @Override - public @Nullable String resolveCustomEventName(String eventName) { - Map customEventType = - (Map) mCustomDirectEvents.get(eventName); - if (customEventType != null) { - return customEventType.get("registrationName"); - } - return eventName; + public @Nullable String resolveCustomEventName(@Nullable String eventName) { + return resolveCustomDirectEventName(eventName); } }; } + @Override + @Deprecated + @Nullable + public String resolveCustomDirectEventName(@Nullable String eventName) { + if (eventName != null) { + Map customEventType = + (Map) mCustomDirectEvents.get(eventName); + if (customEventType != null) { + return customEventType.get("registrationName"); + } + } + return eventName; + } + @Override public void profileNextBatch() { mUIImplementation.profileNextBatch(); 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 69530675e32b31..bb3df5f827e710 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -866,8 +866,7 @@ public void run() { long runStartTime = SystemClock.uptimeMillis(); // All ViewCommands should be executed first as a perf optimization. - // This entire block is only executed if there's a separate viewCommand queue, - // which is currently gated by a ReactFeatureFlag. + // This entire block is only executed if there's at least one ViewCommand queued. if (viewCommandOperations != null) { for (DispatchCommandViewOperation op : viewCommandOperations) { try { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java index fcb4d2bb4f15cc..ac780e8bb4f91b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java @@ -140,6 +140,7 @@ public class ViewProps { public static final String TRANSFORM = "transform"; public static final String ELEVATION = "elevation"; + public static final String SHADOW_COLOR = "shadowColor"; public static final String Z_INDEX = "zIndex"; public static final String RENDER_TO_HARDWARE_TEXTURE = "renderToHardwareTextureAndroid"; public static final String ACCESSIBILITY_LABEL = "accessibilityLabel"; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java index 542f03616f1d33..9754b2de085471 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java @@ -22,6 +22,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; /** @@ -88,7 +89,8 @@ public int compare(Event lhs, Event rhs) { private final Map mEventNameToEventId = MapBuilder.newHashMap(); private final DispatchEventsRunnable mDispatchEventsRunnable = new DispatchEventsRunnable(); private final ArrayList mEventStaging = new ArrayList<>(); - private final ArrayList mListeners = new ArrayList<>(); + private final CopyOnWriteArrayList mListeners = + new CopyOnWriteArrayList<>(); private final List mPostEventDispatchListeners = new ArrayList<>(); private final ScheduleDispatchFrameCallback mCurrentFrameCallback = new ScheduleDispatchFrameCallback(); 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 4fdbd76ababee3..ffc8655c0a47c1 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 @@ -7,6 +7,7 @@ package com.facebook.react.views.text; +import android.os.Build; import android.text.Layout; import android.text.Spannable; import android.text.TextUtils; @@ -14,6 +15,7 @@ import android.view.Gravity; import android.view.View; import androidx.annotation.Nullable; +import com.facebook.common.logging.FLog; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.uimanager.BaseViewManager; import com.facebook.react.uimanager.PixelUtil; @@ -39,6 +41,7 @@ public abstract class ReactTextAnchorViewManagersetLogger(nullptr); + YGConfigSetLogger(config, nullptr); } } diff --git a/ReactAndroid/src/main/jni/react/jni/Android.mk b/ReactAndroid/src/main/jni/react/jni/Android.mk index 38a51019ee0f44..b649da6329c84e 100644 --- a/ReactAndroid/src/main/jni/react/jni/Android.mk +++ b/ReactAndroid/src/main/jni/react/jni/Android.mk @@ -73,6 +73,7 @@ $(call import-module,reactperflogger) $(call import-module,hermes) $(call import-module,runtimeexecutor) +include $(REACT_SRC_DIR)/reactperflogger/jni/Android.mk include $(REACT_SRC_DIR)/turbomodule/core/jni/Android.mk # TODO(ramanpreet): diff --git a/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp b/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp index 4c76a79c7c91dc..2b07dcb7530b38 100644 --- a/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp +++ b/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp @@ -66,7 +66,7 @@ loadScriptFromAssets(AAssetManager *manager, const std::string &assetName) { throw std::runtime_error(folly::to( "Unable to load script. Make sure you're " - "either running a Metro server (run 'react-native start') or that your bundle '", + "either running Metro (run 'react-native start') or that your bundle '", assetName, "' is packaged correctly for release.")); } diff --git a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java index 30a9a3bc41e675..fc08cc8177bbb4 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java @@ -183,6 +183,15 @@ public String resolveCustomEventName(String eventName) { }; } }); + PowerMockito.when(mUIManagerMock.resolveCustomDirectEventName(any(String.class))) + .thenAnswer( + new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + String arg = invocation.getArguments()[0].toString(); + return "on" + arg.substring(3); + } + }); mNativeAnimatedNodesManager = new NativeAnimatedNodesManager(mReactApplicationContextMock); } @@ -946,7 +955,7 @@ public void testNativeAnimatedEventDoUpdate() { mNativeAnimatedNodesManager.addAnimatedEventToView( viewTag, - "topScroll", + "onScroll", JavaOnlyMap.of( "animatedValueTag", 1, "nativeEventPath", JavaOnlyArray.of("contentOffset", "y"))); @@ -999,7 +1008,7 @@ public void testNativeAnimatedEventCustomMapping() { public Object answer(InvocationOnMock invocation) throws Throwable { return MapBuilder.of( "customDirectEventTypes", - MapBuilder.of("topScroll", MapBuilder.of("registrationName", "onScroll"))); + MapBuilder.of("onScroll", MapBuilder.of("registrationName", "onScroll"))); } }); mNativeAnimatedNodesManager = new NativeAnimatedNodesManager(mReactApplicationContextMock); @@ -1024,7 +1033,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { @Test public void testRestoreDefaultProps() { - int viewTag = 1000; + int viewTag = 1001; // restoreDefaultProps not called in Fabric, make sure it's a non-Fabric tag int propsNodeTag = 3; mNativeAnimatedNodesManager.createAnimatedNode( 1, JavaOnlyMap.of("type", "value", "value", 1d, "offset", 0d)); diff --git a/ReactCommon/cxxreact/BUCK b/ReactCommon/cxxreact/BUCK index 9fffd1a414cc2a..5e0d7af0a0f32a 100644 --- a/ReactCommon/cxxreact/BUCK +++ b/ReactCommon/cxxreact/BUCK @@ -87,7 +87,6 @@ CXXREACT_PUBLIC_HEADERS = [ "ErrorUtils.h", "Instance.h", "JSBundleType.h", - "JSDeltaBundleClient.h", "JSExecutor.h", "JSIndexedRAMBundle.h", "JSModulesUnbundle.h", diff --git a/ReactCommon/cxxreact/JSDeltaBundleClient.cpp b/ReactCommon/cxxreact/JSDeltaBundleClient.cpp deleted file mode 100644 index 3f3e01add36442..00000000000000 --- a/ReactCommon/cxxreact/JSDeltaBundleClient.cpp +++ /dev/null @@ -1,100 +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. - */ - -#include "JSDeltaBundleClient.h" - -#include -#include - -namespace facebook { -namespace react { - -namespace { -std::string startupCode(const folly::dynamic *pre, const folly::dynamic *post) { - std::ostringstream startupCode; - - for (auto section : {pre, post}) { - if (section != nullptr) { - startupCode << section->getString() << '\n'; - } - } - - return startupCode.str(); -} -} // namespace - -void JSDeltaBundleClient::patchModules(const folly::dynamic *modules) { - for (const folly::dynamic &pair : *modules) { - auto id = pair[0].getInt(); - auto module = pair[1]; - modules_[id] = std::move(module.getString()); - } -} - -void JSDeltaBundleClient::patch(const folly::dynamic &delta) { - auto const base = delta.get_ptr("base"); - - if (base != nullptr && base->asBool()) { - clear(); - - auto const pre = delta.get_ptr("pre"); - auto const post = delta.get_ptr("post"); - - startupCode_ = startupCode(pre, post); - - const folly::dynamic *modules = delta.get_ptr("modules"); - if (modules != nullptr) { - patchModules(modules); - } - } else { - const folly::dynamic *deleted = delta.get_ptr("deleted"); - if (deleted != nullptr) { - for (const folly::dynamic &id : *deleted) { - modules_.erase(id.getInt()); - } - } - - // TODO T37123645 This is deprecated but necessary in order to support older - // versions of the Metro server. - const folly::dynamic *modules = delta.get_ptr("modules"); - if (modules != nullptr) { - patchModules(modules); - } - - const folly::dynamic *added = delta.get_ptr("added"); - if (added != nullptr) { - patchModules(added); - } - - const folly::dynamic *modified = delta.get_ptr("modified"); - if (modified != nullptr) { - patchModules(modified); - } - } -} - -JSModulesUnbundle::Module JSDeltaBundleClient::getModule( - uint32_t moduleId) const { - auto search = modules_.find(moduleId); - if (search != modules_.end()) { - return {folly::to(search->first, ".js"), search->second}; - } - - throw JSModulesUnbundle::ModuleNotFound(moduleId); -} - -std::unique_ptr JSDeltaBundleClient::getStartupCode() const { - return std::make_unique(startupCode_); -} - -void JSDeltaBundleClient::clear() { - modules_.clear(); - startupCode_.clear(); -} - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/cxxreact/JSDeltaBundleClient.h b/ReactCommon/cxxreact/JSDeltaBundleClient.h deleted file mode 100644 index 9c9db775d5d0e1..00000000000000 --- a/ReactCommon/cxxreact/JSDeltaBundleClient.h +++ /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. - */ - -#pragma once - -#include -#include -#include -#include - -#include -#include -#include - -namespace facebook { -namespace react { - -class JSDeltaBundleClient { - public: - void patch(const folly::dynamic &delta); - JSModulesUnbundle::Module getModule(uint32_t moduleId) const; - std::unique_ptr getStartupCode() const; - void clear(); - - private: - std::unordered_map modules_; - std::string startupCode_; - - void patchModules(const folly::dynamic *delta); -}; - -class JSDeltaBundleClientRAMBundle : public JSModulesUnbundle { - public: - JSDeltaBundleClientRAMBundle( - std::shared_ptr client) - : client_(client) {} - - Module getModule(uint32_t moduleId) const override { - return client_->getModule(moduleId); - } - - private: - const std::shared_ptr client_; -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/cxxreact/tests/BUCK b/ReactCommon/cxxreact/tests/BUCK index dbd72f23d99048..03bb59f6277501 100644 --- a/ReactCommon/cxxreact/tests/BUCK +++ b/ReactCommon/cxxreact/tests/BUCK @@ -8,7 +8,6 @@ load( TEST_SRCS = [ "RecoverableErrorTest.cpp", - "JSDeltaBundleClientTest.cpp", "jsarg_helpers.cpp", "jsbigstring.cpp", "methodcall.cpp", diff --git a/ReactCommon/cxxreact/tests/JSDeltaBundleClientTest.cpp b/ReactCommon/cxxreact/tests/JSDeltaBundleClientTest.cpp deleted file mode 100644 index 2ec8b9a1423e31..00000000000000 --- a/ReactCommon/cxxreact/tests/JSDeltaBundleClientTest.cpp +++ /dev/null @@ -1,133 +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. - */ - -#include - -#include -#include -#include - -#include -#include -#include - -using namespace facebook::react; - -TEST(JSDeltaBundleClient, PatchStartupCode) { - JSDeltaBundleClient client; - - folly::dynamic delta1 = folly::parseJson(R"({ - "base": true, - "revisionId": "rev0", - "pre": "pre", - "post": "post", - "modules": [ - [0, "0"], - [1, "1"] - ] - })"); - - client.patch(delta1); - - EXPECT_STREQ(client.getStartupCode()->c_str(), "pre\npost\n"); - - folly::dynamic delta2 = folly::parseJson(R"({ - "base": true, - "revisionId": "rev1", - "pre": "pre2", - "post": "post2", - "modules": [] - })"); - - client.patch(delta2); - - EXPECT_STREQ(client.getStartupCode()->c_str(), "pre2\npost2\n"); -} - -TEST(JSDeltaBundleClient, PatchModule) { - JSDeltaBundleClient client; - - folly::dynamic delta1 = folly::parseJson(R"({ - "base": true, - "revisionId": "rev0", - "pre": "pre", - "post": "post", - "modules": [ - [0, "0"], - [1, "1"] - ] - })"); - - client.patch(delta1); - - EXPECT_EQ(client.getModule(0).code, "0"); - EXPECT_EQ(client.getModule(1).code, "1"); - - ASSERT_THROW(client.getModule(2), JSModulesUnbundle::ModuleNotFound); - - folly::dynamic delta2 = folly::parseJson(R"({ - "base": false, - "revisionId": "rev1", - "added": [ - [2, "2"] - ], - "modified": [ - [0, "0.1"] - ], - "deleted": [1] - })"); - - client.patch(delta2); - - EXPECT_EQ(client.getModule(0).code, "0.1"); - EXPECT_EQ(client.getModule(2).code, "2"); - ASSERT_THROW(client.getModule(1), JSModulesUnbundle::ModuleNotFound); - - folly::dynamic delta3 = folly::parseJson(R"({ - "base": true, - "revisionId": "rev2", - "pre": "pre", - "post": "post", - "modules": [ - [3, "3"], - [4, "4"] - ] - })"); - - client.patch(delta3); - - ASSERT_THROW(client.getModule(0), JSModulesUnbundle::ModuleNotFound); - ASSERT_THROW(client.getModule(1), JSModulesUnbundle::ModuleNotFound); - ASSERT_THROW(client.getModule(2), JSModulesUnbundle::ModuleNotFound); - - EXPECT_EQ(client.getModule(3).code, "3"); - EXPECT_EQ(client.getModule(4).code, "4"); -} - -TEST(JSDeltaBundleClient, Clear) { - JSDeltaBundleClient client; - - folly::dynamic delta1 = folly::parseJson(R"({ - "base": true, - "revisionId": "rev0", - "pre": "pre", - "post": "post", - "modules": [ - [0, "0"], - [1, "1"] - ] - })"); - - client.patch(delta1); - - client.clear(); - - ASSERT_THROW(client.getModule(0), JSModulesUnbundle::ModuleNotFound); - ASSERT_THROW(client.getModule(1), JSModulesUnbundle::ModuleNotFound); - - EXPECT_STREQ(client.getStartupCode()->c_str(), ""); -} diff --git a/ReactCommon/fabric/attributedstring/TextAttributes.cpp b/ReactCommon/fabric/attributedstring/TextAttributes.cpp index a33b92c9ad5bcb..c30817a8ea8ad6 100644 --- a/ReactCommon/fabric/attributedstring/TextAttributes.cpp +++ b/ReactCommon/fabric/attributedstring/TextAttributes.cpp @@ -94,6 +94,9 @@ void TextAttributes::apply(TextAttributes textAttributes) { layoutDirection = textAttributes.layoutDirection.hasValue() ? textAttributes.layoutDirection : layoutDirection; + accessibilityRole = textAttributes.accessibilityRole.hasValue() + ? textAttributes.accessibilityRole + : accessibilityRole; } #pragma mark - Operators @@ -116,7 +119,8 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const { textShadowOffset, textShadowColor, isHighlighted, - layoutDirection) == + layoutDirection, + accessibilityRole) == std::tie( rhs.foregroundColor, rhs.backgroundColor, @@ -134,7 +138,8 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const { rhs.textShadowOffset, rhs.textShadowColor, rhs.isHighlighted, - rhs.layoutDirection) && + rhs.layoutDirection, + rhs.accessibilityRole) && floatEquality(opacity, rhs.opacity) && floatEquality(fontSize, rhs.fontSize) && floatEquality(fontSizeMultiplier, rhs.fontSizeMultiplier) && @@ -202,6 +207,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const { // Special debugStringConvertibleItem("isHighlighted", isHighlighted), debugStringConvertibleItem("layoutDirection", layoutDirection), + debugStringConvertibleItem("accessibilityRole", accessibilityRole), }; } #endif diff --git a/ReactCommon/fabric/attributedstring/TextAttributes.h b/ReactCommon/fabric/attributedstring/TextAttributes.h index 1c28424fad61c4..2589d89dfd9131 100644 --- a/ReactCommon/fabric/attributedstring/TextAttributes.h +++ b/ReactCommon/fabric/attributedstring/TextAttributes.h @@ -77,6 +77,7 @@ class TextAttributes : public DebugStringConvertible { // Currently, it is intentionally *not* being set as part of BaseTextProps // construction. folly::Optional layoutDirection{}; + folly::Optional accessibilityRole{}; #pragma mark - Operations @@ -127,7 +128,8 @@ struct hash { textAttributes.textShadowRadius, textAttributes.textShadowColor, textAttributes.isHighlighted, - textAttributes.layoutDirection); + textAttributes.layoutDirection, + textAttributes.accessibilityRole); } }; } // namespace std diff --git a/ReactCommon/fabric/attributedstring/conversions.h b/ReactCommon/fabric/attributedstring/conversions.h index 3046e8f79830d3..9e7d484ade0f84 100644 --- a/ReactCommon/fabric/attributedstring/conversions.h +++ b/ReactCommon/fabric/attributedstring/conversions.h @@ -412,6 +412,178 @@ inline std::string toString( } } +inline std::string toString(const AccessibilityRole &accessibilityRole) { + switch (accessibilityRole) { + case AccessibilityRole::None: + return "none"; + case AccessibilityRole::Button: + return "button"; + case AccessibilityRole::Link: + return "link"; + case AccessibilityRole::Search: + return "search"; + case AccessibilityRole::Image: + return "image"; + case AccessibilityRole::Imagebutton: + return "imagebutton"; + case AccessibilityRole::Keyboardkey: + return "keyboardkey"; + case AccessibilityRole::Text: + return "text"; + case AccessibilityRole::Adjustable: + return "adjustable"; + case AccessibilityRole::Summary: + return "summary"; + case AccessibilityRole::Header: + return "header"; + case AccessibilityRole::Alert: + return "alert"; + case AccessibilityRole::Checkbox: + return "checkbox"; + case AccessibilityRole::Combobox: + return "combobox"; + case AccessibilityRole::Menu: + return "menu"; + case AccessibilityRole::Menubar: + return "menubar"; + case AccessibilityRole::Menuitem: + return "menuitem"; + case AccessibilityRole::Progressbar: + return "progressbar"; + case AccessibilityRole::Radio: + return "radio"; + case AccessibilityRole::Radiogroup: + return "radiogroup"; + case AccessibilityRole::Scrollbar: + return "scrollbar"; + case AccessibilityRole::Spinbutton: + return "spinbutton"; + case AccessibilityRole::Switch: + return "switch"; + case AccessibilityRole::Tab: + return "tab"; + case AccessibilityRole::Tablist: + return "tablist"; + case AccessibilityRole::Timer: + return "timer"; + case AccessibilityRole::Toolbar: + return "toolbar"; + } +} + +inline void fromRawValue(const RawValue &value, AccessibilityRole &result) { + auto string = (std::string)value; + if (string == "none") { + result = AccessibilityRole::None; + return; + } + if (string == "button") { + result = AccessibilityRole::Button; + return; + } + if (string == "link") { + result = AccessibilityRole::Link; + return; + } + if (string == "search") { + result = AccessibilityRole::Search; + return; + } + if (string == "image") { + result = AccessibilityRole::Image; + return; + } + if (string == "imagebutton") { + result = AccessibilityRole::Imagebutton; + return; + } + if (string == "keyboardkey") { + result = AccessibilityRole::Keyboardkey; + return; + } + if (string == "text") { + result = AccessibilityRole::Text; + return; + } + if (string == "adjustable") { + result = AccessibilityRole::Adjustable; + return; + } + if (string == "summary") { + result = AccessibilityRole::Summary; + return; + } + if (string == "header") { + result = AccessibilityRole::Header; + return; + } + if (string == "alert") { + result = AccessibilityRole::Alert; + return; + } + if (string == "checkbox") { + result = AccessibilityRole::Checkbox; + return; + } + if (string == "combobox") { + result = AccessibilityRole::Combobox; + return; + } + if (string == "menu") { + result = AccessibilityRole::Menu; + return; + } + if (string == "menubar") { + result = AccessibilityRole::Menubar; + return; + } + if (string == "menuitem") { + result = AccessibilityRole::Menuitem; + return; + } + if (string == "progressbar") { + result = AccessibilityRole::Progressbar; + return; + } + if (string == "radio") { + result = AccessibilityRole::Radio; + return; + } + if (string == "radiogroup") { + result = AccessibilityRole::Radiogroup; + return; + } + if (string == "scrollbar") { + result = AccessibilityRole::Scrollbar; + return; + } + if (string == "spinbutton") { + result = AccessibilityRole::Spinbutton; + return; + } + if (string == "switch") { + result = AccessibilityRole::Switch; + return; + } + if (string == "tab") { + result = AccessibilityRole::Tab; + return; + } + if (string == "tablist") { + result = AccessibilityRole::Tablist; + return; + } + if (string == "timer") { + result = AccessibilityRole::Timer; + return; + } + if (string == "toolbar") { + result = AccessibilityRole::Toolbar; + return; + } + abort(); +} + inline ParagraphAttributes convertRawProp( RawProps const &rawProps, ParagraphAttributes const &sourceParagraphAttributes, diff --git a/ReactCommon/fabric/attributedstring/primitives.h b/ReactCommon/fabric/attributedstring/primitives.h index c44538c546a550..13946ec180fc81 100644 --- a/ReactCommon/fabric/attributedstring/primitives.h +++ b/ReactCommon/fabric/attributedstring/primitives.h @@ -87,6 +87,36 @@ enum class TextDecorationLinePattern { DashDotDot, }; +enum class AccessibilityRole { + None, + Button, + Link, + Search, + Image, + Imagebutton, + Keyboardkey, + Text, + Adjustable, + Summary, + Header, + Alert, + Checkbox, + Combobox, + Menu, + Menubar, + Menuitem, + Progressbar, + Radio, + Radiogroup, + Scrollbar, + Spinbutton, + Switch, + Tab, + Tablist, + Timer, + Toolbar, +}; + } // namespace react } // namespace facebook @@ -160,4 +190,11 @@ struct hash { return hash()(static_cast(v)); } }; + +template <> +struct hash { + size_t operator()(const facebook::react::AccessibilityRole &v) const { + return hash()(static_cast(v)); + } +}; } // namespace std diff --git a/ReactCommon/fabric/components/art/ARTBaseShadowNode.h b/ReactCommon/fabric/components/art/ARTBaseShadowNode.h deleted file mode 100644 index e674551dc23d4e..00000000000000 --- a/ReactCommon/fabric/components/art/ARTBaseShadowNode.h +++ /dev/null @@ -1,36 +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. - */ - -#pragma once - -#include -#include -#include - -namespace facebook { -namespace react { - -class ARTBaseShadowNode { - public: - int test; - virtual ARTElement::Shared getARTElement() const = 0; - - static void buildElements( - ShadowNode const &parentNode, - ARTElement::ListOfShared &outNodes) { - for (auto const &child : parentNode.getChildren()) { - auto baseShadowNode = - std::dynamic_pointer_cast(child); - if (baseShadowNode) { - outNodes.push_back(baseShadowNode->getARTElement()); - } - } - } -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/BUCK b/ReactCommon/fabric/components/art/BUCK deleted file mode 100644 index 50ba2970986320..00000000000000 --- a/ReactCommon/fabric/components/art/BUCK +++ /dev/null @@ -1,94 +0,0 @@ -load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "get_preprocessor_flags_for_build_mode") -load( - "//tools/build_defs/oss:rn_defs.bzl", - "ANDROID", - "APPLE", - "CXX", - "YOGA_CXX_TARGET", - "fb_xplat_cxx_test", - "get_apple_compiler_flags", - "get_apple_inspector_flags", - "react_native_xplat_target", - "rn_xplat_cxx_library", - "subdir_glob", -) - -APPLE_COMPILER_FLAGS = get_apple_compiler_flags() - -rn_xplat_cxx_library( - name = "art", - srcs = glob( - ["**/*.cpp"], - exclude = glob(["tests/**/*.cpp"]), - ), - headers = glob( - ["**/*.h"], - exclude = glob(["tests/**/*.h"]), - ), - header_namespace = "", - exported_headers = subdir_glob( - [ - ("", "*.h"), - ("group", "*.h"), - ("shape", "*.h"), - ("state", "*.h"), - ("surfaceview", "*.h"), - ("text", "*.h"), - ], - prefix = "react/components/art", - ), - compiler_flags = [ - "-fexceptions", - "-frtti", - "-std=c++14", - "-Wall", - ], - cxx_tests = [":tests"], - fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), - force_static = True, - labels = ["supermodule:xplat/default/public.react_native.playground"], - platforms = (ANDROID, APPLE, CXX), - preprocessor_flags = [ - "-DLOG_TAG=\"ReactNative\"", - "-DWITH_FBSYSTRACE=1", - ], - visibility = ["PUBLIC"], - deps = [ - "//third-party/glog:glog", - "//xplat/fbsystrace:fbsystrace", - "//xplat/folly:container_evicting_cache_map", - "//xplat/folly:headers_only", - "//xplat/folly:memory", - "//xplat/folly:molly", - YOGA_CXX_TARGET, - react_native_xplat_target("utils:utils"), - react_native_xplat_target("fabric/core:core"), - react_native_xplat_target("fabric/debug:debug"), - react_native_xplat_target("fabric/graphics:graphics"), - react_native_xplat_target("fabric/uimanager:uimanager"), - ], -) - -fb_xplat_cxx_test( - name = "tests", - srcs = glob(["tests/**/*.cpp"]), - headers = glob(["tests/**/*.h"]), - compiler_flags = [ - "-fexceptions", - "-frtti", - "-std=c++14", - "-Wall", - ], - contacts = ["oncall+react_native@xmail.facebook.com"], - platforms = ( - # ANDROID, - # APPLE, - CXX - ), - deps = [ - ":art", - "//xplat/folly:molly", - "//xplat/third-party/gmock:gtest", - ], -) diff --git a/ReactCommon/fabric/components/art/conversions.h b/ReactCommon/fabric/components/art/conversions.h deleted file mode 100644 index b234c6d0dbdfe8..00000000000000 --- a/ReactCommon/fabric/components/art/conversions.h +++ /dev/null @@ -1,137 +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. - */ - -#include -#include -#include -#include -#include -#include -#include - -namespace facebook { -namespace react { - -#ifdef ANDROID - -inline folly::dynamic toDynamic(ARTGroup const &group); -inline folly::dynamic toDynamic(ARTShape const &shape); -inline folly::dynamic toDynamic(ARTText const &text); -inline folly::dynamic toDynamic(ARTElement const &element); - -inline folly::dynamic toDynamic(std::vector const &elements) { - folly::dynamic result = folly::dynamic::array(); - for (auto const &element : elements) { - result.push_back(element); - } - return result; -} - -inline void addOptionalKey( - folly::dynamic &map, - std::string const &key, - std::vector const &values) { - if (values.size() > 0) { - map[key] = toDynamic(values); - } -} - -inline folly::dynamic toDynamic(ARTElement::ListOfShared const &elements) { - folly::dynamic children = folly::dynamic::array(); - for (auto const &element : elements) { - children.push_back(element->getDynamic()); - } - return children; -} - -inline folly::dynamic toDynamic(ARTGroup const &group) { - folly::dynamic result = folly::dynamic::object(); - result["opacity"] = group.opacity; - result["type"] = 1; - if (group.elements.size() > 0) { - result["elements"] = toDynamic(group.elements); - } - addOptionalKey(result, "clipping", group.clipping); - result["transform"] = toDynamic(group.transform); - return result; -} - -inline folly::dynamic toDynamic(ARTShape const &shape) { - folly::dynamic result = folly::dynamic::object(); - result["type"] = 2; - result["opacity"] = shape.opacity; - result["transform"] = toDynamic(shape.transform); - addOptionalKey(result, "d", shape.d); - addOptionalKey(result, "stroke", shape.stroke); - addOptionalKey(result, "strokeDash", shape.strokeDash); - addOptionalKey(result, "fill", shape.fill); - result["strokeWidth"] = shape.strokeWidth; - result["strokeCap"] = shape.strokeCap; - result["strokeJoin"] = shape.strokeJoin; - return result; -} - -inline folly::dynamic toDynamic(ARTTextAlignment const &aligment) { - switch (aligment) { - case ARTTextAlignment::Right: - return 1; - break; - case ARTTextAlignment::Center: - return 2; - break; - case ARTTextAlignment::Default: - default: - return 0; - break; - } -} - -inline folly::dynamic toDynamic(ARTTextFrame const &frame) { - folly::dynamic result = folly::dynamic::object(); - folly::dynamic font = folly::dynamic::object(); - font["fontSize"] = frame.font.fontSize; - font["fontStyle"] = frame.font.fontStyle; - font["fontFamily"] = frame.font.fontFamily; - font["fontWeight"] = frame.font.fontWeight; - result["font"] = font; - auto lines = frame.lines; - if (lines.size() > 0) { - folly::dynamic serializedLines = folly::dynamic::array(); - for (int i = 0; i < lines.size(); i++) { - serializedLines.push_back(lines[i]); - } - result["lines"] = serializedLines; - } - return result; -} - -inline folly::dynamic toDynamic(ARTText const &text) { - folly::dynamic result = folly::dynamic::object(); - result["type"] = 3; - result["opacity"] = text.opacity; - result["transform"] = toDynamic(text.transform); - addOptionalKey(result, "d", text.d); - addOptionalKey(result, "stroke", text.stroke); - addOptionalKey(result, "strokeDash", text.strokeDash); - addOptionalKey(result, "fill", text.fill); - result["strokeWidth"] = text.strokeWidth; - result["strokeCap"] = text.strokeCap; - result["strokeJoin"] = text.strokeJoin; - result["alignment"] = toDynamic(text.alignment); - result["frame"] = toDynamic(text.frame); - return result; -} - -inline folly::dynamic toDynamic(ARTSurfaceViewState const &surfaceViewState) { - folly::dynamic result = folly::dynamic::object(); - result["elements"] = toDynamic(surfaceViewState.elements); - return result; -} -#endif - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/group/ARTGroupComponentDescriptor.h b/ReactCommon/fabric/components/art/group/ARTGroupComponentDescriptor.h deleted file mode 100644 index d2c3bd9424ce03..00000000000000 --- a/ReactCommon/fabric/components/art/group/ARTGroupComponentDescriptor.h +++ /dev/null @@ -1,20 +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. - */ - -#pragma once - -#include -#include - -namespace facebook { -namespace react { - -using ARTGroupComponentDescriptor = - ConcreteComponentDescriptor; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/group/ARTGroupProps.cpp b/ReactCommon/fabric/components/art/group/ARTGroupProps.cpp deleted file mode 100644 index 2d735569efe33a..00000000000000 --- a/ReactCommon/fabric/components/art/group/ARTGroupProps.cpp +++ /dev/null @@ -1,35 +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. - */ - -#include -#include -#include - -namespace facebook { -namespace react { - -ARTGroupProps::ARTGroupProps( - const ARTGroupProps &sourceProps, - const RawProps &rawProps) - : Props(sourceProps, rawProps), - opacity(convertRawProp(rawProps, "opacity", sourceProps.opacity, {1.0})), - transform( - convertRawProp(rawProps, "transform", sourceProps.transform, {})), - clipping( - convertRawProp(rawProps, "clipping", sourceProps.clipping, {})){}; - -#pragma mark - DebugStringConvertible - -#if RN_DEBUG_STRING_CONVERTIBLE -SharedDebugStringConvertibleList ARTGroupProps::getDebugProps() const { - return SharedDebugStringConvertibleList{ - debugStringConvertibleItem("opacity", opacity)}; -} -#endif - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/group/ARTGroupProps.h b/ReactCommon/fabric/components/art/group/ARTGroupProps.h deleted file mode 100644 index 234e530d3a01ed..00000000000000 --- a/ReactCommon/fabric/components/art/group/ARTGroupProps.h +++ /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. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace facebook { -namespace react { - -class ARTGroupProps; - -class ARTGroupProps : public Props { - public: - ARTGroupProps() = default; - ARTGroupProps(const ARTGroupProps &sourceProps, const RawProps &rawProps); - -#pragma mark - Props - - Float opacity{1.0}; - std::vector transform{}; - std::vector clipping{}; - -#pragma mark - DebugStringConvertible - -#if RN_DEBUG_STRING_CONVERTIBLE - SharedDebugStringConvertibleList getDebugProps() const override; -#endif -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/group/ARTGroupShadowNode.cpp b/ReactCommon/fabric/components/art/group/ARTGroupShadowNode.cpp deleted file mode 100644 index 7d5301a53c43b2..00000000000000 --- a/ReactCommon/fabric/components/art/group/ARTGroupShadowNode.cpp +++ /dev/null @@ -1,32 +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. - */ - -#include -#include -#include -#include - -namespace facebook { -namespace react { - -extern const char ARTGroupComponentName[] = "ARTGroup"; - -ARTElement::Shared ARTGroupShadowNode::getARTElement() const { - auto elements = ARTElement::ListOfShared{}; - for (auto const &child : getChildren()) { - auto node = std::dynamic_pointer_cast(child); - if (node) { - elements.push_back(node->getARTElement()); - } - } - - auto props = getConcreteProps(); - return std::make_shared( - props.opacity, props.transform, elements, props.clipping); -} -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/group/ARTGroupShadowNode.h b/ReactCommon/fabric/components/art/group/ARTGroupShadowNode.h deleted file mode 100644 index dd85dffe66eaae..00000000000000 --- a/ReactCommon/fabric/components/art/group/ARTGroupShadowNode.h +++ /dev/null @@ -1,36 +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. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace facebook { -namespace react { - -extern const char ARTGroupComponentName[]; - -/* - * `ShadowNode` for component. - */ -class ARTGroupShadowNode : public ConcreteShadowNode< - ARTGroupComponentName, - ShadowNode, - ARTGroupProps>, - public ARTBaseShadowNode { - public: - using ConcreteShadowNode::ConcreteShadowNode; - - virtual ARTElement::Shared getARTElement() const override; -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/shape/ARTShapeComponentDescriptor.h b/ReactCommon/fabric/components/art/shape/ARTShapeComponentDescriptor.h deleted file mode 100644 index 5d65696f7dbaf2..00000000000000 --- a/ReactCommon/fabric/components/art/shape/ARTShapeComponentDescriptor.h +++ /dev/null @@ -1,20 +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. - */ - -#pragma once - -#include -#include - -namespace facebook { -namespace react { - -using ARTShapeComponentDescriptor = - ConcreteComponentDescriptor; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/shape/ARTShapeProps.cpp b/ReactCommon/fabric/components/art/shape/ARTShapeProps.cpp deleted file mode 100644 index 9a170c9203e6ca..00000000000000 --- a/ReactCommon/fabric/components/art/shape/ARTShapeProps.cpp +++ /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. - */ - -#include -#include -#include -#include - -namespace facebook { -namespace react { - -ARTShapeProps::ARTShapeProps( - const ARTShapeProps &sourceProps, - const RawProps &rawProps) - : Props(sourceProps, rawProps), - - opacity(convertRawProp(rawProps, "opacity", sourceProps.opacity, {1.0})), - transform( - convertRawProp(rawProps, "transform", sourceProps.transform, {})), - d(convertRawProp(rawProps, "d", sourceProps.d, {})), - stroke(convertRawProp(rawProps, "stroke", sourceProps.stroke, {})), - strokeDash( - convertRawProp(rawProps, "strokeDash", sourceProps.strokeDash, {})), - fill(convertRawProp(rawProps, "fill", sourceProps.fill, {})), - strokeWidth(convertRawProp( - rawProps, - "strokeWidth", - sourceProps.strokeWidth, - {1.0})), - strokeCap( - convertRawProp(rawProps, "strokeCap", sourceProps.strokeCap, {1})), - strokeJoin( - convertRawProp(rawProps, "strokeJoin", sourceProps.strokeJoin, {1})){ - - }; - -#pragma mark - DebugStringConvertible - -#if RN_DEBUG_STRING_CONVERTIBLE -SharedDebugStringConvertibleList ARTShapeProps::getDebugProps() const { - return SharedDebugStringConvertibleList{ - debugStringConvertibleItem("opacity", opacity)}; -} -#endif - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/shape/ARTShapeProps.h b/ReactCommon/fabric/components/art/shape/ARTShapeProps.h deleted file mode 100644 index 7323f7ccd07b25..00000000000000 --- a/ReactCommon/fabric/components/art/shape/ARTShapeProps.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. - */ - -#pragma once - -#include -#include - -#include -#include - -namespace facebook { -namespace react { - -class ARTShapeProps; - -class ARTShapeProps : public Props { - public: - ARTShapeProps() = default; - ARTShapeProps(const ARTShapeProps &sourceProps, const RawProps &rawProps); - -#pragma mark - Props - - Float opacity{1.0}; - std::vector transform{}; - std::vector d{}; - std::vector stroke{}; - std::vector strokeDash{}; - std::vector fill{}; - Float strokeWidth{1.0}; - int strokeCap{1}; - int strokeJoin{1}; - -#pragma mark - DebugStringConvertible - -#if RN_DEBUG_STRING_CONVERTIBLE - SharedDebugStringConvertibleList getDebugProps() const override; -#endif -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/shape/ARTShapeShadowNode.cpp b/ReactCommon/fabric/components/art/shape/ARTShapeShadowNode.cpp deleted file mode 100644 index cb11199fd06576..00000000000000 --- a/ReactCommon/fabric/components/art/shape/ARTShapeShadowNode.cpp +++ /dev/null @@ -1,31 +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. - */ - -#include "ARTShapeShadowNode.h" -#include -#include -namespace facebook { -namespace react { - -extern const char ARTShapeComponentName[] = "ARTShape"; - -ARTElement::Shared ARTShapeShadowNode::getARTElement() const { - auto props = getConcreteProps(); - return std::make_shared( - props.opacity, - props.transform, - props.d, - props.stroke, - props.strokeDash, - props.fill, - props.strokeWidth, - props.strokeCap, - props.strokeJoin); -} - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/shape/ARTShapeShadowNode.h b/ReactCommon/fabric/components/art/shape/ARTShapeShadowNode.h deleted file mode 100644 index 0373aef45cf34c..00000000000000 --- a/ReactCommon/fabric/components/art/shape/ARTShapeShadowNode.h +++ /dev/null @@ -1,35 +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. - */ - -#pragma once - -#include -#include -#include -#include - -namespace facebook { -namespace react { - -extern const char ARTShapeComponentName[]; - -/* - * `ShadowNode` for component. - */ -class ARTShapeShadowNode : public ConcreteShadowNode< - ARTShapeComponentName, - ShadowNode, - ARTShapeProps>, - public ARTBaseShadowNode { - public: - using ConcreteShadowNode::ConcreteShadowNode; - - virtual ARTElement::Shared getARTElement() const override; -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTElement.h b/ReactCommon/fabric/components/art/state/ARTElement.h deleted file mode 100644 index 1b3b82b1685a9e..00000000000000 --- a/ReactCommon/fabric/components/art/state/ARTElement.h +++ /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. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace facebook { -namespace react { - -/* - * Simple, cross-platfrom, React-specific implementation of base ART Element - */ -class ARTElement { - public: - using Shared = std::shared_ptr; - using ListOfShared = better::small_vector; - - ARTElement() = default; - ARTElement( - ARTElementType elementType, - Float opacity, - std::vector transform) - : elementType(elementType), opacity(opacity), transform(transform){}; - virtual ~ARTElement(){}; - - ARTElementType elementType; - Float opacity; - std::vector transform; - - virtual bool operator==(const ARTElement &rhs) const = 0; - virtual bool operator!=(const ARTElement &rhs) const = 0; - friend bool operator==(ListOfShared e1, ListOfShared e2) { - bool equals = e1.size() == e2.size(); - for (int i = 0; i < equals && e1.size(); i++) { - // Pointer equality - this will work if both are pointing at the same - // object, or both are nullptr - if (e1[i] == e2[i]) { - continue; - } - - // Get pointers from both - // If one is null, we know they can't both be null because of the above - // check - auto ptr1 = e1[i].get(); - auto ptr2 = e2[i].get(); - if (ptr1 == nullptr || ptr2 == nullptr) { - equals = false; - break; - } - - // Dereference and compare objects - if (*ptr1 != *ptr2) { - equals = false; - break; - } - } - - return equals; - }; - -#ifdef ANDROID - virtual folly::dynamic getDynamic() const = 0; -#endif -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTGroup.cpp b/ReactCommon/fabric/components/art/state/ARTGroup.cpp deleted file mode 100644 index f3a8c6ee59b5c6..00000000000000 --- a/ReactCommon/fabric/components/art/state/ARTGroup.cpp +++ /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. - */ - -#include -#include -#include - -namespace facebook { -namespace react { - -bool ARTGroup::operator==(const ARTElement &rhs) const { - if (rhs.elementType != ARTElementType::Group) { - return false; - } - auto group = (const ARTGroup &)(rhs); - return std::tie(elementType, opacity, transform, clipping) == - std::tie( - group.elementType, - group.opacity, - group.transform, - group.clipping) && - elements == group.elements; -} - -bool ARTGroup::operator!=(const ARTElement &rhs) const { - return !(*this == rhs); -} - -#ifdef ANDROID -folly::dynamic ARTGroup::getDynamic() const { - return toDynamic(*this); -} -#endif - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTGroup.h b/ReactCommon/fabric/components/art/state/ARTGroup.h deleted file mode 100644 index fa86e7923655c0..00000000000000 --- a/ReactCommon/fabric/components/art/state/ARTGroup.h +++ /dev/null @@ -1,49 +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. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace facebook { -namespace react { - -/* - * Simple, cross-platfrom, React-specific implementation of ART Group element - */ -class ARTGroup : public ARTElement { - public: - using Shared = std::shared_ptr; - ARTGroup( - Float opacity, - std::vector transform, - ARTGroup::ListOfShared elements, - std::vector clipping) - : ARTElement(ARTElementType::Group, opacity, transform), - elements(elements), - clipping(clipping){}; - ARTGroup() = default; - virtual ~ARTGroup(){}; - - ARTElement::ListOfShared elements{}; - - std::vector clipping{}; - - bool operator==(const ARTElement &rhs) const override; - bool operator!=(const ARTElement &rhs) const override; - -#ifdef ANDROID - folly::dynamic getDynamic() const override; -#endif -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTShape.cpp b/ReactCommon/fabric/components/art/state/ARTShape.cpp deleted file mode 100644 index 31fac9ffeb3e79..00000000000000 --- a/ReactCommon/fabric/components/art/state/ARTShape.cpp +++ /dev/null @@ -1,55 +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. - */ - -#include -#include -#include - -namespace facebook { -namespace react { - -bool ARTShape::operator==(const ARTElement &rhs) const { - if (rhs.elementType != ARTElementType::Shape) { - return false; - } - auto shape = (const ARTShape &)(rhs); - return std::tie( - elementType, - opacity, - transform, - d, - stroke, - strokeDash, - fill, - strokeWidth, - strokeCap, - strokeJoin) == - std::tie( - shape.elementType, - shape.opacity, - shape.transform, - shape.d, - shape.stroke, - shape.strokeDash, - shape.fill, - shape.strokeWidth, - shape.strokeCap, - shape.strokeJoin); -} - -bool ARTShape::operator!=(const ARTElement &rhs) const { - return !(*this == rhs); -} - -#ifdef ANDROID -folly::dynamic ARTShape::getDynamic() const { - return toDynamic(*this); -} -#endif - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTShape.h b/ReactCommon/fabric/components/art/state/ARTShape.h deleted file mode 100644 index e50cf966a061ed..00000000000000 --- a/ReactCommon/fabric/components/art/state/ARTShape.h +++ /dev/null @@ -1,63 +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. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace facebook { -namespace react { - -/* - * Simple, cross-platfrom, React-specific implementation of ART Shape Element - */ -class ARTShape : public ARTElement { - public: - using Shared = std::shared_ptr; - ARTShape( - Float opacity, - std::vector transform, - std::vector d, - std::vector stroke, - std::vector strokeDash, - std::vector fill, - Float strokeWidth, - int strokeCap, - int strokeJoin) - : ARTElement(ARTElementType::Shape, opacity, transform), - d(d), - stroke(stroke), - strokeDash(strokeDash), - fill(fill), - strokeWidth(strokeWidth), - strokeCap(strokeCap), - strokeJoin(strokeJoin){}; - ARTShape() = default; - virtual ~ARTShape(){}; - - std::vector d{}; - std::vector stroke{}; - std::vector strokeDash{}; - std::vector fill{}; - Float strokeWidth{1.0}; - int strokeCap{1}; - int strokeJoin{1}; - - bool operator==(const ARTElement &rhs) const override; - bool operator!=(const ARTElement &rhs) const override; - -#ifdef ANDROID - folly::dynamic getDynamic() const override; -#endif -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTText.cpp b/ReactCommon/fabric/components/art/state/ARTText.cpp deleted file mode 100644 index 1a061554134e77..00000000000000 --- a/ReactCommon/fabric/components/art/state/ARTText.cpp +++ /dev/null @@ -1,59 +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. - */ - -#include -#include -#include - -namespace facebook { -namespace react { - -bool ARTText::operator==(const ARTElement &rhs) const { - if (rhs.elementType != ARTElementType::Text) { - return false; - } - auto text = (const ARTText &)(rhs); - return std::tie( - elementType, - opacity, - transform, - d, - stroke, - strokeDash, - fill, - strokeWidth, - strokeCap, - strokeJoin, - alignment, - frame) == - std::tie( - text.elementType, - text.opacity, - text.transform, - text.d, - text.stroke, - text.strokeDash, - text.fill, - text.strokeWidth, - text.strokeCap, - text.strokeJoin, - text.alignment, - text.frame); -} - -bool ARTText::operator!=(const ARTElement &rhs) const { - return !(*this == rhs); -} - -#ifdef ANDROID -folly::dynamic ARTText::getDynamic() const { - return toDynamic(*this); -} -#endif - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/ARTText.h b/ReactCommon/fabric/components/art/state/ARTText.h deleted file mode 100644 index b01da694a377b4..00000000000000 --- a/ReactCommon/fabric/components/art/state/ARTText.h +++ /dev/null @@ -1,68 +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. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include - -namespace facebook { -namespace react { - -/* - * Simple, cross-platfrom, React-specific implementation of ART Text Element - */ -class ARTText : public ARTShape { - public: - using ARTShared = std::shared_ptr; - ARTText( - Float opacity, - std::vector transform, - std::vector d, - std::vector stroke, - std::vector strokeDash, - std::vector fill, - Float strokeWidth, - int strokeCap, - int strokeJoin, - ARTTextAlignment alignment, - ARTTextFrame frame) - : ARTShape( - opacity, - transform, - d, - stroke, - strokeDash, - fill, - strokeWidth, - strokeCap, - strokeJoin), - alignment(alignment), - frame(frame) { - elementType = ARTElementType::Text; - }; - ARTText() = default; - virtual ~ARTText(){}; - - bool operator==(const ARTElement &rhs) const override; - bool operator!=(const ARTElement &rhs) const override; - - ARTTextAlignment alignment{ARTTextAlignment::Default}; - ARTTextFrame frame{}; - -#ifdef ANDROID - folly::dynamic getDynamic() const override; -#endif -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/state/primitives.h b/ReactCommon/fabric/components/art/state/primitives.h deleted file mode 100644 index 677fcf9ce1120e..00000000000000 --- a/ReactCommon/fabric/components/art/state/primitives.h +++ /dev/null @@ -1,56 +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. - */ - -#pragma once - -#include -#include -#include -#include - -namespace facebook { -namespace react { - -enum class ARTElementType { Shape, Text, Group }; - -enum class ARTTextAlignment { Default, Right, Center }; - -struct ARTTextFrameFont { - Float fontSize; - std::string fontStyle; - std::string fontFamily; - std::string fontWeight; - - bool operator==(const ARTTextFrameFont &rhs) const { - return std::tie( - this->fontSize, - this->fontStyle, - this->fontFamily, - this->fontWeight) == - std::tie(rhs.fontSize, rhs.fontStyle, rhs.fontFamily, rhs.fontWeight); - } - - bool operator!=(const ARTTextFrameFont &rhs) const { - return !(*this == rhs); - } -}; - -struct ARTTextFrame { - std::vector lines; - ARTTextFrameFont font; - - bool operator==(const ARTTextFrame &rhs) const { - return std::tie(this->lines, this->font) == std::tie(rhs.lines, rhs.font); - } - - bool operator!=(const ARTTextFrame &rhs) const { - return !(*this == rhs); - } -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewComponentDescriptor.h b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewComponentDescriptor.h deleted file mode 100644 index c48ee29d468fd2..00000000000000 --- a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewComponentDescriptor.h +++ /dev/null @@ -1,20 +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. - */ - -#pragma once - -#include -#include - -namespace facebook { -namespace react { - -using ARTSurfaceViewComponentDescriptor = - ConcreteComponentDescriptor; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewProps.cpp b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewProps.cpp deleted file mode 100644 index 6969d93f1d29cd..00000000000000 --- a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewProps.cpp +++ /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. - */ - -#include -#include - -namespace facebook { -namespace react { - -ARTSurfaceViewProps::ARTSurfaceViewProps( - ARTSurfaceViewProps const &sourceProps, - RawProps const &rawProps) - : ViewProps(sourceProps, rawProps), - - backgroundColor(convertRawProp( - rawProps, - "backgroundColor", - sourceProps.backgroundColor, - {})) {} - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewProps.h b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewProps.h deleted file mode 100644 index c243686d506bb8..00000000000000 --- a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewProps.h +++ /dev/null @@ -1,31 +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. - */ - -#pragma once - -#include -#include -#include -#include - -namespace facebook { -namespace react { - -class ARTSurfaceViewProps final : public ViewProps { - public: - ARTSurfaceViewProps() = default; - ARTSurfaceViewProps( - ARTSurfaceViewProps const &sourceProps, - RawProps const &rawProps); - -#pragma mark - Props - - SharedColor backgroundColor{}; -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewShadowNode.cpp b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewShadowNode.cpp deleted file mode 100644 index 5be367ba3b68b0..00000000000000 --- a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewShadowNode.cpp +++ /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. - */ - -#include -#include -#include -#include - -namespace facebook { -namespace react { - -using Content = ARTSurfaceViewShadowNode::Content; - -extern const char ARTSurfaceViewComponentName[] = "ARTSurfaceView"; - -void ARTSurfaceViewShadowNode::layout(LayoutContext layoutContext) { - ensureUnsealed(); - auto content = getContent(); - updateStateIfNeeded(content); -} - -Content const &ARTSurfaceViewShadowNode::getContent() const { - if (content_.has_value()) { - return content_.value(); - } - ensureUnsealed(); - auto elements = ARTElement::ListOfShared{}; - ARTBaseShadowNode::buildElements(*this, elements); - content_ = Content{elements}; - return content_.value(); -} - -void ARTSurfaceViewShadowNode::updateStateIfNeeded(Content const &content) { - ensureUnsealed(); - auto &state = getStateData(); - if (content.elements == state.elements) { - return; - } - setStateData(ARTSurfaceViewState{content.elements}); -} - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewShadowNode.h b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewShadowNode.h deleted file mode 100644 index 0bd2cdaa43fb1e..00000000000000 --- a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewShadowNode.h +++ /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. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace facebook { -namespace react { - -extern const char ARTSurfaceViewComponentName[]; - -using ARTSurfaceViewEventEmitter = ViewEventEmitter; - -/* - * `ShadowNode` for component, represents -like - * component containing that will be used to display ARTElements. - */ -class ARTSurfaceViewShadowNode : public ConcreteViewShadowNode< - ARTSurfaceViewComponentName, - ARTSurfaceViewProps, - ARTSurfaceViewEventEmitter, - ARTSurfaceViewState> { - public: - using ConcreteViewShadowNode::ConcreteViewShadowNode; - - static ShadowNodeTraits BaseTraits() { - auto traits = ConcreteViewShadowNode::BaseTraits(); - traits.set(ShadowNodeTraits::Trait::LeafYogaNode); - return traits; - } - - class Content final { - public: - ARTElement::ListOfShared elements{}; - }; - - void layout(LayoutContext layoutContext) override; - - private: - Content const &getContent() const; - - void updateStateIfNeeded(Content const &content); - - /* - * Cached content of the subtree started from the node. - */ - mutable better::optional content_{}; -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewState.cpp b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewState.cpp deleted file mode 100644 index 44e47d45c04646..00000000000000 --- a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewState.cpp +++ /dev/null @@ -1,22 +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. - */ - -#include -#include -#include - -namespace facebook { -namespace react { - -#ifdef ANDROID -folly::dynamic ARTSurfaceViewState::getDynamic() const { - return toDynamic(*this); -} -#endif - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewState.h b/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewState.h deleted file mode 100644 index 86eb3d0f5e8c58..00000000000000 --- a/ReactCommon/fabric/components/art/surfaceview/ARTSurfaceViewState.h +++ /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. - */ - -#pragma once -#include - -#ifdef ANDROID -#include -#endif - -namespace facebook { -namespace react { - -/* - * State for component. - * Represents what to render and how to render. - */ -class ARTSurfaceViewState final { - public: - ARTElement::ListOfShared elements{}; - -#ifdef ANDROID - ARTSurfaceViewState(ARTElement::ListOfShared const &elements) - : elements(elements) {} - ARTSurfaceViewState() = default; - ARTSurfaceViewState( - ARTSurfaceViewState const &previousState, - folly::dynamic const &data) { - assert(false && "Not supported"); - }; - folly::dynamic getDynamic() const; -#endif -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/text/ARTTextComponentDescriptor.h b/ReactCommon/fabric/components/art/text/ARTTextComponentDescriptor.h deleted file mode 100644 index ae839d71e40925..00000000000000 --- a/ReactCommon/fabric/components/art/text/ARTTextComponentDescriptor.h +++ /dev/null @@ -1,20 +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. - */ - -#pragma once - -#include -#include - -namespace facebook { -namespace react { - -using ARTTextComponentDescriptor = - ConcreteComponentDescriptor; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/text/ARTTextProps.cpp b/ReactCommon/fabric/components/art/text/ARTTextProps.cpp deleted file mode 100644 index c83c6fad859cf5..00000000000000 --- a/ReactCommon/fabric/components/art/text/ARTTextProps.cpp +++ /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. - */ - -#include -#include -#include - -namespace facebook { -namespace react { - -ARTTextProps::ARTTextProps( - const ARTTextProps &sourceProps, - const RawProps &rawProps) - : Props(sourceProps, rawProps), - - opacity(convertRawProp(rawProps, "opacity", sourceProps.opacity, {1.0})), - transform( - convertRawProp(rawProps, "transform", sourceProps.transform, {})), - d(convertRawProp(rawProps, "d", sourceProps.d, {})), - stroke(convertRawProp(rawProps, "stroke", sourceProps.stroke, {})), - strokeDash( - convertRawProp(rawProps, "strokeDash", sourceProps.strokeDash, {})), - fill(convertRawProp(rawProps, "fill", sourceProps.fill, {})), - strokeWidth(convertRawProp( - rawProps, - "strokeWidth", - sourceProps.strokeWidth, - {1.0})), - strokeCap( - convertRawProp(rawProps, "strokeCap", sourceProps.strokeCap, {1})), - strokeJoin( - convertRawProp(rawProps, "strokeJoin", sourceProps.strokeJoin, {1})), - alignment( - convertRawProp(rawProps, "alignment", sourceProps.alignment, {})), - frame(convertRawProp(rawProps, "frame", sourceProps.frame, {})){}; - -#pragma mark - DebugStringConvertible - -#if RN_DEBUG_STRING_CONVERTIBLE -SharedDebugStringConvertibleList ARTTextProps::getDebugProps() const { - return SharedDebugStringConvertibleList{ - debugStringConvertibleItem("opacity", opacity)}; -} -#endif - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/text/ARTTextProps.h b/ReactCommon/fabric/components/art/text/ARTTextProps.h deleted file mode 100644 index 087e56669cf1ef..00000000000000 --- a/ReactCommon/fabric/components/art/text/ARTTextProps.h +++ /dev/null @@ -1,110 +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. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace facebook { -namespace react { - -static inline void fromRawValue( - const RawValue &value, - ARTTextFrameFont &result) { - auto map = (better::map)value; - auto fontSize = map.find("fontSize"); - if (fontSize != map.end()) { - fromRawValue(fontSize->second, result.fontSize); - } - auto fontStyle = map.find("fontStyle"); - if (fontStyle != map.end()) { - fromRawValue(fontStyle->second, result.fontStyle); - } - auto fontFamily = map.find("fontFamily"); - if (fontFamily != map.end()) { - fromRawValue(fontFamily->second, result.fontFamily); - } - auto fontWeight = map.find("fontWeight"); - if (fontWeight != map.end()) { - fromRawValue(fontWeight->second, result.fontWeight); - } -} - -static inline void fromRawValue( - const RawValue &value, - ARTTextAlignment &result) { - auto alignment = (int)value; - switch (alignment) { - case 1: - result = ARTTextAlignment::Right; - break; - case 2: - result = ARTTextAlignment::Center; - break; - default: - result = ARTTextAlignment::Default; - break; - } -} - -static inline std::string toString(const ARTTextFrameFont &value) { - return "[Object ARTTextFrameFont]"; -} - -static inline void fromRawValue(const RawValue &value, ARTTextFrame &result) { - auto map = (better::map)value; - - auto lines = map.find("lines"); - if (lines != map.end()) { - fromRawValue(lines->second, result.lines); - } - auto font = map.find("font"); - if (font != map.end()) { - fromRawValue(font->second, result.font); - } -} - -static inline std::string toString(const ARTTextFrame &value) { - return "[Object ARTTextFrame]"; -} - -class ARTTextProps; - -using SharedARTTextProps = std::shared_ptr; - -class ARTTextProps : public Props { - public: - ARTTextProps() = default; - ARTTextProps(const ARTTextProps &sourceProps, const RawProps &rawProps); - -#pragma mark - Props - - Float opacity{1.0}; - std::vector transform{}; - std::vector d{}; - std::vector stroke{}; - std::vector strokeDash{}; - std::vector fill{}; - Float strokeWidth{1.0}; - int strokeCap{1}; - int strokeJoin{1}; - ARTTextAlignment alignment{ARTTextAlignment::Default}; - ARTTextFrame frame{}; - -#pragma mark - DebugStringConvertible - -#if RN_DEBUG_STRING_CONVERTIBLE - SharedDebugStringConvertibleList getDebugProps() const override; -#endif -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/text/ARTTextShadowNode.cpp b/ReactCommon/fabric/components/art/text/ARTTextShadowNode.cpp deleted file mode 100644 index aeb596e13348d3..00000000000000 --- a/ReactCommon/fabric/components/art/text/ARTTextShadowNode.cpp +++ /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. - */ - -#include "ARTTextShadowNode.h" -#include -#include - -namespace facebook { -namespace react { - -extern const char ARTTextComponentName[] = "ARTText"; - -ARTElement::Shared ARTTextShadowNode::getARTElement() const { - auto props = getConcreteProps(); - return std::make_shared( - props.opacity, - props.transform, - props.d, - props.stroke, - props.strokeDash, - props.fill, - props.strokeWidth, - props.strokeCap, - props.strokeJoin, - props.alignment, - props.frame); -} - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/art/text/ARTTextShadowNode.h b/ReactCommon/fabric/components/art/text/ARTTextShadowNode.h deleted file mode 100644 index 93d3d4a6567ab1..00000000000000 --- a/ReactCommon/fabric/components/art/text/ARTTextShadowNode.h +++ /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. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace facebook { -namespace react { - -extern const char ARTTextComponentName[]; - -/* - * `ShadowNode` for component. - */ -class ARTTextShadowNode - : public ConcreteShadowNode, - public ARTBaseShadowNode { - public: - using ConcreteShadowNode::ConcreteShadowNode; - - virtual ARTElement::Shared getARTElement() const override; -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/fabric/components/text/basetext/BaseTextProps.cpp b/ReactCommon/fabric/components/text/basetext/BaseTextProps.cpp index 12b53d2da053f8..51fd3821e23acf 100644 --- a/ReactCommon/fabric/components/text/basetext/BaseTextProps.cpp +++ b/ReactCommon/fabric/components/text/basetext/BaseTextProps.cpp @@ -143,6 +143,12 @@ static TextAttributes convertRawProp( sourceTextAttributes.isHighlighted, defaultTextAttributes.isHighlighted); + textAttributes.accessibilityRole = convertRawProp( + rawProps, + "accessibilityRole", + sourceTextAttributes.accessibilityRole, + defaultTextAttributes.accessibilityRole); + return textAttributes; } diff --git a/ReactCommon/fabric/components/view/ConcreteViewShadowNode.h b/ReactCommon/fabric/components/view/ConcreteViewShadowNode.h index 732b36e05a11cc..9fd77d342ea2b6 100644 --- a/ReactCommon/fabric/components/view/ConcreteViewShadowNode.h +++ b/ReactCommon/fabric/components/view/ConcreteViewShadowNode.h @@ -103,7 +103,15 @@ class ConcreteViewShadowNode : public ConcreteShadowNode< private: void initialize() noexcept { - BaseShadowNode::orderIndex_ = BaseShadowNode::getConcreteProps().zIndex; + auto &props = BaseShadowNode::getConcreteProps(); + + BaseShadowNode::orderIndex_ = props.zIndex; + + if (props.yogaStyle.display() == YGDisplayNone) { + BaseShadowNode::traits_.set(ShadowNodeTraits::Trait::Hidden); + } else { + BaseShadowNode::traits_.unset(ShadowNodeTraits::Trait::Hidden); + } } }; diff --git a/ReactCommon/fabric/components/view/ViewShadowNode.cpp b/ReactCommon/fabric/components/view/ViewShadowNode.cpp index 43b6b7229d4ee5..421f69c7c8bd97 100644 --- a/ReactCommon/fabric/components/view/ViewShadowNode.cpp +++ b/ReactCommon/fabric/components/view/ViewShadowNode.cpp @@ -48,7 +48,8 @@ void ViewShadowNode::initialize() noexcept { viewProps.yogaStyle.positionType() == YGPositionTypeAbsolute) || viewProps.yogaStyle.display() == YGDisplayNone || viewProps.getClipsContentToBounds() || - isColorMeaningful(viewProps.shadowColor); + isColorMeaningful(viewProps.shadowColor) || + viewProps.importantForAccessibility != ImportantForAccessibility::Auto; bool formsView = isColorMeaningful(viewProps.backgroundColor) || isColorMeaningful(viewProps.foregroundColor) || diff --git a/ReactCommon/fabric/components/view/accessibility/AccessibilityPrimitives.h b/ReactCommon/fabric/components/view/accessibility/AccessibilityPrimitives.h index a17b9aa44f7a0a..5cd1997f99868a 100644 --- a/ReactCommon/fabric/components/view/accessibility/AccessibilityPrimitives.h +++ b/ReactCommon/fabric/components/view/accessibility/AccessibilityPrimitives.h @@ -61,5 +61,12 @@ constexpr bool operator!=( return !(rhs == lhs); } +enum class ImportantForAccessibility { + Auto, + Yes, + No, + NoHideDescendants, +}; + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp b/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp index 417eccd656524b..584e599b2dfaa3 100644 --- a/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp +++ b/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp @@ -83,6 +83,11 @@ AccessibilityProps::AccessibilityProps( "onAccessibilityAction", sourceProps.onAccessibilityAction, {})), + importantForAccessibility(convertRawProp( + rawProps, + "importantForAccessibility", + sourceProps.importantForAccessibility, + ImportantForAccessibility::Auto)), testId(convertRawProp(rawProps, "testId", sourceProps.testId, "")) {} #pragma mark - DebugStringConvertible diff --git a/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.h b/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.h index fc6088dbf0a149..59537b1d01861d 100644 --- a/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.h +++ b/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.h @@ -37,6 +37,8 @@ class AccessibilityProps { bool onAccessibilityMagicTap{}; bool onAccessibilityEscape{}; bool onAccessibilityAction{}; + ImportantForAccessibility importantForAccessibility{ + ImportantForAccessibility::Auto}; std::string testId{""}; #pragma mark - DebugStringConvertible diff --git a/ReactCommon/fabric/components/view/accessibility/accessibilityPropsConversions.h b/ReactCommon/fabric/components/view/accessibility/accessibilityPropsConversions.h index c0239600a8646d..3c2fa76193ebd6 100644 --- a/ReactCommon/fabric/components/view/accessibility/accessibilityPropsConversions.h +++ b/ReactCommon/fabric/components/view/accessibility/accessibilityPropsConversions.h @@ -122,5 +122,42 @@ inline void fromRawValue(const RawValue &value, AccessibilityState &result) { } } +inline std::string toString( + const ImportantForAccessibility &importantForAccessibility) { + switch (importantForAccessibility) { + case ImportantForAccessibility::Auto: + return "auto"; + case ImportantForAccessibility::Yes: + return "yes"; + case ImportantForAccessibility::No: + return "no"; + case ImportantForAccessibility::NoHideDescendants: + return "no-hide-descendants"; + } +} + +inline void fromRawValue( + const RawValue &value, + ImportantForAccessibility &result) { + auto string = (std::string)value; + if (string == "auto") { + result = ImportantForAccessibility::Auto; + return; + } + if (string == "yes") { + result = ImportantForAccessibility::Yes; + return; + } + if (string == "no") { + result = ImportantForAccessibility::No; + return; + } + if (string == "no-hide-descendants") { + result = ImportantForAccessibility::NoHideDescendants; + return; + } + abort(); +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/core/layout/LayoutableShadowNode.cpp b/ReactCommon/fabric/core/layout/LayoutableShadowNode.cpp index ba26ba40e32376..7841a03a29dc7c 100644 --- a/ReactCommon/fabric/core/layout/LayoutableShadowNode.cpp +++ b/ReactCommon/fabric/core/layout/LayoutableShadowNode.cpp @@ -217,8 +217,7 @@ ShadowNode::Shared LayoutableShadowNode::findNodeAtPoint( return nullptr; } auto frame = layoutableShadowNode->getLayoutMetrics().frame; - auto transformedFrame = frame * layoutableShadowNode->getTransform(); - auto isPointInside = transformedFrame.containsPoint(point); + auto isPointInside = frame.containsPoint(point); if (!isPointInside) { return nullptr; diff --git a/ReactCommon/fabric/core/shadownode/ShadowNodeTraits.h b/ReactCommon/fabric/core/shadownode/ShadowNodeTraits.h index 607d308e96d2a3..16b7129bd00c71 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNodeTraits.h +++ b/ReactCommon/fabric/core/shadownode/ShadowNodeTraits.h @@ -50,6 +50,10 @@ class ShadowNodeTraits { // `ViewShadowNode` (exact!) class. View = 1 << 5, + // The node is hidden. + // Nodes with this trait (and all their descendants) will not produce views. + Hidden = 1 << 6, + // Inherits `YogaLayoutableShadowNode` and enforces that the `YGNode` is a // leaf. LeafYogaNode = 1 << 10, diff --git a/ReactCommon/fabric/core/tests/FindNodeAtPointTest.cpp b/ReactCommon/fabric/core/tests/FindNodeAtPointTest.cpp index 8f7cd298dc333c..ec5540a6badceb 100644 --- a/ReactCommon/fabric/core/tests/FindNodeAtPointTest.cpp +++ b/ReactCommon/fabric/core/tests/FindNodeAtPointTest.cpp @@ -102,18 +102,21 @@ TEST_F(FindNodeAtPointTest, withoutTransform) { LayoutableShadowNode::findNodeAtPoint(nodeA_, {1001, 1001}), nullptr); } -TEST_F(FindNodeAtPointTest, viewIsTranslated) { - nodeA_->_transform = - Transform::Identity() * Transform::Translate(-100, -100, 0); - - EXPECT_EQ( - LayoutableShadowNode::findNodeAtPoint(nodeA_, {15, 15})->getTag(), - nodeAAA_->getTag()); - EXPECT_EQ(LayoutableShadowNode::findNodeAtPoint(nodeA_, {5, 5}), nodeAA_); -} - -TEST_F(FindNodeAtPointTest, viewIsScaled) { - nodeAAA_->_transform = Transform::Identity() * Transform::Scale(0.5, 0.5, 0); - - EXPECT_EQ(LayoutableShadowNode::findNodeAtPoint(nodeA_, {119, 119}), nodeAA_); -} +// Uncomment once T69368852 is resolved. +// TEST_F(FindNodeAtPointTest, viewIsTranslated) { +// nodeA_->_transform = +// Transform::Identity() * Transform::Translate(-100, -100, 0); + +// EXPECT_EQ( +// LayoutableShadowNode::findNodeAtPoint(nodeA_, {15, 15})->getTag(), +// nodeAAA_->getTag()); +// EXPECT_EQ(LayoutableShadowNode::findNodeAtPoint(nodeA_, {5, 5}), nodeAA_); +// } + +// TEST_F(FindNodeAtPointTest, viewIsScaled) { +// nodeAAA_->_transform = Transform::Identity() * Transform::Scale(0.5, 0.5, +// 0); + +// EXPECT_EQ(LayoutableShadowNode::findNodeAtPoint(nodeA_, {119, +// 119})->getTag(), nodeAA_->getTag()); +// } diff --git a/ReactCommon/fabric/core/tests/LayoutableShadowNodeTest.cpp b/ReactCommon/fabric/core/tests/LayoutableShadowNodeTest.cpp index 7d59e94b4534af..924c7adad05937 100644 --- a/ReactCommon/fabric/core/tests/LayoutableShadowNodeTest.cpp +++ b/ReactCommon/fabric/core/tests/LayoutableShadowNodeTest.cpp @@ -101,6 +101,17 @@ class LayoutableShadowNodeTest : public ::testing::Test { TestComponentDescriptor componentDescriptor_; }; +/* + * ┌────────┐ + * │nodeA_ │ + * │ │ + * │ ┌────┴───┐ + * │ │nodeAA_ │ + * └───┤ │ + * │ │ + * │ │ + * └────────┘ + */ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetrics) { auto layoutMetrics = EmptyLayoutMetrics; layoutMetrics.frame.origin = {10, 20}; @@ -119,6 +130,15 @@ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetrics) { EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 20); } +/* + * ┌────────────────────────┐ + * │nodeA_ │ + * │ ┌────────────────┐│ + * │ │nodeAA_ ││ + * │ │ ││ + * │ └────────────────┘│ + * └────────────────────────┘ + */ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnTransformedNode) { auto layoutMetrics = EmptyLayoutMetrics; layoutMetrics.frame.size = {1000, 1000}; @@ -135,14 +155,29 @@ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnTransformedNode) { EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 70); EXPECT_EQ(relativeLayoutMetrics.frame.size.width, 50); EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 100); - - nodeAA_->_transform = Transform::Identity(); } +/* + * ┌────────────────────────┐ + * │nodeA_ │ + * │ ┌─────────────────────┐│ + * │ │ nodeAA_ ││ + * │ │ ┌──────────────┐││ + * │ │ │nodeAAA_ │││ + * │ │ │ ┌──────────┐│││ + * │ │ │ │nodeAAAA_ ││││ + * │ │ │ │ ││││ + * │ │ │ │ ││││ + * │ │ │ └──────────┘│││ + * │ │ └──────────────┘││ + * │ └─────────────────────┘│ + * └────────────────────────┘ + */ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnTransformedParent) { auto layoutMetrics = EmptyLayoutMetrics; layoutMetrics.frame.size = {1000, 1000}; nodeA_->setLayoutMetrics(layoutMetrics); + layoutMetrics.frame.size = {900, 900}; nodeAA_->setLayoutMetrics(layoutMetrics); layoutMetrics.frame.origin = {10, 10}; @@ -162,8 +197,6 @@ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnTransformedParent) { EXPECT_EQ(relativeLayoutMetrics.frame.size.width, 25); EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 25); - - nodeAAA_->_transform = Transform::Identity(); } TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnSameNode) { @@ -180,6 +213,12 @@ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnSameNode) { EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 200); } +/* + * ┌────────────────┐ + * │nodeA_ │ + * │ │ + * └────────────────┘ + */ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnSameTransformedNode) { auto layoutMetrics = EmptyLayoutMetrics; layoutMetrics.frame.origin = {10, 20}; @@ -193,10 +232,14 @@ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnSameTransformedNode) { EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 0); EXPECT_EQ(relativeLayoutMetrics.frame.size.width, 200); EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 400); - - nodeA_->_transform = Transform::Identity(); } +/* + * ┌────────────────┐ + * │nodeA_ │ + * │ │ + * └────────────────┘ + */ TEST_F(LayoutableShadowNodeTest, relativeLayourMetricsOnClonedNode) { // B is cloned and mutated. auto nodeBRevision2 = std::static_pointer_cast( @@ -214,6 +257,18 @@ TEST_F(LayoutableShadowNodeTest, relativeLayourMetricsOnClonedNode) { EXPECT_EQ(newRelativeLayoutMetrics.frame.size.height, 600); } +/* + * ┌─────────────────────────┐ + * │nodeA_ │ + * │ ┌──────────────────────┐│ + * │ │nodeAA_: rootKindNode ││ + * │ │ ┌───────────┐ ││ + * │ │ │nodeAAA_ │ ││ + * │ │ │ │ ││ + * │ │ └───────────┘ ││ + * │ └──────────────────────┘│ + * └─────────────────────────┘ + */ TEST_F( LayoutableShadowNodeTest, relativeLayoutMetricsOnNodesCrossingRootKindNode) { diff --git a/ReactCommon/fabric/graphics/Geometry.h b/ReactCommon/fabric/graphics/Geometry.h index 36f71e8321562f..16777b67855a8a 100644 --- a/ReactCommon/fabric/graphics/Geometry.h +++ b/ReactCommon/fabric/graphics/Geometry.h @@ -7,58 +7,16 @@ #pragma once -#include -#include -#include - -#include #include +#include +#include +#include +#include +#include namespace facebook { namespace react { -/* - * Point - */ -struct Point { - Float x{0}; - Float y{0}; - - Point &operator+=(const Point &point) { - x += point.x; - y += point.y; - return *this; - } - - Point &operator-=(const Point &point) { - x -= point.x; - y -= point.y; - return *this; - } - - Point &operator*=(const Point &point) { - x *= point.x; - y *= point.y; - return *this; - } - - friend Point operator+(Point lhs, const Point &rhs) { - return lhs += rhs; - } - - friend Point operator-(Point lhs, const Point &rhs) { - return lhs -= rhs; - } - - bool operator==(const Point &rhs) const { - return std::tie(this->x, this->y) == std::tie(rhs.x, rhs.y); - } - - bool operator!=(const Point &rhs) const { - return !(*this == rhs); - } -}; - struct Vector { Float x{0}; Float y{0}; @@ -66,251 +24,5 @@ struct Vector { Float w{0}; }; -/* - * Size - */ -struct Size { - Float width{0}; - Float height{0}; - - Size &operator+=(const Point &point) { - width += point.x; - height += point.y; - return *this; - } - - Size &operator*=(const Point &point) { - width *= point.x; - height *= point.y; - return *this; - } - - bool operator==(const Size &rhs) const { - return std::tie(this->width, this->height) == - std::tie(rhs.width, rhs.height); - } - - bool operator!=(const Size &rhs) const { - return !(*this == rhs); - } -}; - -/* - * Rect: Point and Size - */ -struct Rect { - Point origin{0, 0}; - Size size{0, 0}; - - bool operator==(const Rect &rhs) const { - return std::tie(this->origin, this->size) == std::tie(rhs.origin, rhs.size); - } - - bool operator!=(const Rect &rhs) const { - return !(*this == rhs); - } - - Float getMaxX() const { - return size.width > 0 ? origin.x + size.width : origin.x; - } - Float getMaxY() const { - return size.height > 0 ? origin.y + size.height : origin.y; - } - Float getMinX() const { - return size.width >= 0 ? origin.x : origin.x + size.width; - } - Float getMinY() const { - return size.height >= 0 ? origin.y : origin.y + size.height; - } - Float getMidX() const { - return origin.x + size.width / 2; - } - Float getMidY() const { - return origin.y + size.height / 2; - } - Point getCenter() const { - return {getMidX(), getMidY()}; - } - - void unionInPlace(const Rect &rect) { - auto x1 = std::min(getMinX(), rect.getMinX()); - auto y1 = std::min(getMinY(), rect.getMinY()); - auto x2 = std::max(getMaxX(), rect.getMaxX()); - auto y2 = std::max(getMaxY(), rect.getMaxY()); - origin = {x1, y1}; - size = {x2 - x1, y2 - y1}; - } - - bool containsPoint(Point point) { - return point.x >= origin.x && point.y >= origin.y && - point.x <= (origin.x + size.width) && - point.y <= (origin.y + size.height); - } - - static Rect - boundingRect(Point const &a, Point const &b, Point const &c, Point const &d) { - auto leftTopPoint = a; - auto rightBottomPoint = a; - - leftTopPoint.x = std::min(leftTopPoint.x, b.x); - leftTopPoint.x = std::min(leftTopPoint.x, c.x); - leftTopPoint.x = std::min(leftTopPoint.x, d.x); - - leftTopPoint.y = std::min(leftTopPoint.y, b.y); - leftTopPoint.y = std::min(leftTopPoint.y, c.y); - leftTopPoint.y = std::min(leftTopPoint.y, d.y); - - rightBottomPoint.x = std::max(rightBottomPoint.x, b.x); - rightBottomPoint.x = std::max(rightBottomPoint.x, c.x); - rightBottomPoint.x = std::max(rightBottomPoint.x, d.x); - - rightBottomPoint.y = std::max(rightBottomPoint.y, b.y); - rightBottomPoint.y = std::max(rightBottomPoint.y, c.y); - rightBottomPoint.y = std::max(rightBottomPoint.y, d.y); - - return {leftTopPoint, - {rightBottomPoint.x - leftTopPoint.x, - rightBottomPoint.y - leftTopPoint.y}}; - } -}; - -/* - * Generic data structure describes some values associated with *edges* - * of a rectangle. - */ -template -struct RectangleEdges { - T left{}; - T top{}; - T right{}; - T bottom{}; - - bool operator==(const RectangleEdges &rhs) const { - return std::tie(this->left, this->top, this->right, this->bottom) == - std::tie(rhs.left, rhs.top, rhs.right, rhs.bottom); - } - - bool operator!=(const RectangleEdges &rhs) const { - return !(*this == rhs); - } - - bool isUniform() const { - return left == top && left == right && left == bottom; - } -}; - -template -RectangleEdges operator+( - RectangleEdges const &lhs, - RectangleEdges const &rhs) { - return RectangleEdges{lhs.left + rhs.left, - lhs.top + rhs.top, - lhs.right + rhs.right, - lhs.bottom + rhs.bottom}; -} - -template -RectangleEdges operator-( - RectangleEdges const &lhs, - RectangleEdges const &rhs) { - return RectangleEdges{lhs.left - rhs.left, - lhs.top - rhs.top, - lhs.right - rhs.right, - lhs.bottom - rhs.bottom}; -} - -/* - * Generic data structure describes some values associated with *corners* - * of a rectangle. - */ -template -struct RectangleCorners { - T topLeft{}; - T topRight{}; - T bottomLeft{}; - T bottomRight{}; - - bool operator==(const RectangleCorners &rhs) const { - return std::tie( - this->topLeft, - this->topRight, - this->bottomLeft, - this->bottomRight) == - std::tie(rhs.topLeft, rhs.topRight, rhs.bottomLeft, rhs.bottomRight); - } - - bool operator!=(const RectangleCorners &rhs) const { - return !(*this == rhs); - } - - bool isUniform() const { - return topLeft == topRight && topLeft == bottomLeft && - topLeft == bottomRight; - } -}; - -/* - * EdgeInsets - */ -using EdgeInsets = RectangleEdges; - -/* - * CornerInsets - */ -using CornerInsets = RectangleCorners; - -/* - * Adjusts a rectangle by the given edge insets. - */ -inline Rect insetBy(Rect rect, EdgeInsets insets) { - return Rect{{rect.origin.x + insets.left, rect.origin.y + insets.top}, - {rect.size.width - insets.left - insets.right, - rect.size.height - insets.top - insets.bottom}}; -} - } // namespace react } // namespace facebook - -namespace std { -template <> -struct hash { - size_t operator()(const facebook::react::Point &point) const { - return folly::hash::hash_combine(0, point.x, point.y); - } -}; - -template <> -struct hash { - size_t operator()(const facebook::react::Size &size) const { - return folly::hash::hash_combine(0, size.width, size.height); - } -}; - -template <> -struct hash { - size_t operator()(const facebook::react::Rect &rect) const { - return folly::hash::hash_combine(0, rect.origin, rect.size); - } -}; - -template -struct hash> { - size_t operator()(const facebook::react::RectangleEdges &edges) const { - return folly::hash::hash_combine( - 0, edges.left, edges.right, edges.top, edges.bottom); - } -}; - -template -struct hash> { - size_t operator()(const facebook::react::RectangleCorners &corners) const { - return folly::hash::hash_combine( - 0, - corners.topLeft, - corners.bottomLeft, - corners.topRight, - corners.bottomRight); - } -}; - -} // namespace std diff --git a/ReactCommon/fabric/graphics/Point.h b/ReactCommon/fabric/graphics/Point.h new file mode 100644 index 00000000000000..a0637ac54b75e7 --- /dev/null +++ b/ReactCommon/fabric/graphics/Point.h @@ -0,0 +1,72 @@ +/* + * 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 + +namespace facebook { +namespace react { + +/* + * Contains a point in a two-dimensional coordinate system. + */ +struct Point { + Float x{0}; + Float y{0}; + + Point &operator+=(Point const &point) noexcept { + x += point.x; + y += point.y; + return *this; + } + + Point &operator-=(Point const &point) noexcept { + x -= point.x; + y -= point.y; + return *this; + } + + Point &operator*=(Point const &point) noexcept { + x *= point.x; + y *= point.y; + return *this; + } + + friend Point operator+(Point lhs, Point const &rhs) noexcept { + return lhs += rhs; + } + + friend Point operator-(Point lhs, Point const &rhs) noexcept { + return lhs -= rhs; + } +}; + +inline bool operator==(Point const &rhs, Point const &lhs) noexcept { + return std::tie(lhs.x, lhs.y) == std::tie(rhs.x, rhs.y); +} + +inline bool operator!=(Point const &rhs, Point const &lhs) noexcept { + return !(lhs == rhs); +} + +} // namespace react +} // namespace facebook + +namespace std { + +template <> +struct hash { + size_t operator()(facebook::react::Point const &point) const noexcept { + return folly::hash::hash_combine(0, point.x, point.y); + } +}; + +} // namespace std diff --git a/ReactCommon/fabric/graphics/Rect.h b/ReactCommon/fabric/graphics/Rect.h new file mode 100644 index 00000000000000..93b049b5c2b3c5 --- /dev/null +++ b/ReactCommon/fabric/graphics/Rect.h @@ -0,0 +1,114 @@ +/* + * 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 { + +/* + * Contains the location and dimensions of a rectangle. + */ +struct Rect { + Point origin{0, 0}; + Size size{0, 0}; + + bool operator==(Rect const &rhs) const noexcept { + return std::tie(this->origin, this->size) == std::tie(rhs.origin, rhs.size); + } + + bool operator!=(Rect const &rhs) const noexcept { + return !(*this == rhs); + } + + Float getMaxX() const noexcept { + return size.width > 0 ? origin.x + size.width : origin.x; + } + Float getMaxY() const noexcept { + return size.height > 0 ? origin.y + size.height : origin.y; + } + Float getMinX() const noexcept { + return size.width >= 0 ? origin.x : origin.x + size.width; + } + Float getMinY() const noexcept { + return size.height >= 0 ? origin.y : origin.y + size.height; + } + Float getMidX() const noexcept { + return origin.x + size.width / 2; + } + Float getMidY() const noexcept { + return origin.y + size.height / 2; + } + Point getCenter() const noexcept { + return {getMidX(), getMidY()}; + } + + void unionInPlace(Rect const &rect) noexcept { + auto x1 = std::min(getMinX(), rect.getMinX()); + auto y1 = std::min(getMinY(), rect.getMinY()); + auto x2 = std::max(getMaxX(), rect.getMaxX()); + auto y2 = std::max(getMaxY(), rect.getMaxY()); + origin = {x1, y1}; + size = {x2 - x1, y2 - y1}; + } + + bool containsPoint(Point point) noexcept { + return point.x >= origin.x && point.y >= origin.y && + point.x <= (origin.x + size.width) && + point.y <= (origin.y + size.height); + } + + static Rect boundingRect( + Point const &a, + Point const &b, + Point const &c, + Point const &d) noexcept { + auto leftTopPoint = a; + auto rightBottomPoint = a; + + leftTopPoint.x = std::min(leftTopPoint.x, b.x); + leftTopPoint.x = std::min(leftTopPoint.x, c.x); + leftTopPoint.x = std::min(leftTopPoint.x, d.x); + + leftTopPoint.y = std::min(leftTopPoint.y, b.y); + leftTopPoint.y = std::min(leftTopPoint.y, c.y); + leftTopPoint.y = std::min(leftTopPoint.y, d.y); + + rightBottomPoint.x = std::max(rightBottomPoint.x, b.x); + rightBottomPoint.x = std::max(rightBottomPoint.x, c.x); + rightBottomPoint.x = std::max(rightBottomPoint.x, d.x); + + rightBottomPoint.y = std::max(rightBottomPoint.y, b.y); + rightBottomPoint.y = std::max(rightBottomPoint.y, c.y); + rightBottomPoint.y = std::max(rightBottomPoint.y, d.y); + + return {leftTopPoint, + {rightBottomPoint.x - leftTopPoint.x, + rightBottomPoint.y - leftTopPoint.y}}; + } +}; + +} // namespace react +} // namespace facebook + +namespace std { + +template <> +struct hash { + size_t operator()(facebook::react::Rect const &rect) const noexcept { + return folly::hash::hash_combine(0, rect.origin, rect.size); + } +}; + +} // namespace std diff --git a/ReactCommon/fabric/graphics/RectangleCorners.h b/ReactCommon/fabric/graphics/RectangleCorners.h new file mode 100644 index 00000000000000..6d174f921423f0 --- /dev/null +++ b/ReactCommon/fabric/graphics/RectangleCorners.h @@ -0,0 +1,71 @@ +/* + * 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 + +namespace facebook { +namespace react { + +/* + * Generic data structure describes some values associated with *corners* + * of a rectangle. + */ +template +struct RectangleCorners { + T topLeft{}; + T topRight{}; + T bottomLeft{}; + T bottomRight{}; + + bool operator==(RectangleCorners const &rhs) const noexcept { + return std::tie( + this->topLeft, + this->topRight, + this->bottomLeft, + this->bottomRight) == + std::tie(rhs.topLeft, rhs.topRight, rhs.bottomLeft, rhs.bottomRight); + } + + bool operator!=(RectangleCorners const &rhs) const noexcept { + return !(*this == rhs); + } + + bool isUniform() const noexcept { + return topLeft == topRight && topLeft == bottomLeft && + topLeft == bottomRight; + } +}; + +/* + * CornerInsets + */ +using CornerInsets = RectangleCorners; + +} // namespace react +} // namespace facebook + +namespace std { + +template +struct hash> { + size_t operator()(facebook::react::RectangleCorners const &corners) const + noexcept { + return folly::hash::hash_combine( + 0, + corners.topLeft, + corners.bottomLeft, + corners.topRight, + corners.bottomRight); + } +}; + +} // namespace std diff --git a/ReactCommon/fabric/graphics/RectangleEdges.h b/ReactCommon/fabric/graphics/RectangleEdges.h new file mode 100644 index 00000000000000..d4db57752a5cb8 --- /dev/null +++ b/ReactCommon/fabric/graphics/RectangleEdges.h @@ -0,0 +1,92 @@ +/* + * 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 { + +/* + * Generic data structure describes some values associated with *edges* + * of a rectangle. + */ +template +struct RectangleEdges { + T left{}; + T top{}; + T right{}; + T bottom{}; + + bool operator==(RectangleEdges const &rhs) const noexcept { + return std::tie(this->left, this->top, this->right, this->bottom) == + std::tie(rhs.left, rhs.top, rhs.right, rhs.bottom); + } + + bool operator!=(RectangleEdges const &rhs) const noexcept { + return !(*this == rhs); + } + + bool isUniform() const noexcept { + return left == top && left == right && left == bottom; + } +}; + +template +RectangleEdges operator+( + RectangleEdges const &lhs, + RectangleEdges const &rhs) noexcept { + return RectangleEdges{lhs.left + rhs.left, + lhs.top + rhs.top, + lhs.right + rhs.right, + lhs.bottom + rhs.bottom}; +} + +template +RectangleEdges operator-( + RectangleEdges const &lhs, + RectangleEdges const &rhs) noexcept { + return RectangleEdges{lhs.left - rhs.left, + lhs.top - rhs.top, + lhs.right - rhs.right, + lhs.bottom - rhs.bottom}; +} + +/* + * EdgeInsets + */ +using EdgeInsets = RectangleEdges; + +/* + * Adjusts a rectangle by the given edge insets. + */ +inline Rect insetBy(Rect const &rect, EdgeInsets const &insets) noexcept { + return Rect{{rect.origin.x + insets.left, rect.origin.y + insets.top}, + {rect.size.width - insets.left - insets.right, + rect.size.height - insets.top - insets.bottom}}; +} + +} // namespace react +} // namespace facebook + +namespace std { + +template +struct hash> { + size_t operator()(facebook::react::RectangleEdges const &edges) const + noexcept { + return folly::hash::hash_combine( + 0, edges.left, edges.right, edges.top, edges.bottom); + } +}; + +} // namespace std diff --git a/ReactCommon/fabric/graphics/Size.h b/ReactCommon/fabric/graphics/Size.h new file mode 100644 index 00000000000000..ad537db1771437 --- /dev/null +++ b/ReactCommon/fabric/graphics/Size.h @@ -0,0 +1,59 @@ +/* + * 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 { + +/* + * Contains width and height values. + */ +struct Size { + Float width{0}; + Float height{0}; + + Size &operator+=(Point const &point) noexcept { + width += point.x; + height += point.y; + return *this; + } + + Size &operator*=(Point const &point) noexcept { + width *= point.x; + height *= point.y; + return *this; + } +}; + +inline bool operator==(Size const &rhs, Size const &lhs) noexcept { + return std::tie(lhs.width, lhs.height) == std::tie(rhs.width, rhs.height); +} + +inline bool operator!=(Size const &rhs, Size const &lhs) noexcept { + return !(lhs == rhs); +} + +} // namespace react +} // namespace facebook + +namespace std { + +template <> +struct hash { + size_t operator()(facebook::react::Size const &size) const { + return folly::hash::hash_combine(0, size.width, size.height); + } +}; + +} // namespace std diff --git a/ReactCommon/fabric/mounting/Differentiator.cpp b/ReactCommon/fabric/mounting/Differentiator.cpp index e1ac64cbf35dca..62cfd94522f4eb 100644 --- a/ReactCommon/fabric/mounting/Differentiator.cpp +++ b/ReactCommon/fabric/mounting/Differentiator.cpp @@ -188,6 +188,15 @@ static void sliceChildShadowNodeViewPairsRecursively( ShadowNode const &shadowNode) { for (auto const &sharedChildShadowNode : shadowNode.getChildren()) { auto &childShadowNode = *sharedChildShadowNode; + +#ifndef ANDROID + // Temporary disabled on Android because the mounting infrastructure + // is not fully ready yet. + if (childShadowNode.getTraits().check(ShadowNodeTraits::Trait::Hidden)) { + continue; + } +#endif + auto shadowView = ShadowView(childShadowNode); auto origin = layoutOffset; if (shadowView.layoutMetrics != EmptyLayoutMetrics) { diff --git a/ReactCommon/fabric/mounting/MountingCoordinator.cpp b/ReactCommon/fabric/mounting/MountingCoordinator.cpp index d977f35d36bdec..f4d5f3ab7b8473 100644 --- a/ReactCommon/fabric/mounting/MountingCoordinator.cpp +++ b/ReactCommon/fabric/mounting/MountingCoordinator.cpp @@ -24,7 +24,8 @@ MountingCoordinator::MountingCoordinator( std::weak_ptr delegate) : surfaceId_(baseRevision.getRootShadowNode().getSurfaceId()), baseRevision_(baseRevision), - mountingOverrideDelegate_(delegate) { + mountingOverrideDelegate_(delegate), + telemetryController_(*this) { #ifdef RN_SHADOW_TREE_INTROSPECTION stubViewTree_ = stubViewTreeFromShadowNode(baseRevision_.getRootShadowNode()); #endif @@ -172,5 +173,9 @@ better::optional MountingCoordinator::pullTransaction() return transaction; } +TelemetryController const &MountingCoordinator::getTelemetryController() const { + return telemetryController_; +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/mounting/MountingCoordinator.h b/ReactCommon/fabric/mounting/MountingCoordinator.h index 556258ef2eb7e7..ed3ffb1a9d3fdc 100644 --- a/ReactCommon/fabric/mounting/MountingCoordinator.h +++ b/ReactCommon/fabric/mounting/MountingCoordinator.h @@ -14,6 +14,7 @@ #include #include #include +#include #include "ShadowTreeRevision.h" #ifdef RN_SHADOW_TREE_INTROSPECTION @@ -69,6 +70,8 @@ class MountingCoordinator final { */ bool waitForTransaction(std::chrono::duration timeout) const; + TelemetryController const &getTelemetryController() const; + /* * Methods from this section are meant to be used by * `MountingOverrideDelegate` only. @@ -104,6 +107,8 @@ class MountingCoordinator final { mutable std::condition_variable signal_; std::weak_ptr mountingOverrideDelegate_; + TelemetryController telemetryController_; + #ifdef RN_SHADOW_TREE_INTROSPECTION void validateTransactionAgainstStubViewTree( ShadowViewMutationList const &mutations, diff --git a/ReactCommon/fabric/mounting/MountingTelemetry.h b/ReactCommon/fabric/mounting/MountingTelemetry.h index 1a212d6e97b6cd..5f74679559e717 100644 --- a/ReactCommon/fabric/mounting/MountingTelemetry.h +++ b/ReactCommon/fabric/mounting/MountingTelemetry.h @@ -16,8 +16,8 @@ namespace facebook { namespace react { /* - * Represent arbitrary telemetry data that can be associated with the - * particular revision of `ShadowTree`. + * Represents telemetry data associated with a particular revision of + * `ShadowTree`. */ class MountingTelemetry final { public: diff --git a/ReactCommon/fabric/mounting/MountingTransaction.h b/ReactCommon/fabric/mounting/MountingTransaction.h index 0a0c68ab6d0ea9..f5e586162395d9 100644 --- a/ReactCommon/fabric/mounting/MountingTransaction.h +++ b/ReactCommon/fabric/mounting/MountingTransaction.h @@ -9,6 +9,7 @@ #include #include +#include namespace facebook { namespace react { diff --git a/ReactCommon/fabric/mounting/MountingTransactionMetadata.h b/ReactCommon/fabric/mounting/MountingTransactionMetadata.h index 20d2e29aa2db2f..300fda9bb6e789 100644 --- a/ReactCommon/fabric/mounting/MountingTransactionMetadata.h +++ b/ReactCommon/fabric/mounting/MountingTransactionMetadata.h @@ -25,6 +25,7 @@ class MountingTransactionMetadata final { SurfaceId surfaceId; MountingTransaction::Number number; MountingTelemetry telemetry; + SurfaceTelemetry surfaceTelemetry; }; } // namespace react diff --git a/ReactCommon/fabric/mounting/ShadowTreeRegistry.cpp b/ReactCommon/fabric/mounting/ShadowTreeRegistry.cpp index 1564bbee14bd04..6966870619bd73 100644 --- a/ReactCommon/fabric/mounting/ShadowTreeRegistry.cpp +++ b/ReactCommon/fabric/mounting/ShadowTreeRegistry.cpp @@ -21,14 +21,13 @@ void ShadowTreeRegistry::add(std::unique_ptr &&shadowTree) const { registry_.emplace(shadowTree->getSurfaceId(), std::move(shadowTree)); } -std::unique_ptr ShadowTreeRegistry::remove( - SurfaceId surfaceId) const { +void ShadowTreeRegistry::remove(SurfaceId surfaceId) const { std::unique_lock lock(mutex_); auto iterator = registry_.find(surfaceId); - auto shadowTree = std::unique_ptr(iterator->second.release()); - registry_.erase(iterator); - return shadowTree; + if (iterator != registry_.end()) { + registry_.erase(iterator); + } } bool ShadowTreeRegistry::visit( diff --git a/ReactCommon/fabric/mounting/ShadowTreeRegistry.h b/ReactCommon/fabric/mounting/ShadowTreeRegistry.h index 4ccc306021f0e0..6ef6ae2e62c061 100644 --- a/ReactCommon/fabric/mounting/ShadowTreeRegistry.h +++ b/ReactCommon/fabric/mounting/ShadowTreeRegistry.h @@ -34,10 +34,9 @@ class ShadowTreeRegistry final { /* * Removes a `ShadowTree` instance with given `surfaceId` from the registry * and returns it as a result. - * The ownership of the instance is also transferred to the caller. * Can be called from any thread. */ - std::unique_ptr remove(SurfaceId surfaceId) const; + void remove(SurfaceId surfaceId) const; /* * Finds a `ShadowTree` instance with a given `surfaceId` in the registry and diff --git a/ReactCommon/fabric/mounting/SurfaceTelemetry.cpp b/ReactCommon/fabric/mounting/SurfaceTelemetry.cpp new file mode 100644 index 00000000000000..cbe68e0831ab0f --- /dev/null +++ b/ReactCommon/fabric/mounting/SurfaceTelemetry.cpp @@ -0,0 +1,50 @@ +/* + * 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 "SurfaceTelemetry.h" + +namespace facebook { +namespace react { + +void SurfaceTelemetry::incorporate( + MountingTelemetry const &telemetry, + int numberOfMutations) { + layoutTime_ += telemetry.getLayoutEndTime() - telemetry.getLayoutStartTime(); + commitTime_ += telemetry.getCommitEndTime() - telemetry.getCommitStartTime(); + diffTime_ += telemetry.getDiffEndTime() - telemetry.getDiffStartTime(); + mountTime_ += telemetry.getMountEndTime() - telemetry.getMountStartTime(); + + numberOfTransactions_++; + numberOfMutations_ += numberOfMutations; +} + +TelemetryDuration SurfaceTelemetry::getLayoutTime() const { + return layoutTime_; +} + +TelemetryDuration SurfaceTelemetry::getCommitTime() const { + return commitTime_; +} + +TelemetryDuration SurfaceTelemetry::getDiffTime() const { + return diffTime_; +} + +TelemetryDuration SurfaceTelemetry::getMountTime() const { + return mountTime_; +} + +int SurfaceTelemetry::getNumberOfTransactions() const { + return numberOfTransactions_; +} + +int SurfaceTelemetry::getNumberOfMutations() const { + return numberOfMutations_; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/mounting/SurfaceTelemetry.h b/ReactCommon/fabric/mounting/SurfaceTelemetry.h new file mode 100644 index 00000000000000..0151ceb8f9d6df --- /dev/null +++ b/ReactCommon/fabric/mounting/SurfaceTelemetry.h @@ -0,0 +1,50 @@ +/* + * 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 + +namespace facebook { +namespace react { + +/* + * Represents telemetry data associated with a particular running Surface. + * Contains information aggregated from multiple completed transaction. + */ +class SurfaceTelemetry final { + public: + /* + * Metrics + */ + TelemetryDuration getLayoutTime() const; + TelemetryDuration getCommitTime() const; + TelemetryDuration getDiffTime() const; + TelemetryDuration getMountTime() const; + + int getNumberOfTransactions() const; + int getNumberOfMutations() const; + + /* + * Incorporate data from given transaction telemetry into aggregated data + * for the Surface. + */ + void incorporate(MountingTelemetry const &telemetry, int numberOfMutations); + + private: + TelemetryDuration layoutTime_{}; + TelemetryDuration commitTime_{}; + TelemetryDuration diffTime_{}; + TelemetryDuration mountTime_{}; + + int numberOfTransactions_{}; + int numberOfMutations_{}; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/mounting/TelemetryController.cpp b/ReactCommon/fabric/mounting/TelemetryController.cpp new file mode 100644 index 00000000000000..43c91b5eb85ec6 --- /dev/null +++ b/ReactCommon/fabric/mounting/TelemetryController.cpp @@ -0,0 +1,58 @@ +/* + * 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 "TelemetryController.h" + +#include + +namespace facebook { +namespace react { + +TelemetryController::TelemetryController( + MountingCoordinator const &mountingCoordinator) noexcept + : mountingCoordinator_(mountingCoordinator) {} + +bool TelemetryController::pullTransaction( + std::function willMount, + std::function doMount, + std::function didMount) const + noexcept { + auto optional = mountingCoordinator_.pullTransaction(); + if (!optional.has_value()) { + return false; + } + + auto transaction = std::move(*optional); + + auto surfaceId = transaction.getSurfaceId(); + auto number = transaction.getNumber(); + auto telemetry = transaction.getTelemetry(); + auto numberOfMutations = transaction.getMutations().size(); + + mutex_.lock(); + auto compoundTelemetry = compoundTelemetry_; + mutex_.unlock(); + + willMount({surfaceId, number, telemetry, compoundTelemetry}); + + telemetry.willMount(); + doMount(std::move(transaction.getMutations())); + telemetry.didMount(); + + compoundTelemetry.incorporate(telemetry, numberOfMutations); + + didMount({surfaceId, number, telemetry, compoundTelemetry}); + + mutex_.lock(); + compoundTelemetry_ = compoundTelemetry; + mutex_.unlock(); + + return true; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/mounting/TelemetryController.h b/ReactCommon/fabric/mounting/TelemetryController.h new file mode 100644 index 00000000000000..f5af79c0de0365 --- /dev/null +++ b/ReactCommon/fabric/mounting/TelemetryController.h @@ -0,0 +1,58 @@ +/* + * 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 MountingCoordinator; + +/* + * Provides convenient tools for aggregating and accessing telemetry data + * associated with running Surface. + */ +class TelemetryController final { + friend class MountingCoordinator; + + /* + * To be used by `MountingCoordinator`. + */ + TelemetryController(MountingCoordinator const &mountingCoordinator) noexcept; + + /* + * Not copyable. + */ + TelemetryController(TelemetryController const &other) noexcept = delete; + TelemetryController &operator=(TelemetryController const &other) noexcept = + delete; + + public: + /* + * Calls `MountingCoordinator::pullTransaction()` and aggregates telemetry. + */ + bool pullTransaction( + std::function willMount, + std::function doMount, + std::function didMount) const + noexcept; + + private: + MountingCoordinator const &mountingCoordinator_; + mutable SurfaceTelemetry compoundTelemetry_{}; + mutable std::mutex mutex_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTAttributedTextUtils.h b/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTAttributedTextUtils.h index dd142aa3106155..eda44cf6ff6d56 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTAttributedTextUtils.h +++ b/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTAttributedTextUtils.h @@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN NSString *const RCTAttributedStringIsHighlightedAttributeName = @"IsHighlighted"; NSString *const RCTAttributedStringEventEmitterKey = @"EventEmitter"; +NSString *const RCTTextAttributesAccessibilityRoleAttributeName = @"AccessibilityRole"; /* * Creates `NSTextAttributes` from given `facebook::react::TextAttributes` diff --git a/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm b/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm index 4bc9870a628870..f9a6537abe2257 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm +++ b/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTAttributedTextUtils.mm @@ -212,6 +212,93 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex attributes[RCTAttributedStringIsHighlightedAttributeName] = @YES; } + if (textAttributes.accessibilityRole.hasValue()) { + auto accessibilityRole = textAttributes.accessibilityRole.value(); + switch (accessibilityRole) { + case AccessibilityRole::None: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("none"); + break; + case AccessibilityRole::Button: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("button"); + break; + case AccessibilityRole::Link: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("link"); + break; + case AccessibilityRole::Search: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("search"); + break; + case AccessibilityRole::Image: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("image"); + break; + case AccessibilityRole::Imagebutton: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("imagebutton"); + break; + case AccessibilityRole::Keyboardkey: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("keyboardkey"); + break; + case AccessibilityRole::Text: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("text"); + break; + case AccessibilityRole::Adjustable: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("adjustable"); + break; + case AccessibilityRole::Summary: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("summary"); + break; + case AccessibilityRole::Header: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("header"); + break; + case AccessibilityRole::Alert: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("alert"); + break; + case AccessibilityRole::Checkbox: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("checkbox"); + break; + case AccessibilityRole::Combobox: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("combobox"); + break; + case AccessibilityRole::Menu: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("menu"); + break; + case AccessibilityRole::Menubar: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("menubar"); + break; + case AccessibilityRole::Menuitem: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("menuitem"); + break; + case AccessibilityRole::Progressbar: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("progressbar"); + break; + case AccessibilityRole::Radio: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("radio"); + break; + case AccessibilityRole::Radiogroup: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("radiogroup"); + break; + case AccessibilityRole::Scrollbar: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("scrollbar"); + break; + case AccessibilityRole::Spinbutton: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("spinbutton"); + break; + case AccessibilityRole::Switch: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("switch"); + break; + case AccessibilityRole::Tab: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("tab"); + break; + case AccessibilityRole::Tablist: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("tablist"); + break; + case AccessibilityRole::Timer: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("timer"); + break; + case AccessibilityRole::Toolbar: + attributes[RCTTextAttributesAccessibilityRoleAttributeName] = @("toolbar"); + break; + }; + } + return [attributes copy]; } diff --git a/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.h b/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.h index 0e10ce2a085e95..eb495776e8c1d3 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.h +++ b/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.h @@ -15,6 +15,13 @@ NS_ASSUME_NONNULL_BEGIN +/** + @abstract Enumeration block for text fragments. +*/ + +using RCTTextLayoutFragmentEnumerationBlock = + void (^)(CGRect fragmentRect, NSString *_Nonnull fragmentText, NSString *value); + /** * iOS-specific TextLayoutManager */ @@ -38,6 +45,12 @@ NS_ASSUME_NONNULL_BEGIN frame:(CGRect)frame atPoint:(CGPoint)point; +- (void)getRectWithAttributedString:(facebook::react::AttributedString)attributedString + paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes + enumerateAttribute:(NSString *)enumerateAttribute + frame:(CGRect)frame + usingBlock:(RCTTextLayoutFragmentEnumerationBlock)block; + @end NS_ASSUME_NONNULL_END diff --git a/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.mm b/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.mm index 7e0c4a9231a22c..de6e354aa55997 100644 --- a/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.mm +++ b/ReactCommon/fabric/textlayoutmanager/platform/ios/RCTTextLayoutManager.mm @@ -182,4 +182,44 @@ - (NSAttributedString *)_nsAttributedStringFromAttributedString:(AttributedStrin return unwrapManagedObject(sharedNSAttributedString); } +- (void)getRectWithAttributedString:(AttributedString)attributedString + paragraphAttributes:(ParagraphAttributes)paragraphAttributes + enumerateAttribute:(NSString *)enumerateAttribute + frame:(CGRect)frame + usingBlock:(RCTTextLayoutFragmentEnumerationBlock)block +{ + NSTextStorage *textStorage = [self + _textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString] + paragraphAttributes:paragraphAttributes + size:frame.size]; + + NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; + NSTextContainer *textContainer = layoutManager.textContainers.firstObject; + [layoutManager ensureLayoutForTextContainer:textContainer]; + + NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + + [textStorage enumerateAttribute:enumerateAttribute + inRange:characterRange + options:0 + usingBlock:^(NSString *value, NSRange range, BOOL *pause) { + if (!value) { + return; + } + + [layoutManager + enumerateEnclosingRectsForGlyphRange:range + withinSelectedGlyphRange:range + inTextContainer:textContainer + usingBlock:^(CGRect enclosingRect, BOOL *_Nonnull stop) { + block( + enclosingRect, + [textStorage attributedSubstringFromRange:range].string, + value); + *stop = YES; + }]; + }]; +} + @end diff --git a/ReactCommon/hermes/inspector/chrome/cli/main.cpp b/ReactCommon/hermes/inspector/chrome/cli/main.cpp index 18ed974dd4fd33..8a019b8baca5de 100644 --- a/ReactCommon/hermes/inspector/chrome/cli/main.cpp +++ b/ReactCommon/hermes/inspector/chrome/cli/main.cpp @@ -38,7 +38,7 @@ debug server. For instance, running this: will run a WebSocket server on port 9999 that debugs script.js in Hermes. Chrome can connect to this debugging session using a URL like this: - chrome-devtools://devtools/bundled/inspector.html?experiments=false&v8only=true&ws=127.0.0.1:9999 + devtools://devtools/bundled/inspector.html?experiments=false&v8only=true&ws=127.0.0.1:9999 Options: diff --git a/ReactCommon/jsi/jsi/CMakeLists.txt b/ReactCommon/jsi/jsi/CMakeLists.txt index f2610548ee0e8a..d04af0006af915 100644 --- a/ReactCommon/jsi/jsi/CMakeLists.txt +++ b/ReactCommon/jsi/jsi/CMakeLists.txt @@ -22,3 +22,7 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") list(APPEND jsi_compile_flags "/EHsc") endif() target_compile_options(jsi PUBLIC ${jsi_compile_flags}) + +install(DIRECTORY "${PROJECT_SOURCE_DIR}/API/jsi/" DESTINATION include + FILES_MATCHING PATTERN "*.h" + PATTERN "test" EXCLUDE) diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h index abf072a2b7825d..bdd94494689284 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModule.h @@ -103,10 +103,10 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule { * Used by TurboModules to get access to other TurboModules. * * Usage: - * Place `@synthesize turboModuleLookupDelegate = _turboModuleLookupDelegate` + * Place `@synthesize turboModuleRegistry = _turboModuleRegistry` * in the @implementation section of your TurboModule. */ -@property (nonatomic, weak) id turboModuleLookupDelegate; +@property (nonatomic, weak) id turboModuleRegistry; @optional // This should be required, after migration is done. diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.h b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.h index 7edc90ca3869f6..8ee2eaeefe582e 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.h +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.h @@ -19,6 +19,9 @@ - (std::shared_ptr)getTurboModule:(const std::string &)name initParams: (const facebook::react::ObjCTurboModule::InitParams &)params; +@optional +- (NSArray *)getEagerInitModuleNames; +- (NSArray *)getEagerInitMainQueueModuleNames; @optional @@ -41,7 +44,7 @@ @end -@interface RCTTurboModuleManager : NSObject +@interface RCTTurboModuleManager : NSObject - (instancetype)initWithBridge:(RCTBridge *)bridge delegate:(id)delegate diff --git a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm index ce61d802a7f686..f93dfa4f1aecad 100644 --- a/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm +++ b/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm @@ -182,9 +182,6 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge _bridge = bridge; _invalidating = false; - // Necessary to allow NativeModules to lookup TurboModules - [bridge setRCTTurboModuleLookupDelegate:self]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bridgeWillInvalidateModules:) name:RCTBridgeWillInvalidateModulesNotification @@ -399,6 +396,17 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name }; if ([self _requiresMainQueueSetup:moduleClass]) { + /** + * When TurboModule eager initialization is enabled, there shouldn't be any TurboModule initializations on the + * main queue. + * TODO(T69449176) Roll out TurboModule eager initialization, and remove this check. + */ + if (RCTTurboModuleEagerInitEnabled() && !RCTIsMainQueue()) { + RCTLogWarn( + @"TurboModule \"%@\" requires synchronous dispatch onto the main queue to be initialized. This may lead to deadlock.", + moduleClass); + } + RCTUnsafeExecuteOnMainQueueSync(work); } else { work(); @@ -463,8 +471,8 @@ - (void)notifyAboutTurboModuleSetup:(const char *)name TurboModulePerfLogger::moduleCreateSetUpStart(moduleName, moduleId); - if ([module respondsToSelector:@selector(setTurboModuleLookupDelegate:)]) { - [module setTurboModuleLookupDelegate:self]; + if ([module respondsToSelector:@selector(setTurboModuleRegistry:)]) { + [module setTurboModuleRegistry:self]; } /** @@ -690,7 +698,7 @@ - (void)installJSBindingWithRuntimeExecutor:(facebook::react::RuntimeExecutor)ru }); } -#pragma mark RCTTurboModuleLookupDelegate +#pragma mark RCTTurboModuleRegistry - (id)moduleForName:(const char *)moduleName { @@ -714,6 +722,24 @@ - (BOOL)moduleIsInitialized:(const char *)moduleName return _turboModuleHolders.find(moduleName) != _turboModuleHolders.end(); } +- (NSArray *)eagerInitModuleNames +{ + if ([_delegate respondsToSelector:@selector(getEagerInitModuleNames)]) { + return [_delegate getEagerInitModuleNames]; + } + + return @[]; +} + +- (NSArray *)eagerInitMainQueueModuleNames +{ + if ([_delegate respondsToSelector:@selector(getEagerInitMainQueueModuleNames)]) { + return [_delegate getEagerInitMainQueueModuleNames]; + } + + return @[]; +} + #pragma mark Invalidation logic - (void)bridgeWillInvalidateModules:(NSNotification *)notification diff --git a/ReactCommon/turbomodule/samples/platform/ios/RCTSampleTurboModule.mm b/ReactCommon/turbomodule/samples/platform/ios/RCTSampleTurboModule.mm index 8439949dfaf591..f59ed56b43abca 100644 --- a/ReactCommon/turbomodule/samples/platform/ios/RCTSampleTurboModule.mm +++ b/ReactCommon/turbomodule/samples/platform/ios/RCTSampleTurboModule.mm @@ -18,7 +18,7 @@ @implementation RCTSampleTurboModule RCT_EXPORT_MODULE() @synthesize bridge = _bridge; -@synthesize turboModuleLookupDelegate = _turboModuleLookupDelegate; +@synthesize turboModuleRegistry = _turboModuleRegistry; // Backward-compatible queue configuration + (BOOL)requiresMainQueueSetup diff --git a/ReactCommon/yoga/yoga/YGNode.cpp b/ReactCommon/yoga/yoga/YGNode.cpp index 23d5c40bc93806..1ee1bde626e8a6 100644 --- a/ReactCommon/yoga/yoga/YGNode.cpp +++ b/ReactCommon/yoga/yoga/YGNode.cpp @@ -307,6 +307,9 @@ void YGNode::setPosition( const YGFlexDirection crossAxis = YGFlexDirectionCross(mainAxis, directionRespectingRoot); + // Here we should check for `YGPositionTypeStatic` and in this case zero inset + // properties (left, right, top, bottom, begin, end). + // https://www.w3.org/TR/css-position-3/#valdef-position-static const YGFloatOptional relativePositionMain = relativePosition(mainAxis, mainSize); const YGFloatOptional relativePositionCross = @@ -440,7 +443,7 @@ float YGNode::resolveFlexShrink() const { bool YGNode::isNodeFlexible() { return ( - (style_.positionType() == YGPositionTypeRelative) && + (style_.positionType() != YGPositionTypeAbsolute) && (resolveFlexGrow() != 0 || resolveFlexShrink() != 0)); } diff --git a/ReactCommon/yoga/yoga/Yoga.cpp b/ReactCommon/yoga/yoga/Yoga.cpp index cb06c10ea3aeb2..97e640754d89f1 100644 --- a/ReactCommon/yoga/yoga/Yoga.cpp +++ b/ReactCommon/yoga/yoga/Yoga.cpp @@ -1131,7 +1131,7 @@ static bool YGIsBaselineLayout(const YGNodeRef node) { const uint32_t childCount = YGNodeGetChildCount(node); for (uint32_t i = 0; i < childCount; i++) { const YGNodeRef child = YGNodeGetChild(node, i); - if (child->getStyle().positionType() == YGPositionTypeRelative && + if (child->getStyle().positionType() != YGPositionTypeAbsolute && child->getStyle().alignSelf() == YGAlignBaseline) { return true; } @@ -2505,7 +2505,7 @@ static void YGJustifyMainAxis( i < collectedFlexItemsValues.endOfLineIndex; i++) { const YGNodeRef child = node->getChild(i); - if (child->getStyle().positionType() == YGPositionTypeRelative) { + if (child->getStyle().positionType() != YGPositionTypeAbsolute) { if (child->marginLeadingValue(mainAxis).unit == YGUnitAuto) { numberOfAutoMarginsOnCurrentLine++; } @@ -2589,7 +2589,7 @@ static void YGJustifyMainAxis( // Now that we placed the element, we need to update the variables. // We need to do that only for relative elements. Absolute elements do not // take part in that phase. - if (childStyle.positionType() == YGPositionTypeRelative) { + if (childStyle.positionType() != YGPositionTypeAbsolute) { if (child->marginLeadingValue(mainAxis).unit == YGUnitAuto) { collectedFlexItemsValues.mainDim += collectedFlexItemsValues.remainingFreeSpace / @@ -3311,7 +3311,7 @@ static void YGNodelayoutImpl( if (child->getStyle().display() == YGDisplayNone) { continue; } - if (child->getStyle().positionType() == YGPositionTypeRelative) { + if (child->getStyle().positionType() != YGPositionTypeAbsolute) { if (child->getLineIndex() != i) { break; } @@ -3353,7 +3353,7 @@ static void YGNodelayoutImpl( if (child->getStyle().display() == YGDisplayNone) { continue; } - if (child->getStyle().positionType() == YGPositionTypeRelative) { + if (child->getStyle().positionType() != YGPositionTypeAbsolute) { switch (YGNodeAlignItem(node, child)) { case YGAlignFlexStart: { child->setLayoutPosition( @@ -3544,7 +3544,7 @@ static void YGNodelayoutImpl( if (performLayout && node->getStyle().flexWrap() == YGWrapWrapReverse) { for (uint32_t i = 0; i < childCount; i++) { const YGNodeRef child = YGNodeGetChild(node, i); - if (child->getStyle().positionType() == YGPositionTypeRelative) { + if (child->getStyle().positionType() != YGPositionTypeAbsolute) { child->setLayoutPosition( node->getLayout().measuredDimensions[dim[crossAxis]] - child->getLayout().position[pos[crossAxis]] - diff --git a/android-patches/patches/OfficeRNHost/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/android-patches/patches/OfficeRNHost/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 82112a2052a785..84144e189abba4 100644 --- a/android-patches/patches/OfficeRNHost/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/android-patches/patches/OfficeRNHost/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -34,7 +34,7 @@ private class ReactContextInitParams { /** Creates a builder that is capable of creating an instance of {@link ReactInstanceManager}. */ public static ReactInstanceManagerBuilder builder() { return new ReactInstanceManagerBuilder(); -@@ -1266,7 +1277,8 @@ public class ReactInstanceManager { +@@ -1267,7 +1278,8 @@ public class ReactInstanceManager { .setJSExecutor(jsExecutor) .setRegistry(nativeModuleRegistry) .setJSBundleLoader(jsBundleLoader) diff --git a/android-patches/patches/OfficeRNHost/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/android-patches/patches/OfficeRNHost/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index 13636884f481f9..5ee0e73029eacd 100644 --- a/android-patches/patches/OfficeRNHost/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/android-patches/patches/OfficeRNHost/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -58,7 +58,7 @@ private native void initializeBridge( @Override public void setSourceURLs(String deviceURL, String remoteURL) { -@@ -398,7 +407,8 @@ public class CatalystInstanceImpl implements CatalystInstance { +@@ -395,7 +404,8 @@ public class CatalystInstanceImpl implements CatalystInstance { mJavaScriptContextHolder.clear(); mHybridData.resetNative(); @@ -68,7 +68,7 @@ public void setSourceURLs(String deviceURL, String remoteURL) { FLog.d( ReactConstants.TAG, "CatalystInstanceImpl.destroy() end"); -@@ -568,6 +578,7 @@ public class CatalystInstanceImpl implements CatalystInstance { +@@ -565,6 +575,7 @@ public class CatalystInstanceImpl implements CatalystInstance { } private native long getJavaScriptContext(); @@ -76,7 +76,7 @@ public void setSourceURLs(String deviceURL, String remoteURL) { private void incrementPendingJSCalls() { int oldPendingCalls = mPendingJSCalls.getAndIncrement(); -@@ -671,6 +682,7 @@ public class CatalystInstanceImpl implements CatalystInstance { +@@ -668,6 +679,7 @@ public class CatalystInstanceImpl implements CatalystInstance { private @Nullable NativeModuleRegistry mRegistry; private @Nullable JavaScriptExecutor mJSExecutor; private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; @@ -84,7 +84,7 @@ private void incrementPendingJSCalls() { public Builder setReactQueueConfigurationSpec( ReactQueueConfigurationSpec ReactQueueConfigurationSpec) { -@@ -698,13 +710,20 @@ public class CatalystInstanceImpl implements CatalystInstance { +@@ -695,13 +707,20 @@ public class CatalystInstanceImpl implements CatalystInstance { return this; } diff --git a/android-patches/patches/V8/ReactAndroid/src/main/jni/react/jni/Android.mk b/android-patches/patches/V8/ReactAndroid/src/main/jni/react/jni/Android.mk index 1141aa30bb0fe6..ac4c33addd8cad 100644 --- a/android-patches/patches/V8/ReactAndroid/src/main/jni/react/jni/Android.mk +++ b/android-patches/patches/V8/ReactAndroid/src/main/jni/react/jni/Android.mk @@ -8,9 +8,9 @@ index 38a51019e..7425e65a5 100644 $(call import-module,runtimeexecutor) +$(call import-module,v8jsi) + include $(REACT_SRC_DIR)/reactperflogger/jni/Android.mk include $(REACT_SRC_DIR)/turbomodule/core/jni/Android.mk - -@@ -83,3 +84,4 @@ include $(REACT_SRC_DIR)/jscexecutor/Android.mk +@@ -84,3 +85,4 @@ include $(REACT_SRC_DIR)/jscexecutor/Android.mk include $(REACT_SRC_DIR)/../hermes/reactexecutor/Android.mk include $(REACT_SRC_DIR)/../hermes/instrumentation/Android.mk include $(REACT_SRC_DIR)/modules/blob/jni/Android.mk diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 490fda8577df6c..62d4c053550b91 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6623300bebd011..186b71557c5013 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 2fe81a7d95e4f9..fbd7c515832dab 100755 --- a/gradlew +++ b/gradlew @@ -82,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -129,6 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index 9109989e3cbf66..a9f778a7a964b6 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -84,6 +84,7 @@ set CMD_LINE_ARGS=%* set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% diff --git a/jest/renderer.js b/jest/renderer.js index 144b53c26292b7..64d56830cf4be0 100644 --- a/jest/renderer.js +++ b/jest/renderer.js @@ -12,7 +12,8 @@ 'use strict'; const React = require('react'); -const ShallowRenderer = require('react-test-renderer/shallow'); + +const ShallowRenderer = require('react-shallow-renderer'); const TestRenderer = require('react-test-renderer'); /* $FlowFixMe(>=0.125.1 site=react_native_fb) This comment suppresses an error diff --git a/jest/setup.js b/jest/setup.js index 7e9eb680489992..d834e52a99fe0f 100644 --- a/jest/setup.js +++ b/jest/setup.js @@ -17,6 +17,10 @@ jest.requireActual('../Libraries/polyfills/error-guard'); global.__DEV__ = true; +global.performance = { + now: jest.fn(Date.now), +}; + global.Promise = jest.requireActual('promise'); global.regeneratorRuntime = jest.requireActual('regenerator-runtime/runtime'); diff --git a/package.json b/package.json index d078180d18b02f..1fec8ec8fafc1a 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,6 @@ "react": "16.13.1" }, "dependencies": { - "@babel/runtime": "^7.0.0", "@react-native-community/cli": "^4.10.0", "@react-native-community/cli-platform-android": "^4.10.0", "@react-native-community/cli-platform-ios": "^4.10.0", @@ -103,9 +102,9 @@ "hermes-engine": "~0.5.0", "invariant": "^2.2.4", "jsc-android": "^245459.0.0", - "metro-babel-register": "0.59.0", - "metro-react-native-babel-transformer": "0.59.0", - "metro-source-map": "0.59.0", + "metro-babel-register": "0.60.0", + "metro-react-native-babel-transformer": "0.60.0", + "metro-source-map": "0.60.0", "nullthrows": "^1.1.1", "pretty-format": "^26.0.1", "promise": "^8.0.3", @@ -141,10 +140,10 @@ "eslint-plugin-jsx-a11y": "6.2.1", "eslint-plugin-prettier": "2.6.2", "eslint-plugin-react": "7.12.4", - "eslint-plugin-react-hooks": "^3.0.0", + "eslint-plugin-react-hooks": "^4.0.7", "eslint-plugin-react-native": "3.8.1", "eslint-plugin-relay": "1.7.1", - "flow-bin": "^0.127.0", + "flow-bin": "^0.129.0", "flow-remove-types": "1.2.3", "hermes-engine-darwin": "~0.5.0", "jest": "^26.0.1", @@ -154,6 +153,7 @@ "prettier": "1.19.1", "react": "16.13.1", "react-test-renderer": "16.13.1", + "react-shallow-renderer": "16.13.1", "shelljs": "^0.7.8", "signedsource": "^1.0.0", "ws": "^6.1.4", diff --git a/packages/eslint-config-react-native-community/package.json b/packages/eslint-config-react-native-community/package.json index 5a8a3dd6df253c..260cde0f7b70d9 100644 --- a/packages/eslint-config-react-native-community/package.json +++ b/packages/eslint-config-react-native-community/package.json @@ -20,7 +20,7 @@ "eslint-plugin-jest": "22.4.1", "eslint-plugin-prettier": "3.1.2", "eslint-plugin-react": "^7.20.0", - "eslint-plugin-react-hooks": "^4.0.4", + "eslint-plugin-react-hooks": "^4.0.7", "eslint-plugin-react-native": "^3.8.1", "prettier": "^2.0.2" }, diff --git a/packages/eslint-config-react-native-community/yarn.lock b/packages/eslint-config-react-native-community/yarn.lock index ab6ed874d7fb01..b4d6255c4cd3f0 100644 --- a/packages/eslint-config-react-native-community/yarn.lock +++ b/packages/eslint-config-react-native-community/yarn.lock @@ -511,10 +511,10 @@ eslint-plugin-prettier@3.1.2: dependencies: prettier-linter-helpers "^1.0.0" -eslint-plugin-react-hooks@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.4.tgz#aed33b4254a41b045818cacb047b81e6df27fa58" - integrity sha512-equAdEIsUETLFNCmmCkiCGq6rkSK5MoJhXFPFYeUebcjKgBmWWcgVOqZyQC8Bv1BwVCnTq9tBxgJFgAJTWoJtA== +eslint-plugin-react-hooks@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.7.tgz#19f9e3d07dd3a0fb9e6f0f07153707feedea8108" + integrity sha512-5PuW2OMHQyMLr/+MqTluYN3/NeJJ1RuvmEp5TR9Xl2gXKxvcusUZuMz8XBUtbELNaiRYWs693LQs0cljKuuHRQ== eslint-plugin-react-native-globals@^0.1.1: version "0.1.2" diff --git a/packages/eslint-plugin-react-native-community/README.md b/packages/eslint-plugin-react-native-community/README.md index 33aac086a30782..d911b282f5f020 100644 --- a/packages/eslint-plugin-react-native-community/README.md +++ b/packages/eslint-plugin-react-native-community/README.md @@ -1,6 +1,6 @@ # eslint-plugin-react-native-community -This plugin is intended to be used in `@react-native-community/eslint-plugin`. You probably want to install that package instead. +This plugin is intended to be used in [`@react-native-community/eslint-config`](https://github.com/facebook/react-native/tree/master/packages/eslint-config-react-native-community). You probably want to install that package instead. ## Installation diff --git a/packages/react-native-codegen/DEFS.bzl b/packages/react-native-codegen/DEFS.bzl index 51f85ed9b27934..b214f686a81034 100644 --- a/packages/react-native-codegen/DEFS.bzl +++ b/packages/react-native-codegen/DEFS.bzl @@ -49,17 +49,8 @@ def rn_codegen_modules( rn_xplat_cxx_library( name = "generated_objcpp_modules-{}".format(name), - ios_srcs = [ - ":{}".format(generate_module_mm_name), - ], - ios_headers = [ - ":{}".format(generate_module_hobjcpp_name), - ], - ios_exported_headers = { - "{}.h".format(native_module_spec_name): ":{}".format(generate_module_hobjcpp_name), - "{}-generated.mm".format(native_module_spec_name): ":{}".format(generate_module_mm_name), - }, header_namespace = native_module_spec_name, + apple_sdks = (IOS), compiler_flags = [ "-fexceptions", "-frtti", @@ -68,8 +59,18 @@ def rn_codegen_modules( ], fbobjc_compiler_flags = get_apple_compiler_flags(), fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), + ios_exported_headers = { + "{}.h".format(native_module_spec_name): ":{}".format(generate_module_hobjcpp_name), + "{}-generated.mm".format(native_module_spec_name): ":{}".format(generate_module_mm_name), + }, + ios_headers = [ + ":{}".format(generate_module_hobjcpp_name), + ], + ios_srcs = [ + ":{}".format(generate_module_mm_name), + ], + labels = ["codegen_rule"], platforms = (APPLE), - apple_sdks = (IOS), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", "-DWITH_FBSYSTRACE=1", @@ -78,7 +79,6 @@ def rn_codegen_modules( deps = [ "//xplat/js:React", ], - labels = ["codegen_rule"], ) def rn_codegen_components( @@ -186,7 +186,6 @@ def rn_codegen_components( # libs rn_xplat_cxx_library( name = "generated_components-{}".format(name), - tests = [":generated_tests-{}".format(name)], srcs = [ ":{}".format(generate_event_emitter_cpp_name), ":{}".format(generate_props_cpp_name), @@ -198,9 +197,7 @@ def rn_codegen_components( ":{}".format(generate_props_h_name), ":{}".format(generate_shadow_node_h_name), ], - ios_headers = [ - ":{}".format(generate_component_hobjcpp_name), - ], + header_namespace = "react/components/{}".format(name), exported_headers = { "ComponentDescriptors.h": ":{}".format(generate_component_descriptor_h_name), "EventEmitters.h": ":{}".format(generate_event_emitter_h_name), @@ -208,10 +205,6 @@ def rn_codegen_components( "RCTComponentViewHelpers.h": ":{}".format(generate_component_hobjcpp_name), "ShadowNodes.h": ":{}".format(generate_shadow_node_h_name), }, - ios_exported_headers = { - "ComponentViewHelpers.h": ":{}".format(generate_component_hobjcpp_name), - }, - header_namespace = "react/components/{}".format(name), compiler_flags = [ "-fexceptions", "-frtti", @@ -220,18 +213,26 @@ def rn_codegen_components( ], fbobjc_compiler_flags = get_apple_compiler_flags(), fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), + ios_exported_headers = { + "ComponentViewHelpers.h": ":{}".format(generate_component_hobjcpp_name), + }, + ios_headers = [ + ":{}".format(generate_component_hobjcpp_name), + ], + labels = ["codegen_rule"], platforms = (ANDROID, APPLE, CXX), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", "-DWITH_FBSYSTRACE=1", ], + tests = [":generated_tests-{}".format(name)], visibility = ["PUBLIC"], deps = [ + "//third-party/glog:glog", "//xplat/fbsystrace:fbsystrace", "//xplat/folly:headers_only", "//xplat/folly:memory", "//xplat/folly:molly", - "//third-party/glog:glog", YOGA_CXX_TARGET, react_native_xplat_target("fabric/debug:debug"), react_native_xplat_target("fabric/core:core"), @@ -240,7 +241,6 @@ def rn_codegen_components( react_native_xplat_target("fabric/imagemanager:imagemanager"), react_native_xplat_target("fabric/components/view:view"), ], - labels = ["codegen_rule"], ) rn_android_library( @@ -248,13 +248,13 @@ def rn_codegen_components( srcs = [ ":{}".format(zip_generated_java_files), ], + labels = ["codegen_rule"], visibility = ["PUBLIC"], deps = [ react_native_dep("third-party/android/androidx:annotation"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/uimanager:uimanager"), ], - labels = ["codegen_rule"], ) # Tests @@ -263,6 +263,7 @@ def rn_codegen_components( srcs = [ ":{}".format(generate_tests_cpp_name), ], + apple_sdks = (IOS, MACOSX), compiler_flags = [ "-fexceptions", "-frtti", @@ -270,13 +271,12 @@ def rn_codegen_components( "-Wall", ], contacts = ["oncall+react_native@xmail.facebook.com"], - apple_sdks = (IOS, MACOSX), + labels = ["codegen_rule"], platforms = (ANDROID, APPLE, CXX), deps = [ "//xplat/third-party/gmock:gtest", ":generated_components-{}".format(name), ], - labels = ["codegen_rule"], ) def rn_codegen_cxx_modules( @@ -316,11 +316,11 @@ def rn_codegen_cxx_modules( headers = [ ":{}".format(generate_module_h_name), ], + header_namespace = "react/modules/{}".format(name), exported_headers = { "NativeModules.cpp": ":{}".format(generate_module_cpp_name), "NativeModules.h": ":{}".format(generate_module_h_name), }, - header_namespace = "react/modules/{}".format(name), compiler_flags = [ "-fexceptions", "-frtti", @@ -329,6 +329,7 @@ def rn_codegen_cxx_modules( ], fbobjc_compiler_flags = get_apple_compiler_flags(), fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(), + labels = ["codegen_rule"], platforms = (ANDROID, APPLE), preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", @@ -338,5 +339,4 @@ def rn_codegen_cxx_modules( exported_deps = [ react_native_xplat_target("turbomodule/core:core"), ], - labels = ["codegen_rule"], ) diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js index c87add8b58c880..365fd30a552f80 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js @@ -11,7 +11,7 @@ 'use strict'; import type {SchemaType} from '../../CodegenSchema'; - +const {getTypeAliasTypeAnnotation} = require('./ObjCppUtils/Utils'); type FilesOutput = Map; const propertyHeaderTemplate = @@ -62,20 +62,25 @@ namespace react { } // namespace facebook `; -function traverseArg(arg, index): string { +function traverseArg(arg, index, aliases): string { function wrap(suffix) { return `args[${index}]${suffix}`; } const {typeAnnotation} = arg; - switch (typeAnnotation.type) { + + const realTypeAnnotation = + typeAnnotation.type === 'TypeAliasTypeAnnotation' + ? getTypeAliasTypeAnnotation(typeAnnotation.name, aliases) + : typeAnnotation; + switch (realTypeAnnotation.type) { case 'ReservedFunctionValueTypeAnnotation': - switch (typeAnnotation.name) { + switch (realTypeAnnotation.name) { case 'RootTag': return wrap('.getNumber()'); default: - (typeAnnotation.name: empty); + (realTypeAnnotation.name: empty); throw new Error( - `Unknown prop type for "${arg.name}", found: "${typeAnnotation.name}"`, + `Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.name}"`, ); } case 'StringTypeAnnotation': @@ -91,27 +96,26 @@ function traverseArg(arg, index): string { case 'FunctionTypeAnnotation': return `std::move(${wrap('.getObject(rt).getFunction(rt)')})`; case 'GenericObjectTypeAnnotation': - case 'TypeAliasTypeAnnotation': // TODO: Handle aliases case 'ObjectTypeAnnotation': return wrap('.getObject(rt)'); case 'AnyTypeAnnotation': throw new Error(`Any type is not allowed in params for "${arg.name}"`); default: // TODO (T65847278): Figure out why this does not work. - // (typeAnnotation.type: empty); + // (type: empty); throw new Error( - `Unknown prop type for "${arg.name}", found: "${typeAnnotation.type}"`, + `Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.type}"`, ); } } -function traverseProperty(property): string { +function traverseProperty(property, aliases): string { const propertyTemplate = property.typeAnnotation.returnTypeAnnotation.type === 'VoidTypeAnnotation' ? voidPropertyTemplate : nonvoidPropertyTemplate; const traversedArgs = property.typeAnnotation.params - .map(traverseArg) + .map((p, i) => traverseArg(p, i, aliases)) .join(', '); return propertyTemplate .replace(/::_PROPERTY_NAME_::/g, property.name) @@ -138,9 +142,9 @@ module.exports = { const modules = Object.keys(nativeModules) .map(name => { - const {properties} = nativeModules[name]; + const {aliases, properties} = nativeModules[name]; const traversedProperties = properties - .map(property => traverseProperty(property)) + .map(property => traverseProperty(property, aliases)) .join('\n'); return moduleTemplate .replace(/::_MODULE_PROPERTIES_::/g, traversedProperties) diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js index 5de8f8ee3c6518..fb41d160b5b336 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js @@ -14,8 +14,12 @@ import type { SchemaType, FunctionTypeAnnotationParamTypeAnnotation, FunctionTypeAnnotationReturn, + TypeAliasTypeAnnotation, + ObjectTypeAliasTypeShape, } from '../../CodegenSchema'; +const {getTypeAliasTypeAnnotation} = require('./ObjCppUtils/Utils'); + type FilesOutput = Map; const moduleTemplate = ` @@ -51,17 +55,23 @@ namespace react { function translatePrimitiveJSTypeToCpp( typeAnnotation: | FunctionTypeAnnotationParamTypeAnnotation - | FunctionTypeAnnotationReturn, + | FunctionTypeAnnotationReturn + | TypeAliasTypeAnnotation, createErrorMessage: (typeName: string) => string, + aliases: $ReadOnly<{[aliasName: string]: ObjectTypeAliasTypeShape, ...}>, ) { - switch (typeAnnotation.type) { + const realTypeAnnotation = + typeAnnotation.type === 'TypeAliasTypeAnnotation' + ? getTypeAliasTypeAnnotation(typeAnnotation.name, aliases) + : typeAnnotation; + switch (realTypeAnnotation.type) { case 'ReservedFunctionValueTypeAnnotation': - switch (typeAnnotation.name) { + switch (realTypeAnnotation.name) { case 'RootTag': return 'double'; default: - (typeAnnotation.name: empty); - throw new Error(createErrorMessage(typeAnnotation.name)); + (realTypeAnnotation.name: empty); + throw new Error(createErrorMessage(realTypeAnnotation.name)); } case 'VoidTypeAnnotation': return 'void'; @@ -74,7 +84,6 @@ function translatePrimitiveJSTypeToCpp( return 'int'; case 'BooleanTypeAnnotation': return 'bool'; - // case 'TypeAliasTypeAnnotation': // TODO: Handle aliases case 'GenericObjectTypeAnnotation': case 'ObjectTypeAnnotation': return 'jsi::Object'; @@ -86,8 +95,8 @@ function translatePrimitiveJSTypeToCpp( return 'jsi::Value'; default: // TODO (T65847278): Figure out why this does not work. - // (typeAnnotation.type: empty); - throw new Error(createErrorMessage(typeAnnotation.type)); + // (type: empty); + throw new Error(createErrorMessage(realTypeAnnotation.type)); } } @@ -114,7 +123,7 @@ module.exports = { const modules = Object.keys(nativeModules) .map(name => { - const {properties} = nativeModules[name]; + const {aliases, properties} = nativeModules[name]; const traversedProperties = properties .map(prop => { const traversedArgs = prop.typeAnnotation.params @@ -123,6 +132,7 @@ module.exports = { param.typeAnnotation, typeName => `Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`, + aliases, ); const isObject = translatedParam.startsWith('jsi::'); return ( @@ -140,6 +150,7 @@ module.exports = { prop.typeAnnotation.returnTypeAnnotation, typeName => `Unsupported return type for ${prop.name}. Found: ${typeName}`, + aliases, ), ) .replace( diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleHObjCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleHObjCpp.js index a2fd47e7c34fc5..41ebb294c0d436 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleHObjCpp.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleHObjCpp.js @@ -15,13 +15,17 @@ import type { FunctionTypeAnnotationParam, FunctionTypeAnnotationReturn, ObjectParamTypeAnnotation, + ObjectTypeAliasTypeShape, } from '../../CodegenSchema'; const { translateObjectsForStructs, capitalizeFirstLetter, + getNamespacedStructName, } = require('./ObjCppUtils/GenerateStructs'); +const {getTypeAliasTypeAnnotation} = require('./ObjCppUtils/Utils'); + type FilesOutput = Map; const moduleTemplate = ` /** @@ -101,20 +105,26 @@ const constants = `- (facebook::react::ModuleConstants string, + aliases: $ReadOnly<{[aliasName: string]: ObjectTypeAliasTypeShape, ...}>, ) { const {nullable, typeAnnotation} = param; function wrapIntoNullableIfNeeded(generatedType: string) { return nullable ? `${generatedType} _Nullable` : generatedType; } - switch (typeAnnotation.type) { + + const realTypeAnnotation = + typeAnnotation.type === 'TypeAliasTypeAnnotation' + ? getTypeAliasTypeAnnotation(typeAnnotation.name, aliases) + : typeAnnotation; + switch (realTypeAnnotation.type) { case 'ReservedFunctionValueTypeAnnotation': - switch (typeAnnotation.name) { + switch (realTypeAnnotation.name) { case 'RootTag': return nullable ? 'NSNumber *' : 'double'; default: - (typeAnnotation.name: empty); - throw new Error(createErrorMessage(typeAnnotation.name)); + (realTypeAnnotation.name: empty); + throw new Error(createErrorMessage(realTypeAnnotation.name)); } case 'StringTypeAnnotation': return wrapIntoNullableIfNeeded('NSString *'); @@ -124,19 +134,21 @@ function translatePrimitiveJSTypeToObjCType( return nullable ? 'NSNumber *' : 'double'; case 'BooleanTypeAnnotation': return nullable ? 'NSNumber * _Nullable' : 'BOOL'; - case 'TypeAliasTypeAnnotation': // TODO: Handle aliases + case 'ObjectTypeAnnotation': + if (typeAnnotation.type === 'TypeAliasTypeAnnotation') { + return getNamespacedStructName(typeAnnotation.name) + ' &'; + } + return wrapIntoNullableIfNeeded('NSDictionary *'); case 'GenericObjectTypeAnnotation': return wrapIntoNullableIfNeeded('NSDictionary *'); case 'ArrayTypeAnnotation': return wrapIntoNullableIfNeeded('NSArray *'); case 'FunctionTypeAnnotation': return 'RCTResponseSenderBlock'; - case 'ObjectTypeAnnotation': - return wrapIntoNullableIfNeeded('NSDictionary *'); default: // TODO (T65847278): Figure out why this does not work. - // (typeAnnotation.type: empty); - throw new Error(createErrorMessage(typeAnnotation.type)); + // (type: empty); + throw new Error(createErrorMessage(realTypeAnnotation.type)); } } @@ -184,23 +196,26 @@ function translatePrimitiveJSTypeToObjCTypeForReturn( function handleArrayOfObjects( objectForGeneratingStructs: Array, - param: FunctionTypeAnnotationParam, + propOrParam: FunctionTypeAnnotationParam, name: string, ) { if ( - param.typeAnnotation.type === 'ArrayTypeAnnotation' && - param.typeAnnotation.elementType && - param.typeAnnotation.elementType.type === 'ObjectTypeAnnotation' && - param.typeAnnotation.elementType.properties + propOrParam.typeAnnotation.type === 'ArrayTypeAnnotation' && + propOrParam.typeAnnotation.elementType ) { - const {elementType} = param.typeAnnotation; - const {properties} = elementType; - if (properties && properties.length > 0) { + const typeAnnotation = propOrParam.typeAnnotation.elementType; + const type = typeAnnotation.type; + + if ( + type === 'ObjectTypeAnnotation' && + typeAnnotation.properties && + typeAnnotation.properties.length > 0 + ) { objectForGeneratingStructs.push({ name, object: { type: 'ObjectTypeAnnotation', - properties, + properties: typeAnnotation.properties, }, }); } @@ -237,7 +252,7 @@ module.exports = { .sort() .map(name => { const objectForGeneratingStructs: Array = []; - const {properties} = nativeModules[name]; + const {aliases, properties} = nativeModules[name]; const implementations = properties .map(prop => { const nativeArgs = prop.typeAnnotation.params @@ -250,36 +265,55 @@ module.exports = { const variableName = capitalizeFirstLetter(prop.name) + capitalizeFirstLetter(param.name); + const structName = 'Spec' + variableName; objectForGeneratingStructs.push({ - name: variableName, + name: structName, object: { type: 'ObjectTypeAnnotation', properties: param.typeAnnotation.properties, }, }); - paramObjCType = `JS::Native::_MODULE_NAME_::::Spec${variableName}&`; + paramObjCType = getNamespacedStructName(structName) + ' &'; - param.typeAnnotation.properties.map(aProp => - handleArrayOfObjects( + param.typeAnnotation.properties.map(aProp => { + return handleArrayOfObjects( objectForGeneratingStructs, aProp, - capitalizeFirstLetter(prop.name) + + 'Spec' + + capitalizeFirstLetter(prop.name) + capitalizeFirstLetter(param.name) + capitalizeFirstLetter(aProp.name) + 'Element', - ), + ); + }); + } else if ( + param.typeAnnotation.type === 'TypeAliasTypeAnnotation' + ) { + const typeAnnotation = getTypeAliasTypeAnnotation( + param.typeAnnotation.name, + aliases, ); + if (typeAnnotation.type === 'ObjectTypeAnnotation') { + paramObjCType = + getNamespacedStructName(param.typeAnnotation.name) + ' &'; + } else { + throw Error( + `Unsupported type for "${param.typeAnnotation.name}". Found: ${typeAnnotation.type}`, + ); + } } else { paramObjCType = translatePrimitiveJSTypeToObjCType( param, typeName => `Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`, + aliases, ); handleArrayOfObjects( objectForGeneratingStructs, param, - capitalizeFirstLetter(prop.name) + + 'Spec' + + capitalizeFirstLetter(prop.name) + capitalizeFirstLetter(param.name) + 'Element', ); @@ -296,8 +330,7 @@ module.exports = { returnTypeAnnotation.properties ) { objectForGeneratingStructs.push({ - name: capitalizeFirstLetter(prop.name) + 'ReturnType', - + name: 'Spec' + capitalizeFirstLetter(prop.name) + 'ReturnType', object: { type: 'ObjectTypeAnnotation', properties: returnTypeAnnotation.properties, @@ -327,10 +360,44 @@ module.exports = { return implementation; }) .join('\n'); + + Object.keys(aliases) + .reverse() + .map((aliasName, i) => { + const alias = aliases[aliasName]; + + let paramObjCType = ''; + + switch (alias.type) { + case 'ObjectTypeAnnotation': + if (alias.properties) { + objectForGeneratingStructs.push({ + name: aliasName, + object: { + type: 'ObjectTypeAnnotation', + properties: alias.properties, + }, + }); + paramObjCType = getNamespacedStructName(alias.name) + ' &'; + } + break; + default: + throw Error( + `Unsupported type for "${aliasName}". Found: ${alias.type}`, + ); + } + return `${i === 0 ? '' : aliasName}:(${paramObjCType})${aliasName}`; + }) + .join('\n'); + return protocolTemplate .replace( /::_STRUCTS_::/g, - translateObjectsForStructs(objectForGeneratingStructs, name), + translateObjectsForStructs( + objectForGeneratingStructs, + name, + aliases, + ), ) .replace(/::_MODULE_PROPERTIES_::/g, implementations) .replace(/::_MODULE_NAME_::/g, name) diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleMm.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleMm.js index 448541cd995e96..251c242a7c39c2 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleMm.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleMm.js @@ -13,7 +13,10 @@ import type {SchemaType, NativeModuleShape} from '../../CodegenSchema'; const {capitalizeFirstLetter} = require('./ObjCppUtils/GenerateStructs'); -const {flatObjects} = require('./ObjCppUtils/Utils'); +const { + flatObjects, + getTypeAliasTypeAnnotation, +} = require('./ObjCppUtils/Utils'); type FilesOutput = Map; @@ -40,16 +43,16 @@ const moduleTemplate = ` }`.trim(); const getterTemplate = ` -@implementation RCTCxxConvert (Native::_MODULE_NAME_::_Spec::_GETTER_NAME_::) -+ (RCTManagedPointer *)JS_Native::_MODULE_NAME_::_Spec::_GETTER_NAME_:::(id)json +@implementation RCTCxxConvert (Native::_MODULE_NAME_::_::_GETTER_NAME_::) ++ (RCTManagedPointer *)JS_Native::_MODULE_NAME_::_::_GETTER_NAME_:::(id)json { - return facebook::react::managedPointer(json); + return facebook::react::managedPointer(json); } @end `; const argConvertionTemplate = - '\n setMethodArgConversionSelector(@"::_ARG_NAME_::", ::_ARG_NUMBER_::, @"JS_Native::_MODULE_NAME_::_Spec::_SELECTOR_NAME_:::");'; + '\n setMethodArgConversionSelector(@"::_ARG_NAME_::", ::_ARG_NUMBER_::, @"JS_Native::_MODULE_NAME_::_::_SELECTOR_NAME_:::");'; const template = ` /** @@ -171,46 +174,65 @@ module.exports = { const module: NativeModuleShape = nativeModules[moduleName]; return acc.concat( flatObjects( - module.properties.reduce((moduleAcc, property) => { - const {returnTypeAnnotation} = property.typeAnnotation; - if (returnTypeAnnotation.type === 'ObjectTypeAnnotation') { - const {properties} = returnTypeAnnotation; - if (properties) { - moduleAcc.push({ - name: capitalizeFirstLetter(property.name) + 'ReturnType', - object: { - type: 'ObjectTypeAnnotation', - properties: properties, - }, - }); + module.properties + .reduce((moduleAcc, property) => { + const {returnTypeAnnotation} = property.typeAnnotation; + if (returnTypeAnnotation.type === 'ObjectTypeAnnotation') { + const {properties} = returnTypeAnnotation; + if (properties) { + moduleAcc.push({ + name: + 'Spec' + + capitalizeFirstLetter(property.name) + + 'ReturnType', + object: { + type: 'ObjectTypeAnnotation', + properties: properties, + }, + }); + } } - } - if (property.typeAnnotation.params) { - return moduleAcc.concat( - property.typeAnnotation.params - .map(param => { - if ( - param.typeAnnotation.type === 'ObjectTypeAnnotation' - ) { - const {properties} = param.typeAnnotation; - if (properties) { - return { - name: - capitalizeFirstLetter(property.name) + - capitalizeFirstLetter(param.name), - object: { - type: 'ObjectTypeAnnotation', - properties: properties, - }, - }; + if (property.typeAnnotation.params) { + return moduleAcc.concat( + property.typeAnnotation.params + .map(param => { + if ( + param.typeAnnotation.type === 'ObjectTypeAnnotation' + ) { + const {properties} = param.typeAnnotation; + if (properties) { + return { + name: + 'Spec' + + capitalizeFirstLetter(property.name) + + capitalizeFirstLetter(param.name), + object: { + type: 'ObjectTypeAnnotation', + properties: properties, + }, + }; + } } - } - }) - .filter(Boolean), - ); - } - return moduleAcc; - }, []), + }) + .filter(Boolean), + ); + } + return moduleAcc; + }, []) + .concat( + Object.keys(module.aliases).map(aliasName => { + const alias = getTypeAliasTypeAnnotation( + aliasName, + module.aliases, + ); + return { + name: aliasName, + object: {type: alias.type, properties: alias.properties}, + }; + }), + ), + false, + module.aliases, ) .map(object => getterTemplate @@ -224,7 +246,7 @@ module.exports = { const modules = Object.keys(nativeModules) .map(name => { - const {properties} = nativeModules[name]; + const {aliases, properties} = nativeModules[name]; const translatedMethods = properties .map(property => translateMethodForImplementation(property)) .join('\n'); @@ -254,19 +276,33 @@ module.exports = { properties .map(({name: propertyName, typeAnnotation: {params}}) => params - .map((param, index) => - param.typeAnnotation.type === 'ObjectTypeAnnotation' && - param.typeAnnotation.properties - ? argConvertionTemplate - .replace( - '::_SELECTOR_NAME_::', - capitalizeFirstLetter(propertyName) + - capitalizeFirstLetter(param.name), + .map((param, index) => { + const typeAnnotation = + param.typeAnnotation.type === 'TypeAliasTypeAnnotation' + ? getTypeAliasTypeAnnotation( + param.typeAnnotation.name, + aliases, ) - .replace('::_ARG_NUMBER_::', index.toString()) - .replace('::_ARG_NAME_::', propertyName) - : '', - ) + : param.typeAnnotation; + const selectorName = + param.typeAnnotation.type === 'TypeAliasTypeAnnotation' + ? param.typeAnnotation.name + : 'Spec' + + capitalizeFirstLetter(propertyName) + + capitalizeFirstLetter(param.name); + + if ( + typeAnnotation.type === 'ObjectTypeAnnotation' && + typeAnnotation.properties + ) { + return argConvertionTemplate + .replace('::_SELECTOR_NAME_::', selectorName) + .replace('::_ARG_NUMBER_::', index.toString()) + .replace('::_ARG_NAME_::', propertyName); + } + + return ''; + }) .join(''), ) .join(''), diff --git a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js index 9be5bcbd53729c..e860a663270140 100644 --- a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js +++ b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructs.js @@ -10,11 +10,15 @@ 'use strict'; -import type {ObjectParamTypeAnnotation} from '../../../CodegenSchema'; +import type { + ObjectParamTypeAnnotation, + ObjectTypeAliasTypeShape, +} from '../../../CodegenSchema'; const { flatObjects, capitalizeFirstLetter, getSafePropertyName, + getTypeAliasTypeAnnotation, } = require('./Utils'); const {generateStructsForConstants} = require('./GenerateStructsForConstants'); @@ -25,42 +29,38 @@ const template = ` const structTemplate = ` namespace JS { namespace Native::_MODULE_NAME_:: { - struct Spec::_STRUCT_NAME_:: { + struct ::_STRUCT_NAME_:: { ::_STRUCT_PROPERTIES_:: - Spec::_STRUCT_NAME_::(NSDictionary *const v) : _v(v) {} + ::_STRUCT_NAME_::(NSDictionary *const v) : _v(v) {} private: NSDictionary *_v; }; } } -@interface RCTCxxConvert (Native::_MODULE_NAME_::_Spec::_STRUCT_NAME_::) -+ (RCTManagedPointer *)JS_Native::_MODULE_NAME_::_Spec::_STRUCT_NAME_:::(id)json; +@interface RCTCxxConvert (Native::_MODULE_NAME_::_::_STRUCT_NAME_::) ++ (RCTManagedPointer *)JS_Native::_MODULE_NAME_::_::_STRUCT_NAME_:::(id)json; @end `; const inlineTemplate = ` -inline ::_RETURN_TYPE_::JS::Native::_MODULE_NAME_::::Spec::_STRUCT_NAME_::::::_PROPERTY_NAME_::() const +inline ::_RETURN_TYPE_::JS::Native::_MODULE_NAME_::::::_STRUCT_NAME_::::::_PROPERTY_NAME_::() const { id const p = _v[@"::_PROPERTY_NAME_::"]; return ::_RETURN_VALUE_::; } `; -function getNamespacedStructName( - structName: string, - property: ObjectParamTypeAnnotation, -) { - return `JS::Native::_MODULE_NAME_::::Spec${structName}${capitalizeFirstLetter( - getSafePropertyName(property), - )}`; +function getNamespacedStructName(structName: string): string { + return `JS::Native::_MODULE_NAME_::::${structName}`; } function getElementTypeForArray( property: ObjectParamTypeAnnotation, name: string, moduleName: string, + aliases: $ReadOnly<{[aliasName: string]: ObjectTypeAliasTypeShape, ...}>, ): string { const {typeAnnotation} = property; @@ -81,7 +81,11 @@ function getElementTypeForArray( return 'id'; } - const {type} = typeAnnotation.elementType; + const type = + typeAnnotation.elementType.type === 'TypeAliasTypeAnnotation' + ? getTypeAliasTypeAnnotation(typeAnnotation.elementType.name, aliases) + .type + : typeAnnotation.elementType.type; switch (type) { case 'StringTypeAnnotation': return 'NSString *'; @@ -91,8 +95,11 @@ function getElementTypeForArray( case 'Int32TypeAnnotation': return 'double'; case 'ObjectTypeAnnotation': - return getNamespacedStructName(name, property) + 'Element'; - case 'TypeAliasTypeAnnotation': // TODO: Handle aliases + const structName = + typeAnnotation.elementType.type === 'TypeAliasTypeAnnotation' + ? typeAnnotation.elementType.name + : `${property.name}Element`; + return getNamespacedStructName(structName); case 'GenericObjectTypeAnnotation': // TODO(T67565166): Generic objects are not type safe and should be disallowed in the schema. This case should throw an error once it is disallowed in schema. console.error( @@ -118,6 +125,7 @@ function getInlineMethodSignature( property: ObjectParamTypeAnnotation, name: string, moduleName: string, + aliases: $ReadOnly<{[aliasName: string]: ObjectTypeAliasTypeShape, ...}>, ): string { const {typeAnnotation} = property; function markOptionalTypeIfNecessary(type: string) { @@ -135,14 +143,28 @@ function getInlineMethodSignature( return `id ${getSafePropertyName(property)}() const;`; } - switch (typeAnnotation.type) { + const realTypeAnnotation = + typeAnnotation.type === 'TypeAliasTypeAnnotation' + ? getTypeAliasTypeAnnotation(typeAnnotation.name, aliases) + : typeAnnotation; + + const variableName = + typeAnnotation.type === 'TypeAliasTypeAnnotation' + ? `${capitalizeFirstLetter(typeAnnotation.name)}` + : `${capitalizeFirstLetter(name)}${capitalizeFirstLetter( + getSafePropertyName(property), + )}`; + + switch (realTypeAnnotation.type) { case 'ReservedFunctionValueTypeAnnotation': - switch (typeAnnotation.name) { + switch (realTypeAnnotation.name) { case 'RootTag': return `double ${getSafePropertyName(property)}() const;`; default: - (typeAnnotation.name: empty); - throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`); + (realTypeAnnotation.name: empty); + throw new Error( + `Unknown prop type, found: ${realTypeAnnotation.name}"`, + ); } case 'StringTypeAnnotation': return `NSString *${getSafePropertyName(property)}() const;`; @@ -157,10 +179,9 @@ function getInlineMethodSignature( property, )}() const;`; case 'ObjectTypeAnnotation': - return ( - markOptionalTypeIfNecessary(getNamespacedStructName(name, property)) + - ` ${getSafePropertyName(property)}() const;` - ); + return `${markOptionalTypeIfNecessary( + getNamespacedStructName(variableName), + )} ${getSafePropertyName(property)}() const;`; case 'GenericObjectTypeAnnotation': case 'AnyTypeAnnotation': return `id ${ @@ -172,11 +193,12 @@ function getInlineMethodSignature( property, name, moduleName, + aliases, )}>`, )} ${getSafePropertyName(property)}() const;`; case 'FunctionTypeAnnotation': default: - throw new Error(`Unknown prop type, found: ${typeAnnotation.type}"`); + throw new Error(`Unknown prop type, found: ${realTypeAnnotation.type}"`); } } @@ -184,6 +206,7 @@ function getInlineMethodImplementation( property: ObjectParamTypeAnnotation, name: string, moduleName: string, + aliases: $ReadOnly<{[aliasName: string]: ObjectTypeAliasTypeShape, ...}>, ): string { const {typeAnnotation} = property; function markOptionalTypeIfNecessary(type: string): string { @@ -216,7 +239,12 @@ function getInlineMethodImplementation( throw new Error(`Cannot get array element type for ${name}`); } - const {type} = typeAnnotation.elementType; + const type = + typeAnnotation.elementType.type === 'TypeAliasTypeAnnotation' + ? getTypeAliasTypeAnnotation(typeAnnotation.elementType.name, aliases) + .type + : typeAnnotation.elementType.type; + switch (type) { case 'StringTypeAnnotation': return `RCTBridgingToString(${element})`; @@ -228,8 +256,11 @@ function getInlineMethodImplementation( case 'BooleanTypeAnnotation': return `RCTBridgingToBool(${element})`; case 'ObjectTypeAnnotation': - return `${getNamespacedStructName(name, property)}Element(${element})`; - case 'TypeAliasTypeAnnotation': // TODO: Handle aliases + const structName = + typeAnnotation.elementType.type === 'TypeAliasTypeAnnotation' + ? `${typeAnnotation.elementType.name}(${element})` + : `${getSafePropertyName(property)}Element(${element})`; + return getNamespacedStructName(structName); case 'GenericObjectTypeAnnotation': return element; case 'AnyObjectTypeAnnotation': @@ -260,16 +291,23 @@ function getInlineMethodImplementation( .replace(/::_RETURN_VALUE_::/, 'p'); } - switch (typeAnnotation.type) { + const realTypeAnnotation = + typeAnnotation.type === 'TypeAliasTypeAnnotation' + ? getTypeAliasTypeAnnotation(typeAnnotation.name, aliases) + : typeAnnotation; + + switch (realTypeAnnotation.type) { case 'ReservedFunctionValueTypeAnnotation': - switch (typeAnnotation.name) { + switch (realTypeAnnotation.name) { case 'RootTag': return inlineTemplate .replace(/::_RETURN_TYPE_::/, 'double ') .replace(/::_RETURN_VALUE_::/, 'RCTBridgingToDouble(p)'); default: - (typeAnnotation.name: empty); - throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`); + (realTypeAnnotation.name: empty); + throw new Error( + `Unknown prop type, found: ${realTypeAnnotation.name}"`, + ); } case 'StringTypeAnnotation': return inlineTemplate @@ -300,19 +338,21 @@ function getInlineMethodImplementation( ) .replace(/::_RETURN_VALUE_::/, 'p'); case 'ObjectTypeAnnotation': + const structName = + typeAnnotation.type === 'TypeAliasTypeAnnotation' + ? `${capitalizeFirstLetter(typeAnnotation.name)}` + : `${name}${capitalizeFirstLetter(getSafePropertyName(property))}`; + const namespacedStructName = getNamespacedStructName(structName); return inlineTemplate .replace( /::_RETURN_TYPE_::/, - markOptionalTypeIfNecessary(getNamespacedStructName(name, property)), + markOptionalTypeIfNecessary(namespacedStructName), ) .replace( /::_RETURN_VALUE_::/, property.optional - ? `(p == nil ? folly::none : folly::make_optional(${getNamespacedStructName( - name, - property, - )}(p)))` - : `${getNamespacedStructName(name, property)}(p)`, + ? `(p == nil ? folly::none : folly::make_optional(${namespacedStructName}(p)))` + : `${namespacedStructName}(p)`, ); case 'ArrayTypeAnnotation': return inlineTemplate @@ -323,6 +363,7 @@ function getInlineMethodImplementation( property, name, moduleName, + aliases, )}>`, ), ) @@ -332,13 +373,14 @@ function getInlineMethodImplementation( property, name, moduleName, + aliases, )}(id itemValue_0) { return ${bridgeArrayElementValueIfNecessary( 'itemValue_0', )}; })`, ); case 'FunctionTypeAnnotation': default: - throw new Error(`Unknown prop type, found: ${typeAnnotation.type}"`); + throw new Error(`Unknown prop type, found: ${realTypeAnnotation.type}"`); } } @@ -353,15 +395,21 @@ function translateObjectsForStructs( |}>, >, moduleName: string, + aliases: $ReadOnly<{[aliasName: string]: ObjectTypeAliasTypeShape, ...}>, ): string { - const flattenObjects = flatObjects(annotations); + const flattenObjects = flatObjects(annotations, false, aliases); const translatedInlineMethods = flattenObjects .reduce( (acc, object) => acc.concat( object.properties.map(property => - getInlineMethodImplementation(property, object.name, moduleName) + getInlineMethodImplementation( + property, + object.name, + moduleName, + aliases, + ) .replace(/::_PROPERTY_NAME_::/g, getSafePropertyName(property)) .replace(/::_STRUCT_NAME_::/g, object.name), ), @@ -371,22 +419,26 @@ function translateObjectsForStructs( .join('\n'); const translatedStructs = flattenObjects - .map(object => - structTemplate + .map(object => { + return structTemplate .replace( /::_STRUCT_PROPERTIES_::/g, object.properties .map(property => - getInlineMethodSignature(property, object.name, moduleName), + getInlineMethodSignature( + property, + object.name, + moduleName, + aliases, + ), ) .join('\n '), ) - .replace(/::_STRUCT_NAME_::/g, object.name), - ) + .replace(/::_STRUCT_NAME_::/g, object.name); + }) .reverse() .join('\n'); - - const translatedConstants = generateStructsForConstants(annotations); + const translatedConstants = generateStructsForConstants(annotations, aliases); return template .replace(/::_STRUCTS_::/, translatedStructs) @@ -396,4 +448,5 @@ function translateObjectsForStructs( module.exports = { translateObjectsForStructs, capitalizeFirstLetter, + getNamespacedStructName, }; diff --git a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructsForConstants.js b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructsForConstants.js index 5d8dc29af72748..614f2fe4a42c48 100644 --- a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructsForConstants.js +++ b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/GenerateStructsForConstants.js @@ -10,8 +10,15 @@ 'use strict'; -import type {ObjectParamTypeAnnotation} from '../../../CodegenSchema'; -const {flatObjects, capitalizeFirstLetter} = require('./Utils'); +import type { + ObjectParamTypeAnnotation, + ObjectTypeAliasTypeShape, +} from '../../../CodegenSchema'; +const { + flatObjects, + capitalizeFirstLetter, + getTypeAliasTypeAnnotation, +} = require('./Utils'); const structTemplate = ` namespace JS { @@ -54,6 +61,7 @@ inline JS::Native::_MODULE_NAME_::::::_STRUCT_NAME_::::Builder::Builder(::_STRUC function getBuilderInputFieldDeclaration( property: ObjectParamTypeAnnotation, name: string, + aliases: $ReadOnly<{[aliasName: string]: ObjectTypeAliasTypeShape, ...}>, ): string { function markRequiredIfNecessary(annotation) { if (!property.optional) { @@ -70,14 +78,26 @@ function getBuilderInputFieldDeclaration( ); } - switch (typeAnnotation.type) { + const realTypeAnnotation = + typeAnnotation.type === 'TypeAliasTypeAnnotation' + ? getTypeAliasTypeAnnotation(typeAnnotation.name, aliases) + : typeAnnotation; + + const variableName = + typeAnnotation.type === 'TypeAliasTypeAnnotation' + ? typeAnnotation.name + : `${name}${capitalizeFirstLetter(property.name)}`; + + switch (realTypeAnnotation.type) { case 'ReservedFunctionValueTypeAnnotation': - switch (typeAnnotation.name) { + switch (realTypeAnnotation.name) { case 'RootTag': return markRequiredIfNecessary('double'); default: - (typeAnnotation.name: empty); - throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`); + (realTypeAnnotation.name: empty); + throw new Error( + `Unknown prop type, found: ${realTypeAnnotation.name}"`, + ); } case 'StringTypeAnnotation': if (property.optional) { @@ -92,11 +112,8 @@ function getBuilderInputFieldDeclaration( return markRequiredIfNecessary('bool'); case 'ObjectTypeAnnotation': return markRequiredIfNecessary( - `JS::Native::_MODULE_NAME_::::Spec${name}${capitalizeFirstLetter( - property.name, - )}::Builder`, + `JS::Native::_MODULE_NAME_::::${variableName}::Builder`, ); - case 'TypeAliasTypeAnnotation': // TODO: Handle aliases case 'GenericObjectTypeAnnotation': case 'AnyTypeAnnotation': if (property.optional) { @@ -107,7 +124,7 @@ function getBuilderInputFieldDeclaration( return markRequiredIfNecessary('std::vector>'); case 'FunctionTypeAnnotation': default: - throw new Error(`Unknown prop type, found: ${typeAnnotation.type}"`); + throw new Error(`Unknown prop type, found: ${realTypeAnnotation.type}"`); } } @@ -160,7 +177,10 @@ function unsafeGetter(name: string, optional: boolean) { `.trim(); } -function getObjectProperty(property: ObjectParamTypeAnnotation): string { +function getObjectProperty( + property: ObjectParamTypeAnnotation, + aliases: $ReadOnly<{[aliasName: string]: ObjectTypeAliasTypeShape, ...}>, +): string { const {typeAnnotation} = property; // TODO(T67898313): Workaround for NativeLinking's use of union type. This check may be removed once typeAnnotation is non-optional. @@ -170,13 +190,22 @@ function getObjectProperty(property: ObjectParamTypeAnnotation): string { ); } - switch (typeAnnotation.type) { + const type = + typeAnnotation.type === 'TypeAliasTypeAnnotation' + ? getTypeAliasTypeAnnotation(typeAnnotation.name, aliases).type + : typeAnnotation.type; + + switch (type) { case 'ReservedFunctionValueTypeAnnotation': + if (typeAnnotation.name == null) { + throw new Error(`Prop type ${type} has no name.`); + } switch (typeAnnotation.name) { case 'RootTag': return numberGetter(property.name, property.optional); default: - (typeAnnotation.name: empty); + // TODO (T65847278): Figure out why this does not work. + // (typeAnnotation.name: empty); throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`); } case 'NumberTypeAnnotation': @@ -186,7 +215,6 @@ function getObjectProperty(property: ObjectParamTypeAnnotation): string { case 'BooleanTypeAnnotation': return boolGetter(property.name, property.optional); case 'StringTypeAnnotation': - case 'TypeAliasTypeAnnotation': // TODO: Handle aliases case 'GenericObjectTypeAnnotation': case 'AnyTypeAnnotation': return safeGetter(property.name, property.optional); @@ -196,7 +224,7 @@ function getObjectProperty(property: ObjectParamTypeAnnotation): string { return arrayGetter(property.name, property.optional); case 'FunctionTypeAnnotation': default: - throw new Error(`Unknown prop type, found: ${typeAnnotation.type}"`); + throw new Error(`Unknown prop type, found: ${type}"`); } } @@ -210,8 +238,9 @@ function generateStructsForConstants( |}>, |}>, >, + aliases: $ReadOnly<{[aliasName: string]: ObjectTypeAliasTypeShape, ...}>, ): string { - return flatObjects(annotations, true) + return flatObjects(annotations, true, aliases) .reduce( (acc, object) => acc.concat( @@ -220,14 +249,18 @@ function generateStructsForConstants( /::_INPUT_::/g, object.properties .map(property => - getBuilderInputFieldDeclaration(property, object.name), + getBuilderInputFieldDeclaration( + property, + object.name, + aliases, + ), ) .join('\n '), ) .replace( /::_PROPERTIES_::/g, object.properties - .map(property => getObjectProperty(property)) + .map(property => getObjectProperty(property, aliases)) .join('\n'), ) .replace(/::_STRUCT_NAME_::/g, object.name), diff --git a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/Utils.js b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/Utils.js index eb7d73974e8781..8c921c075c6082 100644 --- a/packages/react-native-codegen/src/generators/modules/ObjCppUtils/Utils.js +++ b/packages/react-native-codegen/src/generators/modules/ObjCppUtils/Utils.js @@ -10,7 +10,10 @@ 'use strict'; -import type {ObjectParamTypeAnnotation} from '../../../CodegenSchema'; +import type { + ObjectParamTypeAnnotation, + ObjectTypeAliasTypeShape, +} from '../../../CodegenSchema'; function capitalizeFirstLetter(string: string): string { return string.charAt(0).toUpperCase() + string.slice(1); @@ -27,6 +30,7 @@ function flatObjects( |}>, >, forConstants: boolean = false, + aliases: $ReadOnly<{[aliasName: string]: ObjectTypeAliasTypeShape, ...}>, ): $ReadOnlyArray< $ReadOnly<{| name: string, @@ -37,17 +41,23 @@ function flatObjects( properties: $ReadOnlyArray, name: string, |}> = annotations - .map(annotation => ({ - name: annotation.name, - properties: annotation.object.properties, - })) + .map(annotation => { + if (annotation.object.type === 'TypeAliasTypeAnnotation') { + const alias = getTypeAliasTypeAnnotation(annotation.name, aliases); + return {name: annotation.name, properties: alias.properties}; + } + return { + name: annotation.name, + properties: annotation.object.properties, + }; + }) .filter( annotation => - (annotation.name === 'GetConstantsReturnType') === forConstants, + (annotation.name === 'SpecGetConstantsReturnType') === forConstants, ) .filter( annotation => - annotation.name !== 'GetConstantsReturnType' || + annotation.name !== 'SpecGetConstantsReturnType' || annotation.properties.length > 0, ); @@ -94,8 +104,35 @@ function getSafePropertyName(property: ObjectParamTypeAnnotation): string { return property.name; } +function getTypeAliasTypeAnnotation( + name: string, + aliases: $ReadOnly<{[aliasName: string]: ObjectTypeAliasTypeShape, ...}>, +): $ReadOnly { + const typeAnnotation = aliases[name]; + if (!typeAnnotation) { + throw Error(`No type annotation found for "${name}" in schema`); + } + if (typeAnnotation.type === 'ObjectTypeAnnotation') { + if (typeAnnotation.properties) { + return typeAnnotation; + } + + throw new Error( + `Unsupported type for "${name}". Please provide properties.`, + ); + } + if (typeAnnotation.type === 'TypeAliasTypeAnnotation') { + return getTypeAliasTypeAnnotation(typeAnnotation.name, aliases); + } + + throw Error( + `Unsupported type annotation in alias "${name}", found: ${typeAnnotation.type}`, + ); +} + module.exports = { flatObjects, capitalizeFirstLetter, getSafePropertyName, + getTypeAliasTypeAnnotation, }; diff --git a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js index 6feda946974e16..cea663c8b1b425 100644 --- a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js @@ -707,10 +707,806 @@ const COMPLEX_OBJECTS: SchemaType = { }, }, }; + +const NATIVE_MODULES_WITH_TYPE_ALIASES: SchemaType = { + modules: { + AliasTurboModule: { + nativeModules: { + AliasTurboModule: { + aliases: { + Options: { + properties: [ + { + optional: false, + name: 'offset', + typeAnnotation: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'x', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'y', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + ], + }, + }, + { + optional: false, + name: 'size', + typeAnnotation: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'width', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'height', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + ], + }, + }, + { + optional: true, + name: 'displaySize', + typeAnnotation: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'width', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'height', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + ], + }, + }, + { + optional: true, + name: 'resizeMode', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: true, + name: 'allowExternalStorage', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + }, + }, + ], + type: 'ObjectTypeAnnotation', + }, + }, + properties: [ + { + name: 'getConstants', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + nullable: false, + type: 'ObjectTypeAnnotation', + properties: [], + }, + params: [], + optional: false, + }, + }, + { + name: 'cropImage', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + nullable: false, + type: 'VoidTypeAnnotation', + }, + params: [ + { + nullable: false, + name: 'cropData', + typeAnnotation: { + type: 'TypeAliasTypeAnnotation', + name: 'Options', + }, + }, + ], + optional: false, + }, + }, + ], + }, + }, + }, + }, +}; + +const REAL_MODULE_EXAMPLE: SchemaType = { + modules: { + NativeCameraRollManager: { + nativeModules: { + CameraRollManager: { + aliases: { + PhotoIdentifierImage: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'uri', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: false, + name: 'playableDuration', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'width', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'height', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: true, + name: 'isStored', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + }, + }, + { + optional: false, + name: 'filename', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + ], + }, + PhotoIdentifier: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'node', + typeAnnotation: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'image', + typeAnnotation: { + type: 'TypeAliasTypeAnnotation', + name: 'PhotoIdentifierImage', + }, + }, + { + optional: false, + name: 'type', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: false, + name: 'group_name', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: false, + name: 'timestamp', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'location', + typeAnnotation: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'longitude', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'latitude', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: true, + name: 'altitude', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: true, + name: 'heading', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: true, + name: 'speed', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + PhotoIdentifiersPage: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'edges', + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'TypeAliasTypeAnnotation', + name: 'PhotoIdentifier', + }, + }, + }, + { + optional: false, + name: 'page_info', + typeAnnotation: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'has_next_page', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + }, + }, + { + optional: true, + name: 'start_cursor', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: true, + name: 'end_cursor', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + ], + }, + }, + ], + }, + GetPhotosParams: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'first', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: true, + name: 'after', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: true, + name: 'groupName', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: true, + name: 'groupTypes', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: true, + name: 'assetType', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: true, + name: 'maxSize', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: true, + name: 'mimeTypes', + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'StringTypeAnnotation', + }, + }, + }, + ], + }, + }, + properties: [ + { + name: 'getConstants', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + nullable: false, + type: 'ObjectTypeAnnotation', + properties: [], + }, + params: [], + optional: false, + }, + }, + { + name: 'getPhotos', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'GenericPromiseTypeAnnotation', + nullable: false, + }, + params: [ + { + nullable: false, + name: 'params', + typeAnnotation: { + type: 'TypeAliasTypeAnnotation', + name: 'GetPhotosParams', + }, + }, + ], + optional: false, + }, + }, + { + name: 'saveToCameraRoll', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'GenericPromiseTypeAnnotation', + nullable: false, + }, + params: [ + { + nullable: false, + name: 'uri', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + nullable: false, + name: 'type', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + ], + optional: false, + }, + }, + { + name: 'deletePhotos', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'GenericPromiseTypeAnnotation', + nullable: false, + }, + params: [ + { + name: 'assets', + nullable: false, + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'StringTypeAnnotation', + }, + }, + }, + ], + optional: false, + }, + }, + ], + }, + }, + }, + NativeImagePickerIOS: { + nativeModules: { + ImagePickerIOS: { + aliases: {}, + properties: [ + { + name: 'openCameraDialog', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + nullable: false, + type: 'VoidTypeAnnotation', + }, + params: [ + { + nullable: false, + name: 'config', + typeAnnotation: { + type: 'ObjectTypeAnnotation', + properties: [ + { + optional: false, + name: 'unmirrorFrontFacingCamera', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + }, + }, + { + optional: false, + name: 'videoMode', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + }, + }, + ], + }, + }, + { + name: 'successCallback', + nullable: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + }, + }, + { + name: 'cancelCallback', + nullable: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + }, + }, + ], + optional: false, + }, + }, + ], + }, + }, + }, + NativeExceptionsManager: { + nativeModules: { + ExceptionsManager: { + aliases: { + StackFrame: { + properties: [ + { + optional: true, + name: 'column', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'file', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: true, + name: 'lineNumber', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'methodName', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: true, + name: 'collapse', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + }, + }, + ], + type: 'ObjectTypeAnnotation', + }, + ExceptionData: { + properties: [ + { + optional: false, + name: 'message', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: false, + name: 'originalMessage', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: false, + name: 'name', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: false, + name: 'componentStack', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + optional: false, + name: 'stack', + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'TypeAliasTypeAnnotation', + name: 'StackFrame', + }, + }, + }, + { + optional: false, + name: 'id', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + { + optional: false, + name: 'isFatal', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + }, + }, + { + optional: true, + name: 'extraData', + typeAnnotation: { + type: 'GenericObjectTypeAnnotation', + }, + }, + ], + type: 'ObjectTypeAnnotation', + }, + }, + properties: [ + { + name: 'reportFatalException', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + nullable: false, + type: 'VoidTypeAnnotation', + }, + params: [ + { + nullable: false, + name: 'message', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + name: 'stack', + nullable: false, + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'TypeAliasTypeAnnotation', + name: 'StackFrame', + }, + }, + }, + { + nullable: false, + name: 'exceptionId', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + ], + optional: false, + }, + }, + { + name: 'reportSoftException', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + nullable: false, + type: 'VoidTypeAnnotation', + }, + params: [ + { + nullable: false, + name: 'message', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + name: 'stack', + nullable: false, + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'TypeAliasTypeAnnotation', + name: 'StackFrame', + }, + }, + }, + { + nullable: false, + name: 'exceptionId', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + ], + optional: false, + }, + }, + { + name: 'reportException', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + nullable: false, + type: 'VoidTypeAnnotation', + }, + params: [ + { + nullable: false, + name: 'data', + typeAnnotation: { + type: 'TypeAliasTypeAnnotation', + name: 'ExceptionData', + }, + }, + ], + optional: true, + }, + }, + { + name: 'updateExceptionMessage', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + nullable: false, + type: 'VoidTypeAnnotation', + }, + params: [ + { + nullable: false, + name: 'message', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + name: 'stack', + nullable: false, + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'TypeAliasTypeAnnotation', + name: 'StackFrame', + }, + }, + }, + { + nullable: false, + name: 'exceptionId', + typeAnnotation: { + type: 'NumberTypeAnnotation', + }, + }, + ], + optional: false, + }, + }, + { + name: 'dismissRedbox', + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + nullable: false, + type: 'VoidTypeAnnotation', + }, + params: [], + optional: true, + }, + }, + ], + }, + }, + }, + }, +}; module.exports = { COMPLEX_OBJECTS, TWO_MODULES_SAME_FILE, TWO_MODULES_DIFFERENT_FILES, EMPTY_NATIVE_MODULES, SIMPLE_NATIVE_MODULES, + NATIVE_MODULES_WITH_TYPE_ALIASES, + REAL_MODULE_EXAMPLE, }; diff --git a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/structFixtures.js b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/structFixtures.js index cbb6fb58ec04c7..2de452b87c2c39 100644 --- a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/structFixtures.js +++ b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/structFixtures.js @@ -21,7 +21,7 @@ const SIMPLE_STRUCT: $ReadOnlyArray< |}>, > = [ { - name: 'SampleFuncReturnType', + name: 'SpecSampleFuncReturnType', object: { type: 'ObjectTypeAnnotation', properties: [ @@ -122,7 +122,7 @@ const SIMPLE_CONSTANTS: $ReadOnlyArray< |}>, > = [ { - name: 'GetConstantsReturnType', + name: 'SpecGetConstantsReturnType', object: { type: 'ObjectTypeAnnotation', properties: [ diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateStructs-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateStructs-test.js index 4791308f586b89..a66b5ff7e4c28c 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateStructs-test.js +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateStructs-test.js @@ -23,7 +23,7 @@ describe('GenerateStructs', () => { it(`can generate fixture ${fixtureName}`, () => { expect( generator - .translateObjectsForStructs(fixture, fixtureName) + .translateObjectsForStructs(fixture, fixtureName, {}) .replace(/::_MODULE_NAME_::/g, 'SampleTurboModule'), ).toMatchSnapshot(); }); diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap index be5e11a7f90a0a..d55f56b4b576ee 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap @@ -72,6 +72,130 @@ NativeSampleTurboModuleCxxSpecJSI::NativeSampleTurboModuleCxxSpecJSI(std::shared } +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateModuleCpp can generate fixture NATIVE_MODULES_WITH_TYPE_ALIASES 1`] = ` +Map { + "NativeModules.cpp" => " +/** + * 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 + +namespace facebook { +namespace react { + +static jsi::Value __hostFunction_NativeAliasTurboModuleCxxSpecJSI_getConstants(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->getConstants(rt); +} + +static jsi::Value __hostFunction_NativeAliasTurboModuleCxxSpecJSI_cropImage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->cropImage(rt, args[0].getObject(rt)); + return jsi::Value::undefined(); +} + +NativeAliasTurboModuleCxxSpecJSI::NativeAliasTurboModuleCxxSpecJSI(std::shared_ptr jsInvoker) + : TurboModule(\\"AliasTurboModule\\", jsInvoker) { + methodMap_[\\"getConstants\\"] = MethodMetadata {0, __hostFunction_NativeAliasTurboModuleCxxSpecJSI_getConstants}; + methodMap_[\\"cropImage\\"] = MethodMetadata {1, __hostFunction_NativeAliasTurboModuleCxxSpecJSI_cropImage}; +} + + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateModuleCpp can generate fixture REAL_MODULE_EXAMPLE 1`] = ` +Map { + "NativeModules.cpp" => " +/** + * 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 + +namespace facebook { +namespace react { + +static jsi::Value __hostFunction_NativeCameraRollManagerCxxSpecJSI_getConstants(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->getConstants(rt); +} +static jsi::Value __hostFunction_NativeCameraRollManagerCxxSpecJSI_getPhotos(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->getPhotos(rt, args[0].getObject(rt)); +} +static jsi::Value __hostFunction_NativeCameraRollManagerCxxSpecJSI_saveToCameraRoll(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->saveToCameraRoll(rt, args[0].getString(rt), args[1].getString(rt)); +} +static jsi::Value __hostFunction_NativeCameraRollManagerCxxSpecJSI_deletePhotos(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->deletePhotos(rt, args[0].getObject(rt).getArray(rt)); +} + +NativeCameraRollManagerCxxSpecJSI::NativeCameraRollManagerCxxSpecJSI(std::shared_ptr jsInvoker) + : TurboModule(\\"CameraRollManager\\", jsInvoker) { + methodMap_[\\"getConstants\\"] = MethodMetadata {0, __hostFunction_NativeCameraRollManagerCxxSpecJSI_getConstants}; + methodMap_[\\"getPhotos\\"] = MethodMetadata {1, __hostFunction_NativeCameraRollManagerCxxSpecJSI_getPhotos}; + methodMap_[\\"saveToCameraRoll\\"] = MethodMetadata {2, __hostFunction_NativeCameraRollManagerCxxSpecJSI_saveToCameraRoll}; + methodMap_[\\"deletePhotos\\"] = MethodMetadata {1, __hostFunction_NativeCameraRollManagerCxxSpecJSI_deletePhotos}; +} + +static jsi::Value __hostFunction_NativeImagePickerIOSCxxSpecJSI_openCameraDialog(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->openCameraDialog(rt, args[0].getObject(rt), std::move(args[1].getObject(rt).getFunction(rt)), std::move(args[2].getObject(rt).getFunction(rt))); + return jsi::Value::undefined(); +} + +NativeImagePickerIOSCxxSpecJSI::NativeImagePickerIOSCxxSpecJSI(std::shared_ptr jsInvoker) + : TurboModule(\\"ImagePickerIOS\\", jsInvoker) { + methodMap_[\\"openCameraDialog\\"] = MethodMetadata {3, __hostFunction_NativeImagePickerIOSCxxSpecJSI_openCameraDialog}; +} + +static jsi::Value __hostFunction_NativeExceptionsManagerCxxSpecJSI_reportFatalException(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->reportFatalException(rt, args[0].getString(rt), args[1].getObject(rt).getArray(rt), args[2].getNumber()); + return jsi::Value::undefined(); +} + +static jsi::Value __hostFunction_NativeExceptionsManagerCxxSpecJSI_reportSoftException(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->reportSoftException(rt, args[0].getString(rt), args[1].getObject(rt).getArray(rt), args[2].getNumber()); + return jsi::Value::undefined(); +} + +static jsi::Value __hostFunction_NativeExceptionsManagerCxxSpecJSI_reportException(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->reportException(rt, args[0].getObject(rt)); + return jsi::Value::undefined(); +} + +static jsi::Value __hostFunction_NativeExceptionsManagerCxxSpecJSI_updateExceptionMessage(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->updateExceptionMessage(rt, args[0].getString(rt), args[1].getObject(rt).getArray(rt), args[2].getNumber()); + return jsi::Value::undefined(); +} + +static jsi::Value __hostFunction_NativeExceptionsManagerCxxSpecJSI_dismissRedbox(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->dismissRedbox(rt); + return jsi::Value::undefined(); +} + +NativeExceptionsManagerCxxSpecJSI::NativeExceptionsManagerCxxSpecJSI(std::shared_ptr jsInvoker) + : TurboModule(\\"ExceptionsManager\\", jsInvoker) { + methodMap_[\\"reportFatalException\\"] = MethodMetadata {3, __hostFunction_NativeExceptionsManagerCxxSpecJSI_reportFatalException}; + methodMap_[\\"reportSoftException\\"] = MethodMetadata {3, __hostFunction_NativeExceptionsManagerCxxSpecJSI_reportSoftException}; + methodMap_[\\"reportException\\"] = MethodMetadata {1, __hostFunction_NativeExceptionsManagerCxxSpecJSI_reportException}; + methodMap_[\\"updateExceptionMessage\\"] = MethodMetadata {3, __hostFunction_NativeExceptionsManagerCxxSpecJSI_updateExceptionMessage}; + methodMap_[\\"dismissRedbox\\"] = MethodMetadata {0, __hostFunction_NativeExceptionsManagerCxxSpecJSI_dismissRedbox}; +} + + } // namespace react } // namespace facebook ", diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap index 8a4271cb0206f6..bda31d0a36e4da 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap @@ -59,6 +59,96 @@ protected: public: +}; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateModuleCpp can generate fixture NATIVE_MODULES_WITH_TYPE_ALIASES 1`] = ` +Map { + "NativeModules.h" => " +/** + * 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 + +namespace facebook { +namespace react { + +class JSI_EXPORT NativeAliasTurboModuleCxxSpecJSI : public TurboModule { +protected: + NativeAliasTurboModuleCxxSpecJSI(std::shared_ptr jsInvoker); + +public: +virtual jsi::Object getConstants(jsi::Runtime &rt) = 0; +virtual void cropImage(jsi::Runtime &rt, const jsi::Object &cropData) = 0; + +}; + +} // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateModuleCpp can generate fixture REAL_MODULE_EXAMPLE 1`] = ` +Map { + "NativeModules.h" => " +/** + * 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 + +namespace facebook { +namespace react { + +class JSI_EXPORT NativeCameraRollManagerCxxSpecJSI : public TurboModule { +protected: + NativeCameraRollManagerCxxSpecJSI(std::shared_ptr jsInvoker); + +public: +virtual jsi::Object getConstants(jsi::Runtime &rt) = 0; +virtual jsi::Value getPhotos(jsi::Runtime &rt, const jsi::Object ¶ms) = 0; +virtual jsi::Value saveToCameraRoll(jsi::Runtime &rt, const jsi::String &uri, const jsi::String &type) = 0; +virtual jsi::Value deletePhotos(jsi::Runtime &rt, const jsi::Array &assets) = 0; + +}; + +class JSI_EXPORT NativeImagePickerIOSCxxSpecJSI : public TurboModule { +protected: + NativeImagePickerIOSCxxSpecJSI(std::shared_ptr jsInvoker); + +public: +virtual void openCameraDialog(jsi::Runtime &rt, const jsi::Object &config, const jsi::Function &successCallback, const jsi::Function &cancelCallback) = 0; + +}; + +class JSI_EXPORT NativeExceptionsManagerCxxSpecJSI : public TurboModule { +protected: + NativeExceptionsManagerCxxSpecJSI(std::shared_ptr jsInvoker); + +public: +virtual void reportFatalException(jsi::Runtime &rt, const jsi::String &message, const jsi::Array &stack, double exceptionId) = 0; +virtual void reportSoftException(jsi::Runtime &rt, const jsi::String &message, const jsi::Array &stack, double exceptionId) = 0; +virtual void reportException(jsi::Runtime &rt, const jsi::Object &data) = 0; +virtual void updateExceptionMessage(jsi::Runtime &rt, const jsi::String &message, const jsi::Array &stack, double exceptionId) = 0; +virtual void dismissRedbox(jsi::Runtime &rt) = 0; + }; } // namespace react diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap index 0d92adac32c8aa..939b9bcea8d2f2 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap @@ -96,7 +96,7 @@ namespace JS { folly::Optional> optionalArrayOfNumbers() const; facebook::react::LazyVector arrayOfStrings() const; folly::Optional> optionalArrayOfStrings() const; - facebook::react::LazyVector arrayOfObjects() const; + facebook::react::LazyVector arrayOfObjects() const; SpecGetArraysOptions(NSDictionary *const v) : _v(v) {} private: @@ -305,10 +305,10 @@ inline folly::Optional> JS::NativeSample } -inline facebook::react::LazyVector JS::NativeSampleTurboModule::SpecGetArraysOptions::arrayOfObjects() const +inline facebook::react::LazyVector JS::NativeSampleTurboModule::SpecGetArraysOptions::arrayOfObjects() const { id const p = _v[@\\"arrayOfObjects\\"]; - return RCTBridgingToVec(p, ^JS::NativeSampleTurboModule::SpecGetArraysOptionsArrayOfObjectsElement(id itemValue_0) { return JS::NativeSampleTurboModule::SpecGetArraysOptionsArrayOfObjectsElement(itemValue_0); }); + return RCTBridgingToVec(p, ^JS::NativeSampleTurboModule::arrayOfObjectsElement(id itemValue_0) { return JS::NativeSampleTurboModule::arrayOfObjectsElement(itemValue_0); }); } @@ -363,12 +363,12 @@ inline double JS::NativeSampleTurboModule::SpecOptionalsAOptionalObjectProperty: @protocol NativeSampleTurboModuleSpec -- (NSDictionary *) difficult:(JS::NativeSampleTurboModule::SpecDifficultA&)A; -- (void) optionals:(JS::NativeSampleTurboModule::SpecOptionalsA&)A; +- (NSDictionary *) difficult:(JS::NativeSampleTurboModule::SpecDifficultA &)A; +- (void) optionals:(JS::NativeSampleTurboModule::SpecOptionalsA &)A; - (void) optionalMethod:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback extras:(NSArray * _Nullable)extras; -- (void) getArrays:(JS::NativeSampleTurboModule::SpecGetArraysOptions&)options; +- (void) getArrays:(JS::NativeSampleTurboModule::SpecGetArraysOptions &)options; @end @@ -443,6 +443,827 @@ namespace facebook { } `; +exports[`GenerateModuleHObjCpp can generate fixture NATIVE_MODULES_WITH_TYPE_ALIASES 1`] = ` +Map { + "SampleSpec.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @generated by codegen project: GenerateModuleHObjCpp.js + */ + +#ifndef __cplusplus +#error This file must be compiled as Obj-C++. If you are importing it, you must change your file extension to .mm. +#endif + +#import + +#import + +#import + +#import +#import +#import + +#import +#import +#import + +#import + + + +namespace JS { + namespace NativeAliasTurboModule { + struct OptionsDisplaySize { + double width() const; + double height() const; + + OptionsDisplaySize(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeAliasTurboModule_OptionsDisplaySize) ++ (RCTManagedPointer *)JS_NativeAliasTurboModule_OptionsDisplaySize:(id)json; +@end + + +namespace JS { + namespace NativeAliasTurboModule { + struct OptionsSize { + double width() const; + double height() const; + + OptionsSize(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeAliasTurboModule_OptionsSize) ++ (RCTManagedPointer *)JS_NativeAliasTurboModule_OptionsSize:(id)json; +@end + + +namespace JS { + namespace NativeAliasTurboModule { + struct OptionsOffset { + double x() const; + double y() const; + + OptionsOffset(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeAliasTurboModule_OptionsOffset) ++ (RCTManagedPointer *)JS_NativeAliasTurboModule_OptionsOffset:(id)json; +@end + + +namespace JS { + namespace NativeAliasTurboModule { + struct Options { + JS::NativeAliasTurboModule::OptionsOffset offset() const; + JS::NativeAliasTurboModule::OptionsSize size() const; + folly::Optional displaySize() const; + NSString *resizeMode() const; + folly::Optional allowExternalStorage() const; + + Options(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeAliasTurboModule_Options) ++ (RCTManagedPointer *)JS_NativeAliasTurboModule_Options:(id)json; +@end + +inline JS::NativeAliasTurboModule::OptionsOffset JS::NativeAliasTurboModule::Options::offset() const +{ + id const p = _v[@\\"offset\\"]; + return JS::NativeAliasTurboModule::OptionsOffset(p); +} + + +inline JS::NativeAliasTurboModule::OptionsSize JS::NativeAliasTurboModule::Options::size() const +{ + id const p = _v[@\\"size\\"]; + return JS::NativeAliasTurboModule::OptionsSize(p); +} + + +inline folly::Optional JS::NativeAliasTurboModule::Options::displaySize() const +{ + id const p = _v[@\\"displaySize\\"]; + return (p == nil ? folly::none : folly::make_optional(JS::NativeAliasTurboModule::OptionsDisplaySize(p))); +} + + +inline NSString *JS::NativeAliasTurboModule::Options::resizeMode() const +{ + id const p = _v[@\\"resizeMode\\"]; + return RCTBridgingToString(p); +} + + +inline folly::Optional JS::NativeAliasTurboModule::Options::allowExternalStorage() const +{ + id const p = _v[@\\"allowExternalStorage\\"]; + return RCTBridgingToOptionalBool(p); +} + + +inline double JS::NativeAliasTurboModule::OptionsOffset::x() const +{ + id const p = _v[@\\"x\\"]; + return RCTBridgingToDouble(p); +} + + +inline double JS::NativeAliasTurboModule::OptionsOffset::y() const +{ + id const p = _v[@\\"y\\"]; + return RCTBridgingToDouble(p); +} + + +inline double JS::NativeAliasTurboModule::OptionsSize::width() const +{ + id const p = _v[@\\"width\\"]; + return RCTBridgingToDouble(p); +} + + +inline double JS::NativeAliasTurboModule::OptionsSize::height() const +{ + id const p = _v[@\\"height\\"]; + return RCTBridgingToDouble(p); +} + + +inline double JS::NativeAliasTurboModule::OptionsDisplaySize::width() const +{ + id const p = _v[@\\"width\\"]; + return RCTBridgingToDouble(p); +} + + +inline double JS::NativeAliasTurboModule::OptionsDisplaySize::height() const +{ + id const p = _v[@\\"height\\"]; + return RCTBridgingToDouble(p); +} + + + +@protocol NativeAliasTurboModuleSpec + +- (void) cropImage:(JS::NativeAliasTurboModule::Options &)cropData; +@end + + +namespace facebook { + namespace react { + /** + * ObjC++ class for module 'AliasTurboModule' + */ + class JSI_EXPORT NativeAliasTurboModuleSpecJSI : public ObjCTurboModule { + public: + NativeAliasTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms); + }; + } // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateModuleHObjCpp can generate fixture REAL_MODULE_EXAMPLE 1`] = ` +Map { + "SampleSpec.h" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @generated by codegen project: GenerateModuleHObjCpp.js + */ + +#ifndef __cplusplus +#error This file must be compiled as Obj-C++. If you are importing it, you must change your file extension to .mm. +#endif + +#import + +#import + +#import + +#import +#import +#import + +#import +#import +#import + +#import + + + +namespace JS { + namespace NativeCameraRollManager { + struct PhotoIdentifierNodeLocation { + double longitude() const; + double latitude() const; + folly::Optional altitude() const; + folly::Optional heading() const; + folly::Optional speed() const; + + PhotoIdentifierNodeLocation(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeCameraRollManager_PhotoIdentifierNodeLocation) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_PhotoIdentifierNodeLocation:(id)json; +@end + + +namespace JS { + namespace NativeCameraRollManager { + struct PhotoIdentifierNode { + JS::NativeCameraRollManager::PhotoIdentifierImage image() const; + NSString *type() const; + NSString *group_name() const; + double timestamp() const; + JS::NativeCameraRollManager::PhotoIdentifierNodeLocation location() const; + + PhotoIdentifierNode(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeCameraRollManager_PhotoIdentifierNode) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_PhotoIdentifierNode:(id)json; +@end + + +namespace JS { + namespace NativeCameraRollManager { + struct PhotoIdentifiersPagePage_info { + bool has_next_page() const; + NSString *start_cursor() const; + NSString *end_cursor() const; + + PhotoIdentifiersPagePage_info(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeCameraRollManager_PhotoIdentifiersPagePage_info) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_PhotoIdentifiersPagePage_info:(id)json; +@end + + +namespace JS { + namespace NativeCameraRollManager { + struct PhotoIdentifierImage { + NSString *uri() const; + double playableDuration() const; + double width() const; + double height() const; + folly::Optional isStored() const; + NSString *filename() const; + + PhotoIdentifierImage(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeCameraRollManager_PhotoIdentifierImage) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_PhotoIdentifierImage:(id)json; +@end + + +namespace JS { + namespace NativeCameraRollManager { + struct PhotoIdentifier { + JS::NativeCameraRollManager::PhotoIdentifierNode node() const; + + PhotoIdentifier(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeCameraRollManager_PhotoIdentifier) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_PhotoIdentifier:(id)json; +@end + + +namespace JS { + namespace NativeCameraRollManager { + struct PhotoIdentifiersPage { + facebook::react::LazyVector edges() const; + JS::NativeCameraRollManager::PhotoIdentifiersPagePage_info page_info() const; + + PhotoIdentifiersPage(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeCameraRollManager_PhotoIdentifiersPage) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_PhotoIdentifiersPage:(id)json; +@end + + +namespace JS { + namespace NativeCameraRollManager { + struct GetPhotosParams { + double first() const; + NSString *after() const; + NSString *groupName() const; + NSString *groupTypes() const; + NSString *assetType() const; + folly::Optional maxSize() const; + folly::Optional> mimeTypes() const; + + GetPhotosParams(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeCameraRollManager_GetPhotosParams) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_GetPhotosParams:(id)json; +@end + +inline double JS::NativeCameraRollManager::GetPhotosParams::first() const +{ + id const p = _v[@\\"first\\"]; + return RCTBridgingToDouble(p); +} + + +inline NSString *JS::NativeCameraRollManager::GetPhotosParams::after() const +{ + id const p = _v[@\\"after\\"]; + return RCTBridgingToString(p); +} + + +inline NSString *JS::NativeCameraRollManager::GetPhotosParams::groupName() const +{ + id const p = _v[@\\"groupName\\"]; + return RCTBridgingToString(p); +} + + +inline NSString *JS::NativeCameraRollManager::GetPhotosParams::groupTypes() const +{ + id const p = _v[@\\"groupTypes\\"]; + return RCTBridgingToString(p); +} + + +inline NSString *JS::NativeCameraRollManager::GetPhotosParams::assetType() const +{ + id const p = _v[@\\"assetType\\"]; + return RCTBridgingToString(p); +} + + +inline folly::Optional JS::NativeCameraRollManager::GetPhotosParams::maxSize() const +{ + id const p = _v[@\\"maxSize\\"]; + return RCTBridgingToOptionalDouble(p); +} + + +inline folly::Optional> JS::NativeCameraRollManager::GetPhotosParams::mimeTypes() const +{ + id const p = _v[@\\"mimeTypes\\"]; + return RCTBridgingToOptionalVec(p, ^NSString *(id itemValue_0) { return RCTBridgingToString(itemValue_0); }); +} + + +inline facebook::react::LazyVector JS::NativeCameraRollManager::PhotoIdentifiersPage::edges() const +{ + id const p = _v[@\\"edges\\"]; + return RCTBridgingToVec(p, ^JS::NativeCameraRollManager::PhotoIdentifier(id itemValue_0) { return JS::NativeCameraRollManager::PhotoIdentifier(itemValue_0); }); +} + + +inline JS::NativeCameraRollManager::PhotoIdentifiersPagePage_info JS::NativeCameraRollManager::PhotoIdentifiersPage::page_info() const +{ + id const p = _v[@\\"page_info\\"]; + return JS::NativeCameraRollManager::PhotoIdentifiersPagePage_info(p); +} + + +inline JS::NativeCameraRollManager::PhotoIdentifierNode JS::NativeCameraRollManager::PhotoIdentifier::node() const +{ + id const p = _v[@\\"node\\"]; + return JS::NativeCameraRollManager::PhotoIdentifierNode(p); +} + + +inline NSString *JS::NativeCameraRollManager::PhotoIdentifierImage::uri() const +{ + id const p = _v[@\\"uri\\"]; + return RCTBridgingToString(p); +} + + +inline double JS::NativeCameraRollManager::PhotoIdentifierImage::playableDuration() const +{ + id const p = _v[@\\"playableDuration\\"]; + return RCTBridgingToDouble(p); +} + + +inline double JS::NativeCameraRollManager::PhotoIdentifierImage::width() const +{ + id const p = _v[@\\"width\\"]; + return RCTBridgingToDouble(p); +} + + +inline double JS::NativeCameraRollManager::PhotoIdentifierImage::height() const +{ + id const p = _v[@\\"height\\"]; + return RCTBridgingToDouble(p); +} + + +inline folly::Optional JS::NativeCameraRollManager::PhotoIdentifierImage::isStored() const +{ + id const p = _v[@\\"isStored\\"]; + return RCTBridgingToOptionalBool(p); +} + + +inline NSString *JS::NativeCameraRollManager::PhotoIdentifierImage::filename() const +{ + id const p = _v[@\\"filename\\"]; + return RCTBridgingToString(p); +} + + +inline bool JS::NativeCameraRollManager::PhotoIdentifiersPagePage_info::has_next_page() const +{ + id const p = _v[@\\"has_next_page\\"]; + return RCTBridgingToBool(p); +} + + +inline NSString *JS::NativeCameraRollManager::PhotoIdentifiersPagePage_info::start_cursor() const +{ + id const p = _v[@\\"start_cursor\\"]; + return RCTBridgingToString(p); +} + + +inline NSString *JS::NativeCameraRollManager::PhotoIdentifiersPagePage_info::end_cursor() const +{ + id const p = _v[@\\"end_cursor\\"]; + return RCTBridgingToString(p); +} + + +inline JS::NativeCameraRollManager::PhotoIdentifierImage JS::NativeCameraRollManager::PhotoIdentifierNode::image() const +{ + id const p = _v[@\\"image\\"]; + return JS::NativeCameraRollManager::PhotoIdentifierImage(p); +} + + +inline NSString *JS::NativeCameraRollManager::PhotoIdentifierNode::type() const +{ + id const p = _v[@\\"type\\"]; + return RCTBridgingToString(p); +} + + +inline NSString *JS::NativeCameraRollManager::PhotoIdentifierNode::group_name() const +{ + id const p = _v[@\\"group_name\\"]; + return RCTBridgingToString(p); +} + + +inline double JS::NativeCameraRollManager::PhotoIdentifierNode::timestamp() const +{ + id const p = _v[@\\"timestamp\\"]; + return RCTBridgingToDouble(p); +} + + +inline JS::NativeCameraRollManager::PhotoIdentifierNodeLocation JS::NativeCameraRollManager::PhotoIdentifierNode::location() const +{ + id const p = _v[@\\"location\\"]; + return JS::NativeCameraRollManager::PhotoIdentifierNodeLocation(p); +} + + +inline double JS::NativeCameraRollManager::PhotoIdentifierNodeLocation::longitude() const +{ + id const p = _v[@\\"longitude\\"]; + return RCTBridgingToDouble(p); +} + + +inline double JS::NativeCameraRollManager::PhotoIdentifierNodeLocation::latitude() const +{ + id const p = _v[@\\"latitude\\"]; + return RCTBridgingToDouble(p); +} + + +inline folly::Optional JS::NativeCameraRollManager::PhotoIdentifierNodeLocation::altitude() const +{ + id const p = _v[@\\"altitude\\"]; + return RCTBridgingToOptionalDouble(p); +} + + +inline folly::Optional JS::NativeCameraRollManager::PhotoIdentifierNodeLocation::heading() const +{ + id const p = _v[@\\"heading\\"]; + return RCTBridgingToOptionalDouble(p); +} + + +inline folly::Optional JS::NativeCameraRollManager::PhotoIdentifierNodeLocation::speed() const +{ + id const p = _v[@\\"speed\\"]; + return RCTBridgingToOptionalDouble(p); +} + + + +@protocol NativeCameraRollManagerSpec + +- (void) getPhotos:(JS::NativeCameraRollManager::GetPhotosParams &)params + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject; +- (void) saveToCameraRoll:(NSString *)uri + type:(NSString *)type + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject; +- (void) deletePhotos:(NSArray *)assets + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject; +@end + + + +namespace JS { + namespace NativeExceptionsManager { + struct StackFrame { + folly::Optional column() const; + NSString *file() const; + folly::Optional lineNumber() const; + NSString *methodName() const; + folly::Optional collapse() const; + + StackFrame(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeExceptionsManager_StackFrame) ++ (RCTManagedPointer *)JS_NativeExceptionsManager_StackFrame:(id)json; +@end + + +namespace JS { + namespace NativeExceptionsManager { + struct ExceptionData { + NSString *message() const; + NSString *originalMessage() const; + NSString *name() const; + NSString *componentStack() const; + facebook::react::LazyVector stack() const; + double id_() const; + bool isFatal() const; + id _Nullable extraData() const; + + ExceptionData(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeExceptionsManager_ExceptionData) ++ (RCTManagedPointer *)JS_NativeExceptionsManager_ExceptionData:(id)json; +@end + +inline NSString *JS::NativeExceptionsManager::ExceptionData::message() const +{ + id const p = _v[@\\"message\\"]; + return RCTBridgingToString(p); +} + + +inline NSString *JS::NativeExceptionsManager::ExceptionData::originalMessage() const +{ + id const p = _v[@\\"originalMessage\\"]; + return RCTBridgingToString(p); +} + + +inline NSString *JS::NativeExceptionsManager::ExceptionData::name() const +{ + id const p = _v[@\\"name\\"]; + return RCTBridgingToString(p); +} + + +inline NSString *JS::NativeExceptionsManager::ExceptionData::componentStack() const +{ + id const p = _v[@\\"componentStack\\"]; + return RCTBridgingToString(p); +} + + +inline facebook::react::LazyVector JS::NativeExceptionsManager::ExceptionData::stack() const +{ + id const p = _v[@\\"stack\\"]; + return RCTBridgingToVec(p, ^JS::NativeExceptionsManager::StackFrame(id itemValue_0) { return JS::NativeExceptionsManager::StackFrame(itemValue_0); }); +} + + +inline double JS::NativeExceptionsManager::ExceptionData::id_() const +{ + id const p = _v[@\\"id_\\"]; + return RCTBridgingToDouble(p); +} + + +inline bool JS::NativeExceptionsManager::ExceptionData::isFatal() const +{ + id const p = _v[@\\"isFatal\\"]; + return RCTBridgingToBool(p); +} + + +inline id _Nullable JS::NativeExceptionsManager::ExceptionData::extraData() const +{ + id const p = _v[@\\"extraData\\"]; + return p; +} + + +inline folly::Optional JS::NativeExceptionsManager::StackFrame::column() const +{ + id const p = _v[@\\"column\\"]; + return RCTBridgingToOptionalDouble(p); +} + + +inline NSString *JS::NativeExceptionsManager::StackFrame::file() const +{ + id const p = _v[@\\"file\\"]; + return RCTBridgingToString(p); +} + + +inline folly::Optional JS::NativeExceptionsManager::StackFrame::lineNumber() const +{ + id const p = _v[@\\"lineNumber\\"]; + return RCTBridgingToOptionalDouble(p); +} + + +inline NSString *JS::NativeExceptionsManager::StackFrame::methodName() const +{ + id const p = _v[@\\"methodName\\"]; + return RCTBridgingToString(p); +} + + +inline folly::Optional JS::NativeExceptionsManager::StackFrame::collapse() const +{ + id const p = _v[@\\"collapse\\"]; + return RCTBridgingToOptionalBool(p); +} + + + +@protocol NativeExceptionsManagerSpec +- (void) reportFatalException:(NSString *)message + stack:(NSArray *)stack + exceptionId:(double)exceptionId; +- (void) reportSoftException:(NSString *)message + stack:(NSArray *)stack + exceptionId:(double)exceptionId; +- (void) reportException:(JS::NativeExceptionsManager::ExceptionData &)data; +- (void) updateExceptionMessage:(NSString *)message + stack:(NSArray *)stack + exceptionId:(double)exceptionId; +- (void) dismissRedbox; +@end + + + +namespace JS { + namespace NativeImagePickerIOS { + struct SpecOpenCameraDialogConfig { + bool unmirrorFrontFacingCamera() const; + bool videoMode() const; + + SpecOpenCameraDialogConfig(NSDictionary *const v) : _v(v) {} + private: + NSDictionary *_v; + }; + } +} + +@interface RCTCxxConvert (NativeImagePickerIOS_SpecOpenCameraDialogConfig) ++ (RCTManagedPointer *)JS_NativeImagePickerIOS_SpecOpenCameraDialogConfig:(id)json; +@end + +inline bool JS::NativeImagePickerIOS::SpecOpenCameraDialogConfig::unmirrorFrontFacingCamera() const +{ + id const p = _v[@\\"unmirrorFrontFacingCamera\\"]; + return RCTBridgingToBool(p); +} + + +inline bool JS::NativeImagePickerIOS::SpecOpenCameraDialogConfig::videoMode() const +{ + id const p = _v[@\\"videoMode\\"]; + return RCTBridgingToBool(p); +} + + + +@protocol NativeImagePickerIOSSpec +- (void) openCameraDialog:(JS::NativeImagePickerIOS::SpecOpenCameraDialogConfig &)config + successCallback:(RCTResponseSenderBlock)successCallback + cancelCallback:(RCTResponseSenderBlock)cancelCallback; +@end + + +namespace facebook { + namespace react { + /** + * ObjC++ class for module 'CameraRollManager' + */ + class JSI_EXPORT NativeCameraRollManagerSpecJSI : public ObjCTurboModule { + public: + NativeCameraRollManagerSpecJSI(const ObjCTurboModule::InitParams ¶ms); + }; + /** + * ObjC++ class for module 'ExceptionsManager' + */ + class JSI_EXPORT NativeExceptionsManagerSpecJSI : public ObjCTurboModule { + public: + NativeExceptionsManagerSpecJSI(const ObjCTurboModule::InitParams ¶ms); + }; + /** + * ObjC++ class for module 'ImagePickerIOS' + */ + class JSI_EXPORT NativeImagePickerIOSSpecJSI : public ObjCTurboModule { + public: + NativeImagePickerIOSSpecJSI(const ObjCTurboModule::InitParams ¶ms); + }; + } // namespace react +} // namespace facebook +", +} +`; + exports[`GenerateModuleHObjCpp can generate fixture SIMPLE_NATIVE_MODULES 1`] = ` Map { "SampleSpec.h" => " diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap index 3a278d825a7ae2..6192444e427682 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap @@ -132,6 +132,248 @@ namespace facebook { } `; +exports[`GenerateModuleHObjCpp can generate fixture NATIVE_MODULES_WITH_TYPE_ALIASES 1`] = ` +Map { + "SampleSpec-generated.mm" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @generated by codegen project: GenerateModuleMm.js + */ + +#include +#import + + +@implementation RCTCxxConvert (NativeAliasTurboModule_Options) ++ (RCTManagedPointer *)JS_NativeAliasTurboModule_Options:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + +@implementation RCTCxxConvert (NativeAliasTurboModule_OptionsOffset) ++ (RCTManagedPointer *)JS_NativeAliasTurboModule_OptionsOffset:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + +@implementation RCTCxxConvert (NativeAliasTurboModule_OptionsSize) ++ (RCTManagedPointer *)JS_NativeAliasTurboModule_OptionsSize:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + +@implementation RCTCxxConvert (NativeAliasTurboModule_OptionsDisplaySize) ++ (RCTManagedPointer *)JS_NativeAliasTurboModule_OptionsDisplaySize:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + +namespace facebook { + namespace react { + + + static facebook::jsi::Value __hostFunction_NativeAliasTurboModuleSpecJSI_cropImage(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule) + .invokeObjCMethod(rt, VoidKind, \\"cropImage\\", @selector(cropImage:), args, count); + } + + NativeAliasTurboModuleSpecJSI::NativeAliasTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms) + : ObjCTurboModule(params) { + + methodMap_[\\"cropImage\\"] = MethodMetadata {1, __hostFunction_NativeAliasTurboModuleSpecJSI_cropImage}; + setMethodArgConversionSelector(@\\"cropImage\\", 0, @\\"JS_NativeAliasTurboModule_Options:\\"); + } + } // namespace react +} // namespace facebook +", +} +`; + +exports[`GenerateModuleHObjCpp can generate fixture REAL_MODULE_EXAMPLE 1`] = ` +Map { + "SampleSpec-generated.mm" => " +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @generated by codegen project: GenerateModuleMm.js + */ + +#include +#import + + +@implementation RCTCxxConvert (NativeCameraRollManager_PhotoIdentifierImage) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_PhotoIdentifierImage:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + +@implementation RCTCxxConvert (NativeCameraRollManager_PhotoIdentifier) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_PhotoIdentifier:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + +@implementation RCTCxxConvert (NativeCameraRollManager_PhotoIdentifiersPage) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_PhotoIdentifiersPage:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + +@implementation RCTCxxConvert (NativeCameraRollManager_GetPhotosParams) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_GetPhotosParams:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + +@implementation RCTCxxConvert (NativeCameraRollManager_PhotoIdentifierNode) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_PhotoIdentifierNode:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + +@implementation RCTCxxConvert (NativeCameraRollManager_PhotoIdentifiersPagePage_info) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_PhotoIdentifiersPagePage_info:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + +@implementation RCTCxxConvert (NativeCameraRollManager_PhotoIdentifierNodeLocation) ++ (RCTManagedPointer *)JS_NativeCameraRollManager_PhotoIdentifierNodeLocation:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + +@implementation RCTCxxConvert (NativeImagePickerIOS_SpecOpenCameraDialogConfig) ++ (RCTManagedPointer *)JS_NativeImagePickerIOS_SpecOpenCameraDialogConfig:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + +@implementation RCTCxxConvert (NativeExceptionsManager_StackFrame) ++ (RCTManagedPointer *)JS_NativeExceptionsManager_StackFrame:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + + +@implementation RCTCxxConvert (NativeExceptionsManager_ExceptionData) ++ (RCTManagedPointer *)JS_NativeExceptionsManager_ExceptionData:(id)json +{ + return facebook::react::managedPointer(json); +} +@end + +namespace facebook { + namespace react { + + + static facebook::jsi::Value __hostFunction_NativeCameraRollManagerSpecJSI_getPhotos(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule) + .invokeObjCMethod(rt, PromiseKind, \\"getPhotos\\", @selector(getPhotos:resolve:reject:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeCameraRollManagerSpecJSI_saveToCameraRoll(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule) + .invokeObjCMethod(rt, PromiseKind, \\"saveToCameraRoll\\", @selector(saveToCameraRoll:type:resolve:reject:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeCameraRollManagerSpecJSI_deletePhotos(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule) + .invokeObjCMethod(rt, PromiseKind, \\"deletePhotos\\", @selector(deletePhotos:resolve:reject:), args, count); + } + + NativeCameraRollManagerSpecJSI::NativeCameraRollManagerSpecJSI(const ObjCTurboModule::InitParams ¶ms) + : ObjCTurboModule(params) { + + methodMap_[\\"getPhotos\\"] = MethodMetadata {1, __hostFunction_NativeCameraRollManagerSpecJSI_getPhotos}; + methodMap_[\\"saveToCameraRoll\\"] = MethodMetadata {2, __hostFunction_NativeCameraRollManagerSpecJSI_saveToCameraRoll}; + methodMap_[\\"deletePhotos\\"] = MethodMetadata {1, __hostFunction_NativeCameraRollManagerSpecJSI_deletePhotos}; + setMethodArgConversionSelector(@\\"getPhotos\\", 0, @\\"JS_NativeCameraRollManager_GetPhotosParams:\\"); + } + + static facebook::jsi::Value __hostFunction_NativeImagePickerIOSSpecJSI_openCameraDialog(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule) + .invokeObjCMethod(rt, VoidKind, \\"openCameraDialog\\", @selector(openCameraDialog:successCallback:cancelCallback:), args, count); + } + + NativeImagePickerIOSSpecJSI::NativeImagePickerIOSSpecJSI(const ObjCTurboModule::InitParams ¶ms) + : ObjCTurboModule(params) { + methodMap_[\\"openCameraDialog\\"] = MethodMetadata {3, __hostFunction_NativeImagePickerIOSSpecJSI_openCameraDialog}; + setMethodArgConversionSelector(@\\"openCameraDialog\\", 0, @\\"JS_NativeImagePickerIOS_SpecOpenCameraDialogConfig:\\"); + } + + static facebook::jsi::Value __hostFunction_NativeExceptionsManagerSpecJSI_reportFatalException(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule) + .invokeObjCMethod(rt, VoidKind, \\"reportFatalException\\", @selector(reportFatalException:stack:exceptionId:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeExceptionsManagerSpecJSI_reportSoftException(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule) + .invokeObjCMethod(rt, VoidKind, \\"reportSoftException\\", @selector(reportSoftException:stack:exceptionId:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeExceptionsManagerSpecJSI_reportException(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule) + .invokeObjCMethod(rt, VoidKind, \\"reportException\\", @selector(reportException:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeExceptionsManagerSpecJSI_updateExceptionMessage(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule) + .invokeObjCMethod(rt, VoidKind, \\"updateExceptionMessage\\", @selector(updateExceptionMessage:stack:exceptionId:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeExceptionsManagerSpecJSI_dismissRedbox(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule) + .invokeObjCMethod(rt, VoidKind, \\"dismissRedbox\\", @selector(dismissRedbox), args, count); + } + + NativeExceptionsManagerSpecJSI::NativeExceptionsManagerSpecJSI(const ObjCTurboModule::InitParams ¶ms) + : ObjCTurboModule(params) { + methodMap_[\\"reportFatalException\\"] = MethodMetadata {3, __hostFunction_NativeExceptionsManagerSpecJSI_reportFatalException}; + methodMap_[\\"reportSoftException\\"] = MethodMetadata {3, __hostFunction_NativeExceptionsManagerSpecJSI_reportSoftException}; + methodMap_[\\"reportException\\"] = MethodMetadata {1, __hostFunction_NativeExceptionsManagerSpecJSI_reportException}; + methodMap_[\\"updateExceptionMessage\\"] = MethodMetadata {3, __hostFunction_NativeExceptionsManagerSpecJSI_updateExceptionMessage}; + methodMap_[\\"dismissRedbox\\"] = MethodMetadata {0, __hostFunction_NativeExceptionsManagerSpecJSI_dismissRedbox}; + setMethodArgConversionSelector(@\\"reportException\\", 0, @\\"JS_NativeExceptionsManager_ExceptionData:\\"); + } + } // namespace react +} // namespace facebook +", +} +`; + exports[`GenerateModuleHObjCpp can generate fixture SIMPLE_NATIVE_MODULES 1`] = ` Map { "SampleSpec-generated.mm" => " diff --git a/scripts/.tests.env b/scripts/.tests.env index aefc588000fc7c..5b9f2cdd858a2c 100644 --- a/scripts/.tests.env +++ b/scripts/.tests.env @@ -4,7 +4,7 @@ ## ANDROID ## # Android SDK Build Tools revision -export ANDROID_SDK_BUILD_TOOLS_REVISION=29.0.2 +export ANDROID_SDK_BUILD_TOOLS_REVISION=29.0.3 # Android API Level we build with export ANDROID_SDK_BUILD_API_LEVEL="28" # Google APIs for Android level diff --git a/scripts/android-e2e-test.js b/scripts/android-e2e-test.js index ccc4f32e646bb1..1cba04a24e37b1 100644 --- a/scripts/android-e2e-test.js +++ b/scripts/android-e2e-test.js @@ -84,7 +84,7 @@ describe('Android Test App', function() { }; // React Native in dev mode often starts with Red Box "Can't fibd variable __fbBatchedBridge..." - // This is fixed by clicking Reload JS which will trigger a request to packager server + // This is fixed by clicking Reload JS which will trigger a request to Metro return driver .init(desired) .setImplicitWaitTimeout(5000) diff --git a/scripts/run-ci-e2e-tests.js b/scripts/run-ci-e2e-tests.js index ee16a0a4a5961f..de988e39012fcc 100644 --- a/scripts/run-ci-e2e-tests.js +++ b/scripts/run-ci-e2e-tests.js @@ -170,7 +170,7 @@ try { throw Error(exitCode); } - describe(`Start packager server, ${SERVER_PID}`); + describe(`Start Metro, ${SERVER_PID}`); // shelljs exec('', {async: true}) does not emit stdout events, so we rely on good old spawn const packagerProcess = spawn('yarn', ['start', '--max-workers 1'], { env: process.env, @@ -200,7 +200,7 @@ try { // shelljs exec('', {async: true}) does not emit stdout events, so we rely on good old spawn const packagerEnv = Object.create(process.env); packagerEnv.REACT_NATIVE_MAX_WORKERS = 1; - describe('Start packager server'); + describe('Start Metro'); const packagerProcess = spawn('yarn', ['start'], { stdio: 'inherit', env: packagerEnv, @@ -211,7 +211,7 @@ try { exec( 'response=$(curl --write-out %{http_code} --silent --output /dev/null localhost:8081/index.bundle?platform=ios&dev=true)', ); - echo(`Packager server up and running, ${SERVER_PID}`); + echo(`Metro is running, ${SERVER_PID}`); describe('Install CocoaPod dependencies'); exec('pod install'); diff --git a/template/App.js b/template/App.js index 5d410f6da3f29a..f85f38d87adde7 100644 --- a/template/App.js +++ b/template/App.js @@ -59,20 +59,6 @@ const App: () => Node = () => { backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, }; - const hermes = global.HermesInternal ? ( - - - Engine: Hermes - - - ) : null; - return ( @@ -80,7 +66,6 @@ const App: () => Node = () => { contentInsetAdjustmentBehavior="automatic" style={backgroundStyle}>
- {hermes} Node = () => { }; const styles = StyleSheet.create({ - engine: { - position: 'absolute', - right: 0, - }, sectionContainer: { marginTop: 32, paddingHorizontal: 24, @@ -126,13 +107,6 @@ const styles = StyleSheet.create({ highlight: { fontWeight: '700', }, - footer: { - fontSize: 12, - fontWeight: '600', - padding: 4, - paddingRight: 12, - textAlign: 'right', - }, }); export default App; diff --git a/template/_flowconfig b/template/_flowconfig index b94086e0b04702..8595b44b162e33 100644 --- a/template/_flowconfig +++ b/template/_flowconfig @@ -69,4 +69,4 @@ untyped-import untyped-type-import [version] -^0.127.0 +^0.129.0 diff --git a/template/android/build.gradle b/template/android/build.gradle index ed5a5684245790..8a15ee84738e3f 100644 --- a/template/android/build.gradle +++ b/template/android/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { - buildToolsVersion = "29.0.2" + buildToolsVersion = "29.0.3" minSdkVersion = 16 compileSdkVersion = 29 targetSdkVersion = 29 diff --git a/template/android/gradle/wrapper/gradle-wrapper.jar b/template/android/gradle/wrapper/gradle-wrapper.jar index 490fda8577df6c..62d4c053550b91 100644 Binary files a/template/android/gradle/wrapper/gradle-wrapper.jar and b/template/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/template/android/gradle/wrapper/gradle-wrapper.properties b/template/android/gradle/wrapper/gradle-wrapper.properties index 6623300bebd011..186b71557c5013 100644 --- a/template/android/gradle/wrapper/gradle-wrapper.properties +++ b/template/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/template/android/gradlew b/template/android/gradlew index 2fe81a7d95e4f9..fbd7c515832dab 100755 --- a/template/android/gradlew +++ b/template/android/gradlew @@ -82,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -129,6 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/template/android/gradlew.bat b/template/android/gradlew.bat index 9109989e3cbf66..a9f778a7a964b6 100644 --- a/template/android/gradlew.bat +++ b/template/android/gradlew.bat @@ -84,6 +84,7 @@ set CMD_LINE_ARGS=%* set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% diff --git a/template/package.json b/template/package.json index e5f9beb0d0538c..4604bb182a7d24 100644 --- a/template/package.json +++ b/template/package.json @@ -20,7 +20,7 @@ "babel-jest": "^25.1.0", "eslint": "^6.5.1", "jest": "^25.1.0", - "metro-react-native-babel-preset": "^0.59.0", + "metro-react-native-babel-preset": "^0.60.0", "react-test-renderer": "16.13.1" }, "jest": { diff --git a/yarn.lock b/yarn.lock index c8383b9a0dc562..98979cb37b0703 100644 --- a/yarn.lock +++ b/yarn.lock @@ -681,7 +681,7 @@ pirates "^4.0.0" source-map-support "^0.5.16" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.8.4": version "7.10.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.0.tgz#2cdcd6d7a391c24f7154235134c830cfb58ac0b1" integrity sha512-tgYb3zVApHbLHYOPWtVwg25sBqHhfBXRKeKoTIyoheIxln1nA7oBl7SfHfiTG2GhDPI8EUBkOD/0wJCP/3HN4Q== @@ -2866,10 +2866,10 @@ eslint-plugin-prettier@2.6.2: fast-diff "^1.1.1" jest-docblock "^21.0.0" -eslint-plugin-react-hooks@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-3.0.0.tgz#9e80c71846eb68dd29c3b21d832728aa66e5bd35" - integrity sha512-EjxTHxjLKIBWFgDJdhKKzLh5q+vjTFrqNZX36uIxWS4OfyXe5DawqPj3U5qeJ1ngLwatjzQnmR0Lz0J0YH3kxw== +eslint-plugin-react-hooks@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.7.tgz#19f9e3d07dd3a0fb9e6f0f07153707feedea8108" + integrity sha512-5PuW2OMHQyMLr/+MqTluYN3/NeJJ1RuvmEp5TR9Xl2gXKxvcusUZuMz8XBUtbELNaiRYWs693LQs0cljKuuHRQ== eslint-plugin-react-native-globals@^0.1.1: version "0.1.2" @@ -3319,10 +3319,10 @@ flat-cache@^1.2.1: rimraf "~2.6.2" write "^0.2.1" -flow-bin@^0.127.0: - version "0.127.0" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.127.0.tgz#0614cff4c1b783beef1feeb7108d536e09d77632" - integrity sha512-ywvCCdV4NJWzrqjFrMU5tAiVGyBiXjsJQ1+/kj8thXyX15V17x8BFvNwoAH97NrUU8T1HzmFBjLzWc0l2319qg== +flow-bin@^0.129.0: + version "0.129.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.129.0.tgz#50294d6e335dd97d095c27b096ea0b94c2415d55" + integrity sha512-WLXOj09oCK6nODVKM5uxvAzBpxXeI304E60tELMeQd/TJsyfbykNCZ+e4xml9eUOyoac9nDL3YrJpPZMzq0tMA== flow-parser@0.*: version "0.89.0" @@ -5092,10 +5092,10 @@ metro-babel-register@0.58.0: core-js "^2.2.2" escape-string-regexp "^1.0.5" -metro-babel-register@0.59.0: - version "0.59.0" - resolved "https://registry.yarnpkg.com/metro-babel-register/-/metro-babel-register-0.59.0.tgz#2bcff65641b36794cf083ba732fbc46cf870fb43" - integrity sha512-JtWc29erdsXO/V3loenXKw+aHUXgj7lt0QPaZKPpctLLy8kcEpI/8pfXXgVK9weXICCpCnYtYncIosAyzh0xjg== +metro-babel-register@0.60.0: + version "0.60.0" + resolved "https://registry.yarnpkg.com/metro-babel-register/-/metro-babel-register-0.60.0.tgz#5ece59e0d05c0015d3ad6094932e7679c68a0273" + integrity sha512-zUmx9srlkX0dThdpRN/jWcOGG89mIaLT9+BMrFn2Pb1kqwnThfu2vm+eRIQPbFGrceVCXjL5OSXlBlhguo5bog== dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -5114,13 +5114,13 @@ metro-babel-transformer@0.58.0: "@babel/core" "^7.0.0" metro-source-map "0.58.0" -metro-babel-transformer@0.59.0: - version "0.59.0" - resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.59.0.tgz#dda99c75d831b00142c42c020c51c103b29f199d" - integrity sha512-fdZJl8rs54GVFXokxRdD7ZrQ1TJjxWzOi/xSP25VR3E8tbm3nBZqS+/ylu643qSr/IueABR+jrlqAyACwGEf6w== +metro-babel-transformer@0.60.0: + version "0.60.0" + resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.60.0.tgz#92e0b2309372b3b1335cb94dd1348f841d1d198e" + integrity sha512-7liSVU+Ighta38l12BrIlhUtphxdZddTMrJrnUSbNM9J3fMsWUVZogpnH8reioiEiu4D4mwTHEukIbiFJyGdHg== dependencies: "@babel/core" "^7.0.0" - metro-source-map "0.59.0" + metro-source-map "0.60.0" metro-cache@0.58.0: version "0.58.0" @@ -5213,10 +5213,10 @@ metro-react-native-babel-preset@0.58.0: "@babel/template" "^7.0.0" react-refresh "^0.4.0" -metro-react-native-babel-preset@0.59.0: - version "0.59.0" - resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.59.0.tgz#20e020bc6ac9849e1477de1333d303ed42aba225" - integrity sha512-BoO6ncPfceIDReIH8pQ5tQptcGo5yRWQXJGVXfANbiKLq4tfgdZB1C1e2rMUJ6iypmeJU9dzl+EhPmIFKtgREg== +metro-react-native-babel-preset@0.60.0: + version "0.60.0" + resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.60.0.tgz#9aaaeca10a04c117ea84fb18e54c44ddcaad2ab8" + integrity sha512-eByxCh52XWyMwh4eduzLz0HkraOX7X/tJBsOXvxhUaj8UEolxMYQWD1DO7rJbFiKfZbxRPortltDYzjJI4MpdQ== dependencies: "@babel/plugin-proposal-class-properties" "^7.0.0" "@babel/plugin-proposal-export-default-from" "^7.0.0" @@ -5257,16 +5257,16 @@ metro-react-native-babel-preset@0.59.0: "@babel/template" "^7.0.0" react-refresh "^0.4.0" -metro-react-native-babel-transformer@0.59.0: - version "0.59.0" - resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.59.0.tgz#9b3dfd6ad35c6ef37fc4ce4d20a2eb67fabbb4be" - integrity sha512-1O3wrnMq4NcPQ1asEcl9lRDn/t+F1Oef6S9WaYVIKEhg9m/EQRGVrrTVP+R6B5Eeaj3+zNKbzM8Dx/NWy1hUbQ== +metro-react-native-babel-transformer@0.60.0: + version "0.60.0" + resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.60.0.tgz#669b44b02aca8692b346e1cfd80a57c68fb53edd" + integrity sha512-ky5AGt97tOQnr5YQ9CPKTjlekP+uprFN0FlNW3LH54LpE3q9o++IerBKvq1KJZgYLvpiSAiqsia+IH1LAP8hrA== dependencies: "@babel/core" "^7.0.0" babel-preset-fbjs "^3.3.0" - metro-babel-transformer "0.59.0" - metro-react-native-babel-preset "0.59.0" - metro-source-map "0.59.0" + metro-babel-transformer "0.60.0" + metro-react-native-babel-preset "0.60.0" + metro-source-map "0.60.0" metro-react-native-babel-transformer@^0.58.0: version "0.58.0" @@ -5299,16 +5299,16 @@ metro-source-map@0.58.0: source-map "^0.5.6" vlq "^1.0.0" -metro-source-map@0.59.0: - version "0.59.0" - resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.59.0.tgz#e9beb9fc51bfb4e060f95820cf1508fc122d23f7" - integrity sha512-0w5CmCM+ybSqXIjqU4RiK40t4bvANL6lafabQ2GP2XD3vSwkLY+StWzCtsb4mPuyi9R/SgoLBel+ZOXHXAH0eQ== +metro-source-map@0.60.0: + version "0.60.0" + resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.60.0.tgz#a7abfed408bc07006aecac1f4a845edf5257272c" + integrity sha512-zq7gP4SwZ0dtM3J9kBMVd+FVS2v6/4hmR21cTOAXk7Mi7KnSnb1onuQaTwi4m6fdlr19N9A7Ad2VU0q+uVlFyA== dependencies: "@babel/traverse" "^7.0.0" "@babel/types" "^7.0.0" invariant "^2.2.4" - metro-symbolicate "0.59.0" - ob1 "0.59.0" + metro-symbolicate "0.60.0" + ob1 "0.60.0" source-map "^0.5.6" vlq "^1.0.0" @@ -5323,13 +5323,13 @@ metro-symbolicate@0.58.0: through2 "^2.0.1" vlq "^1.0.0" -metro-symbolicate@0.59.0: - version "0.59.0" - resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.59.0.tgz#fc7f93957a42b02c2bfc57ed1e8f393f5f636a54" - integrity sha512-asLaF2A7rndrToGFIknL13aiohwPJ95RKHf0NM3hP/nipiLDoMzXT6ZnQvBqDxkUKyP+51AI75DMtb+Wcyw4Bw== +metro-symbolicate@0.60.0: + version "0.60.0" + resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.60.0.tgz#11e962d3b5cc0a7c7389a44979772cbce168ddb8" + integrity sha512-H9g6CflHY0k+dpuxvmRIFs1FyGbxs/IhBlfBks0c3L9wr+1mJT63K5pjlhoonKB0c///wMTP+kUuSMRRox2qhQ== dependencies: invariant "^2.2.4" - metro-source-map "0.59.0" + metro-source-map "0.60.0" source-map "^0.5.6" through2 "^2.0.1" vlq "^1.0.0" @@ -5781,10 +5781,10 @@ ob1@0.58.0: resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.58.0.tgz#484a1e9a63a8b79d9ea6f3a83b2a42110faac973" integrity sha512-uZP44cbowAfHafP1k4skpWItk5iHCoRevMfrnUvYCfyNNPPJd3rfDCyj0exklWi2gDXvjlj2ObsfiqP/bs/J7Q== -ob1@0.59.0: - version "0.59.0" - resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.59.0.tgz#ee103619ef5cb697f2866e3577da6f0ecd565a36" - integrity sha512-opXMTxyWJ9m68ZglCxwo0OPRESIC/iGmKFPXEXzMZqsVIrgoRXOHmoMDkQzz4y3irVjbyPJRAh5pI9fd0MJTFQ== +ob1@0.60.0: + version "0.60.0" + resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.60.0.tgz#0f79e7c60dcd666819413e836d8f473ea83cfc7a" + integrity sha512-hi8vHBZiYIIoQMzwieQGv+VnPMFNaDciSyZhQNTXl54NB0MYOviQoeEbVqD6dcTHPE8nuJMbIZ0lIRiksAMB0A== object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" @@ -6386,6 +6386,14 @@ react-refresh@^0.4.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.4.0.tgz#d421f9bd65e0e4b9822a399f14ac56bda9c92292" integrity sha512-bacjSio8GOtzNZKZZM6EWqbhlbb6pr28JWJWFTLwEBKvPIBRo6/Ob68D2EWZA2VyTdQxAh+TRnCYOPNKsQiXTA== +react-shallow-renderer@16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.13.1.tgz#4cfd6dc0f05a8d4d261ff7a80e9b88f15491a00a" + integrity sha512-hLmExm5/ZnjodLgm/4oxYw4i7fL6LLPhbO9mF/4tmaZUurtLrp2aSeDHZmRk0SVCHXPz0VaEbb3Dqi5J7odz7Q== + dependencies: + object-assign "^4.1.1" + react-is "^16.12.0" + react-test-renderer@16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1"