1 // Copyright (c) 2012 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/ui/ash/launcher/chrome_launcher_controller_per_browser.h" 6 7 #include <algorithm> 8 #include <string> 9 #include <vector> 10 11 #include "ash/launcher/launcher_model.h" 12 #include "base/command_line.h" 13 #include "base/compiler_specific.h" 14 #include "base/files/file_path.h" 15 #include "base/memory/scoped_ptr.h" 16 #include "base/run_loop.h" 17 #include "base/values.h" 18 #include "chrome/browser/extensions/extension_service.h" 19 #include "chrome/browser/extensions/test_extension_system.h" 20 #include "chrome/browser/ui/ash/chrome_launcher_prefs.h" 21 #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" 22 #include "chrome/common/extensions/extension.h" 23 #include "chrome/common/pref_names.h" 24 #include "chrome/test/base/testing_pref_service_syncable.h" 25 #include "chrome/test/base/testing_profile.h" 26 #include "content/public/test/test_browser_thread_bundle.h" 27 #include "testing/gtest/include/gtest/gtest.h" 28 29 #if defined(OS_CHROMEOS) 30 #include "chrome/browser/chromeos/login/user_manager.h" 31 #include "chrome/browser/chromeos/settings/cros_settings.h" 32 #include "chrome/browser/chromeos/settings/device_settings_service.h" 33 #endif 34 35 using extensions::Extension; 36 using extensions::Manifest; 37 38 class ChromeLauncherControllerPerBrowserTest : public testing::Test { 39 protected: 40 ChromeLauncherControllerPerBrowserTest() 41 : profile_(new TestingProfile()), 42 extension_service_(NULL) { 43 DictionaryValue manifest; 44 manifest.SetString("name", "launcher controller test extension"); 45 manifest.SetString("version", "1"); 46 manifest.SetString("description", "for testing pinned apps"); 47 48 extensions::TestExtensionSystem* extension_system( 49 static_cast<extensions::TestExtensionSystem*>( 50 extensions::ExtensionSystem::Get(profile_.get()))); 51 extension_service_ = extension_system->CreateExtensionService( 52 CommandLine::ForCurrentProcess(), base::FilePath(), false); 53 54 std::string error; 55 extension1_ = Extension::Create(base::FilePath(), Manifest::UNPACKED, 56 manifest, 57 Extension::NO_FLAGS, 58 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 59 &error); 60 extension2_ = Extension::Create(base::FilePath(), Manifest::UNPACKED, 61 manifest, 62 Extension::NO_FLAGS, 63 "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 64 &error); 65 // Fake gmail extension. 66 extension3_ = Extension::Create(base::FilePath(), Manifest::UNPACKED, 67 manifest, 68 Extension::NO_FLAGS, 69 extension_misc::kGmailAppId, 70 &error); 71 // Fake search extension. 72 extension4_ = Extension::Create(base::FilePath(), Manifest::UNPACKED, 73 manifest, 74 Extension::NO_FLAGS, 75 extension_misc::kGoogleSearchAppId, 76 &error); 77 } 78 79 virtual void TearDown() OVERRIDE { 80 profile_.reset(); 81 // Execute any pending deletion tasks. 82 base::RunLoop().RunUntilIdle(); 83 } 84 85 void InsertPrefValue(base::ListValue* pref_value, 86 int index, 87 const std::string& extension_id) { 88 base::DictionaryValue* entry = new DictionaryValue(); 89 entry->SetString(ash::kPinnedAppsPrefAppIDPath, extension_id); 90 pref_value->Insert(index, entry); 91 } 92 93 // Gets the currently configured app launchers from the controller. 94 void GetAppLaunchers(ChromeLauncherControllerPerBrowser* controller, 95 std::vector<std::string>* launchers) { 96 launchers->clear(); 97 for (ash::LauncherItems::const_iterator iter(model_.items().begin()); 98 iter != model_.items().end(); ++iter) { 99 ChromeLauncherControllerPerBrowser::IDToItemControllerMap::const_iterator 100 entry(controller->id_to_item_controller_map_.find(iter->id)); 101 if (iter->type == ash::TYPE_APP_SHORTCUT && 102 entry != controller->id_to_item_controller_map_.end()) { 103 launchers->push_back(entry->second->app_id()); 104 } 105 } 106 } 107 108 std::string GetPinnedAppStatus( 109 ChromeLauncherController* launcher_controller) { 110 std::string result; 111 for (int i = 0; i < model_.item_count(); i++) { 112 switch (model_.items()[i].type) { 113 case ash::TYPE_APP_SHORTCUT: { 114 const std::string& app = 115 launcher_controller->GetAppIDForLauncherID( 116 model_.items()[i].id); 117 if (app == extension1_->id()) { 118 result += "App1, "; 119 EXPECT_TRUE(launcher_controller->IsAppPinned(extension1_->id())); 120 } else if (app == extension2_->id()) { 121 result += "App2, "; 122 EXPECT_TRUE(launcher_controller->IsAppPinned(extension2_->id())); 123 } else if (app == extension3_->id()) { 124 result += "App3, "; 125 EXPECT_TRUE(launcher_controller->IsAppPinned(extension3_->id())); 126 } else { 127 result += "unknown"; 128 } 129 break; 130 } 131 case ash::TYPE_BROWSER_SHORTCUT: 132 result += "Chrome, "; 133 break; 134 case ash::TYPE_APP_LIST: 135 result += "AppList"; 136 break; 137 default: 138 result += "Unknown"; 139 break; 140 } 141 } 142 return result; 143 } 144 145 // Needed for extension service & friends to work. 146 content::TestBrowserThreadBundle thread_bundle_; 147 148 #if defined OS_CHROMEOS 149 chromeos::ScopedTestDeviceSettingsService test_device_settings_service_; 150 chromeos::ScopedTestCrosSettings test_cros_settings_; 151 chromeos::ScopedTestUserManager test_user_manager_; 152 #endif 153 154 scoped_refptr<Extension> extension1_; 155 scoped_refptr<Extension> extension2_; 156 scoped_refptr<Extension> extension3_; 157 scoped_refptr<Extension> extension4_; 158 scoped_ptr<TestingProfile> profile_; 159 ash::LauncherModel model_; 160 161 ExtensionService* extension_service_; 162 163 DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerPerBrowserTest); 164 }; 165 166 TEST_F(ChromeLauncherControllerPerBrowserTest, DefaultApps) { 167 ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(), 168 &model_); 169 launcher_controller.Init(); 170 171 // Model should only contain the browser shortcut and app list items. 172 EXPECT_EQ(2, model_.item_count()); 173 EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); 174 EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); 175 EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); 176 177 // Installing |extension3_| should add it to the launcher - behind the 178 // chrome icon. 179 extension_service_->AddExtension(extension3_.get()); 180 EXPECT_EQ("Chrome, App3, AppList", GetPinnedAppStatus(&launcher_controller)); 181 EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); 182 EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); 183 } 184 185 // Check that the restauration of launcher items is happening in the same order 186 // as the user has pinned them (on another system) when they are synced reverse 187 // order. 188 TEST_F(ChromeLauncherControllerPerBrowserTest, RestoreDefaultAppsReverseOrder) { 189 ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(), 190 &model_); 191 launcher_controller.Init(); 192 193 base::ListValue policy_value; 194 InsertPrefValue(&policy_value, 0, extension1_->id()); 195 InsertPrefValue(&policy_value, 1, extension2_->id()); 196 InsertPrefValue(&policy_value, 2, extension3_->id()); 197 profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, 198 policy_value.DeepCopy()); 199 EXPECT_EQ(0, profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex)); 200 // Model should only contain the browser shortcut and app list items. 201 EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); 202 EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); 203 EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); 204 EXPECT_EQ("Chrome, AppList", GetPinnedAppStatus(&launcher_controller)); 205 206 // Installing |extension3_| should add it to the launcher - behind the 207 // chrome icon. 208 ash::LauncherItem item; 209 extension_service_->AddExtension(extension3_.get()); 210 EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); 211 EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); 212 EXPECT_EQ("Chrome, App3, AppList", GetPinnedAppStatus(&launcher_controller)); 213 214 // Installing |extension2_| should add it to the launcher - behind the 215 // chrome icon, but in first location. 216 extension_service_->AddExtension(extension2_.get()); 217 EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); 218 EXPECT_EQ("Chrome, App2, App3, AppList", 219 GetPinnedAppStatus(&launcher_controller)); 220 221 // Installing |extension1_| should add it to the launcher - behind the 222 // chrome icon, but in first location. 223 extension_service_->AddExtension(extension1_.get()); 224 EXPECT_EQ("Chrome, App1, App2, App3, AppList", 225 GetPinnedAppStatus(&launcher_controller)); 226 } 227 228 // Check that the restauration of launcher items is happening in the same order 229 // as the user has pinned them (on another system) when they are synced random 230 // order. 231 TEST_F(ChromeLauncherControllerPerBrowserTest, RestoreDefaultAppsRandomOrder) { 232 ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(), 233 &model_); 234 launcher_controller.Init(); 235 236 base::ListValue policy_value; 237 InsertPrefValue(&policy_value, 0, extension1_->id()); 238 InsertPrefValue(&policy_value, 1, extension2_->id()); 239 InsertPrefValue(&policy_value, 2, extension3_->id()); 240 profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, 241 policy_value.DeepCopy()); 242 EXPECT_EQ(0, profile_->GetPrefs()->GetInteger(prefs::kShelfChromeIconIndex)); 243 // Model should only contain the browser shortcut and app list items. 244 EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); 245 EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); 246 EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); 247 EXPECT_EQ("Chrome, AppList", GetPinnedAppStatus(&launcher_controller)); 248 249 // Installing |extension2_| should add it to the launcher - behind the 250 // chrome icon. 251 extension_service_->AddExtension(extension2_.get()); 252 EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); 253 EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); 254 EXPECT_EQ("Chrome, App2, AppList", GetPinnedAppStatus(&launcher_controller)); 255 256 // Installing |extension1_| should add it to the launcher - behind the 257 // chrome icon, but in first location. 258 extension_service_->AddExtension(extension1_.get()); 259 EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); 260 EXPECT_EQ("Chrome, App1, App2, AppList", 261 GetPinnedAppStatus(&launcher_controller)); 262 263 // Installing |extension3_| should add it to the launcher - behind the 264 // chrome icon, but in first location. 265 extension_service_->AddExtension(extension3_.get()); 266 EXPECT_EQ("Chrome, App1, App2, App3, AppList", 267 GetPinnedAppStatus(&launcher_controller)); 268 } 269 270 // Check that the restauration of launcher items is happening in the same order 271 // as the user has pinned / moved them (on another system) when they are synced 272 // random order - including the chrome icon. 273 TEST_F(ChromeLauncherControllerPerBrowserTest, 274 RestoreDefaultAppsRandomOrderChromeMoved) { 275 ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(), 276 &model_); 277 launcher_controller.Init(); 278 base::ListValue policy_value; 279 InsertPrefValue(&policy_value, 0, extension1_->id()); 280 InsertPrefValue(&policy_value, 1, extension2_->id()); 281 InsertPrefValue(&policy_value, 2, extension3_->id()); 282 profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, 283 policy_value.DeepCopy()); 284 profile_->GetTestingPrefService()->SetInteger(prefs::kShelfChromeIconIndex, 285 1); 286 // Model should only contain the browser shortcut and app list items. 287 EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); 288 EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); 289 EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); 290 EXPECT_EQ("Chrome, AppList", GetPinnedAppStatus(&launcher_controller)); 291 292 // Installing |extension2_| should add it to the launcher - behind the 293 // chrome icon. 294 ash::LauncherItem item; 295 extension_service_->AddExtension(extension2_.get()); 296 EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); 297 EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); 298 EXPECT_EQ("Chrome, App2, AppList", GetPinnedAppStatus(&launcher_controller)); 299 300 // Installing |extension1_| should add it to the launcher - behind the 301 // chrome icon, but in first location. 302 extension_service_->AddExtension(extension1_.get()); 303 EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); 304 EXPECT_EQ("App1, Chrome, App2, AppList", 305 GetPinnedAppStatus(&launcher_controller)); 306 307 // Installing |extension3_| should add it to the launcher - behind the 308 // chrome icon, but in first location. 309 extension_service_->AddExtension(extension3_.get()); 310 EXPECT_EQ("App1, Chrome, App2, App3, AppList", 311 GetPinnedAppStatus(&launcher_controller)); 312 } 313 314 TEST_F(ChromeLauncherControllerPerBrowserTest, Policy) { 315 extension_service_->AddExtension(extension1_.get()); 316 extension_service_->AddExtension(extension3_.get()); 317 318 base::ListValue policy_value; 319 InsertPrefValue(&policy_value, 0, extension1_->id()); 320 InsertPrefValue(&policy_value, 1, extension2_->id()); 321 profile_->GetTestingPrefService()->SetManagedPref(prefs::kPinnedLauncherApps, 322 policy_value.DeepCopy()); 323 324 // Only |extension1_| should get pinned. |extension2_| is specified but not 325 // installed, and |extension3_| is part of the default set, but that shouldn't 326 // take effect when the policy override is in place. 327 ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(), 328 &model_); 329 launcher_controller.Init(); 330 EXPECT_EQ(3, model_.item_count()); 331 EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[1].type); 332 EXPECT_TRUE(launcher_controller.IsAppPinned(extension1_->id())); 333 EXPECT_FALSE(launcher_controller.IsAppPinned(extension2_->id())); 334 EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); 335 336 EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_.items()[0].type); 337 EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[1].type); 338 EXPECT_EQ(ash::TYPE_APP_LIST, model_.items()[2].type); 339 340 // Installing |extension2_| should add it to the launcher. 341 extension_service_->AddExtension(extension2_.get()); 342 EXPECT_EQ(4, model_.item_count()); 343 EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_.items()[0].type); 344 EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[1].type); 345 EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[2].type); 346 EXPECT_EQ(ash::TYPE_APP_LIST, model_.items()[3].type); 347 EXPECT_TRUE(launcher_controller.IsAppPinned(extension1_->id())); 348 EXPECT_TRUE(launcher_controller.IsAppPinned(extension2_->id())); 349 EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); 350 351 // Removing |extension1_| from the policy should be reflected in the launcher. 352 policy_value.Remove(0, NULL); 353 profile_->GetTestingPrefService()->SetManagedPref(prefs::kPinnedLauncherApps, 354 policy_value.DeepCopy()); 355 EXPECT_EQ(3, model_.item_count()); 356 EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_.items()[1].type); 357 EXPECT_FALSE(launcher_controller.IsAppPinned(extension1_->id())); 358 EXPECT_TRUE(launcher_controller.IsAppPinned(extension2_->id())); 359 EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); 360 } 361 362 TEST_F(ChromeLauncherControllerPerBrowserTest, UnpinWithUninstall) { 363 extension_service_->AddExtension(extension3_.get()); 364 extension_service_->AddExtension(extension4_.get()); 365 366 ChromeLauncherControllerPerBrowser launcher_controller(profile_.get(), 367 &model_); 368 launcher_controller.Init(); 369 370 EXPECT_TRUE(launcher_controller.IsAppPinned(extension3_->id())); 371 EXPECT_TRUE(launcher_controller.IsAppPinned(extension4_->id())); 372 373 extension_service_->UnloadExtension(extension3_->id(), 374 extension_misc::UNLOAD_REASON_UNINSTALL); 375 376 EXPECT_FALSE(launcher_controller.IsAppPinned(extension3_->id())); 377 EXPECT_TRUE(launcher_controller.IsAppPinned(extension4_->id())); 378 } 379 380 TEST_F(ChromeLauncherControllerPerBrowserTest, PrefUpdates) { 381 extension_service_->AddExtension(extension2_.get()); 382 extension_service_->AddExtension(extension3_.get()); 383 extension_service_->AddExtension(extension4_.get()); 384 ChromeLauncherControllerPerBrowser controller(profile_.get(), &model_); 385 controller.Init(); 386 387 std::vector<std::string> expected_launchers; 388 std::vector<std::string> actual_launchers; 389 base::ListValue pref_value; 390 profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, 391 pref_value.DeepCopy()); 392 GetAppLaunchers(&controller, &actual_launchers); 393 EXPECT_EQ(expected_launchers, actual_launchers); 394 395 // Unavailable extensions don't create launcher items. 396 InsertPrefValue(&pref_value, 0, extension1_->id()); 397 InsertPrefValue(&pref_value, 1, extension2_->id()); 398 InsertPrefValue(&pref_value, 2, extension4_->id()); 399 profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, 400 pref_value.DeepCopy()); 401 expected_launchers.push_back(extension2_->id()); 402 expected_launchers.push_back(extension4_->id()); 403 GetAppLaunchers(&controller, &actual_launchers); 404 EXPECT_EQ(expected_launchers, actual_launchers); 405 406 // Redundant pref entries show up only once. 407 InsertPrefValue(&pref_value, 2, extension3_->id()); 408 InsertPrefValue(&pref_value, 2, extension3_->id()); 409 InsertPrefValue(&pref_value, 5, extension3_->id()); 410 profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, 411 pref_value.DeepCopy()); 412 expected_launchers.insert(expected_launchers.begin() + 1, extension3_->id()); 413 GetAppLaunchers(&controller, &actual_launchers); 414 EXPECT_EQ(expected_launchers, actual_launchers); 415 416 // Order changes are reflected correctly. 417 pref_value.Clear(); 418 InsertPrefValue(&pref_value, 0, extension4_->id()); 419 InsertPrefValue(&pref_value, 1, extension3_->id()); 420 InsertPrefValue(&pref_value, 2, extension2_->id()); 421 profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, 422 pref_value.DeepCopy()); 423 std::reverse(expected_launchers.begin(), expected_launchers.end()); 424 GetAppLaunchers(&controller, &actual_launchers); 425 EXPECT_EQ(expected_launchers, actual_launchers); 426 427 // Clearing works. 428 pref_value.Clear(); 429 profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, 430 pref_value.DeepCopy()); 431 expected_launchers.clear(); 432 GetAppLaunchers(&controller, &actual_launchers); 433 EXPECT_EQ(expected_launchers, actual_launchers); 434 } 435 436 TEST_F(ChromeLauncherControllerPerBrowserTest, PendingInsertionOrder) { 437 extension_service_->AddExtension(extension1_.get()); 438 extension_service_->AddExtension(extension3_.get()); 439 ChromeLauncherControllerPerBrowser controller(profile_.get(), &model_); 440 controller.Init(); 441 442 base::ListValue pref_value; 443 InsertPrefValue(&pref_value, 0, extension1_->id()); 444 InsertPrefValue(&pref_value, 1, extension2_->id()); 445 InsertPrefValue(&pref_value, 2, extension3_->id()); 446 profile_->GetTestingPrefService()->SetUserPref(prefs::kPinnedLauncherApps, 447 pref_value.DeepCopy()); 448 449 std::vector<std::string> expected_launchers; 450 expected_launchers.push_back(extension1_->id()); 451 expected_launchers.push_back(extension3_->id()); 452 std::vector<std::string> actual_launchers; 453 454 GetAppLaunchers(&controller, &actual_launchers); 455 EXPECT_EQ(expected_launchers, actual_launchers); 456 457 // Install |extension2| and verify it shows up between the other two. 458 extension_service_->AddExtension(extension2_.get()); 459 expected_launchers.insert(expected_launchers.begin() + 1, extension2_->id()); 460 GetAppLaunchers(&controller, &actual_launchers); 461 EXPECT_EQ(expected_launchers, actual_launchers); 462 } 463