blob: 744420f7c65ade08499e4f1eb50cb39d7af32a77 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "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.
scoped_refptr<const Extension> GetActionExtension(const std::string& name,
const char* action_key) {
DictionaryBuilder manifest;
manifest.Set("name", name)
.Set("description", "An extension")
.Set("manifest_version", 2)
.Set("version", "1.0.0");
if (action_key)
manifest.Set(action_key, DictionaryBuilder().Pass());
return ExtensionBuilder().SetManifest(manifest.Pass())
.SetID(crx_file::id_util::GenerateId(name))
.Build();
}
// A simple observer that tracks the number of times certain events occur.
class ExtensionToolbarModelTestObserver
: public ExtensionToolbarModel::Observer {
public:
explicit ExtensionToolbarModelTestObserver(ExtensionToolbarModel* model);
~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:
void ToolbarExtensionAdded(const Extension* extension, int index) override {
++inserted_count_;
}
void ToolbarExtensionRemoved(const Extension* extension) override {
++removed_count_;
}
void ToolbarExtensionMoved(const Extension* extension, int index) override {
++moved_count_;
}
void ToolbarExtensionUpdated(const Extension* extension) override {}
bool ShowExtensionActionPopup(const Extension* extension,
bool grant_active_tab) override {
return false;
}
void ToolbarVisibleCountChanged() override {}
void ToolbarHighlightModeChanged(bool is_highlighting) override {
// Add one if highlighting, subtract one if not.
highlight_mode_count_ += is_highlighting ? 1 : -1;
}
void OnToolbarReorderNecessary(content::WebContents* web_contents) override {
++reorder_count_;
}
Browser* GetBrowser() override { return NULL; }
ExtensionToolbarModel* model_;
size_t inserted_count_;
size_t removed_count_;
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_(0),
removed_count_(0),
moved_count_(0),
highlight_mode_count_(0),
reorder_count_(0) {
model_->AddObserver(this);
}
ExtensionToolbarModelTestObserver::~ExtensionToolbarModelTestObserver() {
model_->RemoveObserver(this);
}
} // namespace
class ExtensionToolbarModelUnitTest : public ExtensionServiceTestBase {
protected:
// Initialize the ExtensionService, ExtensionToolbarModel, and
// 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;
testing::AssertionResult RemoveExtension(
const scoped_refptr<const Extension>& extension) WARN_UNUSED_RESULT;
// Adds three extensions, all with browser actions.
testing::AssertionResult AddBrowserActionExtensions() WARN_UNUSED_RESULT;
// Adds three extensions, one each for browser action, page action, and no
// action, and are added in that order.
testing::AssertionResult AddActionExtensions() WARN_UNUSED_RESULT;
// 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_; }
const ExtensionToolbarModelTestObserver* observer() const {
return model_observer_.get();
}
size_t num_toolbar_items() const {
return toolbar_model_->toolbar_items().size();
}
const Extension* browser_action_a() const { return browser_action_a_.get(); }
const Extension* browser_action_b() const { return browser_action_b_.get(); }
const Extension* browser_action_c() const { return browser_action_c_.get(); }
const Extension* browser_action() const {
return browser_action_extension_.get();
}
const Extension* page_action() const { return page_action_extension_.get(); }
const Extension* no_action() const { return no_action_extension_.get(); }
private:
// Verifies that all extensions in |extensions| are added successfully.
testing::AssertionResult AddAndVerifyExtensions(
const ExtensionList& extensions);
// 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.
scoped_ptr<ExtensionToolbarModelTestObserver> model_observer_;
// Sample extensions with only browser actions.
scoped_refptr<const Extension> browser_action_a_;
scoped_refptr<const Extension> browser_action_b_;
scoped_refptr<const Extension> browser_action_c_;
// Sample extensions with different kinds of actions.
scoped_refptr<const Extension> browser_action_extension_;
scoped_refptr<const Extension> page_action_extension_;
scoped_refptr<const Extension> no_action_extension_;
};
void ExtensionToolbarModelUnitTest::Init() {
InitializeEmptyExtensionService();
toolbar_model_ = CreateToolbarModelForProfile(profile());
model_observer_.reset(new ExtensionToolbarModelTestObserver(toolbar_model_));
}
void ExtensionToolbarModelUnitTest::TearDown() {
model_observer_.reset();
ExtensionServiceTestBase::TearDown();
}
testing::AssertionResult ExtensionToolbarModelUnitTest::AddExtension(
const scoped_refptr<const Extension>& extension) {
if (registry()->enabled_extensions().GetByID(extension->id())) {
return testing::AssertionFailure() << "Extension " << extension->name() <<
" already installed!";
}
service()->AddExtension(extension.get());
if (!registry()->enabled_extensions().GetByID(extension->id())) {
return testing::AssertionFailure() << "Failed to install extension: " <<
extension->name();
}
return testing::AssertionSuccess();
}
testing::AssertionResult ExtensionToolbarModelUnitTest::RemoveExtension(
const scoped_refptr<const Extension>& extension) {
if (!registry()->enabled_extensions().GetByID(extension->id())) {
return testing::AssertionFailure() << "Extension " << extension->name() <<
" not installed!";
}
service()->UnloadExtension(extension->id(),
UnloadedExtensionInfo::REASON_DISABLE);
if (registry()->enabled_extensions().GetByID(extension->id())) {
return testing::AssertionFailure() << "Failed to unload extension: " <<
extension->name();
}
return testing::AssertionSuccess();
}
testing::AssertionResult ExtensionToolbarModelUnitTest::AddActionExtensions() {
browser_action_extension_ =
GetActionExtension("browser_action", manifest_keys::kBrowserAction);
page_action_extension_ =
GetActionExtension("page_action", manifest_keys::kPageAction);
no_action_extension_ = GetActionExtension("no_action", NULL);
ExtensionList extensions;
extensions.push_back(browser_action_extension_);
extensions.push_back(page_action_extension_);
extensions.push_back(no_action_extension_);
return AddAndVerifyExtensions(extensions);
}
testing::AssertionResult
ExtensionToolbarModelUnitTest::AddBrowserActionExtensions() {
browser_action_a_ =
GetActionExtension("browser_actionA", manifest_keys::kBrowserAction);
browser_action_b_ =
GetActionExtension("browser_actionB", manifest_keys::kBrowserAction);
browser_action_c_ =
GetActionExtension("browser_actionC", manifest_keys::kBrowserAction);
ExtensionList extensions;
extensions.push_back(browser_action_a_);
extensions.push_back(browser_action_b_);
extensions.push_back(browser_action_c_);
return AddAndVerifyExtensions(extensions);
}
const Extension* ExtensionToolbarModelUnitTest::GetExtensionAtIndex(
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();
iter != extensions.end(); ++iter) {
if (!AddExtension(*iter)) {
return testing::AssertionFailure() << "Failed to install extension: " <<
(*iter)->name();
}
}
return testing::AssertionSuccess();
}
// A basic test for extensions with browser actions showing up in the toolbar.
TEST_F(ExtensionToolbarModelUnitTest, BasicExtensionToolbarModelTest) {
Init();
// Load an extension with no browser action.
scoped_refptr<const Extension> extension1 =
GetActionExtension("no_action", NULL);
ASSERT_TRUE(AddExtension(extension1));
// This extension should not be in the model (has no browser action).
EXPECT_EQ(0u, observer()->inserted_count());
EXPECT_EQ(0u, num_toolbar_items());
EXPECT_EQ(NULL, GetExtensionAtIndex(0u));
// Load an extension with a browser action.
scoped_refptr<const Extension> extension2 =
GetActionExtension("browser_action", manifest_keys::kBrowserAction);
ASSERT_TRUE(AddExtension(extension2));
// We should now find our extension in the model.
EXPECT_EQ(1u, observer()->inserted_count());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(extension2.get(), GetExtensionAtIndex(0u));
// Should be a no-op, but still fires the events.
toolbar_model()->MoveExtensionIcon(extension2->id(), 0);
EXPECT_EQ(1u, observer()->moved_count());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(extension2.get(), GetExtensionAtIndex(0u));
// Remove the extension and verify.
ASSERT_TRUE(RemoveExtension(extension2));
EXPECT_EQ(1u, observer()->removed_count());
EXPECT_EQ(0u, num_toolbar_items());
EXPECT_EQ(NULL, GetExtensionAtIndex(0u));
}
// Test various different reorderings, removals, and reinsertions.
TEST_F(ExtensionToolbarModelUnitTest, ExtensionToolbarReorderAndReinsert) {
Init();
// Add the three browser action extensions.
ASSERT_TRUE(AddBrowserActionExtensions());
// Verify the three extensions are in the model in the proper order.
EXPECT_EQ(3u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(2u));
// Order is now A, B, C. Let's put C first.
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));
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(2u));
// Order is now C, A, B. Let's put A last.
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));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(2u));
// Order is now C, B, A. Let's remove B.
ASSERT_TRUE(RemoveExtension(browser_action_b()));
EXPECT_EQ(1u, observer()->removed_count());
EXPECT_EQ(2u, num_toolbar_items());
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(1u));
// Load extension B again.
ASSERT_TRUE(AddExtension(browser_action_b()));
// Extension B loaded again.
EXPECT_EQ(4u, observer()->inserted_count());
EXPECT_EQ(3u, num_toolbar_items());
// Make sure it gets its old spot in the list.
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
// Unload B again.
ASSERT_TRUE(RemoveExtension(browser_action_b()));
EXPECT_EQ(2u, observer()->removed_count());
EXPECT_EQ(2u, num_toolbar_items());
// Order is now C, A. Flip it.
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()->id(), 0);
EXPECT_EQ(4u, observer()->moved_count());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(1u));
// Order is now A, C. Remove C.
ASSERT_TRUE(RemoveExtension(browser_action_c()));
EXPECT_EQ(3u, observer()->removed_count());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
// Load extension C again.
ASSERT_TRUE(AddExtension(browser_action_c()));
// Extension C loaded again.
EXPECT_EQ(5u, observer()->inserted_count());
EXPECT_EQ(2u, num_toolbar_items());
// Make sure it gets its old spot in the list (at the very end).
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(1u));
}
// Test that order persists after unloading and disabling, but not across
// uninstallation.
TEST_F(ExtensionToolbarModelUnitTest,
ExtensionToolbarUnloadDisableAndUninstall) {
Init();
// Add the three browser action extensions.
ASSERT_TRUE(AddBrowserActionExtensions());
// Verify the three extensions are in the model in the proper order: A, B, C.
EXPECT_EQ(3u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(2u));
// Unload B, then C, then A, and then reload C, then A, then B.
ASSERT_TRUE(RemoveExtension(browser_action_b()));
ASSERT_TRUE(RemoveExtension(browser_action_c()));
ASSERT_TRUE(RemoveExtension(browser_action_a()));
EXPECT_EQ(0u, num_toolbar_items()); // Sanity check: all gone?
ASSERT_TRUE(AddExtension(browser_action_c()));
ASSERT_TRUE(AddExtension(browser_action_a()));
ASSERT_TRUE(AddExtension(browser_action_b()));
EXPECT_EQ(3u, num_toolbar_items()); // Sanity check: all back?
EXPECT_EQ(0u, observer()->moved_count());
// Even though we unloaded and reloaded in a different order, the original
// order (A, B, C) should be preserved.
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(2u));
// Disabling extensions should also preserve order.
service()->DisableExtension(browser_action_b()->id(),
Extension::DISABLE_USER_ACTION);
service()->DisableExtension(browser_action_c()->id(),
Extension::DISABLE_USER_ACTION);
service()->DisableExtension(browser_action_a()->id(),
Extension::DISABLE_USER_ACTION);
service()->EnableExtension(browser_action_c()->id());
service()->EnableExtension(browser_action_a()->id());
service()->EnableExtension(browser_action_b()->id());
// Make sure we still get the original A, B, C order.
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(2u));
// Move browser_action_b() to be first.
toolbar_model()->MoveExtensionIcon(browser_action_b()->id(), 0);
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(0u));
// Uninstall Extension B.
service()->UninstallExtension(browser_action_b()->id(),
UNINSTALL_REASON_FOR_TESTING,
base::Bind(&base::DoNothing),
NULL); // Ignore error.
// List contains only A and C now. Validate that.
EXPECT_EQ(2u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(1u));
ASSERT_TRUE(AddExtension(browser_action_b()));
// Make sure Extension B is _not_ first (its old position should have been
// forgotten at uninstall time). Order should be A, C, B.
EXPECT_EQ(3u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(2u));
}
TEST_F(ExtensionToolbarModelUnitTest, ReorderOnPrefChange) {
Init();
// Add the three browser action extensions.
ASSERT_TRUE(AddBrowserActionExtensions());
EXPECT_EQ(3u, num_toolbar_items());
// Change the value of the toolbar preference.
ExtensionIdList new_order;
new_order.push_back(browser_action_c()->id());
new_order.push_back(browser_action_b()->id());
ExtensionPrefs::Get(profile())->SetToolbarOrder(new_order);
// Verify order is changed.
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(2u));
}
TEST_F(ExtensionToolbarModelUnitTest, ExtensionToolbarHighlightMode) {
Init();
EXPECT_FALSE(toolbar_model()->HighlightExtensions(ExtensionIdList()));
EXPECT_EQ(0, observer()->highlight_mode_count());
// Add the three browser action extensions.
ASSERT_TRUE(AddBrowserActionExtensions());
EXPECT_EQ(3u, num_toolbar_items());
// Highlight one extension.
ExtensionIdList extension_ids;
extension_ids.push_back(browser_action_b()->id());
toolbar_model()->HighlightExtensions(extension_ids);
EXPECT_EQ(1, observer()->highlight_mode_count());
EXPECT_TRUE(toolbar_model()->is_highlighting());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(0u));
// Stop highlighting.
toolbar_model()->StopHighlighting();
EXPECT_EQ(0, observer()->highlight_mode_count());
EXPECT_FALSE(toolbar_model()->is_highlighting());
// Verify that the extensions are back to normal.
EXPECT_EQ(3u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(2u));
// Call stop highlighting a second time (shouldn't be notified).
toolbar_model()->StopHighlighting();
EXPECT_EQ(0, observer()->highlight_mode_count());
EXPECT_FALSE(toolbar_model()->is_highlighting());
// Highlight all extensions.
extension_ids.clear();
extension_ids.push_back(browser_action_a()->id());
extension_ids.push_back(browser_action_b()->id());
extension_ids.push_back(browser_action_c()->id());
toolbar_model()->HighlightExtensions(extension_ids);
EXPECT_EQ(1, observer()->highlight_mode_count());
EXPECT_EQ(3u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(2u));
// Highlight only extension b (shrink the highlight list).
extension_ids.clear();
extension_ids.push_back(browser_action_b()->id());
toolbar_model()->HighlightExtensions(extension_ids);
EXPECT_EQ(2, observer()->highlight_mode_count());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(0u));
// Highlight extensions a and b (grow the highlight list).
extension_ids.clear();
extension_ids.push_back(browser_action_a()->id());
extension_ids.push_back(browser_action_b()->id());
toolbar_model()->HighlightExtensions(extension_ids);
EXPECT_EQ(3, observer()->highlight_mode_count());
EXPECT_EQ(2u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
// Highlight no extensions (empty the highlight list).
extension_ids.clear();
toolbar_model()->HighlightExtensions(extension_ids);
EXPECT_EQ(2, observer()->highlight_mode_count());
EXPECT_FALSE(toolbar_model()->is_highlighting());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(2u));
}
TEST_F(ExtensionToolbarModelUnitTest, ExtensionToolbarHighlightModeRemove) {
Init();
// Add the three browser action extensions.
ASSERT_TRUE(AddBrowserActionExtensions());
EXPECT_EQ(3u, num_toolbar_items());
// Highlight two of the extensions.
ExtensionIdList extension_ids;
extension_ids.push_back(browser_action_a()->id());
extension_ids.push_back(browser_action_b()->id());
toolbar_model()->HighlightExtensions(extension_ids);
EXPECT_TRUE(toolbar_model()->is_highlighting());
EXPECT_EQ(1, observer()->highlight_mode_count());
EXPECT_EQ(2u, num_toolbar_items());
// Disable one of them - only one should remain highlighted.
service()->DisableExtension(browser_action_a()->id(),
Extension::DISABLE_USER_ACTION);
EXPECT_TRUE(toolbar_model()->is_highlighting());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(0u));
// Uninstall the remaining highlighted extension. This should result in
// highlight mode exiting.
service()->UninstallExtension(browser_action_b()->id(),
UNINSTALL_REASON_FOR_TESTING,
base::Bind(&base::DoNothing),
NULL); // Ignore error.
EXPECT_FALSE(toolbar_model()->is_highlighting());
EXPECT_EQ(0, observer()->highlight_mode_count());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(0u));
// Test that removing an unhighlighted extension still works.
// Reinstall extension b, and then highlight extension c.
ASSERT_TRUE(AddExtension(browser_action_b()));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
extension_ids.clear();
extension_ids.push_back(browser_action_c()->id());
toolbar_model()->HighlightExtensions(extension_ids);
EXPECT_EQ(1, observer()->highlight_mode_count());
EXPECT_TRUE(toolbar_model()->is_highlighting());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(0u));
// Uninstalling b should not have visible impact.
service()->UninstallExtension(browser_action_b()->id(),
UNINSTALL_REASON_FOR_TESTING,
base::Bind(&base::DoNothing),
NULL); // Ignore error.
EXPECT_TRUE(toolbar_model()->is_highlighting());
EXPECT_EQ(1, observer()->highlight_mode_count());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(0u));
// When we stop, only extension c should remain.
toolbar_model()->StopHighlighting();
EXPECT_FALSE(toolbar_model()->is_highlighting());
EXPECT_EQ(0, observer()->highlight_mode_count());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(0u));
}
TEST_F(ExtensionToolbarModelUnitTest, ExtensionToolbarHighlightModeAdd) {
Init();
// Add the three browser action extensions.
ASSERT_TRUE(AddBrowserActionExtensions());
EXPECT_EQ(3u, num_toolbar_items());
// Remove one (down to two).
ASSERT_TRUE(RemoveExtension(browser_action_c()));
// Highlight one of the two extensions.
ExtensionIdList extension_ids;
extension_ids.push_back(browser_action_a()->id());
toolbar_model()->HighlightExtensions(extension_ids);
EXPECT_TRUE(toolbar_model()->is_highlighting());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
// Adding a new extension should have no visible effect.
ASSERT_TRUE(AddExtension(browser_action_c()));
EXPECT_TRUE(toolbar_model()->is_highlighting());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
// When we stop highlighting, we should see the new extension show up.
toolbar_model()->StopHighlighting();
EXPECT_FALSE(toolbar_model()->is_highlighting());
EXPECT_EQ(3u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(2u));
}
// Test that the extension toolbar maintains the proper size, even after a pref
// change.
TEST_F(ExtensionToolbarModelUnitTest, ExtensionToolbarSizeAfterPrefChange) {
Init();
// Add the three browser action extensions.
ASSERT_TRUE(AddBrowserActionExtensions());
EXPECT_EQ(3u, num_toolbar_items());
// 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_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
// model only contains extensions with browser actions.
TEST_F(ExtensionToolbarModelUnitTest, TestToolbarExtensionTypesNoSwitch) {
Init();
ASSERT_TRUE(AddActionExtensions());
EXPECT_EQ(1u, num_toolbar_items());
EXPECT_EQ(browser_action(), GetExtensionAtIndex(0u));
}
// Test that, with the extension-action-redesign switch, the model contains
// all types of extensions, except those which should not be displayed on the
// toolbar (like component extensions).
TEST_F(ExtensionToolbarModelUnitTest, TestToolbarExtensionTypesSwitch) {
FeatureSwitch::ScopedOverride enable_redesign(
FeatureSwitch::extension_action_redesign(), true);
Init();
ASSERT_TRUE(AddActionExtensions());
// With the switch on, extensions with page actions and no action should also
// be displayed in the toolbar.
EXPECT_EQ(3u, num_toolbar_items());
EXPECT_EQ(browser_action(), GetExtensionAtIndex(0u));
EXPECT_EQ(page_action(), GetExtensionAtIndex(1u));
EXPECT_EQ(no_action(), GetExtensionAtIndex(2u));
}
// Test that hiding actions on the toolbar results in their removal from the
// model when the redesign switch is not enabled.
TEST_F(ExtensionToolbarModelUnitTest,
ExtensionToolbarActionsVisibilityNoSwitch) {
Init();
ASSERT_TRUE(AddBrowserActionExtensions());
// Sanity check: Order should start as A B C.
EXPECT_EQ(3u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(2u));
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
// By default, all actions should be visible.
EXPECT_TRUE(ExtensionActionAPI::GetBrowserActionVisibility(
prefs, browser_action_a()->id()));
EXPECT_TRUE(ExtensionActionAPI::GetBrowserActionVisibility(
prefs, browser_action_b()->id()));
EXPECT_TRUE(ExtensionActionAPI::GetBrowserActionVisibility(
prefs, browser_action_c()->id()));
// Hiding an action should result in its removal from the toolbar.
ExtensionActionAPI::SetBrowserActionVisibility(
prefs, browser_action_b()->id(), false);
EXPECT_FALSE(ExtensionActionAPI::GetBrowserActionVisibility(
prefs, browser_action_b()->id()));
// Thus, there should now only be two items on the toolbar - A and C.
EXPECT_EQ(2u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_c(), GetExtensionAtIndex(1u));
// Resetting the visibility to 'true' should result in the extension being
// added back at its original position.
ExtensionActionAPI::SetBrowserActionVisibility(
prefs, browser_action_b()->id(), true);
EXPECT_TRUE(ExtensionActionAPI::GetBrowserActionVisibility(
prefs, browser_action_b()->id()));
// So the toolbar order should be A B C.
EXPECT_EQ(3u, num_toolbar_items());
EXPECT_EQ(browser_action_a(), GetExtensionAtIndex(0u));
EXPECT_EQ(browser_action_b(), GetExtensionAtIndex(1u));
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,
ExtensionToolbarActionsVisibilityWithSwitch) {
FeatureSwitch::ScopedOverride enable_redesign(
FeatureSwitch::extension_action_redesign(), true);
Init();
// We choose to use all types of extensions here, since the misnamed
// BrowserActionVisibility is now for toolbar visibility.
ASSERT_TRUE(AddActionExtensions());
// For readability, alias extensions A B C.
const Extension* extension_a = browser_action();
const Extension* extension_b = page_action();
const Extension* extension_c = no_action();
// Sanity check: Order should start as A B C, with all three visible.
EXPECT_EQ(3u, num_toolbar_items());
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));
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
// By default, all actions should be visible.
EXPECT_TRUE(ExtensionActionAPI::GetBrowserActionVisibility(
prefs, extension_a->id()));
EXPECT_TRUE(ExtensionActionAPI::GetBrowserActionVisibility(
prefs, extension_c->id()));
EXPECT_TRUE(ExtensionActionAPI::GetBrowserActionVisibility(
prefs, extension_b->id()));
// Hiding an action should result in it being sent to the overflow menu.
ExtensionActionAPI::SetBrowserActionVisibility(
prefs, extension_b->id(), false);
// Thus, the order should be A C B, with B in the overflow.
EXPECT_EQ(3u, num_toolbar_items());
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));
// Hiding an extension's action should result in it being sent to the overflow
// as well, but as the _first_ extension in the overflow.
ExtensionActionAPI::SetBrowserActionVisibility(
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(1u, toolbar_model()->visible_icon_count());
EXPECT_EQ(extension_c, GetExtensionAtIndex(0u));
EXPECT_EQ(extension_a, GetExtensionAtIndex(1u));
EXPECT_EQ(extension_b, GetExtensionAtIndex(2u));
// Resetting A's visibility to true should send it back to the visible icons
// (and should grow visible icons by 1), but it should be added to the end of
// the visible icon list (not to its original position).
ExtensionActionAPI::SetBrowserActionVisibility(
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(2u, toolbar_model()->visible_icon_count());
EXPECT_EQ(extension_c, GetExtensionAtIndex(0u));
EXPECT_EQ(extension_a, GetExtensionAtIndex(1u));
EXPECT_EQ(extension_b, GetExtensionAtIndex(2u));
// Resetting B to be visible should make the order C A B, with no overflow.
ExtensionActionAPI::SetBrowserActionVisibility(
prefs, extension_b->id(), true);
EXPECT_EQ(3u, num_toolbar_items());
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