blob: 68fc2cee91727d6b6822508416f4cefe3f3385c1 [file] [log] [blame]
[email protected]b6b72222012-02-11 02:04:131// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]d24c4012009-07-28 01:57:312// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
pmonette32a5cfb42016-04-11 22:04:445#include "chrome/browser/shell_integration_win.h"
[email protected]d24c4012009-07-28 01:57:316
7#include <windows.h>
[email protected]caa05352014-03-01 00:43:058#include <shlwapi.h>
[email protected]d24c4012009-07-28 01:57:319#include <shobjidl.h>
thestig18dfb7a52014-08-26 10:44:0410#include <propkey.h> // Needs to come after shobjidl.h.
avi664c07b2015-12-26 02:18:3111#include <stddef.h>
12#include <stdint.h>
[email protected]d24c4012009-07-28 01:57:3113
pmonette32a5cfb42016-04-11 22:04:4414#include <vector>
15
[email protected]3a3e72c2011-11-29 02:59:3816#include "base/bind.h"
pmonette32a5cfb42016-04-11 22:04:4417#include "base/callback.h"
[email protected]d24c4012009-07-28 01:57:3118#include "base/command_line.h"
[email protected]25a4c1c2013-06-08 04:53:3619#include "base/files/file_enumerator.h"
thestig18dfb7a52014-08-26 10:44:0420#include "base/files/file_util.h"
avi664c07b2015-12-26 02:18:3121#include "base/macros.h"
dcheng4af48582016-04-19 00:29:3522#include "base/memory/ptr_util.h"
pmonette32a5cfb42016-04-11 22:04:4423#include "base/memory/weak_ptr.h"
[email protected]fa1e0e12013-07-18 00:10:1424#include "base/message_loop/message_loop.h"
pmonette32a5cfb42016-04-11 22:04:4425#include "base/metrics/histogram_macros.h"
grtc291eea2016-05-26 15:38:4826#include "base/metrics/user_metrics.h"
27#include "base/metrics/user_metrics_action.h"
[email protected]d24c4012009-07-28 01:57:3128#include "base/path_service.h"
[email protected]24a555b62013-06-10 22:01:1729#include "base/strings/string_util.h"
30#include "base/strings/stringprintf.h"
[email protected]e309f312013-06-07 21:50:0831#include "base/strings/utf_string_conversions.h"
pmonette32a5cfb42016-04-11 22:04:4432#include "base/time/time.h"
33#include "base/timer/timer.h"
[email protected]2d6503982010-10-17 04:41:5434#include "base/win/registry.h"
[email protected]8ee65ba2011-04-12 20:53:2335#include "base/win/scoped_comptr.h"
[email protected]07983302013-01-21 19:41:4436#include "base/win/scoped_propvariant.h"
[email protected]f1024e22012-09-12 07:14:5537#include "base/win/shortcut.h"
[email protected]935aa542010-10-15 01:59:1538#include "base/win/windows_version.h"
[email protected]89d43832013-06-29 20:25:2039#include "chrome/browser/policy/policy_path_parser.h"
pmonette32a5cfb42016-04-11 22:04:4440#include "chrome/browser/shell_integration.h"
[email protected]c9bb06f42010-01-13 23:53:4841#include "chrome/browser/web_applications/web_app.h"
grtc291eea2016-05-26 15:38:4842#include "chrome/browser/win/settings_app_monitor.h"
[email protected]d24c4012009-07-28 01:57:3143#include "chrome/common/chrome_constants.h"
[email protected]12f520c2010-01-06 18:11:1544#include "chrome/common/chrome_paths_internal.h"
[email protected]c9bb06f42010-01-13 23:53:4845#include "chrome/common/chrome_switches.h"
pmonette5057ca3b2016-07-04 16:48:4046#include "chrome/common/shell_handler_win.mojom.h"
47#include "chrome/grit/generated_resources.h"
[email protected]4468a5b2011-05-26 07:48:0248#include "chrome/installer/setup/setup_util.h"
[email protected]d24c4012009-07-28 01:57:3149#include "chrome/installer/util/browser_distribution.h"
50#include "chrome/installer/util/create_reg_key_work_item.h"
[email protected]3f69d6e612012-08-03 18:52:2751#include "chrome/installer/util/install_util.h"
pmonette0c40087a2016-04-21 00:05:1652#include "chrome/installer/util/scoped_user_protocol_entry.h"
[email protected]d24c4012009-07-28 01:57:3153#include "chrome/installer/util/set_reg_value_work_item.h"
54#include "chrome/installer/util/shell_util.h"
55#include "chrome/installer/util/util_constants.h"
56#include "chrome/installer/util/work_item.h"
57#include "chrome/installer/util/work_item_list.h"
pmonette2b1dbee2016-01-08 20:18:5858#include "components/variations/variations_associated_data.h"
[email protected]c38831a12011-10-28 12:44:4959#include "content/public/browser/browser_thread.h"
pmonette5057ca3b2016-07-04 16:48:4060#include "content/public/browser/utility_process_mojo_client.h"
61#include "ui/base/l10n/l10n_util.h"
[email protected]d24c4012009-07-28 01:57:3162
[email protected]631bb742011-11-02 11:29:3963using content::BrowserThread;
64
pmonette9fa59e882016-02-10 00:12:1965namespace shell_integration {
66
[email protected]12f520c2010-01-06 18:11:1567namespace {
68
pmonette9fa59e882016-02-10 00:12:1969// Helper function for GetAppId to generates profile id
[email protected]2f1c09d2011-01-14 14:58:1470// from profile path. "profile_id" is composed of sanitized basenames of
[email protected]12f520c2010-01-06 18:11:1571// user data dir and profile dir joined by a ".".
[email protected]6a72a632013-12-12 22:22:0072base::string16 GetProfileIdFromPath(const base::FilePath& profile_path) {
[email protected]12f520c2010-01-06 18:11:1573 // Return empty string if profile_path is empty
74 if (profile_path.empty())
[email protected]0085863a2013-12-06 21:19:0375 return base::string16();
[email protected]12f520c2010-01-06 18:11:1576
[email protected]650b2d52013-02-10 03:41:4577 base::FilePath default_user_data_dir;
[email protected]12f520c2010-01-06 18:11:1578 // Return empty string if profile_path is in default user data
79 // dir and is the default profile.
80 if (chrome::GetDefaultUserDataDirectory(&default_user_data_dir) &&
81 profile_path.DirName() == default_user_data_dir &&
[email protected]162b5992011-03-15 19:40:4882 profile_path.BaseName().value() ==
[email protected]f911df52013-12-24 23:24:2383 base::ASCIIToUTF16(chrome::kInitialProfile)) {
[email protected]0085863a2013-12-06 21:19:0384 return base::string16();
[email protected]162b5992011-03-15 19:40:4885 }
[email protected]12f520c2010-01-06 18:11:1586
87 // Get joined basenames of user data dir and profile.
[email protected]0085863a2013-12-06 21:19:0388 base::string16 basenames = profile_path.DirName().BaseName().value() +
[email protected]12f520c2010-01-06 18:11:1589 L"." + profile_path.BaseName().value();
90
[email protected]0085863a2013-12-06 21:19:0391 base::string16 profile_id;
[email protected]12f520c2010-01-06 18:11:1592 profile_id.reserve(basenames.size());
93
94 // Generate profile_id from sanitized basenames.
95 for (size_t i = 0; i < basenames.length(); ++i) {
brettwb3413062015-06-24 00:39:0296 if (base::IsAsciiAlpha(basenames[i]) ||
97 base::IsAsciiDigit(basenames[i]) ||
[email protected]12f520c2010-01-06 18:11:1598 basenames[i] == L'.')
99 profile_id += basenames[i];
100 }
101
102 return profile_id;
103}
104
taptedc2c690ab2016-05-25 02:49:03105base::string16 GetAppListAppName() {
106 static const base::char16 kAppListAppNameSuffix[] = L"AppList";
107 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
108 base::string16 app_name(dist->GetBaseAppId());
109 app_name.append(kAppListAppNameSuffix);
110 return app_name;
111}
112
[email protected]8ea8f1ef2013-01-06 18:39:03113// Gets expected app id for given Chrome (based on |command_line| and
114// |is_per_user_install|).
avi556c05022014-12-22 23:31:43115base::string16 GetExpectedAppId(const base::CommandLine& command_line,
[email protected]6a72a632013-12-12 22:22:00116 bool is_per_user_install) {
[email protected]b39e32e2013-04-24 08:55:54117 base::FilePath user_data_dir;
118 if (command_line.HasSwitch(switches::kUserDataDir))
119 user_data_dir = command_line.GetSwitchValuePath(switches::kUserDataDir);
120 else
121 chrome::GetDefaultUserDataDirectory(&user_data_dir);
[email protected]89d43832013-06-29 20:25:20122 // Adjust with any policy that overrides any other way to set the path.
123 policy::path_parser::CheckUserDataDirPolicy(&user_data_dir);
[email protected]b39e32e2013-04-24 08:55:54124 DCHECK(!user_data_dir.empty());
[email protected]c9bb06f42010-01-13 23:53:48125
[email protected]b39e32e2013-04-24 08:55:54126 base::FilePath profile_subdir;
127 if (command_line.HasSwitch(switches::kProfileDirectory)) {
128 profile_subdir =
129 command_line.GetSwitchValuePath(switches::kProfileDirectory);
130 } else {
[email protected]f911df52013-12-24 23:24:23131 profile_subdir =
132 base::FilePath(base::ASCIIToUTF16(chrome::kInitialProfile));
[email protected]b39e32e2013-04-24 08:55:54133 }
134 DCHECK(!profile_subdir.empty());
135
136 base::FilePath profile_path = user_data_dir.Append(profile_subdir);
[email protected]0085863a2013-12-06 21:19:03137 base::string16 app_name;
[email protected]c9bb06f42010-01-13 23:53:48138 if (command_line.HasSwitch(switches::kApp)) {
[email protected]f911df52013-12-24 23:24:23139 app_name = base::UTF8ToUTF16(web_app::GenerateApplicationNameFromURL(
[email protected]57ecc4b2010-08-11 03:02:51140 GURL(command_line.GetSwitchValueASCII(switches::kApp))));
[email protected]2f1c09d2011-01-14 14:58:14141 } else if (command_line.HasSwitch(switches::kAppId)) {
[email protected]f911df52013-12-24 23:24:23142 app_name = base::UTF8ToUTF16(
143 web_app::GenerateApplicationNameFromExtensionId(
144 command_line.GetSwitchValueASCII(switches::kAppId)));
taptedc2c690ab2016-05-25 02:49:03145 } else if (command_line.HasSwitch(switches::kShowAppList)) {
146 app_name = GetAppListAppName();
[email protected]c9bb06f42010-01-13 23:53:48147 } else {
[email protected]a0448002012-06-19 04:32:10148 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
[email protected]8ea8f1ef2013-01-06 18:39:03149 app_name = ShellUtil::GetBrowserModelId(dist, is_per_user_install);
[email protected]c9bb06f42010-01-13 23:53:48150 }
[email protected]b39e32e2013-04-24 08:55:54151 DCHECK(!app_name.empty());
[email protected]c9bb06f42010-01-13 23:53:48152
pmonette9e4c1a82016-04-14 18:15:30153 return win::GetAppModelIdForProfile(app_name, profile_path);
[email protected]c9bb06f42010-01-13 23:53:48154}
155
gab88257b62016-01-15 03:19:19156void MigrateTaskbarPinsCallback() {
[email protected]3a3e72c2011-11-29 02:59:38157 // This should run on the file thread.
thestig00844cea2015-09-08 21:44:52158 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
[email protected]3a3e72c2011-11-29 02:59:38159
160 // Get full path of chrome.
[email protected]650b2d52013-02-10 03:41:45161 base::FilePath chrome_exe;
[email protected]3a3e72c2011-11-29 02:59:38162 if (!PathService::Get(base::FILE_EXE, &chrome_exe))
163 return;
164
gab88257b62016-01-15 03:19:19165 base::FilePath pins_path;
166 if (!PathService::Get(base::DIR_TASKBAR_PINS, &pins_path)) {
167 NOTREACHED();
168 return;
[email protected]3a3e72c2011-11-29 02:59:38169 }
gab88257b62016-01-15 03:19:19170
pmonette9e4c1a82016-04-14 18:15:30171 win::MigrateShortcutsInPathInternal(chrome_exe, pins_path);
[email protected]3a3e72c2011-11-29 02:59:38172}
173
[email protected]caa05352014-03-01 00:43:05174// Windows 8 introduced a new protocol->executable binding system which cannot
175// be retrieved in the HKCR registry subkey method implemented below. We call
176// AssocQueryString with the new Win8-only flag ASSOCF_IS_PROTOCOL instead.
177base::string16 GetAppForProtocolUsingAssocQuery(const GURL& url) {
thestig11bf74d2014-11-24 20:14:42178 base::string16 url_scheme = base::ASCIIToUTF16(url.scheme());
[email protected]caa05352014-03-01 00:43:05179 // Don't attempt to query protocol association on an empty string.
[email protected]6e84d372014-05-29 23:36:39180 if (url_scheme.empty())
[email protected]caa05352014-03-01 00:43:05181 return base::string16();
182
[email protected]caa05352014-03-01 00:43:05183 // Query AssocQueryString for a human-readable description of the program
184 // that will be invoked given the provided URL spec. This is used only to
185 // populate the external protocol dialog box the user sees when invoking
186 // an unknown external protocol.
187 wchar_t out_buffer[1024];
188 DWORD buffer_size = arraysize(out_buffer);
189 HRESULT hr = AssocQueryString(ASSOCF_IS_PROTOCOL,
190 ASSOCSTR_FRIENDLYAPPNAME,
[email protected]6e84d372014-05-29 23:36:39191 url_scheme.c_str(),
[email protected]caa05352014-03-01 00:43:05192 NULL,
193 out_buffer,
194 &buffer_size);
195 if (FAILED(hr)) {
196 DLOG(WARNING) << "AssocQueryString failed!";
197 return base::string16();
198 }
199 return base::string16(out_buffer);
200}
201
202base::string16 GetAppForProtocolUsingRegistry(const GURL& url) {
dominickn0c9a5062016-10-06 20:49:00203 base::string16 command_to_launch;
204
205 // First, try and extract the application's display name.
206 base::string16 cmd_key_path = base::ASCIIToUTF16(url.scheme());
207 base::win::RegKey cmd_key_name(HKEY_CLASSES_ROOT, cmd_key_path.c_str(),
208 KEY_READ);
209 if (cmd_key_name.ReadValue(NULL, &command_to_launch) == ERROR_SUCCESS &&
210 !command_to_launch.empty()) {
211 return command_to_launch;
[email protected]caa05352014-03-01 00:43:05212 }
dominickn0c9a5062016-10-06 20:49:00213
214 // Otherwise, parse the command line in the registry, and return the basename
215 // of the program path if it exists.
216 cmd_key_path = base::ASCIIToUTF16(url.scheme() + "\\shell\\open\\command");
217 base::win::RegKey cmd_key_exe(HKEY_CLASSES_ROOT, cmd_key_path.c_str(),
218 KEY_READ);
219 if (cmd_key_exe.ReadValue(NULL, &command_to_launch) == ERROR_SUCCESS) {
220 base::CommandLine command_line(
221 base::CommandLine::FromString(command_to_launch));
222 return command_line.GetProgram().BaseName().value();
223 }
224
[email protected]caa05352014-03-01 00:43:05225 return base::string16();
226}
227
pmonette9fa59e882016-02-10 00:12:19228DefaultWebClientState GetDefaultWebClientStateFromShellUtilDefaultState(
229 ShellUtil::DefaultState default_state) {
[email protected]eb63da72012-10-15 20:39:48230 switch (default_state) {
231 case ShellUtil::NOT_DEFAULT:
pmonette9fa59e882016-02-10 00:12:19232 return DefaultWebClientState::NOT_DEFAULT;
[email protected]eb63da72012-10-15 20:39:48233 case ShellUtil::IS_DEFAULT:
pmonette9fa59e882016-02-10 00:12:19234 return DefaultWebClientState::IS_DEFAULT;
[email protected]eb63da72012-10-15 20:39:48235 default:
236 DCHECK_EQ(ShellUtil::UNKNOWN_DEFAULT, default_state);
pmonette9fa59e882016-02-10 00:12:19237 return DefaultWebClientState::UNKNOWN_DEFAULT;
[email protected]eb63da72012-10-15 20:39:48238 }
239}
240
grtc291eea2016-05-26 15:38:48241// A recorder of user actions in the Windows Settings app.
242class DefaultBrowserActionRecorder : public win::SettingsAppMonitor::Delegate {
243 public:
244 // Creates the recorder and the monitor that drives it. |continuation| will be
245 // run once the monitor's initialization completes (regardless of success or
246 // failure).
247 explicit DefaultBrowserActionRecorder(base::Closure continuation)
248 : continuation_(std::move(continuation)), settings_app_monitor_(this) {}
249
250 private:
251 // win::SettingsAppMonitor::Delegate:
252 void OnInitialized(HRESULT result) override {
253 UMA_HISTOGRAM_BOOLEAN("SettingsAppMonitor.InitializationResult",
254 SUCCEEDED(result));
255 if (SUCCEEDED(result)) {
256 base::RecordAction(
257 base::UserMetricsAction("SettingsAppMonitor.Initialized"));
258 }
259 continuation_.Run();
260 continuation_ = base::Closure();
261 }
262
263 void OnAppFocused() override {
264 base::RecordAction(
265 base::UserMetricsAction("SettingsAppMonitor.AppFocused"));
266 }
267
268 void OnChooserInvoked() override {
269 base::RecordAction(
270 base::UserMetricsAction("SettingsAppMonitor.ChooserInvoked"));
271 }
272
273 void OnBrowserChosen(const base::string16& browser_name) override {
274 if (browser_name ==
275 BrowserDistribution::GetDistribution()->GetDisplayName()) {
276 base::RecordAction(
277 base::UserMetricsAction("SettingsAppMonitor.ChromeBrowserChosen"));
278 } else {
279 base::RecordAction(
280 base::UserMetricsAction("SettingsAppMonitor.OtherBrowserChosen"));
281 }
282 }
283
284 // A closure to be run once initialization completes.
285 base::Closure continuation_;
286
287 // Monitors user interaction with the Windows Settings app for the sake of
288 // reporting user actions.
289 win::SettingsAppMonitor settings_app_monitor_;
290
291 DISALLOW_COPY_AND_ASSIGN(DefaultBrowserActionRecorder);
292};
293
294// A function bound up in a callback with a DefaultBrowserActionRecorder and
295// a closure to keep the former alive until the time comes to run the latter.
296void OnSettingsAppFinished(
297 std::unique_ptr<DefaultBrowserActionRecorder> recorder,
298 const base::Closure& on_finished_callback) {
299 recorder.reset();
300 on_finished_callback.Run();
301}
302
pmonette32a5cfb42016-04-11 22:04:44303// There is no way to make sure the user is done with the system settings, but a
304// signal that the interaction is finished is needed for UMA. A timer of 2
305// minutes is used as a substitute. The registry keys for the protocol
306// association with an app are also monitored to signal the end of the
307// interaction early when it is clear that the user made a choice (e.g. http
308// and https for default browser).
309//
310// This helper class manages both the timer and the registry watchers and makes
311// sure the callback for the end of the settings interaction is only run once.
312// This class also manages its own lifetime.
313class OpenSystemSettingsHelper {
314 public:
315 // Begin the monitoring and will call |on_finished_callback| when done.
316 // Takes in a null-terminated array of |protocols| whose registry keys must be
pmonette0c40087a2016-04-21 00:05:16317 // watched. The array must contain at least one element.
pmonette32a5cfb42016-04-11 22:04:44318 static void Begin(const wchar_t* const protocols[],
319 const base::Closure& on_finished_callback) {
pmonettec541d942016-06-15 22:08:32320 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
321
322 delete instance_;
323 instance_ = new OpenSystemSettingsHelper(protocols, on_finished_callback);
pmonette32a5cfb42016-04-11 22:04:44324 }
325
326 private:
327 // The reason the settings interaction concluded. Do not modify the ordering
328 // because it is used for UMA.
329 enum ConcludeReason { REGISTRY_WATCHER, TIMEOUT, NUM_CONCLUDE_REASON_TYPES };
330
331 OpenSystemSettingsHelper(const wchar_t* const protocols[],
332 const base::Closure& on_finished_callback)
pmonette0c40087a2016-04-21 00:05:16333 : scoped_user_protocol_entry_(protocols[0]),
334 on_finished_callback_(on_finished_callback),
335 weak_ptr_factory_(this) {
pmonette32a5cfb42016-04-11 22:04:44336 static const wchar_t kUrlAssociationFormat[] =
337 L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\"
338 L"%ls\\UserChoice";
339
340 // Remember the start time.
341 start_time_ = base::TimeTicks::Now();
342
343 for (const wchar_t* const* scan = &protocols[0]; *scan != nullptr; ++scan) {
344 AddRegistryKeyWatcher(
345 base::StringPrintf(kUrlAssociationFormat, *scan).c_str());
346 }
347 // Only the watchers that were succesfully initialized are counted.
348 registry_watcher_count_ = registry_key_watchers_.size();
349
350 timer_.Start(
351 FROM_HERE, base::TimeDelta::FromMinutes(2),
352 base::Bind(&OpenSystemSettingsHelper::ConcludeInteraction,
353 weak_ptr_factory_.GetWeakPtr(), ConcludeReason::TIMEOUT));
354 }
355
356 // Called when a change is detected on one of the registry keys being watched.
357 // Note: All types of modification to the registry key will trigger this
358 // function even if the value change is the only one that matters. This
359 // is good enough for now.
360 void OnRegistryKeyChanged() {
361 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
362
363 // Make sure all the registry watchers have fired.
364 if (--registry_watcher_count_ == 0) {
365 UMA_HISTOGRAM_MEDIUM_TIMES(
366 "DefaultBrowser.SettingsInteraction.RegistryWatcherDuration",
367 base::TimeTicks::Now() - start_time_);
368
369 ConcludeInteraction(ConcludeReason::REGISTRY_WATCHER);
370 }
371 }
372
373 // Ends the monitoring with the system settings. Will call
374 // |on_finished_callback_| and then dispose of this class instance to make
375 // sure the callback won't get called subsequently.
376 void ConcludeInteraction(ConcludeReason conclude_reason) {
377 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
378
379 UMA_HISTOGRAM_ENUMERATION(
380 "DefaultBrowser.SettingsInteraction.ConcludeReason", conclude_reason,
381 NUM_CONCLUDE_REASON_TYPES);
382 on_finished_callback_.Run();
pmonettec541d942016-06-15 22:08:32383 delete instance_;
384 instance_ = nullptr;
pmonette32a5cfb42016-04-11 22:04:44385 }
386
387 // Helper function to create a registry watcher for a given |key_path|. Do
388 // nothing on initialization failure.
389 void AddRegistryKeyWatcher(const wchar_t* key_path) {
ricea86fa1dd2016-09-13 05:59:45390 auto reg_key = base::MakeUnique<base::win::RegKey>(HKEY_CURRENT_USER,
391 key_path, KEY_NOTIFY);
pmonette32a5cfb42016-04-11 22:04:44392
393 if (reg_key->Valid() &&
394 reg_key->StartWatching(
395 base::Bind(&OpenSystemSettingsHelper::OnRegistryKeyChanged,
396 weak_ptr_factory_.GetWeakPtr()))) {
397 registry_key_watchers_.push_back(std::move(reg_key));
398 }
399 }
400
pmonettec541d942016-06-15 22:08:32401 // Used to make sure only one instance is alive at the same time.
402 static OpenSystemSettingsHelper* instance_;
403
pmonette0c40087a2016-04-21 00:05:16404 // This is needed to make sure that Windows displays an entry for the protocol
405 // inside the "Choose default apps by protocol" settings page.
406 ScopedUserProtocolEntry scoped_user_protocol_entry_;
407
pmonette32a5cfb42016-04-11 22:04:44408 // The function to call when the interaction with the system settings is
409 // finished.
410 base::Closure on_finished_callback_;
411
412 // The number of time the registry key watchers must fire.
413 int registry_watcher_count_ = 0;
414
415 // There can be multiple registry key watchers as some settings modify
416 // multiple protocol associations. e.g. Changing the default browser modifies
417 // the http and https associations.
dcheng4af48582016-04-19 00:29:35418 std::vector<std::unique_ptr<base::win::RegKey>> registry_key_watchers_;
pmonette32a5cfb42016-04-11 22:04:44419
420 base::OneShotTimer timer_;
421
422 // Records the time it takes for the final registry watcher to get signaled.
423 base::TimeTicks start_time_;
424
425 // Weak ptrs are used to bind this class to the callbacks of the timer and the
426 // registry watcher. This makes it possible to self-delete after one of the
427 // callbacks is executed to cancel the remaining ones.
428 base::WeakPtrFactory<OpenSystemSettingsHelper> weak_ptr_factory_;
429
430 DISALLOW_COPY_AND_ASSIGN(OpenSystemSettingsHelper);
431};
432
pmonettec541d942016-06-15 22:08:32433OpenSystemSettingsHelper* OpenSystemSettingsHelper::instance_ = nullptr;
434
pmonette5057ca3b2016-07-04 16:48:40435void RecordPinnedToTaskbarProcessError(bool error) {
436 UMA_HISTOGRAM_BOOLEAN("Windows.IsPinnedToTaskbar.ProcessError", error);
437}
438
439// Record the UMA histogram when a response is received. The callback that binds
440// to this function holds a reference to the ShellHandlerClient to keep it alive
441// until invokation.
442void OnIsPinnedToTaskbarResult(
443 content::UtilityProcessMojoClient<mojom::ShellHandler>* client,
444 bool succeeded,
445 bool is_pinned_to_taskbar) {
446 // Clean up the utility process.
447 delete client;
448
449 RecordPinnedToTaskbarProcessError(false);
450
451 enum Result { NOT_PINNED, PINNED, FAILURE, NUM_RESULTS };
452
453 Result result = FAILURE;
454 if (succeeded)
455 result = is_pinned_to_taskbar ? PINNED : NOT_PINNED;
456 UMA_HISTOGRAM_ENUMERATION("Windows.IsPinnedToTaskbar", result, NUM_RESULTS);
457}
458
459// Called when a connection error happen with the shell handler process. A call
460// to this function is mutially exclusive with a call to
461// OnIsPinnedToTaskbarResult().
462void OnShellHandlerConnectionError(
463 content::UtilityProcessMojoClient<mojom::ShellHandler>* client) {
464 // Clean up the utility process.
465 delete client;
466
467 RecordPinnedToTaskbarProcessError(true);
468}
469
[email protected]3a3e72c2011-11-29 02:59:38470} // namespace
[email protected]12f520c2010-01-06 18:11:15471
pmonette9fa59e882016-02-10 00:12:19472bool SetAsDefaultBrowser() {
[email protected]650b2d52013-02-10 03:41:45473 base::FilePath chrome_exe;
[email protected]d24c4012009-07-28 01:57:31474 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
475 LOG(ERROR) << "Error getting app exe path";
476 return false;
477 }
478
479 // From UI currently we only allow setting default browser for current user.
[email protected]bf6117c7e2010-12-01 06:00:25480 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
grte76ca2852014-12-05 16:42:10481 if (!ShellUtil::MakeChromeDefault(dist, ShellUtil::CURRENT_USER, chrome_exe,
482 true /* elevate_if_not_admin */)) {
[email protected]d24c4012009-07-28 01:57:31483 LOG(ERROR) << "Chrome could not be set as default browser.";
484 return false;
485 }
486
[email protected]8e96e502010-10-21 20:57:12487 VLOG(1) << "Chrome registered as default browser.";
[email protected]d24c4012009-07-28 01:57:31488 return true;
489}
490
pmonette9fa59e882016-02-10 00:12:19491bool SetAsDefaultProtocolClient(const std::string& protocol) {
[email protected]4468a5b2011-05-26 07:48:02492 if (protocol.empty())
493 return false;
494
[email protected]650b2d52013-02-10 03:41:45495 base::FilePath chrome_exe;
[email protected]4468a5b2011-05-26 07:48:02496 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
497 LOG(ERROR) << "Error getting app exe path";
498 return false;
499 }
500
[email protected]f911df52013-12-24 23:24:23501 base::string16 wprotocol(base::UTF8ToUTF16(protocol));
[email protected]4468a5b2011-05-26 07:48:02502 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
grte76ca2852014-12-05 16:42:10503 if (!ShellUtil::MakeChromeDefaultProtocolClient(dist, chrome_exe,
504 wprotocol)) {
[email protected]4468a5b2011-05-26 07:48:02505 LOG(ERROR) << "Chrome could not be set as default handler for "
506 << protocol << ".";
507 return false;
508 }
509
510 VLOG(1) << "Chrome registered as default handler for " << protocol << ".";
511 return true;
512}
513
pmonette32a5cfb42016-04-11 22:04:44514DefaultWebClientSetPermission GetDefaultWebClientSetPermission() {
pmonette034a03d92015-10-02 21:04:27515 BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
516 if (distribution->GetDefaultBrowserControlPolicy() !=
517 BrowserDistribution::DEFAULT_BROWSER_FULL_CONTROL)
518 return SET_DEFAULT_NOT_ALLOWED;
pmonette034a03d92015-10-02 21:04:27519 if (ShellUtil::CanMakeChromeDefaultUnattended())
520 return SET_DEFAULT_UNATTENDED;
pmonette32a5cfb42016-04-11 22:04:44521 // Windows 8 and 10 both introduced a new way to set the default web client
522 // which require user interaction.
pmonettef89ac7c72015-10-06 03:22:01523 return SET_DEFAULT_INTERACTIVE;
pmonette034a03d92015-10-02 21:04:27524}
525
pmonette9fa59e882016-02-10 00:12:19526bool IsElevationNeededForSettingDefaultProtocolClient() {
pmonette868ca642015-09-02 14:34:02527 return base::win::GetVersion() < base::win::VERSION_WIN8;
528}
529
pmonette9fa59e882016-02-10 00:12:19530base::string16 GetApplicationNameForProtocol(const GURL& url) {
benwellsd0c67aa2016-07-28 02:57:20531 base::string16 application_name;
532 // Windows 8 or above has a new protocol association query.
533 if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
534 application_name = GetAppForProtocolUsingAssocQuery(url);
535 if (!application_name.empty())
536 return application_name;
537 }
538
539 return GetAppForProtocolUsingRegistry(url);
[email protected]42dc9402013-01-30 07:54:20540}
541
pmonette9fa59e882016-02-10 00:12:19542DefaultWebClientState GetDefaultBrowser() {
pmonette034a03d92015-10-02 21:04:27543 return GetDefaultWebClientStateFromShellUtilDefaultState(
544 ShellUtil::GetChromeDefaultState());
545}
546
[email protected]d24c4012009-07-28 01:57:31547// There is no reliable way to say which browser is default on a machine (each
548// browser can have some of the protocols/shortcuts). So we look for only HTTP
549// protocol handler. Even this handler is located at different places in
550// registry on XP and Vista:
551// - HKCR\http\shell\open\command (XP)
552// - HKCU\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\
553// http\UserChoice (Vista)
thakise4a02ff32016-09-20 17:00:42554// This method checks if Firefox is default browser by checking these
[email protected]d24c4012009-07-28 01:57:31555// locations and returns true if Firefox traces are found there. In case of
556// error (or if Firefox is not found)it returns the default value which
557// is false.
pmonette9fa59e882016-02-10 00:12:19558bool IsFirefoxDefaultBrowser() {
[email protected]d24c4012009-07-28 01:57:31559 bool ff_default = false;
[email protected]935aa542010-10-15 01:59:15560 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
[email protected]0085863a2013-12-06 21:19:03561 base::string16 app_cmd;
[email protected]2d6503982010-10-17 04:41:54562 base::win::RegKey key(HKEY_CURRENT_USER,
563 ShellUtil::kRegVistaUrlPrefs, KEY_READ);
[email protected]e06f4d52011-01-19 07:28:46564 if (key.Valid() && (key.ReadValue(L"Progid", &app_cmd) == ERROR_SUCCESS) &&
[email protected]d24c4012009-07-28 01:57:31565 app_cmd == L"FirefoxURL")
566 ff_default = true;
567 } else {
[email protected]0085863a2013-12-06 21:19:03568 base::string16 key_path(L"http");
[email protected]d24c4012009-07-28 01:57:31569 key_path.append(ShellUtil::kRegShellOpen);
[email protected]2d6503982010-10-17 04:41:54570 base::win::RegKey key(HKEY_CLASSES_ROOT, key_path.c_str(), KEY_READ);
[email protected]0085863a2013-12-06 21:19:03571 base::string16 app_cmd;
[email protected]e06f4d52011-01-19 07:28:46572 if (key.Valid() && (key.ReadValue(L"", &app_cmd) == ERROR_SUCCESS) &&
[email protected]cb1f4ac2014-08-07 16:55:42573 base::string16::npos !=
brettwfce8d192015-08-10 19:07:51574 base::ToLowerASCII(app_cmd).find(L"firefox"))
[email protected]d24c4012009-07-28 01:57:31575 ff_default = true;
576 }
577 return ff_default;
578}
[email protected]12f520c2010-01-06 18:11:15579
pmonette9fa59e882016-02-10 00:12:19580DefaultWebClientState IsDefaultProtocolClient(const std::string& protocol) {
pmonette034a03d92015-10-02 21:04:27581 return GetDefaultWebClientStateFromShellUtilDefaultState(
582 ShellUtil::GetChromeDefaultProtocolClientState(
583 base::UTF8ToUTF16(protocol)));
584}
585
pmonette9e4c1a82016-04-14 18:15:30586namespace win {
587
588bool SetAsDefaultBrowserUsingIntentPicker() {
589 base::FilePath chrome_exe;
590 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
591 NOTREACHED() << "Error getting app exe path";
592 return false;
593 }
594
595 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
596 if (!ShellUtil::ShowMakeChromeDefaultSystemUI(dist, chrome_exe)) {
597 LOG(ERROR) << "Failed to launch the set-default-browser Windows UI.";
598 return false;
599 }
600
601 VLOG(1) << "Set-default-browser Windows UI completed.";
602 return true;
603}
604
605void SetAsDefaultBrowserUsingSystemSettings(
606 const base::Closure& on_finished_callback) {
607 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
608
609 base::FilePath chrome_exe;
610 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
611 NOTREACHED() << "Error getting app exe path";
612 on_finished_callback.Run();
613 return;
614 }
615
grtc291eea2016-05-26 15:38:48616 // Create an action recorder that will open the settings app once it has
617 // initialized.
618 std::unique_ptr<DefaultBrowserActionRecorder> recorder(
619 new DefaultBrowserActionRecorder(base::Bind(
620 base::IgnoreResult(&ShellUtil::ShowMakeChromeDefaultSystemUI),
621 base::Unretained(BrowserDistribution::GetDistribution()),
622 chrome_exe)));
pmonette9e4c1a82016-04-14 18:15:30623
grtc291eea2016-05-26 15:38:48624 // The helper manages its own lifetime. Bind the action recorder
625 // into the finished callback to keep it alive throughout the
626 // interaction.
627 static const wchar_t* const kProtocols[] = {L"http", L"https", nullptr};
628 OpenSystemSettingsHelper::Begin(
629 kProtocols, base::Bind(&OnSettingsAppFinished, base::Passed(&recorder),
630 on_finished_callback));
pmonette9e4c1a82016-04-14 18:15:30631}
632
633bool SetAsDefaultProtocolClientUsingIntentPicker(const std::string& protocol) {
634 base::FilePath chrome_exe;
635 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
636 NOTREACHED() << "Error getting app exe path";
637 return false;
638 }
639
640 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
641 base::string16 wprotocol(base::UTF8ToUTF16(protocol));
642 if (!ShellUtil::ShowMakeChromeDefaultProtocolClientSystemUI(dist, chrome_exe,
643 wprotocol)) {
644 LOG(ERROR) << "Failed to launch the set-default-client Windows UI.";
645 return false;
646 }
647
648 VLOG(1) << "Set-default-client Windows UI completed.";
649 return true;
650}
651
pmonette0c40087a2016-04-21 00:05:16652void SetAsDefaultProtocolClientUsingSystemSettings(
653 const std::string& protocol,
654 const base::Closure& on_finished_callback) {
655 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
656
657 base::FilePath chrome_exe;
658 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
659 NOTREACHED() << "Error getting app exe path";
660 on_finished_callback.Run();
661 return;
662 }
663
664 // The helper manages its own lifetime.
665 base::string16 wprotocol(base::UTF8ToUTF16(protocol));
666 const wchar_t* const kProtocols[] = {wprotocol.c_str(), nullptr};
667 OpenSystemSettingsHelper::Begin(kProtocols, on_finished_callback);
668
669 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
670 ShellUtil::ShowMakeChromeDefaultProtocolClientSystemUI(dist, chrome_exe,
671 wprotocol);
672}
673
pmonette9fa59e882016-02-10 00:12:19674base::string16 GetAppModelIdForProfile(const base::string16& app_name,
675 const base::FilePath& profile_path) {
[email protected]d2065e062013-12-12 23:49:52676 std::vector<base::string16> components;
[email protected]a0448002012-06-19 04:32:10677 components.push_back(app_name);
[email protected]0085863a2013-12-06 21:19:03678 const base::string16 profile_id(GetProfileIdFromPath(profile_path));
[email protected]a0448002012-06-19 04:32:10679 if (!profile_id.empty())
680 components.push_back(profile_id);
681 return ShellUtil::BuildAppModelId(components);
[email protected]12f520c2010-01-06 18:11:15682}
683
pmonette9fa59e882016-02-10 00:12:19684base::string16 GetChromiumModelIdForProfile(
[email protected]650b2d52013-02-10 03:41:45685 const base::FilePath& profile_path) {
[email protected]a0448002012-06-19 04:32:10686 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
[email protected]650b2d52013-02-10 03:41:45687 base::FilePath chrome_exe;
[email protected]a0448002012-06-19 04:32:10688 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
689 NOTREACHED();
690 return dist->GetBaseAppId();
691 }
692 return GetAppModelIdForProfile(
grte76ca2852014-12-05 16:42:10693 ShellUtil::GetBrowserModelId(dist,
694 InstallUtil::IsPerUserInstall(chrome_exe)),
[email protected]786799692012-09-26 14:16:48695 profile_path);
[email protected]12f520c2010-01-06 18:11:15696}
[email protected]c9bb06f42010-01-13 23:53:48697
pmonette9fa59e882016-02-10 00:12:19698void MigrateTaskbarPins() {
[email protected]935aa542010-10-15 01:59:15699 if (base::win::GetVersion() < base::win::VERSION_WIN7)
[email protected]c9bb06f42010-01-13 23:53:48700 return;
701
[email protected]81fcf952012-11-26 22:25:14702 // This needs to happen eventually (e.g. so that the appid is fixed and the
703 // run-time Chrome icon is merged with the taskbar shortcut), but this is not
704 // urgent and shouldn't delay Chrome startup.
gab88257b62016-01-15 03:19:19705 static const int64_t kMigrateTaskbarPinsDelaySeconds = 15;
[email protected]81fcf952012-11-26 22:25:14706 BrowserThread::PostDelayedTask(
[email protected]3a3e72c2011-11-29 02:59:38707 BrowserThread::FILE, FROM_HERE,
gab88257b62016-01-15 03:19:19708 base::Bind(&MigrateTaskbarPinsCallback),
709 base::TimeDelta::FromSeconds(kMigrateTaskbarPinsDelaySeconds));
[email protected]c9bb06f42010-01-13 23:53:48710}
[email protected]43903b82012-06-01 05:26:23711
pmonette5057ca3b2016-07-04 16:48:40712void RecordIsPinnedToTaskbarHistogram() {
713 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
714
715 // The code to check if Chrome is pinned to the taskbar brings in shell
716 // extensions which can hinder stability so it is executed in a utility
717 // process.
718 content::UtilityProcessMojoClient<mojom::ShellHandler>* client =
719 new content::UtilityProcessMojoClient<mojom::ShellHandler>(
720 l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_SHELL_HANDLER_NAME));
721
722 client->set_error_callback(
723 base::Bind(&OnShellHandlerConnectionError, client));
724 client->set_disable_sandbox();
725 client->Start();
726
727 client->service()->IsPinnedToTaskbar(
728 base::Bind(&OnIsPinnedToTaskbarResult, client));
729}
730
pmonette9fa59e882016-02-10 00:12:19731int MigrateShortcutsInPathInternal(const base::FilePath& chrome_exe,
732 const base::FilePath& path) {
[email protected]8ea8f1ef2013-01-06 18:39:03733 DCHECK(base::win::GetVersion() >= base::win::VERSION_WIN7);
734
735 // Enumerate all pinned shortcuts in the given path directly.
[email protected]25a4c1c2013-06-08 04:53:36736 base::FileEnumerator shortcuts_enum(
[email protected]8ea8f1ef2013-01-06 18:39:03737 path, false, // not recursive
[email protected]25a4c1c2013-06-08 04:53:36738 base::FileEnumerator::FILES, FILE_PATH_LITERAL("*.lnk"));
[email protected]8ea8f1ef2013-01-06 18:39:03739
grte76ca2852014-12-05 16:42:10740 bool is_per_user_install = InstallUtil::IsPerUserInstall(chrome_exe);
[email protected]8ea8f1ef2013-01-06 18:39:03741
742 int shortcuts_migrated = 0;
[email protected]650b2d52013-02-10 03:41:45743 base::FilePath target_path;
[email protected]0085863a2013-12-06 21:19:03744 base::string16 arguments;
[email protected]07983302013-01-21 19:41:44745 base::win::ScopedPropVariant propvariant;
[email protected]650b2d52013-02-10 03:41:45746 for (base::FilePath shortcut = shortcuts_enum.Next(); !shortcut.empty();
[email protected]8ea8f1ef2013-01-06 18:39:03747 shortcut = shortcuts_enum.Next()) {
748 // TODO(gab): Use ProgramCompare instead of comparing FilePaths below once
749 // it is fixed to work with FilePaths with spaces.
750 if (!base::win::ResolveShortcut(shortcut, &target_path, &arguments) ||
751 chrome_exe != target_path) {
752 continue;
753 }
avi556c05022014-12-22 23:31:43754 base::CommandLine command_line(
755 base::CommandLine::FromString(base::StringPrintf(
756 L"\"%ls\" %ls", target_path.value().c_str(), arguments.c_str())));
[email protected]8ea8f1ef2013-01-06 18:39:03757
758 // Get the expected AppId for this Chrome shortcut.
[email protected]0085863a2013-12-06 21:19:03759 base::string16 expected_app_id(
[email protected]8ea8f1ef2013-01-06 18:39:03760 GetExpectedAppId(command_line, is_per_user_install));
761 if (expected_app_id.empty())
762 continue;
763
764 // Load the shortcut.
765 base::win::ScopedComPtr<IShellLink> shell_link;
766 base::win::ScopedComPtr<IPersistFile> persist_file;
767 if (FAILED(shell_link.CreateInstance(CLSID_ShellLink, NULL,
768 CLSCTX_INPROC_SERVER)) ||
dcheng81762e02014-11-21 21:22:28769 FAILED(persist_file.QueryFrom(shell_link.get())) ||
[email protected]8ea8f1ef2013-01-06 18:39:03770 FAILED(persist_file->Load(shortcut.value().c_str(), STGM_READ))) {
771 DLOG(WARNING) << "Failed loading shortcut at " << shortcut.value();
772 continue;
773 }
774
775 // Any properties that need to be updated on the shortcut will be stored in
776 // |updated_properties|.
777 base::win::ShortcutProperties updated_properties;
778
779 // Validate the existing app id for the shortcut.
780 base::win::ScopedComPtr<IPropertyStore> property_store;
[email protected]07983302013-01-21 19:41:44781 propvariant.Reset();
dcheng81762e02014-11-21 21:22:28782 if (FAILED(property_store.QueryFrom(shell_link.get())) ||
783 property_store->GetValue(PKEY_AppUserModel_ID, propvariant.Receive()) !=
784 S_OK) {
[email protected]8ea8f1ef2013-01-06 18:39:03785 // When in doubt, prefer not updating the shortcut.
786 NOTREACHED();
787 continue;
[email protected]8ea8f1ef2013-01-06 18:39:03788 } else {
[email protected]07983302013-01-21 19:41:44789 switch (propvariant.get().vt) {
790 case VT_EMPTY:
791 // If there is no app_id set, set our app_id if one is expected.
792 if (!expected_app_id.empty())
793 updated_properties.set_app_id(expected_app_id);
794 break;
795 case VT_LPWSTR:
[email protected]0085863a2013-12-06 21:19:03796 if (expected_app_id != base::string16(propvariant.get().pwszVal))
[email protected]07983302013-01-21 19:41:44797 updated_properties.set_app_id(expected_app_id);
798 break;
799 default:
800 NOTREACHED();
801 continue;
[email protected]8ea8f1ef2013-01-06 18:39:03802 }
803 }
804
gab88257b62016-01-15 03:19:19805 // Clear dual_mode property from any shortcuts that previously had it (it
806 // was only ever installed on shortcuts with the
807 // |default_chromium_model_id|).
[email protected]b39e32e2013-04-24 08:55:54808 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
[email protected]0085863a2013-12-06 21:19:03809 base::string16 default_chromium_model_id(
[email protected]b39e32e2013-04-24 08:55:54810 ShellUtil::GetBrowserModelId(dist, is_per_user_install));
gab88257b62016-01-15 03:19:19811 if (expected_app_id == default_chromium_model_id) {
[email protected]07983302013-01-21 19:41:44812 propvariant.Reset();
[email protected]8ea8f1ef2013-01-06 18:39:03813 if (property_store->GetValue(PKEY_AppUserModel_IsDualMode,
[email protected]07983302013-01-21 19:41:44814 propvariant.Receive()) != S_OK) {
[email protected]8ea8f1ef2013-01-06 18:39:03815 // When in doubt, prefer to not update the shortcut.
816 NOTREACHED();
817 continue;
gab38d242d4d2016-01-14 20:38:12818 }
819 if (propvariant.get().vt == VT_BOOL &&
820 !!propvariant.get().boolVal) {
821 updated_properties.set_dual_mode(false);
[email protected]8ea8f1ef2013-01-06 18:39:03822 }
823 }
824
825 persist_file.Release();
826 shell_link.Release();
827
828 // Update the shortcut if some of its properties need to be updated.
829 if (updated_properties.options &&
830 base::win::CreateOrUpdateShortcutLink(
831 shortcut, updated_properties,
832 base::win::SHORTCUT_UPDATE_EXISTING)) {
833 ++shortcuts_migrated;
834 }
835 }
836 return shortcuts_migrated;
837}
838
pmonette9fa59e882016-02-10 00:12:19839base::FilePath GetStartMenuShortcut(const base::FilePath& chrome_exe) {
[email protected]3f69d6e612012-08-03 18:52:27840 static const int kFolderIds[] = {
841 base::DIR_COMMON_START_MENU,
842 base::DIR_START_MENU,
843 };
844 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
taptedd7ef82ae2016-05-13 01:10:19845 const base::string16 shortcut_name(dist->GetShortcutName() +
846 installer::kLnkExt);
grt2be0cd082015-11-12 15:32:21847 base::FilePath programs_folder;
[email protected]650b2d52013-02-10 03:41:45848 base::FilePath shortcut;
[email protected]3f69d6e612012-08-03 18:52:27849
850 // Check both the common and the per-user Start Menu folders for system-level
851 // installs.
grte76ca2852014-12-05 16:42:10852 size_t folder = InstallUtil::IsPerUserInstall(chrome_exe) ? 1 : 0;
[email protected]3f69d6e612012-08-03 18:52:27853 for (; folder < arraysize(kFolderIds); ++folder) {
grt2be0cd082015-11-12 15:32:21854 if (!PathService::Get(kFolderIds[folder], &programs_folder)) {
[email protected]3f69d6e612012-08-03 18:52:27855 NOTREACHED();
856 continue;
857 }
858
grt2be0cd082015-11-12 15:32:21859 shortcut = programs_folder.Append(shortcut_name);
860 if (base::PathExists(shortcut))
861 return shortcut;
[email protected]3f69d6e612012-08-03 18:52:27862 }
863
[email protected]650b2d52013-02-10 03:41:45864 return base::FilePath();
[email protected]3f69d6e612012-08-03 18:52:27865}
pmonettef89ac7c72015-10-06 03:22:01866
pmonette32a5cfb42016-04-11 22:04:44867} // namespace win
868
pmonette9fa59e882016-02-10 00:12:19869} // namespace shell_integration