Bug 1246754 - Complete the implementation of chrome.i18n.detectLanguage, r?kmag draft
authorbsilverberg <bsilverberg@mozilla.com>
Tue, 23 Feb 2016 22:01:11 -0500
changeset 334010 b76f8d404bd486ada2d7c968af1e6be62a3b602f
parent 332881 789a12291942763bc1e3a89f97e0b82dc1c9d00b
child 514797 695d72bdc3cb269373b9bc0d9e57ae0b33df0c01
push id11421
push userbmo:bob.silverberg@gmail.com
push dateWed, 24 Feb 2016 03:02:38 +0000
reviewerskmag
bugs1246754
milestone47.0a1
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>