Fix registerProtocolHandler OS registration on Windows 8

On Windows 8 to register as the default handler for a protocol you need to go through a Windows shell UI flow, it cannot be done anymore via the IApplicationAssociationRegistration interface.

BUG=150850
TEST=Ensure gmail and other protocol handlers can be registered successfully on Windows 7 and 8.


Review URL: https://chromiumcodereview.appspot.com/10963004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@158612 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry.cc b/chrome/browser/custom_handlers/protocol_handler_registry.cc
index 12ca2f2..5d7cd95 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry.cc
+++ b/chrome/browser/custom_handlers/protocol_handler_registry.cc
@@ -243,6 +243,11 @@
   }
 }
 
+bool ProtocolHandlerRegistry::DefaultClientObserver::
+    IsInteractiveSetDefaultPermitted() {
+  return true;
+}
+
 void ProtocolHandlerRegistry::DefaultClientObserver::SetWorker(
     ShellIntegration::DefaultProtocolClientWorker* worker) {
   worker_ = worker;
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry.h b/chrome/browser/custom_handlers/protocol_handler_registry.h
index 895f510d..25de9550 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry.h
+++ b/chrome/browser/custom_handlers/protocol_handler_registry.h
@@ -43,6 +43,8 @@
     virtual void SetDefaultWebClientUIState(
         ShellIntegration::DefaultWebClientUIState state) OVERRIDE;
 
+    virtual bool IsInteractiveSetDefaultPermitted() OVERRIDE;
+
     // Give the observer a handle to the worker, so we can find out the protocol
     // when we're called and also tell the worker if we get deleted.
     void SetWorker(ShellIntegration::DefaultProtocolClientWorker* worker);
diff --git a/chrome/browser/shell_integration.cc b/chrome/browser/shell_integration.cc
index 08a9684..3c8387e 100644
--- a/chrome/browser/shell_integration.cc
+++ b/chrome/browser/shell_integration.cc
@@ -97,6 +97,12 @@
 bool ShellIntegration::SetAsDefaultBrowserInteractive() {
   return false;
 }
+
+// static
+bool ShellIntegration::SetAsDefaultProtocolClientInteractive(
+    const std::string& protocol) {
+  return false;
+}
 #endif
 
 bool ShellIntegration::DefaultWebClientObserver::IsOwnedByWorker() {
@@ -264,5 +270,20 @@
 
 bool ShellIntegration::DefaultProtocolClientWorker::SetAsDefault(
     bool interactive_permitted) {
-  return ShellIntegration::SetAsDefaultProtocolClient(protocol_);
+  bool result = false;
+  switch (ShellIntegration::CanSetAsDefaultProtocolClient()) {
+    case ShellIntegration::SET_DEFAULT_UNATTENDED:
+      result = ShellIntegration::SetAsDefaultProtocolClient(protocol_);
+      break;
+    case ShellIntegration::SET_DEFAULT_INTERACTIVE:
+      if (interactive_permitted) {
+        result = ShellIntegration::SetAsDefaultProtocolClientInteractive(
+            protocol_);
+      }
+      break;
+    default:
+      NOTREACHED();
+  }
+
+  return result;
 }
diff --git a/chrome/browser/shell_integration.h b/chrome/browser/shell_integration.h
index 55ddfcf..ac1ad30 100644
--- a/chrome/browser/shell_integration.h
+++ b/chrome/browser/shell_integration.h
@@ -33,6 +33,14 @@
   // (only for the current user). Returns false if this operation fails.
   static bool SetAsDefaultProtocolClient(const std::string& protocol);
 
+  // Initiates an OS shell flow which (if followed by the user) should set
+  // Chrome as the default handler for |protocol|. Returns false if the flow
+  // cannot be initialized, if it is not supported (introduced for Windows 8)
+  // or if the user cancels the operation. This is a blocking call and requires
+  // a FILE thread.
+  static bool SetAsDefaultProtocolClientInteractive(
+      const std::string& protocol);
+
   // In Windows 8 a browser can be made default-in-metro only in an interactive
   // flow. We will distinguish between two types of permissions here to avoid
   // forcing the user into UI interaction when this should not be done.
diff --git a/chrome/browser/shell_integration_win.cc b/chrome/browser/shell_integration_win.cc
index 4782a3c..27b9212 100644
--- a/chrome/browser/shell_integration_win.cc
+++ b/chrome/browser/shell_integration_win.cc
@@ -430,7 +430,7 @@
     return false;
   }
 
-  string16 wprotocol = UTF8ToUTF16(protocol);
+  string16 wprotocol(UTF8ToUTF16(protocol));
   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
   if (!ShellUtil::MakeChromeDefaultProtocolClient(dist, chrome_exe.value(),
         wprotocol)) {
@@ -456,7 +456,27 @@
     return false;
   }
 
-  VLOG(1) << "Set-as-default Windows UI triggered.";
+  VLOG(1) << "Set-default-browser Windows UI completed.";
+  return true;
+}
+
+bool ShellIntegration::SetAsDefaultProtocolClientInteractive(
+    const std::string& protocol) {
+  FilePath chrome_exe;
+  if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
+    NOTREACHED() << "Error getting app exe path";
+    return false;
+  }
+
+  BrowserDistribution* dist = BrowserDistribution::GetDistribution();
+  string16 wprotocol(UTF8ToUTF16(protocol));
+  if (!ShellUtil::ShowMakeChromeDefaultProtocolClientSystemUI(
+          dist, chrome_exe.value(), wprotocol)) {
+    LOG(ERROR) << "Failed to launch the set-default-client Windows UI.";
+    return false;
+  }
+
+  VLOG(1) << "Set-default-client Windows UI completed.";
   return true;
 }
 
diff --git a/chrome/installer/util/shell_util.cc b/chrome/installer/util/shell_util.cc
index eb0db53..73aeabe 100644
--- a/chrome/installer/util/shell_util.cc
+++ b/chrome/installer/util/shell_util.cc
@@ -911,6 +911,27 @@
   return ret;
 }
 
+// Associates Chrome with |protocol| in the registry. This should not be
+// required on Vista+ but since some applications still read these registry
+// keys directly, we have to do this on Vista+ as well.
+// See http://msdn.microsoft.com/library/aa767914.aspx for more details.
+bool RegisterChromeAsDefaultProtocolClientForXP(BrowserDistribution* dist,
+                                                const string16& chrome_exe,
+                                                const string16& protocol) {
+  ScopedVector<RegistryEntry> entries;
+  const string16 chrome_open(ShellUtil::GetChromeShellOpenCmd(chrome_exe));
+  const string16 chrome_icon(ShellUtil::GetChromeIcon(dist, chrome_exe));
+  RegistryEntry::GetUserProtocolEntries(protocol, chrome_icon, chrome_open,
+                                        &entries);
+  // Change the default protocol handler for current user.
+  if (!AddRegistryEntries(HKEY_CURRENT_USER, entries)) {
+    LOG(ERROR) << "Could not make Chrome default protocol client (XP).";
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace
 
 const wchar_t* ShellUtil::kRegDefaultIcon = L"\\DefaultIcon";
@@ -1343,7 +1364,14 @@
   if (!dist->CanSetAsDefault())
     return false;
 
-  ShellUtil::RegisterChromeForProtocol(dist, chrome_exe, L"", protocol, true);
+  if (!RegisterChromeForProtocol(dist, chrome_exe, string16(), protocol, true))
+    return false;
+
+  // Windows 8 does not permit making a browser default just like that.
+  // This process needs to be routed through the system's UI. Use
+  // ShowMakeChromeDefaultProocolClientSystemUI instead (below).
+  if (!CanMakeChromeDefaultUnattended())
+    return false;
 
   bool ret = true;
   // First use the new "recommended" way on Vista to make Chrome default
@@ -1369,18 +1397,34 @@
   // Now use the old way to associate Chrome with the desired protocol. This
   // should not be required on Vista but since some applications still read
   // Software\Classes\http key directly, we have to do this on Vista also.
+  if (!RegisterChromeAsDefaultProtocolClientForXP(dist, chrome_exe, protocol))
+    ret = false;
 
-  ScopedVector<RegistryEntry> entries;
-  const string16 suffix(GetCurrentInstallationSuffix(dist, chrome_exe));
-  const string16 chrome_open(ShellUtil::GetChromeShellOpenCmd(chrome_exe));
-  const string16 chrome_icon(ShellUtil::GetChromeIcon(dist, chrome_exe));
-  RegistryEntry::GetUserProtocolEntries(protocol, chrome_icon, chrome_open,
-                                        &entries);
-  // Change the default protocol handler for current user.
-  if (!AddRegistryEntries(HKEY_CURRENT_USER, entries)) {
-      ret = false;
-      LOG(ERROR) << "Could not make Chrome default protocol client (XP).";
-  }
+  return ret;
+}
+
+bool ShellUtil::ShowMakeChromeDefaultProtocolClientSystemUI(
+    BrowserDistribution* dist,
+    const string16& chrome_exe,
+    const string16& protocol) {
+  DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN8);
+  if (!dist->CanSetAsDefault())
+    return false;
+
+  if (!RegisterChromeForProtocol(dist, chrome_exe, string16(), protocol, true))
+    return false;
+
+  // On Windows 8, you can't set yourself as the default handler
+  // programatically. In other words IApplicationAssociationRegistration
+  // has been rendered useless. What you can do is to launch
+  // "Set Program Associations" section of the "Default Programs"
+  // control panel, which is a mess, or pop the concise "How you want to open
+  // links of this type (protocol)?" dialog.  We choose the latter.
+  // Return true only when the user took an action and there was no error.
+  const bool ret = LaunchSelectDefaultProtocolHandlerDialog(protocol.c_str());
+
+  if (ret)
+    RegisterChromeAsDefaultProtocolClientForXP(dist, chrome_exe, protocol);
 
   return ret;
 }
diff --git a/chrome/installer/util/shell_util.h b/chrome/installer/util/shell_util.h
index 096b7dc..b6afc95 100644
--- a/chrome/installer/util/shell_util.h
+++ b/chrome/installer/util/shell_util.h
@@ -313,6 +313,18 @@
                                               const string16& chrome_exe,
                                               const string16& protocol);
 
+  // Shows to the user a system dialog where Chrome can be set as the
+  // default handler for the given protocol. This is intended for Windows 8
+  // and above only. This is a blocking call.
+  //
+  // |dist| gives the type of browser distribution currently in use.
+  // |chrome_exe| The chrome.exe path to register as default browser.
+  // |protocol| is the protocol being registered.
+  static bool ShowMakeChromeDefaultProtocolClientSystemUI(
+      BrowserDistribution* dist,
+      const string16& chrome_exe,
+      const string16& protocol);
+
   // Registers Chrome as a potential default browser and handler for filetypes
   // and protocols.
   // If Chrome is already registered, this method is a no-op.