Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / extension_toolbar_model_unittest.cc
index 8883632..744420f 100644 (file)
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/files/file_util.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_ptr.h"
+#include "base/strings/stringprintf.h"
 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
+#include "chrome/browser/extensions/extension_action_manager.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_base.h"
 #include "chrome/browser/extensions/extension_toolbar_model.h"
+#include "chrome/browser/extensions/extension_toolbar_model_factory.h"
+#include "chrome/browser/extensions/extension_util.h"
+#include "chrome/browser/extensions/test_extension_dir.h"
 #include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/browser/extensions/unpacked_installer.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sessions/session_tab_helper.h"
 #include "chrome/common/extensions/api/extension_action/action_info.h"
 #include "components/crx_file/id_util.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
+#include "extensions/browser/test_extension_registry_observer.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/feature_switch.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/value_builder.h"
 
+#if defined(USE_AURA)
+#include "ui/aura/env.h"
+#endif
+
 namespace extensions {
 
 namespace {
 
+// A helper class to create test web contents (tabs) for unit tests, without
+// inheriting from RenderViewTestHarness. Can create web contents, and will
+// clean up after itself upon destruction. Owns all created web contents.
+// A few notes:
+// - Works well allocated on the stack, because it should be destroyed before
+//   associated browser context.
+// - Doesn't play nice with web contents created any other way (because of
+//   the implementation of RenderViewHostTestEnabler). But if you are creating
+//   web contents already, what do you need this for? ;)
+// - This will call aura::Env::Create/DeleteInstance(), because that's needed
+//   for web contents. Nothing else should call it first. (This could be
+//   tweaked by passing in a flag, if there's need.)
+// TODO(devlin): Look around and see if this class is needed elsewhere; if so,
+// move it there and expand the API a bit (methods to, e.g., delete/close a
+// web contents, access existing web contents, etc).
+class TestWebContentsFactory {
+ public:
+  TestWebContentsFactory();
+  ~TestWebContentsFactory();
+
+  // Creates a new WebContents with the given |context|, and returns it.
+  content::WebContents* CreateWebContents(content::BrowserContext* context);
+
+ private:
+  // The test factory (and friends) for creating test web contents.
+  scoped_ptr<content::RenderViewHostTestEnabler> rvh_enabler_;
+
+  // The vector of web contents that this class created.
+  ScopedVector<content::WebContents> web_contents_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestWebContentsFactory);
+};
+
+TestWebContentsFactory::TestWebContentsFactory()
+    : rvh_enabler_(new content::RenderViewHostTestEnabler()) {
+#if defined(USE_AURA)
+  aura::Env::CreateInstance(true);
+#endif
+}
+
+TestWebContentsFactory::~TestWebContentsFactory() {
+  web_contents_.clear();
+  // Let any posted tasks for web contents deletion run.
+  base::RunLoop().RunUntilIdle();
+  rvh_enabler_.reset();
+  // Let any posted tasks for RenderProcess/ViewHost deletion run.
+  base::RunLoop().RunUntilIdle();
+
+#if defined(USE_AURA)
+  aura::Env::DeleteInstance();
+#endif
+}
+
+content::WebContents* TestWebContentsFactory::CreateWebContents(
+    content::BrowserContext* context) {
+  scoped_ptr<content::WebContents> web_contents(
+      content::WebContentsTester::CreateTestWebContents(context, nullptr));
+  DCHECK(web_contents);
+  web_contents_.push_back(web_contents.release());
+  return web_contents_.back();
+}
+
+// Creates a new ExtensionToolbarModel for the given |context|.
+KeyedService* BuildToolbarModel(content::BrowserContext* context) {
+  return new ExtensionToolbarModel(Profile::FromBrowserContext(context),
+                                   ExtensionPrefs::Get(context));
+}
+
+// Given a |profile|, assigns the testing keyed service function to
+// BuildToolbarModel() and uses it to create and initialize a new
+// ExtensionToolbarModel.
+ExtensionToolbarModel* CreateToolbarModelForProfile(Profile* profile) {
+  ExtensionToolbarModel* model = ExtensionToolbarModel::Get(profile);
+  if (model)
+    return model;
+
+  // No existing model means it's a new profile (since we, by default, don't
+  // create the ToolbarModel in testing).
+  ExtensionToolbarModelFactory::GetInstance()->SetTestingFactory(
+      profile, &BuildToolbarModel);
+  model = ExtensionToolbarModel::Get(profile);
+  // Fake the extension system ready signal.
+  // HACK ALERT! In production, the ready task on ExtensionSystem (and most
+  // everything else on it, too) is shared between incognito and normal
+  // profiles, but a TestExtensionSystem doesn't have the concept of "shared".
+  // Because of this, we have to set any new profile's TestExtensionSystem's
+  // ready task, too.
+  static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile))->SetReady();
+  // Run tasks posted to TestExtensionSystem.
+  base::RunLoop().RunUntilIdle();
+
+  return model;
+}
+
 // Create an extension. If |action_key| is non-NULL, it should point to either
 // kBrowserAction or kPageAction, and the extension will have the associated
 // action.
@@ -49,49 +158,48 @@ class ExtensionToolbarModelTestObserver
     : public ExtensionToolbarModel::Observer {
  public:
   explicit ExtensionToolbarModelTestObserver(ExtensionToolbarModel* model);
-  virtual ~ExtensionToolbarModelTestObserver();
+  ~ExtensionToolbarModelTestObserver() override;
 
   size_t inserted_count() const { return inserted_count_; }
   size_t removed_count() const { return removed_count_; }
   size_t moved_count() const { return moved_count_; }
   int highlight_mode_count() const { return highlight_mode_count_; }
+  size_t reorder_count() const { return reorder_count_; }
 
  private:
   // ExtensionToolbarModel::Observer:
-  virtual void ToolbarExtensionAdded(const Extension* extension,
-                                     int index) OVERRIDE {
+  void ToolbarExtensionAdded(const Extension* extension, int index) override {
     ++inserted_count_;
   }
 
-  virtual void ToolbarExtensionRemoved(const Extension* extension) OVERRIDE {
+  void ToolbarExtensionRemoved(const Extension* extension) override {
     ++removed_count_;
   }
 
-  virtual void ToolbarExtensionMoved(const Extension* extension,
-                                     int index) OVERRIDE {
+  void ToolbarExtensionMoved(const Extension* extension, int index) override {
     ++moved_count_;
   }
 
-  virtual void ToolbarExtensionUpdated(const Extension* extension) OVERRIDE {
-  }
+  void ToolbarExtensionUpdated(const Extension* extension) override {}
 
-  virtual bool ShowExtensionActionPopup(const Extension* extension,
-                                        bool grant_active_tab) OVERRIDE {
+  bool ShowExtensionActionPopup(const Extension* extension,
+                                bool grant_active_tab) override {
     return false;
   }
 
-  virtual void ToolbarVisibleCountChanged() OVERRIDE {
-  }
+  void ToolbarVisibleCountChanged() override {}
 
-  virtual void ToolbarHighlightModeChanged(bool is_highlighting) OVERRIDE {
+  void ToolbarHighlightModeChanged(bool is_highlighting) override {
     // Add one if highlighting, subtract one if not.
     highlight_mode_count_ += is_highlighting ? 1 : -1;
   }
 
-  virtual Browser* GetBrowser() OVERRIDE {
-    return NULL;
+  void OnToolbarReorderNecessary(content::WebContents* web_contents) override {
+    ++reorder_count_;
   }
 
+  Browser* GetBrowser() override { return NULL; }
+
   ExtensionToolbarModel* model_;
 
   size_t inserted_count_;
@@ -99,14 +207,16 @@ class ExtensionToolbarModelTestObserver
   size_t moved_count_;
   // Int because it could become negative (if something goes wrong).
   int highlight_mode_count_;
+  size_t reorder_count_;
 };
 
 ExtensionToolbarModelTestObserver::ExtensionToolbarModelTestObserver(
     ExtensionToolbarModel* model) : model_(model),
-                                    inserted_count_(0u),
-                                    removed_count_(0u),
-                                    moved_count_(0u),
-                                    highlight_mode_count_(0) {
+                                    inserted_count_(0),
+                                    removed_count_(0),
+                                    moved_count_(0),
+                                    highlight_mode_count_(0),
+                                    reorder_count_(0) {
   model_->AddObserver(this);
 }
 
@@ -122,6 +232,8 @@ class ExtensionToolbarModelUnitTest : public ExtensionServiceTestBase {
   // ExtensionSystem.
   void Init();
 
+  void TearDown() override;
+
   // Adds or removes the given |extension| and verify success.
   testing::AssertionResult AddExtension(
       const scoped_refptr<const Extension>& extension) WARN_UNUSED_RESULT;
@@ -137,9 +249,13 @@ class ExtensionToolbarModelUnitTest : public ExtensionServiceTestBase {
 
   // Returns the extension at the given index in the toolbar model, or NULL
   // if one does not exist.
+  // If |model| is specified, it is used. Otherwise, this defaults to
+  // |toolbar_model_|.
+  const Extension* GetExtensionAtIndex(
+      size_t index, const ExtensionToolbarModel* model) const;
   const Extension* GetExtensionAtIndex(size_t index) const;
 
-  ExtensionToolbarModel* toolbar_model() { return toolbar_model_.get(); }
+  ExtensionToolbarModel* toolbar_model() { return toolbar_model_; }
 
   const ExtensionToolbarModelTestObserver* observer() const {
     return model_observer_.get();
@@ -161,8 +277,8 @@ class ExtensionToolbarModelUnitTest : public ExtensionServiceTestBase {
   testing::AssertionResult AddAndVerifyExtensions(
       const ExtensionList& extensions);
 
-  // The associated (and owned) toolbar model.
-  scoped_ptr<ExtensionToolbarModel> toolbar_model_;
+  // The toolbar model associated with the testing profile.
+  ExtensionToolbarModel* toolbar_model_;
 
   // The test observer to track events. Must come after toolbar_model_ so that
   // it is destroyed and removes itself as an observer first.
@@ -181,14 +297,13 @@ class ExtensionToolbarModelUnitTest : public ExtensionServiceTestBase {
 
 void ExtensionToolbarModelUnitTest::Init() {
   InitializeEmptyExtensionService();
-  toolbar_model_.reset(
-      new ExtensionToolbarModel(profile(), ExtensionPrefs::Get(profile())));
-  model_observer_.reset(
-      new ExtensionToolbarModelTestObserver(toolbar_model_.get()));
-  static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile()))->
-      SetReady();
-  // Run tasks posted to TestExtensionSystem.
-  base::RunLoop().RunUntilIdle();
+  toolbar_model_ = CreateToolbarModelForProfile(profile());
+  model_observer_.reset(new ExtensionToolbarModelTestObserver(toolbar_model_));
+}
+
+void ExtensionToolbarModelUnitTest::TearDown() {
+  model_observer_.reset();
+  ExtensionServiceTestBase::TearDown();
 }
 
 testing::AssertionResult ExtensionToolbarModelUnitTest::AddExtension(
@@ -253,12 +368,17 @@ ExtensionToolbarModelUnitTest::AddBrowserActionExtensions() {
 }
 
 const Extension* ExtensionToolbarModelUnitTest::GetExtensionAtIndex(
-    size_t index) const {
-  return index < toolbar_model_->toolbar_items().size()
-             ? toolbar_model_->toolbar_items()[index].get()
+    size_t index, const ExtensionToolbarModel* model) const {
+  return index < model->toolbar_items().size()
+             ? model->toolbar_items()[index].get()
              : NULL;
 }
 
+const Extension* ExtensionToolbarModelUnitTest::GetExtensionAtIndex(
+    size_t index) const {
+  return GetExtensionAtIndex(index, toolbar_model_);
+}
+
 testing::AssertionResult ExtensionToolbarModelUnitTest::AddAndVerifyExtensions(
     const ExtensionList& extensions) {
   for (ExtensionList::const_iterator iter = extensions.begin();
@@ -296,7 +416,7 @@ TEST_F(ExtensionToolbarModelUnitTest, BasicExtensionToolbarModelTest) {
   EXPECT_EQ(extension2.get(), GetExtensionAtIndex(0u));
 
   // Should be a no-op, but still fires the events.
-  toolbar_model()->MoveExtensionIcon(extension2.get(), 0);
+  toolbar_model()->MoveExtensionIcon(extension2->id(), 0);
   EXPECT_EQ(1u, observer()->moved_count());
   EXPECT_EQ(1u, num_toolbar_items());
   EXPECT_EQ(extension2.get(), GetExtensionAtIndex(0u));
@@ -322,7 +442,7 @@ TEST_F(ExtensionToolbarModelUnitTest, ExtensionToolbarReorderAndReinsert) {
   EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(2u));
 
   // Order is now A, B, C. Let's put C first.
-  toolbar_model()->MoveExtensionIcon(browser_action_c(), 0);
+  toolbar_model()->MoveExtensionIcon(browser_action_c()->id(), 0);
   EXPECT_EQ(1u, observer()->moved_count());
   EXPECT_EQ(3u, num_toolbar_items());
   EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(0u));
@@ -330,7 +450,7 @@ TEST_F(ExtensionToolbarModelUnitTest, ExtensionToolbarReorderAndReinsert) {
   EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(2u));
 
   // Order is now C, A, B. Let's put A last.
-  toolbar_model()->MoveExtensionIcon(browser_action_a(), 2);
+  toolbar_model()->MoveExtensionIcon(browser_action_a()->id(), 2);
   EXPECT_EQ(2u, observer()->moved_count());
   EXPECT_EQ(3u, num_toolbar_items());
   EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(0u));
@@ -359,13 +479,13 @@ TEST_F(ExtensionToolbarModelUnitTest, ExtensionToolbarReorderAndReinsert) {
   EXPECT_EQ(2u, num_toolbar_items());
 
   // Order is now C, A. Flip it.
-  toolbar_model()->MoveExtensionIcon(browser_action_a(), 0);
+  toolbar_model()->MoveExtensionIcon(browser_action_a()->id(), 0);
   EXPECT_EQ(3u, observer()->moved_count());
   EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
   EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(1u));
 
   // Move A to the location it already occupies.
-  toolbar_model()->MoveExtensionIcon(browser_action_a(), 0);
+  toolbar_model()->MoveExtensionIcon(browser_action_a()->id(), 0);
   EXPECT_EQ(4u, observer()->moved_count());
   EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
   EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(1u));
@@ -435,7 +555,7 @@ TEST_F(ExtensionToolbarModelUnitTest,
   EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(2u));
 
   // Move browser_action_b() to be first.
-  toolbar_model()->MoveExtensionIcon(browser_action_b(), 0);
+  toolbar_model()->MoveExtensionIcon(browser_action_b()->id(), 0);
   EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(0u));
 
   // Uninstall Extension B.
@@ -659,11 +779,13 @@ TEST_F(ExtensionToolbarModelUnitTest, ExtensionToolbarSizeAfterPrefChange) {
   ASSERT_TRUE(AddBrowserActionExtensions());
   EXPECT_EQ(3u, num_toolbar_items());
 
-  // Should be at max size (-1).
-  EXPECT_EQ(-1, toolbar_model()->GetVisibleIconCount());
+  // Should be at max size.
+  EXPECT_TRUE(toolbar_model()->all_icons_visible());
+  EXPECT_EQ(num_toolbar_items(), toolbar_model()->visible_icon_count());
   toolbar_model()->OnExtensionToolbarPrefChange();
   // Should still be at max size.
-  EXPECT_EQ(-1, toolbar_model()->GetVisibleIconCount());
+  EXPECT_TRUE(toolbar_model()->all_icons_visible());
+  EXPECT_EQ(num_toolbar_items(), toolbar_model()->visible_icon_count());
 }
 
 // Test that, in the absence of the extension-action-redesign switch, the
@@ -739,6 +861,171 @@ TEST_F(ExtensionToolbarModelUnitTest,
   EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(2u));
 }
 
+TEST_F(ExtensionToolbarModelUnitTest, ExtensionToolbarIncognitoModeTest) {
+  Init();
+  ASSERT_TRUE(AddBrowserActionExtensions());
+
+  // Give two extensions incognito access.
+  // Note: We use ExtensionPrefs::SetIsIncognitoEnabled instead of
+  // util::SetIsIncognitoEnabled because the latter tries to reload the
+  // extension, which requries a filepath associated with the extension (and,
+  // for this test, reloading the extension is irrelevant to us).
+  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile());
+  extension_prefs->SetIsIncognitoEnabled(browser_action_b()->id(), true);
+  extension_prefs->SetIsIncognitoEnabled(browser_action_c()->id(), true);
+
+  util::SetIsIncognitoEnabled(browser_action_b()->id(), profile(), true);
+  util::SetIsIncognitoEnabled(browser_action_c()->id(), profile(), true);
+
+  // Move C to the second index.
+  toolbar_model()->MoveExtensionIcon(browser_action_c()->id(), 1u);
+  // Set visible count to 2 so that c is overflowed. State is A C [B].
+  toolbar_model()->SetVisibleIconCount(2);
+  EXPECT_EQ(1u, observer()->moved_count());
+
+  // Get an incognito profile and toolbar.
+  ExtensionToolbarModel* incognito_model =
+      CreateToolbarModelForProfile(profile()->GetOffTheRecordProfile());
+
+  ExtensionToolbarModelTestObserver incognito_observer(incognito_model);
+  EXPECT_EQ(0u, incognito_observer.moved_count());
+
+  // We should have two items, C and B, and the order should be preserved from
+  // the original model.
+  EXPECT_EQ(2u, incognito_model->toolbar_items().size());
+  EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(0u, incognito_model));
+  EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u, incognito_model));
+
+  // Extensions in the overflow menu in the regular toolbar should remain in
+  // overflow in the incognito toolbar. So, we should have C [B].
+  EXPECT_EQ(1u, incognito_model->visible_icon_count());
+  // The regular model should still have two icons visible.
+  EXPECT_EQ(2u, toolbar_model()->visible_icon_count());
+
+  // Changing the incognito model size should not affect the regular model.
+  incognito_model->SetVisibleIconCount(0);
+  EXPECT_EQ(0u, incognito_model->visible_icon_count());
+  EXPECT_EQ(2u, toolbar_model()->visible_icon_count());
+
+  // Expanding the incognito model to 2 should register as "all icons"
+  // since it is all of the incognito-enabled extensions.
+  incognito_model->SetVisibleIconCount(2u);
+  EXPECT_EQ(2u, incognito_model->visible_icon_count());
+  EXPECT_TRUE(incognito_model->all_icons_visible());
+
+  // Moving icons in the incognito toolbar should not affect the regular
+  // toolbar. Incognito currently has C B...
+  incognito_model->MoveExtensionIcon(browser_action_b()->id(), 0u);
+  // So now it should be B C...
+  EXPECT_EQ(1u, incognito_observer.moved_count());
+  EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(0u, incognito_model));
+  EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(1u, incognito_model));
+  // ... and the regular toolbar should be unaffected.
+  EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
+  EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(1u));
+  EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(2u));
+
+  // Similarly, the observer for the regular model should not have received
+  // any updates.
+  EXPECT_EQ(1u, observer()->moved_count());
+
+  // And performing moves on the regular model should have no effect on the
+  // incognito model or its observers.
+  toolbar_model()->MoveExtensionIcon(browser_action_c()->id(), 2u);
+  EXPECT_EQ(2u, observer()->moved_count());
+  EXPECT_EQ(1u, incognito_observer.moved_count());
+}
+
+// Test that enabling extensions incognito with an active incognito profile
+// works.
+TEST_F(ExtensionToolbarModelUnitTest,
+       ExtensionToolbarIncognitoEnableExtension) {
+  Init();
+
+  const char* kManifest =
+      "{"
+      "  \"name\": \"%s\","
+      "  \"version\": \"1.0\","
+      "  \"manifest_version\": 2,"
+      "  \"browser_action\": {}"
+      "}";
+
+  // For this test, we need to have "real" extension files, because we need to
+  // be able to reload them during the incognito process. Since the toolbar
+  // needs to be notified of the reload, we need it this time (as opposed to
+  // above, where we simply set the prefs before the incognito bar was
+  // created.
+  TestExtensionDir dir1;
+  dir1.WriteManifest(base::StringPrintf(kManifest, "incognito1"));
+  TestExtensionDir dir2;
+  dir2.WriteManifest(base::StringPrintf(kManifest, "incognito2"));
+
+  TestExtensionDir* dirs[] = { &dir1, &dir2 };
+  const Extension* extensions[] = { nullptr, nullptr };
+  for (size_t i = 0; i < arraysize(dirs); ++i) {
+    // The extension id will be calculated from the file path; we need this to
+    // wait for the extension to load.
+    base::FilePath path_for_id =
+        base::MakeAbsoluteFilePath(dirs[i]->unpacked_path());
+    std::string id = crx_file::id_util::GenerateIdForPath(path_for_id);
+    TestExtensionRegistryObserver observer(registry(), id);
+    UnpackedInstaller::Create(service())->Load(dirs[i]->unpacked_path());
+    observer.WaitForExtensionLoaded();
+    extensions[i] = registry()->enabled_extensions().GetByID(id);
+    ASSERT_TRUE(extensions[i]);
+  }
+
+  // For readability, alias to A and B. Since we'll be reloading these
+  // extensions, we also can't rely on pointers.
+  std::string extension_a = extensions[0]->id();
+  std::string extension_b = extensions[1]->id();
+
+  // The first model should have both extensions visible.
+  EXPECT_EQ(2u, toolbar_model()->toolbar_items().size());
+  EXPECT_EQ(extension_a, GetExtensionAtIndex(0)->id());
+  EXPECT_EQ(extension_b, GetExtensionAtIndex(1)->id());
+
+  // Set the model to only show one extension, so the order is A [B].
+  toolbar_model()->SetVisibleIconCount(1u);
+
+  // Get an incognito profile and toolbar.
+  ExtensionToolbarModel* incognito_model =
+      CreateToolbarModelForProfile(profile()->GetOffTheRecordProfile());
+  ExtensionToolbarModelTestObserver incognito_observer(incognito_model);
+
+  // Right now, no extensions are enabled in incognito mode.
+  EXPECT_EQ(0u, incognito_model->toolbar_items().size());
+
+  // Set extension b (which is overflowed) to be enabled in incognito. This
+  // results in b reloading, so wait for it.
+  {
+    TestExtensionRegistryObserver observer(registry(), extension_b);
+    util::SetIsIncognitoEnabled(extension_b, profile(), true);
+    observer.WaitForExtensionLoaded();
+  }
+
+  // Now, we should have one icon in the incognito bar. But, since B is
+  // overflowed in the main bar, it shouldn't be visible.
+  EXPECT_EQ(1u, incognito_model->toolbar_items().size());
+  EXPECT_EQ(extension_b, GetExtensionAtIndex(0u, incognito_model)->id());
+  EXPECT_EQ(0u, incognito_model->visible_icon_count());
+
+  // Also enable extension a for incognito (again, wait for the reload).
+  {
+    TestExtensionRegistryObserver observer(registry(), extension_a);
+    util::SetIsIncognitoEnabled(extension_a, profile(), true);
+    observer.WaitForExtensionLoaded();
+  }
+
+  // Now, both extensions should be enabled in incognito mode. In addition, the
+  // incognito toolbar should have expanded to show extension a (since it isn't
+  // overflowed in the main bar).
+  EXPECT_EQ(2u, incognito_model->toolbar_items().size());
+  EXPECT_EQ(extension_a, GetExtensionAtIndex(0u, incognito_model)->id());
+  EXPECT_EQ(extension_b, GetExtensionAtIndex(1u, incognito_model)->id());
+  EXPECT_EQ(1u, incognito_model->visible_icon_count());
+}
+
 // Test that hiding actions on the toolbar results in sending them to the
 // overflow menu when the redesign switch is enabled.
 TEST_F(ExtensionToolbarModelUnitTest,
@@ -758,7 +1045,7 @@ TEST_F(ExtensionToolbarModelUnitTest,
 
   // Sanity check: Order should start as A B C, with all three visible.
   EXPECT_EQ(3u, num_toolbar_items());
-  EXPECT_EQ(-1, toolbar_model()->GetVisibleIconCount());  // -1 = 'all'.
+  EXPECT_TRUE(toolbar_model()->all_icons_visible());
   EXPECT_EQ(extension_a, GetExtensionAtIndex(0u));
   EXPECT_EQ(extension_b, GetExtensionAtIndex(1u));
   EXPECT_EQ(extension_c, GetExtensionAtIndex(2u));
@@ -779,7 +1066,7 @@ TEST_F(ExtensionToolbarModelUnitTest,
 
   // Thus, the order should be A C B, with B in the overflow.
   EXPECT_EQ(3u, num_toolbar_items());
-  EXPECT_EQ(2, toolbar_model()->GetVisibleIconCount());
+  EXPECT_EQ(2u, toolbar_model()->visible_icon_count());
   EXPECT_EQ(extension_a, GetExtensionAtIndex(0u));
   EXPECT_EQ(extension_c, GetExtensionAtIndex(1u));
   EXPECT_EQ(extension_b, GetExtensionAtIndex(2u));
@@ -790,7 +1077,7 @@ TEST_F(ExtensionToolbarModelUnitTest,
       prefs, extension_a->id(), false);
   // Thus, the order should be C A B, with A and B in the overflow.
   EXPECT_EQ(3u, num_toolbar_items());
-  EXPECT_EQ(1, toolbar_model()->GetVisibleIconCount());
+  EXPECT_EQ(1u, toolbar_model()->visible_icon_count());
   EXPECT_EQ(extension_c, GetExtensionAtIndex(0u));
   EXPECT_EQ(extension_a, GetExtensionAtIndex(1u));
   EXPECT_EQ(extension_b, GetExtensionAtIndex(2u));
@@ -802,7 +1089,7 @@ TEST_F(ExtensionToolbarModelUnitTest,
       prefs, extension_a->id(), true);
   // So order is C A B, with only B in the overflow.
   EXPECT_EQ(3u, num_toolbar_items());
-  EXPECT_EQ(2, toolbar_model()->GetVisibleIconCount());
+  EXPECT_EQ(2u, toolbar_model()->visible_icon_count());
   EXPECT_EQ(extension_c, GetExtensionAtIndex(0u));
   EXPECT_EQ(extension_a, GetExtensionAtIndex(1u));
   EXPECT_EQ(extension_b, GetExtensionAtIndex(2u));
@@ -811,10 +1098,179 @@ TEST_F(ExtensionToolbarModelUnitTest,
   ExtensionActionAPI::SetBrowserActionVisibility(
       prefs, extension_b->id(), true);
   EXPECT_EQ(3u, num_toolbar_items());
-  EXPECT_EQ(-1, toolbar_model()->GetVisibleIconCount());  // -1 = 'all'
+  EXPECT_TRUE(toolbar_model()->all_icons_visible());
   EXPECT_EQ(extension_c, GetExtensionAtIndex(0u));
   EXPECT_EQ(extension_a, GetExtensionAtIndex(1u));
   EXPECT_EQ(extension_b, GetExtensionAtIndex(2u));
 }
 
+// Test that toolbar actions can pop themselves out of overflow if they want
+// to act on a given web contents.
+TEST_F(ExtensionToolbarModelUnitTest, ToolbarActionsPopOutToAct) {
+  // Extensions popping themselves out to act is part of the toolbar redesign,
+  // and hidden behind a flag.
+  FeatureSwitch::ScopedOverride enable_redesign(
+      FeatureSwitch::extension_action_redesign(), true);
+  Init();
+  TestWebContentsFactory web_contents_factory;
+
+  ASSERT_TRUE(AddActionExtensions());
+
+  // We should start in the order of "browser action" "page action" "no action"
+  // and have all extensions visible.
+  EXPECT_EQ(3u, num_toolbar_items());
+  EXPECT_TRUE(toolbar_model()->all_icons_visible());
+  EXPECT_EQ(browser_action(), GetExtensionAtIndex(0u));
+  EXPECT_EQ(page_action(), GetExtensionAtIndex(1u));
+  EXPECT_EQ(no_action(), GetExtensionAtIndex(2u));
+
+  // Shrink the model to only show one action, and move the page action to the
+  // end.
+  toolbar_model()->SetVisibleIconCount(1);
+  toolbar_model()->MoveExtensionIcon(page_action()->id(), 2u);
+
+  // Quickly verify that the move/visible count worked.
+  EXPECT_EQ(1u, toolbar_model()->visible_icon_count());
+  EXPECT_EQ(browser_action(), GetExtensionAtIndex(0u));
+  EXPECT_EQ(no_action(), GetExtensionAtIndex(1u));
+  EXPECT_EQ(page_action(), GetExtensionAtIndex(2u));
+
+  // Create two test web contents, and a session tab helper for each. We need
+  // a session tab helper, since we rely on tab ids.
+  content::WebContents* web_contents =
+      web_contents_factory.CreateWebContents(profile());
+  ASSERT_TRUE(web_contents);
+  SessionTabHelper::CreateForWebContents(web_contents);
+  content::WebContents* second_web_contents =
+      web_contents_factory.CreateWebContents(profile());
+  ASSERT_TRUE(second_web_contents);
+  SessionTabHelper::CreateForWebContents(second_web_contents);
+
+  // Find the tab ids, ensure that the two web contents have different ids, and
+  // verify that neither is -1 (invalid).
+  int tab_id = SessionTabHelper::IdForTab(web_contents);
+  int second_tab_id = SessionTabHelper::IdForTab(second_web_contents);
+  EXPECT_NE(tab_id, second_tab_id);
+  EXPECT_NE(-1, second_tab_id);
+  EXPECT_NE(-1, tab_id);
+
+  // First, check the model order for the first tab. Since we haven't changed
+  // anything (i.e., no extensions want to act), this should be the same as we
+  // left it: "browser action", "no action", "page action", with only one
+  // visible.
+  ExtensionList tab_order = toolbar_model()->GetItemOrderForTab(web_contents);
+  ASSERT_EQ(3u, tab_order.size());
+  EXPECT_EQ(browser_action(), tab_order[0]);
+  EXPECT_EQ(no_action(), tab_order[1]);
+  EXPECT_EQ(page_action(), tab_order[2]);
+  EXPECT_EQ(1u, toolbar_model()->GetVisibleIconCountForTab(web_contents));
+  // And we should have no notifications to reorder the toolbar.
+  EXPECT_EQ(0u, observer()->reorder_count());
+
+  // Make "page action" want to act by making it's page action visible on the
+  // first tab, and notify the API of the change.
+  ExtensionActionManager* action_manager =
+      ExtensionActionManager::Get(profile());
+  ExtensionAction* action = action_manager->GetExtensionAction(*page_action());
+  ASSERT_TRUE(action);
+  action->SetIsVisible(tab_id, true);
+  ExtensionActionAPI* extension_action_api = ExtensionActionAPI::Get(profile());
+  extension_action_api->NotifyChange(action, web_contents, profile());
+
+  // This should result in "page action" being popped out of the overflow menu.
+  // This has two visible effects:
+  // - page action should move to the second index (the one right after the last
+  //   originally-visible).
+  // - The visible count should increase by one (so page action is visible).
+  tab_order = toolbar_model()->GetItemOrderForTab(web_contents);
+  ASSERT_EQ(3u, tab_order.size());
+  EXPECT_EQ(browser_action(), tab_order[0]);
+  EXPECT_EQ(page_action(), tab_order[1]);
+  EXPECT_EQ(no_action(), tab_order[2]);
+  EXPECT_EQ(2u, toolbar_model()->GetVisibleIconCountForTab(web_contents));
+  // We should also have been told to reorder the toolbar.
+  EXPECT_EQ(1u, observer()->reorder_count());
+
+  // This should not have any effect on the second tab, which should still have
+  // the original order and visible count.
+  tab_order = toolbar_model()->GetItemOrderForTab(second_web_contents);
+  ASSERT_EQ(3u, tab_order.size());
+  EXPECT_EQ(browser_action(), tab_order[0]);
+  EXPECT_EQ(no_action(), tab_order[1]);
+  EXPECT_EQ(page_action(), tab_order[2]);
+  EXPECT_EQ(1u,
+            toolbar_model()->GetVisibleIconCountForTab(second_web_contents));
+
+  // Now, set the action to be hidden again, and notify of the change.
+  action->SetIsVisible(tab_id, false);
+  extension_action_api->NotifyChange(action, web_contents, profile());
+  // The order and visible count should return to normal (the page action should
+  // move back to its original index in overflow). So, order should be "browser
+  // action", "no action", "page action".
+  tab_order = toolbar_model()->GetItemOrderForTab(web_contents);
+  ASSERT_EQ(3u, tab_order.size());
+  EXPECT_EQ(browser_action(), tab_order[0]);
+  EXPECT_EQ(no_action(), tab_order[1]);
+  EXPECT_EQ(page_action(), tab_order[2]);
+  EXPECT_EQ(1u, toolbar_model()->GetVisibleIconCountForTab(web_contents));
+  // This should also result in a reorder.
+  EXPECT_EQ(2u, observer()->reorder_count());
+
+  // Move page action to the first index (so it's naturally visible), and make
+  // it want to act.
+  toolbar_model()->MoveExtensionIcon(page_action()->id(), 0u);
+  action->SetIsVisible(tab_id, true);
+  extension_action_api->NotifyChange(action, web_contents, profile());
+  // Since the action is already visible, this should have no effect - the order
+  // and visible count should remain unchanged. Order is "page action", "browser
+  // action", "no action".
+  tab_order = toolbar_model()->GetItemOrderForTab(web_contents);
+  ASSERT_EQ(3u, tab_order.size());
+  EXPECT_EQ(page_action(), tab_order[0]);
+  EXPECT_EQ(browser_action(), tab_order[1]);
+  EXPECT_EQ(no_action(), tab_order[2]);
+  EXPECT_EQ(1u, toolbar_model()->GetVisibleIconCountForTab(web_contents));
+
+  // We should still be able to increase the size of the model, and to move the
+  // page action.
+  toolbar_model()->SetVisibleIconCount(2);
+  toolbar_model()->MoveExtensionIcon(page_action()->id(), 1u);
+  tab_order = toolbar_model()->GetItemOrderForTab(web_contents);
+  ASSERT_EQ(3u, tab_order.size());
+  EXPECT_EQ(browser_action(), tab_order[0]);
+  EXPECT_EQ(page_action(), tab_order[1]);
+  EXPECT_EQ(no_action(), tab_order[2]);
+  EXPECT_EQ(2u, toolbar_model()->GetVisibleIconCountForTab(web_contents));
+
+  // Neither of the above operations should have precipitated a reorder.
+  EXPECT_EQ(2u, observer()->reorder_count());
+
+  // If we moved the page action, the move should remain in effect even after
+  // the action no longer wants to act.
+  action->SetIsVisible(tab_id, false);
+  extension_action_api->NotifyChange(action, web_contents, profile());
+  tab_order = toolbar_model()->GetItemOrderForTab(web_contents);
+  ASSERT_EQ(3u, tab_order.size());
+  EXPECT_EQ(browser_action(), tab_order[0]);
+  EXPECT_EQ(page_action(), tab_order[1]);
+  EXPECT_EQ(no_action(), tab_order[2]);
+  EXPECT_EQ(2u, toolbar_model()->GetVisibleIconCountForTab(web_contents));
+  // The above change should *not* require a reorder, because the extension is
+  // in a new, visible spot and doesn't need to change its position.
+  EXPECT_EQ(2u, observer()->reorder_count());
+
+  // Test the edge case of having no icons visible.
+  toolbar_model()->SetVisibleIconCount(0);
+  EXPECT_EQ(0u, toolbar_model()->GetVisibleIconCountForTab(web_contents));
+  action->SetIsVisible(tab_id, true);
+  extension_action_api->NotifyChange(action, web_contents, profile());
+  tab_order = toolbar_model()->GetItemOrderForTab(web_contents);
+  ASSERT_EQ(3u, tab_order.size());
+  EXPECT_EQ(page_action(), tab_order[0]);
+  EXPECT_EQ(browser_action(), tab_order[1]);
+  EXPECT_EQ(no_action(), tab_order[2]);
+  EXPECT_EQ(1u, toolbar_model()->GetVisibleIconCountForTab(web_contents));
+  EXPECT_EQ(3u, observer()->reorder_count());
+}
+
 }  // namespace extensions