| 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..2af86ae83edfffe43362d0dcc66821d2db2fbc55
|
| --- /dev/null
|
| +++ b/chrome/browser/chromeos/power/extension_event_observer.cc
|
| @@ -0,0 +1,272 @@
|
| +// 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 {
|
| +
|
| +namespace {
|
| +// The number of milliseconds that we should wait after receiving a
|
| +// DarkSuspendImminent signal before attempting to report readiness to suspend.
|
| +const int kDarkSuspendDelayMs = 1000;
|
| +}
|
| +
|
| +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_)
|
| + 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::CreateTestApi() {
|
| + return make_scoped_ptr(
|
| + new ExtensionEventObserver::TestApi(weak_factory_.GetWeakPtr()));
|
| +}
|
| +
|
| +void ExtensionEventObserver::SetShouldDelaySuspend(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::FromMilliseconds(kDarkSuspendDelayMs)
|
| + : base::TimeDelta());
|
| +}
|
| +
|
| +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
|
|
|