1 // Copyright 2015 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/chromeos/power/extension_event_observer.h"
8 #include "base/logging.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/common/extensions/api/gcm.h"
13 #include "chromeos/dbus/dbus_thread_manager.h"
14 #include "content/public/browser/notification_service.h"
15 #include "extensions/browser/extension_host.h"
16 #include "extensions/browser/process_manager.h"
17 #include "extensions/common/extension.h"
18 #include "extensions/common/manifest_handlers/background_info.h"
19 #include "extensions/common/permissions/api_permission.h"
20 #include "extensions/common/permissions/permissions_data.h"
25 // The number of milliseconds that we should wait after receiving a
26 // DarkSuspendImminent signal before attempting to report readiness to suspend.
27 const int kDarkSuspendDelayMs
= 1000;
30 ExtensionEventObserver::TestApi::TestApi(
31 base::WeakPtr
<ExtensionEventObserver
> parent
)
35 ExtensionEventObserver::TestApi::~TestApi() {
38 bool ExtensionEventObserver::TestApi::MaybeRunSuspendReadinessCallback() {
39 if (!parent_
|| parent_
->suspend_readiness_callback_
.callback().is_null())
42 parent_
->suspend_readiness_callback_
.callback().Run();
43 parent_
->suspend_readiness_callback_
.Cancel();
47 bool ExtensionEventObserver::TestApi::WillDelaySuspendForExtensionHost(
48 extensions::ExtensionHost
* host
) {
52 return parent_
->keepalive_sources_
.contains(host
);
55 struct ExtensionEventObserver::KeepaliveSources
{
56 std::set
<int> unacked_push_messages
;
57 std::set
<uint64
> pending_network_requests
;
60 ExtensionEventObserver::ExtensionEventObserver()
61 : should_delay_suspend_(true),
62 suspend_is_pending_(false),
63 suspend_keepalive_count_(0),
65 registrar_
.Add(this, chrome::NOTIFICATION_PROFILE_ADDED
,
66 content::NotificationService::AllBrowserContextsAndSources());
67 registrar_
.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED
,
68 content::NotificationService::AllBrowserContextsAndSources());
70 DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this);
73 ExtensionEventObserver::~ExtensionEventObserver() {
74 for (Profile
* profile
: active_profiles_
)
75 extensions::ProcessManager::Get(profile
)->RemoveObserver(this);
77 for (const auto& pair
: keepalive_sources_
) {
78 extensions::ExtensionHost
* host
=
79 const_cast<extensions::ExtensionHost
*>(pair
.first
);
80 host
->RemoveObserver(this);
83 DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this);
86 scoped_ptr
<ExtensionEventObserver::TestApi
>
87 ExtensionEventObserver::CreateTestApi() {
88 return make_scoped_ptr(
89 new ExtensionEventObserver::TestApi(weak_factory_
.GetWeakPtr()));
92 void ExtensionEventObserver::SetShouldDelaySuspend(bool should_delay
) {
93 should_delay_suspend_
= should_delay
;
94 if (!should_delay_suspend_
&& suspend_is_pending_
) {
95 // There is a suspend attempt pending but this class should no longer be
96 // delaying it. Immediately report readiness.
97 suspend_is_pending_
= false;
98 power_manager_callback_
.Run();
99 power_manager_callback_
.Reset();
100 suspend_readiness_callback_
.Cancel();
104 void ExtensionEventObserver::Observe(
106 const content::NotificationSource
& source
,
107 const content::NotificationDetails
& details
) {
109 case chrome::NOTIFICATION_PROFILE_ADDED
: {
110 OnProfileAdded(content::Source
<Profile
>(source
).ptr());
113 case chrome::NOTIFICATION_PROFILE_DESTROYED
: {
114 OnProfileDestroyed(content::Source
<Profile
>(source
).ptr());
122 void ExtensionEventObserver::OnBackgroundHostCreated(
123 extensions::ExtensionHost
* host
) {
124 // We only care about ExtensionHosts for extensions that use GCM and have a
125 // lazy background page.
126 if (!host
->extension()->permissions_data()->HasAPIPermission(
127 extensions::APIPermission::kGcm
) ||
128 !extensions::BackgroundInfo::HasLazyBackgroundPage(host
->extension()))
132 keepalive_sources_
.add(host
, make_scoped_ptr(new KeepaliveSources()));
135 host
->AddObserver(this);
138 void ExtensionEventObserver::OnExtensionHostDestroyed(
139 const extensions::ExtensionHost
* host
) {
140 DCHECK(keepalive_sources_
.contains(host
));
142 scoped_ptr
<KeepaliveSources
> sources
=
143 keepalive_sources_
.take_and_erase(host
);
145 suspend_keepalive_count_
-= sources
->unacked_push_messages
.size();
146 suspend_keepalive_count_
-= sources
->pending_network_requests
.size();
147 MaybeReportSuspendReadiness();
150 void ExtensionEventObserver::OnBackgroundEventDispatched(
151 const extensions::ExtensionHost
* host
,
152 const std::string
& event_name
,
154 DCHECK(keepalive_sources_
.contains(host
));
156 if (event_name
!= extensions::api::gcm::OnMessage::kEventName
)
159 keepalive_sources_
.get(host
)->unacked_push_messages
.insert(event_id
);
160 ++suspend_keepalive_count_
;
163 void ExtensionEventObserver::OnBackgroundEventAcked(
164 const extensions::ExtensionHost
* host
,
166 DCHECK(keepalive_sources_
.contains(host
));
168 if (keepalive_sources_
.get(host
)->unacked_push_messages
.erase(event_id
) > 0) {
169 --suspend_keepalive_count_
;
170 MaybeReportSuspendReadiness();
174 void ExtensionEventObserver::OnNetworkRequestStarted(
175 const extensions::ExtensionHost
* host
,
177 DCHECK(keepalive_sources_
.contains(host
));
179 KeepaliveSources
* sources
= keepalive_sources_
.get(host
);
181 // We only care about network requests that were started while a push message
182 // is pending. This is an indication that the network request is related to
184 if (sources
->unacked_push_messages
.empty())
187 sources
->pending_network_requests
.insert(request_id
);
188 ++suspend_keepalive_count_
;
191 void ExtensionEventObserver::OnNetworkRequestDone(
192 const extensions::ExtensionHost
* host
,
194 DCHECK(keepalive_sources_
.contains(host
));
196 if (keepalive_sources_
.get(host
)->pending_network_requests
.erase(request_id
) >
198 --suspend_keepalive_count_
;
199 MaybeReportSuspendReadiness();
203 void ExtensionEventObserver::SuspendImminent() {
204 if (should_delay_suspend_
)
205 OnSuspendImminent(false);
208 void ExtensionEventObserver::DarkSuspendImminent() {
209 if (should_delay_suspend_
)
210 OnSuspendImminent(true);
213 void ExtensionEventObserver::SuspendDone(const base::TimeDelta
& duration
) {
214 suspend_is_pending_
= false;
215 power_manager_callback_
.Reset();
216 suspend_readiness_callback_
.Cancel();
219 void ExtensionEventObserver::OnProfileAdded(Profile
* profile
) {
220 auto result
= active_profiles_
.insert(profile
);
223 extensions::ProcessManager::Get(profile
)->AddObserver(this);
226 void ExtensionEventObserver::OnProfileDestroyed(Profile
* profile
) {
227 if (active_profiles_
.erase(profile
) == 0)
230 extensions::ProcessManager::Get(profile
)->RemoveObserver(this);
233 void ExtensionEventObserver::OnSuspendImminent(bool dark_suspend
) {
234 if (suspend_is_pending_
) {
235 LOG(WARNING
) << "OnSuspendImminent called while previous suspend attempt "
236 << "is still pending.";
239 suspend_is_pending_
= true;
240 power_manager_callback_
= DBusThreadManager::Get()
241 ->GetPowerManagerClient()
242 ->GetSuspendReadinessCallback();
244 suspend_readiness_callback_
.Reset(
245 base::Bind(&ExtensionEventObserver::MaybeReportSuspendReadiness
,
246 weak_factory_
.GetWeakPtr()));
248 // Unfortunately, there is a race between the arrival of the
249 // DarkSuspendImminent signal and OnBackgroundEventDispatched. As a result,
250 // there is no way to tell from within this method if a push message is about
251 // to arrive. To try and deal with this, we wait one second before attempting
252 // to report suspend readiness. If there is a push message pending, we should
253 // receive it within that time and increment |suspend_keepalive_count_| to
254 // prevent this callback from reporting ready.
255 base::MessageLoopProxy::current()->PostDelayedTask(
256 FROM_HERE
, suspend_readiness_callback_
.callback(),
257 dark_suspend
? base::TimeDelta::FromMilliseconds(kDarkSuspendDelayMs
)
258 : base::TimeDelta());
261 void ExtensionEventObserver::MaybeReportSuspendReadiness() {
262 if (!suspend_is_pending_
|| suspend_keepalive_count_
> 0 ||
263 power_manager_callback_
.is_null())
266 suspend_is_pending_
= false;
267 power_manager_callback_
.Run();
268 power_manager_callback_
.Reset();
271 } // namespace chromeos