blob: a8609d0832a79468d90f8c75d2c2f6b220a3dd40 [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 "chrome/browser/ui/toolbar/toolbar_actions_model.h"
#include <stddef.h>
#include <memory>
#include <string>
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/containers/contains.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/extensions/api/extension_action/extension_action_api.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_system.h"
#include "chrome/browser/extensions/unpacked_installer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/toolbar/test_toolbar_action_view_controller.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/crx_file/id_util.h"
#include "components/policy/core/common/policy_map.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/extension_action_manager.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/pref_names.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/browser/uninstall_reason.h"
#include "extensions/common/api/extension_action/action_info.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/manifest.h"
#include "extensions/common/value_builder.h"
#include "extensions/test/test_extension_dir.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace {
using extensions::mojom::ManifestLocation;
using ActionType = extensions::ExtensionBuilder::ActionType;
// 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_; }
size_t initialized_count() const { return initialized_count_; }
const std::vector<ToolbarActionsModel::ActionId>& last_pinned_action_ids()
const {
return last_pinned_action_ids_;
}
private:
// ToolbarActionsModel::Observer:
void OnToolbarActionAdded(const ToolbarActionsModel::ActionId& action_id,
int index) override {
++inserted_count_;
}
void OnToolbarActionRemoved(
const ToolbarActionsModel::ActionId& id) override {
++removed_count_;
}
void OnToolbarActionLoadFailed() override {}
void OnToolbarActionMoved(const ToolbarActionsModel::ActionId& id,
int index) override {
++moved_count_;
}
void OnToolbarActionUpdated(
const ToolbarActionsModel::ActionId& id) override {}
void OnToolbarVisibleCountChanged() override {}
void OnToolbarModelInitialized() override { ++initialized_count_; }
void OnToolbarPinnedActionsChanged() override {
last_pinned_action_ids_ = model_->pinned_action_ids();
}
ToolbarActionsModel* const model_;
size_t inserted_count_;
size_t removed_count_;
size_t moved_count_;
size_t initialized_count_;
std::vector<ToolbarActionsModel::ActionId> last_pinned_action_ids_;
DISALLOW_COPY_AND_ASSIGN(ToolbarActionsModelTestObserver);
};
ToolbarActionsModelTestObserver::ToolbarActionsModelTestObserver(
ToolbarActionsModel* model)
: model_(model),
inserted_count_(0),
removed_count_(0),
moved_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 InitToolbarModelAndObserver();
void TearDown() override;
// Adds or removes the given |extension| and verify success.
testing::AssertionResult AddExtension(
const scoped_refptr<const extensions::Extension>& extension)
WARN_UNUSED_RESULT;
testing::AssertionResult RemoveExtension(
const scoped_refptr<const extensions::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 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;
// Returns true if the |toobar_model_| has an action with the given |id|.
bool ModelHasActionForId(const std::string& id) const;
ToolbarActionsModel* toolbar_model() { return toolbar_model_; }
const ToolbarActionsModelTestObserver* observer() const {
return model_observer_.get();
}
size_t num_actions() const { return toolbar_model_->action_ids().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();
}
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.
std::unique_ptr<ToolbarActionsModelTestObserver> model_observer_;
// Sample extensions with only browser actions.
scoped_refptr<const extensions::Extension> browser_action_a_;
scoped_refptr<const extensions::Extension> browser_action_b_;
scoped_refptr<const extensions::Extension> browser_action_c_;
// Sample extensions with different kinds of actions.
scoped_refptr<const extensions::Extension> browser_action_extension_;
scoped_refptr<const extensions::Extension> page_action_extension_;
scoped_refptr<const extensions::Extension> no_action_extension_;
DISALLOW_COPY_AND_ASSIGN(ToolbarActionsModelUnitTest);
};
void ToolbarActionsModelUnitTest::Init() {
InitializeEmptyExtensionService();
InitToolbarModelAndObserver();
}
void ToolbarActionsModelUnitTest::InitToolbarModelAndObserver() {
toolbar_model_ =
extensions::extension_action_test_util::CreateToolbarModelForProfile(
profile());
model_observer_ =
std::make_unique<ToolbarActionsModelTestObserver>(toolbar_model_);
}
void ToolbarActionsModelUnitTest::TearDown() {
model_observer_.reset();
extensions::ExtensionServiceTestBase::TearDown();
}
testing::AssertionResult ToolbarActionsModelUnitTest::AddExtension(
const scoped_refptr<const extensions::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();
}
// Make sure RegisterClient calls for storage are finished to avoid flaky
// crashes in QuotaManagerImpl::RegisterClient.
// TODO(crbug.com/1182630) : Remove this when 1182630 is fixed.
extensions::util::GetStoragePartitionForExtensionId(extension->id(),
profile());
task_environment()->RunUntilIdle();
return testing::AssertionSuccess();
}
testing::AssertionResult ToolbarActionsModelUnitTest::RemoveExtension(
const scoped_refptr<const extensions::Extension>& extension) {
if (!registry()->enabled_extensions().GetByID(extension->id())) {
return testing::AssertionFailure()
<< "Extension " << extension->name() << " not installed!";
}
service()->UnloadExtension(extension->id(),
extensions::UnloadedExtensionReason::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::ExtensionBuilder("browser_action")
.SetAction(ActionType::BROWSER_ACTION)
.SetLocation(ManifestLocation::kInternal)
.Build();
page_action_extension_ = extensions::ExtensionBuilder("page_action")
.SetAction(ActionType::PAGE_ACTION)
.SetLocation(ManifestLocation::kInternal)
.Build();
no_action_extension_ = extensions::ExtensionBuilder("no_action")
.SetLocation(ManifestLocation::kInternal)
.Build();
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::ExtensionBuilder("browser_actionA")
.SetAction(ActionType::BROWSER_ACTION)
.SetLocation(ManifestLocation::kInternal)
.Build();
browser_action_b_ = extensions::ExtensionBuilder("browser_actionB")
.SetAction(ActionType::BROWSER_ACTION)
.SetLocation(ManifestLocation::kInternal)
.Build();
browser_action_c_ = extensions::ExtensionBuilder("browser_actionC")
.SetAction(ActionType::BROWSER_ACTION)
.SetLocation(ManifestLocation::kInternal)
.Build();
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->action_ids().size() ? model->action_ids()[index]
: std::string();
}
const std::string ToolbarActionsModelUnitTest::GetActionIdAtIndex(
size_t index) const {
return GetActionIdAtIndex(index, toolbar_model_);
}
bool ToolbarActionsModelUnitTest::ModelHasActionForId(
const std::string& id) const {
for (const auto& toolbar_action_id : toolbar_model_->action_ids()) {
if (toolbar_action_id == id)
return true;
}
return false;
}
testing::AssertionResult ToolbarActionsModelUnitTest::AddAndVerifyExtensions(
const extensions::ExtensionList& extensions) {
for (auto 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(ToolbarActionsModelUnitTest, BasicToolbarActionsModelTest) {
Init();
// Load an extension with a browser action.
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("browser_action")
.SetAction(ActionType::BROWSER_ACTION)
.SetLocation(ManifestLocation::kInternal)
.Build();
ASSERT_TRUE(AddExtension(extension));
// We should now find our extension in the model.
EXPECT_EQ(1u, observer()->inserted_count());
EXPECT_EQ(1u, num_actions());
EXPECT_EQ(extension->id(), GetActionIdAtIndex(0u));
// Should be a no-op, but still fires the events.
toolbar_model()->MoveActionIcon(extension->id(), 0);
EXPECT_EQ(1u, observer()->moved_count());
EXPECT_EQ(1u, num_actions());
EXPECT_EQ(extension->id(), GetActionIdAtIndex(0u));
// Remove the extension and verify.
ASSERT_TRUE(RemoveExtension(extension));
EXPECT_EQ(1u, observer()->removed_count());
EXPECT_EQ(0u, num_actions());
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_actions());
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_actions());
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_actions());
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_actions());
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_actions());
// 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_actions());
// 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_actions());
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_actions());
// 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_actions());
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_actions()); // 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_actions()); // 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::disable_reason::DISABLE_USER_ACTION);
service()->DisableExtension(browser_action_c()->id(),
extensions::disable_reason::DISABLE_USER_ACTION);
service()->DisableExtension(browser_action_a()->id(),
extensions::disable_reason::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,
NULL); // Ignore error.
// List contains only A and C now. Validate that.
EXPECT_EQ(2u, num_actions());
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_actions());
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_actions());
// Change the value of the toolbar preference.
std::vector<std::string> 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<const extensions::Extension> extension_a =
extensions::ExtensionBuilder("a")
.SetAction(ActionType::BROWSER_ACTION)
.SetLocation(ManifestLocation::kInternal)
.Build();
scoped_refptr<const extensions::Extension> extension_b =
extensions::ExtensionBuilder("b")
.SetAction(ActionType::BROWSER_ACTION)
.SetLocation(ManifestLocation::kInternal)
.Build();
scoped_refptr<const extensions::Extension> extension_c =
extensions::ExtensionBuilder("c")
.SetAction(ActionType::BROWSER_ACTION)
.SetLocation(ManifestLocation::kInternal)
.Build();
scoped_refptr<const extensions::Extension> extension_d =
extensions::ExtensionBuilder("d")
.SetAction(ActionType::BROWSER_ACTION)
.SetLocation(ManifestLocation::kInternal)
.Build();
// We should start off without any actions.
EXPECT_EQ(0u, num_actions());
EXPECT_EQ(0u, toolbar_model()->visible_icon_count());
// Add one action. It should be visible.
service()->AddExtension(extension_a.get());
EXPECT_EQ(1u, num_actions());
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_actions());
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_actions());
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_actions());
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 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_actions());
// Should be at max size.
EXPECT_TRUE(toolbar_model()->all_icons_visible());
EXPECT_EQ(num_actions(), toolbar_model()->visible_icon_count());
toolbar_model()->OnActionToolbarPrefChange();
// Should still be at max size.
EXPECT_TRUE(toolbar_model()->all_icons_visible());
EXPECT_EQ(num_actions(), toolbar_model()->visible_icon_count());
}
// 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, TestToolbarExtensionTypesEnabledSwitch) {
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_actions());
EXPECT_EQ(browser_action()->id(), GetActionIdAtIndex(0u));
EXPECT_EQ(page_action()->id(), GetActionIdAtIndex(1u));
EXPECT_EQ(no_action()->id(), GetActionIdAtIndex(2u));
// Extensions that are installed by default shouldn't be given an icon.
extensions::DictionaryBuilder default_installed_manifest;
default_installed_manifest.Set("name", "default installed")
.Set("description", "A default installed extension")
.Set("manifest_version", 2)
.Set("version", "1.0.0.0");
scoped_refptr<const extensions::Extension> default_installed_extension =
extensions::ExtensionBuilder()
.SetManifest(default_installed_manifest.Build())
.SetID(crx_file::id_util::GenerateId("default"))
.SetLocation(ManifestLocation::kInternal)
.AddFlags(extensions::Extension::WAS_INSTALLED_BY_DEFAULT)
.Build();
EXPECT_TRUE(AddExtension(default_installed_extension.get()));
EXPECT_EQ(3u, num_actions());
EXPECT_FALSE(ModelHasActionForId(default_installed_extension->id()));
// Component extensions shouldn't be given an icon.
scoped_refptr<const extensions::Extension> component_extension_no_action =
extensions::ExtensionBuilder("component ext no action")
.SetLocation(ManifestLocation::kComponent)
.Build();
EXPECT_TRUE(AddExtension(component_extension_no_action.get()));
EXPECT_EQ(3u, num_actions());
EXPECT_FALSE(ModelHasActionForId(component_extension_no_action->id()));
// Sanity check: A new extension that's installed from the webstore should
// have an icon.
scoped_refptr<const extensions::Extension> internal_extension_no_action =
extensions::ExtensionBuilder("internal ext no action")
.SetLocation(ManifestLocation::kInternal)
.Build();
EXPECT_TRUE(AddExtension(internal_extension_no_action.get()));
EXPECT_EQ(4u, num_actions());
EXPECT_TRUE(ModelHasActionForId(internal_extension_no_action->id()));
}
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()->GetPrimaryOTRProfile(/*create_if_needed=*/true));
ToolbarActionsModelTestObserver incognito_observer(incognito_model);
EXPECT_EQ(0u, incognito_observer.moved_count());
// We should have two actions: C, B, and the order should be preserved from
// the original model.
EXPECT_EQ(2u, incognito_model->action_ids().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 < base::size(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]->UnpackedPath());
std::string id = crx_file::id_util::GenerateIdForPath(path_for_id);
extensions::TestExtensionRegistryObserver observer(registry(), id);
extensions::UnpackedInstaller::Create(service())->Load(
dirs[i]->UnpackedPath());
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()->action_ids().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()->GetPrimaryOTRProfile(/*create_if_needed=*/true));
ToolbarActionsModelTestObserver incognito_observer(incognito_model);
// Right now, no actions are enabled in incognito mode.
EXPECT_EQ(0u, incognito_model->action_ids().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->action_ids().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->action_ids().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 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::TestExtensionSystem*>(
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, LocationBarModelPrefChange) {
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<std::string> 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,
TestUninstallVisibleExtensionDoesntBringOutOther) {
Init();
ASSERT_TRUE(AddBrowserActionExtensions());
toolbar_model()->SetVisibleIconCount(2u);
EXPECT_EQ(3u, num_actions());
EXPECT_EQ(2u, toolbar_model()->visible_icon_count());
EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u));
EXPECT_EQ(browser_action_b()->id(), GetActionIdAtIndex(1u));
EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(2u));
service()->UninstallExtension(browser_action_b()->id(),
extensions::UNINSTALL_REASON_FOR_TESTING,
nullptr);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2u, num_actions());
EXPECT_EQ(1u, toolbar_model()->visible_icon_count());
EXPECT_EQ(browser_action_a()->id(), GetActionIdAtIndex(0u));
EXPECT_EQ(browser_action_c()->id(), GetActionIdAtIndex(1u));
}
// Test that user-script extensions show up on the toolbar.
TEST_F(ToolbarActionsModelUnitTest, AddUserScriptExtension) {
Init();
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("a")
.SetLocation(ManifestLocation::kInternal)
.MergeManifest(extensions::DictionaryBuilder()
.Set("converted_from_user_script", true)
.Build())
.Build();
// We should start off without any actions.
EXPECT_EQ(0u, num_actions());
EXPECT_EQ(0u, toolbar_model()->visible_icon_count());
// Add the extension. It should be visible.
service()->AddExtension(extension.get());
EXPECT_EQ(1u, num_actions());
EXPECT_EQ(1u, toolbar_model()->visible_icon_count());
EXPECT_EQ(extension.get()->id(), GetActionIdAtIndex(0u));
}
TEST_F(ToolbarActionsModelUnitTest, IsActionPinnedCorrespondsToPinningState) {
Init();
ASSERT_TRUE(AddBrowserActionExtensions());
// The actions should initially not be pinned.
EXPECT_FALSE(toolbar_model()->IsActionPinned(browser_action_a()->id()));
// Pinning is reflected in |IsActionPinned|.
toolbar_model()->SetActionVisibility(browser_action_a()->id(), true);
EXPECT_TRUE(toolbar_model()->IsActionPinned(browser_action_a()->id()));
// Removing pinning should also be reflected in |IsActionPinned|.
toolbar_model()->SetActionVisibility(browser_action_a()->id(), false);
EXPECT_FALSE(toolbar_model()->IsActionPinned(browser_action_a()->id()));
}
TEST_F(ToolbarActionsModelUnitTest,
TogglingVisibilityAppendsToPinnedExtensions) {
Init();
ASSERT_TRUE(AddBrowserActionExtensions());
EXPECT_THAT(toolbar_model()->pinned_action_ids(), testing::IsEmpty());
toolbar_model()->SetActionVisibility(browser_action_a()->id(), true);
EXPECT_THAT(toolbar_model()->pinned_action_ids(),
testing::ElementsAre(browser_action_a()->id()));
// Pin the remaining two extensions.
toolbar_model()->SetActionVisibility(browser_action_b()->id(), true);
toolbar_model()->SetActionVisibility(browser_action_c()->id(), true);
// Verify that they are added in order.
EXPECT_THAT(
toolbar_model()->pinned_action_ids(),
testing::ElementsAre(browser_action_a()->id(), browser_action_b()->id(),
browser_action_c()->id()));
// Verify that unpinning an extension updates the list of pinned ids.
toolbar_model()->SetActionVisibility(browser_action_b()->id(), false);
EXPECT_THAT(
toolbar_model()->pinned_action_ids(),
testing::ElementsAre(browser_action_a()->id(), browser_action_c()->id()));
// Verify that re-pinning an extension adds it back to the end of the list.
toolbar_model()->SetActionVisibility(browser_action_b()->id(), true);
EXPECT_THAT(
toolbar_model()->pinned_action_ids(),
testing::ElementsAre(browser_action_a()->id(), browser_action_c()->id(),
browser_action_b()->id()));
}
TEST_F(ToolbarActionsModelUnitTest, ChangesToPinningNotifiesObserver) {
Init();
ASSERT_TRUE(AddBrowserActionExtensions());
// The observer should not think that any extensions are initially pinned.
EXPECT_THAT(observer()->last_pinned_action_ids(), testing::IsEmpty());
// Verify that pinning the action notifies the observer.
toolbar_model()->SetActionVisibility(browser_action_a()->id(), true);
EXPECT_THAT(observer()->last_pinned_action_ids(),
testing::ElementsAre(browser_action_a()->id()));
// Verify that un-pinning an action also notifies the observer.
toolbar_model()->SetActionVisibility(browser_action_a()->id(), false);
EXPECT_THAT(observer()->last_pinned_action_ids(), testing::IsEmpty());
}
TEST_F(ToolbarActionsModelUnitTest, ChangesToPinningSavedInExtensionPrefs) {
Init();
ASSERT_TRUE(AddBrowserActionExtensions());
extensions::ExtensionPrefs* const extension_prefs =
extensions::ExtensionPrefs::Get(profile());
// The preferences shouldn't have any extensions initially pinned.
EXPECT_THAT(extension_prefs->GetPinnedExtensions(), testing::IsEmpty());
// Verify that pinned extensions are reflected in preferences.
toolbar_model()->SetActionVisibility(browser_action_a()->id(), true);
toolbar_model()->SetActionVisibility(browser_action_b()->id(), true);
toolbar_model()->SetActionVisibility(browser_action_c()->id(), true);
EXPECT_THAT(
extension_prefs->GetPinnedExtensions(),
testing::ElementsAre(browser_action_a()->id(), browser_action_b()->id(),
browser_action_c()->id()));
// Verify that un-pinning an action is also reflected in preferences.
toolbar_model()->SetActionVisibility(browser_action_b()->id(), false);
EXPECT_THAT(
extension_prefs->GetPinnedExtensions(),
testing::ElementsAre(browser_action_a()->id(), browser_action_c()->id()));
// Verify that re-pinning is added last.
toolbar_model()->SetActionVisibility(browser_action_b()->id(), true);
EXPECT_THAT(
extension_prefs->GetPinnedExtensions(),
testing::ElementsAre(browser_action_a()->id(), browser_action_c()->id(),
browser_action_b()->id()));
}
TEST_F(ToolbarActionsModelUnitTest, ChangesToExtensionPrefsReflectedInModel) {
Init();
ASSERT_TRUE(AddBrowserActionExtensions());
extensions::ExtensionPrefs* const extension_prefs =
extensions::ExtensionPrefs::Get(profile());
// No actions should be initially pinned.
EXPECT_THAT(toolbar_model()->pinned_action_ids(), testing::IsEmpty());
// Update preferences to indicate that extensions A and C are pinned.
extensions::ExtensionIdList pinned_extension_list = {
browser_action_a()->id(), browser_action_c()->id()};
// Verify that setting the extension preferences updates the model.
extension_prefs->SetPinnedExtensions(pinned_extension_list);
EXPECT_EQ(pinned_extension_list, extension_prefs->GetPinnedExtensions());
EXPECT_EQ(pinned_extension_list, toolbar_model()->pinned_action_ids());
// Verify that the observer is notified as well.
EXPECT_EQ(pinned_extension_list, observer()->last_pinned_action_ids());
}
TEST_F(ToolbarActionsModelUnitTest,
MismatchInPinnedExtensionPreferencesNotReflectedInModel) {
Init();
ASSERT_TRUE(AddBrowserActionExtensions());
extensions::ExtensionPrefs* const extension_prefs =
extensions::ExtensionPrefs::Get(profile());
// No actions should be initially pinned.
EXPECT_THAT(toolbar_model()->pinned_action_ids(), testing::IsEmpty());
// Update preferences to indicate that extensions A and C are pinned.
extensions::ExtensionIdList pinned_extension_list = {
browser_action_a()->id(), browser_action_c()->id()};
extensions::ExtensionIdList pinned_extension_list_with_additional_id =
pinned_extension_list;
pinned_extension_list_with_additional_id.push_back("bogus id");
// Verify that setting the extension preferences updates the model and that
// the additional extension id is filtered out in the model.
extension_prefs->SetPinnedExtensions(
pinned_extension_list_with_additional_id);
EXPECT_EQ(pinned_extension_list_with_additional_id,
extension_prefs->GetPinnedExtensions());
EXPECT_EQ(pinned_extension_list, toolbar_model()->pinned_action_ids());
// Verify that the observer is notified as well.
EXPECT_EQ(pinned_extension_list, observer()->last_pinned_action_ids());
}
TEST_F(ToolbarActionsModelUnitTest, PinnedExtensionsFilteredOnInitialization) {
Init();
ASSERT_TRUE(AddBrowserActionExtensions());
extensions::ExtensionPrefs* const extension_prefs =
extensions::ExtensionPrefs::Get(profile());
// Update preferences to indicate that extensions A and a "bogus id" one is
// set.
extensions::ExtensionIdList pinned_extension_list_with_additional_id = {
browser_action_a()->id(), "bogus id"};
extension_prefs->SetPinnedExtensions(
pinned_extension_list_with_additional_id);
// Create a model after setting the prefs, this is done to ensure that the
// pinned preferences are loaded and correctly filtered.
ToolbarActionsModel model_created_after_prefs_set(profile(), extension_prefs);
// Wait for load to happen (::OnReady is posted from ToolbarActionModel's
// constructor).
base::RunLoop().RunUntilIdle();
EXPECT_EQ(pinned_extension_list_with_additional_id,
extension_prefs->GetPinnedExtensions());
// Verify that the new model loads the same action_ids() and
// pinned_action_ids() from ExtensionPrefs that |toolbar_model()| should have
// saved.
EXPECT_EQ(toolbar_model()->pinned_action_ids(),
model_created_after_prefs_set.pinned_action_ids());
EXPECT_EQ(toolbar_model()->action_ids(),
model_created_after_prefs_set.action_ids());
// Verify that the new model's pinned action IDs have been pruned down to only
// extension a.
EXPECT_THAT(model_created_after_prefs_set.pinned_action_ids(),
testing::ElementsAre(browser_action_a()->id()));
}
TEST_F(ToolbarActionsModelUnitTest, ChangesToPinnedOrderSavedInExtensionPrefs) {
Init();
ASSERT_TRUE(AddBrowserActionExtensions());
extensions::ExtensionPrefs* const extension_prefs =
extensions::ExtensionPrefs::Get(profile());
// The preferences shouldn't have any extensions initially pinned.
EXPECT_THAT(extension_prefs->GetPinnedExtensions(), testing::IsEmpty());
// Verify that pinned extensions are reflected in preferences.
toolbar_model()->SetActionVisibility(browser_action_a()->id(), true);
toolbar_model()->SetActionVisibility(browser_action_b()->id(), true);
toolbar_model()->SetActionVisibility(browser_action_c()->id(), true);
EXPECT_THAT(
extension_prefs->GetPinnedExtensions(),
testing::ElementsAre(browser_action_a()->id(), browser_action_b()->id(),
browser_action_c()->id()));
// Verify that moving an action left to right is reflected in preferences.
toolbar_model()->MovePinnedAction(browser_action_b()->id(), 2);
EXPECT_THAT(
extension_prefs->GetPinnedExtensions(),
testing::ElementsAre(browser_action_a()->id(), browser_action_c()->id(),
browser_action_b()->id()));
// Verify that moving an action right to left is reflected in preferences.
toolbar_model()->MovePinnedAction(browser_action_b()->id(), 0);
EXPECT_THAT(
extension_prefs->GetPinnedExtensions(),
testing::ElementsAre(browser_action_b()->id(), browser_action_a()->id(),
browser_action_c()->id()));
// Verify that moving an action to index greater than rightmost index is
// reflected in preferences as at the right end.
toolbar_model()->MovePinnedAction(browser_action_b()->id(), 4);
EXPECT_THAT(
extension_prefs->GetPinnedExtensions(),
testing::ElementsAre(browser_action_a()->id(), browser_action_c()->id(),
browser_action_b()->id()));
}
TEST_F(ToolbarActionsModelUnitTest, PinStateErasedOnUninstallation) {
Init();
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("extension")
.SetAction(ActionType::BROWSER_ACTION)
.SetLocation(ManifestLocation::kInternal)
.Build();
// Add and pin an extension.
EXPECT_TRUE(AddExtension(extension));
EXPECT_FALSE(toolbar_model()->IsActionPinned(extension->id()));
extensions::ExtensionPrefs* const prefs =
extensions::ExtensionPrefs::Get(profile());
EXPECT_THAT(prefs->GetPinnedExtensions(), testing::IsEmpty());
toolbar_model()->SetActionVisibility(extension->id(), true);
EXPECT_TRUE(toolbar_model()->IsActionPinned(extension->id()));
EXPECT_THAT(prefs->GetPinnedExtensions(),
testing::ElementsAre(extension->id()));
// Uninstall the extension. The pin state should be forgotten.
service()->UninstallExtension(
extension->id(), extensions::UNINSTALL_REASON_FOR_TESTING, nullptr);
EXPECT_FALSE(toolbar_model()->IsActionPinned(extension->id()));
EXPECT_THAT(prefs->GetPinnedExtensions(), testing::IsEmpty());
// Re-add the extension. It should be in the default (unpinned) state.
EXPECT_TRUE(AddExtension(extension));
EXPECT_FALSE(toolbar_model()->IsActionPinned(extension->id()));
EXPECT_THAT(prefs->GetPinnedExtensions(), testing::IsEmpty());
}
TEST_F(ToolbarActionsModelUnitTest, ForcePinnedByPolicy) {
Init();
// Set the extension to force-pin via enterprise policy.
std::string extension_id = crx_file::id_util::GenerateId("qwertyuiop");
std::string json = base::StringPrintf(
R"({
"%s": {
"toolbar_pin": "force_pinned"
}
})",
extension_id.c_str());
base::Optional<base::Value> parsed = base::JSONReader::Read(json);
policy::PolicyMap map;
map.Set("ExtensionSettings", policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_PLATFORM,
std::move(parsed), nullptr);
policy_provider()->UpdateChromePolicy(map);
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("test")
.SetAction(ActionType::BROWSER_ACTION)
.SetLocation(ManifestLocation::kInternal)
.SetID(extension_id)
.Build();
// Add an extension. It should auto-pin because of the ExtensionSettings
// policy.
EXPECT_TRUE(AddExtension(extension));
EXPECT_TRUE(toolbar_model()->IsActionPinned(extension->id()));
auto* prefs = extensions::ExtensionPrefs::Get(profile());
EXPECT_FALSE(base::Contains(prefs->GetPinnedExtensions(), extension_id));
}