| // Copyright 2014 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "chrome/browser/extensions/extension_gcm_app_handler.h" | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/lazy_instance.h" | 
 | #include "base/location.h" | 
 | #include "base/single_thread_task_runner.h" | 
 | #include "base/threading/thread_task_runner_handle.h" | 
 | #include "chrome/browser/chrome_notification_types.h" | 
 | #include "chrome/browser/extensions/api/gcm/gcm_api.h" | 
 | #include "chrome/browser/gcm/gcm_profile_service_factory.h" | 
 | #include "chrome/browser/gcm/instance_id/instance_id_profile_service_factory.h" | 
 | #include "chrome/browser/profiles/profile.h" | 
 | #include "components/gcm_driver/gcm_driver.h" | 
 | #include "components/gcm_driver/gcm_profile_service.h" | 
 | #include "components/gcm_driver/instance_id/instance_id_driver.h" | 
 | #include "components/gcm_driver/instance_id/instance_id_profile_service.h" | 
 | #include "extensions/browser/extension_system.h" | 
 | #include "extensions/browser/unloaded_extension_reason.h" | 
 | #include "extensions/common/extension.h" | 
 | #include "extensions/common/permissions/permissions_data.h" | 
 |  | 
 | namespace extensions { | 
 |  | 
 | namespace { | 
 |  | 
 | const char kDummyAppId[] = "extension.guard.dummy.id"; | 
 |  | 
 | base::LazyInstance<BrowserContextKeyedAPIFactory<ExtensionGCMAppHandler>>:: | 
 |     DestructorAtExit g_extension_gcm_app_handler_factory = | 
 |         LAZY_INSTANCE_INITIALIZER; | 
 |  | 
 | bool IsGCMPermissionEnabled(const Extension* extension) { | 
 |   return extension->permissions_data()->HasAPIPermission(APIPermission::kGcm); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 |  | 
 | // static | 
 | BrowserContextKeyedAPIFactory<ExtensionGCMAppHandler>* | 
 | ExtensionGCMAppHandler::GetFactoryInstance() { | 
 |   return g_extension_gcm_app_handler_factory.Pointer(); | 
 | } | 
 |  | 
 | ExtensionGCMAppHandler::ExtensionGCMAppHandler(content::BrowserContext* context) | 
 |     : profile_(Profile::FromBrowserContext(context)) { | 
 |   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); | 
 |   js_event_router_.reset(new extensions::GcmJsEventRouter(profile_)); | 
 | } | 
 |  | 
 | ExtensionGCMAppHandler::~ExtensionGCMAppHandler() = default; | 
 |  | 
 | void ExtensionGCMAppHandler::Shutdown() { | 
 |   const ExtensionSet& enabled_extensions = | 
 |       ExtensionRegistry::Get(profile_)->enabled_extensions(); | 
 |   for (ExtensionSet::const_iterator extension = enabled_extensions.begin(); | 
 |        extension != enabled_extensions.end(); | 
 |        ++extension) { | 
 |     if (IsGCMPermissionEnabled(extension->get())) | 
 |       GetGCMDriver()->RemoveAppHandler((*extension)->id()); | 
 |   } | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::ShutdownHandler() { | 
 |   js_event_router_.reset(); | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::OnStoreReset() { | 
 |   // TODO(crbug.com/661660): Notify the extension somehow that its registration | 
 |   // was invalidated and deleted? | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::OnMessage(const std::string& app_id, | 
 |                                        const gcm::IncomingMessage& message) { | 
 |   js_event_router_->OnMessage(app_id, message); | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::OnMessagesDeleted(const std::string& app_id) { | 
 |   js_event_router_->OnMessagesDeleted(app_id); | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::OnSendError( | 
 |     const std::string& app_id, | 
 |     const gcm::GCMClient::SendErrorDetails& send_error_details) { | 
 |   js_event_router_->OnSendError(app_id, send_error_details); | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::OnSendAcknowledged( | 
 |     const std::string& app_id, | 
 |     const std::string& message_id) { | 
 |   // This event is not exposed to JS API. It terminates here. | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::OnExtensionLoaded( | 
 |     content::BrowserContext* browser_context, | 
 |     const Extension* extension) { | 
 |   if (IsGCMPermissionEnabled(extension)) | 
 |     AddAppHandler(extension->id()); | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::OnExtensionUnloaded( | 
 |     content::BrowserContext* browser_context, | 
 |     const Extension* extension, | 
 |     UnloadedExtensionReason reason) { | 
 |   if (!IsGCMPermissionEnabled(extension)) | 
 |     return; | 
 |  | 
 |   if (reason == UnloadedExtensionReason::UPDATE && | 
 |       !GetGCMDriver()->app_handlers().empty()) { | 
 |     // When the extension is being updated, it will be first unloaded and then | 
 |     // loaded again by ExtensionService::AddExtension. If the app handler for | 
 |     // this extension is the only handler, removing it and adding it again will | 
 |     // cause the GCM service being stopped and restarted unnecessarily. To work | 
 |     // around this, we add a dummy app handler to guard against it. This dummy | 
 |     // app handler will be removed once the extension loading logic is done. | 
 |     // | 
 |     // Note that this dummy app handler is added when there is at least one | 
 |     // handler. This is because there might be a built-in app handler, like | 
 |     // GCMAccountMapper, which is automatically added and removed by | 
 |     // GCMDriverDesktop. | 
 |     // | 
 |     // Also note that the GCM message routing will not be interruptted during | 
 |     // the update process since unloading and reloading extension are done in | 
 |     // the single function ExtensionService::AddExtension. | 
 |     AddDummyAppHandler(); | 
 |  | 
 |     base::ThreadTaskRunnerHandle::Get()->PostTask( | 
 |         FROM_HERE, | 
 |         base::BindOnce(&ExtensionGCMAppHandler::RemoveDummyAppHandler, | 
 |                        weak_factory_.GetWeakPtr())); | 
 |   } | 
 |  | 
 |   // When the extention is being uninstalled, it will be unloaded first. We | 
 |   // should not remove the app handler in this case and it will be handled | 
 |   // in OnExtensionUninstalled. | 
 |   if (reason != UnloadedExtensionReason::UNINSTALL) | 
 |     RemoveAppHandler(extension->id()); | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::OnExtensionUninstalled( | 
 |     content::BrowserContext* browser_context, | 
 |     const Extension* extension, | 
 |     extensions::UninstallReason reason) { | 
 |   if (IsGCMPermissionEnabled(extension)) { | 
 |     // Let's first remove InstanceID data. GCM unregistration will be triggered | 
 |     // after the asynchronous call is returned in OnDeleteIDCompleted. | 
 |     GetInstanceIDDriver() | 
 |         ->GetInstanceID(extension->id()) | 
 |         ->DeleteID(base::BindOnce(&ExtensionGCMAppHandler::OnDeleteIDCompleted, | 
 |                                   weak_factory_.GetWeakPtr(), extension->id())); | 
 |   } | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::AddDummyAppHandler() { | 
 |   AddAppHandler(kDummyAppId); | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::RemoveDummyAppHandler() { | 
 |   RemoveAppHandler(kDummyAppId); | 
 | } | 
 |  | 
 | gcm::GCMDriver* ExtensionGCMAppHandler::GetGCMDriver() const { | 
 |   return gcm::GCMProfileServiceFactory::GetForProfile(profile_)->driver(); | 
 | } | 
 |  | 
 | instance_id::InstanceIDDriver* ExtensionGCMAppHandler::GetInstanceIDDriver() | 
 |     const { | 
 |   return instance_id::InstanceIDProfileServiceFactory::GetForProfile(profile_)-> | 
 |       driver(); | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::OnUnregisterCompleted( | 
 |     const std::string& app_id, gcm::GCMClient::Result result) { | 
 |   RemoveAppHandler(app_id); | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::OnDeleteIDCompleted( | 
 |     const std::string& app_id, instance_id::InstanceID::Result result) { | 
 |   GetGCMDriver()->Unregister( | 
 |       app_id, base::BindOnce(&ExtensionGCMAppHandler::OnUnregisterCompleted, | 
 |                              weak_factory_.GetWeakPtr(), app_id)); | 
 |  | 
 |   // InstanceIDDriver::RemoveInstanceID will delete the InstanceID itself. | 
 |   // Postpone to do it outside this calling context to avoid any risk to | 
 |   // the caller. | 
 |   base::ThreadTaskRunnerHandle::Get()->PostTask( | 
 |       FROM_HERE, base::BindOnce(&ExtensionGCMAppHandler::RemoveInstanceID, | 
 |                                 weak_factory_.GetWeakPtr(), app_id)); | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::RemoveInstanceID(const std::string& app_id) { | 
 |   GetInstanceIDDriver()->RemoveInstanceID(app_id); | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::AddAppHandler(const std::string& app_id) { | 
 |   GetGCMDriver()->AddAppHandler(app_id, this); | 
 | } | 
 |  | 
 | void ExtensionGCMAppHandler::RemoveAppHandler(const std::string& app_id) { | 
 |   GetGCMDriver()->RemoveAppHandler(app_id); | 
 | } | 
 |  | 
 | }  // namespace extensions |