diff options
-rw-r--r-- | chrome/browser/browser.cc | 67 | ||||
-rw-r--r-- | chrome/browser/browser.h | 30 | ||||
-rw-r--r-- | chrome/browser/browser_browsertest.cc | 68 | ||||
-rw-r--r-- | chrome/browser/browser_init.cc | 105 | ||||
-rw-r--r-- | chrome/browser/browser_init.h | 48 | ||||
-rw-r--r-- | chrome/browser/browser_list.cc | 10 | ||||
-rw-r--r-- | chrome/browser/browser_prefs.cc | 2 | ||||
-rw-r--r-- | chrome/browser/profile.cc | 3 | ||||
-rw-r--r-- | chrome/browser/profile.h | 2 | ||||
-rw-r--r-- | chrome/browser/tab_contents/tab_contents.cc | 13 | ||||
-rw-r--r-- | chrome/browser/tab_contents/tab_contents.h | 6 | ||||
-rw-r--r-- | chrome/browser/tabs/pinned_tab_codec.cc | 134 | ||||
-rw-r--r-- | chrome/browser/tabs/pinned_tab_codec.h | 44 | ||||
-rw-r--r-- | chrome/browser/tabs/pinned_tab_codec_unittest.cc | 63 | ||||
-rw-r--r-- | chrome/browser/tabs/pinned_tab_service.cc | 50 | ||||
-rw-r--r-- | chrome/browser/tabs/pinned_tab_service.h | 41 | ||||
-rwxr-xr-x | chrome/chrome_browser.gypi | 4 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | chrome/common/notification_type.h | 14 | ||||
-rw-r--r-- | chrome/common/pref_names.cc | 2 | ||||
-rw-r--r-- | chrome/common/pref_names.h | 1 |
21 files changed, 629 insertions, 79 deletions
diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc index 3dc499b..61de33c 100644 --- a/chrome/browser/browser.cc +++ b/chrome/browser/browser.cc @@ -638,11 +638,15 @@ void Browser::OnWindowClosing() { if (!ShouldCloseWindow()) return; + bool exiting = false; + #if defined(OS_WIN) || defined(OS_LINUX) // We don't want to do this on Mac since closing all windows isn't a sign // that the app is shutting down. - if (BrowserList::size() == 1) + if (BrowserList::size() == 1) { browser_shutdown::OnShutdownStarting(browser_shutdown::WINDOW_CLOSE); + exiting = true; + } #endif // Don't use HasSessionService here, we want to force creation of the @@ -655,6 +659,12 @@ void Browser::OnWindowClosing() { if (tab_restore_service) tab_restore_service->BrowserClosing(this); + // TODO(sky): convert session/tab restore to use notification. + NotificationService::current()->Notify( + NotificationType::BROWSER_CLOSING, + Source<Browser>(this), + Details<bool>(&exiting)); + CloseAllTabs(); } @@ -684,6 +694,22 @@ TabContents* Browser::AddTabWithURL( const GURL& url, const GURL& referrer, PageTransition::Type transition, bool foreground, int index, bool force_index, SiteInstance* instance) { + int add_types = 0; + if (force_index) + add_types |= ADD_FORCE_INDEX; + if (foreground) + add_types |= ADD_SELECTED; + return AddTabWithURL(url, referrer, transition, index, add_types, instance, + std::string()); +} + +TabContents* Browser::AddTabWithURL(const GURL& url, + const GURL& referrer, + PageTransition::Type transition, + int index, + int add_types, + SiteInstance* instance, + const std::string& app_extension_id) { TabContents* contents = NULL; if (type_ == TYPE_NORMAL || tabstrip_model()->empty()) { GURL url_to_load = url; @@ -691,18 +717,29 @@ TabContents* Browser::AddTabWithURL( url_to_load = GetHomePage(); contents = CreateTabContentsForURL(url_to_load, referrer, profile_, transition, false, instance); - tabstrip_model_.AddTabContents(contents, index, force_index, - transition, foreground); + contents->SetAppExtensionById(app_extension_id); + // TODO(sky): TabStripModel::AddTabContents should take add_types directly. + tabstrip_model_.AddTabContents(contents, index, + (add_types & ADD_FORCE_INDEX) != 0, + transition, + (add_types & ADD_SELECTED) != 0); + tabstrip_model_.SetTabPinned( + tabstrip_model_.GetIndexOfTabContents(contents), + (add_types & ADD_PINNED) != 0); + // By default, content believes it is not hidden. When adding contents // in the background, tell it that it's hidden. - if (!foreground) + if ((add_types & ADD_SELECTED) == 0) { + // TODO(sky): see if this is really needed. I suspect not as + // TabStripModel::AddTabContents invokes HideContents if not foreground. contents->WasHidden(); + } } else { // We're in an app window or a popup window. Find an existing browser to // open this URL in, creating one if none exists. Browser* b = GetOrCreateTabbedBrowser(profile_); - contents = b->AddTabWithURL(url, referrer, transition, foreground, index, - force_index, instance); + contents = b->AddTabWithURL(url, referrer, transition, index, add_types, + instance, app_extension_id); b->window()->Show(); } return contents; @@ -748,7 +785,7 @@ TabContents* Browser::AddRestoredTab( bool from_last_session) { TabContents* new_tab = new TabContents(profile(), NULL, MSG_ROUTING_NONE, tabstrip_model_.GetSelectedTabContents()); - SetAppExtensionById(new_tab, app_extension_id); + new_tab->SetAppExtensionById(app_extension_id); new_tab->controller().RestoreFromState(navigations, selected_navigation, from_last_session); @@ -774,7 +811,7 @@ void Browser::ReplaceRestoredTab( const std::string& app_extension_id) { TabContents* replacement = new TabContents(profile(), NULL, MSG_ROUTING_NONE, tabstrip_model_.GetSelectedTabContents()); - SetAppExtensionById(replacement, app_extension_id); + replacement->SetAppExtensionById(app_extension_id); replacement->controller().RestoreFromState(navigations, selected_navigation, from_last_session); @@ -3402,17 +3439,3 @@ bool Browser::RunUnloadEventsHelper(TabContents* contents) { } return false; } - -void Browser::SetAppExtensionById(TabContents* contents, - const std::string& app_extension_id) { - if (app_extension_id.empty()) - return; - - ExtensionsService* extension_service = profile()->GetExtensionsService(); - if (extension_service && extension_service->is_ready()) { - Extension* extension = - extension_service->GetExtensionById(app_extension_id, false); - if (extension) - contents->SetAppExtension(extension); - } -} diff --git a/chrome/browser/browser.h b/chrome/browser/browser.h index 37e62505..51470f7 100644 --- a/chrome/browser/browser.h +++ b/chrome/browser/browser.h @@ -91,6 +91,18 @@ class Browser : public TabStripModelDelegate, MAXIMIZED_STATE_UNMAXIMIZED }; + // Constants passed to AddTabWithURL. + enum AddTabTypes { + // The tab should be selected. + ADD_SELECTED = 1 << 0, + + // The tab should be pinned. + ADD_PINNED = 1 << 1, + + // See TabStripModel::AddTabContents for details. + ADD_FORCE_INDEX = 1 << 2, + }; + // Constructors, Creation, Showing ////////////////////////////////////////// // Creates a new browser of the given |type| and for the given |profile|. The @@ -283,6 +295,7 @@ class Browser : public TabStripModelDelegate, // will be used to render the tab. |force_index| is passed through to // TabStripModel::AddTabContents and its meaning is documented with its // declaration. + // TODO(sky): nuke this and convert callers to new AddTablWithURL variant. TabContents* AddTabWithURL(const GURL& url, const GURL& referrer, PageTransition::Type transition, @@ -291,6 +304,18 @@ class Browser : public TabStripModelDelegate, bool force_index, SiteInstance* instance); + // Adds a new tab at the specified index. |add_types| is a bitmask of the + // values defined by AddTabTypes; see AddTabTypes for details. If |instance| + // is not null, its process will be used to render the tab. If + // |app_extension_id| is non-empty the new tab is an app tab. + TabContents* AddTabWithURL(const GURL& url, + const GURL& referrer, + PageTransition::Type transition, + int index, + int add_types, + SiteInstance* instance, + const std::string& app_extension_id); + // Add a new tab, given a TabContents. A TabContents appropriate to // display the last committed entry is created and returned. TabContents* AddTab(TabContents* tab_contents, PageTransition::Type type); @@ -793,11 +818,6 @@ class Browser : public TabStripModelDelegate, // done only once per application name / per session. static void RegisterAppPrefs(const std::wstring& app_name); - // If |app_extension_id| is not empty this sets the application extension of - // |contents| to the extension whose id is |app_extension_id|. - void SetAppExtensionById(TabContents* contents, - const std::string& app_extension_id); - // Shared code between Reload() and ReloadAll(). void ReloadInternal(bool ignore_cache); diff --git a/chrome/browser/browser_browsertest.cc b/chrome/browser/browser_browsertest.cc index f789b7d..b18095f 100644 --- a/chrome/browser/browser_browsertest.cc +++ b/chrome/browser/browser_browsertest.cc @@ -9,6 +9,8 @@ #include "chrome/app/chrome_dll_resource.h" #include "chrome/browser/app_modal_dialog.h" #include "chrome/browser/browser.h" +#include "chrome/browser/browser_init.h" +#include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/defaults.h" #include "chrome/browser/extensions/extension_browsertest.h" @@ -17,6 +19,7 @@ #include "chrome/browser/profile.h" #include "chrome/browser/renderer_host/render_process_host.h" #include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/tabs/pinned_tab_codec.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" @@ -117,7 +120,6 @@ class BrowserTest : public ExtensionBrowserTest { EXPECT_TRUE(model->GetTabContentsAt(0) != app_contents); } - protected: virtual void SetUpCommandLine(CommandLine* command_line) { ExtensionBrowserTest::SetUpCommandLine(command_line); @@ -457,3 +459,67 @@ IN_PROC_BROWSER_TEST_F(BrowserTest, PageLanguageDetection) { EXPECT_EQ("fr", lang); EXPECT_EQ("fr", current_tab->language_state().original_language()); } + +// Makes sure pinned tabs are restored correctly on start. +IN_PROC_BROWSER_TEST_F(BrowserTest, RestorePinnedTabs) { + HTTPTestServer* server = StartHTTPServer(); + ASSERT_TRUE(server); + + // Add an pinned app tab. + host_resolver()->AddRule("www.example.com", "127.0.0.1"); + GURL url(server->TestServerPage("empty.html")); + TabStripModel* model = browser()->tabstrip_model(); + ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("app/"))); + Extension* app_extension = GetExtension(); + ui_test_utils::NavigateToURL(browser(), url); + TabContents* app_contents = new TabContents(browser()->profile(), NULL, + MSG_ROUTING_NONE, NULL); + app_contents->SetAppExtension(app_extension); + model->AddTabContents(app_contents, 0, false, 0, false); + model->SetTabPinned(0, true); + ui_test_utils::NavigateToURL(browser(), url); + + // Add a non pinned tab. + browser()->NewTab(); + + // Add a pinned non-app tab. + browser()->NewTab(); + ui_test_utils::NavigateToURL(browser(), GURL("about:blank")); + model->SetTabPinned(2, true); + + // Write out the pinned tabs. + PinnedTabCodec::WritePinnedTabs(browser()->profile()); + + // Simulate launching again. + CommandLine dummy(CommandLine::ARGUMENTS_ONLY); + BrowserInit::LaunchWithProfile launch(std::wstring(), dummy); + launch.profile_ = browser()->profile(); + launch.OpenStartupURLs(std::vector<GURL>()); + + // The launch should have created a new browser. + ASSERT_EQ(2u, BrowserList::GetBrowserCount(browser()->profile())); + + // Find the new browser. + Browser* new_browser = NULL; + for (BrowserList::const_iterator i = BrowserList::begin(); + i != BrowserList::end() && !new_browser; ++i) { + if (*i != browser()) + new_browser = *i; + } + ASSERT_TRUE(new_browser); + ASSERT_TRUE(new_browser != browser()); + + // We should get back an additional tab for the app. + ASSERT_EQ(2, new_browser->tab_count()); + + // Make sure the state matches. + TabStripModel* new_model = new_browser->tabstrip_model(); + EXPECT_TRUE(new_model->IsAppTab(0)); + EXPECT_FALSE(new_model->IsAppTab(1)); + + EXPECT_TRUE(new_model->IsTabPinned(0)); + EXPECT_TRUE(new_model->IsTabPinned(1)); + + EXPECT_TRUE(new_model->GetTabContentsAt(0)->app_extension() == + app_extension); +} diff --git a/chrome/browser/browser_init.cc b/chrome/browser/browser_init.cc index a983a76..72a134b 100644 --- a/chrome/browser/browser_init.cc +++ b/chrome/browser/browser_init.cc @@ -28,6 +28,7 @@ #include "chrome/browser/session_startup_pref.h" #include "chrome/browser/sessions/session_restore.h" #include "chrome/browser/shell_integration.h" +#include "chrome/browser/tabs/pinned_tab_codec.h" #include "chrome/browser/tab_contents/infobar_delegate.h" #include "chrome/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/tab_contents.h" @@ -334,6 +335,16 @@ void ShowPackExtensionMessage(const std::wstring caption, #endif } +void UrlsToTabs(const std::vector<GURL>& urls, + std::vector<BrowserInit::LaunchWithProfile::Tab>* tabs) { + for (size_t i = 0; i < urls.size(); ++i) { + BrowserInit::LaunchWithProfile::Tab tab; + tab.is_pinned = false; + tab.url = urls[i]; + tabs->push_back(tab); + } +} + } // namespace // static @@ -465,9 +476,7 @@ bool BrowserInit::LaunchWithProfile::Launch(Profile* 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)) { + if (!process_startup || !OpenStartupURLs(urls_to_open)) { // Add the home page and any special first run URLs. Browser* browser = NULL; if (urls_to_open.empty()) @@ -590,11 +599,9 @@ bool BrowserInit::LaunchWithProfile::OpenApplicationWindow(Profile* profile) { } bool BrowserInit::LaunchWithProfile::OpenStartupURLs( - bool is_process_startup, const std::vector<GURL>& urls_to_open) { SessionStartupPref pref = GetSessionStartupPref(command_line_, profile_); - if (is_process_startup && - command_line_.HasSwitch(switches::kTestingChannelID) && + if (command_line_.HasSwitch(switches::kTestingChannelID) && !command_line_.HasSwitch(switches::kRestoreLastSession) && browser_defaults::kDefaultSessionStartupType != SessionStartupPref::DEFAULT) { @@ -603,48 +610,52 @@ bool BrowserInit::LaunchWithProfile::OpenStartupURLs( // we explicitly ignore it during testing. return false; } - 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()) { - // Open a New Tab page. - std::vector<GURL> urls; - urls.push_back(GURL(chrome::kChromeUINewTabURL)); - OpenURLsInBrowser(NULL, is_process_startup, urls); - return true; - } - OpenURLsInBrowser(NULL, is_process_startup, pref.urls); - return true; - } + if (pref.type == SessionStartupPref::LAST) { + 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; + } - default: - return false; + std::vector<Tab> tabs = PinnedTabCodec::ReadPinnedTabs(profile_); + + if (!urls_to_open.empty()) { + // If urls were specified on the command line, use them. + UrlsToTabs(urls_to_open, &tabs); + } else if (pref.type == SessionStartupPref::URLS && !pref.urls.empty()) { + // Only use the set of urls specified in preferences if nothing was + // specified on the command line. + UrlsToTabs(pref.urls, &tabs); } + + if (tabs.empty()) + return false; + + OpenTabsInBrowser(NULL, true, tabs); + return true; } Browser* BrowserInit::LaunchWithProfile::OpenURLsInBrowser( Browser* browser, bool process_startup, const std::vector<GURL>& urls) { - DCHECK(!urls.empty()); + std::vector<Tab> tabs; + UrlsToTabs(urls, &tabs); + return OpenTabsInBrowser(browser, process_startup, tabs); +} + +Browser* BrowserInit::LaunchWithProfile::OpenTabsInBrowser( + Browser* browser, + bool process_startup, + const std::vector<Tab>& 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. @@ -660,17 +671,27 @@ Browser* BrowserInit::LaunchWithProfile::OpenURLsInBrowser( browser->ToggleFullscreenMode(); #endif - for (size_t i = 0; i < urls.size(); ++i) { + bool first_tab = true; + 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. - if (!process_startup && !URLRequest::IsHandledURL(urls[i])) + if (!process_startup && !URLRequest::IsHandledURL(tabs[i].url)) continue; + + int add_types = first_tab ? Browser::ADD_SELECTED : 0; + if (tabs[i].is_pinned) + add_types |= Browser::ADD_PINNED; + TabContents* tab = browser->AddTabWithURL( - urls[i], GURL(), PageTransition::START_PAGE, (i == 0), -1, false, NULL); - if (profile_ && i == 0 && process_startup) + tabs[i].url, GURL(), PageTransition::START_PAGE, -1, add_types, NULL, + tabs[i].app_id); + + if (profile_ && first_tab && process_startup) AddCrashedInfoBarIfNecessary(tab); + + first_tab = false; } browser->window()->Show(); // TODO(jcampan): http://crbug.com/8123 we should not need to set the initial diff --git a/chrome/browser/browser_init.h b/chrome/browser/browser_init.h index 1f736b5..562173b 100644 --- a/chrome/browser/browser_init.h +++ b/chrome/browser/browser_init.h @@ -10,6 +10,7 @@ #include "base/basictypes.h" #include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest_prod.h" class Browser; class CommandLine; @@ -84,6 +85,24 @@ class BrowserInit { class LaunchWithProfile { public: + // Used by OpenTabsInBrowser. + struct Tab { + Tab() : is_app(false), is_pinned(true) {} + + // The url to load. + GURL url; + + // If true, the tab corresponds to an app an |app_id| gives the id of the + // app. + bool is_app; + + // True if the is tab pinned. + bool is_pinned; + + // Id of the app. + std::string app_id; + }; + // There are two ctors. The first one implies a NULL browser_init object // and thus no access to distribution-specific first-run behaviors. The // second one is always called when the browser starts even if it is not @@ -101,15 +120,24 @@ class BrowserInit { // already running and the user wants to launch another instance. bool Launch(Profile* profile, bool process_startup); - // Opens the list of urls. If browser is non-null and a tabbed browser, the - // URLs are opened in it. Otherwise a new tabbed browser is created and the - // URLs are added to it. The browser the tabs are added to is returned, - // which is either |browser| or the newly created browser. + // Convenience for OpenTabsInBrowser that converts |urls| into a set of + // Tabs. Browser* OpenURLsInBrowser(Browser* browser, bool process_startup, const std::vector<GURL>& urls); + // Creates a tab for each of the Tabs in |tabs|. If browser is non-null + // and a tabbed browser, the tabs are added to it. Otherwise a new tabbed + // browser is created and the tabs are added to it. The browser the tabs + // are added to is returned, which is either |browser| or the newly created + // browser. + Browser* OpenTabsInBrowser(Browser* browser, + bool process_startup, + const std::vector<Tab>& tabs); + private: + FRIEND_TEST(BrowserTest, RestorePinnedTabs); + // If the process was launched with the web application command line flags, // e.g. --app=http://www.google.com/ or --app_id=... return true. // In this case |app_url| or |app_id| are populated if they're non-null. @@ -124,12 +152,14 @@ class BrowserInit { // . If the user's startup pref is to restore the last session (or the // command line flag is present to force using last session), it is // restored, and true is returned. - // . If the user's startup pref is to launch a specific set of URLs, and - // urls_to_open is empty, the user specified set of URLs is openned. + // . Attempts to restore any pinned tabs from last run of chrome and: + // . If urls_to_open is non-empty, they are opened and true is returned. + // . If the user's startup pref is to launch a specific set of URLs they + // are opened. // - // Otherwise false is returned. - bool OpenStartupURLs(bool is_process_startup, - const std::vector<GURL>& urls_to_open); + // Otherwise false is returned, which indicates the caller must create a + // new browser. + bool OpenStartupURLs(const std::vector<GURL>& urls_to_open); // If the last session didn't exit cleanly and tab is a web contents tab, // an infobar is added allowing the user to restore the last session. diff --git a/chrome/browser/browser_list.cc b/chrome/browser/browser_list.cc index 63712d7..1d97388 100644 --- a/chrome/browser/browser_list.cc +++ b/chrome/browser/browser_list.cc @@ -235,6 +235,11 @@ void BrowserList::CloseAllBrowsers(bool use_post) { // static void BrowserList::CloseAllBrowsersAndExit() { + NotificationService::current()->Notify( + NotificationType::APP_EXITING, + NotificationService::AllSources(), + NotificationService::NoDetails()); + #if !defined(OS_MACOSX) // On most platforms, closing all windows causes the application to exit. CloseAllBrowsers(true); @@ -256,6 +261,11 @@ void BrowserList::WindowsSessionEnding() { browser_shutdown::OnShutdownStarting(browser_shutdown::END_SESSION); + NotificationService::current()->Notify( + NotificationType::APP_EXITING, + NotificationService::AllSources(), + NotificationService::NoDetails()); + // Write important data first. g_browser_process->EndSession(); diff --git a/chrome/browser/browser_prefs.cc b/chrome/browser/browser_prefs.cc index a973d43..85eac53 100644 --- a/chrome/browser/browser_prefs.cc +++ b/chrome/browser/browser_prefs.cc @@ -33,6 +33,7 @@ #include "chrome/browser/search_engines/template_url_prepopulate_data.h" #include "chrome/browser/session_startup_pref.h" #include "chrome/browser/ssl/ssl_manager.h" +#include "chrome/browser/tabs/pinned_tab_codec.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/task_manager.h" @@ -99,6 +100,7 @@ void RegisterUserPrefs(PrefService* user_prefs) { HostZoomMap::RegisterUserPrefs(user_prefs); DevToolsManager::RegisterUserPrefs(user_prefs); Blacklist::RegisterUserPrefs(user_prefs); + PinnedTabCodec::RegisterUserPrefs(user_prefs); ExtensionPrefs::RegisterUserPrefs(user_prefs); #if defined(TOOLKIT_VIEWS) BrowserActionsContainer::RegisterUserPrefs(user_prefs); diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc index dc5eb19..01d4824 100644 --- a/chrome/browser/profile.cc +++ b/chrome/browser/profile.cc @@ -49,6 +49,7 @@ #include "chrome/browser/ssl/ssl_host_state.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_factory_impl.h" +#include "chrome/browser/tabs/pinned_tab_service.h" #include "chrome/browser/thumbnail_store.h" #include "chrome/browser/user_style_sheet_watcher.h" #include "chrome/browser/visitedlink_master.h" @@ -646,6 +647,8 @@ ProfileImpl::ProfileImpl(const FilePath& path) #if defined(OS_CHROMEOS) chromeos_preferences_.Init(prefs); #endif + + pinned_tab_service_.reset(new PinnedTabService(this)); } void ProfileImpl::InitExtensions() { diff --git a/chrome/browser/profile.h b/chrome/browser/profile.h index 5787716..de6ed47 100644 --- a/chrome/browser/profile.h +++ b/chrome/browser/profile.h @@ -49,6 +49,7 @@ class NavigationController; class NTPResourceCache; class PasswordStore; class PersonalDataManager; +class PinnedTabService; class PrefService; class ProfileSyncService; class ProfileSyncFactory; @@ -570,6 +571,7 @@ class ProfileImpl : public Profile, scoped_refptr<WebKitContext> webkit_context_; scoped_ptr<DesktopNotificationService> desktop_notification_service_; scoped_ptr<PersonalDataManager> personal_data_manager_; + scoped_ptr<PinnedTabService> pinned_tab_service_; bool history_service_created_; bool favicon_service_created_; bool created_web_data_service_; diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc index 4645315..97e49b3 100644 --- a/chrome/browser/tab_contents/tab_contents.cc +++ b/chrome/browser/tab_contents/tab_contents.cc @@ -482,6 +482,19 @@ void TabContents::SetAppExtension(Extension* extension) { NotificationService::NoDetails()); } +void TabContents::SetAppExtensionById(const std::string& app_extension_id) { + if (app_extension_id.empty()) + return; + + ExtensionsService* extension_service = profile()->GetExtensionsService(); + if (extension_service && extension_service->is_ready()) { + Extension* extension = + extension_service->GetExtensionById(app_extension_id, false); + if (extension) + SetAppExtension(extension); + } +} + const GURL& TabContents::GetURL() const { // We may not have a navigation entry yet NavigationEntry* entry = controller_.GetActiveEntry(); diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h index 3a63ec8..15f1a09 100644 --- a/chrome/browser/tab_contents/tab_contents.h +++ b/chrome/browser/tab_contents/tab_contents.h @@ -186,6 +186,12 @@ class TabContents : public PageNavigator, // TODO(sky): resolve if this is the right way to identify an app tab. If it // is, than this should be passed in the constructor. void SetAppExtension(Extension* extension); + + // Convenience for setting the app extension by id. This does nothing if + // |app_extension_id| is empty, or an extension can't be found given the + // specified id. + void SetAppExtensionById(const std::string& app_extension_id); + Extension* app_extension() const { return app_extension_; } bool is_app() const { return app_extension_ != NULL; } diff --git a/chrome/browser/tabs/pinned_tab_codec.cc b/chrome/browser/tabs/pinned_tab_codec.cc new file mode 100644 index 0000000..51b8fd2 --- /dev/null +++ b/chrome/browser/tabs/pinned_tab_codec.cc @@ -0,0 +1,134 @@ +// 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/tabs/pinned_tab_codec.h" + +#include "base/values.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/pref_service.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tabs/tab_strip_model.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/page_transition_types.h" +#include "chrome/common/pref_names.h" +#include "ipc/ipc_message.h" + +typedef BrowserInit::LaunchWithProfile::Tab Tab; + +// Key used in dictionaries for the app id. +static const wchar_t kAppID[] = L"app_id"; + +// Key used in dictionaries for the url. +static const wchar_t kURL[] = L"url"; + +// Returns true if |browser| has any pinned tabs. +static bool HasPinnedTabs(Browser* browser) { + TabStripModel* tab_model = browser->tabstrip_model(); + for (int i = 0; i < tab_model->count(); ++i) { + if (tab_model->IsTabPinned(i)) + return true; + } + return false; +} + +// Adds a DictionaryValue to |values| representing the pinned tab at the +// specified index. +static void EncodePinnedTab(TabStripModel* model, + int index, + ListValue* values) { + scoped_ptr<DictionaryValue> value(new DictionaryValue()); + + TabContents* tab_contents = model->GetTabContentsAt(index); + if (model->IsAppTab(index)) { + Extension* extension = tab_contents->app_extension(); + DCHECK(extension); + value->SetString(kAppID, extension->id()); + // For apps we use the launch url. We do this for two reasons: + // . the user is effectively restarting the app, so that returning them to + // the app's launch page seems closest to what they expect. + // . we do the same when restoring a phantom tab. + value->SetString(kURL, extension->app_launch_url().spec()); + values->Append(value.release()); + } else { + NavigationEntry* entry = tab_contents->controller().GetActiveEntry(); + if (!entry && tab_contents->controller().entry_count()) + entry = tab_contents->controller().GetEntryAtIndex(0); + if (entry) { + value->SetString(kURL, entry->url().spec()); + values->Append(value.release()); + } + } +} + +// Invokes EncodePinnedTab for each pinned tab in browser. +static void EncodePinnedTabs(Browser* browser, ListValue* values) { + TabStripModel* tab_model = browser->tabstrip_model(); + for (int i = 0; i < tab_model->count() && tab_model->IsTabPinned(i); ++i) + EncodePinnedTab(tab_model, i, values); +} + +// Decodes the previously written values in |value| to |tab|, returning true +// on success. +static bool DecodeTab(const DictionaryValue& value, Tab* tab) { + tab->is_app = false; + + std::string url_string; + if (!value.GetString(kURL, &url_string)) + return false; + tab->url = GURL(url_string); + + if (value.GetString(kAppID, &(tab->app_id))) + tab->is_app = true; + + return true; +} + +// static +void PinnedTabCodec::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterListPref(prefs::kPinnedTabs); +} + +// static +void PinnedTabCodec::WritePinnedTabs(Profile* profile) { + PrefService* prefs = profile->GetPrefs(); + if (!prefs) + return; + + ListValue values; + for (BrowserList::const_iterator i = BrowserList::begin(); + i != BrowserList::end(); ++i) { + Browser* browser = *i; + if (browser->type() == Browser::TYPE_NORMAL && + browser->profile() == profile && HasPinnedTabs(browser)) { + EncodePinnedTabs(browser, &values); + } + } + prefs->Set(prefs::kPinnedTabs, values); + prefs->ScheduleSavePersistentPrefs(); +} + +// static +std::vector<Tab> PinnedTabCodec::ReadPinnedTabs(Profile* profile) { + std::vector<Tab> results; + + PrefService* prefs = profile->GetPrefs(); + if (!prefs) + return results; + + const ListValue* pref_value = prefs->GetList(prefs::kPinnedTabs); + if (!pref_value) + return results; + + for (size_t i = 0, max = pref_value->GetSize(); i < max; ++i) { + DictionaryValue* values = NULL; + if (pref_value->GetDictionary(i, &values)) { + Tab tab; + if (DecodeTab(*values, &tab)) + results.push_back(tab); + } + } + return results; +} diff --git a/chrome/browser/tabs/pinned_tab_codec.h b/chrome/browser/tabs/pinned_tab_codec.h new file mode 100644 index 0000000..1b1aed3 --- /dev/null +++ b/chrome/browser/tabs/pinned_tab_codec.h @@ -0,0 +1,44 @@ +// 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. + +#ifndef CHROME_BROWSER_TABS_PINNED_TAB_CODEC_H_ +#define CHROME_BROWSER_TABS_PINNED_TAB_CODEC_H_ + +#include <string> +#include <vector> + +#include "chrome/browser/browser_init.h" +#include "googleurl/src/gurl.h" + +class Browser; +class PrefService; +class Profile; + +// PinnedTabCodec is used to read and write the set of pinned tabs to +// preferences. When Chrome exits the sets of pinned tabs are written to prefs. +// On startup if the user has not chosen to restore the last session the set of +// pinned tabs is opened. +// +// The entries are stored in preferences as a list of dictionaries, with each +// dictionary describing the entry. +class PinnedTabCodec { + public: + // Registers the preference used by this class. + static void RegisterUserPrefs(PrefService* prefs); + + // Resets the preferences state. + static void WritePinnedTabs(Profile* profile); + + // Reads and returns the set of pinned tabs to restore from preferences. + static std::vector<BrowserInit::LaunchWithProfile::Tab> ReadPinnedTabs( + Profile* profile); + + private: + PinnedTabCodec(); + ~PinnedTabCodec(); + + DISALLOW_COPY_AND_ASSIGN(PinnedTabCodec); +}; + +#endif // CHROME_BROWSER_TABS_PINNED_TAB_CODEC_H_ diff --git a/chrome/browser/tabs/pinned_tab_codec_unittest.cc b/chrome/browser/tabs/pinned_tab_codec_unittest.cc new file mode 100644 index 0000000..c9461eb --- /dev/null +++ b/chrome/browser/tabs/pinned_tab_codec_unittest.cc @@ -0,0 +1,63 @@ +// 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 <string> +#include <vector> + +#include "chrome/browser/tabs/pinned_tab_codec.h" +#include "chrome/test/browser_with_test_window_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +typedef BrowserInit::LaunchWithProfile::Tab Tab; + +typedef BrowserWithTestWindowTest PinnedTabCodecTest; + +namespace { + +std::string TabToString(const Tab& tab) { + return tab.url.spec() + ":" + (tab.is_app ? "app" : "") + ":" + + (tab.is_pinned ? "pinned" : "") + ":" + tab.app_id; +} + +std::string TabsToString(const std::vector<Tab>& values) { + std::string result; + for (size_t i = 0; i < values.size(); ++i) { + if (i != 0) + result += " "; + result += TabToString(values[i]); + } + return result; +} + +} // namespace + +// Make sure nothing is restored when the browser has no pinned tabs. +TEST_F(PinnedTabCodecTest, NoPinnedTabs) { + GURL url1("http://www.google.com"); + AddTab(browser(), url1); + + PinnedTabCodec::WritePinnedTabs(profile()); + + std::string result = TabsToString(PinnedTabCodec::ReadPinnedTabs(profile())); + EXPECT_EQ("", result); +} + +// Creates a browser with one pinned tab and one normal tab, does restore and +// makes sure we get back another pinned tab. +TEST_F(PinnedTabCodecTest, PinnedAndNonPinned) { + GURL url1("http://www.google.com"); + GURL url2("http://www.google.com/2"); + AddTab(browser(), url2); + + // AddTab inserts at index 0, so order after this is url1, url2. + AddTab(browser(), url1); + + browser()->tabstrip_model()->SetTabPinned(0, true); + + PinnedTabCodec::WritePinnedTabs(profile()); + + std::string result = TabsToString(PinnedTabCodec::ReadPinnedTabs(profile())); + EXPECT_EQ("http://www.google.com/::pinned:", result); +} + diff --git a/chrome/browser/tabs/pinned_tab_service.cc b/chrome/browser/tabs/pinned_tab_service.cc new file mode 100644 index 0000000..826ca65 --- /dev/null +++ b/chrome/browser/tabs/pinned_tab_service.cc @@ -0,0 +1,50 @@ +// 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/tabs/pinned_tab_service.h" + +#include "chrome/browser/browser.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/tabs/pinned_tab_codec.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_type.h" + +PinnedTabService::PinnedTabService(Profile* profile) + : profile_(profile), + got_exiting_(false) { + registrar_.Add(this, NotificationType::BROWSER_CLOSING, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::APP_EXITING, + NotificationService::AllSources()); +} + +void PinnedTabService::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (got_exiting_) + return; + + switch (type.value) { + case NotificationType::BROWSER_CLOSING: { + Browser* browser = Source<Browser>(source).ptr(); + if (browser->profile() == profile_ && *(Details<bool>(details)).ptr()) + GotExit(); + break; + } + + case NotificationType::APP_EXITING: { + GotExit(); + break; + } + + default: + NOTREACHED(); + } +} + +void PinnedTabService::GotExit() { + DCHECK(!got_exiting_); + got_exiting_ = true; + PinnedTabCodec::WritePinnedTabs(profile_); +} diff --git a/chrome/browser/tabs/pinned_tab_service.h b/chrome/browser/tabs/pinned_tab_service.h new file mode 100644 index 0000000..3437857 --- /dev/null +++ b/chrome/browser/tabs/pinned_tab_service.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef CHROME_BROWSER_TABS_PINNED_TAB_SERVICE_H_ +#define CHROME_BROWSER_TABS_PINNED_TAB_SERVICE_H_ + +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +class Browser; +class Profile; + +// PinnedTabService is responsible for updating preferences with the set of +// pinned tabs to restore at startup. PinnedTabService listens for the +// appropriate set of notifications to know it should update preferences. +class PinnedTabService : public NotificationObserver { + public: + explicit PinnedTabService(Profile* profile); + + private: + // Invoked when we're about to exit. + void GotExit(); + + // NotificationObserver. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + Profile* profile_; + + // If true we've seen an exit event (or the last browser is closing which + // triggers an exit) and can ignore all other events. + bool got_exiting_; + + NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(PinnedTabService); +}; + +#endif // CHROME_BROWSER_TABS_PINNED_TAB_SERVICE_H_ diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 0c399b2..3a4c82f 100755 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1984,6 +1984,10 @@ 'browser/tab_contents/web_drop_target_win.h', 'browser/tab_menu_model.cc', 'browser/tab_menu_model.h', + 'browser/tabs/pinned_tab_codec.cc', + 'browser/tabs/pinned_tab_codec.h', + 'browser/tabs/pinned_tab_service.cc', + 'browser/tabs/pinned_tab_service.h', 'browser/tabs/tab_strip_model.cc', 'browser/tabs/tab_strip_model.h', 'browser/tabs/tab_strip_model_order_controller.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index c5522a0..e1933dd 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -892,6 +892,7 @@ 'browser/tab_contents/thumbnail_generator_unittest.cc', 'browser/tab_contents/web_contents_unittest.cc', 'browser/tab_menu_model_unittest.cc', + 'browser/tabs/pinned_tab_codec_unittest.cc', 'browser/tabs/tab_strip_model_unittest.cc', 'browser/task_manager_unittest.cc', 'browser/translate/translate_manager_unittest.cc', diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h index b379f8a..a321e0b 100644 --- a/chrome/common/notification_type.h +++ b/chrome/common/notification_type.h @@ -191,6 +191,14 @@ class NotificationType { // containing the affected Browser. No details are expected. BROWSER_WINDOW_READY, + // This message is sent when a browser is closing. The source is a + // Source<Browser> containing the affected Browser. Details is a boolean + // that if true indicates that the application will be closed as a result of + // this browser window closure (i.e. this was the last opened browser + // window on win/linux). This is sent prior to BROWSER_CLOSED, and may be + // sent more than once for a particular browser. + BROWSER_CLOSING, + // This message is sent after a window has been closed. The source is a // Source<Browser> containing the affected Browser. Details is a boolean // that if true indicates that the application will be closed as a result of @@ -215,6 +223,12 @@ class NotificationType { APP_TERMINATING, #endif + // This is sent when the user has chosen to exit the app, but before any + // browsers have closed. This is only sent if the user chooses the exit menu + // item, not if Chrome exists by some other means (such as the user closing + // the last window). The source and details are unspecified. + APP_EXITING, + // Indicates that a top window has been closed. The source is the HWND // that was closed, no details are expected. WINDOW_CLOSED, diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index a926587..24f5f67c 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc @@ -349,6 +349,8 @@ const wchar_t kUseVerticalTabs[] = L"tabs.use_vertical_tabs"; // Boolean that is true when the translate feature is enabled. const wchar_t kEnableTranslate[] = L"translate.enabled"; +const wchar_t kPinnedTabs[] = L"pinned_tabs"; + // *************** LOCAL STATE *************** // These are attached to the machine/installation diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h index 240e401..1e0006f 100644 --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h @@ -132,6 +132,7 @@ extern const wchar_t kAutoFillDialogPlacement[]; extern const wchar_t kPrivacyFilterRules[]; extern const wchar_t kUseVerticalTabs[]; extern const wchar_t kEnableTranslate[]; +extern const wchar_t kPinnedTabs[]; // Local state extern const wchar_t kMetricsClientID[]; |