1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/extensions/extension_action_manager.h" 6 7 #include "base/strings/string_number_conversions.h" 8 #include "chrome/browser/extensions/extension_action.h" 9 #include "chrome/test/base/testing_profile.h" 10 #include "extensions/browser/extension_registry.h" 11 #include "extensions/common/extension_builder.h" 12 #include "extensions/common/manifest_handlers/icons_handler.h" 13 #include "extensions/common/value_builder.h" 14 #include "testing/gtest/include/gtest/gtest.h" 15 16 namespace extensions { 17 18 namespace { 19 20 const char kBrowserAction[] = "browser_action"; 21 const char kPageAction[] = "page_action"; 22 23 } // namespace 24 25 class ExtensionActionManagerTest : public testing::Test { 26 public: 27 ExtensionActionManagerTest(); 28 29 protected: 30 // Build an extension, populating |action_type| key with |action|, and 31 // "icons" key with |extension_icons|. 32 scoped_refptr<Extension> BuildExtension(DictionaryBuilder& extension_icons, 33 DictionaryBuilder& action, 34 const char* action_type); 35 36 // Returns true if |action|'s title matches |extension|'s name. 37 bool TitlesMatch(const Extension& extension, const ExtensionAction& action); 38 39 // Returns true if |action|'s icon for size |action_key| matches 40 // |extension|'s icon for size |extension_key|; 41 bool IconsMatch(const Extension& extension, 42 int extension_key, 43 const ExtensionAction& action, 44 int action_key); 45 46 // Returns the appropriate action for |extension| according to |action_type|. 47 ExtensionAction* GetAction(const char* action_type, 48 const Extension& extension); 49 50 // Tests that values that are missing from the |action_type| key are properly 51 // populated with values from the other keys in the manifest (e.g. 52 // "default_icon" key of |action_type| is populated with "icons" key). 53 void TestPopulateMissingValues(const char* action_type); 54 55 ExtensionActionManager* manager() { return manager_; } 56 57 private: 58 ExtensionRegistry* registry_; 59 int curr_id_; 60 ExtensionActionManager* manager_; 61 scoped_ptr<TestingProfile> profile_; 62 }; 63 64 ExtensionActionManagerTest::ExtensionActionManagerTest() 65 : curr_id_(0), 66 profile_(new TestingProfile) { 67 registry_ = ExtensionRegistry::Get(profile_.get()); 68 manager_ = ExtensionActionManager::Get(profile_.get()); 69 } 70 71 scoped_refptr<Extension> ExtensionActionManagerTest::BuildExtension( 72 DictionaryBuilder& extension_icons, 73 DictionaryBuilder& action, 74 const char* action_type) { 75 std::string id = base::IntToString(curr_id_++); 76 scoped_refptr<Extension> extension = ExtensionBuilder() 77 .SetManifest(DictionaryBuilder().Set("version", "1") 78 .Set("manifest_version", 2) 79 .Set("icons", extension_icons) 80 .Set(action_type, action) 81 .Set("name", 82 std::string("Test Extension").append(id))) 83 .SetID(id) 84 .Build(); 85 registry_->AddEnabled(extension); 86 return extension; 87 } 88 89 bool ExtensionActionManagerTest::TitlesMatch(const Extension& extension, 90 const ExtensionAction& action) { 91 return action.GetTitle(ExtensionAction::kDefaultTabId) == extension.name(); 92 } 93 94 bool ExtensionActionManagerTest::IconsMatch(const Extension& extension, 95 int extension_key, 96 const ExtensionAction& action, 97 int action_key) { 98 return action.default_icon()->Get(action_key, 99 ExtensionIconSet::MATCH_EXACTLY) == 100 IconsInfo::GetIcons(&extension).Get(extension_key, 101 ExtensionIconSet::MATCH_EXACTLY); 102 } 103 104 ExtensionAction* ExtensionActionManagerTest::GetAction( 105 const char* action_type, 106 const Extension& extension) { 107 return (action_type == kBrowserAction) ? 108 manager_->GetBrowserAction(extension) : 109 manager_->GetPageAction(extension); 110 } 111 112 void ExtensionActionManagerTest::TestPopulateMissingValues( 113 const char* action_type) { 114 // Test that the largest icon from the extension's "icons" key is chosen as a 115 // replacement for missing action default_icons keys. "19" should not be 116 // replaced because "38" can always be used in its place. 117 scoped_refptr<Extension> extension = BuildExtension( 118 DictionaryBuilder().Set("48", "icon48.png") 119 .Set("128", "icon128.png"), 120 DictionaryBuilder().Pass(), 121 action_type); 122 123 ASSERT_TRUE(extension.get()); 124 const ExtensionAction* action = GetAction(action_type, *extension.get()); 125 ASSERT_TRUE(action); 126 127 ASSERT_TRUE(TitlesMatch(*extension.get(), *action)); 128 ASSERT_TRUE(IconsMatch(*extension.get(), 128, *action, 38)); 129 130 // Test that the action's missing default_icons are not replaced with smaller 131 // icons. 132 extension = BuildExtension( 133 DictionaryBuilder().Set("24", "icon24.png"), 134 DictionaryBuilder().Pass(), 135 action_type); 136 137 ASSERT_TRUE(extension.get()); 138 action = GetAction(action_type, *extension.get()); 139 ASSERT_TRUE(action); 140 141 ASSERT_TRUE(IconsMatch(*extension.get(), 24, *action, 19)); 142 ASSERT_FALSE(IconsMatch(*extension.get(), 24, *action, 38)); 143 144 // Test that an action's 19px icon is not replaced if a 38px action icon 145 // exists. 146 extension = BuildExtension( 147 DictionaryBuilder().Set("128", "icon128.png"), 148 DictionaryBuilder().Set("default_icon", DictionaryBuilder() 149 .Set("38", "action38.png")), 150 action_type); 151 152 ASSERT_TRUE(extension.get()); 153 action = GetAction(action_type, *extension.get()); 154 ASSERT_TRUE(action); 155 156 ASSERT_FALSE(IconsMatch(*extension.get(), 128, *action, 19)); 157 158 // Test that existing default_icons and default_title are not replaced. 159 extension = BuildExtension( 160 DictionaryBuilder().Set("128", "icon128.png"), 161 DictionaryBuilder().Set("default_title", "Action!") 162 .Set("default_icon", DictionaryBuilder() 163 .Set("19", "action19.png") 164 .Set("38", "action38.png")), 165 action_type); 166 167 ASSERT_TRUE(extension.get()); 168 action = GetAction(action_type, *extension.get()); 169 ASSERT_TRUE(action); 170 171 ASSERT_FALSE(TitlesMatch(*extension.get(), *action)); 172 ASSERT_FALSE(IconsMatch(*extension.get(), 128, *action, 19)); 173 ASSERT_FALSE(IconsMatch(*extension.get(), 128, *action, 38)); 174 } 175 176 namespace { 177 178 TEST_F(ExtensionActionManagerTest, PopulateBrowserAction) { 179 TestPopulateMissingValues(kBrowserAction); 180 } 181 182 TEST_F(ExtensionActionManagerTest, PopulatePageAction) { 183 TestPopulateMissingValues(kPageAction); 184 } 185 186 TEST_F(ExtensionActionManagerTest, GetBestFitActionTest) { 187 // Create an extension with page action defaults. 188 scoped_refptr<Extension> extension = BuildExtension( 189 DictionaryBuilder().Set("48", "icon48.png"), 190 DictionaryBuilder().Set("default_title", "Action!") 191 .Set("default_icon", DictionaryBuilder() 192 .Set("38", "action38.png")), 193 kPageAction); 194 ASSERT_TRUE(extension.get()); 195 196 // Get a "best fit" browser action for |extension|. 197 scoped_ptr<ExtensionAction> action = 198 manager()->GetBestFitAction(*extension.get(), ActionInfo::TYPE_BROWSER); 199 ASSERT_TRUE(action.get()); 200 ASSERT_EQ(action->action_type(), ActionInfo::TYPE_BROWSER); 201 202 // |action|'s title and default icon should match |extension|'s page action's. 203 ASSERT_EQ(action->GetTitle(ExtensionAction::kDefaultTabId), "Action!"); 204 ASSERT_EQ(action->default_icon()->Get(38, ExtensionIconSet::MATCH_EXACTLY), 205 "action38.png"); 206 207 // Create a new extension without page action defaults. 208 extension = BuildExtension( 209 DictionaryBuilder().Set("48", "icon48.png"), 210 DictionaryBuilder().Pass(), 211 kPageAction); 212 ASSERT_TRUE(extension.get()); 213 214 action = 215 manager()->GetBestFitAction(*extension.get(), ActionInfo::TYPE_BROWSER); 216 217 // Now these values match because |extension| does not have page action 218 // defaults. 219 ASSERT_TRUE(TitlesMatch(*extension.get(), *action)); 220 ASSERT_TRUE(IconsMatch(*extension.get(), 48, *action, 38)); 221 } 222 223 } // namespace 224 } // namespace extensions 225