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: 43 explicit TestDriveServiceBridge(drive::DriveAppRegistry* registry) 44 : registry_(registry) {} 45 virtual ~TestDriveServiceBridge() {} 46 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: 62 DriveAppProviderTest() {} 63 virtual ~DriveAppProviderTest() {} 64 65 // ExtensionBrowserTest: 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 80 virtual void CleanUpOnMainThread() OVERRIDE { 81 provider_.reset(); 82 apps_registry_.reset(); 83 fake_drive_service_.reset(); 84 85 ExtensionBrowserTest::CleanUpOnMainThread(); 86 } 87 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 101 void RefreshDriveAppRegistry() { 102 apps_registry_->Update(); 103 content::RunAllPendingInMessageLoop(); 104 } 105 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 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 148 bool HasPendingConverters() const { 149 return !provider_->pending_converters_.empty(); 150 } 151 152 drive::FakeDriveService* fake_drive_service() { 153 return fake_drive_service_.get(); 154 } 155 DriveAppProvider* provider() { return provider_.get(); } 156 DriveAppMapping* mapping() { return provider_->mapping_.get(); } 157 158 private: 159 void OnPendingDriveAppConverterCheckTimer() { 160 if (!HasPendingConverters()) 161 runner_->Quit(); 162 } 163 164 // extensions::InstallObserver 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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