summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/browser.cc67
-rw-r--r--chrome/browser/browser.h30
-rw-r--r--chrome/browser/browser_browsertest.cc68
-rw-r--r--chrome/browser/browser_init.cc105
-rw-r--r--chrome/browser/browser_init.h48
-rw-r--r--chrome/browser/browser_list.cc10
-rw-r--r--chrome/browser/browser_prefs.cc2
-rw-r--r--chrome/browser/profile.cc3
-rw-r--r--chrome/browser/profile.h2
-rw-r--r--chrome/browser/tab_contents/tab_contents.cc13
-rw-r--r--chrome/browser/tab_contents/tab_contents.h6
-rw-r--r--chrome/browser/tabs/pinned_tab_codec.cc134
-rw-r--r--chrome/browser/tabs/pinned_tab_codec.h44
-rw-r--r--chrome/browser/tabs/pinned_tab_codec_unittest.cc63
-rw-r--r--chrome/browser/tabs/pinned_tab_service.cc50
-rw-r--r--chrome/browser/tabs/pinned_tab_service.h41
-rwxr-xr-xchrome/chrome_browser.gypi4
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/notification_type.h14
-rw-r--r--chrome/common/pref_names.cc2
-rw-r--r--chrome/common/pref_names.h1
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[];