Bug 1095739 - Allow a "new user" experience to happen subsequent to Firefox being uninstalled. r?gijs,jimm draft
authorJared Wein <jwein@mozilla.com>
Tue, 26 Apr 2016 17:29:46 -0400
changeset 356656 1dbf8049de211f771c3d5398cfad51ac11d3a494
parent 356074 0f07f975526f3abda2f997bbb2feb0a25f771227
child 519446 9029a6a71f509dad52087a438c98cceafb5e5c1f
push id16562
push userjwein@mozilla.com
push dateTue, 26 Apr 2016 21:30:00 +0000
reviewersgijs, jimm
bugs1095739
milestone48.0a1
Bug 1095739 - Allow a "new user" experience to happen subsequent to Firefox being uninstalled. r?gijs,jimm MozReview-Commit-ID: 3Atu6pU4IH4
browser/components/nsBrowserGlue.js
browser/installer/windows/nsis/uninstaller.nsi
toolkit/locales/en-US/chrome/global/resetProfile.properties
toolkit/modules/WindowsRegistry.jsm
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -151,16 +151,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/LightweightThemeManager.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                   "resource://gre/modules/ExtensionManagement.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ShellService",
                                   "resource:///modules/ShellService.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
+                                  "resource://gre/modules/WindowsRegistry.jsm");
+
 XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
                                    "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
 
 XPCOMUtils.defineLazyServiceGetter(this, "AlertsService",
                                    "@mozilla.org/alerts-service;1", "nsIAlertsService");
 
 const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
 const PREF_PLUGINS_UPDATEURL  = "plugins.update.url";
@@ -900,44 +903,55 @@ BrowserGlue.prototype = {
 
     let nb = win.document.getElementById("global-notificationbox");
     nb.appendNotification(message, "slow-startup",
                           "chrome://browser/skin/slowStartup-16.png",
                           nb.PRIORITY_INFO_LOW, buttons);
   },
 
   /**
-   * Show a notification bar offering a reset if the profile has been unused for some time.
+   * Show a notification bar offering a reset.
+   *
+   * @param reason
+   *        String of either "unused" or "uninstall", specifying the reason
+   *        why a profile reset is offered.
    */
-  _resetUnusedProfileNotification: function () {
+  _resetProfileNotification: function (reason) {
     let win = RecentWindow.getMostRecentBrowserWindow();
     if (!win)
       return;
 
     Cu.import("resource://gre/modules/ResetProfile.jsm");
     if (!ResetProfile.resetSupported())
       return;
 
     let productName = gBrandBundle.GetStringFromName("brandShortName");
     let resetBundle = Services.strings
                               .createBundle("chrome://global/locale/resetProfile.properties");
 
-    let message = resetBundle.formatStringFromName("resetUnusedProfile.message", [productName], 1);
+    let message;
+    if (reason == "unused") {
+      message = resetBundle.formatStringFromName("resetUnusedProfile.message", [productName], 1);
+    } else if (reason == "uninstall") {
+      message = resetBundle.formatStringFromName("resetUninstalled.message", [productName, productName], 2);
+    } else {
+      throw new Error(`Unknown reason (${reason}) given to _resetProfileNotification.`);
+    }
     let buttons = [
       {
         label:     resetBundle.formatStringFromName("refreshProfile.resetButton.label", [productName], 1),
         accessKey: resetBundle.GetStringFromName("refreshProfile.resetButton.accesskey"),
         callback: function () {
           ResetProfile.openConfirmationDialog(win);
         }
       },
     ];
 
     let nb = win.document.getElementById("global-notificationbox");
-    nb.appendNotification(message, "reset-unused-profile",
+    nb.appendNotification(message, "reset-profile-notification",
                           "chrome://global/skin/icons/question-16.png",
                           nb.PRIORITY_INFO_LOW, buttons);
   },
 
   _notifyUnsignedAddonsDisabled: function () {
     let win = RecentWindow.getMostRecentBrowserWindow();
     if (!win)
       return;
@@ -1025,19 +1039,40 @@ BrowserGlue.prototype = {
 
     // Offer to reset a user's profile if it hasn't been used for 60 days.
     const OFFER_PROFILE_RESET_INTERVAL_MS = 60 * 24 * 60 * 60 * 1000;
     let lastUse = Services.appinfo.replacedLockTime;
     let disableResetPrompt = false;
     try {
       disableResetPrompt = Services.prefs.getBoolPref("browser.disableResetPrompt");
     } catch(e) {}
+
     if (!disableResetPrompt && lastUse &&
         Date.now() - lastUse >= OFFER_PROFILE_RESET_INTERVAL_MS) {
-      this._resetUnusedProfileNotification();
+      this._resetProfileNotification("unused");
+    } else if (AppConstants.platform == "win") {
+      // Check if we were just re-installed and offer Firefox Reset
+      let updateChannel;
+      try {
+        updateChannel = Cu.import("resource://gre/modules/UpdateUtils.jsm", {}).UpdateUtils.UpdateChannel;
+      } catch (ex) {}
+      if (updateChannel) {
+        let uninstalledValue =
+          WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                                     "Software\\Mozilla\\Firefox",
+                                     `Uninstalled-${updateChannel}`);
+        let removalSuccessful =
+          WindowsRegistry.removeRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                                       "Software\\Mozilla\\Firefox",
+                                       `Uninstalled-${updateChannel}`);
+        if (!disableResetPrompt && removalSuccessful &&
+            uninstalledValue == "True") {
+          this._resetProfileNotification("uninstall");
+        }
+      }
     }
 
     this._checkForOldBuildUpdates();
 
     this._firstWindowTelemetry(aWindow);
     this._firstWindowLoaded();
   },
 
--- a/browser/installer/windows/nsis/uninstaller.nsi
+++ b/browser/installer/windows/nsis/uninstaller.nsi
@@ -445,16 +445,23 @@ Section "Uninstall"
     ${EndIf}
   ${EndIf}
 
   ; Refresh desktop icons otherwise the start menu internet item won't be
   ; removed and other ugly things will happen like recreation of the app's
   ; clients registry key by the OS under some conditions.
   System::Call "shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i 0, i 0, i 0)"
 
+  ; Users who uninstall then reinstall expecting Firefox to use a clean profile
+  ; may be surprised during first-run. This key is checked during startup of Firefox and
+  ; subsequently deleted after checking. If the value is found during startup
+  ; the browser will offer to Reset Firefox. We use the UpdateChannel to match
+  ; uninstalls of Firefox-release with reinstalls of Firefox-release, for example.
+  WriteRegStr HKCU "Software\Mozilla\Firefox" "Uninstalled-${UpdateChannel}" "True"
+
 !ifdef MOZ_MAINTENANCE_SERVICE
   ; Get the path the allowed cert is at and remove it
   ; Keep this block of code last since it modfies the reg view
   ServicesHelper::PathToUniqueRegistryPath "$INSTDIR"
   Pop $MaintCertKey
   ${If} $MaintCertKey != ""
     ; Always use the 64bit registry for certs on 64bit systems.
     ${If} ${RunningX64}
--- a/toolkit/locales/en-US/chrome/global/resetProfile.properties
+++ b/toolkit/locales/en-US/chrome/global/resetProfile.properties
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # LOCALIZATION NOTE: These strings are used for profile reset.
 
 # LOCALIZATION NOTE (resetUnusedProfile.message): %S is brandShortName.
 resetUnusedProfile.message=It looks like you haven't started %S in a while. Do you want to clean it up for a fresh, like-new experience? And by the way, welcome back!
+# LOCALIZATION NOTE (resetUninstalled.message): %S is brandShortName.
+resetUninstalled.message=It looks like you've reinstalled %S. Do you want to clean it up for a fresh, like-new experience? And by the way, thanks for using %S!
 
 # LOCALIZATION NOTE (refreshProfile.resetButton.label): %S is brandShortName.
 refreshProfile.resetButton.label=Refresh %S…
 refreshProfile.resetButton.accesskey=e
--- a/toolkit/modules/WindowsRegistry.jsm
+++ b/toolkit/modules/WindowsRegistry.jsm
@@ -59,26 +59,30 @@ var WindowsRegistry = {
    *        The root registry to use.
    * @param aPath
    *        The registry path to the key.
    * @param aKey
    *        The key name.
    * @param [aRegistryNode=0]
    *        Optionally set to nsIWindowsRegKey.WOW64_64 (or nsIWindowsRegKey.WOW64_32)
    *        to access a 64-bit (32-bit) key from either a 32-bit or 64-bit application.
+   * @return True if the key was removed or never existed, false otherwise.
    */
   removeRegKey: function(aRoot, aPath, aKey, aRegistryNode=0) {
     let registry = Cc["@mozilla.org/windows-registry-key;1"].
                    createInstance(Ci.nsIWindowsRegKey);
+    let result = true;
     try {
       let mode = Ci.nsIWindowsRegKey.ACCESS_QUERY_VALUE |
                  Ci.nsIWindowsRegKey.ACCESS_SET_VALUE |
                  aRegistryNode;
       registry.open(aRoot, aPath, mode);
       if (registry.hasValue(aKey)) {
         registry.removeValue(aKey);
+        result = !registry.hasValue(aKey);
       }
     } catch (ex) {
     } finally {
       registry.close();
+      return result;
     }
   }
 };