Chromium Code Reviews| Index: chrome/installer/util/shell_util.cc |
| diff --git a/chrome/installer/util/shell_util.cc b/chrome/installer/util/shell_util.cc |
| index 1e67163d9173198f8f002a714d59d160dfbf932c..cf901ba5f2f1984db042f644b790ffae59e95f5e 100644 |
| --- a/chrome/installer/util/shell_util.cc |
| +++ b/chrome/installer/util/shell_util.cc |
| @@ -191,6 +191,43 @@ class RegistryEntry { |
| LOOK_IN_HKCU_THEN_HKLM = LOOK_IN_HKCU | LOOK_IN_HKLM, |
| }; |
| + // Details about a Windows application, to be entered into the registry for |
| + // the purpose of file associations. |
| + struct ApplicationInfo { |
| + ApplicationInfo() : file_type_icon_index(0), application_icon_index(0) {} |
| + |
| + // The ProgId used by Windows for file associations with this application. |
| + // Must not be empty or start with a '.'. |
| + base::string16 prog_id; |
| + // The friendly name, and the path of the icon that will be used for files |
| + // of these types when associated with this application by default. (They |
| + // are NOT the name/icon that will represent the application under the Open |
| + // With menu.) |
| + base::string16 file_type_name; |
| + // TODO(mgiuca): |file_type_icon_path| should be a base::FilePath. |
| + base::string16 file_type_icon_path; |
| + int file_type_icon_index; |
| + // The command to execute when opening a file via this association. It |
| + // should contain "%1" (to tell Windows to pass the filename as an |
| + // argument). |
| + // TODO(mgiuca): |command_line| should be a base::CommandLine. |
| + base::string16 command_line; |
| + // The AppUserModelId used by Windows 8 for this application. Distinct from |
| + // |prog_id|. |
| + base::string16 app_id; |
| + |
| + // User-visible details about this application. Any of these may be empty. |
| + base::string16 application_name; |
| + // TODO(mgiuca): |application_icon_path| should be a base::FilePath. |
| + base::string16 application_icon_path; |
| + int application_icon_index; |
| + base::string16 application_description; |
| + base::string16 publisher_name; |
| + |
| + // The CLSID for the application's DelegateExecute handler. May be empty. |
| + base::string16 delegate_clsid; |
| + }; |
| + |
| // Returns the Windows browser client registration key for Chrome. For |
| // example: "Software\Clients\StartMenuInternet\Chromium[.user]". Strictly |
| // speaking, we should use the name of the executable (e.g., "chrome.exe"), |
| @@ -218,32 +255,48 @@ class RegistryEntry { |
| // This method returns a list of all the registry entries that |
| // are needed to register this installation's ProgId and AppId. |
| // These entries need to be registered in HKLM prior to Win8. |
| - static void GetProgIdEntries(BrowserDistribution* dist, |
| - const base::string16& chrome_exe, |
| - const base::string16& suffix, |
| - ScopedVector<RegistryEntry>* entries) { |
| - base::string16 icon_path( |
| - ShellUtil::FormatIconLocation( |
| - chrome_exe, |
| - dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME))); |
| - base::string16 open_cmd(ShellUtil::GetChromeShellOpenCmd(chrome_exe)); |
| + static void GetChromeProgIdEntries(BrowserDistribution* dist, |
| + const base::string16& chrome_exe, |
| + const base::string16& suffix, |
| + ScopedVector<RegistryEntry>* entries) { |
| + int chrome_icon_index = |
| + dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME); |
| + |
| + ApplicationInfo app_info; |
| + app_info.prog_id = GetBrowserProgId(suffix); |
| + app_info.file_type_name = dist->GetBrowserProgIdDesc(); |
| + // File types associated with Chrome are just given the Chrome icon. |
| + app_info.file_type_icon_path = chrome_exe; |
| + app_info.file_type_icon_index = chrome_icon_index; |
| + app_info.command_line = ShellUtil::GetChromeShellOpenCmd(chrome_exe); |
| + // For user-level installs: entries for the app id will be in HKCU; thus we |
| + // do not need a suffix on those entries. |
| + app_info.app_id = ShellUtil::GetBrowserModelId( |
| + dist, InstallUtil::IsPerUserInstall(chrome_exe.c_str())); |
| + |
| + // The command to execute when opening this application via the Metro UI. |
| base::string16 delegate_command( |
| ShellUtil::GetChromeDelegateCommand(chrome_exe)); |
| - // For user-level installs: entries for the app id and DelegateExecute verb |
| - // handler will be in HKCU; thus we do not need a suffix on those entries. |
| - base::string16 app_id( |
| - ShellUtil::GetBrowserModelId( |
| - dist, InstallUtil::IsPerUserInstall(chrome_exe.c_str()))); |
| - base::string16 delegate_guid; |
| bool set_delegate_execute = |
| IsChromeMetroSupported() && |
| - dist->GetCommandExecuteImplClsid(&delegate_guid); |
| + dist->GetCommandExecuteImplClsid(&app_info.delegate_clsid); |
| + |
| + // TODO(grt): http://crbug.com/75152 Write a reference to a localized |
| + // resource for name, description, and company. |
| + app_info.application_name = dist->GetDisplayName(); |
| + app_info.application_icon_path = chrome_exe; |
| + app_info.application_icon_index = chrome_icon_index; |
| + app_info.application_description = dist->GetAppDescription(); |
| + app_info.publisher_name = dist->GetPublisherName(); |
| - // DelegateExecute ProgId. Needed for Chrome Metro in Windows 8. |
| + GetProgIdEntries(app_info, entries); |
| + |
| + // DelegateExecute ProgId. Needed for Chrome Metro in Windows 8. This is |
| + // only needed for registring a web browser, not for general associations. |
| if (set_delegate_execute) { |
| base::string16 model_id_shell(ShellUtil::kRegClasses); |
| model_id_shell.push_back(base::FilePath::kSeparators[0]); |
| - model_id_shell.append(app_id); |
| + model_id_shell.append(app_info.app_id); |
| model_id_shell.append(ShellUtil::kRegExePath); |
| model_id_shell.append(ShellUtil::kRegShellPath); |
| @@ -286,50 +339,76 @@ class RegistryEntry { |
| // <root hkey>\Software\Classes\<app_id>\.exe\shell\<verb>\command |
| entries->push_back(new RegistryEntry(sub_path, delegate_command)); |
| entries->push_back(new RegistryEntry( |
| - sub_path, ShellUtil::kRegDelegateExecute, delegate_guid)); |
| + sub_path, ShellUtil::kRegDelegateExecute, app_info.delegate_clsid)); |
| } |
| } |
| + } |
| + |
| + // Gets the registry entries to register an application in the Windows |
| + // registry. |app_info| provides all of the information needed. |
| + static void GetProgIdEntries(const ApplicationInfo& app_info, |
| + ScopedVector<RegistryEntry>* entries) { |
| + // Basic sanity checks. |
| + DCHECK(!app_info.prog_id.empty()); |
| + DCHECK_NE(L'.', app_info.prog_id[0]); |
|
gab
2014/10/02 13:03:59
Another restriction worth enforcing here and in th
Matt Giuca
2014/10/03 00:26:40
Actually, I looked into this yesterday (since I fo
gab
2014/10/03 15:26:25
So I just dug further into this since I remembered
gab
2014/11/05 15:47:41
ping; just back from a month leave, has anything b
|
| // File association ProgId |
| - base::string16 chrome_html_prog_id(ShellUtil::kRegClasses); |
| - chrome_html_prog_id.push_back(base::FilePath::kSeparators[0]); |
| - chrome_html_prog_id.append(GetBrowserProgId(suffix)); |
| + base::string16 prog_id_path(ShellUtil::kRegClasses); |
| + prog_id_path.push_back(base::FilePath::kSeparators[0]); |
| + prog_id_path.append(app_info.prog_id); |
| + entries->push_back( |
| + new RegistryEntry(prog_id_path, app_info.file_type_name)); |
| entries->push_back(new RegistryEntry( |
| - chrome_html_prog_id, dist->GetBrowserProgIdDesc())); |
| + prog_id_path + ShellUtil::kRegDefaultIcon, |
| + ShellUtil::FormatIconLocation(app_info.file_type_icon_path, |
| + app_info.file_type_icon_index))); |
| entries->push_back(new RegistryEntry( |
| - chrome_html_prog_id + ShellUtil::kRegDefaultIcon, icon_path)); |
| - entries->push_back(new RegistryEntry( |
| - chrome_html_prog_id + ShellUtil::kRegShellOpen, open_cmd)); |
| - if (set_delegate_execute) { |
| - entries->push_back(new RegistryEntry( |
| - chrome_html_prog_id + ShellUtil::kRegShellOpen, |
| - ShellUtil::kRegDelegateExecute, delegate_guid)); |
| + prog_id_path + ShellUtil::kRegShellOpen, app_info.command_line)); |
| + if (!app_info.delegate_clsid.empty()) { |
| + entries->push_back( |
| + new RegistryEntry(prog_id_path + ShellUtil::kRegShellOpen, |
| + ShellUtil::kRegDelegateExecute, |
| + app_info.delegate_clsid)); |
| } |
| // The following entries are required as of Windows 8, but do not |
| // depend on the DelegateExecute verb handler being set. |
| if (base::win::GetVersion() >= base::win::VERSION_WIN8) { |
| - entries->push_back(new RegistryEntry( |
| - chrome_html_prog_id, ShellUtil::kRegAppUserModelId, app_id)); |
| + if (!app_info.app_id.empty()) { |
| + entries->push_back(new RegistryEntry( |
| + prog_id_path, ShellUtil::kRegAppUserModelId, app_info.app_id)); |
| + } |
| - // Add \Software\Classes\ChromeHTML\Application entries |
| - base::string16 chrome_application(chrome_html_prog_id + |
| - ShellUtil::kRegApplication); |
| - entries->push_back(new RegistryEntry( |
| - chrome_application, ShellUtil::kRegAppUserModelId, app_id)); |
| - entries->push_back(new RegistryEntry( |
| - chrome_application, ShellUtil::kRegApplicationIcon, icon_path)); |
| - // TODO(grt): http://crbug.com/75152 Write a reference to a localized |
| - // resource for name, description, and company. |
| - entries->push_back(new RegistryEntry( |
| - chrome_application, ShellUtil::kRegApplicationName, |
| - dist->GetDisplayName())); |
| - entries->push_back(new RegistryEntry( |
| - chrome_application, ShellUtil::kRegApplicationDescription, |
| - dist->GetAppDescription())); |
| - entries->push_back(new RegistryEntry( |
| - chrome_application, ShellUtil::kRegApplicationCompany, |
| - dist->GetPublisherName())); |
| + // Add \Software\Classes\<prog_id>\Application entries |
| + base::string16 application_path(prog_id_path + |
| + ShellUtil::kRegApplication); |
| + if (!app_info.app_id.empty()) { |
| + entries->push_back(new RegistryEntry( |
| + application_path, ShellUtil::kRegAppUserModelId, app_info.app_id)); |
| + } |
| + if (!app_info.application_icon_path.empty()) { |
| + entries->push_back(new RegistryEntry( |
| + application_path, |
| + ShellUtil::kRegApplicationIcon, |
| + ShellUtil::FormatIconLocation(app_info.application_icon_path, |
| + app_info.application_icon_index))); |
| + } |
| + if (!app_info.application_name.empty()) { |
| + entries->push_back(new RegistryEntry(application_path, |
| + ShellUtil::kRegApplicationName, |
| + app_info.application_name)); |
| + } |
| + if (!app_info.application_description.empty()) { |
| + entries->push_back( |
| + new RegistryEntry(application_path, |
| + ShellUtil::kRegApplicationDescription, |
| + app_info.application_description)); |
| + } |
| + if (!app_info.publisher_name.empty()) { |
| + entries->push_back(new RegistryEntry(application_path, |
| + ShellUtil::kRegApplicationCompany, |
| + app_info.publisher_name)); |
| + } |
| } |
| } |
| @@ -433,9 +512,10 @@ class RegistryEntry { |
| // - File Associations |
| // http://msdn.microsoft.com/en-us/library/bb166549 |
| // These entries need to be registered in HKLM prior to Win8. |
| - static void GetAppRegistrationEntries(const base::string16& chrome_exe, |
| - const base::string16& suffix, |
| - ScopedVector<RegistryEntry>* entries) { |
| + static void GetChromeAppRegistrationEntries( |
| + const base::string16& chrome_exe, |
| + const base::string16& suffix, |
| + ScopedVector<RegistryEntry>* entries) { |
| const base::FilePath chrome_path(chrome_exe); |
| base::string16 app_path_key(ShellUtil::kAppPathsRegistryKey); |
| app_path_key.push_back(base::FilePath::kSeparators[0]); |
| @@ -446,13 +526,55 @@ class RegistryEntry { |
| const base::string16 html_prog_id(GetBrowserProgId(suffix)); |
| for (int i = 0; ShellUtil::kPotentialFileAssociations[i] != NULL; i++) { |
| - base::string16 key(ShellUtil::kRegClasses); |
| - key.push_back(base::FilePath::kSeparators[0]); |
| - key.append(ShellUtil::kPotentialFileAssociations[i]); |
| - key.push_back(base::FilePath::kSeparators[0]); |
| - key.append(ShellUtil::kRegOpenWithProgids); |
| - entries->push_back( |
| - new RegistryEntry(key, html_prog_id, base::string16())); |
| + GetAppExtRegistrationEntries( |
| + html_prog_id, ShellUtil::kPotentialFileAssociations[i], entries); |
| + } |
| + } |
| + |
| + // Gets the registry entries to register an application as a handler for a |
| + // particular file extension. |prog_id| is the ProgId used by Windows for the |
| + // application. |ext| is the file extension, which must begin with a '.'. |
| + static void GetAppExtRegistrationEntries( |
| + const base::string16& prog_id, |
| + const base::string16& ext, |
| + ScopedVector<RegistryEntry>* entries) { |
| + // In HKEY_CURRENT_USER\Software\Classes\EXT\OpenWithProgids, create an |
| + // empty value with this class's ProgId. |
| + base::string16 key_name(ShellUtil::kRegClasses); |
| + key_name.push_back(base::FilePath::kSeparators[0]); |
| + key_name.append(ext); |
| + key_name.push_back(base::FilePath::kSeparators[0]); |
| + key_name.append(ShellUtil::kRegOpenWithProgids); |
| + entries->push_back(new RegistryEntry(key_name, prog_id, base::string16())); |
| + } |
| + |
| + // Gets the registry entries to register an application as the default handler |
| + // for a particular file extension. |prog_id| is the ProgId used by Windows |
| + // for the application. |ext| is the file extension, which must begin with a |
| + // '.'. If |overwrite_existing|, always sets the default handler; otherwise |
| + // only sets if there is no existing default. |
| + // |
| + // This has no effect on Windows 8. Windows 8 ignores the default and lets the |
| + // user choose. If there is only one handler for a file, it will automatically |
| + // become the default. Otherwise, the first time the user opens a file, they |
| + // are presented with the dialog to set the default handler. (This is roughly |
| + // equivalent to being called with |overwrite_existing| false.) |
| + static void GetAppDefaultRegistrationEntries( |
| + const base::string16& prog_id, |
| + const base::string16& ext, |
| + bool overwrite_existing, |
| + ScopedVector<RegistryEntry>* entries) { |
| + // Set the default value of HKEY_CURRENT_USER\Software\Classes\EXT to this |
| + // class's name. |
| + base::string16 key_name(ShellUtil::kRegClasses); |
| + key_name.push_back(base::FilePath::kSeparators[0]); |
| + key_name.append(ext); |
| + scoped_ptr<RegistryEntry> default_association( |
| + new RegistryEntry(key_name, prog_id)); |
| + if (overwrite_existing || |
| + !default_association->KeyExistsInRegistry( |
| + RegistryEntry::LOOK_IN_HKCU)) { |
| + entries->push_back(default_association.release()); |
| } |
| } |
| @@ -504,10 +626,8 @@ class RegistryEntry { |
| // File extension associations. |
| base::string16 html_prog_id(GetBrowserProgId(suffix)); |
| for (int i = 0; ShellUtil::kDefaultFileAssociations[i] != NULL; i++) { |
| - base::string16 ext_key(ShellUtil::kRegClasses); |
| - ext_key.push_back(base::FilePath::kSeparators[0]); |
| - ext_key.append(ShellUtil::kDefaultFileAssociations[i]); |
| - entries->push_back(new RegistryEntry(ext_key, html_prog_id)); |
| + GetAppDefaultRegistrationEntries( |
| + html_prog_id, ShellUtil::kDefaultFileAssociations[i], true, entries); |
| } |
| // Protocols associations. |
| @@ -563,6 +683,21 @@ class RegistryEntry { |
| return status == SAME_VALUE; |
| } |
| + // Checks if the current registry entry exists in \|key_path_|\|name_|, |
| + // regardless of value. Same lookup rules as ExistsInRegistry. |
| + // Unlike ExistsInRegistry, this returns true if some other value is present |
| + // with the same key. |
| + bool KeyExistsInRegistry(uint32 look_for_in) const { |
| + DCHECK(look_for_in); |
| + |
| + RegistryStatus status = DOES_NOT_EXIST; |
| + if (look_for_in & LOOK_IN_HKCU) |
| + status = StatusInRegistryUnderRoot(HKEY_CURRENT_USER); |
| + if (status == DOES_NOT_EXIST && (look_for_in & LOOK_IN_HKLM)) |
| + status = StatusInRegistryUnderRoot(HKEY_LOCAL_MACHINE); |
| + return status != DOES_NOT_EXIST; |
| + } |
| + |
| private: |
| // States this RegistryKey can be in compared to the registry. |
| enum RegistryStatus { |
| @@ -676,9 +811,9 @@ bool IsChromeRegistered(BrowserDistribution* dist, |
| const base::string16& suffix, |
| uint32 look_for_in) { |
| ScopedVector<RegistryEntry> entries; |
| - RegistryEntry::GetProgIdEntries(dist, chrome_exe, suffix, &entries); |
| + RegistryEntry::GetChromeProgIdEntries(dist, chrome_exe, suffix, &entries); |
| RegistryEntry::GetShellIntegrationEntries(dist, chrome_exe, suffix, &entries); |
| - RegistryEntry::GetAppRegistrationEntries(chrome_exe, suffix, &entries); |
| + RegistryEntry::GetChromeAppRegistrationEntries(chrome_exe, suffix, &entries); |
| return AreEntriesRegistered(entries, look_for_in); |
| } |
| @@ -2063,10 +2198,10 @@ bool ShellUtil::RegisterChromeBrowser(BrowserDistribution* dist, |
| // an admin. |
| ScopedVector<RegistryEntry> progid_and_appreg_entries; |
| ScopedVector<RegistryEntry> shell_entries; |
| - RegistryEntry::GetProgIdEntries(dist, chrome_exe, suffix, |
| - &progid_and_appreg_entries); |
| - RegistryEntry::GetAppRegistrationEntries(chrome_exe, suffix, |
| - &progid_and_appreg_entries); |
| + RegistryEntry::GetChromeProgIdEntries( |
| + dist, chrome_exe, suffix, &progid_and_appreg_entries); |
| + RegistryEntry::GetChromeAppRegistrationEntries( |
| + chrome_exe, suffix, &progid_and_appreg_entries); |
| RegistryEntry::GetShellIntegrationEntries( |
| dist, chrome_exe, suffix, &shell_entries); |
| result = (AddRegistryEntries(root, progid_and_appreg_entries) && |
| @@ -2083,7 +2218,7 @@ bool ShellUtil::RegisterChromeBrowser(BrowserDistribution* dist, |
| // If we got to this point then all we can do is create ProgId and basic app |
| // registrations under HKCU. |
| ScopedVector<RegistryEntry> entries; |
| - RegistryEntry::GetProgIdEntries( |
| + RegistryEntry::GetChromeProgIdEntries( |
| dist, chrome_exe, base::string16(), &entries); |
| // Prefer to use |suffix|; unless Chrome's ProgIds are already registered |
| // with no suffix (as per the old registration style): in which case some |
| @@ -2092,8 +2227,10 @@ bool ShellUtil::RegisterChromeBrowser(BrowserDistribution* dist, |
| if (!AreEntriesRegistered(entries, RegistryEntry::LOOK_IN_HKCU)) { |
| if (!suffix.empty()) { |
| entries.clear(); |
| - RegistryEntry::GetProgIdEntries(dist, chrome_exe, suffix, &entries); |
| - RegistryEntry::GetAppRegistrationEntries(chrome_exe, suffix, &entries); |
| + RegistryEntry::GetChromeProgIdEntries( |
| + dist, chrome_exe, suffix, &entries); |
| + RegistryEntry::GetChromeAppRegistrationEntries( |
| + chrome_exe, suffix, &entries); |
| } |
| result = AddRegistryEntries(HKEY_CURRENT_USER, entries); |
| } else { |
| @@ -2102,8 +2239,8 @@ bool ShellUtil::RegisterChromeBrowser(BrowserDistribution* dist, |
| // thus needs to be done after the above check for the unsuffixed |
| // registration). |
| entries.clear(); |
| - RegistryEntry::GetAppRegistrationEntries(chrome_exe, base::string16(), |
| - &entries); |
| + RegistryEntry::GetChromeAppRegistrationEntries( |
| + chrome_exe, base::string16(), &entries); |
| result = AddRegistryEntries(HKEY_CURRENT_USER, entries); |
| } |
| } |
| @@ -2292,3 +2429,58 @@ base::string16 ShellUtil::ByteArrayToBase32(const uint8* bytes, size_t size) { |
| DCHECK_EQ(ret.length(), encoded_length); |
| return ret; |
| } |
| + |
| +// static |
| +bool ShellUtil::AddFileAssociations( |
| + const base::string16& prog_id, |
| + const base::CommandLine& command_line, |
| + const base::string16& file_type_name, |
| + const base::FilePath& icon_path, |
| + const std::set<base::string16>& file_extensions) { |
| + ScopedVector<RegistryEntry> entries; |
| + |
| + // Create a class for this app. |
| + RegistryEntry::ApplicationInfo app_info; |
| + app_info.prog_id = prog_id; |
| + app_info.file_type_name = file_type_name; |
| + app_info.file_type_icon_path = icon_path.value(); |
| + app_info.file_type_icon_index = 0; |
| + app_info.command_line = command_line.GetCommandLineString(); |
| + RegistryEntry::GetProgIdEntries(app_info, &entries); |
| + |
| + // Associate each extension that the app can handle with the class. Set this |
| + // app as the default handler if and only if there is no existing default. |
| + for (std::set<base::string16>::const_iterator it = file_extensions.begin(); |
| + it != file_extensions.end(); |
| + ++it) { |
| + // Do not allow empty file extensions, or extensions beginning with a '.'. |
| + DCHECK(!it->empty()); |
| + DCHECK_NE(L'.', (*it)[0]); |
| + base::string16 ext(1, L'.'); |
| + ext.append(*it); |
| + RegistryEntry::GetAppExtRegistrationEntries(prog_id, ext, &entries); |
| + |
| + // Regstering as the default will have no effect on Windows 8 (see |
| + // documentation for GetAppDefaultRegistrationEntries). However, if our app |
| + // is the only handler, it will automatically become the default, so the |
| + // same effect is achieved. |
| + RegistryEntry::GetAppDefaultRegistrationEntries( |
| + prog_id, ext, false, &entries); |
| + } |
| + |
| + return AddRegistryEntries(HKEY_CURRENT_USER, entries); |
| +} |
| + |
| +// static |
| +bool ShellUtil::DeleteFileAssociations(const base::string16& prog_id) { |
| + // Delete the key HKEY_CURRENT_USER\Software\Classes\PROGID. |
| + base::string16 key_path(ShellUtil::kRegClasses); |
| + key_path.push_back(base::FilePath::kSeparators[0]); |
| + key_path.append(prog_id); |
| + return InstallUtil::DeleteRegistryKey( |
| + HKEY_CURRENT_USER, key_path, WorkItem::kWow64Default); |
| + |
| + // TODO(mgiuca): Remove the extension association entries. This requires that |
| + // the extensions associated with a particular prog_id are stored in that |
| + // prog_id's key. |
| +} |