Add Hermes support to React Native on Android (#25613)

Summary:
Yesterday we shipped hermesengine.dev as part of the current 0.60 release. This PR brings those changes to master.

## Changelog

[General] [Added] - Added support for Hermes
Pull Request resolved: https://github.com/facebook/react-native/pull/25613

Test Plan:
* CI is green both on GitHub and at FB
* Creating a new app from source can use Hermes on Android

Reviewed By: cpojer

Differential Revision: D16221777

Pulled By: willholen

fbshipit-source-id: aa6be10537863039cb666292465ba2e1d44b64ef
This commit is contained in:
cpojer
2019-07-25 22:59:00 -07:00
committed by Facebook Github Bot
parent fee7f0617e
commit d7f5153cd8
106 changed files with 17050 additions and 56 deletions
@@ -0,0 +1,9 @@
load("//tools/build_defs/oss:rn_defs.bzl", "rn_android_library")
rn_android_library(
name = "instrumentation",
srcs = glob(["**/*.java"]),
visibility = [
"PUBLIC",
],
)
@@ -0,0 +1,48 @@
/**
* 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 <fb/fbjni.h>
#include <string>
namespace facebook {
namespace jsi {
namespace jni {
namespace jni = ::facebook::jni;
class HermesMemoryDumper : public jni::JavaClass<HermesMemoryDumper> {
public:
constexpr static auto kJavaDescriptor =
"Lcom/facebook/hermes/instrumentation/HermesMemoryDumper;";
bool shouldSaveSnapshot() {
static auto shouldSaveSnapshotMethod =
javaClassStatic()->getMethod<jboolean()>("shouldSaveSnapshot");
return shouldSaveSnapshotMethod(self());
}
std::string getInternalStorage() {
static auto getInternalStorageMethod =
javaClassStatic()->getMethod<jstring()>("getInternalStorage");
return getInternalStorageMethod(self())->toStdString();
}
std::string getId() {
static auto getInternalStorageMethod =
javaClassStatic()->getMethod<jstring()>("getId");
return getInternalStorageMethod(self())->toStdString();
}
void setMetaData(std::string crashId) {
static auto getIdMethod =
javaClassStatic()->getMethod<void(std::string)>("setMetaData");
getIdMethod(self(), crashId);
}
};
} // namespace jni
} // namespace jsi
} // namespace facebook
@@ -0,0 +1,17 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>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.hermes.instrumentation;
public interface HermesMemoryDumper {
boolean shouldSaveSnapshot();
String getInternalStorage();
String getId();
void setMetaData(String crashId);
}
@@ -0,0 +1,40 @@
# 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)
REACT_NATIVE := $(LOCAL_PATH)/../../../../../../../..
LOCAL_MODULE := hermes-executor-release
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(REACT_NATIVE)/ReactCommon/jsi $(REACT_NATIVE)/node_modules/hermesvm/android/include $(REACT_NATIVE)/../hermesvm/android/include $(REACT_NATIVE)/../node_modules/hermesvm/include
LOCAL_CPP_FEATURES := exceptions
LOCAL_STATIC_LIBRARIES := libjsireact libjsi
LOCAL_SHARED_LIBRARIES := libfolly_json libfb libreactnativejni libhermes
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
REACT_NATIVE := $(LOCAL_PATH)/../../../../../../../..
LOCAL_MODULE := hermes-executor-debug
LOCAL_CFLAGS := -DHERMES_ENABLE_DEBUGGER=1
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(REACT_NATIVE)/ReactCommon/jsi $(REACT_NATIVE)/node_modules/hermesvm/android/include $(REACT_NATIVE)/../hermesvm/android/include $(REACT_NATIVE)/../node_modules/hermesvm/include
LOCAL_CPP_FEATURES := exceptions
LOCAL_STATIC_LIBRARIES := libjsireact libjsi libhermes-inspector
LOCAL_SHARED_LIBRARIES := libfolly_json libfb libreactnativejni libhermes
include $(BUILD_SHARED_LIBRARY)
@@ -0,0 +1,68 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>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.hermes.reactexecutor;
import com.facebook.hermes.instrumentation.HermesMemoryDumper;
import com.facebook.jni.HybridData;
import com.facebook.react.bridge.JavaScriptExecutor;
import com.facebook.soloader.SoLoader;
import javax.annotation.Nullable;
public class HermesExecutor extends JavaScriptExecutor {
private static String mode_;
static {
// libhermes must be loaded explicitly to invoke its JNI_OnLoad.
SoLoader.loadLibrary("hermes");
try {
SoLoader.loadLibrary("hermes-executor-release");
mode_ = "Release";
} catch (UnsatisfiedLinkError e) {
SoLoader.loadLibrary("hermes-executor-debug");
mode_ = "Debug";
}
}
HermesExecutor(@Nullable RuntimeConfig config) {
super(
config == null
? initHybridDefaultConfig()
: initHybrid(
config.heapSizeMB,
config.es6Symbol,
config.bytecodeWarmupPercent,
config.tripWireEnabled,
config.heapDumper,
config.tripWireCooldownMS,
config.tripWireLimitBytes));
}
@Override
public String getName() {
return "HermesExecutor" + mode_;
}
/**
* Return whether this class can load a file at the given path, based on a binary compatibility
* check between the contents of the file and the Hermes VM.
*
* @param path the path containing the file to inspect.
* @return whether the given file is compatible with the Hermes VM.
*/
public static native boolean canLoadFile(String path);
private static native HybridData initHybridDefaultConfig();
private static native HybridData initHybrid(
long heapSizeMB,
boolean es6Symbol,
int bytecodeWarmupPercent,
boolean tripWireEnabled,
@Nullable HermesMemoryDumper heapDumper,
long tripWireCooldownMS,
long tripWireLimitBytes);
}
@@ -0,0 +1,230 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "HermesExecutorFactory.h"
#include <thread>
#include <cxxreact/MessageQueueThread.h>
#include <cxxreact/SystraceSection.h>
#include <hermes/hermes_tracing.h>
#include <jsi/decorator.h>
#ifdef HERMES_ENABLE_DEBUGGER
#include <hermes/inspector/RuntimeAdapter.h>
#include <hermes/inspector/chrome/Registration.h>
#endif
#include "JSITracing.h"
using namespace facebook::hermes;
using namespace facebook::jsi;
namespace facebook {
namespace react {
namespace {
std::unique_ptr<HermesRuntime> makeHermesRuntimeSystraced(
const ::hermes::vm::RuntimeConfig &runtimeConfig) {
SystraceSection s("HermesExecutorFactory::makeHermesRuntimeSystraced");
return hermes::makeHermesRuntime(runtimeConfig);
}
#ifdef HERMES_ENABLE_DEBUGGER
class HermesExecutorRuntimeAdapter
: public facebook::hermes::inspector::RuntimeAdapter {
public:
HermesExecutorRuntimeAdapter(
std::shared_ptr<Runtime> runtime,
HermesRuntime &hermesRuntime,
std::shared_ptr<MessageQueueThread> thread)
: runtime_(runtime),
hermesRuntime_(hermesRuntime),
thread_(std::move(thread)) {}
virtual ~HermesExecutorRuntimeAdapter() = default;
HermesRuntime &getRuntime() override {
return hermesRuntime_;
}
void tickleJs() override {
// The queue will ensure that runtime_ is still valid when this
// gets invoked.
// clang-format off
thread_->runOnQueue([&runtime = hermesRuntime_]() {
// clang-format on
auto func = runtime.global().getPropertyAsFunction(runtime, "__tickleJs");
func.call(runtime);
});
}
private:
std::shared_ptr<Runtime> runtime_;
HermesRuntime &hermesRuntime_;
std::shared_ptr<MessageQueueThread> thread_;
};
#endif
struct ReentrancyCheck {
// This is effectively a very subtle and complex assert, so only
// include it in builds which would include asserts.
#ifndef NDEBUG
ReentrancyCheck() : tid(std::thread::id()), depth(0) {}
void before() {
std::thread::id this_id = std::this_thread::get_id();
std::thread::id expected = std::thread::id();
// A note on memory ordering: the main purpose of these checks is
// to observe a before/before race, without an intervening after.
// This will be detected by the compare_exchange_strong atomicity
// properties, regardless of memory order.
//
// For everything else, it is easiest to think of 'depth' as a
// proxy for any access made inside the VM. If access to depth
// are reordered incorrectly, the same could be true of any other
// operation made by the VM. In fact, using acquire/release
// memory ordering could create barriers which mask a programmer
// error. So, we use relaxed memory order, to avoid masking
// actual ordering errors. Although, in practice, ordering errors
// of this sort would be surprising, because the decorator would
// need to call after() without before().
if (tid.compare_exchange_strong(
expected, this_id, std::memory_order_relaxed)) {
// Returns true if tid and expected were the same. If they
// were, then the stored tid referred to no thread, and we
// atomically saved this thread's tid. Now increment depth.
assert(depth == 0 && "No thread id, but depth != 0");
++depth;
} else if (expected == this_id) {
// If the stored tid referred to a thread, expected was set to
// that value. If that value is this thread's tid, that's ok,
// just increment depth again.
assert(depth != 0 && "Thread id was set, but depth == 0");
++depth;
} else {
// The stored tid was some other thread. This indicates a bad
// programmer error, where VM methods were called on two
// different threads unsafely. Fail fast (and hard) so the
// crash can be analyzed.
__builtin_trap();
}
}
void after() {
assert(
tid.load(std::memory_order_relaxed) == std::this_thread::get_id() &&
"No thread id in after()");
if (--depth == 0) {
// If we decremented depth to zero, store no-thread into tid.
std::thread::id expected = std::this_thread::get_id();
bool didWrite = tid.compare_exchange_strong(
expected, std::thread::id(), std::memory_order_relaxed);
assert(didWrite && "Decremented to zero, but no tid write");
}
}
std::atomic<std::thread::id> tid;
// This is not atomic, as it is only written or read from the owning
// thread.
unsigned int depth;
#endif
};
// This adds ReentrancyCheck and debugger enable/teardown to the given
// Runtime.
class DecoratedRuntime : public jsi::WithRuntimeDecorator<ReentrancyCheck> {
public:
// The first argument may be a tracing runtime which itself
// decorates the real HermesRuntime, depending on the build config.
// The second argument is the the real HermesRuntime as well to
// manage the debugger registration.
DecoratedRuntime(
std::unique_ptr<Runtime> runtime,
HermesRuntime &hermesRuntime,
std::shared_ptr<MessageQueueThread> jsQueue)
: jsi::WithRuntimeDecorator<ReentrancyCheck>(*runtime, reentrancyCheck_),
runtime_(std::move(runtime)),
hermesRuntime_(hermesRuntime) {
#ifdef HERMES_ENABLE_DEBUGGER
auto adapter = std::make_unique<HermesExecutorRuntimeAdapter>(
runtime_, hermesRuntime_, jsQueue);
facebook::hermes::inspector::chrome::enableDebugging(
std::move(adapter), "Hermes React Native");
#else
(void)hermesRuntime_;
#endif
}
~DecoratedRuntime() {
#ifdef HERMES_ENABLE_DEBUGGER
facebook::hermes::inspector::chrome::disableDebugging(hermesRuntime_);
#endif
}
private:
// runtime_ is a TracingRuntime, but we don't need to worry about
// the details. hermesRuntime is a reference to the HermesRuntime
// managed by the TracingRuntime.
//
// HermesExecutorRuntimeAdapter requirements are kept, because the
// dtor will disable debugging on the HermesRuntime before the
// member managing it is destroyed.
std::shared_ptr<Runtime> runtime_;
ReentrancyCheck reentrancyCheck_;
HermesRuntime &hermesRuntime_;
};
} // namespace
std::unique_ptr<JSExecutor> HermesExecutorFactory::createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) {
std::unique_ptr<HermesRuntime> hermesRuntime =
makeHermesRuntimeSystraced(runtimeConfig_);
HermesRuntime& hermesRuntimeRef = *hermesRuntime;
auto decoratedRuntime = std::make_shared<DecoratedRuntime>(
makeTracingHermesRuntime(std::move(hermesRuntime), runtimeConfig_),
hermesRuntimeRef,
jsQueue);
// So what do we have now?
// DecoratedRuntime -> TracingRuntime -> HermesRuntime
//
// DecoratedRuntime is held by JSIExecutor. When it gets used, it
// will check that it's on the right thread, do any necessary trace
// logging, then call the real HermesRuntime. When it is destroyed,
// it will shut down the debugger before the HermesRuntime is. In
// the normal case where tracing and debugging are not compiled in,
// all that's left is the thread checking.
// Add js engine information to Error.prototype so in error reporting we
// can send this information.
auto errorPrototype =
decoratedRuntime->global()
.getPropertyAsObject(*decoratedRuntime, "Error")
.getPropertyAsObject(*decoratedRuntime, "prototype");
errorPrototype.setProperty(*decoratedRuntime, "jsEngine", "hermes");
return std::make_unique<HermesExecutor>(
decoratedRuntime, delegate, jsQueue, timeoutInvoker_, runtimeInstaller_);
}
HermesExecutor::HermesExecutor(
std::shared_ptr<jsi::Runtime> runtime,
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue,
const JSIScopedTimeoutInvoker &timeoutInvoker,
RuntimeInstaller runtimeInstaller)
: JSIExecutor(runtime, delegate, timeoutInvoker, runtimeInstaller) {
jsi::addNativeTracingHooks(*runtime);
}
} // namespace react
} // namespace facebook
@@ -0,0 +1,50 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <hermes/hermes.h>
#include <jsireact/JSIExecutor.h>
#include <functional>
#include <utility>
namespace facebook {
namespace react {
class HermesExecutorFactory : public JSExecutorFactory {
public:
explicit HermesExecutorFactory(
JSIExecutor::RuntimeInstaller runtimeInstaller,
const JSIScopedTimeoutInvoker& timeoutInvoker =
JSIExecutor::defaultTimeoutInvoker,
::hermes::vm::RuntimeConfig runtimeConfig = ::hermes::vm::RuntimeConfig())
: runtimeInstaller_(runtimeInstaller),
timeoutInvoker_(timeoutInvoker),
runtimeConfig_(std::move(runtimeConfig)) {
assert(timeoutInvoker_ && "Should not have empty timeoutInvoker");
}
std::unique_ptr<JSExecutor> createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) override;
private:
JSIExecutor::RuntimeInstaller runtimeInstaller_;
JSIScopedTimeoutInvoker timeoutInvoker_;
::hermes::vm::RuntimeConfig runtimeConfig_;
};
class HermesExecutor : public JSIExecutor {
public:
HermesExecutor(
std::shared_ptr<jsi::Runtime> runtime,
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue,
const JSIScopedTimeoutInvoker& timeoutInvoker,
RuntimeInstaller runtimeInstaller);
private:
JSIScopedTimeoutInvoker timeoutInvoker_;
};
} // namespace react
} // namespace facebook
@@ -0,0 +1,40 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>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.hermes.reactexecutor;
import com.facebook.react.bridge.JavaScriptExecutor;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
public class HermesExecutorFactory implements JavaScriptExecutorFactory {
private static final String TAG = "Hermes";
private final RuntimeConfig mConfig;
public HermesExecutorFactory() {
this(null);
}
public HermesExecutorFactory(RuntimeConfig config) {
mConfig = config;
}
@Override
public JavaScriptExecutor create() {
return new HermesExecutor(mConfig);
}
@Override
public void startSamplingProfiler() {}
@Override
public void stopSamplingProfiler(String filename) {}
@Override
public String toString() {
return "JSIExecutor+HermesRuntime";
}
}
@@ -0,0 +1,15 @@
/**
* 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 "JSITracing.h"
namespace facebook {
namespace jsi {
void addNativeTracingHooks(Runtime &rt) {
assert(false && "unimplemented");
}
} // namespace jsi
} // namespace facebook
@@ -0,0 +1,15 @@
/**
* 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 <jsi/jsi.h>
namespace facebook {
namespace jsi {
void addNativeTracingHooks(Runtime &rt);
} // namespace jsi
} // namespace facebook
@@ -0,0 +1,154 @@
/**
* 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 <../instrumentation/HermesMemoryDumper.h>
#include <HermesExecutorFactory.h>
#include <fb/fbjni.h>
#include <folly/Memory.h>
#include <hermes/Public/GCConfig.h>
#include <hermes/Public/RuntimeConfig.h>
#include <jni.h>
#include <react/jni/JReactMarker.h>
#include <react/jni/JSLogging.h>
#include <react/jni/JavaScriptExecutorHolder.h>
namespace facebook {
namespace react {
/// Converts a duration given as a long from Java, into a std::chrono duration.
static constexpr std::chrono::hours msToHours(jlong ms) {
using namespace std::chrono;
return duration_cast<hours>(milliseconds(ms));
}
static ::hermes::vm::RuntimeConfig makeRuntimeConfig(
jlong heapSizeMB,
bool es6Symbol,
jint bytecodeWarmupPercent,
bool tripWireEnabled,
jni::alias_ref<jsi::jni::HermesMemoryDumper> heapDumper,
jlong tripWireCooldownMS,
jlong tripWireLimitBytes) {
namespace vm = ::hermes::vm;
auto gcConfigBuilder =
vm::GCConfig::Builder()
.withMaxHeapSize(heapSizeMB << 20)
.withName("RN")
// For the next two arguments: avoid GC before TTI by initializing the
// runtime to allocate directly in the old generation, but revert to
// normal operation when we reach the (first) TTI point.
.withAllocInYoung(false)
.withRevertToYGAtTTI(true);
if (tripWireEnabled) {
assert(
heapDumper &&
"Must provide a heap dumper instance if tripwire is enabled");
gcConfigBuilder.withTripwireConfig(
vm::GCTripwireConfig::Builder()
.withLimit(tripWireLimitBytes)
.withCooldown(msToHours(tripWireCooldownMS))
.withCallback([globalHeapDumper = jni::make_global(heapDumper)](
vm::GCTripwireContext &ctx) mutable {
if (!globalHeapDumper->shouldSaveSnapshot()) {
return;
}
std::string crashId = globalHeapDumper->getId();
std::string path = globalHeapDumper->getInternalStorage();
path += "/dump_";
path += crashId;
path += ".hermes";
bool successful = ctx.createSnapshotToFile(path, true);
if (!successful) {
LOG(ERROR) << "Failed to write Hermes Memory Dump to " << path
<< "\n";
return;
}
LOG(INFO) << "Hermes Memory Dump saved on: " << path << "\n";
globalHeapDumper->setMetaData(crashId);
})
.build());
}
return vm::RuntimeConfig::Builder()
.withGCConfig(gcConfigBuilder.build())
.withES6Symbol(es6Symbol)
.withBytecodeWarmupPercent(bytecodeWarmupPercent)
.build();
}
static void installBindings(jsi::Runtime &runtime) {
react::Logger androidLogger =
static_cast<void (*)(const std::string &, unsigned int)>(
&reactAndroidLoggingHook);
react::bindNativeLogger(runtime, androidLogger);
}
class HermesExecutorHolder
: public jni::HybridClass<HermesExecutorHolder, JavaScriptExecutorHolder> {
public:
static constexpr auto kJavaDescriptor =
"Lcom/facebook/hermes/reactexecutor/HermesExecutor;";
static jni::local_ref<jhybriddata> initHybridDefaultConfig(
jni::alias_ref<jclass>) {
JReactMarker::setLogPerfMarkerIfNeeded();
return makeCxxInstance(
folly::make_unique<HermesExecutorFactory>(installBindings));
}
static jni::local_ref<jhybriddata> initHybrid(
jni::alias_ref<jclass>,
jlong heapSizeMB,
bool es6Symbol,
jint bytecodeWarmupPercent,
bool tripWireEnabled,
jni::alias_ref<jsi::jni::HermesMemoryDumper> heapDumper,
jlong tripWireCooldownMS,
jlong tripWireLimitBytes) {
JReactMarker::setLogPerfMarkerIfNeeded();
auto runtimeConfig = makeRuntimeConfig(
heapSizeMB,
es6Symbol,
bytecodeWarmupPercent,
tripWireEnabled,
heapDumper,
tripWireCooldownMS,
tripWireLimitBytes);
return makeCxxInstance(folly::make_unique<HermesExecutorFactory>(
installBindings, JSIExecutor::defaultTimeoutInvoker, runtimeConfig));
}
static bool canLoadFile(jni::alias_ref<jclass>, const std::string &path) {
return true;
}
static void registerNatives() {
registerHybrid(
{makeNativeMethod("initHybrid", HermesExecutorHolder::initHybrid),
makeNativeMethod(
"initHybridDefaultConfig",
HermesExecutorHolder::initHybridDefaultConfig),
makeNativeMethod("canLoadFile", HermesExecutorHolder::canLoadFile)});
}
private:
friend HybridBase;
using HybridBase::HybridBase;
};
} // namespace react
} // namespace facebook
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return facebook::jni::initialize(
vm, [] { facebook::react::HermesExecutorHolder::registerNatives(); });
}
@@ -0,0 +1,22 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>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.hermes.reactexecutor;
import com.facebook.hermes.instrumentation.HermesMemoryDumper;
import javax.annotation.Nullable;
/** Holds runtime configuration for a Hermes VM instance (master or snapshot). */
public final class RuntimeConfig {
public long heapSizeMB;
public boolean enableSampledStats;
public boolean es6Symbol;
public int bytecodeWarmupPercent;
public boolean tripWireEnabled;
@Nullable public HermesMemoryDumper heapDumper;
public long tripWireCooldownMS;
public long tripWireLimitBytes;
}
@@ -0,0 +1,16 @@
# 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.
# For common use cases for the hybrid pattern, keep symbols which may
# be referenced only from C++.
-keepclassmembers class * {
com.facebook.jni.HybridData *;
<init>(com.facebook.jni.HybridData);
}
-keepclasseswithmembers class * {
com.facebook.jni.HybridData *;
}
@@ -0,0 +1,81 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>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.hermes.unicode;
import com.facebook.proguard.annotations.DoNotStrip;
import java.text.Collator;
import java.text.DateFormat;
import java.text.Normalizer;
import java.util.Locale;
// TODO: use com.facebook.common.locale.Locales.getApplicationLocale() as the current locale,
// rather than the device locale. This is challenging because getApplicationLocale() is only
// available via DI.
@DoNotStrip
public class AndroidUnicodeUtils {
@DoNotStrip
public static int localeCompare(String left, String right) {
Collator collator = Collator.getInstance();
return collator.compare(left, right);
}
@DoNotStrip
public static String dateFormat(double unixtimeMs, boolean formatDate, boolean formatTime) {
DateFormat format;
if (formatDate && formatTime) {
format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
} else if (formatDate) {
format = DateFormat.getDateInstance(DateFormat.MEDIUM);
} else if (formatTime) {
format = DateFormat.getTimeInstance(DateFormat.MEDIUM);
} else {
throw new RuntimeException("Bad dateFormat configuration");
}
return format.format((long) unixtimeMs).toString();
}
@DoNotStrip
public static String convertToCase(String input, int targetCase, boolean useCurrentLocale) {
// These values must match CaseConversion in PlatformUnicode.h
final int targetUppercase = 0;
final int targetLowercase = 1;
// Note Java's case conversions use the user's locale. For example "I".toLowerCase()
// will produce a dotless i. From Java's docs: "To obtain correct results for locale
// insensitive strings, use toLowerCase(Locale.ENGLISH)."
Locale locale = useCurrentLocale ? Locale.getDefault() : Locale.ENGLISH;
switch (targetCase) {
case targetLowercase:
return input.toLowerCase(locale);
case targetUppercase:
return input.toUpperCase(locale);
default:
throw new RuntimeException("Invalid target case");
}
}
@DoNotStrip
public static String normalize(String input, int form) {
// Values must match NormalizationForm in PlatformUnicode.h.
final int formC = 0;
final int formD = 1;
final int formKC = 2;
final int formKD = 3;
switch (form) {
case formC:
return Normalizer.normalize(input, Normalizer.Form.NFC);
case formD:
return Normalizer.normalize(input, Normalizer.Form.NFD);
case formKC:
return Normalizer.normalize(input, Normalizer.Form.NFKC);
case formKD:
return Normalizer.normalize(input, Normalizer.Form.NFKD);
default:
throw new RuntimeException("Invalid form");
}
}
}
@@ -0,0 +1,12 @@
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_library")
rn_android_library(
name = "unicode",
srcs = glob(["**/*.java"]),
visibility = [
"PUBLIC",
],
deps = [
react_native_dep("java/com/facebook/proguard/annotations:annotations"),
],
)
@@ -12,8 +12,9 @@ import com.facebook.proguard.annotations.DoNotStrip;
@DoNotStrip
public class NativeRunnable implements Runnable {
private final HybridData mHybridData;
@DoNotStrip private final HybridData mHybridData;
@DoNotStrip
private NativeRunnable(HybridData hybridData) {
mHybridData = hybridData;
}
@@ -21,6 +21,7 @@ rn_android_library(
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_dep("java/com/facebook/hermes/reactexecutor:reactexecutor"),
react_native_target("java/com/facebook/debug/holder:holder"),
react_native_target("java/com/facebook/debug/tags:tags"),
react_native_target("java/com/facebook/react/bridge:bridge"),
@@ -10,6 +10,7 @@ import static com.facebook.react.modules.systeminfo.AndroidInfoHelpers.getFriend
import android.app.Activity;
import android.app.Application;
import androidx.annotation.Nullable;
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JSIModulePackage;
@@ -24,6 +25,7 @@ import com.facebook.react.jscexecutor.JSCExecutorFactory;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.packagerconnection.RequestHandler;
import com.facebook.react.uimanager.UIImplementationProvider;
import com.facebook.soloader.SoLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -260,7 +262,7 @@ public class ReactInstanceManagerBuilder {
mCurrentActivity,
mDefaultHardwareBackBtnHandler,
mJavaScriptExecutorFactory == null
? new JSCExecutorFactory(appName, deviceName)
? getDefaultJSExecutorFactory(appName, deviceName)
: mJavaScriptExecutorFactory,
(mJSBundleLoader == null && mJSBundleAssetUrl != null)
? JSBundleLoader.createAssetLoader(
@@ -281,4 +283,15 @@ public class ReactInstanceManagerBuilder {
mJSIModulesPackage,
mCustomPackagerCommandHandlers);
}
private JavaScriptExecutorFactory getDefaultJSExecutorFactory(String appName, String deviceName) {
try {
// If JSC is included, use it as normal
SoLoader.loadLibrary("jscexecutor");
return new JSCExecutorFactory(appName, deviceName);
} catch (UnsatisfiedLinkError jscE) {
// Otherwise use Hermes
return new HermesExecutorFactory();
}
}
}