Bug 1522070 - Part 3: Remove support for privateuse-only language tags. r=jwalden
authorAndré Bargull <andre.bargull@gmail.com>
Tue, 09 Apr 2019 09:16:25 +0000
changeset 468534 b215b68fbccce1f1297b79ae471341bbe1cc7164
parent 468533 5a50fd4ec8bab22f316b9f33db4ed51f298b5ae1
child 468535 c5a97d3424310716d3a849dfb95f1ec86f7eb783
push id112733
push usercsabou@mozilla.com
push dateTue, 09 Apr 2019 16:30:22 +0000
treeherdermozilla-inbound@e14dba56bbfd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden
bugs1522070
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1522070 - Part 3: Remove support for privateuse-only language tags. r=jwalden Language tags only consisting of a private-use subtags are not allowed in Unicode BCP 47 locale identifiers. Differential Revision: https://phabricator.services.mozilla.com/D23538
js/src/builtin/intl/CommonFunctions.js
js/src/tests/jstests.list
js/src/tests/non262/Intl/NumberFormat/duplicate-singleton-variant.js
js/src/tests/non262/Intl/NumberFormat/remove-unicode-extensions.js
js/src/tests/non262/Intl/String/toLocaleLowerCase.js
js/src/tests/non262/Intl/String/toLocaleUpperCase.js
js/src/tests/non262/Intl/extensions/unicode-extension-sequences.js
js/src/tests/non262/Intl/getCanonicalLocales-weird-cases.js
js/src/tests/non262/Intl/uppercase-privateuse.js
--- a/js/src/builtin/intl/CommonFunctions.js
+++ b/js/src/builtin/intl/CommonFunctions.js
@@ -27,29 +27,16 @@
  * match the extension production in RFC 5646, where the singleton component is
  * "u".
  *
  * Spec: ECMAScript Internationalization API Specification, 6.2.1.
  */
 function startOfUnicodeExtensions(locale) {
     assert(typeof locale === "string", "locale is a string");
 
-    #define HYPHEN 0x2D
-    assert(std_String_fromCharCode(HYPHEN) === "-",
-           "code unit constant should match the expected character");
-
-    // A wholly-privateuse locale has no extension sequences.
-    if (callFunction(std_String_charCodeAt, locale, 1) === HYPHEN) {
-        assert(locale[0] === "x",
-               "locale[1] === '-' implies a privateuse-only locale");
-        return -1;
-    }
-
-    #undef HYPHEN
-
     // Search for "-u-" marking the start of a Unicode extension sequence.
     var start = callFunction(std_String_indexOf, locale, "-u-");
     if (start < 0)
         return -1;
 
     // And search for "-x-" marking the start of any privateuse component to
     // handle the case when "-u-" was only found within a privateuse subtag.
     var privateExt = callFunction(std_String_indexOf, locale, "-x-");
@@ -137,16 +124,17 @@ function getUnicodeExtensions(locale) {
  * Parser for BCP 47 language tags.
  *
  * ---------------------------------------------------------------------------
  * The following features were removed because the spec was changed to use
  * Unicode BCP 47 locale identifier instead:
  * - extlang subtags
  * - irregular grandfathered language tags.
  * - regular grandfathered language tags with extlang-like subtags.
+ * - privateuse-only language tags.
  *
  * The removed features may still be referenced in some comments. This will be
  * cleaned up when everything has been updated to follow the new specification.
  *
  * Ref: https://github.com/tc39/ecma402/pull/289
  * ---------------------------------------------------------------------------
  *
  * Returns null if |locale| can't be parsed as a Language-Tag. If the input is
@@ -155,17 +143,17 @@ function getUnicodeExtensions(locale) {
  *   {
  *     locale: locale (normalized to canonical form),
  *     grandfathered: true,
  *   }
  *
  * is returned. Otherwise the returned object has the following structure:
  *
  *   {
- *     language: language subtag / undefined,
+ *     language: language subtag,
  *     script: script subtag / undefined,
  *     region: region subtag / undefined,
  *     variants: array of variant subtags,
  *     extensions: array of extension subtags,
  *     privateuse: privateuse subtag / undefined,
  *   }
  *
  * All language tag subtags are returned in their normalized case:
@@ -262,158 +250,153 @@ function parseLanguageTag(locale) {
     }
 
     // Returns the current token part transformed to lower-case.
     function tokenStringLower() {
         return Substring(localeLowercase, tokenStart, tokenLength);
     }
 
     // Language-Tag = langtag           ; normal language tags
-    //              / privateuse        ; private use tag
     //              / grandfathered     ; grandfathered tags
     if (!nextToken())
         return null;
 
-    // All Language-Tag productions start with the ALPHA token and contain
-    // less-or-equal to eight characters.
-    if (token !== ALPHA || tokenLength > 8)
+    // All Language-Tag productions start with the ALPHA token, have at least
+    // two characters, and contain less-or-equal to eight characters.
+    if (token !== ALPHA || tokenLength < 2 || tokenLength > 8)
         return null;
 
-    assert(tokenLength > 0, "token length is not zero if type is ALPHA");
-
     var language, script, region, privateuse;
     var variants = [];
     var extensions = [];
 
     // langtag = language
     //           ["-" script]
     //           ["-" region]
     //           *("-" variant)
     //           *("-" extension)
     //           ["-" privateuse]
-    if (tokenLength > 1) {
-        // language = 2*3ALPHA          ; shortest ISO 639 code
-        //          / 4ALPHA            ; or reserved for future use
-        //          / 5*8ALPHA          ; or registered language subtag
-        if (tokenLength <= 3) {
-            language = tokenStringLower();
-            if (!nextToken())
-                return null;
-        } else {
-            assert(4 <= tokenLength && tokenLength <= 8, "reserved/registered language subtags");
-            language = tokenStringLower();
-            if (!nextToken())
-                return null;
-        }
+
+    // language = 2*3ALPHA          ; shortest ISO 639 code
+    //          / 4ALPHA            ; or reserved for future use
+    //          / 5*8ALPHA          ; or registered language subtag
+    if (tokenLength <= 3) {
+        language = tokenStringLower();
+        if (!nextToken())
+            return null;
+    } else {
+        assert(4 <= tokenLength && tokenLength <= 8, "reserved/registered language subtags");
+        language = tokenStringLower();
+        if (!nextToken())
+            return null;
+    }
+
+    // script = 4ALPHA              ; ISO 15924 code
+    if (tokenLength === 4 && token === ALPHA) {
+        script = tokenStringLower();
+
+        // The first character of a script code needs to be capitalized.
+        // "hans" -> "Hans"
+        script = callFunction(std_String_toUpperCase, script[0]) +
+                 Substring(script, 1, script.length - 1);
+
+        if (!nextToken())
+            return null;
+    }
+
+    // region = 2ALPHA              ; ISO 3166-1 code
+    //        / 3DIGIT              ; UN M.49 code
+    if ((tokenLength === 2 && token === ALPHA) || (tokenLength === 3 && token === DIGIT)) {
+        region = tokenStringLower();
+
+        // Region codes need to be in upper-case. "bu" -> "BU"
+        region = callFunction(std_String_toUpperCase, region);
+
+        if (!nextToken())
+            return null;
+    }
+
+    // variant = 5*8alphanum        ; registered variants
+    //         / (DIGIT 3alphanum)
+    //
+    // RFC 5646 section 2.1
+    // alphanum = (ALPHA / DIGIT)   ; letters and numbers
+    while ((5 <= tokenLength && tokenLength <= 8) ||
+           (tokenLength === 4 && tokenStartCodeUnitLower() <= DIGIT_NINE))
+    {
+        assert(!(tokenStartCodeUnitLower() <= DIGIT_NINE) ||
+               tokenStartCodeUnitLower() >= DIGIT_ZERO,
+               "token-start-code-unit <= '9' implies token-start-code-unit is in '0'..'9'");
 
-        // script = 4ALPHA              ; ISO 15924 code
-        if (tokenLength === 4 && token === ALPHA) {
-            script = tokenStringLower();
+        // Language tags are case insensitive (RFC 5646 section 2.1.1).
+        // All seen variants are compared ignoring case differences by
+        // using the lower-case form. This allows to properly detect and
+        // reject variant repetitions with differing case, e.g.
+        // "en-variant-Variant".
+        var variant = tokenStringLower();
 
-            // The first character of a script code needs to be capitalized.
-            // "hans" -> "Hans"
-            script = callFunction(std_String_toUpperCase, script[0]) +
-                     Substring(script, 1, script.length - 1);
+        // Reject the language tag if a duplicate variant was found.
+        //
+        // This linear-time verification step means the whole variant
+        // subtag checking is potentially quadratic, but we're okay doing
+        // that because language tags are unlikely to be deliberately
+        // pathological.
+        if (callFunction(ArrayIndexOf, variants, variant) !== -1)
+            return null;
+        _DefineDataProperty(variants, variants.length, variant);
+
+        if (!nextToken())
+            return null;
+    }
 
-            if (!nextToken())
-                return null;
-        }
+    // extension = singleton 1*("-" (2*8alphanum))
+    // singleton = DIGIT            ; 0 - 9
+    //           / %x41-57          ; A - W
+    //           / %x59-5A          ; Y - Z
+    //           / %x61-77          ; a - w
+    //           / %x79-7A          ; y - z
+    var seenSingletons = [];
+    while (tokenLength === 1) {
+        var extensionStart = tokenStart;
+        var singleton = tokenStartCodeUnitLower();
+        if (singleton === LOWER_X)
+            break;
 
-        // region = 2ALPHA              ; ISO 3166-1 code
-        //        / 3DIGIT              ; UN M.49 code
-        if ((tokenLength === 2 && token === ALPHA) || (tokenLength === 3 && token === DIGIT)) {
-            region = tokenStringLower();
+        // Language tags are case insensitive (RFC 5646 section 2.1.1).
+        // Ensure |tokenStartCodeUnitLower()| does not return the code
+        // unit of an upper-case character, so we can properly detect and
+        // reject language tags with different case, e.g. "en-u-foo-U-foo".
+        assert(!(UPPER_A <= singleton && singleton <= UPPER_Z),
+               "unexpected upper-case code unit");
 
-            // Region codes need to be in upper-case. "bu" -> "BU"
-            region = callFunction(std_String_toUpperCase, region);
+        // Reject the input if a duplicate singleton was found.
+        //
+        // Similar to the variant validation step this check is O(n**2),
+        // but given that there are only 35 possible singletons the
+        // quadratic runtime is negligible.
+        if (callFunction(ArrayIndexOf, seenSingletons, singleton) !== -1)
+            return null;
+        _DefineDataProperty(seenSingletons, seenSingletons.length, singleton);
 
+        if (!nextToken())
+            return null;
+
+        if (!(2 <= tokenLength && tokenLength <= 8))
+            return null;
+        do {
             if (!nextToken())
                 return null;
-        }
-
-        // variant = 5*8alphanum        ; registered variants
-        //         / (DIGIT 3alphanum)
-        //
-        // RFC 5646 section 2.1
-        // alphanum = (ALPHA / DIGIT)   ; letters and numbers
-        while ((5 <= tokenLength && tokenLength <= 8) ||
-               (tokenLength === 4 && tokenStartCodeUnitLower() <= DIGIT_NINE))
-        {
-            assert(!(tokenStartCodeUnitLower() <= DIGIT_NINE) ||
-                   tokenStartCodeUnitLower() >= DIGIT_ZERO,
-                   "token-start-code-unit <= '9' implies token-start-code-unit is in '0'..'9'");
-
-            // Language tags are case insensitive (RFC 5646 section 2.1.1).
-            // All seen variants are compared ignoring case differences by
-            // using the lower-case form. This allows to properly detect and
-            // reject variant repetitions with differing case, e.g.
-            // "en-variant-Variant".
-            var variant = tokenStringLower();
-
-            // Reject the language tag if a duplicate variant was found.
-            //
-            // This linear-time verification step means the whole variant
-            // subtag checking is potentially quadratic, but we're okay doing
-            // that because language tags are unlikely to be deliberately
-            // pathological.
-            if (callFunction(ArrayIndexOf, variants, variant) !== -1)
-                return null;
-            _DefineDataProperty(variants, variants.length, variant);
-
-            if (!nextToken())
-                return null;
-        }
+        } while (2 <= tokenLength && tokenLength <= 8);
 
-        // extension = singleton 1*("-" (2*8alphanum))
-        // singleton = DIGIT            ; 0 - 9
-        //           / %x41-57          ; A - W
-        //           / %x59-5A          ; Y - Z
-        //           / %x61-77          ; a - w
-        //           / %x79-7A          ; y - z
-        var seenSingletons = [];
-        while (tokenLength === 1) {
-            var extensionStart = tokenStart;
-            var singleton = tokenStartCodeUnitLower();
-            if (singleton === LOWER_X)
-                break;
-
-            // Language tags are case insensitive (RFC 5646 section 2.1.1).
-            // Ensure |tokenStartCodeUnitLower()| does not return the code
-            // unit of an upper-case character, so we can properly detect and
-            // reject language tags with different case, e.g. "en-u-foo-U-foo".
-            assert(!(UPPER_A <= singleton && singleton <= UPPER_Z),
-                   "unexpected upper-case code unit");
-
-            // Reject the input if a duplicate singleton was found.
-            //
-            // Similar to the variant validation step this check is O(n**2),
-            // but given that there are only 35 possible singletons the
-            // quadratic runtime is negligible.
-            if (callFunction(ArrayIndexOf, seenSingletons, singleton) !== -1)
-                return null;
-            _DefineDataProperty(seenSingletons, seenSingletons.length, singleton);
-
-            if (!nextToken())
-                return null;
-
-            if (!(2 <= tokenLength && tokenLength <= 8))
-                return null;
-            do {
-                if (!nextToken())
-                    return null;
-            } while (2 <= tokenLength && tokenLength <= 8);
-
-            var extension = Substring(localeLowercase, extensionStart,
-                                      (tokenStart - 1 - extensionStart));
-            _DefineDataProperty(extensions, extensions.length, extension);
-        }
+        var extension = Substring(localeLowercase, extensionStart,
+                                  (tokenStart - 1 - extensionStart));
+        _DefineDataProperty(extensions, extensions.length, extension);
     }
 
-    // Either trailing privateuse component of the langtag production or
-    // standalone privateuse tag.
+    // Trailing privateuse component of the langtag production.
     //
     // privateuse = "x" 1*("-" (1*8alphanum))
     if (tokenLength === 1 && tokenStartCodeUnitLower() === LOWER_X) {
         var privateuseStart = tokenStart;
         if (!nextToken())
             return null;
 
         if (!(1 <= tokenLength && tokenLength <= 8))
@@ -444,18 +427,17 @@ function parseLanguageTag(locale) {
     if (hasOwn(localeLowercase, grandfatheredMappings)) {
         return {
             locale: grandfatheredMappings[localeLowercase],
             grandfathered: true,
         };
     }
 
     // Return if the complete input was successfully parsed and it is not a
-    // regular grandfathered language tag. That means it is either a langtag
-    // or privateuse-only language tag
+    // regular grandfathered language tag.
     return {
         language,
         script,
         region,
         variants,
         extensions,
         privateuse,
     };
@@ -517,22 +499,16 @@ function CanonicalizeLanguageTagFromObje
         language,
         script,
         region,
         variants,
         extensions,
         privateuse,
     } = localeObj;
 
-    // Be careful of a Language-Tag that is entirely privateuse.
-    if (!language) {
-        assert(typeof privateuse === "string", "language or privateuse subtag required");
-        return privateuse;
-    }
-
     // Replace deprecated language tags with their preferred values.
     // "in" -> "id"
     if (hasOwn(language, languageMappings))
         language = languageMappings[language];
 
     var canonical = language;
 
     // No script replacements are currently present, so append as is.
@@ -790,23 +766,21 @@ function IsASCIIAlphaString(s) {
 
 /**
  * Validates and canonicalizes the given language tag.
  */
 function ValidateAndCanonicalizeLanguageTag(locale) {
     assert(typeof locale === "string", "ValidateAndCanonicalizeLanguageTag");
 
     // Handle the common case (a standalone language) first.
-    // Only the following BCP47 subset is accepted:
-    //   Language-Tag  = langtag
-    //   langtag       = language
-    //   language      = 2*3ALPHA ; shortest ISO 639 code
-    // For three character long strings we need to make sure it's not a
-    // private use only language tag, for example "x-x".
-    if (locale.length === 2 || (locale.length === 3 && locale[1] !== "-")) {
+    // Only the following Unicode BCP 47 locale identifier subset is accepted:
+    //   unicode_locale_id = unicode_language_id
+    //   unicode_language_id = unicode_language_subtag
+    //   unicode_language_subtag = alpha{2,3}
+    if (locale.length === 2 || locale.length === 3) {
         if (!IsASCIIAlphaString(locale))
             ThrowRangeError(JSMSG_INVALID_LANGUAGE_TAG, locale);
         assert(IsStructurallyValidLanguageTag(locale), "2*3ALPHA is a valid language tag");
 
         // The language subtag is canonicalized to lower case.
         locale = callFunction(std_String_toLowerCase, locale);
 
         // updateLangTagMappings doesn't modify tags containing only
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -725,8 +725,11 @@ skip script test262/intl402/language-tag
 skip script test262/intl402/Intl/getCanonicalLocales/canonicalized-tags.js
 
 # Irregular grandfathered tags no longer allowed: "i-klingon"
 skip script test262/intl402/language-tags-valid.js
 
 # Irregular grandfathered tags no longer allowed: "en-GB-oed"
 skip script test262/intl402/Intl/getCanonicalLocales/preferred-grandfathered.js
 skip script test262/intl402/RelativeTimeFormat/constructor/constructor/locales-valid.js
+
+# Privateuse-only tags no longer allowed: "x-u-foo"
+skip script test262/intl402/Intl/getCanonicalLocales/weird-cases.js
--- a/js/src/tests/non262/Intl/NumberFormat/duplicate-singleton-variant.js
+++ b/js/src/tests/non262/Intl/NumberFormat/duplicate-singleton-variant.js
@@ -29,19 +29,19 @@ var badLocales =
    "de-DE-u-kn-true-U-kn-true",
    "ar-u-foo-q-bar-u-baz",
    "ar-z-moo-u-foo-q-bar-z-eit-u-baz",
   ];
 
 for (var locale of badLocales)
   checkInvalidLocale(locale);
 
-// Fully-privateuse locales are okay.
+// Fully-privateuse locales are rejected.
 for (var locale of badLocales)
-  new Intl.NumberFormat("x-" + locale).format(5);
+  assertThrowsInstanceOf(() => new Intl.NumberFormat("x-" + locale), RangeError);
 
 // Locales with trailing privateuse also okay.
 for (var locale of badLocales)
 {
   new Intl.NumberFormat("en-x-" + locale).format(5);
   new Intl.NumberFormat("en-u-foo-x-u-" + locale).format(5);
 }
 
--- a/js/src/tests/non262/Intl/NumberFormat/remove-unicode-extensions.js
+++ b/js/src/tests/non262/Intl/NumberFormat/remove-unicode-extensions.js
@@ -5,20 +5,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Locale processing is supposed to internally remove any Unicode extension
 // sequences in the locale.  Test that various weird testcases invoking
 // algorithmic edge cases don't assert or throw exceptions.
 
 var weirdCases =
   [
-   "x-u-foo",
    "en-x-u-foo",
    "en-a-bar-x-u-foo",
    "en-x-u-foo-a-bar",
    "en-a-bar-u-baz-x-u-foo",
   ];
 
 for (var locale of weirdCases)
   Intl.NumberFormat(locale).format(5);
 
+assertThrowsInstanceOf(() => Intl.NumberFormat("x-u-foo"), RangeError);
+
 if (typeof reportCompare === "function")
   reportCompare(true, true);
--- a/js/src/tests/non262/Intl/String/toLocaleLowerCase.js
+++ b/js/src/tests/non262/Intl/String/toLocaleLowerCase.js
@@ -19,21 +19,16 @@ assertEq("\u0130".toLocaleLowerCase(["tr
 for (let locale of ["no_locale", "tr-invalid_ext", ["no_locale"], ["en", "no_locale"]]) {
     // Empty input string.
     assertThrowsInstanceOf(() => "".toLocaleLowerCase(locale), RangeError);
 
     // Non-empty input string.
     assertThrowsInstanceOf(() => "x".toLocaleLowerCase(locale), RangeError);
 }
 
-// The language tag fast-path for String.prototype.toLocaleLowerCase doesn't
-// trip up on three element private-use only language tags.
-assertEq("A".toLocaleLowerCase("x-x"), "a");
-assertEq("A".toLocaleLowerCase("x-0"), "a");
-
 // No locale argument, undefined as locale, and empty array or array-like all
 // return the same result. Testing with "a/A" because it has only simple case
 // mappings.
 assertEq("A".toLocaleLowerCase(), "a");
 assertEq("A".toLocaleLowerCase(undefined), "a");
 assertEq("A".toLocaleLowerCase([]), "a");
 assertEq("A".toLocaleLowerCase({}), "a");
 assertEq("A".toLocaleLowerCase({length: 0}), "a");
--- a/js/src/tests/non262/Intl/String/toLocaleUpperCase.js
+++ b/js/src/tests/non262/Intl/String/toLocaleUpperCase.js
@@ -19,21 +19,16 @@ assertEq("i\u0307".toLocaleUpperCase(["l
 for (let locale of ["no_locale", "lt-invalid_ext", ["no_locale"], ["en", "no_locale"]]) {
     // Empty input string.
     assertThrowsInstanceOf(() => "".toLocaleUpperCase(locale), RangeError);
 
     // Non-empty input string.
     assertThrowsInstanceOf(() => "a".toLocaleUpperCase(locale), RangeError);
 }
 
-// The language tag fast-path for String.prototype.toLocaleUpperCase doesn't
-// trip up on three element private-use only language tags.
-assertEq("a".toLocaleUpperCase("x-x"), "A");
-assertEq("a".toLocaleUpperCase("x-0"), "A");
-
 // No locale argument, undefined as locale, and empty array or array-like all
 // return the same result. Testing with "a/A" because it has only simple case
 // mappings.
 assertEq("a".toLocaleUpperCase(), "A");
 assertEq("a".toLocaleUpperCase(undefined), "A");
 assertEq("a".toLocaleUpperCase([]), "A");
 assertEq("a".toLocaleUpperCase({}), "A");
 assertEq("a".toLocaleUpperCase({length: 0}), "A");
--- a/js/src/tests/non262/Intl/extensions/unicode-extension-sequences.js
+++ b/js/src/tests/non262/Intl/extensions/unicode-extension-sequences.js
@@ -6,21 +6,16 @@ const endOfUnicodeExtensions = getSelfHo
 const testcases = [
     // Language tag without Unicode extension.
     { locale: "en", start: -1, end: 0 },
     { locale: "en-Latn", start: -1, end: 0 },
     { locale: "en-x-y", start: -1, end: 0 },
     { locale: "en-x-yz", start: -1, end: 0 },
     { locale: "en-x-u-kf", start: -1, end: 0 },
 
-    // Privateuse only language tag.
-    { locale: "x-only", start: -1, end: 0 },
-    { locale: "x-only-u", start: -1, end: 0 },
-    { locale: "x-only-u-kf", start: -1, end: 0 },
-
     // Unicode extension sequence starts with key subtag.
     // - no suceeding key or type subtags.
     { locale: "en-u-ab", start: 2, end: 7 },
     { locale: "en-u-ab-x-y", start: 2, end: 7 },
     { locale: "en-u-ab-x-yz", start: 2, end: 7 },
     { locale: "en-u-ab-x-u-kn", start: 2, end: 7 },
     // - followed by key subtag.
     { locale: "en-u-ab-cd", start: 2, end: 10 },
--- a/js/src/tests/non262/Intl/getCanonicalLocales-weird-cases.js
+++ b/js/src/tests/non262/Intl/getCanonicalLocales-weird-cases.js
@@ -4,21 +4,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Locale processing is supposed to internally remove any Unicode extension
 // sequences in the locale.  Test that various weird testcases invoking
 // algorithmic edge cases don't assert or throw exceptions.
 
 var weirdCases =
   [
-   "x-u-foo",
    "en-x-u-foo",
    "en-a-bar-x-u-foo",
    "en-x-u-foo-a-bar",
    "en-a-bar-u-baz-x-u-foo",
   ];
 
 for (let weird of weirdCases)
   assertEqArray(Intl.getCanonicalLocales(weird), [weird]);
 
+assertThrowsInstanceOf(() => Intl.getCanonicalLocales("x-u-foo"), RangeError);
+
 if (typeof reportCompare === 'function')
     reportCompare(0, 0);
 
deleted file mode 100644
--- a/js/src/tests/non262/Intl/uppercase-privateuse.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// |reftest| skip-if(!this.hasOwnProperty("Intl"))
-
-// privateuse subtags can start with upper-case 'X'.
-assertEqArray(Intl.getCanonicalLocales("de-X-a-a"), ["de-x-a-a"]);
-assertEqArray(Intl.getCanonicalLocales("X-a-a"), ["x-a-a"]);
-
-if (typeof reportCompare === 'function')
-    reportCompare(0, 0);