// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/power/renderer_freezer.h"
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/process/process_handle.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/process_map.h"
#include "extensions/common/extension.h"
#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/permissions_data.h"
namespace ash {
RendererFreezer::RendererFreezer(
std::unique_ptr<RendererFreezer::Delegate> delegate)
: delegate_(std::move(delegate)) {
delegate_->CheckCanFreezeRenderers(
base::BindOnce(&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::BindOnce(
&RendererFreezer::OnThawRenderersComplete, weak_factory_.GetWeakPtr()));
}
void RendererFreezer::OnRenderProcessHostCreated(
content::RenderProcessHost* rph) {
if (!can_freeze_renderers_) {
return;
}
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();
if (const extensions::Extension* extension =
extensions::ProcessMap::Get(context)->GetEnabledExtensionByProcessID(
rph_id)) {
if (extension->permissions_data()->HasAPIPermission(
extensions::mojom::APIPermissionID::kGcm)) {
// 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);
}
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) {
can_freeze_renderers_ = can_freeze;
if (!can_freeze)
return;
chromeos::PowerManagerClient::Get()->SetRenderProcessManagerDelegate(
weak_factory_.GetWeakPtr());
}
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.";
}
} // namespace ash