Bug 1429610 Add internal api to look up available language packs draft
authorAndrew Swan <aswan@mozilla.com>
Mon, 14 May 2018 10:18:32 -0700
changeset 795497 ca539f07691c6e243bcb0f4a3137d0e0940ab2a0
parent 794191 4303d49c53931385892231969e40048f096b4d4c
push id109999
push useraswan@mozilla.com
push dateTue, 15 May 2018 22:37:48 +0000
bugs1429610
milestone62.0a1
Bug 1429610 Add internal api to look up available language packs MozReview-Commit-ID: ISIu9mDIvbP
browser/app/profile/firefox.js
mobile/android/app/mobile.js
toolkit/mozapps/extensions/internal/AddonRepository.jsm
toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_langpacks.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -44,16 +44,17 @@ pref("extensions.webextOptionalPermissio
 // Preferences for AMO integration
 pref("extensions.getAddons.cache.enabled", true);
 pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/api/v3/addons/search/?guid=%IDS%&lang=%LOCALE%");
 pref("extensions.getAddons.compatOverides.url", "https://services.addons.mozilla.org/api/v3/addons/compat-override/?guid=%IDS%&lang=%LOCALE%");
 pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%&platform=%OS%&appver=%VERSION%");
 pref("extensions.webservice.discoverURL", "https://discovery.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%/%COMPATIBILITY_MODE%");
 pref("extensions.getAddons.link.url", "https://addons.mozilla.org/%LOCALE%/firefox/");
 pref("extensions.getAddons.themes.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/themes/?src=firefox");
+pref("extensions.getAddons.langpacks.url", "https://services.addons.mozilla.org/api/v3/addons/language-tools/?app=firefox&type=language&appversion=%VERSION%");
 
 pref("extensions.update.autoUpdateDefault", true);
 
 // Check AUS for system add-on updates.
 pref("extensions.systemAddon.update.url", "https://aus5.mozilla.org/update/3/SystemAddons/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
 
 // Disable add-ons that are not installed by the user in all scopes by default.
 // See the SCOPE constants in AddonManager.jsm for values to use here.
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -207,16 +207,17 @@ pref("extensions.update.url", "https://v
 pref("extensions.update.background.url", "https://versioncheck-bg.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
 
 /* preferences for the Get Add-ons pane */
 pref("extensions.getAddons.cache.enabled", true);
 pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/android/search?q=%TERMS%&platform=%OS%&appver=%VERSION%");
 pref("extensions.getAddons.browseAddons", "https://addons.mozilla.org/%LOCALE%/android/");
 pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/api/v3/addons/search/?guid=%IDS%&lang=%LOCALE%");
 pref("extensions.getAddons.compatOverides.url", "https://services.addons.mozilla.org/api/v3/addons/compat-override/?guid=%IDS%&lang=%LOCALE%");
+pref("extensions.getAddons.langpacks.url", "https://services.addons.mozilla.org/api/v3/addons/language-tools/?app=android&type=language&appversion=%VERSION%");
 
 /* preference for the locale picker */
 pref("extensions.getLocales.get.url", "");
 pref("extensions.compatability.locales.buildid", "0");
 
 /* Don't let XPIProvider install distribution add-ons; we do our own thing on mobile. */
 pref("extensions.installDistroAddons", false);
 
--- a/toolkit/mozapps/extensions/internal/AddonRepository.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonRepository.jsm
@@ -15,24 +15,27 @@ XPCOMUtils.defineLazyModuleGetters(this,
   ServiceRequest: "resource://gre/modules/ServiceRequest.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   Preferences: "resource://gre/modules/Preferences.jsm",
 });
 
 var EXPORTED_SYMBOLS = [ "AddonRepository" ];
 
+Cu.importGlobalProperties(["fetch"]);
+
 const PREF_GETADDONS_CACHE_ENABLED       = "extensions.getAddons.cache.enabled";
 const PREF_GETADDONS_CACHE_TYPES         = "extensions.getAddons.cache.types";
 const PREF_GETADDONS_CACHE_ID_ENABLED    = "extensions.%ID%.getAddons.cache.enabled";
 const PREF_GETADDONS_BROWSEADDONS        = "extensions.getAddons.browseAddons";
 const PREF_GETADDONS_BYIDS               = "extensions.getAddons.get.url";
 const PREF_COMPAT_OVERRIDES              = "extensions.getAddons.compatOverides.url";
 const PREF_GETADDONS_BROWSESEARCHRESULTS = "extensions.getAddons.search.browseURL";
 const PREF_GETADDONS_DB_SCHEMA           = "extensions.getAddons.databaseSchema";
+const PREF_GET_LANGPACKS                 = "extensions.getAddons.langpacks.url";
 
 const PREF_METADATA_LASTUPDATE           = "extensions.getAddons.cache.lastUpdate";
 const PREF_METADATA_UPDATETHRESHOLD_SEC  = "extensions.getAddons.cache.updateThreshold";
 const DEFAULT_METADATA_UPDATETHRESHOLD_SEC = 172800; // two days
 
 const DEFAULT_CACHE_TYPES = "extension,theme,locale,dictionary";
 
 const FILE_DATABASE         = "addons.json";
@@ -713,17 +716,17 @@ var AddonRepository = {
       }
       compat.compatRanges.push(override);
     }
 
     return compat;
   },
 
   // Create url from preference, returning null if preference does not exist
-  _formatURLPref(aPreference, aSubstitutions) {
+  _formatURLPref(aPreference, aSubstitutions = {}) {
     let url = Services.prefs.getCharPref(aPreference, "");
     if (!url) {
       logger.warn("_formatURLPref: Couldn't get pref: " + aPreference);
       return null;
     }
 
     url = url.replace(/%([A-Z_]+)%/g, function(aMatch, aKey) {
       return (aKey in aSubstitutions) ? encodeURIComponent(aSubstitutions[aKey])
@@ -754,16 +757,49 @@ var AddonRepository = {
       }
     }
     return null;
   },
 
   flush() {
     return AddonDatabase.flush();
   },
+
+  async getAvailableLangpacks() {
+    // This should be the API endpoint documented at:
+    // http://addons-server.readthedocs.io/en/latest/topics/api/addons.html#language-tools
+    let url = this._formatURLPref(PREF_GET_LANGPACKS);
+
+    let response = await fetch(url);
+    if (!response.ok) {
+      throw new Error("fetching available language packs failed");
+    }
+
+    let data = await response.json();
+
+    let result = [];
+    for (let entry of data.results) {
+      if (!entry.current_compatible_version ||
+          !entry.current_compatible_version.files) {
+         continue;
+      }
+
+      for (let file of entry.current_compatible_version.files) {
+        if (file.platform == "all" || file.platform == Services.appinfo.OS.toLowerCase()) {
+          result.push({
+            target_locale: entry.target_locale,
+            url: file.url,
+            hash: file.hash,
+          });
+        }
+      }
+    }
+
+    return result;
+  },
 };
 
 var AddonDatabase = {
   connectionPromise: null,
   _loaded: false,
   _saveTask: null,
   _blockerAdded: false,
 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_langpacks.js
@@ -0,0 +1,95 @@
+ChromeUtils.import("resource://gre/modules/addons/AddonRepository.jsm");
+
+const PREF_GET_LANGPACKS = "extensions.getAddons.langpacks.url";
+
+let server = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
+
+add_task(async function setup() {
+  Services.prefs.setStringPref(PREF_GET_LANGPACKS, "http://example.com/langpacks.json");
+
+  function setData(data) {
+    if (typeof data != "string") {
+      data = JSON.stringify(data);
+    }
+
+    server.registerPathHandler("/langpacks.json", (request, response) => {
+      response.setHeader("content-type", "application/json");
+      response.write(data);
+    });
+  }
+
+  const EXPECTED = [
+    {
+      target_locale: "kl",
+      url: "http://example.com/langpack1.xpi",
+      hash: "sha256:0123456789abcdef",
+    },
+    {
+      target_locale: "fo",
+      url: "http://example.com/langpack2.xpi",
+      hash: "sha256:fedcba9876543210",
+    },
+  ];
+
+  setData({
+    results: [
+      // A simple entry
+      {
+        target_locale: EXPECTED[0].target_locale,
+        current_compatible_version: {
+          files: [
+            {
+              platform: "all",
+              url: EXPECTED[0].url,
+              hash: EXPECTED[0].hash,
+            },
+          ],
+        },
+      },
+
+      // An entry with multiple supported platforms
+      {
+        target_locale: EXPECTED[1].target_locale,
+        current_compatible_version: {
+          files: [
+            {
+              platform: "somethingelse",
+              url: "http://example.com/bogus.xpi",
+              hash: "sha256:abcd",
+            },
+            {
+              platform: Services.appinfo.OS.toLowerCase(),
+              url: EXPECTED[1].url,
+              hash: EXPECTED[1].hash,
+            },
+          ],
+        },
+      },
+
+      // An entry with no matching platform
+      {
+        target_locale: "bla",
+        current_compatible_version: {
+          files: [
+            {
+              platform: "unsupportedplatform",
+              url: "http://example.com/bogus2.xpi",
+              hash: "sha256:1234",
+            },
+          ],
+        },
+      },
+    ]
+  });
+
+  let result = await AddonRepository.getAvailableLangpacks();
+  equal(result.length, 2, "Got 2 results");
+
+  deepEqual(result[0], EXPECTED[0], "Got expected result for simple entry");
+  deepEqual(result[1], EXPECTED[1], "Got expected result for multi-platform entry");
+
+  setData("not valid json");
+  Assert.rejects(AddonRepository.getAvailableLangpacks(),
+                 /SyntaxError/, "Got parse error on invalid JSON");
+
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -6,16 +6,17 @@ firefox-appdir = browser
 dupe-manifest =
 support-files =
   data/**
 
 [test_AddonRepository.js]
 [test_AddonRepository_cache.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
+[test_AddonRepository_langpacks.js]
 [test_AddonRepository_paging.js]
 [test_LightweightThemeManager.js]
 [test_ProductAddonChecker.js]
 [test_XPIStates.js]
 [test_XPIcancel.js]
 [test_addonStartup.js]
 [test_asyncBlocklistLoad.js]
 tags = blocklist