// 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_action_test_util.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service_test_base.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/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.h" #include "chrome/browser/ui/toolbar/component_toolbar_actions_factory.h" #include "chrome/browser/ui/toolbar/mock_component_toolbar_actions_factory.h" #include "chrome/browser/ui/toolbar/test_toolbar_action_view_controller.h" #include "chrome/browser/ui/toolbar/toolbar_actions_model.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/pref_names.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/value_builder.h" namespace { // A simple observer that tracks the number of times certain events occur. class ToolbarActionsModelTestObserver : public ToolbarActionsModel::Observer { public: explicit ToolbarActionsModelTestObserver(ToolbarActionsModel* model); ~ToolbarActionsModelTestObserver() 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 initialized_count() const { return initialized_count_; } private: // ToolbarActionsModel::Observer: void OnToolbarActionAdded(const std::string& id, int index) override { ++inserted_count_; } void OnToolbarActionRemoved(const std::string& id) override { ++removed_count_; } void OnToolbarActionMoved(const std::string& id, int index) override { ++moved_count_; } void OnToolbarActionUpdated(const std::string& id) override {} bool ShowToolbarActionPopup(const std::string& id, bool grant_active_tab) override { return false; } void OnToolbarVisibleCountChanged() override {} void OnToolbarHighlightModeChanged(bool is_highlighting) override { // Add one if highlighting, subtract one if not. highlight_mode_count_ += is_highlighting ? 1 : -1; } void OnToolbarModelInitialized() override { ++initialized_count_; } Browser* GetBrowser() override { return NULL; } ToolbarActionsModel* 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 initialized_count_; DISALLOW_COPY_AND_ASSIGN(ToolbarActionsModelTestObserver); }; ToolbarActionsModelTestObserver::ToolbarActionsModelTestObserver( ToolbarActionsModel* model) : model_(model), inserted_count_(0), removed_count_(0), moved_count_(0), highlight_mode_count_(0), initialized_count_(0) { model_->AddObserver(this); } ToolbarActionsModelTestObserver::~ToolbarActionsModelTestObserver() { model_->RemoveObserver(this); } } // namespace class ToolbarActionsModelUnitTest : public extensions::ExtensionServiceTestBase { public: ToolbarActionsModelUnitTest(){}; ~ToolbarActionsModelUnitTest() override{}; protected: // Initialize the ExtensionService, ToolbarActionsModel, and // ExtensionSystem. void Init(); void TearDown() override; // Adds or removes the given |extension| and verify success. testing::AssertionResult AddExtension( const scoped_refptr& extension) WARN_UNUSED_RESULT; testing::AssertionResult RemoveExtension( const scoped_refptr& 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 action's id at the given index in the toolbar model, or empty // if one does not exist. // If |model| is specified, it is used. Otherwise, this defaults to // |toolbar_model_|. const std::string GetActionIdAtIndex(size_t index, const ToolbarActionsModel* model) const; const std::string GetActionIdAtIndex(size_t index) const; void SetMockActionsFactory(MockComponentToolbarActionsFactory* factory); ToolbarActionsModel* toolbar_model() { return toolbar_model_; } const ToolbarActionsModelTestObserver* observer() const { return model_observer_.get(); } size_t num_toolbar_items() const { return toolbar_model_->toolbar_items().size(); } const extensions::Extension* browser_action_a() const { return browser_action_a_.get(); } const extensions::Extension* browser_action_b() const { return browser_action_b_.get(); } const extensions::Extension* browser_action_c() const { return browser_action_c_.get(); } const extensions::Extension* browser_action() const { return browser_action_extension_.get(); } const extensions::Extension* page_action() const { return page_action_extension_.get(); } const extensions::Extension* no_action() const { return no_action_extension_.get(); } // The mock component action will be referred to as "MCA" below. const char* component_action_id() { return ComponentToolbarActionsFactory::kActionIdForTesting; } private: // Verifies that all extensions in |extensions| are added successfully. testing::AssertionResult AddAndVerifyExtensions( const extensions::ExtensionList& extensions); // The toolbar model associated with the testing profile. ToolbarActionsModel* 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 model_observer_; // Sample extensions with only browser actions. scoped_refptr browser_action_a_; scoped_refptr browser_action_b_; scoped_refptr browser_action_c_; // Sample extensions with different kinds of actions. scoped_refptr browser_action_extension_; scoped_refptr page_action_extension_; scoped_refptr no_action_extension_; scoped_ptr mock_actions_factory_; DISALLOW_COPY_AND_ASSIGN(ToolbarActionsModelUnitTest); }; void ToolbarActionsModelUnitTest::Init() { InitializeEmptyExtensionService(); toolbar_model_ = extensions::extension_action_test_util::CreateToolbarModelForProfile( profile()); model_observer_.reset(new ToolbarActionsModelTestObserver(toolbar_model_)); } void ToolbarActionsModelUnitTest::TearDown() { model_observer_.reset(); extensions::ExtensionServiceTestBase::TearDown(); } testing::AssertionResult ToolbarActionsModelUnitTest::AddExtension( const scoped_refptr& 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 ToolbarActionsModelUnitTest::RemoveExtension( const scoped_refptr& extension) { if (!registry()->enabled_extensions().GetByID(extension->id())) { return testing::AssertionFailure() << "Extension " << extension->name() << " not installed!"; } service()->UnloadExtension(extension->id(), extensions::UnloadedExtensionInfo::REASON_DISABLE); if (registry()->enabled_extensions().GetByID(extension->id())) { return testing::AssertionFailure() << "Failed to unload extension: " << extension->name(); } return testing::AssertionSuccess(); } testing::AssertionResult ToolbarActionsModelUnitTest::AddActionExtensions() { browser_action_extension_ = extensions::extension_action_test_util::CreateActionExtension( "browser_action", extensions::extension_action_test_util::BROWSER_ACTION); page_action_extension_ = extensions::extension_action_test_util::CreateActionExtension( "page_action", extensions::extension_action_test_util::PAGE_ACTION); no_action_extension_ = extensions::extension_action_test_util::CreateActionExtension( "no_action", extensions::extension_action_test_util::NO_ACTION); extensions::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 ToolbarActionsModelUnitTest::AddBrowserActionExtensions() { browser_action_a_ = extensions::extension_action_test_util::CreateActionExtension( "browser_actionA", extensions::extension_action_test_util::BROWSER_ACTION); browser_action_b_ = extensions::extension_action_test_util::CreateActionExtension( "browser_actionB", extensions::extension_action_test_util::BROWSER_ACTION); browser_action_c_ = extensions::extension_action_test_util::CreateActionExtension( "browser_actionC", extensions::extension_action_test_util::BROWSER_ACTION); extensions::ExtensionList extensions; extensions.push_back(browser_action_a_); extensions.push_back(browser_action_b_); extensions.push_back(browser_action_c_); return AddAndVerifyExtensions(extensions); } const std::string ToolbarActionsModelUnitTest::GetActionIdAtIndex( size_t index, const ToolbarActionsModel* model) const { return index < model->toolbar_items().size() ? model->toolbar_items()[index].id : std::string(); } const std::string ToolbarActionsModelUnitTest::GetActionIdAtIndex( size_t index) const { return GetActionIdAtIndex(index, toolbar_model_); } testing::AssertionResult ToolbarActionsModelUnitTest::AddAndVerifyExtensions( const extensions::ExtensionList& extensions) { for (extensions::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(); } void ToolbarActionsModelUnitTest::SetMockActionsFactory( MockComponentToolbarActionsFactory* factory) { mock_actions_factory_.reset(factory); } // A basic test for component actions and extensions with browser actions // showing up in the toolbar. TEST_F(ToolbarActionsModelUnitTest, BasicToolbarActionsModelTest) { Init(); // Load an extension with no browser action. scoped_refptr extension1 = extensions::extension_action_test_util::CreateActionExtension( "no_action", extensions::extension_action_test_util::NO_ACTION); 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(std::string(), GetActionIdAtIndex(0u)); // Load an extension with a browser action. scoped_refptr extension2 = extensions::extension_action_test_util::CreateActionExtension( "browser_action", extensions::extension_action_test_util::BROWSER_ACTION); 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->id(), GetActionIdAtIndex(0u)); // Should be a no-op, but still fires the events. toolbar_model()->MoveActionIcon(extension2->id(), 0); EXPECT_EQ(1u, observer()->moved_count()); EXPECT_EQ(1u, num_toolbar_items()); EXPECT_EQ(extension2->id(), GetActionIdAtIndex(0u)); // Remove the extension and verify. ASSERT_TRUE(RemoveExtension(extension2)); EXPECT_EQ(1u, observer()->removed_count()); EXPECT_EQ(0u, num_toolbar_items()); EXPECT_EQ(std::string(), GetActionIdAtIndex(0u)); } // Test various different reorderings, removals, and reinsertions. TEST_F(ToolbarActionsModelUnitTest, ActionsToolbarReorderAndReinsert) { Init(); // Add the three browser action extensions. ASSERT_TRUE(AddBrowserActionExtensions()); // Verify the three actions are in the model in the proper order. EXPECT_EQ(3u, num_toolbar_items()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2u)); // Order is now A, B, C. Let's put C first. toolbar_model()->MoveActionIcon(browser_action_c()->id(), 0); EXPECT_EQ(1u, observer()->moved_count()); EXPECT_EQ(3u, num_toolbar_items()); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(2u)); // Order is now C, A, B. Let's put A last. toolbar_model()->MoveActionIcon(browser_action_a()->id(), 2); EXPECT_EQ(2u, observer()->moved_count()); EXPECT_EQ(3u, num_toolbar_items()); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(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()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(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()->id(), GetActionIdAtIndex(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()->MoveActionIcon(browser_action_a()->id(), 0); EXPECT_EQ(3u, observer()->moved_count()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(1u)); // Move A to the location it already occupies. toolbar_model()->MoveActionIcon(browser_action_a()->id(), 0); EXPECT_EQ(4u, observer()->moved_count()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(1u)); // Order is now A, C. ASSERT_TRUE(RemoveExtension(browser_action_c())); EXPECT_EQ(3u, observer()->removed_count()); EXPECT_EQ(1u, num_toolbar_items()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(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()->id(), GetActionIdAtIndex(1u)); } // Test that order persists after unloading and disabling, but not across // uninstallation. TEST_F(ToolbarActionsModelUnitTest, ActionsToolbarUnloadDisableAndUninstall) { Init(); // Add the three browser action extensions. ASSERT_TRUE(AddBrowserActionExtensions()); // Verify the three actions are in the model in the proper order: A, B, C. EXPECT_EQ(3u, num_toolbar_items()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(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()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2u)); // Disabling extensions should also preserve order. service()->DisableExtension(browser_action_b()->id(), extensions::Extension::DISABLE_USER_ACTION); service()->DisableExtension(browser_action_c()->id(), extensions::Extension::DISABLE_USER_ACTION); service()->DisableExtension(browser_action_a()->id(), extensions::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()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2u)); // Move browser_action_b() to be first. toolbar_model()->MoveActionIcon(browser_action_b()->id(), 0); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(0u)); // Uninstall Extension B. service()->UninstallExtension(browser_action_b()->id(), extensions::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()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(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()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(2u)); } TEST_F(ToolbarActionsModelUnitTest, ReorderOnPrefChange) { Init(); // Add the three browser action extensions. ASSERT_TRUE(AddBrowserActionExtensions()); EXPECT_EQ(3u, num_toolbar_items()); // Change the value of the toolbar preference. std::vector new_order; new_order.push_back(browser_action_c()->id()); new_order.push_back(browser_action_b()->id()); extensions::ExtensionPrefs::Get(profile())->SetToolbarOrder(new_order); // Verify order is changed. EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(2u)); } // Test that new extension actions are always visible on installation and // inserted at the "end" of the visible section. TEST_F(ToolbarActionsModelUnitTest, NewToolbarExtensionsAreVisible) { Init(); // Three extensions with actions. scoped_refptr extension_a = extensions::extension_action_test_util::CreateActionExtension( "a", extensions::extension_action_test_util::BROWSER_ACTION); scoped_refptr extension_b = extensions::extension_action_test_util::CreateActionExtension( "b", extensions::extension_action_test_util::BROWSER_ACTION); scoped_refptr extension_c = extensions::extension_action_test_util::CreateActionExtension( "c", extensions::extension_action_test_util::BROWSER_ACTION); scoped_refptr extension_d = extensions::extension_action_test_util::CreateActionExtension( "d", extensions::extension_action_test_util::BROWSER_ACTION); // We should start off without any actions. EXPECT_EQ(0u, num_toolbar_items()); EXPECT_EQ(0u, toolbar_model()->visible_icon_count()); // Add one action. It should be visible. service()->AddExtension(extension_a.get()); EXPECT_EQ(1u, num_toolbar_items()); EXPECT_EQ(1u, toolbar_model()->visible_icon_count()); EXPECT_EQ(extension_a.get()->id(), GetActionIdAtIndex(0u)); // Hide all actions. toolbar_model()->SetVisibleIconCount(0); EXPECT_EQ(0u, toolbar_model()->visible_icon_count()); // Add a new action - it should be visible, so it should be in the first // index. The other action should remain hidden. service()->AddExtension(extension_b.get()); EXPECT_EQ(2u, num_toolbar_items()); EXPECT_EQ(1u, toolbar_model()->visible_icon_count()); EXPECT_EQ(extension_b.get()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(extension_a.get()->id(), GetActionIdAtIndex(1u)); // Show all actions. toolbar_model()->SetVisibleIconCount(2); EXPECT_EQ(2u, toolbar_model()->visible_icon_count()); EXPECT_TRUE(toolbar_model()->all_icons_visible()); // Add the third action. Since all action are visible, it should go in the // last index. service()->AddExtension(extension_c.get()); EXPECT_EQ(3u, num_toolbar_items()); EXPECT_EQ(3u, toolbar_model()->visible_icon_count()); EXPECT_TRUE(toolbar_model()->all_icons_visible()); EXPECT_EQ(extension_b.get()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(extension_a.get()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(extension_c.get()->id(), GetActionIdAtIndex(2u)); // Hide one action (two remaining visible). toolbar_model()->SetVisibleIconCount(2); EXPECT_EQ(2u, toolbar_model()->visible_icon_count()); // Add a fourth action. It should go at the end of the visible section and // be visible, so it increases visible count by 1, and goes into the fourth // index. The hidden action should remain hidden. service()->AddExtension(extension_d.get()); EXPECT_EQ(4u, num_toolbar_items()); EXPECT_EQ(3u, toolbar_model()->visible_icon_count()); EXPECT_EQ(extension_b.get()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(extension_a.get()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(extension_d.get()->id(), GetActionIdAtIndex(2u)); EXPECT_EQ(extension_c.get()->id(), GetActionIdAtIndex(3u)); } TEST_F(ToolbarActionsModelUnitTest, ActionsToolbarHighlightMode) { Init(); EXPECT_FALSE(toolbar_model()->HighlightActions( std::vector(), ToolbarActionsModel::HIGHLIGHT_WARNING)); EXPECT_EQ(0, observer()->highlight_mode_count()); // Add the three browser action extensions. ASSERT_TRUE(AddBrowserActionExtensions()); EXPECT_EQ(3u, num_toolbar_items()); // Start with a visible count of 2 (non-zero, and not all). toolbar_model()->SetVisibleIconCount(2u); // Highlight one extension. std::vector action_ids; action_ids.push_back(browser_action_b()->id()); toolbar_model()->HighlightActions(action_ids, ToolbarActionsModel::HIGHLIGHT_WARNING); EXPECT_EQ(1, observer()->highlight_mode_count()); EXPECT_TRUE(toolbar_model()->is_highlighting()); EXPECT_EQ(1u, num_toolbar_items()); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(1u, toolbar_model()->visible_icon_count()); // 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()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2u)); EXPECT_EQ(2u, toolbar_model()->visible_icon_count()); // 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. action_ids.clear(); action_ids.push_back(browser_action_a()->id()); action_ids.push_back(browser_action_b()->id()); action_ids.push_back(browser_action_c()->id()); toolbar_model()->HighlightActions(action_ids, ToolbarActionsModel::HIGHLIGHT_WARNING); EXPECT_EQ(1, observer()->highlight_mode_count()); EXPECT_EQ(3u, num_toolbar_items()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2u)); EXPECT_EQ(3u, toolbar_model()->visible_icon_count()); // Even though the visible count is 3, we shouldn't adjust the stored // preference. EXPECT_EQ(2, profile()->GetPrefs()->GetInteger( extensions::pref_names::kToolbarSize)); // Highlight only extension B (shrink the highlight list). action_ids.clear(); action_ids.push_back(browser_action_b()->id()); toolbar_model()->HighlightActions(action_ids, ToolbarActionsModel::HIGHLIGHT_WARNING); EXPECT_EQ(2, observer()->highlight_mode_count()); EXPECT_EQ(1u, num_toolbar_items()); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(0u)); // Highlight extensions A and B (grow the highlight list). action_ids.clear(); action_ids.push_back(browser_action_a()->id()); action_ids.push_back(browser_action_b()->id()); toolbar_model()->HighlightActions(action_ids, ToolbarActionsModel::HIGHLIGHT_WARNING); EXPECT_EQ(3, observer()->highlight_mode_count()); EXPECT_EQ(2u, num_toolbar_items()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); // Highlight no extensions (empty the highlight list). action_ids.clear(); toolbar_model()->HighlightActions(action_ids, ToolbarActionsModel::HIGHLIGHT_WARNING); EXPECT_EQ(2, observer()->highlight_mode_count()); EXPECT_FALSE(toolbar_model()->is_highlighting()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2u)); // Our toolbar size should be back to normal. EXPECT_EQ(2u, toolbar_model()->visible_icon_count()); EXPECT_EQ(2, profile()->GetPrefs()->GetInteger( extensions::pref_names::kToolbarSize)); } TEST_F(ToolbarActionsModelUnitTest, ActionsToolbarHighlightModeRemove) { Init(); // Add the three browser action extensions. ASSERT_TRUE(AddBrowserActionExtensions()); EXPECT_EQ(3u, num_toolbar_items()); // Highlight two of the extensions. std::vector action_ids; action_ids.push_back(browser_action_a()->id()); action_ids.push_back(browser_action_b()->id()); toolbar_model()->HighlightActions(action_ids, ToolbarActionsModel::HIGHLIGHT_WARNING); 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(), extensions::Extension::DISABLE_USER_ACTION); EXPECT_TRUE(toolbar_model()->is_highlighting()); EXPECT_EQ(1u, num_toolbar_items()); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(0u)); // Uninstall the remaining highlighted extension. This should result in // highlight mode exiting. service()->UninstallExtension(browser_action_b()->id(), extensions::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()->id(), GetActionIdAtIndex(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()->id(), GetActionIdAtIndex(1u)); action_ids.clear(); action_ids.push_back(browser_action_c()->id()); toolbar_model()->HighlightActions(action_ids, ToolbarActionsModel::HIGHLIGHT_WARNING); EXPECT_EQ(1, observer()->highlight_mode_count()); EXPECT_TRUE(toolbar_model()->is_highlighting()); EXPECT_EQ(1u, num_toolbar_items()); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(0u)); // Uninstalling B should not have visible impact. service()->UninstallExtension(browser_action_b()->id(), extensions::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()->id(), GetActionIdAtIndex(0u)); // When we stop, only action 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()->id(), GetActionIdAtIndex(0u)); } TEST_F(ToolbarActionsModelUnitTest, ActionsToolbarHighlightModeAdd) { 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 actions. std::vector action_ids; action_ids.push_back(browser_action_a()->id()); toolbar_model()->HighlightActions(action_ids, ToolbarActionsModel::HIGHLIGHT_WARNING); EXPECT_TRUE(toolbar_model()->is_highlighting()); EXPECT_EQ(1u, num_toolbar_items()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(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()->id(), GetActionIdAtIndex(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()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2u)); } // Test that the action toolbar maintains the proper size, even after a pref // change. TEST_F(ToolbarActionsModelUnitTest, ActionsToolbarSizeAfterPrefChange) { 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()->OnActionToolbarPrefChange(); // 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 and component actions. TEST_F(ToolbarActionsModelUnitTest, TestToolbarExtensionTypesNoSwitch) { Init(); ASSERT_TRUE(AddActionExtensions()); EXPECT_EQ(1u, num_toolbar_items()); EXPECT_EQ(browser_action()->id(), GetActionIdAtIndex(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(ToolbarActionsModelUnitTest, TestToolbarExtensionTypesSwitch) { extensions::FeatureSwitch::ScopedOverride enable_redesign( extensions::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()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(page_action()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(no_action()->id(), GetActionIdAtIndex(2u)); } // Test that hiding actions on the toolbar results in their removal from the // model when the redesign switch is not enabled. TEST_F(ToolbarActionsModelUnitTest, ActionsToolbarActionsVisibilityNoSwitch) { Init(); extensions::ExtensionActionAPI* action_api = extensions::ExtensionActionAPI::Get(profile()); ASSERT_TRUE(AddBrowserActionExtensions()); // Sanity check: Order should start as A , B, C. EXPECT_EQ(3u, num_toolbar_items()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2u)); // By default, all actions should be visible. EXPECT_TRUE(action_api->GetBrowserActionVisibility(browser_action_a()->id())); EXPECT_TRUE(action_api->GetBrowserActionVisibility(browser_action_b()->id())); EXPECT_TRUE(action_api->GetBrowserActionVisibility(browser_action_c()->id())); // Hiding an action should result in its removal from the toolbar. action_api->SetBrowserActionVisibility(browser_action_b()->id(), false); EXPECT_FALSE( action_api->GetBrowserActionVisibility(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()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(1u)); // Resetting the visibility to 'true' should result in the extension being // added back at its original position. action_api->SetBrowserActionVisibility(browser_action_b()->id(), true); EXPECT_TRUE(action_api->GetBrowserActionVisibility(browser_action_b()->id())); // So the toolbar order should be A, B, C. EXPECT_EQ(3u, num_toolbar_items()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2u)); } TEST_F(ToolbarActionsModelUnitTest, ActionsToolbarIncognitoModeTest) { 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). extensions::ExtensionPrefs* extension_prefs = extensions::ExtensionPrefs::Get(profile()); extension_prefs->SetIsIncognitoEnabled(browser_action_b()->id(), true); extension_prefs->SetIsIncognitoEnabled(browser_action_c()->id(), true); extensions::util::SetIsIncognitoEnabled(browser_action_b()->id(), profile(), true); extensions::util::SetIsIncognitoEnabled(browser_action_c()->id(), profile(), true); // Move C to the second index. toolbar_model()->MoveActionIcon(browser_action_c()->id(), 1u); // Set visible count to 3 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. ToolbarActionsModel* incognito_model = extensions::extension_action_test_util::CreateToolbarModelForProfile( profile()->GetOffTheRecordProfile()); ToolbarActionsModelTestObserver incognito_observer(incognito_model); EXPECT_EQ(0u, incognito_observer.moved_count()); // We should have two items: C, B, and the order should be preserved from the // original model. EXPECT_EQ(2u, incognito_model->toolbar_items().size()); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(0u, incognito_model)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u, incognito_model)); // Actions 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 3 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->MoveActionIcon(browser_action_b()->id(), 0u); // So now it should be B, C... EXPECT_EQ(1u, incognito_observer.moved_count()); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(0u, incognito_model)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(1u, incognito_model)); // ... and the regular toolbar should be unaffected. EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(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()->MoveActionIcon(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(ToolbarActionsModelUnitTest, ActionsToolbarIncognitoEnableExtension) { 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. extensions::TestExtensionDir dir1; dir1.WriteManifest(base::StringPrintf(kManifest, "incognito1")); extensions::TestExtensionDir dir2; dir2.WriteManifest(base::StringPrintf(kManifest, "incognito2")); extensions::TestExtensionDir* dirs[] = {&dir1, &dir2}; const extensions::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); extensions::TestExtensionRegistryObserver observer(registry(), id); extensions::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, GetActionIdAtIndex(0u)); EXPECT_EQ(extension_b, GetActionIdAtIndex(1u)); // Set the model to only show one extension, so the order is A, [B]. toolbar_model()->SetVisibleIconCount(1u); // Get an incognito profile and toolbar. ToolbarActionsModel* incognito_model = extensions::extension_action_test_util::CreateToolbarModelForProfile( profile()->GetOffTheRecordProfile()); ToolbarActionsModelTestObserver incognito_observer(incognito_model); // Right now, no actions 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. { extensions::TestExtensionRegistryObserver observer(registry(), extension_b); extensions::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, GetActionIdAtIndex(0u, incognito_model)); EXPECT_EQ(0u, incognito_model->visible_icon_count()); // Also enable extension a for incognito (again, wait for the reload). { extensions::TestExtensionRegistryObserver observer(registry(), extension_a); extensions::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, GetActionIdAtIndex(0u, incognito_model)); EXPECT_EQ(extension_b, GetActionIdAtIndex(1u, incognito_model)); 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(ToolbarActionsModelUnitTest, ActionsToolbarActionsVisibilityWithSwitchAndComponentActions) { extensions::FeatureSwitch::ScopedOverride enable_redesign( extensions::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 extensions::Extension* extension_a = browser_action(); const extensions::Extension* extension_b = page_action(); const extensions::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->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(extension_b->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(extension_c->id(), GetActionIdAtIndex(2u)); extensions::ExtensionActionAPI* action_api = extensions::ExtensionActionAPI::Get(profile()); // By default, all actions should be visible. EXPECT_TRUE(action_api->GetBrowserActionVisibility(extension_a->id())); EXPECT_TRUE(action_api->GetBrowserActionVisibility(extension_c->id())); EXPECT_TRUE(action_api->GetBrowserActionVisibility(extension_b->id())); // Hiding an action should result in it being sent to the overflow menu. action_api->SetBrowserActionVisibility(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->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(extension_c->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(extension_b->id(), GetActionIdAtIndex(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. action_api->SetBrowserActionVisibility(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->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(extension_a->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(extension_b->id(), GetActionIdAtIndex(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). action_api->SetBrowserActionVisibility(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->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(extension_a->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(extension_b->id(), GetActionIdAtIndex(2u)); // Resetting B to be visible should make the order C, A, B, with no // overflow. action_api->SetBrowserActionVisibility(extension_b->id(), true); EXPECT_EQ(3u, num_toolbar_items()); EXPECT_TRUE(toolbar_model()->all_icons_visible()); EXPECT_EQ(extension_c->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(extension_a->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(extension_b->id(), GetActionIdAtIndex(2u)); // Regression test for crbug.com/515963. Check that an extension's visibility // is updated when it is moved out because another extension was removed. toolbar_model()->SetVisibleIconCount(1); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(action_api->GetBrowserActionVisibility(extension_a->id())); service()->DisableExtension(extension_c->id(), extensions::Extension::DISABLE_USER_ACTION); EXPECT_EQ(1u, toolbar_model()->visible_icon_count()); EXPECT_TRUE(action_api->GetBrowserActionVisibility(extension_a->id())); } // Test that observers receive no Added notifications until after the // ExtensionSystem has initialized. TEST_F(ToolbarActionsModelUnitTest, ModelWaitsForExtensionSystemReady) { InitializeEmptyExtensionService(); ToolbarActionsModel* toolbar_model = extensions::extension_action_test_util:: CreateToolbarModelForProfileWithoutWaitingForReady(profile()); ToolbarActionsModelTestObserver model_observer(toolbar_model); EXPECT_TRUE(AddBrowserActionExtensions()); // Since the model hasn't been initialized (the ExtensionSystem::ready task // hasn't been run), there should be no insertion notifications. EXPECT_EQ(0u, model_observer.inserted_count()); EXPECT_EQ(0u, model_observer.initialized_count()); EXPECT_FALSE(toolbar_model->actions_initialized()); // Run the ready task. static_cast( extensions::ExtensionSystem::Get(profile())) ->SetReady(); // Run tasks posted to TestExtensionSystem. base::RunLoop().RunUntilIdle(); // We should still have no insertions, but should have an initialized count. EXPECT_TRUE(toolbar_model->actions_initialized()); EXPECT_EQ(0u, model_observer.inserted_count()); EXPECT_EQ(1u, model_observer.initialized_count()); } // Check that the toolbar model correctly clears and reorders when it detects // a preference change. TEST_F(ToolbarActionsModelUnitTest, ToolbarModelPrefChange) { Init(); ASSERT_TRUE(AddBrowserActionExtensions()); // We should start in the basic A, B, C order. ASSERT_TRUE(browser_action_a()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2)); // Record the difference between the inserted and removed counts. The actual // value of the counts is not important, but we need to be sure that if we // call to remove any, we also add them back. size_t inserted_and_removed_difference = observer()->inserted_count() - observer()->removed_count(); // Assign a new order, B, C, A, and write it in the prefs. std::vector new_order; new_order.push_back(browser_action_b()->id()); new_order.push_back(browser_action_c()->id()); new_order.push_back(browser_action_a()->id()); extensions::ExtensionPrefs::Get(profile())->SetToolbarOrder(new_order); // Ensure everything has time to run. base::RunLoop().RunUntilIdle(); // The new order should be reflected in the model. EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(0)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(1)); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(2)); EXPECT_EQ(inserted_and_removed_difference, observer()->inserted_count() - observer()->removed_count()); } TEST_F(ToolbarActionsModelUnitTest, ComponentExtensionsAddedToEnd) { Init(); ASSERT_TRUE(AddBrowserActionExtensions()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2)); const char kName[] = "component"; extensions::DictionaryBuilder manifest; manifest.Set("name", kName) .Set("description", "An extension") .Set("manifest_version", 2) .Set("version", "1.0.0") .Set("browser_action", extensions::DictionaryBuilder().Pass()); scoped_refptr component_extension = extensions::ExtensionBuilder() .SetManifest(manifest.Pass()) .SetID(crx_file::id_util::GenerateId(kName)) .SetLocation(extensions::Manifest::COMPONENT) .Build(); service()->AddExtension(component_extension.get()); EXPECT_EQ(component_extension.get()->id(), GetActionIdAtIndex(0)); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(1)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(2)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(3)); } TEST_F(ToolbarActionsModelUnitTest, ToolbarModelHighlightsForToolbarRedesign) { extensions::FeatureSwitch::ScopedOverride enable_redesign( extensions::FeatureSwitch::extension_action_redesign(), true); InitializeEmptyExtensionService(); EXPECT_TRUE(AddActionExtensions()); ToolbarActionsModel* toolbar_model = extensions::extension_action_test_util::CreateToolbarModelForProfile( profile()); EXPECT_TRUE(toolbar_model); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(ExtensionToolbarIconSurfacingBubbleDelegate::ShouldShowForProfile( profile())); EXPECT_TRUE(toolbar_model->is_highlighting()); EXPECT_EQ(ToolbarActionsModel::HIGHLIGHT_INFO, toolbar_model->highlight_type()); EXPECT_EQ(3u, toolbar_model->visible_icon_count()); EXPECT_EQ(3u, toolbar_model->toolbar_items().size()); scoped_ptr bubble( new ExtensionToolbarIconSurfacingBubbleDelegate(profile())); bubble->OnBubbleClosed(ToolbarActionsBarBubbleDelegate::CLOSE_DISMISS); EXPECT_FALSE(toolbar_model->is_highlighting()); EXPECT_EQ(ToolbarActionsModel::HIGHLIGHT_NONE, toolbar_model->highlight_type()); } // Test various different reorderings, removals, and reinsertions of the // toolbar with component actions. TEST_F(ToolbarActionsModelUnitTest, ActionsToolbarReorderAndReinsertWithSwitchAndCOmponentActions) { extensions::FeatureSwitch::ScopedOverride enable_redesign( extensions::FeatureSwitch::extension_action_redesign(), true); SetMockActionsFactory(new MockComponentToolbarActionsFactory(nullptr)); Init(); // One component action was added when the model was initialized. EXPECT_EQ(1u, num_toolbar_items()); EXPECT_EQ(component_action_id(), GetActionIdAtIndex(0u)); // Add the three browser action extensions. ASSERT_TRUE(AddBrowserActionExtensions()); // Verify the four actions are in the model in the proper order. EXPECT_EQ(4u, num_toolbar_items()); EXPECT_EQ(component_action_id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(2u)); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(3u)); // Order is now MCA, A, B, C. Let's put C first. toolbar_model()->MoveActionIcon(browser_action_c()->id(), 0); EXPECT_EQ(1u, observer()->moved_count()); EXPECT_EQ(4u, num_toolbar_items()); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(component_action_id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(2u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(3u)); // Order is now C, MCA, A, B. Let's put MCA last. toolbar_model()->MoveActionIcon(component_action_id(), 3); EXPECT_EQ(2u, observer()->moved_count()); EXPECT_EQ(4u, num_toolbar_items()); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(2u)); EXPECT_EQ(component_action_id(), GetActionIdAtIndex(3u)); // Order is now C, A, B, MCA. Move MCA to the location it already occupies. toolbar_model()->MoveActionIcon(component_action_id(), 3); EXPECT_EQ(3u, observer()->moved_count()); EXPECT_EQ(4u, num_toolbar_items()); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(2u)); EXPECT_EQ(component_action_id(), GetActionIdAtIndex(3u)); // Order is still C, A, B, MCA. Move MCA to second to last, in preparation // for visibility checks. toolbar_model()->MoveActionIcon(component_action_id(), 2); EXPECT_EQ(4u, observer()->moved_count()); EXPECT_EQ(4u, num_toolbar_items()); EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(1u)); EXPECT_EQ(component_action_id(), GetActionIdAtIndex(2u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(3u)); // Order is now C, A, MCA, B. Show only three icons: C, A, MCA, [B]. toolbar_model()->SetVisibleIconCount(3); EXPECT_EQ(3u, toolbar_model()->visible_icon_count()); EXPECT_FALSE(toolbar_model()->all_icons_visible()); // Show only two icons so we test MCA in the overflow. The icons should // be: C, A, [MCA], [B]. toolbar_model()->SetVisibleIconCount(2); EXPECT_EQ(2u, toolbar_model()->visible_icon_count()); EXPECT_FALSE(toolbar_model()->all_icons_visible()); // Show all the icons again. Order should be C, A, MCA, B. toolbar_model()->SetVisibleIconCount(4); EXPECT_EQ(4u, toolbar_model()->visible_icon_count()); EXPECT_TRUE(toolbar_model()->all_icons_visible()); // Order is C, A, MCA, B. Remove C. ASSERT_TRUE(RemoveExtension(browser_action_c())); EXPECT_EQ(1u, observer()->removed_count()); EXPECT_EQ(3u, num_toolbar_items()); EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(component_action_id(), GetActionIdAtIndex(1u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(2u)); // Order is now A, MCA, B. Remove A. ASSERT_TRUE(RemoveExtension(browser_action_a())); EXPECT_EQ(2u, observer()->removed_count()); EXPECT_EQ(2u, num_toolbar_items()); EXPECT_EQ(component_action_id(), GetActionIdAtIndex(0u)); EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u)); // Order is now MCA, B. Remove B. ASSERT_TRUE(RemoveExtension(browser_action_b())); EXPECT_EQ(3u, observer()->removed_count()); EXPECT_EQ(1u, num_toolbar_items()); EXPECT_EQ(component_action_id(), GetActionIdAtIndex(0u)); // Load extension C again. ASSERT_TRUE(AddExtension(browser_action_c())); EXPECT_EQ(4u, observer()->inserted_count()); EXPECT_EQ(2u, num_toolbar_items()); // Make sure it gets its old spot in the list (at the beginning). EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(0u)); EXPECT_EQ(component_action_id(), GetActionIdAtIndex(1u)); }