| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/shell_integration.h" |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/timer/timer.h" |
| #include "chrome/browser/policy/policy_path_parser.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "components/version_info/version_info.h" |
| #include "content/public/browser/browser_thread.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "chromeos/chromeos_switches.h" |
| #endif |
| |
| #if !defined(OS_WIN) |
| #include "chrome/common/channel_info.h" |
| #include "chrome/grit/chromium_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #endif |
| |
| using content::BrowserThread; |
| |
| namespace { |
| |
| const struct ShellIntegration::AppModeInfo* gAppModeInfo = nullptr; |
| |
| } // namespace |
| |
| #if !defined(OS_WIN) |
| // static |
| bool ShellIntegration::SetAsDefaultBrowserInteractive() { |
| return false; |
| } |
| |
| // static |
| bool ShellIntegration::IsSetAsDefaultAsynchronous() { |
| return false; |
| } |
| |
| // static |
| bool ShellIntegration::SetAsDefaultProtocolClientInteractive( |
| const std::string& protocol) { |
| return false; |
| } |
| #endif // !defined(OS_WIN) |
| |
| // static |
| ShellIntegration::DefaultWebClientSetPermission |
| ShellIntegration::CanSetAsDefaultProtocolClient() { |
| // Allowed as long as the browser can become the operating system default |
| // browser. |
| DefaultWebClientSetPermission permission = CanSetAsDefaultBrowser(); |
| |
| // Set as default asynchronous is only supported for default web browser. |
| return (permission == SET_DEFAULT_ASYNCHRONOUS) ? SET_DEFAULT_INTERACTIVE |
| : permission; |
| } |
| |
| #if !defined(OS_WIN) |
| // static |
| bool ShellIntegration::IsElevationNeededForSettingDefaultProtocolClient() { |
| return false; |
| } |
| #endif // !defined(OS_WIN) |
| |
| // static |
| void ShellIntegration::SetAppModeInfo(const struct AppModeInfo* info) { |
| gAppModeInfo = info; |
| } |
| |
| // static |
| const struct ShellIntegration::AppModeInfo* ShellIntegration::AppModeInfo() { |
| return gAppModeInfo; |
| } |
| |
| // static |
| bool ShellIntegration::IsRunningInAppMode() { |
| return gAppModeInfo != NULL; |
| } |
| |
| // static |
| base::CommandLine ShellIntegration::CommandLineArgsForLauncher( |
| const GURL& url, |
| const std::string& extension_app_id, |
| const base::FilePath& profile_path) { |
| base::ThreadRestrictions::AssertIOAllowed(); |
| base::CommandLine new_cmd_line(base::CommandLine::NO_PROGRAM); |
| |
| AppendProfileArgs( |
| extension_app_id.empty() ? base::FilePath() : profile_path, |
| &new_cmd_line); |
| |
| // If |extension_app_id| is present, we use the kAppId switch rather than |
| // the kApp switch (the launch url will be read from the extension app |
| // during launch. |
| if (!extension_app_id.empty()) { |
| new_cmd_line.AppendSwitchASCII(switches::kAppId, extension_app_id); |
| } else { |
| // Use '--app=url' instead of just 'url' to launch the browser with minimal |
| // chrome. |
| // Note: Do not change this flag! Old Gears shortcuts will break if you do! |
| new_cmd_line.AppendSwitchASCII(switches::kApp, url.spec()); |
| } |
| return new_cmd_line; |
| } |
| |
| // static |
| void ShellIntegration::AppendProfileArgs(const base::FilePath& profile_path, |
| base::CommandLine* command_line) { |
| DCHECK(command_line); |
| const base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess(); |
| |
| // Use the same UserDataDir for new launches that we currently have set. |
| base::FilePath user_data_dir = |
| cmd_line.GetSwitchValuePath(switches::kUserDataDir); |
| #if defined(OS_MACOSX) || defined(OS_WIN) |
| policy::path_parser::CheckUserDataDirPolicy(&user_data_dir); |
| #endif |
| if (!user_data_dir.empty()) { |
| // Make sure user_data_dir is an absolute path. |
| user_data_dir = base::MakeAbsoluteFilePath(user_data_dir); |
| if (!user_data_dir.empty() && base::PathExists(user_data_dir)) |
| command_line->AppendSwitchPath(switches::kUserDataDir, user_data_dir); |
| } |
| |
| #if defined(OS_CHROMEOS) |
| base::FilePath profile = cmd_line.GetSwitchValuePath( |
| chromeos::switches::kLoginProfile); |
| if (!profile.empty()) |
| command_line->AppendSwitchPath(chromeos::switches::kLoginProfile, profile); |
| #else |
| if (!profile_path.empty()) |
| command_line->AppendSwitchPath(switches::kProfileDirectory, |
| profile_path.BaseName()); |
| #endif |
| } |
| |
| #if !defined(OS_WIN) |
| base::string16 ShellIntegration::GetAppShortcutsSubdirName() { |
| if (chrome::GetChannel() == version_info::Channel::CANARY) |
| return l10n_util::GetStringUTF16(IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY); |
| return l10n_util::GetStringUTF16(IDS_APP_SHORTCUTS_SUBDIR_NAME); |
| } |
| #endif // !defined(OS_WIN) |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // ShellIntegration::DefaultWebClientObserver |
| // |
| |
| bool ShellIntegration::DefaultWebClientObserver::IsOwnedByWorker() { |
| return false; |
| } |
| |
| bool ShellIntegration::DefaultWebClientObserver:: |
| IsInteractiveSetDefaultPermitted() { |
| return false; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // ShellIntegration::DefaultWebClientWorker |
| // |
| |
| ShellIntegration::DefaultWebClientWorker::DefaultWebClientWorker( |
| DefaultWebClientObserver* observer) |
| : observer_(observer) {} |
| |
| void ShellIntegration::DefaultWebClientWorker::StartCheckIsDefault() { |
| if (observer_) { |
| observer_->SetDefaultWebClientUIState(STATE_PROCESSING); |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, |
| base::Bind(&DefaultWebClientWorker::CheckIsDefault, this)); |
| } |
| } |
| |
| void ShellIntegration::DefaultWebClientWorker::StartSetAsDefault() { |
| // Cancel the already running process if another start is requested. |
| if (set_as_default_in_progress_) { |
| if (set_as_default_initialized_) { |
| FinalizeSetAsDefault(); |
| set_as_default_initialized_ = false; |
| } |
| |
| ReportAttemptResult(AttemptResult::RETRY); |
| } |
| |
| set_as_default_in_progress_ = true; |
| bool interactive_permitted = false; |
| if (observer_) { |
| observer_->SetDefaultWebClientUIState(STATE_PROCESSING); |
| interactive_permitted = observer_->IsInteractiveSetDefaultPermitted(); |
| |
| // The initialization is only useful when there is an observer. |
| set_as_default_initialized_ = InitializeSetAsDefault(); |
| } |
| |
| // Remember the start time. |
| start_time_ = base::TimeTicks::Now(); |
| |
| BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
| base::Bind(&DefaultWebClientWorker::SetAsDefault, |
| this, interactive_permitted)); |
| } |
| |
| void ShellIntegration::DefaultWebClientWorker::ObserverDestroyed() { |
| // Our associated view has gone away, so we shouldn't call back to it if |
| // our worker thread returns after the view is dead. |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| observer_ = nullptr; |
| |
| if (set_as_default_initialized_) { |
| FinalizeSetAsDefault(); |
| set_as_default_initialized_ = false; |
| } |
| |
| if (set_as_default_in_progress_) |
| ReportAttemptResult(AttemptResult::ABANDONED); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // DefaultWebClientWorker, private: |
| |
| ShellIntegration::DefaultWebClientWorker::~DefaultWebClientWorker() {} |
| |
| void ShellIntegration::DefaultWebClientWorker::OnCheckIsDefaultComplete( |
| DefaultWebClientState state) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| UpdateUI(state); |
| // The worker has finished everything it needs to do, so free the observer |
| // if we own it. |
| if (observer_ && observer_->IsOwnedByWorker()) { |
| delete observer_; |
| observer_ = nullptr; |
| } |
| } |
| |
| void ShellIntegration::DefaultWebClientWorker::OnSetAsDefaultAttemptComplete( |
| AttemptResult result) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| // Hold on to a reference because if this was called via the default browser |
| // callback in StartupBrowserCreator, clearing the callback in |
| // FinalizeSetAsDefault would otherwise remove the last reference and delete |
| // us in the middle of this function. |
| scoped_refptr<DefaultWebClientWorker> scoped_ref(this); |
| |
| if (set_as_default_in_progress_) { |
| set_as_default_in_progress_ = false; |
| |
| if (set_as_default_initialized_) { |
| FinalizeSetAsDefault(); |
| set_as_default_initialized_ = false; |
| } |
| if (observer_) { |
| bool succeeded = result == AttemptResult::SUCCESS || |
| result == AttemptResult::ALREADY_DEFAULT; |
| observer_->OnSetAsDefaultConcluded(succeeded); |
| } |
| |
| ReportAttemptResult(result); |
| |
| // Start the default browser check which will notify the observer as to |
| // whether Chrome is really the default browser. This is needed because |
| // detecting that the process was successful is not 100% sure. |
| // For example, on Windows 10+, the user might have unchecked the "Always |
| // use this app" checkbox which can't be detected. |
| StartCheckIsDefault(); |
| } |
| } |
| |
| void ShellIntegration::DefaultWebClientWorker::ReportAttemptResult( |
| AttemptResult result) { |
| if (!ShouldReportAttemptResults()) |
| return; |
| |
| UMA_HISTOGRAM_ENUMERATION("DefaultBrowser.AsyncSetAsDefault.Result", result, |
| AttemptResult::NUM_ATTEMPT_RESULT_TYPES); |
| |
| switch (result) { |
| case SUCCESS: |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "DefaultBrowser.AsyncSetAsDefault.Duration_Success", |
| base::TimeTicks::Now() - start_time_); |
| break; |
| case FAILURE: |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "DefaultBrowser.AsyncSetAsDefault.Duration_Failure", |
| base::TimeTicks::Now() - start_time_); |
| break; |
| case ABANDONED: |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "DefaultBrowser.AsyncSetAsDefault.Duration_Abandoned", |
| base::TimeTicks::Now() - start_time_); |
| break; |
| case RETRY: |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "DefaultBrowser.AsyncSetAsDefault.Duration_Retry", |
| base::TimeTicks::Now() - start_time_); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| bool ShellIntegration::DefaultWebClientWorker::InitializeSetAsDefault() { |
| return true; |
| } |
| |
| void ShellIntegration::DefaultWebClientWorker::FinalizeSetAsDefault() {} |
| |
| #if !defined(OS_WIN) |
| // static |
| bool ShellIntegration::DefaultWebClientWorker::ShouldReportAttemptResults() { |
| return false; |
| } |
| #endif // !defined(OS_WIN) |
| |
| void ShellIntegration::DefaultWebClientWorker::UpdateUI( |
| DefaultWebClientState state) { |
| if (observer_) { |
| switch (state) { |
| case NOT_DEFAULT: |
| observer_->SetDefaultWebClientUIState(STATE_NOT_DEFAULT); |
| break; |
| case IS_DEFAULT: |
| observer_->SetDefaultWebClientUIState(STATE_IS_DEFAULT); |
| break; |
| case UNKNOWN_DEFAULT: |
| observer_->SetDefaultWebClientUIState(STATE_UNKNOWN); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // ShellIntegration::DefaultBrowserWorker |
| // |
| |
| ShellIntegration::DefaultBrowserWorker::DefaultBrowserWorker( |
| DefaultWebClientObserver* observer) |
| : DefaultWebClientWorker(observer) { |
| } |
| |
| ShellIntegration::DefaultBrowserWorker::~DefaultBrowserWorker() {} |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // DefaultBrowserWorker, private: |
| |
| void ShellIntegration::DefaultBrowserWorker::CheckIsDefault() { |
| DefaultWebClientState state = GetDefaultBrowser(); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&DefaultBrowserWorker::OnCheckIsDefaultComplete, this, state)); |
| } |
| |
| void ShellIntegration::DefaultBrowserWorker::SetAsDefault( |
| bool interactive_permitted) { |
| AttemptResult result = AttemptResult::FAILURE; |
| switch (CanSetAsDefaultBrowser()) { |
| case SET_DEFAULT_NOT_ALLOWED: |
| NOTREACHED(); |
| break; |
| case SET_DEFAULT_UNATTENDED: |
| if (SetAsDefaultBrowser()) |
| result = AttemptResult::SUCCESS; |
| break; |
| case SET_DEFAULT_INTERACTIVE: |
| if (interactive_permitted && SetAsDefaultBrowserInteractive()) |
| result = AttemptResult::SUCCESS; |
| break; |
| case SET_DEFAULT_ASYNCHRONOUS: |
| #if defined(OS_WIN) |
| if (!interactive_permitted) |
| break; |
| if (GetDefaultBrowser() == IS_DEFAULT) { |
| // Don't start the asynchronous operation since it could result in |
| // losing the default browser status. |
| result = AttemptResult::ALREADY_DEFAULT; |
| break; |
| } |
| // This function will cause OnSetAsDefaultAttemptComplete() to be called |
| // asynchronously via a filter established in InitializeSetAsDefault(). |
| if (!SetAsDefaultBrowserAsynchronous()) { |
| result = AttemptResult::LAUNCH_FAILURE; |
| break; |
| } |
| return; |
| #else |
| NOTREACHED(); |
| break; |
| #endif |
| } |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&DefaultBrowserWorker::OnSetAsDefaultAttemptComplete, this, |
| result)); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // ShellIntegration::DefaultProtocolClientWorker |
| // |
| |
| ShellIntegration::DefaultProtocolClientWorker::DefaultProtocolClientWorker( |
| DefaultWebClientObserver* observer, const std::string& protocol) |
| : DefaultWebClientWorker(observer), |
| protocol_(protocol) { |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // DefaultProtocolClientWorker, private: |
| |
| ShellIntegration::DefaultProtocolClientWorker::~DefaultProtocolClientWorker() {} |
| |
| void ShellIntegration::DefaultProtocolClientWorker::CheckIsDefault() { |
| DefaultWebClientState state = IsDefaultProtocolClient(protocol_); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&DefaultProtocolClientWorker::OnCheckIsDefaultComplete, this, |
| state)); |
| } |
| |
| void ShellIntegration::DefaultProtocolClientWorker::SetAsDefault( |
| bool interactive_permitted) { |
| AttemptResult result = AttemptResult::FAILURE; |
| switch (CanSetAsDefaultProtocolClient()) { |
| case SET_DEFAULT_NOT_ALLOWED: |
| // Not allowed, do nothing. |
| break; |
| case SET_DEFAULT_UNATTENDED: |
| if (SetAsDefaultProtocolClient(protocol_)) |
| result = AttemptResult::SUCCESS; |
| break; |
| case SET_DEFAULT_INTERACTIVE: |
| if (interactive_permitted && |
| SetAsDefaultProtocolClientInteractive(protocol_)) { |
| result = AttemptResult::SUCCESS; |
| } |
| break; |
| case SET_DEFAULT_ASYNCHRONOUS: |
| NOTREACHED(); |
| break; |
| } |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&DefaultProtocolClientWorker::OnSetAsDefaultAttemptComplete, |
| this, result)); |
| } |