Native Modules -> Native Extensions

Reviewed By: danzimm

Differential Revision: D6887988

fbshipit-source-id: 475c05f60a2e1ddcfaa9263ab363bff8a528236a
This commit is contained in:
Dmitry Zakharov 2018-02-07 07:29:49 -08:00 committed by Facebook Github Bot
parent 06d8f96a64
commit 0c49c1f332
10 changed files with 123 additions and 92 deletions

View File

@ -20,54 +20,17 @@ var invariant = require('fbjs/lib/invariant');
var eventEmitter = new EventEmitter(); var eventEmitter = new EventEmitter();
var dimensionsInitialized = false; var dimensionsInitialized = false;
var dimensions = {}; var dimensions = {};
var dimensionsProvider: ?(() => {[key:string]: Object});
class Dimensions { class Dimensions {
static setProvider(value: () => {[key:string]: Object}): void { /**
dimensionsProvider = value; * This should only be called from native code by sending the
dimensionsInitialized = false; * didUpdateDimensions event.
dimensions = {}; *
} * @param {object} dims Simple string-keyed object of dimensions to set
*/
static getDimensions(): {[key:string]: Object} { static set(dims: {[key:string]: any}): void {
if (dimensionsInitialized) {
return dimensions;
}
// We calculate the window dimensions in JS so that we don't encounter loss of // 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 // precision in transferring the dimensions (which could be non-integers) over
// the bridge. // 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');`
*
* @param {string} dim Name of dimension as defined when calling `set`.
* @returns {Object?} Value for the dimension.
*/
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} {
if (dims && dims.windowPhysicalPixels) { if (dims && dims.windowPhysicalPixels) {
// parse/stringify => Clone hack // parse/stringify => Clone hack
dims = JSON.parse(JSON.stringify(dims)); dims = JSON.parse(JSON.stringify(dims));
@ -108,7 +71,26 @@ class Dimensions {
} else { } else {
dimensionsInitialized = true; 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} { let dims: ?{[key: string]: any} = global.nativeExtensions && global.nativeExtensions.DeviceInfo && global.nativeExtensions.DeviceInfo.Dimensions;
return require('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; module.exports = Dimensions;

View File

@ -339,6 +339,7 @@ struct RCTInstanceCallback : public InstanceCallback {
#if RCT_PROFILE #if RCT_PROFILE
("StartSamplingProfilerOnInit", (bool)self.devSettings.startSamplingProfilerOnLaunch) ("StartSamplingProfilerOnInit", (bool)self.devSettings.startSamplingProfilerOnLaunch)
#endif #endif
, nullptr
)); ));
} }
} else { } else {

View File

@ -58,7 +58,7 @@ public class DeviceInfoModule extends BaseJavaModule implements
HashMap<String, Object> constants = new HashMap<>(); HashMap<String, Object> constants = new HashMap<>();
constants.put( constants.put(
"Dimensions", "Dimensions",
getDimensionsConstants()); DisplayMetricsHolder.getDisplayMetricsMap(mFontScale));
return constants; return constants;
} }
@ -90,31 +90,6 @@ public class DeviceInfoModule extends BaseJavaModule implements
mReactApplicationContext mReactApplicationContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("didUpdateDimensions", getDimensionsConstants()); .emit("didUpdateDimensions", DisplayMetricsHolder.getDisplayMetricsMap(mFontScale));
}
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;
} }
} }

View File

@ -2,13 +2,17 @@ load("//ReactNative:DEFS", "rn_android_library", "YOGA_TARGET", "react_native_de
rn_android_library( rn_android_library(
name = "uimanager", name = "uimanager",
srcs = glob([ srcs = glob(
"*.java", [
"debug/*.java", "*.java",
"events/*.java", "debug/*.java",
"layoutanimation/*.java", "events/*.java",
]), "layoutanimation/*.java",
],
excludes = ["DisplayMetricsHolder.java"],
),
exported_deps = [ exported_deps = [
":DisplayMetrics",
react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/jsr-305:jsr-305"),
], ],
provided_deps = [ provided_deps = [
@ -21,6 +25,7 @@ rn_android_library(
], ],
deps = [ deps = [
YOGA_TARGET, YOGA_TARGET,
":DisplayMetrics",
react_native_dep("java/com/facebook/systrace:systrace"), react_native_dep("java/com/facebook/systrace:systrace"),
react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"), react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"),
react_native_dep("third-party/java/infer-annotations:infer-annotations"), react_native_dep("third-party/java/infer-annotations:infer-annotations"),
@ -38,3 +43,19 @@ rn_android_library(
react_native_target("res:uimanager"), 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"),
],
)

View File

@ -21,6 +21,7 @@ import android.view.Display;
import android.view.WindowManager; import android.view.WindowManager;
import com.facebook.infer.annotation.Assertions; 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 * Holds an instance of the current DisplayMetrics so we don't have to thread it through all the
@ -104,4 +105,26 @@ public class DisplayMetricsHolder {
public static DisplayMetrics getScreenDisplayMetrics() { public static DisplayMetrics getScreenDisplayMetrics() {
return sScreenDisplayMetrics; 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;
}
} }

View File

@ -90,9 +90,9 @@ void injectJSCExecutorAndroidPlatform() {
} }
std::unique_ptr<JSExecutorFactory> makeAndroidJSCExecutorFactory( std::unique_ptr<JSExecutorFactory> makeAndroidJSCExecutorFactory(
const folly::dynamic& jscConfig) { const folly::dynamic& jscConfig, std::function<folly::dynamic(const std::string&)> nativeExtensionsProvider) {
detail::injectJSCExecutorAndroidPlatform(); detail::injectJSCExecutorAndroidPlatform();
return folly::make_unique<JSCExecutorFactory>(std::move(jscConfig)); return folly::make_unique<JSCExecutorFactory>(std::move(jscConfig), std::move(nativeExtensionsProvider));
} }
} }

View File

@ -23,7 +23,7 @@ void injectJSCExecutorAndroidPlatform();
} }
std::unique_ptr<JSExecutorFactory> makeAndroidJSCExecutorFactory( std::unique_ptr<JSExecutorFactory> makeAndroidJSCExecutorFactory(
const folly::dynamic& jscConfig); const folly::dynamic& jscConfig, std::function<folly::dynamic(const std::string&)> nativeExtensionsProvider);
} }
} }

View File

@ -38,7 +38,7 @@ class JSCJavaScriptExecutorHolder : public HybridClass<JSCJavaScriptExecutorHold
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/JSCJavaScriptExecutor;"; static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/JSCJavaScriptExecutor;";
static local_ref<jhybriddata> initHybrid(alias_ref<jclass>, ReadableNativeMap* jscConfig) { static local_ref<jhybriddata> initHybrid(alias_ref<jclass>, ReadableNativeMap* jscConfig) {
return makeCxxInstance(makeAndroidJSCExecutorFactory(jscConfig->consume())); return makeCxxInstance(makeAndroidJSCExecutorFactory(jscConfig->consume(), nullptr));
} }
static void registerNatives() { static void registerNatives() {

View File

@ -115,16 +115,18 @@ namespace facebook {
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor( std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate, std::shared_ptr<MessageQueueThread> jsQueue) { std::shared_ptr<ExecutorDelegate> delegate, std::shared_ptr<MessageQueueThread> jsQueue) {
return folly::make_unique<JSCExecutor>(delegate, jsQueue, m_jscConfig); return folly::make_unique<JSCExecutor>(delegate, jsQueue, m_jscConfig, m_nativeExtensionsProvider);
} }
JSCExecutor::JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate, JSCExecutor::JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> messageQueueThread, std::shared_ptr<MessageQueueThread> messageQueueThread,
const folly::dynamic& jscConfig) throw(JSException) : const folly::dynamic& jscConfig,
std::function<folly::dynamic(const std::string &)> nativeExtensionsProvider) throw(JSException) :
m_delegate(delegate), m_delegate(delegate),
m_messageQueueThread(messageQueueThread), m_messageQueueThread(messageQueueThread),
m_nativeModules(delegate ? delegate->getModuleRegistry() : nullptr), m_nativeModules(delegate ? delegate->getModuleRegistry() : nullptr),
m_jscConfig(jscConfig) { m_jscConfig(jscConfig),
m_nativeExtensionsProvider(nativeExtensionsProvider) {
initOnJSVMThread(); initOnJSVMThread();
{ {
@ -132,6 +134,8 @@ namespace facebook {
installGlobalProxy(m_context, "nativeModuleProxy", installGlobalProxy(m_context, "nativeModuleProxy",
exceptionWrapMethod<&JSCExecutor::getNativeModule>()); exceptionWrapMethod<&JSCExecutor::getNativeModule>());
} }
installGlobalProxy(m_context, "nativeExtensions",
exceptionWrapMethod<&JSCExecutor::getNativeExtension>());
} }
JSCExecutor::~JSCExecutor() { JSCExecutor::~JSCExecutor() {
@ -632,8 +636,8 @@ namespace facebook {
return String::adopt(m_context, jsString); return String::adopt(m_context, jsString);
#else #else
return script->isAscii() return script->isAscii()
? String::createExpectingAscii(m_context, script->c_str(), script->size()) ? String::createExpectingAscii(m_context, script->c_str(), script->size())
: String(m_context, script->c_str()); : String(m_context, script->c_str());
#endif #endif
} }
@ -677,6 +681,14 @@ namespace facebook {
return m_nativeModules.getModule(m_context, propertyName); 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( JSValueRef JSCExecutor::nativeRequire(
size_t argumentCount, size_t argumentCount,
const JSValueRef arguments[]) { const JSValueRef arguments[]) {

View File

@ -27,14 +27,15 @@ class RAMBundleRegistry;
class RN_EXPORT JSCExecutorFactory : public JSExecutorFactory { class RN_EXPORT JSCExecutorFactory : public JSExecutorFactory {
public: public:
JSCExecutorFactory(const folly::dynamic& jscConfig) : JSCExecutorFactory(const folly::dynamic& jscConfig, std::function<folly::dynamic(const std::string &)> provider) :
m_jscConfig(jscConfig) {} m_jscConfig(jscConfig), m_nativeExtensionsProvider(provider) {}
std::unique_ptr<JSExecutor> createJSExecutor( std::unique_ptr<JSExecutor> createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate, std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) override; std::shared_ptr<MessageQueueThread> jsQueue) override;
private: private:
std::string m_cacheDir; std::string m_cacheDir;
folly::dynamic m_jscConfig; folly::dynamic m_jscConfig;
std::function<folly::dynamic(const std::string &)> m_nativeExtensionsProvider;
}; };
template<typename T> template<typename T>
@ -58,7 +59,8 @@ public:
*/ */
explicit JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate, explicit JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> messageQueueThread, std::shared_ptr<MessageQueueThread> messageQueueThread,
const folly::dynamic& jscConfig) throw(JSException); const folly::dynamic& jscConfig,
std::function<folly::dynamic(const std::string &)> nativeExtensionsProvider) throw(JSException);
~JSCExecutor() override; ~JSCExecutor() override;
virtual void loadApplicationScript( virtual void loadApplicationScript(
@ -112,6 +114,7 @@ private:
JSCNativeModules m_nativeModules; JSCNativeModules m_nativeModules;
folly::dynamic m_jscConfig; folly::dynamic m_jscConfig;
std::once_flag m_bindFlag; std::once_flag m_bindFlag;
std::function<folly::dynamic(const std::string &)> m_nativeExtensionsProvider;
folly::Optional<Object> m_invokeCallbackAndReturnFlushedQueueJS; folly::Optional<Object> m_invokeCallbackAndReturnFlushedQueueJS;
folly::Optional<Object> m_callFunctionReturnFlushedQueueJS; folly::Optional<Object> m_callFunctionReturnFlushedQueueJS;
@ -134,7 +137,9 @@ private:
template<JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])> template<JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])>
void installNativeHook(const char* name); void installNativeHook(const char* name);
JSValueRef getNativeModule(JSObjectRef object, JSStringRef propertyName); JSValueRef getNativeModule(JSObjectRef object, JSStringRef propertyName);
JSValueRef getNativeExtension(JSObjectRef object, JSStringRef propertyName);
JSValueRef nativeRequire( JSValueRef nativeRequire(
size_t argumentCount, size_t argumentCount,