summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/browser.cc56
-rw-r--r--chrome/browser/browser.h20
-rw-r--r--chrome/browser/browser_browsertest.cc60
-rw-r--r--chrome/browser/cocoa/browser_window_controller.mm2
-rw-r--r--chrome/browser/defaults.cc6
-rw-r--r--chrome/browser/defaults.h3
-rw-r--r--chrome/browser/extensions/extension_browser_event_router.cc6
-rw-r--r--chrome/browser/extensions/extension_browser_event_router.h26
-rw-r--r--chrome/browser/gtk/browser_window_gtk.cc2
-rw-r--r--chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc4
-rw-r--r--chrome/browser/tab_contents/tab_contents.cc22
-rw-r--r--chrome/browser/tab_contents/tab_contents.h7
-rw-r--r--chrome/browser/tabs/tab_strip_model.cc146
-rw-r--r--chrome/browser/tabs/tab_strip_model.h72
-rw-r--r--chrome/browser/tabs/tab_strip_model_order_controller.cc16
-rw-r--r--chrome/browser/tabs/tab_strip_model_order_controller.h14
-rw-r--r--chrome/browser/views/frame/browser_view.cc2
-rw-r--r--chrome/browser/views/tabs/dragged_tab_controller.cc4
-rw-r--r--chrome/browser/views/tabs/dragged_tab_view.cc2
-rw-r--r--chrome/browser/views/tabs/tab_renderer.cc11
-rw-r--r--chrome/browser/views/tabs/tab_renderer.h3
-rw-r--r--chrome/browser/views/tabs/tab_strip.cc11
-rw-r--r--chrome/browser/views/tabs/tab_strip.h3
-rw-r--r--chrome/common/chrome_switches.cc3
-rw-r--r--chrome/common/chrome_switches.h1
25 files changed, 405 insertions, 97 deletions
diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc
index 92bf8eb..3259fd6 100644
--- a/chrome/browser/browser.cc
+++ b/chrome/browser/browser.cc
@@ -178,8 +178,8 @@ Browser::Browser(Type type, Profile* profile)
}
Browser::~Browser() {
- // The tab strip should be empty at this point.
- DCHECK(tabstrip_model_.empty());
+ // The tab strip should not have any significant tabs at this point.
+ DCHECK(!tabstrip_model_.HasNonPhantomTabs());
tabstrip_model_.RemoveObserver(this);
BrowserList::RemoveBrowser(this);
@@ -1854,7 +1854,7 @@ void Browser::TabInsertedAt(TabContents* contents,
contents->set_delegate(this);
contents->controller().SetWindowID(session_id());
- SyncHistoryWithTabs(tabstrip_model_.GetIndexOfTabContents(contents));
+ SyncHistoryWithTabs(index);
// Make sure the loading state is updated correctly, otherwise the throbber
// won't start if the page is loading.
@@ -1877,20 +1877,7 @@ void Browser::TabClosingAt(TabContents* contents, int index) {
}
void Browser::TabDetachedAt(TabContents* contents, int index) {
- // Save what the user's currently typing.
- window_->GetLocationBar()->SaveStateToContents(contents);
-
- contents->set_delegate(NULL);
- if (!tabstrip_model_.closing_all())
- SyncHistoryWithTabs(0);
-
- RemoveScheduledUpdatesFor(contents);
-
- if (find_bar_controller_.get() && index == tabstrip_model_.selected_index())
- find_bar_controller_->ChangeTabContents(NULL);
-
- registrar_.Remove(this, NotificationType::TAB_CONTENTS_DISCONNECTED,
- Source<TabContents>(contents));
+ TabDetachedAtImpl(contents, index, DETACH_TYPE_DETACH);
}
void Browser::TabDeselectedAt(TabContents* contents, int index) {
@@ -1952,6 +1939,21 @@ void Browser::TabMoved(TabContents* contents,
SyncHistoryWithTabs(std::min(from_index, to_index));
}
+void Browser::TabReplacedAt(TabContents* old_contents,
+ TabContents* new_contents, int index) {
+ TabDetachedAtImpl(old_contents, index, DETACH_TYPE_REPLACE);
+ TabInsertedAt(new_contents, index,
+ (index == tabstrip_model_.selected_index()));
+
+ int entry_count = new_contents->controller().entry_count();
+ if (entry_count > 0) {
+ // Send out notification so that observers are updated appropriately.
+ new_contents->controller().NotifyEntryChanged(
+ new_contents->controller().GetEntryAtIndex(entry_count - 1),
+ entry_count - 1);
+ }
+}
+
void Browser::TabPinnedStateChanged(TabContents* contents, int index) {
if (!profile()->HasSessionService())
return;
@@ -3178,6 +3180,26 @@ void Browser::CloseFrame() {
window_->Close();
}
+void Browser::TabDetachedAtImpl(TabContents* contents, int index,
+ DetachType type) {
+ if (type == DETACH_TYPE_DETACH) {
+ // Save what the user's currently typed.
+ window_->GetLocationBar()->SaveStateToContents(contents);
+
+ if (!tabstrip_model_.closing_all())
+ SyncHistoryWithTabs(0);
+ }
+
+ contents->set_delegate(NULL);
+ RemoveScheduledUpdatesFor(contents);
+
+ if (find_bar_controller_.get() && index == tabstrip_model_.selected_index())
+ find_bar_controller_->ChangeTabContents(NULL);
+
+ registrar_.Remove(this, NotificationType::TAB_CONTENTS_DISCONNECTED,
+ Source<TabContents>(contents));
+}
+
// static
void Browser::RegisterAppPrefs(const std::wstring& app_name) {
// A set of apps that we've already started.
diff --git a/chrome/browser/browser.h b/chrome/browser/browser.h
index 50fd49d..e4effcb 100644
--- a/chrome/browser/browser.h
+++ b/chrome/browser/browser.h
@@ -500,6 +500,21 @@ class Browser : public TabStripModelDelegate,
virtual void ExecuteCommand(int id);
private:
+ FRIEND_TEST(BrowserTest, NoTabsInPopups);
+
+ // Used to describe why a tab is being detached. This is used by
+ // TabDetachedAtImpl.
+ enum DetachType {
+ // Result of TabDetachedAt.
+ DETACH_TYPE_DETACH,
+
+ // Result of TabReplacedAt.
+ DETACH_TYPE_REPLACE,
+
+ // Result of the tab strip not having any significant tabs.
+ DETACH_TYPE_EMPTY
+ };
+
// Overridden from TabStripModelDelegate:
virtual TabContents* AddBlankTab(bool foreground);
virtual TabContents* AddBlankTabAt(int index, bool foreground);
@@ -542,6 +557,9 @@ class Browser : public TabStripModelDelegate,
int from_index,
int to_index,
bool pinned_state_changed);
+ virtual void TabReplacedAt(TabContents* old_contents,
+ TabContents* new_contents,
+ int index);
virtual void TabPinnedStateChanged(TabContents* contents, int index);
virtual void TabStripEmpty();
@@ -730,7 +748,7 @@ class Browser : public TabStripModelDelegate,
// after a return to the message loop.
void CloseFrame();
- FRIEND_TEST(BrowserTest, NoTabsInPopups);
+ void TabDetachedAtImpl(TabContents* contents, int index, DetachType type);
// Create a preference dictionary for the provided application name. This is
// done only once per application name / per session.
diff --git a/chrome/browser/browser_browsertest.cc b/chrome/browser/browser_browsertest.cc
index 35a04da5..7146210 100644
--- a/chrome/browser/browser_browsertest.cc
+++ b/chrome/browser/browser_browsertest.cc
@@ -10,6 +10,7 @@
#include "chrome/browser/app_modal_dialog.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_process.h"
+#include "chrome/browser/defaults.h"
#include "chrome/browser/renderer_host/render_process_host.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/url_constants.h"
@@ -58,6 +59,35 @@ int CountRenderProcessHosts() {
} // namespace
class BrowserTest : public InProcessBrowserTest {
+ public:
+ // Used by phantom tab tests. Creates two tabs, pins the first and makes it
+ // a phantom tab (by closing it).
+ void PhantomTabTest() {
+ static const wchar_t kDocRoot[] = L"chrome/test/data";
+ scoped_refptr<HTTPTestServer> server(
+ HTTPTestServer::CreateServer(kDocRoot, NULL));
+ ASSERT_TRUE(NULL != server.get());
+ GURL url(server->TestServerPage("empty.html"));
+ TabStripModel* model = browser()->tabstrip_model();
+
+ ui_test_utils::NavigateToURL(browser(), url);
+ model->SetTabPinned(0, true);
+
+ browser()->AddTabWithURL(GURL("about:blank"), GURL(), PageTransition::TYPED,
+ true, 1, false, NULL);
+ ui_test_utils::NavigateToURL(browser(), url);
+
+ // Close the first, which should make it a phantom.
+ TabContents* initial_contents = model->GetTabContentsAt(0);
+ model->CloseTabContentsAt(0);
+ // There should still be two tabs.
+ ASSERT_EQ(2, browser()->tab_count());
+ // The first tab should be a phantom.
+ EXPECT_TRUE(model->IsPhantomTab(0));
+ // And the tab contents of the first tab should have changed.
+ EXPECT_TRUE(model->GetTabContentsAt(0) != initial_contents);
+ }
+
protected:
// In RTL locales wrap the page title with RTL embedding characters so that it
// matches the value returned by GetWindowTitle().
@@ -293,6 +323,36 @@ IN_PROC_BROWSER_TEST_F(BrowserTest, FaviconOfOnloadRedirectToAnchorPage) {
EXPECT_EQ(expected_favicon_url.spec(), entry->favicon().url().spec());
}
+// TODO(sky): enable these once phantom tabs aren't behind a flag.
+/*
+IN_PROC_BROWSER_TEST_F(BrowserTest, PhantomTab) {
+ if (!browser_defaults::kPinnedTabsActLikeApps)
+ return;
+
+ PhantomTabTest();
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserTest, RevivePhantomTab) {
+ if (!browser_defaults::kPinnedTabsActLikeApps)
+ return;
+
+ PhantomTabTest();
+
+ if (HasFatalFailure())
+ return;
+
+ TabStripModel* model = browser()->tabstrip_model();
+
+ // Revive the phantom tab by selecting it.
+ browser()->SelectTabContentsAt(0, true);
+
+ // There should still be two tabs.
+ ASSERT_EQ(2, browser()->tab_count());
+ // The first tab should no longer be a phantom.
+ EXPECT_FALSE(model->IsPhantomTab(0));
+}
+*/
+
// Tests that the CLD (Compact Language Detection) works properly.
IN_PROC_BROWSER_TEST_F(BrowserTest, PageLanguageDetection) {
static const wchar_t kDocRoot[] = L"chrome/test/data";
diff --git a/chrome/browser/cocoa/browser_window_controller.mm b/chrome/browser/cocoa/browser_window_controller.mm
index de344ad..64c9f26 100644
--- a/chrome/browser/cocoa/browser_window_controller.mm
+++ b/chrome/browser/cocoa/browser_window_controller.mm
@@ -420,7 +420,7 @@ willPositionSheet:(NSWindow*)sheet
// have to save the window position before we call orderOut:.
[self saveWindowPositionIfNeeded];
- if (!browser_->tabstrip_model()->empty()) {
+ if (browser_->tabstrip_model()->HasNonPhantomTabs()) {
// Tab strip isn't empty. Hide the frame (so it appears to have closed
// immediately) and close all the tabs, allowing the renderers to shut
// down. When the tab strip is empty we'll be called back again.
diff --git a/chrome/browser/defaults.cc b/chrome/browser/defaults.cc
index a901949..080ec265 100644
--- a/chrome/browser/defaults.cc
+++ b/chrome/browser/defaults.cc
@@ -6,6 +6,12 @@
namespace browser_defaults {
+#if defined(TOOLKIT_VIEWS)
+const bool kPinnedTabsActLikeApps = true;
+#else
+const bool kPinnedTabsActLikeApps = false;
+#endif
+
#if defined(OS_CHROMEOS)
const double kAutocompleteEditFontPixelSize = 12.0;
diff --git a/chrome/browser/defaults.h b/chrome/browser/defaults.h
index 595bb70..78c7be4 100644
--- a/chrome/browser/defaults.h
+++ b/chrome/browser/defaults.h
@@ -60,6 +60,9 @@ extern const bool kDownloadPageHasShowInFolder;
// Should the tab strip be sized to the top of the tab strip?
extern const bool kSizeTabButtonToTopOfTabStrip;
+// Should pinned tabs be treated as though they are apps as well?
+extern const bool kPinnedTabsActLikeApps;
+
// Whether we should bootstrap the sync authentication using cookies instead of
// asking the user for credentials.
extern const bool kBootstrapSyncAuthentication;
diff --git a/chrome/browser/extensions/extension_browser_event_router.cc b/chrome/browser/extensions/extension_browser_event_router.cc
index af03497..667c9e3 100644
--- a/chrome/browser/extensions/extension_browser_event_router.cc
+++ b/chrome/browser/extensions/extension_browser_event_router.cc
@@ -396,6 +396,12 @@ void ExtensionBrowserEventRouter::TabChangedAt(TabContents* contents,
TabUpdated(contents, false);
}
+void ExtensionBrowserEventRouter::TabReplacedAt(TabContents* old_contents,
+ TabContents* new_contents,
+ int index) {
+ // TODO: figure out the right notification to send.
+}
+
void ExtensionBrowserEventRouter::TabStripEmpty() { }
void ExtensionBrowserEventRouter::DispatchOldPageActionEvent(
diff --git a/chrome/browser/extensions/extension_browser_event_router.h b/chrome/browser/extensions/extension_browser_event_router.h
index a740f58..0b56c45 100644
--- a/chrome/browser/extensions/extension_browser_event_router.h
+++ b/chrome/browser/extensions/extension_browser_event_router.h
@@ -40,18 +40,20 @@ class ExtensionBrowserEventRouter : public TabStripModelObserver,
void OnBrowserWindowReady(const Browser* browser);
// TabStripModelObserver
- void TabInsertedAt(TabContents* contents, int index, bool foreground);
- void TabClosingAt(TabContents* contents, int index);
- void TabDetachedAt(TabContents* contents, int index);
- void TabSelectedAt(TabContents* old_contents,
- TabContents* new_contents,
- int index,
- bool user_gesture);
- void TabMoved(TabContents* contents, int from_index, int to_index,
- bool pinned_state_changed);
- void TabChangedAt(TabContents* contents, int index,
- TabChangeType change_type);
- void TabStripEmpty();
+ virtual void TabInsertedAt(TabContents* contents, int index, bool foreground);
+ virtual void TabClosingAt(TabContents* contents, int index);
+ virtual void TabDetachedAt(TabContents* contents, int index);
+ virtual void TabSelectedAt(TabContents* old_contents,
+ TabContents* new_contents,
+ int index,
+ bool user_gesture);
+ virtual void TabMoved(TabContents* contents, int from_index, int to_index,
+ bool pinned_state_changed);
+ virtual void TabChangedAt(TabContents* contents, int index,
+ TabChangeType change_type);
+ virtual void TabReplacedAt(TabContents* old_contents,
+ TabContents* new_contents, int index);
+ virtual void TabStripEmpty();
// Page Action execute event.
void PageActionExecuted(Profile* profile,
diff --git a/chrome/browser/gtk/browser_window_gtk.cc b/chrome/browser/gtk/browser_window_gtk.cc
index 3a2d070..1e2a448 100644
--- a/chrome/browser/gtk/browser_window_gtk.cc
+++ b/chrome/browser/gtk/browser_window_gtk.cc
@@ -1261,7 +1261,7 @@ bool BrowserWindowGtk::CanClose() const {
if (!browser_->ShouldCloseWindow())
return false;
- if (!browser_->tabstrip_model()->empty()) {
+ if (browser_->tabstrip_model()->HasNonPhantomTabs()) {
// Tab strip isn't empty. Hide the window (so it appears to have closed
// immediately) and close all the tabs, allowing the renderers to shut
// down. When the tab strip is empty we'll be called back again.
diff --git a/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc b/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc
index 65fa966..415710c 100644
--- a/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc
+++ b/chrome/browser/gtk/tabs/dragged_tab_controller_gtk.cc
@@ -556,7 +556,7 @@ void DraggedTabControllerGtk::Detach() {
}
// If we've removed the last tab from the tabstrip, hide the frame now.
- if (attached_model->empty())
+ if (!attached_model->HasNonPhantomTabs())
HideWindow();
// Update the dragged tab. This NULL check is necessary apparently in some
@@ -865,7 +865,7 @@ void DraggedTabControllerGtk::ShowWindow() {
void DraggedTabControllerGtk::CleanUpHiddenFrame() {
// If the model we started dragging from is now empty, we must ask the
// delegate to close the frame.
- if (source_tabstrip_->model()->empty())
+ if (!source_tabstrip_->model()->HasNonPhantomTabs())
source_tabstrip_->model()->delegate()->CloseFrameAfterDragSession();
}
diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc
index f4bc6d4..7d141b6 100644
--- a/chrome/browser/tab_contents/tab_contents.cc
+++ b/chrome/browser/tab_contents/tab_contents.cc
@@ -47,6 +47,7 @@
#include "chrome/browser/renderer_host/site_instance.h"
#include "chrome/browser/renderer_host/web_cache_manager.h"
#include "chrome/browser/renderer_preferences_util.h"
+#include "chrome/browser/sessions/session_types.h"
#include "chrome/browser/tab_contents/infobar_delegate.h"
#include "chrome/browser/tab_contents/interstitial_page.h"
#include "chrome/browser/tab_contents/navigation_entry.h"
@@ -1193,6 +1194,27 @@ void TabContents::OnCloseStarted() {
tab_close_start_time_ = base::TimeTicks::Now();
}
+TabContents* TabContents::CloneAndMakePhantom() {
+ // TODO: the initial URL, title and what not should come from the app.
+ NavigationEntry* entry = controller().GetActiveEntry();
+
+ TabNavigation tab_nav;
+ if (entry)
+ tab_nav.SetFromNavigationEntry(*entry);
+ std::vector<TabNavigation> navigations;
+ navigations.push_back(tab_nav);
+
+ TabContents* new_contents =
+ new TabContents(profile(), NULL, MSG_ROUTING_NONE, NULL);
+ new_contents->controller().RestoreFromState(navigations, 0, false);
+ if (entry) {
+ // TODO: this should come from the app.
+ new_contents->controller().GetActiveEntry()->favicon() = entry->favicon();
+ }
+
+ return new_contents;
+}
+
// Notifies the RenderWidgetHost instance about the fact that the page is
// loading, or done loading and calls the base implementation.
void TabContents::SetIsLoading(bool is_loading,
diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h
index 1418684..0603d6b 100644
--- a/chrome/browser/tab_contents/tab_contents.h
+++ b/chrome/browser/tab_contents/tab_contents.h
@@ -639,6 +639,13 @@ class TabContents : public PageNavigator,
return request_context_;
}
+ // Creates a duplicate of this TabContents. The returned TabContents is
+ // configured such that the renderer has not been loaded (it'll load the first
+ // time it is selected).
+ // This is intended for use with apps.
+ // The caller owns the returned object.
+ TabContents* CloneAndMakePhantom();
+
// JavaScriptMessageBoxClient ------------------------------------------------
virtual std::wstring GetMessageBoxTitle(const GURL& frame_url,
bool is_alert);
diff --git a/chrome/browser/tabs/tab_strip_model.cc b/chrome/browser/tabs/tab_strip_model.cc
index 56ec106..39f4b66 100644
--- a/chrome/browser/tabs/tab_strip_model.cc
+++ b/chrome/browser/tabs/tab_strip_model.cc
@@ -12,6 +12,7 @@
#include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/browser_shutdown.h"
+#include "chrome/browser/defaults.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/renderer_host/render_process_host.h"
@@ -71,6 +72,14 @@ void TabStripModel::RemoveObserver(TabStripModelObserver* observer) {
observers_.RemoveObserver(observer);
}
+bool TabStripModel::HasNonPhantomTabs() const {
+ for (int i = 0; i < count(); i++) {
+ if (!IsPhantomTab(i))
+ return true;
+ }
+ return false;
+}
+
bool TabStripModel::HasObserver(TabStripModelObserver* observer) {
for (size_t i = 0; i < observers_.size(); ++i) {
if (observers_.GetElementAt(i) == observer)
@@ -147,18 +156,20 @@ TabContents* TabStripModel::DetachTabContentsAt(int index) {
DCHECK(ContainsIndex(index));
TabContents* removed_contents = GetContentsAt(index);
- next_selected_index_ = order_controller_->DetermineNewSelectedIndex(index);
+ next_selected_index_ =
+ order_controller_->DetermineNewSelectedIndex(index, true);
+ next_selected_index_ = IndexOfNextNonPhantomTab(next_selected_index_, -1);
delete contents_data_.at(index);
contents_data_.erase(contents_data_.begin() + index);
- if (contents_data_.empty())
+ if (!HasNonPhantomTabs())
closing_all_ = true;
TabStripModelObservers::Iterator iter(observers_);
while (TabStripModelObserver* obs = iter.GetNext()) {
obs->TabDetachedAt(removed_contents, index);
- if (empty())
+ if (!HasNonPhantomTabs())
obs->TabStripEmpty();
}
- if (!contents_data_.empty()) {
+ if (HasNonPhantomTabs()) {
if (index == selected_index_) {
ChangeSelectedContentsFrom(removed_contents, next_selected_index_,
false);
@@ -257,20 +268,21 @@ int TabStripModel::GetIndexOfNextTabContentsOpenedBy(
DCHECK(opener);
DCHECK(ContainsIndex(start_index));
- TabContentsData* start_data = contents_data_.at(start_index);
TabContentsDataVector::const_iterator iter =
- find(contents_data_.begin(), contents_data_.end(), start_data);
+ contents_data_.begin() + start_index;
TabContentsDataVector::const_iterator next;
for (; iter != contents_data_.end(); ++iter) {
next = iter + 1;
if (next == contents_data_.end())
break;
- if (OpenerMatches(*next, opener, use_group))
+ if (OpenerMatches(*next, opener, use_group) &&
+ !IsPhantomTab(static_cast<int>(next - contents_data_.begin()))) {
return static_cast<int>(next - contents_data_.begin());
+ }
}
- iter = find(contents_data_.begin(), contents_data_.end(), start_data);
+ iter = contents_data_.begin() + start_index;
if (iter != contents_data_.begin()) {
- for (--iter; iter > contents_data_.begin(); --iter) {
+ for (--iter; iter != contents_data_.begin(); --iter) {
if (OpenerMatches(*iter, opener, use_group))
return static_cast<int>(iter - contents_data_.begin());
}
@@ -283,18 +295,18 @@ int TabStripModel::GetIndexOfLastTabContentsOpenedBy(
DCHECK(opener);
DCHECK(ContainsIndex(start_index));
- TabContentsData* start_data = contents_data_.at(start_index);
TabContentsDataVector::const_iterator end =
- find(contents_data_.begin(), contents_data_.end(), start_data);
- TabContentsDataVector::const_iterator iter =
- contents_data_.end();
+ contents_data_.begin() + start_index;
+ TabContentsDataVector::const_iterator iter = contents_data_.end();
TabContentsDataVector::const_iterator next;
for (; iter != end; --iter) {
next = iter - 1;
if (next == end)
break;
- if ((*next)->opener == opener)
+ if ((*next)->opener == opener &&
+ !IsPhantomTab(static_cast<int>(next - contents_data_.begin()))) {
return static_cast<int>(next - contents_data_.begin());
+ }
}
return kNoTab;
}
@@ -383,6 +395,19 @@ bool TabStripModel::IsTabPinned(int index) const {
return contents_data_[index]->pinned;
}
+bool TabStripModel::IsAppTab(int index) const {
+ // TODO (sky): this is temporary and should be integrated with real apps.
+ return CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnablePhantomTabs) &&
+ browser_defaults::kPinnedTabsActLikeApps &&
+ IsTabPinned(index);
+}
+
+bool TabStripModel::IsPhantomTab(int index) const {
+ return IsTabPinned(index) && IsAppTab(index) &&
+ GetTabContentsAt(index)->controller().needs_reload();
+}
+
bool TabStripModel::IsTabBlocked(int index) const {
return contents_data_[index]->blocked;
}
@@ -461,20 +486,11 @@ void TabStripModel::CloseSelectedTab() {
}
void TabStripModel::SelectNextTab() {
- // This may happen during automated testing or if a user somehow buffers
- // many key accelerators.
- if (empty())
- return;
-
- int next_index = (selected_index_ + 1) % count();
- SelectTabContentsAt(next_index, true);
+ SelectRelativeTab(true);
}
void TabStripModel::SelectPreviousTab() {
- int prev_index = selected_index_ - 1;
- if (prev_index < 0)
- prev_index = count() + prev_index;
- SelectTabContentsAt(prev_index, true);
+ SelectRelativeTab(false);
}
void TabStripModel::SelectLastTab() {
@@ -562,7 +578,7 @@ void TabStripModel::ExecuteContextMenuCommand(
TabContents* contents = GetTabContentsAt(context_index);
std::vector<int> closing_tabs;
for (int i = count() - 1; i >= 0; --i) {
- if (GetTabContentsAt(i) != contents)
+ if (GetTabContentsAt(i) != contents && !IsTabPinned(i))
closing_tabs.push_back(i);
}
InternalCloseTabs(closing_tabs, true);
@@ -572,7 +588,8 @@ void TabStripModel::ExecuteContextMenuCommand(
UserMetrics::RecordAction("TabContextMenu_CloseTabsToRight", profile_);
std::vector<int> closing_tabs;
for (int i = count() - 1; i > context_index; --i) {
- closing_tabs.push_back(i);
+ if (!IsTabPinned(i))
+ closing_tabs.push_back(i);
}
InternalCloseTabs(closing_tabs, true);
break;
@@ -580,6 +597,13 @@ void TabStripModel::ExecuteContextMenuCommand(
case CommandCloseTabsOpenedBy: {
UserMetrics::RecordAction("TabContextMenu_CloseTabsOpenedBy", profile_);
std::vector<int> closing_tabs = GetIndexesOpenedBy(context_index);
+ for (std::vector<int>::iterator i = closing_tabs.begin();
+ i != closing_tabs.end();) {
+ if (IsTabPinned(*i))
+ i = closing_tabs.erase(i);
+ else
+ ++i;
+ }
InternalCloseTabs(closing_tabs, true);
break;
}
@@ -631,7 +655,14 @@ void TabStripModel::Observe(NotificationType type,
if (index != TabStripModel::kNoTab) {
// Note that we only detach the contents here, not close it - it's already
// been closed. We just want to undo our bookkeeping.
- DetachTabContentsAt(index);
+ if (IsAppTab(index) && IsTabPinned(index) && !IsPhantomTab(index) &&
+ !closing_all_) {
+ // We don't actually allow pinned tabs to close. Instead they become
+ // phantom.
+ MakePhantom(index);
+ } else {
+ DetachTabContentsAt(index);
+ }
}
}
@@ -776,6 +807,65 @@ void TabStripModel::SetOpenerForContents(TabContents* contents,
contents_data_.at(index)->opener = &opener->controller();
}
+void TabStripModel::SelectRelativeTab(bool next) {
+ // This may happen during automated testing or if a user somehow buffers
+ // many key accelerators.
+ if (contents_data_.empty())
+ return;
+
+ // Skip pinned-app-phantom tabs when iterating.
+ int index = selected_index_;
+ int delta = next ? 1 : -1;
+ do {
+ index = (index + count() + delta) % count();
+ } while (index != selected_index_ && IsTabPinned(index) && IsAppTab(index) &&
+ IsPhantomTab(index));
+ SelectTabContentsAt(index, true);
+}
+
+int TabStripModel::IndexOfNextNonPhantomTab(int index,
+ int ignore_index) {
+ if (index == kNoTab)
+ return kNoTab;
+
+ if (empty())
+ return index;
+
+ index = std::min(count() - 1, std::max(0, index));
+ int start = index;
+ do {
+ if (index != ignore_index && !IsPhantomTab(index))
+ return index;
+ index = (index + 1) % count();
+ } while (index != start);
+
+ // All phantom tabs.
+ return start;
+}
+
+void TabStripModel::MakePhantom(int index) {
+ if (selected_index_ == index) {
+ // Change the selection, otherwise we're going to force the phantom tab
+ // to become selected.
+ // NOTE: we must do this before switching the TabContents, otherwise
+ // observers are notified with the wrong tab contents.
+ int new_selected_index =
+ order_controller_->DetermineNewSelectedIndex(index, false);
+ new_selected_index = IndexOfNextNonPhantomTab(new_selected_index,
+ index);
+ SelectTabContentsAt(new_selected_index, true);
+ }
+
+ TabContents* old_contents = GetContentsAt(index);
+ TabContents* new_contents = old_contents->CloneAndMakePhantom();
+
+ contents_data_[index]->contents = new_contents;
+
+ // And notify observers.
+ FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
+ TabReplacedAt(old_contents, new_contents, index));
+}
+
// static
bool TabStripModel::OpenerMatches(const TabContentsData* data,
const NavigationController* opener,
diff --git a/chrome/browser/tabs/tab_strip_model.h b/chrome/browser/tabs/tab_strip_model.h
index bdd92c3..788aaa3 100644
--- a/chrome/browser/tabs/tab_strip_model.h
+++ b/chrome/browser/tabs/tab_strip_model.h
@@ -99,6 +99,12 @@ class TabStripModelObserver {
virtual void TabChangedAt(TabContents* contents, int index,
TabChangeType change_type) {}
+ // The tab contents was replaced at the specified index. This is invoked when
+ // a tab becomes phantom. See description of phantom tabs in class description
+ // of TabStripModel for details.
+ virtual void TabReplacedAt(TabContents* old_contents,
+ TabContents* new_contents, int index) {}
+
// Invoked when the pinned state of a tab changes.
// NOTE: this is only invoked if the tab doesn't move as a result of its
// pinned state changing. If the tab moves as a result, the observer is
@@ -110,10 +116,10 @@ class TabStripModelObserver {
// window.
virtual void TabBlockedStateChanged(TabContents* contents, int index) { }
- // The TabStripModel now no longer has any "significant" (user created or
- // user manipulated) tabs. The implementer may use this as a trigger to try
- // and close the window containing the TabStripModel, for example...
- virtual void TabStripEmpty() { }
+ // The TabStripModel now no longer has any phantom tabs. The implementer may
+ // use this as a trigger to try and close the window containing the
+ // TabStripModel, for example...
+ virtual void TabStripEmpty() {}
};
///////////////////////////////////////////////////////////////////////////////
@@ -227,15 +233,26 @@ class TabStripModelDelegate {
// them, as well as a higher level API for doing specific Browser-related
// tasks like adding new Tabs from just a URL, etc.
//
-// Each tab may additionally be pinned. The view typically renders pinned tabs
-// differently. The model makes sure all pinned tabs are organized at the
-// beginning of the tabstrip. Inserting a tab between pinned tabs
-// implicitly makes the inserted tab pinned. Similarly moving a tab may pin or
-// unpin the tab, again enforcing that all pinned tabs occur at the beginning
-// of the tabstrip. Lastly, changing the pinned state of a tab moves the
-// tab to be grouped with the pinned or unpinned tabs. For example, if the
-// first two tabs are pinned, and the tenth tab is pinned, it is moved to
-// become the third tab.
+// Each tab may be any one of the following states:
+// . Pinned. The view typically renders pinned tabs differently. The model makes
+// sure all pinned tabs are organized at the beginning of the tabstrip.
+// Inserting a tab between pinned tabs implicitly makes the inserted tab
+// pinned. Similarly moving a tab may pin or unpin the tab, again enforcing
+// that all pinned tabs occur at the beginning of the tabstrip. Lastly,
+// changing the pinned state of a tab moves the tab to be grouped with the
+// pinned or unpinned tabs. For example, if the first two tabs are pinned, and
+// the tenth tab is pinned, it is moved to become the third tab.
+// . App. An app tab corresponds to an app extension.
+// . Phantom. Only pinned app tabs may be made phantom (or if
+// browser_defaults::kPinnedTabsActLikeApps is true then any pinned tab may be
+// made phantom). When a tab that can be made phantom is closed the renderer
+// is shutdown, a new TabContents/NavigationController is created that has
+// not yet loaded the renderer and observers are notified via the
+// TabReplacedAt method. When a phantom tab is selected the renderer is
+// loaded and the tab is no longer phantom.
+// Phantom tabs do not prevent the tabstrip from closing, for example if the
+// tabstrip has one phantom and one non-phantom tab and the non-phantom tab is
+// closed, then the tabstrip/browser are closed.
//
// A TabStripModel has one delegate that it relies on to perform certain tasks
// like creating new TabStripModels (probably hosted in Browser windows) when
@@ -267,6 +284,11 @@ class TabStripModel : public NotificationObserver {
int count() const { return static_cast<int>(contents_data_.size()); }
bool empty() const { return contents_data_.empty(); }
+ // Returns true if there are any non-phantom tabs. When there are no
+ // non-phantom tabs the delegate is notified by way of TabStripEmpty and the
+ // browser closes.
+ bool HasNonPhantomTabs() const;
+
// Retrieve the Profile associated with this TabStripModel.
Profile* profile() const { return profile_; }
@@ -391,12 +413,14 @@ class TabStripModel : public NotificationObserver {
// If |use_group| is true, the group property of the tab is used instead of
// the opener to find the next tab. Under some circumstances the group
// relationship may exist but the opener may not.
+ // NOTE: this skips phantom tabs.
int GetIndexOfNextTabContentsOpenedBy(const NavigationController* opener,
int start_index,
bool use_group) const;
// Returns the index of the last TabContents in the model opened by the
// specified opener, starting at |start_index|.
+ // NOTE: this skips phantom tabs.
int GetIndexOfLastTabContentsOpenedBy(const NavigationController* opener,
int start_index) const;
@@ -434,6 +458,17 @@ class TabStripModel : public NotificationObserver {
// Returns true if the tab at |index| is pinned.
bool IsTabPinned(int index) const;
+ // Is the tab at |index| an app?
+ // See description above class for details on this.
+ // This is currently only true if browser_defaults::kPinnedTabsActLikeApps is
+ // true and the tab is pinned.
+ bool IsAppTab(int index) const;
+
+ // Returns true if the tab is a phantom tab. A phantom tab is one where the
+ // renderer has not been loaded.
+ // See description above class for details on this.
+ bool IsPhantomTab(int index) const;
+
// Returns true if the tab at |index| is blocked by a tab modal dialog.
bool IsTabBlocked(int index) const;
@@ -560,6 +595,17 @@ class TabStripModel : public NotificationObserver {
// be |opener|'s NavigationController.
void SetOpenerForContents(TabContents* contents, TabContents* opener);
+ // Selects either the next tab (|foward| is true), or the previous tab
+ // (|forward| is false).
+ void SelectRelativeTab(bool forward);
+
+ // Returns the first non-phantom tab starting at |index|, skipping the tab at
+ // |ignore_index|.
+ int IndexOfNextNonPhantomTab(int index, int ignore_index);
+
+ // Makes the tab a phantom tab.
+ void MakePhantom(int index);
+
// Returns true if the tab represented by the specified data has an opener
// that matches the specified one. If |use_group| is true, then this will
// fall back to check the group relationship as well.
diff --git a/chrome/browser/tabs/tab_strip_model_order_controller.cc b/chrome/browser/tabs/tab_strip_model_order_controller.cc
index b91c622..de0ab23 100644
--- a/chrome/browser/tabs/tab_strip_model_order_controller.cc
+++ b/chrome/browser/tabs/tab_strip_model_order_controller.cc
@@ -53,7 +53,8 @@ int TabStripModelOrderController::DetermineInsertionIndex(
}
int TabStripModelOrderController::DetermineNewSelectedIndex(
- int removing_index) const {
+ int removing_index,
+ bool is_remove) const {
int tab_count = tabstrip_->count();
DCHECK(removing_index >= 0 && removing_index < tab_count);
NavigationController* parent_opener =
@@ -67,7 +68,7 @@ int TabStripModelOrderController::DetermineNewSelectedIndex(
removing_index,
false);
if (index != TabStripModel::kNoTab)
- return GetValidIndex(index, removing_index);
+ return GetValidIndex(index, removing_index, is_remove);
if (parent_opener) {
// If the tab was in a group, shift selection to the next tab in the group.
@@ -75,19 +76,19 @@ int TabStripModelOrderController::DetermineNewSelectedIndex(
removing_index,
false);
if (index != TabStripModel::kNoTab)
- return GetValidIndex(index, removing_index);
+ return GetValidIndex(index, removing_index, is_remove);
// If we can't find a subsequent group member, just fall back to the
// parent_opener itself. Note that we use "group" here since opener is
// reset by select operations..
index = tabstrip_->GetIndexOfController(parent_opener);
if (index != TabStripModel::kNoTab)
- return GetValidIndex(index, removing_index);
+ return GetValidIndex(index, removing_index, is_remove);
}
// No opener set, fall through to the default handler...
int selected_index = tabstrip_->selected_index();
- if (selected_index >= (tab_count - 1))
+ if (is_remove && selected_index >= (tab_count - 1))
return selected_index - 1;
return selected_index;
}
@@ -121,8 +122,9 @@ void TabStripModelOrderController::TabSelectedAt(TabContents* old_contents,
// TabStripModelOrderController, private:
int TabStripModelOrderController::GetValidIndex(int index,
- int removing_index) const {
- if (removing_index < index)
+ int removing_index,
+ bool is_remove) const {
+ if (is_remove && removing_index < index)
index = std::max(0, index - 1);
return index;
}
diff --git a/chrome/browser/tabs/tab_strip_model_order_controller.h b/chrome/browser/tabs/tab_strip_model_order_controller.h
index 045b821..9beaa03 100644
--- a/chrome/browser/tabs/tab_strip_model_order_controller.h
+++ b/chrome/browser/tabs/tab_strip_model_order_controller.h
@@ -29,8 +29,11 @@ class TabStripModelOrderController : public TabStripModelObserver {
PageTransition::Type transition,
bool foreground);
- // Determine where to shift selection after a tab is closed.
- int DetermineNewSelectedIndex(int removed_index) const;
+ // Determine where to shift selection after a tab is closed is made phantom.
+ // If |is_remove| is false, the tab is not being removed but rather made
+ // phantom (see description of phantom tabs in TabStripModel).
+ int DetermineNewSelectedIndex(int removed_index,
+ bool is_remove) const;
// Overridden from TabStripModelObserver:
virtual void TabSelectedAt(TabContents* old_contents,
@@ -40,9 +43,10 @@ class TabStripModelOrderController : public TabStripModelObserver {
protected:
// Returns a valid index to be selected after the tab at |removing_index| is
- // closed. If |index| is after |removing_index|, |index| is adjusted to
- // reflect the fact that |removing_index| is going away.
- int GetValidIndex(int index, int removing_index) const;
+ // closed. If |index| is after |removing_index| and |is_remove| is true,
+ // |index| is adjusted to reflect the fact that |removing_index| is going
+ // away. This also skips any phantom tabs.
+ int GetValidIndex(int index, int removing_index, bool is_remove) const;
TabStripModel* tabstrip_;
};
diff --git a/chrome/browser/views/frame/browser_view.cc b/chrome/browser/views/frame/browser_view.cc
index ca001a3..f0a595c 100644
--- a/chrome/browser/views/frame/browser_view.cc
+++ b/chrome/browser/views/frame/browser_view.cc
@@ -1475,7 +1475,7 @@ bool BrowserView::CanClose() const {
if (!browser_->ShouldCloseWindow())
return false;
- if (!browser_->tabstrip_model()->empty()) {
+ if (browser_->tabstrip_model()->HasNonPhantomTabs()) {
// Tab strip isn't empty. Hide the frame (so it appears to have closed
// immediately) and close all the tabs, allowing the renderers to shut
// down. When the tab strip is empty we'll be called back again.
diff --git a/chrome/browser/views/tabs/dragged_tab_controller.cc b/chrome/browser/views/tabs/dragged_tab_controller.cc
index f7f81b8..e354e56 100644
--- a/chrome/browser/views/tabs/dragged_tab_controller.cc
+++ b/chrome/browser/views/tabs/dragged_tab_controller.cc
@@ -959,7 +959,7 @@ void DraggedTabController::Detach() {
}
// If we've removed the last Tab from the TabStrip, hide the frame now.
- if (attached_model->empty())
+ if (!attached_model->HasNonPhantomTabs())
HideFrame();
// Set up the photo booth to start capturing the contents of the dragged
@@ -1343,7 +1343,7 @@ void DraggedTabController::HideFrame() {
void DraggedTabController::CleanUpHiddenFrame() {
// If the model we started dragging from is now empty, we must ask the
// delegate to close the frame.
- if (source_tabstrip_->model()->empty())
+ if (!source_tabstrip_->model()->HasNonPhantomTabs())
source_tabstrip_->model()->delegate()->CloseFrameAfterDragSession();
}
diff --git a/chrome/browser/views/tabs/dragged_tab_view.cc b/chrome/browser/views/tabs/dragged_tab_view.cc
index d3a9263..5cd7531 100644
--- a/chrome/browser/views/tabs/dragged_tab_view.cc
+++ b/chrome/browser/views/tabs/dragged_tab_view.cc
@@ -42,7 +42,7 @@ DraggedTabView::DraggedTabView(TabContents* datasource,
tab_width_(0) {
set_parent_owned(false);
- renderer_->UpdateData(datasource, false);
+ renderer_->UpdateData(datasource, false, false);
#if defined(OS_WIN)
container_.reset(new views::WidgetWin);
diff --git a/chrome/browser/views/tabs/tab_renderer.cc b/chrome/browser/views/tabs/tab_renderer.cc
index 60d24c9..34bee57 100644
--- a/chrome/browser/views/tabs/tab_renderer.cc
+++ b/chrome/browser/views/tabs/tab_renderer.cc
@@ -258,6 +258,7 @@ TabRenderer::TabRenderer()
data_.blocked = false;
data_.pinned = false;
data_.animating_pinned_change = false;
+ data_.phantom = false;
// Add the Close Button.
close_button_ = new TabCloseButton(this);
@@ -293,13 +294,16 @@ ThemeProvider* TabRenderer::GetThemeProvider() {
return NULL;
}
-void TabRenderer::UpdateData(TabContents* contents, bool loading_only) {
+void TabRenderer::UpdateData(TabContents* contents,
+ bool phantom,
+ bool loading_only) {
DCHECK(contents);
- if (!loading_only) {
+ if (data_.phantom != phantom || !loading_only) {
data_.title = contents->GetTitle();
data_.off_the_record = contents->profile()->IsOffTheRecord();
data_.crashed = contents->is_crashed();
data_.favicon = contents->GetFavIcon();
+ data_.phantom = phantom;
}
// TODO(glen): Temporary hax.
@@ -457,7 +461,8 @@ void TabRenderer::Paint(gfx::Canvas* canvas) {
show_close_button != showing_close_button_)
Layout();
- PaintTabBackground(canvas);
+ if (!data_.phantom)
+ PaintTabBackground(canvas);
SkColor title_color = GetThemeProvider()->
GetColor(IsSelected() ?
diff --git a/chrome/browser/views/tabs/tab_renderer.h b/chrome/browser/views/tabs/tab_renderer.h
index 4cecfbe..c08dd8a 100644
--- a/chrome/browser/views/tabs/tab_renderer.h
+++ b/chrome/browser/views/tabs/tab_renderer.h
@@ -45,7 +45,7 @@ class TabRenderer : public views::View,
// TabContents.
//
// See TabStripModel::TabChangedAt documentation for what loading_only means.
- void UpdateData(TabContents* contents, bool loading_only);
+ void UpdateData(TabContents* contents, bool phantom, bool loading_only);
// Sets the pinned state of the tab.
void SetBlocked(bool blocked);
@@ -207,6 +207,7 @@ class TabRenderer : public views::View,
bool pinned;
bool blocked;
bool animating_pinned_change;
+ bool phantom;
};
TabData data_;
diff --git a/chrome/browser/views/tabs/tab_strip.cc b/chrome/browser/views/tabs/tab_strip.cc
index 27bbb68..e051f7d 100644
--- a/chrome/browser/views/tabs/tab_strip.cc
+++ b/chrome/browser/views/tabs/tab_strip.cc
@@ -1111,7 +1111,7 @@ void TabStrip::TabInsertedAt(TabContents* contents,
if (!contains_tab) {
TabData d = { tab, gfx::Rect() };
tab_data_.insert(tab_data_.begin() + index, d);
- tab->UpdateData(contents, false);
+ tab->UpdateData(contents, model_->IsPhantomTab(index), false);
}
tab->set_pinned(model_->IsTabPinned(index));
tab->SetBlocked(model_->IsTabBlocked(index));
@@ -1186,10 +1186,17 @@ void TabStrip::TabChangedAt(TabContents* contents, int index,
// We'll receive another notification of the change asynchronously.
return;
}
- tab->UpdateData(contents, change_type == LOADING_ONLY);
+ tab->UpdateData(contents, model_->IsPhantomTab(index),
+ change_type == LOADING_ONLY);
tab->UpdateFromModel();
}
+void TabStrip::TabReplacedAt(TabContents* old_contents,
+ TabContents* new_contents,
+ int index) {
+ TabChangedAt(new_contents, index, ALL);
+}
+
void TabStrip::TabPinnedStateChanged(TabContents* contents, int index) {
GetTabAt(index)->set_pinned(model_->IsTabPinned(index));
StartPinnedTabAnimation(index);
diff --git a/chrome/browser/views/tabs/tab_strip.h b/chrome/browser/views/tabs/tab_strip.h
index 27f6a97..309a9f3 100644
--- a/chrome/browser/views/tabs/tab_strip.h
+++ b/chrome/browser/views/tabs/tab_strip.h
@@ -139,6 +139,9 @@ class TabStrip : public views::View,
bool pinned_state_changed);
virtual void TabChangedAt(TabContents* contents, int index,
TabChangeType change_type);
+ virtual void TabReplacedAt(TabContents* old_contents,
+ TabContents* new_contents,
+ int index);
virtual void TabPinnedStateChanged(TabContents* contents, int index);
virtual void TabBlockedStateChanged(TabContents* contents, int index);
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 251b382..e762c36 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -228,6 +228,9 @@ const char kEnableNativeWebWorkers[] = "enable-native-web-workers";
// Enable AutoFill++.
const char kEnableNewAutoFill[] = "enable-new-autofill";
+// Are phantom tabs enabled?
+const char kEnablePhantomTabs[] = "enable-phantom-tabs";
+
// Enable Privacy Blacklists.
const char kEnablePrivacyBlacklists[] = "enable-privacy-blacklists";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 15b1975..a4a7c69 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -81,6 +81,7 @@ extern const char kEnableLogging[];
extern const char kEnableMonitorProfile[];
extern const char kEnableNativeWebWorkers[];
extern const char kEnableNewAutoFill[];
+extern const char kEnablePhantomTabs[];
extern const char kEnablePrivacyBlacklists[];
extern const char kEnableRendererAccessibility[];
extern const char kEnableSeccompSandbox[];