diff --git a/packages/react-native/ReactCommon/jsi/jsi/decorator.h b/packages/react-native/ReactCommon/jsi/jsi/decorator.h index d952ccbee0f6cc..c0ccc683311a8a 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/decorator.h +++ b/packages/react-native/ReactCommon/jsi/jsi/decorator.h @@ -182,6 +182,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { PropNameID createPropNameIDFromString(const String& str) override { return plain_.createPropNameIDFromString(str); }; + PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) + override { + return plain_.createPropNameIDFromUtf16(utf16, length); + } PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { return plain_.createPropNameIDFromSymbol(sym); }; @@ -221,6 +225,9 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { String createStringFromUtf8(const uint8_t* utf8, size_t length) override { return plain_.createStringFromUtf8(utf8, length); }; + String createStringFromUtf16(const char16_t* utf16, size_t length) override { + return plain_.createStringFromUtf16(utf16, length); + } std::string utf8(const String& s) override { return plain_.utf8(s); } @@ -637,6 +644,11 @@ class WithRuntimeDecorator : public RuntimeDecorator { Around around{with_}; return RD::createPropNameIDFromUtf8(utf8, length); }; + PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) + override { + Around around{with_}; + return RD::createPropNameIDFromUtf16(utf16, length); + } PropNameID createPropNameIDFromString(const String& str) override { Around around{with_}; return RD::createPropNameIDFromString(str); @@ -692,6 +704,10 @@ class WithRuntimeDecorator : public RuntimeDecorator { Around around{with_}; return RD::createStringFromUtf8(utf8, length); }; + String createStringFromUtf16(const char16_t* utf16, size_t length) override { + Around around{with_}; + return RD::createStringFromUtf16(utf16, length); + } std::string utf8(const String& s) override { Around around{with_}; return RD::utf8(s); diff --git a/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp b/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp index bce1e27f0ec55e..01db8dce246467 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp @@ -163,6 +163,37 @@ std::u16string convertUTF8ToUTF16(const std::string& utf8) { return ret; } +// Given a unsigned number, which is less than 16, return the hex character. +char hexDigit(unsigned int x) { + return x < 10 ? '0' + x : 'A' + (x - 10); +} + +// Given a sequences of UTF 16 code units, return a string that explicitly +// expresses the code units +std::string getUtf16CodeUnitString(const char16_t* utf16, size_t length) { + std::string s = "'"; + auto* curr = utf16; + auto* end = curr + length; + while (curr < end) { + s.append("\\u"); + + char buffer[4]; + memset(buffer, '0', 4); + + char* bufferEnd = buffer + 4; + char16_t unit = *curr; + while (unit) { + unsigned char x = static_cast(unit) % 16; + *--bufferEnd = hexDigit(x); + unit /= 16; + } + s.append(buffer, 4); + curr++; + } + s.append("'"); + return s; +} + } // namespace Buffer::~Buffer() = default; @@ -248,6 +279,25 @@ Value Runtime::createValueFromJsonUtf8(const uint8_t* json, size_t length) { return parseJson.call(*this, String::createFromUtf8(*this, json, length)); } +String Runtime::createStringFromUtf16(const char16_t* utf16, size_t length) { + auto s = getUtf16CodeUnitString(utf16, length); + return global() + .getPropertyAsFunction(*this, "eval") + .call(*this, s) + .getString(*this); +} + +PropNameID Runtime::createPropNameIDFromUtf16( + const char16_t* utf16, + size_t length) { + auto s = getUtf16CodeUnitString(utf16, length); + auto jsString = global() + .getPropertyAsFunction(*this, "eval") + .call(*this, s) + .getString(*this); + return createPropNameIDFromString(jsString); +} + std::u16string Runtime::utf16(const PropNameID& sym) { auto utf8Str = utf8(sym); return convertUTF8ToUTF16(utf8Str); diff --git a/packages/react-native/ReactCommon/jsi/jsi/jsi.h b/packages/react-native/ReactCommon/jsi/jsi/jsi.h index 4e997aa7fd6c4d..c64bac65c27b8e 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi.h +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi.h @@ -306,6 +306,9 @@ class JSI_EXPORT Runtime { virtual PropNameID createPropNameIDFromUtf8( const uint8_t* utf8, size_t length) = 0; + virtual PropNameID createPropNameIDFromUtf16( + const char16_t* utf16, + size_t length); virtual PropNameID createPropNameIDFromString(const String& str) = 0; virtual PropNameID createPropNameIDFromSymbol(const Symbol& sym) = 0; virtual std::string utf8(const PropNameID&) = 0; @@ -322,6 +325,7 @@ class JSI_EXPORT Runtime { virtual String createStringFromAscii(const char* str, size_t length) = 0; virtual String createStringFromUtf8(const uint8_t* utf8, size_t length) = 0; + virtual String createStringFromUtf16(const char16_t* utf16, size_t length); virtual std::string utf8(const String&) = 0; // \return a \c Value created from a utf8-encoded JSON string. The default @@ -509,6 +513,21 @@ class JSI_EXPORT PropNameID : public Pointer { reinterpret_cast(utf8.data()), utf8.size()); } + /// Given a series of UTF-16 encoded code units, create a PropNameId. The + /// input may contain unpaired surrogates, which will be interpreted as a code + /// point of the same value. + static PropNameID + forUtf16(Runtime& runtime, const char16_t* utf16, size_t length) { + return runtime.createPropNameIDFromUtf16(utf16, length); + } + + /// Given a series of UTF-16 encoded code units stored inside std::u16string, + /// create a PropNameId. The input may contain unpaired surrogates, which + /// will be interpreted as a code point of the same value. + static PropNameID forUtf16(Runtime& runtime, const std::u16string& str) { + return runtime.createPropNameIDFromUtf16(str.data(), str.size()); + } + /// Create a PropNameID from a JS string. static PropNameID forString(Runtime& runtime, const jsi::String& str) { return runtime.createPropNameIDFromString(str); @@ -693,6 +712,21 @@ class JSI_EXPORT String : public Pointer { reinterpret_cast(utf8.data()), utf8.length()); } + /// Given a series of UTF-16 encoded code units, create a JS String. The input + /// may contain unpaired surrogates, which will be interpreted as a code point + /// of the same value. + static String + createFromUtf16(Runtime& runtime, const char16_t* utf16, size_t length) { + return runtime.createStringFromUtf16(utf16, length); + } + + /// Given a series of UTF-16 encoded code units stored inside std::u16string, + /// create a JS String. The input may contain unpaired surrogates, which will + /// be interpreted as a code point of the same value. + static String createFromUtf16(Runtime& runtime, const std::u16string& utf16) { + return runtime.createStringFromUtf16(utf16.data(), utf16.length()); + } + /// \return whether a and b contain the same characters. static bool strictEquals(Runtime& runtime, const String& a, const String& b) { return runtime.strictEquals(a, b); diff --git a/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp b/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp index 5f779427d0f0a3..6da174c2f90792 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp +++ b/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp @@ -1664,6 +1664,46 @@ TEST_P(JSITest, GetStringDataTest) { EXPECT_EQ(buf, str.utf16(rd)); } +TEST_P(JSITest, CreateFromUtf16Test) { + // This Runtime Decorator is used to test the default createStringFromUtf16 + // and createPropNameIDFromUtf16 implementation for VMs that do not provide + // their own implementation + class RD : public RuntimeDecorator { + public: + RD(Runtime& rt) : RuntimeDecorator(rt) {} + + String createStringFromUtf16(const char16_t* utf16, size_t length) + override { + return Runtime::createStringFromUtf16(utf16, length); + } + + PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) + override { + return Runtime::createPropNameIDFromUtf16(utf16, length); + } + }; + + RD rd = RD(rt); + std::u16string utf16 = u"foobar"; + + auto jsString = String::createFromUtf16(rd, utf16); + EXPECT_EQ(jsString.utf16(rd), utf16); + auto prop = PropNameID::forUtf16(rd, utf16); + EXPECT_EQ(prop.utf16(rd), utf16); + + utf16 = u"hello!👋"; + jsString = String::createFromUtf16(rd, utf16.data(), utf16.length()); + EXPECT_EQ(jsString.utf16(rd), utf16); + prop = PropNameID::forUtf16(rd, utf16); + EXPECT_EQ(prop.utf16(rd), utf16); + + utf16 = u"\xd83d"; + jsString = String::createFromUtf16(rd, utf16.data(), utf16.length()); + EXPECT_EQ(jsString.utf16(rd), utf16); + prop = PropNameID::forUtf16(rd, utf16); + EXPECT_EQ(prop.utf16(rd), utf16); +} + INSTANTIATE_TEST_CASE_P( Runtimes, JSITest,