1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/chrome_browser_main_win.h" 6 7 #include <windows.h> 8 #include <shellapi.h> 9 10 #include <algorithm> 11 12 #include "base/command_line.h" 13 #include "base/environment.h" 14 #include "base/files/file_path.h" 15 #include "base/i18n/rtl.h" 16 #include "base/memory/scoped_ptr.h" 17 #include "base/metrics/histogram.h" 18 #include "base/path_service.h" 19 #include "base/scoped_native_library.h" 20 #include "base/strings/string_number_conversions.h" 21 #include "base/strings/utf_string_conversions.h" 22 #include "base/win/metro.h" 23 #include "base/win/win_util.h" 24 #include "base/win/windows_version.h" 25 #include "base/win/wrapped_window_proc.h" 26 #include "chrome/browser/browser_util_win.h" 27 #include "chrome/browser/chrome_elf_init_win.h" 28 #include "chrome/browser/first_run/first_run.h" 29 #include "chrome/browser/install_verification/win/install_verification.h" 30 #include "chrome/browser/prefs/incognito_mode_prefs.h" 31 #include "chrome/browser/profiles/profile_info_cache.h" 32 #include "chrome/browser/profiles/profile_shortcut_manager.h" 33 #include "chrome/browser/shell_integration.h" 34 #include "chrome/browser/ui/simple_message_box.h" 35 #include "chrome/browser/ui/uninstall_browser_prompt.h" 36 #include "chrome/common/chrome_constants.h" 37 #include "chrome/common/chrome_result_codes.h" 38 #include "chrome/common/chrome_switches.h" 39 #include "chrome/common/chrome_version_info.h" 40 #include "chrome/common/env_vars.h" 41 #include "chrome/installer/launcher_support/chrome_launcher_support.h" 42 #include "chrome/installer/util/browser_distribution.h" 43 #include "chrome/installer/util/helper.h" 44 #include "chrome/installer/util/install_util.h" 45 #include "chrome/installer/util/l10n_string_util.h" 46 #include "chrome/installer/util/shell_util.h" 47 #include "content/public/browser/browser_thread.h" 48 #include "content/public/common/main_function_params.h" 49 #include "grit/app_locale_settings.h" 50 #include "grit/chromium_strings.h" 51 #include "grit/generated_resources.h" 52 #include "installer_util_strings/installer_util_strings.h" 53 #include "ui/base/cursor/cursor_loader_win.h" 54 #include "ui/base/l10n/l10n_util.h" 55 #include "ui/base/l10n/l10n_util_win.h" 56 #include "ui/base/ui_base_switches.h" 57 #include "ui/base/win/message_box_win.h" 58 #include "ui/gfx/platform_font_win.h" 59 #include "ui/gfx/switches.h" 60 61 namespace { 62 63 typedef HRESULT (STDAPICALLTYPE* RegisterApplicationRestartProc)( 64 const wchar_t* command_line, 65 DWORD flags); 66 67 void InitializeWindowProcExceptions() { 68 // Get the breakpad pointer from chrome.exe 69 base::win::WinProcExceptionFilter exception_filter = 70 reinterpret_cast<base::win::WinProcExceptionFilter>( 71 ::GetProcAddress(::GetModuleHandle( 72 chrome::kBrowserProcessExecutableName), 73 "CrashForException")); 74 exception_filter = base::win::SetWinProcExceptionFilter(exception_filter); 75 DCHECK(!exception_filter); 76 } 77 78 // gfx::Font callbacks 79 void AdjustUIFont(LOGFONT* logfont) { 80 l10n_util::AdjustUIFont(logfont); 81 } 82 83 int GetMinimumFontSize() { 84 int min_font_size; 85 base::StringToInt(l10n_util::GetStringUTF16(IDS_MINIMUM_UI_FONT_SIZE), 86 &min_font_size); 87 return min_font_size; 88 } 89 90 class TranslationDelegate : public installer::TranslationDelegate { 91 public: 92 virtual base::string16 GetLocalizedString(int installer_string_id) OVERRIDE; 93 }; 94 95 bool IsSafeModeStart() { 96 return ::GetEnvironmentVariableA(chrome::kSafeModeEnvVar, NULL, 0) != 0; 97 } 98 99 } // namespace 100 101 void ShowCloseBrowserFirstMessageBox() { 102 int message_id = IDS_UNINSTALL_CLOSE_APP; 103 if (base::win::GetVersion() >= base::win::VERSION_WIN8 && 104 (ShellIntegration::GetDefaultBrowser() == ShellIntegration::IS_DEFAULT)) { 105 message_id = IDS_UNINSTALL_CLOSE_APP_IMMERSIVE; 106 } 107 chrome::ShowMessageBox(NULL, 108 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), 109 l10n_util::GetStringUTF16(message_id), 110 chrome::MESSAGE_BOX_TYPE_WARNING); 111 } 112 113 int DoUninstallTasks(bool chrome_still_running) { 114 // We want to show a warning to user (and exit) if Chrome is already running 115 // *before* we show the uninstall confirmation dialog box. But while the 116 // uninstall confirmation dialog is up, user might start Chrome, so we 117 // check once again after user acknowledges Uninstall dialog. 118 if (chrome_still_running) { 119 ShowCloseBrowserFirstMessageBox(); 120 return chrome::RESULT_CODE_UNINSTALL_CHROME_ALIVE; 121 } 122 int result = chrome::ShowUninstallBrowserPrompt( 123 !chrome_launcher_support::IsAppLauncherPresent()); 124 // Don't offer to delete the profile if the App Launcher is also installed. 125 if (browser_util::IsBrowserAlreadyRunning()) { 126 ShowCloseBrowserFirstMessageBox(); 127 return chrome::RESULT_CODE_UNINSTALL_CHROME_ALIVE; 128 } 129 130 if (result != chrome::RESULT_CODE_UNINSTALL_USER_CANCEL) { 131 // The following actions are just best effort. 132 // TODO(gab): Look into removing this code which is now redundant with the 133 // work done by setup.exe on uninstall. 134 VLOG(1) << "Executing uninstall actions"; 135 base::FilePath chrome_exe; 136 if (PathService::Get(base::FILE_EXE, &chrome_exe)) { 137 ShellUtil::ShortcutLocation user_shortcut_locations[] = { 138 ShellUtil::SHORTCUT_LOCATION_DESKTOP, 139 ShellUtil::SHORTCUT_LOCATION_QUICK_LAUNCH, 140 ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_DIR, 141 ShellUtil::SHORTCUT_LOCATION_START_MENU_CHROME_APPS_DIR, 142 }; 143 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 144 for (size_t i = 0; i < arraysize(user_shortcut_locations); ++i) { 145 if (!ShellUtil::RemoveShortcuts(user_shortcut_locations[i], dist, 146 ShellUtil::CURRENT_USER, chrome_exe)) { 147 VLOG(1) << "Failed to delete shortcut at location " 148 << user_shortcut_locations[i]; 149 } 150 } 151 } else { 152 NOTREACHED(); 153 } 154 } 155 return result; 156 } 157 158 void MaybeEnableHighResolutionTimeEverywhere() { 159 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 160 bool user_enabled = CommandLine::ForCurrentProcess()->HasSwitch( 161 switches::kEnableHighResolutionTime); 162 if (user_enabled || channel == chrome::VersionInfo::CHANNEL_CANARY) { 163 bool is_enabled = base::TimeTicks::SetNowIsHighResNowIfSupported(); 164 if (is_enabled && !user_enabled) { 165 // Ensure that all of the renderers will enable it too. 166 CommandLine::ForCurrentProcess()->AppendSwitch( 167 switches::kEnableHighResolutionTime); 168 } 169 } 170 } 171 172 // ChromeBrowserMainPartsWin --------------------------------------------------- 173 174 ChromeBrowserMainPartsWin::ChromeBrowserMainPartsWin( 175 const content::MainFunctionParams& parameters) 176 : ChromeBrowserMainParts(parameters) { 177 MaybeEnableHighResolutionTimeEverywhere(); 178 if (base::win::IsMetroProcess()) { 179 typedef const wchar_t* (*GetMetroSwitches)(void); 180 GetMetroSwitches metro_switches_proc = reinterpret_cast<GetMetroSwitches>( 181 GetProcAddress(base::win::GetMetroModule(), 182 "GetMetroCommandLineSwitches")); 183 if (metro_switches_proc) { 184 base::string16 metro_switches = (*metro_switches_proc)(); 185 if (!metro_switches.empty()) { 186 CommandLine extra_switches(CommandLine::NO_PROGRAM); 187 extra_switches.ParseFromString(metro_switches); 188 CommandLine::ForCurrentProcess()->AppendArguments(extra_switches, 189 false); 190 } 191 } 192 } 193 } 194 195 ChromeBrowserMainPartsWin::~ChromeBrowserMainPartsWin() { 196 } 197 198 void ChromeBrowserMainPartsWin::ToolkitInitialized() { 199 ChromeBrowserMainParts::ToolkitInitialized(); 200 gfx::PlatformFontWin::adjust_font_callback = &AdjustUIFont; 201 gfx::PlatformFontWin::get_minimum_font_size_callback = &GetMinimumFontSize; 202 #if defined(USE_AURA) 203 ui::CursorLoaderWin::SetCursorResourceModule(chrome::kBrowserResourcesDll); 204 #endif 205 } 206 207 void ChromeBrowserMainPartsWin::PreMainMessageLoopStart() { 208 // installer_util references strings that are normally compiled into 209 // setup.exe. In Chrome, these strings are in the locale files. 210 SetupInstallerUtilStrings(); 211 212 ChromeBrowserMainParts::PreMainMessageLoopStart(); 213 if (!parameters().ui_task) { 214 // Make sure that we know how to handle exceptions from the message loop. 215 InitializeWindowProcExceptions(); 216 } 217 218 IncognitoModePrefs::InitializePlatformParentalControls(); 219 } 220 221 int ChromeBrowserMainPartsWin::PreCreateThreads() { 222 int rv = ChromeBrowserMainParts::PreCreateThreads(); 223 224 if (IsSafeModeStart()) { 225 // TODO(cpu): disable other troublesome features for safe mode. 226 CommandLine::ForCurrentProcess()->AppendSwitch( 227 switches::kDisableGpu); 228 } 229 // TODO(viettrungluu): why don't we run this earlier? 230 if (!parsed_command_line().HasSwitch(switches::kNoErrorDialogs) && 231 base::win::GetVersion() < base::win::VERSION_XP) { 232 chrome::ShowMessageBox(NULL, 233 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), 234 l10n_util::GetStringUTF16(IDS_UNSUPPORTED_OS_PRE_WIN_XP), 235 chrome::MESSAGE_BOX_TYPE_WARNING); 236 } 237 238 return rv; 239 } 240 241 void ChromeBrowserMainPartsWin::ShowMissingLocaleMessageBox() { 242 ui::MessageBox(NULL, 243 base::ASCIIToUTF16(chrome_browser::kMissingLocaleDataMessage), 244 base::ASCIIToUTF16(chrome_browser::kMissingLocaleDataTitle), 245 MB_OK | MB_ICONERROR | MB_TOPMOST); 246 } 247 248 void ChromeBrowserMainPartsWin::PostBrowserStart() { 249 ChromeBrowserMainParts::PostBrowserStart(); 250 251 UMA_HISTOGRAM_BOOLEAN("Windows.Tablet", base::win::IsTabletDevice()); 252 253 // Set up a task to verify installed modules in the current process. Use a 254 // delay to reduce the impact on startup time. 255 content::BrowserThread::GetMessageLoopProxyForThread( 256 content::BrowserThread::UI)->PostDelayedTask( 257 FROM_HERE, 258 base::Bind(&VerifyInstallation), 259 base::TimeDelta::FromSeconds(45)); 260 261 InitializeChromeElf(); 262 } 263 264 // static 265 void ChromeBrowserMainPartsWin::PrepareRestartOnCrashEnviroment( 266 const CommandLine& parsed_command_line) { 267 // Clear this var so child processes don't show the dialog by default. 268 scoped_ptr<base::Environment> env(base::Environment::Create()); 269 env->UnSetVar(env_vars::kShowRestart); 270 271 // For non-interactive tests we don't restart on crash. 272 if (env->HasVar(env_vars::kHeadless)) 273 return; 274 275 // If the known command-line test options are used we don't create the 276 // environment block which means we don't get the restart dialog. 277 if (parsed_command_line.HasSwitch(switches::kBrowserCrashTest) || 278 parsed_command_line.HasSwitch(switches::kBrowserAssertTest) || 279 parsed_command_line.HasSwitch(switches::kNoErrorDialogs)) 280 return; 281 282 // The encoding we use for the info is "title|context|direction" where 283 // direction is either env_vars::kRtlLocale or env_vars::kLtrLocale depending 284 // on the current locale. 285 base::string16 dlg_strings(l10n_util::GetStringUTF16(IDS_CRASH_RECOVERY_TITLE)); 286 dlg_strings.push_back('|'); 287 base::string16 adjusted_string( 288 l10n_util::GetStringUTF16(IDS_CRASH_RECOVERY_CONTENT)); 289 base::i18n::AdjustStringForLocaleDirection(&adjusted_string); 290 dlg_strings.append(adjusted_string); 291 dlg_strings.push_back('|'); 292 dlg_strings.append(base::ASCIIToUTF16( 293 base::i18n::IsRTL() ? env_vars::kRtlLocale : env_vars::kLtrLocale)); 294 295 env->SetVar(env_vars::kRestartInfo, base::UTF16ToUTF8(dlg_strings)); 296 } 297 298 // static 299 void ChromeBrowserMainPartsWin::RegisterApplicationRestart( 300 const CommandLine& parsed_command_line) { 301 DCHECK(base::win::GetVersion() >= base::win::VERSION_VISTA); 302 base::ScopedNativeLibrary library(base::FilePath(L"kernel32.dll")); 303 // Get the function pointer for RegisterApplicationRestart. 304 RegisterApplicationRestartProc register_application_restart = 305 reinterpret_cast<RegisterApplicationRestartProc>( 306 library.GetFunctionPointer("RegisterApplicationRestart")); 307 if (!register_application_restart) { 308 LOG(WARNING) << "Cannot find RegisterApplicationRestart in kernel32.dll"; 309 return; 310 } 311 // The Windows Restart Manager expects a string of command line flags only, 312 // without the program. 313 CommandLine command_line(CommandLine::NO_PROGRAM); 314 command_line.AppendArguments(parsed_command_line, false); 315 if (!command_line.HasSwitch(switches::kRestoreLastSession)) 316 command_line.AppendSwitch(switches::kRestoreLastSession); 317 318 // Restart Chrome if the computer is restarted as the result of an update. 319 // This could be extended to handle crashes, hangs, and patches. 320 HRESULT hr = register_application_restart( 321 command_line.GetCommandLineString().c_str(), 322 RESTART_NO_CRASH | RESTART_NO_HANG | RESTART_NO_PATCH); 323 if (FAILED(hr)) { 324 if (hr == E_INVALIDARG) { 325 LOG(WARNING) << "Command line too long for RegisterApplicationRestart"; 326 } else { 327 NOTREACHED() << "RegisterApplicationRestart failed. hr: " << hr << 328 ", command_line: " << command_line.GetCommandLineString(); 329 } 330 } 331 } 332 333 // static 334 int ChromeBrowserMainPartsWin::HandleIconsCommands( 335 const CommandLine& parsed_command_line) { 336 if (parsed_command_line.HasSwitch(switches::kHideIcons)) { 337 base::string16 cp_applet; 338 base::win::Version version = base::win::GetVersion(); 339 if (version >= base::win::VERSION_VISTA) { 340 cp_applet.assign(L"Programs and Features"); // Windows Vista and later. 341 } else if (version >= base::win::VERSION_XP) { 342 cp_applet.assign(L"Add/Remove Programs"); // Windows XP. 343 } else { 344 return chrome::RESULT_CODE_UNSUPPORTED_PARAM; // Not supported 345 } 346 347 const base::string16 msg = 348 l10n_util::GetStringFUTF16(IDS_HIDE_ICONS_NOT_SUPPORTED, cp_applet); 349 const base::string16 caption = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME); 350 const UINT flags = MB_OKCANCEL | MB_ICONWARNING | MB_TOPMOST; 351 if (IDOK == ui::MessageBox(NULL, msg, caption, flags)) 352 ShellExecute(NULL, NULL, L"appwiz.cpl", NULL, NULL, SW_SHOWNORMAL); 353 354 // Exit as we are not launching the browser. 355 return content::RESULT_CODE_NORMAL_EXIT; 356 } 357 // We don't hide icons so we shouldn't do anything special to show them 358 return chrome::RESULT_CODE_UNSUPPORTED_PARAM; 359 } 360 361 // static 362 bool ChromeBrowserMainPartsWin::CheckMachineLevelInstall() { 363 // TODO(tommi): Check if using the default distribution is always the right 364 // thing to do. 365 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 366 Version version; 367 InstallUtil::GetChromeVersion(dist, true, &version); 368 if (version.IsValid()) { 369 base::FilePath exe_path; 370 PathService::Get(base::DIR_EXE, &exe_path); 371 std::wstring exe = exe_path.value(); 372 base::FilePath user_exe_path(installer::GetChromeInstallPath(false, dist)); 373 if (base::FilePath::CompareEqualIgnoreCase(exe, user_exe_path.value())) { 374 bool is_metro = base::win::IsMetroProcess(); 375 if (!is_metro) { 376 // The dialog cannot be shown in Win8 Metro as doing so hangs Chrome on 377 // an invisible dialog. 378 // TODO (gab): Get rid of this dialog altogether and auto-launch 379 // system-level Chrome instead. 380 const base::string16 text = 381 l10n_util::GetStringUTF16(IDS_MACHINE_LEVEL_INSTALL_CONFLICT); 382 const base::string16 caption = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME); 383 const UINT flags = MB_OK | MB_ICONERROR | MB_TOPMOST; 384 ui::MessageBox(NULL, text, caption, flags); 385 } 386 CommandLine uninstall_cmd( 387 InstallUtil::GetChromeUninstallCmd(false, dist->GetType())); 388 if (!uninstall_cmd.GetProgram().empty()) { 389 uninstall_cmd.AppendSwitch(installer::switches::kSelfDestruct); 390 uninstall_cmd.AppendSwitch(installer::switches::kForceUninstall); 391 uninstall_cmd.AppendSwitch( 392 installer::switches::kDoNotRemoveSharedItems); 393 394 // Trigger Active Setup for the system-level Chrome to make sure 395 // per-user shortcuts to the system-level Chrome are created. Skip this 396 // if the system-level Chrome will undergo first run anyway, as Active 397 // Setup is triggered on system-level Chrome's first run. 398 // TODO(gab): Instead of having callers of Active Setup think about 399 // other callers, have Active Setup itself register when it ran and 400 // no-op otherwise (http://crbug.com/346843). 401 if (!first_run::IsChromeFirstRun()) 402 uninstall_cmd.AppendSwitch(installer::switches::kTriggerActiveSetup); 403 404 const base::FilePath setup_exe(uninstall_cmd.GetProgram()); 405 const base::string16 params(uninstall_cmd.GetArgumentsString()); 406 407 SHELLEXECUTEINFO sei = { sizeof(sei) }; 408 sei.fMask = SEE_MASK_NOASYNC; 409 sei.nShow = SW_SHOWNORMAL; 410 sei.lpFile = setup_exe.value().c_str(); 411 sei.lpParameters = params.c_str(); 412 // On Windows 8 SEE_MASK_FLAG_LOG_USAGE is necessary to guarantee we 413 // flip to the Desktop when launching. 414 if (is_metro) 415 sei.fMask |= SEE_MASK_FLAG_LOG_USAGE; 416 417 if (!::ShellExecuteEx(&sei)) 418 DPCHECK(false); 419 } 420 return true; 421 } 422 } 423 return false; 424 } 425 426 base::string16 TranslationDelegate::GetLocalizedString( 427 int installer_string_id) { 428 int resource_id = 0; 429 switch (installer_string_id) { 430 // HANDLE_STRING is used by the DO_INSTALLER_STRING_MAPPING macro which is in 431 // the generated header installer_util_strings.h. 432 #define HANDLE_STRING(base_id, chrome_id) \ 433 case base_id: \ 434 resource_id = chrome_id; \ 435 break; 436 DO_INSTALLER_STRING_MAPPING 437 #undef HANDLE_STRING 438 default: 439 NOTREACHED(); 440 } 441 if (resource_id) 442 return l10n_util::GetStringUTF16(resource_id); 443 return base::string16(); 444 } 445 446 // static 447 void ChromeBrowserMainPartsWin::SetupInstallerUtilStrings() { 448 CR_DEFINE_STATIC_LOCAL(TranslationDelegate, delegate, ()); 449 installer::SetTranslationDelegate(&delegate); 450 } 451