// 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 "base/thread_task_runner_handle.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 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 unacked_push_messages; std::set 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(pair.first); host->RemoveObserver(this); } DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this); } scoped_ptr 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(source).ptr()); break; } case chrome::NOTIFICATION_PROFILE_DESTROYED: { OnProfileDestroyed(content::Source(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 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::OnBackgroundEventDispatched( const extensions::ExtensionHost* host, const std::string& event_name, int event_id) { DCHECK(keepalive_sources_.contains(host)); if (event_name != extensions::api::gcm::OnMessage::kEventName) return; keepalive_sources_.get(host)->unacked_push_messages.insert(event_id); ++suspend_keepalive_count_; } void ExtensionEventObserver::OnBackgroundEventAcked( const extensions::ExtensionHost* host, int event_id) { DCHECK(keepalive_sources_.contains(host)); if (keepalive_sources_.get(host)->unacked_push_messages.erase(event_id) > 0) { --suspend_keepalive_count_; MaybeReportSuspendReadiness(); } } void ExtensionEventObserver::OnNetworkRequestStarted( const extensions::ExtensionHost* host, uint64_t 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_t 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 OnBackgroundEventDispatched. 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::ThreadTaskRunnerHandle::Get()->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