OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 #include "chrome/browser/chromeos/power/extension_event_observer.h" |
| 6 |
| 7 #include "base/bind.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" |
| 21 |
| 22 namespace chromeos { |
| 23 |
| 24 namespace { |
| 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; |
| 28 } |
| 29 |
| 30 ExtensionEventObserver::TestApi::TestApi( |
| 31 base::WeakPtr<ExtensionEventObserver> parent) |
| 32 : parent_(parent) { |
| 33 } |
| 34 |
| 35 ExtensionEventObserver::TestApi::~TestApi() { |
| 36 } |
| 37 |
| 38 bool ExtensionEventObserver::TestApi::MaybeRunSuspendReadinessCallback() { |
| 39 if (!parent_ || parent_->suspend_readiness_callback_.callback().is_null()) |
| 40 return false; |
| 41 |
| 42 parent_->suspend_readiness_callback_.callback().Run(); |
| 43 parent_->suspend_readiness_callback_.Cancel(); |
| 44 return true; |
| 45 } |
| 46 |
| 47 bool ExtensionEventObserver::TestApi::WillDelaySuspendForExtensionHost( |
| 48 extensions::ExtensionHost* host) { |
| 49 if (!parent_) |
| 50 return false; |
| 51 |
| 52 return parent_->keepalive_sources_.contains(host); |
| 53 } |
| 54 |
| 55 struct ExtensionEventObserver::KeepaliveSources { |
| 56 std::set<int> unacked_push_messages; |
| 57 std::set<uint64> pending_network_requests; |
| 58 }; |
| 59 |
| 60 ExtensionEventObserver::ExtensionEventObserver() |
| 61 : should_delay_suspend_(true), |
| 62 suspend_is_pending_(false), |
| 63 suspend_keepalive_count_(0), |
| 64 weak_factory_(this) { |
| 65 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_ADDED, |
| 66 content::NotificationService::AllBrowserContextsAndSources()); |
| 67 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, |
| 68 content::NotificationService::AllBrowserContextsAndSources()); |
| 69 |
| 70 DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this); |
| 71 } |
| 72 |
| 73 ExtensionEventObserver::~ExtensionEventObserver() { |
| 74 for (Profile* profile : active_profiles_) |
| 75 extensions::ProcessManager::Get(profile)->RemoveObserver(this); |
| 76 |
| 77 for (const auto& pair : keepalive_sources_) { |
| 78 extensions::ExtensionHost* host = |
| 79 const_cast<extensions::ExtensionHost*>(pair.first); |
| 80 host->RemoveObserver(this); |
| 81 } |
| 82 |
| 83 DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this); |
| 84 } |
| 85 |
| 86 scoped_ptr<ExtensionEventObserver::TestApi> |
| 87 ExtensionEventObserver::CreateTestApi() { |
| 88 return make_scoped_ptr( |
| 89 new ExtensionEventObserver::TestApi(weak_factory_.GetWeakPtr())); |
| 90 } |
| 91 |
| 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(); |
| 101 } |
| 102 } |
| 103 |
| 104 void ExtensionEventObserver::Observe( |
| 105 int type, |
| 106 const content::NotificationSource& source, |
| 107 const content::NotificationDetails& details) { |
| 108 switch (type) { |
| 109 case chrome::NOTIFICATION_PROFILE_ADDED: { |
| 110 OnProfileAdded(content::Source<Profile>(source).ptr()); |
| 111 break; |
| 112 } |
| 113 case chrome::NOTIFICATION_PROFILE_DESTROYED: { |
| 114 OnProfileDestroyed(content::Source<Profile>(source).ptr()); |
| 115 break; |
| 116 } |
| 117 default: |
| 118 NOTREACHED(); |
| 119 } |
| 120 } |
| 121 |
| 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())) |
| 129 return; |
| 130 |
| 131 auto result = |
| 132 keepalive_sources_.add(host, make_scoped_ptr(new KeepaliveSources())); |
| 133 |
| 134 if (result.second) |
| 135 host->AddObserver(this); |
| 136 } |
| 137 |
| 138 void ExtensionEventObserver::OnExtensionHostDestroyed( |
| 139 const extensions::ExtensionHost* host) { |
| 140 DCHECK(keepalive_sources_.contains(host)); |
| 141 |
| 142 scoped_ptr<KeepaliveSources> sources = |
| 143 keepalive_sources_.take_and_erase(host); |
| 144 |
| 145 suspend_keepalive_count_ -= sources->unacked_push_messages.size(); |
| 146 suspend_keepalive_count_ -= sources->pending_network_requests.size(); |
| 147 MaybeReportSuspendReadiness(); |
| 148 } |
| 149 |
| 150 void ExtensionEventObserver::OnExtensionMessageDispatched( |
| 151 const extensions::ExtensionHost* host, |
| 152 const std::string& event_name, |
| 153 int message_id) { |
| 154 DCHECK(keepalive_sources_.contains(host)); |
| 155 |
| 156 if (event_name != extensions::api::gcm::OnMessage::kEventName) |
| 157 return; |
| 158 |
| 159 keepalive_sources_.get(host)->unacked_push_messages.insert(message_id); |
| 160 ++suspend_keepalive_count_; |
| 161 } |
| 162 |
| 163 void ExtensionEventObserver::OnExtensionMessageAcked( |
| 164 const extensions::ExtensionHost* host, |
| 165 int message_id) { |
| 166 DCHECK(keepalive_sources_.contains(host)); |
| 167 |
| 168 if (keepalive_sources_.get(host)->unacked_push_messages.erase(message_id) > |
| 169 0) { |
| 170 --suspend_keepalive_count_; |
| 171 MaybeReportSuspendReadiness(); |
| 172 } |
| 173 } |
| 174 |
| 175 void ExtensionEventObserver::OnNetworkRequestStarted( |
| 176 const extensions::ExtensionHost* host, |
| 177 uint64 request_id) { |
| 178 DCHECK(keepalive_sources_.contains(host)); |
| 179 |
| 180 KeepaliveSources* sources = keepalive_sources_.get(host); |
| 181 |
| 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 |
| 184 // the push message. |
| 185 if (sources->unacked_push_messages.empty()) |
| 186 return; |
| 187 |
| 188 sources->pending_network_requests.insert(request_id); |
| 189 ++suspend_keepalive_count_; |
| 190 } |
| 191 |
| 192 void ExtensionEventObserver::OnNetworkRequestDone( |
| 193 const extensions::ExtensionHost* host, |
| 194 uint64 request_id) { |
| 195 DCHECK(keepalive_sources_.contains(host)); |
| 196 |
| 197 if (keepalive_sources_.get(host)->pending_network_requests.erase(request_id) > |
| 198 0) { |
| 199 --suspend_keepalive_count_; |
| 200 MaybeReportSuspendReadiness(); |
| 201 } |
| 202 } |
| 203 |
| 204 void ExtensionEventObserver::SuspendImminent() { |
| 205 if (should_delay_suspend_) |
| 206 OnSuspendImminent(false); |
| 207 } |
| 208 |
| 209 void ExtensionEventObserver::DarkSuspendImminent() { |
| 210 if (should_delay_suspend_) |
| 211 OnSuspendImminent(true); |
| 212 } |
| 213 |
| 214 void ExtensionEventObserver::SuspendDone(const base::TimeDelta& duration) { |
| 215 suspend_is_pending_ = false; |
| 216 power_manager_callback_.Reset(); |
| 217 suspend_readiness_callback_.Cancel(); |
| 218 } |
| 219 |
| 220 void ExtensionEventObserver::OnProfileAdded(Profile* profile) { |
| 221 auto result = active_profiles_.insert(profile); |
| 222 |
| 223 if (result.second) |
| 224 extensions::ProcessManager::Get(profile)->AddObserver(this); |
| 225 } |
| 226 |
| 227 void ExtensionEventObserver::OnProfileDestroyed(Profile* profile) { |
| 228 if (active_profiles_.erase(profile) == 0) |
| 229 return; |
| 230 |
| 231 extensions::ProcessManager::Get(profile)->RemoveObserver(this); |
| 232 } |
| 233 |
| 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."; |
| 238 } |
| 239 |
| 240 suspend_is_pending_ = true; |
| 241 power_manager_callback_ = DBusThreadManager::Get() |
| 242 ->GetPowerManagerClient() |
| 243 ->GetSuspendReadinessCallback(); |
| 244 |
| 245 suspend_readiness_callback_.Reset( |
| 246 base::Bind(&ExtensionEventObserver::MaybeReportSuspendReadiness, |
| 247 weak_factory_.GetWeakPtr())); |
| 248 |
| 249 // Unfortunately, there is a race between the arrival of the |
| 250 // DarkSuspendImminent signal and OnExtensionMessageDispatched. 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::MessageLoopProxy::current()->PostDelayedTask( |
| 257 FROM_HERE, suspend_readiness_callback_.callback(), |
| 258 dark_suspend ? base::TimeDelta::FromMilliseconds(kDarkSuspendDelayMs) |
| 259 : base::TimeDelta()); |
| 260 } |
| 261 |
| 262 void ExtensionEventObserver::MaybeReportSuspendReadiness() { |
| 263 if (!suspend_is_pending_ || suspend_keepalive_count_ > 0 || |
| 264 power_manager_callback_.is_null()) |
| 265 return; |
| 266 |
| 267 suspend_is_pending_ = false; |
| 268 power_manager_callback_.Run(); |
| 269 power_manager_callback_.Reset(); |
| 270 } |
| 271 |
| 272 } // namespace chromeos |
OLD | NEW |