From 2ae43e5aa0c18ec8a8877e077d0975cbc043443b Mon Sep 17 00:00:00 2001 From: Emily Janzer Date: Wed, 12 Jun 2019 12:10:44 -0700 Subject: [PATCH] Replace UIManager with stub for bridgeless RN Summary: Replacing UIManager.js with a shim that redirects to either PaperUIManager (containing old impl) or DummyUIManager, if `global.RN$Bridgeless` is set. The UIManager native module doesn't exist in bridgeless mode, which means requiring UIManager.js currently fatals. This is a bit hacky, but it's a lot easier than implementing a dummy native module to make it happy. I did have to stub out all the properties in UIManagerJSInterface to appease flow, though... Reviewed By: yungsters Differential Revision: D15775582 fbshipit-source-id: 8e2628f75b2242971895583696122760acdad7af --- Libraries/ReactNative/DummyUIManager.js | 118 +++++++++++++++ Libraries/ReactNative/PaperUIManager.js | 182 ++++++++++++++++++++++++ Libraries/ReactNative/UIManager.js | 174 +--------------------- 3 files changed, 305 insertions(+), 169 deletions(-) create mode 100644 Libraries/ReactNative/DummyUIManager.js create mode 100644 Libraries/ReactNative/PaperUIManager.js diff --git a/Libraries/ReactNative/DummyUIManager.js b/Libraries/ReactNative/DummyUIManager.js new file mode 100644 index 00000000000..160e1901f8c --- /dev/null +++ b/Libraries/ReactNative/DummyUIManager.js @@ -0,0 +1,118 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ +'use strict'; + +module.exports = { + getViewManagerConfig: (viewManagerName: string) => { + throw new Error( + 'Attempting to get config for view manager ' + viewManagerName, + ); + }, + getConstants: () => ({}), + getConstantsForViewManager: (viewManagerName: string) => {}, + getDefaultEventTypes: () => [], + playTouchSound: () => {}, + lazilyLoadView: (name: string) => {}, + createView: ( + reactTag: ?number, + viewName: string, + rootTag: number, + props: Object, + ) => {}, + updateView: (reactTag: number, viewName: string, props: Object) => {}, + focus: (reactTag: ?number) => {}, + blur: (reactTag: ?number) => {}, + findSubviewIn: ( + reactTag: ?number, + point: Array, + callback: ( + nativeViewTag: number, + left: number, + top: number, + width: number, + height: number, + ) => void, + ) => {}, + dispatchViewManagerCommand: ( + reactTag: ?number, + commandID: number, + commandArgs: ?Array, + ) => {}, + measure: ( + reactTag: ?number, + callback: ( + left: number, + top: number, + width: number, + height: number, + pageX: number, + pageY: number, + ) => void, + ) => {}, + measureInWindow: ( + reactTag: ?number, + callback: (x: number, y: number, width: number, height: number) => void, + ) => {}, + viewIsDescendantOf: ( + reactTag: ?number, + ancestorReactTag: ?number, + callback: (result: Array) => void, + ) => {}, + measureLayout: ( + reactTag: ?number, + ancestorReactTag: ?number, + errorCallback: (error: Object) => void, + callback: ( + left: number, + top: number, + width: number, + height: number, + ) => void, + ) => {}, + measureLayoutRelativeToParent: ( + reactTag: ?number, + errorCallback: (error: Object) => void, + callback: ( + left: number, + top: number, + width: number, + height: number, + ) => void, + ) => {}, + setJSResponder: (reactTag: ?number, blockNativeResponder: boolean) => {}, + clearJSResponder: () => {}, + configureNextLayoutAnimation: ( + config: Object, + callback: () => void, + errorCallback: (error: Object) => void, + ) => {}, + removeSubviewsFromContainerWithID: (containerID: number) => {}, + replaceExistingNonRootView: (reactTag: ?number, newReactTag: ?number) => {}, + setChildren: (containerTag: ?number, reactTags: Array) => {}, + manageChildren: ( + containerTag: ?number, + moveFromIndices: Array, + moveToIndices: Array, + addChildReactTags: Array, + addAtIndices: Array, + removeAtIndices: Array, + ) => {}, + + // Android only + setLayoutAnimationEnabledExperimental: (enabled: boolean) => {}, + sendAccessibilityEvent: (reactTag: ?number, eventType: number) => {}, + showPopupMenu: ( + reactTag: ?number, + items: Array, + error: (error: Object) => void, + success: (event: string, selected?: number) => void, + ) => {}, + dismissPopupMenu: () => {}, +}; diff --git a/Libraries/ReactNative/PaperUIManager.js b/Libraries/ReactNative/PaperUIManager.js new file mode 100644 index 00000000000..f37841fdb62 --- /dev/null +++ b/Libraries/ReactNative/PaperUIManager.js @@ -0,0 +1,182 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ +'use strict'; + +const NativeModules = require('../BatchedBridge/NativeModules'); +const Platform = require('../Utilities/Platform'); +const UIManagerProperties = require('./UIManagerProperties'); + +const defineLazyObjectProperty = require('../Utilities/defineLazyObjectProperty'); + +import NativeUIManager from './NativeUIManager'; + +const viewManagerConfigs = {}; + +const triedLoadingConfig = new Set(); + +let NativeUIManagerConstants = {}; +let isNativeUIManagerConstantsSet = false; +function getConstants(): Object { + if (!isNativeUIManagerConstantsSet) { + NativeUIManagerConstants = NativeUIManager.getConstants(); + isNativeUIManagerConstantsSet = true; + } + return NativeUIManagerConstants; +} + +const UIManagerJS = { + ...NativeUIManager, + getConstants(): Object { + return getConstants(); + }, + getViewManagerConfig: function(viewManagerName: string) { + if ( + viewManagerConfigs[viewManagerName] === undefined && + NativeUIManager.getConstantsForViewManager + ) { + try { + viewManagerConfigs[ + viewManagerName + ] = NativeUIManager.getConstantsForViewManager(viewManagerName); + } catch (e) { + viewManagerConfigs[viewManagerName] = null; + } + } + + const config = viewManagerConfigs[viewManagerName]; + if (config) { + return config; + } + + // If we're in the Chrome Debugger, let's not even try calling the sync + // method. + if (!global.nativeCallSyncHook) { + return config; + } + + if ( + NativeUIManager.lazilyLoadView && + !triedLoadingConfig.has(viewManagerName) + ) { + const result = NativeUIManager.lazilyLoadView(viewManagerName); + triedLoadingConfig.add(viewManagerName); + if (result.viewConfig) { + getConstants()[viewManagerName] = result.viewConfig; + lazifyViewManagerConfig(viewManagerName); + } + } + + return viewManagerConfigs[viewManagerName]; + }, +}; + +// TODO (T45220498): Remove this. +// 3rd party libs may be calling `NativeModules.UIManager.getViewManagerConfig()` +// instead of `UIManager.getViewManagerConfig()` off UIManager.js. +// This is a workaround for now. +// $FlowFixMe +NativeUIManager.getViewManagerConfig = UIManagerJS.getViewManagerConfig; + +function lazifyViewManagerConfig(viewName) { + const viewConfig = getConstants()[viewName]; + if (viewConfig.Manager) { + viewManagerConfigs[viewName] = viewConfig; + defineLazyObjectProperty(viewConfig, 'Constants', { + get: () => { + const viewManager = NativeModules[viewConfig.Manager]; + const constants = {}; + viewManager && + Object.keys(viewManager).forEach(key => { + const value = viewManager[key]; + if (typeof value !== 'function') { + constants[key] = value; + } + }); + return constants; + }, + }); + defineLazyObjectProperty(viewConfig, 'Commands', { + get: () => { + const viewManager = NativeModules[viewConfig.Manager]; + const commands = {}; + let index = 0; + viewManager && + Object.keys(viewManager).forEach(key => { + const value = viewManager[key]; + if (typeof value === 'function') { + commands[key] = index++; + } + }); + return commands; + }, + }); + } +} + +/** + * Copies the ViewManager constants and commands into UIManager. This is + * only needed for iOS, which puts the constants in the ViewManager + * namespace instead of UIManager, unlike Android. + */ +if (Platform.OS === 'ios') { + Object.keys(getConstants()).forEach(viewName => { + lazifyViewManagerConfig(viewName); + }); +} else if (getConstants().ViewManagerNames) { + // We want to add all the view managers to the UIManager. + // However, the way things are set up, the list of view managers is not known at compile time. + // As Prepack runs at compile it, it cannot process this loop. + // So we wrap it in a special __residual call, which basically tells Prepack to ignore it. + let residual = global.__residual + ? global.__residual + : (_, f, ...args) => f.apply(undefined, args); + residual( + 'void', + (UIManager, defineLazyObjectProperty) => { + UIManager.getConstants().ViewManagerNames.forEach(viewManagerName => { + defineLazyObjectProperty(UIManager, viewManagerName, { + get: () => UIManager.getConstantsForViewManager(viewManagerName), + }); + }); + }, + NativeUIManager, + defineLazyObjectProperty, + ); + + // As Prepack now no longer knows which properties exactly the UIManager has, + // we also tell Prepack that it has only partial knowledge of the UIManager, + // so that any accesses to unknown properties along the global code will fail + // when Prepack encounters them. + if (global.__makePartial) { + global.__makePartial(NativeUIManager); + } +} + +if (!global.nativeCallSyncHook) { + Object.keys(getConstants()).forEach(viewManagerName => { + if (!UIManagerProperties.includes(viewManagerName)) { + if (!viewManagerConfigs[viewManagerName]) { + viewManagerConfigs[viewManagerName] = getConstants()[viewManagerName]; + } + defineLazyObjectProperty(NativeUIManager, viewManagerName, { + get: () => { + console.warn( + `Accessing view manager configs directly off UIManager via UIManager['${viewManagerName}'] ` + + `is no longer supported. Use UIManager.getViewManagerConfig('${viewManagerName}') instead.`, + ); + + return UIManagerJS.getViewManagerConfig(viewManagerName); + }, + }); + } + }); +} + +module.exports = UIManagerJS; diff --git a/Libraries/ReactNative/UIManager.js b/Libraries/ReactNative/UIManager.js index 19dfa44853a..15a74e2bef0 100644 --- a/Libraries/ReactNative/UIManager.js +++ b/Libraries/ReactNative/UIManager.js @@ -9,17 +9,8 @@ */ 'use strict'; -const NativeModules = require('../BatchedBridge/NativeModules'); -const Platform = require('../Utilities/Platform'); -const UIManagerProperties = require('./UIManagerProperties'); - -const defineLazyObjectProperty = require('../Utilities/defineLazyObjectProperty'); - -import NativeUIManager from './NativeUIManager'; import type {Spec} from './NativeUIManager'; -const viewManagerConfigs = {}; - interface UIManagerJSInterface extends Spec { +getViewManagerConfig: (viewManagerName: string) => Object; // The following are not marked read-only due to logic in UIManagerStatTracker. @@ -40,164 +31,9 @@ interface UIManagerJSInterface extends Spec { ) => void; } -const triedLoadingConfig = new Set(); +const UIManager: UIManagerJSInterface = + global.RN$Bridgeless === true + ? require('./DummyUIManager') // No UIManager in bridgeless mode + : require('./PaperUIManager'); -let NativeUIManagerConstants = {}; -let isNativeUIManagerConstantsSet = false; -function getConstants(): Object { - if (!isNativeUIManagerConstantsSet) { - NativeUIManagerConstants = NativeUIManager.getConstants(); - isNativeUIManagerConstantsSet = true; - } - return NativeUIManagerConstants; -} - -const UIManagerJS: UIManagerJSInterface = { - ...NativeUIManager, - getConstants(): Object { - return getConstants(); - }, - getViewManagerConfig: function(viewManagerName: string) { - if ( - viewManagerConfigs[viewManagerName] === undefined && - NativeUIManager.getConstantsForViewManager - ) { - try { - viewManagerConfigs[ - viewManagerName - ] = NativeUIManager.getConstantsForViewManager(viewManagerName); - } catch (e) { - viewManagerConfigs[viewManagerName] = null; - } - } - - const config = viewManagerConfigs[viewManagerName]; - if (config) { - return config; - } - - // If we're in the Chrome Debugger, let's not even try calling the sync - // method. - if (!global.nativeCallSyncHook) { - return config; - } - - if ( - NativeUIManager.lazilyLoadView && - !triedLoadingConfig.has(viewManagerName) - ) { - const result = NativeUIManager.lazilyLoadView(viewManagerName); - triedLoadingConfig.add(viewManagerName); - if (result.viewConfig) { - getConstants()[viewManagerName] = result.viewConfig; - lazifyViewManagerConfig(viewManagerName); - } - } - - return viewManagerConfigs[viewManagerName]; - }, -}; - -// TODO (T45220498): Remove this. -// 3rd party libs may be calling `NativeModules.UIManager.getViewManagerConfig()` -// instead of `UIManager.getViewManagerConfig()` off UIManager.js. -// This is a workaround for now. -// $FlowFixMe -NativeUIManager.getViewManagerConfig = UIManagerJS.getViewManagerConfig; - -function lazifyViewManagerConfig(viewName) { - const viewConfig = getConstants()[viewName]; - if (viewConfig.Manager) { - viewManagerConfigs[viewName] = viewConfig; - defineLazyObjectProperty(viewConfig, 'Constants', { - get: () => { - const viewManager = NativeModules[viewConfig.Manager]; - const constants = {}; - viewManager && - Object.keys(viewManager).forEach(key => { - const value = viewManager[key]; - if (typeof value !== 'function') { - constants[key] = value; - } - }); - return constants; - }, - }); - defineLazyObjectProperty(viewConfig, 'Commands', { - get: () => { - const viewManager = NativeModules[viewConfig.Manager]; - const commands = {}; - let index = 0; - viewManager && - Object.keys(viewManager).forEach(key => { - const value = viewManager[key]; - if (typeof value === 'function') { - commands[key] = index++; - } - }); - return commands; - }, - }); - } -} - -/** - * Copies the ViewManager constants and commands into UIManager. This is - * only needed for iOS, which puts the constants in the ViewManager - * namespace instead of UIManager, unlike Android. - */ -if (Platform.OS === 'ios') { - Object.keys(getConstants()).forEach(viewName => { - lazifyViewManagerConfig(viewName); - }); -} else if (getConstants().ViewManagerNames) { - // We want to add all the view managers to the UIManager. - // However, the way things are set up, the list of view managers is not known at compile time. - // As Prepack runs at compile it, it cannot process this loop. - // So we wrap it in a special __residual call, which basically tells Prepack to ignore it. - let residual = global.__residual - ? global.__residual - : (_, f, ...args) => f.apply(undefined, args); - residual( - 'void', - (UIManager, defineLazyObjectProperty) => { - UIManager.getConstants().ViewManagerNames.forEach(viewManagerName => { - defineLazyObjectProperty(UIManager, viewManagerName, { - get: () => UIManager.getConstantsForViewManager(viewManagerName), - }); - }); - }, - NativeUIManager, - defineLazyObjectProperty, - ); - - // As Prepack now no longer knows which properties exactly the UIManager has, - // we also tell Prepack that it has only partial knowledge of the UIManager, - // so that any accesses to unknown properties along the global code will fail - // when Prepack encounters them. - if (global.__makePartial) { - global.__makePartial(NativeUIManager); - } -} - -if (!global.nativeCallSyncHook) { - Object.keys(getConstants()).forEach(viewManagerName => { - if (!UIManagerProperties.includes(viewManagerName)) { - if (!viewManagerConfigs[viewManagerName]) { - viewManagerConfigs[viewManagerName] = getConstants()[viewManagerName]; - } - defineLazyObjectProperty(NativeUIManager, viewManagerName, { - get: () => { - console.warn( - `Accessing view manager configs directly off UIManager via UIManager['${viewManagerName}'] ` + - `is no longer supported. Use UIManager.getViewManagerConfig('${viewManagerName}') instead.`, - ); - - return UIManagerJS.getViewManagerConfig(viewManagerName); - }, - }); - } - }); -} - -module.exports = UIManagerJS; +module.exports = UIManager;