Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5b2671f1e | ||
|
|
4249686af4 | ||
|
|
69ba22d624 | ||
|
|
4359582e67 | ||
|
|
fa06d2de31 |
No files matched your search
@@ -732,7 +732,6 @@ function initializeIntlObject(obj, type, lazyData) {
|
||||
|
||||
assert((type === "Collator" && IsCollator(obj)) ||
|
||||
(type === "DateTimeFormat" && IsDateTimeFormat(obj)) ||
|
||||
(type === "ListFormat" && IsListFormat(obj) !== null) ||
|
||||
(type === "NumberFormat" && IsNumberFormat(obj)) ||
|
||||
(type === "PluralRules" && IsPluralRules(obj)) ||
|
||||
(type === "RelativeTimeFormat" && IsRelativeTimeFormat(obj)),
|
||||
@@ -816,7 +815,6 @@ function getIntlObjectInternals(obj) {
|
||||
assert(hasOwn("type", internals), "missing type");
|
||||
assert((internals.type === "Collator" && IsCollator(obj)) ||
|
||||
(internals.type === "DateTimeFormat" && IsDateTimeFormat(obj)) ||
|
||||
(internals.type === "ListFormat" && IsListFormat(obj)) ||
|
||||
(internals.type === "NumberFormat" && IsNumberFormat(obj)) ||
|
||||
(internals.type === "PluralRules" && IsPluralRules(obj)) ||
|
||||
(internals.type === "RelativeTimeFormat" && IsRelativeTimeFormat(obj)),
|
||||
@@ -852,9 +850,6 @@ function getInternals(obj) {
|
||||
case "DateTimeFormat":
|
||||
internalProps = resolveDateTimeFormatInternals(internals.lazyData);
|
||||
break;
|
||||
case "ListFormat":
|
||||
internalProps = resolveListFormatInternals(internals.lazyData);
|
||||
break;
|
||||
case "PluralRules":
|
||||
internalProps = resolvePluralRulesInternals(internals.lazyData);
|
||||
break;
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "builtin/intl/CommonFunctions.h"
|
||||
#include "builtin/intl/DateTimeFormat.h"
|
||||
#include "builtin/intl/LanguageTag.h"
|
||||
#include "builtin/intl/ListFormat.h"
|
||||
#include "builtin/intl/ICUHeader.h"
|
||||
#include "builtin/intl/Locale.h"
|
||||
#include "builtin/intl/NumberFormat.h"
|
||||
@@ -524,8 +523,6 @@ js::intl_BestAvailableLocale(JSContext* cx, unsigned argc, Value* vp)
|
||||
kind = SupportedLocaleKind::Collator;
|
||||
} else if (StringEqualsAscii(typeStr, "DateTimeFormat")) {
|
||||
kind = SupportedLocaleKind::DateTimeFormat;
|
||||
} else if (StringEqualsAscii(typeStr, "ListFormat")) {
|
||||
kind = SupportedLocaleKind::ListFormat;
|
||||
} else if (StringEqualsAscii(typeStr, "NumberFormat")) {
|
||||
kind = SupportedLocaleKind::NumberFormat;
|
||||
} else if (StringEqualsAscii(typeStr, "PluralRules")) {
|
||||
@@ -652,12 +649,12 @@ js::intl_supportedLocaleOrFallback(JSContext* cx, unsigned argc, Value* vp)
|
||||
// That implies we must ignore any candidate which isn't supported by all Intl
|
||||
// service constructors.
|
||||
//
|
||||
// Note: We don't test the supported locales of either Intl.ListFormat,
|
||||
// Intl.PluralRules, Intl.RelativeTimeFormat, because ICU doesn't provide the
|
||||
// necessary API to return actual set of supported locales for these
|
||||
// constructors. Instead it returns the complete set of available locales for
|
||||
// ULocale, which is a superset of the locales supported by Collator,
|
||||
// NumberFormat, and DateTimeFormat.
|
||||
// Note: We don't test the supported locales of either Intl.PluralRules or
|
||||
// Intl.RelativeTimeFormat, because ICU doesn't provide the necessary API to
|
||||
// return actual set of supported locales for these constructors. Instead it
|
||||
// returns the complete set of available locales for ULocale, which is a
|
||||
// superset of the locales supported by Collator, NumberFormat, and
|
||||
// DateTimeFormat.
|
||||
bool isSupported = true;
|
||||
for (auto kind : {SupportedLocaleKind::Collator, SupportedLocaleKind::DateTimeFormat,
|
||||
SupportedLocaleKind::NumberFormat}) {
|
||||
|
||||
@@ -1,501 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* 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/. */
|
||||
|
||||
#include "builtin/intl/ListFormat.h"
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Casting.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "builtin/intl/CommonFunctions.h"
|
||||
#include "builtin/intl/ScopedICUObject.h"
|
||||
#include "js/Utility.h"
|
||||
#include "js/Vector.h"
|
||||
#include "unicode/uformattedvalue.h"
|
||||
#include "unicode/ulistformatter.h"
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
#include "jscntxt.h"
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
#include "vm/SelfHosting.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
using mozilla::AssertedCast;
|
||||
|
||||
using js::intl::CallICU;
|
||||
using js::intl::IcuLocale;
|
||||
|
||||
const ClassOps ListFormatObject::classOps_ = {nullptr, /* addProperty */
|
||||
nullptr, /* delProperty */
|
||||
nullptr, /* getProperty */
|
||||
nullptr, /* setProperty */
|
||||
nullptr, /* enumerate */
|
||||
nullptr, /* resolve */
|
||||
nullptr, /* mayResolve */
|
||||
ListFormatObject::finalize};
|
||||
const Class ListFormatObject::class_ = {
|
||||
js_Object_str,
|
||||
JSCLASS_HAS_RESERVED_SLOTS(ListFormatObject::SLOT_COUNT) |
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_ListFormat) |
|
||||
JSCLASS_FOREGROUND_FINALIZE,
|
||||
&ListFormatObject::classOps_
|
||||
};
|
||||
|
||||
const Class& ListFormatObject::protoClass_ = PlainObject::class_;
|
||||
|
||||
#if JS_HAS_TOSOURCE
|
||||
static bool
|
||||
listFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
args.rval().setString(cx->names().ListFormat);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const JSFunctionSpec listFormat_static_methods[] = {
|
||||
JS_SELF_HOSTED_FN("supportedLocalesOf",
|
||||
"Intl_ListFormat_supportedLocalesOf", 1, 0),
|
||||
JS_FS_END};
|
||||
|
||||
static const JSFunctionSpec listFormat_methods[] = {
|
||||
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_ListFormat_resolvedOptions", 0,
|
||||
0),
|
||||
JS_SELF_HOSTED_FN("format", "Intl_ListFormat_format", 1, 0),
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
JS_SELF_HOSTED_FN("formatToParts", "Intl_ListFormat_formatToParts", 1, 0),
|
||||
#endif
|
||||
#if JS_HAS_TOSOURCE
|
||||
JS_FN(js_toSource_str, listFormat_toSource, 0, 0),
|
||||
#endif
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
|
||||
static const JSPropertySpec listFormat_properties[] = {
|
||||
JS_STRING_SYM_PS(toStringTag, "Intl.ListFormat", JSPROP_READONLY),
|
||||
JS_PS_END};
|
||||
|
||||
static JSObject*
|
||||
CreateListFormatPrototype(JSContext* cx, JSProtoKey key) {
|
||||
return GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global());
|
||||
}
|
||||
|
||||
static bool ListFormat(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
const ClassSpec ListFormatObject::classSpec_ = {
|
||||
GenericCreateConstructor<ListFormat, 0, gc::AllocKind::FUNCTION>,
|
||||
CreateListFormatPrototype,
|
||||
listFormat_static_methods,
|
||||
nullptr,
|
||||
listFormat_methods,
|
||||
listFormat_properties
|
||||
// nullptr,
|
||||
// ClassSpec::DontDefineConstructor
|
||||
};
|
||||
|
||||
/**
|
||||
* Intl.ListFormat([ locales [, options]])
|
||||
*/
|
||||
static bool ListFormat(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1.
|
||||
if (!ThrowIfNotConstructing(cx, args, "Intl.ListFormat")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
|
||||
RootedObject proto(cx);
|
||||
if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<ListFormatObject*> listFormat(
|
||||
cx, NewObjectWithClassProto<ListFormatObject>(cx, proto));
|
||||
if (!listFormat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HandleValue locales = args.get(0);
|
||||
HandleValue options = args.get(1);
|
||||
|
||||
// Step 3.
|
||||
if (!intl::InitializeObject(cx, listFormat, cx->names().InitializeListFormat,
|
||||
locales, options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*listFormat);
|
||||
return true;
|
||||
}
|
||||
|
||||
void js::ListFormatObject::finalize(FreeOp* fop, JSObject* obj) {
|
||||
MOZ_ASSERT(fop->onMainThread());
|
||||
|
||||
if (UListFormatter* lf = obj->as<ListFormatObject>().getListFormatter()) {
|
||||
ulistfmt_close(lf);
|
||||
}
|
||||
}
|
||||
|
||||
bool js::AddListFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl) {
|
||||
JSObject* ctor = GlobalObject::createConstructor(cx, ListFormat, cx->names().ListFormat, 0);
|
||||
if (!ctor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedValue ctorValue(cx, ObjectValue(*ctor));
|
||||
return DefineProperty(cx, intl, cx->names().ListFormat, ctorValue, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new UListFormatter with the locale and list formatting options
|
||||
* of the given ListFormat.
|
||||
*/
|
||||
static UListFormatter* NewUListFormatter(JSContext* cx,
|
||||
Handle<ListFormatObject*> listFormat) {
|
||||
RootedObject internals(cx, intl::GetInternalsObject(cx, listFormat));
|
||||
if (!internals) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedValue value(cx);
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
JSAutoByteString bytes;
|
||||
char* locale = bytes.encodeLatin1(cx, value.toString());
|
||||
if (!locale) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
enum class ListFormatType { Conjunction, Disjunction, Unit };
|
||||
|
||||
ListFormatType type;
|
||||
if (!GetProperty(cx, internals, internals, cx->names().type, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
{
|
||||
JSLinearString* strType = value.toString()->ensureLinear(cx);
|
||||
if (!strType) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (StringEqualsAscii(strType, "conjunction")) {
|
||||
type = ListFormatType::Conjunction;
|
||||
} else if (StringEqualsAscii(strType, "disjunction")) {
|
||||
type = ListFormatType::Disjunction;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(strType, "unit"));
|
||||
type = ListFormatType::Unit;
|
||||
}
|
||||
}
|
||||
|
||||
enum class ListFormatStyle { Long, Short, Narrow };
|
||||
|
||||
ListFormatStyle style;
|
||||
if (!GetProperty(cx, internals, internals, cx->names().style, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
{
|
||||
JSLinearString* strStyle = value.toString()->ensureLinear(cx);
|
||||
if (!strStyle) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (StringEqualsAscii(strStyle, "long")) {
|
||||
style = ListFormatStyle::Long;
|
||||
} else if (StringEqualsAscii(strStyle, "short")) {
|
||||
style = ListFormatStyle::Short;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(strStyle, "narrow"));
|
||||
style = ListFormatStyle::Narrow;
|
||||
}
|
||||
}
|
||||
|
||||
// We're currently only supporting "conjunctive-long" list formatters due to
|
||||
// missing ICU APIs: https://unicode-org.atlassian.net/browse/ICU-12863
|
||||
MOZ_ASSERT(type == ListFormatType::Conjunction);
|
||||
MOZ_ASSERT(style == ListFormatStyle::Long);
|
||||
|
||||
mozilla::Unused << type;
|
||||
mozilla::Unused << style;
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UListFormatter* lf = ulistfmt_open(IcuLocale(locale), &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
return lf;
|
||||
}
|
||||
|
||||
static constexpr size_t DEFAULT_LIST_LENGTH = 8;
|
||||
|
||||
using ListFormatStringVector = Vector<UniqueTwoByteChars, DEFAULT_LIST_LENGTH>;
|
||||
using ListFormatStringLengthVector = Vector<int32_t, DEFAULT_LIST_LENGTH>;
|
||||
|
||||
static_assert(sizeof(UniqueTwoByteChars) == sizeof(char16_t*),
|
||||
"UniqueTwoByteChars are stored efficiently and are held in "
|
||||
"continuous memory");
|
||||
|
||||
/**
|
||||
* FormatList ( listFormat, list )
|
||||
*/
|
||||
static bool FormatList(JSContext* cx, UListFormatter* lf,
|
||||
const ListFormatStringVector& strings,
|
||||
const ListFormatStringLengthVector& stringLengths,
|
||||
MutableHandleValue result) {
|
||||
MOZ_ASSERT(strings.length() == stringLengths.length());
|
||||
MOZ_ASSERT(strings.length() <= INT32_MAX);
|
||||
|
||||
JSString* str = intl::CallICU(cx, [lf, &strings, &stringLengths](
|
||||
UChar* chars, int32_t size,
|
||||
UErrorCode* status) {
|
||||
return ulistfmt_format(
|
||||
lf, reinterpret_cast<char16_t* const*>(strings.begin()),
|
||||
stringLengths.begin(), int32_t(strings.length()), chars, size, status);
|
||||
});
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result.setString(str);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
static JSString* FormattedValueToString(JSContext* cx,
|
||||
const UFormattedValue* formattedValue) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
int32_t strLength;
|
||||
const char16_t* str = ufmtval_getString(formattedValue, &strLength, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return NewStringCopyN<CanGC>(cx, str, AssertedCast<uint32_t>(strLength));
|
||||
}
|
||||
|
||||
/**
|
||||
* FormatListToParts ( listFormat, list )
|
||||
*/
|
||||
static bool FormatListToParts(JSContext* cx, UListFormatter* lf,
|
||||
const ListFormatStringVector& strings,
|
||||
const ListFormatStringLengthVector& stringLengths,
|
||||
MutableHandleValue result) {
|
||||
MOZ_ASSERT(strings.length() == stringLengths.length());
|
||||
MOZ_ASSERT(strings.length() <= INT32_MAX);
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UFormattedList* formatted = ulistfmt_openResult(&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
ScopedICUObject<UFormattedList, ulistfmt_closeResult> toClose(formatted);
|
||||
|
||||
ulistfmt_formatStringsToResult(
|
||||
lf, reinterpret_cast<char16_t* const*>(strings.begin()),
|
||||
stringLengths.begin(), int32_t(strings.length()), formatted, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
const UFormattedValue* formattedValue =
|
||||
ulistfmt_resultAsValue(formatted, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedString overallResult(cx, FormattedValueToString(cx, formattedValue));
|
||||
if (!overallResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
|
||||
if (!partsArray) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using FieldType = js::ImmutablePropertyNamePtr JSAtomState::*;
|
||||
|
||||
size_t lastEndIndex = 0;
|
||||
RootedObject singlePart(cx);
|
||||
RootedValue partType(cx);
|
||||
RootedValue val(cx);
|
||||
|
||||
auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) {
|
||||
singlePart = NewBuiltinClassInstance<PlainObject>(cx);
|
||||
if (!singlePart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
partType = StringValue(cx->names().*type);
|
||||
if (!DefineProperty(cx, singlePart, cx->names().type, partType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JSLinearString* partSubstr = NewDependentString(
|
||||
cx, overallResult, beginIndex, endIndex - beginIndex);
|
||||
if (!partSubstr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
val = StringValue(partSubstr);
|
||||
if (!DefineProperty(cx, singlePart, cx->names().value, val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NewbornArrayPush(cx, partsArray, ObjectValue(*singlePart))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lastEndIndex = endIndex;
|
||||
return true;
|
||||
};
|
||||
|
||||
UConstrainedFieldPosition* fpos = ucfpos_open(&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
|
||||
|
||||
// We're only interested in ULISTFMT_ELEMENT_FIELD fields.
|
||||
ucfpos_constrainField(fpos, UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD,
|
||||
&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
if (!hasMore) {
|
||||
break;
|
||||
}
|
||||
|
||||
int32_t beginIndexInt, endIndexInt;
|
||||
ucfpos_getIndexes(fpos, &beginIndexInt, &endIndexInt, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(beginIndexInt >= 0);
|
||||
MOZ_ASSERT(endIndexInt >= 0);
|
||||
MOZ_ASSERT(beginIndexInt <= endIndexInt,
|
||||
"field iterator returning invalid range");
|
||||
|
||||
size_t beginIndex = size_t(beginIndexInt);
|
||||
size_t endIndex = size_t(endIndexInt);
|
||||
|
||||
// Indices are guaranteed to be returned in order (from left to right).
|
||||
MOZ_ASSERT(lastEndIndex <= beginIndex,
|
||||
"field iteration didn't return fields in order start to "
|
||||
"finish as expected");
|
||||
|
||||
if (lastEndIndex < beginIndex) {
|
||||
if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!AppendPart(&JSAtomState::element, beginIndex, endIndex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Append any final literal.
|
||||
if (lastEndIndex < overallResult->length()) {
|
||||
if (!AppendPart(&JSAtomState::literal, lastEndIndex,
|
||||
overallResult->length())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result.setObject(*partsArray);
|
||||
return true;
|
||||
}
|
||||
#endif // U_HIDE_DRAFT_API
|
||||
|
||||
bool js::intl_FormatList(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 3);
|
||||
|
||||
Rooted<ListFormatObject*> listFormat(
|
||||
cx, &args[0].toObject().as<ListFormatObject>());
|
||||
|
||||
bool formatToParts = args[2].toBoolean();
|
||||
|
||||
// Obtain a cached UListFormatter object.
|
||||
UListFormatter* lf = listFormat->getListFormatter();
|
||||
if (!lf) {
|
||||
lf = NewUListFormatter(cx, listFormat);
|
||||
if (!lf) {
|
||||
return false;
|
||||
}
|
||||
listFormat->setListFormatter(lf);
|
||||
}
|
||||
|
||||
// Collect all strings and their lengths.
|
||||
ListFormatStringVector strings(cx);
|
||||
ListFormatStringLengthVector stringLengths(cx);
|
||||
|
||||
RootedArrayObject list(cx, &args[1].toObject().as<ArrayObject>());
|
||||
RootedValue value(cx);
|
||||
for (uint32_t i = 0; i < list->length(); i++) {
|
||||
if (!GetElement(cx, list, list, i, &value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JSLinearString* linear = value.toString()->ensureLinear(cx);
|
||||
if (!linear) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t linearLength = linear->length();
|
||||
if (!stringLengths.append(linearLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UniqueTwoByteChars chars = cx->make_pod_array<char16_t>(linearLength);
|
||||
if (!chars) {
|
||||
return false;
|
||||
}
|
||||
CopyChars(chars.get(), *linear);
|
||||
|
||||
if (!strings.append(std::move(chars))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the UListFormatter to actually format the strings.
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
if (formatToParts) {
|
||||
return FormatListToParts(cx, lf, strings, stringLengths, args.rval());
|
||||
}
|
||||
#else
|
||||
MOZ_ASSERT(!formatToParts);
|
||||
#endif
|
||||
|
||||
return FormatList(cx, lf, strings, stringLengths, args.rval());
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
#ifndef builtin_intl_ListFormat_h
|
||||
#define builtin_intl_ListFormat_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "builtin/intl/CommonFunctions.h"
|
||||
#include "builtin/SelfHostingDefines.h"
|
||||
#include "js/Class.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "vm/NativeObject.h"
|
||||
|
||||
struct UListFormatter;
|
||||
|
||||
namespace js {
|
||||
|
||||
class ListFormatObject : public NativeObject {
|
||||
public:
|
||||
static const Class class_;
|
||||
static const Class& protoClass_;
|
||||
|
||||
static constexpr uint32_t INTERNALS_SLOT = 0;
|
||||
static constexpr uint32_t ULIST_FORMATTER_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");
|
||||
|
||||
UListFormatter* getListFormatter() const {
|
||||
const auto& slot = getFixedSlot(ULIST_FORMATTER_SLOT);
|
||||
if (slot.isUndefined()) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<UListFormatter*>(slot.toPrivate());
|
||||
}
|
||||
|
||||
void setListFormatter(UListFormatter* formatter) {
|
||||
setFixedSlot(ULIST_FORMATTER_SLOT, PrivateValue(formatter));
|
||||
}
|
||||
|
||||
private:
|
||||
static const ClassOps classOps_;
|
||||
static const ClassSpec classSpec_;
|
||||
|
||||
static void finalize(FreeOp* fop, JSObject* obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a string representing the array of string values |list| according to
|
||||
* the effective locale and the formatting options of the given ListFormat.
|
||||
*
|
||||
* Usage: formatted = intl_FormatList(listFormat, list, formatToParts)
|
||||
*/
|
||||
extern bool intl_FormatList(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* builtin_intl_ListFormat_h */
|
||||
@@ -1,262 +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/. */
|
||||
|
||||
/**
|
||||
* ListFormat internal properties.
|
||||
*/
|
||||
var listFormatInternalProperties = {
|
||||
localeData: function() // eslint-disable-line object-shorthand
|
||||
{
|
||||
// ListFormat don't support any extension keys.
|
||||
return {};
|
||||
},
|
||||
relevantExtensionKeys: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Intl.ListFormat ( [ locales [ , options ] ] )
|
||||
*
|
||||
* Compute an internal properties object from |lazyListFormatData|.
|
||||
*/
|
||||
function resolveListFormatInternals(lazyListFormatData) {
|
||||
assert(IsObject(lazyListFormatData), "lazy data not an object?");
|
||||
|
||||
var internalProps = std_Object_create(null);
|
||||
|
||||
var ListFormat = listFormatInternalProperties;
|
||||
|
||||
// Compute effective locale.
|
||||
|
||||
// Step 9.
|
||||
var localeData = ListFormat.localeData;
|
||||
|
||||
// Step 10.
|
||||
var r = ResolveLocale("ListFormat",
|
||||
lazyListFormatData.requestedLocales,
|
||||
lazyListFormatData.opt,
|
||||
ListFormat.relevantExtensionKeys,
|
||||
localeData);
|
||||
|
||||
// Step 11.
|
||||
internalProps.locale = r.locale;
|
||||
|
||||
// Step 13.
|
||||
internalProps.type = lazyListFormatData.type;
|
||||
|
||||
// Step 15.
|
||||
internalProps.style = lazyListFormatData.style;
|
||||
|
||||
// Steps 16-23 (not applicable in our implementation).
|
||||
|
||||
// The caller is responsible for associating |internalProps| with the right
|
||||
// object using |setInternalProperties|.
|
||||
return internalProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing the ListFormat internal properties of |obj|.
|
||||
*/
|
||||
function getListFormatInternals(obj) {
|
||||
assert(IsObject(obj), "getListFormatInternals called with non-object");
|
||||
assert(GuardToListFormat(obj) !== null, "getListFormatInternals called with non-ListFormat");
|
||||
|
||||
var internals = getIntlObjectInternals(obj);
|
||||
assert(internals.type === "ListFormat", "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 = resolveListFormatInternals(internals.lazyData);
|
||||
setInternalProperties(internals, internalProps);
|
||||
return internalProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intl.ListFormat ( [ locales [ , options ] ] )
|
||||
*
|
||||
* Initializes an object as a ListFormat.
|
||||
*
|
||||
* 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 ListFormat.
|
||||
* This later work occurs in |resolveListFormatInternals|; steps not noted
|
||||
* here occur there.
|
||||
*/
|
||||
function InitializeListFormat(listFormat, locales, options) {
|
||||
assert(IsObject(listFormat), "InitializeListFormat called with non-object");
|
||||
assert(GuardToListFormat(listFormat) !== null, "InitializeListFormat called with non-ListFormat");
|
||||
|
||||
// Lazy ListFormat data has the following structure:
|
||||
//
|
||||
// {
|
||||
// requestedLocales: List of locales,
|
||||
// type: "conjunction" / "disjunction" / "unit",
|
||||
// style: "long" / "short" / "narrow",
|
||||
//
|
||||
// opt: // opt object computed in InitializeListFormat
|
||||
// {
|
||||
// localeMatcher: "lookup" / "best fit",
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Note that lazy data is only installed as a final step of initialization,
|
||||
// so every ListFormat lazy data object has *all* these properties, never a
|
||||
// subset of them.
|
||||
var lazyListFormatData = std_Object_create(null);
|
||||
|
||||
// Step 3.
|
||||
var requestedLocales = CanonicalizeLocaleList(locales);
|
||||
lazyListFormatData.requestedLocales = requestedLocales;
|
||||
|
||||
// Steps 4-5.
|
||||
if (options === undefined)
|
||||
options = std_Object_create(null);
|
||||
else
|
||||
options = ToObject(options);
|
||||
|
||||
// Step 6.
|
||||
var opt = new Record();
|
||||
lazyListFormatData.opt = opt;
|
||||
|
||||
// Steps 7-8.
|
||||
let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
|
||||
opt.localeMatcher = matcher;
|
||||
|
||||
// Compute formatting options.
|
||||
|
||||
// We're currently only supporting "conjunctive-long" list formatters due to
|
||||
// missing ICU APIs: https://unicode-org.atlassian.net/browse/ICU-12863
|
||||
|
||||
// Steps 12-13.
|
||||
var type = GetOption(options, "type", "string", ["conjunction"], "conjunction");
|
||||
lazyListFormatData.type = type;
|
||||
|
||||
// Steps 14-15.
|
||||
var style = GetOption(options, "style", "string", ["long"], "long");
|
||||
lazyListFormatData.style = style;
|
||||
|
||||
// We've done everything that must be done now: mark the lazy data as fully
|
||||
// computed and install it.
|
||||
initializeIntlObject(listFormat, "ListFormat", lazyListFormatData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subset of the given locale list for which this locale list has a
|
||||
* matching (possibly fallback) locale. Locales appear in the same order in the
|
||||
* returned list as in the input list.
|
||||
*/
|
||||
function Intl_ListFormat_supportedLocalesOf(locales /*, options*/) {
|
||||
var options = arguments.length > 1 ? arguments[1] : undefined;
|
||||
|
||||
// Step 1.
|
||||
var availableLocales = "ListFormat";
|
||||
|
||||
// Step 2.
|
||||
var requestedLocales = CanonicalizeLocaleList(locales);
|
||||
|
||||
// Step 3.
|
||||
return SupportedLocales(availableLocales, requestedLocales, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* StringListFromIterable ( iterable )
|
||||
*/
|
||||
function StringListFromIterable(iterable, methodName) {
|
||||
// Step 1.
|
||||
if (iterable === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Step 3.
|
||||
var list = [];
|
||||
|
||||
// Steps 2, 4-5.
|
||||
for (var element of allowContentIter(iterable)) {
|
||||
// Step 5.b.ii.
|
||||
if (typeof element !== "string") {
|
||||
ThrowTypeError(JSMSG_NOT_EXPECTED_TYPE, methodName, "string", typeof element);
|
||||
}
|
||||
|
||||
// Step 5.b.iii.
|
||||
_DefineDataProperty(list, list.length, element);
|
||||
}
|
||||
|
||||
// Step 6.
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intl.ListFormat.prototype.format ( list )
|
||||
*/
|
||||
function Intl_ListFormat_format(list) {
|
||||
// Step 1.
|
||||
var listFormat = this;
|
||||
|
||||
// Steps 2-3.
|
||||
if (!IsObject(listFormat) || (listFormat = GuardToListFormat(listFormat)) === null) {
|
||||
return callFunction(CallListFormatMethodIfWrapped, this, list,
|
||||
"Intl_ListFormat_format");
|
||||
}
|
||||
|
||||
// Ensure the ListFormat internals are resolved.
|
||||
getListFormatInternals(listFormat);
|
||||
|
||||
// Step 4.
|
||||
var stringList = StringListFromIterable(list, "format");
|
||||
|
||||
// Step 5.
|
||||
return intl_FormatList(listFormat, stringList, /* formatToParts = */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Intl.ListFormat.prototype.formatToParts ( list )
|
||||
*/
|
||||
function Intl_ListFormat_formatToParts(list) {
|
||||
// Step 1.
|
||||
var listFormat = this;
|
||||
|
||||
// Steps 2-3.
|
||||
if (!IsObject(listFormat) || (listFormat = GuardToListFormat(listFormat)) === null) {
|
||||
return callFunction(CallListFormatMethodIfWrapped, this, list,
|
||||
"Intl_ListFormat_formatToParts");
|
||||
}
|
||||
|
||||
// Ensure the ListFormat internals are resolved.
|
||||
getListFormatInternals(listFormat);
|
||||
|
||||
// Step 4.
|
||||
var stringList = StringListFromIterable(list, "formatToParts");
|
||||
|
||||
// Step 5.
|
||||
return intl_FormatList(listFormat, stringList, /* formatToParts = */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resolved options for a ListFormat object.
|
||||
*/
|
||||
function Intl_ListFormat_resolvedOptions() {
|
||||
// Step 1.
|
||||
var listFormat = this;
|
||||
|
||||
// Steps 2-3.
|
||||
if (!IsObject(listFormat) || (listFormat = GuardToListFormat(listFormat)) === null) {
|
||||
return callFunction(CallListFormatMethodIfWrapped, this,
|
||||
"Intl_ListFormat_resolvedOptions");
|
||||
}
|
||||
|
||||
var internals = getListFormatInternals(listFormat);
|
||||
|
||||
// Steps 4-5.
|
||||
var result = {
|
||||
locale: internals.locale,
|
||||
type: internals.type,
|
||||
style: internals.style,
|
||||
};
|
||||
|
||||
// Step 6.
|
||||
return result;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,286 +1,285 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* 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/. */
|
||||
|
||||
#ifndef builtin_intl_SharedIntlData_h
|
||||
#define builtin_intl_SharedIntlData_h
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "jsalloc.h"
|
||||
#include "js/CharacterEncoding.h"
|
||||
#include "js/GCAPI.h"
|
||||
#include "js/GCHashTable.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "js/Utility.h"
|
||||
#include "vm/String.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
namespace intl {
|
||||
|
||||
/**
|
||||
* Stores Intl data which can be shared across compartments (but not contexts).
|
||||
*
|
||||
* Used for data which is expensive when computed repeatedly or is not
|
||||
* available through ICU.
|
||||
*/
|
||||
class SharedIntlData
|
||||
{
|
||||
struct LinearStringLookup
|
||||
{
|
||||
union {
|
||||
const JS::Latin1Char* latin1Chars;
|
||||
const char16_t* twoByteChars;
|
||||
};
|
||||
bool isLatin1;
|
||||
size_t length;
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
HashNumber hash = 0;
|
||||
|
||||
explicit LinearStringLookup(JSLinearString* string)
|
||||
: isLatin1(string->hasLatin1Chars()), length(string->length())
|
||||
{
|
||||
if (isLatin1)
|
||||
latin1Chars = string->latin1Chars(nogc);
|
||||
else
|
||||
twoByteChars = string->twoByteChars(nogc);
|
||||
}
|
||||
|
||||
LinearStringLookup(const char* chars, size_t length)
|
||||
: isLatin1(true), length(length)
|
||||
{
|
||||
latin1Chars = reinterpret_cast<const JS::Latin1Char*>(chars);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
/**
|
||||
* Information tracking the set of the supported time zone names, derived
|
||||
* from the IANA time zone database <https://www.iana.org/time-zones>.
|
||||
*
|
||||
* There are two kinds of IANA time zone names: Zone and Link (denoted as
|
||||
* such in database source files). Zone names are the canonical, preferred
|
||||
* name for a time zone, e.g. Asia/Kolkata. Link names simply refer to
|
||||
* target Zone names for their meaning, e.g. Asia/Calcutta targets
|
||||
* Asia/Kolkata. That a name is a Link doesn't *necessarily* reflect a
|
||||
* sense of deprecation: some Link names also exist partly for convenience,
|
||||
* e.g. UTC and GMT as Link names targeting the Zone name Etc/UTC.
|
||||
*
|
||||
* Two data sources determine the time zone names we support: those ICU
|
||||
* supports and IANA's zone information.
|
||||
*
|
||||
* Unfortunately the names ICU and IANA support, and their Link
|
||||
* relationships from name to target, aren't identical, so we can't simply
|
||||
* implicitly trust ICU's name handling. We must perform various
|
||||
* preprocessing of user-provided zone names and post-processing of
|
||||
* ICU-provided zone names to implement ECMA-402's IANA-consistent behavior.
|
||||
*
|
||||
* Also see <https://ssl.icu-project.org/trac/ticket/12044> and
|
||||
* <http://unicode.org/cldr/trac/ticket/9892>.
|
||||
*/
|
||||
|
||||
using TimeZoneName = JSAtom*;
|
||||
|
||||
struct TimeZoneHasher
|
||||
{
|
||||
struct Lookup : LinearStringLookup
|
||||
{
|
||||
explicit Lookup(JSFlatString* timeZone);
|
||||
};
|
||||
|
||||
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
|
||||
static bool match(TimeZoneName key, const Lookup& lookup);
|
||||
};
|
||||
|
||||
using TimeZoneSet = js::GCHashSet<TimeZoneName,
|
||||
TimeZoneHasher,
|
||||
js::SystemAllocPolicy>;
|
||||
|
||||
using TimeZoneMap = js::GCHashMap<TimeZoneName,
|
||||
TimeZoneName,
|
||||
TimeZoneHasher,
|
||||
js::SystemAllocPolicy>;
|
||||
|
||||
/**
|
||||
* As a threshold matter, available time zones are those time zones ICU
|
||||
* supports, via ucal_openTimeZones. But ICU supports additional non-IANA
|
||||
* time zones described in intl/icu/source/tools/tzcode/icuzones (listed in
|
||||
* IntlTimeZoneData.cpp's |legacyICUTimeZones|) for its own backwards
|
||||
* compatibility purposes. This set consists of ICU's supported time zones,
|
||||
* minus all backwards-compatibility time zones.
|
||||
*/
|
||||
TimeZoneSet availableTimeZones;
|
||||
|
||||
/**
|
||||
* IANA treats some time zone names as Zones, that ICU instead treats as
|
||||
* Links. For example, IANA considers "America/Indiana/Indianapolis" to be
|
||||
* a Zone and "America/Fort_Wayne" a Link that targets it, but ICU
|
||||
* considers the former a Link that targets "America/Indianapolis" (which
|
||||
* IANA treats as a Link).
|
||||
*
|
||||
* ECMA-402 requires that we respect IANA data, so if we're asked to
|
||||
* canonicalize a time zone name in this set, we must *not* return ICU's
|
||||
* canonicalization.
|
||||
*/
|
||||
TimeZoneSet ianaZonesTreatedAsLinksByICU;
|
||||
|
||||
/**
|
||||
* IANA treats some time zone names as Links to one target, that ICU
|
||||
* instead treats as either Zones, or Links to different targets. An
|
||||
* example of the former is "Asia/Calcutta, which IANA assigns the target
|
||||
* "Asia/Kolkata" but ICU considers its own Zone. An example of the latter
|
||||
* is "America/Virgin", which IANA assigns the target
|
||||
* "America/Port_of_Spain" but ICU assigns the target "America/St_Thomas".
|
||||
*
|
||||
* ECMA-402 requires that we respect IANA data, so if we're asked to
|
||||
* canonicalize a time zone name that's a key in this map, we *must* return
|
||||
* the corresponding value and *must not* return ICU's canonicalization.
|
||||
*/
|
||||
TimeZoneMap ianaLinksCanonicalizedDifferentlyByICU;
|
||||
|
||||
bool timeZoneDataInitialized = false;
|
||||
|
||||
/**
|
||||
* Precomputes the available time zone names, because it's too expensive to
|
||||
* call ucal_openTimeZones() repeatedly.
|
||||
*/
|
||||
bool ensureTimeZones(JSContext* cx);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns the validated time zone name in |result|. If the input time zone
|
||||
* isn't a valid IANA time zone name, |result| remains unchanged.
|
||||
*/
|
||||
bool validateTimeZoneName(JSContext* cx, JS::HandleString timeZone,
|
||||
JS::MutableHandleString result);
|
||||
|
||||
/**
|
||||
* Returns the canonical time zone name in |result|. If no canonical name
|
||||
* was found, |result| remains unchanged.
|
||||
*
|
||||
* This method only handles time zones which are canonicalized differently
|
||||
* by ICU when compared to IANA.
|
||||
*/
|
||||
bool tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, JS::HandleString timeZone,
|
||||
JS::MutableHandleString result);
|
||||
private:
|
||||
using Locale = JSAtom*;
|
||||
|
||||
struct LocaleHasher
|
||||
{
|
||||
struct Lookup : LinearStringLookup
|
||||
{
|
||||
explicit Lookup(JSLinearString* locale);
|
||||
Lookup(const char* chars, size_t length);
|
||||
};
|
||||
|
||||
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
|
||||
static bool match(Locale key, const Lookup& lookup);
|
||||
};
|
||||
|
||||
using LocaleSet = GCHashSet<Locale, LocaleHasher, SystemAllocPolicy>;
|
||||
|
||||
// Set of supported locales for all Intl service constructors except Collator,
|
||||
// which uses its own set.
|
||||
//
|
||||
// UDateFormat:
|
||||
// udat_[count,get]Available() return the same results as their
|
||||
// uloc_[count,get]Available() counterparts.
|
||||
//
|
||||
// UNumberFormatter:
|
||||
// unum_[count,get]Available() return the same results as their
|
||||
// uloc_[count,get]Available() counterparts.
|
||||
//
|
||||
// UListFormatter, UPluralRules and URelativeDateTimeFormatter:
|
||||
// We're going to use ULocale availableLocales as per ICU recommendation:
|
||||
// https://unicode-org.atlassian.net/browse/ICU-12756
|
||||
LocaleSet supportedLocales;
|
||||
|
||||
// ucol_[count,get]Available() return different results compared to
|
||||
// uloc_[count,get]Available(), we can't use |supportedLocales| here.
|
||||
LocaleSet collatorSupportedLocales;
|
||||
|
||||
bool supportedLocalesInitialized = false;
|
||||
|
||||
// CountAvailable and GetAvailable describe the signatures used for ICU API
|
||||
// to determine available locales for various functionality.
|
||||
using CountAvailable = int32_t (*)();
|
||||
using GetAvailable = const char* (*)(int32_t localeIndex);
|
||||
|
||||
static bool getAvailableLocales(JSContext* cx, LocaleSet& locales,
|
||||
CountAvailable countAvailable,
|
||||
GetAvailable getAvailable);
|
||||
|
||||
/**
|
||||
* Precomputes the available locales sets.
|
||||
*/
|
||||
bool ensureSupportedLocales(JSContext* cx);
|
||||
|
||||
public:
|
||||
enum class SupportedLocaleKind {
|
||||
Collator,
|
||||
DateTimeFormat,
|
||||
ListFormat,
|
||||
NumberFormat,
|
||||
PluralRules,
|
||||
RelativeTimeFormat
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets |supported| to true if |locale| is supported by the requested Intl
|
||||
* service constructor. Otherwise sets |supported| to false.
|
||||
*/
|
||||
[[nodiscard]] bool isSupportedLocale(JSContext* cx, SupportedLocaleKind kind,
|
||||
JS::Handle<JSString*> locale,
|
||||
bool* supported);
|
||||
|
||||
private:
|
||||
/**
|
||||
* The case first parameter (BCP47 key "kf") allows to switch the order of
|
||||
* upper- and lower-case characters. ICU doesn't directly provide an API
|
||||
* to query the default case first value of a given locale, but instead
|
||||
* requires to instantiate a collator object and then query the case first
|
||||
* attribute (UCOL_CASE_FIRST).
|
||||
* To avoid instantiating an additional collator object whenever we need
|
||||
* to retrieve the default case first value of a specific locale, we
|
||||
* compute the default case first value for every supported locale only
|
||||
* once and then keep a list of all locales which don't use the default
|
||||
* case first setting.
|
||||
* There is almost no difference between lower-case first and when case
|
||||
* first is disabled (UCOL_LOWER_FIRST resp. UCOL_OFF), so we only need to
|
||||
* track locales which use upper-case first as their default setting.
|
||||
*/
|
||||
|
||||
LocaleSet upperCaseFirstLocales;
|
||||
|
||||
bool upperCaseFirstInitialized = false;
|
||||
|
||||
/**
|
||||
* Precomputes the available locales which use upper-case first sorting.
|
||||
*/
|
||||
bool ensureUpperCaseFirstLocales(JSContext* cx);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Sets |isUpperFirst| to true if |locale| sorts upper-case characters
|
||||
* before lower-case characters.
|
||||
*/
|
||||
bool isUpperCaseFirst(JSContext* cx, JS::HandleString locale, bool* isUpperFirst);
|
||||
|
||||
public:
|
||||
void destroyInstance();
|
||||
|
||||
void trace(JSTracer* trc);
|
||||
|
||||
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
|
||||
};
|
||||
|
||||
} // namespace intl
|
||||
|
||||
} // namespace js
|
||||
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* 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/. */
|
||||
|
||||
#ifndef builtin_intl_SharedIntlData_h
|
||||
#define builtin_intl_SharedIntlData_h
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "jsalloc.h"
|
||||
#include "js/CharacterEncoding.h"
|
||||
#include "js/GCAPI.h"
|
||||
#include "js/GCHashTable.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "js/Utility.h"
|
||||
#include "vm/String.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
namespace intl {
|
||||
|
||||
/**
|
||||
* Stores Intl data which can be shared across compartments (but not contexts).
|
||||
*
|
||||
* Used for data which is expensive when computed repeatedly or is not
|
||||
* available through ICU.
|
||||
*/
|
||||
class SharedIntlData
|
||||
{
|
||||
struct LinearStringLookup
|
||||
{
|
||||
union {
|
||||
const JS::Latin1Char* latin1Chars;
|
||||
const char16_t* twoByteChars;
|
||||
};
|
||||
bool isLatin1;
|
||||
size_t length;
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
HashNumber hash = 0;
|
||||
|
||||
explicit LinearStringLookup(JSLinearString* string)
|
||||
: isLatin1(string->hasLatin1Chars()), length(string->length())
|
||||
{
|
||||
if (isLatin1)
|
||||
latin1Chars = string->latin1Chars(nogc);
|
||||
else
|
||||
twoByteChars = string->twoByteChars(nogc);
|
||||
}
|
||||
|
||||
LinearStringLookup(const char* chars, size_t length)
|
||||
: isLatin1(true), length(length)
|
||||
{
|
||||
latin1Chars = reinterpret_cast<const JS::Latin1Char*>(chars);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
/**
|
||||
* Information tracking the set of the supported time zone names, derived
|
||||
* from the IANA time zone database <https://www.iana.org/time-zones>.
|
||||
*
|
||||
* There are two kinds of IANA time zone names: Zone and Link (denoted as
|
||||
* such in database source files). Zone names are the canonical, preferred
|
||||
* name for a time zone, e.g. Asia/Kolkata. Link names simply refer to
|
||||
* target Zone names for their meaning, e.g. Asia/Calcutta targets
|
||||
* Asia/Kolkata. That a name is a Link doesn't *necessarily* reflect a
|
||||
* sense of deprecation: some Link names also exist partly for convenience,
|
||||
* e.g. UTC and GMT as Link names targeting the Zone name Etc/UTC.
|
||||
*
|
||||
* Two data sources determine the time zone names we support: those ICU
|
||||
* supports and IANA's zone information.
|
||||
*
|
||||
* Unfortunately the names ICU and IANA support, and their Link
|
||||
* relationships from name to target, aren't identical, so we can't simply
|
||||
* implicitly trust ICU's name handling. We must perform various
|
||||
* preprocessing of user-provided zone names and post-processing of
|
||||
* ICU-provided zone names to implement ECMA-402's IANA-consistent behavior.
|
||||
*
|
||||
* Also see <https://ssl.icu-project.org/trac/ticket/12044> and
|
||||
* <http://unicode.org/cldr/trac/ticket/9892>.
|
||||
*/
|
||||
|
||||
using TimeZoneName = JSAtom*;
|
||||
|
||||
struct TimeZoneHasher
|
||||
{
|
||||
struct Lookup : LinearStringLookup
|
||||
{
|
||||
explicit Lookup(JSFlatString* timeZone);
|
||||
};
|
||||
|
||||
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
|
||||
static bool match(TimeZoneName key, const Lookup& lookup);
|
||||
};
|
||||
|
||||
using TimeZoneSet = js::GCHashSet<TimeZoneName,
|
||||
TimeZoneHasher,
|
||||
js::SystemAllocPolicy>;
|
||||
|
||||
using TimeZoneMap = js::GCHashMap<TimeZoneName,
|
||||
TimeZoneName,
|
||||
TimeZoneHasher,
|
||||
js::SystemAllocPolicy>;
|
||||
|
||||
/**
|
||||
* As a threshold matter, available time zones are those time zones ICU
|
||||
* supports, via ucal_openTimeZones. But ICU supports additional non-IANA
|
||||
* time zones described in intl/icu/source/tools/tzcode/icuzones (listed in
|
||||
* IntlTimeZoneData.cpp's |legacyICUTimeZones|) for its own backwards
|
||||
* compatibility purposes. This set consists of ICU's supported time zones,
|
||||
* minus all backwards-compatibility time zones.
|
||||
*/
|
||||
TimeZoneSet availableTimeZones;
|
||||
|
||||
/**
|
||||
* IANA treats some time zone names as Zones, that ICU instead treats as
|
||||
* Links. For example, IANA considers "America/Indiana/Indianapolis" to be
|
||||
* a Zone and "America/Fort_Wayne" a Link that targets it, but ICU
|
||||
* considers the former a Link that targets "America/Indianapolis" (which
|
||||
* IANA treats as a Link).
|
||||
*
|
||||
* ECMA-402 requires that we respect IANA data, so if we're asked to
|
||||
* canonicalize a time zone name in this set, we must *not* return ICU's
|
||||
* canonicalization.
|
||||
*/
|
||||
TimeZoneSet ianaZonesTreatedAsLinksByICU;
|
||||
|
||||
/**
|
||||
* IANA treats some time zone names as Links to one target, that ICU
|
||||
* instead treats as either Zones, or Links to different targets. An
|
||||
* example of the former is "Asia/Calcutta, which IANA assigns the target
|
||||
* "Asia/Kolkata" but ICU considers its own Zone. An example of the latter
|
||||
* is "America/Virgin", which IANA assigns the target
|
||||
* "America/Port_of_Spain" but ICU assigns the target "America/St_Thomas".
|
||||
*
|
||||
* ECMA-402 requires that we respect IANA data, so if we're asked to
|
||||
* canonicalize a time zone name that's a key in this map, we *must* return
|
||||
* the corresponding value and *must not* return ICU's canonicalization.
|
||||
*/
|
||||
TimeZoneMap ianaLinksCanonicalizedDifferentlyByICU;
|
||||
|
||||
bool timeZoneDataInitialized = false;
|
||||
|
||||
/**
|
||||
* Precomputes the available time zone names, because it's too expensive to
|
||||
* call ucal_openTimeZones() repeatedly.
|
||||
*/
|
||||
bool ensureTimeZones(JSContext* cx);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns the validated time zone name in |result|. If the input time zone
|
||||
* isn't a valid IANA time zone name, |result| remains unchanged.
|
||||
*/
|
||||
bool validateTimeZoneName(JSContext* cx, JS::HandleString timeZone,
|
||||
JS::MutableHandleString result);
|
||||
|
||||
/**
|
||||
* Returns the canonical time zone name in |result|. If no canonical name
|
||||
* was found, |result| remains unchanged.
|
||||
*
|
||||
* This method only handles time zones which are canonicalized differently
|
||||
* by ICU when compared to IANA.
|
||||
*/
|
||||
bool tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, JS::HandleString timeZone,
|
||||
JS::MutableHandleString result);
|
||||
private:
|
||||
using Locale = JSAtom*;
|
||||
|
||||
struct LocaleHasher
|
||||
{
|
||||
struct Lookup : LinearStringLookup
|
||||
{
|
||||
explicit Lookup(JSLinearString* locale);
|
||||
Lookup(const char* chars, size_t length);
|
||||
};
|
||||
|
||||
static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
|
||||
static bool match(Locale key, const Lookup& lookup);
|
||||
};
|
||||
|
||||
using LocaleSet = GCHashSet<Locale, LocaleHasher, SystemAllocPolicy>;
|
||||
|
||||
// Set of supported locales for all Intl service constructors except Collator,
|
||||
// which uses its own set.
|
||||
//
|
||||
// UDateFormat:
|
||||
// udat_[count,get]Available() return the same results as their
|
||||
// uloc_[count,get]Available() counterparts.
|
||||
//
|
||||
// UNumberFormatter:
|
||||
// unum_[count,get]Available() return the same results as their
|
||||
// uloc_[count,get]Available() counterparts.
|
||||
//
|
||||
// UPluralRules and URelativeDateTimeFormatter:
|
||||
// We're going to use ULocale availableLocales as per ICU recommendation:
|
||||
// https://unicode-org.atlassian.net/browse/ICU-12756
|
||||
LocaleSet supportedLocales;
|
||||
|
||||
// ucol_[count,get]Available() return different results compared to
|
||||
// uloc_[count,get]Available(), we can't use |supportedLocales| here.
|
||||
LocaleSet collatorSupportedLocales;
|
||||
|
||||
bool supportedLocalesInitialized = false;
|
||||
|
||||
// CountAvailable and GetAvailable describe the signatures used for ICU API
|
||||
// to determine available locales for various functionality.
|
||||
using CountAvailable = int32_t (*)();
|
||||
using GetAvailable = const char* (*)(int32_t localeIndex);
|
||||
|
||||
static bool getAvailableLocales(JSContext* cx, LocaleSet& locales,
|
||||
CountAvailable countAvailable,
|
||||
GetAvailable getAvailable);
|
||||
|
||||
/**
|
||||
* Precomputes the available locales sets.
|
||||
*/
|
||||
bool ensureSupportedLocales(JSContext* cx);
|
||||
|
||||
public:
|
||||
enum class SupportedLocaleKind {
|
||||
Collator,
|
||||
DateTimeFormat,
|
||||
NumberFormat,
|
||||
PluralRules,
|
||||
RelativeTimeFormat
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets |supported| to true if |locale| is supported by the requested Intl
|
||||
* service constructor. Otherwise sets |supported| to false.
|
||||
*/
|
||||
[[nodiscard]] bool isSupportedLocale(JSContext* cx, SupportedLocaleKind kind,
|
||||
JS::Handle<JSString*> locale,
|
||||
bool* supported);
|
||||
|
||||
private:
|
||||
/**
|
||||
* The case first parameter (BCP47 key "kf") allows to switch the order of
|
||||
* upper- and lower-case characters. ICU doesn't directly provide an API
|
||||
* to query the default case first value of a given locale, but instead
|
||||
* requires to instantiate a collator object and then query the case first
|
||||
* attribute (UCOL_CASE_FIRST).
|
||||
* To avoid instantiating an additional collator object whenever we need
|
||||
* to retrieve the default case first value of a specific locale, we
|
||||
* compute the default case first value for every supported locale only
|
||||
* once and then keep a list of all locales which don't use the default
|
||||
* case first setting.
|
||||
* There is almost no difference between lower-case first and when case
|
||||
* first is disabled (UCOL_LOWER_FIRST resp. UCOL_OFF), so we only need to
|
||||
* track locales which use upper-case first as their default setting.
|
||||
*/
|
||||
|
||||
LocaleSet upperCaseFirstLocales;
|
||||
|
||||
bool upperCaseFirstInitialized = false;
|
||||
|
||||
/**
|
||||
* Precomputes the available locales which use upper-case first sorting.
|
||||
*/
|
||||
bool ensureUpperCaseFirstLocales(JSContext* cx);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Sets |isUpperFirst| to true if |locale| sorts upper-case characters
|
||||
* before lower-case characters.
|
||||
*/
|
||||
bool isUpperCaseFirst(JSContext* cx, JS::HandleString locale, bool* isUpperFirst);
|
||||
|
||||
public:
|
||||
void destroyInstance();
|
||||
|
||||
void trace(JSTracer* trc);
|
||||
|
||||
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
|
||||
};
|
||||
|
||||
} // namespace intl
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* builtin_intl_SharedIntlData_h */
|
||||
@@ -28,7 +28,6 @@
|
||||
\
|
||||
_(IntlIsCollator) \
|
||||
_(IntlIsDateTimeFormat) \
|
||||
_(IntlIsListFormat) \
|
||||
_(IntlIsNumberFormat) \
|
||||
_(IntlIsPluralRules) \
|
||||
_(IntlIsRelativeTimeFormat) \
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "builtin/TypedObject.h"
|
||||
#include "builtin/intl/Collator.h"
|
||||
#include "builtin/intl/DateTimeFormat.h"
|
||||
#include "builtin/intl/ListFormat.h"
|
||||
#include "builtin/intl/NumberFormat.h"
|
||||
#include "builtin/intl/PluralRules.h"
|
||||
#include "builtin/intl/RelativeTimeFormat.h"
|
||||
@@ -114,8 +113,6 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target)
|
||||
return inlineHasClass(callInfo, &CollatorObject::class_);
|
||||
case InlinableNative::IntlIsDateTimeFormat:
|
||||
return inlineHasClass(callInfo, &DateTimeFormatObject::class_);
|
||||
case InlinableNative::IntlIsListFormat:
|
||||
return inlineHasClass(callInfo, &ListFormatObject::class_);
|
||||
case InlinableNative::IntlIsNumberFormat:
|
||||
return inlineHasClass(callInfo, &NumberFormatObject::class_);
|
||||
case InlinableNative::IntlIsPluralRules:
|
||||
|
||||
@@ -2877,11 +2877,6 @@ ToWindowIfWindowProxy(JSObject* obj);
|
||||
extern bool
|
||||
AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
|
||||
|
||||
// Create and add the Intl.ListFormat constructor function to the provided
|
||||
// object.
|
||||
extern bool
|
||||
AddListFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
class NativeProfiler
|
||||
|
||||
@@ -96,7 +96,6 @@
|
||||
real(Symbol, InitSymbolClass, OCLASP(Symbol)) \
|
||||
IF_SAB(real,imaginary)(SharedArrayBuffer, InitViaClassSpec, OCLASP(SharedArrayBuffer)) \
|
||||
IF_INTL(real,imaginary) (Intl, InitIntlClass, CLASP(Intl)) \
|
||||
IF_INTL(real,imaginary) (ListFormat, InitViaClassSpec, OCLASP(ListFormat)) \
|
||||
IF_BDATA(real,imaginary)(TypedObject, InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \
|
||||
real(Reflect, InitReflect, nullptr) \
|
||||
real(WeakSet, InitWeakSetClass, OCLASP(WeakSet)) \
|
||||
|
||||
@@ -126,7 +126,6 @@ main_deunified_sources = [
|
||||
'builtin/intl/IntlObject.cpp',
|
||||
'builtin/intl/LanguageTag.cpp',
|
||||
'builtin/intl/LanguageTagGenerated.cpp',
|
||||
'builtin/intl/ListFormat.cpp',
|
||||
'builtin/intl/Locale.cpp',
|
||||
'builtin/intl/NumberFormat.cpp',
|
||||
'builtin/intl/PluralRules.cpp',
|
||||
@@ -746,7 +745,6 @@ selfhosted.inputs = [
|
||||
'builtin/intl/CommonFunctions.js',
|
||||
'builtin/intl/DateTimeFormat.js',
|
||||
'builtin/intl/IntlObject.js',
|
||||
'builtin/intl/ListFormat.js',
|
||||
'builtin/intl/NumberFormat.js',
|
||||
'builtin/intl/PluralRules.js',
|
||||
'builtin/intl/RelativeTimeFormat.js',
|
||||
|
||||
@@ -935,10 +935,6 @@ AddIntlExtras(JSContext* cx, unsigned argc, Value* vp)
|
||||
if (!js::AddMozDateTimeFormatConstructor(cx, intl))
|
||||
return false;
|
||||
|
||||
if (!js::AddListFormatConstructor(cx, intl)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -118,7 +118,6 @@
|
||||
macro(dotStaticInitializers, dotStaticInitializers, ".staticInitializers") \
|
||||
macro(dotStaticFieldKeys, dotStaticFieldKeys, ".staticFieldKeys") \
|
||||
macro(each, each, "each") \
|
||||
macro(element, element, "element") \
|
||||
macro(elementType, elementType, "elementType") \
|
||||
macro(else, else_, "else") \
|
||||
macro(empty, empty, "") \
|
||||
@@ -205,7 +204,6 @@
|
||||
macro(Infinity, Infinity, "Infinity") \
|
||||
macro(InitializeCollator, InitializeCollator, "InitializeCollator") \
|
||||
macro(InitializeDateTimeFormat, InitializeDateTimeFormat, "InitializeDateTimeFormat") \
|
||||
macro(InitializeListFormat, InitializeListFormat, "InitializeListFormat") \
|
||||
macro(InitializeLocale, InitializeLocale, "InitializeLocale") \
|
||||
macro(InitializeNumberFormat, InitializeNumberFormat, "InitializeNumberFormat") \
|
||||
macro(InitializePluralRules, InitializePluralRules, "InitializePluralRules") \
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include "builtin/WeakMapObject.h"
|
||||
#include "builtin/WeakRefObject.h"
|
||||
#include "builtin/WeakSetObject.h"
|
||||
#include "builtin/intl/ListFormat.h"
|
||||
#include "vm/Debugger.h"
|
||||
#include "vm/EnvironmentObject.h"
|
||||
#include "vm/HelperThreads.h"
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "builtin/intl/Collator.h"
|
||||
#include "builtin/intl/DateTimeFormat.h"
|
||||
#include "builtin/intl/IntlObject.h"
|
||||
#include "builtin/intl/ListFormat.h"
|
||||
#include "builtin/intl/Locale.h"
|
||||
#include "builtin/intl/NumberFormat.h"
|
||||
#include "builtin/intl/PluralRules.h"
|
||||
@@ -2557,7 +2556,6 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
JS_FN("intl_patternForStyle", intl_patternForStyle, 6, 0),
|
||||
JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
|
||||
JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),
|
||||
JS_FN("intl_FormatList", intl_FormatList, 3, 0),
|
||||
JS_FN("intl_toLocaleLowerCase", intl_toLocaleLowerCase, 2,0),
|
||||
JS_FN("intl_toLocaleUpperCase", intl_toLocaleUpperCase, 2,0),
|
||||
JS_FN("intl_ValidateAndCanonicalizeLanguageTag", intl_ValidateAndCanonicalizeLanguageTag, 2, 0),
|
||||
@@ -2571,9 +2569,6 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
JS_INLINABLE_FN("IsDateTimeFormat",
|
||||
intrinsic_IsInstanceOfBuiltin<DateTimeFormatObject>, 1,0,
|
||||
IntlIsDateTimeFormat),
|
||||
JS_INLINABLE_FN("IsListFormat",
|
||||
intrinsic_IsInstanceOfBuiltin<ListFormatObject>, 1, 0,
|
||||
IntlIsListFormat),
|
||||
JS_INLINABLE_FN("IsNumberFormat",
|
||||
intrinsic_IsInstanceOfBuiltin<NumberFormatObject>, 1,0,
|
||||
IntlIsNumberFormat),
|
||||
|
||||
@@ -1737,7 +1737,9 @@ nsCSSRendering::PaintBackground(const PaintBGParams& aParams)
|
||||
}
|
||||
|
||||
static bool
|
||||
IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::Side aSide)
|
||||
IsOpaqueBorderEdge(const nsStyleBorder& aBorder,
|
||||
const nsStyleColor& aColor,
|
||||
mozilla::Side aSide)
|
||||
{
|
||||
if (aBorder.GetComputedBorder().Side(aSide) == 0)
|
||||
return true;
|
||||
@@ -1759,25 +1761,20 @@ IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::Side aSide)
|
||||
if (aBorder.mBorderImageSource.GetType() != eStyleImageType_Null)
|
||||
return false;
|
||||
|
||||
StyleComplexColor color = aBorder.mBorderColor[aSide];
|
||||
// We don't know the foreground color here, so if it's being used
|
||||
// we must assume it might be transparent.
|
||||
if (!color.IsNumericColor()) {
|
||||
return false;
|
||||
}
|
||||
return NS_GET_A(color.mColor) == 255;
|
||||
nscolor color = aColor.CalcComplexColor(aBorder.mBorderColor[aSide]);
|
||||
return NS_GET_A(color) == 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all border edges are either missing or opaque.
|
||||
*/
|
||||
static bool
|
||||
IsOpaqueBorder(const nsStyleBorder& aBorder)
|
||||
IsOpaqueBorder(const nsStyleBorder& aBorder, const nsStyleColor& aColor)
|
||||
{
|
||||
if (aBorder.mBorderColors)
|
||||
return false;
|
||||
NS_FOR_CSS_SIDES(i) {
|
||||
if (!IsOpaqueBorderEdge(aBorder, i))
|
||||
if (!IsOpaqueBorderEdge(aBorder, aColor, i))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1910,7 +1907,7 @@ nsCSSRendering::GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer,
|
||||
}
|
||||
|
||||
bool isSolidBorder =
|
||||
aWillPaintBorder && IsOpaqueBorder(aBorder);
|
||||
aWillPaintBorder && IsOpaqueBorder(aBorder, *aForFrame->StyleColor());
|
||||
if (isSolidBorder && layerClip == StyleGeometryBox::Border) {
|
||||
// If we have rounded corners, we need to inflate the background
|
||||
// drawing area a bit to avoid seams between the border and
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
:root {
|
||||
--color: #fff;
|
||||
background: var(--color);
|
||||
color: var(--color);
|
||||
}
|
||||
div {
|
||||
display: block;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
border-radius: 50%;
|
||||
background-color: #323232;
|
||||
border: 10px solid var(--color);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
:root {
|
||||
--color: #fff;
|
||||
background: var(--color);
|
||||
color: var(--color);
|
||||
}
|
||||
div {
|
||||
display: block;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
border-radius: 50%;
|
||||
background-color: #323232;
|
||||
border: 10px solid currentcolor;
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
@@ -89,6 +89,8 @@ fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.2/.te
|
||||
# Test for antialiasing gaps between background and border
|
||||
fuzzy-if(gtkWidget,1,9) fuzzy-if(winWidget&&!d2d,1,9) fuzzy-if(d2d,5,40) fuzzy-if(skiaContent,1,9) == curved-border-background-nogap.html curved-border-background-nogap-ref.html
|
||||
|
||||
== currentcolor-border-radius.html currentcolor-border-radius-ref.html
|
||||
|
||||
== color-layer-1a.html color-layer-1-ref.html
|
||||
|
||||
== corner-split.html corner-split-ref.svg # bug 1185636
|
||||
@@ -1273,7 +1273,8 @@ CSS_PROP_SHORTHAND(
|
||||
border_width,
|
||||
BorderWidth,
|
||||
CSS_PROPERTY_PARSE_FUNCTION |
|
||||
CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
|
||||
CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
|
||||
CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
|
||||
"")
|
||||
CSS_PROP_POSITION(
|
||||
bottom,
|
||||
|
||||
@@ -3193,6 +3193,66 @@ nsComputedDOMStyle::DoGetBorderRightWidth()
|
||||
return GetBorderWidthFor(eSideRight);
|
||||
}
|
||||
|
||||
already_AddRefed<CSSValue>
|
||||
nsComputedDOMStyle::DoGetBorderWidth()
|
||||
{
|
||||
nscoord widths[4];
|
||||
if (mInnerFrame) {
|
||||
AssertFlushedPendingReflows();
|
||||
const nsMargin& usedBorder = mInnerFrame->GetUsedBorder();
|
||||
NS_FOR_CSS_SIDES(side) {
|
||||
widths[side] = usedBorder.Side(side);
|
||||
}
|
||||
} else {
|
||||
const nsStyleBorder* border = StyleBorder();
|
||||
NS_FOR_CSS_SIDES(side) {
|
||||
widths[side] = border->GetComputedBorderWidth(side);
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> top = new nsROCSSPrimitiveValue;
|
||||
top->SetAppUnits(widths[eSideTop]);
|
||||
valueList->AppendCSSValue(top.forget());
|
||||
|
||||
if (widths[eSideRight] == widths[eSideLeft]) {
|
||||
if (widths[eSideTop] == widths[eSideBottom]) {
|
||||
if (widths[eSideTop] == widths[eSideRight]) {
|
||||
return valueList.forget();
|
||||
}
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> right = new nsROCSSPrimitiveValue;
|
||||
right->SetAppUnits(widths[eSideRight]);
|
||||
valueList->AppendCSSValue(right.forget());
|
||||
return valueList.forget();
|
||||
}
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> right = new nsROCSSPrimitiveValue;
|
||||
right->SetAppUnits(widths[eSideRight]);
|
||||
valueList->AppendCSSValue(right.forget());
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> bottom = new nsROCSSPrimitiveValue;
|
||||
bottom->SetAppUnits(widths[eSideBottom]);
|
||||
valueList->AppendCSSValue(bottom.forget());
|
||||
return valueList.forget();
|
||||
}
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> right = new nsROCSSPrimitiveValue;
|
||||
right->SetAppUnits(widths[eSideRight]);
|
||||
valueList->AppendCSSValue(right.forget());
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> bottom = new nsROCSSPrimitiveValue;
|
||||
bottom->SetAppUnits(widths[eSideBottom]);
|
||||
valueList->AppendCSSValue(bottom.forget());
|
||||
|
||||
RefPtr<nsROCSSPrimitiveValue> left = new nsROCSSPrimitiveValue;
|
||||
left->SetAppUnits(widths[eSideLeft]);
|
||||
valueList->AppendCSSValue(left.forget());
|
||||
|
||||
return valueList.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<CSSValue>
|
||||
nsComputedDOMStyle::DoGetBorderTopColor()
|
||||
{
|
||||
@@ -6843,4 +6903,3 @@ nsComputedDOMStyle::DoGetOverflowBlockEnd()
|
||||
return DoGetOverflowBlock();
|
||||
}
|
||||
|
||||
|
||||
@@ -344,6 +344,7 @@ private:
|
||||
already_AddRefed<CSSValue> DoGetBorderBottomWidth();
|
||||
already_AddRefed<CSSValue> DoGetBorderLeftWidth();
|
||||
already_AddRefed<CSSValue> DoGetBorderRightWidth();
|
||||
already_AddRefed<CSSValue> DoGetBorderWidth();
|
||||
already_AddRefed<CSSValue> DoGetBorderTopColor();
|
||||
already_AddRefed<CSSValue> DoGetBorderBottomColor();
|
||||
already_AddRefed<CSSValue> DoGetBorderLeftColor();
|
||||
|
||||
@@ -95,7 +95,7 @@ COMPUTED_STYLE_PROP(border_top_left_radius, BorderTopLeftRadius)
|
||||
COMPUTED_STYLE_PROP(border_top_right_radius, BorderTopRightRadius)
|
||||
COMPUTED_STYLE_PROP(border_top_style, BorderTopStyle)
|
||||
COMPUTED_STYLE_PROP(border_top_width, BorderTopWidth)
|
||||
//// COMPUTED_STYLE_PROP(border_width, BorderWidth)
|
||||
COMPUTED_STYLE_PROP(border_width, BorderWidth)
|
||||
COMPUTED_STYLE_PROP(bottom, Bottom)
|
||||
COMPUTED_STYLE_PROP(box_decoration_break, BoxDecorationBreak)
|
||||
COMPUTED_STYLE_PROP(box_shadow, BoxShadow)
|
||||
|
||||
+128
-22
@@ -319,6 +319,11 @@ nsRuleNode::EnsureInlineDisplay(StyleDisplay& display)
|
||||
}
|
||||
}
|
||||
|
||||
enum class AppUnitRounding {
|
||||
Default,
|
||||
TowardZero
|
||||
};
|
||||
|
||||
static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
nscoord aFontSize,
|
||||
const nsStyleFont* aStyleFont,
|
||||
@@ -326,7 +331,9 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
nsPresContext* aPresContext,
|
||||
bool aUseProvidedRootEmSize,
|
||||
bool aUseUserFontSet,
|
||||
RuleNodeCacheConditions& aConditions);
|
||||
RuleNodeCacheConditions& aConditions,
|
||||
AppUnitRounding aRounding =
|
||||
AppUnitRounding::Default);
|
||||
|
||||
struct CalcLengthCalcOps : public css::BasicCoordCalcOps,
|
||||
public css::NumbersAlreadyNormalizedOps
|
||||
@@ -339,18 +346,21 @@ struct CalcLengthCalcOps : public css::BasicCoordCalcOps,
|
||||
const bool mUseProvidedRootEmSize;
|
||||
const bool mUseUserFontSet;
|
||||
RuleNodeCacheConditions& mConditions;
|
||||
const AppUnitRounding mRounding;
|
||||
|
||||
CalcLengthCalcOps(nscoord aFontSize, const nsStyleFont* aStyleFont,
|
||||
nsStyleContext* aStyleContext, nsPresContext* aPresContext,
|
||||
bool aUseProvidedRootEmSize, bool aUseUserFontSet,
|
||||
RuleNodeCacheConditions& aConditions)
|
||||
RuleNodeCacheConditions& aConditions,
|
||||
AppUnitRounding aRounding)
|
||||
: mFontSize(aFontSize),
|
||||
mStyleFont(aStyleFont),
|
||||
mStyleContext(aStyleContext),
|
||||
mPresContext(aPresContext),
|
||||
mUseProvidedRootEmSize(aUseProvidedRootEmSize),
|
||||
mUseUserFontSet(aUseUserFontSet),
|
||||
mConditions(aConditions)
|
||||
mConditions(aConditions),
|
||||
mRounding(aRounding)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -358,15 +368,84 @@ struct CalcLengthCalcOps : public css::BasicCoordCalcOps,
|
||||
{
|
||||
return CalcLengthWith(aValue, mFontSize, mStyleFont,
|
||||
mStyleContext, mPresContext, mUseProvidedRootEmSize,
|
||||
mUseUserFontSet, mConditions);
|
||||
mUseUserFontSet, mConditions, mRounding);
|
||||
}
|
||||
};
|
||||
|
||||
static inline nscoord ScaleCoordRound(const nsCSSValue& aValue, float aFactor)
|
||||
static inline nscoord
|
||||
ScaleCoordWithRounding(const nsCSSValue& aValue, float aFactor,
|
||||
AppUnitRounding aRounding)
|
||||
{
|
||||
if (aRounding == AppUnitRounding::TowardZero) {
|
||||
return NSToCoordTruncClamped(aValue.GetFloatValue() * aFactor);
|
||||
}
|
||||
return NSToCoordRoundWithClamp(aValue.GetFloatValue() * aFactor);
|
||||
}
|
||||
|
||||
static inline nscoord
|
||||
CSSPixelsToAppUnitsWithRounding(float aPixels, AppUnitRounding aRounding)
|
||||
{
|
||||
if (aRounding == AppUnitRounding::TowardZero) {
|
||||
return NSToCoordTruncClamped(double(aPixels) *
|
||||
nsPresContext::AppUnitsPerCSSPixel());
|
||||
}
|
||||
return nsPresContext::CSSPixelsToAppUnits(aPixels);
|
||||
}
|
||||
|
||||
static nscoord
|
||||
GetFixedLengthWithRounding(const nsCSSValue& aValue,
|
||||
nsPresContext* aPresContext,
|
||||
AppUnitRounding aRounding)
|
||||
{
|
||||
if (aRounding == AppUnitRounding::Default) {
|
||||
return aValue.GetFixedLength(aPresContext);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_PhysicalMillimeter,
|
||||
"not a fixed length unit");
|
||||
double inches = double(aValue.GetFloatValue()) / MM_PER_INCH_FLOAT;
|
||||
return NSToCoordTruncClamped(
|
||||
inches * aPresContext->DeviceContext()->AppUnitsPerPhysicalInch());
|
||||
}
|
||||
|
||||
static nscoord
|
||||
GetPixelLengthWithRounding(const nsCSSValue& aValue,
|
||||
AppUnitRounding aRounding)
|
||||
{
|
||||
MOZ_ASSERT(aValue.IsPixelLengthUnit(), "not a pixel length unit");
|
||||
|
||||
double scaleFactor;
|
||||
switch (aValue.GetUnit()) {
|
||||
case eCSSUnit_Pixel:
|
||||
return CSSPixelsToAppUnitsWithRounding(aValue.GetFloatValue(),
|
||||
aRounding);
|
||||
case eCSSUnit_Pica:
|
||||
scaleFactor = 16.0;
|
||||
break;
|
||||
case eCSSUnit_Point:
|
||||
scaleFactor = 4.0 / 3.0;
|
||||
break;
|
||||
case eCSSUnit_Inch:
|
||||
scaleFactor = 96.0;
|
||||
break;
|
||||
case eCSSUnit_Millimeter:
|
||||
scaleFactor = 96.0 / 25.4;
|
||||
break;
|
||||
case eCSSUnit_Centimeter:
|
||||
scaleFactor = 96.0 / 2.54;
|
||||
break;
|
||||
case eCSSUnit_Quarter:
|
||||
scaleFactor = 96.0 / 101.6;
|
||||
break;
|
||||
default:
|
||||
NS_ERROR("should never get here");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return CSSPixelsToAppUnitsWithRounding(
|
||||
float(double(aValue.GetFloatValue()) * scaleFactor), aRounding);
|
||||
}
|
||||
|
||||
static inline nscoord ScaleViewportCoordTrunc(const nsCSSValue& aValue,
|
||||
nscoord aViewportSize)
|
||||
{
|
||||
@@ -465,7 +544,8 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
// except when called from
|
||||
// CalcLengthWithInitialFont.
|
||||
bool aUseUserFontSet,
|
||||
RuleNodeCacheConditions& aConditions)
|
||||
RuleNodeCacheConditions& aConditions,
|
||||
AppUnitRounding aRounding)
|
||||
{
|
||||
NS_ASSERTION(aValue.IsLengthUnit() || aValue.IsCalcUnit(),
|
||||
"not a length or calc unit");
|
||||
@@ -476,10 +556,10 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
NS_ASSERTION(aPresContext, "Must have prescontext");
|
||||
|
||||
if (aValue.IsFixedLengthUnit()) {
|
||||
return aValue.GetFixedLength(aPresContext);
|
||||
return GetFixedLengthWithRounding(aValue, aPresContext, aRounding);
|
||||
}
|
||||
if (aValue.IsPixelLengthUnit()) {
|
||||
return aValue.GetPixelLength();
|
||||
return GetPixelLengthWithRounding(aValue, aRounding);
|
||||
}
|
||||
if (aValue.IsCalcUnit()) {
|
||||
// For properties for which lengths are the *only* units accepted in
|
||||
@@ -490,7 +570,7 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
CalcLengthCalcOps ops(aFontSize, aStyleFont,
|
||||
aStyleContext, aPresContext,
|
||||
aUseProvidedRootEmSize, aUseUserFontSet,
|
||||
aConditions);
|
||||
aConditions, aRounding);
|
||||
return css::ComputeCalc(aValue, ops);
|
||||
}
|
||||
switch (aValue.GetUnit()) {
|
||||
@@ -623,7 +703,7 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
rootFontSize = rootStyleFont->mFont.size;
|
||||
}
|
||||
|
||||
return ScaleCoordRound(aValue, float(rootFontSize));
|
||||
return ScaleCoordWithRounding(aValue, float(rootFontSize), aRounding);
|
||||
}
|
||||
default:
|
||||
// Fall through to the code for units that can't be stored in the
|
||||
@@ -648,7 +728,7 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
// CSS2.1 specifies that this unit scales to the computed font
|
||||
// size, not the em-width in the font metrics, despite the name.
|
||||
aConditions.SetFontSizeDependency(aFontSize);
|
||||
return ScaleCoordRound(aValue, float(aFontSize));
|
||||
return ScaleCoordWithRounding(aValue, float(aFontSize), aRounding);
|
||||
}
|
||||
case eCSSUnit_XHeight: {
|
||||
aPresContext->SetUsesExChUnits(true);
|
||||
@@ -656,7 +736,7 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
GetMetricsFor(aPresContext, aStyleContext, styleFont,
|
||||
aFontSize, aUseUserFontSet);
|
||||
aConditions.SetUncacheable();
|
||||
return ScaleCoordRound(aValue, float(fm->XHeight()));
|
||||
return ScaleCoordWithRounding(aValue, float(fm->XHeight()), aRounding);
|
||||
}
|
||||
case eCSSUnit_Char: {
|
||||
aPresContext->SetUsesExChUnits(true);
|
||||
@@ -668,8 +748,9 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
|
||||
GetMetrics(fm->Orientation()).zeroOrAveCharWidth;
|
||||
|
||||
aConditions.SetUncacheable();
|
||||
return ScaleCoordRound(aValue, ceil(aPresContext->AppUnitsPerDevPixel() *
|
||||
zeroWidth));
|
||||
return ScaleCoordWithRounding(
|
||||
aValue, ceil(aPresContext->AppUnitsPerDevPixel() * zeroWidth),
|
||||
aRounding);
|
||||
}
|
||||
default:
|
||||
NS_NOTREACHED("unexpected unit");
|
||||
@@ -701,6 +782,20 @@ static inline nscoord CalcLength(const nsCSSValue& aValue,
|
||||
aPresContext, aConditions);
|
||||
}
|
||||
|
||||
static inline nscoord
|
||||
CalcLengthTowardZero(const nsCSSValue& aValue,
|
||||
nsStyleContext* aStyleContext,
|
||||
nsPresContext* aPresContext,
|
||||
RuleNodeCacheConditions& aConditions)
|
||||
{
|
||||
NS_ASSERTION(aStyleContext, "Must have style data");
|
||||
|
||||
return CalcLengthWith(aValue, -1, nullptr,
|
||||
aStyleContext, aPresContext,
|
||||
false, true, aConditions,
|
||||
AppUnitRounding::TowardZero);
|
||||
}
|
||||
|
||||
/* static */ nscoord
|
||||
nsRuleNode::CalcLengthWithInitialFont(nsPresContext* aPresContext,
|
||||
const nsCSSValue& aValue)
|
||||
@@ -8018,13 +8113,11 @@ nsRuleNode::ComputeBorderData(void* aStartStruct,
|
||||
"Unexpected enum value");
|
||||
border->SetBorderWidth(side,
|
||||
(mPresContext->GetBorderWidthTable())[value.GetIntValue()]);
|
||||
// OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT
|
||||
} else if (SetCoord(value, coord, nsStyleCoord(),
|
||||
SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY,
|
||||
aContext, mPresContext, conditions)) {
|
||||
NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
|
||||
} else if (value.IsLengthUnit() || value.IsCalcUnit()) {
|
||||
// clamp negative calc() to 0.
|
||||
border->SetBorderWidth(side, std::max(coord.GetCoordValue(), 0));
|
||||
border->SetBorderWidth(side,
|
||||
std::max(CalcLengthTowardZero(value, aContext, mPresContext,
|
||||
conditions), 0));
|
||||
} else if (eCSSUnit_Inherit == value.GetUnit()) {
|
||||
conditions.SetUncacheable();
|
||||
border->SetBorderWidth(side,
|
||||
@@ -8302,6 +8395,11 @@ nsRuleNode::ComputeOutlineData(void* aStartStruct,
|
||||
eCSSUnit_Revert == outlineWidthValue->GetUnit()) {
|
||||
outline->mOutlineWidth =
|
||||
nsStyleCoord(NS_STYLE_BORDER_WIDTH_MEDIUM, eStyleUnit_Enumerated);
|
||||
} else if (outlineWidthValue->IsLengthUnit() ||
|
||||
outlineWidthValue->IsCalcUnit()) {
|
||||
outline->mOutlineWidth.SetCoordValue(
|
||||
CalcLengthTowardZero(*outlineWidthValue, aContext, mPresContext,
|
||||
conditions));
|
||||
} else {
|
||||
SetCoord(*outlineWidthValue, outline->mOutlineWidth,
|
||||
parentOutline->mOutlineWidth,
|
||||
@@ -8312,13 +8410,21 @@ nsRuleNode::ComputeOutlineData(void* aStartStruct,
|
||||
// outline-offset: length, inherit
|
||||
nsStyleCoord tempCoord;
|
||||
const nsCSSValue* outlineOffsetValue = aRuleData->ValueForOutlineOffset();
|
||||
if (SetCoord(*outlineOffsetValue, tempCoord,
|
||||
if (outlineOffsetValue->IsLengthUnit() || outlineOffsetValue->IsCalcUnit()) {
|
||||
outline->mOutlineOffset =
|
||||
NS_ROUND_OFFSET_TO_PIXELS(
|
||||
CalcLengthTowardZero(*outlineOffsetValue, aContext, mPresContext,
|
||||
conditions),
|
||||
mPresContext->AppUnitsPerDevPixel());
|
||||
} else if (SetCoord(*outlineOffsetValue, tempCoord,
|
||||
nsStyleCoord(parentOutline->mOutlineOffset,
|
||||
nsStyleCoord::CoordConstructor),
|
||||
SETCOORD_LH | SETCOORD_INITIAL_ZERO | SETCOORD_CALC_LENGTH_ONLY |
|
||||
SETCOORD_UNSET_INITIAL,
|
||||
aContext, mPresContext, conditions)) {
|
||||
outline->mOutlineOffset = tempCoord.GetCoordValue();
|
||||
outline->mOutlineOffset =
|
||||
NS_ROUND_OFFSET_TO_PIXELS(tempCoord.GetCoordValue(),
|
||||
mPresContext->AppUnitsPerDevPixel());
|
||||
} else {
|
||||
NS_ASSERTION(outlineOffsetValue->GetUnit() == eCSSUnit_Null,
|
||||
"unexpected unit");
|
||||
|
||||
@@ -1145,21 +1145,19 @@ private:
|
||||
nsCSSShadowItem mArray[1]; // This MUST be the last item
|
||||
};
|
||||
|
||||
// Border widths are rounded to the nearest integer number of pixels, but values
|
||||
// between zero and one device pixels are always rounded up to one device pixel.
|
||||
// Border widths are rounded down to integer pixels, but values between zero and
|
||||
// one device pixel are always rounded up to one device pixel.
|
||||
#define NS_ROUND_BORDER_TO_PIXELS(l,tpp) \
|
||||
((l) == 0) ? 0 : std::max((tpp), ((l) + ((tpp) / 2)) / (tpp) * (tpp))
|
||||
(((l) == 0) ? 0 : std::max((tpp), (l) / (tpp) * (tpp)))
|
||||
// Caret widths are rounded to the nearest-below integer number of pixels, but values
|
||||
// between zero and one device pixels are always rounded up to one device pixel.
|
||||
#define NS_ROUND_CARET_TO_PIXELS(l,tpp) \
|
||||
((l) == 0) ? 0 : std::max((tpp), (l) / (tpp) * (tpp))
|
||||
// Outline offset is rounded to the nearest integer number of pixels, but values
|
||||
// between zero and one device pixels are always rounded up to one device pixel.
|
||||
// Note that the offset can be negative.
|
||||
NS_ROUND_BORDER_TO_PIXELS(l,tpp)
|
||||
// Outline offset is snapped like border widths, preserving the sign.
|
||||
#define NS_ROUND_OFFSET_TO_PIXELS(l,tpp) \
|
||||
(((l) == 0) ? 0 : \
|
||||
((l) > 0) ? std::max( (tpp), ((l) + ((tpp) / 2)) / (tpp) * (tpp)) : \
|
||||
std::min(-(tpp), ((l) - ((tpp) / 2)) / (tpp) * (tpp)))
|
||||
(((l) > 0) ? NS_ROUND_BORDER_TO_PIXELS((l), (tpp)) : \
|
||||
std::min(-(tpp), (l) / (tpp) * (tpp))))
|
||||
|
||||
// Returns if the given border style type is visible or not
|
||||
static bool IsVisibleBorderStyle(uint8_t aStyle)
|
||||
|
||||
@@ -75,6 +75,7 @@ support-files = file_animations_with_disabled_properties.html
|
||||
[test_nesting_flattening_parser_edges.html]
|
||||
[test_nesting_flattening_recovery.html]
|
||||
[test_box_size_keywords.html]
|
||||
[test_border_width_rounding.html]
|
||||
[test_bug73586.html]
|
||||
[test_bug74880.html]
|
||||
[test_bug98997.html]
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test fractional border and outline width rounding</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="display"></div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var display = document.getElementById("display");
|
||||
|
||||
var tests = [
|
||||
["0px", "0px"],
|
||||
["0.1px", "1px"],
|
||||
["0.25px", "1px"],
|
||||
["0.5px", "1px"],
|
||||
["0.9px", "1px"],
|
||||
["1px", "1px"],
|
||||
["1.25px", "1px"],
|
||||
["1.5px", "1px"],
|
||||
["2px", "2px"],
|
||||
["2.75px", "2px"],
|
||||
["2.999px", "2px"],
|
||||
["3px", "3px"],
|
||||
["3.001px", "3px"],
|
||||
];
|
||||
|
||||
function checkWidth(property, setupProperty, setupValue) {
|
||||
tests.forEach(function(test) {
|
||||
var specified = test[0];
|
||||
var expected = test[1];
|
||||
var div = document.createElement("div");
|
||||
div.style[setupProperty] = setupValue;
|
||||
div.style[property] = specified;
|
||||
display.appendChild(div);
|
||||
|
||||
is(document.defaultView.getComputedStyle(div, "")[property], expected,
|
||||
property + " computes " + specified + " as " + expected);
|
||||
|
||||
display.removeChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
function checkOffset(property) {
|
||||
tests.forEach(function(test) {
|
||||
var specified = test[0];
|
||||
var expected = test[1];
|
||||
var div = document.createElement("div");
|
||||
div.style[property] = specified;
|
||||
display.appendChild(div);
|
||||
|
||||
is(document.defaultView.getComputedStyle(div, "")[property], expected,
|
||||
property + " computes " + specified + " as " + expected);
|
||||
|
||||
display.removeChild(div);
|
||||
});
|
||||
|
||||
tests.forEach(function(test) {
|
||||
var specified = test[0];
|
||||
var expected = test[0] == "0px" ? "0px" : "-" + test[1];
|
||||
var div = document.createElement("div");
|
||||
div.style[property] = "-" + specified;
|
||||
display.appendChild(div);
|
||||
|
||||
is(document.defaultView.getComputedStyle(div, "")[property], expected,
|
||||
property + " computes -" + specified + " as " + expected);
|
||||
|
||||
display.removeChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
checkWidth("borderWidth", "borderStyle", "solid");
|
||||
checkWidth("outlineWidth", "outlineStyle", "solid");
|
||||
checkOffset("outlineOffset");
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user