blob: 87954bbf20853122cf2dbe7c68a0ef83659fe98c [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"
[email protected]4468a5b2011-05-26 07:48:0246#include "chrome/installer/setup/setup_util.h"
[email protected]d24c4012009-07-28 01:57:3147#include "chrome/installer/util/browser_distribution.h"
48#include "chrome/installer/util/create_reg_key_work_item.h"
[email protected]3f69d6e612012-08-03 18:52:2749#include "chrome/installer/util/install_util.h"
pmonette0c40087a2016-04-21 00:05:1650#include "chrome/installer/util/scoped_user_protocol_entry.h"
[email protected]d24c4012009-07-28 01:57:3151#include "chrome/installer/util/set_reg_value_work_item.h"
52#include "chrome/installer/util/shell_util.h"
53#include "chrome/installer/util/util_constants.h"
54#include "chrome/installer/util/work_item.h"
55#include "chrome/installer/util/work_item_list.h"
pmonette2b1dbee2016-01-08 20:18:5856#include "components/variations/variations_associated_data.h"
[email protected]c38831a12011-10-28 12:44:4957#include "content/public/browser/browser_thread.h"
[email protected]d24c4012009-07-28 01:57:3158
[email protected]631bb742011-11-02 11:29:3959using content::BrowserThread;
60
pmonette9fa59e882016-02-10 00:12:1961namespace shell_integration {
62
[email protected]12f520c2010-01-06 18:11:1563namespace {
64
pmonette9fa59e882016-02-10 00:12:1965// Helper function for GetAppId to generates profile id
[email protected]2f1c09d2011-01-14 14:58:1466// from profile path. "profile_id" is composed of sanitized basenames of
[email protected]12f520c2010-01-06 18:11:1567// user data dir and profile dir joined by a ".".
[email protected]6a72a632013-12-12 22:22:0068base::string16 GetProfileIdFromPath(const base::FilePath& profile_path) {
[email protected]12f520c2010-01-06 18:11:1569 // Return empty string if profile_path is empty
70 if (profile_path.empty())
[email protected]0085863a2013-12-06 21:19:0371 return base::string16();
[email protected]12f520c2010-01-06 18:11:1572
[email protected]650b2d52013-02-10 03:41:4573 base::FilePath default_user_data_dir;
[email protected]12f520c2010-01-06 18:11:1574 // Return empty string if profile_path is in default user data
75 // dir and is the default profile.
76 if (chrome::GetDefaultUserDataDirectory(&default_user_data_dir) &&
77 profile_path.DirName() == default_user_data_dir &&
[email protected]162b5992011-03-15 19:40:4878 profile_path.BaseName().value() ==
[email protected]f911df52013-12-24 23:24:2379 base::ASCIIToUTF16(chrome::kInitialProfile)) {
[email protected]0085863a2013-12-06 21:19:0380 return base::string16();
[email protected]162b5992011-03-15 19:40:4881 }
[email protected]12f520c2010-01-06 18:11:1582
83 // Get joined basenames of user data dir and profile.
[email protected]0085863a2013-12-06 21:19:0384 base::string16 basenames = profile_path.DirName().BaseName().value() +
[email protected]12f520c2010-01-06 18:11:1585 L"." + profile_path.BaseName().value();
86
[email protected]0085863a2013-12-06 21:19:0387 base::string16 profile_id;
[email protected]12f520c2010-01-06 18:11:1588 profile_id.reserve(basenames.size());
89
90 // Generate profile_id from sanitized basenames.
91 for (size_t i = 0; i < basenames.length(); ++i) {
brettwb3413062015-06-24 00:39:0292 if (base::IsAsciiAlpha(basenames[i]) ||
93 base::IsAsciiDigit(basenames[i]) ||
[email protected]12f520c2010-01-06 18:11:1594 basenames[i] == L'.')
95 profile_id += basenames[i];
96 }
97
98 return profile_id;
99}
100
taptedc2c690ab2016-05-25 02:49:03101base::string16 GetAppListAppName() {
102 static const base::char16 kAppListAppNameSuffix[] = L"AppList";
103 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
104 base::string16 app_name(dist->GetBaseAppId());
105 app_name.append(kAppListAppNameSuffix);
106 return app_name;
107}
108
[email protected]8ea8f1ef2013-01-06 18:39:03109// Gets expected app id for given Chrome (based on |command_line| and
110// |is_per_user_install|).
avi556c05022014-12-22 23:31:43111base::string16 GetExpectedAppId(const base::CommandLine& command_line,
[email protected]6a72a632013-12-12 22:22:00112 bool is_per_user_install) {
[email protected]b39e32e2013-04-24 08:55:54113 base::FilePath user_data_dir;
114 if (command_line.HasSwitch(switches::kUserDataDir))
115 user_data_dir = command_line.GetSwitchValuePath(switches::kUserDataDir);
116 else
117 chrome::GetDefaultUserDataDirectory(&user_data_dir);
[email protected]89d43832013-06-29 20:25:20118 // Adjust with any policy that overrides any other way to set the path.
119 policy::path_parser::CheckUserDataDirPolicy(&user_data_dir);
[email protected]b39e32e2013-04-24 08:55:54120 DCHECK(!user_data_dir.empty());
[email protected]c9bb06f42010-01-13 23:53:48121
[email protected]b39e32e2013-04-24 08:55:54122 base::FilePath profile_subdir;
123 if (command_line.HasSwitch(switches::kProfileDirectory)) {
124 profile_subdir =
125 command_line.GetSwitchValuePath(switches::kProfileDirectory);
126 } else {
[email protected]f911df52013-12-24 23:24:23127 profile_subdir =
128 base::FilePath(base::ASCIIToUTF16(chrome::kInitialProfile));
[email protected]b39e32e2013-04-24 08:55:54129 }
130 DCHECK(!profile_subdir.empty());
131
132 base::FilePath profile_path = user_data_dir.Append(profile_subdir);
[email protected]0085863a2013-12-06 21:19:03133 base::string16 app_name;
[email protected]c9bb06f42010-01-13 23:53:48134 if (command_line.HasSwitch(switches::kApp)) {
[email protected]f911df52013-12-24 23:24:23135 app_name = base::UTF8ToUTF16(web_app::GenerateApplicationNameFromURL(
[email protected]57ecc4b2010-08-11 03:02:51136 GURL(command_line.GetSwitchValueASCII(switches::kApp))));
[email protected]2f1c09d2011-01-14 14:58:14137 } else if (command_line.HasSwitch(switches::kAppId)) {
[email protected]f911df52013-12-24 23:24:23138 app_name = base::UTF8ToUTF16(
139 web_app::GenerateApplicationNameFromExtensionId(
140 command_line.GetSwitchValueASCII(switches::kAppId)));
taptedc2c690ab2016-05-25 02:49:03141 } else if (command_line.HasSwitch(switches::kShowAppList)) {
142 app_name = GetAppListAppName();
[email protected]c9bb06f42010-01-13 23:53:48143 } else {
[email protected]a0448002012-06-19 04:32:10144 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
[email protected]8ea8f1ef2013-01-06 18:39:03145 app_name = ShellUtil::GetBrowserModelId(dist, is_per_user_install);
[email protected]c9bb06f42010-01-13 23:53:48146 }
[email protected]b39e32e2013-04-24 08:55:54147 DCHECK(!app_name.empty());
[email protected]c9bb06f42010-01-13 23:53:48148
pmonette9e4c1a82016-04-14 18:15:30149 return win::GetAppModelIdForProfile(app_name, profile_path);
[email protected]c9bb06f42010-01-13 23:53:48150}
151
gab88257b62016-01-15 03:19:19152void MigrateTaskbarPinsCallback() {
[email protected]3a3e72c2011-11-29 02:59:38153 // This should run on the file thread.
thestig00844cea2015-09-08 21:44:52154 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
[email protected]3a3e72c2011-11-29 02:59:38155
156 // Get full path of chrome.
[email protected]650b2d52013-02-10 03:41:45157 base::FilePath chrome_exe;
[email protected]3a3e72c2011-11-29 02:59:38158 if (!PathService::Get(base::FILE_EXE, &chrome_exe))
159 return;
160
gab88257b62016-01-15 03:19:19161 base::FilePath pins_path;
162 if (!PathService::Get(base::DIR_TASKBAR_PINS, &pins_path)) {
163 NOTREACHED();
164 return;
[email protected]3a3e72c2011-11-29 02:59:38165 }
gab88257b62016-01-15 03:19:19166
pmonette9e4c1a82016-04-14 18:15:30167 win::MigrateShortcutsInPathInternal(chrome_exe, pins_path);
[email protected]3a3e72c2011-11-29 02:59:38168}
169
[email protected]caa05352014-03-01 00:43:05170// Windows 8 introduced a new protocol->executable binding system which cannot
171// be retrieved in the HKCR registry subkey method implemented below. We call
172// AssocQueryString with the new Win8-only flag ASSOCF_IS_PROTOCOL instead.
173base::string16 GetAppForProtocolUsingAssocQuery(const GURL& url) {
thestig11bf74d2014-11-24 20:14:42174 base::string16 url_scheme = base::ASCIIToUTF16(url.scheme());
[email protected]caa05352014-03-01 00:43:05175 // Don't attempt to query protocol association on an empty string.
[email protected]6e84d372014-05-29 23:36:39176 if (url_scheme.empty())
[email protected]caa05352014-03-01 00:43:05177 return base::string16();
178
[email protected]caa05352014-03-01 00:43:05179 // Query AssocQueryString for a human-readable description of the program
180 // that will be invoked given the provided URL spec. This is used only to
181 // populate the external protocol dialog box the user sees when invoking
182 // an unknown external protocol.
183 wchar_t out_buffer[1024];
184 DWORD buffer_size = arraysize(out_buffer);
185 HRESULT hr = AssocQueryString(ASSOCF_IS_PROTOCOL,
186 ASSOCSTR_FRIENDLYAPPNAME,
[email protected]6e84d372014-05-29 23:36:39187 url_scheme.c_str(),
[email protected]caa05352014-03-01 00:43:05188 NULL,
189 out_buffer,
190 &buffer_size);
191 if (FAILED(hr)) {
192 DLOG(WARNING) << "AssocQueryString failed!";
193 return base::string16();
194 }
195 return base::string16(out_buffer);
196}
197
198base::string16 GetAppForProtocolUsingRegistry(const GURL& url) {
[email protected]caa05352014-03-01 00:43:05199 const base::string16 cmd_key_path =
thestig11bf74d2014-11-24 20:14:42200 base::ASCIIToUTF16(url.scheme() + "\\shell\\open\\command");
[email protected]caa05352014-03-01 00:43:05201 base::win::RegKey cmd_key(HKEY_CLASSES_ROOT,
202 cmd_key_path.c_str(),
203 KEY_READ);
[email protected]caa05352014-03-01 00:43:05204 base::string16 application_to_launch;
205 if (cmd_key.ReadValue(NULL, &application_to_launch) == ERROR_SUCCESS) {
benwellse84d8392015-08-18 05:23:58206 const base::string16 url_spec =
207 base::ASCIIToUTF16(url.possibly_invalid_spec());
brettwe6dae462015-06-24 20:54:45208 base::ReplaceSubstringsAfterOffset(&application_to_launch,
209 0,
210 L"%1",
benwellse84d8392015-08-18 05:23:58211 url_spec);
[email protected]caa05352014-03-01 00:43:05212 return application_to_launch;
213 }
214 return base::string16();
215}
216
pmonette9fa59e882016-02-10 00:12:19217DefaultWebClientState GetDefaultWebClientStateFromShellUtilDefaultState(
218 ShellUtil::DefaultState default_state) {
[email protected]eb63da72012-10-15 20:39:48219 switch (default_state) {
220 case ShellUtil::NOT_DEFAULT:
pmonette9fa59e882016-02-10 00:12:19221 return DefaultWebClientState::NOT_DEFAULT;
[email protected]eb63da72012-10-15 20:39:48222 case ShellUtil::IS_DEFAULT:
pmonette9fa59e882016-02-10 00:12:19223 return DefaultWebClientState::IS_DEFAULT;
[email protected]eb63da72012-10-15 20:39:48224 default:
225 DCHECK_EQ(ShellUtil::UNKNOWN_DEFAULT, default_state);
pmonette9fa59e882016-02-10 00:12:19226 return DefaultWebClientState::UNKNOWN_DEFAULT;
[email protected]eb63da72012-10-15 20:39:48227 }
228}
229
grtc291eea2016-05-26 15:38:48230// A recorder of user actions in the Windows Settings app.
231class DefaultBrowserActionRecorder : public win::SettingsAppMonitor::Delegate {
232 public:
233 // Creates the recorder and the monitor that drives it. |continuation| will be
234 // run once the monitor's initialization completes (regardless of success or
235 // failure).
236 explicit DefaultBrowserActionRecorder(base::Closure continuation)
237 : continuation_(std::move(continuation)), settings_app_monitor_(this) {}
238
239 private:
240 // win::SettingsAppMonitor::Delegate:
241 void OnInitialized(HRESULT result) override {
242 UMA_HISTOGRAM_BOOLEAN("SettingsAppMonitor.InitializationResult",
243 SUCCEEDED(result));
244 if (SUCCEEDED(result)) {
245 base::RecordAction(
246 base::UserMetricsAction("SettingsAppMonitor.Initialized"));
247 }
248 continuation_.Run();
249 continuation_ = base::Closure();
250 }
251
252 void OnAppFocused() override {
253 base::RecordAction(
254 base::UserMetricsAction("SettingsAppMonitor.AppFocused"));
255 }
256
257 void OnChooserInvoked() override {
258 base::RecordAction(
259 base::UserMetricsAction("SettingsAppMonitor.ChooserInvoked"));
260 }
261
262 void OnBrowserChosen(const base::string16& browser_name) override {
263 if (browser_name ==
264 BrowserDistribution::GetDistribution()->GetDisplayName()) {
265 base::RecordAction(
266 base::UserMetricsAction("SettingsAppMonitor.ChromeBrowserChosen"));
267 } else {
268 base::RecordAction(
269 base::UserMetricsAction("SettingsAppMonitor.OtherBrowserChosen"));
270 }
271 }
272
273 // A closure to be run once initialization completes.
274 base::Closure continuation_;
275
276 // Monitors user interaction with the Windows Settings app for the sake of
277 // reporting user actions.
278 win::SettingsAppMonitor settings_app_monitor_;
279
280 DISALLOW_COPY_AND_ASSIGN(DefaultBrowserActionRecorder);
281};
282
283// A function bound up in a callback with a DefaultBrowserActionRecorder and
284// a closure to keep the former alive until the time comes to run the latter.
285void OnSettingsAppFinished(
286 std::unique_ptr<DefaultBrowserActionRecorder> recorder,
287 const base::Closure& on_finished_callback) {
288 recorder.reset();
289 on_finished_callback.Run();
290}
291
pmonette32a5cfb42016-04-11 22:04:44292// There is no way to make sure the user is done with the system settings, but a
293// signal that the interaction is finished is needed for UMA. A timer of 2
294// minutes is used as a substitute. The registry keys for the protocol
295// association with an app are also monitored to signal the end of the
296// interaction early when it is clear that the user made a choice (e.g. http
297// and https for default browser).
298//
299// This helper class manages both the timer and the registry watchers and makes
300// sure the callback for the end of the settings interaction is only run once.
301// This class also manages its own lifetime.
302class OpenSystemSettingsHelper {
303 public:
304 // Begin the monitoring and will call |on_finished_callback| when done.
305 // Takes in a null-terminated array of |protocols| whose registry keys must be
pmonette0c40087a2016-04-21 00:05:16306 // watched. The array must contain at least one element.
pmonette32a5cfb42016-04-11 22:04:44307 static void Begin(const wchar_t* const protocols[],
308 const base::Closure& on_finished_callback) {
pmonettec541d942016-06-15 22:08:32309 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
310
311 delete instance_;
312 instance_ = new OpenSystemSettingsHelper(protocols, on_finished_callback);
pmonette32a5cfb42016-04-11 22:04:44313 }
314
315 private:
316 // The reason the settings interaction concluded. Do not modify the ordering
317 // because it is used for UMA.
318 enum ConcludeReason { REGISTRY_WATCHER, TIMEOUT, NUM_CONCLUDE_REASON_TYPES };
319
320 OpenSystemSettingsHelper(const wchar_t* const protocols[],
321 const base::Closure& on_finished_callback)
pmonette0c40087a2016-04-21 00:05:16322 : scoped_user_protocol_entry_(protocols[0]),
323 on_finished_callback_(on_finished_callback),
324 weak_ptr_factory_(this) {
pmonette32a5cfb42016-04-11 22:04:44325 static const wchar_t kUrlAssociationFormat[] =
326 L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\"
327 L"%ls\\UserChoice";
328
329 // Remember the start time.
330 start_time_ = base::TimeTicks::Now();
331
332 for (const wchar_t* const* scan = &protocols[0]; *scan != nullptr; ++scan) {
333 AddRegistryKeyWatcher(
334 base::StringPrintf(kUrlAssociationFormat, *scan).c_str());
335 }
336 // Only the watchers that were succesfully initialized are counted.
337 registry_watcher_count_ = registry_key_watchers_.size();
338
339 timer_.Start(
340 FROM_HERE, base::TimeDelta::FromMinutes(2),
341 base::Bind(&OpenSystemSettingsHelper::ConcludeInteraction,
342 weak_ptr_factory_.GetWeakPtr(), ConcludeReason::TIMEOUT));
343 }
344
345 // Called when a change is detected on one of the registry keys being watched.
346 // Note: All types of modification to the registry key will trigger this
347 // function even if the value change is the only one that matters. This
348 // is good enough for now.
349 void OnRegistryKeyChanged() {
350 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
351
352 // Make sure all the registry watchers have fired.
353 if (--registry_watcher_count_ == 0) {
354 UMA_HISTOGRAM_MEDIUM_TIMES(
355 "DefaultBrowser.SettingsInteraction.RegistryWatcherDuration",
356 base::TimeTicks::Now() - start_time_);
357
358 ConcludeInteraction(ConcludeReason::REGISTRY_WATCHER);
359 }
360 }
361
362 // Ends the monitoring with the system settings. Will call
363 // |on_finished_callback_| and then dispose of this class instance to make
364 // sure the callback won't get called subsequently.
365 void ConcludeInteraction(ConcludeReason conclude_reason) {
366 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
367
368 UMA_HISTOGRAM_ENUMERATION(
369 "DefaultBrowser.SettingsInteraction.ConcludeReason", conclude_reason,
370 NUM_CONCLUDE_REASON_TYPES);
371 on_finished_callback_.Run();
pmonettec541d942016-06-15 22:08:32372 delete instance_;
373 instance_ = nullptr;
pmonette32a5cfb42016-04-11 22:04:44374 }
375
376 // Helper function to create a registry watcher for a given |key_path|. Do
377 // nothing on initialization failure.
378 void AddRegistryKeyWatcher(const wchar_t* key_path) {
dcheng4af48582016-04-19 00:29:35379 auto reg_key = base::WrapUnique(
pmonette32a5cfb42016-04-11 22:04:44380 new base::win::RegKey(HKEY_CURRENT_USER, key_path, KEY_NOTIFY));
381
382 if (reg_key->Valid() &&
383 reg_key->StartWatching(
384 base::Bind(&OpenSystemSettingsHelper::OnRegistryKeyChanged,
385 weak_ptr_factory_.GetWeakPtr()))) {
386 registry_key_watchers_.push_back(std::move(reg_key));
387 }
388 }
389
pmonettec541d942016-06-15 22:08:32390 // Used to make sure only one instance is alive at the same time.
391 static OpenSystemSettingsHelper* instance_;
392
pmonette0c40087a2016-04-21 00:05:16393 // This is needed to make sure that Windows displays an entry for the protocol
394 // inside the "Choose default apps by protocol" settings page.
395 ScopedUserProtocolEntry scoped_user_protocol_entry_;
396
pmonette32a5cfb42016-04-11 22:04:44397 // The function to call when the interaction with the system settings is
398 // finished.
399 base::Closure on_finished_callback_;
400
401 // The number of time the registry key watchers must fire.
402 int registry_watcher_count_ = 0;
403
404 // There can be multiple registry key watchers as some settings modify
405 // multiple protocol associations. e.g. Changing the default browser modifies
406 // the http and https associations.
dcheng4af48582016-04-19 00:29:35407 std::vector<std::unique_ptr<base::win::RegKey>> registry_key_watchers_;
pmonette32a5cfb42016-04-11 22:04:44408
409 base::OneShotTimer timer_;
410
411 // Records the time it takes for the final registry watcher to get signaled.
412 base::TimeTicks start_time_;
413
414 // Weak ptrs are used to bind this class to the callbacks of the timer and the
415 // registry watcher. This makes it possible to self-delete after one of the
416 // callbacks is executed to cancel the remaining ones.
417 base::WeakPtrFactory<OpenSystemSettingsHelper> weak_ptr_factory_;
418
419 DISALLOW_COPY_AND_ASSIGN(OpenSystemSettingsHelper);
420};
421
pmonettec541d942016-06-15 22:08:32422OpenSystemSettingsHelper* OpenSystemSettingsHelper::instance_ = nullptr;
423
[email protected]3a3e72c2011-11-29 02:59:38424} // namespace
[email protected]12f520c2010-01-06 18:11:15425
pmonette9fa59e882016-02-10 00:12:19426bool SetAsDefaultBrowser() {
[email protected]650b2d52013-02-10 03:41:45427 base::FilePath chrome_exe;
[email protected]d24c4012009-07-28 01:57:31428 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
429 LOG(ERROR) << "Error getting app exe path";
430 return false;
431 }
432
433 // From UI currently we only allow setting default browser for current user.
[email protected]bf6117c7e2010-12-01 06:00:25434 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
grte76ca2852014-12-05 16:42:10435 if (!ShellUtil::MakeChromeDefault(dist, ShellUtil::CURRENT_USER, chrome_exe,
436 true /* elevate_if_not_admin */)) {
[email protected]d24c4012009-07-28 01:57:31437 LOG(ERROR) << "Chrome could not be set as default browser.";
438 return false;
439 }
440
[email protected]8e96e502010-10-21 20:57:12441 VLOG(1) << "Chrome registered as default browser.";
[email protected]d24c4012009-07-28 01:57:31442 return true;
443}
444
pmonette9fa59e882016-02-10 00:12:19445bool SetAsDefaultProtocolClient(const std::string& protocol) {
[email protected]4468a5b2011-05-26 07:48:02446 if (protocol.empty())
447 return false;
448
[email protected]650b2d52013-02-10 03:41:45449 base::FilePath chrome_exe;
[email protected]4468a5b2011-05-26 07:48:02450 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
451 LOG(ERROR) << "Error getting app exe path";
452 return false;
453 }
454
[email protected]f911df52013-12-24 23:24:23455 base::string16 wprotocol(base::UTF8ToUTF16(protocol));
[email protected]4468a5b2011-05-26 07:48:02456 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
grte76ca2852014-12-05 16:42:10457 if (!ShellUtil::MakeChromeDefaultProtocolClient(dist, chrome_exe,
458 wprotocol)) {
[email protected]4468a5b2011-05-26 07:48:02459 LOG(ERROR) << "Chrome could not be set as default handler for "
460 << protocol << ".";
461 return false;
462 }
463
464 VLOG(1) << "Chrome registered as default handler for " << protocol << ".";
465 return true;
466}
467
pmonette32a5cfb42016-04-11 22:04:44468DefaultWebClientSetPermission GetDefaultWebClientSetPermission() {
pmonette034a03d92015-10-02 21:04:27469 BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
470 if (distribution->GetDefaultBrowserControlPolicy() !=
471 BrowserDistribution::DEFAULT_BROWSER_FULL_CONTROL)
472 return SET_DEFAULT_NOT_ALLOWED;
pmonette034a03d92015-10-02 21:04:27473 if (ShellUtil::CanMakeChromeDefaultUnattended())
474 return SET_DEFAULT_UNATTENDED;
pmonette32a5cfb42016-04-11 22:04:44475 // Windows 8 and 10 both introduced a new way to set the default web client
476 // which require user interaction.
pmonettef89ac7c72015-10-06 03:22:01477 return SET_DEFAULT_INTERACTIVE;
pmonette034a03d92015-10-02 21:04:27478}
479
pmonette9fa59e882016-02-10 00:12:19480bool IsElevationNeededForSettingDefaultProtocolClient() {
pmonette868ca642015-09-02 14:34:02481 return base::win::GetVersion() < base::win::VERSION_WIN8;
482}
483
pmonette9fa59e882016-02-10 00:12:19484base::string16 GetApplicationNameForProtocol(const GURL& url) {
[email protected]caa05352014-03-01 00:43:05485 // Windows 8 or above requires a new protocol association query.
486 if (base::win::GetVersion() >= base::win::VERSION_WIN8)
487 return GetAppForProtocolUsingAssocQuery(url);
488 else
489 return GetAppForProtocolUsingRegistry(url);
[email protected]42dc9402013-01-30 07:54:20490}
491
pmonette9fa59e882016-02-10 00:12:19492DefaultWebClientState GetDefaultBrowser() {
pmonette034a03d92015-10-02 21:04:27493 return GetDefaultWebClientStateFromShellUtilDefaultState(
494 ShellUtil::GetChromeDefaultState());
495}
496
[email protected]d24c4012009-07-28 01:57:31497// There is no reliable way to say which browser is default on a machine (each
498// browser can have some of the protocols/shortcuts). So we look for only HTTP
499// protocol handler. Even this handler is located at different places in
500// registry on XP and Vista:
501// - HKCR\http\shell\open\command (XP)
502// - HKCU\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\
503// http\UserChoice (Vista)
504// This method checks if Firefox is defualt browser by checking these
505// locations and returns true if Firefox traces are found there. In case of
506// error (or if Firefox is not found)it returns the default value which
507// is false.
pmonette9fa59e882016-02-10 00:12:19508bool IsFirefoxDefaultBrowser() {
[email protected]d24c4012009-07-28 01:57:31509 bool ff_default = false;
[email protected]935aa542010-10-15 01:59:15510 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
[email protected]0085863a2013-12-06 21:19:03511 base::string16 app_cmd;
[email protected]2d6503982010-10-17 04:41:54512 base::win::RegKey key(HKEY_CURRENT_USER,
513 ShellUtil::kRegVistaUrlPrefs, KEY_READ);
[email protected]e06f4d52011-01-19 07:28:46514 if (key.Valid() && (key.ReadValue(L"Progid", &app_cmd) == ERROR_SUCCESS) &&
[email protected]d24c4012009-07-28 01:57:31515 app_cmd == L"FirefoxURL")
516 ff_default = true;
517 } else {
[email protected]0085863a2013-12-06 21:19:03518 base::string16 key_path(L"http");
[email protected]d24c4012009-07-28 01:57:31519 key_path.append(ShellUtil::kRegShellOpen);
[email protected]2d6503982010-10-17 04:41:54520 base::win::RegKey key(HKEY_CLASSES_ROOT, key_path.c_str(), KEY_READ);
[email protected]0085863a2013-12-06 21:19:03521 base::string16 app_cmd;
[email protected]e06f4d52011-01-19 07:28:46522 if (key.Valid() && (key.ReadValue(L"", &app_cmd) == ERROR_SUCCESS) &&
[email protected]cb1f4ac2014-08-07 16:55:42523 base::string16::npos !=
brettwfce8d192015-08-10 19:07:51524 base::ToLowerASCII(app_cmd).find(L"firefox"))
[email protected]d24c4012009-07-28 01:57:31525 ff_default = true;
526 }
527 return ff_default;
528}
[email protected]12f520c2010-01-06 18:11:15529
pmonette9fa59e882016-02-10 00:12:19530DefaultWebClientState IsDefaultProtocolClient(const std::string& protocol) {
pmonette034a03d92015-10-02 21:04:27531 return GetDefaultWebClientStateFromShellUtilDefaultState(
532 ShellUtil::GetChromeDefaultProtocolClientState(
533 base::UTF8ToUTF16(protocol)));
534}
535
pmonette9e4c1a82016-04-14 18:15:30536namespace win {
537
538bool SetAsDefaultBrowserUsingIntentPicker() {
539 base::FilePath chrome_exe;
540 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
541 NOTREACHED() << "Error getting app exe path";
542 return false;
543 }
544
545 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
546 if (!ShellUtil::ShowMakeChromeDefaultSystemUI(dist, chrome_exe)) {
547 LOG(ERROR) << "Failed to launch the set-default-browser Windows UI.";
548 return false;
549 }
550
551 VLOG(1) << "Set-default-browser Windows UI completed.";
552 return true;
553}
554
555void SetAsDefaultBrowserUsingSystemSettings(
556 const base::Closure& on_finished_callback) {
557 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
558
559 base::FilePath chrome_exe;
560 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
561 NOTREACHED() << "Error getting app exe path";
562 on_finished_callback.Run();
563 return;
564 }
565
grtc291eea2016-05-26 15:38:48566 // Create an action recorder that will open the settings app once it has
567 // initialized.
568 std::unique_ptr<DefaultBrowserActionRecorder> recorder(
569 new DefaultBrowserActionRecorder(base::Bind(
570 base::IgnoreResult(&ShellUtil::ShowMakeChromeDefaultSystemUI),
571 base::Unretained(BrowserDistribution::GetDistribution()),
572 chrome_exe)));
pmonette9e4c1a82016-04-14 18:15:30573
grtc291eea2016-05-26 15:38:48574 // The helper manages its own lifetime. Bind the action recorder
575 // into the finished callback to keep it alive throughout the
576 // interaction.
577 static const wchar_t* const kProtocols[] = {L"http", L"https", nullptr};
578 OpenSystemSettingsHelper::Begin(
579 kProtocols, base::Bind(&OnSettingsAppFinished, base::Passed(&recorder),
580 on_finished_callback));
pmonette9e4c1a82016-04-14 18:15:30581}
582
583bool SetAsDefaultProtocolClientUsingIntentPicker(const std::string& protocol) {
584 base::FilePath chrome_exe;
585 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
586 NOTREACHED() << "Error getting app exe path";
587 return false;
588 }
589
590 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
591 base::string16 wprotocol(base::UTF8ToUTF16(protocol));
592 if (!ShellUtil::ShowMakeChromeDefaultProtocolClientSystemUI(dist, chrome_exe,
593 wprotocol)) {
594 LOG(ERROR) << "Failed to launch the set-default-client Windows UI.";
595 return false;
596 }
597
598 VLOG(1) << "Set-default-client Windows UI completed.";
599 return true;
600}
601
pmonette0c40087a2016-04-21 00:05:16602void SetAsDefaultProtocolClientUsingSystemSettings(
603 const std::string& protocol,
604 const base::Closure& on_finished_callback) {
605 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
606
607 base::FilePath chrome_exe;
608 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
609 NOTREACHED() << "Error getting app exe path";
610 on_finished_callback.Run();
611 return;
612 }
613
614 // The helper manages its own lifetime.
615 base::string16 wprotocol(base::UTF8ToUTF16(protocol));
616 const wchar_t* const kProtocols[] = {wprotocol.c_str(), nullptr};
617 OpenSystemSettingsHelper::Begin(kProtocols, on_finished_callback);
618
619 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
620 ShellUtil::ShowMakeChromeDefaultProtocolClientSystemUI(dist, chrome_exe,
621 wprotocol);
622}
623
pmonette9fa59e882016-02-10 00:12:19624base::string16 GetAppModelIdForProfile(const base::string16& app_name,
625 const base::FilePath& profile_path) {
[email protected]d2065e062013-12-12 23:49:52626 std::vector<base::string16> components;
[email protected]a0448002012-06-19 04:32:10627 components.push_back(app_name);
[email protected]0085863a2013-12-06 21:19:03628 const base::string16 profile_id(GetProfileIdFromPath(profile_path));
[email protected]a0448002012-06-19 04:32:10629 if (!profile_id.empty())
630 components.push_back(profile_id);
631 return ShellUtil::BuildAppModelId(components);
[email protected]12f520c2010-01-06 18:11:15632}
633
pmonette9fa59e882016-02-10 00:12:19634base::string16 GetChromiumModelIdForProfile(
[email protected]650b2d52013-02-10 03:41:45635 const base::FilePath& profile_path) {
[email protected]a0448002012-06-19 04:32:10636 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
[email protected]650b2d52013-02-10 03:41:45637 base::FilePath chrome_exe;
[email protected]a0448002012-06-19 04:32:10638 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
639 NOTREACHED();
640 return dist->GetBaseAppId();
641 }
642 return GetAppModelIdForProfile(
grte76ca2852014-12-05 16:42:10643 ShellUtil::GetBrowserModelId(dist,
644 InstallUtil::IsPerUserInstall(chrome_exe)),
[email protected]786799692012-09-26 14:16:48645 profile_path);
[email protected]12f520c2010-01-06 18:11:15646}
[email protected]c9bb06f42010-01-13 23:53:48647
pmonette9fa59e882016-02-10 00:12:19648void MigrateTaskbarPins() {
[email protected]935aa542010-10-15 01:59:15649 if (base::win::GetVersion() < base::win::VERSION_WIN7)
[email protected]c9bb06f42010-01-13 23:53:48650 return;
651
[email protected]81fcf952012-11-26 22:25:14652 // This needs to happen eventually (e.g. so that the appid is fixed and the
653 // run-time Chrome icon is merged with the taskbar shortcut), but this is not
654 // urgent and shouldn't delay Chrome startup.
gab88257b62016-01-15 03:19:19655 static const int64_t kMigrateTaskbarPinsDelaySeconds = 15;
[email protected]81fcf952012-11-26 22:25:14656 BrowserThread::PostDelayedTask(
[email protected]3a3e72c2011-11-29 02:59:38657 BrowserThread::FILE, FROM_HERE,
gab88257b62016-01-15 03:19:19658 base::Bind(&MigrateTaskbarPinsCallback),
659 base::TimeDelta::FromSeconds(kMigrateTaskbarPinsDelaySeconds));
[email protected]c9bb06f42010-01-13 23:53:48660}
[email protected]43903b82012-06-01 05:26:23661
pmonette9fa59e882016-02-10 00:12:19662int MigrateShortcutsInPathInternal(const base::FilePath& chrome_exe,
663 const base::FilePath& path) {
[email protected]8ea8f1ef2013-01-06 18:39:03664 DCHECK(base::win::GetVersion() >= base::win::VERSION_WIN7);
665
666 // Enumerate all pinned shortcuts in the given path directly.
[email protected]25a4c1c2013-06-08 04:53:36667 base::FileEnumerator shortcuts_enum(
[email protected]8ea8f1ef2013-01-06 18:39:03668 path, false, // not recursive
[email protected]25a4c1c2013-06-08 04:53:36669 base::FileEnumerator::FILES, FILE_PATH_LITERAL("*.lnk"));
[email protected]8ea8f1ef2013-01-06 18:39:03670
grte76ca2852014-12-05 16:42:10671 bool is_per_user_install = InstallUtil::IsPerUserInstall(chrome_exe);
[email protected]8ea8f1ef2013-01-06 18:39:03672
673 int shortcuts_migrated = 0;
[email protected]650b2d52013-02-10 03:41:45674 base::FilePath target_path;
[email protected]0085863a2013-12-06 21:19:03675 base::string16 arguments;
[email protected]07983302013-01-21 19:41:44676 base::win::ScopedPropVariant propvariant;
[email protected]650b2d52013-02-10 03:41:45677 for (base::FilePath shortcut = shortcuts_enum.Next(); !shortcut.empty();
[email protected]8ea8f1ef2013-01-06 18:39:03678 shortcut = shortcuts_enum.Next()) {
679 // TODO(gab): Use ProgramCompare instead of comparing FilePaths below once
680 // it is fixed to work with FilePaths with spaces.
681 if (!base::win::ResolveShortcut(shortcut, &target_path, &arguments) ||
682 chrome_exe != target_path) {
683 continue;
684 }
avi556c05022014-12-22 23:31:43685 base::CommandLine command_line(
686 base::CommandLine::FromString(base::StringPrintf(
687 L"\"%ls\" %ls", target_path.value().c_str(), arguments.c_str())));
[email protected]8ea8f1ef2013-01-06 18:39:03688
689 // Get the expected AppId for this Chrome shortcut.
[email protected]0085863a2013-12-06 21:19:03690 base::string16 expected_app_id(
[email protected]8ea8f1ef2013-01-06 18:39:03691 GetExpectedAppId(command_line, is_per_user_install));
692 if (expected_app_id.empty())
693 continue;
694
695 // Load the shortcut.
696 base::win::ScopedComPtr<IShellLink> shell_link;
697 base::win::ScopedComPtr<IPersistFile> persist_file;
698 if (FAILED(shell_link.CreateInstance(CLSID_ShellLink, NULL,
699 CLSCTX_INPROC_SERVER)) ||
dcheng81762e02014-11-21 21:22:28700 FAILED(persist_file.QueryFrom(shell_link.get())) ||
[email protected]8ea8f1ef2013-01-06 18:39:03701 FAILED(persist_file->Load(shortcut.value().c_str(), STGM_READ))) {
702 DLOG(WARNING) << "Failed loading shortcut at " << shortcut.value();
703 continue;
704 }
705
706 // Any properties that need to be updated on the shortcut will be stored in
707 // |updated_properties|.
708 base::win::ShortcutProperties updated_properties;
709
710 // Validate the existing app id for the shortcut.
711 base::win::ScopedComPtr<IPropertyStore> property_store;
[email protected]07983302013-01-21 19:41:44712 propvariant.Reset();
dcheng81762e02014-11-21 21:22:28713 if (FAILED(property_store.QueryFrom(shell_link.get())) ||
714 property_store->GetValue(PKEY_AppUserModel_ID, propvariant.Receive()) !=
715 S_OK) {
[email protected]8ea8f1ef2013-01-06 18:39:03716 // When in doubt, prefer not updating the shortcut.
717 NOTREACHED();
718 continue;
[email protected]8ea8f1ef2013-01-06 18:39:03719 } else {
[email protected]07983302013-01-21 19:41:44720 switch (propvariant.get().vt) {
721 case VT_EMPTY:
722 // If there is no app_id set, set our app_id if one is expected.
723 if (!expected_app_id.empty())
724 updated_properties.set_app_id(expected_app_id);
725 break;
726 case VT_LPWSTR:
[email protected]0085863a2013-12-06 21:19:03727 if (expected_app_id != base::string16(propvariant.get().pwszVal))
[email protected]07983302013-01-21 19:41:44728 updated_properties.set_app_id(expected_app_id);
729 break;
730 default:
731 NOTREACHED();
732 continue;
[email protected]8ea8f1ef2013-01-06 18:39:03733 }
734 }
735
gab88257b62016-01-15 03:19:19736 // Clear dual_mode property from any shortcuts that previously had it (it
737 // was only ever installed on shortcuts with the
738 // |default_chromium_model_id|).
[email protected]b39e32e2013-04-24 08:55:54739 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
[email protected]0085863a2013-12-06 21:19:03740 base::string16 default_chromium_model_id(
[email protected]b39e32e2013-04-24 08:55:54741 ShellUtil::GetBrowserModelId(dist, is_per_user_install));
gab88257b62016-01-15 03:19:19742 if (expected_app_id == default_chromium_model_id) {
[email protected]07983302013-01-21 19:41:44743 propvariant.Reset();
[email protected]8ea8f1ef2013-01-06 18:39:03744 if (property_store->GetValue(PKEY_AppUserModel_IsDualMode,
[email protected]07983302013-01-21 19:41:44745 propvariant.Receive()) != S_OK) {
[email protected]8ea8f1ef2013-01-06 18:39:03746 // When in doubt, prefer to not update the shortcut.
747 NOTREACHED();
748 continue;
gab38d242d4d2016-01-14 20:38:12749 }
750 if (propvariant.get().vt == VT_BOOL &&
751 !!propvariant.get().boolVal) {
752 updated_properties.set_dual_mode(false);
[email protected]8ea8f1ef2013-01-06 18:39:03753 }
754 }
755
756 persist_file.Release();
757 shell_link.Release();
758
759 // Update the shortcut if some of its properties need to be updated.
760 if (updated_properties.options &&
761 base::win::CreateOrUpdateShortcutLink(
762 shortcut, updated_properties,
763 base::win::SHORTCUT_UPDATE_EXISTING)) {
764 ++shortcuts_migrated;
765 }
766 }
767 return shortcuts_migrated;
768}
769
pmonette9fa59e882016-02-10 00:12:19770base::FilePath GetStartMenuShortcut(const base::FilePath& chrome_exe) {
[email protected]3f69d6e612012-08-03 18:52:27771 static const int kFolderIds[] = {
772 base::DIR_COMMON_START_MENU,
773 base::DIR_START_MENU,
774 };
775 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
taptedd7ef82ae2016-05-13 01:10:19776 const base::string16 shortcut_name(dist->GetShortcutName() +
777 installer::kLnkExt);
grt2be0cd082015-11-12 15:32:21778 base::FilePath programs_folder;
[email protected]650b2d52013-02-10 03:41:45779 base::FilePath shortcut;
[email protected]3f69d6e612012-08-03 18:52:27780
781 // Check both the common and the per-user Start Menu folders for system-level
782 // installs.
grte76ca2852014-12-05 16:42:10783 size_t folder = InstallUtil::IsPerUserInstall(chrome_exe) ? 1 : 0;
[email protected]3f69d6e612012-08-03 18:52:27784 for (; folder < arraysize(kFolderIds); ++folder) {
grt2be0cd082015-11-12 15:32:21785 if (!PathService::Get(kFolderIds[folder], &programs_folder)) {
[email protected]3f69d6e612012-08-03 18:52:27786 NOTREACHED();
787 continue;
788 }
789
grt2be0cd082015-11-12 15:32:21790 shortcut = programs_folder.Append(shortcut_name);
791 if (base::PathExists(shortcut))
792 return shortcut;
[email protected]3f69d6e612012-08-03 18:52:27793 }
794
[email protected]650b2d52013-02-10 03:41:45795 return base::FilePath();
[email protected]3f69d6e612012-08-03 18:52:27796}
pmonettef89ac7c72015-10-06 03:22:01797
pmonette32a5cfb42016-04-11 22:04:44798} // namespace win
799
pmonette9fa59e882016-02-10 00:12:19800} // namespace shell_integration