diff options
author | evan@chromium.org <evan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-20 19:06:31 +0000 |
---|---|---|
committer | evan@chromium.org <evan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-20 19:06:31 +0000 |
commit | f8ed9234ed347388a4091b0ef910a8f5dab56641 (patch) | |
tree | e2430dc9ecfc7f425c7a24c38a746ff29e9aabb2 | |
parent | 3404f0885a2b893d049b4fc920925d8b9a2330cb (diff) | |
download | chromium_src-f8ed9234ed347388a4091b0ef910a8f5dab56641.zip chromium_src-f8ed9234ed347388a4091b0ef910a8f5dab56641.tar.gz chromium_src-f8ed9234ed347388a4091b0ef910a8f5dab56641.tar.bz2 |
Copy first_run.cc to first_run_win.cc for history preservation.
A subsequent change will modify these files.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16502 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/first_run_win.cc | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/chrome/browser/first_run_win.cc b/chrome/browser/first_run_win.cc new file mode 100644 index 0000000..3a24488 --- /dev/null +++ b/chrome/browser/first_run_win.cc @@ -0,0 +1,667 @@ +// Copyright (c) 2006-2008 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 <atlbase.h> +#include <atlcom.h> +#include <windows.h> +#include <shlobj.h> + +#include <sstream> + +#include "app/app_switches.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/string_util.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/hang_monitor/hung_window_detector.h" +#include "chrome/browser/importer/importer.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_view.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/pref_service.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/locale_settings.h" +#include "views/widget/accelerator_handler.h" +#include "views/window/window.h" + +namespace { + +// The kSentinelFile file absence will tell us it is a first run. +const wchar_t kSentinelFile[] = L"First Run"; + +// Gives the full path to the sentinel file. The file might not exist. +bool GetFirstRunSentinelFilePath(std::wstring* path) { + std::wstring exe_path; + if (!PathService::Get(base::DIR_EXE, &exe_path)) + return false; + + std::wstring first_run_sentinel; + if (InstallUtil::IsPerUserInstall(exe_path.c_str())) { + first_run_sentinel = exe_path; + } else { + if (!PathService::Get(chrome::DIR_USER_DATA, &first_run_sentinel)) + return false; + } + + file_util::AppendToPath(&first_run_sentinel, kSentinelFile); + *path = first_run_sentinel; + return true; +} + +bool GetNewerChromeFile(std::wstring* path) { + if (!PathService::Get(base::DIR_EXE, path)) + return false; + file_util::AppendToPath(path, 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; +} + +std::wstring GetDefaultPrefFilePath(bool create_profile_dir, + const std::wstring& user_data_dir) { + FilePath default_pref_dir = ProfileManager::GetDefaultProfileDir( + FilePath::FromWStringHack(user_data_dir)); + if (create_profile_dir) { + if (!file_util::PathExists(default_pref_dir)) { + if (!file_util::CreateDirectory(default_pref_dir)) + return std::wstring(); + } + } + return ProfileManager::GetDefaultProfilePath(default_pref_dir) + .ToWStringHack(); +} + +bool InvokeGoogleUpdateForRename() { + CComPtr<IProcessLauncher> ipl; + if (!FAILED(ipl.CoCreateInstance(__uuidof(ProcessLauncherClass)))) { + ULONG_PTR phandle = NULL; + DWORD id = GetCurrentProcessId(); + if (!FAILED(ipl->LaunchCmdElevated(google_update::kChromeGuid, + 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::wstring& 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.ToWStringHack()); + cl.AppendSwitchWithValue(param, value); + 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) { + std::string terms = + ResourceBundle::GetSharedInstance().GetDataResource(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.c_str(), terms.size()) > 0); +} + +} // namespace + +bool FirstRun::IsChromeFirstRun() { + // A troolean, 0 means not yet set, 1 means set to true, 2 set to false. + static int first_run = 0; + if (first_run != 0) + return first_run == 1; + + std::wstring first_run_sentinel; + if (!GetFirstRunSentinelFilePath(&first_run_sentinel) || + file_util::PathExists(first_run_sentinel)) { + first_run = 2; + return false; + } + first_run = 1; + return true; +} + +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::RemoveSentinel() { + std::wstring first_run_sentinel; + if (!GetFirstRunSentinelFilePath(&first_run_sentinel)) + return false; + return file_util::Delete(first_run_sentinel, false); +} + +bool FirstRun::CreateSentinel() { + std::wstring first_run_sentinel; + if (!GetFirstRunSentinelFilePath(&first_run_sentinel)) + return false; + HANDLE file = ::CreateFileW(first_run_sentinel.c_str(), + FILE_READ_DATA | FILE_WRITE_DATA, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CREATE_ALWAYS, 0, NULL); + if (INVALID_HANDLE_VALUE == file) + return false; + ::CloseHandle(file); + return true; +} + +bool FirstRun::ProcessMasterPreferences(const FilePath& user_data_dir, + const FilePath& master_prefs_path, + int* preference_details, + std::vector<std::wstring>* new_tabs) { + DCHECK(!user_data_dir.empty()); + if (preference_details) + *preference_details = 0; + + std::wstring master_prefs; + if (master_prefs_path.empty()) { + // The default location of the master prefs is next to the chrome exe. + std::wstring master_path; + if (!PathService::Get(base::DIR_EXE, &master_path)) + return true; + file_util::AppendToPath(&master_path, installer_util::kDefaultMasterPrefs); + master_prefs = master_path; + } else { + master_prefs = master_prefs_path.ToWStringHack(); + } + + int parse_result = installer_util::ParseDistributionPreferences(master_prefs); + if (preference_details) + *preference_details = parse_result; + + if (parse_result & installer_util::MASTER_PROFILE_ERROR) + return true; + + if (new_tabs) + *new_tabs = installer_util::ParseFirstRunTabs(master_prefs); + + if (parse_result & installer_util::MASTER_PROFILE_REQUIRE_EULA) { + // 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::wstring& eula = 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 (parse_result & installer_util::MASTER_PROFILE_OEM_FIRST_RUN_BUBBLE) + FirstRun::SetOEMFirstRunBubblePref(); + + FilePath user_prefs = FilePath::FromWStringHack( + GetDefaultPrefFilePath(true, user_data_dir.ToWStringHack())); + 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.ToWStringHack())) + return true; + + if (!(parse_result & installer_util::MASTER_PROFILE_NO_FIRST_RUN_UI)) + return true; + + // From here on we won't show first run so we need to do the work to set the + // required state given that FirstRunView is not going to be called. + FirstRun::SetShowFirstRunBubblePref(); + + // 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 (parse_result & installer_util::MASTER_PROFILE_SHOW_WELCOME) + FirstRun::SetShowWelcomePagePref(); + + int import_items = 0; + if (parse_result & installer_util::MASTER_PROFILE_IMPORT_SEARCH_ENGINE) + import_items += SEARCH_ENGINES; + if (parse_result & installer_util::MASTER_PROFILE_IMPORT_HISTORY) + import_items += HISTORY; + if (parse_result & installer_util::MASTER_PROFILE_IMPORT_BOOKMARKS) + import_items += FAVORITES; + + if (import_items) { + // 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, + import_items, NULL)) { + LOG(WARNING) << "silent import failed"; + } + } + + if (parse_result & + installer_util::MASTER_PROFILE_MAKE_CHROME_DEFAULT_FOR_USER) + ShellIntegration::SetAsDefaultBrowser(); + + 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(google_update::kEnvProductVersionKey, NULL); + return base::LaunchApp(command_line.command_line_string(), + false, false, NULL); +} + +bool Upgrade::SwapNewChromeExeIfPresent() { + std::wstring 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.c_str(), + backup_exe.c_str(), REPLACEFILE_IGNORE_MERGE_ERRORS, + NULL, NULL)) { + return true; + } + return false; +} + +void OpenFirstRunDialog(Profile* profile, + ProcessSingleton* process_singleton) { + DCHECK(profile); + DCHECK(process_singleton); + + views::Window* first_run_ui = views::Window::CreateChromeWindow( + NULL, gfx::Rect(), new FirstRunView(profile)); + DCHECK(first_run_ui); + + // We need to avoid dispatching new tabs when we are doing the import + // because that will lead to data corruption or a crash. Lock() does that. + // If a CopyData message does come in while the First Run UI is visible, + // then we will attempt to set first_run_ui as the foreground window. + process_singleton->Lock(first_run_ui->GetNativeWindow()); + + first_run_ui->Show(); + + // We must now run a message loop (will be terminated when the First Run UI + // is closed) so that the window can receive messages and we block the + // browser window from showing up. We pass the accelerator handler here so + // that keyboard accelerators (Enter, Esc, etc) work in the dialog box. + MessageLoopForUI::current()->Run(g_browser_process->accelerator_handler()); + process_singleton->Unlock(); +} + +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_EVIL_CONSTRUCTORS(HungImporterMonitor); +}; + +// This class is used by FirstRun::ImportNow to get notified of the outcome of +// the import operation. It differs from ImportProcessRunner in that this +// class executes in the context of importing child process. +// The values that it handles are meant to be used as the process exit code. +class FirstRunImportObserver : public ImportObserver { + public: + FirstRunImportObserver() + : loop_running_(false), import_result_(ResultCodes::NORMAL_EXIT) { + } + int import_result() const { + return import_result_; + } + virtual void ImportCanceled() { + import_result_ = ResultCodes::IMPORTER_CANCEL; + Finish(); + } + virtual void ImportComplete() { + import_result_ = ResultCodes::NORMAL_EXIT; + Finish(); + } + + void RunLoop() { + loop_running_ = true; + MessageLoop::current()->Run(); + } + + private: + void Finish() { + if (loop_running_) + MessageLoop::current()->Quit(); + } + + bool loop_running_; + int import_result_; + DISALLOW_EVIL_CONSTRUCTORS(FirstRunImportObserver); +}; + +std::wstring EncodeImportParams(int browser_type, int options, HWND window) { + return StringPrintf(L"%d@%d@%d", browser_type, options, window); +} + +bool DecodeImportParams(const std::wstring& encoded, + int* browser_type, int* options, HWND* window) { + std::vector<std::wstring> v; + SplitString(encoded, L'@', &v); + if (v.size() != 3) + return false; + *browser_type = static_cast<int>(StringToInt64(v[0])); + *options = static_cast<int>(StringToInt64(v[1])); + *window = reinterpret_cast<HWND>(StringToInt64(v[2])); + return true; +} + +} // namespace + +bool FirstRun::ImportSettings(Profile* profile, int browser_type, + int items_to_import, HWND parent_window) { + const CommandLine& cmdline = *CommandLine::ForCurrentProcess(); + CommandLine import_cmd(cmdline.program()); + // Propagate user data directory switch. + if (cmdline.HasSwitch(switches::kUserDataDir)) { + import_cmd.AppendSwitchWithValue( + switches::kUserDataDir, + cmdline.GetSwitchValue(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, + g_browser_process->GetApplicationLocale()); + + import_cmd.CommandLine::AppendSwitchWithValue(switches::kImport, + EncodeImportParams(browser_type, items_to_import, parent_window)); + + // 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); +} + +int FirstRun::ImportNow(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; + HWND parent_window = NULL; + if (!DecodeImportParams(import_info, &browser_type, &items_to_import, + &parent_window)) { + NOTREACHED(); + return false; + } + scoped_refptr<ImporterHost> importer_host = new ImporterHost(); + FirstRunImportObserver observer; + + // If there is no parent window, we run in headless mode which amounts + // to having the windows hidden and if there is user action required the + // import is automatically canceled. + if (!parent_window) + 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(); +} + +bool FirstRun::SetShowFirstRunBubblePref() { + PrefService* local_state = g_browser_process->local_state(); + if (!local_state) + return false; + if (!local_state->IsPrefRegistered(prefs::kShouldShowFirstRunBubble)) { + local_state->RegisterBooleanPref(prefs::kShouldShowFirstRunBubble, false); + local_state->SetBoolean(prefs::kShouldShowFirstRunBubble, true); + } + return true; +} + +bool FirstRun::SetOEMFirstRunBubblePref() { + PrefService* local_state = g_browser_process->local_state(); + if (!local_state) + return false; + if (!local_state->IsPrefRegistered(prefs::kShouldUseOEMFirstRunBubble)) { + local_state->RegisterBooleanPref(prefs::kShouldUseOEMFirstRunBubble, + false); + local_state->SetBoolean(prefs::kShouldUseOEMFirstRunBubble, true); + } + return true; +} + +bool FirstRun::SetShowWelcomePagePref() { + PrefService* local_state = g_browser_process->local_state(); + if (!local_state) + return false; + if (!local_state->IsPrefRegistered(prefs::kShouldShowWelcomePage)) { + local_state->RegisterBooleanPref(prefs::kShouldShowWelcomePage, false); + local_state->SetBoolean(prefs::kShouldShowWelcomePage, true); + } + return true; +} |