Bug 1246754 - Complete the implementation of chrome.i18n.detectLanguage, r=kmag
authorbsilverberg <bsilverberg@mozilla.com>
Tue, 23 Feb 2016 22:01:11 -0500
changeset 321653 35b8118cf841fcfb22823cc94db2669e4432dda7
parent 321652 49cd9160c34bdda3d494521867b53d91a9bdf874
child 321654 3474b34808f788723370a0d9b99e959d21cccce9
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1246754
milestone47.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 1246754 - Complete the implementation of chrome.i18n.detectLanguage, r=kmag MozReview-Commit-ID: 7cvJj0QP5XO
toolkit/components/extensions/ExtensionContent.jsm
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/ext-i18n.js
toolkit/components/extensions/schemas/i18n.json
toolkit/components/extensions/test/mochitest/test_ext_i18n.html
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -41,16 +41,17 @@ Cu.import("resource://gre/modules/Extens
 var {
   runSafeSyncWithoutClone,
   BaseContext,
   LocaleData,
   MessageBroker,
   Messenger,
   injectAPI,
   flushJarCache,
+  detectLanguage,
 } = ExtensionUtils;
 
 function isWhenBeforeOrSame(when1, when2) {
   let table = {"document_start": 0,
                "document_end": 1,
                "document_idle": 2};
   return table[when1] <= table[when2];
 }
@@ -117,16 +118,21 @@ var api = context => {
     i18n: {
       getMessage: function(messageName, substitutions) {
         return context.extension.localizeMessage(messageName, substitutions);
       },
 
       getUILanguage: function() {
         return context.extension.localeData.uiLocale;
       },
+
+      detectLanguage: function(text, callback) {
+        let result = detectLanguage(text);
+        return context.wrapPromise(result, callback);
+      },
     },
   };
 };
 
 // Represents a content script.
 function Script(options, deferred = PromiseUtils.defer()) {
   this.options = options;
   this.run_at = this.options.run_at;
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -11,17 +11,18 @@ const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
-
+XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
+                                  "resource:///modules/translation/LanguageDetector.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Locale",
                                   "resource://gre/modules/Locale.jsm");
 
 function filterStack(error) {
   return String(error.stack).replace(/(^.*(Task\.jsm|Promise-backend\.js).*\n)+/gm, "<Promise Chain>\n");
 }
 
 // Run a function and report exceptions.
@@ -428,17 +429,16 @@ LocaleData.prototype = {
     return result;
   },
 
   get uiLocale() {
     // Return the browser locale, but convert it to a Chrome-style
     // locale code.
     return Locale.getLocale().replace(/-/g, "_");
   },
-
 };
 
 // This is a generic class for managing event listeners. Example usage:
 //
 // new EventManager(context, "api.subAPI", fire => {
 //   let listener = (...) => {
 //     // Fire any listeners registered with addListener.
 //     fire(arg1, arg2);
@@ -956,16 +956,28 @@ const PlatformInfo = Object.freeze({
       arch = "x86-32";
     } else if (arch == "x86_64") {
       arch = "x86-64";
     }
     return arch;
   })(),
 });
 
+function detectLanguage(text) {
+  return LanguageDetector.detectLanguage(text).then(result => ({
+    isReliable: result.confident,
+    languages: result.languages.map(lang => {
+      return {
+        language: lang.languageCode,
+        percentage: lang.percent,
+      };
+    }),
+  }));
+}
+
 this.ExtensionUtils = {
   runSafeWithoutClone,
   runSafeSyncWithoutClone,
   runSafe,
   runSafeSync,
   BaseContext,
   DefaultWeakMap,
   EventManager,
@@ -975,9 +987,10 @@ this.ExtensionUtils = {
   injectAPI,
   MessageBroker,
   Messenger,
   PlatformInfo,
   SpreadArgs,
   extend,
   flushJarCache,
   instanceOf,
+  detectLanguage,
 };
--- a/toolkit/components/extensions/ext-i18n.js
+++ b/toolkit/components/extensions/ext-i18n.js
@@ -1,15 +1,24 @@
 "use strict";
 
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+var {
+  detectLanguage,
+} = ExtensionUtils;
+
 extensions.registerSchemaAPI("i18n", null, (extension, context) => {
   return {
     i18n: {
       getMessage: function(messageName, substitutions) {
         return extension.localizeMessage(messageName, substitutions);
       },
 
       getUILanguage: function() {
         return extension.localeData.uiLocale;
       },
+
+      detectLanguage: function(text) {
+        return detectLanguage(text);
+      },
     },
   };
 });
--- a/toolkit/components/extensions/schemas/i18n.json
+++ b/toolkit/components/extensions/schemas/i18n.json
@@ -73,17 +73,16 @@
         "parameters": [],
         "returns": {
           "type": "string",
           "description": "The browser UI language code such as en-US or fr-FR."
         }
       },
       {
         "name": "detectLanguage",
-        "unsupported": true,
         "type": "function",
         "description": "Detects the language of the provided text using CLD.",
         "async": "callback",
         "parameters": [
           {
             "type": "string",
             "name": "text",
             "description": "User input string to be translated."
--- a/toolkit/components/extensions/test/mochitest/test_ext_i18n.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_i18n.html
@@ -237,12 +237,125 @@ add_task(function* test_get_ui_language(
   extension.sendMessage(["expect-results", "he"]);
   yield extension.awaitMessage("done");
 
   win.close();
 
   yield extension.unload();
 });
 
+
+add_task(function* test_detect_language() {
+  const af_string = " aam skukuza die naam beteken hy wat skoonvee of hy wat alles onderstebo keer wysig " +
+    "bosveldkampe boskampe is kleiner afgeleë ruskampe wat oor min fasiliteite beskik daar is geen restaurante " +
+    "of winkels nie en slegs oornagbesoekers word toegelaat bateleur";
+  // String with intermixed French/English text
+  const fr_en_string = "France is the largest country in Western Europe and the third-largest in Europe as a whole. " +
+    "A accès aux chiens et aux frontaux qui lui ont été il peut consulter et modifier ses collections et exporter " +
+    "Cet article concerne le pays européen aujourd’hui appelé République française. Pour d’autres usages du nom France, " +
+    "Pour une aide rapide et effective, veuiller trouver votre aide dans le menu ci-dessus." +
+    "Motoring events began soon after the construction of the first successful gasoline-fueled automobiles. The quick brown fox jumped over the lazy dog";
+
+  function backgroundScript() {
+    function checkResult(source, result, expected) {
+      browser.test.assertEq(expected.isReliable, result.isReliable, "result.confident is true");
+      browser.test.assertEq(
+        expected.languages.length,
+        result.languages.length,
+        `result.languages contains the expected number of languages in ${source}`);
+      expected.languages.forEach((lang, index) => {
+        browser.test.assertEq(
+          lang.percentage,
+          result.languages[index].percentage,
+          `element ${index} of result.languages array has the expected percentage in ${source}`);
+        browser.test.assertEq(
+          lang.language,
+          result.languages[index].language,
+          `element ${index} of result.languages array has the expected language in ${source}`);
+      });
+    }
+
+    let tabId;
+
+    browser.tabs.query({currentWindow: true, active: true}, tabs => {
+      tabId = tabs[0].id;
+      browser.test.sendMessage("ready");
+    });
+
+    browser.test.onMessage.addListener(([msg, expected]) => {
+      Promise.all([
+        browser.i18n.detectLanguage(msg),
+        new Promise(
+          resolve => browser.tabs.sendMessage(tabId, msg, resolve)),
+      ]).then(([backgroundResults, contentResults]) => {
+        checkResult("background", backgroundResults, expected);
+        checkResult("contentScript", contentResults, expected);
+
+        browser.test.sendMessage("done");
+      });
+    });
+  }
+
+  function content() {
+    browser.runtime.onMessage.addListener((msg, sender, respond) => {
+      browser.i18n.detectLanguage(msg, respond);
+      return true;
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "content_scripts": [{
+        "matches": ["http://mochi.test/*/file_sample.html"],
+        "run_at": "document_start",
+        "js": ["content_script.js"],
+      }],
+    },
+
+    background: `(${backgroundScript})()`,
+
+    files: {
+      "content_script.js": `(${content})()`,
+    },
+  });
+
+  let win = window.open("file_sample.html");
+
+  yield extension.startup();
+  yield extension.awaitMessage("ready");
+
+  let expected = {
+    isReliable: true,
+    languages: [
+      {
+        language: "fr",
+        percentage: 67,
+      },
+      {
+        language: "en",
+        percentage: 32,
+      },
+    ],
+  };
+  extension.sendMessage([fr_en_string, expected]);
+  yield extension.awaitMessage("done");
+
+  expected = {
+    isReliable: true,
+    languages: [
+      {
+        language: "af",
+        percentage: 99,
+      },
+    ],
+  };
+  extension.sendMessage([af_string, expected]);
+  yield extension.awaitMessage("done");
+
+  win.close();
+
+  yield extension.unload();
+});
+
 </script>
 
 </body>
 </html>