summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-22 00:15:17 +0000
committersky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-22 00:15:17 +0000
commitc37e3b6de9ccfd2bc4c5f2852ae2e4b5ec9b985d (patch)
treee5df41c5242a176e955db360d5de40225fded383
parent7e3544bd5859cad57261bc4827686f266a8d3961 (diff)
downloadchromium_src-c37e3b6de9ccfd2bc4c5f2852ae2e4b5ec9b985d.zip
chromium_src-c37e3b6de9ccfd2bc4c5f2852ae2e4b5ec9b985d.tar.gz
chromium_src-c37e3b6de9ccfd2bc4c5f2852ae2e4b5ec9b985d.tar.bz2
Adds support for phantom tabs. A pinned tab becomes a phantom tab when
it is closed, and effectively unloads the renderer and replaces it with a new TabContents that loads when selected. A phantom tab is currently rendered without a border. Phantom tabs do not prevent a window from closing. Long term only pinned app tabs will have the ability to be made phantom, but this allows us to test the feature until app support is all wired in. BUG=32845 TEST=none yet Review URL: http://codereview.chromium.org/553008 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@36815 0039d316-1c4b-4281-b951-d872f2087c98
-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[];