// 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 #include "base/bind.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/process/process_handle.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/chromeos/login/lock/screen_locker.h" #include "chrome/browser/chromeos/login/lock/screen_locker_delegate.h" #include "chromeos/dbus/dbus_thread_manager.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_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(scoped_ptr delegate) : delegate_(delegate.Pass()), 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 chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED: { OnScreenLockStateChanged( content::Source(source).ptr(), *(content::Details(details).ptr())); break; } case content::NOTIFICATION_RENDERER_PROCESS_CREATED: { content::RenderProcessHost* process = content::Source(source).ptr(); OnRenderProcessCreated(process); break; } default: { NOTREACHED(); break; } } } void RendererFreezer::RenderProcessExited(content::RenderProcessHost* host, base::TerminationStatus status, int exit_code) { 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; DBusThreadManager::Get() ->GetPowerManagerClient() ->SetRenderProcessManagerDelegate(weak_factory_.GetWeakPtr()); registrar_.Add(this, chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED, content::NotificationService::AllBrowserContextsAndSources()); 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::OnScreenLockStateChanged(chromeos::ScreenLocker* locker, bool is_locked) { // The ScreenLocker class sends NOTIFICATION_SCREEN_LOCK_STATE_CHANGED when // the lock screen becomes ready, resulting in this code running synchronously // to mark the screen locker renderer to remain unfrozen during a suspend // request. Since this happens before the PowerManagerClient calls // RendererFreezer::SuspendImminent(), it is guaranteed that the screen locker // renderer will not be frozen at any point. if (is_locked) { delegate_->SetShouldFreezeRenderer(locker->delegate() ->GetAssociatedWebUI() ->GetWebContents() ->GetRenderProcessHost() ->GetHandle(), false); } } 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->GetHandle(), 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->GetHandle(), true); } } // namespace chromeos