blob: 90f671bdb1fe517495370051322b09fb49a9fce9 [file] [log] [blame]
// Copyright 2014 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/renderer_freezer.h"
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/process/process_handle.h"
#include "chrome/browser/chrome_notification_types.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/notification_types.h"
#include "extensions/browser/process_map.h"
#include "extensions/common/extension.h"
#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/permissions_data.h"
namespace chromeos {
RendererFreezer::RendererFreezer(
std::unique_ptr<RendererFreezer::Delegate> delegate)
: delegate_(std::move(delegate)), weak_factory_(this) {
delegate_->CheckCanFreezeRenderers(
base::Bind(&RendererFreezer::OnCheckCanFreezeRenderersComplete,
weak_factory_.GetWeakPtr()));
}
RendererFreezer::~RendererFreezer() {
for (int rph_id : gcm_extension_processes_) {
content::RenderProcessHost* host =
content::RenderProcessHost::FromID(rph_id);
if (host)
host->RemoveObserver(this);
}
}
void RendererFreezer::SuspendImminent() {
// All the delegate's operations are asynchronous so they may not complete
// before the system suspends. This is ok since the renderers only need to be
// frozen in dark resume. As long as they do get frozen soon after we enter
// dark resume, there shouldn't be a problem.
delegate_->FreezeRenderers();
}
void RendererFreezer::SuspendDone() {
delegate_->ThawRenderers(base::Bind(&RendererFreezer::OnThawRenderersComplete,
weak_factory_.GetWeakPtr()));
}
void RendererFreezer::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case content::NOTIFICATION_RENDERER_PROCESS_CREATED: {
content::RenderProcessHost* process =
content::Source<content::RenderProcessHost>(source).ptr();
OnRenderProcessCreated(process);
break;
}
default: {
NOTREACHED();
break;
}
}
}
void RendererFreezer::RenderProcessExited(
content::RenderProcessHost* host,
const content::ChildProcessTerminationInfo& info) {
auto it = gcm_extension_processes_.find(host->GetID());
if (it == gcm_extension_processes_.end()) {
LOG(ERROR) << "Received unrequested RenderProcessExited message";
return;
}
gcm_extension_processes_.erase(it);
// When this function is called, the renderer process has died but the
// RenderProcessHost will not be destroyed. If a new renderer process is
// created for this RPH, registering as an observer again will trigger a
// warning about duplicate observers. To prevent this we just stop observing
// this RPH until another renderer process is created for it.
host->RemoveObserver(this);
}
void RendererFreezer::RenderProcessHostDestroyed(
content::RenderProcessHost* host) {
auto it = gcm_extension_processes_.find(host->GetID());
if (it == gcm_extension_processes_.end()) {
LOG(ERROR) << "Received unrequested RenderProcessHostDestroyed message";
return;
}
gcm_extension_processes_.erase(it);
}
void RendererFreezer::OnCheckCanFreezeRenderersComplete(bool can_freeze) {
if (!can_freeze)
return;
PowerManagerClient::Get()->SetRenderProcessManagerDelegate(
weak_factory_.GetWeakPtr());
registrar_.Add(
this,
content::NOTIFICATION_RENDERER_PROCESS_CREATED,
content::NotificationService::AllBrowserContextsAndSources());
}
void RendererFreezer::OnThawRenderersComplete(bool success) {
if (success)
return;
// We failed to write the thaw command and the renderers are still frozen. We
// are in big trouble because none of the tabs will be responsive so let's
// crash the browser instead.
LOG(FATAL) << "Unable to thaw renderers.";
}
void RendererFreezer::OnRenderProcessCreated(content::RenderProcessHost* rph) {
const int rph_id = rph->GetID();
if (gcm_extension_processes_.find(rph_id) != gcm_extension_processes_.end()) {
LOG(ERROR) << "Received duplicate notifications about the creation of a "
<< "RenderProcessHost with id " << rph_id;
return;
}
// According to extensions::ProcessMap, extensions and renderers have a
// many-to-many relationship. Specifically, a hosted app can appear in many
// renderers while any other kind of extension can be running in "split mode"
// if there is an incognito window open and so could appear in two renderers.
//
// We don't care about hosted apps because they cannot use GCM so we only need
// to worry about extensions in "split mode". Luckily for us this function is
// called any time a new renderer process is created so we don't really need
// to care whether we are currently in an incognito context. We just need to
// iterate over all the extensions in the newly created process and take the
// appropriate action based on whether we find an extension using GCM.
content::BrowserContext* context = rph->GetBrowserContext();
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(context);
for (const std::string& extension_id :
extensions::ProcessMap::Get(context)->GetExtensionsInProcess(rph_id)) {
const extensions::Extension* extension = registry->GetExtensionById(
extension_id, extensions::ExtensionRegistry::ENABLED);
if (!extension ||
!extension->permissions_data()->HasAPIPermission(
extensions::APIPermission::kGcm)) {
continue;
}
// This renderer has an extension that is using GCM. Make sure it is not
// frozen during suspend.
delegate_->SetShouldFreezeRenderer(rph->GetProcess().Handle(), false);
gcm_extension_processes_.insert(rph_id);
// Watch to see if the renderer process or the RenderProcessHost is
// destroyed.
rph->AddObserver(this);
return;
}
// We didn't find an extension in this RenderProcessHost that is using GCM so
// we can go ahead and freeze it on suspend.
delegate_->SetShouldFreezeRenderer(rph->GetProcess().Handle(), true);
}
} // namespace chromeos