diff --git a/src/libraries/Common/src/Interop/Browser/Interop.CompareInfo.cs b/src/libraries/Common/src/Interop/Browser/Interop.CompareInfo.cs
index a536d9dfbe65e3..cb7edf5b380a26 100644
--- a/src/libraries/Common/src/Interop/Browser/Interop.CompareInfo.cs
+++ b/src/libraries/Common/src/Interop/Browser/Interop.CompareInfo.cs
@@ -8,15 +8,15 @@ internal static partial class Interop
internal static unsafe partial class JsGlobalization
{
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern unsafe int CompareString(out string exceptionMessage, in string culture, char* str1, int str1Len, char* str2, int str2Len, global::System.Globalization.CompareOptions options);
+ internal static extern unsafe int CompareString(in string culture, char* str1, int str1Len, char* str2, int str2Len, global::System.Globalization.CompareOptions options, out int exceptionalResult, out object result);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern unsafe bool StartsWith(out string exceptionMessage, in string culture, char* str1, int str1Len, char* str2, int str2Len, global::System.Globalization.CompareOptions options);
+ internal static extern unsafe bool StartsWith(in string culture, char* str1, int str1Len, char* str2, int str2Len, global::System.Globalization.CompareOptions options, out int exceptionalResult, out object result);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern unsafe bool EndsWith(out string exceptionMessage, in string culture, char* str1, int str1Len, char* str2, int str2Len, global::System.Globalization.CompareOptions options);
+ internal static extern unsafe bool EndsWith(in string culture, char* str1, int str1Len, char* str2, int str2Len, global::System.Globalization.CompareOptions options, out int exceptionalResult, out object result);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern unsafe int IndexOf(out string exceptionMessage, in string culture, char* str1, int str1Len, char* str2, int str2Len, global::System.Globalization.CompareOptions options, bool fromBeginning);
+ internal static extern unsafe int IndexOf(in string culture, char* str1, int str1Len, char* str2, int str2Len, global::System.Globalization.CompareOptions options, bool fromBeginning, out int exceptionalResult, out object result);
}
}
diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Normalization.cs b/src/libraries/Common/src/Interop/Browser/Interop.Normalization.cs
new file mode 100644
index 00000000000000..b55cf5307d704f
--- /dev/null
+++ b/src/libraries/Common/src/Interop/Browser/Interop.Normalization.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+using System.Text;
+
+internal static partial class Interop
+{
+ internal static unsafe partial class JsGlobalization
+ {
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern unsafe int IsNormalized(NormalizationForm normalizationForm, in string source, out int exceptionalResult, out object result);
+
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern unsafe int NormalizeString(NormalizationForm normalizationForm, in string source, char* dstBuffer, int dstBufferCapacity, out int exceptionalResult, out object result);
+ }
+}
diff --git a/src/libraries/Common/src/Interop/Browser/Interop.TextInfo.cs b/src/libraries/Common/src/Interop/Browser/Interop.TextInfo.cs
index 8ab038b851e63d..ff157eb45f26cf 100644
--- a/src/libraries/Common/src/Interop/Browser/Interop.TextInfo.cs
+++ b/src/libraries/Common/src/Interop/Browser/Interop.TextInfo.cs
@@ -8,8 +8,8 @@ internal static partial class Interop
internal static unsafe partial class JsGlobalization
{
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern unsafe void ChangeCaseInvariant(out string exceptionMessage, char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper);
+ internal static extern unsafe void ChangeCaseInvariant(char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper, out int exceptionalResult, out object result);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern unsafe void ChangeCase(out string exceptionMessage, in string culture, char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper);
+ internal static extern unsafe void ChangeCase(in string culture, char* src, int srcLen, char* dstBuffer, int dstBufferCapacity, bool bToUpper, out int exceptionalResult, out object result);
}
}
diff --git a/src/libraries/System.Globalization.Extensions/tests/Hybrid/System.Globalization.Extensions.Hybrid.WASM.Tests.csproj b/src/libraries/System.Globalization.Extensions/tests/Hybrid/System.Globalization.Extensions.Hybrid.WASM.Tests.csproj
new file mode 100644
index 00000000000000..0e7c98c19436f5
--- /dev/null
+++ b/src/libraries/System.Globalization.Extensions/tests/Hybrid/System.Globalization.Extensions.Hybrid.WASM.Tests.csproj
@@ -0,0 +1,20 @@
+
+
+ $(NetCoreAppCurrent)-browser
+ true
+ true
+
+
+
+
+
+
+
+
+ NormalizationDataWin8
+
+
+ NormalizationDataWin7
+
+
+
diff --git a/src/libraries/System.Globalization/System.Globalization.sln b/src/libraries/System.Globalization/System.Globalization.sln
index 16427cb5f577e8..d709c1ea3ffc45 100644
--- a/src/libraries/System.Globalization/System.Globalization.sln
+++ b/src/libraries/System.Globalization/System.Globalization.sln
@@ -1,5 +1,5 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-#
+#
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Private.CoreLib", "..\..\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj", "{E269F8BB-F629-4C96-B9B2-03A00D8B1BFB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities.Unicode", "..\Common\tests\TestUtilities.Unicode\TestUtilities.Unicode.csproj", "{79613DED-481D-44EF-BB89-7AC6BD53026B}"
@@ -34,9 +34,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{A93AFF96-DB2
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{0378EF1C-9838-4AD0-867D-506FB02F8BBB}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hybrid.IOS.Tests", "tests\Hybrid\Hybrid.IOS.Tests.csproj", "{16D9996B-A4E1-440B-8D74-C9ED3715158D}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Globalization.Hybrid.IOS.Tests", "tests\Hybrid\System.Globalization.Hybrid.IOS.Tests.csproj", "{16D9996B-A4E1-440B-8D74-C9ED3715158D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hybrid.WASM.Tests", "tests\Hybrid\Hybrid.WASM.Tests.csproj", "{CAA35471-75A3-41A8-B09D-0CC9822A8E3B}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Globalization.Hybrid.WASM.Tests", "tests\Hybrid\System.Globalization.Hybrid.WASM.Tests.csproj", "{CAA35471-75A3-41A8-B09D-0CC9822A8E3B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/src/libraries/System.Globalization/tests/Hybrid/Hybrid.WASM.Tests.csproj b/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.Hybrid.WASM.Tests.csproj
similarity index 100%
rename from src/libraries/System.Globalization/tests/Hybrid/Hybrid.WASM.Tests.csproj
rename to src/libraries/System.Globalization/tests/Hybrid/System.Globalization.Hybrid.WASM.Tests.csproj
diff --git a/src/libraries/System.Globalization/tests/Hybrid/Hybrid.IOS.Tests.csproj b/src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj
similarity index 100%
rename from src/libraries/System.Globalization/tests/Hybrid/Hybrid.IOS.Tests.csproj
rename to src/libraries/System.Globalization/tests/Hybrid/System.Globalization.IOS.Tests.csproj
diff --git a/src/libraries/System.Globalization/tests/System/Globalization/TextInfoTests.cs b/src/libraries/System.Globalization/tests/System/Globalization/TextInfoTests.cs
index 4fba5e19ff37d3..11b4dd5249843e 100644
--- a/src/libraries/System.Globalization/tests/System/Globalization/TextInfoTests.cs
+++ b/src/libraries/System.Globalization/tests/System/Globalization/TextInfoTests.cs
@@ -325,14 +325,12 @@ public void ToLower_Netcore(string name, string str, string expected)
[Fact]
public void ToLower_InvalidSurrogates()
{
- bool usesTextDecoder = PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsBrowserDomSupportedOrNodeJS;
-
// Invalid UTF-16 in a string (mismatched surrogate pairs) should be unchanged.
foreach (string cultureName in new string[] { "", "en-US", "fr" })
{
- ToLower(cultureName, "\uD83C\uD83C", usesTextDecoder ? "\uFFFD\uFFFD" : "\uD83C\uD83C");
- ToLower(cultureName, "BE CAREFUL, \uDF08\uD83C, THIS ONE IS TRICKY", usesTextDecoder ? "be careful, \uFFFD\uFFFD, this one is tricky" : "be careful, \uDF08\uD83C, this one is tricky");
- ToLower(cultureName, "BE CAREFUL, \uDF08\uDF08, THIS ONE IS TRICKY", usesTextDecoder ? "be careful, \uFFFD\uFFFD, this one is tricky" : "be careful, \uDF08\uDF08, this one is tricky");
+ ToLower(cultureName, "BE CAREFUL, \uD83C\uD83C, THIS ONE IS TRICKY", "be careful, \uD83C\uD83C, this one is tricky");
+ ToLower(cultureName, "BE CAREFUL, \uDF08\uD83C, THIS ONE IS TRICKY", "be careful, \uDF08\uD83C, this one is tricky");
+ ToLower(cultureName, "BE CAREFUL, \uDF08\uDF08, THIS ONE IS TRICKY", "be careful, \uDF08\uDF08, this one is tricky");
}
}
@@ -454,14 +452,12 @@ public void ToUpper_netcore(string name, string str, string expected)
[Fact]
public void ToUpper_InvalidSurrogates()
{
- bool usesTextDecoder = PlatformDetection.IsHybridGlobalizationOnBrowser && PlatformDetection.IsBrowserDomSupportedOrNodeJS;
-
// Invalid UTF-16 in a string (mismatched surrogate pairs) should be unchanged.
foreach (string cultureName in new string[] { "", "en-US", "fr"})
{
- ToUpper(cultureName, "be careful, \uD83C\uD83C, this one is tricky", usesTextDecoder ? "BE CAREFUL, \uFFFD\uFFFD, THIS ONE IS TRICKY" : "BE CAREFUL, \uD83C\uD83C, THIS ONE IS TRICKY");
- ToUpper(cultureName, "be careful, \uDF08\uD83C, this one is tricky", usesTextDecoder ? "BE CAREFUL, \uFFFD\uFFFD, THIS ONE IS TRICKY" : "BE CAREFUL, \uDF08\uD83C, THIS ONE IS TRICKY");
- ToUpper(cultureName, "be careful, \uDF08\uDF08, this one is tricky", usesTextDecoder ? "BE CAREFUL, \uFFFD\uFFFD, THIS ONE IS TRICKY" : "BE CAREFUL, \uDF08\uDF08, THIS ONE IS TRICKY");
+ ToUpper(cultureName, "be careful, \uD83C\uD83C, this one is tricky", "BE CAREFUL, \uD83C\uD83C, THIS ONE IS TRICKY");
+ ToUpper(cultureName, "be careful, \uDF08\uD83C, this one is tricky", "BE CAREFUL, \uDF08\uD83C, THIS ONE IS TRICKY");
+ ToUpper(cultureName, "be careful, \uDF08\uDF08, this one is tricky", "BE CAREFUL, \uDF08\uDF08, THIS ONE IS TRICKY");
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index 10b27e30eb6b04..23d8a0512bd8fb 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -370,6 +370,7 @@
+
@@ -1253,6 +1254,9 @@
+
+ Common\Interop\Interop.Normalization.cs
+
Common\Interop\Interop.CompareInfo.cs
diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs
index a31c6a71b165b0..0d15aa194f9e05 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs
@@ -188,11 +188,9 @@ private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan source, Rea
#if TARGET_BROWSER
if (GlobalizationMode.Hybrid)
{
- int result = Interop.JsGlobalization.IndexOf(out string exceptionMessage, m_name, b, target.Length, a, source.Length, options, fromBeginning);
- if (!string.IsNullOrEmpty(exceptionMessage))
- {
- throw new Exception(exceptionMessage);
- }
+ int result = Interop.JsGlobalization.IndexOf(m_name, b, target.Length, a, source.Length, options, fromBeginning, out int exception, out object ex_result);
+ if (exception != 0)
+ throw new Exception((string)ex_result);
return result;
}
#endif
@@ -288,7 +286,12 @@ private unsafe int IndexOfOrdinalHelper(ReadOnlySpan source, ReadOnlySpan<
InteropCall:
#if TARGET_BROWSER
if (GlobalizationMode.Hybrid)
- return Interop.JsGlobalization.IndexOf(out string exceptionMessage, m_name, b, target.Length, a, source.Length, options, fromBeginning);
+ {
+ int result = Interop.JsGlobalization.IndexOf(m_name, b, target.Length, a, source.Length, options, fromBeginning, out int exception, out object ex_result);
+ if (exception != 0)
+ throw new Exception((string)ex_result);
+ return result;
+ }
#endif
if (fromBeginning)
return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.WebAssembly.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.WebAssembly.cs
index d0258a2f43d9a4..08256545244c36 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.WebAssembly.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.WebAssembly.cs
@@ -49,10 +49,9 @@ private unsafe int JsCompareString(ReadOnlySpan string1, ReadOnlySpan source, ReadOnlySpan p
fixed (char* pSource = &MemoryMarshal.GetReference(source))
fixed (char* pPrefix = &MemoryMarshal.GetReference(prefix))
{
- result = Interop.JsGlobalization.StartsWith(out string exceptionMessage, cultureName, pSource, source.Length, pPrefix, prefix.Length, options);
-
- if (!string.IsNullOrEmpty(exceptionMessage))
- throw new Exception(exceptionMessage);
+ result = Interop.JsGlobalization.StartsWith(cultureName, pSource, source.Length, pPrefix, prefix.Length, options, out int exception, out object ex_result);
+ if (exception != 0)
+ throw new Exception((string)ex_result);
}
@@ -90,10 +88,9 @@ private unsafe bool JsEndsWith(ReadOnlySpan source, ReadOnlySpan pre
fixed (char* pSource = &MemoryMarshal.GetReference(source))
fixed (char* pPrefix = &MemoryMarshal.GetReference(prefix))
{
- result = Interop.JsGlobalization.EndsWith(out string exceptionMessage, cultureName, pSource, source.Length, pPrefix, prefix.Length, options);
-
- if (!string.IsNullOrEmpty(exceptionMessage))
- throw new Exception(exceptionMessage);
+ result = Interop.JsGlobalization.EndsWith(cultureName, pSource, source.Length, pPrefix, prefix.Length, options, out int exception, out object ex_result);
+ if (exception != 0)
+ throw new Exception((string)ex_result);
}
return result;
@@ -118,10 +115,9 @@ private unsafe int JsIndexOfCore(ReadOnlySpan source, ReadOnlySpan t
fixed (char* pSource = &MemoryMarshal.GetReference(source))
fixed (char* pTarget = &MemoryMarshal.GetReference(target))
{
- idx = Interop.JsGlobalization.IndexOf(out string exceptionMessage, m_name, pTarget, target.Length, pSource, source.Length, options, fromBeginning);
-
- if (!string.IsNullOrEmpty(exceptionMessage))
- throw new Exception(exceptionMessage);
+ idx = Interop.JsGlobalization.IndexOf(m_name, pTarget, target.Length, pSource, source.Length, options, fromBeginning, out int exception, out object ex_result);
+ if (exception != 0)
+ throw new Exception((string)ex_result);
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.WebAssembly.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.WebAssembly.cs
new file mode 100644
index 00000000000000..b930f6812904af
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.WebAssembly.cs
@@ -0,0 +1,88 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.Globalization
+{
+ internal static partial class Normalization
+ {
+ private static unsafe bool JsIsNormalized(string strInput, NormalizationForm normalizationForm)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(!GlobalizationMode.UseNls);
+
+ ValidateArguments(strInput, normalizationForm);
+
+ int ret = Interop.JsGlobalization.IsNormalized(normalizationForm, strInput, out int exception, out object ex_result);
+ if (exception != 0)
+ throw new Exception((string)ex_result);
+
+ return ret == 1;
+ }
+
+ private static unsafe string JsNormalize(string strInput, NormalizationForm normalizationForm)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+ Debug.Assert(!GlobalizationMode.UseNls);
+
+ ValidateArguments(strInput, normalizationForm);
+
+ char[]? toReturn = null;
+ try
+ {
+ const int StackallocThreshold = 512;
+
+ Span buffer = strInput.Length <= StackallocThreshold
+ ? stackalloc char[StackallocThreshold]
+ : (toReturn = ArrayPool.Shared.Rent(strInput.Length));
+
+ for (int attempt = 0; attempt < 2; attempt++)
+ {
+ int realLen;
+ fixed (char* pDest = &MemoryMarshal.GetReference(buffer))
+ {
+ realLen = Interop.JsGlobalization.NormalizeString(normalizationForm, strInput, pDest, buffer.Length, out int exception, out object ex_result);
+ if (exception != 0)
+ throw new Exception((string)ex_result);
+ }
+
+ if (realLen <= buffer.Length)
+ {
+ ReadOnlySpan result = buffer.Slice(0, realLen);
+ return result.SequenceEqual(strInput)
+ ? strInput
+ : new string(result);
+ }
+
+ Debug.Assert(realLen > StackallocThreshold);
+
+ if (attempt == 0)
+ {
+ if (toReturn != null)
+ {
+ // Clear toReturn first to ensure we don't return the same buffer twice
+ char[] temp = toReturn;
+ toReturn = null;
+ ArrayPool.Shared.Return(temp);
+ }
+
+ buffer = toReturn = ArrayPool.Shared.Rent(realLen);
+ }
+ }
+
+ throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex, nameof(strInput));
+ }
+ finally
+ {
+ if (toReturn != null)
+ {
+ ArrayPool.Shared.Return(toReturn);
+ }
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.cs
index d120302a8aa8e7..2bc6107b5dad6c 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Normalization.cs
@@ -19,6 +19,10 @@ internal static bool IsNormalized(string strInput, NormalizationForm normalizati
return GlobalizationMode.UseNls ?
NlsIsNormalized(strInput, normalizationForm) :
+#if TARGET_BROWSER
+ GlobalizationMode.Hybrid ?
+ JsIsNormalized(strInput, normalizationForm) :
+#endif
IcuIsNormalized(strInput, normalizationForm);
}
@@ -33,6 +37,10 @@ internal static string Normalize(string strInput, NormalizationForm normalizatio
return GlobalizationMode.UseNls ?
NlsNormalize(strInput, normalizationForm) :
+#if TARGET_BROWSER
+ GlobalizationMode.Hybrid ?
+ JsNormalize(strInput, normalizationForm) :
+#endif
IcuNormalize(strInput, normalizationForm);
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.WebAssembly.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.WebAssembly.cs
index e5ad1ec6420a45..a2dd2a02c05f18 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.WebAssembly.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.WebAssembly.cs
@@ -13,17 +13,18 @@ internal unsafe void JsChangeCase(char* src, int srcLen, char* dstBuffer, int ds
Debug.Assert(!GlobalizationMode.UseNls);
Debug.Assert(GlobalizationMode.Hybrid);
- string exceptionMessage;
+ int exception;
+ object ex_result;
if (HasEmptyCultureName)
{
- Interop.JsGlobalization.ChangeCaseInvariant(out exceptionMessage, src, srcLen, dstBuffer, dstBufferCapacity, toUpper);
+ Interop.JsGlobalization.ChangeCaseInvariant(src, srcLen, dstBuffer, dstBufferCapacity, toUpper, out exception, out ex_result);
}
else
{
- Interop.JsGlobalization.ChangeCase(out exceptionMessage, _cultureName, src, srcLen, dstBuffer, dstBufferCapacity, toUpper);
+ Interop.JsGlobalization.ChangeCase(_cultureName, src, srcLen, dstBuffer, dstBufferCapacity, toUpper, out exception, out ex_result);
}
- if (!string.IsNullOrEmpty(exceptionMessage))
- throw new Exception(exceptionMessage);
+ if (exception != 0)
+ throw new Exception((string)ex_result);
}
}
}
diff --git a/src/mono/sample/wasm/browser-bench/String.cs b/src/mono/sample/wasm/browser-bench/String.cs
index d57ec2063363fb..2ac38b481d8e43 100644
--- a/src/mono/sample/wasm/browser-bench/String.cs
+++ b/src/mono/sample/wasm/browser-bench/String.cs
@@ -17,6 +17,7 @@ public StringTask()
{
measurements = new Measurement[] {
new NormalizeMeasurement(),
+ new IsNormalizedMeasurement(),
new NormalizeMeasurementASCII(),
new TextInfoToLower(),
new TextInfoToUpper(),
@@ -79,6 +80,12 @@ public class NormalizeMeasurement : StringMeasurement
public override void RunStep() => str.Normalize();
}
+ public class IsNormalizedMeasurement : StringMeasurement
+ {
+ public override string Name => "IsNormalized";
+ public override void RunStep() => str.IsNormalized();
+ }
+
public abstract class ASCIIStringMeasurement : StringMeasurement
{
public override Task BeforeBatch()
diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c
index 8d92fdc5e0ea73..1991b4c930c1d0 100644
--- a/src/mono/wasm/runtime/corebindings.c
+++ b/src/mono/wasm/runtime/corebindings.c
@@ -43,12 +43,14 @@ extern void* mono_wasm_invoke_js_blazor (MonoString **exceptionMessage, void *ca
#endif /* DISABLE_LEGACY_JS_INTEROP */
// HybridGlobalization
-extern void mono_wasm_change_case_invariant(MonoString **exceptionMessage, const uint16_t* src, int32_t srcLength, uint16_t* dst, int32_t dstLength, mono_bool bToUpper);
-extern void mono_wasm_change_case(MonoString **exceptionMessage, MonoString **culture, const uint16_t* src, int32_t srcLength, uint16_t* dst, int32_t dstLength, mono_bool bToUpper);
-extern int mono_wasm_compare_string(MonoString **exceptionMessage, MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options);
-extern mono_bool mono_wasm_starts_with(MonoString **exceptionMessage, MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options);
-extern mono_bool mono_wasm_ends_with(MonoString **exceptionMessage, MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options);
-extern int mono_wasm_index_of(MonoString **exceptionMessage, MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options, mono_bool fromBeginning);
+extern void mono_wasm_change_case_invariant(const uint16_t* src, int32_t srcLength, uint16_t* dst, int32_t dstLength, mono_bool bToUpper, int *is_exception, MonoObject** ex_result);
+extern void mono_wasm_change_case(MonoString **culture, const uint16_t* src, int32_t srcLength, uint16_t* dst, int32_t dstLength, mono_bool bToUpper, int *is_exception, MonoObject** ex_result);
+extern int mono_wasm_compare_string(MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options, int *is_exception, MonoObject** ex_result);
+extern mono_bool mono_wasm_starts_with(MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options, int *is_exception, MonoObject** ex_result);
+extern mono_bool mono_wasm_ends_with(MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options, int *is_exception, MonoObject** ex_result);
+extern int mono_wasm_index_of(MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options, mono_bool fromBeginning, int *is_exception, MonoObject** ex_result);
+extern mono_bool mono_wasm_is_normalized(int32_t normalizationForm, MonoString **src, int *is_exception, MonoObject** ex_result);
+extern int mono_wasm_normalize_string(int32_t normalizationForm, MonoString **src, uint16_t* dst, int32_t dstLength, int *is_exception, MonoObject** ex_result);
void bindings_initialize_internals (void)
{
@@ -83,4 +85,6 @@ void bindings_initialize_internals (void)
mono_add_internal_call ("Interop/JsGlobalization::StartsWith", mono_wasm_starts_with);
mono_add_internal_call ("Interop/JsGlobalization::EndsWith", mono_wasm_ends_with);
mono_add_internal_call ("Interop/JsGlobalization::IndexOf", mono_wasm_index_of);
+ mono_add_internal_call ("Interop/JsGlobalization::IsNormalized", mono_wasm_is_normalized);
+ mono_add_internal_call ("Interop/JsGlobalization::NormalizeString", mono_wasm_normalize_string);
}
diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js
index ab293f667aea85..23f27df9485603 100644
--- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js
+++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js
@@ -112,6 +112,10 @@ let linked_functions = [
"mono_wasm_starts_with",
"mono_wasm_ends_with",
"mono_wasm_index_of",
+ "mono_wasm_is_normalized",
+ "mono_wasm_normalize_string",
+ "mono_wasm_to_Unicode",
+ "mono_wasm_to_ASCII",
"icudt68_dat",
];
diff --git a/src/mono/wasm/runtime/exports-linker.ts b/src/mono/wasm/runtime/exports-linker.ts
index f4be31cfa0912f..0aa96cc9cdecdd 100644
--- a/src/mono/wasm/runtime/exports-linker.ts
+++ b/src/mono/wasm/runtime/exports-linker.ts
@@ -27,7 +27,9 @@ import {
mono_wasm_invoke_js_blazor, mono_wasm_invoke_js_with_args_ref, mono_wasm_get_object_property_ref, mono_wasm_set_object_property_ref,
mono_wasm_get_by_index_ref, mono_wasm_set_by_index_ref, mono_wasm_get_global_object_ref
} from "./net6-legacy/method-calls";
-import { mono_wasm_change_case, mono_wasm_change_case_invariant, mono_wasm_compare_string, mono_wasm_ends_with, mono_wasm_index_of, mono_wasm_starts_with } from "./hybrid-globalization";
+import { mono_wasm_change_case, mono_wasm_change_case_invariant } from "./hybrid-globalization/change-case";
+import { mono_wasm_compare_string, mono_wasm_ends_with, mono_wasm_starts_with, mono_wasm_index_of } from "./hybrid-globalization/collations";
+import { mono_wasm_is_normalized, mono_wasm_normalize_string } from "./hybrid-globalization/normalization";
// the methods would be visible to EMCC linker
// --- keep in sync with dotnet.cjs.lib.js ---
@@ -103,6 +105,8 @@ export function export_linker(): any {
mono_wasm_starts_with,
mono_wasm_ends_with,
mono_wasm_index_of,
+ mono_wasm_is_normalized,
+ mono_wasm_normalize_string,
// threading exports, if threading is enabled
...mono_wasm_threads_exports,
diff --git a/src/mono/wasm/runtime/hybrid-globalization/change-case.ts b/src/mono/wasm/runtime/hybrid-globalization/change-case.ts
new file mode 100644
index 00000000000000..fddd80e9823829
--- /dev/null
+++ b/src/mono/wasm/runtime/hybrid-globalization/change-case.ts
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { Module } from "../globals";
+import { setU16 } from "../memory";
+import { mono_wasm_new_external_root } from "../roots";
+import { conv_string_root } from "../strings";
+import { MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "../types/internal";
+import { Int32Ptr } from "../types/emscripten";
+import { wrap_error_root, wrap_no_error_root } from "../invoke-js";
+
+export function mono_wasm_change_case_invariant(src: number, srcLength: number, dst: number, dstLength: number, toUpper: number, is_exception: Int32Ptr, ex_address: MonoObjectRef) : void{
+ const exceptionRoot = mono_wasm_new_external_root(ex_address);
+ try{
+ const input = get_utf16_string(src, srcLength);
+ let result = toUpper ? input.toUpperCase() : input.toLowerCase();
+ // Unicode defines some codepoints which expand into multiple codepoints,
+ // originally we do not support this expansion
+ if (result.length > dstLength)
+ result = input;
+
+ for (let i = 0; i < result.length; i++)
+ setU16(dst + i*2, result.charCodeAt(i));
+ wrap_no_error_root(is_exception, exceptionRoot);
+ }
+ catch (ex: any) {
+ wrap_error_root(is_exception, ex, exceptionRoot);
+ }
+ finally {
+ exceptionRoot.release();
+ }
+}
+
+export function mono_wasm_change_case(culture: MonoStringRef, src: number, srcLength: number, dst: number, destLength: number, toUpper: number, is_exception: Int32Ptr, ex_address: MonoObjectRef) : void{
+ const cultureRoot = mono_wasm_new_external_root(culture),
+ exceptionRoot = mono_wasm_new_external_root(ex_address);
+ try{
+ const cultureName = conv_string_root(cultureRoot);
+ if (!cultureName)
+ throw new Error("Cannot change case, the culture name is null.");
+ const input = get_utf16_string(src, srcLength);
+ let result = toUpper ? input.toLocaleUpperCase(cultureName) : input.toLocaleLowerCase(cultureName);
+ if (result.length > destLength)
+ result = input;
+
+ for (let i = 0; i < destLength; i++)
+ setU16(dst + i*2, result.charCodeAt(i));
+ wrap_no_error_root(is_exception, exceptionRoot);
+ }
+ catch (ex: any) {
+ wrap_error_root(is_exception, ex, exceptionRoot);
+ }
+ finally {
+ cultureRoot.release();
+ exceptionRoot.release();
+ }
+}
+
+function get_utf16_string(ptr: number, length: number): string{
+ const view = new Uint16Array(Module.HEAPU16.buffer, ptr, length);
+ let string = "";
+ for (let i = 0; i < length; i++)
+ string += String.fromCharCode(view[i]);
+ return string;
+}
diff --git a/src/mono/wasm/runtime/hybrid-globalization.ts b/src/mono/wasm/runtime/hybrid-globalization/collations.ts
similarity index 68%
rename from src/mono/wasm/runtime/hybrid-globalization.ts
rename to src/mono/wasm/runtime/hybrid-globalization/collations.ts
index b94a2d1ed4e47e..0df5b6dd641488 100644
--- a/src/mono/wasm/runtime/hybrid-globalization.ts
+++ b/src/mono/wasm/runtime/hybrid-globalization/collations.ts
@@ -1,87 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { mono_wasm_new_external_root } from "./roots";
-import { MonoString, MonoStringRef } from "./types/internal";
-import { Int32Ptr } from "./types/emscripten";
-import { conv_string_root, js_string_to_mono_string_root, string_decoder } from "./strings";
-import { setU16_unchecked } from "./memory";
+import { mono_wasm_new_external_root } from "../roots";
+import { conv_string_root, string_decoder } from "../strings";
+import { MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "../types/internal";
+import { Int32Ptr } from "../types/emscripten";
+import { wrap_error_root, wrap_no_error_root } from "../invoke-js";
-export function mono_wasm_change_case_invariant(exceptionMessage: Int32Ptr, src: number, srcLength: number, dst: number, dstLength: number, toUpper: number): void {
- try {
- const input = string_decoder.decode(src, (src + 2 * srcLength));
- let result = toUpper ? input.toUpperCase() : input.toLowerCase();
- // Unicode defines some codepoints which expand into multiple codepoints,
- // originally we do not support this expansion
- if (result.length > dstLength)
- result = input;
-
- for (let i = 0, j = dst; i < result.length; i++, j += 2)
- setU16_unchecked(j, result.charCodeAt(i));
- }
- catch (ex: any) {
- pass_exception_details(ex, exceptionMessage);
- }
-}
-
-export function mono_wasm_change_case(exceptionMessage: Int32Ptr, culture: MonoStringRef, src: number, srcLength: number, dst: number, destLength: number, toUpper: number): void {
- const cultureRoot = mono_wasm_new_external_root(culture);
- try {
- const cultureName = conv_string_root(cultureRoot);
- if (!cultureName)
- throw new Error("Cannot change case, the culture name is null.");
- const input = string_decoder.decode(src, (src + 2 * srcLength));
- let result = toUpper ? input.toLocaleUpperCase(cultureName) : input.toLocaleLowerCase(cultureName);
- if (result.length > destLength)
- result = input;
-
- for (let i = 0, j = dst; i < result.length; i++, j += 2)
- setU16_unchecked(j, result.charCodeAt(i));
- }
- catch (ex: any) {
- pass_exception_details(ex, exceptionMessage);
- }
- finally {
- cultureRoot.release();
- }
-}
+const COMPARISON_ERROR = -2;
+const INDEXING_ERROR = -1;
-export function mono_wasm_compare_string(exceptionMessage: Int32Ptr, culture: MonoStringRef, str1: number, str1Length: number, str2: number, str2Length: number, options: number): number {
- const cultureRoot = mono_wasm_new_external_root(culture);
- try {
+export function mono_wasm_compare_string(culture: MonoStringRef, str1: number, str1Length: number, str2: number, str2Length: number, options: number, is_exception: Int32Ptr, ex_address: MonoObjectRef) : number{
+ const cultureRoot = mono_wasm_new_external_root(culture),
+ exceptionRoot = mono_wasm_new_external_root(ex_address);
+ try{
const cultureName = conv_string_root(cultureRoot);
- const string1 = string_decoder.decode(str1, (str1 + 2 * str1Length));
- const string2 = string_decoder.decode(str2, (str2 + 2 * str2Length));
+ const string1 = string_decoder.decode(str1, (str1 + 2*str1Length));
+ const string2 = string_decoder.decode(str2, (str2 + 2*str2Length));
const casePicker = (options & 0x1f);
const locale = cultureName ? cultureName : undefined;
+ wrap_no_error_root(is_exception, exceptionRoot);
return compare_strings(string1, string2, locale, casePicker);
}
catch (ex: any) {
- pass_exception_details(ex, exceptionMessage);
- return -2;
+ wrap_error_root(is_exception, ex, exceptionRoot);
+ return COMPARISON_ERROR;
}
finally {
cultureRoot.release();
+ exceptionRoot.release();
}
}
-function pass_exception_details(ex: any, exceptionMessage: Int32Ptr) {
- const exceptionJsString = ex.message + "\n" + ex.stack;
- const exceptionRoot = mono_wasm_new_external_root(exceptionMessage);
- js_string_to_mono_string_root(exceptionJsString, exceptionRoot);
- exceptionRoot.release();
-}
-
-export function mono_wasm_starts_with(exceptionMessage: Int32Ptr, culture: MonoStringRef, srcPtr: number, srcLength: number, prefixPtr: number, prefixLength: number, options: number): number{
- const cultureRoot = mono_wasm_new_external_root(culture);
- try {
+export function mono_wasm_starts_with(culture: MonoStringRef, str1: number, str1Length: number, str2: number, str2Length: number, options: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): number{
+ const cultureRoot = mono_wasm_new_external_root(culture),
+ exceptionRoot = mono_wasm_new_external_root(ex_address);
+ try{
const cultureName = conv_string_root(cultureRoot);
- const prefix = decode_to_clean_string(prefixPtr, prefixLength);
+ const prefix = decode_to_clean_string(str2, str2Length);
// no need to look for an empty string
if (prefix.length == 0)
return 1; // true
- const source = decode_to_clean_string(srcPtr, srcLength);
+ const source = decode_to_clean_string(str1, str1Length);
if (source.length < prefix.length)
return 0; //false
const sourceOfPrefixLength = source.slice(0, prefix.length);
@@ -89,26 +50,29 @@ export function mono_wasm_starts_with(exceptionMessage: Int32Ptr, culture: MonoS
const casePicker = (options & 0x1f);
const locale = cultureName ? cultureName : undefined;
const result = compare_strings(sourceOfPrefixLength, prefix, locale, casePicker);
+ wrap_no_error_root(is_exception, exceptionRoot);
return result === 0 ? 1 : 0; // equals ? true : false
}
catch (ex: any) {
- pass_exception_details(ex, exceptionMessage);
- return -1;
+ wrap_error_root(is_exception, ex, exceptionRoot);
+ return INDEXING_ERROR;
}
finally {
cultureRoot.release();
+ exceptionRoot.release();
}
}
-export function mono_wasm_ends_with(exceptionMessage: Int32Ptr, culture: MonoStringRef, srcPtr: number, srcLength: number, suffixPtr: number, suffixLength: number, options: number): number{
- const cultureRoot = mono_wasm_new_external_root(culture);
- try {
+export function mono_wasm_ends_with(culture: MonoStringRef, str1: number, str1Length: number, str2: number, str2Length: number, options: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): number{
+ const cultureRoot = mono_wasm_new_external_root(culture),
+ exceptionRoot = mono_wasm_new_external_root(ex_address);
+ try{
const cultureName = conv_string_root(cultureRoot);
- const suffix = decode_to_clean_string(suffixPtr, suffixLength);
+ const suffix = decode_to_clean_string(str2, str2Length);
if (suffix.length == 0)
return 1; // true
- const source = decode_to_clean_string(srcPtr, srcLength);
+ const source = decode_to_clean_string(str1, str1Length);
const diff = source.length - suffix.length;
if (diff < 0)
return 0; //false
@@ -117,41 +81,38 @@ export function mono_wasm_ends_with(exceptionMessage: Int32Ptr, culture: MonoStr
const casePicker = (options & 0x1f);
const locale = cultureName ? cultureName : undefined;
const result = compare_strings(sourceOfSuffixLength, suffix, locale, casePicker);
+ wrap_no_error_root(is_exception, exceptionRoot);
return result === 0 ? 1 : 0; // equals ? true : false
}
catch (ex: any) {
- pass_exception_details(ex, exceptionMessage);
- return -1;
+ wrap_error_root(is_exception, ex, exceptionRoot);
+ return INDEXING_ERROR;
}
finally {
cultureRoot.release();
+ exceptionRoot.release();
}
}
-function decode_to_clean_string(strPtr: number, strLen: number)
-{
- const str = string_decoder.decode(strPtr, (strPtr + 2*strLen));
- return clean_string(str);
-}
-
-function clean_string(str: string)
-{
- const nStr = str.normalize();
- return nStr.replace(/[\u200B-\u200D\uFEFF\0]/g, "");
-}
-
-export function mono_wasm_index_of(exceptionMessage: Int32Ptr, culture: MonoStringRef, needlePtr: number, needleLength: number, srcPtr: number, srcLength: number, options: number, fromBeginning: number): number{
- const cultureRoot = mono_wasm_new_external_root(culture);
+export function mono_wasm_index_of(culture: MonoStringRef, needlePtr: number, needleLength: number, srcPtr: number, srcLength: number, options: number, fromBeginning: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): number{
+ const cultureRoot = mono_wasm_new_external_root(culture),
+ exceptionRoot = mono_wasm_new_external_root(ex_address);
try {
const needle = string_decoder.decode(needlePtr, (needlePtr + 2*needleLength));
// no need to look for an empty string
if (clean_string(needle).length == 0)
+ {
+ wrap_no_error_root(is_exception, exceptionRoot);
return fromBeginning ? 0 : srcLength;
+ }
const source = string_decoder.decode(srcPtr, (srcPtr + 2*srcLength));
// no need to look in an empty string
if (clean_string(source).length == 0)
+ {
+ wrap_no_error_root(is_exception, exceptionRoot);
return fromBeginning ? 0 : srcLength;
+ }
const cultureName = conv_string_root(cultureRoot);
const locale = cultureName ? cultureName : undefined;
const casePicker = (options & 0x1f);
@@ -210,14 +171,16 @@ export function mono_wasm_index_of(exceptionMessage: Int32Ptr, culture: MonoStri
}
i = nextIndex;
}
+ wrap_no_error_root(is_exception, exceptionRoot);
return result;
}
catch (ex: any) {
- pass_exception_details(ex, exceptionMessage);
- return -1;
+ wrap_error_root(is_exception, ex, exceptionRoot);
+ return INDEXING_ERROR;
}
finally {
cultureRoot.release();
+ exceptionRoot.release();
}
function check_match_found(str1: string, str2: string, locale: string | undefined, casePicker: number) : boolean
@@ -226,19 +189,20 @@ export function mono_wasm_index_of(exceptionMessage: Int32Ptr, culture: MonoStri
}
}
-export function compare_strings(string1: string, string2: string, locale: string | undefined, casePicker: number): number {
- switch (casePicker) {
+function compare_strings(string1: string, string2: string, locale: string | undefined, casePicker: number) : number{
+ switch (casePicker)
+ {
case 0:
// 0: None - default algorithm for the platform OR
- // StringSort - since .Net 5 StringSort gives the same result as None, even for hyphen etc.
+ // StringSort - for ICU it gives the same result as None, see: https://github.com/dotnet/dotnet-api-docs/issues
// does not work for "ja"
- if (locale && locale.startsWith("ja"))
- return -2;
+ if (locale && locale.split("-")[0] === "ja")
+ return COMPARISON_ERROR;
return string1.localeCompare(string2, locale); // a ≠ b, a ≠ á, a ≠ A
case 8:
// 8: IgnoreKanaType works only for "ja"
- if (locale && !locale.startsWith("ja"))
- return -2;
+ if (locale && locale.split("-")[0] !== "ja")
+ return COMPARISON_ERROR;
return string1.localeCompare(string2, locale); // a ≠ b, a ≠ á, a ≠ A
case 1:
// 1: IgnoreCase
@@ -317,3 +281,15 @@ export function compare_strings(string1: string, string2: string, locale: string
throw new Error(`Invalid comparison option. Option=${casePicker}`);
}
}
+
+function decode_to_clean_string(strPtr: number, strLen: number)
+{
+ const str = string_decoder.decode(strPtr, (strPtr + 2*strLen));
+ return clean_string(str);
+}
+
+function clean_string(str: string)
+{
+ const nStr = str.normalize();
+ return nStr.replace(/[\u200B-\u200D\uFEFF\0]/g, "");
+}
diff --git a/src/mono/wasm/runtime/hybrid-globalization/normalization.ts b/src/mono/wasm/runtime/hybrid-globalization/normalization.ts
new file mode 100644
index 00000000000000..a3e92e252817f6
--- /dev/null
+++ b/src/mono/wasm/runtime/hybrid-globalization/normalization.ts
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { setU16 } from "../memory";
+import { mono_wasm_new_external_root } from "../roots";
+import { conv_string_root } from "../strings";
+import { MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "../types/internal";
+import { Int32Ptr } from "../types/emscripten";
+import { wrap_error_root, wrap_no_error_root } from "../invoke-js";
+
+const NORMALIZATION_FORM_MAP = [undefined, "NFC", "NFD", undefined, undefined, "NFKC", "NFKD"];
+const ERROR = -1;
+
+export function mono_wasm_is_normalized(normalizationForm: number, inputStr: MonoStringRef, is_exception: Int32Ptr, ex_address: MonoObjectRef) : number{
+ const inputRoot = mono_wasm_new_external_root(inputStr),
+ exceptionRoot = mono_wasm_new_external_root(ex_address);
+ try{
+ const jsString = conv_string_root(inputRoot);
+ if (!jsString)
+ throw new Error("Invalid string was received.");
+
+ const normalization = normalization_to_string(normalizationForm);
+ const result = jsString.normalize(normalization);
+ wrap_no_error_root(is_exception, exceptionRoot);
+ return result === jsString ? 1 : 0;
+ }
+ catch (ex) {
+ wrap_error_root(is_exception, ex, exceptionRoot);
+ return ERROR;
+ } finally {
+ inputRoot.release();
+ exceptionRoot.release();
+ }
+}
+
+export function mono_wasm_normalize_string(normalizationForm: number, inputStr: MonoStringRef, dstPtr: number, dstLength: number, is_exception: Int32Ptr, ex_address: MonoObjectRef) : number{
+ const inputRoot = mono_wasm_new_external_root(inputStr),
+ exceptionRoot = mono_wasm_new_external_root(ex_address);
+ try {
+ const jsString = conv_string_root(inputRoot);
+ if (!jsString)
+ throw new Error("Invalid string was received.");
+
+ const normalization = normalization_to_string(normalizationForm);
+ const result = jsString.normalize(normalization);
+
+ // increase the dest buffer
+ if (result.length > dstLength)
+ return result.length;
+ for (let i = 0; i < result.length; i++)
+ setU16(dstPtr + i*2, result.charCodeAt(i));
+ return result.length;
+ } catch (ex) {
+ wrap_error_root(is_exception, ex, exceptionRoot);
+ return ERROR;
+ } finally {
+ inputRoot.release();
+ exceptionRoot.release();
+ }
+}
+
+const normalization_to_string = (normalizationForm: number): string => NORMALIZATION_FORM_MAP[normalizationForm] ?? "NFC";
+