Index: chrome/browser/chromeos/power/renderer_freezer.cc |
diff --git a/chrome/browser/chromeos/power/renderer_freezer.cc b/chrome/browser/chromeos/power/renderer_freezer.cc |
index 1aaa79a75293dc5590e924c1c0aa189f970a0c29..6548c9d5c7416fe7e448b3866708ea06c1b9fb43 100644 |
--- a/chrome/browser/chromeos/power/renderer_freezer.cc |
+++ b/chrome/browser/chromeos/power/renderer_freezer.cc |
@@ -4,10 +4,24 @@ |
#include "chrome/browser/chromeos/power/renderer_freezer.h" |
+#include <string> |
+ |
#include "base/bind.h" |
#include "base/logging.h" |
#include "base/message_loop/message_loop.h" |
+#include "base/process/process_handle.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 "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 { |
@@ -15,36 +29,33 @@ RendererFreezer::RendererFreezer(scoped_ptr<RendererFreezer::Delegate> delegate) |
: frozen_(false), |
delegate_(delegate.Pass()), |
weak_factory_(this) { |
- if (delegate_->CanFreezeRenderers()) |
- DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this); |
+ if (delegate_->CanFreezeRenderers()) { |
+ DBusThreadManager::Get() |
+ ->GetPowerManagerClient() |
+ ->SetRenderProcessManagerDelegate(weak_factory_.GetWeakPtr()); |
+ |
+ registrar_.Add( |
+ this, |
+ content::NOTIFICATION_RENDERER_PROCESS_CREATED, |
+ content::NotificationService::AllBrowserContextsAndSources()); |
+ } |
} |
RendererFreezer::~RendererFreezer() { |
- if (delegate_->CanFreezeRenderers()) |
- DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this); |
+ for (int rph_id : gcm_extension_processes_) { |
+ content::RenderProcessHost* host = |
+ content::RenderProcessHost::FromID(rph_id); |
+ if (host) |
+ host->RemoveObserver(this); |
+ } |
} |
void RendererFreezer::SuspendImminent() { |
- // If there was already a callback pending, this will cancel it and create a |
- // new one. |
- suspend_readiness_callback_.Reset( |
- base::Bind(&RendererFreezer::OnReadyToSuspend, |
- weak_factory_.GetWeakPtr(), |
- DBusThreadManager::Get() |
- ->GetPowerManagerClient() |
- ->GetSuspendReadinessCallback())); |
- |
- base::MessageLoop::current()->PostTask( |
- FROM_HERE, suspend_readiness_callback_.callback()); |
+ if (delegate_->FreezeRenderers()) |
+ frozen_ = true; |
} |
-void RendererFreezer::SuspendDone(const base::TimeDelta& sleep_duration) { |
- // If we get a SuspendDone before we've had a chance to run OnReadyForSuspend, |
- // we should cancel it because we no longer want to freeze the renderers. If |
- // we've already run it then cancelling the callback shouldn't really make a |
- // difference. |
- suspend_readiness_callback_.Cancel(); |
- |
+void RendererFreezer::SuspendDone() { |
if (!frozen_) |
return; |
@@ -58,13 +69,98 @@ void RendererFreezer::SuspendDone(const base::TimeDelta& sleep_duration) { |
frozen_ = false; |
} |
-void RendererFreezer::OnReadyToSuspend( |
- const base::Closure& power_manager_callback) { |
- if (delegate_->FreezeRenderers()) |
- frozen_ = true; |
+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, |
+ 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::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)) { |
+ if (!registry->GetExtensionById(extension_id, |
+ extensions::ExtensionRegistry::ENABLED) |
+ ->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; |
+ } |
- DCHECK(!power_manager_callback.is_null()); |
- power_manager_callback.Run(); |
+ // 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 |