Bug 1210478 - Add Meta URL resolution in RemoteNewTabLocation r?mconley draft
authorOlivier Yiptong <olivier@olivieryiptong.com>
Fri, 13 Nov 2015 05:36:21 -0500
changeset 308623 9c65e91ae2cacdbbe2fcf1374bbb5fb9d589f756
parent 308622 cbb1af56e7a6edc1d775a79e01462dd1db9b4aed
child 511193 49bed9e4c97da337f062b0122129a0f12701c7c5
push id7504
push userolivier@olivieryiptong.com
push dateFri, 13 Nov 2015 10:43:35 +0000
reviewersmconley
bugs1210478
milestone45.0a1
Bug 1210478 - Add Meta URL resolution in RemoteNewTabLocation r?mconley
browser/components/newtab/RemoteNewTabLocation.jsm
browser/components/newtab/tests/xpcshell/test_RemoteNewTabLocation.js
browser/components/nsBrowserGlue.js
--- a/browser/components/newtab/RemoteNewTabLocation.jsm
+++ b/browser/components/newtab/RemoteNewTabLocation.jsm
@@ -1,42 +1,141 @@
-/* globals Services */
+/* globals Services, UpdateUtils, XPCOMUtils, URL, NewTabPrefsProvider, Locale */
+/* exported RemoteNewTabLocation */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["RemoteNewTabLocation"];
 
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.importGlobalProperties(["URL"]);
+const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.importGlobalProperties(["URL"]);
+
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+  "resource://gre/modules/UpdateUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
+  "resource:///modules/NewTabPrefsProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Locale",
+  "resource://gre/modules/Locale.jsm");
+
+// The preference that tells whether to match the OS locale
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+
+// The preference that tells what locale the user selected
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
 
-// TODO: will get dynamically set in bug 1210478
-const DEFAULT_PAGE_LOCATION = "https://newtab.cdn.mozilla.net/v0/nightly/en-US/index.html";
+const DEFAULT_PAGE_LOCATION = "https://newtab.cdn.mozilla.net/" +
+                              "v%VERSION%/%CHANNEL%/%LOCALE%/index.html";
+
+const VALID_CHANNELS = new Set(["esr", "release", "beta", "aurora", "nightly"]);
+
+const NEWTAB_VERSION = "0";
 
-this.RemoteNewTabLocation = {
-  _url: new URL(DEFAULT_PAGE_LOCATION),
+let RemoteNewTabLocation = {
+  /*
+   * Generate a default url based on locale and update channel
+   */
+  _generateDefaultURL() {
+    let releaseName = this._releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
+    let uri = DEFAULT_PAGE_LOCATION
+      .replace("%VERSION%", this.version)
+      .replace("%LOCALE%", Locale.getLocale())
+      .replace("%CHANNEL%", releaseName);
+    return new URL(uri);
+  },
+
+  _url: null,
   _overridden: false,
 
   get href() {
     return this._url.href;
   },
 
   get origin() {
     return this._url.origin;
   },
 
   get overridden() {
     return this._overridden;
   },
 
-  override: function(newURL) {
-    this._url = new URL(newURL);
-    this._overridden = true;
-    Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
-      this._url.href);
+  get version() {
+    return NEWTAB_VERSION;
+  },
+
+  get channels() {
+    return VALID_CHANNELS;
+  },
+
+  /**
+   * Returns the release name from an Update Channel name
+   *
+   * @return {String} a release name based on the update channel. Defaults to nightly
+   */
+  _releaseFromUpdateChannel(channel) {
+    let result = "nightly";
+    if (VALID_CHANNELS.has(channel)) {
+      result = channel;
+    }
+    return result;
+  },
+
+  /*
+   * Updates the location when the page is not overriden.
+   * Useful when there is a pref change
+   */
+  _updateMaybe() {
+    if (!this.overridden) {
+      let url = this._generateDefaultURL();
+      if (url.href !== this._url.href) {
+        this._url = url;
+        Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
+          this._url.href);
+      }
+    }
   },
 
-  reset: function() {
-    this._url = new URL(DEFAULT_PAGE_LOCATION);
+  /*
+   * Override the Remote newtab page location.
+   */
+  override(newURL) {
+    let url = new URL(newURL);
+    if (url.href !== this._url.href) {
+      this._overridden = true;
+      this._url = url;
+      Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
+                                   this._url.href);
+    }
+  },
+
+  /*
+   * Reset the newtab page location to the default value
+   */
+  reset() {
+    let url = this._generateDefaultURL();
+    if (url.href !== this._url.href) {
+      this._url = url;
+      this._overridden = false;
+      Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
+        this._url.href);
+    }
+  },
+
+  init() {
+    NewTabPrefsProvider.prefs.on(
+      PREF_SELECTED_LOCALE,
+      this._updateMaybe.bind(this));
+
+    NewTabPrefsProvider.prefs.on(
+      PREF_MATCH_OS_LOCALE,
+      this._updateMaybe.bind(this));
+
+    this._url = this._generateDefaultURL();
+  },
+
+  uninit() {
+    this._url = null;
     this._overridden = false;
-    Services.obs.notifyObservers(null, "remote-new-tab-location-changed",
-      this._url.href);
+    NewTabPrefsProvider.prefs.off(PREF_SELECTED_LOCALE, this._updateMaybe);
+    NewTabPrefsProvider.prefs.off(PREF_MATCH_OS_LOCALE, this._updateMaybe);
   }
 };
--- a/browser/components/newtab/tests/xpcshell/test_RemoteNewTabLocation.js
+++ b/browser/components/newtab/tests/xpcshell/test_RemoteNewTabLocation.js
@@ -1,40 +1,141 @@
-/* globals ok, equal, RemoteNewTabLocation, Services */
+/* globals ok, equal, RemoteNewTabLocation, NewTabPrefsProvider, Services, Preferences */
+/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
 "use strict";
 
 Components.utils.import("resource:///modules/RemoteNewTabLocation.jsm");
+Components.utils.import("resource:///modules/NewTabPrefsProvider.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/Preferences.jsm");
 Components.utils.importGlobalProperties(["URL"]);
 
-add_task(function* () {
-  var notificationPromise;
-  let defaultHref = RemoteNewTabLocation.href;
+RemoteNewTabLocation.init();
+const DEFAULT_HREF = RemoteNewTabLocation.href;
+RemoteNewTabLocation.uninit();
 
+add_task(function* test_defaults() {
+  RemoteNewTabLocation.init();
   ok(RemoteNewTabLocation.href, "Default location has an href");
   ok(RemoteNewTabLocation.origin, "Default location has an origin");
   ok(!RemoteNewTabLocation.overridden, "Default location is not overridden");
+  RemoteNewTabLocation.uninit();
+});
 
+/**
+ * Tests the overriding of the default URL
+ */
+add_task(function* test_overrides() {
+  RemoteNewTabLocation.init();
   let testURL = new URL("https://example.com/");
+  let notificationPromise;
 
-  notificationPromise = changeNotificationPromise(testURL.href);
+  notificationPromise = nextChangeNotificationPromise(
+    testURL.href, "Remote Location should change");
   RemoteNewTabLocation.override(testURL.href);
   yield notificationPromise;
   ok(RemoteNewTabLocation.overridden, "Remote location should be overridden");
-  equal(RemoteNewTabLocation.href, testURL.href, "Remote href should be the custom URL");
-  equal(RemoteNewTabLocation.origin, testURL.origin, "Remote origin should be the custom URL");
+  equal(RemoteNewTabLocation.href, testURL.href,
+        "Remote href should be the custom URL");
+  equal(RemoteNewTabLocation.origin, testURL.origin,
+        "Remote origin should be the custom URL");
 
-  notificationPromise = changeNotificationPromise(defaultHref);
+  notificationPromise = nextChangeNotificationPromise(
+    DEFAULT_HREF, "Remote href should be reset");
   RemoteNewTabLocation.reset();
   yield notificationPromise;
   ok(!RemoteNewTabLocation.overridden, "Newtab URL should not be overridden");
-  equal(RemoteNewTabLocation.href, defaultHref, "Remote href should be reset");
+  RemoteNewTabLocation.uninit();
 });
 
-function changeNotificationPromise(aNewURL) {
+/**
+ * Tests how RemoteNewTabLocation responds to updates to prefs
+ */
+add_task(function* test_updates() {
+  RemoteNewTabLocation.init();
+  let notificationPromise;
+  let expectedHref = "https://newtab.cdn.mozilla.net" +
+                     `/v${RemoteNewTabLocation.version}` +
+                     "/nightly" +
+                     "/en-GB" +
+                     "/index.html";
+  Preferences.set("intl.locale.matchOS", true);
+  Preferences.set("general.useragent.locale", "en-GB");
+  NewTabPrefsProvider.prefs.init();
+
+  // test update checks for prefs
+  notificationPromise = nextChangeNotificationPromise(
+    expectedHref, "Remote href should be updated");
+  Preferences.set("intl.locale.matchOS", false);
+  yield notificationPromise;
+
+  notificationPromise = nextChangeNotificationPromise(
+    DEFAULT_HREF, "Remote href changes back to default");
+  Preferences.set("general.useragent.locale", "en-US");
+
+  yield notificationPromise;
+
+  // test update fires on override and reset
+  let testURL = new URL("https://example.com/");
+  notificationPromise = nextChangeNotificationPromise(
+    testURL.href, "a notification occurs on override");
+  RemoteNewTabLocation.override(testURL.href);
+  yield notificationPromise;
+
+  // from overridden to default
+  notificationPromise = nextChangeNotificationPromise(
+    DEFAULT_HREF, "a notification occurs on reset");
+  RemoteNewTabLocation.reset();
+  yield notificationPromise;
+
+  // override to default URL from default URL
+  notificationPromise = nextChangeNotificationPromise(
+    testURL.href, "a notification only occurs for a change in overridden urls");
+  RemoteNewTabLocation.override(DEFAULT_HREF);
+  RemoteNewTabLocation.override(testURL.href);
+  yield notificationPromise;
+
+  // reset twice, only one notification for default URL
+  notificationPromise = nextChangeNotificationPromise(
+    DEFAULT_HREF, "reset occurs");
+  RemoteNewTabLocation.reset();
+  yield notificationPromise;
+
+  notificationPromise = nextChangeNotificationPromise(
+    testURL.href, "a notification only occurs for a change in reset urls");
+  RemoteNewTabLocation.reset();
+  RemoteNewTabLocation.override(testURL.href);
+  yield notificationPromise;
+
+  NewTabPrefsProvider.prefs.uninit();
+  RemoteNewTabLocation.uninit();
+});
+
+/**
+ * Verifies that RemoteNewTabLocation's _releaseFromUpdateChannel
+ * Returns the correct release names
+ */
+add_task(function* test_release_names() {
+  RemoteNewTabLocation.init();
+  let valid_channels = RemoteNewTabLocation.channels;
+  let invalid_channels = new Set(["default", "invalid"]);
+
+  for (let channel of valid_channels) {
+    equal(channel, RemoteNewTabLocation._releaseFromUpdateChannel(channel),
+          "release == channel name when valid");
+  }
+
+  for (let channel of invalid_channels) {
+    equal("nightly", RemoteNewTabLocation._releaseFromUpdateChannel(channel),
+          "release == nightly when invalid");
+  }
+  RemoteNewTabLocation.uninit();
+});
+
+function nextChangeNotificationPromise(aNewURL, testMessage) {
   return new Promise(resolve => {
     Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint ignore:line
       Services.obs.removeObserver(observer, aTopic);
-      equal(aData, aNewURL, "remote-new-tab-location-changed data should be new URL.");
+      equal(aData, aNewURL, testMessage);
       resolve();
     }, "remote-new-tab-location-changed", false);
   });
 }
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -30,16 +30,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/RemoteAboutNewTab.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabUtils",
                                   "resource:///modules/RemoteNewTabUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
                                   "resource:///modules/NewTabPrefsProvider.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "RemoteNewTabLocation",
+                                  "resource:///modules/RemoteNewTabLocation.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "UITour",
                                   "resource:///modules/UITour.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContentClick",
                                   "resource:///modules/ContentClick.jsm");
@@ -846,16 +849,17 @@ BrowserGlue.prototype = {
     DirectoryLinksProvider.init();
     NewTabUtils.init();
     NewTabUtils.links.addProvider(DirectoryLinksProvider);
     AboutNewTab.init();
 
     RemoteNewTabUtils.init();
     RemoteNewTabUtils.links.addProvider(DirectoryLinksProvider);
     RemoteAboutNewTab.init();
+    RemoteNewTabLocation.init();
     NewTabPrefsProvider.prefs.init();
 
     SessionStore.init();
     BrowserUITelemetry.init();
     ContentSearch.init();
     FormValidationHandler.init();
 
     ContentClick.init();
@@ -1168,16 +1172,17 @@ BrowserGlue.prototype = {
     }
 
     SelfSupportBackend.uninit();
 
     CustomizationTabPreloader.uninit();
     WebappManager.uninit();
 
     RemoteAboutNewTab.uninit();
+    RemoteNewTabLocation.uninit();
     NewTabPrefsProvider.prefs.uninit();
     AboutNewTab.uninit();
 #ifdef NIGHTLY_BUILD
     if (Services.prefs.getBoolPref("dom.identity.enabled")) {
       SignInToWebsiteUX.uninit();
     }
 #endif
     webrtcUI.uninit();