blob: efe2e09883e2e6fa7feec644f580ee70e4954deb [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
#include <stddef.h>
#include <algorithm>
#include <iterator>
#include "base/auto_reset.h"
#include "base/base_paths.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/i18n/case_conversion.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/optional.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/task/post_task.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/apps/apps_launch.h"
#include "chrome/browser/apps/platform_apps/install_chrome_app.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/custom_handlers/protocol_handler_registry.h"
#include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/infobars/infobar_service.h"
#include "chrome/browser/obsolete_system/obsolete_system.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_io_data.h"
#include "chrome/browser/sessions/session_service.h"
#include "chrome/browser/sessions/session_service_factory.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/session_crashed_bubble.h"
#include "chrome/browser/ui/startup/automation_infobar_delegate.h"
#include "chrome/browser/ui/startup/bad_flags_prompt.h"
#include "chrome/browser/ui/startup/default_browser_prompt.h"
#include "chrome/browser/ui/startup/google_api_keys_infobar_delegate.h"
#include "chrome/browser/ui/startup/obsolete_system_infobar_delegate.h"
#include "chrome/browser/ui/startup/startup_browser_creator.h"
#include "chrome/browser/ui/startup/startup_tab_provider.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/prefs/pref_service.h"
#include "components/rappor/public/rappor_utils.h"
#include "components/rappor/rappor_service_impl.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/dom_storage_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_switches.h"
#include "google_apis/google_api_keys.h"
#include "rlz/buildflags/buildflags.h"
#include "ui/base/buildflags.h"
#if !defined(OS_CHROMEOS)
#include "chrome/browser/ui/webui/welcome/nux_helper.h"
#endif // !defined(OS_CHROMEOS)
#if defined(OS_MACOSX)
#include "base/mac/mac_util.h"
#import "chrome/browser/mac/dock.h"
#include "chrome/browser/mac/install_from_dmg.h"
#include "chrome/browser/ui/cocoa/keystone_infobar_delegate.h"
#endif
#if defined(OS_WIN)
#if defined(GOOGLE_CHROME_BUILD)
#include "chrome/browser/win/conflicts/incompatible_applications_updater.h"
#endif // defined(GOOGLE_CHROME_BUILD)
#include "chrome/browser/notifications/notification_platform_bridge_win.h"
#include "chrome/browser/shell_integration_win.h"
#include "chrome/browser/ui/startup/credential_provider_signin_dialog_win.h"
#include "chrome/credential_provider/common/gcp_strings.h"
#endif // defined(OS_WIN)
#if BUILDFLAG(ENABLE_PLUGINS)
#include "chrome/browser/plugins/flash_deprecation_infobar_delegate.h"
#endif
#if BUILDFLAG(ENABLE_RLZ)
#include "components/google/core/common/google_util.h"
#include "components/rlz/rlz_tracker.h" // nogncheck
#endif
#if BUILDFLAG(ENABLE_SUPERVISED_USERS) && !defined(OS_ANDROID) && \
!defined(OS_CHROMEOS)
#include "chrome/browser/ui/startup/supervised_users_deprecated_infobar_delegate.h"
#endif
namespace {
// Utility functions ----------------------------------------------------------
// This enum is used to define the buckets for an enumerated UMA histogram.
// Hence,
// (a) existing enumerated constants should never be deleted or reordered, and
// (b) new constants should only be appended at the end of the enumeration.
enum LaunchMode {
LM_TO_BE_DECIDED = 0, // Possibly direct launch or via a shortcut.
LM_AS_WEBAPP_IN_WINDOW = 1, // Launched as an installed web application in a
// standalone window.
LM_WITH_URLS = 2, // Launched with urls in the cmd line.
LM_OTHER = 3, // Not launched from a shortcut.
LM_SHORTCUT_NONAME = 4, // Launched from shortcut but no name available.
LM_SHORTCUT_UNKNOWN = 5, // Launched from user-defined shortcut.
LM_SHORTCUT_QUICKLAUNCH = 6, // Launched from the quick launch bar.
LM_SHORTCUT_DESKTOP = 7, // Launched from a desktop shortcut.
LM_SHORTCUT_TASKBAR = 8, // Launched from the taskbar.
LM_USER_EXPERIMENT = 9, // Launched after acceptance of a user experiment.
LM_OTHER_OS = 10, // Result bucket for OSes with no coverage here.
LM_MAC_UNDOCKED_DISK_LAUNCH = 11, // Undocked launch from disk.
LM_MAC_DOCKED_DISK_LAUNCH = 12, // Docked launch from disk.
LM_MAC_UNDOCKED_DMG_LAUNCH = 13, // Undocked launch from a dmg.
LM_MAC_DOCKED_DMG_LAUNCH = 14, // Docked launch from a dmg.
LM_MAC_DOCK_STATUS_ERROR = 15, // Error determining dock status.
LM_MAC_DMG_STATUS_ERROR = 16, // Error determining dmg status.
LM_MAC_DOCK_DMG_STATUS_ERROR = 17, // Error determining dock and dmg status.
LM_WIN_PLATFORM_NOTIFICATION = 18, // Launched from toast notification
// activation on Windows.
LM_SHORTCUT_START_MENU = 19, // A Windows Start Menu shortcut.
LM_CREDENTIAL_PROVIDER_SIGNIN = 20, // Started as a logon stub for the Google
// Credential Provider for Windows.
LM_AS_WEBAPP_IN_TAB = 21, // Launched as an installed web
// application in a browser tab.
};
// Returns a LaunchMode value if one can be determined with low overhead, or
// LM_TO_BE_DECIDED if a call to GetLaunchModeSlow is required.
LaunchMode GetLaunchModeFast();
// Returns a LaunchMode value; may require a bit of extra work. This will be
// called on a background thread outside of the critical startup path.
LaunchMode GetLaunchModeSlow();
#if defined(OS_WIN)
// Returns the path to the shortcut from which Chrome was launched, or null if
// not launched via a shortcut.
base::Optional<const wchar_t*> GetShortcutPath() {
STARTUPINFOW si = { sizeof(si) };
GetStartupInfoW(&si);
if (!(si.dwFlags & STARTF_TITLEISLINKNAME))
return base::nullopt;
return base::Optional<const wchar_t*>(si.lpTitle);
}
LaunchMode GetLaunchModeFast() {
auto shortcut_path = GetShortcutPath();
if (!shortcut_path)
return LM_OTHER;
if (!shortcut_path.value())
return LM_SHORTCUT_NONAME;
return LM_TO_BE_DECIDED;
}
LaunchMode GetLaunchModeSlow() {
auto shortcut_path = GetShortcutPath();
DCHECK(shortcut_path);
DCHECK(shortcut_path.value());
const base::string16 shortcut(base::i18n::ToLower(shortcut_path.value()));
// The windows quick launch path is not localized.
if (shortcut.find(L"\\quick launch\\") != base::StringPiece16::npos)
return LM_SHORTCUT_TASKBAR;
// Check the common shortcut locations.
static constexpr struct {
int path_key;
LaunchMode launch_mode;
} kPathKeysAndModes[] = {
{base::DIR_COMMON_START_MENU, LM_SHORTCUT_START_MENU},
{base::DIR_START_MENU, LM_SHORTCUT_START_MENU},
{base::DIR_COMMON_DESKTOP, LM_SHORTCUT_DESKTOP},
{base::DIR_USER_DESKTOP, LM_SHORTCUT_DESKTOP},
};
base::FilePath candidate;
for (const auto& item : kPathKeysAndModes) {
if (base::PathService::Get(item.path_key, &candidate) &&
base::StartsWith(shortcut, base::i18n::ToLower(candidate.value()),
base::CompareCase::SENSITIVE)) {
return item.launch_mode;
}
}
return LM_SHORTCUT_UNKNOWN;
}
#elif defined(OS_MACOSX) // defined(OS_WIN)
LaunchMode GetLaunchModeFast() {
DiskImageStatus dmg_launch_status =
IsAppRunningFromReadOnlyDiskImage(nullptr);
dock::ChromeInDockStatus dock_launch_status = dock::ChromeIsInTheDock();
if (dock_launch_status == dock::ChromeInDockFailure &&
dmg_launch_status == DiskImageStatusFailure)
return LM_MAC_DOCK_DMG_STATUS_ERROR;
if (dock_launch_status == dock::ChromeInDockFailure)
return LM_MAC_DOCK_STATUS_ERROR;
if (dmg_launch_status == DiskImageStatusFailure)
return LM_MAC_DMG_STATUS_ERROR;
bool dmg_launch = dmg_launch_status == DiskImageStatusTrue;
bool dock_launch = dock_launch_status == dock::ChromeInDockTrue;
if (dmg_launch && dock_launch)
return LM_MAC_DOCKED_DMG_LAUNCH;
if (dmg_launch)
return LM_MAC_UNDOCKED_DMG_LAUNCH;
if (dock_launch)
return LM_MAC_DOCKED_DISK_LAUNCH;
return LM_MAC_UNDOCKED_DISK_LAUNCH;
}
LaunchMode GetLaunchModeSlow() {
NOTREACHED();
return LM_TO_BE_DECIDED;
}
#else // defined(OS_WIN)
// TODO(cpu): Port to other platforms.
LaunchMode GetLaunchModeFast() {
return LM_OTHER_OS;
}
LaunchMode GetLaunchModeSlow() {
NOTREACHED();
return LM_OTHER_OS;
}
#endif // defined(OS_WIN)
// Log in a histogram the frequency of launching by the different methods. See
// LaunchMode enum for the actual values of the buckets.
void RecordLaunchModeHistogram(LaunchMode mode) {
static constexpr char kHistogramName[] = "Launch.Modes";
if (mode == LM_TO_BE_DECIDED &&
(mode = GetLaunchModeFast()) == LM_TO_BE_DECIDED) {
// The mode couldn't be determined with a fast path. Perform a more
// expensive evaluation out of the critical startup path.
base::PostTaskWithTraits(FROM_HERE,
{base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce([]() {
base::UmaHistogramSparse(kHistogramName,
GetLaunchModeSlow());
}));
} else {
base::UmaHistogramSparse(kHistogramName, mode);
}
}
void UrlsToTabs(const std::vector<GURL>& urls, StartupTabs* tabs) {
for (size_t i = 0; i < urls.size(); ++i) {
StartupTab tab;
tab.is_pinned = false;
tab.url = urls[i];
tabs->push_back(tab);
}
}
std::vector<GURL> TabsToUrls(const StartupTabs& tabs) {
std::vector<GURL> urls;
urls.reserve(tabs.size());
std::transform(tabs.begin(), tabs.end(), std::back_inserter(urls),
[](const StartupTab& tab) { return tab.url; });
return urls;
}
// Appends the contents of |from| to the end of |to|.
void AppendTabs(const StartupTabs& from, StartupTabs* to) {
if (!from.empty())
to->insert(to->end(), from.begin(), from.end());
}
bool ShouldShowBadFlagsSecurityWarnings() {
#if !defined(OS_CHROMEOS)
PrefService* local_state = g_browser_process->local_state();
if (!local_state)
return true;
const auto* pref = local_state->FindPreference(
prefs::kCommandLineFlagSecurityWarningsEnabled);
DCHECK(pref);
// The warnings can only be disabled by policy. Default to show warnings.
if (pref->IsManaged())
return pref->GetValue()->GetBool();
#endif
return true;
}
} // namespace
StartupBrowserCreatorImpl::StartupBrowserCreatorImpl(
const base::FilePath& cur_dir,
const base::CommandLine& command_line,
chrome::startup::IsFirstRun is_first_run)
: cur_dir_(cur_dir),
command_line_(command_line),
profile_(NULL),
browser_creator_(NULL),
is_first_run_(is_first_run == chrome::startup::IS_FIRST_RUN) {}
StartupBrowserCreatorImpl::StartupBrowserCreatorImpl(
const base::FilePath& cur_dir,
const base::CommandLine& command_line,
StartupBrowserCreator* browser_creator,
chrome::startup::IsFirstRun is_first_run)
: cur_dir_(cur_dir),
command_line_(command_line),
profile_(NULL),
browser_creator_(browser_creator),
is_first_run_(is_first_run == chrome::startup::IS_FIRST_RUN) {}
StartupBrowserCreatorImpl::~StartupBrowserCreatorImpl() {
}
bool StartupBrowserCreatorImpl::Launch(Profile* profile,
const std::vector<GURL>& urls_to_open,
bool process_startup) {
UMA_HISTOGRAM_COUNTS_100(
"Startup.BrowserLaunchURLCount",
static_cast<base::HistogramBase::Sample>(urls_to_open.size()));
RecordRapporOnStartupURLs(urls_to_open);
DCHECK(profile);
profile_ = profile;
#if defined(OS_WIN)
// If the command line has the kNotificationLaunchId switch, then this
// Launch() call is from notification_helper.exe to process toast activation.
// Delegate to the notification system; do not open a browser window here.
if (command_line_.HasSwitch(switches::kNotificationLaunchId)) {
if (NotificationPlatformBridgeWin::HandleActivation(command_line_)) {
RecordLaunchModeHistogram(LM_WIN_PLATFORM_NOTIFICATION);
return true;
}
return false;
}
// If being started for credential provider logon purpose, only show the
// signin page.
if (command_line_.HasSwitch(credential_provider::kGcpwSigninSwitch)) {
DCHECK(profile_->IsIncognito());
// NOTE: All launch urls are ignored when running with --gcpw-logon since
// this mode only loads Google's sign in page.
StartGCPWSignin(command_line_, profile_);
RecordLaunchModeHistogram(LM_CREDENTIAL_PROVIDER_SIGNIN);
return true;
}
#endif // defined(OS_WIN)
if (command_line_.HasSwitch(switches::kAppId)) {
std::string app_id = command_line_.GetSwitchValueASCII(switches::kAppId);
// If |app_id| is a disabled or terminated platform app we handle it
// specially here, otherwise it will be handled below.
if (apps::OpenApplicationWithReenablePrompt(profile, app_id, command_line_,
cur_dir_)) {
return true;
}
}
// Open the required browser windows and tabs. If we're being run as an
// application window or application tab, don't restore tabs or open initial
// URLs as the user has directly launched an app shortcut. In the first case,
// the user should see a standlone app window. In the second case, the tab
// should either open in an existing Chrome window for this profile, or spawn
// a new Chrome window without any NTP if no window exists (see
// crbug.com/528385).
if (OpenApplicationWindow(profile)) {
RecordLaunchModeHistogram(LM_AS_WEBAPP_IN_WINDOW);
} else if (OpenApplicationTab(profile)) {
RecordLaunchModeHistogram(LM_AS_WEBAPP_IN_TAB);
} else {
// Check the true process command line for --try-chrome-again=N rather than
// the one parsed for startup URLs and such.
if (!base::CommandLine::ForCurrentProcess()
->GetSwitchValueNative(switches::kTryChromeAgain)
.empty()) {
RecordLaunchModeHistogram(LM_USER_EXPERIMENT);
} else {
RecordLaunchModeHistogram(urls_to_open.empty() ? LM_TO_BE_DECIDED
: LM_WITH_URLS);
}
DetermineURLsAndLaunch(process_startup, urls_to_open);
if (command_line_.HasSwitch(switches::kInstallChromeApp)) {
install_chrome_app::InstallChromeApp(
command_line_.GetSwitchValueASCII(switches::kInstallChromeApp));
}
#if defined(OS_MACOSX)
if (process_startup) {
// Check whether the auto-update system needs to be promoted from user
// to system.
KeystoneInfoBar::PromotionInfoBar(profile);
}
#endif
}
// In kiosk mode, we want to always be fullscreen, so switch to that now.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode) ||
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kStartFullscreen)) {
// It's possible for there to be no browser window, e.g. if someone
// specified a non-sensical combination of options
// ("--kiosk --no_startup_window"); do nothing in that case.
Browser* browser = BrowserList::GetInstance()->GetLastActive();
if (browser)
chrome::ToggleFullscreenMode(browser);
}
#if defined(OS_WIN)
// TODO(gab): This could now run only during Active Setup (i.e. on explicit
// Active Setup versioning and on OS upgrades) instead of every startup.
// http://crbug.com/577697
if (process_startup)
shell_integration::win::MigrateTaskbarPins();
#endif // defined(OS_WIN)
return true;
}
Browser* StartupBrowserCreatorImpl::OpenURLsInBrowser(
Browser* browser,
bool process_startup,
const std::vector<GURL>& urls) {
StartupTabs tabs;
UrlsToTabs(urls, &tabs);
return OpenTabsInBrowser(browser, process_startup, tabs);
}
Browser* StartupBrowserCreatorImpl::OpenTabsInBrowser(Browser* browser,
bool process_startup,
const StartupTabs& tabs) {
DCHECK(!tabs.empty());
// If we don't yet have a profile, try to use the one we're given from
// |browser|. While we may not end up actually using |browser| (since it
// could be a popup window), we can at least use the profile.
if (!profile_ && browser)
profile_ = browser->profile();
if (!browser || !browser->is_type_tabbed()) {
// Startup browsers are not counted as being created by a user_gesture
// because of historical accident, even though the startup browser was
// created in response to the user clicking on chrome. There was an
// incomplete check on whether a user gesture created a window which looked
// at the state of the MessageLoop.
Browser::CreateParams params = Browser::CreateParams(profile_, false);
browser = new Browser(params);
}
bool first_tab = true;
ProtocolHandlerRegistry* registry = profile_ ?
ProtocolHandlerRegistryFactory::GetForBrowserContext(profile_) : NULL;
for (size_t i = 0; i < tabs.size(); ++i) {
// We skip URLs that we'd have to launch an external protocol handler for.
// This avoids us getting into an infinite loop asking ourselves to open
// a URL, should the handler be (incorrectly) configured to be us. Anyone
// asking us to open such a URL should really ask the handler directly.
bool handled_by_chrome = ProfileIOData::IsHandledURL(tabs[i].url) ||
(registry && registry->IsHandledProtocol(tabs[i].url.scheme()));
if (!process_startup && !handled_by_chrome)
continue;
int add_types = first_tab ? TabStripModel::ADD_ACTIVE :
TabStripModel::ADD_NONE;
add_types |= TabStripModel::ADD_FORCE_INDEX;
if (tabs[i].is_pinned)
add_types |= TabStripModel::ADD_PINNED;
NavigateParams params(browser, tabs[i].url,
ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
params.disposition = first_tab ? WindowOpenDisposition::NEW_FOREGROUND_TAB
: WindowOpenDisposition::NEW_BACKGROUND_TAB;
params.tabstrip_add_types = add_types;
#if BUILDFLAG(ENABLE_RLZ)
if (process_startup && google_util::IsGoogleHomePageUrl(tabs[i].url)) {
params.extra_headers = rlz::RLZTracker::GetAccessPointHttpHeader(
rlz::RLZTracker::ChromeHomePage());
}
#endif // BUILDFLAG(ENABLE_RLZ)
Navigate(&params);
first_tab = false;
}
if (!browser->tab_strip_model()->GetActiveWebContents()) {
// TODO(sky): this is a work around for 110909. Figure out why it's needed.
if (!browser->tab_strip_model()->count())
chrome::AddTabAt(browser, GURL(), -1, true);
else
browser->tab_strip_model()->ActivateTabAt(0);
}
// The default behavior is to show the window, as expressed by the default
// value of StartupBrowserCreated::show_main_browser_window_. If this was set
// to true ahead of this place, it means another task must have been spawned
// to take care of that.
if (!browser_creator_ || browser_creator_->show_main_browser_window())
browser->window()->Show();
return browser;
}
bool StartupBrowserCreatorImpl::IsAppLaunch(std::string* app_url,
std::string* app_id) {
if (command_line_.HasSwitch(switches::kApp)) {
if (app_url)
*app_url = command_line_.GetSwitchValueASCII(switches::kApp);
return true;
}
if (command_line_.HasSwitch(switches::kAppId)) {
if (app_id)
*app_id = command_line_.GetSwitchValueASCII(switches::kAppId);
return true;
}
return false;
}
bool StartupBrowserCreatorImpl::OpenApplicationWindow(Profile* profile) {
std::string url_string, app_id;
if (!IsAppLaunch(&url_string, &app_id))
return false;
// This can fail if the app_id is invalid. It can also fail if the
// extension is external, and has not yet been installed.
// TODO(skerner): Do something reasonable here. Pop up a warning panel?
// Open an URL to the gallery page of the extension id?
if (!app_id.empty()) {
return apps::OpenApplicationWindow(profile, app_id, command_line_,
cur_dir_);
}
if (url_string.empty())
return false;
#if defined(OS_WIN) // Fix up Windows shortcuts.
base::ReplaceSubstringsAfterOffset(&url_string, 0, "\\x", "%");
#endif
GURL url(url_string);
// Restrict allowed URLs for --app switch.
if (!url.is_empty() && url.is_valid()) {
content::ChildProcessSecurityPolicy* policy =
content::ChildProcessSecurityPolicy::GetInstance();
if (policy->IsWebSafeScheme(url.scheme()) ||
url.SchemeIs(url::kFileScheme)) {
return apps::OpenAppShortcutWindow(profile, url);
}
}
return false;
}
bool StartupBrowserCreatorImpl::OpenApplicationTab(Profile* profile) {
std::string app_id;
// App shortcuts to URLs always open in an app window. Because this
// function will open an app that should be in a tab, there is no need
// to look at the app URL. OpenApplicationWindow() will open app url
// shortcuts.
if (!IsAppLaunch(nullptr, &app_id) || app_id.empty())
return false;
return apps::OpenApplicationTab(profile, app_id);
}
void StartupBrowserCreatorImpl::DetermineURLsAndLaunch(
bool process_startup,
const std::vector<GURL>& cmd_line_urls) {
// Don't open any browser windows if starting up in "background mode".
if (process_startup && command_line_.HasSwitch(switches::kNoStartupWindow))
return;
StartupTabs cmd_line_tabs;
UrlsToTabs(cmd_line_urls, &cmd_line_tabs);
const bool is_incognito_or_guest = profile_->IsOffTheRecord();
bool is_post_crash_launch = HasPendingUncleanExit(profile_);
bool has_incompatible_applications = false;
#if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
if (is_post_crash_launch) {
// Check if there are any incompatible applications cached from the last
// Chrome run.
has_incompatible_applications =
IncompatibleApplicationsUpdater::HasCachedApplications();
}
#endif
// Presentation of promotional and/or educational tabs may be controlled via
// administrative policy.
bool promotional_tabs_enabled = true;
const PrefService::Preference* enabled_pref = nullptr;
#if !defined(OS_CHROMEOS)
PrefService* local_state = g_browser_process->local_state();
if (local_state)
enabled_pref = local_state->FindPreference(prefs::kPromotionalTabsEnabled);
#endif // !defined(OS_CHROMEOS)
if (enabled_pref && enabled_pref->IsManaged()) {
// Presentation is managed; obey the policy setting.
promotional_tabs_enabled = enabled_pref->GetValue()->GetBool();
} else {
// Presentation is not managed. Infer an intent to disable if any value for
// the RestoreOnStartup policy is mandatory or recommended.
promotional_tabs_enabled =
!SessionStartupPref::TypeIsManaged(profile_->GetPrefs()) &&
!SessionStartupPref::TypeHasRecommendedValue(profile_->GetPrefs());
}
#if !defined(OS_CHROMEOS)
// No promo if we *could* onboard, but have no modules to show.
if (nux::IsNuxOnboardingEnabled(profile_))
promotional_tabs_enabled &= nux::DoesOnboardingHaveModulesToShow(profile_);
#endif // !defined(OS_CHROMEOS)
StartupTabs tabs = DetermineStartupTabs(
StartupTabProviderImpl(), cmd_line_tabs, process_startup,
is_incognito_or_guest, is_post_crash_launch,
has_incompatible_applications, promotional_tabs_enabled);
// Return immediately if we start an async restore, since the remainder of
// that process is self-contained.
if (MaybeAsyncRestore(tabs, process_startup, is_post_crash_launch))
return;
BrowserOpenBehaviorOptions behavior_options = 0;
if (process_startup)
behavior_options |= PROCESS_STARTUP;
if (is_post_crash_launch)
behavior_options |= IS_POST_CRASH_LAUNCH;
if (command_line_.HasSwitch(switches::kOpenInNewWindow))
behavior_options |= HAS_NEW_WINDOW_SWITCH;
if (!cmd_line_tabs.empty())
behavior_options |= HAS_CMD_LINE_TABS;
BrowserOpenBehavior behavior = DetermineBrowserOpenBehavior(
StartupBrowserCreator::GetSessionStartupPref(command_line_, profile_),
behavior_options);
SessionRestore::BehaviorBitmask restore_options = 0;
if (behavior == BrowserOpenBehavior::SYNCHRONOUS_RESTORE) {
#if defined(OS_MACOSX)
bool was_mac_login_or_resume = base::mac::WasLaunchedAsLoginOrResumeItem();
#else
bool was_mac_login_or_resume = false;
#endif
restore_options = DetermineSynchronousRestoreOptions(
browser_defaults::kAlwaysCreateTabbedBrowserOnSessionRestore,
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kCreateBrowserOnStartupForTests),
was_mac_login_or_resume);
}
Browser* browser = RestoreOrCreateBrowser(
tabs, behavior, restore_options, process_startup, is_post_crash_launch);
// Finally, add info bars.
AddInfoBarsIfNecessary(
browser, process_startup ? chrome::startup::IS_PROCESS_STARTUP
: chrome::startup::IS_NOT_PROCESS_STARTUP);
}
StartupTabs StartupBrowserCreatorImpl::DetermineStartupTabs(
const StartupTabProvider& provider,
const StartupTabs& cmd_line_tabs,
bool process_startup,
bool is_incognito_or_guest,
bool is_post_crash_launch,
bool has_incompatible_applications,
bool promotional_tabs_enabled) {
// Only the New Tab Page or command line URLs may be shown in incognito mode.
// A similar policy exists for crash recovery launches, to prevent getting the
// user stuck in a crash loop.
if (is_incognito_or_guest || is_post_crash_launch) {
if (!cmd_line_tabs.empty())
return cmd_line_tabs;
if (is_post_crash_launch) {
const StartupTabs tabs =
provider.GetPostCrashTabs(has_incompatible_applications);
if (!tabs.empty())
return tabs;
}
return StartupTabs({StartupTab(GURL(chrome::kChromeUINewTabURL), false)});
}
// A trigger on a profile may indicate that we should show a tab which
// offers to reset the user's settings. When this appears, it is first, and
// may be shown alongside command-line tabs.
StartupTabs tabs = provider.GetResetTriggerTabs(profile_);
// URLs passed on the command line supersede all others.
AppendTabs(cmd_line_tabs, &tabs);
if (!cmd_line_tabs.empty())
return tabs;
// A Master Preferences file provided with this distribution may specify
// tabs to be displayed on first run, overriding all non-command-line tabs,
// including the profile reset tab.
StartupTabs distribution_tabs =
provider.GetDistributionFirstRunTabs(browser_creator_);
if (!distribution_tabs.empty())
return distribution_tabs;
StartupTabs onboarding_tabs;
if (promotional_tabs_enabled) {
// This is a launch from a prompt presented to an inactive user who chose to
// open Chrome and is being brought to a specific URL for this one launch.
// Launch the browser with the desired welcome back URL in the foreground
// and the other ordinary URLs (e.g., a restored session) in the background.
StartupTabs welcome_back_tabs = provider.GetWelcomeBackTabs(
profile_, browser_creator_, process_startup);
AppendTabs(welcome_back_tabs, &tabs);
// Policies for onboarding (e.g., first run) may show promotional and
// introductory content depending on a number of system status factors,
// including OS and whether or not this is First Run.
onboarding_tabs = provider.GetOnboardingTabs(profile_);
AppendTabs(onboarding_tabs, &tabs);
}
// If the user has set the preference indicating URLs to show on opening,
// read and add those.
StartupTabs prefs_tabs = provider.GetPreferencesTabs(command_line_, profile_);
AppendTabs(prefs_tabs, &tabs);
// Potentially add the New Tab Page. Onboarding content is designed to
// replace (and eventually funnel the user to) the NTP. Likewise, URLs
// from preferences are explicitly meant to override showing the NTP.
if (onboarding_tabs.empty() && prefs_tabs.empty())
AppendTabs(provider.GetNewTabPageTabs(command_line_, profile_), &tabs);
// Maybe add any tabs which the user has previously pinned.
AppendTabs(provider.GetPinnedTabs(command_line_, profile_), &tabs);
return tabs;
}
bool StartupBrowserCreatorImpl::MaybeAsyncRestore(const StartupTabs& tabs,
bool process_startup,
bool is_post_crash_launch) {
// Restore is performed synchronously on startup, and is never performed when
// launching after crashing.
if (process_startup || is_post_crash_launch)
return false;
// Note: there's no session service in incognito or guest mode.
SessionService* service =
SessionServiceFactory::GetForProfileForSessionRestore(profile_);
return service && service->RestoreIfNecessary(TabsToUrls(tabs));
}
Browser* StartupBrowserCreatorImpl::RestoreOrCreateBrowser(
const StartupTabs& tabs,
BrowserOpenBehavior behavior,
SessionRestore::BehaviorBitmask restore_options,
bool process_startup,
bool is_post_crash_launch) {
Browser* browser = nullptr;
if (behavior == BrowserOpenBehavior::SYNCHRONOUS_RESTORE) {
browser = SessionRestore::RestoreSession(profile_, nullptr, restore_options,
TabsToUrls(tabs));
if (browser)
return browser;
} else if (behavior == BrowserOpenBehavior::USE_EXISTING) {
browser = chrome::FindTabbedBrowser(profile_, process_startup);
}
base::AutoReset<bool> synchronous_launch_resetter(
&StartupBrowserCreator::in_synchronous_profile_launch_, true);
// OpenTabsInBrowser requires at least one tab be passed. As a fallback to
// prevent a crash, use the NTP if |tabs| is empty. This could happen if
// we expected a session restore to happen but it did not occur/succeed.
browser = OpenTabsInBrowser(
browser, process_startup,
(tabs.empty()
? StartupTabs({StartupTab(GURL(chrome::kChromeUINewTabURL), false)})
: tabs));
// Now that a restore is no longer possible, it is safe to clear DOM storage,
// unless this is a crash recovery.
if (!is_post_crash_launch) {
content::BrowserContext::GetDefaultStoragePartition(profile_)
->GetDOMStorageContext()
->StartScavengingUnusedSessionStorage();
}
return browser;
}
void StartupBrowserCreatorImpl::AddInfoBarsIfNecessary(
Browser* browser,
chrome::startup::IsProcessStartup is_process_startup) {
if (!browser || !profile_ || browser->tab_strip_model()->count() == 0)
return;
if (HasPendingUncleanExit(browser->profile())) {
SessionCrashedBubble::Show(browser);
}
bool show_bad_flags_security_warnings = ShouldShowBadFlagsSecurityWarnings();
if (command_line_.HasSwitch(switches::kEnableAutomation) &&
show_bad_flags_security_warnings) {
AutomationInfoBarDelegate::Create();
}
// The below info bars are only added to the first profile which is launched.
// Other profiles might be restoring the browsing sessions asynchronously,
// so we cannot add the info bars to the focused tabs here.
//
// These info bars are not shown when the browser is being controlled by
// automated tests, so that they don't interfere with tests that assume no
// info bars.
if (is_process_startup == chrome::startup::IS_PROCESS_STARTUP &&
!command_line_.HasSwitch(switches::kTestType) &&
!command_line_.HasSwitch(switches::kEnableAutomation)) {
content::WebContents* web_contents =
browser->tab_strip_model()->GetActiveWebContents();
DCHECK(web_contents);
if (show_bad_flags_security_warnings)
chrome::ShowBadFlagsPrompt(web_contents);
InfoBarService* infobar_service =
InfoBarService::FromWebContents(web_contents);
if (!google_apis::HasAPIKeyConfigured() ||
!google_apis::HasOAuthClientConfigured()) {
GoogleApiKeysInfoBarDelegate::Create(infobar_service);
}
if (ObsoleteSystem::IsObsoleteNowOrSoon()) {
PrefService* local_state = g_browser_process->local_state();
if (!local_state ||
!local_state->GetBoolean(prefs::kSuppressUnsupportedOSWarning))
ObsoleteSystemInfoBarDelegate::Create(infobar_service);
}
#if BUILDFLAG(ENABLE_SUPERVISED_USERS) && !defined(OS_ANDROID) && \
!defined(OS_CHROMEOS)
if (profile_->IsLegacySupervised())
SupervisedUsersDeprecatedInfoBarDelegate::Create(infobar_service);
#endif
#if !defined(OS_CHROMEOS)
if (!command_line_.HasSwitch(switches::kNoDefaultBrowserCheck)) {
// Generally, the default browser prompt should not be shown on first
// run. However, when the set-as-default dialog has been suppressed, we
// need to allow it.
if (!is_first_run_ ||
(browser_creator_ &&
browser_creator_->is_default_browser_dialog_suppressed())) {
ShowDefaultBrowserPrompt(profile_);
}
}
#endif
#if BUILDFLAG(ENABLE_PLUGINS)
if (FlashDeprecationInfoBarDelegate::ShouldDisplayFlashDeprecation(
profile_)) {
FlashDeprecationInfoBarDelegate::Create(infobar_service);
}
#endif
}
}
void StartupBrowserCreatorImpl::RecordRapporOnStartupURLs(
const std::vector<GURL>& urls_to_open) {
for (const GURL& url : urls_to_open) {
rappor::SampleDomainAndRegistryFromGURL(g_browser_process->rappor_service(),
"Startup.BrowserLaunchURL", url);
}
}
// static
StartupBrowserCreatorImpl::BrowserOpenBehavior
StartupBrowserCreatorImpl::DetermineBrowserOpenBehavior(
const SessionStartupPref& pref,
BrowserOpenBehaviorOptions options) {
if (!(options & PROCESS_STARTUP)) {
// For existing processes, restore would have happened before invoking this
// function. If Chrome was launched with passed URLs, assume these should
// be appended to an existing window if possible, unless overridden by a
// switch.
return ((options & HAS_CMD_LINE_TABS) && !(options & HAS_NEW_WINDOW_SWITCH))
? BrowserOpenBehavior::USE_EXISTING
: BrowserOpenBehavior::NEW;
}
if (pref.type == SessionStartupPref::LAST) {
// Don't perform a session restore on a post-crash launch, as this could
// cause a crash loop.
if (!(options & IS_POST_CRASH_LAUNCH))
return BrowserOpenBehavior::SYNCHRONOUS_RESTORE;
}
return BrowserOpenBehavior::NEW;
}
// static
SessionRestore::BehaviorBitmask
StartupBrowserCreatorImpl::DetermineSynchronousRestoreOptions(
bool has_create_browser_default,
bool has_create_browser_switch,
bool was_mac_login_or_resume) {
SessionRestore::BehaviorBitmask options = SessionRestore::SYNCHRONOUS;
// Suppress the creation of a new window on Mac when restoring with no windows
// if launching Chrome via a login item or the resume feature in OS 10.7+.
if (!was_mac_login_or_resume &&
(has_create_browser_default || has_create_browser_switch))
options |= SessionRestore::ALWAYS_CREATE_TABBED_BROWSER;
return options;
}