Skip to content
Snippets Groups Projects
Commit d92ecbcd authored by Yoshi Cheng-Hao Huang's avatar Yoshi Cheng-Hao Huang
Browse files

Bug 1719747 - Part 3: ListFormat::FormatToParts takes a callback. r=gregtatum,tcampbell

As discussed in https://bugzilla.mozilla.org/show_bug.cgi?id=1719747#c6,
In Part 1, the ownership of the Span in PartsVector is maintained by
ICU. This method adds a callback so js::intl::ListFormat could copy the
content of the Span to Spidermonkey, and mozilla::intl::ListFormat could
use ScopedICUObject to release the string owned by ICU earlier.

Differential Revision: https://phabricator.services.mozilla.com/D123166
parent f45fe880
No related branches found
No related tags found
No related merge requests found
...@@ -119,22 +119,24 @@ TEST(IntlListFormat, FormatToParts) ...@@ -119,22 +119,24 @@ TEST(IntlListFormat, FormatToParts)
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Alice"))); MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Alice")));
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Bob"))); MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Bob")));
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Charlie"))); MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Charlie")));
ListFormat::PartVector parts;
ASSERT_TRUE(lf->FormatToParts(list, parts).isOk());
// 3 elements, and 2 literals. ASSERT_TRUE(
ASSERT_EQ(parts.length(), 5u); lf->FormatToParts(list, [](const ListFormat::PartVector& parts) {
// 3 elements, and 2 literals.
EXPECT_EQ((parts.length()), (5u));
ASSERT_EQ(parts[0], (ListFormat::Part{ListFormat::PartType::Element, EXPECT_EQ(parts[0], (ListFormat::Part{ListFormat::PartType::Element,
MakeStringSpan(u"Alice")})); MakeStringSpan(u"Alice")}));
ASSERT_EQ(parts[1], (ListFormat::Part{ListFormat::PartType::Literal, EXPECT_EQ(parts[1], (ListFormat::Part{ListFormat::PartType::Literal,
MakeStringSpan(u", ")})); MakeStringSpan(u", ")}));
ASSERT_EQ(parts[2], (ListFormat::Part{ListFormat::PartType::Element, EXPECT_EQ(parts[2], (ListFormat::Part{ListFormat::PartType::Element,
MakeStringSpan(u"Bob")})); MakeStringSpan(u"Bob")}));
ASSERT_EQ(parts[3], (ListFormat::Part{ListFormat::PartType::Literal, EXPECT_EQ(parts[3], (ListFormat::Part{ListFormat::PartType::Literal,
MakeStringSpan(u", and ")})); MakeStringSpan(u", and ")}));
ASSERT_EQ(parts[4], (ListFormat::Part{ListFormat::PartType::Element, EXPECT_EQ(parts[4], (ListFormat::Part{ListFormat::PartType::Element,
MakeStringSpan(u"Charlie")})); MakeStringSpan(u"Charlie")}));
return true;
}).isOk());
} }
} // namespace mozilla::intl } // namespace mozilla::intl
...@@ -17,22 +17,13 @@ namespace mozilla::intl { ...@@ -17,22 +17,13 @@ namespace mozilla::intl {
return Err(ICUError::InternalError); return Err(ICUError::InternalError);
} }
UFormattedList* fl = ulistfmt_openResult(&status); return UniquePtr<ListFormat>(new ListFormat(fmt));
if (U_FAILURE(status)) {
return Err(ICUError::InternalError);
}
return UniquePtr<ListFormat>(new ListFormat(fmt, fl));
} }
ListFormat::~ListFormat() { ListFormat::~ListFormat() {
if (mListFormatter) { if (mListFormatter) {
ulistfmt_close(mListFormatter.GetMut()); ulistfmt_close(mListFormatter.GetMut());
} }
if (mFormattedList) {
ulistfmt_closeResult(mFormattedList.GetMut());
}
} }
/* static */ UListFormatterType ListFormat::ToUListFormatterType(Type type) { /* static */ UListFormatterType ListFormat::ToUListFormatterType(Type type) {
...@@ -62,22 +53,28 @@ ListFormat::~ListFormat() { ...@@ -62,22 +53,28 @@ ListFormat::~ListFormat() {
return ULISTFMT_WIDTH_WIDE; return ULISTFMT_WIDTH_WIDE;
} }
ICUResult ListFormat::FormatToParts(const StringList& list, PartVector& parts) { ICUResult ListFormat::FormatToParts(const StringList& list,
PartsCallback&& callback) {
UErrorCode status = U_ZERO_ERROR; UErrorCode status = U_ZERO_ERROR;
mozilla::Vector<const char16_t*, DEFAULT_LIST_LENGTH> u16strings; mozilla::Vector<const char16_t*, DEFAULT_LIST_LENGTH> u16strings;
mozilla::Vector<int32_t, DEFAULT_LIST_LENGTH> u16stringLens; mozilla::Vector<int32_t, DEFAULT_LIST_LENGTH> u16stringLens;
MOZ_TRY(ConvertStringListToVectors(list, u16strings, u16stringLens)); MOZ_TRY(ConvertStringListToVectors(list, u16strings, u16stringLens));
UFormattedList* formatted = ulistfmt_openResult(&status);
if (U_FAILURE(status)) {
return Err(ICUError::InternalError);
}
ScopedICUObject<UFormattedList, ulistfmt_closeResult> toClose(formatted);
ulistfmt_formatStringsToResult(mListFormatter.GetConst(), u16strings.begin(), ulistfmt_formatStringsToResult(mListFormatter.GetConst(), u16strings.begin(),
u16stringLens.begin(), int32_t(list.length()), u16stringLens.begin(), int32_t(list.length()),
mFormattedList.GetMut(), &status); formatted, &status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return Err(ICUError::InternalError); return Err(ICUError::InternalError);
} }
const UFormattedValue* formattedValue = const UFormattedValue* formattedValue =
ulistfmt_resultAsValue(mFormattedList.GetConst(), &status); ulistfmt_resultAsValue(formatted, &status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return Err(ICUError::InternalError); return Err(ICUError::InternalError);
} }
...@@ -93,6 +90,7 @@ ICUResult ListFormat::FormatToParts(const StringList& list, PartVector& parts) { ...@@ -93,6 +90,7 @@ ICUResult ListFormat::FormatToParts(const StringList& list, PartVector& parts) {
mozilla::Span<const char16_t> formattedSpan{formattedChars, formattedSize}; mozilla::Span<const char16_t> formattedSpan{formattedChars, formattedSize};
size_t lastEndIndex = 0; size_t lastEndIndex = 0;
PartVector parts;
auto AppendPart = [&](PartType type, size_t beginIndex, size_t endIndex) { auto AppendPart = [&](PartType type, size_t beginIndex, size_t endIndex) {
if (!parts.emplaceBack(type, formattedSpan.FromTo(beginIndex, endIndex))) { if (!parts.emplaceBack(type, formattedSpan.FromTo(beginIndex, endIndex))) {
return false; return false;
...@@ -159,6 +157,9 @@ ICUResult ListFormat::FormatToParts(const StringList& list, PartVector& parts) { ...@@ -159,6 +157,9 @@ ICUResult ListFormat::FormatToParts(const StringList& list, PartVector& parts) {
} }
} }
if (!callback(parts)) {
return Err(ICUError::InternalError);
}
return Ok(); return Ok();
} }
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#ifndef intl_components_ListFormat_h_ #ifndef intl_components_ListFormat_h_
#define intl_components_ListFormat_h_ #define intl_components_ListFormat_h_
#include <functional>
#include "mozilla/CheckedInt.h" #include "mozilla/CheckedInt.h"
#include "mozilla/intl/ICU4CGlue.h" #include "mozilla/intl/ICU4CGlue.h"
#include "mozilla/Result.h" #include "mozilla/Result.h"
...@@ -12,7 +14,6 @@ ...@@ -12,7 +14,6 @@
#include "unicode/ulistformatter.h" #include "unicode/ulistformatter.h"
struct UListFormatter; struct UListFormatter;
struct UFormattedList;
namespace mozilla::intl { namespace mozilla::intl {
...@@ -109,27 +110,33 @@ class ListFormat final { ...@@ -109,27 +110,33 @@ class ListFormat final {
using Part = std::pair<PartType, mozilla::Span<const char16_t>>; using Part = std::pair<PartType, mozilla::Span<const char16_t>>;
using PartVector = mozilla::Vector<Part, DEFAULT_LIST_LENGTH>; using PartVector = mozilla::Vector<Part, DEFAULT_LIST_LENGTH>;
using PartsCallback = std::function<bool(const PartVector& parts)>;
/** /**
* Format the list to a list of parts, and write the result into parts. * Format the list to a list of parts, and the callback will be called with
* the formatted parts.
*
* In the callback, it has a argument of type PartVector&, which is the vector
* of the formatted parts. The life-cycle of the PartVector is valid only
* during the callback call, so the caller of FormatToParts needs to copy the
* data in PartVector to its own storage during the callback.
*
* The PartVector contains mozilla::Span which point to memory which may be * The PartVector contains mozilla::Span which point to memory which may be
* overridden when the next format method is called. * overridden when the next format method is called.
* *
* https://tc39.es/ecma402/#sec-Intl.ListFormat.prototype.formatToParts * https://tc39.es/ecma402/#sec-Intl.ListFormat.prototype.formatToParts
* https://tc39.es/ecma402/#sec-formatlisttoparts * https://tc39.es/ecma402/#sec-formatlisttoparts
*/ */
ICUResult FormatToParts(const StringList& list, PartVector& parts); ICUResult FormatToParts(const StringList& list, PartsCallback&& callback);
private: private:
ListFormat() = delete; ListFormat() = delete;
ListFormat(UListFormatter* fmt, UFormattedList* fl) explicit ListFormat(UListFormatter* fmt) : mListFormatter(fmt) {}
: mListFormatter(fmt), mFormattedList(fl) {}
ListFormat(const ListFormat&) = delete; ListFormat(const ListFormat&) = delete;
ListFormat& operator=(const ListFormat&) = delete; ListFormat& operator=(const ListFormat&) = delete;
ICUPointer<UListFormatter> mListFormatter = ICUPointer<UListFormatter> mListFormatter =
ICUPointer<UListFormatter>(nullptr); ICUPointer<UListFormatter>(nullptr);
ICUPointer<UFormattedList> mFormattedList =
ICUPointer<UFormattedList>(nullptr);
// Convert StringList to an array of type 'const char16_t*' and an array of // Convert StringList to an array of type 'const char16_t*' and an array of
// int32 for ICU-API. // int32 for ICU-API.
......
...@@ -243,55 +243,58 @@ static bool FormatList(JSContext* cx, mozilla::intl::ListFormat* lf, ...@@ -243,55 +243,58 @@ static bool FormatList(JSContext* cx, mozilla::intl::ListFormat* lf,
static bool FormatListToParts(JSContext* cx, mozilla::intl::ListFormat* lf, static bool FormatListToParts(JSContext* cx, mozilla::intl::ListFormat* lf,
const mozilla::intl::ListFormat::StringList& list, const mozilla::intl::ListFormat::StringList& list,
MutableHandleValue result) { MutableHandleValue result) {
mozilla::intl::ListFormat::PartVector parts; auto formatResult = lf->FormatToParts(
auto formatResult = lf->FormatToParts(list, parts); list,
[cx,
&result](const mozilla::intl::ListFormat::PartVector& parts) -> bool {
RootedArrayObject partsArray(
cx, NewDenseFullyAllocatedArray(cx, parts.length()));
if (!partsArray) {
return false;
}
partsArray->ensureDenseInitializedLength(0, parts.length());
RootedObject singlePart(cx);
RootedValue val(cx);
size_t index = 0;
for (const mozilla::intl::ListFormat::Part& part : parts) {
singlePart = NewPlainObject(cx);
if (!singlePart) {
return false;
}
if (part.first == mozilla::intl::ListFormat::PartType::Element) {
val = StringValue(cx->names().element);
} else {
val = StringValue(cx->names().literal);
}
if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) {
return false;
}
JSString* partStr =
NewStringCopyN<CanGC>(cx, part.second.data(), part.second.size());
if (!partStr) {
return false;
}
val = StringValue(partStr);
if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) {
return false;
}
partsArray->initDenseElement(index++, ObjectValue(*singlePart));
}
MOZ_ASSERT(index == parts.length());
result.setObject(*partsArray);
return true;
});
if (formatResult.isErr()) { if (formatResult.isErr()) {
js::intl::ReportInternalError(cx, formatResult.unwrapErr()); js::intl::ReportInternalError(cx, formatResult.unwrapErr());
return false; return false;
} }
RootedArrayObject partsArray(cx,
NewDenseFullyAllocatedArray(cx, parts.length()));
if (!partsArray) {
return false;
}
partsArray->ensureDenseInitializedLength(0, parts.length());
RootedObject singlePart(cx);
RootedValue val(cx);
size_t index = 0;
for (const mozilla::intl::ListFormat::Part& part : parts) {
singlePart = NewPlainObject(cx);
if (!singlePart) {
return false;
}
if (part.first == mozilla::intl::ListFormat::PartType::Element) {
val = StringValue(cx->names().element);
} else {
val = StringValue(cx->names().literal);
}
if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) {
return false;
}
JSString* partStr =
NewStringCopyN<CanGC>(cx, part.second.data(), part.second.size());
if (!partStr) {
return false;
}
val = StringValue(partStr);
if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) {
return false;
}
partsArray->initDenseElement(index++, ObjectValue(*singlePart));
}
MOZ_ASSERT(index == parts.length());
result.setObject(*partsArray);
return true; return true;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment