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.
5 #include "chrome/browser/extensions/extension_gcm_app_handler.h"
10 #include "base/bind_helpers.h"
11 #include "base/command_line.h"
12 #include "base/file_util.h"
13 #include "base/files/file_path.h"
14 #include "base/files/scoped_temp_dir.h"
15 #include "base/location.h"
16 #include "base/logging.h"
17 #include "base/memory/ref_counted.h"
18 #include "base/message_loop/message_loop.h"
19 #include "base/path_service.h"
20 #include "base/prefs/pref_service.h"
21 #include "base/run_loop.h"
22 #include "base/values.h"
23 #include "chrome/browser/chrome_notification_types.h"
24 #include "chrome/browser/extensions/extension_service.h"
25 #include "chrome/browser/extensions/test_extension_service.h"
26 #include "chrome/browser/extensions/test_extension_system.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/services/gcm/fake_signin_manager.h"
29 #include "chrome/browser/services/gcm/gcm_profile_service.h"
30 #include "chrome/browser/services/gcm/gcm_profile_service_factory.h"
31 #include "chrome/browser/signin/signin_manager_factory.h"
32 #include "chrome/common/chrome_paths.h"
33 #include "chrome/common/pref_names.h"
34 #include "chrome/test/base/testing_profile.h"
35 #include "components/gcm_driver/fake_gcm_app_handler.h"
36 #include "components/gcm_driver/fake_gcm_client.h"
37 #include "components/gcm_driver/fake_gcm_client_factory.h"
38 #include "components/gcm_driver/gcm_client_factory.h"
39 #include "components/gcm_driver/gcm_driver.h"
40 #include "components/keyed_service/core/keyed_service.h"
41 #include "content/public/browser/browser_context.h"
42 #include "content/public/browser/browser_thread.h"
43 #include "content/public/test/test_browser_thread_bundle.h"
44 #include "content/public/test/test_utils.h"
45 #include "extensions/browser/extension_system.h"
46 #include "extensions/common/extension.h"
47 #include "extensions/common/manifest.h"
48 #include "extensions/common/manifest_constants.h"
49 #include "extensions/common/permissions/api_permission.h"
50 #include "extensions/common/permissions/permissions_data.h"
51 #include "testing/gtest/include/gtest/gtest.h"
53 #if !defined(OS_ANDROID)
54 #include "chrome/browser/extensions/api/gcm/gcm_api.h"
57 #if defined(OS_CHROMEOS)
58 #include "chrome/browser/chromeos/login/users/user_manager.h"
59 #include "chrome/browser/chromeos/settings/cros_settings.h"
60 #include "chrome/browser/chromeos/settings/device_settings_service.h"
63 namespace extensions {
67 const char kTestExtensionName[] = "FooBar";
68 const char kTestingUsername[] = "user1@example.com";
72 // Helper class for asynchronous waiting.
78 // Waits until the asynchronous operation finishes.
79 void WaitUntilCompleted() {
80 run_loop_.reset(new base::RunLoop);
84 // Signals that the asynchronous operation finishes.
85 void SignalCompleted() {
86 if (run_loop_ && run_loop_->running())
90 // Runs until UI loop becomes idle.
92 base::MessageLoop::current()->RunUntilIdle();
95 // Runs until IO loop becomes idle.
97 content::BrowserThread::PostTask(
98 content::BrowserThread::IO,
100 base::Bind(&Waiter::OnIOLoopPump, base::Unretained(this)));
102 WaitUntilCompleted();
106 void PumpIOLoopCompleted() {
107 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
112 void OnIOLoopPump() {
113 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
115 content::BrowserThread::PostTask(
116 content::BrowserThread::IO,
118 base::Bind(&Waiter::OnIOLoopPumpCompleted, base::Unretained(this)));
121 void OnIOLoopPumpCompleted() {
122 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
124 content::BrowserThread::PostTask(
125 content::BrowserThread::UI,
127 base::Bind(&Waiter::PumpIOLoopCompleted, base::Unretained(this)));
130 scoped_ptr<base::RunLoop> run_loop_;
132 DISALLOW_COPY_AND_ASSIGN(Waiter);
135 class FakeExtensionGCMAppHandler : public ExtensionGCMAppHandler {
137 FakeExtensionGCMAppHandler(Profile* profile, Waiter* waiter)
138 : ExtensionGCMAppHandler(profile),
140 unregistration_result_(gcm::GCMClient::UNKNOWN_ERROR),
141 app_handler_count_drop_to_zero_(false) {
144 virtual ~FakeExtensionGCMAppHandler() {
147 virtual void OnMessage(
148 const std::string& app_id,
149 const gcm::GCMClient::IncomingMessage& message) OVERRIDE {
152 virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE {
155 virtual void OnSendError(
156 const std::string& app_id,
157 const gcm::GCMClient::SendErrorDetails& send_error_details) OVERRIDE {
160 virtual void OnUnregisterCompleted(const std::string& app_id,
161 gcm::GCMClient::Result result) OVERRIDE {
162 unregistration_result_ = result;
163 waiter_->SignalCompleted();
166 virtual void RemoveAppHandler(const std::string& app_id) OVERRIDE{
167 ExtensionGCMAppHandler::RemoveAppHandler(app_id);
168 if (!GetGCMDriver()->app_handlers().size())
169 app_handler_count_drop_to_zero_ = true;
172 gcm::GCMClient::Result unregistration_result() const {
173 return unregistration_result_;
175 bool app_handler_count_drop_to_zero() const {
176 return app_handler_count_drop_to_zero_;
181 gcm::GCMClient::Result unregistration_result_;
182 bool app_handler_count_drop_to_zero_;
184 DISALLOW_COPY_AND_ASSIGN(FakeExtensionGCMAppHandler);
187 class ExtensionGCMAppHandlerTest : public testing::Test {
189 static KeyedService* BuildGCMProfileService(
190 content::BrowserContext* context) {
191 return new gcm::GCMProfileService(
192 Profile::FromBrowserContext(context),
193 scoped_ptr<gcm::GCMClientFactory>(new gcm::FakeGCMClientFactory(
194 gcm::FakeGCMClient::NO_DELAY_START,
195 content::BrowserThread::GetMessageLoopProxyForThread(
196 content::BrowserThread::UI),
197 content::BrowserThread::GetMessageLoopProxyForThread(
198 content::BrowserThread::IO))));
201 ExtensionGCMAppHandlerTest()
202 : extension_service_(NULL),
203 registration_result_(gcm::GCMClient::UNKNOWN_ERROR),
204 unregistration_result_(gcm::GCMClient::UNKNOWN_ERROR) {
207 virtual ~ExtensionGCMAppHandlerTest() {
210 // Overridden from test::Test:
211 virtual void SetUp() OVERRIDE {
212 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
214 // Make BrowserThread work in unittest.
215 thread_bundle_.reset(new content::TestBrowserThreadBundle(
216 content::TestBrowserThreadBundle::REAL_IO_THREAD));
218 // Allow extension update to unpack crx in process.
219 in_process_utility_thread_helper_.reset(
220 new content::InProcessUtilityThreadHelper);
222 // This is needed to create extension service under CrOS.
223 #if defined(OS_CHROMEOS)
224 test_user_manager_.reset(new chromeos::ScopedTestUserManager());
227 // Create a new profile.
228 TestingProfile::Builder builder;
229 builder.AddTestingFactory(SigninManagerFactory::GetInstance(),
230 gcm::FakeSigninManager::Build);
231 profile_ = builder.Build();
232 signin_manager_ = static_cast<gcm::FakeSigninManager*>(
233 SigninManagerFactory::GetInstance()->GetForProfile(profile_.get()));
235 // Create extension service in order to uninstall the extension.
236 TestExtensionSystem* extension_system(
237 static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile())));
238 base::FilePath extensions_install_dir =
239 temp_dir_.path().Append(FILE_PATH_LITERAL("Extensions"));
240 extension_system->CreateExtensionService(
241 CommandLine::ForCurrentProcess(), extensions_install_dir, false);
242 extension_service_ = extension_system->Get(profile())->extension_service();
243 extension_service_->set_extensions_enabled(true);
244 extension_service_->set_show_extensions_prompts(false);
245 extension_service_->set_install_updates_when_idle_for_test(false);
247 // Enable GCM such that tests could be run on all channels.
248 profile()->GetPrefs()->SetBoolean(prefs::kGCMChannelEnabled, true);
250 // Create GCMProfileService that talks with fake GCMClient.
251 gcm::GCMProfileServiceFactory::GetInstance()->SetTestingFactoryAndUse(
252 profile(), &ExtensionGCMAppHandlerTest::BuildGCMProfileService);
254 // Create a fake version of ExtensionGCMAppHandler.
255 gcm_app_handler_.reset(new FakeExtensionGCMAppHandler(profile(), &waiter_));
258 virtual void TearDown() OVERRIDE {
259 #if defined(OS_CHROMEOS)
260 test_user_manager_.reset();
263 waiter_.PumpUILoop();
266 // Returns a barebones test extension.
267 scoped_refptr<Extension> CreateExtension() {
268 base::DictionaryValue manifest;
269 manifest.SetString(manifest_keys::kVersion, "1.0.0.0");
270 manifest.SetString(manifest_keys::kName, kTestExtensionName);
271 base::ListValue* permission_list = new base::ListValue;
272 permission_list->Append(base::Value::CreateStringValue("gcm"));
273 manifest.Set(manifest_keys::kPermissions, permission_list);
276 scoped_refptr<Extension> extension = Extension::Create(
281 "ldnnhddmnhbkjipkidpdiheffobcpfmf",
283 EXPECT_TRUE(extension.get()) << error;
285 extension->permissions_data()->HasAPIPermission(APIPermission::kGcm));
290 void LoadExtension(const Extension* extension) {
291 extension_service_->AddExtension(extension);
294 static bool IsCrxInstallerDone(extensions::CrxInstaller** installer,
295 const content::NotificationSource& source,
296 const content::NotificationDetails& details) {
297 return content::Source<extensions::CrxInstaller>(source).ptr() ==
301 void UpdateExtension(const Extension* extension,
302 const std::string& update_crx) {
303 base::FilePath data_dir;
304 if (!PathService::Get(chrome::DIR_TEST_DATA, &data_dir)) {
308 data_dir = data_dir.AppendASCII("extensions");
309 data_dir = data_dir.AppendASCII(update_crx);
311 base::FilePath path = temp_dir_.path();
312 path = path.Append(data_dir.BaseName());
313 ASSERT_TRUE(base::CopyFile(data_dir, path));
315 extensions::CrxInstaller* installer = NULL;
316 content::WindowedNotificationObserver observer(
317 chrome::NOTIFICATION_CRX_INSTALLER_DONE,
318 base::Bind(&IsCrxInstallerDone, &installer));
319 extension_service_->UpdateExtension(
320 extension->id(), path, true, &installer);
326 void DisableExtension(const Extension* extension) {
327 extension_service_->DisableExtension(
328 extension->id(), Extension::DISABLE_USER_ACTION);
331 void EnableExtension(const Extension* extension) {
332 extension_service_->EnableExtension(extension->id());
335 void UninstallExtension(const Extension* extension) {
336 extension_service_->UninstallExtension(extension->id(), false, NULL);
339 void SignIn(const std::string& username) {
340 signin_manager_->SignIn(username);
341 waiter_.PumpIOLoop();
345 signin_manager_->SignOut(signin_metrics::SIGNOUT_TEST);
346 waiter_.PumpIOLoop();
349 void Register(const std::string& app_id,
350 const std::vector<std::string>& sender_ids) {
351 GetGCMDriver()->Register(
354 base::Bind(&ExtensionGCMAppHandlerTest::RegisterCompleted,
355 base::Unretained(this)));
358 void RegisterCompleted(const std::string& registration_id,
359 gcm::GCMClient::Result result) {
360 registration_result_ = result;
361 waiter_.SignalCompleted();
364 gcm::GCMDriver* GetGCMDriver() const {
365 return gcm::GCMProfileServiceFactory::GetForProfile(profile())->driver();
368 bool HasAppHandlers(const std::string& app_id) const {
369 return GetGCMDriver()->app_handlers().count(app_id);
372 Profile* profile() const { return profile_.get(); }
373 Waiter* waiter() { return &waiter_; }
374 FakeExtensionGCMAppHandler* gcm_app_handler() const {
375 return gcm_app_handler_.get();
377 gcm::GCMClient::Result registration_result() const {
378 return registration_result_;
380 gcm::GCMClient::Result unregistration_result() const {
381 return unregistration_result_;
385 scoped_ptr<content::TestBrowserThreadBundle> thread_bundle_;
386 scoped_ptr<content::InProcessUtilityThreadHelper>
387 in_process_utility_thread_helper_;
388 scoped_ptr<TestingProfile> profile_;
389 ExtensionService* extension_service_; // Not owned.
390 gcm::FakeSigninManager* signin_manager_; // Not owned.
391 base::ScopedTempDir temp_dir_;
393 // This is needed to create extension service under CrOS.
394 #if defined(OS_CHROMEOS)
395 chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
396 chromeos::ScopedTestCrosSettings test_cros_settings_;
397 scoped_ptr<chromeos::ScopedTestUserManager> test_user_manager_;
401 scoped_ptr<FakeExtensionGCMAppHandler> gcm_app_handler_;
402 gcm::GCMClient::Result registration_result_;
403 gcm::GCMClient::Result unregistration_result_;
405 DISALLOW_COPY_AND_ASSIGN(ExtensionGCMAppHandlerTest);
408 TEST_F(ExtensionGCMAppHandlerTest, AddAndRemoveAppHandler) {
409 scoped_refptr<Extension> extension(CreateExtension());
411 // App handler is added when extension is loaded.
412 LoadExtension(extension);
413 waiter()->PumpUILoop();
414 EXPECT_TRUE(HasAppHandlers(extension->id()));
416 // App handler is removed when extension is unloaded.
417 DisableExtension(extension);
418 waiter()->PumpUILoop();
419 EXPECT_FALSE(HasAppHandlers(extension->id()));
421 // App handler is added when extension is reloaded.
422 EnableExtension(extension);
423 waiter()->PumpUILoop();
424 EXPECT_TRUE(HasAppHandlers(extension->id()));
426 // App handler is removed when extension is uninstalled.
427 UninstallExtension(extension);
428 waiter()->PumpUILoop();
429 EXPECT_FALSE(HasAppHandlers(extension->id()));
432 TEST_F(ExtensionGCMAppHandlerTest, UnregisterOnExtensionUninstall) {
433 scoped_refptr<Extension> extension(CreateExtension());
434 LoadExtension(extension);
436 // Sign-in is needed for registration.
437 SignIn(kTestingUsername);
439 // Kick off registration.
440 std::vector<std::string> sender_ids;
441 sender_ids.push_back("sender1");
442 Register(extension->id(), sender_ids);
443 waiter()->WaitUntilCompleted();
444 EXPECT_EQ(gcm::GCMClient::SUCCESS, registration_result());
446 // Add another app handler in order to prevent the GCM service from being
447 // stopped when the extension is uninstalled. This is needed because otherwise
448 // we are not able to receive the unregistration result.
449 GetGCMDriver()->AddAppHandler("Foo", gcm_app_handler());
451 // Unregistration should be triggered when the extension is uninstalled.
452 UninstallExtension(extension);
453 waiter()->WaitUntilCompleted();
454 EXPECT_EQ(gcm::GCMClient::SUCCESS,
455 gcm_app_handler()->unregistration_result());
458 GetGCMDriver()->RemoveAppHandler("Foo");
461 TEST_F(ExtensionGCMAppHandlerTest, UpdateExtensionWithGcmPermissionKept) {
462 scoped_refptr<Extension> extension(CreateExtension());
464 // App handler is added when the extension is loaded.
465 LoadExtension(extension);
466 waiter()->PumpUILoop();
467 EXPECT_TRUE(HasAppHandlers(extension->id()));
469 // App handler count should not drop to zero when the extension is updated.
470 UpdateExtension(extension, "gcm2.crx");
471 waiter()->PumpUILoop();
472 EXPECT_FALSE(gcm_app_handler()->app_handler_count_drop_to_zero());
473 EXPECT_TRUE(HasAppHandlers(extension->id()));
476 TEST_F(ExtensionGCMAppHandlerTest, UpdateExtensionWithGcmPermissionRemoved) {
477 scoped_refptr<Extension> extension(CreateExtension());
479 // App handler is added when the extension is loaded.
480 LoadExtension(extension);
481 waiter()->PumpUILoop();
482 EXPECT_TRUE(HasAppHandlers(extension->id()));
484 // App handler is removed when the extension is updated to the version that
485 // has GCM permission removed.
486 UpdateExtension(extension, "good2.crx");
487 waiter()->PumpUILoop();
488 EXPECT_TRUE(gcm_app_handler()->app_handler_count_drop_to_zero());
489 EXPECT_FALSE(HasAppHandlers(extension->id()));
492 } // namespace extensions