blob: 59101baef07077522348f4c76a6a8d18dcd59b7f [file] [log] [blame]
// Copyright (c) 2010 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/first_run.h"
#include <windows.h>
#include <shellapi.h>
#include <shlobj.h>
#include <set>
#include <sstream>
// TODO(port): trim this include list once first run has been refactored fully.
#include "app/app_switches.h"
#include "app/l10n_util.h"
#include "app/l10n_util_win.h"
#include "app/resource_bundle.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/object_watcher.h"
#include "base/path_service.h"
#include "base/process.h"
#include "base/process_util.h"
#include "base/registry.h"
#include "base/scoped_comptr_win.h"
#include "base/string_util.h"
#include "base/win_util.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/notification_type.h"
#include "chrome/common/pref_names.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extensions_service.h"
#include "chrome/browser/extensions/extension_updater.h"
#include "chrome/browser/hang_monitor/hung_window_detector.h"
#include "chrome/browser/importer/importer.h"
#include "chrome/browser/importer/importer_data_types.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/pref_service.h"
#include "chrome/browser/process_singleton.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/profile_manager.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/views/first_run_search_engine_view.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/result_codes.h"
#include "chrome/installer/util/browser_distribution.h"
#include "chrome/installer/util/google_update_constants.h"
#include "chrome/installer/util/google_update_settings.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/master_preferences.h"
#include "chrome/installer/util/shell_util.h"
#include "chrome/installer/util/util_constants.h"
#include "google_update_idl.h"
#include "grit/app_resources.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "grit/theme_resources.h"
#include "views/background.h"
#include "views/controls/button/image_button.h"
#include "views/controls/button/native_button.h"
#include "views/controls/button/radio_button.h"
#include "views/controls/image_view.h"
#include "views/controls/label.h"
#include "views/controls/link.h"
#include "views/focus/accelerator_handler.h"
#include "views/grid_layout.h"
#include "views/standard_layout.h"
#include "views/widget/root_view.h"
#include "views/widget/widget_win.h"
#include "views/window/window.h"
#include "views/window/window_delegate.h"
#include "views/window/window_win.h"
namespace {
bool GetNewerChromeFile(FilePath* path) {
if (!PathService::Get(base::DIR_EXE, path))
return false;
*path = path->Append(installer_util::kChromeNewExe);
return true;
}
bool GetBackupChromeFile(std::wstring* path) {
if (!PathService::Get(base::DIR_EXE, path))
return false;
file_util::AppendToPath(path, installer_util::kChromeOldExe);
return true;
}
FilePath GetDefaultPrefFilePath(bool create_profile_dir,
const FilePath& user_data_dir) {
FilePath default_pref_dir =
ProfileManager::GetDefaultProfileDir(user_data_dir);
if (create_profile_dir) {
if (!file_util::PathExists(default_pref_dir)) {
if (!file_util::CreateDirectory(default_pref_dir))
return FilePath();
}
}
return ProfileManager::GetProfilePrefsPath(default_pref_dir);
}
bool InvokeGoogleUpdateForRename() {
ScopedComPtr<IProcessLauncher> ipl;
if (!FAILED(ipl.CreateInstance(__uuidof(ProcessLauncherClass)))) {
ULONG_PTR phandle = NULL;
DWORD id = GetCurrentProcessId();
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
if (!FAILED(ipl->LaunchCmdElevated(dist->GetAppGuid().c_str(),
google_update::kRegRenameCmdField,
id, &phandle))) {
HANDLE handle = HANDLE(phandle);
DWORD exit_code;
::GetExitCodeProcess(handle, &exit_code);
::CloseHandle(handle);
if (exit_code == installer_util::RENAME_SUCCESSFUL)
return true;
}
}
return false;
}
bool LaunchSetupWithParam(const std::string& param, const std::wstring& value,
int* ret_code) {
FilePath exe_path;
if (!PathService::Get(base::DIR_MODULE, &exe_path))
return false;
exe_path = exe_path.Append(installer_util::kInstallerDir);
exe_path = exe_path.Append(installer_util::kSetupExe);
base::ProcessHandle ph;
CommandLine cl(exe_path);
cl.AppendSwitchWithValue(param, value);
CommandLine* browser_command_line = CommandLine::ForCurrentProcess();
if (browser_command_line->HasSwitch(switches::kChromeFrame)) {
cl.AppendSwitch(switches::kChromeFrame);
}
if (!base::LaunchApp(cl, false, false, &ph))
return false;
DWORD wr = ::WaitForSingleObject(ph, INFINITE);
if (wr != WAIT_OBJECT_0)
return false;
return (TRUE == ::GetExitCodeProcess(ph, reinterpret_cast<DWORD*>(ret_code)));
}
bool WriteEULAtoTempFile(FilePath* eula_path) {
base::StringPiece terms =
ResourceBundle::GetSharedInstance().GetRawDataResource(IDR_TERMS_HTML);
if (terms.empty())
return false;
FilePath temp_dir;
if (!file_util::GetTempDir(&temp_dir))
return false;
*eula_path = temp_dir.Append(L"chrome_eula_iframe.html");
return (file_util::WriteFile(*eula_path, terms.data(), terms.size()) > 0);
}
// Helper class that performs delayed first-run tasks that need more of the
// chrome infrastructure to be up and running before they can be attempted.
class FirstRunDelayedTasks : public NotificationObserver {
public:
enum Tasks {
NO_TASK,
INSTALL_EXTENSIONS
};
explicit FirstRunDelayedTasks(Tasks task) {
if (task == INSTALL_EXTENSIONS) {
registrar_.Add(this, NotificationType::EXTENSIONS_READY,
NotificationService::AllSources());
}
registrar_.Add(this, NotificationType::BROWSER_CLOSED,
NotificationService::AllSources());
}
virtual void Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
// After processing the notification we always delete ourselves.
if (type.value == NotificationType::EXTENSIONS_READY)
DoExtensionWork(Source<Profile>(source).ptr()->GetExtensionsService());
delete this;
return;
}
private:
// Private ctor forces it to be created only in the heap.
~FirstRunDelayedTasks() {}
// The extension work is to basically trigger an extension update check.
// If the extension specified in the master pref is older than the live
// extension it will get updated which is the same as get it installed.
void DoExtensionWork(ExtensionsService* service) {
if (!service)
return;
service->updater()->CheckNow();
return;
}
NotificationRegistrar registrar_;
};
} // namespace
CommandLine* Upgrade::new_command_line_ = NULL;
bool FirstRun::CreateChromeDesktopShortcut() {
std::wstring chrome_exe;
if (!PathService::Get(base::FILE_EXE, &chrome_exe))
return false;
BrowserDistribution *dist = BrowserDistribution::GetDistribution();
if (!dist)
return false;
return ShellUtil::CreateChromeDesktopShortcut(chrome_exe,
dist->GetAppDescription(), ShellUtil::CURRENT_USER,
false, true); // create if doesn't exist.
}
bool FirstRun::CreateChromeQuickLaunchShortcut() {
std::wstring chrome_exe;
if (!PathService::Get(base::FILE_EXE, &chrome_exe))
return false;
return ShellUtil::CreateChromeQuickLaunchShortcut(chrome_exe,
ShellUtil::CURRENT_USER, // create only for current user.
true); // create if doesn't exist.
}
bool FirstRun::ProcessMasterPreferences(const FilePath& user_data_dir,
MasterPrefs* out_prefs) {
DCHECK(!user_data_dir.empty());
// The standard location of the master prefs is next to the chrome exe.
FilePath master_prefs;
if (!PathService::Get(base::DIR_EXE, &master_prefs))
return true;
master_prefs = master_prefs.AppendASCII(installer_util::kDefaultMasterPrefs);
scoped_ptr<DictionaryValue> prefs(
installer_util::ParseDistributionPreferences(master_prefs));
if (!prefs.get())
return true;
out_prefs->new_tabs = installer_util::GetFirstRunTabs(prefs.get());
if (!installer_util::GetDistroIntegerPreference(prefs.get(),
installer_util::master_preferences::kDistroPingDelay,
&out_prefs->ping_delay)) {
// 90 seconds is the default that we want to use in case master
// preferences is missing, corrupt or ping_delay is missing.
out_prefs->ping_delay = 90;
}
std::string not_used;
out_prefs->homepage_defined = prefs->GetString(prefs::kHomePage, &not_used);
bool value = false;
if (installer_util::GetDistroBooleanPreference(prefs.get(),
installer_util::master_preferences::kRequireEula, &value) && value) {
// Show the post-installation EULA. This is done by setup.exe and the
// result determines if we continue or not. We wait here until the user
// dismisses the dialog.
// The actual eula text is in a resource in chrome. We extract it to
// a text file so setup.exe can use it as an inner frame.
FilePath inner_html;
if (WriteEULAtoTempFile(&inner_html)) {
int retcode = 0;
const std::string eula = WideToASCII(installer_util::switches::kShowEula);
if (!LaunchSetupWithParam(eula, inner_html.ToWStringHack(), &retcode) ||
(retcode == installer_util::EULA_REJECTED)) {
LOG(WARNING) << "EULA rejected. Fast exit.";
::ExitProcess(1);
}
if (retcode == installer_util::EULA_ACCEPTED) {
LOG(INFO) << "EULA : no collection";
GoogleUpdateSettings::SetCollectStatsConsent(false);
} else if (retcode == installer_util::EULA_ACCEPTED_OPT_IN) {
LOG(INFO) << "EULA : collection consent";
GoogleUpdateSettings::SetCollectStatsConsent(true);
}
}
}
if (installer_util::GetDistroBooleanPreference(prefs.get(),
installer_util::master_preferences::kAltFirstRunBubble, &value) && value)
FirstRun::SetOEMFirstRunBubblePref();
FilePath user_prefs = GetDefaultPrefFilePath(true, user_data_dir);
if (user_prefs.empty())
return true;
// The master prefs are regular prefs so we can just copy the file
// to the default place and they just work.
if (!file_util::CopyFile(master_prefs, user_prefs))
return true;
DictionaryValue* extensions = 0;
if (installer_util::HasExtensionsBlock(prefs.get(), &extensions)) {
LOG(INFO) << "Extensions block found in master preferences";
new FirstRunDelayedTasks(FirstRunDelayedTasks::INSTALL_EXTENSIONS);
}
if (installer_util::GetDistroBooleanPreference(prefs.get(),
installer_util::master_preferences::kDistroImportSearchPref, &value)) {
if (value) {
out_prefs->do_import_items |= importer::SEARCH_ENGINES;
} else {
out_prefs->dont_import_items |= importer::SEARCH_ENGINES;
}
}
// Check to see if search engine logos should be randomized.
if (installer_util::GetDistroBooleanPreference(prefs.get(),
installer_util::master_preferences::kSearchEngineExperimentRandomizePref,
&value) && value)
out_prefs->randomize_search_engine_experiment = true;
// If we're suppressing the first-run bubble, set that preference now.
// Otherwise, wait until the user has completed first run to set it, so the
// user is guaranteed to see the bubble iff he or she has completed the first
// run process.
if (installer_util::GetDistroBooleanPreference(prefs.get(),
installer_util::master_preferences::kDistroSuppressFirstRunBubble,
&value) && value)
FirstRun::SetShowFirstRunBubblePref(false);
if (installer_util::GetDistroBooleanPreference(prefs.get(),
installer_util::master_preferences::kDistroImportHistoryPref, &value)) {
if (value) {
out_prefs->do_import_items |= importer::HISTORY;
} else {
out_prefs->dont_import_items |= importer::HISTORY;
}
}
if (installer_util::GetDistroBooleanPreference(prefs.get(),
installer_util::master_preferences::kDistroImportHomePagePref, &value)) {
if (value) {
out_prefs->do_import_items |= importer::HOME_PAGE;
} else {
out_prefs->dont_import_items |= importer::HOME_PAGE;
}
}
// Bookmarks are never imported unless specifically turned on.
if (installer_util::GetDistroBooleanPreference(prefs.get(),
installer_util::master_preferences::kDistroImportBookmarksPref, &value)
&& value) {
out_prefs->do_import_items |= importer::FAVORITES;
}
if (installer_util::GetDistroBooleanPreference(prefs.get(),
installer_util::master_preferences::kMakeChromeDefaultForUser, &value) &&
value)
ShellIntegration::SetAsDefaultBrowser();
// TODO(mirandac): Refactor skip-first-run-ui process into regular first run
// import process. http://crbug.com/49647
// Note we are skipping all other master preferences if skip-first-run-ui
// is *not* specified. (That is, we continue only if skipping first run ui.)
if (!installer_util::GetDistroBooleanPreference(prefs.get(),
installer_util::master_preferences::kDistroSkipFirstRunPref, &value) ||
!value)
return true;
// We need to be able to create the first run sentinel or else we cannot
// proceed because ImportSettings will launch the importer process which
// would end up here if the sentinel is not present.
if (!FirstRun::CreateSentinel())
return false;
if (installer_util::GetDistroBooleanPreference(prefs.get(),
installer_util::master_preferences::kDistroShowWelcomePage, &value) &&
value)
FirstRun::SetShowWelcomePagePref();
std::wstring import_bookmarks_path;
installer_util::GetDistroStringPreference(prefs.get(),
installer_util::master_preferences::kDistroImportBookmarksFromFilePref,
&import_bookmarks_path);
std::wstring brand;
GoogleUpdateSettings::GetBrand(&brand);
// This should generally be true, as skip-first-run-ui is a setting used only
// for non-organic builds.
if (GoogleUpdateSettings::IsOrganic(brand)) {
// If search engines aren't explicitly imported, don't import.
if (!(out_prefs->do_import_items & importer::SEARCH_ENGINES)) {
out_prefs->dont_import_items |= importer::SEARCH_ENGINES;
}
// If home page isn't explicitly imported, don't import.
if (!(out_prefs->do_import_items & importer::HOME_PAGE)) {
out_prefs->dont_import_items |= importer::HOME_PAGE;
}
// If history isn't explicitly forbidden, do import.
if (!(out_prefs->dont_import_items & importer::HISTORY)) {
out_prefs->do_import_items |= importer::HISTORY;
}
}
if (out_prefs->do_import_items || !import_bookmarks_path.empty()) {
// There is something to import from the default browser. This launches
// the importer process and blocks until done or until it fails.
scoped_refptr<ImporterHost> importer_host = new ImporterHost();
if (!FirstRun::ImportSettings(NULL,
importer_host->GetSourceProfileInfoAt(0).browser_type,
out_prefs->do_import_items, import_bookmarks_path, true, NULL)) {
LOG(WARNING) << "silent import failed";
}
}
return false;
}
bool Upgrade::IsBrowserAlreadyRunning() {
static HANDLE handle = NULL;
std::wstring exe;
PathService::Get(base::FILE_EXE, &exe);
std::replace(exe.begin(), exe.end(), '\\', '!');
std::transform(exe.begin(), exe.end(), exe.begin(), tolower);
exe = L"Global\\" + exe;
if (handle != NULL)
CloseHandle(handle);
handle = CreateEvent(NULL, TRUE, TRUE, exe.c_str());
int error = GetLastError();
return (error == ERROR_ALREADY_EXISTS || error == ERROR_ACCESS_DENIED);
}
bool Upgrade::RelaunchChromeBrowser(const CommandLine& command_line) {
::SetEnvironmentVariable(
BrowserDistribution::GetDistribution()->GetEnvVersionKey().c_str(),
NULL);
return base::LaunchApp(command_line.command_line_string(),
false, false, NULL);
}
bool Upgrade::SwapNewChromeExeIfPresent() {
FilePath new_chrome_exe;
if (!GetNewerChromeFile(&new_chrome_exe))
return false;
if (!file_util::PathExists(new_chrome_exe))
return false;
std::wstring curr_chrome_exe;
if (!PathService::Get(base::FILE_EXE, &curr_chrome_exe))
return false;
// First try to rename exe by launching rename command ourselves.
bool user_install = InstallUtil::IsPerUserInstall(curr_chrome_exe.c_str());
HKEY reg_root = user_install ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
BrowserDistribution *dist = BrowserDistribution::GetDistribution();
RegKey key;
std::wstring rename_cmd;
if (key.Open(reg_root, dist->GetVersionKey().c_str(), KEY_READ) &&
key.ReadValue(google_update::kRegRenameCmdField, &rename_cmd)) {
base::ProcessHandle handle;
if (base::LaunchApp(rename_cmd, true, true, &handle)) {
DWORD exit_code;
::GetExitCodeProcess(handle, &exit_code);
::CloseHandle(handle);
if (exit_code == installer_util::RENAME_SUCCESSFUL)
return true;
}
}
// Rename didn't work so try to rename by calling Google Update
if (InvokeGoogleUpdateForRename())
return true;
// Rename still didn't work so just try to rename exe ourselves (for
// backward compatibility, can be deleted once the new process works).
std::wstring backup_exe;
if (!GetBackupChromeFile(&backup_exe))
return false;
if (::ReplaceFileW(curr_chrome_exe.c_str(), new_chrome_exe.value().c_str(),
backup_exe.c_str(), REPLACEFILE_IGNORE_MERGE_ERRORS,
NULL, NULL)) {
return true;
}
return false;
}
// static
bool Upgrade::DoUpgradeTasks(const CommandLine& command_line) {
if (!Upgrade::SwapNewChromeExeIfPresent())
return false;
// At this point the chrome.exe has been swapped with the new one.
if (!Upgrade::RelaunchChromeBrowser(command_line)) {
// The re-launch fails. Feel free to panic now.
NOTREACHED();
}
return true;
}
// static
bool Upgrade::IsUpdatePendingRestart() {
FilePath new_chrome_exe;
if (!GetNewerChromeFile(&new_chrome_exe))
return false;
return file_util::PathExists(new_chrome_exe);
}
namespace {
// This class is used by FirstRun::ImportSettings to determine when the import
// process has ended and what was the result of the operation as reported by
// the process exit code. This class executes in the context of the main chrome
// process.
class ImportProcessRunner : public base::ObjectWatcher::Delegate {
public:
// The constructor takes the importer process to watch and then it does a
// message loop blocking wait until the process ends. This object now owns
// the import_process handle.
explicit ImportProcessRunner(base::ProcessHandle import_process)
: import_process_(import_process),
exit_code_(ResultCodes::NORMAL_EXIT) {
watcher_.StartWatching(import_process, this);
MessageLoop::current()->Run();
}
virtual ~ImportProcessRunner() {
::CloseHandle(import_process_);
}
// Returns the child process exit code. There are 3 expected values:
// NORMAL_EXIT, IMPORTER_CANCEL or IMPORTER_HUNG.
int exit_code() const {
return exit_code_;
}
// The child process has terminated. Find the exit code and quit the loop.
virtual void OnObjectSignaled(HANDLE object) {
DCHECK(object == import_process_);
if (!::GetExitCodeProcess(import_process_, &exit_code_)) {
NOTREACHED();
}
MessageLoop::current()->Quit();
}
private:
base::ObjectWatcher watcher_;
base::ProcessHandle import_process_;
DWORD exit_code_;
};
// Check every 3 seconds if the importer UI has hung.
const int kPollHangFrequency = 3000;
// This class specializes on finding hung 'owned' windows. Unfortunately, the
// HungWindowDetector class cannot be used here because it assumes child
// windows and not owned top-level windows.
// This code is executed in the context of the main browser process and will
// terminate the importer process if it is hung.
class HungImporterMonitor : public WorkerThreadTicker::Callback {
public:
// The ctor takes the owner popup window and the process handle of the
// process to kill in case the popup or its owned active popup become
// unresponsive.
HungImporterMonitor(HWND owner_window, base::ProcessHandle import_process)
: owner_window_(owner_window),
import_process_(import_process),
ticker_(kPollHangFrequency) {
ticker_.RegisterTickHandler(this);
ticker_.Start();
}
virtual ~HungImporterMonitor() {
ticker_.Stop();
ticker_.UnregisterTickHandler(this);
}
private:
virtual void OnTick() {
if (!import_process_)
return;
// We find the top active popup that we own, this will be either the
// owner_window_ itself or the dialog window of the other process. In
// both cases it is worth hung testing because both windows share the
// same message queue and at some point the other window could be gone
// while the other process still not pumping messages.
HWND active_window = ::GetLastActivePopup(owner_window_);
if (::IsHungAppWindow(active_window) || ::IsHungAppWindow(owner_window_)) {
::TerminateProcess(import_process_, ResultCodes::IMPORTER_HUNG);
import_process_ = NULL;
}
}
HWND owner_window_;
base::ProcessHandle import_process_;
WorkerThreadTicker ticker_;
DISALLOW_COPY_AND_ASSIGN(HungImporterMonitor);
};
std::wstring EncodeImportParams(int browser_type, int options,
int skip_first_run_ui, HWND window) {
return StringPrintf(L"%d@%d@%d@%d", browser_type, options, skip_first_run_ui,
window);
}
bool DecodeImportParams(const std::wstring& encoded, int* browser_type,
int* options, int* skip_first_run_ui, HWND* window) {
std::vector<std::wstring> v;
SplitString(encoded, L'@', &v);
if (v.size() != 4)
return false;
if (!StringToInt(v[0], browser_type))
return false;
if (!StringToInt(v[1], options))
return false;
if (!StringToInt(v[2], skip_first_run_ui))
return false;
*window = reinterpret_cast<HWND>(StringToInt64(v[3]));
return true;
}
} // namespace
void FirstRun::AutoImport(Profile* profile,
bool homepage_defined,
int import_items,
int dont_import_items,
bool search_engine_experiment,
bool randomize_search_engine_experiment,
ProcessSingleton* process_singleton) {
FirstRun::CreateChromeDesktopShortcut();
// Windows 7 has deprecated the quick launch bar.
if (win_util::GetWinVersion() < win_util::WINVERSION_WIN7)
CreateChromeQuickLaunchShortcut();
scoped_refptr<ImporterHost> importer_host;
importer_host = new ImporterHost();
int items = 0;
// History is always imported unless turned off in master_preferences.
if (!(dont_import_items & importer::HISTORY))
items = items | importer::HISTORY;
// Home page is imported in organic builds only unless turned off or defined
// in master_preferences.
std::wstring brand;
GoogleUpdateSettings::GetBrand(&brand);
if (GoogleUpdateSettings::IsOrganic(brand)) {
if (!(dont_import_items & importer::HOME_PAGE) && !homepage_defined) {
items = items | importer::HOME_PAGE;
}
} else {
if (import_items & importer::HOME_PAGE) {
items = items | importer::HOME_PAGE;
}
}
// Search engines are imported in organic builds only, unless turned on
// in master_preferences.
if (GoogleUpdateSettings::IsOrganic(brand)) {
if (!(dont_import_items & importer::SEARCH_ENGINES)) {
items = items | importer::SEARCH_ENGINES;
}
} else {
if (import_items & importer::SEARCH_ENGINES) {
items = items | importer::SEARCH_ENGINES;
}
}
// Bookmarks are never imported unless turned on in master_preferences.
if (import_items & importer::FAVORITES)
items = items | importer::FAVORITES;
// We need to avoid dispatching new tabs when we are importing because
// that will lead to data corruption or a crash. Because there is no UI for
// the import process, we pass NULL as the window to bring to the foreground
// when a CopyData message comes in; this causes the message to be silently
// discarded, which is the correct behavior during the import process.
process_singleton->Lock(NULL);
// Index 0 is the default browser.
FirstRun::ImportSettings(profile,
importer_host->GetSourceProfileInfoAt(0).browser_type, items, NULL);
UserMetrics::RecordAction(UserMetricsAction("FirstRunDef_Accept"));
// Launch the search engine dialog only if build is organic, and user has not
// already set search preferences.
FilePath local_state_path;
PathService::Get(chrome::FILE_LOCAL_STATE, &local_state_path);
bool local_state_file_exists = file_util::PathExists(local_state_path);
if (GoogleUpdateSettings::IsOrganic(brand) && !local_state_file_exists) {
// The home page string may be set in the preferences, but the user should
// initially use Chrome with the NTP as home page in organic builds.
profile->GetPrefs()->SetBoolean(prefs::kHomePageIsNewTabPage, true);
views::Window* search_engine_dialog = views::Window::CreateChromeWindow(
NULL,
gfx::Rect(),
new FirstRunSearchEngineView(profile,
randomize_search_engine_experiment));
DCHECK(search_engine_dialog);
search_engine_dialog->Show();
views::AcceleratorHandler accelerator_handler;
MessageLoopForUI::current()->Run(&accelerator_handler);
search_engine_dialog->Close();
}
FirstRun::SetShowFirstRunBubblePref(true);
// Set the first run bubble to minimal.
FirstRun::SetMinimalFirstRunBubblePref();
FirstRun::SetShowWelcomePagePref();
process_singleton->Unlock();
FirstRun::CreateSentinel();
}
bool FirstRun::ImportSettings(Profile* profile, int browser_type,
int items_to_import,
const std::wstring& import_bookmarks_path,
bool skip_first_run_ui,
HWND parent_window) {
const CommandLine& cmdline = *CommandLine::ForCurrentProcess();
CommandLine import_cmd(cmdline.GetProgram());
// Propagate user data directory switch.
if (cmdline.HasSwitch(switches::kUserDataDir)) {
import_cmd.AppendSwitchWithValue(
switches::kUserDataDir,
cmdline.GetSwitchValueASCII(switches::kUserDataDir));
}
// Since ImportSettings is called before the local state is stored on disk
// we pass the language as an argument. GetApplicationLocale checks the
// current command line as fallback.
import_cmd.AppendSwitchWithValue(
switches::kLang,
ASCIIToWide(g_browser_process->GetApplicationLocale()));
if (items_to_import) {
import_cmd.CommandLine::AppendSwitchWithValue(switches::kImport,
EncodeImportParams(browser_type, items_to_import,
skip_first_run_ui ? 1 : 0, parent_window));
}
if (!import_bookmarks_path.empty()) {
import_cmd.CommandLine::AppendSwitchWithValue(
switches::kImportFromFile, import_bookmarks_path.c_str());
}
if (cmdline.HasSwitch(switches::kChromeFrame)) {
import_cmd.AppendSwitch(switches::kChromeFrame);
}
if (cmdline.HasSwitch(switches::kCountry)) {
import_cmd.AppendSwitchWithValue(switches::kCountry,
cmdline.GetSwitchValueASCII(switches::kCountry));
}
// Time to launch the process that is going to do the import.
base::ProcessHandle import_process;
if (!base::LaunchApp(import_cmd, false, false, &import_process))
return false;
// Activate the importer monitor. It awakes periodically in another thread
// and checks that the importer UI is still pumping messages.
if (parent_window)
HungImporterMonitor hang_monitor(parent_window, import_process);
// We block inside the import_runner ctor, pumping messages until the
// importer process ends. This can happen either by completing the import
// or by hang_monitor killing it.
ImportProcessRunner import_runner(import_process);
// Import process finished. Reload the prefs, because importer may set
// the pref value.
if (profile)
profile->GetPrefs()->ReloadPersistentPrefs();
return (import_runner.exit_code() == ResultCodes::NORMAL_EXIT);
}
bool FirstRun::ImportSettings(Profile* profile, int browser_type,
int items_to_import,
HWND parent_window) {
return ImportSettings(profile, browser_type, items_to_import,
std::wstring(), false, parent_window);
}
int FirstRun::ImportFromBrowser(Profile* profile,
const CommandLine& cmdline) {
std::wstring import_info = cmdline.GetSwitchValue(switches::kImport);
if (import_info.empty()) {
NOTREACHED();
return false;
}
int browser_type = 0;
int items_to_import = 0;
int skip_first_run_ui = 0;
HWND parent_window = NULL;
if (!DecodeImportParams(import_info, &browser_type, &items_to_import,
&skip_first_run_ui, &parent_window)) {
NOTREACHED();
return false;
}
scoped_refptr<ImporterHost> importer_host = new ImporterHost();
FirstRunImportObserver observer;
// If |skip_first_run_ui|, we run in headless mode. This means that if
// there is user action required the import is automatically canceled.
if (skip_first_run_ui > 0)
importer_host->set_headless();
StartImportingWithUI(
parent_window,
items_to_import,
importer_host,
importer_host->GetSourceProfileInfoForBrowserType(browser_type),
profile,
&observer,
true);
observer.RunLoop();
return observer.import_result();
}
// static
bool FirstRun::InSearchExperimentLocale() {
static std::set<std::string> allowed_locales;
if (allowed_locales.empty()) {
// List of locales in which search experiment can be run.
allowed_locales.insert("en-GB");
allowed_locales.insert("en-US");
}
const std::string app_locale = g_browser_process->GetApplicationLocale();
std::set<std::string>::iterator locale = allowed_locales.find(app_locale);
return locale != allowed_locales.end();
}
//////////////////////////////////////////////////////////////////////////
namespace {
const wchar_t kHelpCenterUrl[] =
L"http://www.google.com/support/chrome/bin/answer.py?answer=150752";
// This class displays a modal dialog using the views system. The dialog asks
// the user to give chrome another try. This class only handles the UI so the
// resulting actions are up to the caller. One version looks like this:
//
// /----------------------------------------\
// | |icon| You stopped using Google [x] |
// | |icon| Chrome. Would you like to.. |
// | [o] Give the new version a try |
// | [ ] Uninstall Google Chrome |
// | [ OK ] [Don't bug me] |
// | _why_am_I_seeign this?__ |
// ------------------------------------------
class TryChromeDialog : public views::ButtonListener,
public views::LinkController {
public:
TryChromeDialog()
: popup_(NULL),
try_chrome_(NULL),
kill_chrome_(NULL),
result_(Upgrade::TD_LAST_ENUM) {
}
virtual ~TryChromeDialog() {
};
// Shows the modal dialog asking the user to try chrome. Note that the dialog
// has no parent and it will position itself in a lower corner of the screen.
// The dialog does not steal focus and does not have an entry in the taskbar.
Upgrade::TryResult ShowModal() {
using views::GridLayout;
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
views::ImageView* icon = new views::ImageView();
icon->SetImage(*rb.GetBitmapNamed(IDR_PRODUCT_ICON_32));
gfx::Size icon_size = icon->GetPreferredSize();
// An approximate window size. After Layout() we'll get better bounds.
gfx::Rect pos(310, 160);
views::WidgetWin* popup = new views::WidgetWin();
if (!popup) {
NOTREACHED();
return Upgrade::TD_DIALOG_ERROR;
}
popup->set_delete_on_destroy(true);
popup->set_window_style(WS_POPUP | WS_CLIPCHILDREN);
popup->set_window_ex_style(WS_EX_TOOLWINDOW);
popup->Init(NULL, pos);
views::RootView* root_view = popup->GetRootView();
// The window color is a tiny bit off-white.
root_view->set_background(
views::Background::CreateSolidBackground(0xfc, 0xfc, 0xfc));
views::GridLayout* layout = CreatePanelGridLayout(root_view);
if (!layout) {
NOTREACHED();
return Upgrade::TD_DIALOG_ERROR;
}
root_view->SetLayoutManager(layout);
views::ColumnSet* columns;
// First row: [icon][pad][text][button].
columns = layout->AddColumnSet(0);
columns->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0,
GridLayout::FIXED, icon_size.width(),
icon_size.height());
columns->AddPaddingColumn(0, kRelatedControlHorizontalSpacing);
columns->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
GridLayout::USE_PREF, 0, 0);
columns->AddColumn(GridLayout::TRAILING, GridLayout::FILL, 1,
GridLayout::USE_PREF, 0, 0);
// Second row: [pad][pad][radio 1].
columns = layout->AddColumnSet(1);
columns->AddPaddingColumn(0, icon_size.width());
columns->AddPaddingColumn(0, kRelatedControlHorizontalSpacing);
columns->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
GridLayout::USE_PREF, 0, 0);
// Third row: [pad][pad][radio 2].
columns = layout->AddColumnSet(2);
columns->AddPaddingColumn(0, icon_size.width());
columns->AddPaddingColumn(0, kRelatedControlHorizontalSpacing);
columns->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
GridLayout::USE_PREF, 0, 0);
// Fourth row: [pad][pad][button][pad][button].
columns = layout->AddColumnSet(3);
columns->AddPaddingColumn(0, icon_size.width());
columns->AddPaddingColumn(0, kRelatedControlHorizontalSpacing);
columns->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
GridLayout::USE_PREF, 0, 0);
columns->AddPaddingColumn(0, kRelatedButtonHSpacing);
columns->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
GridLayout::USE_PREF, 0, 0);
// Fifth row: [pad][pad][link].
columns = layout->AddColumnSet(4);
columns->AddPaddingColumn(0, icon_size.width());
columns->AddPaddingColumn(0, kRelatedControlHorizontalSpacing);
columns->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
GridLayout::USE_PREF, 0, 0);
// First row views.
layout->StartRow(0, 0);
layout->AddView(icon);
// The heading has two flavors of text, the alt one features extensions but
// we only use it in the US until some international issues are fixed.
const std::string app_locale = g_browser_process->GetApplicationLocale();
const std::wstring heading = (app_locale == "en-US") ?
l10n_util::GetString(IDS_TRY_TOAST_ALT_HEADING) :
l10n_util::GetString(IDS_TRY_TOAST_HEADING);
views::Label* label =
new views::Label(heading);
label->SetFont(rb.GetFont(ResourceBundle::MediumBoldFont));
label->SetMultiLine(true);
label->SizeToFit(200);
label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
layout->AddView(label);
// The close button is custom.
views::ImageButton* close_button = new views::ImageButton(this);
close_button->SetImage(views::CustomButton::BS_NORMAL,
rb.GetBitmapNamed(IDR_CLOSE_BAR));
close_button->SetImage(views::CustomButton::BS_HOT,
rb.GetBitmapNamed(IDR_CLOSE_BAR_H));
close_button->SetImage(views::CustomButton::BS_PUSHED,
rb.GetBitmapNamed(IDR_CLOSE_BAR_P));
close_button->set_tag(BT_CLOSE_BUTTON);
layout->AddView(close_button);
// Second row views.
const std::wstring try_it(l10n_util::GetString(IDS_TRY_TOAST_TRY_OPT));
layout->StartRowWithPadding(0, 1, 0, 10);
try_chrome_ = new views::RadioButton(try_it, 1);
layout->AddView(try_chrome_);
try_chrome_->SetChecked(true);
// Third row views.
const std::wstring
kill_it(l10n_util::GetString(IDS_UNINSTALL_CHROME));
layout->StartRow(0, 2);
kill_chrome_ = new views::RadioButton(kill_it, 1);
layout->AddView(kill_chrome_);
// Fourth row views.
const std::wstring ok_it(l10n_util::GetString(IDS_OK));
const std::wstring cancel_it(l10n_util::GetString(IDS_TRY_TOAST_CANCEL));
const std::wstring why_this(l10n_util::GetString(IDS_TRY_TOAST_WHY));
layout->StartRowWithPadding(0, 3, 0, 10);
views::Button* accept_button = new views::NativeButton(this, ok_it);
accept_button->set_tag(BT_OK_BUTTON);
layout->AddView(accept_button);
views::Button* cancel_button = new views::NativeButton(this, cancel_it);
cancel_button->set_tag(BT_CLOSE_BUTTON);
layout->AddView(cancel_button);
// Fifth row views.
layout->StartRowWithPadding(0, 4, 0, 10);
views::Link* link = new views::Link(why_this);
link->SetController(this);
layout->AddView(link);
// We resize the window according to the layout manager. This takes into
// account the differences between XP and Vista fonts and buttons.
layout->Layout(root_view);
gfx::Size preferred = layout->GetPreferredSize(root_view);
pos = ComputeWindowPosition(preferred.width(), preferred.height(),
base::i18n::IsRTL());
popup->SetBounds(pos);
// Carve the toast shape into the window.
SetToastRegion(popup->GetNativeView(),
preferred.width(), preferred.height());
// Time to show the window in a modal loop.
popup_ = popup;
popup_->Show();
MessageLoop::current()->Run();
return result_;
}
protected:
// Overridden from ButtonListener. We have two buttons and according to
// what the user clicked we set |result_| and we should always close and
// end the modal loop.
virtual void ButtonPressed(views::Button* sender, const views::Event& event) {
if (sender->tag() == BT_CLOSE_BUTTON) {
// The user pressed cancel or the [x] button.
result_ = Upgrade::TD_NOT_NOW;
} else if (!try_chrome_) {
// We don't have radio buttons, the user pressed ok.
result_ = Upgrade::TD_TRY_CHROME;
} else {
// The outcome is according to the selected ratio button.
result_ = try_chrome_->checked() ? Upgrade::TD_TRY_CHROME :
Upgrade::TD_UNINSTALL_CHROME;
}
popup_->Close();
MessageLoop::current()->Quit();
}
// Overridden from LinkController. If the user selects the link we need to
// fire off the default browser that by some convoluted logic should not be
// chrome.
virtual void LinkActivated(views::Link* source, int event_flags) {
::ShellExecuteW(NULL, L"open", kHelpCenterUrl, NULL, NULL, SW_SHOW);
}
private:
enum ButtonTags {
BT_NONE,
BT_CLOSE_BUTTON,
BT_OK_BUTTON,
};
// Returns a screen rectangle that is fit to show the window. In particular
// it has the following properties: a) is visible and b) is attached to
// the bottom of the working area. For LTR machines it returns a left side
// rectangle and for RTL it returns a right side rectangle so that the
// dialog does not compete with the standar place of the start menu.
gfx::Rect ComputeWindowPosition(int width, int height, bool is_RTL) {
// The 'Shell_TrayWnd' is the taskbar. We like to show our window in that
// monitor if we can. This code works even if such window is not found.
HWND taskbar = ::FindWindowW(L"Shell_TrayWnd", NULL);
HMONITOR monitor =
::MonitorFromWindow(taskbar, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO info = {sizeof(info)};
if (!GetMonitorInfoW(monitor, &info)) {
// Quite unexpected. Do a best guess at a visible rectangle.
return gfx::Rect(20, 20, width + 20, height + 20);
}
// The |rcWork| is the work area. It should account for the taskbars that
// are in the screen when we called the function.
int left = is_RTL ? info.rcWork.left : info.rcWork.right - width;
int top = info.rcWork.bottom - height;
return gfx::Rect(left, top, width, height);
}
// Create a windows region that looks like a toast of width |w| and
// height |h|. This is best effort, so we don't care much if the operation
// fails.
void SetToastRegion(HWND window, int w, int h) {
static const POINT polygon[] = {
{0, 4}, {1, 2}, {2, 1}, {4, 0}, // Left side.
{w-4, 0}, {w-2, 1}, {w-1, 2}, {w, 4}, // Right side.
{w, h}, {0, h}
};
HRGN region = ::CreatePolygonRgn(polygon, arraysize(polygon), WINDING);
::SetWindowRgn(window, region, FALSE);
}
// controls which version of the text to use.
size_t version_;
// We don't own any of this pointers. The |popup_| owns itself and owns
// the other views.
views::WidgetWin* popup_;
views::RadioButton* try_chrome_;
views::RadioButton* kill_chrome_;
Upgrade::TryResult result_;
DISALLOW_COPY_AND_ASSIGN(TryChromeDialog);
};
} // namespace
Upgrade::TryResult Upgrade::ShowTryChromeDialog(size_t version) {
if (version > 10000) {
// This is a test value. We want to make sure we exercise
// returning this early. See EarlyReturnTest test harness.
return Upgrade::TD_NOT_NOW;
}
TryChromeDialog td;
return td.ShowModal();
}