From 88ceab8d948f3c67f22438c1cca2ffdfd21fbc2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Thu, 29 Jan 2026 07:42:53 +0000 Subject: [PATCH 001/116] Bug 2010219 - Part 1: Move Intl.Segmenter initialisation to C++. r=dminor Moves initialisation, `resolvedOptions`, and `segment` to C++. Part 2 removes the self-hosted code. Differential Revision: https://phabricator.services.mozilla.com/D279032 --- js/src/builtin/intl/Segmenter.cpp | 272 +++++++++++++++++++++++------- js/src/builtin/intl/Segmenter.h | 42 ++--- js/src/builtin/intl/Segmenter.js | 3 - js/src/vm/SelfHosting.cpp | 1 - 4 files changed, 233 insertions(+), 85 deletions(-) diff --git a/js/src/builtin/intl/Segmenter.cpp b/js/src/builtin/intl/Segmenter.cpp index 7c6e9f1a92734..4bc6158559509 100644 --- a/js/src/builtin/intl/Segmenter.cpp +++ b/js/src/builtin/intl/Segmenter.cpp @@ -17,7 +17,9 @@ #include "builtin/Array.h" #include "builtin/intl/CommonFunctions.h" #include "builtin/intl/LocaleNegotiation.h" +#include "builtin/intl/ParameterNegotiation.h" #include "builtin/intl/StringAsciiChars.h" +#include "builtin/intl/UsingEnum.h" #include "gc/AllocKind.h" #include "gc/GCContext.h" #include "icu4x/GraphemeClusterSegmenter.hpp" @@ -71,6 +73,10 @@ const JSClass& SegmenterObject::protoClass_ = PlainObject::class_; static bool segmenter_supportedLocalesOf(JSContext* cx, unsigned argc, Value* vp); +static bool segmenter_segment(JSContext* cx, unsigned argc, Value* vp); + +static bool segmenter_resolvedOptions(JSContext* cx, unsigned argc, Value* vp); + static bool segmenter_toSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setString(cx->names().Segmenter); @@ -83,9 +89,8 @@ static const JSFunctionSpec segmenter_static_methods[] = { }; static const JSFunctionSpec segmenter_methods[] = { - JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Segmenter_resolvedOptions", 0, - 0), - JS_SELF_HOSTED_FN("segment", "Intl_Segmenter_segment", 1, 0), + JS_FN("resolvedOptions", segmenter_resolvedOptions, 0, 0), + JS_FN("segment", segmenter_segment, 1, 0), JS_FN("toSource", segmenter_toSource, 0, 0), JS_FS_END, }; @@ -108,6 +113,24 @@ const ClassSpec SegmenterObject::classSpec_ = { ClassSpec::DontDefineConstructor, }; +static constexpr std::string_view GranularityToString( + SegmenterGranularity granularity) { +#ifndef USING_ENUM + using enum SegmenterGranularity; +#else + USING_ENUM(SegmenterGranularity, Grapheme, Word, Sentence); +#endif + switch (granularity) { + case Grapheme: + return "grapheme"; + case Word: + return "word"; + case Sentence: + return "sentence"; + } + MOZ_CRASH("invalid segmenter granularity"); +} + /** * Intl.Segmenter ([ locales [ , options ]]) */ @@ -132,16 +155,69 @@ static bool Segmenter(JSContext* cx, unsigned argc, Value* vp) { return false; } - HandleValue locales = args.get(0); - HandleValue options = args.get(1); + // Step 4. (Inlined ResolveOptions) + + // ResolveOptions, step 1. + Rooted requestedLocales(cx, cx); + if (!CanonicalizeLocaleList(cx, args.get(0), &requestedLocales)) { + return false; + } - // Steps 4-13. - if (!intl::InitializeObject(cx, segmenter, cx->names().InitializeSegmenter, - locales, options)) { + Rooted requestedLocalesArray( + cx, LocalesListToArray(cx, requestedLocales)); + if (!requestedLocalesArray) { return false; } + segmenter->setRequestedLocales(requestedLocalesArray); + + auto granularity = SegmenterGranularity::Grapheme; + if (args.hasDefined(1)) { + // ResolveOptions, steps 2-3. + Rooted options( + cx, RequireObjectArg(cx, "options", "Intl.Segmenter", args[1])); + if (!options) { + return false; + } + + // ResolveOptions, step 4. + LocaleMatcher matcher; + if (!GetLocaleMatcherOption(cx, options, &matcher)) { + return false; + } + + // ResolveOptions, step 5. + // + // This implementation only supports the "lookup" locale matcher, therefore + // the "localeMatcher" option doesn't need to be stored. + + // ResolveOptions, step 6. + // + // Intl.Segmenter doesn't support any Unicode extension keys. + + // ResolveOptions, step 7. (Not applicable) + + // ResolveOptions, step 8. (Performed in ResolveLocale) + + // ResolveOptions, step 9. (Return) + + // Step 5. (Not applicable when ResolveOptions is inlined.) + + // Steps 6-7. (Performed in ResolveLocale) + + // Step 8. + static constexpr auto granularities = MapOptions( + SegmenterGranularity::Grapheme, SegmenterGranularity::Word, + SegmenterGranularity::Sentence); + if (!GetStringOption(cx, options, cx->names().granularity, granularities, + SegmenterGranularity::Grapheme, &granularity)) { + return false; + } + } - // Step 14. + // Step 9. + segmenter->setGranularity(granularity); + + // Step 10. args.rval().setObject(*segmenter); return true; } @@ -516,46 +592,36 @@ static typename Interface::Segmenter* CreateSegmenter( return result.ok; } -static bool EnsureInternalsResolved(JSContext* cx, - Handle segmenter) { - if (segmenter->getLocale()) { +/** + * Resolve the actual locale to finish initialization of the Segmenter. + */ +static bool ResolveLocale(JSContext* cx, Handle segmenter) { + // Return if the locale was already resolved. + if (segmenter->isLocaleResolved()) { return true; } - Rooted value(cx); + Rooted requestedLocales( + cx, &segmenter->getRequestedLocales()->as()); - Rooted internals(cx, intl::GetInternalsObject(cx, segmenter)); - if (!internals) { - return false; - } + // %Intl.Segmenter%.[[RelevantExtensionKeys]] is « ». + mozilla::EnumSet relevantExtensionKeys{}; - if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) { - return false; - } - Rooted locale(cx, value.toString()); - - if (!GetProperty(cx, internals, internals, cx->names().granularity, &value)) { - return false; - } + // Initialize locale options from constructor arguments. + Rooted localeOptions(cx); - SegmenterGranularity granularity; - { - JSLinearString* linear = value.toString()->ensureLinear(cx); - if (!linear) { - return false; - } + // Use the default locale data. + auto localeData = LocaleData::Default; - if (StringEqualsLiteral(linear, "grapheme")) { - granularity = SegmenterGranularity::Grapheme; - } else if (StringEqualsLiteral(linear, "word")) { - granularity = SegmenterGranularity::Word; - } else { - MOZ_ASSERT(StringEqualsLiteral(linear, "sentence")); - granularity = SegmenterGranularity::Sentence; - } + // Resolve the actual locale. + Rooted resolved(cx); + if (!ResolveLocale(cx, AvailableLocaleKind::ListFormat, requestedLocales, + localeOptions, relevantExtensionKeys, localeData, + &resolved)) { + return false; } - switch (granularity) { + switch (segmenter->getGranularity()) { case SegmenterGranularity::Grapheme: { auto* seg = CreateSegmenter(); if (!seg) { @@ -565,7 +631,7 @@ static bool EnsureInternalsResolved(JSContext* cx, break; } case SegmenterGranularity::Word: { - auto* seg = CreateSegmenter(cx, locale); + auto* seg = CreateSegmenter(cx, resolved.dataLocale()); if (!seg) { return false; } @@ -573,7 +639,7 @@ static bool EnsureInternalsResolved(JSContext* cx, break; } case SegmenterGranularity::Sentence: { - auto* seg = CreateSegmenter(cx, locale); + auto* seg = CreateSegmenter(cx, resolved.dataLocale()); if (!seg) { return false; } @@ -582,9 +648,10 @@ static bool EnsureInternalsResolved(JSContext* cx, } } - segmenter->setLocale(locale); - segmenter->setGranularity(granularity); + // Finish initialization by setting the actual locale. + segmenter->setLocale(resolved.dataLocale()); + MOZ_ASSERT(segmenter->isLocaleResolved(), "locale successfully resolved"); return true; } @@ -824,6 +891,14 @@ static bool EnsureBreakIterator(JSContext* cx, Handle segments, segments->setIndex(0); } + // Ensure the locale is resolved. + if (!segments->getSegmenter()->isLocaleResolved()) { + Rooted segmenter(cx, segments->getSegmenter()); + if (!ResolveLocale(cx, segmenter)) { + return false; + } + } + // Ensure the string characters can be passed to ICU4X. if (!EnsureStringChars(cx, segments)) { return false; @@ -925,28 +1000,21 @@ static ArrayObject* FindSegmentBoundaries(JSContext* cx, Handle segments, return CreateBoundaries(cx, boundaries, segments->getGranularity()); } -bool js::intl_CreateSegmentsObject(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 2); - - Rooted segmenter(cx, - &args[0].toObject().as()); - Rooted string(cx, args[1].toString()); - - // Ensure the internal properties are resolved. - if (!EnsureInternalsResolved(cx, segmenter)) { - return false; - } - +/** + * Create a new Segments object. + */ +static SegmentsObject* CreateSegmentsObject(JSContext* cx, + Handle segmenter, + Handle string) { Rooted proto( cx, GlobalObject::getOrCreateSegmentsPrototype(cx, cx->global())); if (!proto) { - return false; + return nullptr; } auto* segments = NewObjectWithGivenProto(cx, proto); if (!segments) { - return false; + return nullptr; } segments->setSegmenter(segmenter); @@ -954,8 +1022,7 @@ bool js::intl_CreateSegmentsObject(JSContext* cx, unsigned argc, Value* vp) { segments->setString(string); segments->setIndex(0); - args.rval().setObject(*segments); - return true; + return segments; } bool js::intl_CreateSegmentIterator(JSContext* cx, unsigned argc, Value* vp) { @@ -1028,6 +1095,89 @@ bool js::intl_FindNextSegmentBoundaries(JSContext* cx, unsigned argc, return true; } +static bool IsSegmenter(Handle v) { + return v.isObject() && v.toObject().is(); +} + +/** + * Intl.Segmenter.prototype.segment ( string ) + */ +static bool segmenter_segment(JSContext* cx, const CallArgs& args) { + Rooted segmenter( + cx, &args.thisv().toObject().as()); + + // Step 3. + Rooted string(cx, JS::ToString(cx, args.get(0))); + if (!string) { + return false; + } + + // Step 4. + auto* result = CreateSegmentsObject(cx, segmenter, string); + if (!result) { + return false; + } + args.rval().setObject(*result); + return true; +} + +/** + * Intl.Segmenter.prototype.segment ( string ) + */ +static bool segmenter_segment(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/** + * Intl.Segmenter.prototype.resolvedOptions ( ) + */ +static bool segmenter_resolvedOptions(JSContext* cx, const CallArgs& args) { + Rooted segmenter( + cx, &args.thisv().toObject().as()); + + if (!ResolveLocale(cx, segmenter)) { + return false; + } + + // Step 3. + Rooted options(cx, cx); + + // Step 4. + if (!options.emplaceBack(NameToId(cx->names().locale), + StringValue(segmenter->getLocale()))) { + return false; + } + + auto* granularity = NewStringCopy( + cx, GranularityToString(segmenter->getGranularity())); + if (!granularity) { + return false; + } + if (!options.emplaceBack(NameToId(cx->names().granularity), + StringValue(granularity))) { + return false; + } + + // Step 5. + auto* result = NewPlainObjectWithUniqueNames(cx, options); + if (!result) { + return false; + } + args.rval().setObject(*result); + return true; +} + +/** + * Intl.Segmenter.prototype.resolvedOptions ( ) + */ +static bool segmenter_resolvedOptions(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + /** * Intl.Segmenter.supportedLocalesOf ( locales [ , options ] ) */ diff --git a/js/src/builtin/intl/Segmenter.h b/js/src/builtin/intl/Segmenter.h index 2b75b86488257..b7ff42d6e26de 100644 --- a/js/src/builtin/intl/Segmenter.h +++ b/js/src/builtin/intl/Segmenter.h @@ -14,6 +14,7 @@ #include "js/Class.h" #include "js/Value.h" #include "vm/NativeObject.h" +#include "vm/StringType.h" struct JS_PUBLIC_API JSContext; class JSString; @@ -31,26 +32,35 @@ class SegmenterObject : public NativeObject { static const JSClass class_; static const JSClass& protoClass_; - static constexpr uint32_t INTERNALS_SLOT = 0; - static constexpr uint32_t LOCALE_SLOT = 1; - static constexpr uint32_t GRANULARITY_SLOT = 2; - static constexpr uint32_t SEGMENTER_SLOT = 3; - static constexpr uint32_t SLOT_COUNT = 4; + static constexpr uint32_t LOCALE_SLOT = 0; + static constexpr uint32_t GRANULARITY_SLOT = 1; + static constexpr uint32_t SEGMENTER_SLOT = 2; + static constexpr uint32_t SLOT_COUNT = 3; - static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT, - "INTERNALS_SLOT must match self-hosting define for internals " - "object slot"); + bool isLocaleResolved() const { return getFixedSlot(LOCALE_SLOT).isString(); } - JSString* getLocale() const { + JSObject* getRequestedLocales() const { const auto& slot = getFixedSlot(LOCALE_SLOT); if (slot.isUndefined()) { return nullptr; } - return slot.toString(); + return &slot.toObject(); + } + + void setRequestedLocales(JSObject* requestedLocales) { + setFixedSlot(LOCALE_SLOT, JS::ObjectValue(*requestedLocales)); } - void setLocale(JSString* locale) { - setFixedSlot(LOCALE_SLOT, StringValue(locale)); + JSLinearString* getLocale() const { + const auto& slot = getFixedSlot(LOCALE_SLOT); + if (slot.isUndefined()) { + return nullptr; + } + return &slot.toString()->asLinear(); + } + + void setLocale(JSLinearString* locale) { + setFixedSlot(LOCALE_SLOT, JS::StringValue(locale)); } SegmenterGranularity getGranularity() const { @@ -346,14 +356,6 @@ class SegmentIteratorObject : public NativeObject { static void finalize(JS::GCContext* gcx, JSObject* obj); }; -/** - * Create a new Segments object. - * - * Usage: segment = intl_CreateSegmentsObject(segmenter, string) - */ -[[nodiscard]] extern bool intl_CreateSegmentsObject(JSContext* cx, - unsigned argc, Value* vp); - /** * Create a new Segment Iterator object. * diff --git a/js/src/builtin/intl/Segmenter.js b/js/src/builtin/intl/Segmenter.js index 2636a6a4e3948..226acb2cf0a19 100644 --- a/js/src/builtin/intl/Segmenter.js +++ b/js/src/builtin/intl/Segmenter.js @@ -161,9 +161,6 @@ function Intl_Segmenter_segment(value) { ); } - // Ensure the Segmenter internals are resolved. - getSegmenterInternals(segmenter); - // Step 3. var string = ToString(value); diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index fb4210b7fc51f..349b7599a4bfc 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -1862,7 +1862,6 @@ static const JSFunctionSpec intrinsic_functions[] = { CallNonGenericSelfhostedMethod>, 2, 0), JS_FN("intl_CompareStrings", intl_CompareStrings, 3, 0), JS_FN("intl_CreateSegmentIterator", intl_CreateSegmentIterator, 1, 0), - JS_FN("intl_CreateSegmentsObject", intl_CreateSegmentsObject, 2, 0), JS_FN("intl_DefaultTimeZone", intrinsic_DefaultTimeZone, 0, 0), JS_FN("intl_FindNextSegmentBoundaries", intl_FindNextSegmentBoundaries, 1, 0), From 3b41d03e4c21ac94504184bcb6c09609faf9577d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Thu, 29 Jan 2026 07:42:53 +0000 Subject: [PATCH 002/116] Bug 2010219 - Part 2: Remove no longer used self-hosting code for Intl.Segmenter. r=dminor Differential Revision: https://phabricator.services.mozilla.com/D279033 --- js/src/builtin/intl/CommonFunctions.js | 14 +- js/src/builtin/intl/Segmenter.js | 199 ------------------------- js/src/jit/CacheIR.cpp | 1 - js/src/jit/InlinableNatives.cpp | 4 - js/src/jit/InlinableNatives.h | 1 - js/src/vm/SelfHosting.cpp | 5 - 6 files changed, 4 insertions(+), 220 deletions(-) diff --git a/js/src/builtin/intl/CommonFunctions.js b/js/src/builtin/intl/CommonFunctions.js index 9d3050cf0c4e9..ad57c643a2f22 100644 --- a/js/src/builtin/intl/CommonFunctions.js +++ b/js/src/builtin/intl/CommonFunctions.js @@ -262,8 +262,7 @@ function initializeIntlObject(obj, type, lazyData) { (type === "DateTimeFormat" && intl_GuardToDateTimeFormat(obj) !== null) || (type === "DurationFormat" && intl_GuardToDurationFormat(obj) !== null) || (type === "NumberFormat" && intl_GuardToNumberFormat(obj) !== null) || - (type === "PluralRules" && intl_GuardToPluralRules(obj) !== null) || - (type === "Segmenter" && intl_GuardToSegmenter(obj) !== null), + (type === "PluralRules" && intl_GuardToPluralRules(obj) !== null), "type must match the object's class" ); assert(IsObject(lazyData), "non-object lazy data"); @@ -277,7 +276,6 @@ function initializeIntlObject(obj, type, lazyData) { // - DurationFormat // - NumberFormat // - PluralRules - // - Segmenter // // The .lazyData property stores information needed to compute -- without // observable side effects -- the actual internal Intl properties of @@ -344,8 +342,7 @@ function getIntlObjectInternals(obj) { intl_GuardToDateTimeFormat(obj) !== null || intl_GuardToDurationFormat(obj) !== null || intl_GuardToNumberFormat(obj) !== null || - intl_GuardToPluralRules(obj) !== null || - intl_GuardToSegmenter(obj) !== null, + intl_GuardToPluralRules(obj) !== null, "getIntlObjectInternals called with non-Intl object" ); @@ -362,9 +359,7 @@ function getIntlObjectInternals(obj) { (internals.type === "NumberFormat" && intl_GuardToNumberFormat(obj) !== null) || (internals.type === "PluralRules" && - intl_GuardToPluralRules(obj) !== null) || - (internals.type === "Segmenter" && - intl_GuardToSegmenter(obj) !== null), + intl_GuardToPluralRules(obj) !== null), "type must match the object's class" ); assert(hasOwn("lazyData", internals), "missing lazyData"); @@ -399,8 +394,7 @@ function getInternals(obj) { } else if (type === "PluralRules") { internalProps = resolvePluralRulesInternals(internals.lazyData); } else { - assert(type === "Segmenter", "unexpected Intl type"); - internalProps = resolveSegmenterInternals(internals.lazyData); + assert(false, "unexpected Intl constructor"); } setInternalProperties(internals, internalProps); return internalProps; diff --git a/js/src/builtin/intl/Segmenter.js b/js/src/builtin/intl/Segmenter.js index 226acb2cf0a19..472b2f67356f5 100644 --- a/js/src/builtin/intl/Segmenter.js +++ b/js/src/builtin/intl/Segmenter.js @@ -2,205 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/** - * Intl.Segmenter ( [ locales [ , options ] ] ) - * - * Compute an internal properties object from |lazySegmenterData|. - */ -function resolveSegmenterInternals(lazySegmenterData) { - assert(IsObject(lazySegmenterData), "lazy data not an object?"); - - var internalProps = std_Object_create(null); - - // Compute effective locale. - - // Steps 9-10. - var r = intl_ResolveLocale( - "Segmenter", - lazySegmenterData.requestedLocales, - lazySegmenterData.opt, - ); - - // Step 11. - internalProps.locale = r.locale; - - // Step 13. - internalProps.granularity = lazySegmenterData.granularity; - - // The caller is responsible for associating |internalProps| with the right - // object using |setInternalProperties|. - return internalProps; -} - -/** - * Returns an object containing the Segmenter internal properties of |obj|. - */ -function getSegmenterInternals(obj) { - assert(IsObject(obj), "getSegmenterInternals called with non-object"); - assert( - intl_GuardToSegmenter(obj) !== null, - "getSegmenterInternals called with non-Segmenter" - ); - - var internals = getIntlObjectInternals(obj); - assert( - internals.type === "Segmenter", - "bad type escaped getIntlObjectInternals" - ); - - // If internal properties have already been computed, use them. - var internalProps = maybeInternalProperties(internals); - if (internalProps) { - return internalProps; - } - - // Otherwise it's time to fully create them. - internalProps = resolveSegmenterInternals(internals.lazyData); - setInternalProperties(internals, internalProps); - return internalProps; -} - -/** - * Intl.Segmenter ( [ locales [ , options ] ] ) - * - * Initializes an object as a Segmenter. - * - * This method is complicated a moderate bit by its implementing initialization - * as a *lazy* concept. Everything that must happen now, does -- but we defer - * all the work we can until the object is actually used as a Segmenter. - * This later work occurs in |resolveSegmenterInternals|; steps not noted here - * occur there. - */ -function InitializeSegmenter(segmenter, locales, options) { - assert(IsObject(segmenter), "InitializeSegmenter called with non-object"); - assert( - intl_GuardToSegmenter(segmenter) !== null, - "InitializeSegmenter called with non-Segmenter" - ); - - // Lazy Segmenter data has the following structure: - // - // { - // requestedLocales: List of locales, - // - // opt: // opt object computed in InitializeSegmenter - // { - // localeMatcher: "lookup" / "best fit", - // } - // - // granularity: "grapheme" / "word" / "sentence", - // } - // - // Note that lazy data is only installed as a final step of initialization, - // so every Segmenter lazy data object has *all* these properties, never a - // subset of them. - var lazySegmenterData = std_Object_create(null); - - // Step 4. - var requestedLocales = CanonicalizeLocaleList(locales); - lazySegmenterData.requestedLocales = requestedLocales; - - // Step 5. - if (options === undefined) { - options = std_Object_create(null); - } else if (!IsObject(options)) { - ThrowTypeError( - JSMSG_OBJECT_REQUIRED, - options === null ? "null" : typeof options - ); - } - - // Step 6. - var opt = NEW_RECORD(); - lazySegmenterData.opt = opt; - - // Steps 7-8. - var matcher = GetOption( - options, - "localeMatcher", - "string", - ["lookup", "best fit"], - "best fit" - ); - opt.localeMatcher = matcher; - - // Steps 12-13. - var granularity = GetOption( - options, - "granularity", - "string", - ["grapheme", "word", "sentence"], - "grapheme" - ); - lazySegmenterData.granularity = granularity; - - // We've done everything that must be done now: mark the lazy data as fully - // computed and install it. - initializeIntlObject(segmenter, "Segmenter", lazySegmenterData); -} - -/** - * Intl.Segmenter.prototype.segment ( string ) - * - * Create a new Segments object. - */ -function Intl_Segmenter_segment(value) { - // Step 1. - var segmenter = this; - - // Step 2. - if ( - !IsObject(segmenter) || - (segmenter = intl_GuardToSegmenter(segmenter)) === null - ) { - return callFunction( - intl_CallSegmenterMethodIfWrapped, - this, - value, - "Intl_Segmenter_segment" - ); - } - - // Step 3. - var string = ToString(value); - - // Step 4. - return intl_CreateSegmentsObject(segmenter, string); -} - -/** - * Intl.Segmenter.prototype.resolvedOptions () - * - * Returns the resolved options for a Segmenter object. - */ -function Intl_Segmenter_resolvedOptions() { - // Step 1. - var segmenter = this; - - // Step 2. - if ( - !IsObject(segmenter) || - (segmenter = intl_GuardToSegmenter(segmenter)) === null - ) { - return callFunction( - intl_CallSegmenterMethodIfWrapped, - this, - "Intl_Segmenter_resolvedOptions" - ); - } - - var internals = getSegmenterInternals(segmenter); - - // Steps 3-4. - var options = { - locale: internals.locale, - granularity: internals.granularity, - }; - - // Step 5. - return options; -} - /** * CreateSegmentDataObject ( segmenter, string, startIndex, endIndex ) */ diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp index 12540d07672fb..24c7a361ca323 100644 --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -13185,7 +13185,6 @@ AttachDecision InlinableNativeIRGenerator::tryAttachStub() { case InlinableNative::IntlGuardToDurationFormat: case InlinableNative::IntlGuardToNumberFormat: case InlinableNative::IntlGuardToPluralRules: - case InlinableNative::IntlGuardToSegmenter: case InlinableNative::IntlGuardToSegments: case InlinableNative::IntlGuardToSegmentIterator: return tryAttachGuardToClass(native); diff --git a/js/src/jit/InlinableNatives.cpp b/js/src/jit/InlinableNatives.cpp index 483bf70259a6b..df8922bb85a3a 100644 --- a/js/src/jit/InlinableNatives.cpp +++ b/js/src/jit/InlinableNatives.cpp @@ -51,8 +51,6 @@ const JSClass* js::jit::InlinableNativeGuardToClass(InlinableNative native) { return &NumberFormatObject::class_; case InlinableNative::IntlGuardToPluralRules: return &PluralRulesObject::class_; - case InlinableNative::IntlGuardToSegmenter: - return &SegmenterObject::class_; case InlinableNative::IntlGuardToSegments: return &SegmentsObject::class_; case InlinableNative::IntlGuardToSegmentIterator: @@ -62,7 +60,6 @@ const JSClass* js::jit::InlinableNativeGuardToClass(InlinableNative native) { case InlinableNative::IntlGuardToDateTimeFormat: case InlinableNative::IntlGuardToNumberFormat: case InlinableNative::IntlGuardToPluralRules: - case InlinableNative::IntlGuardToSegmenter: case InlinableNative::IntlGuardToSegments: case InlinableNative::IntlGuardToSegmentIterator: MOZ_CRASH("Intl API disabled"); @@ -172,7 +169,6 @@ bool js::jit::CanInlineNativeCrossRealm(InlinableNative native) { case InlinableNative::IntlGuardToDurationFormat: case InlinableNative::IntlGuardToNumberFormat: case InlinableNative::IntlGuardToPluralRules: - case InlinableNative::IntlGuardToSegmenter: case InlinableNative::IntlGuardToSegments: case InlinableNative::IntlGuardToSegmentIterator: case InlinableNative::IsRegExpObject: diff --git a/js/src/jit/InlinableNatives.h b/js/src/jit/InlinableNatives.h index ec1ea2ae45763..9087746c43006 100644 --- a/js/src/jit/InlinableNatives.h +++ b/js/src/jit/InlinableNatives.h @@ -100,7 +100,6 @@ _(IntlGuardToDurationFormat) \ _(IntlGuardToNumberFormat) \ _(IntlGuardToPluralRules) \ - _(IntlGuardToSegmenter) \ _(IntlGuardToSegments) \ _(IntlGuardToSegmentIterator) \ \ diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 349b7599a4bfc..d3acf7ff43773 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -1856,8 +1856,6 @@ static const JSFunctionSpec intrinsic_functions[] = { CallNonGenericSelfhostedMethod>, 2, 0), JS_FN("intl_CallSegmentIteratorMethodIfWrapped", CallNonGenericSelfhostedMethod>, 2, 0), - JS_FN("intl_CallSegmenterMethodIfWrapped", - CallNonGenericSelfhostedMethod>, 2, 0), JS_FN("intl_CallSegmentsMethodIfWrapped", CallNonGenericSelfhostedMethod>, 2, 0), JS_FN("intl_CompareStrings", intl_CompareStrings, 3, 0), @@ -1889,9 +1887,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_INLINABLE_FN("intl_GuardToSegmentIterator", intrinsic_GuardToBuiltin, 1, 0, IntlGuardToSegmentIterator), - JS_INLINABLE_FN("intl_GuardToSegmenter", - intrinsic_GuardToBuiltin, 1, 0, - IntlGuardToSegmenter), JS_INLINABLE_FN("intl_GuardToSegments", intrinsic_GuardToBuiltin, 1, 0, IntlGuardToSegments), From 759d06c2bdf79a6893cf8193318a390391b20f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Thu, 29 Jan 2026 07:42:53 +0000 Subject: [PATCH 003/116] Bug 2010220 - Part 1: Move Intl.Collator to C++. r=spidermonkey-reviewers,dminor Part 2 removes the self-hosted code. Differential Revision: https://phabricator.services.mozilla.com/D279034 --- js/src/builtin/intl/Collator.cpp | 660 +++++++++++++++++++++++++------ js/src/builtin/intl/Collator.h | 100 ++++- 2 files changed, 624 insertions(+), 136 deletions(-) diff --git a/js/src/builtin/intl/Collator.cpp b/js/src/builtin/intl/Collator.cpp index bceff97853ff6..50fb32026fd6f 100644 --- a/js/src/builtin/intl/Collator.cpp +++ b/js/src/builtin/intl/Collator.cpp @@ -17,7 +17,9 @@ #include "builtin/intl/FormatBuffer.h" #include "builtin/intl/LanguageTag.h" #include "builtin/intl/LocaleNegotiation.h" +#include "builtin/intl/ParameterNegotiation.h" #include "builtin/intl/SharedIntlData.h" +#include "builtin/intl/UsingEnum.h" #include "gc/GCContext.h" #include "js/PropertySpec.h" #include "js/StableStringChars.h" @@ -36,9 +38,6 @@ using namespace js::intl; using JS::AutoStableStringChars; -using js::intl::ReportInternalError; -using js::intl::SharedIntlData; - const JSClassOps CollatorObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty @@ -66,6 +65,10 @@ const JSClass& CollatorObject::protoClass_ = PlainObject::class_; static bool collator_supportedLocalesOf(JSContext* cx, unsigned argc, Value* vp); +static bool collator_compare(JSContext* cx, unsigned argc, Value* vp); + +static bool collator_resolvedOptions(JSContext* cx, unsigned argc, Value* vp); + static bool collator_toSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setString(cx->names().Collator); @@ -78,13 +81,13 @@ static const JSFunctionSpec collator_static_methods[] = { }; static const JSFunctionSpec collator_methods[] = { - JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Collator_resolvedOptions", 0, 0), + JS_FN("resolvedOptions", collator_resolvedOptions, 0, 0), JS_FN("toSource", collator_toSource, 0, 0), JS_FS_END, }; static const JSPropertySpec collator_properties[] = { - JS_SELF_HOSTED_GET("compare", "$Intl_Collator_compare_get", 0), + JS_PSG("compare", collator_compare, 0), JS_STRING_SYM_PS(toStringTag, "Intl.Collator", JSPROP_READONLY), JS_PS_END, }; @@ -102,6 +105,173 @@ const ClassSpec CollatorObject::classSpec_ = { ClassSpec::DontDefineConstructor, }; +static constexpr std::string_view UsageToString(CollatorOptions::Usage usage) { +#ifndef USING_ENUM + using enum CollatorOptions::Usage; +#else + USING_ENUM(CollatorOptions::Usage, Sort, Search); +#endif + switch (usage) { + case Sort: + return "sort"; + case Search: + return "search"; + } + MOZ_CRASH("invalid collator usage"); +} + +static constexpr std::string_view SensitivityToString( + CollatorOptions::Sensitivity sensitivity) { +#ifndef USING_ENUM + using enum CollatorOptions::Sensitivity; +#else + USING_ENUM(CollatorOptions::Sensitivity, Base, Accent, Case, Variant); +#endif + switch (sensitivity) { + case Base: + return "base"; + case Accent: + return "accent"; + case Case: + return "case"; + case Variant: + return "variant"; + } + MOZ_CRASH("invalid collator sensitivity"); +} + +static constexpr std::string_view CaseFirstToString( + CollatorOptions::CaseFirst caseFirst) { +#ifndef USING_ENUM + using enum CollatorOptions::CaseFirst; +#else + USING_ENUM(CollatorOptions::CaseFirst, False, Lower, Upper); +#endif + switch (caseFirst) { + case False: + return "false"; + case Lower: + return "lower"; + case Upper: + return "upper"; + } + MOZ_CRASH("invalid collator case first"); +} + +/** + * 10.1.2 Intl.Collator([ locales [, options]]) + * + * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b + */ +static bool InitializeCollator(JSContext* cx, Handle collator, + Handle locales, + Handle optionsValue) { + // Steps 4-5. + Rooted requestedLocales(cx, cx); + if (!CanonicalizeLocaleList(cx, locales, &requestedLocales)) { + return false; + } + + Rooted requestedLocalesArray( + cx, LocalesListToArray(cx, requestedLocales)); + if (!requestedLocalesArray) { + return false; + } + collator->setRequestedLocales(requestedLocalesArray); + + auto colOptions = cx->make_unique(); + if (!colOptions) { + return false; + } + + if (!optionsValue.isUndefined()) { + // Step 6. + Rooted options(cx, JS::ToObject(cx, optionsValue)); + if (!options) { + return false; + } + + // Steps 7-8. + static constexpr auto usages = MapOptions( + CollatorOptions::Usage::Sort, CollatorOptions::Usage::Search); + if (!GetStringOption(cx, options, cx->names().usage, usages, + CollatorOptions::Usage::Sort, &colOptions->usage)) { + return false; + } + + // Step 9-10. (Performed in ResolveLocale) + + // Step 11. (Inlined ResolveOptions) + + // ResolveOptions, steps 1-3. (Already performed above) + + // ResolveOptions, step 4. + LocaleMatcher matcher; + if (!GetLocaleMatcherOption(cx, options, &matcher)) { + return false; + } + + // ResolveOptions, step 5. + // + // This implementation only supports the "lookup" locale matcher, therefore + // the "localeMatcher" option doesn't need to be stored. + + // ResolveOptions, step 6. + Rooted collation(cx); + if (!GetUnicodeExtensionOption(cx, options, UnicodeExtensionKey::Collation, + &collation)) { + return false; + } + if (collation) { + collator->setCollation(collation); + } + + // ResolveOptions, step 6. + if (!GetBooleanOption(cx, options, cx->names().numeric, + &colOptions->numeric)) { + return false; + } + + // ResolveOptions, step 6. + static constexpr auto caseFirsts = MapOptions( + CollatorOptions::CaseFirst::Upper, CollatorOptions::CaseFirst::Lower, + CollatorOptions::CaseFirst::False); + if (!GetStringOption(cx, options, cx->names().caseFirst, caseFirsts, + &colOptions->caseFirst)) { + return false; + } + + // ResolveOptions, step 7. (Not applicable) + + // ResolveOptions, step 8. (Performed in ResolveLocale) + + // ResolveOptions, step 9. (Return) + + // Steps 12-19 and 21. (Performed in ResolveLocale) + + // Step 20. + static constexpr auto sensitivities = + MapOptions(CollatorOptions::Sensitivity::Base, + CollatorOptions::Sensitivity::Accent, + CollatorOptions::Sensitivity::Case, + CollatorOptions::Sensitivity::Variant); + if (!GetStringOption(cx, options, cx->names().sensitivity, sensitivities, + &colOptions->sensitivity)) { + return false; + } + + // Step 22. + if (!GetBooleanOption(cx, options, cx->names().ignorePunctuation, + &colOptions->ignorePunctuation)) { + return false; + } + } + collator->setOptions(colOptions.release()); + AddCellMemory(collator, sizeof(CollatorOptions), js::MemoryUse::IntlOptions); + + return true; +} + /** * 10.1.2 Intl.Collator([ locales [, options]]) * @@ -113,8 +283,8 @@ static bool Collator(JSContext* cx, unsigned argc, Value* vp) { // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code). - // Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor). - RootedObject proto(cx); + // Steps 2-3 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + Rooted proto(cx); if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Collator, &proto)) { return false; } @@ -125,15 +295,12 @@ static bool Collator(JSContext* cx, unsigned argc, Value* vp) { return false; } - HandleValue locales = args.get(0); - HandleValue options = args.get(1); - - // Step 6. - if (!intl::InitializeObject(cx, collator, cx->names().InitializeCollator, - locales, options)) { + // Steps 4-22. + if (!InitializeCollator(cx, collator, args.get(0), args.get(1))) { return false; } + // Step 23. args.rval().setObject(*collator); return true; } @@ -146,11 +313,9 @@ CollatorObject* js::intl::CreateCollator(JSContext* cx, Handle locales, return nullptr; } - if (!InitializeObject(cx, collator, cx->names().InitializeCollator, locales, - options)) { + if (!InitializeCollator(cx, collator, locales, options)) { return nullptr; } - return collator; } @@ -175,147 +340,238 @@ CollatorObject* js::intl::GetOrCreateCollator(JSContext* cx, } void js::CollatorObject::finalize(JS::GCContext* gcx, JSObject* obj) { - if (mozilla::intl::Collator* coll = obj->as().getCollator()) { - intl::RemoveICUCellMemory(gcx, obj, CollatorObject::EstimatedMemoryUse); + auto* collator = &obj->as(); + + if (auto* options = collator->getOptions()) { + gcx->delete_(obj, options, MemoryUse::IntlOptions); + } + + if (auto* coll = collator->getCollator()) { + RemoveICUCellMemory(gcx, obj, CollatorObject::EstimatedMemoryUse); delete coll; } } /** - * Returns a new mozilla::intl::Collator with the locale and collation options - * of the given Collator. + * Resolve the actual locale to finish initialization of the Collator. */ -static mozilla::intl::Collator* NewIntlCollator( - JSContext* cx, Handle collator) { - RootedValue value(cx); - RootedObject internals(cx, intl::GetInternalsObject(cx, collator)); - if (!internals) { - return nullptr; +static bool ResolveLocale(JSContext* cx, Handle collator) { + // Return if the locale was already resolved. + if (collator->isLocaleResolved()) { + return true; } + auto* colOptions = collator->getOptions(); - using mozilla::intl::Collator; + Rooted requestedLocales( + cx, &collator->getRequestedLocales()->as()); - Collator::Options options{}; + // %Intl.Collator%.[[RelevantExtensionKeys]] is « "co", "kf", "kn" ». + mozilla::EnumSet relevantExtensionKeys{ + UnicodeExtensionKey::Collation, + UnicodeExtensionKey::CollationCaseFirst, + UnicodeExtensionKey::CollationNumeric, + }; - if (!GetProperty(cx, internals, internals, cx->names().usage, &value)) { - return nullptr; + // Initialize locale options from constructor arguments. + Rooted localeOptions(cx); + if (auto* co = collator->getCollation()) { + localeOptions.setUnicodeExtension(UnicodeExtensionKey::Collation, co); } + if (auto caseFirst = colOptions->caseFirst) { +#ifndef USING_ENUM + using enum CollatorOptions::CaseFirst; +#else + USING_ENUM(CollatorOptions::CaseFirst, False, Lower, Upper); +#endif - enum class Usage { Search, Sort }; - - Usage usage; - { - JSLinearString* str = value.toString()->ensureLinear(cx); - if (!str) { - return nullptr; - } - - if (StringEqualsLiteral(str, "search")) { - usage = Usage::Search; - } else { - MOZ_ASSERT(StringEqualsLiteral(str, "sort")); - usage = Usage::Sort; + JSLinearString* kf; + switch (*caseFirst) { + case Upper: + kf = cx->names().upper; + break; + case Lower: + kf = cx->names().lower; + break; + case False: + kf = cx->names().false_; + break; } + localeOptions.setUnicodeExtension(UnicodeExtensionKey::CollationCaseFirst, + kf); + } + if (auto numeric = colOptions->numeric) { + JSLinearString* kn = *numeric ? cx->names().true_ : cx->names().false_; + localeOptions.setUnicodeExtension(UnicodeExtensionKey::CollationNumeric, + kn); } - JS::RootedVector keywords(cx); - - // ICU expects collation as Unicode locale extensions on locale. - if (usage == Usage::Search) { - if (!keywords.emplaceBack("co", cx->names().search)) { - return nullptr; - } + // Use the default locale data for "sort" usage. + // Use the collator search locale data for "search" usage. + LocaleData localeData; + if (colOptions->usage == CollatorOptions::Usage::Sort) { + localeData = LocaleData::Default; + } else { + localeData = LocaleData::CollatorSearch; + } - // Search collations can't select a different collation, so the collation - // property is guaranteed to be "default". -#ifdef DEBUG - if (!GetProperty(cx, internals, internals, cx->names().collation, &value)) { - return nullptr; - } + // Resolve the actual locale. + Rooted resolved(cx); + if (!ResolveLocale(cx, AvailableLocaleKind::Collator, requestedLocales, + localeOptions, relevantExtensionKeys, localeData, + &resolved)) { + return false; + } - JSLinearString* collation = value.toString()->ensureLinear(cx); - if (!collation) { - return nullptr; - } + if (colOptions->sensitivity.isNothing()) { + // In theory the default sensitivity for the "search" collator is locale + // dependent; in reality the CLDR/ICU default strength is always tertiary. + // Therefore use "variant" as the default value for both collation modes. + colOptions->sensitivity = + mozilla::Some(CollatorOptions::Sensitivity::Variant); + } - MOZ_ASSERT(StringEqualsLiteral(collation, "default")); -#endif - } else { - if (!GetProperty(cx, internals, internals, cx->names().collation, &value)) { - return nullptr; + if (colOptions->ignorePunctuation.isNothing()) { + // If |locale| is the default locale (e.g. da-DK), but only supported + // through a fallback (da), we need to get the actual data locale first. + Rooted dataLocale(cx); + if (!BestAvailableLocale(cx, AvailableLocaleKind::Collator, + resolved.dataLocale(), nullptr, &dataLocale)) { + return false; } + MOZ_ASSERT(dataLocale); - JSLinearString* collation = value.toString()->ensureLinear(cx); - if (!collation) { - return nullptr; - } + auto& sharedIntlData = cx->runtime()->sharedIntlData.ref(); - // Set collation as a Unicode locale extension when it was specified. - if (!StringEqualsLiteral(collation, "default")) { - if (!keywords.emplaceBack("co", collation)) { - return nullptr; - } + bool ignorePunctuation; + if (!sharedIntlData.isIgnorePunctuation(cx, dataLocale, + &ignorePunctuation)) { + return false; } + colOptions->ignorePunctuation = mozilla::Some(ignorePunctuation); } - UniqueChars locale = intl::FormatLocale(cx, internals, keywords); + // Finish initialization by setting the actual locale and collation. + auto* locale = resolved.toLocale(cx); if (!locale) { - return nullptr; + return false; } + collator->setLocale(locale); - if (!GetProperty(cx, internals, internals, cx->names().sensitivity, &value)) { - return nullptr; + if (auto co = resolved.extension(UnicodeExtensionKey::Collation)) { + collator->setCollation(co); + } else { + collator->setCollation(cx->names().default_); } - { - JSLinearString* sensitivity = value.toString()->ensureLinear(cx); - if (!sensitivity) { - return nullptr; - } - if (StringEqualsLiteral(sensitivity, "base")) { - options.sensitivity = Collator::Sensitivity::Base; - } else if (StringEqualsLiteral(sensitivity, "accent")) { - options.sensitivity = Collator::Sensitivity::Accent; - } else if (StringEqualsLiteral(sensitivity, "case")) { - options.sensitivity = Collator::Sensitivity::Case; - } else { - MOZ_ASSERT(StringEqualsLiteral(sensitivity, "variant")); - options.sensitivity = Collator::Sensitivity::Variant; - } - } + auto kf = resolved.extension(UnicodeExtensionKey::CollationCaseFirst); + MOZ_ASSERT(kf, "resolved case first is non-null"); - if (!GetProperty(cx, internals, internals, cx->names().ignorePunctuation, - &value)) { - return nullptr; + if (StringEqualsLiteral(kf, "upper")) { + colOptions->caseFirst = mozilla::Some(CollatorOptions::CaseFirst::Upper); + } else if (StringEqualsLiteral(kf, "lower")) { + colOptions->caseFirst = mozilla::Some(CollatorOptions::CaseFirst::Lower); + } else { + MOZ_ASSERT(StringEqualsLiteral(kf, "false")); + colOptions->caseFirst = mozilla::Some(CollatorOptions::CaseFirst::False); } - options.ignorePunctuation = value.toBoolean(); - if (!GetProperty(cx, internals, internals, cx->names().numeric, &value)) { - return nullptr; - } - if (!value.isUndefined()) { - options.numeric = value.toBoolean(); + auto kn = resolved.extension(UnicodeExtensionKey::CollationNumeric); + MOZ_ASSERT(kn, "resolved numeric is non-null"); + + if (StringEqualsLiteral(kn, "true")) { + colOptions->numeric = mozilla::Some(true); + } else { + MOZ_ASSERT(StringEqualsLiteral(kn, "false")); + colOptions->numeric = mozilla::Some(false); } - if (!GetProperty(cx, internals, internals, cx->names().caseFirst, &value)) { + MOZ_ASSERT(collator->isLocaleResolved(), "locale successfully resolved"); + return true; +} + +static auto ToCollatorSensitivity(CollatorOptions::Sensitivity sensitivity) { +#ifndef USING_ENUM + using enum mozilla::intl::Collator::Sensitivity; +#else + USING_ENUM(mozilla::intl::Collator::Sensitivity, Base, Accent, Case, Variant); +#endif + switch (sensitivity) { + case CollatorOptions::Sensitivity::Base: + return Base; + case CollatorOptions::Sensitivity::Accent: + return Accent; + case CollatorOptions::Sensitivity::Case: + return Case; + case CollatorOptions::Sensitivity::Variant: + return Variant; + } + MOZ_CRASH("invalid collator sensitivity"); +} + +static auto ToCollatorCaseFirst(CollatorOptions::CaseFirst caseFirst) { +#ifndef USING_ENUM + using enum mozilla::intl::Collator::CaseFirst; +#else + USING_ENUM(mozilla::intl::Collator::CaseFirst, False, Lower, Upper); +#endif + switch (caseFirst) { + case CollatorOptions::CaseFirst::Upper: + return Upper; + case CollatorOptions::CaseFirst::Lower: + return Lower; + case CollatorOptions::CaseFirst::False: + return False; + } + MOZ_CRASH("invalid collator case first"); +} + +/** + * Returns a new mozilla::intl::Collator with the locale and collation options + * of the given Collator. + */ +static mozilla::intl::Collator* NewIntlCollator( + JSContext* cx, Handle collator) { + if (!ResolveLocale(cx, collator)) { return nullptr; } - if (!value.isUndefined()) { - JSLinearString* caseFirst = value.toString()->ensureLinear(cx); - if (!caseFirst) { + auto colOptions = *collator->getOptions(); + + JS::RootedVector keywords(cx); + + // ICU expects collation as Unicode locale extensions on locale. + if (colOptions.usage == CollatorOptions::Usage::Search) { + if (!keywords.emplaceBack("co", cx->names().search)) { return nullptr; } - if (StringEqualsLiteral(caseFirst, "upper")) { - options.caseFirst = Collator::CaseFirst::Upper; - } else if (StringEqualsLiteral(caseFirst, "lower")) { - options.caseFirst = Collator::CaseFirst::Lower; - } else { - MOZ_ASSERT(StringEqualsLiteral(caseFirst, "false")); - options.caseFirst = Collator::CaseFirst::False; + + // Search collations can't select a different collation, so the collation + // property is guaranteed to be "default". + MOZ_ASSERT(StringEqualsLiteral(collator->getCollation(), "default")); + } else { + auto* collation = collator->getCollation(); + + // Set collation as a Unicode locale extension when it was specified. + if (!StringEqualsLiteral(collation, "default")) { + if (!keywords.emplaceBack("co", collation)) { + return nullptr; + } } } - auto collResult = Collator::TryCreate(locale.get()); + Rooted localeStr(cx, collator->getLocale()); + auto locale = FormatLocale(cx, localeStr, keywords); + if (!locale) { + return nullptr; + } + + mozilla::intl::Collator::Options options = { + .sensitivity = ToCollatorSensitivity(*colOptions.sensitivity), + .caseFirst = ToCollatorCaseFirst(*colOptions.caseFirst), + .ignorePunctuation = *colOptions.ignorePunctuation, + .numeric = *colOptions.numeric, + }; + + auto collResult = mozilla::intl::Collator::TryCreate(locale.get()); if (collResult.isErr()) { ReportInternalError(cx, collResult.unwrapErr()); return nullptr; @@ -327,31 +583,29 @@ static mozilla::intl::Collator* NewIntlCollator( ReportInternalError(cx, optResult.unwrapErr()); return nullptr; } - return coll.release(); } static mozilla::intl::Collator* GetOrCreateCollator( JSContext* cx, Handle collator) { // Obtain a cached mozilla::intl::Collator object. - mozilla::intl::Collator* coll = collator->getCollator(); - if (coll) { + if (auto* coll = collator->getCollator()) { return coll; } - coll = NewIntlCollator(cx, collator); + auto* coll = NewIntlCollator(cx, collator); if (!coll) { return nullptr; } collator->setCollator(coll); - intl::AddICUCellMemory(collator, CollatorObject::EstimatedMemoryUse); + AddICUCellMemory(collator, CollatorObject::EstimatedMemoryUse); return coll; } -static bool intl_CompareStrings(JSContext* cx, mozilla::intl::Collator* coll, - JSString* str1, JSString* str2, - MutableHandleValue result) { +static bool CompareStrings(JSContext* cx, mozilla::intl::Collator* coll, + JSString* str1, JSString* str2, + MutableHandleValue result) { MOZ_ASSERT(str1); MOZ_ASSERT(str2); @@ -395,7 +649,7 @@ bool js::intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp) { // Use the UCollator to actually compare the strings. JSString* str1 = args[1].toString(); JSString* str2 = args[2].toString(); - return intl_CompareStrings(cx, coll, str1, str2, args.rval()); + return CompareStrings(cx, coll, str1, str2, args.rval()); } bool js::intl::CompareStrings(JSContext* cx, Handle collator, @@ -405,7 +659,7 @@ bool js::intl::CompareStrings(JSContext* cx, Handle collator, if (!coll) { return false; } - return intl_CompareStrings(cx, coll, str1, str2, result); + return CompareStrings(cx, coll, str1, str2, result); } bool js::intl_isUpperCaseFirst(JSContext* cx, unsigned argc, Value* vp) { @@ -450,6 +704,160 @@ bool js::intl_isIgnorePunctuation(JSContext* cx, unsigned argc, Value* vp) { return true; } +static bool IsCollator(Handle v) { + return v.isObject() && v.toObject().is(); +} + +static constexpr uint32_t CollatorCompareFunction_Collator = 0; + +/** + * Collator Compare Functions + */ +static bool CollatorCompareFunction(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-2. + auto* compare = &args.callee().as(); + auto collValue = compare->getExtendedSlot(CollatorCompareFunction_Collator); + Rooted collator(cx, + &collValue.toObject().as()); + + // Step 3. + Rooted x(cx, JS::ToString(cx, args.get(0))); + if (!x) { + return false; + } + + // Step 4. + Rooted y(cx, JS::ToString(cx, args.get(1))); + if (!y) { + return false; + } + + // Step 5. + return CompareStrings(cx, collator, x, y, args.rval()); +} + +/** + * get Intl.Collator.prototype.compare + */ +static bool collator_compare(JSContext* cx, const CallArgs& args) { + Rooted collator( + cx, &args.thisv().toObject().as()); + + // Step 3. + auto* boundCompare = collator->getBoundCompare(); + if (!boundCompare) { + Handle funName = cx->names().empty_; + auto* fn = + NewNativeFunction(cx, CollatorCompareFunction, 2, funName, + gc::AllocKind::FUNCTION_EXTENDED, GenericObject); + if (!fn) { + return false; + } + fn->initExtendedSlot(CollatorCompareFunction_Collator, + ObjectValue(*collator)); + + collator->setBoundCompare(fn); + boundCompare = fn; + } + + // Step 4. + args.rval().setObject(*boundCompare); + return true; +} + +/** + * get Intl.Collator.prototype.compare + */ +static bool collator_compare(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/** + * Intl.Collator.prototype.resolvedOptions ( ) + */ +static bool collator_resolvedOptions(JSContext* cx, const CallArgs& args) { + Rooted collator( + cx, &args.thisv().toObject().as()); + + if (!ResolveLocale(cx, collator)) { + return false; + } + auto colOptions = *collator->getOptions(); + + // Step 3. + Rooted options(cx, cx); + + // Step 4. + if (!options.emplaceBack(NameToId(cx->names().locale), + StringValue(collator->getLocale()))) { + return false; + } + + auto* usage = NewStringCopy(cx, UsageToString(colOptions.usage)); + if (!usage) { + return false; + } + if (!options.emplaceBack(NameToId(cx->names().usage), StringValue(usage))) { + return false; + } + + auto* sensitivity = + NewStringCopy(cx, SensitivityToString(*colOptions.sensitivity)); + if (!sensitivity) { + return false; + } + if (!options.emplaceBack(NameToId(cx->names().sensitivity), + StringValue(sensitivity))) { + return false; + } + + if (!options.emplaceBack(NameToId(cx->names().ignorePunctuation), + BooleanValue(*colOptions.ignorePunctuation))) { + return false; + } + + if (!options.emplaceBack(NameToId(cx->names().collation), + StringValue(collator->getCollation()))) { + return false; + } + + if (!options.emplaceBack(NameToId(cx->names().numeric), + BooleanValue(*colOptions.numeric))) { + return false; + } + + auto* caseFirst = + NewStringCopy(cx, CaseFirstToString(*colOptions.caseFirst)); + if (!caseFirst) { + return false; + } + if (!options.emplaceBack(NameToId(cx->names().caseFirst), + StringValue(caseFirst))) { + return false; + } + + // Step 5. + auto* result = NewPlainObjectWithUniqueNames(cx, options); + if (!result) { + return false; + } + args.rval().setObject(*result); + return true; +} + +/** + * Intl.Collator.prototype.resolvedOptions ( ) + */ +static bool collator_resolvedOptions(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + /** * Intl.Collator.supportedLocalesOf ( locales [ , options ] ) */ diff --git a/js/src/builtin/intl/Collator.h b/js/src/builtin/intl/Collator.h index 146bf2fe74ea3..843dd69def79c 100644 --- a/js/src/builtin/intl/Collator.h +++ b/js/src/builtin/intl/Collator.h @@ -7,11 +7,15 @@ #ifndef builtin_intl_Collator_h #define builtin_intl_Collator_h +#include "mozilla/Maybe.h" + +#include #include -#include "builtin/SelfHostingDefines.h" #include "js/Class.h" +#include "js/Value.h" #include "vm/NativeObject.h" +#include "vm/StringType.h" namespace mozilla::intl { class Collator; @@ -19,24 +23,88 @@ class Collator; namespace js { -/******************** Collator ********************/ +namespace intl { +struct CollatorOptions { + enum class Usage : int8_t { Sort, Search }; + Usage usage = Usage::Sort; + + enum class Sensitivity : int8_t { Base, Accent, Case, Variant }; + mozilla::Maybe sensitivity{}; + + mozilla::Maybe ignorePunctuation{}; + + mozilla::Maybe numeric{}; + + enum class CaseFirst : int8_t { Upper, Lower, False }; + mozilla::Maybe caseFirst{}; +}; +} // namespace intl class CollatorObject : public NativeObject { public: static const JSClass class_; static const JSClass& protoClass_; - static constexpr uint32_t INTERNALS_SLOT = 0; - static constexpr uint32_t INTL_COLLATOR_SLOT = 1; - static constexpr uint32_t SLOT_COUNT = 2; - - static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT, - "INTERNALS_SLOT must match self-hosting define for internals " - "object slot"); + static constexpr uint32_t LOCALE_SLOT = 0; + static constexpr uint32_t COLLATION_SLOT = 1; + static constexpr uint32_t OPTIONS_SLOT = 2; + static constexpr uint32_t INTL_COLLATOR_SLOT = 3; + static constexpr uint32_t BOUND_COMPARE_SLOT = 4; + static constexpr uint32_t SLOT_COUNT = 5; // Estimated memory use for UCollator (see IcuMemoryUsage). static constexpr size_t EstimatedMemoryUse = 1128; + bool isLocaleResolved() const { return getFixedSlot(LOCALE_SLOT).isString(); } + + JSObject* getRequestedLocales() const { + const auto& slot = getFixedSlot(LOCALE_SLOT); + if (slot.isUndefined()) { + return nullptr; + } + return &slot.toObject(); + } + + void setRequestedLocales(JSObject* requestedLocales) { + setFixedSlot(LOCALE_SLOT, JS::ObjectValue(*requestedLocales)); + } + + JSLinearString* getLocale() const { + const auto& slot = getFixedSlot(LOCALE_SLOT); + if (slot.isUndefined()) { + return nullptr; + } + return &slot.toString()->asLinear(); + } + + void setLocale(JSLinearString* locale) { + setFixedSlot(LOCALE_SLOT, JS::StringValue(locale)); + } + + JSLinearString* getCollation() const { + const auto& slot = getFixedSlot(COLLATION_SLOT); + if (slot.isUndefined()) { + return nullptr; + } + return &slot.toString()->asLinear(); + } + + void setCollation(JSLinearString* collation) { + setFixedSlot(COLLATION_SLOT, JS::StringValue(collation)); + } + + intl::CollatorOptions* getOptions() const { + const auto& slot = getFixedSlot(OPTIONS_SLOT); + if (slot.isUndefined()) { + return nullptr; + } + return static_cast(slot.toPrivate()); + } + + void setOptions(intl::CollatorOptions* options) { + setFixedSlot(OPTIONS_SLOT, JS::PrivateValue(options)); + } + mozilla::intl::Collator* getCollator() const { const auto& slot = getFixedSlot(INTL_COLLATOR_SLOT); if (slot.isUndefined()) { @@ -46,7 +114,19 @@ class CollatorObject : public NativeObject { } void setCollator(mozilla::intl::Collator* collator) { - setFixedSlot(INTL_COLLATOR_SLOT, PrivateValue(collator)); + setFixedSlot(INTL_COLLATOR_SLOT, JS::PrivateValue(collator)); + } + + JSObject* getBoundCompare() const { + const auto& slot = getFixedSlot(BOUND_COMPARE_SLOT); + if (slot.isUndefined()) { + return nullptr; + } + return &slot.toObject(); + } + + void setBoundCompare(JSObject* boundCompare) { + setFixedSlot(BOUND_COMPARE_SLOT, JS::ObjectValue(*boundCompare)); } private: From c87a5afc1234bc8a51d5e1d474b5ce3be845a39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Thu, 29 Jan 2026 07:42:54 +0000 Subject: [PATCH 004/116] Bug 2010220 - Part 2: Remove no longer used self-hosting code for Intl.Collator. r=spidermonkey-reviewers,dminor Differential Revision: https://phabricator.services.mozilla.com/D279035 --- js/src/builtin/intl/Collator.cpp | 92 +------ js/src/builtin/intl/Collator.h | 35 +-- js/src/builtin/intl/Collator.js | 353 ------------------------- js/src/builtin/intl/CommonFunctions.js | 14 +- js/src/builtin/moz.build | 1 - js/src/jit/CacheIR.cpp | 1 - js/src/jit/InlinableNatives.cpp | 5 - js/src/jit/InlinableNatives.h | 1 - js/src/vm/SelfHosting.cpp | 9 - 9 files changed, 20 insertions(+), 491 deletions(-) delete mode 100644 js/src/builtin/intl/Collator.js diff --git a/js/src/builtin/intl/Collator.cpp b/js/src/builtin/intl/Collator.cpp index 50fb32026fd6f..0fd1294a4e700 100644 --- a/js/src/builtin/intl/Collator.cpp +++ b/js/src/builtin/intl/Collator.cpp @@ -36,8 +36,6 @@ using namespace js; using namespace js::intl; -using JS::AutoStableStringChars; - const JSClassOps CollatorObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty @@ -603,9 +601,9 @@ static mozilla::intl::Collator* GetOrCreateCollator( return coll; } -static bool CompareStrings(JSContext* cx, mozilla::intl::Collator* coll, - JSString* str1, JSString* str2, - MutableHandleValue result) { +bool js::intl::CompareStrings(JSContext* cx, Handle collator, + Handle str1, Handle str2, + MutableHandle result) { MOZ_ASSERT(str1); MOZ_ASSERT(str2); @@ -614,93 +612,25 @@ static bool CompareStrings(JSContext* cx, mozilla::intl::Collator* coll, return true; } - AutoStableStringChars stableChars1(cx); - if (!stableChars1.initTwoByte(cx, str1)) { - return false; - } - - AutoStableStringChars stableChars2(cx); - if (!stableChars2.initTwoByte(cx, str2)) { - return false; - } - - mozilla::Range chars1 = stableChars1.twoByteRange(); - mozilla::Range chars2 = stableChars2.twoByteRange(); - - result.setInt32(coll->CompareStrings(chars1, chars2)); - return true; -} - -bool js::intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 3); - MOZ_ASSERT(args[0].isObject()); - MOZ_ASSERT(args[1].isString()); - MOZ_ASSERT(args[2].isString()); - - Rooted collator(cx, - &args[0].toObject().as()); - - mozilla::intl::Collator* coll = GetOrCreateCollator(cx, collator); - if (!coll) { - return false; - } - - // Use the UCollator to actually compare the strings. - JSString* str1 = args[1].toString(); - JSString* str2 = args[2].toString(); - return CompareStrings(cx, coll, str1, str2, args.rval()); -} - -bool js::intl::CompareStrings(JSContext* cx, Handle collator, - Handle str1, Handle str2, - MutableHandle result) { - mozilla::intl::Collator* coll = GetOrCreateCollator(cx, collator); + auto* coll = GetOrCreateCollator(cx, collator); if (!coll) { return false; } - return CompareStrings(cx, coll, str1, str2, result); -} - -bool js::intl_isUpperCaseFirst(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 1); - MOZ_ASSERT(args[0].isString()); - - SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref(); - - Rooted locale(cx, args[0].toString()->ensureLinear(cx)); - if (!locale) { - return false; - } - bool isUpperFirst; - if (!sharedIntlData.isUpperCaseFirst(cx, locale, &isUpperFirst)) { + JS::AutoStableStringChars stableChars1(cx); + if (!stableChars1.initTwoByte(cx, str1)) { return false; } - args.rval().setBoolean(isUpperFirst); - return true; -} - -bool js::intl_isIgnorePunctuation(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 1); - MOZ_ASSERT(args[0].isString()); - - SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref(); - - Rooted locale(cx, args[0].toString()->ensureLinear(cx)); - if (!locale) { + JS::AutoStableStringChars stableChars2(cx); + if (!stableChars2.initTwoByte(cx, str2)) { return false; } - bool isIgnorePunctuation; - if (!sharedIntlData.isIgnorePunctuation(cx, locale, &isIgnorePunctuation)) { - return false; - } + auto chars1 = stableChars1.twoByteRange(); + auto chars2 = stableChars2.twoByteRange(); - args.rval().setBoolean(isIgnorePunctuation); + result.setInt32(coll->CompareStrings(chars1, chars2)); return true; } diff --git a/js/src/builtin/intl/Collator.h b/js/src/builtin/intl/Collator.h index 843dd69def79c..84fb256e1dde1 100644 --- a/js/src/builtin/intl/Collator.h +++ b/js/src/builtin/intl/Collator.h @@ -136,36 +136,6 @@ class CollatorObject : public NativeObject { static void finalize(JS::GCContext* gcx, JSObject* obj); }; -/** - * Compares x and y (which must be String values), and returns a number less - * than 0 if x < y, 0 if x = y, or a number greater than 0 if x > y according - * to the sort order for the locale and collation options of the given - * Collator. - * - * Spec: ECMAScript Internationalization API Specification, 10.3.2. - * - * Usage: result = intl_CompareStrings(collator, x, y) - */ -[[nodiscard]] extern bool intl_CompareStrings(JSContext* cx, unsigned argc, - JS::Value* vp); - -/** - * Returns true if the given locale sorts upper-case before lower-case - * characters. - * - * Usage: result = intl_isUpperCaseFirst(locale) - */ -[[nodiscard]] extern bool intl_isUpperCaseFirst(JSContext* cx, unsigned argc, - JS::Value* vp); - -/** - * Returns true if the given locale ignores punctuation by default. - * - * Usage: result = intl_isIgnorePunctuation(locale) - */ -[[nodiscard]] extern bool intl_isIgnorePunctuation(JSContext* cx, unsigned argc, - JS::Value* vp); - namespace intl { /** @@ -183,6 +153,11 @@ namespace intl { JSContext* cx, JS::Handle locales, JS::Handle options); +/** + * Compares x and y, and returns a number less than 0 if x < y, 0 if x = y, or a + * number greater than 0 if x > y according to the sort order for the locale and + * collation options of the given Collator. + */ [[nodiscard]] extern bool CompareStrings(JSContext* cx, JS::Handle collator, JS::Handle str1, diff --git a/js/src/builtin/intl/Collator.js b/js/src/builtin/intl/Collator.js deleted file mode 100644 index c4c4f3048de21..0000000000000 --- a/js/src/builtin/intl/Collator.js +++ /dev/null @@ -1,353 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/* Portions Copyright Norbert Lindenberg 2011-2012. */ - -/** - * Compute an internal properties object from |lazyCollatorData|. - */ -function resolveCollatorInternals(lazyCollatorData) { - assert(IsObject(lazyCollatorData), "lazy data not an object?"); - - var internalProps = std_Object_create(null); - - // Step 5. - internalProps.usage = lazyCollatorData.usage; - - // Compute effective locale. - - // Steps 6-7 and 16-17. - var r = intl_ResolveLocale( - "Collator", - lazyCollatorData.requestedLocales, - lazyCollatorData.opt, - lazyCollatorData.usage === "search" - ); - - // Step 18. - internalProps.locale = r.locale; - - // Step 19. - var collation = r.co; - - // Step 20. - if (collation === null) { - collation = "default"; - } - - // Step 21. - internalProps.collation = collation; - - // Step 22. - internalProps.numeric = r.kn === "true"; - - // Step 23. - internalProps.caseFirst = r.kf; - - // Compute remaining collation options. - // Step 25. - var s = lazyCollatorData.rawSensitivity; - if (s === undefined) { - // In theory the default sensitivity for the "search" collator is - // locale dependent; in reality the CLDR/ICU default strength is - // always tertiary. Therefore use "variant" as the default value for - // both collation modes. - s = "variant"; - } - - // Step 26. - internalProps.sensitivity = s; - - // Step 28. - var ignorePunctuation = lazyCollatorData.ignorePunctuation; - if (ignorePunctuation === undefined) { - var actualLocale = collatorActualLocale(r.dataLocale); - ignorePunctuation = intl_isIgnorePunctuation(actualLocale); - } - internalProps.ignorePunctuation = ignorePunctuation; - - // The caller is responsible for associating |internalProps| with the right - // object using |setInternalProperties|. - return internalProps; -} - -/** - * Returns an object containing the Collator internal properties of |obj|. - */ -function getCollatorInternals(obj) { - assert(IsObject(obj), "getCollatorInternals called with non-object"); - assert( - intl_GuardToCollator(obj) !== null, - "getCollatorInternals called with non-Collator" - ); - - var internals = getIntlObjectInternals(obj); - assert( - internals.type === "Collator", - "bad type escaped getIntlObjectInternals" - ); - - // If internal properties have already been computed, use them. - var internalProps = maybeInternalProperties(internals); - if (internalProps) { - return internalProps; - } - - // Otherwise it's time to fully create them. - internalProps = resolveCollatorInternals(internals.lazyData); - setInternalProperties(internals, internalProps); - return internalProps; -} - -/** - * Initializes an object as a Collator. - * - * This method is complicated a moderate bit by its implementing initialization - * as a *lazy* concept. Everything that must happen now, does -- but we defer - * all the work we can until the object is actually used as a Collator. This - * later work occurs in |resolveCollatorInternals|; steps not noted here occur - * there. - * - * Spec: ECMAScript Internationalization API Specification, 10.1.1. - */ -function InitializeCollator(collator, locales, options) { - assert(IsObject(collator), "InitializeCollator called with non-object"); - assert( - intl_GuardToCollator(collator) !== null, - "InitializeCollator called with non-Collator" - ); - - // Lazy Collator data has the following structure: - // - // { - // requestedLocales: List of locales, - // usage: "sort" / "search", - // opt: // opt object computed in InitializeCollator - // { - // localeMatcher: "lookup" / "best fit", - // co: string matching a Unicode extension type / undefined - // kn: true / false / undefined, - // kf: "upper" / "lower" / "false" / undefined - // } - // rawSensitivity: "base" / "accent" / "case" / "variant" / undefined, - // ignorePunctuation: true / false / undefined - // } - // - // Note that lazy data is only installed as a final step of initialization, - // so every Collator lazy data object has *all* these properties, never a - // subset of them. - var lazyCollatorData = std_Object_create(null); - - // Step 1. - var requestedLocales = CanonicalizeLocaleList(locales); - lazyCollatorData.requestedLocales = requestedLocales; - - // Steps 2-3. - // - // If we ever need more speed here at startup, we should try to detect the - // case where |options === undefined| and then directly use the default - // value for each option. For now, just keep it simple. - if (options === undefined) { - options = std_Object_create(null); - } else { - options = ToObject(options); - } - - // Compute options that impact interpretation of locale. - // Step 4. - var u = GetOption(options, "usage", "string", ["sort", "search"], "sort"); - lazyCollatorData.usage = u; - - // Step 8. - var opt = NEW_RECORD(); - lazyCollatorData.opt = opt; - - // Steps 9-10. - var matcher = GetOption( - options, - "localeMatcher", - "string", - ["lookup", "best fit"], - "best fit" - ); - opt.localeMatcher = matcher; - - // https://github.com/tc39/ecma402/pull/459 - var collation = GetOption( - options, - "collation", - "string", - undefined, - undefined - ); - if (collation !== undefined) { - collation = intl_ValidateAndCanonicalizeUnicodeExtensionType( - collation, - "collation", - "co" - ); - } - opt.co = collation; - - // Steps 11-13. - var numericValue = GetOption( - options, - "numeric", - "boolean", - undefined, - undefined - ); - if (numericValue !== undefined) { - numericValue = numericValue ? "true" : "false"; - } - opt.kn = numericValue; - - // Steps 14-15. - var caseFirstValue = GetOption( - options, - "caseFirst", - "string", - ["upper", "lower", "false"], - undefined - ); - opt.kf = caseFirstValue; - - // Compute remaining collation options. - // Step 24. - var s = GetOption( - options, - "sensitivity", - "string", - ["base", "accent", "case", "variant"], - undefined - ); - lazyCollatorData.rawSensitivity = s; - - // Step 27. - var ip = GetOption(options, "ignorePunctuation", "boolean", undefined, undefined); - lazyCollatorData.ignorePunctuation = ip; - - // Step 29. - // - // We've done everything that must be done now: mark the lazy data as fully - // computed and install it. - initializeIntlObject(collator, "Collator", lazyCollatorData); -} - -/** - * Returns the actual locale used when a collator for |locale| is constructed. - */ -function collatorActualLocale(locale) { - assert(typeof locale === "string", "locale should be string"); - - // If |locale| is the default locale (e.g. da-DK), but only supported - // through a fallback (da), we need to get the actual locale before we - // can call intl_isUpperCaseFirst. Also see intl_BestAvailableLocale. - return BestAvailableLocaleIgnoringDefault("Collator", locale); -} - -/** - * Create function to be cached and returned by Intl.Collator.prototype.compare. - * - * Spec: ECMAScript Internationalization API Specification, 10.3.3.1. - */ -function createCollatorCompare(collator) { - // This function is not inlined in $Intl_Collator_compare_get to avoid - // creating a call-object on each call to $Intl_Collator_compare_get. - return function(x, y) { - // Step 1 (implicit). - - // Step 2. - assert(IsObject(collator), "collatorCompareToBind called with non-object"); - assert( - intl_GuardToCollator(collator) !== null, - "collatorCompareToBind called with non-Collator" - ); - - // Steps 3-6 - var X = ToString(x); - var Y = ToString(y); - - // Step 7. - return intl_CompareStrings(collator, X, Y); - }; -} - -/** - * Returns a function bound to this Collator that compares x (converted to a - * String value) and y (converted to a String value), - * and returns a number less than 0 if x < y, 0 if x = y, or a number greater - * than 0 if x > y according to the sort order for the locale and collation - * options of this Collator object. - * - * Spec: ECMAScript Internationalization API Specification, 10.3.3. - */ -// Uncloned functions with `$` prefix are allocated as extended function -// to store the original name in `SetCanonicalName`. -function $Intl_Collator_compare_get() { - // Step 1. - var collator = this; - - // Steps 2-3. - if ( - !IsObject(collator) || - (collator = intl_GuardToCollator(collator)) === null - ) { - return callFunction( - intl_CallCollatorMethodIfWrapped, - this, - "$Intl_Collator_compare_get" - ); - } - - var internals = getCollatorInternals(collator); - - // Step 4. - if (internals.boundCompare === undefined) { - // Steps 4.a-c. - internals.boundCompare = createCollatorCompare(collator); - } - - // Step 5. - return internals.boundCompare; -} -SetCanonicalName($Intl_Collator_compare_get, "get compare"); - -/** - * Returns the resolved options for a Collator object. - * - * Spec: ECMAScript Internationalization API Specification, 10.3.4. - */ -function Intl_Collator_resolvedOptions() { - // Step 1. - var collator = this; - - // Steps 2-3. - if ( - !IsObject(collator) || - (collator = intl_GuardToCollator(collator)) === null - ) { - return callFunction( - intl_CallCollatorMethodIfWrapped, - this, - "Intl_Collator_resolvedOptions" - ); - } - - var internals = getCollatorInternals(collator); - - // Steps 4-5. - var result = { - locale: internals.locale, - usage: internals.usage, - sensitivity: internals.sensitivity, - ignorePunctuation: internals.ignorePunctuation, - collation: internals.collation, - numeric: internals.numeric, - caseFirst: internals.caseFirst, - }; - - // Step 6. - return result; -} diff --git a/js/src/builtin/intl/CommonFunctions.js b/js/src/builtin/intl/CommonFunctions.js index ad57c643a2f22..5c168be6a9a07 100644 --- a/js/src/builtin/intl/CommonFunctions.js +++ b/js/src/builtin/intl/CommonFunctions.js @@ -258,8 +258,7 @@ function intlFallbackSymbol() { function initializeIntlObject(obj, type, lazyData) { assert(IsObject(obj), "Non-object passed to initializeIntlObject"); assert( - (type === "Collator" && intl_GuardToCollator(obj) !== null) || - (type === "DateTimeFormat" && intl_GuardToDateTimeFormat(obj) !== null) || + (type === "DateTimeFormat" && intl_GuardToDateTimeFormat(obj) !== null) || (type === "DurationFormat" && intl_GuardToDurationFormat(obj) !== null) || (type === "NumberFormat" && intl_GuardToNumberFormat(obj) !== null) || (type === "PluralRules" && intl_GuardToPluralRules(obj) !== null), @@ -271,7 +270,6 @@ function initializeIntlObject(obj, type, lazyData) { // // The .type property indicates the type of Intl object that |obj| is. It // must be one of: - // - Collator // - DateTimeFormat // - DurationFormat // - NumberFormat @@ -338,8 +336,7 @@ function maybeInternalProperties(internals) { function getIntlObjectInternals(obj) { assert(IsObject(obj), "getIntlObjectInternals called with non-Object"); assert( - intl_GuardToCollator(obj) !== null || - intl_GuardToDateTimeFormat(obj) !== null || + intl_GuardToDateTimeFormat(obj) !== null || intl_GuardToDurationFormat(obj) !== null || intl_GuardToNumberFormat(obj) !== null || intl_GuardToPluralRules(obj) !== null, @@ -351,8 +348,7 @@ function getIntlObjectInternals(obj) { assert(IsObject(internals), "internals not an object"); assert(hasOwn("type", internals), "missing type"); assert( - (internals.type === "Collator" && intl_GuardToCollator(obj) !== null) || - (internals.type === "DateTimeFormat" && + (internals.type === "DateTimeFormat" && intl_GuardToDateTimeFormat(obj) !== null) || (internals.type === "DurationFormat" && intl_GuardToDurationFormat(obj) !== null) || @@ -383,9 +379,7 @@ function getInternals(obj) { // Otherwise it's time to fully create them. var type = internals.type; - if (type === "Collator") { - internalProps = resolveCollatorInternals(internals.lazyData); - } else if (type === "DateTimeFormat") { + if (type === "DateTimeFormat") { internalProps = resolveDateTimeFormatInternals(internals.lazyData); } else if (type === "DurationFormat") { internalProps = resolveDurationFormatInternals(internals.lazyData); diff --git a/js/src/builtin/moz.build b/js/src/builtin/moz.build index 125d8c81b0ef9..abde0f665c6e5 100644 --- a/js/src/builtin/moz.build +++ b/js/src/builtin/moz.build @@ -89,7 +89,6 @@ selfhosted_inputs = [ ] + ( [ "intl/NumberingSystemsGenerated.h", - "intl/Collator.js", "intl/CommonFunctions.js", "intl/CurrencyDataGenerated.js", "intl/DateTimeFormat.js", diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp index 24c7a361ca323..51e37bca65f9f 100644 --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -13180,7 +13180,6 @@ AttachDecision InlinableNativeIRGenerator::tryAttachStub() { return tryAttachFunctionBind(); // Intl natives. - case InlinableNative::IntlGuardToCollator: case InlinableNative::IntlGuardToDateTimeFormat: case InlinableNative::IntlGuardToDurationFormat: case InlinableNative::IntlGuardToNumberFormat: diff --git a/js/src/jit/InlinableNatives.cpp b/js/src/jit/InlinableNatives.cpp index df8922bb85a3a..869f8fe12474e 100644 --- a/js/src/jit/InlinableNatives.cpp +++ b/js/src/jit/InlinableNatives.cpp @@ -7,7 +7,6 @@ #include "jit/InlinableNatives.h" #ifdef JS_HAS_INTL_API -# include "builtin/intl/Collator.h" # include "builtin/intl/DateTimeFormat.h" # include "builtin/intl/DurationFormat.h" # include "builtin/intl/NumberFormat.h" @@ -41,8 +40,6 @@ const JSClass* js::jit::InlinableNativeGuardToClass(InlinableNative native) { switch (native) { #ifdef JS_HAS_INTL_API // Intl natives. - case InlinableNative::IntlGuardToCollator: - return &CollatorObject::class_; case InlinableNative::IntlGuardToDateTimeFormat: return &DateTimeFormatObject::class_; case InlinableNative::IntlGuardToDurationFormat: @@ -56,7 +53,6 @@ const JSClass* js::jit::InlinableNativeGuardToClass(InlinableNative native) { case InlinableNative::IntlGuardToSegmentIterator: return &SegmentIteratorObject::class_; #else - case InlinableNative::IntlGuardToCollator: case InlinableNative::IntlGuardToDateTimeFormat: case InlinableNative::IntlGuardToNumberFormat: case InlinableNative::IntlGuardToPluralRules: @@ -164,7 +160,6 @@ bool js::jit::CanInlineNativeCrossRealm(InlinableNative native) { // RNG state is per-realm. return false; - case InlinableNative::IntlGuardToCollator: case InlinableNative::IntlGuardToDateTimeFormat: case InlinableNative::IntlGuardToDurationFormat: case InlinableNative::IntlGuardToNumberFormat: diff --git a/js/src/jit/InlinableNatives.h b/js/src/jit/InlinableNatives.h index 9087746c43006..f42ccb7a61d58 100644 --- a/js/src/jit/InlinableNatives.h +++ b/js/src/jit/InlinableNatives.h @@ -95,7 +95,6 @@ \ _(FunctionBind) \ \ - _(IntlGuardToCollator) \ _(IntlGuardToDateTimeFormat) \ _(IntlGuardToDurationFormat) \ _(IntlGuardToNumberFormat) \ diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index d3acf7ff43773..d4a7d795503e3 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -24,7 +24,6 @@ #include "builtin/Array.h" #include "builtin/BigInt.h" #ifdef JS_HAS_INTL_API -# include "builtin/intl/Collator.h" # include "builtin/intl/DateTimeFormat.h" # include "builtin/intl/DurationFormat.h" # include "builtin/intl/IntlObject.h" @@ -1844,8 +1843,6 @@ static const JSFunctionSpec intrinsic_functions[] = { // Intrinsics and standard functions used by Intl API implementation. #ifdef JS_HAS_INTL_API JS_FN("intl_BestAvailableLocale", intl_BestAvailableLocale, 3, 0), - JS_FN("intl_CallCollatorMethodIfWrapped", - CallNonGenericSelfhostedMethod>, 2, 0), JS_FN("intl_CallDateTimeFormatMethodIfWrapped", CallNonGenericSelfhostedMethod>, 2, 0), JS_FN("intl_CallDurationFormatMethodIfWrapped", @@ -1858,7 +1855,6 @@ static const JSFunctionSpec intrinsic_functions[] = { CallNonGenericSelfhostedMethod>, 2, 0), JS_FN("intl_CallSegmentsMethodIfWrapped", CallNonGenericSelfhostedMethod>, 2, 0), - JS_FN("intl_CompareStrings", intl_CompareStrings, 3, 0), JS_FN("intl_CreateSegmentIterator", intl_CreateSegmentIterator, 1, 0), JS_FN("intl_DefaultTimeZone", intrinsic_DefaultTimeZone, 0, 0), JS_FN("intl_FindNextSegmentBoundaries", intl_FindNextSegmentBoundaries, 1, @@ -1869,9 +1865,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("intl_FormatNumber", intl_FormatNumber, 3, 0), JS_FN("intl_FormatNumberRange", intl_FormatNumberRange, 4, 0), JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 1, 0), - JS_INLINABLE_FN("intl_GuardToCollator", - intrinsic_GuardToBuiltin, 1, 0, - IntlGuardToCollator), JS_INLINABLE_FN("intl_GuardToDateTimeFormat", intrinsic_GuardToBuiltin, 1, 0, IntlGuardToDateTimeFormat), @@ -1908,8 +1901,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("intl_availableMeasurementUnits", intl_availableMeasurementUnits, 0, 0), # endif - JS_FN("intl_isIgnorePunctuation", intl_isIgnorePunctuation, 1, 0), - JS_FN("intl_isUpperCaseFirst", intl_isUpperCaseFirst, 1, 0), JS_FN("intl_resolveDateTimeFormatComponents", intl_resolveDateTimeFormatComponents, 3, 0), #endif // JS_HAS_INTL_API From eff29d99caecaebb575df6b153a3d9636bebf002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Thu, 29 Jan 2026 07:42:54 +0000 Subject: [PATCH 005/116] Bug 2010221 - Part 1: Move Intl.DurationFormat initialisation to C++. r=spidermonkey-reviewers,mgaudet Differential Revision: https://phabricator.services.mozilla.com/D279036 --- js/src/builtin/intl/DurationFormat.cpp | 1302 +++++++++++++++--------- js/src/builtin/intl/DurationFormat.h | 106 +- 2 files changed, 894 insertions(+), 514 deletions(-) diff --git a/js/src/builtin/intl/DurationFormat.cpp b/js/src/builtin/intl/DurationFormat.cpp index d8250c23d2017..0816e6ce446cf 100644 --- a/js/src/builtin/intl/DurationFormat.cpp +++ b/js/src/builtin/intl/DurationFormat.cpp @@ -12,10 +12,12 @@ #include "mozilla/intl/DateTimeFormat.h" #include "mozilla/intl/ListFormat.h" #include "mozilla/intl/NumberFormat.h" +#include "mozilla/Maybe.h" #include "mozilla/Span.h" #include #include +#include #include "jspubtd.h" #include "NamespaceImports.h" @@ -26,6 +28,8 @@ #include "builtin/intl/ListFormat.h" #include "builtin/intl/LocaleNegotiation.h" #include "builtin/intl/NumberFormat.h" +#include "builtin/intl/ParameterNegotiation.h" +#include "builtin/intl/UsingEnum.h" #include "builtin/temporal/Duration.h" #include "gc/AllocKind.h" #include "gc/GCContext.h" @@ -47,12 +51,14 @@ using namespace js; using namespace js::intl; +using js::temporal::TemporalUnit; + static constexpr auto durationUnits = std::array{ - temporal::TemporalUnit::Year, temporal::TemporalUnit::Month, - temporal::TemporalUnit::Week, temporal::TemporalUnit::Day, - temporal::TemporalUnit::Hour, temporal::TemporalUnit::Minute, - temporal::TemporalUnit::Second, temporal::TemporalUnit::Millisecond, - temporal::TemporalUnit::Microsecond, temporal::TemporalUnit::Nanosecond, + TemporalUnit::Year, TemporalUnit::Month, + TemporalUnit::Week, TemporalUnit::Day, + TemporalUnit::Hour, TemporalUnit::Minute, + TemporalUnit::Second, TemporalUnit::Millisecond, + TemporalUnit::Microsecond, TemporalUnit::Nanosecond, }; const JSClass DurationFormatObject::class_ = { @@ -67,8 +73,13 @@ const JSClass DurationFormatObject::class_ = { const JSClass& DurationFormatObject::protoClass_ = PlainObject::class_; static bool durationFormat_format(JSContext* cx, unsigned argc, Value* vp); + static bool durationFormat_formatToParts(JSContext* cx, unsigned argc, Value* vp); + +static bool durationFormat_resolvedOptions(JSContext* cx, unsigned argc, + Value* vp); + static bool durationFormat_supportedLocalesOf(JSContext* cx, unsigned argc, Value* vp); @@ -84,8 +95,7 @@ static const JSFunctionSpec durationFormat_static_methods[] = { }; static const JSFunctionSpec durationFormat_methods[] = { - JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DurationFormat_resolvedOptions", - 0, 0), + JS_FN("resolvedOptions", durationFormat_resolvedOptions, 0, 0), JS_FN("format", durationFormat_format, 1, 0), JS_FN("formatToParts", durationFormat_formatToParts, 1, 0), JS_FN("toSource", durationFormat_toSource, 0, 0), @@ -143,6 +153,545 @@ void js::DurationFormatObject::finalize(JS::GCContext* gcx, JSObject* obj) { } } +static constexpr std::string_view DisplayToString(DurationDisplay display) { +#ifndef USING_ENUM + using enum DurationDisplay; +#else + USING_ENUM(DurationDisplay, Auto, Always); +#endif + switch (display) { + case Auto: + return "auto"; + case Always: + return "always"; + } + MOZ_CRASH("invalid duration format display"); +} + +static constexpr std::string_view DurationStyleToString(DurationStyle style) { +#ifndef USING_ENUM + using enum DurationStyle; +#else + USING_ENUM(DurationStyle, Long, Short, Narrow, Numeric, TwoDigit); +#endif + switch (style) { + case Long: + return "long"; + case Short: + return "short"; + case Narrow: + return "narrow"; + case Numeric: + return "numeric"; + case TwoDigit: + return "2-digit"; + } + MOZ_CRASH("invalid duration format style"); +} + +static constexpr std::string_view BaseStyleToString(DurationBaseStyle style) { +#ifndef USING_ENUM + using enum DurationBaseStyle; +#else + USING_ENUM(DurationBaseStyle, Long, Short, Narrow, Digital); +#endif + switch (style) { + case Long: + return "long"; + case Short: + return "short"; + case Narrow: + return "narrow"; + case Digital: + return "digital"; + } + MOZ_CRASH("invalid duration format base style"); +} + +/** + * Return the singular name for |unit|. + */ +static std::string_view SingularUnitName(TemporalUnit unit) { + switch (unit) { + case TemporalUnit::Year: + return "year"; + case TemporalUnit::Month: + return "month"; + case TemporalUnit::Week: + return "week"; + case TemporalUnit::Day: + return "day"; + case TemporalUnit::Hour: + return "hour"; + case TemporalUnit::Minute: + return "minute"; + case TemporalUnit::Second: + return "second"; + case TemporalUnit::Millisecond: + return "millisecond"; + case TemporalUnit::Microsecond: + return "microsecond"; + case TemporalUnit::Nanosecond: + return "nanosecond"; + case TemporalUnit::Unset: + case TemporalUnit::Auto: + break; + } + MOZ_CRASH("invalid temporal unit"); +} + +/** + * Return the plural name for |unit|. + */ +static std::string_view PluralUnitName(TemporalUnit unit) { + switch (unit) { + case TemporalUnit::Year: + return "years"; + case TemporalUnit::Month: + return "months"; + case TemporalUnit::Week: + return "weeks"; + case TemporalUnit::Day: + return "days"; + case TemporalUnit::Hour: + return "hours"; + case TemporalUnit::Minute: + return "minutes"; + case TemporalUnit::Second: + return "seconds"; + case TemporalUnit::Millisecond: + return "milliseconds"; + case TemporalUnit::Microsecond: + return "microseconds"; + case TemporalUnit::Nanosecond: + return "nanoseconds"; + case TemporalUnit::Unset: + case TemporalUnit::Auto: + break; + } + MOZ_CRASH("invalid temporal unit"); +} + +/** + * Return the "style" property name for |unit|. + */ +static Handle DurationStyleName(TemporalUnit unit, + JSContext* cx) { + switch (unit) { + case TemporalUnit::Year: + return cx->names().years; + case TemporalUnit::Month: + return cx->names().months; + case TemporalUnit::Week: + return cx->names().weeks; + case TemporalUnit::Day: + return cx->names().days; + case TemporalUnit::Hour: + return cx->names().hours; + case TemporalUnit::Minute: + return cx->names().minutes; + case TemporalUnit::Second: + return cx->names().seconds; + case TemporalUnit::Millisecond: + return cx->names().milliseconds; + case TemporalUnit::Microsecond: + return cx->names().microseconds; + case TemporalUnit::Nanosecond: + return cx->names().nanoseconds; + case TemporalUnit::Unset: + case TemporalUnit::Auto: + break; + } + MOZ_CRASH("invalid temporal unit"); +} + +/** + * Return the "display" property name for |unit|. + */ +static Handle DurationDisplayName(TemporalUnit unit, + JSContext* cx) { + switch (unit) { + case TemporalUnit::Year: + return cx->names().yearsDisplay; + case TemporalUnit::Month: + return cx->names().monthsDisplay; + case TemporalUnit::Week: + return cx->names().weeksDisplay; + case TemporalUnit::Day: + return cx->names().daysDisplay; + case TemporalUnit::Hour: + return cx->names().hoursDisplay; + case TemporalUnit::Minute: + return cx->names().minutesDisplay; + case TemporalUnit::Second: + return cx->names().secondsDisplay; + case TemporalUnit::Millisecond: + return cx->names().millisecondsDisplay; + case TemporalUnit::Microsecond: + return cx->names().microsecondsDisplay; + case TemporalUnit::Nanosecond: + return cx->names().nanosecondsDisplay; + case TemporalUnit::Unset: + case TemporalUnit::Auto: + break; + } + MOZ_CRASH("invalid temporal unit"); +} + +/** + * IsFractionalSecondUnitName ( unit ) + */ +static inline bool IsFractionalSecondUnitName(TemporalUnit unit) { + return TemporalUnit::Millisecond <= unit && unit <= TemporalUnit::Nanosecond; +} + +/** + * GetDurationUnitOptions ( unit, options, baseStyle, stylesList, digitalBase, + * prevStyle, twoDigitHours ) + */ +static bool GetDurationUnitOptions( + JSContext* cx, TemporalUnit unit, Handle options, + DurationBaseStyle baseStyle, DurationStyle digitalBase, + DurationStyle prevStyle, + std::pair* result) { + // Step 1. + mozilla::Maybe styleOption{}; + switch (unit) { + case TemporalUnit::Year: + case TemporalUnit::Month: + case TemporalUnit::Week: + case TemporalUnit::Day: { + static constexpr auto styles = MapOptions( + DurationStyle::Long, DurationStyle::Short, DurationStyle::Narrow); + if (!GetStringOption(cx, options, DurationStyleName(unit, cx), styles, + &styleOption)) { + return false; + } + break; + } + + case TemporalUnit::Hour: + case TemporalUnit::Minute: + case TemporalUnit::Second: { + static constexpr auto styles = MapOptions( + DurationStyle::Long, DurationStyle::Short, DurationStyle::Narrow, + DurationStyle::Numeric, DurationStyle::TwoDigit); + if (!GetStringOption(cx, options, DurationStyleName(unit, cx), styles, + &styleOption)) { + return false; + } + break; + } + + case TemporalUnit::Millisecond: + case TemporalUnit::Microsecond: + case TemporalUnit::Nanosecond: { + static constexpr auto styles = MapOptions( + DurationStyle::Long, DurationStyle::Short, DurationStyle::Narrow, + DurationStyle::Numeric); + if (!GetStringOption(cx, options, DurationStyleName(unit, cx), styles, + &styleOption)) { + return false; + } + break; + } + + case TemporalUnit::Unset: + case TemporalUnit::Auto: + MOZ_CRASH("invalid temporal unit"); + } + + // Step 2. + auto displayDefault = DurationDisplay::Always; + + // Step 3. + if (styleOption.isNothing()) { + // Step 3.a. + if (baseStyle == DurationBaseStyle::Digital) { + // Step 3.a.i. + styleOption = mozilla::Some(digitalBase); + + // Step 3.a.ii. + if (!(TemporalUnit::Hour <= unit && unit <= TemporalUnit::Second)) { + displayDefault = DurationDisplay::Auto; + } + } + + // Step 3.b. ("fractional" handled implicitly) + else if (prevStyle == DurationStyle::Numeric || + prevStyle == DurationStyle::TwoDigit) { + // Step 3.b.i. + styleOption = mozilla::Some(DurationStyle::Numeric); + + // Step 3.b.ii. + if (unit != TemporalUnit::Minute && unit != TemporalUnit::Second) { + displayDefault = DurationDisplay::Auto; + } + } + + // Step 3.c. + else { + // Step 3.c.i. + styleOption = mozilla::Some(static_cast(baseStyle)); + + // Step 3.c.ii. + displayDefault = DurationDisplay::Auto; + } + } + auto style = *styleOption; + + // Step 4. + bool isFractional = + style == DurationStyle::Numeric && IsFractionalSecondUnitName(unit); + if (isFractional) { + // Step 4.a. (Not applicable in our implementation) + + // Step 4.b. + displayDefault = DurationDisplay::Auto; + } + + // Steps 5-6. + static constexpr auto displays = MapOptions( + DurationDisplay::Auto, DurationDisplay::Always); + + mozilla::Maybe displayOption{}; + if (!GetStringOption(cx, options, DurationDisplayName(unit, cx), displays, + &displayOption)) { + return false; + } + auto display = displayOption.valueOr(displayDefault); + + // Step 7. (Inlined ValidateDurationUnitStyle) + + // ValidateDurationUnitStyle, step 1. + if (display == DurationDisplay::Always && isFractional) { + MOZ_ASSERT(styleOption.isSome() || displayOption.isSome(), + "no error is thrown when both 'style' and 'display' are absent"); + + JSErrNum errorNumber = + styleOption.isSome() && displayOption.isSome() + ? JSMSG_INTL_DURATION_INVALID_DISPLAY_OPTION + : displayOption.isSome() + ? JSMSG_INTL_DURATION_INVALID_DISPLAY_OPTION_DEFAULT_STYLE + : JSMSG_INTL_DURATION_INVALID_DISPLAY_OPTION_DEFAULT_DISPLAY; + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, + PluralUnitName(unit).data()); + return false; + } + + // ValidateDurationUnitStyle, steps 2-3. + if ((prevStyle == DurationStyle::Numeric || + prevStyle == DurationStyle::TwoDigit) && + !(style == DurationStyle::Numeric || style == DurationStyle::TwoDigit)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_INTL_DURATION_INVALID_NON_NUMERIC_OPTION, + PluralUnitName(unit).data(), + DurationStyleToString(style).data()); + return false; + } + + // Step 8. (Our implementation doesn't use |twoDigitHours|.) + + // Step 9. + if ((TemporalUnit::Minute == unit || unit == TemporalUnit::Second) && + (prevStyle == DurationStyle::Numeric || + prevStyle == DurationStyle::TwoDigit)) { + style = DurationStyle::TwoDigit; + } + + // Step 10. + *result = {style, display}; + return true; +} + +/** + * Intl.DurationFormat ( [ locales [ , options ] ] ) + */ +static bool InitializeDurationFormat( + JSContext* cx, Handle durationFormat, + const CallArgs& args) { + // Step 3. (Inlined ResolveOptions) + + // ResolveOptions, step 1. + Rooted requestedLocales(cx, cx); + if (!CanonicalizeLocaleList(cx, args.get(0), &requestedLocales)) { + return false; + } + + Rooted requestedLocalesArray( + cx, LocalesListToArray(cx, requestedLocales)); + if (!requestedLocalesArray) { + return false; + } + durationFormat->setRequestedLocales(requestedLocalesArray); + + auto dfOptions = cx->make_unique(); + if (!dfOptions) { + return false; + } + + if (args.hasDefined(1)) { + // ResolveOptions, steps 2-3. + Rooted options(cx, JS::ToObject(cx, args[1])); + if (!options) { + return false; + } + + // ResolveOptions, step 4. + LocaleMatcher matcher; + if (!GetLocaleMatcherOption(cx, options, &matcher)) { + return false; + } + + // ResolveOptions, step 5. + // + // This implementation only supports the "lookup" locale matcher, therefore + // the "localeMatcher" option doesn't need to be stored. + + // ResolveOptions, step 6. + Rooted numberingSystem(cx); + if (!GetUnicodeExtensionOption(cx, options, + UnicodeExtensionKey::NumberingSystem, + &numberingSystem)) { + return false; + } + if (numberingSystem) { + durationFormat->setNumberingSystem(numberingSystem); + } + + // ResolveOptions, step 7. (Not applicable) + + // ResolveOptions, step 8. (Performed in ResolveLocale) + + // ResolveOptions, step 9. (Return) + + // Step 4. (Not applicable when ResolveOptions is inlined.) + + // Steps 5-11. (Performed in ResolveLocale) + + // Steps 12-13. + static constexpr auto styles = MapOptions( + DurationBaseStyle::Long, DurationBaseStyle::Short, + DurationBaseStyle::Narrow, DurationBaseStyle::Digital); + DurationBaseStyle style; + if (!GetStringOption(cx, options, cx->names().style, styles, + DurationBaseStyle::Short, &style)) { + return false; + } + dfOptions->style = style; + + // Step 14. + // + // This implementation doesn't support passing an empty string for + // |prevStyle|. Using one of the textual styles has the same effect, so we + // use "long" here. + constexpr auto emptyPrevStyle = DurationStyle::Long; + + // Step 15. (Loop unrolled) + using DurationUnitOption = std::pair; + + DurationUnitOption years; + if (!GetDurationUnitOptions(cx, TemporalUnit::Year, options, style, + DurationStyle::Short, emptyPrevStyle, &years)) { + return false; + } + dfOptions->yearsStyle = years.first; + dfOptions->yearsDisplay = years.second; + + DurationUnitOption months; + if (!GetDurationUnitOptions(cx, TemporalUnit::Month, options, style, + DurationStyle::Short, emptyPrevStyle, + &months)) { + return false; + } + dfOptions->monthsStyle = months.first; + dfOptions->monthsDisplay = months.second; + + DurationUnitOption weeks; + if (!GetDurationUnitOptions(cx, TemporalUnit::Week, options, style, + DurationStyle::Short, emptyPrevStyle, &weeks)) { + return false; + } + dfOptions->weeksStyle = weeks.first; + dfOptions->weeksDisplay = weeks.second; + + DurationUnitOption days; + if (!GetDurationUnitOptions(cx, TemporalUnit::Day, options, style, + DurationStyle::Short, emptyPrevStyle, &days)) { + return false; + } + dfOptions->daysStyle = days.first; + dfOptions->daysDisplay = days.second; + + DurationUnitOption hours; + if (!GetDurationUnitOptions(cx, TemporalUnit::Hour, options, style, + DurationStyle::Numeric, emptyPrevStyle, + &hours)) { + return false; + } + dfOptions->hoursStyle = hours.first; + dfOptions->hoursDisplay = hours.second; + + DurationUnitOption minutes; + if (!GetDurationUnitOptions(cx, TemporalUnit::Minute, options, style, + DurationStyle::Numeric, hours.first, + &minutes)) { + return false; + } + dfOptions->minutesStyle = minutes.first; + dfOptions->minutesDisplay = minutes.second; + + DurationUnitOption seconds; + if (!GetDurationUnitOptions(cx, TemporalUnit::Second, options, style, + DurationStyle::Numeric, minutes.first, + &seconds)) { + return false; + } + dfOptions->secondsStyle = seconds.first; + dfOptions->secondsDisplay = seconds.second; + + DurationUnitOption milliseconds; + if (!GetDurationUnitOptions(cx, TemporalUnit::Millisecond, options, style, + DurationStyle::Numeric, seconds.first, + &milliseconds)) { + return false; + } + dfOptions->millisecondsStyle = milliseconds.first; + dfOptions->millisecondsDisplay = milliseconds.second; + + DurationUnitOption microseconds; + if (!GetDurationUnitOptions(cx, TemporalUnit::Microsecond, options, style, + DurationStyle::Numeric, milliseconds.first, + µseconds)) { + return false; + } + dfOptions->microsecondsStyle = microseconds.first; + dfOptions->microsecondsDisplay = microseconds.second; + + DurationUnitOption nanoseconds; + if (!GetDurationUnitOptions(cx, TemporalUnit::Nanosecond, options, style, + DurationStyle::Numeric, microseconds.first, + &nanoseconds)) { + return false; + } + dfOptions->nanosecondsStyle = nanoseconds.first; + dfOptions->nanosecondsDisplay = nanoseconds.second; + + // Step 16. + mozilla::Maybe fractionalDigits{}; + if (!GetNumberOption(cx, options, cx->names().fractionalDigits, 0, 9, + &fractionalDigits)) { + return false; + } + dfOptions->fractionalDigits = fractionalDigits.valueOr(-1); + } + durationFormat->setOptions(dfOptions.release()); + AddCellMemory(durationFormat, sizeof(DurationFormatOptions), + MemoryUse::IntlOptions); + + return true; +} + /** * Intl.DurationFormat ( [ locales [ , options ] ] ) */ @@ -155,7 +704,7 @@ static bool DurationFormat(JSContext* cx, unsigned argc, Value* vp) { } // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). - RootedObject proto(cx); + Rooted proto(cx); if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_DurationFormat, &proto)) { return false; @@ -167,51 +716,86 @@ static bool DurationFormat(JSContext* cx, unsigned argc, Value* vp) { return false; } - HandleValue locales = args.get(0); - HandleValue options = args.get(1); - - // Steps 3-28. - if (!InitializeObject(cx, durationFormat, - cx->names().InitializeDurationFormat, locales, - options)) { + // Steps 3-16. + if (!InitializeDurationFormat(cx, durationFormat, args)) { return false; } + // Step 17. args.rval().setObject(*durationFormat); return true; } /** - * Returns the time separator string for the given locale and numbering system. + * Resolve the actual locale to finish initialization of the DurationFormat. */ -static JSString* GetTimeSeparator( - JSContext* cx, Handle durationFormat) { - if (auto* separator = durationFormat->getTimeSeparator()) { - return separator; +static bool ResolveLocale(JSContext* cx, + Handle durationFormat) { + // Return if the locale was already resolved. + if (durationFormat->isLocaleResolved()) { + return true; } - Rooted internals(cx, GetInternalsObject(cx, durationFormat)); - if (!internals) { - return nullptr; + Rooted requestedLocales( + cx, &durationFormat->getRequestedLocales()->as()); + + // %Intl.DurationFormat%.[[RelevantExtensionKeys]] is « "nu" ». + mozilla::EnumSet relevantExtensionKeys{ + UnicodeExtensionKey::NumberingSystem, + }; + + // Initialize locale options from constructor arguments. + Rooted localeOptions(cx); + if (auto* nu = durationFormat->getNumberingSystem()) { + localeOptions.setUnicodeExtension(UnicodeExtensionKey::NumberingSystem, nu); } - Rooted value(cx); + // Use the default locale data. + auto localeData = LocaleData::Default; - if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) { - return nullptr; + // Resolve the actual locale. + Rooted resolved(cx); + if (!ResolveLocale(cx, AvailableLocaleKind::DurationFormat, requestedLocales, + localeOptions, relevantExtensionKeys, localeData, + &resolved)) { + return false; } - UniqueChars locale = EncodeLocale(cx, value.toString()); + // Finish initialization by setting the actual locale and numbering system. + auto* locale = resolved.toLocale(cx); if (!locale) { + return false; + } + durationFormat->setLocale(locale); + + auto nu = resolved.extension(UnicodeExtensionKey::NumberingSystem); + MOZ_ASSERT(nu, "resolved numbering system is non-null"); + durationFormat->setNumberingSystem(nu); + + MOZ_ASSERT(durationFormat->isLocaleResolved(), + "locale successfully resolved"); + return true; +} + +/** + * Returns the time separator string for the given locale and numbering system. + */ +static JSString* GetTimeSeparator( + JSContext* cx, Handle durationFormat) { + if (auto* separator = durationFormat->getTimeSeparator()) { + return separator; + } + + if (!ResolveLocale(cx, durationFormat)) { return nullptr; } - if (!GetProperty(cx, internals, internals, cx->names().numberingSystem, - &value)) { + auto locale = EncodeLocale(cx, durationFormat->getLocale()); + if (!locale) { return nullptr; } - UniqueChars numberingSystem = EncodeAscii(cx, value.toString()); + auto numberingSystem = EncodeAscii(cx, durationFormat->getNumberingSystem()); if (!numberingSystem) { return nullptr; } @@ -242,169 +826,76 @@ struct DurationValue { /* sign */ 1 + /* seconds part */ 16 + /* decimal dot */ 1 + - /* nanoseconds part */ 9; - - // Next power of two after `MaximumDecimalStringLength`. - static constexpr size_t DecimalStringCapacity = 32; - - double number = 0; - char decimal[DecimalStringCapacity] = {}; - - explicit DurationValue() = default; - explicit DurationValue(double number) : number(number) {} - - bool isNegative() const { - return mozilla::IsNegative(number) || decimal[0] == '-'; - } - - auto abs() const { - // Return unchanged if not negative. - if (!isNegative()) { - return *this; - } - - // Call |std::abs| for non-decimal values. - if (!isDecimal()) { - return DurationValue{std::abs(number)}; - } - - // Copy decimal strings without the leading '-' sign character. - auto result = DurationValue{}; - std::copy(std::next(decimal), std::end(decimal), result.decimal); - return result; - } - - // |number| is active by default unless |decimal| is used. - bool isDecimal() const { return decimal[0] != '\0'; } - - // Return true if this value represents either +0 or -0. - bool isZero() const { return number == 0 && !isDecimal(); } - - operator std::string_view() const { - MOZ_ASSERT(isDecimal()); - return {decimal}; - } -}; - -/** - * Return the |unit| value from |duration|. - */ -static auto ToDurationValue(const temporal::Duration& duration, - temporal::TemporalUnit unit) { - using namespace temporal; - - switch (unit) { - case TemporalUnit::Year: - return DurationValue{duration.years}; - case TemporalUnit::Month: - return DurationValue{duration.months}; - case TemporalUnit::Week: - return DurationValue{duration.weeks}; - case TemporalUnit::Day: - return DurationValue{duration.days}; - case TemporalUnit::Hour: - return DurationValue{duration.hours}; - case TemporalUnit::Minute: - return DurationValue{duration.minutes}; - case TemporalUnit::Second: - return DurationValue{duration.seconds}; - case TemporalUnit::Millisecond: - return DurationValue{duration.milliseconds}; - case TemporalUnit::Microsecond: - return DurationValue{duration.microseconds}; - case TemporalUnit::Nanosecond: - return DurationValue{duration.nanoseconds}; - case TemporalUnit::Unset: - case TemporalUnit::Auto: - break; - } - MOZ_CRASH("invalid temporal unit"); -} - -/** - * Return the "display" property name for |unit|. - */ -static PropertyName* DurationDisplayName(temporal::TemporalUnit unit, - JSContext* cx) { - using namespace temporal; - - switch (unit) { - case TemporalUnit::Year: - return cx->names().yearsDisplay; - case TemporalUnit::Month: - return cx->names().monthsDisplay; - case TemporalUnit::Week: - return cx->names().weeksDisplay; - case TemporalUnit::Day: - return cx->names().daysDisplay; - case TemporalUnit::Hour: - return cx->names().hoursDisplay; - case TemporalUnit::Minute: - return cx->names().minutesDisplay; - case TemporalUnit::Second: - return cx->names().secondsDisplay; - case TemporalUnit::Millisecond: - return cx->names().millisecondsDisplay; - case TemporalUnit::Microsecond: - return cx->names().microsecondsDisplay; - case TemporalUnit::Nanosecond: - return cx->names().nanosecondsDisplay; - case TemporalUnit::Unset: - case TemporalUnit::Auto: - break; + /* nanoseconds part */ 9; + + // Next power of two after `MaximumDecimalStringLength`. + static constexpr size_t DecimalStringCapacity = 32; + + double number = 0; + char decimal[DecimalStringCapacity] = {}; + + explicit DurationValue() = default; + explicit DurationValue(double number) : number(number) {} + + bool isNegative() const { + return mozilla::IsNegative(number) || decimal[0] == '-'; } - MOZ_CRASH("invalid temporal unit"); -} -/** - * Convert |value|, which must be a string, to a |DurationDisplay|. - */ -static bool ToDurationDisplay(JSContext* cx, const Value& value, - DurationDisplay* result) { - MOZ_ASSERT(value.isString()); + auto abs() const { + // Return unchanged if not negative. + if (!isNegative()) { + return *this; + } - auto* linear = value.toString()->ensureLinear(cx); - if (!linear) { - return false; + // Call |std::abs| for non-decimal values. + if (!isDecimal()) { + return DurationValue{std::abs(number)}; + } + + // Copy decimal strings without the leading '-' sign character. + auto result = DurationValue{}; + std::copy(std::next(decimal), std::end(decimal), result.decimal); + return result; } - if (StringEqualsAscii(linear, "auto")) { - *result = DurationDisplay::Auto; - } else { - MOZ_ASSERT(StringEqualsAscii(linear, "always")); - *result = DurationDisplay::Always; + // |number| is active by default unless |decimal| is used. + bool isDecimal() const { return decimal[0] != '\0'; } + + // Return true if this value represents either +0 or -0. + bool isZero() const { return number == 0 && !isDecimal(); } + + operator std::string_view() const { + MOZ_ASSERT(isDecimal()); + return {decimal}; } - return true; -} +}; /** - * Return the "style" property name for |unit|. + * Return the |unit| value from |duration|. */ -static PropertyName* DurationStyleName(temporal::TemporalUnit unit, - JSContext* cx) { - using namespace temporal; - +static auto ToDurationValue(const temporal::Duration& duration, + TemporalUnit unit) { switch (unit) { case TemporalUnit::Year: - return cx->names().yearsStyle; + return DurationValue{duration.years}; case TemporalUnit::Month: - return cx->names().monthsStyle; + return DurationValue{duration.months}; case TemporalUnit::Week: - return cx->names().weeksStyle; + return DurationValue{duration.weeks}; case TemporalUnit::Day: - return cx->names().daysStyle; + return DurationValue{duration.days}; case TemporalUnit::Hour: - return cx->names().hoursStyle; + return DurationValue{duration.hours}; case TemporalUnit::Minute: - return cx->names().minutesStyle; + return DurationValue{duration.minutes}; case TemporalUnit::Second: - return cx->names().secondsStyle; + return DurationValue{duration.seconds}; case TemporalUnit::Millisecond: - return cx->names().millisecondsStyle; + return DurationValue{duration.milliseconds}; case TemporalUnit::Microsecond: - return cx->names().microsecondsStyle; + return DurationValue{duration.microseconds}; case TemporalUnit::Nanosecond: - return cx->names().nanosecondsStyle; + return DurationValue{duration.nanoseconds}; case TemporalUnit::Unset: case TemporalUnit::Auto: break; @@ -412,33 +903,6 @@ static PropertyName* DurationStyleName(temporal::TemporalUnit unit, MOZ_CRASH("invalid temporal unit"); } -/** - * Convert |value|, which must be a string, to a |DurationStyle|. - */ -static bool ToDurationStyle(JSContext* cx, const Value& value, - DurationStyle* result) { - MOZ_ASSERT(value.isString()); - - auto* linear = value.toString()->ensureLinear(cx); - if (!linear) { - return false; - } - - if (StringEqualsAscii(linear, "long")) { - *result = DurationStyle::Long; - } else if (StringEqualsAscii(linear, "short")) { - *result = DurationStyle::Short; - } else if (StringEqualsAscii(linear, "narrow")) { - *result = DurationStyle::Narrow; - } else if (StringEqualsAscii(linear, "numeric")) { - *result = DurationStyle::Numeric; - } else { - MOZ_ASSERT(StringEqualsAscii(linear, "2-digit")); - *result = DurationStyle::TwoDigit; - } - return true; -} - /** * Return the fractional digits setting from |durationFormat|. */ @@ -457,9 +921,7 @@ static std::pair GetFractionalDigits( } static DurationUnitOptions GetUnitOptions(const DurationFormatOptions& options, - temporal::TemporalUnit unit) { - using namespace temporal; - + TemporalUnit unit) { switch (unit) { #define GET_UNIT_OPTIONS(name) \ DurationUnitOptions { options.name##Display, options.name##Style } @@ -493,167 +955,27 @@ static DurationUnitOptions GetUnitOptions(const DurationFormatOptions& options, MOZ_CRASH("invalid duration unit"); } -static void SetUnitOptions(DurationFormatOptions& options, - temporal::TemporalUnit unit, - const DurationUnitOptions& unitOptions) { - using namespace temporal; - - switch (unit) { -#define SET_UNIT_OPTIONS(name) \ - do { \ - options.name##Display = unitOptions.display_; \ - options.name##Style = unitOptions.style_; \ - } while (0) - - case TemporalUnit::Year: - SET_UNIT_OPTIONS(years); - return; - case TemporalUnit::Month: - SET_UNIT_OPTIONS(months); - return; - case TemporalUnit::Week: - SET_UNIT_OPTIONS(weeks); - return; - case TemporalUnit::Day: - SET_UNIT_OPTIONS(days); - return; - case TemporalUnit::Hour: - SET_UNIT_OPTIONS(hours); - return; - case TemporalUnit::Minute: - SET_UNIT_OPTIONS(minutes); - return; - case TemporalUnit::Second: - SET_UNIT_OPTIONS(seconds); - return; - case TemporalUnit::Millisecond: - SET_UNIT_OPTIONS(milliseconds); - return; - case TemporalUnit::Microsecond: - SET_UNIT_OPTIONS(microseconds); - return; - case TemporalUnit::Nanosecond: - SET_UNIT_OPTIONS(nanoseconds); - return; - case TemporalUnit::Unset: - case TemporalUnit::Auto: - break; - -#undef SET_UNIT_OPTIONS - } - MOZ_CRASH("invalid duration unit"); -} - -static DurationFormatOptions* NewDurationFormatOptions( - JSContext* cx, Handle durationFormat) { - Rooted internals(cx, GetInternalsObject(cx, durationFormat)); - if (!internals) { - return nullptr; - } - - auto options = cx->make_unique(); - if (!options) { - return nullptr; - } - - Rooted value(cx); - for (temporal::TemporalUnit unit : durationUnits) { - DurationDisplay display; - if (!GetProperty(cx, internals, internals, DurationDisplayName(unit, cx), - &value)) { - return nullptr; - } - if (!ToDurationDisplay(cx, value, &display)) { - return nullptr; - } - - DurationStyle style; - if (!GetProperty(cx, internals, internals, DurationStyleName(unit, cx), - &value)) { - return nullptr; - } - if (!ToDurationStyle(cx, value, &style)) { - return nullptr; - } - - SetUnitOptions(*options, unit, - DurationUnitOptions{static_cast(display), - static_cast(style)}); - } - - if (!GetProperty(cx, internals, internals, cx->names().fractionalDigits, - &value)) { - return nullptr; - } - if (value.isUndefined()) { - options->fractionalDigits = -1; - } else { - options->fractionalDigits = value.toInt32(); - } - - return options.release(); -} - -static DurationFormatOptions* GetOrCreateDurationFormatOptions( - JSContext* cx, Handle durationFormat) { - auto* options = durationFormat->getOptions(); - if (options) { - return options; - } - - options = NewDurationFormatOptions(cx, durationFormat); - if (!options) { +/** + * Create a `mozilla::intl::NumberFormat` instance based on + * |durationFormat.locale| and |options|. + */ +static mozilla::intl::NumberFormat* NewDurationNumberFormat( + JSContext* cx, Handle durationFormat, + const mozilla::intl::NumberFormatOptions& options) { + if (!ResolveLocale(cx, durationFormat)) { return nullptr; } - durationFormat->setOptions(options); - - AddCellMemory(durationFormat, sizeof(DurationFormatOptions), - MemoryUse::IntlOptions); - return options; -} -/** - * Return the locale for `mozilla::intl::NumberFormat` objects. - */ -static UniqueChars NewDurationNumberFormatLocale( - JSContext* cx, Handle durationFormat) { // ICU expects numberingSystem as a Unicode locale extensions on locale. - Rooted internals(cx, GetInternalsObject(cx, durationFormat)); - if (!internals) { - return nullptr; - } + Rooted localeStr(cx, durationFormat->getLocale()); JS::RootedVector keywords(cx); - - Rooted value(cx); - if (!GetProperty(cx, internals, internals, cx->names().numberingSystem, - &value)) { + if (!keywords.emplaceBack("nu", durationFormat->getNumberingSystem())) { return nullptr; } - { - auto* numberingSystem = value.toString()->ensureLinear(cx); - if (!numberingSystem) { - return nullptr; - } - - if (!keywords.emplaceBack("nu", numberingSystem)) { - return nullptr; - } - } - - return FormatLocale(cx, internals, keywords); -} - -/** - * Create a `mozilla::intl::NumberFormat` instance based on |internals.locale| - * and |options|. - */ -static mozilla::intl::NumberFormat* NewDurationNumberFormat( - JSContext* cx, Handle durationFormat, - const mozilla::intl::NumberFormatOptions& options) { - auto locale = NewDurationNumberFormatLocale(cx, durationFormat); + auto locale = FormatLocale(cx, localeStr, keywords); if (!locale) { return nullptr; } @@ -669,43 +991,7 @@ static mozilla::intl::NumberFormat* NewDurationNumberFormat( /** * Return the singular name for |unit|. */ -static std::string_view UnitName(temporal::TemporalUnit unit) { - using namespace temporal; - - switch (unit) { - case TemporalUnit::Year: - return "year"; - case TemporalUnit::Month: - return "month"; - case TemporalUnit::Week: - return "week"; - case TemporalUnit::Day: - return "day"; - case TemporalUnit::Hour: - return "hour"; - case TemporalUnit::Minute: - return "minute"; - case TemporalUnit::Second: - return "second"; - case TemporalUnit::Millisecond: - return "millisecond"; - case TemporalUnit::Microsecond: - return "microsecond"; - case TemporalUnit::Nanosecond: - return "nanosecond"; - case TemporalUnit::Unset: - case TemporalUnit::Auto: - break; - } - MOZ_CRASH("invalid temporal unit"); -} - -/** - * Return the singular name for |unit|. - */ -static auto PartUnitName(temporal::TemporalUnit unit) { - using namespace temporal; - +static auto PartUnitName(TemporalUnit unit) { switch (unit) { case TemporalUnit::Year: return &JSAtomState::year; @@ -763,15 +1049,13 @@ static auto UnitDisplay(DurationStyle style) { * decimal string when the fractional part is non-zero. */ static auto ComputeFractionalDigits(const temporal::Duration& duration, - temporal::TemporalUnit unit) { - using namespace temporal; - + TemporalUnit unit) { MOZ_ASSERT(IsValidDuration(duration)); MOZ_ASSERT(TemporalUnit::Second <= unit && unit <= TemporalUnit::Microsecond); // Directly return the duration amount when no sub-seconds are present, i.e. // the fractional part is zero. - TimeDuration timeDuration; + temporal::TimeDuration timeDuration; int32_t exponent; switch (unit) { case TemporalUnit::Second: { @@ -779,7 +1063,7 @@ static auto ComputeFractionalDigits(const temporal::Duration& duration, duration.nanoseconds == 0) { return DurationValue{duration.seconds}; } - timeDuration = TimeDurationFromComponents({ + timeDuration = temporal::TimeDurationFromComponents({ 0, 0, 0, @@ -799,7 +1083,7 @@ static auto ComputeFractionalDigits(const temporal::Duration& duration, if (duration.microseconds == 0 && duration.nanoseconds == 0) { return DurationValue{duration.milliseconds}; } - timeDuration = TimeDurationFromComponents({ + timeDuration = temporal::TimeDurationFromComponents({ 0, 0, 0, @@ -819,7 +1103,7 @@ static auto ComputeFractionalDigits(const temporal::Duration& duration, if (duration.nanoseconds == 0) { return DurationValue{duration.microseconds}; } - timeDuration = TimeDurationFromComponents({ + timeDuration = temporal::TimeDurationFromComponents({ 0, 0, 0, @@ -846,7 +1130,7 @@ static auto ComputeFractionalDigits(const temporal::Duration& duration, char* chars = result.decimal; // Leading '-' sign when the duration is negative. - if (timeDuration < TimeDuration{}) { + if (timeDuration < temporal::TimeDuration{}) { *chars++ = '-'; timeDuration = timeDuration.abs(); } @@ -890,7 +1174,7 @@ static auto ComputeFractionalDigits(const temporal::Duration& duration, */ static mozilla::intl::NumberFormat* NewNumericFormatter( JSContext* cx, Handle durationFormat, - temporal::TemporalUnit unit) { + TemporalUnit unit) { // FormatNumericHours, step 1. (Not applicable in our implementation.) // FormatNumericMinutes, steps 1-2. (Not applicable in our implementation.) // FormatNumericSeconds, steps 1-2. (Not applicable in our implementation.) @@ -901,7 +1185,7 @@ static mozilla::intl::NumberFormat* NewNumericFormatter( auto* dfOptions = durationFormat->getOptions(); MOZ_ASSERT(dfOptions, "unexpected unresolved duration format options"); - auto style = GetUnitOptions(*dfOptions, unit).style(); + auto style = GetUnitOptions(*dfOptions, unit).style; // FormatNumericHours, step 3. // FormatNumericMinutes, step 4. @@ -935,7 +1219,7 @@ static mozilla::intl::NumberFormat* NewNumericFormatter( options.mGrouping = mozilla::intl::NumberFormatOptions::Grouping::Never; // FormatNumericSeconds, steps 11-14. - if (unit == temporal::TemporalUnit::Second) { + if (unit == TemporalUnit::Second) { // FormatNumericSeconds, step 11. auto fractionalDigits = GetFractionalDigits(durationFormat); @@ -955,7 +1239,7 @@ static mozilla::intl::NumberFormat* NewNumericFormatter( static mozilla::intl::NumberFormat* GetOrCreateNumericFormatter( JSContext* cx, Handle durationFormat, - temporal::TemporalUnit unit) { + TemporalUnit unit) { // Obtain a cached mozilla::intl::NumberFormat object. auto* nf = durationFormat->getNumberFormat(unit); if (nf) { @@ -976,9 +1260,7 @@ static mozilla::intl::NumberFormat* GetOrCreateNumericFormatter( * NextUnitFractional ( durationFormat, unit ) */ static bool NextUnitFractional(const DurationFormatObject* durationFormat, - temporal::TemporalUnit unit) { - using namespace temporal; - + TemporalUnit unit) { // Steps 1-3. if (TemporalUnit::Second <= unit && unit <= TemporalUnit::Microsecond) { auto* options = durationFormat->getOptions(); @@ -988,7 +1270,7 @@ static bool NextUnitFractional(const DurationFormatObject* durationFormat, auto nextUnit = static_cast(static_cast(unit) + 1); - auto nextStyle = GetUnitOptions(*options, nextUnit).style(); + auto nextStyle = GetUnitOptions(*options, nextUnit).style; return nextStyle == DurationStyle::Numeric; } @@ -1001,7 +1283,7 @@ static bool NextUnitFractional(const DurationFormatObject* durationFormat, */ static mozilla::intl::NumberFormat* NewNumberFormat( JSContext* cx, Handle durationFormat, - temporal::TemporalUnit unit, DurationStyle style) { + TemporalUnit unit, DurationStyle style) { // Step 4.h.i. mozilla::intl::NumberFormatOptions options{}; @@ -1017,7 +1299,8 @@ static mozilla::intl::NumberFormat* NewNumberFormat( } // Steps 4.h.iii.4-6. - options.mUnit = mozilla::Some(std::pair{UnitName(unit), UnitDisplay(style)}); + options.mUnit = + mozilla::Some(std::pair{SingularUnitName(unit), UnitDisplay(style)}); // Step 4.h.iii.7. return NewDurationNumberFormat(cx, durationFormat, options); @@ -1025,14 +1308,13 @@ static mozilla::intl::NumberFormat* NewNumberFormat( static mozilla::intl::NumberFormat* GetOrCreateNumberFormat( JSContext* cx, Handle durationFormat, - temporal::TemporalUnit unit, DurationStyle style) { + TemporalUnit unit, DurationStyle style) { // Obtain a cached mozilla::intl::NumberFormat object. - auto* nf = durationFormat->getNumberFormat(unit); - if (nf) { + if (auto* nf = durationFormat->getNumberFormat(unit)) { return nf; } - nf = NewNumberFormat(cx, durationFormat, unit, style); + auto* nf = NewNumberFormat(cx, durationFormat, unit, style); if (!nf) { return nullptr; } @@ -1054,7 +1336,7 @@ static JSLinearString* FormatDurationValueToString( static ArrayObject* FormatDurationValueToParts(JSContext* cx, mozilla::intl::NumberFormat* nf, const DurationValue& value, - temporal::TemporalUnit unit) { + TemporalUnit unit) { if (value.isDecimal()) { return FormatNumberToParts(cx, nf, std::string_view{value}, PartUnitName(unit)); @@ -1063,8 +1345,8 @@ static ArrayObject* FormatDurationValueToParts(JSContext* cx, } static bool FormatDurationValue(JSContext* cx, mozilla::intl::NumberFormat* nf, - temporal::TemporalUnit unit, - const DurationValue& value, bool formatToParts, + TemporalUnit unit, const DurationValue& value, + bool formatToParts, MutableHandle result) { if (!formatToParts) { auto* str = FormatDurationValueToString(cx, nf, value); @@ -1093,10 +1375,9 @@ static bool FormatDurationValue(JSContext* cx, mozilla::intl::NumberFormat* nf, */ static bool FormatNumericHoursOrMinutesOrSeconds( JSContext* cx, Handle durationFormat, - temporal::TemporalUnit unit, const DurationValue& value, bool formatToParts, + TemporalUnit unit, const DurationValue& value, bool formatToParts, MutableHandle result) { - MOZ_ASSERT(temporal::TemporalUnit::Hour <= unit && - unit <= temporal::TemporalUnit::Second); + MOZ_ASSERT(TemporalUnit::Hour <= unit && unit <= TemporalUnit::Second); // FormatNumericHours, steps 1-10. // FormatNumericMinutes, steps 1-11. @@ -1133,11 +1414,9 @@ static PlainObject* NewLiteralPart(JSContext* cx, JSString* value) { static bool FormatNumericUnits(JSContext* cx, Handle durationFormat, const temporal::Duration& duration, - temporal::TemporalUnit firstNumericUnit, + TemporalUnit firstNumericUnit, bool signDisplayed, bool formatToParts, MutableHandle result) { - using namespace temporal; - auto* options = durationFormat->getOptions(); MOZ_ASSERT(options, "unexpected unresolved duration format options"); @@ -1158,21 +1437,19 @@ static bool FormatNumericUnits(JSContext* cx, auto hoursValue = DurationValue{duration.hours}; // Step 4. - auto hoursDisplay = GetUnitOptions(*options, TemporalUnit::Hour).display(); + auto hoursDisplay = GetUnitOptions(*options, TemporalUnit::Hour).display; // Step 5. auto minutesValue = DurationValue{duration.minutes}; // Step 6. - auto minutesDisplay = - GetUnitOptions(*options, TemporalUnit::Minute).display(); + auto minutesDisplay = GetUnitOptions(*options, TemporalUnit::Minute).display; // Step 7-8. auto secondsValue = ComputeFractionalDigits(duration, TemporalUnit::Second); // Step 9. - auto secondsDisplay = - GetUnitOptions(*options, TemporalUnit::Second).display(); + auto secondsDisplay = GetUnitOptions(*options, TemporalUnit::Second).display; // Step 10. bool hoursFormatted = false; @@ -1353,47 +1630,41 @@ static bool FormatNumericUnits(JSContext* cx, return true; } +static auto ToListFormatStyle(DurationBaseStyle style) { +#ifndef USING_ENUM + using enum mozilla::intl::ListFormat::Style; +#else + USING_ENUM(mozilla::intl::ListFormat::Style, Long, Short, Narrow); +#endif + switch (style) { + case DurationBaseStyle::Long: + return Long; + case DurationBaseStyle::Short: + return Short; + case DurationBaseStyle::Narrow: + return Narrow; + case DurationBaseStyle::Digital: + return Short; + } + MOZ_CRASH("invalid duration format base style"); +} + static mozilla::intl::ListFormat* NewDurationListFormat( JSContext* cx, Handle durationFormat) { - Rooted internals(cx, GetInternalsObject(cx, durationFormat)); - if (!internals) { - return nullptr; - } - - Rooted value(cx); - if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) { + if (!ResolveLocale(cx, durationFormat)) { return nullptr; } + auto dfOptions = *durationFormat->getOptions(); - UniqueChars locale = EncodeLocale(cx, value.toString()); + auto locale = EncodeLocale(cx, durationFormat->getLocale()); if (!locale) { return nullptr; } - mozilla::intl::ListFormat::Options options; - options.mType = mozilla::intl::ListFormat::Type::Unit; - - if (!GetProperty(cx, internals, internals, cx->names().style, &value)) { - return nullptr; - } - { - auto* linear = value.toString()->ensureLinear(cx); - if (!linear) { - return nullptr; - } - - using ListFormatStyle = mozilla::intl::ListFormat::Style; - if (StringEqualsLiteral(linear, "long")) { - options.mStyle = ListFormatStyle::Long; - } else if (StringEqualsLiteral(linear, "short")) { - options.mStyle = ListFormatStyle::Short; - } else if (StringEqualsLiteral(linear, "narrow")) { - options.mStyle = ListFormatStyle::Narrow; - } else { - MOZ_ASSERT(StringEqualsLiteral(linear, "digital")); - options.mStyle = ListFormatStyle::Short; - } - } + mozilla::intl::ListFormat::Options options = { + .mType = mozilla::intl::ListFormat::Type::Unit, + .mStyle = ToListFormatStyle(dfOptions.style), + }; auto result = mozilla::intl::ListFormat::TryCreate( mozilla::MakeStringSpan(locale.get()), options); @@ -1407,12 +1678,11 @@ static mozilla::intl::ListFormat* NewDurationListFormat( static mozilla::intl::ListFormat* GetOrCreateListFormat( JSContext* cx, Handle durationFormat) { // Obtain a cached mozilla::intl::ListFormat object. - auto* lf = durationFormat->getListFormat(); - if (lf) { + if (auto* lf = durationFormat->getListFormat()) { return lf; } - lf = NewDurationListFormat(cx, durationFormat); + auto* lf = NewDurationListFormat(cx, durationFormat); if (!lf) { return nullptr; } @@ -1623,9 +1893,7 @@ static bool PartitionDurationFormatPattern( JSContext* cx, Handle durationFormat, Handle durationLike, bool formatToParts, MutableHandle result) { - using namespace temporal; - - Duration duration; + temporal::Duration duration; if (!ToTemporalDuration(cx, durationLike, &duration)) { return false; } @@ -1645,10 +1913,7 @@ static bool PartitionDurationFormatPattern( static_assert(durationUnits.size() == FormattedDurationValueVectorCapacity, "inline stack capacity large enough for all duration units"); - auto* options = GetOrCreateDurationFormatOptions(cx, durationFormat); - if (!options) { - return false; - } + auto options = *durationFormat->getOptions(); Rooted formattedValue(cx); @@ -1673,13 +1938,13 @@ static bool PartitionDurationFormatPattern( // Step 4.a. (Moved below) // Step 4.b. - auto unitOptions = GetUnitOptions(*options, unit); + auto unitOptions = GetUnitOptions(options, unit); // Step 4.c. - auto style = unitOptions.style(); + auto style = unitOptions.style; // Step 4.d. - auto display = unitOptions.display(); + auto display = unitOptions.display; // Steps 4.e-f. (Not applicable in our implementation.) @@ -1756,7 +2021,7 @@ static bool PartitionDurationFormatPattern( result); } -static bool IsDurationFormat(HandleValue v) { +static bool IsDurationFormat(Handle v) { return v.isObject() && v.toObject().is(); } @@ -1800,6 +2065,93 @@ static bool durationFormat_formatToParts(JSContext* cx, unsigned argc, cx, args); } +/** + * Intl.DurationFormat.prototype.resolvedOptions ( ) + */ +static bool durationFormat_resolvedOptions(JSContext* cx, + const JS::CallArgs& args) { + Rooted durationFormat( + cx, &args.thisv().toObject().as()); + + if (!ResolveLocale(cx, durationFormat)) { + return false; + } + auto dfOptions = *durationFormat->getOptions(); + + // Step 3. + Rooted options(cx, cx); + + // Step 4. + if (!options.emplaceBack(NameToId(cx->names().locale), + StringValue(durationFormat->getLocale()))) { + return false; + } + + if (!options.emplaceBack(NameToId(cx->names().numberingSystem), + StringValue(durationFormat->getNumberingSystem()))) { + return false; + } + + auto* style = NewStringCopy(cx, BaseStyleToString(dfOptions.style)); + if (!style) { + return false; + } + if (!options.emplaceBack(NameToId(cx->names().style), StringValue(style))) { + return false; + } + + for (auto unit : durationUnits) { + auto unitOptions = GetUnitOptions(dfOptions, unit); + + auto* style = + NewStringCopy(cx, DurationStyleToString(unitOptions.style)); + if (!style) { + return false; + } + if (!options.emplaceBack(NameToId(DurationStyleName(unit, cx)), + StringValue(style))) { + return false; + } + + auto* display = + NewStringCopy(cx, DisplayToString(unitOptions.display)); + if (!display) { + return false; + } + if (!options.emplaceBack(NameToId(DurationDisplayName(unit, cx)), + StringValue(display))) { + return false; + } + } + + if (dfOptions.fractionalDigits >= 0) { + MOZ_ASSERT(dfOptions.fractionalDigits <= 9); + if (!options.emplaceBack(NameToId(cx->names().fractionalDigits), + Int32Value(dfOptions.fractionalDigits))) { + return false; + } + } + + // Step 5. + auto* result = NewPlainObjectWithUniqueNames(cx, options); + if (!result) { + return false; + } + args.rval().setObject(*result); + return true; +} + +/** + * Intl.DurationFormat.prototype.resolvedOptions ( ) + */ +static bool durationFormat_resolvedOptions(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod( + cx, args); +} + /** * Intl.DurationFormat.supportedLocalesOf ( locales [ , options ] ) */ @@ -1828,9 +2180,7 @@ bool js::TemporalDurationToLocaleString(JSContext* cx, return false; } - if (!intl::InitializeObject(cx, durationFormat, - cx->names().InitializeDurationFormat, args.get(0), - args.get(1))) { + if (!InitializeDurationFormat(cx, durationFormat, args)) { return false; } diff --git a/js/src/builtin/intl/DurationFormat.h b/js/src/builtin/intl/DurationFormat.h index d287299302f96..fbe0ae54a77ce 100644 --- a/js/src/builtin/intl/DurationFormat.h +++ b/js/src/builtin/intl/DurationFormat.h @@ -9,10 +9,10 @@ #include -#include "builtin/SelfHostingDefines.h" #include "builtin/temporal/TemporalUnit.h" #include "js/Class.h" #include "vm/NativeObject.h" +#include "vm/StringType.h" namespace mozilla::intl { class ListFormat; @@ -24,15 +24,13 @@ namespace js { namespace intl { enum class DurationDisplay : uint8_t { Auto, Always }; enum class DurationStyle : uint8_t { Long, Short, Narrow, Numeric, TwoDigit }; +enum class DurationBaseStyle : uint8_t { Long, Short, Narrow, Digital }; struct DurationFormatOptions { - // Packed representation to keep the unit options as small as possible. - // - // Use |uint8_t| instead of the actual enum type to avoid GCC warnings: - // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414 -#define DECLARE_DURATION_UNIT(name) \ - /* DurationDisplay */ uint8_t name##Display : 1; \ - /* DurationStyle */ uint8_t name##Style : 3; +// Packed representation to keep the unit options as small as possible. +#define DECLARE_DURATION_UNIT(name) \ + DurationDisplay name##Display : 1 = DurationDisplay::Auto; \ + DurationStyle name##Style : 3 = DurationStyle::Short; DECLARE_DURATION_UNIT(years); DECLARE_DURATION_UNIT(months); @@ -47,17 +45,14 @@ struct DurationFormatOptions { #undef DECLARE_DURATION_UNIT - int8_t fractionalDigits; + DurationBaseStyle style = DurationBaseStyle::Short; + int8_t fractionalDigits = -1; }; struct DurationUnitOptions { // Use the same bit-widths for fast extraction from DurationFormatOptions. - /* DurationDisplay */ uint8_t display_ : 1; - /* DurationStyle */ uint8_t style_ : 3; - - auto display() const { return static_cast(display_); } - - auto style() const { return static_cast(style_); } + DurationDisplay display : 1; + DurationStyle style : 3; }; } // namespace intl @@ -67,8 +62,8 @@ class DurationFormatObject : public NativeObject { static const JSClass class_; static const JSClass& protoClass_; - static constexpr uint32_t INTERNALS_SLOT = 0; - static constexpr uint32_t LIST_FORMAT_SLOT = 1; + static constexpr uint32_t LOCALE_SLOT = 0; + static constexpr uint32_t NUMBERING_SYSTEM = 1; static constexpr uint32_t NUMBER_FORMAT_YEARS_SLOT = 2; static constexpr uint32_t NUMBER_FORMAT_MONTHS_SLOT = 3; static constexpr uint32_t NUMBER_FORMAT_WEEKS_SLOT = 4; @@ -79,13 +74,10 @@ class DurationFormatObject : public NativeObject { static constexpr uint32_t NUMBER_FORMAT_MILLISECONDS_SLOT = 9; static constexpr uint32_t NUMBER_FORMAT_MICROSECONDS_SLOT = 10; static constexpr uint32_t NUMBER_FORMAT_NANOSECONDS_SLOT = 11; - static constexpr uint32_t OPTIONS_SLOT = 12; - static constexpr uint32_t TIME_SEPARATOR_SLOT = 13; - static constexpr uint32_t SLOT_COUNT = 14; - - static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT, - "INTERNALS_SLOT must match self-hosting define for internals " - "object slot"); + static constexpr uint32_t LIST_FORMAT_SLOT = 12; + static constexpr uint32_t OPTIONS_SLOT = 13; + static constexpr uint32_t TIME_SEPARATOR_SLOT = 14; + static constexpr uint32_t SLOT_COUNT = 15; private: static constexpr uint32_t numberFormatSlot(temporal::TemporalUnit unit) { @@ -101,30 +93,42 @@ class DurationFormatObject : public NativeObject { } public: - mozilla::intl::NumberFormat* getNumberFormat( - temporal::TemporalUnit unit) const { - const auto& slot = getFixedSlot(numberFormatSlot(unit)); + bool isLocaleResolved() const { return getFixedSlot(LOCALE_SLOT).isString(); } + + JSObject* getRequestedLocales() const { + const auto& slot = getFixedSlot(LOCALE_SLOT); if (slot.isUndefined()) { return nullptr; } - return static_cast(slot.toPrivate()); + return &slot.toObject(); } - void setNumberFormat(temporal::TemporalUnit unit, - mozilla::intl::NumberFormat* numberFormat) { - setFixedSlot(numberFormatSlot(unit), PrivateValue(numberFormat)); + void setRequestedLocales(JSObject* requestedLocales) { + setFixedSlot(LOCALE_SLOT, JS::ObjectValue(*requestedLocales)); } - mozilla::intl::ListFormat* getListFormat() const { - const auto& slot = getFixedSlot(LIST_FORMAT_SLOT); + JSLinearString* getLocale() const { + const auto& slot = getFixedSlot(LOCALE_SLOT); if (slot.isUndefined()) { return nullptr; } - return static_cast(slot.toPrivate()); + return &slot.toString()->asLinear(); } - void setListFormat(mozilla::intl::ListFormat* listFormat) { - setFixedSlot(LIST_FORMAT_SLOT, PrivateValue(listFormat)); + void setLocale(JSLinearString* locale) { + setFixedSlot(LOCALE_SLOT, JS::StringValue(locale)); + } + + JSLinearString* getNumberingSystem() const { + const auto& slot = getFixedSlot(NUMBERING_SYSTEM); + if (slot.isUndefined()) { + return nullptr; + } + return &slot.toString()->asLinear(); + } + + void setNumberingSystem(JSLinearString* numberingSystem) { + setFixedSlot(NUMBERING_SYSTEM, JS::StringValue(numberingSystem)); } intl::DurationFormatOptions* getOptions() const { @@ -136,7 +140,33 @@ class DurationFormatObject : public NativeObject { } void setOptions(intl::DurationFormatOptions* options) { - setFixedSlot(OPTIONS_SLOT, PrivateValue(options)); + setFixedSlot(OPTIONS_SLOT, JS::PrivateValue(options)); + } + + mozilla::intl::NumberFormat* getNumberFormat( + temporal::TemporalUnit unit) const { + const auto& slot = getFixedSlot(numberFormatSlot(unit)); + if (slot.isUndefined()) { + return nullptr; + } + return static_cast(slot.toPrivate()); + } + + void setNumberFormat(temporal::TemporalUnit unit, + mozilla::intl::NumberFormat* numberFormat) { + setFixedSlot(numberFormatSlot(unit), JS::PrivateValue(numberFormat)); + } + + mozilla::intl::ListFormat* getListFormat() const { + const auto& slot = getFixedSlot(LIST_FORMAT_SLOT); + if (slot.isUndefined()) { + return nullptr; + } + return static_cast(slot.toPrivate()); + } + + void setListFormat(mozilla::intl::ListFormat* listFormat) { + setFixedSlot(LIST_FORMAT_SLOT, JS::PrivateValue(listFormat)); } JSString* getTimeSeparator() const { @@ -148,7 +178,7 @@ class DurationFormatObject : public NativeObject { } void setTimeSeparator(JSString* timeSeparator) { - setFixedSlot(TIME_SEPARATOR_SLOT, StringValue(timeSeparator)); + setFixedSlot(TIME_SEPARATOR_SLOT, JS::StringValue(timeSeparator)); } private: From 161968231671738aa1360c62d3d1dc43a13300d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Thu, 29 Jan 2026 07:42:55 +0000 Subject: [PATCH 006/116] Bug 2010221 - Part 2: Remove no longer used self-hosting code for Intl.DurationFormat. r=spidermonkey-reviewers,mgaudet Differential Revision: https://phabricator.services.mozilla.com/D279037 --- js/src/builtin/intl/CommonFunctions.js | 7 - js/src/builtin/intl/DurationFormat.js | 556 ------------------------- js/src/builtin/moz.build | 1 - js/src/jit/CacheIR.cpp | 1 - js/src/jit/InlinableNatives.cpp | 4 - js/src/jit/InlinableNatives.h | 1 - js/src/vm/SelfHosting.cpp | 6 - 7 files changed, 576 deletions(-) delete mode 100644 js/src/builtin/intl/DurationFormat.js diff --git a/js/src/builtin/intl/CommonFunctions.js b/js/src/builtin/intl/CommonFunctions.js index 5c168be6a9a07..874560a4efc8b 100644 --- a/js/src/builtin/intl/CommonFunctions.js +++ b/js/src/builtin/intl/CommonFunctions.js @@ -259,7 +259,6 @@ function initializeIntlObject(obj, type, lazyData) { assert(IsObject(obj), "Non-object passed to initializeIntlObject"); assert( (type === "DateTimeFormat" && intl_GuardToDateTimeFormat(obj) !== null) || - (type === "DurationFormat" && intl_GuardToDurationFormat(obj) !== null) || (type === "NumberFormat" && intl_GuardToNumberFormat(obj) !== null) || (type === "PluralRules" && intl_GuardToPluralRules(obj) !== null), "type must match the object's class" @@ -271,7 +270,6 @@ function initializeIntlObject(obj, type, lazyData) { // The .type property indicates the type of Intl object that |obj| is. It // must be one of: // - DateTimeFormat - // - DurationFormat // - NumberFormat // - PluralRules // @@ -337,7 +335,6 @@ function getIntlObjectInternals(obj) { assert(IsObject(obj), "getIntlObjectInternals called with non-Object"); assert( intl_GuardToDateTimeFormat(obj) !== null || - intl_GuardToDurationFormat(obj) !== null || intl_GuardToNumberFormat(obj) !== null || intl_GuardToPluralRules(obj) !== null, "getIntlObjectInternals called with non-Intl object" @@ -350,8 +347,6 @@ function getIntlObjectInternals(obj) { assert( (internals.type === "DateTimeFormat" && intl_GuardToDateTimeFormat(obj) !== null) || - (internals.type === "DurationFormat" && - intl_GuardToDurationFormat(obj) !== null) || (internals.type === "NumberFormat" && intl_GuardToNumberFormat(obj) !== null) || (internals.type === "PluralRules" && @@ -381,8 +376,6 @@ function getInternals(obj) { var type = internals.type; if (type === "DateTimeFormat") { internalProps = resolveDateTimeFormatInternals(internals.lazyData); - } else if (type === "DurationFormat") { - internalProps = resolveDurationFormatInternals(internals.lazyData); } else if (type === "NumberFormat") { internalProps = resolveNumberFormatInternals(internals.lazyData); } else if (type === "PluralRules") { diff --git a/js/src/builtin/intl/DurationFormat.js b/js/src/builtin/intl/DurationFormat.js deleted file mode 100644 index 3e812b8ca452d..0000000000000 --- a/js/src/builtin/intl/DurationFormat.js +++ /dev/null @@ -1,556 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Intl.DurationFormat ( [ locales [ , options ] ] ) - * - * Compute an internal properties object from |lazyDurationFormatData|. - */ -function resolveDurationFormatInternals(lazyDurationFormatData) { - assert(IsObject(lazyDurationFormatData), "lazy data not an object?"); - - var internalProps = std_Object_create(null); - - // Compute effective locale. - - // Step 9. - var r = intl_ResolveLocale( - "DurationFormat", - lazyDurationFormatData.requestedLocales, - lazyDurationFormatData.opt, - ); - - // Steps 10-11. - internalProps.locale = r.locale; - - // Steps 12-21. (Not applicable in our implementation.) - - // Step 22. - internalProps.numberingSystem = r.nu; - - // Step 24. - internalProps.style = lazyDurationFormatData.style; - - // Step 26. - internalProps.yearsStyle = lazyDurationFormatData.yearsStyle; - internalProps.yearsDisplay = lazyDurationFormatData.yearsDisplay; - - internalProps.weeksStyle = lazyDurationFormatData.weeksStyle; - internalProps.weeksDisplay = lazyDurationFormatData.weeksDisplay; - - internalProps.monthsStyle = lazyDurationFormatData.monthsStyle; - internalProps.monthsDisplay = lazyDurationFormatData.monthsDisplay; - - internalProps.daysStyle = lazyDurationFormatData.daysStyle; - internalProps.daysDisplay = lazyDurationFormatData.daysDisplay; - - internalProps.hoursStyle = lazyDurationFormatData.hoursStyle; - internalProps.hoursDisplay = lazyDurationFormatData.hoursDisplay; - - internalProps.minutesStyle = lazyDurationFormatData.minutesStyle; - internalProps.minutesDisplay = lazyDurationFormatData.minutesDisplay; - - internalProps.secondsStyle = lazyDurationFormatData.secondsStyle; - internalProps.secondsDisplay = lazyDurationFormatData.secondsDisplay; - - internalProps.millisecondsStyle = lazyDurationFormatData.millisecondsStyle; - internalProps.millisecondsDisplay = - lazyDurationFormatData.millisecondsDisplay; - - internalProps.microsecondsStyle = lazyDurationFormatData.microsecondsStyle; - internalProps.microsecondsDisplay = - lazyDurationFormatData.microsecondsDisplay; - - internalProps.nanosecondsStyle = lazyDurationFormatData.nanosecondsStyle; - internalProps.nanosecondsDisplay = lazyDurationFormatData.nanosecondsDisplay; - - // Step 27. - internalProps.fractionalDigits = lazyDurationFormatData.fractionalDigits; - - // The caller is responsible for associating |internalProps| with the right - // object using |setInternalProperties|. - return internalProps; -} - -/** - * Returns an object containing the DurationFormat internal properties of |obj|. - */ -function getDurationFormatInternals(obj) { - assert(IsObject(obj), "getDurationFormatInternals called with non-object"); - assert( - intl_GuardToDurationFormat(obj) !== null, - "getDurationFormatInternals called with non-DurationFormat" - ); - - var internals = getIntlObjectInternals(obj); - assert( - internals.type === "DurationFormat", - "bad type escaped getIntlObjectInternals" - ); - - // If internal properties have already been computed, use them. - var internalProps = maybeInternalProperties(internals); - if (internalProps) { - return internalProps; - } - - // Otherwise it's time to fully create them. - internalProps = resolveDurationFormatInternals(internals.lazyData); - setInternalProperties(internals, internalProps); - return internalProps; -} - -/** - * Intl.DurationFormat ( [ locales [ , options ] ] ) - * - * Initializes an object as a DurationFormat. - * - * This method is complicated a moderate bit by its implementing initialization - * as a *lazy* concept. Everything that must happen now, does -- but we defer - * all the work we can until the object is actually used as a DurationFormat. - * This later work occurs in |resolveDurationFormatInternals|; steps not noted - * here occur there. - */ -function InitializeDurationFormat(durationFormat, locales, options) { - assert( - IsObject(durationFormat), - "InitializeDurationFormat called with non-object" - ); - assert( - intl_GuardToDurationFormat(durationFormat) !== null, - "InitializeDurationFormat called with non-DurationFormat" - ); - - // Lazy DurationFormat data has the following structure: - // - // { - // requestedLocales: List of locales, - // style: "long" / "short" / "narrow" / "digital", - // - // yearsStyle: "long" / "short" / "narrow", - // yearsDisplay: "auto" / "always", - // - // monthsStyle: "long" / "short" / "narrow", - // monthsDisplay: "auto" / "always", - // - // weeksStyle: "long" / "short" / "narrow", - // weeksDisplay: "auto" / "always", - // - // daysStyle: "long" / "short" / "narrow", - // daysDisplay: "auto" / "always", - // - // hoursStyle: "long" / "short" / "narrow" / "numeric" / "2-digit", - // hoursDisplay: "auto" / "always", - // - // minutesStyle: "long" / "short" / "narrow" / "numeric" / "2-digit", - // minutesDisplay: "auto" / "always", - // - // secondsStyle: "long" / "short" / "narrow" / "numeric" / "2-digit", - // secondsDisplay: "auto" / "always", - // - // millisecondsStyle: "long" / "short" / "narrow" / "numeric", - // millisecondsDisplay: "auto" / "always", - // - // microsecondsStyle: "long" / "short" / "narrow" / "numeric", - // microsecondsDisplay: "auto" / "always", - // - // nanosecondsStyle: "long" / "short" / "narrow" / "numeric", - // nanosecondsDisplay: "auto" / "always", - // - // fractionalDigits: integer ∈ [0, 9] / undefined, - // - // opt: // opt object computed in InitializeDurationFormat - // { - // localeMatcher: "lookup" / "best fit", - // - // nu: string matching a Unicode extension type, // optional - // } - // } - // - // Note that lazy data is only installed as a final step of initialization, - // so every DurationFormat lazy data object has *all* these properties, - // never a subset of them. - var lazyDurationFormatData = std_Object_create(null); - - // Step 3. - var requestedLocales = CanonicalizeLocaleList(locales); - lazyDurationFormatData.requestedLocales = requestedLocales; - - // Step 4. - if (options === undefined) { - options = std_Object_create(null); - } else if (!IsObject(options)) { - ThrowTypeError( - JSMSG_OBJECT_REQUIRED, - options === null ? "null" : typeof options - ); - } - - // Step 5. - var matcher = GetOption( - options, - "localeMatcher", - "string", - ["lookup", "best fit"], - "best fit" - ); - - // Step 6. - var numberingSystem = GetOption( - options, - "numberingSystem", - "string", - undefined, - undefined - ); - - // Step 7. - if (numberingSystem !== undefined) { - numberingSystem = intl_ValidateAndCanonicalizeUnicodeExtensionType( - numberingSystem, - "numberingSystem", - "nu" - ); - } - - // Step 8. - var opt = NEW_RECORD(); - opt.localeMatcher = matcher; - opt.nu = numberingSystem; - - lazyDurationFormatData.opt = opt; - - // Compute formatting options. - - // Steps 23-24. - var style = GetOption( - options, - "style", - "string", - ["long", "short", "narrow", "digital"], - "short" - ); - lazyDurationFormatData.style = style; - - // Step 25. (Not applicable in our implementation) - - // Step 26, unit = "years". - var yearsOptions = GetDurationUnitOptions( - "years", - options, - style, - ["long", "short", "narrow"], - "short", - /* prevStyle= */ "" - ); - lazyDurationFormatData.yearsStyle = yearsOptions.style; - lazyDurationFormatData.yearsDisplay = yearsOptions.display; - - // Step 26, unit = "months". - var monthsOptions = GetDurationUnitOptions( - "months", - options, - style, - ["long", "short", "narrow"], - "short", - /* prevStyle= */ "" - ); - lazyDurationFormatData.monthsStyle = monthsOptions.style; - lazyDurationFormatData.monthsDisplay = monthsOptions.display; - - // Step 26, unit = "weeks". - var weeksOptions = GetDurationUnitOptions( - "weeks", - options, - style, - ["long", "short", "narrow"], - "short", - /* prevStyle= */ "" - ); - lazyDurationFormatData.weeksStyle = weeksOptions.style; - lazyDurationFormatData.weeksDisplay = weeksOptions.display; - - // Step 26, unit = "days". - var daysOptions = GetDurationUnitOptions( - "days", - options, - style, - ["long", "short", "narrow"], - "short", - /* prevStyle= */ "" - ); - lazyDurationFormatData.daysStyle = daysOptions.style; - lazyDurationFormatData.daysDisplay = daysOptions.display; - - // Step 26, unit = "hours". - var hoursOptions = GetDurationUnitOptions( - "hours", - options, - style, - ["long", "short", "narrow", "numeric", "2-digit"], - "numeric", - /* prevStyle= */ "" - ); - lazyDurationFormatData.hoursStyle = hoursOptions.style; - lazyDurationFormatData.hoursDisplay = hoursOptions.display; - - // Step 26, unit = "minutes". - var minutesOptions = GetDurationUnitOptions( - "minutes", - options, - style, - ["long", "short", "narrow", "numeric", "2-digit"], - "numeric", - hoursOptions.style - ); - lazyDurationFormatData.minutesStyle = minutesOptions.style; - lazyDurationFormatData.minutesDisplay = minutesOptions.display; - - // Step 26, unit = "seconds". - var secondsOptions = GetDurationUnitOptions( - "seconds", - options, - style, - ["long", "short", "narrow", "numeric", "2-digit"], - "numeric", - minutesOptions.style - ); - lazyDurationFormatData.secondsStyle = secondsOptions.style; - lazyDurationFormatData.secondsDisplay = secondsOptions.display; - - // Step 26, unit = "milliseconds". - var millisecondsOptions = GetDurationUnitOptions( - "milliseconds", - options, - style, - ["long", "short", "narrow", "numeric"], - "numeric", - secondsOptions.style - ); - lazyDurationFormatData.millisecondsStyle = millisecondsOptions.style; - lazyDurationFormatData.millisecondsDisplay = millisecondsOptions.display; - - // Step 26, unit = "microseconds". - var microsecondsOptions = GetDurationUnitOptions( - "microseconds", - options, - style, - ["long", "short", "narrow", "numeric"], - "numeric", - millisecondsOptions.style - ); - lazyDurationFormatData.microsecondsStyle = microsecondsOptions.style; - lazyDurationFormatData.microsecondsDisplay = microsecondsOptions.display; - - // Step 26, unit = "milliseconds". - var nanosecondsOptions = GetDurationUnitOptions( - "nanoseconds", - options, - style, - ["long", "short", "narrow", "numeric"], - "numeric", - microsecondsOptions.style - ); - lazyDurationFormatData.nanosecondsStyle = nanosecondsOptions.style; - lazyDurationFormatData.nanosecondsDisplay = nanosecondsOptions.display; - - // Step 27. - lazyDurationFormatData.fractionalDigits = GetNumberOption( - options, - "fractionalDigits", - 0, - 9, - undefined - ); - - // We've done everything that must be done now: mark the lazy data as fully - // computed and install it. - initializeIntlObject( - durationFormat, - "DurationFormat", - lazyDurationFormatData - ); -} - -/** - * GetDurationUnitOptions ( unit, options, baseStyle, stylesList, digitalBase, prevStyle, twoDigitHours ) - */ -function GetDurationUnitOptions( - unit, - options, - baseStyle, - stylesList, - digitalBase, - prevStyle -) { - assert(typeof unit === "string", "unit is a string"); - assert(IsObject(options), "options is an object"); - assert(typeof baseStyle === "string", "baseStyle is a string"); - assert(IsArray(stylesList), "stylesList is an array"); - assert(typeof digitalBase === "string", "digitalBase is a string"); - assert(typeof prevStyle === "string", "prevStyle is a string"); - - // Step 1. - var styleOption = GetOption(options, unit, "string", stylesList, undefined); - - var style = styleOption; - - // Step 2. - var displayDefault = "always"; - - // Step 3. - if (style === undefined) { - // Steps 3.a-b. - if (baseStyle === "digital") { - // Step 3.a.i. - if (unit !== "hours" && unit !== "minutes" && unit !== "seconds") { - displayDefault = "auto"; - } - - // Step 3.a.ii. - style = digitalBase; - } else { - // Steps 3.b.i-ii. ("fractional" handled implicitly) - if (prevStyle === "numeric" || prevStyle === "2-digit") { - // Step 3.b.i.1. - if (unit !== "minutes" && unit !== "seconds") { - // Step 3.b.i.1.a. - displayDefault = "auto"; - } - - // Step 3.b.i.2. - style = "numeric"; - } else { - // Step 3.b.ii.1. - displayDefault = "auto"; - - // Step 3.b.ii.2. - style = baseStyle; - } - } - } - - // Step 4. - var isFractional = - style === "numeric" && - (unit === "milliseconds" || - unit === "microseconds" || - unit === "nanoseconds"); - if (isFractional) { - // Step 4.a.i. (Not applicable in our implementation) - - // Step 4.a.ii. - displayDefault = "auto"; - } - - // Step 5. - var displayField = unit + "Display"; - - // Step 6. - var displayOption = GetOption( - options, - displayField, - "string", - ["auto", "always"], - undefined - ); - - var display = displayOption ?? displayDefault; - - // Step 7. - if (display === "always" && isFractional) { - assert( - styleOption !== undefined || displayOption !== undefined, - "no error is thrown when both 'style' and 'display' are absent" - ); - - ThrowRangeError( - // eslint-disable-next-line no-nested-ternary - styleOption !== undefined && displayOption !== undefined - ? JSMSG_INTL_DURATION_INVALID_DISPLAY_OPTION - : displayOption !== undefined - ? JSMSG_INTL_DURATION_INVALID_DISPLAY_OPTION_DEFAULT_STYLE - : JSMSG_INTL_DURATION_INVALID_DISPLAY_OPTION_DEFAULT_DISPLAY, - unit - ); - } - - // Steps 8-9. - if (prevStyle === "numeric" || prevStyle === "2-digit") { - // Step 8.a. and 9.a. - if (style !== "numeric" && style !== "2-digit") { - ThrowRangeError( - JSMSG_INTL_DURATION_INVALID_NON_NUMERIC_OPTION, - unit, - `"${style}"` - ); - } - - // Step 9.b. - else if (unit === "minutes" || unit === "seconds") { - style = "2-digit"; - } - } - - // Step 10. (Our implementation doesn't use |twoDigitHours|.) - - // Step 11. - return { style, display }; -} - -/** - * Returns the resolved options for a DurationFormat object. - */ -function Intl_DurationFormat_resolvedOptions() { - // Step 1. - var durationFormat = this; - - // Step 2. - if ( - !IsObject(durationFormat) || - (durationFormat = intl_GuardToDurationFormat(durationFormat)) === null - ) { - return callFunction( - intl_CallDurationFormatMethodIfWrapped, - this, - "Intl_DurationFormat_resolvedOptions" - ); - } - - var internals = getDurationFormatInternals(durationFormat); - - // Steps 3-4. - var result = { - locale: internals.locale, - numberingSystem: internals.numberingSystem, - style: internals.style, - years: internals.yearsStyle, - yearsDisplay: internals.yearsDisplay, - months: internals.monthsStyle, - monthsDisplay: internals.monthsDisplay, - weeks: internals.weeksStyle, - weeksDisplay: internals.weeksDisplay, - days: internals.daysStyle, - daysDisplay: internals.daysDisplay, - hours: internals.hoursStyle, - hoursDisplay: internals.hoursDisplay, - minutes: internals.minutesStyle, - minutesDisplay: internals.minutesDisplay, - seconds: internals.secondsStyle, - secondsDisplay: internals.secondsDisplay, - milliseconds: internals.millisecondsStyle, - millisecondsDisplay: internals.millisecondsDisplay, - microseconds: internals.microsecondsStyle, - microsecondsDisplay: internals.microsecondsDisplay, - nanoseconds: internals.nanosecondsStyle, - nanosecondsDisplay: internals.nanosecondsDisplay, - }; - - if (internals.fractionalDigits !== undefined) { - DefineDataProperty(result, "fractionalDigits", internals.fractionalDigits); - } - - // Step 5. - return result; -} diff --git a/js/src/builtin/moz.build b/js/src/builtin/moz.build index abde0f665c6e5..04d55984f44f5 100644 --- a/js/src/builtin/moz.build +++ b/js/src/builtin/moz.build @@ -92,7 +92,6 @@ selfhosted_inputs = [ "intl/CommonFunctions.js", "intl/CurrencyDataGenerated.js", "intl/DateTimeFormat.js", - "intl/DurationFormat.js", "intl/NumberFormat.js", "intl/PluralRules.js", "intl/SanctionedSimpleUnitIdentifiersGenerated.js", diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp index 51e37bca65f9f..cb63f93265e6c 100644 --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -13181,7 +13181,6 @@ AttachDecision InlinableNativeIRGenerator::tryAttachStub() { // Intl natives. case InlinableNative::IntlGuardToDateTimeFormat: - case InlinableNative::IntlGuardToDurationFormat: case InlinableNative::IntlGuardToNumberFormat: case InlinableNative::IntlGuardToPluralRules: case InlinableNative::IntlGuardToSegments: diff --git a/js/src/jit/InlinableNatives.cpp b/js/src/jit/InlinableNatives.cpp index 869f8fe12474e..ad723f3507ec4 100644 --- a/js/src/jit/InlinableNatives.cpp +++ b/js/src/jit/InlinableNatives.cpp @@ -8,7 +8,6 @@ #ifdef JS_HAS_INTL_API # include "builtin/intl/DateTimeFormat.h" -# include "builtin/intl/DurationFormat.h" # include "builtin/intl/NumberFormat.h" # include "builtin/intl/PluralRules.h" # include "builtin/intl/Segmenter.h" @@ -42,8 +41,6 @@ const JSClass* js::jit::InlinableNativeGuardToClass(InlinableNative native) { // Intl natives. case InlinableNative::IntlGuardToDateTimeFormat: return &DateTimeFormatObject::class_; - case InlinableNative::IntlGuardToDurationFormat: - return &DurationFormatObject::class_; case InlinableNative::IntlGuardToNumberFormat: return &NumberFormatObject::class_; case InlinableNative::IntlGuardToPluralRules: @@ -161,7 +158,6 @@ bool js::jit::CanInlineNativeCrossRealm(InlinableNative native) { return false; case InlinableNative::IntlGuardToDateTimeFormat: - case InlinableNative::IntlGuardToDurationFormat: case InlinableNative::IntlGuardToNumberFormat: case InlinableNative::IntlGuardToPluralRules: case InlinableNative::IntlGuardToSegments: diff --git a/js/src/jit/InlinableNatives.h b/js/src/jit/InlinableNatives.h index f42ccb7a61d58..5f7d6145e755e 100644 --- a/js/src/jit/InlinableNatives.h +++ b/js/src/jit/InlinableNatives.h @@ -96,7 +96,6 @@ _(FunctionBind) \ \ _(IntlGuardToDateTimeFormat) \ - _(IntlGuardToDurationFormat) \ _(IntlGuardToNumberFormat) \ _(IntlGuardToPluralRules) \ _(IntlGuardToSegments) \ diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index d4a7d795503e3..9359bcea8ef96 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -25,7 +25,6 @@ #include "builtin/BigInt.h" #ifdef JS_HAS_INTL_API # include "builtin/intl/DateTimeFormat.h" -# include "builtin/intl/DurationFormat.h" # include "builtin/intl/IntlObject.h" # include "builtin/intl/Locale.h" # include "builtin/intl/NumberFormat.h" @@ -1845,8 +1844,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("intl_BestAvailableLocale", intl_BestAvailableLocale, 3, 0), JS_FN("intl_CallDateTimeFormatMethodIfWrapped", CallNonGenericSelfhostedMethod>, 2, 0), - JS_FN("intl_CallDurationFormatMethodIfWrapped", - CallNonGenericSelfhostedMethod>, 2, 0), JS_FN("intl_CallNumberFormatMethodIfWrapped", CallNonGenericSelfhostedMethod>, 2, 0), JS_FN("intl_CallPluralRulesMethodIfWrapped", @@ -1868,9 +1865,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_INLINABLE_FN("intl_GuardToDateTimeFormat", intrinsic_GuardToBuiltin, 1, 0, IntlGuardToDateTimeFormat), - JS_INLINABLE_FN("intl_GuardToDurationFormat", - intrinsic_GuardToBuiltin, 1, 0, - IntlGuardToDurationFormat), JS_INLINABLE_FN("intl_GuardToNumberFormat", intrinsic_GuardToBuiltin, 1, 0, IntlGuardToNumberFormat), From d920164e06e4276fefd73394d4adfafc30ea0de1 Mon Sep 17 00:00:00 2001 From: Updatebot Date: Thu, 29 Jan 2026 07:53:42 +0000 Subject: [PATCH 007/116] Bug 2012986 - Update PDF.js to 550520193085788c1dbb1dac0d4565109fbbb49f r=pdfjs-reviewers,calixte Differential Revision: https://phabricator.services.mozilla.com/D280736 --- .../components/pdfjs/content/build/pdf.mjs | 264 ++++++- .../pdfjs/content/build/pdf.scripting.mjs | 4 +- .../pdfjs/content/build/pdf.worker.mjs | 84 ++- .../pdfjs/content/web/viewer-geckoview.mjs | 244 ++++--- .../components/pdfjs/content/web/viewer.css | 17 +- .../components/pdfjs/content/web/viewer.html | 19 +- .../components/pdfjs/content/web/viewer.mjs | 655 +++++++++--------- toolkit/components/pdfjs/moz.yaml | 4 +- .../en-US/toolkit/pdfviewer/viewer.ftl | 1 + 9 files changed, 793 insertions(+), 499 deletions(-) diff --git a/toolkit/components/pdfjs/content/build/pdf.mjs b/toolkit/components/pdfjs/content/build/pdf.mjs index 7185e46fed07d..6817711e299b1 100644 --- a/toolkit/components/pdfjs/content/build/pdf.mjs +++ b/toolkit/components/pdfjs/content/build/pdf.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.569 - * pdfjsBuild = 6a4a3b060 + * pdfjsVersion = 5.4.590 + * pdfjsBuild = 550520193 */ /******/ // The require scope /******/ var __webpack_require__ = {}; @@ -1848,6 +1848,115 @@ function makePathFromDrawOPS(data) { } return path; } +class PagesMapper { + static #idToPageNumber = null; + static #pageNumberToId = null; + static #prevIdToPageNumber = null; + static #pagesNumber = 0; + static #listeners = []; + get pagesNumber() { + return PagesMapper.#pagesNumber; + } + set pagesNumber(n) { + if (PagesMapper.#pagesNumber === n) { + return; + } + PagesMapper.#pagesNumber = n; + if (n === 0) { + PagesMapper.#pageNumberToId = null; + PagesMapper.#idToPageNumber = null; + } + } + addListener(listener) { + PagesMapper.#listeners.push(listener); + } + removeListener(listener) { + const index = PagesMapper.#listeners.indexOf(listener); + if (index >= 0) { + PagesMapper.#listeners.splice(index, 1); + } + } + #updateListeners() { + for (const listener of PagesMapper.#listeners) { + listener(); + } + } + #init(mustInit) { + if (PagesMapper.#pageNumberToId) { + return; + } + const n = PagesMapper.#pagesNumber; + const array = new Uint32Array(3 * n); + const pageNumberToId = PagesMapper.#pageNumberToId = array.subarray(0, n); + const idToPageNumber = PagesMapper.#idToPageNumber = array.subarray(n, 2 * n); + if (mustInit) { + for (let i = 0; i < n; i++) { + pageNumberToId[i] = idToPageNumber[i] = i + 1; + } + } + PagesMapper.#prevIdToPageNumber = array.subarray(2 * n); + } + movePages(selectedPages, pagesToMove, index) { + this.#init(true); + const pageNumberToId = PagesMapper.#pageNumberToId; + const idToPageNumber = PagesMapper.#idToPageNumber; + PagesMapper.#prevIdToPageNumber.set(idToPageNumber); + const movedCount = pagesToMove.length; + const mappedPagesToMove = new Uint32Array(movedCount); + let removedBeforeTarget = 0; + for (let i = 0; i < movedCount; i++) { + const pageIndex = pagesToMove[i] - 1; + mappedPagesToMove[i] = pageNumberToId[pageIndex]; + if (pageIndex < index) { + removedBeforeTarget += 1; + } + } + const pagesNumber = PagesMapper.#pagesNumber; + let adjustedTarget = index - removedBeforeTarget; + const remainingLen = pagesNumber - movedCount; + adjustedTarget = MathClamp(adjustedTarget, 0, remainingLen); + for (let i = 0, r = 0; i < pagesNumber; i++) { + if (!selectedPages.has(i + 1)) { + pageNumberToId[r++] = pageNumberToId[i]; + } + } + pageNumberToId.copyWithin(adjustedTarget + movedCount, adjustedTarget, remainingLen); + pageNumberToId.set(mappedPagesToMove, adjustedTarget); + let hasChanged = false; + for (let i = 0, ii = pagesNumber; i < ii; i++) { + const id = pageNumberToId[i]; + hasChanged ||= id !== i + 1; + idToPageNumber[id - 1] = i + 1; + } + this.#updateListeners(); + if (!hasChanged) { + this.pagesNumber = 0; + } + } + hasBeenAltered() { + return PagesMapper.#pageNumberToId !== null; + } + getPageMappingForSaving() { + return { + pageIndices: PagesMapper.#idToPageNumber ? PagesMapper.#idToPageNumber.map(x => x - 1) : null + }; + } + getPrevPageNumber(pageNumber) { + return PagesMapper.#prevIdToPageNumber[PagesMapper.#pageNumberToId[pageNumber - 1] - 1]; + } + getPageNumber(id) { + return PagesMapper.#idToPageNumber?.[id - 1] ?? id; + } + getPageId(pageNumber) { + return PagesMapper.#pageNumberToId?.[pageNumber - 1] ?? pageNumber; + } + static get instance() { + return shadow(this, "instance", new PagesMapper()); + } + getMapping() { + return PagesMapper.#pageNumberToId.subarray(0, this.pagesNumber); + } +} ;// ./src/display/editor/toolbar.js @@ -2728,6 +2837,9 @@ class AnnotationEditorUIManager { eventBus._on("switchannotationeditorparams", evt => this.updateParams(evt.type, evt.value), { signal }); + eventBus._on("pagesedited", this.onPagesEdited.bind(this), { + signal + }); window.addEventListener("pointerdown", () => { this.#isPointerDown = true; }, { @@ -2889,6 +3001,21 @@ class AnnotationEditorUIManager { removeComment(editor) { this.#commentManager?.removeComments([editor.uid]); } + deleteComment(editor, savedData) { + const undo = () => { + editor.comment = savedData; + }; + const cmd = () => { + this._editorUndoBar?.show(undo, "comment"); + this.toggleComment(null); + editor.comment = null; + }; + this.addCommands({ + cmd, + undo, + mustExec: true + }); + } toggleComment(editor, isSelected, visibility = undefined) { this.#commentManager?.toggleCommentPopup(editor, isSelected, visibility); } @@ -2951,6 +3078,24 @@ class AnnotationEditorUIManager { break; } } + onPagesEdited({ + pagesMapper + }) { + for (const editor of this.#allEditors.values()) { + editor.updatePageIndex(pagesMapper.getPrevPageNumber(editor.pageIndex + 1) - 1); + } + const allLayers = this.#allLayers; + const newAllLayers = this.#allLayers = new Map(); + for (const [pageIndex, layer] of allLayers) { + const prevPageIndex = pagesMapper.getPrevPageNumber(pageIndex + 1) - 1; + if (prevPageIndex === -1) { + layer.destroy(); + continue; + } + newAllLayers.set(prevPageIndex, layer); + layer.updatePageIndex(prevPageIndex); + } + } onPageChanging({ pageNumber }) { @@ -4710,6 +4855,16 @@ class Comment { this.#date = new Date(); this.#deleted = false; } + restoreData({ + text, + richText, + date + }) { + this.#text = text; + this.#richText = richText; + this.#date = date; + this.#deleted = false; + } setInitialText(text, richText = null) { this.#initialText = text; this.data = text; @@ -5013,6 +5168,9 @@ class AnnotationEditor { this.isAttachedToDOM = false; this.deleted = false; } + updatePageIndex(newPageIndex) { + this.pageIndex = newPageIndex; + } get editorType() { return Object.getPrototypeOf(this).constructor._type; } @@ -5719,9 +5877,13 @@ class AnnotationEditor { opacity: this.opacity ?? 1 }; } - set comment(text) { + set comment(value) { this.#comment ||= new Comment(this); - this.#comment.data = text; + if (typeof value === "object" && value !== null) { + this.#comment.restoreData(value); + } else { + this.#comment.data = value; + } if (this.hasComment) { this.removeCommentButtonFromToolbar(); this.addStandaloneCommentButton(); @@ -6766,6 +6928,10 @@ class AnnotationStorage { let hasBitmap = false; for (const [key, val] of this.#storage) { const serialized = val instanceof AnnotationEditor ? val.serialize(false, context) : val; + if (val.page) { + val.pageIndex = val.page._pageIndex; + delete val.page; + } if (serialized) { map.set(key, serialized); hash.update(`${key}:${JSON.stringify(serialized)}`); @@ -13146,7 +13312,7 @@ function getDocument(src = {}) { } const docParams = { docId, - apiVersion: "5.4.569", + apiVersion: "5.4.590", data, password, disableAutoFetch, @@ -13425,6 +13591,7 @@ class PDFDocumentProxy { } class PDFPageProxy { #pendingCleanup = false; + #pagesMapper = PagesMapper.instance; constructor(pageIndex, pageInfo, transport, pdfBug = false) { this._pageIndex = pageIndex; this._pageInfo = pageInfo; @@ -13440,6 +13607,9 @@ class PDFPageProxy { get pageNumber() { return this._pageIndex + 1; } + set pageNumber(value) { + this._pageIndex = value - 1; + } get rotate() { return this._pageInfo.rotate; } @@ -13626,6 +13796,7 @@ class PDFPageProxy { } = {}) { const TEXT_CONTENT_CHUNK_SIZE = 100; return this._transport.messageHandler.sendWithStream("GetTextContent", { + pageId: this.#pagesMapper.getPageId(this._pageIndex + 1) - 1, pageIndex: this._pageIndex, includeMarkedContent: includeMarkedContent === true, disableNormalization: disableNormalization === true @@ -13748,6 +13919,7 @@ class PDFPageProxy { transfer } = annotationStorageSerializable; const readableStream = this._transport.messageHandler.sendWithStream("GetOperatorList", { + pageId: this.#pagesMapper.getPageId(this._pageIndex + 1) - 1, pageIndex: this._pageIndex, intent: renderingIntent, cacheKey, @@ -14028,6 +14200,7 @@ class WorkerTransport { #pagePromises = new Map(); #pageRefCache = new Map(); #passwordCapability = null; + #pagesMapper = PagesMapper.instance; constructor(messageHandler, loadingTask, networkStream, params, factory, enableHWA) { this.messageHandler = messageHandler; this.loadingTask = loadingTask; @@ -14051,6 +14224,24 @@ class WorkerTransport { this.downloadInfoCapability = Promise.withResolvers(); this.enableHWA = enableHWA; this.setupMessageHandler(); + this.#pagesMapper.addListener(this.#updateCaches.bind(this)); + } + #updateCaches() { + const newPageCache = new Map(); + const newPromiseCache = new Map(); + for (let i = 0, ii = this.#pagesMapper.pagesNumber; i < ii; i++) { + const prevPageIndex = this.#pagesMapper.getPrevPageNumber(i + 1) - 1; + const page = this.#pageCache.get(prevPageIndex); + if (page) { + newPageCache.set(i, page); + } + const promise = this.#pagePromises.get(prevPageIndex); + if (promise) { + newPromiseCache.set(i, promise); + } + } + this.#pageCache = newPageCache; + this.#pagePromises = newPromiseCache; } #cacheSimpleMethod(name, data = null) { const cachedPromise = this.#methodPromises.get(name); @@ -14244,6 +14435,7 @@ class WorkerTransport { messageHandler.on("GetDoc", ({ pdfInfo }) => { + this.#pagesMapper.pagesNumber = pdfInfo.numPages; this._numPages = pdfInfo.numPages; this._htmlForXfa = pdfInfo.htmlForXfa; delete pdfInfo.htmlForXfa; @@ -14407,22 +14599,23 @@ class WorkerTransport { }); } getPage(pageNumber) { - if (!Number.isInteger(pageNumber) || pageNumber <= 0 || pageNumber > this._numPages) { + if (!Number.isInteger(pageNumber) || pageNumber <= 0 || pageNumber > this.#pagesMapper.pagesNumber) { return Promise.reject(new Error("Invalid page request.")); } - const pageIndex = pageNumber - 1, - cachedPromise = this.#pagePromises.get(pageIndex); + const pageIndex = pageNumber - 1; + const newPageIndex = this.#pagesMapper.getPageId(pageNumber) - 1; + const cachedPromise = this.#pagePromises.get(pageIndex); if (cachedPromise) { return cachedPromise; } const promise = this.messageHandler.sendWithPromise("GetPage", { - pageIndex + pageIndex: newPageIndex }).then(pageInfo => { if (this.destroyed) { throw new Error("Transport destroyed"); } if (pageInfo.refStr) { - this.#pageRefCache.set(pageInfo.refStr, pageNumber); + this.#pageRefCache.set(pageInfo.refStr, newPageIndex); } const page = new PDFPageProxy(pageIndex, pageInfo, this, this._params.pdfBug); this.#pageCache.set(pageIndex, page); @@ -14431,18 +14624,19 @@ class WorkerTransport { this.#pagePromises.set(pageIndex, promise); return promise; } - getPageIndex(ref) { + async getPageIndex(ref) { if (!isRefProxy(ref)) { - return Promise.reject(new Error("Invalid pageIndex request.")); + throw new Error("Invalid pageIndex request."); } - return this.messageHandler.sendWithPromise("GetPageIndex", { + const index = await this.messageHandler.sendWithPromise("GetPageIndex", { num: ref.num, gen: ref.gen }); + return this.#pagesMapper.getPageNumber(index + 1) - 1; } getAnnotations(pageIndex, intent) { return this.messageHandler.sendWithPromise("GetAnnotations", { - pageIndex, + pageIndex: this.#pagesMapper.getPageId(pageIndex + 1) - 1, intent }); } @@ -14495,12 +14689,12 @@ class WorkerTransport { } getPageJSActions(pageIndex) { return this.messageHandler.sendWithPromise("GetPageJSActions", { - pageIndex + pageIndex: this.#pagesMapper.getPageId(pageIndex + 1) - 1 }); } getStructTree(pageIndex) { return this.messageHandler.sendWithPromise("GetStructTree", { - pageIndex + pageIndex: this.#pagesMapper.getPageId(pageIndex + 1) - 1 }); } getOutline() { @@ -14555,32 +14749,33 @@ class WorkerTransport { return null; } const refStr = ref.gen === 0 ? `${ref.num}R` : `${ref.num}R${ref.gen}`; - return this.#pageRefCache.get(refStr) ?? null; + const pageIndex = this.#pageRefCache.get(refStr); + return pageIndex >= 0 ? this.#pagesMapper.getPageNumber(pageIndex + 1) : null; } } class RenderTask { - #internalRenderTask = null; + _internalRenderTask = null; onContinue = null; onError = null; constructor(internalRenderTask) { - this.#internalRenderTask = internalRenderTask; + this._internalRenderTask = internalRenderTask; } get promise() { - return this.#internalRenderTask.capability.promise; + return this._internalRenderTask.capability.promise; } cancel(extraDelay = 0) { - this.#internalRenderTask.cancel(null, extraDelay); + this._internalRenderTask.cancel(null, extraDelay); } get separateAnnots() { const { separateAnnots - } = this.#internalRenderTask.operatorList; + } = this._internalRenderTask.operatorList; if (!separateAnnots) { return false; } const { annotationCanvasMap - } = this.#internalRenderTask; + } = this._internalRenderTask; return separateAnnots.form || separateAnnots.canvas && annotationCanvasMap?.size > 0; } } @@ -14737,8 +14932,8 @@ class InternalRenderTask { } } } -const version = "5.4.569"; -const build = "6a4a3b060"; +const version = "5.4.590"; +const build = "550520193"; ;// ./src/display/editor/color_picker.js @@ -15325,7 +15520,7 @@ class AnnotationElement { this.annotationStorage.setValue(`${AnnotationEditorPrefix}${data.id}`, { id: data.id, annotationType: data.annotationType, - pageIndex: this.parent.page._pageIndex, + page: this.parent.page, popup, popupRef: data.popupRef, modificationDate: new Date() @@ -24000,6 +24195,9 @@ class AnnotationEditorLayer { this._structTree = structTreeLayer; this.#uiManager.addLayer(this); } + updatePageIndex(newPageIndex) { + this.pageIndex = newPageIndex; + } get isEmpty() { return this.#editors.size === 0; } @@ -24707,11 +24905,6 @@ class DrawLayer { #mapping = new Map(); #toUpdate = new Map(); static #id = 0; - constructor({ - pageIndex - }) { - this.pageIndex = pageIndex; - } setParent(parent) { if (!this.#parent) { this.#parent = parent; @@ -24773,7 +24966,7 @@ class DrawLayer { root.append(defs); const path = DrawLayer._svgFactory.createElement("path"); defs.append(path); - const pathId = `path_p${this.pageIndex}_${id}`; + const pathId = `path_${id}`; path.setAttribute("id", pathId); path.setAttribute("vector-effect", "non-scaling-stroke"); if (isPathUpdatable) { @@ -24797,14 +24990,14 @@ class DrawLayer { root.append(defs); const path = DrawLayer._svgFactory.createElement("path"); defs.append(path); - const pathId = `path_p${this.pageIndex}_${id}`; + const pathId = `path_${id}`; path.setAttribute("id", pathId); path.setAttribute("vector-effect", "non-scaling-stroke"); let maskId; if (mustRemoveSelfIntersections) { const mask = DrawLayer._svgFactory.createElement("mask"); defs.append(mask); - maskId = `mask_p${this.pageIndex}_${id}`; + maskId = `mask_${id}`; mask.setAttribute("id", maskId); mask.setAttribute("maskUnits", "objectBoundingBox"); const rect = DrawLayer._svgFactory.createElement("rect"); @@ -24955,6 +25148,7 @@ globalThis.pdfjsLib = { normalizeUnicode: normalizeUnicode, OPS: OPS, OutputScale: OutputScale, + PagesMapper: PagesMapper, PasswordResponses: PasswordResponses, PDFDataRangeTransport: PDFDataRangeTransport, PDFDateString: PDFDateString, @@ -24978,4 +25172,4 @@ globalThis.pdfjsLib = { XfaLayer: XfaLayer }; -export { AbortException, AnnotationEditorLayer, AnnotationEditorParamsType, AnnotationEditorType, AnnotationEditorUIManager, AnnotationLayer, AnnotationMode, AnnotationType, CSSConstants, ColorPicker, DOMSVGFactory, DrawLayer, util_FeatureTest as FeatureTest, GlobalWorkerOptions, util_ImageKind as ImageKind, InvalidPDFException, MathClamp, OPS, OutputScale, PDFDataRangeTransport, PDFDateString, PDFWorker, PasswordResponses, PermissionFlag, PixelsPerInch, RenderingCancelledException, ResponseException, SignatureExtractor, SupportedImageMimeTypes, TextLayer, TouchManager, Util, VerbosityLevel, XfaLayer, applyOpacity, build, createValidAbsoluteUrl, fetchData, findContrastColor, getDocument, getFilenameFromUrl, getPdfFilenameFromUrl, getRGB, getUuid, getXfaPageViewport, isDataScheme, isPdfFile, isValidExplicitDest, noContextMenu, normalizeUnicode, renderRichText, setLayerDimensions, shadow, stopEvent, updateUrlHash, version }; +export { AbortException, AnnotationEditorLayer, AnnotationEditorParamsType, AnnotationEditorType, AnnotationEditorUIManager, AnnotationLayer, AnnotationMode, AnnotationType, CSSConstants, ColorPicker, DOMSVGFactory, DrawLayer, util_FeatureTest as FeatureTest, GlobalWorkerOptions, util_ImageKind as ImageKind, InvalidPDFException, MathClamp, OPS, OutputScale, PDFDataRangeTransport, PDFDateString, PDFWorker, PagesMapper, PasswordResponses, PermissionFlag, PixelsPerInch, RenderingCancelledException, ResponseException, SignatureExtractor, SupportedImageMimeTypes, TextLayer, TouchManager, Util, VerbosityLevel, XfaLayer, applyOpacity, build, createValidAbsoluteUrl, fetchData, findContrastColor, getDocument, getFilenameFromUrl, getPdfFilenameFromUrl, getRGB, getUuid, getXfaPageViewport, isDataScheme, isPdfFile, isValidExplicitDest, noContextMenu, normalizeUnicode, renderRichText, setLayerDimensions, shadow, stopEvent, updateUrlHash, version }; diff --git a/toolkit/components/pdfjs/content/build/pdf.scripting.mjs b/toolkit/components/pdfjs/content/build/pdf.scripting.mjs index cd1133946d4a4..f924044a64184 100644 --- a/toolkit/components/pdfjs/content/build/pdf.scripting.mjs +++ b/toolkit/components/pdfjs/content/build/pdf.scripting.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.569 - * pdfjsBuild = 6a4a3b060 + * pdfjsVersion = 5.4.590 + * pdfjsBuild = 550520193 */ ;// ./src/scripting_api/constants.js diff --git a/toolkit/components/pdfjs/content/build/pdf.worker.mjs b/toolkit/components/pdfjs/content/build/pdf.worker.mjs index 8e276cac6bdb9..e038fe0744a2d 100644 --- a/toolkit/components/pdfjs/content/build/pdf.worker.mjs +++ b/toolkit/components/pdfjs/content/build/pdf.worker.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.569 - * pdfjsBuild = 6a4a3b060 + * pdfjsVersion = 5.4.590 + * pdfjsBuild = 550520193 */ /******/ // The require scope /******/ var __webpack_require__ = {}; @@ -2882,6 +2882,7 @@ class Stream extends BaseStream { const pos = this.pos; const strEnd = this.end; if (!length) { + this.pos = strEnd; return bytes.subarray(pos, strEnd); } let end = pos + length; @@ -8085,6 +8086,7 @@ const distDecode = new Int32Array([0x00001, 0x00002, 0x00003, 0x00004, 0x10005, const fixedLitCodeTab = [new Int32Array([0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff]), 9]; const fixedDistCodeTab = [new Int32Array([0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000]), 5]; class FlateStream extends DecodeStream { + #isAsync = true; constructor(str, maybeLength) { super(maybeLength); this.stream = str; @@ -8118,7 +8120,7 @@ class FlateStream extends DecodeStream { } async asyncGetBytes() { this.stream.reset(); - const bytes = this.stream.getBytes(); + const bytes = this.stream.isAsync ? await this.stream.asyncGetBytes() : this.stream.getBytes(); try { const { readable, @@ -8144,13 +8146,14 @@ class FlateStream extends DecodeStream { } return data; } catch { + this.#isAsync = false; this.stream = new Stream(bytes, 2, bytes.length, this.stream.dict); this.reset(); return null; } } get isAsync() { - return true; + return this.#isAsync; } getBits(bits) { const str = this.stream; @@ -12409,6 +12412,12 @@ class CMapFactory { if (encoding instanceof Name) { return createBuiltInCMap(encoding.name, fetchBuiltInCMap); } else if (encoding instanceof BaseStream) { + if (encoding.isAsync) { + const bytes = await encoding.asyncGetBytes(); + if (bytes) { + encoding = new Stream(bytes, 0, bytes.length, encoding.dict); + } + } const parsedCMap = await parseCMap(new CMap(), new Lexer(encoding), fetchBuiltInCMap, useCMap); if (parsedCMap.isIdentityCMap) { return createBuiltInCMap(parsedCMap.name, fetchBuiltInCMap); @@ -33461,7 +33470,7 @@ class PartialEvaluator { } return null; } - getOperatorList({ + async getOperatorList({ stream, task, resources, @@ -33470,6 +33479,12 @@ class PartialEvaluator { fallbackFontDict = null, prevRefs = null }) { + if (stream.isAsync) { + const bytes = await stream.asyncGetBytes(); + if (bytes) { + stream = new Stream(bytes, 0, bytes.length, stream.dict); + } + } const objId = stream.dict?.objId; const seenRefs = new RefSet(prevRefs); if (objId) { @@ -33975,7 +33990,7 @@ class PartialEvaluator { throw reason; }); } - getTextContent({ + async getTextContent({ stream, task, resources, @@ -33991,6 +34006,12 @@ class PartialEvaluator { prevRefs = null, intersector = null }) { + if (stream.isAsync) { + const bytes = await stream.asyncGetBytes(); + if (bytes) { + stream = new Stream(bytes, 0, bytes.length, stream.dict); + } + } const objId = stream.dict?.objId; const seenRefs = new RefSet(prevRefs); if (objId) { @@ -35515,8 +35536,16 @@ class PartialEvaluator { if (fontFile) { if (!(fontFile instanceof BaseStream)) { throw new FormatError("FontFile should be a stream"); - } else if (fontFile.isEmpty) { - throw new FormatError("FontFile is empty"); + } else { + if (fontFile.isAsync) { + const bytes = await fontFile.asyncGetBytes(); + if (bytes) { + fontFile = new Stream(bytes, 0, bytes.length, fontFile.dict); + } + } + if (fontFile.isEmpty) { + throw new FormatError("FontFile is empty"); + } } } } catch (ex) { @@ -56606,9 +56635,29 @@ class Page { async getContentStream() { const content = await this.pdfManager.ensure(this, "content"); if (content instanceof BaseStream && !content.isImageStream) { + if (content.isAsync) { + const bytes = await content.asyncGetBytes(); + if (bytes) { + return new Stream(bytes, 0, bytes.length, content.dict); + } + } return content; } if (Array.isArray(content)) { + const promises = []; + for (let i = 0, ii = content.length; i < ii; i++) { + const item = content[i]; + if (item instanceof BaseStream && item.isAsync) { + promises.push(item.asyncGetBytes().then(bytes => { + if (bytes) { + content[i] = new Stream(bytes, 0, bytes.length, item.dict); + } + })); + } + } + if (promises.length > 0) { + await Promise.all(promises); + } return new StreamsSequenceStream(content, this.#onSubStreamError.bind(this)); } return new NullStream(); @@ -56720,6 +56769,8 @@ class Page { task, intent, cacheKey, + pageId = this.pageIndex, + pageIndex = this.pageIndex, annotationStorage = null, modifiedIds = null }) { @@ -56772,7 +56823,7 @@ class Page { const opList = new OperatorList(intent, sink); handler.send("StartRenderPage", { transparency: partialEvaluator.hasBlendModes(resources, this.nonBlendModesSet), - pageIndex: this.pageIndex, + pageIndex, cacheKey }); await partialEvaluator.getOperatorList({ @@ -60315,7 +60366,7 @@ class WorkerMessageHandler { docId, apiVersion } = docParams; - const workerVersion = "5.4.569"; + const workerVersion = "5.4.590"; if (apiVersion !== workerVersion) { throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`); } @@ -60859,8 +60910,11 @@ class WorkerMessageHandler { }); }); handler.on("GetOperatorList", function (data, sink) { - const pageIndex = data.pageIndex; - pdfManager.getPage(pageIndex).then(function (page) { + const { + pageId, + pageIndex + } = data; + pdfManager.getPage(pageId).then(function (page) { const task = new WorkerTask(`GetOperatorList: page ${pageIndex}`); startWorkerTask(task); const start = verbosity >= VerbosityLevel.INFOS ? Date.now() : 0; @@ -60871,7 +60925,8 @@ class WorkerMessageHandler { intent: data.intent, cacheKey: data.cacheKey, annotationStorage: data.annotationStorage, - modifiedIds: data.modifiedIds + modifiedIds: data.modifiedIds, + pageIndex }).then(function (operatorListInfo) { finishWorkerTask(task); if (start) { @@ -60889,11 +60944,12 @@ class WorkerMessageHandler { }); handler.on("GetTextContent", function (data, sink) { const { + pageId, pageIndex, includeMarkedContent, disableNormalization } = data; - pdfManager.getPage(pageIndex).then(function (page) { + pdfManager.getPage(pageId).then(function (page) { const task = new WorkerTask("GetTextContent: page " + pageIndex); startWorkerTask(task); const start = verbosity >= VerbosityLevel.INFOS ? Date.now() : 0; diff --git a/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs b/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs index 6384e5a90e46d..8e0003acb18d9 100644 --- a/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs +++ b/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.569 - * pdfjsBuild = 6a4a3b060 + * pdfjsVersion = 5.4.590 + * pdfjsBuild = 550520193 */ /******/ // The require scope /******/ var __webpack_require__ = {}; @@ -85,6 +85,7 @@ const { normalizeUnicode, OPS, OutputScale, + PagesMapper, PasswordResponses, PDFDataRangeTransport, PDFDateString, @@ -597,65 +598,6 @@ function toggleExpandedBtn(button, toggle, view = null) { view?.classList.toggle("hidden", !toggle); } const calcRound = Math.fround; -class PagesMapper { - static #idToPageNumber = null; - static #pageNumberToId = null; - static #pagesNumber = 0; - get pagesNumber() { - return PagesMapper.#pagesNumber; - } - set pagesNumber(n) { - if (PagesMapper.#pagesNumber === n) { - return; - } - PagesMapper.#pagesNumber = n; - const pageNumberToId = PagesMapper.#pageNumberToId = new Uint32Array(2 * n); - const idToPageNumber = PagesMapper.#idToPageNumber = pageNumberToId.subarray(n); - for (let i = 0; i < n; i++) { - pageNumberToId[i] = idToPageNumber[i] = i + 1; - } - } - movePages(selectedPages, pagesToMove, index) { - const pageNumberToId = PagesMapper.#pageNumberToId; - const idToPageNumber = PagesMapper.#idToPageNumber; - const movedCount = pagesToMove.length; - const mappedPagesToMove = new Uint32Array(movedCount); - let removedBeforeTarget = 0; - for (let i = 0; i < movedCount; i++) { - const pageIndex = pagesToMove[i] - 1; - mappedPagesToMove[i] = pageNumberToId[pageIndex]; - if (pageIndex < index) { - removedBeforeTarget += 1; - } - } - const pagesNumber = PagesMapper.#pagesNumber; - let adjustedTarget = index - removedBeforeTarget; - const remainingLen = pagesNumber - movedCount; - adjustedTarget = MathClamp(adjustedTarget, 0, remainingLen); - for (let i = 0, r = 0; i < pagesNumber; i++) { - if (!selectedPages.has(i + 1)) { - pageNumberToId[r++] = pageNumberToId[i]; - } - } - pageNumberToId.copyWithin(adjustedTarget + movedCount, adjustedTarget, remainingLen); - pageNumberToId.set(mappedPagesToMove, adjustedTarget); - for (let i = 0, ii = pagesNumber; i < ii; i++) { - idToPageNumber[pageNumberToId[i] - 1] = i + 1; - } - } - getPageNumber(id) { - return PagesMapper.#idToPageNumber?.[id - 1] ?? id; - } - getPageId(pageNumber) { - return PagesMapper.#pageNumberToId?.[pageNumber - 1] ?? pageNumber; - } - static get instance() { - return shadow(this, "instance", new PagesMapper()); - } - getMapping() { - return PagesMapper.#pageNumberToId.subarray(0, this.pagesNumber); - } -} ;// ./web/app_options.js const OptionKind = { @@ -3441,9 +3383,15 @@ class CommentPopup { } } }); - this.#editor.comment = null; - this.#editor.focus(); + const editor = this.#editor; + const savedData = editor.comment; this.destroy(); + if (savedData?.text) { + editor._uiManager.deleteComment(editor, savedData); + } else { + editor.comment = null; + } + editor.focus(); }); del.addEventListener("contextmenu", noContextMenu); buttons.append(edit, del); @@ -3655,6 +3603,7 @@ class EditorUndoBar { stamp: "pdfjs-editor-undo-bar-message-stamp", ink: "pdfjs-editor-undo-bar-message-ink", signature: "pdfjs-editor-undo-bar-message-signature", + comment: "pdfjs-editor-undo-bar-message-comment", _multiple: "pdfjs-editor-undo-bar-message-multiple" }); constructor({ @@ -4150,7 +4099,6 @@ class PDFFindController { #state = null; #updateMatchesCountOnProgress = true; #visitedPagesCount = 0; - #pagesMapper = PagesMapper.instance; constructor({ linkService, eventBus, @@ -4407,12 +4355,11 @@ class PDFFindController { if (query.length === 0) { return; } - const pageId = this.getPageId(pageIndex); - const pageContent = this._pageContents[pageId]; + const pageContent = this._pageContents[pageIndex]; const matcherResult = this.match(query, pageContent, pageIndex); const matches = this._pageMatches[pageIndex] = []; const matchesLength = this._pageMatchesLength[pageIndex] = []; - const diffs = this._pageDiffs[pageId]; + const diffs = this._pageDiffs[pageIndex]; matcherResult?.forEach(({ index, length @@ -4441,7 +4388,7 @@ class PDFFindController { } } match(query, pageContent, pageIndex) { - const hasDiacritics = this._hasDiacritics[this.getPageId(pageIndex)]; + const hasDiacritics = this._hasDiacritics[pageIndex]; let isUnicode = false; if (typeof query === "string") { [isUnicode, query] = this.#convertToRegExpString(query, hasDiacritics); @@ -4514,27 +4461,19 @@ class PDFFindController { }); } } - getPageNumber(idx) { - return this.#pagesMapper.getPageNumber(idx + 1) - 1; - } - getPageId(pageNumber) { - return this.#pagesMapper.getPageId(pageNumber + 1) - 1; - } #updatePage(index) { if (this._scrollMatches && this._selected.pageIdx === index) { this._linkService.page = index + 1; } this._eventBus.dispatch("updatetextlayermatches", { source: this, - pageIndex: index, - pageId: this.getPageId(index) + pageIndex: index }); } #updateAllPages() { this._eventBus.dispatch("updatetextlayermatches", { source: this, - pageIndex: -1, - pageId: -1 + pageIndex: -1 }); } #nextMatch() { @@ -4559,7 +4498,7 @@ class PDFFindController { continue; } this._pendingFindMatches.add(i); - this._extractTextPromises[this.getPageId(i)].then(() => { + this._extractTextPromises[i].then(() => { this._pendingFindMatches.delete(i); this.#calculateMatch(i); }); @@ -4649,12 +4588,23 @@ class PDFFindController { this.#updatePage(this._selected.pageIdx); } } - #onPagesEdited() { + #onPagesEdited({ + pagesMapper + }) { if (this._extractTextPromises.length === 0) { return; } this.#onFindBarClose(); this._dirtyMatch = true; + const prevTextPromises = this._extractTextPromises; + const extractTextPromises = this._extractTextPromises.length = []; + for (let i = 0, ii = pagesMapper.length; i < ii; i++) { + const prevPageIndex = pagesMapper.getPrevPageNumber(i + 1) - 1; + if (prevPageIndex === -1) { + continue; + } + extractTextPromises.push(prevTextPromises[prevPageIndex] || Promise.resolve()); + } } #onFindBarClose(evt) { const pdfDocument = this._pdfDocument; @@ -6609,18 +6559,13 @@ class BasePDFPageView { class DrawLayerBuilder { #drawLayer = null; - constructor(options) { - this.pageIndex = options.pageIndex; - } async render({ intent = "display" }) { if (intent !== "display" || this.#drawLayer || this._cancelled) { return; } - this.#drawLayer = new DrawLayer({ - pageIndex: this.pageIndex - }); + this.#drawLayer = new DrawLayer(); } cancel() { this._cancelled = true; @@ -7319,7 +7264,7 @@ class TextHighlighter { if (!this.#eventAbortController) { this.#eventAbortController = new AbortController(); this.eventBus._on("updatetextlayermatches", evt => { - if (evt.pageId === this.pageIdx || evt.pageId === -1) { + if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) { this._updateMatches(); } }, { @@ -7388,7 +7333,7 @@ class TextHighlighter { textContentItemsStr, textDivs } = this; - const isSelectedPage = findController.getPageNumber(pageIdx) === findController.selected.pageIdx; + const isSelectedPage = pageIdx === findController.selected.pageIdx; const selectedMatchIdx = findController.selected.matchIdx; const highlightAll = findController.state.highlightAll; let prevEnd = null; @@ -7473,7 +7418,7 @@ class TextHighlighter { findController.scrollMatchIntoView({ element: textDivs[begin.divIdx], selectedLeft, - pageIndex: findController.getPageNumber(pageIdx), + pageIndex: pageIdx, matchIndex: selectedMatchIdx }); } @@ -7508,9 +7453,8 @@ class TextHighlighter { if (!findController?.highlightMatches || reset) { return; } - const pageNumber = findController.getPageNumber(pageIdx); - const pageMatches = findController.pageMatches[pageNumber] || null; - const pageMatchesLength = findController.pageMatchesLength[pageNumber] || null; + const pageMatches = findController.pageMatches[pageIdx] || null; + const pageMatchesLength = findController.pageMatchesLength[pageIdx] || null; this.matches = this._convertMatches(pageMatches, pageMatchesLength); this._renderMatches(this.matches); } @@ -7807,6 +7751,25 @@ class PDFPageView extends BasePDFPageView { } setLayerDimensions(div, viewport, true, false); } + updatePageNumber(newPageNumber) { + if (this.id === newPageNumber) { + return; + } + this.id = newPageNumber; + this.renderingId = `page${newPageNumber}`; + if (this.pdfPage) { + this.pdfPage.pageNumber = newPageNumber; + } + this.setPageLabel(this.pageLabel); + const { + div + } = this; + div.setAttribute("data-page-number", newPageNumber); + div.setAttribute("data-l10n-args", JSON.stringify({ + page: newPageNumber + })); + this._textHighlighter.pageIdx = newPageNumber - 1; + } setPdfPage(pdfPage) { this.pdfPage = pdfPage; this.pdfPageRotate = pdfPage.rotate; @@ -8381,9 +8344,7 @@ class PDFPageView extends BasePDFPageView { if (!annotationEditorUIManager) { return; } - this.drawLayer ||= new DrawLayerBuilder({ - pageIndex: this.id - }); + this.drawLayer ||= new DrawLayerBuilder(); await this.#renderDrawLayer(); this.drawLayer.setParent(canvasWrapper); if (this.annotationLayer || this.#annotationMode === AnnotationMode.DISABLE) { @@ -8540,10 +8501,9 @@ class PDFViewer { #supportsPinchToZoom = true; #textLayerMode = TextLayerMode.ENABLE; #viewerAlert = null; - #originalPages = null; #pagesMapper = PagesMapper.instance; constructor(options) { - const viewerVersion = "5.4.569"; + const viewerVersion = "5.4.590"; if (version !== viewerVersion) { throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`); } @@ -8906,6 +8866,7 @@ class PDFViewer { this.#annotationEditorUIManager = null; this.#annotationEditorMode = AnnotationEditorType.NONE; this.#printingAllowed = true; + this.#pagesMapper.pagesNumber = 0; } this.pdfDocument = pdfDocument; if (!pdfDocument) { @@ -9114,36 +9075,43 @@ class PDFViewer { this._pagesCapability.reject(reason); }); } - onBeforePagesEdited() { - this._currentPageId = this.#pagesMapper.getPageId(this._currentPageNumber); + async onBeforePagesEdited({ + pagesMapper + }) { + await this._pagesCapability.promise; + this._currentPageId = pagesMapper.getPageId(this._currentPageNumber); } onPagesEdited({ - index, - pagesToMove + pagesMapper }) { - const pagesMapper = this.#pagesMapper; this._currentPageNumber = pagesMapper.getPageNumber(this._currentPageId); + const prevPages = this._pages; + const newPages = this._pages = []; + for (let i = 0, ii = pagesMapper.pagesNumber; i < ii; i++) { + const prevPageNumber = pagesMapper.getPrevPageNumber(i + 1) - 1; + if (prevPageNumber === -1) { + continue; + } + const page = prevPages[prevPageNumber]; + newPages[i] = page; + page.updatePageNumber(i + 1); + } const viewerElement = this._scrollMode === ScrollMode.PAGE ? null : this.viewer; if (viewerElement) { - const pages = this._pages; - let page = pages[pagesToMove[0] - 1].div; - if (index === 0) { - pages[0].div.before(page); - } else { - pages[index - 1].div.after(page); - } - for (let i = 1, ii = pagesToMove.length; i < ii; i++) { - const newPage = pages[pagesToMove[i] - 1].div; - page.after(newPage); - page = newPage; + viewerElement.replaceChildren(); + const fragment = document.createDocumentFragment(); + for (let i = 0, ii = pagesMapper.pagesNumber; i < ii; i++) { + const { + div + } = newPages[i]; + div.setAttribute("data-page-number", i + 1); + fragment.append(div); } + viewerElement.append(fragment); } - this.#originalPages ||= this._pages; - const newPages = this._pages = []; - for (let i = 0, ii = pagesMapper.pagesNumber; i < ii; i++) { - const pageView = this.#originalPages[pagesMapper.getPageId(i + 1) - 1]; - newPages.push(pageView); - } + setTimeout(() => { + this.forceRendering(); + }); } setPageLabels(labels) { if (!this.pdfDocument) { @@ -9255,9 +9223,8 @@ class PDFViewer { div, id } = pageView; - const pageNumber = this.#pagesMapper.getPageNumber(id); - if (this._currentPageNumber !== pageNumber) { - this._setCurrentPageNumber(pageNumber); + if (this._currentPageNumber !== id) { + this._setCurrentPageNumber(id); } if (this._scrollMode === ScrollMode.PAGE) { this.#ensurePageViewVisible(); @@ -9567,18 +9534,18 @@ class PDFViewer { } this.renderingQueue.renderHighestPriority(visible); const isSimpleLayout = this._spreadMode === SpreadMode.NONE && (this._scrollMode === ScrollMode.PAGE || this._scrollMode === ScrollMode.VERTICAL); - const currentId = this.#pagesMapper.getPageId(this._currentPageNumber); + const currentPageNumber = this._currentPageNumber; let stillFullyVisible = false; for (const page of visiblePages) { if (page.percent < 100) { break; } - if (page.id === currentId && isSimpleLayout) { + if (page.id === currentPageNumber && isSimpleLayout) { stillFullyVisible = true; break; } } - this._setCurrentPageNumber(stillFullyVisible ? this._currentPageNumber : this.#pagesMapper.getPageNumber(visiblePages[0].id)); + this._setCurrentPageNumber(stillFullyVisible ? this._currentPageNumber : visiblePages[0].id); this._updateLocation(visible.first); this.eventBus.dispatch("updateviewarea", { source: this, @@ -10604,7 +10571,8 @@ const PDFViewerApplication = { pageColors, abortSignal, enableHWA, - enableSplitMerge: AppOptions.get("enableSplitMerge") + enableSplitMerge: AppOptions.get("enableSplitMerge"), + manageMenu: appConfig.viewsManager.manageMenu }); renderingQueue.setThumbnailViewer(this.pdfThumbnailViewer); } @@ -11532,6 +11500,7 @@ const PDFViewerApplication = { eventBus._on("setpreference", evt => preferences.set(evt.name, evt.value), opts); eventBus._on("pagesedited", this.onPagesEdited.bind(this), opts); eventBus._on("beforepagesedited", this.onBeforePagesEdited.bind(this), opts); + eventBus._on("savepageseditedpdf", this.onSavePagesEditedPDF.bind(this), opts); }, bindWindowEvents() { if (this._windowAbortController) { @@ -11666,6 +11635,29 @@ const PDFViewerApplication = { onPagesEdited(data) { this.pdfViewer.onPagesEdited(data); }, + async onSavePagesEditedPDF({ + data: { + includePages, + excludePages, + pageIndices + } + }) { + if (!this.pdfDocument) { + return; + } + const pageInfo = { + document: null, + includePages, + excludePages, + pageIndices + }; + const modifiedPdfBytes = await this.pdfDocument.extractPages([pageInfo]); + if (!modifiedPdfBytes) { + console.error("Something wrong happened when saving the edited PDF.\nPlease file a bug."); + return; + } + this.downloadManager.download(modifiedPdfBytes, this._downloadUrl, this._docFilename); + }, _accumulateTicks(ticks, prop) { if (this[prop] > 0 && ticks < 0 || this[prop] < 0 && ticks > 0) { this[prop] = 0; diff --git a/toolkit/components/pdfjs/content/web/viewer.css b/toolkit/components/pdfjs/content/web/viewer.css index 5c68d7cd5006a..d3bfd3b71e745 100644 --- a/toolkit/components/pdfjs/content/web/viewer.css +++ b/toolkit/components/pdfjs/content/web/viewer.css @@ -4554,6 +4554,16 @@ } } +button.hasPopupMenu{ + &[aria-expanded="true"] + menu{ + visibility:visible; + } + + &[aria-expanded="false"] + menu{ + visibility:hidden; + } +} + .popupMenu{ --menuitem-checkmark-icon:url(images/checkmark.svg); --menu-mark-icon-size:0; @@ -4614,6 +4624,7 @@ top:1px; margin:0; padding:5px; + box-sizing:border-box; background:var(--menu-bg); background-blend-mode:var(--menu-background-blend-mode); @@ -5202,6 +5213,7 @@ #actionSelector{ height:32px; + min-width:115px; width:auto; display:block; @@ -5233,8 +5245,9 @@ } } - > .contextMenu{ - min-width:115px; + > .popupMenu{ + width:auto; + z-index:1; } } } diff --git a/toolkit/components/pdfjs/content/web/viewer.html b/toolkit/components/pdfjs/content/web/viewer.html index 2a37b96a273af..d4272c7a4d753 100644 --- a/toolkit/components/pdfjs/content/web/viewer.html +++ b/toolkit/components/pdfjs/content/web/viewer.html @@ -68,7 +68,7 @@
-
-