Chromium Code Reviews| Index: chrome/browser/chromeos/power/extension_event_observer.cc |
| diff --git a/chrome/browser/chromeos/power/extension_event_observer.cc b/chrome/browser/chromeos/power/extension_event_observer.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b9be73dbf8b36076c39bbb4261a813cb9cb5940d |
| --- /dev/null |
| +++ b/chrome/browser/chromeos/power/extension_event_observer.cc |
| @@ -0,0 +1,266 @@ |
| +// Copyright 2015 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/chromeos/power/extension_event_observer.h" |
| + |
| +#include "base/bind.h" |
| +#include "base/logging.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "chrome/browser/chrome_notification_types.h" |
| +#include "chrome/browser/profiles/profile.h" |
| +#include "chrome/common/extensions/api/gcm.h" |
| +#include "chromeos/dbus/dbus_thread_manager.h" |
| +#include "content/public/browser/notification_service.h" |
| +#include "extensions/browser/extension_host.h" |
| +#include "extensions/browser/process_manager.h" |
| +#include "extensions/common/extension.h" |
| +#include "extensions/common/manifest_handlers/background_info.h" |
| +#include "extensions/common/permissions/api_permission.h" |
| +#include "extensions/common/permissions/permissions_data.h" |
| + |
| +namespace chromeos { |
| + |
| +ExtensionEventObserver::TestApi::TestApi( |
| + base::WeakPtr<ExtensionEventObserver> parent) |
| + : parent_(parent) { |
| +} |
| + |
| +ExtensionEventObserver::TestApi::~TestApi() { |
| +} |
| + |
| +bool ExtensionEventObserver::TestApi::MaybeRunSuspendReadinessCallback() { |
| + if (!parent_ || parent_->suspend_readiness_callback_.callback().is_null()) |
| + return false; |
| + |
| + parent_->suspend_readiness_callback_.callback().Run(); |
| + parent_->suspend_readiness_callback_.Cancel(); |
| + return true; |
| +} |
| + |
| +bool ExtensionEventObserver::TestApi::WillDelaySuspendForExtensionHost( |
| + extensions::ExtensionHost* host) { |
| + if (!parent_) |
| + return false; |
| + |
| + return parent_->keepalive_sources_.contains(host); |
| +} |
| + |
| +struct ExtensionEventObserver::KeepaliveSources { |
| + std::set<int> unacked_push_messages; |
| + std::set<uint64> pending_network_requests; |
| +}; |
| + |
| +ExtensionEventObserver::ExtensionEventObserver() |
| + : should_delay_suspend_(true), |
| + suspend_is_pending_(false), |
| + suspend_keepalive_count_(0), |
| + weak_factory_(this) { |
| + registrar_.Add(this, chrome::NOTIFICATION_PROFILE_ADDED, |
| + content::NotificationService::AllBrowserContextsAndSources()); |
| + registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, |
| + content::NotificationService::AllBrowserContextsAndSources()); |
| + |
| + DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this); |
| +} |
| + |
| +ExtensionEventObserver::~ExtensionEventObserver() { |
| + for (Profile* profile : active_profiles_) { |
|
Daniel Erat
2015/01/13 23:49:09
nit: omit curly brackets
Chirantan Ekbote
2015/01/15 05:06:35
Done.
|
| + extensions::ProcessManager::Get(profile)->RemoveObserver(this); |
| + } |
| + |
| + for (const auto& pair : keepalive_sources_) { |
| + extensions::ExtensionHost* host = |
| + const_cast<extensions::ExtensionHost*>(pair.first); |
| + host->RemoveObserver(this); |
| + } |
| + |
| + DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this); |
| +} |
| + |
| +scoped_ptr<ExtensionEventObserver::TestApi> |
| +ExtensionEventObserver::GetApiForTesting() { |
| + return make_scoped_ptr( |
| + new ExtensionEventObserver::TestApi(weak_factory_.GetWeakPtr())); |
| +} |
| + |
| +void ExtensionEventObserver::ShouldDelaySuspend(bool should_delay) { |
| + should_delay_suspend_ = should_delay; |
| + if (!should_delay_suspend_ && suspend_is_pending_) { |
| + // There is a suspend attempt pending but this class should no longer be |
| + // delaying it. Immediately report readiness. |
| + suspend_is_pending_ = false; |
| + power_manager_callback_.Run(); |
| + power_manager_callback_.Reset(); |
| + suspend_readiness_callback_.Cancel(); |
| + } |
| +} |
| + |
| +void ExtensionEventObserver::Observe( |
| + int type, |
| + const content::NotificationSource& source, |
| + const content::NotificationDetails& details) { |
| + switch (type) { |
| + case chrome::NOTIFICATION_PROFILE_ADDED: { |
| + OnProfileAdded(content::Source<Profile>(source).ptr()); |
| + break; |
| + } |
| + case chrome::NOTIFICATION_PROFILE_DESTROYED: { |
| + OnProfileDestroyed(content::Source<Profile>(source).ptr()); |
| + break; |
| + } |
| + default: |
| + NOTREACHED(); |
| + } |
| +} |
| + |
| +void ExtensionEventObserver::OnBackgroundHostCreated( |
| + extensions::ExtensionHost* host) { |
| + // We only care about ExtensionHosts for extensions that use GCM and have a |
| + // lazy background page. |
| + if (!host->extension()->permissions_data()->HasAPIPermission( |
| + extensions::APIPermission::kGcm) || |
| + !extensions::BackgroundInfo::HasLazyBackgroundPage(host->extension())) |
| + return; |
| + |
| + auto result = |
| + keepalive_sources_.add(host, make_scoped_ptr(new KeepaliveSources())); |
| + |
| + if (result.second) |
| + host->AddObserver(this); |
| +} |
| + |
| +void ExtensionEventObserver::OnExtensionHostDestroyed( |
| + const extensions::ExtensionHost* host) { |
| + DCHECK(keepalive_sources_.contains(host)); |
| + |
| + scoped_ptr<KeepaliveSources> sources = |
| + keepalive_sources_.take_and_erase(host); |
| + |
| + suspend_keepalive_count_ -= sources->unacked_push_messages.size(); |
| + suspend_keepalive_count_ -= sources->pending_network_requests.size(); |
| + MaybeReportSuspendReadiness(); |
| +} |
| + |
| +void ExtensionEventObserver::OnExtensionMessageDispatched( |
| + const extensions::ExtensionHost* host, |
| + const std::string& event_name, |
| + int message_id) { |
| + DCHECK(keepalive_sources_.contains(host)); |
| + |
| + if (event_name != extensions::api::gcm::OnMessage::kEventName) |
| + return; |
| + |
| + keepalive_sources_.get(host)->unacked_push_messages.insert(message_id); |
| + ++suspend_keepalive_count_; |
| +} |
| + |
| +void ExtensionEventObserver::OnExtensionMessageAcked( |
| + const extensions::ExtensionHost* host, |
| + int message_id) { |
| + DCHECK(keepalive_sources_.contains(host)); |
| + |
| + if (keepalive_sources_.get(host)->unacked_push_messages.erase(message_id) > |
| + 0) { |
| + --suspend_keepalive_count_; |
| + MaybeReportSuspendReadiness(); |
| + } |
| +} |
| + |
| +void ExtensionEventObserver::OnNetworkRequestStarted( |
| + const extensions::ExtensionHost* host, |
| + uint64 request_id) { |
| + DCHECK(keepalive_sources_.contains(host)); |
| + |
| + KeepaliveSources* sources = keepalive_sources_.get(host); |
| + |
| + // We only care about network requests that were started while a push message |
| + // is pending. This is an indication that the network request is related to |
| + // the push message. |
| + if (sources->unacked_push_messages.empty()) |
| + return; |
| + |
| + sources->pending_network_requests.insert(request_id); |
| + ++suspend_keepalive_count_; |
| +} |
| + |
| +void ExtensionEventObserver::OnNetworkRequestDone( |
| + const extensions::ExtensionHost* host, |
| + uint64 request_id) { |
| + DCHECK(keepalive_sources_.contains(host)); |
| + |
| + if (keepalive_sources_.get(host)->pending_network_requests.erase(request_id) > |
| + 0) { |
| + --suspend_keepalive_count_; |
| + MaybeReportSuspendReadiness(); |
| + } |
| +} |
| + |
| +void ExtensionEventObserver::SuspendImminent() { |
| + if (should_delay_suspend_) |
| + OnSuspendImminent(false); |
| +} |
| + |
| +void ExtensionEventObserver::DarkSuspendImminent() { |
| + if (should_delay_suspend_) |
| + OnSuspendImminent(true); |
| +} |
| + |
| +void ExtensionEventObserver::SuspendDone(const base::TimeDelta& duration) { |
| + suspend_is_pending_ = false; |
| + power_manager_callback_.Reset(); |
| + suspend_readiness_callback_.Cancel(); |
| +} |
| + |
| +void ExtensionEventObserver::OnProfileAdded(Profile* profile) { |
| + auto result = active_profiles_.insert(profile); |
| + |
| + if (result.second) |
| + extensions::ProcessManager::Get(profile)->AddObserver(this); |
| +} |
| + |
| +void ExtensionEventObserver::OnProfileDestroyed(Profile* profile) { |
| + if (active_profiles_.erase(profile) == 0) |
| + return; |
| + |
| + extensions::ProcessManager::Get(profile)->RemoveObserver(this); |
| +} |
| + |
| +void ExtensionEventObserver::OnSuspendImminent(bool dark_suspend) { |
| + if (suspend_is_pending_) { |
| + LOG(WARNING) << "OnSuspendImminent called while previous suspend attempt " |
| + << "is still pending."; |
| + } |
| + |
| + suspend_is_pending_ = true; |
| + power_manager_callback_ = DBusThreadManager::Get() |
| + ->GetPowerManagerClient() |
| + ->GetSuspendReadinessCallback(); |
| + |
| + suspend_readiness_callback_.Reset( |
| + base::Bind(&ExtensionEventObserver::MaybeReportSuspendReadiness, |
| + weak_factory_.GetWeakPtr())); |
| + |
| + // Unfortunately, there is a race between the arrival of the |
| + // DarkSuspendImminent signal and OnExtensionMessageDispatched. As a result, |
| + // there is no way to tell from within this method if a push message is about |
| + // to arrive. To try and deal with this, we wait one second before attempting |
| + // to report suspend readiness. If there is a push message pending, we should |
| + // receive it within that time and increment |suspend_keepalive_count_| to |
| + // prevent this callback from reporting ready. |
| + base::MessageLoopProxy::current()->PostDelayedTask( |
| + FROM_HERE, suspend_readiness_callback_.callback(), |
| + dark_suspend ? base::TimeDelta::FromSeconds(1) : base::TimeDelta()); |
|
Daniel Erat
2015/01/13 23:49:10
nit: move this to a millisecond constant at the to
Chirantan Ekbote
2015/01/15 05:06:35
Done.
|
| +} |
| + |
| +void ExtensionEventObserver::MaybeReportSuspendReadiness() { |
| + if (!suspend_is_pending_ || suspend_keepalive_count_ > 0 || |
| + power_manager_callback_.is_null()) |
| + return; |
| + |
| + suspend_is_pending_ = false; |
| + power_manager_callback_.Run(); |
| + power_manager_callback_.Reset(); |
| +} |
| + |
| +} // namespace chromeos |