diff --git a/Libraries/Utilities/Dimensions.js b/Libraries/Utilities/Dimensions.js index a0fd7f308dc8bb..898c8cbe89580f 100644 --- a/Libraries/Utilities/Dimensions.js +++ b/Libraries/Utilities/Dimensions.js @@ -20,54 +20,17 @@ var invariant = require('fbjs/lib/invariant'); var eventEmitter = new EventEmitter(); var dimensionsInitialized = false; var dimensions = {}; - -var dimensionsProvider: ?(() => {[key:string]: Object}); - class Dimensions { - static setProvider(value: () => {[key:string]: Object}): void { - dimensionsProvider = value; - dimensionsInitialized = false; - dimensions = {}; - } - - static getDimensions(): {[key:string]: Object} { - if (dimensionsInitialized) { - return dimensions; - } - - // We calculate the window dimensions in JS so that we don't encounter loss of - // precision in transferring the dimensions (which could be non-integers) over - // the bridge. - const dims = (dimensionsProvider || defaultDimProvider)(); - const result = Dimensions.updateDimensions(dims); - RCTDeviceEventEmitter.addListener('didUpdateDimensions', function(update) { - Dimensions.updateDimensions(update); - }); - return result; - } - /** - * Initial dimensions are set before `runApplication` is called so they should - * be available before any other require's are run, but may be updated later. - * - * Note: Although dimensions are available immediately, they may change (e.g - * due to device rotation) so any rendering logic or styles that depend on - * these constants should try to call this function on every render, rather - * than caching the value (for example, using inline styles rather than - * setting a value in a `StyleSheet`). - * - * Example: `var {height, width} = Dimensions.get('window');` + * This should only be called from native code by sending the + * didUpdateDimensions event. * - * @param {string} dim Name of dimension as defined when calling `set`. - * @returns {Object?} Value for the dimension. + * @param {object} dims Simple string-keyed object of dimensions to set */ - static get(dim: string): Object { - const dims = Dimensions.getDimensions(); - invariant(dims[dim], 'No dimension set for key ' + dim); - return dims[dim]; - } - - static updateDimensions(dims: ?{[key:string]: Object}): {[key:string]: Object} { + static set(dims: {[key:string]: any}): void { + // We calculate the window dimensions in JS so that we don't encounter loss of + // precision in transferring the dimensions (which could be non-integers) over + // the bridge. if (dims && dims.windowPhysicalPixels) { // parse/stringify => Clone hack dims = JSON.parse(JSON.stringify(dims)); @@ -108,7 +71,26 @@ class Dimensions { } else { dimensionsInitialized = true; } - return dimensions; + } + + /** + * Initial dimensions are set before `runApplication` is called so they should + * be available before any other require's are run, but may be updated later. + * + * Note: Although dimensions are available immediately, they may change (e.g + * due to device rotation) so any rendering logic or styles that depend on + * these constants should try to call this function on every render, rather + * than caching the value (for example, using inline styles rather than + * setting a value in a `StyleSheet`). + * + * Example: `var {height, width} = Dimensions.get('window');` + * + * @param {string} dim Name of dimension as defined when calling `set`. + * @returns {Object?} Value for the dimension. + */ + static get(dim: string): Object { + invariant(dimensions[dim], 'No dimension set for key ' + dim); + return dimensions[dim]; } /** @@ -145,8 +127,20 @@ class Dimensions { } } -function defaultDimProvider(): {[key:string]: Object} { - return require('DeviceInfo').Dimensions; +let dims: ?{[key: string]: any} = global.nativeExtensions && global.nativeExtensions.DeviceInfo && global.nativeExtensions.DeviceInfo.Dimensions; +let nativeExtensionsEnabled = true; +if (!dims) { + const DeviceInfo = require('DeviceInfo'); + dims = DeviceInfo.Dimensions; + nativeExtensionsEnabled = false; +} + +invariant(dims, 'Either DeviceInfo native extension or DeviceInfo Native Module must be registered'); +Dimensions.set(dims); +if (!nativeExtensionsEnabled) { + RCTDeviceEventEmitter.addListener('didUpdateDimensions', function(update) { + Dimensions.set(update); + }); } module.exports = Dimensions; diff --git a/React/CxxBridge/RCTCxxBridge.mm b/React/CxxBridge/RCTCxxBridge.mm index 72ad763f5c6acd..1fbb2fd43032d7 100644 --- a/React/CxxBridge/RCTCxxBridge.mm +++ b/React/CxxBridge/RCTCxxBridge.mm @@ -339,6 +339,7 @@ - (void)start #if RCT_PROFILE ("StartSamplingProfilerOnInit", (bool)self.devSettings.startSamplingProfilerOnLaunch) #endif + , nullptr )); } } else { diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.java index dec707a8506b4f..d6b43bf4b795a4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.java @@ -58,7 +58,7 @@ public String getName() { HashMap constants = new HashMap<>(); constants.put( "Dimensions", - getDimensionsConstants()); + DisplayMetricsHolder.getDisplayMetricsMap(mFontScale)); return constants; } @@ -90,31 +90,6 @@ public void emitUpdateDimensionsEvent() { mReactApplicationContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("didUpdateDimensions", getDimensionsConstants()); - } - - private WritableMap getDimensionsConstants() { - DisplayMetrics windowDisplayMetrics = DisplayMetricsHolder.getWindowDisplayMetrics(); - DisplayMetrics screenDisplayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics(); - - WritableMap windowDisplayMetricsMap = Arguments.createMap(); - windowDisplayMetricsMap.putInt("width", windowDisplayMetrics.widthPixels); - windowDisplayMetricsMap.putInt("height", windowDisplayMetrics.heightPixels); - windowDisplayMetricsMap.putDouble("scale", windowDisplayMetrics.density); - windowDisplayMetricsMap.putDouble("fontScale", mFontScale); - windowDisplayMetricsMap.putDouble("densityDpi", windowDisplayMetrics.densityDpi); - - WritableMap screenDisplayMetricsMap = Arguments.createMap(); - screenDisplayMetricsMap.putInt("width", screenDisplayMetrics.widthPixels); - screenDisplayMetricsMap.putInt("height", screenDisplayMetrics.heightPixels); - screenDisplayMetricsMap.putDouble("scale", screenDisplayMetrics.density); - screenDisplayMetricsMap.putDouble("fontScale", mFontScale); - screenDisplayMetricsMap.putDouble("densityDpi", screenDisplayMetrics.densityDpi); - - WritableMap dimensionsMap = Arguments.createMap(); - dimensionsMap.putMap("windowPhysicalPixels", windowDisplayMetricsMap); - dimensionsMap.putMap("screenPhysicalPixels", screenDisplayMetricsMap); - - return dimensionsMap; + .emit("didUpdateDimensions", DisplayMetricsHolder.getDisplayMetricsMap(mFontScale)); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK index 08e25d2c55f1f2..563bbe479a32b5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK @@ -2,13 +2,17 @@ load("//ReactNative:DEFS", "rn_android_library", "YOGA_TARGET", "react_native_de rn_android_library( name = "uimanager", - srcs = glob([ - "*.java", - "debug/*.java", - "events/*.java", - "layoutanimation/*.java", - ]), + srcs = glob( + [ + "*.java", + "debug/*.java", + "events/*.java", + "layoutanimation/*.java", + ], + excludes = ["DisplayMetricsHolder.java"], + ), exported_deps = [ + ":DisplayMetrics", react_native_dep("third-party/java/jsr-305:jsr-305"), ], provided_deps = [ @@ -21,6 +25,7 @@ rn_android_library( ], deps = [ YOGA_TARGET, + ":DisplayMetrics", react_native_dep("java/com/facebook/systrace:systrace"), react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"), react_native_dep("third-party/java/infer-annotations:infer-annotations"), @@ -38,3 +43,19 @@ rn_android_library( react_native_target("res:uimanager"), ], ) + +rn_android_library( + name = "DisplayMetrics", + srcs = glob([ + "DisplayMetricsHolder.java", + ]), + required_for_source_only_abi = True, + visibility = [ + "PUBLIC", + ], + deps = [ + react_native_dep("third-party/java/infer-annotations:infer-annotations"), + react_native_dep("third-party/java/jsr-305:jsr-305"), + react_native_target("java/com/facebook/react/bridge:bridge"), + ], +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.java index 04c26d8a23873a..a8d130ca621aa9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.java @@ -21,6 +21,7 @@ import android.view.WindowManager; import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.WritableNativeMap; /** * Holds an instance of the current DisplayMetrics so we don't have to thread it through all the @@ -104,4 +105,26 @@ public static void setScreenDisplayMetrics(DisplayMetrics screenDisplayMetrics) public static DisplayMetrics getScreenDisplayMetrics() { return sScreenDisplayMetrics; } + + public static WritableNativeMap getDisplayMetricsMap(double fontScale) { + Assertions.assertNotNull( + sWindowDisplayMetrics != null || sScreenDisplayMetrics != null, + "DisplayMetricsHolder must be initialized with initDisplayMetricsIfNotInitialized or initDisplayMetrics"); + final WritableNativeMap result = new WritableNativeMap(); + result.putMap("windowPhysicalPixels", getPhysicalPixelsMap(sWindowDisplayMetrics, fontScale)); + result.putMap("screenPhysicalPixels", getPhysicalPixelsMap(sScreenDisplayMetrics, fontScale)); + + return result; + } + + private static WritableNativeMap getPhysicalPixelsMap(DisplayMetrics displayMetrics, double fontScale) { + final WritableNativeMap result = new WritableNativeMap(); + result.putInt("width", displayMetrics.widthPixels); + result.putInt("height", displayMetrics.heightPixels); + result.putDouble("scale", displayMetrics.density); + result.putDouble("fontScale", fontScale); + result.putDouble("densityDpi", displayMetrics.densityDpi); + return result; + } + } diff --git a/ReactAndroid/src/main/jni/react/jni/AndroidJSCFactory.cpp b/ReactAndroid/src/main/jni/react/jni/AndroidJSCFactory.cpp index 0aba406fc24b86..ed551a61a2b590 100644 --- a/ReactAndroid/src/main/jni/react/jni/AndroidJSCFactory.cpp +++ b/ReactAndroid/src/main/jni/react/jni/AndroidJSCFactory.cpp @@ -90,9 +90,9 @@ void injectJSCExecutorAndroidPlatform() { } std::unique_ptr makeAndroidJSCExecutorFactory( - const folly::dynamic& jscConfig) { + const folly::dynamic& jscConfig, std::function nativeExtensionsProvider) { detail::injectJSCExecutorAndroidPlatform(); - return folly::make_unique(std::move(jscConfig)); + return folly::make_unique(std::move(jscConfig), std::move(nativeExtensionsProvider)); } } diff --git a/ReactAndroid/src/main/jni/react/jni/AndroidJSCFactory.h b/ReactAndroid/src/main/jni/react/jni/AndroidJSCFactory.h index 3bc5052978aea7..e1e782145c91e8 100644 --- a/ReactAndroid/src/main/jni/react/jni/AndroidJSCFactory.h +++ b/ReactAndroid/src/main/jni/react/jni/AndroidJSCFactory.h @@ -23,7 +23,7 @@ void injectJSCExecutorAndroidPlatform(); } std::unique_ptr makeAndroidJSCExecutorFactory( - const folly::dynamic& jscConfig); + const folly::dynamic& jscConfig, std::function nativeExtensionsProvider); } } diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 9804be9caadd4a..ec97e56e5b1d02 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -38,7 +38,7 @@ class JSCJavaScriptExecutorHolder : public HybridClass initHybrid(alias_ref, ReadableNativeMap* jscConfig) { - return makeCxxInstance(makeAndroidJSCExecutorFactory(jscConfig->consume())); + return makeCxxInstance(makeAndroidJSCExecutorFactory(jscConfig->consume(), nullptr)); } static void registerNatives() { diff --git a/ReactCommon/cxxreact/JSCExecutor.cpp b/ReactCommon/cxxreact/JSCExecutor.cpp index 09452f153b932b..a28b99e92126df 100644 --- a/ReactCommon/cxxreact/JSCExecutor.cpp +++ b/ReactCommon/cxxreact/JSCExecutor.cpp @@ -115,16 +115,18 @@ namespace facebook { std::unique_ptr JSCExecutorFactory::createJSExecutor( std::shared_ptr delegate, std::shared_ptr jsQueue) { - return folly::make_unique(delegate, jsQueue, m_jscConfig); + return folly::make_unique(delegate, jsQueue, m_jscConfig, m_nativeExtensionsProvider); } JSCExecutor::JSCExecutor(std::shared_ptr delegate, std::shared_ptr messageQueueThread, - const folly::dynamic& jscConfig) throw(JSException) : + const folly::dynamic& jscConfig, + std::function nativeExtensionsProvider) throw(JSException) : m_delegate(delegate), m_messageQueueThread(messageQueueThread), m_nativeModules(delegate ? delegate->getModuleRegistry() : nullptr), - m_jscConfig(jscConfig) { + m_jscConfig(jscConfig), + m_nativeExtensionsProvider(nativeExtensionsProvider) { initOnJSVMThread(); { @@ -132,6 +134,8 @@ namespace facebook { installGlobalProxy(m_context, "nativeModuleProxy", exceptionWrapMethod<&JSCExecutor::getNativeModule>()); } + installGlobalProxy(m_context, "nativeExtensions", + exceptionWrapMethod<&JSCExecutor::getNativeExtension>()); } JSCExecutor::~JSCExecutor() { @@ -632,8 +636,8 @@ namespace facebook { return String::adopt(m_context, jsString); #else return script->isAscii() - ? String::createExpectingAscii(m_context, script->c_str(), script->size()) - : String(m_context, script->c_str()); + ? String::createExpectingAscii(m_context, script->c_str(), script->size()) + : String(m_context, script->c_str()); #endif } @@ -677,6 +681,14 @@ namespace facebook { return m_nativeModules.getModule(m_context, propertyName); } + JSValueRef JSCExecutor::getNativeExtension(JSObjectRef object, JSStringRef propertyName) { + if (m_nativeExtensionsProvider) { + folly::dynamic value = m_nativeExtensionsProvider(String::ref(m_context, propertyName).str()); + return Value::fromDynamic(m_context, std::move(value)); + } + return JSC_JSValueMakeUndefined(m_context); + } + JSValueRef JSCExecutor::nativeRequire( size_t argumentCount, const JSValueRef arguments[]) { diff --git a/ReactCommon/cxxreact/JSCExecutor.h b/ReactCommon/cxxreact/JSCExecutor.h index 66c101c5362d9f..258697af5c9587 100644 --- a/ReactCommon/cxxreact/JSCExecutor.h +++ b/ReactCommon/cxxreact/JSCExecutor.h @@ -27,14 +27,15 @@ class RAMBundleRegistry; class RN_EXPORT JSCExecutorFactory : public JSExecutorFactory { public: - JSCExecutorFactory(const folly::dynamic& jscConfig) : - m_jscConfig(jscConfig) {} + JSCExecutorFactory(const folly::dynamic& jscConfig, std::function provider) : + m_jscConfig(jscConfig), m_nativeExtensionsProvider(provider) {} std::unique_ptr createJSExecutor( std::shared_ptr delegate, std::shared_ptr jsQueue) override; private: std::string m_cacheDir; folly::dynamic m_jscConfig; + std::function m_nativeExtensionsProvider; }; template @@ -58,7 +59,8 @@ class RN_EXPORT JSCExecutor : public JSExecutor, public PrivateDataBase { */ explicit JSCExecutor(std::shared_ptr delegate, std::shared_ptr messageQueueThread, - const folly::dynamic& jscConfig) throw(JSException); + const folly::dynamic& jscConfig, + std::function nativeExtensionsProvider) throw(JSException); ~JSCExecutor() override; virtual void loadApplicationScript( @@ -112,6 +114,7 @@ class RN_EXPORT JSCExecutor : public JSExecutor, public PrivateDataBase { JSCNativeModules m_nativeModules; folly::dynamic m_jscConfig; std::once_flag m_bindFlag; + std::function m_nativeExtensionsProvider; folly::Optional m_invokeCallbackAndReturnFlushedQueueJS; folly::Optional m_callFunctionReturnFlushedQueueJS; @@ -134,7 +137,9 @@ class RN_EXPORT JSCExecutor : public JSExecutor, public PrivateDataBase { template void installNativeHook(const char* name); + JSValueRef getNativeModule(JSObjectRef object, JSStringRef propertyName); + JSValueRef getNativeExtension(JSObjectRef object, JSStringRef propertyName); JSValueRef nativeRequire( size_t argumentCount,