• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/apps/drive/drive_app_provider.h"
6 
7 #include "base/logging.h"
8 #include "base/macros.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/path_service.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/timer/timer.h"
13 #include "chrome/browser/apps/drive/drive_app_mapping.h"
14 #include "chrome/browser/apps/drive/drive_service_bridge.h"
15 #include "chrome/browser/drive/drive_app_registry.h"
16 #include "chrome/browser/drive/fake_drive_service.h"
17 #include "chrome/browser/extensions/crx_installer.h"
18 #include "chrome/browser/extensions/extension_browsertest.h"
19 #include "chrome/browser/extensions/install_tracker.h"
20 #include "chrome/common/chrome_paths.h"
21 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
22 #include "chrome/common/web_application_info.h"
23 #include "content/public/test/test_utils.h"
24 #include "extensions/browser/extension_registry.h"
25 #include "extensions/browser/extension_system.h"
26 
27 using extensions::AppLaunchInfo;
28 using extensions::Extension;
29 using extensions::ExtensionRegistry;
30 
31 namespace {
32 
33 const char kDriveAppId[] = "drive_app_id";
34 const char kDriveAppName[] = "Fake Drive App";
35 const char kLaunchUrl[] = "http://example.com/drive";
36 
37 // App id of hosted_app.crx.
38 const char kChromeAppId[] = "kbmnembihfiondgfjekmnmcbddelicoi";
39 
40 // Stub drive service bridge.
41 class TestDriveServiceBridge : public DriveServiceBridge {
42  public:
TestDriveServiceBridge(drive::DriveAppRegistry * registry)43   explicit TestDriveServiceBridge(drive::DriveAppRegistry* registry)
44       : registry_(registry) {}
~TestDriveServiceBridge()45   virtual ~TestDriveServiceBridge() {}
46 
GetAppRegistry()47   virtual drive::DriveAppRegistry* GetAppRegistry() OVERRIDE {
48     return registry_;
49   }
50 
51  private:
52   drive::DriveAppRegistry* registry_;
53 
54   DISALLOW_COPY_AND_ASSIGN(TestDriveServiceBridge);
55 };
56 
57 }  // namespace
58 
59 class DriveAppProviderTest : public ExtensionBrowserTest,
60                              public extensions::InstallObserver {
61  public:
DriveAppProviderTest()62   DriveAppProviderTest() {}
~DriveAppProviderTest()63   virtual ~DriveAppProviderTest() {}
64 
65   // ExtensionBrowserTest:
SetUpOnMainThread()66   virtual void SetUpOnMainThread() OVERRIDE {
67     ExtensionBrowserTest::SetUpOnMainThread();
68 
69     fake_drive_service_.reset(new drive::FakeDriveService);
70     fake_drive_service_->LoadAppListForDriveApi("drive/applist_empty.json");
71     apps_registry_.reset(
72         new drive::DriveAppRegistry(fake_drive_service_.get()));
73 
74     provider_.reset(new DriveAppProvider(profile()));
75     provider_->SetDriveServiceBridgeForTest(
76         make_scoped_ptr(new TestDriveServiceBridge(apps_registry_.get()))
77             .PassAs<DriveServiceBridge>());
78   }
79 
CleanUpOnMainThread()80   virtual void CleanUpOnMainThread() OVERRIDE {
81     provider_.reset();
82     apps_registry_.reset();
83     fake_drive_service_.reset();
84 
85     ExtensionBrowserTest::CleanUpOnMainThread();
86   }
87 
InstallChromeApp(int expected_change)88   const Extension* InstallChromeApp(int expected_change) {
89     base::FilePath test_data_path;
90     if (!PathService::Get(chrome::DIR_TEST_DATA, &test_data_path)) {
91       ADD_FAILURE();
92       return NULL;
93     }
94     test_data_path =
95         test_data_path.AppendASCII("extensions").AppendASCII("hosted_app.crx");
96     const Extension* extension =
97         InstallExtension(test_data_path, expected_change);
98     return extension;
99   }
100 
RefreshDriveAppRegistry()101   void RefreshDriveAppRegistry() {
102     apps_registry_->Update();
103     content::RunAllPendingInMessageLoop();
104   }
105 
WaitForPendingDriveAppConverters()106   void WaitForPendingDriveAppConverters() {
107     DCHECK(!runner_);
108 
109     if (provider_->pending_converters_.empty())
110       return;
111 
112     runner_ = new content::MessageLoopRunner;
113 
114     pending_drive_app_converter_check_timer_.Start(
115         FROM_HERE,
116         base::TimeDelta::FromMilliseconds(50),
117         base::Bind(&DriveAppProviderTest::OnPendingDriveAppConverterCheckTimer,
118                    base::Unretained(this)));
119 
120     runner_->Run();
121 
122     pending_drive_app_converter_check_timer_.Stop();
123     runner_ = NULL;
124   }
125 
InstallUserUrlApp(const std::string & url)126   void InstallUserUrlApp(const std::string& url) {
127     DCHECK(!runner_);
128     runner_ = new content::MessageLoopRunner;
129 
130     WebApplicationInfo web_app;
131     web_app.title = base::ASCIIToUTF16("User installed Url app");
132     web_app.app_url = GURL(url);
133 
134     scoped_refptr<extensions::CrxInstaller> crx_installer =
135         extensions::CrxInstaller::CreateSilent(
136             extensions::ExtensionSystem::Get(profile())->extension_service());
137     crx_installer->set_creation_flags(Extension::FROM_BOOKMARK);
138     extensions::InstallTracker::Get(profile())->AddObserver(this);
139     crx_installer->InstallWebApp(web_app);
140 
141     runner_->Run();
142     runner_ = NULL;
143     extensions::InstallTracker::Get(profile())->RemoveObserver(this);
144 
145     content::RunAllPendingInMessageLoop();
146   }
147 
HasPendingConverters() const148   bool HasPendingConverters() const {
149     return !provider_->pending_converters_.empty();
150   }
151 
fake_drive_service()152   drive::FakeDriveService* fake_drive_service() {
153     return fake_drive_service_.get();
154   }
provider()155   DriveAppProvider* provider() { return provider_.get(); }
mapping()156   DriveAppMapping* mapping() { return provider_->mapping_.get(); }
157 
158  private:
OnPendingDriveAppConverterCheckTimer()159   void OnPendingDriveAppConverterCheckTimer() {
160     if (!HasPendingConverters())
161       runner_->Quit();
162   }
163 
164   // extensions::InstallObserver
OnFinishCrxInstall(const std::string & extension_id,bool success)165   virtual void OnFinishCrxInstall(const std::string& extension_id,
166                                   bool success) OVERRIDE {
167     runner_->Quit();
168   }
169 
170   scoped_ptr<drive::FakeDriveService> fake_drive_service_;
171   scoped_ptr<drive::DriveAppRegistry> apps_registry_;
172   scoped_ptr<DriveAppProvider> provider_;
173 
174   base::RepeatingTimer<DriveAppProviderTest>
175       pending_drive_app_converter_check_timer_;
176   scoped_refptr<content::MessageLoopRunner> runner_;
177 
178   DISALLOW_COPY_AND_ASSIGN(DriveAppProviderTest);
179 };
180 
181 // A Drive app maps to an existing Chrome app that has a matching id.
182 // Uninstalling the chrome app would also disconnect the drive app.
IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,ExistingChromeApp)183 IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, ExistingChromeApp) {
184   // Prepare an existing chrome app.
185   const Extension* chrome_app = InstallChromeApp(1);
186   ASSERT_TRUE(chrome_app);
187 
188   // Prepare a Drive app that matches the chrome app id.
189   fake_drive_service()->AddApp(
190       kDriveAppId, kDriveAppName, chrome_app->id(), kLaunchUrl);
191   RefreshDriveAppRegistry();
192   EXPECT_FALSE(HasPendingConverters());
193 
194   // The Drive app should use the matching chrome app.
195   EXPECT_EQ(chrome_app->id(), mapping()->GetChromeApp(kDriveAppId));
196   EXPECT_FALSE(mapping()->IsChromeAppGenerated(chrome_app->id()));
197 
198   // Unintalling chrome app should disconnect the Drive app on server.
199   EXPECT_TRUE(fake_drive_service()->HasApp(kDriveAppId));
200   UninstallExtension(chrome_app->id());
201   EXPECT_FALSE(fake_drive_service()->HasApp(kDriveAppId));
202 }
203 
204 // A Drive app creates an URL app when no matching Chrome app presents.
IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,CreateUrlApp)205 IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, CreateUrlApp) {
206   // Prepare a Drive app with no underlying chrome app.
207   fake_drive_service()->AddApp(kDriveAppId, kDriveAppName, "", kLaunchUrl);
208   RefreshDriveAppRegistry();
209   WaitForPendingDriveAppConverters();
210 
211   // An Url app should be created.
212   const Extension* chrome_app =
213       ExtensionRegistry::Get(profile())->GetExtensionById(
214           mapping()->GetChromeApp(kDriveAppId), ExtensionRegistry::EVERYTHING);
215   ASSERT_TRUE(chrome_app);
216   EXPECT_EQ(kDriveAppName, chrome_app->name());
217   EXPECT_TRUE(chrome_app->is_hosted_app());
218   EXPECT_TRUE(chrome_app->from_bookmark());
219   EXPECT_EQ(GURL(kLaunchUrl), AppLaunchInfo::GetLaunchWebURL(chrome_app));
220 
221   EXPECT_EQ(chrome_app->id(), mapping()->GetChromeApp(kDriveAppId));
222   EXPECT_TRUE(mapping()->IsChromeAppGenerated(chrome_app->id()));
223 
224   // Unintalling the chrome app should disconnect the Drive app on server.
225   EXPECT_TRUE(fake_drive_service()->HasApp(kDriveAppId));
226   UninstallExtension(chrome_app->id());
227   EXPECT_FALSE(fake_drive_service()->HasApp(kDriveAppId));
228 }
229 
230 // A matching Chrome app replaces the created URL app.
IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,MatchingChromeAppInstalled)231 IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, MatchingChromeAppInstalled) {
232   // Prepare a Drive app that matches the not-yet-installed kChromeAppId.
233   fake_drive_service()->AddApp(
234       kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
235   RefreshDriveAppRegistry();
236   WaitForPendingDriveAppConverters();
237 
238   // An Url app should be created.
239   const Extension* url_app =
240       ExtensionRegistry::Get(profile())->GetExtensionById(
241           mapping()->GetChromeApp(kDriveAppId), ExtensionRegistry::EVERYTHING);
242   EXPECT_TRUE(url_app->is_hosted_app());
243   EXPECT_TRUE(url_app->from_bookmark());
244 
245   const std::string url_app_id = url_app->id();
246   EXPECT_NE(kChromeAppId, url_app_id);
247   EXPECT_EQ(url_app_id, mapping()->GetChromeApp(kDriveAppId));
248   EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
249 
250   // Installs a chrome app with matching id.
251   InstallChromeApp(0);
252 
253   // The Drive app should be mapped to chrome app.
254   EXPECT_EQ(kChromeAppId, mapping()->GetChromeApp(kDriveAppId));
255   EXPECT_FALSE(mapping()->IsChromeAppGenerated(kChromeAppId));
256 
257   // Url app should be auto uninstalled.
258   EXPECT_FALSE(ExtensionRegistry::Get(profile())->GetExtensionById(
259       url_app_id, ExtensionRegistry::EVERYTHING));
260 }
261 
262 // Tests that the corresponding URL app is uninstalled when a Drive app is
263 // disconnected.
IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,DisconnectDriveAppUninstallUrlApp)264 IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,
265                        DisconnectDriveAppUninstallUrlApp) {
266   // Prepare a Drive app that matches the not-yet-installed kChromeAppId.
267   fake_drive_service()->AddApp(
268       kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
269   RefreshDriveAppRegistry();
270   WaitForPendingDriveAppConverters();
271 
272   // Url app is created.
273   const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
274   EXPECT_TRUE(ExtensionRegistry::Get(profile())->GetExtensionById(
275       url_app_id, ExtensionRegistry::EVERYTHING));
276 
277   fake_drive_service()->RemoveAppByProductId(kChromeAppId);
278   RefreshDriveAppRegistry();
279 
280   // Url app is auto uninstalled.
281   EXPECT_FALSE(ExtensionRegistry::Get(profile())->GetExtensionById(
282       url_app_id, ExtensionRegistry::EVERYTHING));
283 }
284 
285 // Tests that the matching Chrome app is preserved when a Drive app is
286 // disconnected.
IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,DisconnectDriveAppPreserveChromeApp)287 IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,
288                        DisconnectDriveAppPreserveChromeApp) {
289   // Prepare an existing chrome app.
290   const Extension* chrome_app = InstallChromeApp(1);
291   ASSERT_TRUE(chrome_app);
292 
293   // Prepare a Drive app that matches the chrome app id.
294   fake_drive_service()->AddApp(
295       kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
296   RefreshDriveAppRegistry();
297   EXPECT_FALSE(HasPendingConverters());
298 
299   fake_drive_service()->RemoveAppByProductId(kChromeAppId);
300   RefreshDriveAppRegistry();
301 
302   // Chrome app is still present after the Drive app is disconnected.
303   EXPECT_TRUE(ExtensionRegistry::Get(profile())->GetExtensionById(
304       kChromeAppId, ExtensionRegistry::EVERYTHING));
305 }
306 
307 // The "generated" flag of an app should stay across Drive app conversion.
IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,KeepGeneratedFlagBetweenUpdates)308 IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, KeepGeneratedFlagBetweenUpdates) {
309   // Prepare a Drive app with no underlying chrome app.
310   fake_drive_service()->AddApp(
311       kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
312   RefreshDriveAppRegistry();
313   WaitForPendingDriveAppConverters();
314 
315   const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
316   EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
317 
318   // Change name to trigger an update.
319   const char kChangedName[] = "Changed name";
320   fake_drive_service()->RemoveAppByProductId(kChromeAppId);
321   fake_drive_service()->AddApp(
322       kDriveAppId, kChangedName, kChromeAppId, kLaunchUrl);
323   RefreshDriveAppRegistry();
324   WaitForPendingDriveAppConverters();
325 
326   // It should still map to the same url app id and tagged as generated.
327   EXPECT_EQ(url_app_id, mapping()->GetChromeApp(kDriveAppId));
328   EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
329 }
330 
331 // A new URL app replaces the existing one and keeps existing// position when a
332 // Drive app changes its name or URL.
IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,DriveAppChanged)333 IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, DriveAppChanged) {
334   // Prepare a Drive app with no underlying chrome app.
335   fake_drive_service()->AddApp(
336       kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
337   RefreshDriveAppRegistry();
338   WaitForPendingDriveAppConverters();
339 
340   // An Url app should be created.
341   const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
342   const Extension* url_app =
343       ExtensionRegistry::Get(profile())
344           ->GetExtensionById(url_app_id, ExtensionRegistry::EVERYTHING);
345   ASSERT_TRUE(url_app);
346   EXPECT_EQ(kDriveAppName, url_app->name());
347   EXPECT_TRUE(url_app->is_hosted_app());
348   EXPECT_TRUE(url_app->from_bookmark());
349   EXPECT_EQ(GURL(kLaunchUrl), AppLaunchInfo::GetLaunchWebURL(url_app));
350   EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
351 
352   // Register the Drive app with a different name and URL.
353   const char kAnotherName[] = "Another drive app name";
354   const char kAnotherLaunchUrl[] = "http://example.com/another_end_point";
355   fake_drive_service()->RemoveAppByProductId(kChromeAppId);
356   fake_drive_service()->AddApp(
357       kDriveAppId, kAnotherName, kChromeAppId, kAnotherLaunchUrl);
358   RefreshDriveAppRegistry();
359   WaitForPendingDriveAppConverters();
360 
361   // Old URL app should be auto uninstalled.
362   url_app = ExtensionRegistry::Get(profile())
363                 ->GetExtensionById(url_app_id, ExtensionRegistry::EVERYTHING);
364   EXPECT_FALSE(url_app);
365 
366   // New URL app should be used.
367   const std::string new_url_app_id = mapping()->GetChromeApp(kDriveAppId);
368   EXPECT_NE(new_url_app_id, url_app_id);
369   EXPECT_TRUE(mapping()->IsChromeAppGenerated(new_url_app_id));
370 
371   const Extension* new_url_app =
372       ExtensionRegistry::Get(profile())
373           ->GetExtensionById(new_url_app_id, ExtensionRegistry::EVERYTHING);
374   ASSERT_TRUE(new_url_app);
375   EXPECT_EQ(kAnotherName, new_url_app->name());
376   EXPECT_TRUE(new_url_app->is_hosted_app());
377   EXPECT_TRUE(new_url_app->from_bookmark());
378   EXPECT_EQ(GURL(kAnotherLaunchUrl),
379             AppLaunchInfo::GetLaunchWebURL(new_url_app));
380 }
381 
382 // An existing URL app is not changed when underlying drive app data (name and
383 // URL) is not changed.
IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,NoChange)384 IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, NoChange) {
385   // Prepare one Drive app.
386   fake_drive_service()->AddApp(
387       kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
388   RefreshDriveAppRegistry();
389   WaitForPendingDriveAppConverters();
390 
391   const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
392   const Extension* url_app =
393       ExtensionRegistry::Get(profile())
394           ->GetExtensionById(url_app_id, ExtensionRegistry::EVERYTHING);
395 
396   // Refresh with no actual change.
397   RefreshDriveAppRegistry();
398   EXPECT_FALSE(HasPendingConverters());
399 
400   // Url app should remain unchanged.
401   const std::string new_url_app_id = mapping()->GetChromeApp(kDriveAppId);
402   EXPECT_EQ(new_url_app_id, url_app_id);
403 
404   const Extension* new_url_app =
405       ExtensionRegistry::Get(profile())
406           ->GetExtensionById(new_url_app_id, ExtensionRegistry::EVERYTHING);
407   EXPECT_EQ(url_app, new_url_app);
408 }
409 
410 // User installed url app before Drive app conversion should not be tagged
411 // as generated and not auto uninstalled.
IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,UserInstalledBeforeDriveApp)412 IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, UserInstalledBeforeDriveApp) {
413   InstallUserUrlApp(kLaunchUrl);
414 
415   fake_drive_service()->AddApp(
416       kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
417   RefreshDriveAppRegistry();
418   WaitForPendingDriveAppConverters();
419 
420   const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
421   EXPECT_FALSE(mapping()->IsChromeAppGenerated(url_app_id));
422 
423   fake_drive_service()->RemoveAppByProductId(kChromeAppId);
424   RefreshDriveAppRegistry();
425 
426   // Url app is still present after the Drive app is disconnected.
427   EXPECT_TRUE(ExtensionRegistry::Get(profile())->GetExtensionById(
428       url_app_id, ExtensionRegistry::EVERYTHING));
429 }
430 
431 // Similar to UserInstalledBeforeDriveApp but test the case where user
432 // installation happens after Drive app conversion.
IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,UserInstalledAfterDriveApp)433 IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, UserInstalledAfterDriveApp) {
434   fake_drive_service()->AddApp(
435       kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
436   RefreshDriveAppRegistry();
437   WaitForPendingDriveAppConverters();
438 
439   // Drive app converted and tagged as generated.
440   const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
441   EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
442 
443   // User installation resets the generated flag.
444   InstallUserUrlApp(kLaunchUrl);
445   EXPECT_FALSE(mapping()->IsChromeAppGenerated(url_app_id));
446 
447   fake_drive_service()->RemoveAppByProductId(kChromeAppId);
448   RefreshDriveAppRegistry();
449 
450   // Url app is still present after the Drive app is disconnected.
451   EXPECT_TRUE(ExtensionRegistry::Get(profile())->GetExtensionById(
452       url_app_id, ExtensionRegistry::EVERYTHING));
453 }
454