| // 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/browser_init.h" |
| |
| #include "base/basictypes.h" |
| #include "base/command_line.h" |
| #include "base/event_recorder.h" |
| #include "base/histogram.h" |
| #include "base/path_service.h" |
| #include "base/string_util.h" |
| #include "base/sys_info.h" |
| #include "chrome/browser/autocomplete/autocomplete.h" |
| #include "chrome/browser/automation/automation_provider.h" |
| #include "chrome/browser/automation/automation_provider_list.h" |
| #include "chrome/browser/browser_list.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/extensions/extensions_service.h" |
| #include "chrome/browser/net/dns_global.h" |
| #include "chrome/browser/profile.h" |
| #include "chrome/browser/renderer_host/render_process_host.h" |
| #include "chrome/browser/search_engines/template_url_model.h" |
| #include "chrome/browser/session_startup_pref.h" |
| #include "chrome/browser/sessions/session_restore.h" |
| #include "chrome/browser/tab_contents/infobar_delegate.h" |
| #include "chrome/browser/tab_contents/navigation_controller.h" |
| #include "chrome/browser/tab_contents/web_contents.h" |
| #include "chrome/browser/tab_contents/tab_contents_view.h" |
| #include "chrome/browser/net/url_fixer_upper.h" |
| #include "chrome/common/chrome_constants.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/l10n_util.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/pref_service.h" |
| #include "chrome/common/resource_bundle.h" |
| #include "chrome/common/result_codes.h" |
| #include "grit/chromium_strings.h" |
| #include "grit/generated_resources.h" |
| #include "grit/locale_settings.h" |
| #include "grit/theme_resources.h" |
| #include "net/base/cookie_monster.h" |
| #include "webkit/glue/webkit_glue.h" |
| |
| #if defined(OS_WIN) |
| #include "base/win_util.h" |
| #endif |
| |
| #ifdef FRAME_WINDOW |
| #include "frame_window/frame_window.h" |
| #endif |
| |
| namespace { |
| |
| // A delegate for the InfoBar shown when the previous session has crashed. The |
| // bar deletes itself automatically after it is closed. |
| // TODO(timsteele): This delegate can leak when a tab is closed, see |
| // http://crbug.com/6520 |
| class SessionCrashedInfoBarDelegate : public ConfirmInfoBarDelegate { |
| public: |
| explicit SessionCrashedInfoBarDelegate(TabContents* contents) |
| : ConfirmInfoBarDelegate(contents), |
| profile_(contents->profile()) { |
| } |
| |
| // Overridden from ConfirmInfoBarDelegate: |
| virtual void InfoBarClosed() { |
| delete this; |
| } |
| virtual std::wstring GetMessageText() const { |
| return l10n_util::GetString(IDS_SESSION_CRASHED_VIEW_MESSAGE); |
| } |
| virtual SkBitmap* GetIcon() const { |
| return ResourceBundle::GetSharedInstance().GetBitmapNamed( |
| IDR_INFOBAR_RESTORE_SESSION); |
| } |
| virtual int GetButtons() const { return BUTTON_OK; } |
| virtual std::wstring GetButtonLabel(InfoBarButton button) const { |
| return l10n_util::GetString(IDS_SESSION_CRASHED_VIEW_RESTORE_BUTTON); |
| } |
| virtual bool Accept() { |
| // Restore the session. |
| SessionRestore::RestoreSession(profile_, NULL, true, false, |
| std::vector<GURL>()); |
| return true; |
| } |
| |
| private: |
| // The Profile that we restore sessions from. |
| Profile* profile_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SessionCrashedInfoBarDelegate); |
| }; |
| |
| void SetOverrideHomePage(const CommandLine& command_line, |
| PrefService* prefs) { |
| // If homepage is specified on the command line, canonify & store it. |
| if (command_line.HasSwitch(switches::kHomePage)) { |
| std::wstring browser_directory; |
| PathService::Get(base::DIR_CURRENT, &browser_directory); |
| std::wstring new_homepage = URLFixerUpper::FixupRelativeFile( |
| browser_directory, |
| command_line.GetSwitchValue(switches::kHomePage)); |
| prefs->transient()->SetString(prefs::kHomePage, new_homepage); |
| prefs->transient()->SetBoolean(prefs::kHomePageIsNewTabPage, false); |
| } |
| } |
| |
| SessionStartupPref GetSessionStartupPref(const CommandLine& command_line, |
| Profile* profile) { |
| SessionStartupPref pref = SessionStartupPref::GetStartupPref(profile); |
| if (command_line.HasSwitch(switches::kRestoreLastSession)) |
| pref.type = SessionStartupPref::LAST; |
| if (command_line.HasSwitch(switches::kIncognito) && |
| pref.type == SessionStartupPref::LAST) { |
| // We don't store session information when incognito. If the user has |
| // chosen to restore last session and launched incognito, fallback to |
| // default launch behavior. |
| pref.type = SessionStartupPref::DEFAULT; |
| } |
| return pref; |
| } |
| |
| enum LaunchMode { |
| LM_TO_BE_DECIDED = 0, // Possibly direct launch or via a shortcut. |
| LM_AS_WEBAPP, // Launched as a installed web application. |
| LM_WITH_URLS, // Launched with urls in the cmd line. |
| LM_SHORTCUT_NONE, // Not launched from a shortcut. |
| LM_SHORTCUT_NONAME, // Launched from shortcut but no name available. |
| LM_SHORTCUT_UNKNOWN, // Launched from user-defined shortcut. |
| LM_SHORTCUT_QUICKLAUNCH, // Launched from the quick launch bar. |
| LM_SHORTCUT_DESKTOP, // Launched from a desktop shortcut. |
| LM_SHORTCUT_STARTMENU, // Launched from start menu. |
| LM_LINUX_MAC_BEOS // Other OS buckets start here. |
| }; |
| |
| #if defined(OS_WIN) |
| // Undocumented flag in the startup info structure tells us what shortcut was |
| // used to launch the browser. See http://www.catch22.net/tuts/undoc01 for |
| // more information. Confirmed to work on XP, Vista and Win7. |
| LaunchMode GetLaunchShortcutKind() { |
| STARTUPINFOW si = { sizeof(si) }; |
| GetStartupInfoW(&si); |
| if (si.dwFlags & 0x800) { |
| if (!si.lpTitle) |
| return LM_SHORTCUT_NONAME; |
| std::wstring shortcut(si.lpTitle); |
| // The windows quick launch path is not localized. |
| if (shortcut.find(L"\\Quick Launch\\") != std::wstring::npos) |
| return LM_SHORTCUT_QUICKLAUNCH; |
| std::wstring appdata_path = base::SysInfo::GetEnvVar(L"USERPROFILE"); |
| if (!appdata_path.empty() && |
| shortcut.find(appdata_path) != std::wstring::npos) |
| return LM_SHORTCUT_DESKTOP; |
| return LM_SHORTCUT_UNKNOWN; |
| } |
| return LM_SHORTCUT_NONE; |
| } |
| #else |
| // TODO(cpu): Port to other platforms. |
| LaunchMode GetLaunchShortcutKind() { |
| return LM_LINUX_MAC_BEOS; |
| } |
| #endif |
| |
| // 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) { |
| int bucket = (mode == LM_TO_BE_DECIDED) ? GetLaunchShortcutKind() : mode; |
| UMA_HISTOGRAM_COUNTS_100("Launch.Modes", bucket); |
| } |
| |
| } // namespace |
| |
| static bool in_startup = false; |
| |
| // static |
| bool BrowserInit::InProcessStartup() { |
| return in_startup; |
| } |
| |
| // LaunchWithProfile ---------------------------------------------------------- |
| |
| BrowserInit::LaunchWithProfile::LaunchWithProfile( |
| const std::wstring& cur_dir, |
| const CommandLine& command_line) |
| : cur_dir_(cur_dir), |
| command_line_(command_line) { |
| } |
| |
| bool BrowserInit::LaunchWithProfile::Launch(Profile* profile, |
| bool process_startup) { |
| DCHECK(profile); |
| profile_ = profile; |
| |
| if (command_line_.HasSwitch(switches::kDnsLogDetails)) |
| chrome_browser_net::EnableDnsDetailedLog(true); |
| if (command_line_.HasSwitch(switches::kDnsPrefetchDisable)) |
| chrome_browser_net::EnableDnsPrefetch(false); |
| |
| if (command_line_.HasSwitch(switches::kDumpHistogramsOnExit)) |
| StatisticsRecorder::set_dump_on_exit(true); |
| |
| if (command_line_.HasSwitch(switches::kRemoteShellPort)) { |
| if (!RenderProcessHost::run_renderer_in_process()) { |
| std::wstring port_str = |
| command_line_.GetSwitchValue(switches::kRemoteShellPort); |
| int64 port = StringToInt64(WideToUTF16Hack(port_str)); |
| if (port > 0 && port < 65535) { |
| g_browser_process->InitDebuggerWrapper(static_cast<int>(port)); |
| } else { |
| DLOG(WARNING) << "Invalid port number " << port; |
| } |
| } |
| } |
| |
| if (command_line_.HasSwitch(switches::kEnableFileCookies)) |
| net::CookieMonster::EnableFileScheme(); |
| |
| if (command_line_.HasSwitch(switches::kUserAgent)) { |
| #if defined(OS_WIN) |
| webkit_glue::SetUserAgent(WideToUTF8( |
| command_line_.GetSwitchValue(switches::kUserAgent))); |
| // TODO(port): hook this up when we bring in webkit. |
| #endif |
| } |
| |
| // Open the required browser windows and tabs. |
| // First, see if we're being run as a web application (thin frame window). |
| if (!OpenApplicationURL(profile)) { |
| std::vector<GURL> urls_to_open = GetURLsFromCommandLine(profile_); |
| RecordLaunchModeHistogram(urls_to_open.empty()? |
| LM_TO_BE_DECIDED : LM_WITH_URLS); |
| // Always attempt to restore the last session. OpenStartupURLs only opens |
| // the home pages if no additional URLs were passed on the command line. |
| if (!OpenStartupURLs(process_startup, urls_to_open)) { |
| // Add the home page and any special first run URLs. |
| Browser* browser = NULL; |
| if (urls_to_open.empty()) |
| AddStartupURLs(&urls_to_open); |
| else |
| browser = BrowserList::GetLastActive(); |
| OpenURLsInBrowser(browser, process_startup, urls_to_open); |
| } |
| } else { |
| RecordLaunchModeHistogram(LM_AS_WEBAPP); |
| } |
| |
| // If we're recording or playing back, startup the EventRecorder now |
| // unless otherwise specified. |
| if (!command_line_.HasSwitch(switches::kNoEvents)) { |
| std::wstring script_path; |
| PathService::Get(chrome::FILE_RECORDED_SCRIPT, &script_path); |
| |
| bool record_mode = command_line_.HasSwitch(switches::kRecordMode); |
| bool playback_mode = command_line_.HasSwitch(switches::kPlaybackMode); |
| |
| if (record_mode && chrome::kRecordModeEnabled) |
| base::EventRecorder::current()->StartRecording(script_path); |
| if (playback_mode) |
| base::EventRecorder::current()->StartPlayback(script_path); |
| } |
| |
| return true; |
| } |
| |
| bool BrowserInit::LaunchWithProfile::OpenApplicationURL(Profile* profile) { |
| if (!command_line_.HasSwitch(switches::kApp)) |
| return false; |
| |
| GURL url(WideToUTF8(command_line_.GetSwitchValue(switches::kApp))); |
| if (!url.is_empty() && url.is_valid()) { |
| Browser::OpenApplicationWindow(profile, url); |
| return true; |
| } |
| return false; |
| } |
| |
| bool BrowserInit::LaunchWithProfile::OpenStartupURLs( |
| bool is_process_startup, |
| const std::vector<GURL>& urls_to_open) { |
| SessionStartupPref pref = GetSessionStartupPref(command_line_, profile_); |
| switch (pref.type) { |
| case SessionStartupPref::LAST: |
| if (!is_process_startup) |
| return false; |
| |
| if (!profile_->DidLastSessionExitCleanly() && |
| !command_line_.HasSwitch(switches::kRestoreLastSession)) { |
| // The last session crashed. It's possible automatically loading the |
| // page will trigger another crash, locking the user out of chrome. |
| // To avoid this, don't restore on startup but instead show the crashed |
| // infobar. |
| return false; |
| } |
| SessionRestore::RestoreSessionSynchronously(profile_, urls_to_open); |
| return true; |
| |
| case SessionStartupPref::URLS: |
| // When the user launches the app only open the default set of URLs if |
| // we aren't going to open any URLs on the command line. |
| if (urls_to_open.empty()) { |
| if (pref.urls.empty()) |
| return false; // No URLs to open. |
| OpenURLsInBrowser(NULL, is_process_startup, pref.urls); |
| return true; |
| } |
| return false; |
| |
| default: |
| return false; |
| } |
| } |
| |
| Browser* BrowserInit::LaunchWithProfile::OpenURLsInBrowser( |
| Browser* browser, |
| bool process_startup, |
| const std::vector<GURL>& urls) { |
| DCHECK(!urls.empty()); |
| if (!browser || browser->type() != Browser::TYPE_NORMAL) |
| browser = Browser::Create(profile_); |
| |
| for (size_t i = 0; i < urls.size(); ++i) { |
| TabContents* tab = browser->AddTabWithURL( |
| urls[i], GURL(), PageTransition::START_PAGE, (i == 0), -1, NULL); |
| if (i == 0 && process_startup) |
| AddCrashedInfoBarIfNecessary(tab); |
| } |
| browser->window()->Show(); |
| // TODO(jcampan): http://crbug.com/8123 we should not need to set the initial |
| // focus explicitly. |
| if (browser->GetSelectedTabContents()->AsWebContents()) { |
| browser->GetSelectedTabContents()->AsWebContents()->view()-> |
| SetInitialFocus(); |
| } |
| |
| return browser; |
| } |
| |
| void BrowserInit::LaunchWithProfile::AddCrashedInfoBarIfNecessary( |
| TabContents* tab) { |
| // Assume that if the user is launching incognito they were previously |
| // running incognito so that we have nothing to restore from. |
| if (!profile_->DidLastSessionExitCleanly() && |
| !profile_->IsOffTheRecord()) { |
| // The last session didn't exit cleanly. Show an infobar to the user |
| // so that they can restore if they want. The delegate deletes itself when |
| // it is closed. |
| tab->AddInfoBar(new SessionCrashedInfoBarDelegate(tab)); |
| } |
| } |
| |
| std::vector<GURL> BrowserInit::LaunchWithProfile::GetURLsFromCommandLine( |
| Profile* profile) { |
| std::vector<GURL> urls; |
| std::vector<std::wstring> params = command_line_.GetLooseValues(); |
| for (size_t i = 0; i < params.size(); ++i) { |
| std::wstring& value = params[i]; |
| // Handle Vista way of searching - "? <search-term>" |
| if (value.find(L"? ") == 0) { |
| const TemplateURL* default_provider = |
| profile->GetTemplateURLModel()->GetDefaultSearchProvider(); |
| if (!default_provider || !default_provider->url()) { |
| // No search provider available. Just treat this as regular URL. |
| urls.push_back( |
| GURL(WideToUTF8(URLFixerUpper::FixupRelativeFile(cur_dir_, |
| value)))); |
| continue; |
| } |
| const TemplateURLRef* search_url = default_provider->url(); |
| DCHECK(search_url->SupportsReplacement()); |
| urls.push_back(GURL(search_url->ReplaceSearchTerms(*default_provider, |
| value.substr(2), TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, |
| std::wstring()))); |
| } else { |
| // This will create a file URL or a regular URL. |
| urls.push_back(GURL(WideToUTF8( |
| URLFixerUpper::FixupRelativeFile(cur_dir_, value)))); |
| } |
| } |
| return urls; |
| } |
| |
| void BrowserInit::LaunchWithProfile::AddStartupURLs( |
| std::vector<GURL>* startup_urls) const { |
| // Otherwise open at least the new tab page (and the welcome page, if this |
| // is the first time the browser is being started), or the set of URLs |
| // specified on the command line. |
| if (startup_urls->empty()) { |
| startup_urls->push_back(GURL()); // New tab page. |
| PrefService* prefs = g_browser_process->local_state(); |
| if (prefs->IsPrefRegistered(prefs::kShouldShowWelcomePage) && |
| prefs->GetBoolean(prefs::kShouldShowWelcomePage)) { |
| // Reset the preference so we don't show the welcome page next time. |
| prefs->ClearPref(prefs::kShouldShowWelcomePage); |
| |
| #if defined(OS_WIN) |
| // Add the welcome page. |
| std::wstring welcome_url = l10n_util::GetString(IDS_WELCOME_PAGE_URL); |
| startup_urls->push_back(GURL(welcome_url)); |
| #else |
| // TODO(port): implement welcome page. |
| NOTIMPLEMENTED(); |
| #endif |
| } |
| } |
| } |
| |
| bool BrowserInit::ProcessCommandLine( |
| const CommandLine& command_line, const std::wstring& cur_dir, |
| bool process_startup, Profile* profile, int* return_code) { |
| DCHECK(profile); |
| if (process_startup) { |
| const std::wstring popup_count_string = |
| command_line.GetSwitchValue(switches::kOmniBoxPopupCount); |
| if (!popup_count_string.empty()) { |
| int count = 0; |
| if (StringToInt(WideToUTF16Hack(popup_count_string), &count)) { |
| const int popup_count = std::max(0, count); |
| AutocompleteResult::set_max_matches(popup_count); |
| AutocompleteProvider::set_max_matches(popup_count / 2); |
| } |
| } |
| |
| if (command_line.HasSwitch(switches::kDisablePromptOnRepost)) |
| NavigationController::DisablePromptOnRepost(); |
| |
| const std::wstring tab_count_string = |
| command_line.GetSwitchValue(switches::kTabCountToLoadOnSessionRestore); |
| if (!tab_count_string.empty()) { |
| int count = 0; |
| if (StringToInt(WideToUTF16Hack(tab_count_string), &count)) { |
| const int tab_count = std::max(0, count); |
| SessionRestore::num_tabs_to_load_ = static_cast<size_t>(tab_count); |
| } |
| } |
| |
| // Look for the testing channel ID ONLY during process startup |
| if (command_line.HasSwitch(switches::kTestingChannelID)) { |
| std::wstring testing_channel_id = |
| command_line.GetSwitchValue(switches::kTestingChannelID); |
| // TODO(sanjeevr) Check if we need to make this a singleton for |
| // compatibility with the old testing code |
| // If there are any loose parameters, we expect each one to generate a |
| // new tab; if there are none then we get one homepage tab. |
| int expected_tab_count = 1; |
| if (command_line.HasSwitch(switches::kRestoreLastSession)) { |
| std::wstring restore_session_value( |
| command_line.GetSwitchValue(switches::kRestoreLastSession)); |
| StringToInt(WideToUTF16Hack(restore_session_value), |
| &expected_tab_count); |
| } else { |
| expected_tab_count = |
| std::max(1, static_cast<int>(command_line.GetLooseValues().size())); |
| } |
| CreateAutomationProvider<TestingAutomationProvider>( |
| testing_channel_id, |
| profile, |
| static_cast<size_t>(expected_tab_count)); |
| } |
| } |
| |
| // Allow the command line to override the persisted setting of home page. |
| SetOverrideHomePage(command_line, profile->GetPrefs()); |
| |
| bool silent_launch = false; |
| if (command_line.HasSwitch(switches::kAutomationClientChannelID)) { |
| std::wstring automation_channel_id = |
| command_line.GetSwitchValue(switches::kAutomationClientChannelID); |
| // If there are any loose parameters, we expect each one to generate a |
| // new tab; if there are none then we have no tabs |
| size_t expected_tabs = |
| std::max(static_cast<int>(command_line.GetLooseValues().size()), |
| 0); |
| if (expected_tabs == 0) |
| silent_launch = true; |
| CreateAutomationProvider<AutomationProvider>(automation_channel_id, |
| profile, expected_tabs); |
| } |
| |
| if (command_line.HasSwitch(switches::kLoadExtension)) { |
| std::wstring path_string = |
| command_line.GetSwitchValue(switches::kLoadExtension); |
| FilePath path = FilePath::FromWStringHack(path_string); |
| profile->GetExtensionsService()->LoadExtension(path); |
| profile->GetUserScriptMaster()->AddWatchedPath(path); |
| } |
| |
| if (command_line.HasSwitch(switches::kInstallExtension)) { |
| std::wstring path_string = |
| command_line.GetSwitchValue(switches::kInstallExtension); |
| FilePath path = FilePath::FromWStringHack(path_string); |
| profile->GetExtensionsService()->InstallExtension(path); |
| |
| // If the chrome process was already running, install the extension without |
| // popping up another browser window. |
| silent_launch = !process_startup; |
| } |
| |
| // If we don't want to launch a new browser window or tab (in the case |
| // of an automation request), we are done here. |
| if (!silent_launch) { |
| return LaunchBrowser(command_line, profile, cur_dir, process_startup, |
| return_code); |
| } |
| return true; |
| } |
| |
| bool BrowserInit::LaunchBrowser(const CommandLine& command_line, |
| Profile* profile, const std::wstring& cur_dir, |
| bool process_startup, int* return_code) { |
| in_startup = process_startup; |
| bool result = LaunchBrowserImpl(command_line, profile, cur_dir, |
| process_startup, return_code); |
| in_startup = false; |
| return result; |
| } |
| |
| template <class AutomationProviderClass> |
| void BrowserInit::CreateAutomationProvider(const std::wstring& channel_id, |
| Profile* profile, |
| size_t expected_tabs) { |
| scoped_refptr<AutomationProviderClass> automation = |
| new AutomationProviderClass(profile); |
| automation->ConnectToChannel(channel_id); |
| automation->SetExpectedTabCount(expected_tabs); |
| |
| AutomationProviderList* list = |
| g_browser_process->InitAutomationProviderList(); |
| DCHECK(list); |
| list->AddProvider(automation); |
| } |
| |
| bool BrowserInit::LaunchBrowserImpl(const CommandLine& command_line, |
| Profile* profile, |
| const std::wstring& cur_dir, |
| bool process_startup, |
| int* return_code) { |
| DCHECK(profile); |
| |
| #ifdef FRAME_WINDOW |
| FrameWindow::Show(profile); |
| return true; |
| #endif |
| |
| // Continue with the off-the-record profile from here on if --incognito |
| if (command_line.HasSwitch(switches::kIncognito)) |
| profile = profile->GetOffTheRecordProfile(); |
| |
| LaunchWithProfile lwp(cur_dir, command_line); |
| bool launched = lwp.Launch(profile, process_startup); |
| if (!launched) { |
| LOG(ERROR) << "launch error"; |
| if (return_code != NULL) |
| *return_code = ResultCodes::INVALID_CMDLINE_URL; |
| return false; |
| } |
| |
| return true; |
| } |