Bug 1184508 - Remember registry hash for later use on Win8+. r=jimm, a=lmandel
authorMasatoshi Kimura <VYV03354@nifty.ne.jp>
Thu, 23 Jul 2015 04:14:00 -0400
changeset 275429 e52fe64a174f4070ba38ab94c26fe3156ff2202a
parent 275428 9468920c54b3732e4f0025b6087c3a450d42b32e
child 275430 3a5c5b34a7d58a5f547357a15490df9c050c4059
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimm, lmandel
bugs1184508
milestone40.0
Bug 1184508 - Remember registry hash for later use on Win8+. r=jimm, a=lmandel
browser/components/shell/nsWindowsShellService.cpp
--- a/browser/components/shell/nsWindowsShellService.cpp
+++ b/browser/components/shell/nsWindowsShellService.cpp
@@ -1,27 +1,28 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
+#include "nsWindowsShellService.h"
+
 #include "imgIContainer.h"
 #include "imgIRequest.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/RefPtr.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMHTMLImageElement.h"
 #include "nsIImageLoadingContent.h"
 #include "nsIPrefService.h"
 #include "nsIPrefLocalizedString.h"
 #include "nsIServiceManager.h"
 #include "nsIStringBundle.h"
 #include "nsNetUtil.h"
 #include "nsShellService.h"
-#include "nsWindowsShellService.h"
 #include "nsIProcess.h"
 #include "nsICategoryManager.h"
 #include "nsBrowserCompsCID.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIWindowsRegKey.h"
 #include "nsUnicharUtils.h"
@@ -78,16 +79,48 @@ OpenKeyForReading(HKEY aKeyRoot, const n
     return NS_ERROR_FILE_ACCESS_DENIED;
   case ERROR_FILE_NOT_FOUND:
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return NS_OK;
 }
 
+static bool
+GetPrefString(const nsCString& aPrefName, nsAString& aValue)
+{
+  nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+  if (!prefs) {
+    return false;
+  }
+
+  nsAutoCString prefCStr;
+  nsresult rv = prefs->GetCharPref(aPrefName.get(),
+                                   getter_Copies(prefCStr));
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  CopyUTF8toUTF16(prefCStr, aValue);
+
+  return true;
+}
+
+static bool
+SetPrefString(const nsCString& aPrefName, const nsString& aValue)
+{
+  nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+  if (!prefs) {
+    return false;
+  }
+
+  nsresult rv = prefs->SetCharPref(aPrefName.get(),
+                                   NS_ConvertUTF16toUTF8(aValue).get());
+  return NS_SUCCEEDED(rv);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Default Browser Registry Settings
 //
 // The setting of these values are made by an external binary since writing
 // these values may require elevation.
 //
 // - File Extension Mappings
 //   -----------------------
@@ -315,91 +348,262 @@ nsWindowsShellService::ShortcutMaintenan
     return NS_ERROR_UNEXPECTED;
 
   appHelperPath.AppendLiteral(" /UpdateShortcutAppUserModelIds");
 
   return LaunchHelper(appHelperPath);
 }
 
 static bool
-IsAARDefaultHTTP(IApplicationAssociationRegistration* pAAR,
-                 bool* aIsDefaultBrowser)
+IsAARDefault(const nsRefPtr<IApplicationAssociationRegistration>& pAAR,
+             LPCWSTR aClassName)
 {
   // Make sure the Prog ID matches what we have
   LPWSTR registeredApp;
-  HRESULT hr = pAAR->QueryCurrentDefault(L"http", AT_URLPROTOCOL, AL_EFFECTIVE,
+  bool isProtocol = *aClassName != L'.';
+  ASSOCIATIONTYPE queryType = isProtocol ? AT_URLPROTOCOL : AT_FILEEXTENSION;
+  HRESULT hr = pAAR->QueryCurrentDefault(aClassName, queryType, AL_EFFECTIVE,
                                          &registeredApp);
-  if (SUCCEEDED(hr)) {
-    LPCWSTR firefoxHTTPProgID = L"FirefoxURL";
-    *aIsDefaultBrowser = !wcsicmp(registeredApp, firefoxHTTPProgID);
-    CoTaskMemFree(registeredApp);
-  } else {
-    *aIsDefaultBrowser = false;
+  if (FAILED(hr)) {
+    return false;
   }
-  return SUCCEEDED(hr);
+
+  LPCWSTR progID = isProtocol ? L"FirefoxURL" : L"FirefoxHTML";
+  bool isDefault = !wcsicmp(registeredApp, progID);
+  CoTaskMemFree(registeredApp);
+
+  return isDefault;
+}
+
+static void
+GetUserChoiceKeyName(LPCWSTR aClassName, bool aIsProtocol,
+                     nsAString& aKeyName)
+{
+  aKeyName.AssignLiteral(aIsProtocol
+    ? "Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\"
+    : "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\");
+  aKeyName.Append(aClassName);
+  aKeyName.AppendLiteral("\\UserChoice");
+}
+
+static void
+GetHashPrefName(LPCWSTR aClassName, nsACString& aPrefName)
+{
+  aPrefName.AssignLiteral("browser.shell.associationHash.");
+  aPrefName.Append(NS_ConvertUTF16toUTF8(*aClassName == L'.' ? aClassName + 1
+                                                             : aClassName));
+}
+
+static bool
+SaveWin8RegistryHash(const nsRefPtr<IApplicationAssociationRegistration>& pAAR,
+                     LPCWSTR aClassName)
+{
+  bool isProtocol = *aClassName != L'.';
+  bool isDefault = IsAARDefault(pAAR, aClassName);
+  // We can save the value only if Firefox is the default.
+  if (!isDefault) {
+    return isDefault;
+  }
+
+  nsAutoString keyName;
+  GetUserChoiceKeyName(aClassName, isProtocol, keyName);
+
+  nsCOMPtr<nsIWindowsRegKey> regKey =
+    do_CreateInstance("@mozilla.org/windows-registry-key;1");
+  if (!regKey) {
+    return isDefault;
+  }
+
+  nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+                             keyName, nsIWindowsRegKey::ACCESS_READ);
+  if (NS_FAILED(rv)) {
+    return isDefault;
+  }
+
+  nsAutoString hash;
+  rv = regKey->ReadStringValue(NS_LITERAL_STRING("Hash"), hash);
+  if (NS_FAILED(rv)) {
+    return isDefault;
+  }
+
+  nsAutoCString prefName;
+  GetHashPrefName(aClassName, prefName);
+  SetPrefString(prefName, hash);
+
+  return isDefault;
 }
 
 static bool
-IsAARDefaultHTML(IApplicationAssociationRegistration* pAAR,
-                 bool* aIsDefaultBrowser)
+RestoreWin8RegistryHash(const nsRefPtr<IApplicationAssociationRegistration>& pAAR,
+                        LPCWSTR aClassName)
 {
-  LPWSTR registeredApp;
-  HRESULT hr = pAAR->QueryCurrentDefault(L".html", AT_FILEEXTENSION, AL_EFFECTIVE,
-                                         &registeredApp);
-  if (SUCCEEDED(hr)) {
-    LPCWSTR firefoxHTMLProgID = L"FirefoxHTML";
-    *aIsDefaultBrowser = !wcsicmp(registeredApp, firefoxHTMLProgID);
-    CoTaskMemFree(registeredApp);
-  } else {
-    *aIsDefaultBrowser = false;
+  nsAutoCString prefName;
+  GetHashPrefName(aClassName, prefName);
+  nsAutoString hash;
+  if (!GetPrefString(prefName, hash)) {
+    return false;
+  }
+
+  bool isProtocol = *aClassName != L'.';
+  nsString progId = isProtocol ? NS_LITERAL_STRING("FirefoxURL")
+                               : NS_LITERAL_STRING("FirefoxHTML");
+
+  nsAutoString keyName;
+  GetUserChoiceKeyName(aClassName, isProtocol, keyName);
+
+  nsCOMPtr<nsIWindowsRegKey> regKey =
+    do_CreateInstance("@mozilla.org/windows-registry-key;1");
+  if (!regKey) {
+    return false;
+  }
+
+  nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+                             keyName, nsIWindowsRegKey::ACCESS_READ);
+  if (NS_SUCCEEDED(rv)) {
+    nsAutoString currValue;
+    if (NS_SUCCEEDED(regKey->ReadStringValue(NS_LITERAL_STRING("Hash"),
+                                             currValue)) &&
+        currValue.Equals(hash) &&
+        NS_SUCCEEDED(regKey->ReadStringValue(NS_LITERAL_STRING("ProgId"),
+                                             currValue)) &&
+        currValue.Equals(progId)) {
+      // The value is already set.
+      return true;
+    }
+    // We need to close this explicitly because nsIWindowsRegKey::SetKey
+    // does not close the old key.
+    regKey->Close();
+  }
+
+  // We have to use the registry function directly because
+  // nsIWindowsRegKey::Create will only return NS_ERROR_FAILURE
+  // on failure.
+  HKEY theKey;
+  DWORD res = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName.get(), 0,
+                              KEY_READ | KEY_SET_VALUE, &theKey);
+  if (REG_FAILED(res)) {
+    if (res != ERROR_ACCESS_DENIED && res != ERROR_FILE_NOT_FOUND) {
+      return false;
+    }
+    if (res == ERROR_ACCESS_DENIED) {
+      res = ::RegDeleteKeyW(HKEY_CURRENT_USER, keyName.get());
+      if (REG_FAILED(res)) {
+        return false;
+      }
+    }
+    res = ::RegCreateKeyExW(HKEY_CURRENT_USER, keyName.get(), 0,
+                            nullptr, 0, KEY_READ | KEY_SET_VALUE,
+                            nullptr, &theKey, nullptr);
+    if (REG_FAILED(res)) {
+      return false;
+    }
   }
-  return SUCCEEDED(hr);
+  regKey->SetKey(theKey);
+
+  rv = regKey->WriteStringValue(NS_LITERAL_STRING("Hash"), hash);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  rv = regKey->WriteStringValue(NS_LITERAL_STRING("ProgId"), progId);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  return IsAARDefault(pAAR, aClassName);
+}
+
+static void
+SaveWin8RegistryHashes(bool aCheckAllTypes, bool* aIsDefaultBrowser)
+{
+  nsRefPtr<IApplicationAssociationRegistration> pAAR;
+  HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
+                                nullptr,
+                                CLSCTX_INPROC,
+                                IID_IApplicationAssociationRegistration,
+                                getter_AddRefs(pAAR));
+  if (FAILED(hr)) {
+    return;
+  }
+
+  bool res = SaveWin8RegistryHash(pAAR, L"http");
+  if (*aIsDefaultBrowser) {
+    *aIsDefaultBrowser = res;
+  }
+  SaveWin8RegistryHash(pAAR, L"https");
+  SaveWin8RegistryHash(pAAR, L"ftp");
+  res = SaveWin8RegistryHash(pAAR, L".html");
+  if (*aIsDefaultBrowser && aCheckAllTypes) {
+    *aIsDefaultBrowser = res;
+  }
+  SaveWin8RegistryHash(pAAR, L".htm");
+  SaveWin8RegistryHash(pAAR, L".shtml");
+  SaveWin8RegistryHash(pAAR, L".xhtml");
+  SaveWin8RegistryHash(pAAR, L".xht");
+}
+
+static bool
+RestoreWin8RegistryHashes(bool aClaimAllTypes)
+{
+  nsRefPtr<IApplicationAssociationRegistration> pAAR;
+  HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
+                                nullptr,
+                                CLSCTX_INPROC,
+                                IID_IApplicationAssociationRegistration,
+                                getter_AddRefs(pAAR));
+  if (FAILED(hr)) {
+    return false;
+  }
+
+  bool res = RestoreWin8RegistryHash(pAAR, L"http");
+  res = RestoreWin8RegistryHash(pAAR, L"https") && res;
+  RestoreWin8RegistryHash(pAAR, L"ftp");
+  bool res2 = RestoreWin8RegistryHash(pAAR, L".html");
+  res2 = RestoreWin8RegistryHash(pAAR, L".htm") && res2;
+  if (aClaimAllTypes) {
+    res = res && res2;
+  }
+  RestoreWin8RegistryHash(pAAR, L".shtml");
+  RestoreWin8RegistryHash(pAAR, L".xhtml");
+  RestoreWin8RegistryHash(pAAR, L".xht");
+
+  return res;
 }
 
 /*
  * Query's the AAR for the default status.
  * This only checks for FirefoxURL and if aCheckAllTypes is set, then
  * it also checks for FirefoxHTML.  Note that those ProgIDs are shared
  * by all Firefox browsers.
 */
 bool
 nsWindowsShellService::IsDefaultBrowserVista(bool aCheckAllTypes,
                                              bool* aIsDefaultBrowser)
 {
-  IApplicationAssociationRegistration* pAAR;
+  nsRefPtr<IApplicationAssociationRegistration> pAAR;
   HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
                                 nullptr,
                                 CLSCTX_INPROC,
                                 IID_IApplicationAssociationRegistration,
-                                (void**)&pAAR);
-
-  if (SUCCEEDED(hr)) {
-    if (aCheckAllTypes) {
-      BOOL res;
-      hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE,
-                                      APP_REG_NAME,
-                                      &res);
-      *aIsDefaultBrowser = res;
+                                getter_AddRefs(pAAR));
+  if (FAILED(hr)) {
+    return false;
+  }
 
-      // If we have all defaults, let's make sure that our ProgID
-      // is explicitly returned as well. Needed only for Windows 8.
-      if (*aIsDefaultBrowser && IsWin8OrLater()) {
-        IsAARDefaultHTTP(pAAR, aIsDefaultBrowser);
-        if (*aIsDefaultBrowser) {
-          IsAARDefaultHTML(pAAR, aIsDefaultBrowser);
-        }
-      }
-    } else {
-      IsAARDefaultHTTP(pAAR, aIsDefaultBrowser);
-    }
+  if (aCheckAllTypes) {
+    BOOL res;
+    hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE,
+                                    APP_REG_NAME,
+                                    &res);
+    *aIsDefaultBrowser = res;
+  } else if (!IsWin8OrLater()) {
+    *aIsDefaultBrowser = IsAARDefault(pAAR, L"http");
+  }
 
-    pAAR->Release();
-    return true;
-  }
-  return false;
+  return true;
 }
 
 NS_IMETHODIMP
 nsWindowsShellService::IsDefaultBrowser(bool aStartupCheck,
                                         bool aForAllTypes,
                                         bool* aIsDefaultBrowser)
 {
   // If this is the first browser window, maintain internal state that we've
@@ -489,16 +693,19 @@ nsWindowsShellService::IsDefaultBrowser(
       }
     }
   }
 
   // Only check if Firefox is the default browser on Vista and above if the
   // previous checks show that Firefox is the default browser.
   if (*aIsDefaultBrowser) {
     IsDefaultBrowserVista(aForAllTypes, aIsDefaultBrowser);
+    if (IsWin8OrLater()) {
+      SaveWin8RegistryHashes(aForAllTypes, aIsDefaultBrowser);
+    }
   }
 
   // To handle the case where DDE isn't disabled due for a user because there
   // account didn't perform a Firefox update this will check if Firefox is the
   // default browser and if dde is disabled for each handler
   // and if it isn't disable it. When Firefox is not the default browser the
   // helper application will disable dde for each handler.
   if (*aIsDefaultBrowser && aForAllTypes) {
@@ -693,17 +900,18 @@ nsWindowsShellService::SetDefaultBrowser
 
   if (aForAllUsers) {
     appHelperPath.AppendLiteral(" /SetAsDefaultAppGlobal");
   } else {
     appHelperPath.AppendLiteral(" /SetAsDefaultAppUser");
   }
 
   nsresult rv = LaunchHelper(appHelperPath);
-  if (NS_SUCCEEDED(rv) && IsWin8OrLater()) {
+  if (NS_SUCCEEDED(rv) && IsWin8OrLater() &&
+      !RestoreWin8RegistryHashes(aClaimAllTypes)) {
     if (aClaimAllTypes) {
       rv = LaunchControlPanelDefaultsSelectionUI();
       // The above call should never really fail, but just in case
       // fall back to showing the HTTP association screen only.
       if (NS_FAILED(rv)) {
         rv = LaunchHTTPHandlerPane();
       }
     } else {
@@ -716,16 +924,18 @@ nsWindowsShellService::SetDefaultBrowser
         rv = LaunchHTTPHandlerPane();
       }
 
       // The above call should never really fail, but just in case
       // fall back to showing control panel for all defaults
       if (NS_FAILED(rv)) {
         rv = LaunchControlPanelDefaultsSelectionUI();
       }
+      bool isDefault;
+      SaveWin8RegistryHashes(aClaimAllTypes, &isDefault);
     }
   }
 
   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
   if (prefs) {
     (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
   }