// Copyright (c) 2011 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/first_run.h"
#include <shlobj.h>
#include <windows.h>
#include <set>
#include <sstream>
#include "base/environment.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/string_number_conversions.h"
#include "base/string_split.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "base/win/object_watcher.h"
#include "base/win/windows_version.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_updater.h"
#include "chrome/browser/first_run/first_run_import_observer.h"
#include "chrome/browser/importer/importer_host.h"
#include "chrome/browser/importer/importer_list.h"
#include "chrome/browser/importer/importer_progress_dialog.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/worker_thread_ticker.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/shell_util.h"
#include "chrome/installer/util/util_constants.h"
#include "content/common/notification_service.h"
#include "content/common/result_codes.h"
#include "google_update_idl.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "grit/theme_resources.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_switches.h"
namespace {
// 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()->GetExtensionService());
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(ExtensionService* service) {
if (!service)
return;
service->updater()->CheckNow();
return;
}
NotificationRegistrar registrar_;
};
// Creates the desktop shortcut to chrome for the current user. Returns
// false if it fails. It will overwrite the shortcut if it exists.
bool CreateChromeDesktopShortcut() {
FilePath chrome_exe;
if (!PathService::Get(base::FILE_EXE, &chrome_exe))
return false;
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
if (!dist)
return false;
return ShellUtil::CreateChromeDesktopShortcut(
dist,
chrome_exe.value(),
dist->GetAppDescription(),
ShellUtil::CURRENT_USER,
false,
true); // create if doesn't exist.
}
// Creates the quick launch shortcut to chrome for the current user. Returns
// false if it fails. It will overwrite the shortcut if it exists.
bool CreateChromeQuickLaunchShortcut() {
FilePath chrome_exe;
if (!PathService::Get(base::FILE_EXE, &chrome_exe))
return false;
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
return ShellUtil::CreateChromeQuickLaunchShortcut(
dist,
chrome_exe.value(),
ShellUtil::CURRENT_USER, // create only for current user.
true); // create if doesn't exist.
}
} // namespace
bool FirstRun::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::kInstallerDir);
exe_path = exe_path.Append(installer::kSetupExe);
base::ProcessHandle ph;
CommandLine cl(exe_path);
cl.AppendSwitchNative(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 FirstRun::WriteEULAtoTempFile(FilePath* eula_path) {
base::StringPiece terms =
ResourceBundle::GetSharedInstance().GetRawDataResource(IDR_TERMS_HTML);
if (terms.empty())
return false;
FILE *file = file_util::CreateAndOpenTemporaryFile(eula_path);
if (!file)
return false;
bool good = fwrite(terms.data(), terms.size(), 1, file) == 1;
fclose(file);
return good;
}
void FirstRun::DoDelayedInstallExtensions() {
new FirstRunDelayedTasks(FirstRunDelayedTasks::INSTALL_EXTENSIONS);
}
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::win::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 2 expected values:
// NORMAL_EXIT, 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::win::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::string EncodeImportParams(int importer_type,
int options,
int skip_first_run_ui,
HWND window) {
return base::StringPrintf(
"%d@%d@%d@%d", importer_type, options, skip_first_run_ui, window);
}
bool DecodeImportParams(const std::string& encoded,
int* importer_type,
int* options,
int* skip_first_run_ui,
HWND* window) {
std::vector<std::string> parts;
base::SplitString(encoded, '@', &parts);
if (parts.size() != 4)
return false;
if (!base::StringToInt(parts[0], importer_type))
return false;
if (!base::StringToInt(parts[1], options))
return false;
if (!base::StringToInt(parts[2], skip_first_run_ui))
return false;
int64 window_int;
base::StringToInt64(parts[3], &window_int);
*window = reinterpret_cast<HWND>(window_int);
return true;
}
} // namespace
// static
void FirstRun::PlatformSetup() {
CreateChromeDesktopShortcut();
// Windows 7 has deprecated the quick launch bar.
if (base::win::GetVersion() < base::win::VERSION_WIN7)
CreateChromeQuickLaunchShortcut();
}
// static
bool FirstRun::IsOrganicFirstRun() {
std::wstring brand;
GoogleUpdateSettings::GetBrand(&brand);
return GoogleUpdateSettings::IsOrganicFirstRun(brand);
}
// static
bool FirstRun::ImportSettings(Profile* profile,
int importer_type,
int items_to_import,
const FilePath& import_bookmarks_path,
bool skip_first_run_ui,
HWND parent_window) {
const CommandLine& cmdline = *CommandLine::ForCurrentProcess();
CommandLine import_cmd(cmdline.GetProgram());
const char* kSwitchNames[] = {
switches::kUserDataDir,
switches::kChromeFrame,
switches::kCountry,
};
import_cmd.CopySwitchesFrom(cmdline, kSwitchNames, arraysize(kSwitchNames));
// 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.AppendSwitchASCII(switches::kLang,
g_browser_process->GetApplicationLocale());
if (items_to_import) {
import_cmd.CommandLine::AppendSwitchASCII(switches::kImport,
EncodeImportParams(importer_type, items_to_import,
skip_first_run_ui ? 1 : 0, NULL));
}
if (!import_bookmarks_path.empty()) {
import_cmd.CommandLine::AppendSwitchPath(
switches::kImportFromFile, import_bookmarks_path);
}
// 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;
// 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);
}
// static
bool FirstRun::ImportSettings(Profile* profile,
scoped_refptr<ImporterHost> importer_host,
scoped_refptr<ImporterList> importer_list,
int items_to_import) {
return ImportSettings(
profile,
importer_list->GetSourceProfileAt(0).importer_type,
items_to_import,
FilePath(),
false,
NULL);
}
int FirstRun::ImportFromBrowser(Profile* profile,
const CommandLine& cmdline) {
std::string import_info = cmdline.GetSwitchValueASCII(switches::kImport);
if (import_info.empty()) {
NOTREACHED();
return false;
}
int importer_type = 0;
int items_to_import = 0;
int skip_first_run_ui = 0;
HWND parent_window = NULL;
if (!DecodeImportParams(import_info, &importer_type, &items_to_import,
&skip_first_run_ui, &parent_window)) {
NOTREACHED();
return false;
}
scoped_refptr<ImporterHost> importer_host(new ImporterHost);
FirstRunImportObserver importer_observer;
scoped_refptr<ImporterList> importer_list(new ImporterList);
importer_list->DetectSourceProfilesHack();
// 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();
importer::ShowImportProgressDialog(
parent_window,
static_cast<uint16>(items_to_import),
importer_host,
&importer_observer,
importer_list->GetSourceProfileForImporterType(importer_type),
profile,
true);
importer_observer.RunLoop();
return importer_observer.import_result();
}