/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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.Segmenter implementation. */ #include "builtin/intl/Segmenter.h" #include "mozilla/Assertions.h" #include "mozilla/IntegerTypeTraits.h" #include "mozilla/Range.h" #include "mozilla/UniquePtr.h" #if defined(MOZ_ICU4X) # include "mozilla/intl/ICU4XGeckoDataProvider.h" # include "ICU4XGraphemeClusterSegmenter.h" # include "ICU4XSentenceSegmenter.h" # include "ICU4XWordSegmenter.h" #endif #include "jspubtd.h" #include "NamespaceImports.h" #include "builtin/Array.h" #include "builtin/intl/CommonFunctions.h" #include "gc/AllocKind.h" #include "gc/GCContext.h" #include "js/CallArgs.h" #include "js/PropertyDescriptor.h" #include "js/PropertySpec.h" #include "js/RootingAPI.h" #include "js/StableStringChars.h" #include "js/TypeDecls.h" #include "js/Value.h" #include "util/Unicode.h" #include "vm/ArrayObject.h" #include "vm/GlobalObject.h" #include "vm/JSContext.h" #include "vm/PlainObject.h" #include "vm/WellKnownAtom.h" #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" using namespace js; const JSClassOps SegmenterObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve SegmenterObject::finalize, // finalize nullptr, // call nullptr, // construct nullptr, // trace }; const JSClass SegmenterObject::class_ = { "Intl.Segmenter", JSCLASS_HAS_RESERVED_SLOTS(SegmenterObject::SLOT_COUNT) | JSCLASS_HAS_CACHED_PROTO(JSProto_Segmenter) | JSCLASS_FOREGROUND_FINALIZE, &SegmenterObject::classOps_, &SegmenterObject::classSpec_, }; const JSClass& SegmenterObject::protoClass_ = PlainObject::class_; static bool segmenter_toSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setString(cx->names().Segmenter); return true; } static const JSFunctionSpec segmenter_static_methods[] = { JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_Segmenter_supportedLocalesOf", 1, 0), JS_FS_END, }; 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("toSource", segmenter_toSource, 0, 0), JS_FS_END, }; static const JSPropertySpec segmenter_properties[] = { JS_STRING_SYM_PS(toStringTag, "Intl.Segmenter", JSPROP_READONLY), JS_PS_END, }; static bool Segmenter(JSContext* cx, unsigned argc, Value* vp); const ClassSpec SegmenterObject::classSpec_ = { GenericCreateConstructor, GenericCreatePrototype, segmenter_static_methods, nullptr, segmenter_methods, segmenter_properties, nullptr, ClassSpec::DontDefineConstructor, }; /** * Intl.Segmenter ([ locales [ , options ]]) */ static bool Segmenter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. if (!ThrowIfNotConstructing(cx, args, "Intl.Segmenter")) { return false; } // Steps 2-3 (Inlined 9.1.14, OrdinaryCreateFromConstructor). Rooted proto(cx); if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Segmenter, &proto)) { return false; } Rooted segmenter(cx); segmenter = NewObjectWithClassProto(cx, proto); if (!segmenter) { return false; } HandleValue locales = args.get(0); HandleValue options = args.get(1); // Steps 4-13. if (!intl::InitializeObject(cx, segmenter, cx->names().InitializeSegmenter, locales, options)) { return false; } // Step 14. args.rval().setObject(*segmenter); return true; } const JSClassOps SegmentsObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve SegmentsObject::finalize, // finalize nullptr, // call nullptr, // construct nullptr, // trace }; const JSClass SegmentsObject::class_ = { "Intl.Segments", JSCLASS_HAS_RESERVED_SLOTS(SegmentsObject::SLOT_COUNT) | JSCLASS_FOREGROUND_FINALIZE, &SegmentsObject::classOps_, }; static const JSFunctionSpec segments_methods[] = { JS_SELF_HOSTED_FN("containing", "Intl_Segments_containing", 1, 0), JS_SELF_HOSTED_SYM_FN(iterator, "Intl_Segments_iterator", 0, 0), JS_FS_END, }; bool GlobalObject::initSegmentsProto(JSContext* cx, Handle global) { Rooted proto( cx, GlobalObject::createBlankPrototype(cx, global)); if (!proto) { return false; } if (!JS_DefineFunctions(cx, proto, segments_methods)) { return false; } global->initBuiltinProto(ProtoKind::SegmentsProto, proto); return true; } const JSClassOps SegmentIteratorObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve SegmentIteratorObject::finalize, // finalize nullptr, // call nullptr, // construct nullptr, // trace }; const JSClass SegmentIteratorObject::class_ = { "Intl.SegmentIterator", JSCLASS_HAS_RESERVED_SLOTS(SegmentIteratorObject::SLOT_COUNT) | JSCLASS_FOREGROUND_FINALIZE, &SegmentIteratorObject::classOps_, }; static const JSFunctionSpec segment_iterator_methods[] = { JS_SELF_HOSTED_FN("next", "Intl_SegmentIterator_next", 0, 0), JS_FS_END, }; static const JSPropertySpec segment_iterator_properties[] = { JS_STRING_SYM_PS(toStringTag, "Segmenter String Iterator", JSPROP_READONLY), JS_PS_END, }; bool GlobalObject::initSegmentIteratorProto(JSContext* cx, Handle global) { Rooted iteratorProto( cx, GlobalObject::getOrCreateIteratorPrototype(cx, global)); if (!iteratorProto) { return false; } Rooted proto( cx, GlobalObject::createBlankPrototypeInheriting( cx, iteratorProto)); if (!proto) { return false; } if (!JS_DefineFunctions(cx, proto, segment_iterator_methods)) { return false; } if (!JS_DefineProperties(cx, proto, segment_iterator_properties)) { return false; } global->initBuiltinProto(ProtoKind::SegmentIteratorProto, proto); return true; } struct Boundaries { // Start index of this segmentation boundary. int32_t startIndex = 0; // End index of this segmentation boundary. int32_t endIndex = 0; // |true| if the segment is word-like. (Only used for word segmentation.) bool isWordLike = false; }; /** * Find the segmentation boundary for the string character whose position is * |index|. The end position of the last segment boundary is |previousIndex|. */ template static Boundaries FindBoundaryFrom(const T& iter, int32_t previousIndex, int32_t index) { MOZ_ASSERT(previousIndex <= index, "previous index must not exceed the search index"); int32_t previous = previousIndex; while (true) { // Find the next possible break index. int32_t next = iter.next(); // If |next| is larger than the search index, we've found our segment end // index. if (next > index) { return {previous, next, iter.isWordLike()}; } // Otherwise store |next| as the start index of the next segment, previous = next; } } // TODO: Consider switching to the ICU4X C++ headers when the C++ headers // are in better shape: https://github.com/rust-diplomat/diplomat/issues/280 template class SegmenterBreakIteratorType { typename Interface::BreakIterator* impl_; public: explicit SegmenterBreakIteratorType(void* impl) : impl_(static_cast(impl)) { MOZ_ASSERT(impl); } int32_t next() const { return Interface::next(impl_); } bool isWordLike() const { return Interface::isWordLike(impl_); } }; #if defined(MOZ_ICU4X) // Each SegmenterBreakIterator interface contains the following definitions: // // - BreakIterator: Type of the ICU4X break iterator. // - Segmenter: Type of the ICU4X segmenter. // - Char: Character type, either `JS::Latin1Char` or `char16_t`. // - create: Static method to create a new instance of `BreakIterator`. // - destroy: Static method to destroy an instance of `BreakIterator`. // - next: Static method to fetch the next break iteration index. // - isWordLike: Static method to determine if the current segment is word-like. // // // Each Segmenter interface contains the following definitions: // // - Segmenter: Type of the ICU4X segmenter. // - BreakIteratorLatin1: SegmenterBreakIterator interface to Latin1 strings. // - BreakIteratorTwoByte: SegmenterBreakIterator interface to TwoByte strings. // - create: Static method to create a new instance of `Segmenter`. // - destroy: Static method to destroy an instance of `Segmenter`. struct GraphemeClusterSegmenterBreakIteratorLatin1 { using BreakIterator = capi::ICU4XGraphemeClusterBreakIteratorLatin1; using Segmenter = capi::ICU4XGraphemeClusterSegmenter; using Char = JS::Latin1Char; static constexpr auto& create = capi::ICU4XGraphemeClusterSegmenter_segment_latin1; static constexpr auto& destroy = capi::ICU4XGraphemeClusterBreakIteratorLatin1_destroy; static constexpr auto& next = capi::ICU4XGraphemeClusterBreakIteratorLatin1_next; static bool isWordLike(const BreakIterator*) { return false; } }; struct GraphemeClusterSegmenterBreakIteratorTwoByte { using BreakIterator = capi::ICU4XGraphemeClusterBreakIteratorUtf16; using Segmenter = capi::ICU4XGraphemeClusterSegmenter; using Char = char16_t; static constexpr auto& create = capi::ICU4XGraphemeClusterSegmenter_segment_utf16; static constexpr auto& destroy = capi::ICU4XGraphemeClusterBreakIteratorUtf16_destroy; static constexpr auto& next = capi::ICU4XGraphemeClusterBreakIteratorUtf16_next; static bool isWordLike(const BreakIterator*) { return false; } }; struct GraphemeClusterSegmenter { using Segmenter = capi::ICU4XGraphemeClusterSegmenter; using BreakIteratorLatin1 = SegmenterBreakIteratorType; using BreakIteratorTwoByte = SegmenterBreakIteratorType; static constexpr auto& create = capi::ICU4XGraphemeClusterSegmenter_create; static constexpr auto& destroy = capi::ICU4XGraphemeClusterSegmenter_destroy; }; struct WordSegmenterBreakIteratorLatin1 { using BreakIterator = capi::ICU4XWordBreakIteratorLatin1; using Segmenter = capi::ICU4XWordSegmenter; using Char = JS::Latin1Char; static constexpr auto& create = capi::ICU4XWordSegmenter_segment_latin1; static constexpr auto& destroy = capi::ICU4XWordBreakIteratorLatin1_destroy; static constexpr auto& next = capi::ICU4XWordBreakIteratorLatin1_next; static constexpr auto& isWordLike = capi::ICU4XWordBreakIteratorLatin1_is_word_like; }; struct WordSegmenterBreakIteratorTwoByte { using BreakIterator = capi::ICU4XWordBreakIteratorUtf16; using Segmenter = capi::ICU4XWordSegmenter; using Char = char16_t; static constexpr auto& create = capi::ICU4XWordSegmenter_segment_utf16; static constexpr auto& destroy = capi::ICU4XWordBreakIteratorUtf16_destroy; static constexpr auto& next = capi::ICU4XWordBreakIteratorUtf16_next; static constexpr auto& isWordLike = capi::ICU4XWordBreakIteratorUtf16_is_word_like; }; struct WordSegmenter { using Segmenter = capi::ICU4XWordSegmenter; using BreakIteratorLatin1 = SegmenterBreakIteratorType; using BreakIteratorTwoByte = SegmenterBreakIteratorType; static constexpr auto& create = capi::ICU4XWordSegmenter_create_auto; static constexpr auto& destroy = capi::ICU4XWordSegmenter_destroy; }; struct SentenceSegmenterBreakIteratorLatin1 { using BreakIterator = capi::ICU4XSentenceBreakIteratorLatin1; using Segmenter = capi::ICU4XSentenceSegmenter; using Char = JS::Latin1Char; static constexpr auto& create = capi::ICU4XSentenceSegmenter_segment_latin1; static constexpr auto& destroy = capi::ICU4XSentenceBreakIteratorLatin1_destroy; static constexpr auto& next = capi::ICU4XSentenceBreakIteratorLatin1_next; static bool isWordLike(const BreakIterator*) { return false; } }; struct SentenceSegmenterBreakIteratorTwoByte { using BreakIterator = capi::ICU4XSentenceBreakIteratorUtf16; using Segmenter = capi::ICU4XSentenceSegmenter; using Char = char16_t; static constexpr auto& create = capi::ICU4XSentenceSegmenter_segment_utf16; static constexpr auto& destroy = capi::ICU4XSentenceBreakIteratorUtf16_destroy; static constexpr auto& next = capi::ICU4XSentenceBreakIteratorUtf16_next; static bool isWordLike(const BreakIterator*) { return false; } }; struct SentenceSegmenter { using Segmenter = capi::ICU4XSentenceSegmenter; using BreakIteratorLatin1 = SegmenterBreakIteratorType; using BreakIteratorTwoByte = SegmenterBreakIteratorType; static constexpr auto& create = capi::ICU4XSentenceSegmenter_create; static constexpr auto& destroy = capi::ICU4XSentenceSegmenter_destroy; }; #endif /** * Create a new ICU4X segmenter instance. */ template static typename Interface::Segmenter* CreateSegmenter(JSContext* cx) { auto result = Interface::create(mozilla::intl::GetDataProvider()); if (!result.is_ok) { intl::ReportInternalError(cx); return nullptr; } return result.ok; } static bool EnsureInternalsResolved(JSContext* cx, Handle segmenter) { if (segmenter->getLocale()) { return true; } Rooted value(cx); Rooted internals(cx, intl::GetInternalsObject(cx, segmenter)); if (!internals) { return false; } 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; } SegmenterGranularity granularity; { JSLinearString* linear = value.toString()->ensureLinear(cx); if (!linear) { return false; } if (StringEqualsLiteral(linear, "grapheme")) { granularity = SegmenterGranularity::Grapheme; } else if (StringEqualsLiteral(linear, "word")) { granularity = SegmenterGranularity::Word; } else { MOZ_ASSERT(StringEqualsLiteral(linear, "sentence")); granularity = SegmenterGranularity::Sentence; } } #if defined(MOZ_ICU4X) switch (granularity) { case SegmenterGranularity::Grapheme: { auto* seg = CreateSegmenter(cx); if (!seg) { return false; } segmenter->setSegmenter(seg); break; } case SegmenterGranularity::Word: { auto* seg = CreateSegmenter(cx); if (!seg) { return false; } segmenter->setSegmenter(seg); break; } case SegmenterGranularity::Sentence: { auto* seg = CreateSegmenter(cx); if (!seg) { return false; } segmenter->setSegmenter(seg); break; } } #endif segmenter->setLocale(locale); segmenter->setGranularity(granularity); return true; } /** * Destroy an ICU4X segmenter instance. */ template static void DestroySegmenter(void* seg) { auto* segmenter = static_cast(seg); Interface::destroy(segmenter); } void SegmenterObject::finalize(JS::GCContext* gcx, JSObject* obj) { MOZ_ASSERT(gcx->onMainThread()); auto& segmenter = obj->as(); if (void* seg = segmenter.getSegmenter()) { #if defined(MOZ_ICU4X) switch (segmenter.getGranularity()) { case SegmenterGranularity::Grapheme: { DestroySegmenter(seg); break; } case SegmenterGranularity::Word: { DestroySegmenter(seg); break; } case SegmenterGranularity::Sentence: { DestroySegmenter(seg); break; } } #else MOZ_CRASH("ICU4X disabled"); #endif } } /** * Destroy an ICU4X break iterator instance. */ template static void DestroyBreakIterator(void* brk) { auto* breakIterator = static_cast(brk); Interface::destroy(breakIterator); } /** * Destroy the ICU4X break iterator attached to |segments|. */ template static void DestroyBreakIterator(const T* segments) { #if defined(MOZ_ICU4X) void* brk = segments->getBreakIterator(); MOZ_ASSERT(brk); bool isLatin1 = segments->getString()->hasLatin1Chars(); switch (segments->getGranularity()) { case SegmenterGranularity::Grapheme: { if (isLatin1) { DestroyBreakIterator(brk); } else { DestroyBreakIterator(brk); } break; } case SegmenterGranularity::Word: { if (isLatin1) { DestroyBreakIterator(brk); } else { DestroyBreakIterator(brk); } break; } case SegmenterGranularity::Sentence: { if (isLatin1) { DestroyBreakIterator(brk); } else { DestroyBreakIterator(brk); } break; } } #else MOZ_CRASH("ICU4X disabled"); #endif } void SegmentsObject::finalize(JS::GCContext* gcx, JSObject* obj) { MOZ_ASSERT(gcx->onMainThread()); auto* segments = &obj->as(); bool isLatin1 = segments->getString()->hasLatin1Chars(); if (void* chars = segments->getStringChars()) { size_t length = segments->getString()->length(); if (isLatin1) { intl::RemoveICUCellMemory(gcx, segments, length * sizeof(JS::Latin1Char)); } else { intl::RemoveICUCellMemory(gcx, segments, length * sizeof(char16_t)); } js_free(chars); } if (segments->getBreakIterator()) { DestroyBreakIterator(segments); } } void SegmentIteratorObject::finalize(JS::GCContext* gcx, JSObject* obj) { MOZ_ASSERT(gcx->onMainThread()); auto* iterator = &obj->as(); bool isLatin1 = iterator->getString()->hasLatin1Chars(); if (void* chars = iterator->getStringChars()) { size_t length = iterator->getString()->length(); if (isLatin1) { intl::RemoveICUCellMemory(gcx, iterator, length * sizeof(JS::Latin1Char)); } else { intl::RemoveICUCellMemory(gcx, iterator, length * sizeof(char16_t)); } js_free(chars); } if (iterator->getBreakIterator()) { DestroyBreakIterator(iterator); } } template static Boundaries FindBoundaryFrom(Handle segments, int32_t index) { MOZ_ASSERT(0 <= index && uint32_t(index) < segments->getString()->length()); Iterator iter(segments->getBreakIterator()); return FindBoundaryFrom(iter, segments->getIndex(), index); } template static Boundaries GraphemeBoundaries(Handle segments, int32_t index) { #if defined(MOZ_ICU4X) if (segments->getString()->hasLatin1Chars()) { return FindBoundaryFrom( segments, index); } return FindBoundaryFrom( segments, index); #else MOZ_CRASH("ICU4X disabled"); #endif } template static Boundaries WordBoundaries(Handle segments, int32_t index) { #if defined(MOZ_ICU4X) if (segments->getString()->hasLatin1Chars()) { return FindBoundaryFrom(segments, index); } return FindBoundaryFrom(segments, index); #else MOZ_CRASH("ICU4X disabled"); #endif } template static Boundaries SentenceBoundaries(Handle segments, int32_t index) { #if defined(MOZ_ICU4X) if (segments->getString()->hasLatin1Chars()) { return FindBoundaryFrom(segments, index); } return FindBoundaryFrom(segments, index); #else MOZ_CRASH("ICU4X disabled"); #endif } /** * Ensure the string characters have been copied into |segments| in preparation * for passing the string characters to ICU4X. */ template static bool EnsureStringChars(JSContext* cx, Handle segments) { if (segments->hasStringChars()) { return true; } Rooted string(cx, segments->getString()->ensureLinear(cx)); if (!string) { return false; } size_t length = string->length(); JS::AutoCheckCannotGC nogc; if (string->hasLatin1Chars()) { auto chars = DuplicateString(cx, string->latin1Chars(nogc), length); if (!chars) { return false; } segments->setLatin1Chars(chars.release()); intl::AddICUCellMemory(segments, length * sizeof(JS::Latin1Char)); } else { auto chars = DuplicateString(cx, string->twoByteChars(nogc), length); if (!chars) { return false; } segments->setTwoByteChars(chars.release()); intl::AddICUCellMemory(segments, length * sizeof(char16_t)); } return true; } /** * Create a new ICU4X break iterator instance. */ template static auto* CreateBreakIterator(Handle segments) { void* segmenter = segments->getSegmenter()->getSegmenter(); MOZ_ASSERT(segmenter); void* chars = segments->getStringChars(); MOZ_ASSERT(chars); size_t length = segments->getString()->length(); using Unsigned = typename mozilla::UnsignedStdintTypeForSize::Type; auto* seg = static_cast(segmenter); auto* ch = static_cast(chars); return Interface::create(seg, ch, length); } /** * Ensure |segments| has a break iterator whose current segment index is at most * |index|. */ template static bool EnsureBreakIterator(JSContext* cx, Handle segments, int32_t index) { if (segments->getBreakIterator()) { // Reuse the break iterator if its current segment index is at most |index|. if (index >= segments->getIndex()) { return true; } // Reverse iteration not supported. Destroy the previous break iterator and // start from fresh. DestroyBreakIterator(segments.get()); // Reset internal state. segments->setBreakIterator(nullptr); segments->setIndex(0); } // Ensure the string characters can be passed to ICU4X. if (!EnsureStringChars(cx, segments)) { return false; } #if defined(MOZ_ICU4X) bool isLatin1 = segments->getString()->hasLatin1Chars(); // Create a new break iterator based on the granularity and character type. void* brk; switch (segments->getGranularity()) { case SegmenterGranularity::Grapheme: { if (isLatin1) { brk = CreateBreakIterator( segments); } else { brk = CreateBreakIterator( segments); } break; } case SegmenterGranularity::Word: { if (isLatin1) { brk = CreateBreakIterator(segments); } else { brk = CreateBreakIterator(segments); } break; } case SegmenterGranularity::Sentence: { if (isLatin1) { brk = CreateBreakIterator(segments); } else { brk = CreateBreakIterator( segments); } break; } } MOZ_RELEASE_ASSERT(brk); segments->setBreakIterator(brk); MOZ_ASSERT(segments->getIndex() == 0, "index is initially zero"); return true; #else MOZ_CRASH("ICU4X disabled"); #endif } /** * Create the boundaries result array for self-hosted code. */ static ArrayObject* CreateBoundaries(JSContext* cx, Boundaries boundaries, SegmenterGranularity granularity) { auto [startIndex, endIndex, isWordLike] = boundaries; auto* result = NewDenseFullyAllocatedArray(cx, 3); if (!result) { return nullptr; } result->setDenseInitializedLength(3); result->initDenseElement(0, Int32Value(startIndex)); result->initDenseElement(1, Int32Value(endIndex)); if (granularity == SegmenterGranularity::Word) { result->initDenseElement(2, BooleanValue(isWordLike)); } else { result->initDenseElement(2, UndefinedValue()); } return result; } template static ArrayObject* FindSegmentBoundaries(JSContext* cx, Handle segments, int32_t index) { // Ensure break iteration can start at |index|. if (!EnsureBreakIterator(cx, segments, index)) { return nullptr; } // Find the actual segment boundaries. Boundaries boundaries{}; switch (segments->getGranularity()) { case SegmenterGranularity::Grapheme: { boundaries = GraphemeBoundaries(segments, index); break; } case SegmenterGranularity::Word: { boundaries = WordBoundaries(segments, index); break; } case SegmenterGranularity::Sentence: { boundaries = SentenceBoundaries(segments, index); break; } } // Remember the end index of the current boundary segment. segments->setIndex(boundaries.endIndex); 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; } Rooted proto( cx, GlobalObject::getOrCreateSegmentsPrototype(cx, cx->global())); if (!proto) { return false; } auto* segments = NewObjectWithGivenProto(cx, proto); if (!segments) { return false; } segments->setSegmenter(segmenter); segments->setGranularity(segmenter->getGranularity()); segments->setString(string); segments->setIndex(0); args.rval().setObject(*segments); return true; } bool js::intl_CreateSegmentIterator(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); Rooted segments(cx, &args[0].toObject().as()); Rooted proto( cx, GlobalObject::getOrCreateSegmentIteratorPrototype(cx, cx->global())); if (!proto) { return false; } auto* iterator = NewObjectWithGivenProto(cx, proto); if (!iterator) { return false; } iterator->setSegmenter(segments->getSegmenter()); iterator->setGranularity(segments->getGranularity()); iterator->setString(segments->getString()); iterator->setIndex(0); args.rval().setObject(*iterator); return true; } bool js::intl_FindSegmentBoundaries(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); Rooted segments(cx, &args[0].toObject().as()); int32_t index = args[1].toInt32(); MOZ_ASSERT(index >= 0); MOZ_ASSERT(uint32_t(index) < segments->getString()->length()); auto* result = FindSegmentBoundaries( cx, static_cast>(segments), index); if (!result) { return false; } args.rval().setObject(*result); return true; } bool js::intl_FindNextSegmentBoundaries(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); Rooted iterator( cx, &args[0].toObject().as()); int32_t index = iterator->getIndex(); MOZ_ASSERT(index >= 0); MOZ_ASSERT(uint32_t(index) < iterator->getString()->length()); auto* result = FindSegmentBoundaries( cx, static_cast>(iterator), index); if (!result) { return false; } args.rval().setObject(*result); return true; }