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 "base/thread_task_runner_handle.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/common/extensions/api/gcm.h"
14 #include "chromeos/dbus/dbus_thread_manager.h"
15 #include "content/public/browser/notification_service.h"
16 #include "extensions/browser/extension_host.h"
17 #include "extensions/browser/process_manager.h"
18 #include "extensions/common/extension.h"
19 #include "extensions/common/manifest_handlers/background_info.h"
20 #include "extensions/common/permissions/api_permission.h"
21 #include "extensions/common/permissions/permissions_data.h"
26 // The number of milliseconds that we should wait after receiving a
27 // DarkSuspendImminent signal before attempting to report readiness to suspend.
28 const int kDarkSuspendDelayMs
= 1000;
31 ExtensionEventObserver::TestApi::TestApi(
32 base::WeakPtr
<ExtensionEventObserver
> parent
)
36 ExtensionEventObserver::TestApi::~TestApi() {
39 bool ExtensionEventObserver::TestApi::MaybeRunSuspendReadinessCallback() {
40 if (!parent_
|| parent_
->suspend_readiness_callback_
.callback().is_null())
43 parent_
->suspend_readiness_callback_
.callback().Run();
44 parent_
->suspend_readiness_callback_
.Cancel();
48 bool ExtensionEventObserver::TestApi::WillDelaySuspendForExtensionHost(
49 extensions::ExtensionHost
* host
) {
53 return parent_
->keepalive_sources_
.contains(host
);
56 struct ExtensionEventObserver::KeepaliveSources
{
57 std::set
<int> unacked_push_messages
;
58 std::set
<uint64
> pending_network_requests
;
61 ExtensionEventObserver::ExtensionEventObserver()
62 : should_delay_suspend_(true),
63 suspend_is_pending_(false),
64 suspend_keepalive_count_(0),
66 registrar_
.Add(this, chrome::NOTIFICATION_PROFILE_ADDED
,
67 content::NotificationService::AllBrowserContextsAndSources());
68 registrar_
.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED
,
69 content::NotificationService::AllBrowserContextsAndSources());
71 DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this);
74 ExtensionEventObserver::~ExtensionEventObserver() {
75 for (Profile
* profile
: active_profiles_
)
76 extensions::ProcessManager::Get(profile
)->RemoveObserver(this);
78 for (const auto& pair
: keepalive_sources_
) {
79 extensions::ExtensionHost
* host
=
80 const_cast<extensions::ExtensionHost
*>(pair
.first
);
81 host
->RemoveObserver(this);
84 DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this);
87 scoped_ptr
<ExtensionEventObserver::TestApi
>
88 ExtensionEventObserver::CreateTestApi() {
89 return make_scoped_ptr(
90 new ExtensionEventObserver::TestApi(weak_factory_
.GetWeakPtr()));
93 void ExtensionEventObserver::SetShouldDelaySuspend(bool should_delay
) {
94 should_delay_suspend_
= should_delay
;
95 if (!should_delay_suspend_
&& suspend_is_pending_
) {
96 // There is a suspend attempt pending but this class should no longer be
97 // delaying it. Immediately report readiness.
98 suspend_is_pending_
= false;
99 power_manager_callback_
.Run();
100 power_manager_callback_
.Reset();
101 suspend_readiness_callback_
.Cancel();
105 void ExtensionEventObserver::Observe(
107 const content::NotificationSource
& source
,
108 const content::NotificationDetails
& details
) {
110 case chrome::NOTIFICATION_PROFILE_ADDED
: {
111 OnProfileAdded(content::Source
<Profile
>(source
).ptr());
114 case chrome::NOTIFICATION_PROFILE_DESTROYED
: {
115 OnProfileDestroyed(content::Source
<Profile
>(source
).ptr());
123 void ExtensionEventObserver::OnBackgroundHostCreated(
124 extensions::ExtensionHost
* host
) {
125 // We only care about ExtensionHosts for extensions that use GCM and have a
126 // lazy background page.
127 if (!host
->extension()->permissions_data()->HasAPIPermission(
128 extensions::APIPermission::kGcm
) ||
129 !extensions::BackgroundInfo::HasLazyBackgroundPage(host
->extension()))
133 keepalive_sources_
.add(host
, make_scoped_ptr(new KeepaliveSources()));
136 host
->AddObserver(this);
139 void ExtensionEventObserver::OnExtensionHostDestroyed(
140 const extensions::ExtensionHost
* host
) {
141 DCHECK(keepalive_sources_
.contains(host
));
143 scoped_ptr
<KeepaliveSources
> sources
=
144 keepalive_sources_
.take_and_erase(host
);
146 suspend_keepalive_count_
-= sources
->unacked_push_messages
.size();
147 suspend_keepalive_count_
-= sources
->pending_network_requests
.size();
148 MaybeReportSuspendReadiness();
151 void ExtensionEventObserver::OnBackgroundEventDispatched(
152 const extensions::ExtensionHost
* host
,
153 const std::string
& event_name
,
155 DCHECK(keepalive_sources_
.contains(host
));
157 if (event_name
!= extensions::api::gcm::OnMessage::kEventName
)
160 keepalive_sources_
.get(host
)->unacked_push_messages
.insert(event_id
);
161 ++suspend_keepalive_count_
;
164 void ExtensionEventObserver::OnBackgroundEventAcked(
165 const extensions::ExtensionHost
* host
,
167 DCHECK(keepalive_sources_
.contains(host
));
169 if (keepalive_sources_
.get(host
)->unacked_push_messages
.erase(event_id
) > 0) {
170 --suspend_keepalive_count_
;
171 MaybeReportSuspendReadiness();
175 void ExtensionEventObserver::OnNetworkRequestStarted(
176 const extensions::ExtensionHost
* host
,
178 DCHECK(keepalive_sources_
.contains(host
));
180 KeepaliveSources
* sources
= keepalive_sources_
.get(host
);
182 // We only care about network requests that were started while a push message
183 // is pending. This is an indication that the network request is related to
185 if (sources
->unacked_push_messages
.empty())
188 sources
->pending_network_requests
.insert(request_id
);
189 ++suspend_keepalive_count_
;
192 void ExtensionEventObserver::OnNetworkRequestDone(
193 const extensions::ExtensionHost
* host
,
195 DCHECK(keepalive_sources_
.contains(host
));
197 if (keepalive_sources_
.get(host
)->pending_network_requests
.erase(request_id
) >
199 --suspend_keepalive_count_
;
200 MaybeReportSuspendReadiness();
204 void ExtensionEventObserver::SuspendImminent() {
205 if (should_delay_suspend_
)
206 OnSuspendImminent(false);
209 void ExtensionEventObserver::DarkSuspendImminent() {
210 if (should_delay_suspend_
)
211 OnSuspendImminent(true);
214 void ExtensionEventObserver::SuspendDone(const base::TimeDelta
& duration
) {
215 suspend_is_pending_
= false;
216 power_manager_callback_
.Reset();
217 suspend_readiness_callback_
.Cancel();
220 void ExtensionEventObserver::OnProfileAdded(Profile
* profile
) {
221 auto result
= active_profiles_
.insert(profile
);
224 extensions::ProcessManager::Get(profile
)->AddObserver(this);
227 void ExtensionEventObserver::OnProfileDestroyed(Profile
* profile
) {
228 if (active_profiles_
.erase(profile
) == 0)
231 extensions::ProcessManager::Get(profile
)->RemoveObserver(this);
234 void ExtensionEventObserver::OnSuspendImminent(bool dark_suspend
) {
235 if (suspend_is_pending_
) {
236 LOG(WARNING
) << "OnSuspendImminent called while previous suspend attempt "
237 << "is still pending.";
240 suspend_is_pending_
= true;
241 power_manager_callback_
= DBusThreadManager::Get()
242 ->GetPowerManagerClient()
243 ->GetSuspendReadinessCallback();
245 suspend_readiness_callback_
.Reset(
246 base::Bind(&ExtensionEventObserver::MaybeReportSuspendReadiness
,
247 weak_factory_
.GetWeakPtr()));
249 // Unfortunately, there is a race between the arrival of the
250 // DarkSuspendImminent signal and OnBackgroundEventDispatched. As a result,
251 // there is no way to tell from within this method if a push message is about
252 // to arrive. To try and deal with this, we wait one second before attempting
253 // to report suspend readiness. If there is a push message pending, we should
254 // receive it within that time and increment |suspend_keepalive_count_| to
255 // prevent this callback from reporting ready.
256 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
257 FROM_HERE
, suspend_readiness_callback_
.callback(),
258 dark_suspend
? base::TimeDelta::FromMilliseconds(kDarkSuspendDelayMs
)
259 : base::TimeDelta());
262 void ExtensionEventObserver::MaybeReportSuspendReadiness() {
263 if (!suspend_is_pending_
|| suspend_keepalive_count_
> 0 ||
264 power_manager_callback_
.is_null())
267 suspend_is_pending_
= false;
268 power_manager_callback_
.Run();
269 power_manager_callback_
.Reset();
272 } // namespace chromeos