webkit2gtk3/icu60.patch
Michael Catanzaro 1df915795c Update to WebKitGTK 2.48.1
Also, switch to Clang, because this is the preferred compiler anyway,
and otherwise ld runs out of memory when linking on i686.

Resolves: RHEL-84089
2025-04-14 18:59:37 -05:00

1833 lines
79 KiB
Diff

From 5f60bd9605e60231e6d430e7559a2af9d6aa4c33 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Thu, 27 Mar 2025 15:56:41 -0500
Subject: [PATCH 1/5] Revert "[CMake] Update minimum ICU version to 70.1"
This reverts commit 95d71be25d5b838b1171e6b9b2cd526190118fba.
---
Source/cmake/OptionsGTK.cmake | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Source/cmake/OptionsGTK.cmake b/Source/cmake/OptionsGTK.cmake
index 0a30e0c4c7c1..814009146300 100644
--- a/Source/cmake/OptionsGTK.cmake
+++ b/Source/cmake/OptionsGTK.cmake
@@ -10,11 +10,11 @@ set(USER_AGENT_BRANDING "" CACHE STRING "Branding to add to user agent string")
find_package(Cairo 1.14.0 REQUIRED)
find_package(LibGcrypt 1.7.0 REQUIRED)
find_package(Libtasn1 REQUIRED)
-find_package(HarfBuzz 2.7.4 REQUIRED COMPONENTS ICU)
-find_package(ICU 70.1 REQUIRED COMPONENTS data i18n uc)
+find_package(HarfBuzz 1.4.2 REQUIRED COMPONENTS ICU)
+find_package(ICU 61.2 REQUIRED COMPONENTS data i18n uc)
find_package(JPEG REQUIRED)
find_package(Epoxy 1.5.4 REQUIRED)
-find_package(LibXml2 2.9.13 REQUIRED)
+find_package(LibXml2 2.8.0 REQUIRED)
find_package(PNG REQUIRED)
find_package(SQLite3 REQUIRED)
find_package(Threads REQUIRED)
--
2.49.0
From 3e1a6690440b9cb3a11a4cadc69a985845a4a5bd Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Fri, 28 Mar 2025 14:45:03 -0500
Subject: [PATCH 2/5] Revert "[JSC] Remove unnecessary ICU version checks"
This reverts commit af62f09a1fad0b72293a7f0d082704d92116cb9a.
---
.../runtime/IntlDurationFormat.cpp | 2 +-
.../runtime/IntlNumberFormat.cpp | 12 ++--
.../runtime/IntlNumberFormatInlines.h | 57 ++++++++++++-------
3 files changed, 47 insertions(+), 24 deletions(-)
diff --git a/Source/JavaScriptCore/runtime/IntlDurationFormat.cpp b/Source/JavaScriptCore/runtime/IntlDurationFormat.cpp
index 61264bfe23d9..a1c628082dd5 100644
--- a/Source/JavaScriptCore/runtime/IntlDurationFormat.cpp
+++ b/Source/JavaScriptCore/runtime/IntlDurationFormat.cpp
@@ -457,7 +457,7 @@ static Vector<Element> collectElements(JSGlobalObject* globalObject, const IntlD
// 3.k. If style is "2-digit", then
// i. Perform ! CreateDataPropertyOrThrow(nfOpts, "minimumIntegerDigits", 2F).
- skeletonBuilder.append(" integer-width/*"_s);
+ skeletonBuilder.append(" integer-width/"_s, WTF::ICU::majorVersion() >= 67 ? '*' : '+'); // Prior to ICU 67, use the symbol + instead of *.
if (style == IntlDurationFormat::UnitStyle::TwoDigit)
skeletonBuilder.append("00"_s);
else
diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp b/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp
index 67708f1d8d33..0938d2d5b4a5 100644
--- a/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp
+++ b/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp
@@ -517,10 +517,14 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa
skeletonBuilder.append(" sign-except-zero"_s);
break;
case SignDisplay::Negative:
- if (useAccounting)
- skeletonBuilder.append(" sign-accounting-negative"_s);
- else
- skeletonBuilder.append(" sign-negative"_s);
+ // Only ICU69~ supports negative sign display. Ignore this option if linked ICU does not support it.
+ // https://github.com/unicode-org/icu/commit/1aa0dad8e06ecc99bff442dd37f6daa2d39d9a5a
+ if (WTF::ICU::majorVersion() >= 69) {
+ if (useAccounting)
+ skeletonBuilder.append(" sign-accounting-negative"_s);
+ else
+ skeletonBuilder.append(" sign-negative"_s);
+ }
break;
}
diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h b/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h
index ddccce2fd112..980da4e3c74a 100644
--- a/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h
+++ b/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h
@@ -199,12 +199,24 @@ void appendNumberFormatDigitOptionsToSkeleton(IntlType* intlInstance, StringBuil
case RoundingMode::Trunc:
skeletonBuilder.append(" rounding-mode-down"_s);
break;
- case RoundingMode::HalfCeil:
- skeletonBuilder.append(" rounding-mode-half-ceiling"_s);
+ case RoundingMode::HalfCeil: {
+ // Only ICU69~ supports half-ceiling. Ignore this option if linked ICU does not support it.
+ // https://github.com/unicode-org/icu/commit/e8dfea9bb6bb27596731173b352759e44ad06b21
+ if (WTF::ICU::majorVersion() >= 69)
+ skeletonBuilder.append(" rounding-mode-half-ceiling"_s);
+ else
+ skeletonBuilder.append(" rounding-mode-half-up"_s); // Default option.
break;
- case RoundingMode::HalfFloor:
- skeletonBuilder.append(" rounding-mode-half-floor"_s);
+ }
+ case RoundingMode::HalfFloor: {
+ // Only ICU69~ supports half-flooring. Ignore this option if linked ICU does not support it.
+ // https://github.com/unicode-org/icu/commit/e8dfea9bb6bb27596731173b352759e44ad06b21
+ if (WTF::ICU::majorVersion() >= 69)
+ skeletonBuilder.append(" rounding-mode-half-floor"_s);
+ else
+ skeletonBuilder.append(" rounding-mode-half-up"_s); // Default option.
break;
+ }
case RoundingMode::HalfExpand:
skeletonBuilder.append(" rounding-mode-half-up"_s);
break;
@@ -217,7 +229,7 @@ void appendNumberFormatDigitOptionsToSkeleton(IntlType* intlInstance, StringBuil
}
// https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#integer-width
- skeletonBuilder.append(" integer-width/*"_s);
+ skeletonBuilder.append(" integer-width/"_s, WTF::ICU::majorVersion() >= 67 ? '*' : '+'); // Prior to ICU 67, use the symbol + instead of *.
for (unsigned i = 0; i < intlInstance->m_minimumIntegerDigits; ++i)
skeletonBuilder.append('0');
@@ -255,19 +267,23 @@ void appendNumberFormatDigitOptionsToSkeleton(IntlType* intlInstance, StringBuil
}
case IntlRoundingType::MorePrecision:
case IntlRoundingType::LessPrecision:
- // https://github.com/unicode-org/icu/commit/d7db6c1f8655bb53153695b09a50029fd04a8364
- // https://github.com/unicode-org/icu/blob/main/docs/userguide/format_parse/numbers/skeletons.md#precision
- skeletonBuilder.append(" ."_s);
- for (unsigned i = 0; i < intlInstance->m_minimumFractionDigits; ++i)
- skeletonBuilder.append('0');
- for (unsigned i = 0; i < intlInstance->m_maximumFractionDigits - intlInstance->m_minimumFractionDigits; ++i)
- skeletonBuilder.append('#');
- skeletonBuilder.append('/');
- for (unsigned i = 0; i < intlInstance->m_minimumSignificantDigits; ++i)
- skeletonBuilder.append('@');
- for (unsigned i = 0; i < intlInstance->m_maximumSignificantDigits - intlInstance->m_minimumSignificantDigits; ++i)
- skeletonBuilder.append('#');
- skeletonBuilder.append(intlInstance->m_roundingType == IntlRoundingType::MorePrecision ? 'r' : 's');
+ // Before Intl.NumberFormat v3, it was CompactRounding mode, where we do not configure anything.
+ // So, if linked ICU is ~68, we do nothing.
+ if (WTF::ICU::majorVersion() >= 69) {
+ // https://github.com/unicode-org/icu/commit/d7db6c1f8655bb53153695b09a50029fd04a8364
+ // https://github.com/unicode-org/icu/blob/main/docs/userguide/format_parse/numbers/skeletons.md#precision
+ skeletonBuilder.append(" ."_s);
+ for (unsigned i = 0; i < intlInstance->m_minimumFractionDigits; ++i)
+ skeletonBuilder.append('0');
+ for (unsigned i = 0; i < intlInstance->m_maximumFractionDigits - intlInstance->m_minimumFractionDigits; ++i)
+ skeletonBuilder.append('#');
+ skeletonBuilder.append('/');
+ for (unsigned i = 0; i < intlInstance->m_minimumSignificantDigits; ++i)
+ skeletonBuilder.append('@');
+ for (unsigned i = 0; i < intlInstance->m_maximumSignificantDigits - intlInstance->m_minimumSignificantDigits; ++i)
+ skeletonBuilder.append('#');
+ skeletonBuilder.append(intlInstance->m_roundingType == IntlRoundingType::MorePrecision ? 'r' : 's');
+ }
break;
}
}
@@ -278,7 +294,10 @@ void appendNumberFormatDigitOptionsToSkeleton(IntlType* intlInstance, StringBuil
case IntlTrailingZeroDisplay::Auto:
break;
case IntlTrailingZeroDisplay::StripIfInteger:
- skeletonBuilder.append("/w"_s);
+ // Only ICU69~ supports trailing zero display. Ignore this option if linked ICU does not support it.
+ // https://github.com/unicode-org/icu/commit/b79c299f90d4023ac237db3d0335d568bf21cd36
+ if (WTF::ICU::majorVersion() >= 69)
+ skeletonBuilder.append("/w"_s);
break;
}
}
--
2.49.0
From cf69f3f40771d8724228c3af3a2c96dc04e38f85 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Fri, 28 Mar 2025 15:01:00 -0500
Subject: [PATCH 3/5] Revert "[JSC] TimeZone Cache should be per-process level"
This reverts commit c779aa30eced87609c7c808d672a8f23c5c4821d.
---
Source/JavaScriptCore/runtime/JSDateMath.cpp | 59 +++++++-------------
1 file changed, 20 insertions(+), 39 deletions(-)
diff --git a/Source/JavaScriptCore/runtime/JSDateMath.cpp b/Source/JavaScriptCore/runtime/JSDateMath.cpp
index c6a194568dab..756cbccb1668 100644
--- a/Source/JavaScriptCore/runtime/JSDateMath.cpp
+++ b/Source/JavaScriptCore/runtime/JSDateMath.cpp
@@ -453,12 +453,9 @@ String DateCache::timeZoneDisplayName(bool isDST)
return m_timeZoneStandardDisplayNameCache;
}
-static Lock timeZoneCacheLock;
-
#if PLATFORM(COCOA)
static void timeZoneChangeNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
{
- Locker locker { timeZoneCacheLock };
ASSERT(isMainThread());
++lastTimeZoneID;
}
@@ -475,40 +472,6 @@ DateCache::DateCache()
#endif
}
-static std::tuple<String, Vector<UChar, 32>> retrieveTimeZoneInformation()
-{
- Locker locker { timeZoneCacheLock };
- static NeverDestroyed<std::tuple<String, Vector<UChar, 32>, uint64_t>> globalCache;
-
- bool isCacheStale = true;
- uint64_t currentID = 0;
-#if PLATFORM(COCOA)
- currentID = lastTimeZoneID.load();
- isCacheStale = std::get<2>(globalCache.get()) != currentID;
-#endif
- if (isCacheStale) {
- Vector<UChar, 32> timeZoneID;
- getTimeZoneOverride(timeZoneID);
- String canonical;
- UErrorCode status = U_ZERO_ERROR;
- if (timeZoneID.isEmpty()) {
- status = callBufferProducingFunction(ucal_getHostTimeZone, timeZoneID);
- ASSERT_UNUSED(status, U_SUCCESS(status));
- }
- if (U_SUCCESS(status)) {
- Vector<UChar, 32> canonicalBuffer;
- auto status = callBufferProducingFunction(ucal_getCanonicalTimeZoneID, timeZoneID.data(), timeZoneID.size(), canonicalBuffer, nullptr);
- if (U_SUCCESS(status))
- canonical = String(canonicalBuffer);
- }
- if (canonical.isNull() || isUTCEquivalent(canonical))
- canonical = "UTC"_s;
-
- globalCache.get() = std::tuple { canonical.isolatedCopy(), WTFMove(timeZoneID), currentID };
- }
- return std::tuple { std::get<0>(globalCache.get()).isolatedCopy(), std::get<1>(globalCache.get()) };
-}
-
DateCache::~DateCache() = default;
Ref<DateInstanceData> DateCache::cachedDateInstanceData(double millisecondsFromEpoch)
@@ -519,10 +482,28 @@ Ref<DateInstanceData> DateCache::cachedDateInstanceData(double millisecondsFromE
void DateCache::timeZoneCacheSlow()
{
ASSERT(!m_timeZoneCache);
- auto [canonical, timeZoneID] = retrieveTimeZoneInformation();
+
+ Vector<UChar, 32> timeZoneID;
+ getTimeZoneOverride(timeZoneID);
auto* cache = new OpaqueICUTimeZone;
- cache->m_canonicalTimeZoneID = WTFMove(canonical);
+
+ String canonical;
UErrorCode status = U_ZERO_ERROR;
+ if (timeZoneID.isEmpty()) {
+ status = callBufferProducingFunction(ucal_getHostTimeZone, timeZoneID);
+ ASSERT_UNUSED(status, U_SUCCESS(status));
+ }
+ if (U_SUCCESS(status)) {
+ Vector<UChar, 32> canonicalBuffer;
+ auto status = callBufferProducingFunction(ucal_getCanonicalTimeZoneID, timeZoneID.data(), timeZoneID.size(), canonicalBuffer, nullptr);
+ if (U_SUCCESS(status))
+ canonical = String(canonicalBuffer);
+ }
+ if (canonical.isNull() || isUTCEquivalent(canonical))
+ canonical = "UTC"_s;
+ cache->m_canonicalTimeZoneID = WTFMove(canonical);
+
+ status = U_ZERO_ERROR;
cache->m_calendar = std::unique_ptr<UCalendar, ICUDeleter<ucal_close>>(ucal_open(timeZoneID.data(), timeZoneID.size(), "", UCAL_DEFAULT, &status));
ASSERT_UNUSED(status, U_SUCCESS(status));
ucal_setGregorianChange(cache->m_calendar.get(), minECMAScriptTime, &status); // Ignore "unsupported" error.
--
2.49.0
From 43c41bebf484bb3e08adf19cfadff01d820f6872 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Fri, 28 Mar 2025 15:01:11 -0500
Subject: [PATCH 4/5] Revert "[JSC] Rebaseline Intl implementation based on
lowest dependency ICU 70"
This reverts commit 31a358087be7e5e70c7a03bdfcf89de35628a2a2.
---
.../runtime/IntlDateTimeFormat.cpp | 27 ++++
.../runtime/IntlDateTimeFormat.h | 6 +
.../runtime/IntlDateTimeFormatPrototype.cpp | 6 +-
.../runtime/IntlDurationFormat.cpp | 27 ++++
.../runtime/IntlDurationFormat.h | 2 +
.../JavaScriptCore/runtime/IntlListFormat.cpp | 24 +++
.../JavaScriptCore/runtime/IntlListFormat.h | 6 +
.../runtime/IntlNumberFormat.cpp | 150 ++++++++++++++++++
.../JavaScriptCore/runtime/IntlNumberFormat.h | 35 ++++
.../runtime/IntlNumberFormatPrototype.cpp | 35 +++-
Source/JavaScriptCore/runtime/IntlObject.cpp | 9 +-
.../runtime/IntlPluralRules.cpp | 41 +++++
.../JavaScriptCore/runtime/IntlPluralRules.h | 12 +-
.../runtime/IntlPluralRulesPrototype.cpp | 8 +-
.../JavaScriptCore/runtime/IntlWorkaround.cpp | 12 +-
Source/JavaScriptCore/runtime/JSDateMath.cpp | 95 ++++++++++-
16 files changed, 479 insertions(+), 16 deletions(-)
diff --git a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp b/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp
index e3e7671fcf45..2f2371649667 100644
--- a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp
+++ b/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp
@@ -42,12 +42,16 @@
#include <wtf/unicode/CharacterNames.h>
#include <wtf/unicode/icu/ICUHelpers.h>
+#if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
#include <unicode/uformattedvalue.h>
#ifdef U_HIDE_DRAFT_API
#undef U_HIDE_DRAFT_API
#endif
+#endif // HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
#include <unicode/udateintervalformat.h>
+#if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
#define U_HIDE_DRAFT_API 1
+#endif // HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
@@ -1446,6 +1450,8 @@ UDateIntervalFormat* IntlDateTimeFormat::createDateIntervalFormatIfNecessary(JSG
return m_dateIntervalFormat.get();
}
+#if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
+
static std::unique_ptr<UFormattedDateInterval, ICUDeleter<udtitvfmt_closeResult>> formattedValueFromDateRange(UDateIntervalFormat& dateIntervalFormat, UDateFormat& dateFormat, double startDate, double endDate, UErrorCode& status)
{
auto result = std::unique_ptr<UFormattedDateInterval, ICUDeleter<udtitvfmt_closeResult>>(udtitvfmt_openResult(&status));
@@ -1524,6 +1530,8 @@ static bool dateFieldsPracticallyEqual(const UFormattedValue* formattedValue, UE
return !hasSpan;
}
+#endif // HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
+
JSValue IntlDateTimeFormat::formatRange(JSGlobalObject* globalObject, double startDate, double endDate)
{
ASSERT(m_dateFormat);
@@ -1542,6 +1550,7 @@ JSValue IntlDateTimeFormat::formatRange(JSGlobalObject* globalObject, double sta
auto* dateIntervalFormat = createDateIntervalFormatIfNecessary(globalObject);
RETURN_IF_EXCEPTION(scope, { });
+#if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
UErrorCode status = U_ZERO_ERROR;
auto result = formattedValueFromDateRange(*dateIntervalFormat, *m_dateFormat, startDate, endDate, status);
if (U_FAILURE(status)) {
@@ -1579,6 +1588,17 @@ JSValue IntlDateTimeFormat::formatRange(JSGlobalObject* globalObject, double sta
replaceNarrowNoBreakSpaceOrThinSpaceWithNormalSpace(buffer);
return jsString(vm, String(WTFMove(buffer)));
+#else
+ Vector<UChar, 32> buffer;
+ auto status = callBufferProducingFunction(udtitvfmt_format, dateIntervalFormat, startDate, endDate, buffer, nullptr);
+ if (U_FAILURE(status)) {
+ throwTypeError(globalObject, scope, "Failed to format date interval"_s);
+ return { };
+ }
+ replaceNarrowNoBreakSpaceOrThinSpaceWithNormalSpace(buffer);
+
+ return jsString(vm, String(WTFMove(buffer)));
+#endif
}
JSValue IntlDateTimeFormat::formatRangeToParts(JSGlobalObject* globalObject, double startDate, double endDate)
@@ -1588,6 +1608,7 @@ JSValue IntlDateTimeFormat::formatRangeToParts(JSGlobalObject* globalObject, dou
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
+#if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
// http://tc39.es/proposal-intl-DateTimeFormat-formatRange/#sec-partitiondatetimerangepattern
startDate = timeClip(startDate);
endDate = timeClip(endDate);
@@ -1791,6 +1812,12 @@ JSValue IntlDateTimeFormat::formatRangeToParts(JSGlobalObject* globalObject, dou
}
return parts;
+#else
+ UNUSED_PARAM(startDate);
+ UNUSED_PARAM(endDate);
+ throwTypeError(globalObject, scope, "Failed to format date interval"_s);
+ return { };
+#endif
}
diff --git a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.h b/Source/JavaScriptCore/runtime/IntlDateTimeFormat.h
index 121417aab9e1..14c476c72f69 100644
--- a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.h
+++ b/Source/JavaScriptCore/runtime/IntlDateTimeFormat.h
@@ -32,6 +32,12 @@
struct UDateIntervalFormat;
+#if !defined(HAVE_ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
+#if U_ICU_VERSION_MAJOR_NUM >= 64
+#define HAVE_ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS 1
+#endif
+#endif
+
namespace JSC {
enum class RelevantExtensionKey : uint8_t;
diff --git a/Source/JavaScriptCore/runtime/IntlDateTimeFormatPrototype.cpp b/Source/JavaScriptCore/runtime/IntlDateTimeFormatPrototype.cpp
index b7ef4bab865f..694ab7afea0c 100644
--- a/Source/JavaScriptCore/runtime/IntlDateTimeFormatPrototype.cpp
+++ b/Source/JavaScriptCore/runtime/IntlDateTimeFormatPrototype.cpp
@@ -56,7 +56,6 @@ const ClassInfo IntlDateTimeFormatPrototype::s_info = { "Intl.DateTimeFormat"_s,
@begin dateTimeFormatPrototypeTable
format intlDateTimeFormatPrototypeGetterFormat DontEnum|ReadOnly|CustomAccessor
formatRange intlDateTimeFormatPrototypeFuncFormatRange DontEnum|Function 2
- formatRangeToParts intlDateTimeFormatPrototypeFuncFormatRangeToParts DontEnum|Function 2
formatToParts intlDateTimeFormatPrototypeFuncFormatToParts DontEnum|Function 1
resolvedOptions intlDateTimeFormatPrototypeFuncResolvedOptions DontEnum|Function 0
@end
@@ -83,7 +82,12 @@ void IntlDateTimeFormatPrototype::finishCreation(VM& vm, JSGlobalObject* globalO
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
+#if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
+ JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("formatRangeToParts"_s, intlDateTimeFormatPrototypeFuncFormatRangeToParts, static_cast<unsigned>(PropertyAttribute::DontEnum), 2, ImplementationVisibility::Public);
+#else
UNUSED_PARAM(globalObject);
+ UNUSED_PARAM(&intlDateTimeFormatPrototypeFuncFormatRangeToParts);
+#endif
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
diff --git a/Source/JavaScriptCore/runtime/IntlDurationFormat.cpp b/Source/JavaScriptCore/runtime/IntlDurationFormat.cpp
index a1c628082dd5..78e9106ee0a4 100644
--- a/Source/JavaScriptCore/runtime/IntlDurationFormat.cpp
+++ b/Source/JavaScriptCore/runtime/IntlDurationFormat.cpp
@@ -36,14 +36,21 @@
// While UListFormatter APIs are draft in ICU 67, they are stable in ICU 68 with the same function signatures.
// So we can assume that these signatures of draft APIs are stable.
// If UListFormatter is available, UNumberFormatter is also available.
+#if HAVE(ICU_U_LIST_FORMATTER)
#ifdef U_HIDE_DRAFT_API
#undef U_HIDE_DRAFT_API
#endif
+#endif
#include <unicode/ulistformatter.h>
#include <unicode/unumberformatter.h>
#include <unicode/ures.h>
+#if HAVE(ICU_U_LIST_FORMATTER)
#define U_HIDE_DRAFT_API 1
+#endif
+
+#if HAVE(ICU_U_LIST_FORMATTER)
#include <unicode/uformattedvalue.h>
+#endif
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
@@ -240,6 +247,7 @@ void IntlDurationFormat::initializeDurationFormat(JSGlobalObject* globalObject,
m_fractionalDigits = intlNumberOption(globalObject, options, vm.propertyNames->fractionalDigits, 0, 9, fractionalDigitsUndefinedValue);
RETURN_IF_EXCEPTION(scope, void());
+#if HAVE(ICU_U_LIST_FORMATTER)
{
auto toUListFormatterWidth = [](Style style) {
// 6. Let listStyle be durationFormat.[[Style]].
@@ -266,8 +274,15 @@ void IntlDurationFormat::initializeDurationFormat(JSGlobalObject* globalObject,
return;
}
}
+#else
+ UNUSED_PARAM(IntlDurationFormatInternal::verbose);
+ throwTypeError(globalObject, scope, "Failed to initialize Intl.DurationFormat since this feature is not supported in the linked ICU version"_s);
+ return;
+#endif
}
+#if HAVE(ICU_U_LIST_FORMATTER)
+
static String retrieveSeparator(const CString& locale, const String& numberingSystem)
{
ASCIILiteral fallbackTimeSeparator = ":"_s;
@@ -635,12 +650,15 @@ static Vector<Element> collectElements(JSGlobalObject* globalObject, const IntlD
return elements;
}
+#endif
+
// https://tc39.es/proposal-intl-duration-format/#sec-Intl.DurationFormat.prototype.format
JSValue IntlDurationFormat::format(JSGlobalObject* globalObject, ISO8601::Duration duration) const
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
+#if HAVE(ICU_U_LIST_FORMATTER)
auto elements = collectElements(globalObject, this, WTFMove(duration));
RETURN_IF_EXCEPTION(scope, { });
@@ -676,6 +694,10 @@ JSValue IntlDurationFormat::format(JSGlobalObject* globalObject, ISO8601::Durati
return throwTypeError(globalObject, scope, "failed to format list of strings"_s);
return jsString(vm, String(WTFMove(result)));
+#else
+ UNUSED_PARAM(duration);
+ return throwTypeError(globalObject, scope, "failed to format list of strings"_s);
+#endif
}
// https://tc39.es/proposal-intl-duration-format/#sec-Intl.DurationFormat.prototype.formatToParts
@@ -684,6 +706,7 @@ JSValue IntlDurationFormat::formatToParts(JSGlobalObject* globalObject, ISO8601:
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
+#if HAVE(ICU_U_LIST_FORMATTER)
auto elements = collectElements(globalObject, this, WTFMove(duration));
RETURN_IF_EXCEPTION(scope, { });
@@ -836,6 +859,10 @@ JSValue IntlDurationFormat::formatToParts(JSGlobalObject* globalObject, ISO8601:
}
return parts;
+#else
+ UNUSED_PARAM(duration);
+ return throwTypeError(globalObject, scope, "failed to format list of strings"_s);
+#endif
}
// https://tc39.es/proposal-intl-duration-format/#sec-Intl.DurationFormat.prototype.resolvedOptions
diff --git a/Source/JavaScriptCore/runtime/IntlDurationFormat.h b/Source/JavaScriptCore/runtime/IntlDurationFormat.h
index 69e64f9c8332..b3f781a54ad4 100644
--- a/Source/JavaScriptCore/runtime/IntlDurationFormat.h
+++ b/Source/JavaScriptCore/runtime/IntlDurationFormat.h
@@ -101,7 +101,9 @@ private:
static ASCIILiteral unitStyleString(UnitStyle);
static ASCIILiteral displayString(Display);
+#if HAVE(ICU_U_LIST_FORMATTER)
std::unique_ptr<UListFormatter, UListFormatterDeleter> m_listFormat;
+#endif
String m_locale;
String m_numberingSystem;
CString m_dataLocaleWithExtensions;
diff --git a/Source/JavaScriptCore/runtime/IntlListFormat.cpp b/Source/JavaScriptCore/runtime/IntlListFormat.cpp
index 8fb342027af5..c25cc5f9e90f 100644
--- a/Source/JavaScriptCore/runtime/IntlListFormat.cpp
+++ b/Source/JavaScriptCore/runtime/IntlListFormat.cpp
@@ -33,12 +33,19 @@
// While UListFormatter APIs are draft in ICU 67, they are stable in ICU 68 with the same function signatures.
// So we can assume that these signatures of draft APIs are stable.
+#if HAVE(ICU_U_LIST_FORMATTER)
#ifdef U_HIDE_DRAFT_API
#undef U_HIDE_DRAFT_API
#endif
+#endif
#include <unicode/ulistformatter.h>
+#if HAVE(ICU_U_LIST_FORMATTER)
#define U_HIDE_DRAFT_API 1
+#endif
+
+#if HAVE(ICU_U_LIST_FORMATTER)
#include <unicode/uformattedvalue.h>
+#endif
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
@@ -107,6 +114,7 @@ void IntlListFormat::initializeListFormat(JSGlobalObject* globalObject, JSValue
m_style = intlOption<Style>(globalObject, options, vm.propertyNames->style, { { "long"_s, Style::Long }, { "short"_s, Style::Short }, { "narrow"_s, Style::Narrow } }, "style must be either \"long\", \"short\", or \"narrow\""_s, Style::Long);
RETURN_IF_EXCEPTION(scope, void());
+#if HAVE(ICU_U_LIST_FORMATTER)
auto toUListFormatterType = [](Type type) {
switch (type) {
case Type::Conjunction:
@@ -137,8 +145,13 @@ void IntlListFormat::initializeListFormat(JSGlobalObject* globalObject, JSValue
throwTypeError(globalObject, scope, "failed to initialize ListFormat"_s);
return;
}
+#else
+ throwTypeError(globalObject, scope, "Failed to initialize Intl.ListFormat since this feature is not supported in the linked ICU version"_s);
+ return;
+#endif
}
+#if HAVE(ICU_U_LIST_FORMATTER)
static Vector<String, 4> stringListFromIterable(JSGlobalObject* globalObject, JSValue iterable)
{
Vector<String, 4> result;
@@ -158,6 +171,7 @@ static Vector<String, 4> stringListFromIterable(JSGlobalObject* globalObject, JS
});
return result;
}
+#endif
// https://tc39.es/proposal-intl-list-format/#sec-Intl.ListFormat.prototype.format
JSValue IntlListFormat::format(JSGlobalObject* globalObject, JSValue list) const
@@ -165,6 +179,7 @@ JSValue IntlListFormat::format(JSGlobalObject* globalObject, JSValue list) const
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
+#if HAVE(ICU_U_LIST_FORMATTER)
auto stringList = stringListFromIterable(globalObject, list);
RETURN_IF_EXCEPTION(scope, { });
@@ -176,6 +191,10 @@ JSValue IntlListFormat::format(JSGlobalObject* globalObject, JSValue list) const
return throwTypeError(globalObject, scope, "failed to format list of strings"_s);
return jsString(vm, String(WTFMove(result)));
+#else
+ UNUSED_PARAM(list);
+ return throwTypeError(globalObject, scope, "failed to format list of strings"_s);
+#endif
}
// https://tc39.es/proposal-intl-list-format/#sec-Intl.ListFormat.prototype.formatToParts
@@ -184,6 +203,7 @@ JSValue IntlListFormat::formatToParts(JSGlobalObject* globalObject, JSValue list
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
+#if HAVE(ICU_U_LIST_FORMATTER)
auto stringList = stringListFromIterable(globalObject, list);
RETURN_IF_EXCEPTION(scope, { });
@@ -269,6 +289,10 @@ JSValue IntlListFormat::formatToParts(JSGlobalObject* globalObject, JSValue list
}
return parts;
+#else
+ UNUSED_PARAM(list);
+ return throwTypeError(globalObject, scope, "failed to format list of strings"_s);
+#endif
}
// https://tc39.es/proposal-intl-list-format/#sec-Intl.ListFormat.prototype.resolvedOptions
diff --git a/Source/JavaScriptCore/runtime/IntlListFormat.h b/Source/JavaScriptCore/runtime/IntlListFormat.h
index 2d071949e06f..af6f2c2e7b0a 100644
--- a/Source/JavaScriptCore/runtime/IntlListFormat.h
+++ b/Source/JavaScriptCore/runtime/IntlListFormat.h
@@ -28,6 +28,12 @@
#include "JSObject.h"
#include <wtf/unicode/icu/ICUHelpers.h>
+#if !defined(HAVE_ICU_U_LIST_FORMATTER)
+#if U_ICU_VERSION_MAJOR_NUM >= 67 || (U_ICU_VERSION_MAJOR_NUM >= 66 && USE(APPLE_INTERNAL_SDK))
+#define HAVE_ICU_U_LIST_FORMATTER 1
+#endif
+#endif
+
struct UListFormatter;
namespace JSC {
diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp b/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp
index 0938d2d5b4a5..1f0aa041c201 100644
--- a/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp
+++ b/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp
@@ -43,8 +43,12 @@
#ifdef U_HIDE_DRAFT_API
#undef U_HIDE_DRAFT_API
#endif
+#if HAVE(ICU_U_NUMBER_FORMATTER)
#include <unicode/unumberformatter.h>
+#endif
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
#include <unicode/unumberrangeformatter.h>
+#endif
#define U_HIDE_DRAFT_API 1
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
@@ -57,17 +61,21 @@ namespace IntlNumberFormatInternal {
static constexpr bool verbose = false;
}
+#if HAVE(ICU_U_NUMBER_FORMATTER)
void UNumberFormatterDeleter::operator()(UNumberFormatter* formatter)
{
if (formatter)
unumf_close(formatter);
}
+#endif
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
void UNumberRangeFormatterDeleter::operator()(UNumberRangeFormatter* formatter)
{
if (formatter)
unumrf_close(formatter);
}
+#endif
IntlNumberFormat* IntlNumberFormat::create(VM& vm, Structure* structure)
{
@@ -226,10 +234,12 @@ static std::optional<WellFormedUnit> wellFormedUnitIdentifier(StringView unitIde
return WellFormedUnit(numeratorUnit.value(), denominatorUnit.value());
}
+#if HAVE(ICU_U_NUMBER_FORMATTER)
// We intentionally avoid using ICU's UNUM_APPROXIMATELY_SIGN_FIELD and define the same value here.
// UNUM_APPROXIMATELY_SIGN_FIELD can be defined in the header after ICU 71. But dylib ICU can be newer while ICU header version is old.
// We can define UNUM_APPROXIMATELY_SIGN_FIELD here so that we can support old ICU header + newer ICU library combination.
static constexpr UNumberFormatFields UNUM_APPROXIMATELY_SIGN_FIELD = static_cast<UNumberFormatFields>(UNUM_COMPACT_FIELD + 1);
+#endif
static ASCIILiteral partTypeString(UNumberFormatFields field, IntlNumberFormat::Style style, bool sign, IntlMathematicalValue::NumberType type)
{
@@ -265,6 +275,7 @@ static ASCIILiteral partTypeString(UNumberFormatFields field, IntlNumberFormat::
return (style == IntlNumberFormat::Style::Unit) ? "unit"_s : "percentSign"_s;
case UNUM_SIGN_FIELD:
return sign ? "minusSign"_s : "plusSign"_s;
+#if HAVE(ICU_U_NUMBER_FORMATTER)
case UNUM_MEASURE_UNIT_FIELD:
return "unit"_s;
case UNUM_COMPACT_FIELD:
@@ -273,6 +284,7 @@ IGNORE_GCC_WARNINGS_BEGIN("switch")
case UNUM_APPROXIMATELY_SIGN_FIELD:
return "approximatelySign"_s;
IGNORE_GCC_WARNINGS_END
+#endif
// These should not show up because there is no way to specify them in NumberFormat options.
// If they do, they don't fit well into any of known part types, so consider it an "unknown".
case UNUM_PERMILL_FIELD:
@@ -399,6 +411,7 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa
// Options are obtained. Configure formatter here.
+#if HAVE(ICU_U_NUMBER_FORMATTER)
// Constructing ICU Number Skeletons to configure UNumberFormatter.
// https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md
@@ -557,11 +570,120 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa
return;
}
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
m_numberRangeFormatter = std::unique_ptr<UNumberRangeFormatter, UNumberRangeFormatterDeleter>(unumrf_openForSkeletonWithCollapseAndIdentityFallback(upconverted.get(), skeletonView.length(), UNUM_RANGE_COLLAPSE_AUTO, UNUM_IDENTITY_FALLBACK_APPROXIMATELY, dataLocaleWithExtensions.data(), nullptr, &status));
if (U_FAILURE(status)) {
throwTypeError(globalObject, scope, "failed to initialize NumberFormat"_s);
return;
}
+#endif
+#else
+ UNumberFormatStyle style = UNUM_DEFAULT;
+ switch (m_style) {
+ case Style::Decimal:
+ style = UNUM_DECIMAL;
+ break;
+ case Style::Percent:
+ style = UNUM_PERCENT;
+ break;
+ case Style::Currency:
+ switch (m_currencyDisplay) {
+ case CurrencyDisplay::Code:
+ style = UNUM_CURRENCY_ISO;
+ break;
+ case CurrencyDisplay::Symbol:
+ style = UNUM_CURRENCY;
+ break;
+ case CurrencyDisplay::NarrowSymbol:
+ style = UNUM_CURRENCY; // Use the same option to "symbol" since linked-ICU does not support it.
+ break;
+ case CurrencyDisplay::Name:
+ style = UNUM_CURRENCY_PLURAL;
+ break;
+ }
+ switch (m_currencySign) {
+ case CurrencySign::Standard:
+ break;
+ case CurrencySign::Accounting:
+ // Ignore this case since linked ICU does not support it.
+ break;
+ }
+ break;
+ case Style::Unit:
+ // Ignore this case since linked ICU does not support it.
+ break;
+ }
+
+ switch (m_notation) {
+ case IntlNotation::Standard:
+ break;
+ case IntlNotation::Scientific:
+ case IntlNotation::Engineering:
+ case IntlNotation::Compact:
+ // Ignore this case since linked ICU does not support it.
+ break;
+ }
+
+ switch (m_signDisplay) {
+ case SignDisplay::Auto:
+ break;
+ case SignDisplay::Never:
+ case SignDisplay::Always:
+ case SignDisplay::ExceptZero:
+ case SignDisplay::Negative:
+ // Ignore this case since linked ICU does not support it.
+ break;
+ }
+
+ UErrorCode status = U_ZERO_ERROR;
+ m_numberFormat = std::unique_ptr<UNumberFormat, ICUDeleter<unum_close>>(unum_open(style, nullptr, 0, dataLocaleWithExtensions.data(), nullptr, &status));
+ if (U_FAILURE(status)) {
+ throwTypeError(globalObject, scope, "failed to initialize NumberFormat"_s);
+ return;
+ }
+
+ if (m_style == Style::Currency) {
+ unum_setTextAttribute(m_numberFormat.get(), UNUM_CURRENCY_CODE, StringView(m_currency).upconvertedCharacters(), m_currency.length(), &status);
+ if (U_FAILURE(status)) {
+ throwTypeError(globalObject, scope, "failed to initialize NumberFormat"_s);
+ return;
+ }
+ }
+
+ switch (m_roundingType) {
+ case IntlRoundingType::FractionDigits:
+ unum_setAttribute(m_numberFormat.get(), UNUM_MIN_INTEGER_DIGITS, m_minimumIntegerDigits);
+ unum_setAttribute(m_numberFormat.get(), UNUM_MIN_FRACTION_DIGITS, m_minimumFractionDigits);
+ unum_setAttribute(m_numberFormat.get(), UNUM_MAX_FRACTION_DIGITS, m_maximumFractionDigits);
+ break;
+ case IntlRoundingType::SignificantDigits:
+ unum_setAttribute(m_numberFormat.get(), UNUM_SIGNIFICANT_DIGITS_USED, true);
+ unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits);
+ unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits);
+ break;
+ case IntlRoundingType::MorePrecision:
+ // Ignore this case since linked ICU does not support it.
+ break;
+ case IntlRoundingType::LessPrecision:
+ // Ignore this case since linked ICU does not support it.
+ break;
+ }
+
+ switch (m_useGrouping) {
+ case UseGrouping::False:
+ unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, false);
+ break;
+ case UseGrouping::Min2:
+ // Ignore this case since linked ICU does not support it.
+ break;
+ case UseGrouping::Auto:
+ break;
+ case UseGrouping::Always:
+ unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, true);
+ break;
+ }
+ unum_setAttribute(m_numberFormat.get(), UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP);
+#endif
}
// https://tc39.es/ecma402/#sec-formatnumber
@@ -573,6 +695,7 @@ JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, double value) con
value = purifyNaN(value);
Vector<UChar, 32> buffer;
+#if HAVE(ICU_U_NUMBER_FORMATTER)
ASSERT(m_numberFormatter);
UErrorCode status = U_ZERO_ERROR;
auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status));
@@ -584,6 +707,12 @@ JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, double value) con
status = callBufferProducingFunction(unumf_resultToString, formattedNumber.get(), buffer);
if (U_FAILURE(status))
return throwTypeError(globalObject, scope, "Failed to format a number."_s);
+#else
+ ASSERT(m_numberFormat);
+ auto status = callBufferProducingFunction(unum_formatDouble, m_numberFormat.get(), value, buffer, nullptr);
+ if (U_FAILURE(status))
+ return throwTypeError(globalObject, scope, "Failed to format a number."_s);
+#endif
return jsString(vm, String(WTFMove(buffer)));
}
@@ -597,6 +726,7 @@ JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, IntlMathematicalV
const auto& string = value.getString();
Vector<UChar, 32> buffer;
+#if HAVE(ICU_U_NUMBER_FORMATTER)
ASSERT(m_numberFormatter);
UErrorCode status = U_ZERO_ERROR;
auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status));
@@ -608,9 +738,16 @@ JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, IntlMathematicalV
status = callBufferProducingFunction(unumf_resultToString, formattedNumber.get(), buffer);
if (U_FAILURE(status))
return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s);
+#else
+ ASSERT(m_numberFormat);
+ auto status = callBufferProducingFunction(unum_formatDecimal, m_numberFormat.get(), string.data(), string.length(), buffer, nullptr);
+ if (U_FAILURE(status))
+ return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s);
+#endif
return jsString(vm, String(WTFMove(buffer)));
}
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
JSValue IntlNumberFormat::formatRange(JSGlobalObject* globalObject, double start, double end) const
{
VM& vm = globalObject->vm();
@@ -678,6 +815,7 @@ JSValue IntlNumberFormat::formatRange(JSGlobalObject* globalObject, IntlMathemat
return jsString(vm, String({ string, static_cast<size_t>(length) }));
}
+#endif
static constexpr int32_t literalField = -1;
struct IntlNumberFormatField {
@@ -790,6 +928,7 @@ static Vector<IntlNumberFormatField> flattenFields(Vector<IntlNumberFormatField>
return flatten;
}
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS)
static bool numberFieldsPracticallyEqual(const UFormattedValue* formattedValue, UErrorCode& status)
{
auto iterator = std::unique_ptr<UConstrainedFieldPosition, ICUDeleter<ucfpos_close>>(ucfpos_open(&status));
@@ -1031,6 +1170,7 @@ JSValue IntlNumberFormat::formatRangeToParts(JSGlobalObject* globalObject, IntlM
return parts;
}
+#endif
ASCIILiteral IntlNumberFormat::styleString(Style style)
{
@@ -1328,6 +1468,7 @@ JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, double val
return throwTypeError(globalObject, scope, "failed to open field position iterator"_s);
Vector<UChar, 32> result;
+#if HAVE(ICU_U_NUMBER_FORMATTER)
ASSERT(m_numberFormatter);
auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status));
if (U_FAILURE(status))
@@ -1342,6 +1483,13 @@ JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, double val
if (U_FAILURE(status))
return throwTypeError(globalObject, scope, "Failed to format a number."_s);
IntlFieldIterator iterator(*fieldItr.get());
+#else
+ ASSERT(m_numberFormat);
+ status = callBufferProducingFunction(unum_formatDoubleForFields, m_numberFormat.get(), value, result, fieldItr.get());
+ if (U_FAILURE(status))
+ return throwTypeError(globalObject, scope, "failed to format a number."_s);
+ IntlFieldIterator iterator(*fieldItr.get());
+#endif
auto resultString = String(WTFMove(result));
@@ -1355,6 +1503,7 @@ JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, double val
return parts;
}
+#if HAVE(ICU_U_NUMBER_FORMATTER)
JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, IntlMathematicalValue&& value, JSString* sourceType) const
{
VM& vm = globalObject->vm();
@@ -1399,6 +1548,7 @@ JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, IntlMathem
return parts;
}
+#endif
IntlMathematicalValue IntlMathematicalValue::parseString(JSGlobalObject* globalObject, StringView view)
{
diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormat.h b/Source/JavaScriptCore/runtime/IntlNumberFormat.h
index 1bd75559a1d6..4928e5cc4d02 100644
--- a/Source/JavaScriptCore/runtime/IntlNumberFormat.h
+++ b/Source/JavaScriptCore/runtime/IntlNumberFormat.h
@@ -34,6 +34,25 @@
#include <wtf/TZoneMalloc.h>
#include <wtf/unicode/icu/ICUHelpers.h>
+#if !defined(HAVE_ICU_U_NUMBER_FORMATTER)
+// UNUM_COMPACT_FIELD and UNUM_MEASURE_UNIT_FIELD are available after ICU 64.
+#if U_ICU_VERSION_MAJOR_NUM >= 64
+#define HAVE_ICU_U_NUMBER_FORMATTER 1
+#endif
+#endif
+
+#if !defined(HAVE_ICU_U_NUMBER_RANGE_FORMATTER)
+#if U_ICU_VERSION_MAJOR_NUM >= 68
+#define HAVE_ICU_U_NUMBER_RANGE_FORMATTER 1
+#endif
+#endif
+
+#if !defined(HAVE_ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS)
+#if U_ICU_VERSION_MAJOR_NUM >= 69
+#define HAVE_ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS 1
+#endif
+#endif
+
struct UFormattedValue;
struct UNumberFormatter;
struct UNumberRangeFormatter;
@@ -51,13 +70,17 @@ enum class IntlNotation : uint8_t { Standard, Scientific, Engineering, Compact }
template<typename IntlType> void setNumberFormatDigitOptions(JSGlobalObject*, IntlType*, JSObject*, unsigned minimumFractionDigitsDefault, unsigned maximumFractionDigitsDefault, IntlNotation);
template<typename IntlType> void appendNumberFormatDigitOptionsToSkeleton(IntlType*, StringBuilder&);
+#if HAVE(ICU_U_NUMBER_FORMATTER)
struct UNumberFormatterDeleter {
JS_EXPORT_PRIVATE void operator()(UNumberFormatter*);
};
+#endif
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
struct UNumberRangeFormatterDeleter {
JS_EXPORT_PRIVATE void operator()(UNumberRangeFormatter*);
};
+#endif
class IntlMathematicalValue {
WTF_MAKE_TZONE_ALLOCATED(IntlMathematicalValue);
@@ -159,14 +182,20 @@ public:
JSValue format(JSGlobalObject*, double) const;
JSValue format(JSGlobalObject*, IntlMathematicalValue&&) const;
JSValue formatToParts(JSGlobalObject*, double, JSString* sourceType = nullptr) const;
+#if HAVE(ICU_U_NUMBER_FORMATTER)
JSValue formatToParts(JSGlobalObject*, IntlMathematicalValue&&, JSString* sourceType = nullptr) const;
+#endif
JSObject* resolvedOptions(JSGlobalObject*) const;
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
JSValue formatRange(JSGlobalObject*, double, double) const;
JSValue formatRange(JSGlobalObject*, IntlMathematicalValue&&, IntlMathematicalValue&&) const;
+#endif
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS)
JSValue formatRangeToParts(JSGlobalObject*, double, double) const;
JSValue formatRangeToParts(JSGlobalObject*, IntlMathematicalValue&&, IntlMathematicalValue&&) const;
+#endif
JSBoundFunction* boundFormat() const { return m_boundFormat.get(); }
void setBoundFormat(VM&, JSBoundFunction*);
@@ -212,8 +241,14 @@ private:
static JSValue useGroupingValue(VM&, UseGrouping);
WriteBarrier<JSBoundFunction> m_boundFormat;
+#if HAVE(ICU_U_NUMBER_FORMATTER)
std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter> m_numberFormatter;
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
std::unique_ptr<UNumberRangeFormatter, UNumberRangeFormatterDeleter> m_numberRangeFormatter;
+#endif
+#else
+ std::unique_ptr<UNumberFormat, ICUDeleter<unum_close>> m_numberFormat;
+#endif
String m_locale;
String m_numberingSystem;
diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp b/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp
index 6459273641c6..026b4269652e 100644
--- a/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp
+++ b/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp
@@ -35,12 +35,18 @@
namespace JSC {
static JSC_DECLARE_CUSTOM_GETTER(intlNumberFormatPrototypeGetterFormat);
-static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRange);
-static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRangeToParts);
static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatToParts);
static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncResolvedOptions);
static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatFuncFormat);
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
+static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRange);
+#endif
+
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS)
+static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRangeToParts);
+#endif
+
}
#include "IntlNumberFormatPrototype.lut.h"
@@ -51,11 +57,9 @@ const ClassInfo IntlNumberFormatPrototype::s_info = { "Intl.NumberFormat"_s, &Ba
/* Source for IntlNumberFormatPrototype.lut.h
@begin numberFormatPrototypeTable
- format intlNumberFormatPrototypeGetterFormat DontEnum|ReadOnly|CustomAccessor
- formatRange intlNumberFormatPrototypeFuncFormatRange DontEnum|Function 2
- formatRangeToParts intlNumberFormatPrototypeFuncFormatRangeToParts DontEnum|Function 2
- formatToParts intlNumberFormatPrototypeFuncFormatToParts DontEnum|Function 1
- resolvedOptions intlNumberFormatPrototypeFuncResolvedOptions DontEnum|Function 0
+ format intlNumberFormatPrototypeGetterFormat DontEnum|ReadOnly|CustomAccessor
+ formatToParts intlNumberFormatPrototypeFuncFormatToParts DontEnum|Function 1
+ resolvedOptions intlNumberFormatPrototypeFuncResolvedOptions DontEnum|Function 0
@end
*/
@@ -82,6 +86,12 @@ void IntlNumberFormatPrototype::finishCreation(VM& vm, JSGlobalObject* globalObj
ASSERT(inherits(info()));
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
UNUSED_PARAM(globalObject);
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
+ JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("formatRange"_s, intlNumberFormatPrototypeFuncFormatRange, static_cast<unsigned>(PropertyAttribute::DontEnum), 2, ImplementationVisibility::Public);
+#endif
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS)
+ JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("formatRangeToParts"_s, intlNumberFormatPrototypeFuncFormatRangeToParts, static_cast<unsigned>(PropertyAttribute::DontEnum), 2, ImplementationVisibility::Public);
+#endif
}
// https://tc39.es/ecma402/#sec-number-format-functions
@@ -134,6 +144,7 @@ JSC_DEFINE_CUSTOM_GETTER(intlNumberFormatPrototypeGetterFormat, (JSGlobalObject*
return JSValue::encode(boundFormat);
}
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRange, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
@@ -163,6 +174,7 @@ JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRange, (JSGlobalObje
RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatRange(globalObject, WTFMove(start), WTFMove(end))));
}
+#endif
JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatToParts, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
@@ -177,6 +189,7 @@ JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatToParts, (JSGlobalOb
if (UNLIKELY(!numberFormat))
return JSValue::encode(throwTypeError(globalObject, scope, "Intl.NumberFormat.prototype.formatToParts called on value that's not a NumberFormat"_s));
+#if HAVE(ICU_U_NUMBER_FORMATTER)
auto value = toIntlMathematicalValue(globalObject, callFrame->argument(0));
RETURN_IF_EXCEPTION(scope, { });
@@ -184,8 +197,15 @@ JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatToParts, (JSGlobalOb
RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatToParts(globalObject, number.value())));
RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatToParts(globalObject, WTFMove(value))));
+#else
+ double value = callFrame->argument(0).toNumber(globalObject);
+ RETURN_IF_EXCEPTION(scope, { });
+
+ RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatToParts(globalObject, value)));
+#endif
}
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS)
JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRangeToParts, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
@@ -215,6 +235,7 @@ JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRangeToParts, (JSGlo
RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatRangeToParts(globalObject, WTFMove(start), WTFMove(end))));
}
+#endif
JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncResolvedOptions, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
diff --git a/Source/JavaScriptCore/runtime/IntlObject.cpp b/Source/JavaScriptCore/runtime/IntlObject.cpp
index dab995d4c294..51dbe4447db3 100644
--- a/Source/JavaScriptCore/runtime/IntlObject.cpp
+++ b/Source/JavaScriptCore/runtime/IntlObject.cpp
@@ -169,8 +169,6 @@ namespace JSC {
Collator createCollatorConstructor DontEnum|PropertyCallback
DateTimeFormat createDateTimeFormatConstructor DontEnum|PropertyCallback
DisplayNames createDisplayNamesConstructor DontEnum|PropertyCallback
- DurationFormat createDurationFormatConstructor DontEnum|PropertyCallback
- ListFormat createListFormatConstructor DontEnum|PropertyCallback
Locale createLocaleConstructor DontEnum|PropertyCallback
NumberFormat createNumberFormatConstructor DontEnum|PropertyCallback
PluralRules createPluralRulesConstructor DontEnum|PropertyCallback
@@ -258,6 +256,13 @@ void IntlObject::finishCreation(VM& vm, JSGlobalObject*)
Base::finishCreation(vm);
ASSERT(inherits(info()));
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+#if HAVE(ICU_U_LIST_FORMATTER)
+ putDirectWithoutTransition(vm, vm.propertyNames->DurationFormat, createDurationFormatConstructor(vm, this), static_cast<unsigned>(PropertyAttribute::DontEnum));
+ putDirectWithoutTransition(vm, vm.propertyNames->ListFormat, createListFormatConstructor(vm, this), static_cast<unsigned>(PropertyAttribute::DontEnum));
+#else
+ UNUSED_PARAM(&createDurationFormatConstructor);
+ UNUSED_PARAM(&createListFormatConstructor);
+#endif
}
Structure* IntlObject::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
diff --git a/Source/JavaScriptCore/runtime/IntlPluralRules.cpp b/Source/JavaScriptCore/runtime/IntlPluralRules.cpp
index 584c6ff2c435..af58373c78e8 100644
--- a/Source/JavaScriptCore/runtime/IntlPluralRules.cpp
+++ b/Source/JavaScriptCore/runtime/IntlPluralRules.cpp
@@ -36,8 +36,12 @@
#undef U_HIDE_DRAFT_API
#endif
#include <unicode/upluralrules.h>
+#if HAVE(ICU_U_NUMBER_FORMATTER)
#include <unicode/unumberformatter.h>
+#endif
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
#include <unicode/unumberrangeformatter.h>
+#endif
#define U_HIDE_DRAFT_API 1
namespace JSC {
@@ -118,6 +122,7 @@ void IntlPluralRules::initializePluralRules(JSGlobalObject* globalObject, JSValu
auto locale = m_locale.utf8();
UErrorCode status = U_ZERO_ERROR;
+#if HAVE(ICU_U_NUMBER_FORMATTER)
StringBuilder skeletonBuilder;
appendNumberFormatDigitOptionsToSkeleton(this, skeletonBuilder);
@@ -132,11 +137,36 @@ void IntlPluralRules::initializePluralRules(JSGlobalObject* globalObject, JSValu
return;
}
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
m_numberRangeFormatter = std::unique_ptr<UNumberRangeFormatter, UNumberRangeFormatterDeleter>(unumrf_openForSkeletonWithCollapseAndIdentityFallback(upconverted.get(), skeletonView.length(), UNUM_RANGE_COLLAPSE_NONE, UNUM_IDENTITY_FALLBACK_RANGE, locale.data(), nullptr, &status));
if (U_FAILURE(status)) {
throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s);
return;
}
+#endif
+#else
+ m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(UNUM_DECIMAL, nullptr, 0, locale.data(), nullptr, &status));
+ if (U_FAILURE(status)) {
+ throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s);
+ return;
+ }
+
+ switch (m_roundingType) {
+ case IntlRoundingType::FractionDigits:
+ unum_setAttribute(m_numberFormat.get(), UNUM_MIN_INTEGER_DIGITS, m_minimumIntegerDigits);
+ unum_setAttribute(m_numberFormat.get(), UNUM_MIN_FRACTION_DIGITS, m_minimumFractionDigits);
+ unum_setAttribute(m_numberFormat.get(), UNUM_MAX_FRACTION_DIGITS, m_maximumFractionDigits);
+ break;
+ case IntlRoundingType::SignificantDigits:
+ unum_setAttribute(m_numberFormat.get(), UNUM_SIGNIFICANT_DIGITS_USED, true);
+ unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits);
+ unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits);
+ break;
+ case IntlRoundingType::MorePrecision:
+ case IntlRoundingType::LessPrecision:
+ break;
+ }
+#endif
m_pluralRules = std::unique_ptr<UPluralRules, UPluralRulesDeleter>(uplrules_openForType(locale.data(), m_type == Type::Ordinal ? UPLURAL_TYPE_ORDINAL : UPLURAL_TYPE_CARDINAL, &status));
if (U_FAILURE(status)) {
@@ -230,6 +260,7 @@ JSValue IntlPluralRules::select(JSGlobalObject* globalObject, double value) cons
UErrorCode status = U_ZERO_ERROR;
+#if HAVE(ICU_U_NUMBER_FORMATTER)
auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status));
if (U_FAILURE(status))
return throwTypeError(globalObject, scope, "failed to select plural value"_s);
@@ -241,8 +272,17 @@ JSValue IntlPluralRules::select(JSGlobalObject* globalObject, double value) cons
if (U_FAILURE(status))
return throwTypeError(globalObject, scope, "failed to select plural value"_s);
return jsString(vm, String(WTFMove(buffer)));
+#else
+ Vector<UChar, 8> result(8);
+ auto length = uplrules_selectWithFormat(m_pluralRules.get(), value, m_numberFormat.get(), result.data(), result.size(), &status);
+ if (U_FAILURE(status))
+ return throwTypeError(globalObject, scope, "failed to select plural value"_s);
+
+ return jsString(vm, String({ result.data(), static_cast<size_t>(length) }));
+#endif
}
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
JSValue IntlPluralRules::selectRange(JSGlobalObject* globalObject, double start, double end) const
{
ASSERT(m_numberRangeFormatter);
@@ -268,5 +308,6 @@ JSValue IntlPluralRules::selectRange(JSGlobalObject* globalObject, double start,
return throwTypeError(globalObject, scope, "failed to select plural value"_s);
return jsString(vm, String(WTFMove(buffer)));
}
+#endif
} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/IntlPluralRules.h b/Source/JavaScriptCore/runtime/IntlPluralRules.h
index 26b7d72b16aa..7f476e03ca03 100644
--- a/Source/JavaScriptCore/runtime/IntlPluralRules.h
+++ b/Source/JavaScriptCore/runtime/IntlPluralRules.h
@@ -69,9 +69,12 @@ public:
void initializePluralRules(JSGlobalObject*, JSValue locales, JSValue options);
JSValue select(JSGlobalObject*, double value) const;
- JSValue selectRange(JSGlobalObject*, double start, double end) const;
JSObject* resolvedOptions(JSGlobalObject*) const;
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
+ JSValue selectRange(JSGlobalObject*, double start, double end) const;
+#endif
+
private:
IntlPluralRules(VM&, Structure*);
DECLARE_DEFAULT_FINISH_CREATION;
@@ -82,8 +85,15 @@ private:
enum class Type : bool { Cardinal, Ordinal };
std::unique_ptr<UPluralRules, UPluralRulesDeleter> m_pluralRules;
+#if HAVE(ICU_U_NUMBER_FORMATTER)
std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter> m_numberFormatter;
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
std::unique_ptr<UNumberRangeFormatter, UNumberRangeFormatterDeleter> m_numberRangeFormatter;
+#endif
+#else
+ using UNumberFormatDeleter = ICUDeleter<unum_close>;
+ std::unique_ptr<UNumberFormat, UNumberFormatDeleter> m_numberFormat;
+#endif
String m_locale;
unsigned m_minimumIntegerDigits { 1 };
diff --git a/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp b/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp
index aae2cabe2aa7..6c908d3a7d53 100644
--- a/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp
+++ b/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp
@@ -33,7 +33,9 @@
namespace JSC {
static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelect);
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelectRange);
+#endif
static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncResolvedOptions);
}
@@ -47,7 +49,6 @@ const ClassInfo IntlPluralRulesPrototype::s_info = { "Intl.PluralRules"_s, &Base
/* Source for IntlPluralRulesPrototype.lut.h
@begin pluralRulesPrototypeTable
select intlPluralRulesPrototypeFuncSelect DontEnum|Function 1
- selectRange intlPluralRulesPrototypeFuncSelectRange DontEnum|Function 2
resolvedOptions intlPluralRulesPrototypeFuncResolvedOptions DontEnum|Function 0
@end
*/
@@ -75,6 +76,9 @@ void IntlPluralRulesPrototype::finishCreation(VM& vm, JSGlobalObject* globalObje
ASSERT(inherits(info()));
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
UNUSED_PARAM(globalObject);
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
+ JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->selectRange, intlPluralRulesPrototypeFuncSelectRange, static_cast<unsigned>(PropertyAttribute::DontEnum), 2, ImplementationVisibility::Public);
+#endif
}
JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelect, (JSGlobalObject* globalObject, CallFrame* callFrame))
@@ -95,6 +99,7 @@ JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelect, (JSGlobalObject* gl
RELEASE_AND_RETURN(scope, JSValue::encode(pluralRules->select(globalObject, value)));
}
+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelectRange, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
@@ -119,6 +124,7 @@ JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelectRange, (JSGlobalObjec
RELEASE_AND_RETURN(scope, JSValue::encode(pluralRules->selectRange(globalObject, start, end)));
}
+#endif
JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncResolvedOptions, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
diff --git a/Source/JavaScriptCore/runtime/IntlWorkaround.cpp b/Source/JavaScriptCore/runtime/IntlWorkaround.cpp
index c6c1fa0cd228..85d7e85469a7 100644
--- a/Source/JavaScriptCore/runtime/IntlWorkaround.cpp
+++ b/Source/JavaScriptCore/runtime/IntlWorkaround.cpp
@@ -26,17 +26,25 @@
#include "config.h"
#include "IntlWorkaround.h"
-#ifdef U_HIDE_DRAFT_API
+// ICU 69 introduces draft API ubrk_clone and deprecates ubrk_safeClone.
+#if defined(U_HIDE_DRAFT_API)
#undef U_HIDE_DRAFT_API
#endif
#include <unicode/ubrk.h>
-#define U_HIDE_DRAFT_API 1
+
+#if U_ICU_VERSION_MAJOR_NUM >= 69
+#define HAVE_ICU_UBRK_CLONE 1
+#endif
namespace JSC {
UBreakIterator* cloneUBreakIterator(const UBreakIterator* iterator, UErrorCode* status)
{
+#if HAVE(ICU_UBRK_CLONE)
return ubrk_clone(iterator, status);
+#else
+ return ubrk_safeClone(iterator, nullptr, nullptr, status);
+#endif
}
} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/JSDateMath.cpp b/Source/JavaScriptCore/runtime/JSDateMath.cpp
index 756cbccb1668..30ff52a13c68 100644
--- a/Source/JavaScriptCore/runtime/JSDateMath.cpp
+++ b/Source/JavaScriptCore/runtime/JSDateMath.cpp
@@ -74,6 +74,7 @@
#include "ExceptionHelpers.h"
#include "VM.h"
+#include <bit>
#include <limits>
#include <wtf/DateMath.h>
#include <wtf/Language.h>
@@ -81,11 +82,24 @@
#include <wtf/unicode/CharacterNames.h>
#include <wtf/unicode/icu/ICUHelpers.h>
+#if U_ICU_VERSION_MAJOR_NUM >= 69 || (U_ICU_VERSION_MAJOR_NUM == 68 && USE(APPLE_INTERNAL_SDK))
+#define HAVE_ICU_C_TIMEZONE_API 1
#ifdef U_HIDE_DRAFT_API
#undef U_HIDE_DRAFT_API
#endif
#include <unicode/ucal.h>
-#define U_HIDE_DRAFT_API 1
+#else
+// icu::TimeZone and icu::BasicTimeZone features are only available in ICU C++ APIs.
+// We use these C++ APIs as an exception.
+#undef U_SHOW_CPLUSPLUS_API
+#define U_SHOW_CPLUSPLUS_API 1
+#include <unicode/basictz.h>
+#include <unicode/locid.h>
+#include <unicode/timezone.h>
+#include <unicode/unistr.h>
+#undef U_SHOW_CPLUSPLUS_API
+#define U_SHOW_CPLUSPLUS_API 0
+#endif
namespace JSC {
namespace JSDateMathInternal {
@@ -96,6 +110,7 @@ static constexpr bool verbose = false;
std::atomic<uint64_t> lastTimeZoneID { 1 };
#endif
+#if HAVE(ICU_C_TIMEZONE_API)
class OpaqueICUTimeZone {
WTF_MAKE_TZONE_ALLOCATED(OpaqueICUTimeZone);
public:
@@ -105,10 +120,26 @@ public:
WTF_MAKE_TZONE_ALLOCATED_IMPL(OpaqueICUTimeZone);
+#else
+static icu::TimeZone* toICUTimeZone(OpaqueICUTimeZone* timeZone)
+{
+ return std::bit_cast<icu::TimeZone*>(timeZone);
+}
+static OpaqueICUTimeZone* toOpaqueICUTimeZone(icu::TimeZone* timeZone)
+{
+ return std::bit_cast<OpaqueICUTimeZone*>(timeZone);
+}
+#endif
+
void OpaqueICUTimeZoneDeleter::operator()(OpaqueICUTimeZone* timeZone)
{
- if (timeZone)
+ if (timeZone) {
+#if HAVE(ICU_C_TIMEZONE_API)
delete timeZone;
+#else
+ delete toICUTimeZone(timeZone);
+#endif
+ }
}
// Get the combined UTC + DST offset for the time passed in.
@@ -126,6 +157,7 @@ LocalTimeOffset DateCache::calculateLocalTimeOffset(double millisecondsFromEpoch
// We can return any values in this case since later we fail when computing non timezone offset part anyway.
constexpr LocalTimeOffset failed { false, 0 };
+#if HAVE(ICU_C_TIMEZONE_API)
auto& timeZoneCache = *this->timeZoneCache();
ucal_setMillis(timeZoneCache.m_calendar.get(), millisecondsFromEpoch, &status);
if (U_FAILURE(status))
@@ -143,6 +175,21 @@ LocalTimeOffset DateCache::calculateLocalTimeOffset(double millisecondsFromEpoch
if (U_FAILURE(status))
return failed;
}
+#else
+ auto& timeZoneCache = *toICUTimeZone(this->timeZoneCache());
+ if (inputTimeType != WTF::LocalTime) {
+ constexpr bool isLocalTime = false;
+ timeZoneCache.getOffset(millisecondsFromEpoch, isLocalTime, rawOffset, dstOffset, status);
+ if (U_FAILURE(status))
+ return failed;
+ } else {
+ // icu::TimeZone is a timezone instance which inherits icu::BasicTimeZone.
+ // https://unicode-org.atlassian.net/browse/ICU-13705 will move getOffsetFromLocal to icu::TimeZone.
+ static_cast<const icu::BasicTimeZone&>(timeZoneCache).getOffsetFromLocal(millisecondsFromEpoch, icu::BasicTimeZone::kFormer, icu::BasicTimeZone::kFormer, rawOffset, dstOffset, status);
+ if (U_FAILURE(status))
+ return failed;
+ }
+#endif
return { !!dstOffset, rawOffset + dstOffset };
}
@@ -427,12 +474,32 @@ double DateCache::parseDate(JSGlobalObject* globalObject, VM& vm, const String&
// https://tc39.es/ecma402/#sec-defaulttimezone
String DateCache::defaultTimeZone()
{
+#if HAVE(ICU_C_TIMEZONE_API)
return timeZoneCache()->m_canonicalTimeZoneID;
+#else
+ icu::UnicodeString timeZoneID;
+ icu::UnicodeString canonicalTimeZoneID;
+ auto& timeZone = *toICUTimeZone(timeZoneCache());
+ timeZone.getID(timeZoneID);
+
+ UErrorCode status = U_ZERO_ERROR;
+ UBool isSystem = false;
+ icu::TimeZone::getCanonicalID(timeZoneID, canonicalTimeZoneID, isSystem, status);
+ if (U_FAILURE(status))
+ return "UTC"_s;
+
+ String canonical = String({ canonicalTimeZoneID.getBuffer(), static_cast<size_t>(canonicalTimeZoneID.length()) });
+ if (isUTCEquivalent(canonical))
+ return "UTC"_s;
+
+ return canonical;
+#endif
}
String DateCache::timeZoneDisplayName(bool isDST)
{
if (m_timeZoneStandardDisplayNameCache.isNull()) {
+#if HAVE(ICU_C_TIMEZONE_API)
auto& timeZoneCache = *this->timeZoneCache();
CString language = defaultLanguage().utf8();
{
@@ -447,6 +514,21 @@ String DateCache::timeZoneDisplayName(bool isDST)
if (U_SUCCESS(status))
m_timeZoneDSTDisplayNameCache = String::adopt(WTFMove(dstDisplayNameBuffer));
}
+#else
+ auto& timeZoneCache = *toICUTimeZone(this->timeZoneCache());
+ String language = defaultLanguage();
+ icu::Locale locale(language.utf8().data());
+ {
+ icu::UnicodeString standardDisplayName;
+ timeZoneCache.getDisplayName(false /* inDaylight */, icu::TimeZone::LONG, locale, standardDisplayName);
+ m_timeZoneStandardDisplayNameCache = String({ standardDisplayName.getBuffer(), static_cast<size_t>(standardDisplayName.length()) });
+ }
+ {
+ icu::UnicodeString dstDisplayName;
+ timeZoneCache.getDisplayName(true /* inDaylight */, icu::TimeZone::LONG, locale, dstDisplayName);
+ m_timeZoneDSTDisplayNameCache = String({ dstDisplayName.getBuffer(), static_cast<size_t>(dstDisplayName.length()) });
+ }
+#endif
}
if (isDST)
return m_timeZoneDSTDisplayNameCache;
@@ -485,6 +567,7 @@ void DateCache::timeZoneCacheSlow()
Vector<UChar, 32> timeZoneID;
getTimeZoneOverride(timeZoneID);
+#if HAVE(ICU_C_TIMEZONE_API)
auto* cache = new OpaqueICUTimeZone;
String canonical;
@@ -508,6 +591,14 @@ void DateCache::timeZoneCacheSlow()
ASSERT_UNUSED(status, U_SUCCESS(status));
ucal_setGregorianChange(cache->m_calendar.get(), minECMAScriptTime, &status); // Ignore "unsupported" error.
m_timeZoneCache = std::unique_ptr<OpaqueICUTimeZone, OpaqueICUTimeZoneDeleter>(cache);
+#else
+ if (!timeZoneID.isEmpty()) {
+ m_timeZoneCache = std::unique_ptr<OpaqueICUTimeZone, OpaqueICUTimeZoneDeleter>(toOpaqueICUTimeZone(icu::TimeZone::createTimeZone(icu::UnicodeString(timeZoneID.data(), timeZoneID.size()))));
+ return;
+ }
+ // Do not use icu::TimeZone::createDefault. ICU internally has a cache for timezone and createDefault returns this cached value.
+ m_timeZoneCache = std::unique_ptr<OpaqueICUTimeZone, OpaqueICUTimeZoneDeleter>(toOpaqueICUTimeZone(icu::TimeZone::detectHostTimeZone()));
+#endif
}
void DateCache::resetIfNecessarySlow()
--
2.49.0
From 4553b849dd0f3fd2bfd834cda53abc2f2256d2f9 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Wed, 2 Apr 2025 15:56:18 -0500
Subject: [PATCH 5/5] Build against ICU 60
---
Source/JavaScriptCore/runtime/IntlCache.cpp | 3 +++
Source/JavaScriptCore/runtime/IntlCache.h | 3 +++
Source/JavaScriptCore/runtime/IntlDisplayNames.cpp | 11 +++++++++++
Source/JavaScriptCore/runtime/IntlDisplayNames.h | 7 +++++++
Source/JavaScriptCore/runtime/IntlDurationFormat.cpp | 2 +-
Source/JavaScriptCore/runtime/IntlObject.cpp | 6 +++++-
Source/WTF/wtf/URLHelpers.cpp | 4 +++-
Source/WTF/wtf/URLParser.cpp | 6 +++---
Source/WTF/wtf/unicode/UTF8Conversion.cpp | 4 +++-
Source/WebCore/dom/TextEncoder.cpp | 3 ++-
Source/cmake/OptionsGTK.cmake | 2 +-
11 files changed, 42 insertions(+), 9 deletions(-)
diff --git a/Source/JavaScriptCore/runtime/IntlCache.cpp b/Source/JavaScriptCore/runtime/IntlCache.cpp
index 0941a7278e2a..75134587adbb 100644
--- a/Source/JavaScriptCore/runtime/IntlCache.cpp
+++ b/Source/JavaScriptCore/runtime/IntlCache.cpp
@@ -26,6 +26,7 @@
#include "config.h"
#include "IntlCache.h"
+#include "IntlDisplayNames.h"
#include <wtf/TZoneMallocInlines.h>
#include <wtf/Vector.h>
@@ -56,6 +57,7 @@ Vector<UChar, 32> IntlCache::getBestDateTimePattern(const CString& locale, std::
return patternBuffer;
}
+#if HAVE(ICU_U_LOCALE_DISPLAY_NAMES)
Vector<UChar, 32> IntlCache::getFieldDisplayName(const CString& locale, UDateTimePatternField field, UDateTimePGDisplayWidth width, UErrorCode& status)
{
auto sharedGenerator = getSharedPatternGenerator(locale, status);
@@ -67,5 +69,6 @@ Vector<UChar, 32> IntlCache::getFieldDisplayName(const CString& locale, UDateTim
return { };
return buffer;
}
+#endif
} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/IntlCache.h b/Source/JavaScriptCore/runtime/IntlCache.h
index 4c818fd59424..2c7e464a6955 100644
--- a/Source/JavaScriptCore/runtime/IntlCache.h
+++ b/Source/JavaScriptCore/runtime/IntlCache.h
@@ -25,6 +25,7 @@
#pragma once
+#include "IntlDisplayNames.h"
#include <unicode/udatpg.h>
#include <wtf/Noncopyable.h>
#include <wtf/TZoneMalloc.h>
@@ -40,7 +41,9 @@ public:
IntlCache() = default;
Vector<UChar, 32> getBestDateTimePattern(const CString& locale, std::span<const UChar> skeleton, UErrorCode&);
+#if HAVE(ICU_U_LOCALE_DISPLAY_NAMES)
Vector<UChar, 32> getFieldDisplayName(const CString& locale, UDateTimePatternField, UDateTimePGDisplayWidth, UErrorCode&);
+#endif
private:
UDateTimePatternGenerator* getSharedPatternGenerator(const CString& locale, UErrorCode& status)
diff --git a/Source/JavaScriptCore/runtime/IntlDisplayNames.cpp b/Source/JavaScriptCore/runtime/IntlDisplayNames.cpp
index 972ab5f84300..971f9321ca20 100644
--- a/Source/JavaScriptCore/runtime/IntlDisplayNames.cpp
+++ b/Source/JavaScriptCore/runtime/IntlDisplayNames.cpp
@@ -106,6 +106,7 @@ void IntlDisplayNames::initializeDisplayNames(JSGlobalObject* globalObject, JSVa
m_languageDisplay = intlOption<LanguageDisplay>(globalObject, options, vm.propertyNames->languageDisplay, { { "dialect"_s, LanguageDisplay::Dialect }, { "standard"_s, LanguageDisplay::Standard } }, "languageDisplay must be either \"dialect\" or \"standard\""_s, LanguageDisplay::Dialect);
RETURN_IF_EXCEPTION(scope, void());
+#if HAVE(ICU_U_LOCALE_DISPLAY_NAMES)
UErrorCode status = U_ZERO_ERROR;
UDisplayContext contexts[] = {
@@ -133,6 +134,10 @@ void IntlDisplayNames::initializeDisplayNames(JSGlobalObject* globalObject, JSVa
throwTypeError(globalObject, scope, "failed to initialize DisplayNames"_s);
return;
}
+#else
+ throwTypeError(globalObject, scope, "failed to initialize Intl.DisplayNames since feature is not supported by the ICU version"_s);
+ return;
+#endif
}
// https://tc39.es/proposal-intl-displaynames/#sec-Intl.DisplayNames.prototype.of
@@ -142,6 +147,7 @@ JSValue IntlDisplayNames::of(JSGlobalObject* globalObject, JSValue codeValue) co
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
+#if HAVE(ICU_U_LOCALE_DISPLAY_NAMES)
ASSERT(m_displayNames);
auto code = codeValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, { });
@@ -346,6 +352,11 @@ JSValue IntlDisplayNames::of(JSGlobalObject* globalObject, JSValue codeValue) co
return throwTypeError(globalObject, scope, "Failed to query a display name."_s);
}
return jsString(vm, String(WTFMove(buffer)));
+#else
+ UNUSED_PARAM(codeValue);
+ throwTypeError(globalObject, scope, "failed to initialize Intl.DisplayNames since feature is not supported by the ICU version"_s);
+ return { };
+#endif
}
// https://tc39.es/proposal-intl-displaynames/#sec-Intl.DisplayNames.prototype.resolvedOptions
diff --git a/Source/JavaScriptCore/runtime/IntlDisplayNames.h b/Source/JavaScriptCore/runtime/IntlDisplayNames.h
index 17f257cb04a5..883825ed93f4 100644
--- a/Source/JavaScriptCore/runtime/IntlDisplayNames.h
+++ b/Source/JavaScriptCore/runtime/IntlDisplayNames.h
@@ -29,6 +29,13 @@
#include <unicode/uldnames.h>
#include <wtf/unicode/icu/ICUHelpers.h>
+#if !defined(HAVE_ICU_U_LOCALE_DISPLAY_NAMES)
+// We need 61 or later since part of implementation uses UCURR_NARROW_SYMBOL_NAME.
+#if U_ICU_VERSION_MAJOR_NUM >= 61
+#define HAVE_ICU_U_LOCALE_DISPLAY_NAMES 1
+#endif
+#endif
+
namespace JSC {
enum class RelevantExtensionKey : uint8_t;
diff --git a/Source/JavaScriptCore/runtime/IntlDurationFormat.cpp b/Source/JavaScriptCore/runtime/IntlDurationFormat.cpp
index 78e9106ee0a4..0a4017d65243 100644
--- a/Source/JavaScriptCore/runtime/IntlDurationFormat.cpp
+++ b/Source/JavaScriptCore/runtime/IntlDurationFormat.cpp
@@ -42,7 +42,6 @@
#endif
#endif
#include <unicode/ulistformatter.h>
-#include <unicode/unumberformatter.h>
#include <unicode/ures.h>
#if HAVE(ICU_U_LIST_FORMATTER)
#define U_HIDE_DRAFT_API 1
@@ -50,6 +49,7 @@
#if HAVE(ICU_U_LIST_FORMATTER)
#include <unicode/uformattedvalue.h>
+#include <unicode/unumberformatter.h>
#endif
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
diff --git a/Source/JavaScriptCore/runtime/IntlObject.cpp b/Source/JavaScriptCore/runtime/IntlObject.cpp
index 51dbe4447db3..5c3f10f07427 100644
--- a/Source/JavaScriptCore/runtime/IntlObject.cpp
+++ b/Source/JavaScriptCore/runtime/IntlObject.cpp
@@ -168,7 +168,6 @@ namespace JSC {
supportedValuesOf intlObjectFuncSupportedValuesOf DontEnum|Function 1
Collator createCollatorConstructor DontEnum|PropertyCallback
DateTimeFormat createDateTimeFormatConstructor DontEnum|PropertyCallback
- DisplayNames createDisplayNamesConstructor DontEnum|PropertyCallback
Locale createLocaleConstructor DontEnum|PropertyCallback
NumberFormat createNumberFormatConstructor DontEnum|PropertyCallback
PluralRules createPluralRulesConstructor DontEnum|PropertyCallback
@@ -256,6 +255,11 @@ void IntlObject::finishCreation(VM& vm, JSGlobalObject*)
Base::finishCreation(vm);
ASSERT(inherits(info()));
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+#if HAVE(ICU_U_LOCALE_DISPLAY_NAMES)
+ putDirectWithoutTransition(vm, vm.propertyNames->DisplayNames, createDisplayNamesConstructor(vm, this), static_cast<unsigned>(PropertyAttribute::DontEnum));
+#else
+ UNUSED_PARAM(&createDisplayNamesConstructor);
+#endif
#if HAVE(ICU_U_LIST_FORMATTER)
putDirectWithoutTransition(vm, vm.propertyNames->DurationFormat, createDurationFormatConstructor(vm, this), static_cast<unsigned>(PropertyAttribute::DontEnum));
putDirectWithoutTransition(vm, vm.propertyNames->ListFormat, createListFormatConstructor(vm, this), static_cast<unsigned>(PropertyAttribute::DontEnum));
diff --git a/Source/WTF/wtf/URLHelpers.cpp b/Source/WTF/wtf/URLHelpers.cpp
index 955d1925917b..742f06a6868b 100644
--- a/Source/WTF/wtf/URLHelpers.cpp
+++ b/Source/WTF/wtf/URLHelpers.cpp
@@ -865,7 +865,9 @@ static String escapeUnsafeCharacters(const String& sourceBuffer)
std::array<uint8_t, 4> utf8Buffer;
size_t offset = 0;
UBool failure = false;
- U8_APPEND(utf8Buffer, offset, 4, c, failure);
+ int32_t narrowedOffset = offset;
+ U8_APPEND(utf8Buffer.data(), narrowedOffset, 4, c, failure);
+ offset = narrowedOffset;
ASSERT_UNUSED(failure, !failure);
for (size_t j = 0; j < offset; ++j) {
diff --git a/Source/WTF/wtf/URLParser.cpp b/Source/WTF/wtf/URLParser.cpp
index b1a6c8421807..8ec338d63b36 100644
--- a/Source/WTF/wtf/URLParser.cpp
+++ b/Source/WTF/wtf/URLParser.cpp
@@ -516,7 +516,7 @@ ALWAYS_INLINE void URLParser::utf8PercentEncode(const CodePointIterator<Characte
std::array<uint8_t, U8_MAX_LENGTH> buffer;
int32_t offset = 0;
UBool isError = false;
- U8_APPEND(buffer, offset, U8_MAX_LENGTH, codePoint, isError);
+ U8_APPEND(buffer.data(), offset, U8_MAX_LENGTH, codePoint, isError);
if (isError) {
appendToASCIIBuffer(replacementCharacterUTF8PercentEncoded.span8());
return;
@@ -544,7 +544,7 @@ ALWAYS_INLINE void URLParser::utf8QueryEncode(const CodePointIterator<CharacterT
std::array<uint8_t, U8_MAX_LENGTH> buffer;
int32_t offset = 0;
UBool isError = false;
- U8_APPEND(buffer, offset, U8_MAX_LENGTH, codePoint, isError);
+ U8_APPEND(buffer.data(), offset, U8_MAX_LENGTH, codePoint, isError);
if (isError) {
appendToASCIIBuffer(replacementCharacterUTF8PercentEncoded.span8());
return;
@@ -2851,7 +2851,7 @@ auto URLParser::parseHostAndPort(CodePointIterator<CharacterType> iterator) -> H
std::array<uint8_t, U8_MAX_LENGTH> buffer;
size_t offset = 0;
UBool isError = false;
- U8_APPEND(buffer, offset, U8_MAX_LENGTH, *iterator, isError);
+ U8_APPEND(buffer.data(), offset, U8_MAX_LENGTH, *iterator, isError);
if (isError)
return HostParsingResult::InvalidHost;
utf8Encoded.append(std::span { buffer }.first(offset));
diff --git a/Source/WTF/wtf/unicode/UTF8Conversion.cpp b/Source/WTF/wtf/unicode/UTF8Conversion.cpp
index b9e499b625b4..c89fd18ea548 100644
--- a/Source/WTF/wtf/unicode/UTF8Conversion.cpp
+++ b/Source/WTF/wtf/unicode/UTF8Conversion.cpp
@@ -78,7 +78,9 @@ template<> char32_t next<Replacement::ReplaceInvalidSequences, char16_t>(std::sp
template<> bool append<Replacement::None, char8_t>(std::span<char8_t> characters, size_t& offset, char32_t character)
{
UBool sawError = false;
- U8_APPEND(characters, offset, characters.size(), character, sawError);
+ int32_t narrowedOffset = offset;
+ U8_APPEND((uint8_t*)characters.data(), narrowedOffset, static_cast<int32_t>(characters.size()), character, sawError);
+ offset = narrowedOffset;
return sawError;
}
diff --git a/Source/WebCore/dom/TextEncoder.cpp b/Source/WebCore/dom/TextEncoder.cpp
index fe8694cfce9d..ea496e7a3d34 100644
--- a/Source/WebCore/dom/TextEncoder.cpp
+++ b/Source/WebCore/dom/TextEncoder.cpp
@@ -55,7 +55,7 @@ auto TextEncoder::encodeInto(String&& input, Ref<Uint8Array>&& array) -> EncodeI
break;
}
UBool sawError = false;
- U8_APPEND(destinationBytes, written, destinationBytes.size(), token, sawError);
+ U8_APPEND(destinationBytes.data(), written, destinationBytes.size(), token, sawError);
if (sawError)
break;
if (U_IS_BMP(token))
@@ -68,3 +68,4 @@ auto TextEncoder::encodeInto(String&& input, Ref<Uint8Array>&& array) -> EncodeI
}
}
+
diff --git a/Source/cmake/OptionsGTK.cmake b/Source/cmake/OptionsGTK.cmake
index 814009146300..d36261da758e 100644
--- a/Source/cmake/OptionsGTK.cmake
+++ b/Source/cmake/OptionsGTK.cmake
@@ -11,7 +11,7 @@ find_package(Cairo 1.14.0 REQUIRED)
find_package(LibGcrypt 1.7.0 REQUIRED)
find_package(Libtasn1 REQUIRED)
find_package(HarfBuzz 1.4.2 REQUIRED COMPONENTS ICU)
-find_package(ICU 61.2 REQUIRED COMPONENTS data i18n uc)
+find_package(ICU 60 REQUIRED COMPONENTS data i18n uc)
find_package(JPEG REQUIRED)
find_package(Epoxy 1.5.4 REQUIRED)
find_package(LibXml2 2.8.0 REQUIRED)
--
2.49.0