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/extensions/extension_gcm_app_handler.h"
6
7#include "base/bind.h"
8#include "base/lazy_instance.h"
9#include "base/location.h"
10#include "chrome/browser/chrome_notification_types.h"
11#include "chrome/browser/extensions/api/gcm/gcm_api.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/services/gcm/gcm_profile_service.h"
14#include "chrome/browser/services/gcm/gcm_profile_service_factory.h"
15#include "components/gcm_driver/gcm_driver.h"
16#include "extensions/browser/extension_registry.h"
17#include "extensions/browser/extension_system.h"
18#include "extensions/common/extension.h"
19#include "extensions/common/permissions/permissions_data.h"
20
21namespace extensions {
22
23namespace {
24
25const char kDummyAppId[] = "extension.guard.dummy.id";
26
27base::LazyInstance<BrowserContextKeyedAPIFactory<ExtensionGCMAppHandler> >
28    g_factory = LAZY_INSTANCE_INITIALIZER;
29
30bool IsGCMPermissionEnabled(const Extension* extension) {
31  return extension->permissions_data()->HasAPIPermission(APIPermission::kGcm);
32}
33
34}  // namespace
35
36
37// static
38BrowserContextKeyedAPIFactory<ExtensionGCMAppHandler>*
39ExtensionGCMAppHandler::GetFactoryInstance() {
40  return g_factory.Pointer();
41}
42
43ExtensionGCMAppHandler::ExtensionGCMAppHandler(content::BrowserContext* context)
44    : profile_(Profile::FromBrowserContext(context)),
45      extension_registry_observer_(this),
46      weak_factory_(this) {
47  extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
48  js_event_router_.reset(new extensions::GcmJsEventRouter(profile_));
49}
50
51ExtensionGCMAppHandler::~ExtensionGCMAppHandler() {
52  const ExtensionSet& enabled_extensions =
53      ExtensionRegistry::Get(profile_)->enabled_extensions();
54  for (ExtensionSet::const_iterator extension = enabled_extensions.begin();
55       extension != enabled_extensions.end();
56       ++extension) {
57    if (IsGCMPermissionEnabled(extension->get()))
58      GetGCMDriver()->RemoveAppHandler((*extension)->id());
59  }
60}
61
62void ExtensionGCMAppHandler::ShutdownHandler() {
63  js_event_router_.reset();
64}
65
66void ExtensionGCMAppHandler::OnMessage(
67    const std::string& app_id,
68    const gcm::GCMClient::IncomingMessage& message) {
69  js_event_router_->OnMessage(app_id, message);
70}
71
72void ExtensionGCMAppHandler::OnMessagesDeleted(const std::string& app_id) {
73  js_event_router_->OnMessagesDeleted(app_id);
74}
75
76void ExtensionGCMAppHandler::OnSendError(
77    const std::string& app_id,
78    const gcm::GCMClient::SendErrorDetails& send_error_details) {
79  js_event_router_->OnSendError(app_id, send_error_details);
80}
81
82void ExtensionGCMAppHandler::OnSendAcknowledged(
83    const std::string& app_id,
84    const std::string& message_id) {
85  // This event is not exposed to JS API. It terminates here.
86}
87
88void ExtensionGCMAppHandler::OnExtensionLoaded(
89    content::BrowserContext* browser_context,
90    const Extension* extension) {
91  if (IsGCMPermissionEnabled(extension))
92    AddAppHandler(extension->id());
93}
94
95void ExtensionGCMAppHandler::OnExtensionUnloaded(
96    content::BrowserContext* browser_context,
97    const Extension* extension,
98    UnloadedExtensionInfo::Reason reason) {
99  if (!IsGCMPermissionEnabled(extension))
100    return;
101
102  if (reason == UnloadedExtensionInfo::REASON_UPDATE &&
103      GetGCMDriver()->app_handlers().size() == 1) {
104    // When the extension is being updated, it will be first unloaded and then
105    // loaded again by ExtensionService::AddExtension. If the app handler for
106    // this extension is the only handler, removing it and adding it again will
107    // cause the GCM service being stopped and restarted unnecessarily. To work
108    // around this, we add a dummy app handler to guard against it. This dummy
109    // app handler will be removed once the extension loading logic is done.
110    //
111    // Also note that the GCM message routing will not be interruptted during
112    // the update process since unloading and reloading extension are done in
113    // the single function ExtensionService::AddExtension.
114    AddDummyAppHandler();
115
116    base::MessageLoop::current()->PostTask(
117        FROM_HERE,
118        base::Bind(&ExtensionGCMAppHandler::RemoveDummyAppHandler,
119                   weak_factory_.GetWeakPtr()));
120  }
121
122  RemoveAppHandler(extension->id());
123}
124
125void ExtensionGCMAppHandler::OnExtensionUninstalled(
126    content::BrowserContext* browser_context,
127    const Extension* extension,
128    extensions::UninstallReason reason) {
129  if (IsGCMPermissionEnabled(extension)) {
130    GetGCMDriver()->Unregister(
131        extension->id(),
132        base::Bind(&ExtensionGCMAppHandler::OnUnregisterCompleted,
133                   weak_factory_.GetWeakPtr(),
134                   extension->id()));
135    RemoveAppHandler(extension->id());
136  }
137}
138
139void ExtensionGCMAppHandler::AddDummyAppHandler() {
140  AddAppHandler(kDummyAppId);
141}
142
143void ExtensionGCMAppHandler::RemoveDummyAppHandler() {
144  RemoveAppHandler(kDummyAppId);
145}
146
147gcm::GCMDriver* ExtensionGCMAppHandler::GetGCMDriver() const {
148  return gcm::GCMProfileServiceFactory::GetForProfile(profile_)->driver();
149}
150
151void ExtensionGCMAppHandler::OnUnregisterCompleted(
152    const std::string& app_id, gcm::GCMClient::Result result) {
153  // Nothing to do.
154}
155
156void ExtensionGCMAppHandler::AddAppHandler(const std::string& app_id) {
157  GetGCMDriver()->AddAppHandler(app_id, this);
158}
159
160void ExtensionGCMAppHandler::RemoveAppHandler(const std::string& app_id) {
161  GetGCMDriver()->RemoveAppHandler(app_id);
162}
163
164}  // namespace extensions
165