Bug 1697222 - Show upgrade dialog without conflicting with existing default browser prompt. r=andreio
authorEd Lee <edilee@mozilla.com>
Tue, 13 Apr 2021 00:04:30 +0000
changeset 575568 6c72185b0f4445be2da32a2ce523ad9fc32a7587
parent 575567 4d8df052a78060bd54db95d355d235f786f1f6d5
child 575569 7388f5dc0e03eca6bba358ef97c2c0764fe8520a
push id38367
push userabutkovits@mozilla.com
push dateTue, 13 Apr 2021 03:56:47 +0000
treeherdermozilla-central@d68cc4d521d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersandreio
bugs1697222
milestone89.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 1697222 - Show upgrade dialog without conflicting with existing default browser prompt. r=andreio Add new NimbusFeatures.upgradeDialog with fallback enabled pref. Remember if the browser just did a major uprgade and check other conditions for showing before other default browser messages. Differential Revision: https://phabricator.services.mozilla.com/D111041
browser/app/profile/firefox.js
browser/components/BrowserContentHandler.jsm
browser/components/BrowserGlue.jsm
browser/components/nsIBrowserHandler.idl
browser/components/tests/browser/browser_browserGlue_upgradeDialog.js
browser/components/tests/browser/whats_new_page/browser.ini
toolkit/components/nimbus/ExperimentAPI.jsm
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -296,16 +296,19 @@ pref("browser.startup.firstrunSkipsHomep
 #if defined(XP_WIN)
 #ifdef NIGHTLY_BUILD
 pref("browser.startup.preXulSkeletonUI", true);
 #else
 pref("browser.startup.preXulSkeletonUI", false);
 #endif
 #endif
 
+// Show an upgrade dialog on major upgrades.
+pref("browser.startup.upgradeDialog.enabled", true);
+
 // Don't create the hidden window during startup on
 // platforms that don't always need it (Win/Linux).
 pref("toolkit.lazyHiddenWindow", true);
 
 pref("browser.chrome.site_icons", true);
 // browser.warnOnQuit == false will override all other possible prompts when quitting or restarting
 pref("browser.warnOnQuit", true);
 
--- a/browser/components/BrowserContentHandler.jsm
+++ b/browser/components/BrowserContentHandler.jsm
@@ -87,17 +87,17 @@ function resolveURIInternal(aCmdLine, aA
   } catch (e) {
     Cu.reportError(e);
   }
 
   return uri;
 }
 
 let gKiosk = false;
-
+let gMajorUpgrade = false;
 var gFirstWindow = false;
 
 const OVERRIDE_NONE = 0;
 const OVERRIDE_NEW_PROFILE = 1;
 const OVERRIDE_NEW_MSTONE = 2;
 const OVERRIDE_NEW_BUILD_ID = 3;
 const OVERRIDE_ALTERNATE_PROFILE = 4;
 /**
@@ -137,16 +137,19 @@ function needHomepageOverride(prefb) {
 
   if (mstone != savedmstone) {
     // Bug 462254. Previous releases had a default pref to suppress the EULA
     // agreement if the platform's installer had already shown one. Now with
     // about:rights we've removed the EULA stuff and default pref, but we need
     // a way to make existing profiles retain the default that we removed.
     if (savedmstone) {
       prefb.setBoolPref("browser.rights.3.shown", true);
+
+      // Remember that we saw a major version change.
+      gMajorUpgrade = true;
     }
 
     prefb.setCharPref("browser.startup.homepage_override.mstone", mstone);
     prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
     return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE;
   }
 
   if (buildID != savedBuildID) {
@@ -832,16 +835,24 @@ nsBrowserContentHandler.prototype = {
 
     return this.mFeatures;
   },
 
   get kiosk() {
     return gKiosk;
   },
 
+  get majorUpgrade() {
+    return gMajorUpgrade;
+  },
+
+  set majorUpgrade(val) {
+    gMajorUpgrade = val;
+  },
+
   /* nsIContentHandler */
 
   handleContent: function bch_handleContent(contentType, context, request) {
     const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
 
     try {
       var webNavInfo = Cc["@mozilla.org/webnavigation-info;1"].getService(
         Ci.nsIWebNavigationInfo
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -50,16 +50,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   FirefoxMonitor: "resource:///modules/FirefoxMonitor.jsm",
   FxAccounts: "resource://gre/modules/FxAccounts.jsm",
   HomePage: "resource:///modules/HomePage.jsm",
   Integration: "resource://gre/modules/Integration.jsm",
   Log: "resource://gre/modules/Log.jsm",
   LoginBreaches: "resource:///modules/LoginBreaches.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
+  NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
   Normandy: "resource://normandy/Normandy.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   OsEnvironment: "resource://gre/modules/OsEnvironment.jsm",
   PageActions: "resource:///modules/PageActions.jsm",
   PageThumbs: "resource://gre/modules/PageThumbs.jsm",
   PdfJs: "resource://pdf.js/PdfJs.jsm",
   PermissionUI: "resource:///modules/PermissionUI.jsm",
   PlacesBackups: "resource://gre/modules/PlacesBackups.jsm",
@@ -108,22 +109,20 @@ let initializedModules = {};
 ].forEach(([name, resource, init]) => {
   XPCOMUtils.defineLazyGetter(this, name, () => {
     ChromeUtils.import(resource, initializedModules);
     initializedModules[name][init]();
     return initializedModules[name];
   });
 });
 
-XPCOMUtils.defineLazyServiceGetter(
-  this,
-  "PushService",
-  "@mozilla.org/push/Service;1",
-  "nsIPushService"
-);
+XPCOMUtils.defineLazyServiceGetters(this, {
+  BrowserHandler: ["@mozilla.org/browser/clh;1", "nsIBrowserHandler"],
+  PushService: ["@mozilla.org/push/Service;1", "nsIPushService"],
+});
 
 const PREF_PDFJS_ISDEFAULT_CACHE_STATE = "pdfjs.enabledCache.state";
 
 /**
  * Fission-compatible JSProcess implementations.
  * Each actor options object takes the form of a ProcessActorOptions dictionary.
  * Detailed documentation of these options is in dom/docs/ipc/jsactors.rst,
  * available at https://firefox-source-docs.mozilla.org/dom/ipc/jsactors.html
@@ -3755,51 +3754,94 @@ BrowserGlue.prototype = {
   },
 
   _showUpgradeDialog() {
     BrowserWindowTracker.getTopWindow().gDialogBox.open(
       "chrome://browser/content/upgradeDialog.html"
     );
   },
 
-  _maybeShowDefaultBrowserPrompt() {
-    Promise.all([
-      DefaultBrowserCheck.willCheckDefaultBrowser(/* isStartupCheck */ true),
-      ExperimentAPI.ready,
-    ]).then(async ([willPrompt]) => {
-      let { DefaultBrowserNotification } = ChromeUtils.import(
-        "resource:///actors/AboutNewTabParent.jsm",
-        {}
-      );
-      let isFeatureEnabled = ExperimentAPI.getExperiment({
-        featureId: "infobar",
-      })?.branch.feature.enabled;
-      if (willPrompt) {
-        // Prevent the related notification from appearing and
-        // show the modal prompt.
-        DefaultBrowserNotification.notifyModalDisplayed();
+  async _maybeShowDefaultBrowserPrompt() {
+    // Highest priority is the upgrade dialog, which can include a "primary
+    // browser" request and is limited in various ways, e.g., major upgrades.
+    const dialogVersion = 89;
+    const dialogVersionPref = "browser.startup.upgradeDialog.version";
+    const dialogReason = await (async () => {
+      if (!Services.prefs.getBoolPref("browser.proton.enabled", true)) {
+        return "no-proton";
+      }
+      if (!BrowserHandler.majorUpgrade) {
+        return "not-major";
+      }
+      const lastVersion = Services.prefs.getIntPref(dialogVersionPref, 0);
+      if (lastVersion > dialogVersion) {
+        return "newer-shown";
+      }
+      if (lastVersion === dialogVersion) {
+        return "already-shown";
+      }
+      if (
+        !Services.prefs.getBoolPref(
+          "browser.messaging-system.whatsNewPanel.enabled",
+          true
+        )
+      ) {
+        return "no-whatsNew";
+      }
+      if (!Services.prefs.getBoolPref("browser.aboutwelcome.enabled", true)) {
+        return "no-welcome";
+      }
+      if (!Services.policies.isAllowed("postUpdateCustomPage")) {
+        return "disallow-postUpdate";
       }
-      // If no experiment go ahead with default experience
-      if (willPrompt && !isFeatureEnabled) {
-        let win = BrowserWindowTracker.getTopWindow();
-        DefaultBrowserCheck.prompt(win);
-      }
-      // If in experiment notify ASRouter to dispatch message
-      if (isFeatureEnabled) {
-        ASRouter.waitForInitialized.then(() =>
-          ASRouter.sendTriggerMessage({
-            browser: BrowserWindowTracker.getTopWindow()?.gBrowser
-              .selectedBrowser,
-            // triggerId and triggerContext
-            id: "defaultBrowserCheck",
-            context: { willShowDefaultPrompt: willPrompt },
-          })
-        );
-      }
-    });
+
+      // Check enabled last to avoid waiting on remote data in the common case.
+      await NimbusFeatures.upgradeDialog.ready();
+      return NimbusFeatures.upgradeDialog.isEnabled() ? "" : "disabled";
+    })();
+
+    // Show the upgrade dialog if allowed and remember the version.
+    if (!dialogReason) {
+      Services.prefs.setIntPref(dialogVersionPref, dialogVersion);
+      this._showUpgradeDialog();
+      return;
+    }
+
+    // Determine if the modal prompt or infobar message should be shown.
+    const [willPrompt] = await Promise.all([
+      DefaultBrowserCheck.willCheckDefaultBrowser(/* isStartupCheck */ true),
+      ExperimentAPI.ready(),
+    ]);
+    let { DefaultBrowserNotification } = ChromeUtils.import(
+      "resource:///actors/AboutNewTabParent.jsm",
+      {}
+    );
+    let isFeatureEnabled = ExperimentAPI.getExperiment({
+      featureId: "infobar",
+    })?.branch.feature.enabled;
+    if (willPrompt) {
+      // Prevent the related notification from appearing and
+      // show the modal prompt.
+      DefaultBrowserNotification.notifyModalDisplayed();
+    }
+    // If no experiment go ahead with default experience
+    if (willPrompt && !isFeatureEnabled) {
+      let win = BrowserWindowTracker.getTopWindow();
+      DefaultBrowserCheck.prompt(win);
+    }
+    // If in experiment notify ASRouter to dispatch message
+    if (isFeatureEnabled) {
+      await ASRouter.waitForInitialized;
+      ASRouter.sendTriggerMessage({
+        browser: BrowserWindowTracker.getTopWindow()?.gBrowser.selectedBrowser,
+        // triggerId and triggerContext
+        id: "defaultBrowserCheck",
+        context: { willShowDefaultPrompt: willPrompt },
+      });
+    }
   },
 
   /**
    * Open preferences even if there are no open windows.
    */
   _openPreferences(...args) {
     let chromeWindow = BrowserWindowTracker.getTopWindow();
     if (chromeWindow) {
--- a/browser/components/nsIBrowserHandler.idl
+++ b/browser/components/nsIBrowserHandler.idl
@@ -7,15 +7,16 @@
 interface nsICommandLine;
 
 [scriptable, uuid(8D3F5A9D-118D-4548-A137-CF7718679069)]
 interface nsIBrowserHandler : nsISupports
 {
   attribute AUTF8String startPage;
   attribute AUTF8String defaultArgs;
   attribute boolean kiosk;
+  attribute boolean majorUpgrade;
 
   /**
    * Extract the width and height specified on the command line, if present.
    * @return A feature string with a prepended comma, e.g. ",width=500,height=400"
    */
   AUTF8String getFeatures(in nsICommandLine aCmdLine);
 };
--- a/browser/components/tests/browser/browser_browserGlue_upgradeDialog.js
+++ b/browser/components/tests/browser/browser_browserGlue_upgradeDialog.js
@@ -208,11 +208,47 @@ add_task(async function exit_early() {
 
   Assert.equal(
     mock.setAsDefault.callCount,
     0,
     "Only 1 screen to skip when default"
   );
 });
 
+add_task(async function not_major_upgrade() {
+  await BROWSER_GLUE._maybeShowDefaultBrowserPrompt();
+});
+
+add_task(async function remote_disabled() {
+  // TODO(bug 1701948): Set up actual remote defaults.
+  NimbusFeatures.upgradeDialog._onRemoteReady();
+  Services.prefs.setBoolPref("browser.startup.upgradeDialog.enabled", false);
+
+  // Simulate starting from a previous version.
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.startup.homepage_override.mstone", "88.0"]],
+  });
+  Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler).defaultArgs;
+
+  await BROWSER_GLUE._maybeShowDefaultBrowserPrompt();
+
+  Services.prefs.clearUserPref("browser.startup.upgradeDialog.enabled");
+});
+
+add_task(async function show_major_upgrade() {
+  const promise = waitForDialog();
+  await BROWSER_GLUE._maybeShowDefaultBrowserPrompt();
+  await promise;
+
+  Assert.ok(true, "Upgrade dialog opened and closed from major upgrade");
+});
+
+add_task(async function dont_reshow() {
+  await BROWSER_GLUE._maybeShowDefaultBrowserPrompt();
+});
+
 registerCleanupFunction(() => {
   getShellService.restore();
+  Cc["@mozilla.org/browser/clh;1"].getService(
+    Ci.nsIBrowserHandler
+  ).majorUpgrade = false;
+  Services.prefs.clearUserPref("browser.startup.upgradeDialog.version");
 });
--- a/browser/components/tests/browser/whats_new_page/browser.ini
+++ b/browser/components/tests/browser/whats_new_page/browser.ini
@@ -3,10 +3,11 @@ skip-if = verify
 reason = This is a startup test. Verify runs tests multiple times after startup.
 support-files =
   active-update.xml
   updates/0/update.status
 prefs =
   app.update.altUpdateDirPath='<test-root>/browser/components/tests/browser/whats_new_page'
   app.update.disabledForTesting=false
   browser.startup.homepage_override.mstone="60.0"
+  browser.startup.upgradeDialog.enabled=false
 
 [browser_whats_new_page.js]
--- a/toolkit/components/nimbus/ExperimentAPI.jsm
+++ b/toolkit/components/nimbus/ExperimentAPI.jsm
@@ -60,16 +60,20 @@ const MANIFEST = {
       prefsButtonIcon: {
         type: "string",
       },
     },
   },
   "password-autocomplete": {
     description: "A special autocomplete UI for password fields.",
   },
+  upgradeDialog: {
+    description: "The dialog shown for major upgrades",
+    enabledFallbackPref: "browser.startup.upgradeDialog.enabled",
+  },
 };
 
 function isBooleanValueDefined(value) {
   return typeof value === "boolean";
 }
 
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const { XPCOMUtils } = ChromeUtils.import(