Home | History | Annotate | Download | only in launcher
      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