author Zibi Braniecki <>
Thu, 14 Sep 2017 15:21:33 -0700
changeset 424146 48109fce6741a2448a9d71f48deccbd72f732003
parent 416020 1075b94a522dbadf874f224312fa542d3fecdc9d
child 426158 58f678547059f27e2dd930b7c9fa4b7bf44365e2
permissions -rw-r--r--
Bug 1400006 - Extend language negotiation in LocaleService to support looking for the best likelySubtag for the locale with region stripped. r=Pike, a=lizzard Add additional logic to our language negotation to do apply likelySubtags when a direct match is not available. Currently, if the user specifies the locale with region, and we do not have a direct for that region, we pick all locales for the same language and other regions in no order. The example of where it returns suboptimal results: 1) Requested locale "en-CA" 2) Available locales ["en-ZA", "en-GB", "en-US"] 3) Negotiated locales ["en-ZA", "en-GB", "en-US"] This would not happen, if the user requested a generic "de", "en" etc.: 1) Requested locale "en" 2) Available locales ["en-ZA", "en-GB", "en-US"] 3) Negotiated locales ["en-US", "en-ZA", "en-GB"] because after not finding a direct match, we would use likelySubtags to extend "en" to "en-Latn-US" and then find the priority match in "en-US". This patch extends this logic to "en-US" or "de-LU" by adding a step which strips the region tag and then applies likelySubtag on the result. This means that in absence of direct match the following fallbacks would happen: "de-LU" -> "de-DE" "es-CL" -> "es-ES" "en-CA" -> "en-US" This does not affect languages that use multiple scripts, so ar, sr and zh are not affected. MozReview-Commit-ID: BR1WrgXSf6a

<!DOCTYPE html>
  Any copyright is dedicated to the Public Domain.


    <meta charset="utf8">

    <script type="application/javascript"
    <link rel="stylesheet" type="text/css"


    <script type="application/javascript">
      "use strict";

      const { utils: Cu } = Components;
      const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
      const promise = require("promise");
      const EventEmitter = require("devtools/shared/event-emitter");
      const { Task } = require("devtools/shared/task");



          .catch(ok.bind(null, false))

      function testEmitter(aObject) {
        let emitter;

        if (aObject) {
          emitter = aObject;
        } else {
          emitter = new EventEmitter();

        ok(emitter, "We have an event emitter");

        let beenHere1 = false;
        let beenHere2 = false;

        emitter.on("next", next);
        emitter.emit("next", "abc", "def");

        function next(eventName, str1, str2) {
          is(eventName, "next", "Got event");
          is(str1, "abc", "Argument 1 is correct");
          is(str2, "def", "Argument 2 is correct");

          ok(!beenHere1, "first time in next callback");
          beenHere1 = true;

"next", next);


          emitter.once("onlyonce", onlyOnce);


        function onlyOnce() {
          ok(!beenHere2, "\"once\" listener has been called once");
          beenHere2 = true;


        function testThrowingExceptionInListener() {
          function throwListener() {
            throw {
              toString: () => "foo",
              stack: "bar",

          emitter.on("throw-exception", throwListener);


        function killItWhileEmitting() {
          function c1() {
            ok(true, "c1 called");
          function c2() {
            ok(true, "c2 called");
  "tick", c3);
          function c3() {
            ok(false, "c3 should not be called");
          function c4() {
            ok(true, "c4 called");

          emitter.on("tick", c1);
          emitter.on("tick", c2);
          emitter.on("tick", c3);
          emitter.on("tick", c4);



        function offAfterOnce() {
          let enteredC1 = false;

          function c1() {
            enteredC1 = true;

          emitter.once("oao", c1);
"oao", c1);


          ok(!enteredC1, "c1 should not be called");

      function testPromise() {
        let emitter = new EventEmitter();
        let p = emitter.once("thing");

        // Check that the promise is only resolved once event though we
        // emit("thing") more than once
        let firstCallbackCalled = false;
        let check1 = p.then(arg => {
          is(firstCallbackCalled, false, "first callback called only once");
          firstCallbackCalled = true;
          is(arg, "happened", "correct arg in promise");
          return "rval from c1";

        emitter.emit("thing", "happened", "ignored");

        // Check that the promise is resolved asynchronously
        let secondCallbackCalled = false;
        let check2 = p.then(arg => {
          ok(true, "second callback called");
          is(arg, "happened", "correct arg in promise");
          secondCallbackCalled = true;
          is(arg, "happened", "correct arg in promise (a second time)");
          return "rval from c2";

        // Shouldn't call any of the above listeners
        emitter.emit("thing", "trashinate");

        // Check that we can still separate events with different names
        // and that it works with no parameters
        let pfoo = emitter.once("foo");
        let pbar = emitter.once("bar");

        let check3 = pfoo.then(arg => {
          ok(arg === undefined, "no arg for foo event");
          return "rval from c3";

        pbar.then(() => {
          ok(false, "pbar should not be called");


        is(secondCallbackCalled, false, "second callback not called yet");

        return promise.all([ check1, check2, check3 ]).then(args => {
          is(args[0], "rval from c1", "callback 1 done good");
          is(args[1], "rval from c2", "callback 2 done good");
          is(args[2], "rval from c3", "callback 3 done good");