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/app_list/extension_app_model_builder.h"
6
7#include <string>
8
9#include "base/files/file_path.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/prefs/pref_service.h"
12#include "base/run_loop.h"
13#include "base/values.h"
14#include "chrome/browser/extensions/extension_function_test_utils.h"
15#include "chrome/browser/extensions/extension_service.h"
16#include "chrome/browser/extensions/install_tracker.h"
17#include "chrome/browser/extensions/install_tracker_factory.h"
18#include "chrome/browser/ui/app_list/app_list_controller_delegate_impl.h"
19#include "chrome/browser/ui/app_list/app_list_test_util.h"
20#include "chrome/common/chrome_constants.h"
21#include "chrome/common/extensions/extension_constants.h"
22#include "chrome/common/pref_names.h"
23#include "chrome/test/base/testing_profile.h"
24#include "extensions/browser/app_sorting.h"
25#include "extensions/browser/extension_prefs.h"
26#include "extensions/browser/uninstall_reason.h"
27#include "extensions/common/constants.h"
28#include "extensions/common/extension_set.h"
29#include "extensions/common/manifest.h"
30#include "testing/gtest/include/gtest/gtest.h"
31#include "ui/app_list/app_list_item.h"
32
33namespace {
34
35// Get a string of all apps in |model| joined with ','.
36std::string GetModelContent(app_list::AppListModel* model) {
37  std::string content;
38  for (size_t i = 0; i < model->top_level_item_list()->item_count(); ++i) {
39    if (i > 0)
40      content += ',';
41    content += model->top_level_item_list()->item_at(i)->name();
42  }
43  return content;
44}
45
46scoped_refptr<extensions::Extension> MakeApp(const std::string& name,
47                                             const std::string& version,
48                                             const std::string& url,
49                                             const std::string& id) {
50  std::string err;
51  base::DictionaryValue value;
52  value.SetString("name", name);
53  value.SetString("version", version);
54  value.SetString("app.launch.web_url", url);
55  scoped_refptr<extensions::Extension> app =
56      extensions::Extension::Create(
57          base::FilePath(),
58          extensions::Manifest::INTERNAL,
59          value,
60          extensions::Extension::WAS_INSTALLED_BY_DEFAULT,
61          id,
62          &err);
63  EXPECT_EQ(err, "");
64  return app;
65}
66
67class TestAppListControllerDelegate : public AppListControllerDelegate {
68 public:
69  virtual ~TestAppListControllerDelegate() {}
70  virtual void DismissView() OVERRIDE {}
71  virtual gfx::NativeWindow GetAppListWindow() OVERRIDE { return NULL; }
72  virtual gfx::ImageSkia GetWindowIcon() OVERRIDE { return gfx::ImageSkia(); }
73  virtual bool IsAppPinned(const std::string& extension_id) OVERRIDE {
74    return false;
75  }
76  virtual void PinApp(const std::string& extension_id) OVERRIDE {}
77  virtual void UnpinApp(const std::string& extension_id) OVERRIDE {}
78  virtual Pinnable GetPinnable() OVERRIDE { return NO_PIN; }
79  virtual bool CanDoCreateShortcutsFlow() OVERRIDE { return false; }
80  virtual void DoCreateShortcutsFlow(Profile* profile,
81                                     const std::string& extension_id) OVERRIDE {
82  }
83  virtual bool CanDoShowAppInfoFlow() OVERRIDE { return false; }
84  virtual void DoShowAppInfoFlow(Profile* profile,
85                                 const std::string& extension_id) OVERRIDE {
86  };
87  virtual void CreateNewWindow(Profile* profile, bool incognito) OVERRIDE {}
88  virtual void ActivateApp(Profile* profile,
89                           const extensions::Extension* extension,
90                           AppListSource source,
91                           int event_flags) OVERRIDE {}
92  virtual void LaunchApp(Profile* profile,
93                         const extensions::Extension* extension,
94                         AppListSource source,
95                         int event_flags) OVERRIDE {}
96  virtual void ShowForProfileByPath(
97      const base::FilePath& profile_path) OVERRIDE {}
98  virtual bool ShouldShowUserIcon() OVERRIDE { return false; }
99};
100
101const char kDefaultApps[] = "Packaged App 1,Packaged App 2,Hosted App";
102const size_t kDefaultAppCount = 3u;
103
104}  // namespace
105
106class ExtensionAppModelBuilderTest : public AppListTestBase {
107 public:
108  ExtensionAppModelBuilderTest() {}
109  virtual ~ExtensionAppModelBuilderTest() {}
110
111  virtual void SetUp() OVERRIDE {
112    AppListTestBase::SetUp();
113
114    CreateBuilder();
115  }
116
117  virtual void TearDown() OVERRIDE {
118    ResetBuilder();
119  }
120
121 protected:
122  // Creates a new builder, destroying any existing one.
123  void CreateBuilder() {
124    ResetBuilder();  // Destroy any existing builder in the correct order.
125
126    model_.reset(new app_list::AppListModel);
127    controller_.reset(new TestAppListControllerDelegate);
128    builder_.reset(new ExtensionAppModelBuilder(controller_.get()));
129    builder_->InitializeWithProfile(profile_.get(), model_.get());
130  }
131
132  void ResetBuilder() {
133    builder_.reset();
134    controller_.reset();
135    model_.reset();
136  }
137
138  scoped_ptr<app_list::AppListModel> model_;
139  scoped_ptr<TestAppListControllerDelegate> controller_;
140  scoped_ptr<ExtensionAppModelBuilder> builder_;
141
142  base::ScopedTempDir second_profile_temp_dir_;
143
144 private:
145  DISALLOW_COPY_AND_ASSIGN(ExtensionAppModelBuilderTest);
146};
147
148TEST_F(ExtensionAppModelBuilderTest, Build) {
149  // The apps list would have 3 extension apps in the profile.
150  EXPECT_EQ(kDefaultAppCount, model_->top_level_item_list()->item_count());
151  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
152}
153
154TEST_F(ExtensionAppModelBuilderTest, HideWebStore) {
155  // Install a "web store" app.
156  scoped_refptr<extensions::Extension> store =
157      MakeApp("webstore",
158              "0.0",
159              "http://google.com",
160              std::string(extensions::kWebStoreAppId));
161  service_->AddExtension(store.get());
162
163  // Install an "enterprise web store" app.
164  scoped_refptr<extensions::Extension> enterprise_store =
165      MakeApp("enterprise_webstore",
166              "0.0",
167              "http://google.com",
168              std::string(extension_misc::kEnterpriseWebStoreAppId));
169  service_->AddExtension(enterprise_store.get());
170
171  // Web stores should be present in the AppListModel.
172  app_list::AppListModel model1;
173  ExtensionAppModelBuilder builder1(controller_.get());
174  builder1.InitializeWithProfile(profile_.get(), &model1);
175  EXPECT_TRUE(model1.FindItem(store->id()));
176  EXPECT_TRUE(model1.FindItem(enterprise_store->id()));
177
178  // Activate the HideWebStoreIcon policy.
179  profile_->GetPrefs()->SetBoolean(prefs::kHideWebStoreIcon, true);
180
181  // Now the web stores should not be present anymore.
182  EXPECT_FALSE(model1.FindItem(store->id()));
183  EXPECT_FALSE(model1.FindItem(enterprise_store->id()));
184
185  // Build a new AppListModel; web stores should NOT be present.
186  app_list::AppListModel model2;
187  ExtensionAppModelBuilder builder2(controller_.get());
188  builder2.InitializeWithProfile(profile_.get(), &model2);
189  EXPECT_FALSE(model2.FindItem(store->id()));
190  EXPECT_FALSE(model2.FindItem(enterprise_store->id()));
191
192  // Deactivate the HideWebStoreIcon policy again.
193  profile_->GetPrefs()->SetBoolean(prefs::kHideWebStoreIcon, false);
194
195  // Now the web stores should have appeared.
196  EXPECT_TRUE(model2.FindItem(store->id()));
197  EXPECT_TRUE(model2.FindItem(enterprise_store->id()));
198}
199
200TEST_F(ExtensionAppModelBuilderTest, DisableAndEnable) {
201  service_->DisableExtension(kHostedAppId,
202                             extensions::Extension::DISABLE_NONE);
203  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
204
205  service_->EnableExtension(kHostedAppId);
206  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
207}
208
209TEST_F(ExtensionAppModelBuilderTest, Uninstall) {
210  service_->UninstallExtension(kPackagedApp2Id,
211                               extensions::UNINSTALL_REASON_FOR_TESTING,
212                               base::Bind(&base::DoNothing),
213                               NULL);
214  EXPECT_EQ(std::string("Packaged App 1,Hosted App"),
215            GetModelContent(model_.get()));
216
217  base::RunLoop().RunUntilIdle();
218}
219
220TEST_F(ExtensionAppModelBuilderTest, UninstallTerminatedApp) {
221  const extensions::Extension* app =
222      service_->GetInstalledExtension(kPackagedApp2Id);
223  ASSERT_TRUE(app != NULL);
224
225  // Simulate an app termination.
226  service_->TrackTerminatedExtensionForTest(app);
227
228  service_->UninstallExtension(kPackagedApp2Id,
229                               extensions::UNINSTALL_REASON_FOR_TESTING,
230                               base::Bind(&base::DoNothing),
231                               NULL);
232  EXPECT_EQ(std::string("Packaged App 1,Hosted App"),
233            GetModelContent(model_.get()));
234
235  base::RunLoop().RunUntilIdle();
236}
237
238TEST_F(ExtensionAppModelBuilderTest, Reinstall) {
239  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
240
241  // Install kPackagedApp1Id again should not create a new entry.
242  extensions::InstallTracker* tracker =
243      extensions::InstallTrackerFactory::GetForBrowserContext(profile_.get());
244  extensions::InstallObserver::ExtensionInstallParams params(
245      kPackagedApp1Id, "", gfx::ImageSkia(), true, true);
246  tracker->OnBeginExtensionInstall(params);
247
248  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
249}
250
251TEST_F(ExtensionAppModelBuilderTest, OrdinalPrefsChange) {
252  extensions::AppSorting* sorting =
253      extensions::ExtensionPrefs::Get(profile_.get())->app_sorting();
254
255  syncer::StringOrdinal package_app_page =
256      sorting->GetPageOrdinal(kPackagedApp1Id);
257  sorting->SetPageOrdinal(kHostedAppId, package_app_page.CreateBefore());
258  // Old behavior: This would be "Hosted App,Packaged App 1,Packaged App 2"
259  // New behavior: Sorting order doesn't change.
260  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
261
262  syncer::StringOrdinal app1_ordinal =
263      sorting->GetAppLaunchOrdinal(kPackagedApp1Id);
264  syncer::StringOrdinal app2_ordinal =
265      sorting->GetAppLaunchOrdinal(kPackagedApp2Id);
266  sorting->SetPageOrdinal(kHostedAppId, package_app_page);
267  sorting->SetAppLaunchOrdinal(kHostedAppId,
268                               app1_ordinal.CreateBetween(app2_ordinal));
269  // Old behavior: This would be "Packaged App 1,Hosted App,Packaged App 2"
270  // New behavior: Sorting order doesn't change.
271  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
272}
273
274TEST_F(ExtensionAppModelBuilderTest, OnExtensionMoved) {
275  extensions::AppSorting* sorting =
276      extensions::ExtensionPrefs::Get(profile_.get())->app_sorting();
277  sorting->SetPageOrdinal(kHostedAppId,
278                          sorting->GetPageOrdinal(kPackagedApp1Id));
279
280  sorting->OnExtensionMoved(kHostedAppId, kPackagedApp1Id, kPackagedApp2Id);
281  // Old behavior: This would be "Packaged App 1,Hosted App,Packaged App 2"
282  // New behavior: Sorting order doesn't change.
283  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
284
285  sorting->OnExtensionMoved(kHostedAppId, kPackagedApp2Id, std::string());
286  // Old behavior: This would be restored to the default order.
287  // New behavior: Sorting order still doesn't change.
288  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
289
290  sorting->OnExtensionMoved(kHostedAppId, std::string(), kPackagedApp1Id);
291  // Old behavior: This would be "Hosted App,Packaged App 1,Packaged App 2"
292  // New behavior: Sorting order doesn't change.
293  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
294}
295
296TEST_F(ExtensionAppModelBuilderTest, InvalidOrdinal) {
297  // Creates a no-ordinal case.
298  extensions::AppSorting* sorting =
299      extensions::ExtensionPrefs::Get(profile_.get())->app_sorting();
300  sorting->ClearOrdinals(kPackagedApp1Id);
301
302  // Creates a corrupted ordinal case.
303  extensions::ExtensionScopedPrefs* scoped_prefs =
304      extensions::ExtensionPrefs::Get(profile_.get());
305  scoped_prefs->UpdateExtensionPref(
306      kHostedAppId,
307      "page_ordinal",
308      new base::StringValue("a corrupted ordinal"));
309
310  // This should not assert or crash.
311  CreateBuilder();
312}
313
314TEST_F(ExtensionAppModelBuilderTest, OrdinalConfilicts) {
315  // Creates conflict ordinals for app1 and app2.
316  syncer::StringOrdinal conflict_ordinal =
317      syncer::StringOrdinal::CreateInitialOrdinal();
318
319  extensions::AppSorting* sorting =
320      extensions::ExtensionPrefs::Get(profile_.get())->app_sorting();
321  sorting->SetPageOrdinal(kHostedAppId, conflict_ordinal);
322  sorting->SetAppLaunchOrdinal(kHostedAppId, conflict_ordinal);
323
324  sorting->SetPageOrdinal(kPackagedApp1Id, conflict_ordinal);
325  sorting->SetAppLaunchOrdinal(kPackagedApp1Id, conflict_ordinal);
326
327  sorting->SetPageOrdinal(kPackagedApp2Id, conflict_ordinal);
328  sorting->SetAppLaunchOrdinal(kPackagedApp2Id, conflict_ordinal);
329
330  // This should not assert or crash.
331  CreateBuilder();
332
333  // By default, conflicted items are sorted by their app ids (= order added).
334  EXPECT_EQ(std::string("Hosted App,Packaged App 1,Packaged App 2"),
335            GetModelContent(model_.get()));
336}
337
338// This test adds a bookmark app to the app list.
339TEST_F(ExtensionAppModelBuilderTest, BookmarkApp) {
340  const std::string kAppName = "Bookmark App";
341  const std::string kAppVersion = "2014.1.24.19748";
342  const std::string kAppUrl = "http://google.com";
343  const std::string kAppId = "podhdnefolignjhecmjkbimfgioanahm";
344  std::string err;
345  base::DictionaryValue value;
346  value.SetString("name", kAppName);
347  value.SetString("version", kAppVersion);
348  value.SetString("app.launch.web_url", kAppUrl);
349  scoped_refptr<extensions::Extension> bookmark_app =
350      extensions::Extension::Create(
351          base::FilePath(),
352          extensions::Manifest::INTERNAL,
353          value,
354          extensions::Extension::WAS_INSTALLED_BY_DEFAULT |
355              extensions::Extension::FROM_BOOKMARK,
356          kAppId,
357          &err);
358  EXPECT_TRUE(err.empty());
359
360  service_->AddExtension(bookmark_app.get());
361  EXPECT_EQ(kDefaultAppCount + 1, model_->top_level_item_list()->item_count());
362  EXPECT_NE(std::string::npos, GetModelContent(model_.get()).find(kAppName));
363}
364