Bug 1312053 - Expose an API to get locale information. r?waldo draft
authorZibi Braniecki <gandalf@mozilla.com>
Tue, 24 Jan 2017 22:47:22 -0800
changeset 466081 501f8e6ce32b8474eb0601035fbe6a637d7de677
parent 465527 8ff550409e1d1f8b54f6f7f115545dbef857be0b
child 543313 dbe89fab65b9feab32117b914ad490c67540c7cf
push id42783
push userzbraniecki@mozilla.com
push dateWed, 25 Jan 2017 07:10:31 +0000
reviewerswaldo
bugs1312053
milestone54.0a1
Bug 1312053 - Expose an API to get locale information. r?waldo MozReview-Commit-ID: CCM4jLZVCED
js/src/builtin/Intl.cpp
js/src/builtin/Intl.h
js/src/builtin/Intl.js
js/src/shell/js.cpp
js/src/tests/Intl/getLocaleInfo.js
js/src/vm/CommonPropertyNames.h
js/src/vm/SelfHosting.cpp
toolkit/components/mozintl/MozIntl.cpp
toolkit/components/mozintl/mozIMozIntl.idl
toolkit/components/mozintl/test/test_mozintl.js
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -28,16 +28,17 @@
 #include "ds/Sort.h"
 #if ENABLE_INTL_API
 #include "unicode/plurrule.h"
 #include "unicode/ucal.h"
 #include "unicode/ucol.h"
 #include "unicode/udat.h"
 #include "unicode/udatpg.h"
 #include "unicode/uenum.h"
+#include "unicode/uloc.h"
 #include "unicode/unum.h"
 #include "unicode/unumsys.h"
 #include "unicode/upluralrules.h"
 #include "unicode/ustring.h"
 #endif
 #include "vm/DateTime.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
@@ -161,16 +162,22 @@ uloc_getAvailable(int32_t n)
 }
 
 int32_t
 uloc_countAvailable()
 {
     MOZ_CRASH("uloc_countAvailable: Intl API disabled");
 }
 
+UBool
+uloc_isRightToLeft(const char* locale)
+{
+    MOZ_CRASH("uloc_isRightToLeft: Intl API disabled");
+}
+
 struct UFormattable;
 
 void
 ufmt_close(UFormattable* fmt)
 {
     MOZ_CRASH("ufmt_close: Intl API disabled");
 }
 
@@ -4388,16 +4395,43 @@ js::intl_ComputeDisplayNames(JSContext* 
             return false;
     }
 
     // 6. Return result.
     args.rval().setObject(*result);
     return true;
 }
 
+bool
+js::intl_GetLocaleInfo(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 1);
+
+    JSAutoByteString locale(cx, args[0].toString());
+    if (!locale)
+        return false;
+
+    RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx));
+    if (!info)
+        return false;
+
+    bool rtl = uloc_isRightToLeft(icuLocale(locale.ptr()));
+
+    RootedValue dirVal(cx);
+
+    dirVal = StringValue(rtl ? cx->names().rtl : cx->names().ltr);
+
+    if (!DefineProperty(cx, info, cx->names().direction, dirVal))
+        return false;
+
+    args.rval().setObject(*info);
+    return true;
+}
+
 /******************** Intl ********************/
 
 const Class js::IntlClass = {
     js_Object_str,
     JSCLASS_HAS_CACHED_PROTO(JSProto_Intl)
 };
 
 #if JS_HAS_TOSOURCE
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -423,16 +423,28 @@ intl_GetPluralCategories(JSContext* cx, 
  *     1 for bn-IN (note that "weekend" is *not* necessarily two days)
  *
  * NOTE: "calendar" and "locale" properties are *not* added to the object.
  */
 extern MOZ_MUST_USE bool
 intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp);
 
 /**
+ * Returns a plain object with locale information for a single valid locale
+ * (callers must perform this validation).  The object will have these
+ * properties:
+ *
+ *   direction
+ *     a string with a value "ltr" for left-to-right locale, and "rtl" for
+ *     right-to-left locale.
+ */
+extern MOZ_MUST_USE bool
+intl_GetLocaleInfo(JSContext* cx, unsigned argc, Value* vp);
+
+/**
  * Returns an Array with CLDR-based fields display names.
  * The function takes three arguments:
  *
  *   locale
  *     BCP47 compliant locale string
  *   style
  *     A string with values: long or short or narrow
  *   keys
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -3212,16 +3212,39 @@ function Intl_getCalendarInfo(locales) {
 
   const result = intl_GetCalendarInfo(r.locale);
   result.calendar = r.ca;
   result.locale = r.locale;
 
   return result;
 }
 
+function Intl_getLocaleInfo(locales) {
+  const requestedLocales = CanonicalizeLocaleList(locales);
+
+  // In the future, we may want to expose uloc_getAvailable and use it here.
+  const DateTimeFormat = dateTimeFormatInternalProperties;
+  const localeData = DateTimeFormat.localeData;
+
+  const localeOpt = new Record();
+  localeOpt.localeMatcher = "best fit";
+
+  const r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat),
+                          requestedLocales,
+                          localeOpt,
+                          DateTimeFormat.relevantExtensionKeys,
+                          localeData);
+
+  const info = intl_GetLocaleInfo(r.locale);
+  return {
+      direction: info.direction,
+      locale: r.locale
+  };
+}
+
 /**
  * This function is a custom method designed after Intl API, but currently
  * not part of the spec or spec proposal.
  * We want to use it internally to retrieve translated values from CLDR in
  * order to ensure they're aligned with what Intl API returns.
  *
  * This API may one day be a foundation for an ECMA402 API spec proposal.
  *
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -898,16 +898,17 @@ AddIntlExtras(JSContext* cx, unsigned ar
     if (!args.get(0).isObject()) {
         JS_ReportErrorASCII(cx, "addIntlExtras must be passed an object");
         return false;
     }
     JS::RootedObject intl(cx, &args[0].toObject());
 
     static const JSFunctionSpec funcs[] = {
         JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
+        JS_SELF_HOSTED_FN("getLocaleInfo", "Intl_getLocaleInfo", 1, 0),
         JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
         JS_FS_END
     };
 
     if (!JS_DefineFunctions(cx, intl, funcs))
         return false;
 
     if (!js::AddPluralRulesConstructor(cx, intl))
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/getLocaleInfo.js
@@ -0,0 +1,38 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
+/* 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/. */
+
+// Tests the getCalendarInfo function with a diverse set of arguments.
+
+function checkLocaleInfo(info, expected)
+{
+  assertEq(Object.getPrototypeOf(info), Object.prototype);
+
+  assertEq(info.direction, expected.direction);
+  assertEq(info.locale, expected.locale);
+}
+
+addIntlExtras(Intl);
+
+let gLI = Intl.getLocaleInfo;
+
+assertEq(gLI.length, 1);
+
+checkLocaleInfo(gLI('en-US'), {
+  direction: "ltr",
+  locale: "en-US"
+});
+
+checkLocaleInfo(gLI('fr'), {
+  direction: "ltr",
+  locale: "fr"
+});
+
+checkLocaleInfo(gLI('ar'), {
+  direction: "rtl",
+  locale: "ar"
+});
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -81,16 +81,17 @@
     macro(DefaultDerivedClassConstructor, DefaultDerivedClassConstructor, "DefaultDerivedClassConstructor") \
     macro(default_, default_, "default") \
     macro(defineGetter, defineGetter, "__defineGetter__") \
     macro(defineProperty, defineProperty, "defineProperty") \
     macro(defineSetter, defineSetter, "__defineSetter__") \
     macro(delete, delete_, "delete") \
     macro(deleteProperty, deleteProperty, "deleteProperty") \
     macro(displayURL, displayURL, "displayURL") \
+    macro(direction, direction, "direction") \
     macro(done, done, "done") \
     macro(dotGenerator, dotGenerator, ".generator") \
     macro(dotThis, dotThis, ".this") \
     macro(each, each, "each") \
     macro(elementType, elementType, "elementType") \
     macro(empty, empty, "") \
     macro(emptyRegExp, emptyRegExp, "(?:)") \
     macro(encodeURI, encodeURI, "encodeURI") \
@@ -186,16 +187,17 @@
     macro(let, let, "let") \
     macro(line, line, "line") \
     macro(lineNumber, lineNumber, "lineNumber") \
     macro(literal, literal, "literal") \
     macro(loc, loc, "loc") \
     macro(locale, locale, "locale") \
     macro(lookupGetter, lookupGetter, "__lookupGetter__") \
     macro(lookupSetter, lookupSetter, "__lookupSetter__") \
+    macro(ltr, ltr, "ltr") \
     macro(MapConstructorInit, MapConstructorInit, "MapConstructorInit") \
     macro(MapIterator, MapIterator, "Map Iterator") \
     macro(maximumFractionDigits, maximumFractionDigits, "maximumFractionDigits") \
     macro(maximumSignificantDigits, maximumSignificantDigits, "maximumSignificantDigits") \
     macro(message, message, "message") \
     macro(minDays, minDays, "minDays") \
     macro(minimumFractionDigits, minimumFractionDigits, "minimumFractionDigits") \
     macro(minimumIntegerDigits, minimumIntegerDigits, "minimumIntegerDigits") \
@@ -268,16 +270,17 @@
     macro(Reify, Reify, "Reify") \
     macro(reject, reject, "reject") \
     macro(rejected, rejected, "rejected") \
     macro(RequireObjectCoercible, RequireObjectCoercible, "RequireObjectCoercible") \
     macro(resolve, resolve, "resolve") \
     macro(resumeGenerator, resumeGenerator, "resumeGenerator") \
     macro(return, return_, "return") \
     macro(revoke, revoke, "revoke") \
+    macro(rtl, rtl, "rtl") \
     macro(script, script, "script") \
     macro(scripts, scripts, "scripts") \
     macro(second, second, "second") \
     macro(selfHosted, selfHosted, "self-hosted") \
     macro(sensitivity, sensitivity, "sensitivity") \
     macro(set, set, "set") \
     macro(SetConstructorInit, SetConstructorInit, "SetConstructorInit") \
     macro(SetIterator, SetIterator, "Set Iterator") \
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2467,16 +2467,17 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("intl_CompareStrings", intl_CompareStrings, 3,0),
     JS_FN("intl_DateTimeFormat", intl_DateTimeFormat, 2,0),
     JS_FN("intl_DateTimeFormat_availableLocales", intl_DateTimeFormat_availableLocales, 0,0),
     JS_FN("intl_defaultTimeZone", intl_defaultTimeZone, 0,0),
     JS_FN("intl_defaultTimeZoneOffset", intl_defaultTimeZoneOffset, 0,0),
     JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),
     JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
     JS_FN("intl_GetCalendarInfo", intl_GetCalendarInfo, 1,0),
+    JS_FN("intl_GetLocaleInfo", intl_GetLocaleInfo, 1,0),
     JS_FN("intl_ComputeDisplayNames", intl_ComputeDisplayNames, 3,0),
     JS_FN("intl_IsValidTimeZoneName", intl_IsValidTimeZoneName, 1,0),
     JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
     JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
     JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
     JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
     JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0),
     JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
--- a/toolkit/components/mozintl/MozIntl.cpp
+++ b/toolkit/components/mozintl/MozIntl.cpp
@@ -77,16 +77,42 @@ MozIntl::AddPluralRulesConstructor(JS::H
 
   if (!js::AddPluralRulesConstructor(cx, realIntlObj)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+MozIntl::AddGetLocaleInfo(JS::Handle<JS::Value> val, JSContext* cx)
+{
+  if (!val.isObject()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  JS::Rooted<JSObject*> realIntlObj(cx, js::CheckedUnwrap(&val.toObject()));
+  if (!realIntlObj) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  JSAutoCompartment ac(cx, realIntlObj);
+
+  static const JSFunctionSpec funcs[] = {
+    JS_SELF_HOSTED_FN("getLocaleInfo", "Intl_getLocaleInfo", 1, 0),
+    JS_FS_END
+  };
+
+  if (!JS_DefineFunctions(cx, realIntlObj, funcs)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
 NS_GENERIC_FACTORY_CONSTRUCTOR(MozIntl)
 NS_DEFINE_NAMED_CID(MOZ_MOZINTL_CID);
 
 static const Module::CIDEntry kMozIntlCIDs[] = {
   { &kMOZ_MOZINTL_CID, false, nullptr, MozIntlConstructor },
   { nullptr }
 };
 
--- a/toolkit/components/mozintl/mozIMozIntl.idl
+++ b/toolkit/components/mozintl/mozIMozIntl.idl
@@ -5,16 +5,17 @@
 
 #include "nsISupports.idl"
 
 [scriptable, uuid(9f9bc42e-54f4-11e6-9aed-4b1429ac0ba0)]
 interface mozIMozIntl : nsISupports
 {
   [implicit_jscontext] void addGetCalendarInfo(in jsval intlObject);
   [implicit_jscontext] void addGetDisplayNames(in jsval intlObject);
+  [implicit_jscontext] void addGetLocaleInfo(in jsval intlObject);
 
   /**
    * Adds a PluralRules constructor to the given object.  This function may only
    * be called once within a realm/global object: calling it multiple times will
    * throw.
    */
   [implicit_jscontext] void addPluralRulesConstructor(in jsval intlObject);
 };
--- a/toolkit/components/mozintl/test/test_mozintl.js
+++ b/toolkit/components/mozintl/test/test_mozintl.js
@@ -38,9 +38,13 @@ function test_methods_presence(mozIntl) 
 
   let x = {};
 
   mozIntl.addGetCalendarInfo(x);
   equal(x.getCalendarInfo instanceof Function, true);
 
   mozIntl.addGetDisplayNames(x);
   equal(x.getDisplayNames instanceof Function, true);
+
+  mozIntl.addGetLocaleInfo(x);
+  equal(x.getDisplayNames instanceof Function, true);
+  equal(x.getLocaleInfo() instanceof Object, true);
 }