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