| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/chromeos/power/renderer_freezer.h" | 5 #include "chrome/browser/chromeos/power/renderer_freezer.h" |
| 6 | 6 |
| 7 #include <string> | |
| 8 | |
| 9 #include "base/bind.h" | 7 #include "base/bind.h" |
| 10 #include "base/logging.h" | 8 #include "base/logging.h" |
| 11 #include "base/message_loop/message_loop.h" | 9 #include "base/message_loop/message_loop.h" |
| 12 #include "base/process/process_handle.h" | |
| 13 #include "chromeos/dbus/dbus_thread_manager.h" | 10 #include "chromeos/dbus/dbus_thread_manager.h" |
| 14 #include "content/public/browser/notification_details.h" | |
| 15 #include "content/public/browser/notification_service.h" | |
| 16 #include "content/public/browser/notification_source.h" | |
| 17 #include "content/public/browser/notification_types.h" | |
| 18 #include "content/public/browser/render_process_host.h" | |
| 19 #include "extensions/browser/extension_registry.h" | |
| 20 #include "extensions/browser/notification_types.h" | |
| 21 #include "extensions/browser/process_map.h" | |
| 22 #include "extensions/common/extension.h" | |
| 23 #include "extensions/common/permissions/api_permission.h" | |
| 24 #include "extensions/common/permissions/permissions_data.h" | |
| 25 | 11 |
| 26 namespace chromeos { | 12 namespace chromeos { |
| 27 | 13 |
| 28 RendererFreezer::RendererFreezer(scoped_ptr<RendererFreezer::Delegate> delegate) | 14 RendererFreezer::RendererFreezer(scoped_ptr<RendererFreezer::Delegate> delegate) |
| 29 : frozen_(false), | 15 : frozen_(false), |
| 30 delegate_(delegate.Pass()), | 16 delegate_(delegate.Pass()), |
| 31 weak_factory_(this) { | 17 weak_factory_(this) { |
| 32 if (delegate_->CanFreezeRenderers()) { | 18 if (delegate_->CanFreezeRenderers()) |
| 33 DBusThreadManager::Get() | 19 DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this); |
| 34 ->GetPowerManagerClient() | |
| 35 ->SetRenderProcessManagerDelegate(weak_factory_.GetWeakPtr()); | |
| 36 | |
| 37 registrar_.Add( | |
| 38 this, | |
| 39 content::NOTIFICATION_RENDERER_PROCESS_CREATED, | |
| 40 content::NotificationService::AllBrowserContextsAndSources()); | |
| 41 } | |
| 42 } | 20 } |
| 43 | 21 |
| 44 RendererFreezer::~RendererFreezer() { | 22 RendererFreezer::~RendererFreezer() { |
| 45 for (int rph_id : gcm_extension_processes_) { | 23 if (delegate_->CanFreezeRenderers()) |
| 46 content::RenderProcessHost* host = | 24 DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this); |
| 47 content::RenderProcessHost::FromID(rph_id); | |
| 48 if (host) | |
| 49 host->RemoveObserver(this); | |
| 50 } | |
| 51 } | 25 } |
| 52 | 26 |
| 53 void RendererFreezer::SuspendImminent() { | 27 void RendererFreezer::SuspendImminent() { |
| 54 if (delegate_->FreezeRenderers()) | 28 // If there was already a callback pending, this will cancel it and create a |
| 55 frozen_ = true; | 29 // new one. |
| 30 suspend_readiness_callback_.Reset( |
| 31 base::Bind(&RendererFreezer::OnReadyToSuspend, |
| 32 weak_factory_.GetWeakPtr(), |
| 33 DBusThreadManager::Get() |
| 34 ->GetPowerManagerClient() |
| 35 ->GetSuspendReadinessCallback())); |
| 36 |
| 37 base::MessageLoop::current()->PostTask( |
| 38 FROM_HERE, suspend_readiness_callback_.callback()); |
| 56 } | 39 } |
| 57 | 40 |
| 58 void RendererFreezer::SuspendDone() { | 41 void RendererFreezer::SuspendDone(const base::TimeDelta& sleep_duration) { |
| 42 // If we get a SuspendDone before we've had a chance to run OnReadyForSuspend, |
| 43 // we should cancel it because we no longer want to freeze the renderers. If |
| 44 // we've already run it then cancelling the callback shouldn't really make a |
| 45 // difference. |
| 46 suspend_readiness_callback_.Cancel(); |
| 47 |
| 59 if (!frozen_) | 48 if (!frozen_) |
| 60 return; | 49 return; |
| 61 | 50 |
| 62 if (!delegate_->ThawRenderers()) { | 51 if (!delegate_->ThawRenderers()) { |
| 63 // We failed to write the thaw command and the renderers are still frozen. | 52 // We failed to write the thaw command and the renderers are still frozen. |
| 64 // We are in big trouble because none of the tabs will be responsive so | 53 // We are in big trouble because none of the tabs will be responsive so |
| 65 // let's crash the browser instead. | 54 // let's crash the browser instead. |
| 66 LOG(FATAL) << "Unable to thaw renderers."; | 55 LOG(FATAL) << "Unable to thaw renderers."; |
| 67 } | 56 } |
| 68 | 57 |
| 69 frozen_ = false; | 58 frozen_ = false; |
| 70 } | 59 } |
| 71 | 60 |
| 72 void RendererFreezer::Observe(int type, | 61 void RendererFreezer::OnReadyToSuspend( |
| 73 const content::NotificationSource& source, | 62 const base::Closure& power_manager_callback) { |
| 74 const content::NotificationDetails& details) { | 63 if (delegate_->FreezeRenderers()) |
| 75 switch (type) { | 64 frozen_ = true; |
| 76 case content::NOTIFICATION_RENDERER_PROCESS_CREATED: { | |
| 77 content::RenderProcessHost* process = | |
| 78 content::Source<content::RenderProcessHost>(source).ptr(); | |
| 79 OnRenderProcessCreated(process); | |
| 80 break; | |
| 81 } | |
| 82 default: { | |
| 83 NOTREACHED(); | |
| 84 break; | |
| 85 } | |
| 86 } | |
| 87 } | |
| 88 | 65 |
| 89 void RendererFreezer::RenderProcessExited(content::RenderProcessHost* host, | 66 DCHECK(!power_manager_callback.is_null()); |
| 90 base::TerminationStatus status, | 67 power_manager_callback.Run(); |
| 91 int exit_code) { | |
| 92 auto it = gcm_extension_processes_.find(host->GetID()); | |
| 93 if (it == gcm_extension_processes_.end()) { | |
| 94 LOG(ERROR) << "Received unrequested RenderProcessExited message"; | |
| 95 return; | |
| 96 } | |
| 97 gcm_extension_processes_.erase(it); | |
| 98 | |
| 99 // When this function is called, the renderer process has died but the | |
| 100 // RenderProcessHost will not be destroyed. If a new renderer process is | |
| 101 // created for this RPH, registering as an observer again will trigger a | |
| 102 // warning about duplicate observers. To prevent this we just stop observing | |
| 103 // this RPH until another renderer process is created for it. | |
| 104 host->RemoveObserver(this); | |
| 105 } | |
| 106 | |
| 107 void RendererFreezer::RenderProcessHostDestroyed( | |
| 108 content::RenderProcessHost* host) { | |
| 109 auto it = gcm_extension_processes_.find(host->GetID()); | |
| 110 if (it == gcm_extension_processes_.end()) { | |
| 111 LOG(ERROR) << "Received unrequested RenderProcessHostDestroyed message"; | |
| 112 return; | |
| 113 } | |
| 114 | |
| 115 gcm_extension_processes_.erase(it); | |
| 116 } | |
| 117 | |
| 118 void RendererFreezer::OnRenderProcessCreated(content::RenderProcessHost* rph) { | |
| 119 const int rph_id = rph->GetID(); | |
| 120 | |
| 121 if (gcm_extension_processes_.find(rph_id) != gcm_extension_processes_.end()) { | |
| 122 LOG(ERROR) << "Received duplicate notifications about the creation of a " | |
| 123 << "RenderProcessHost with id " << rph_id; | |
| 124 return; | |
| 125 } | |
| 126 | |
| 127 // According to extensions::ProcessMap, extensions and renderers have a | |
| 128 // many-to-many relationship. Specifically, a hosted app can appear in many | |
| 129 // renderers while any other kind of extension can be running in "split mode" | |
| 130 // if there is an incognito window open and so could appear in two renderers. | |
| 131 // | |
| 132 // We don't care about hosted apps because they cannot use GCM so we only need | |
| 133 // to worry about extensions in "split mode". Luckily for us this function is | |
| 134 // called any time a new renderer process is created so we don't really need | |
| 135 // to care whether we are currently in an incognito context. We just need to | |
| 136 // iterate over all the extensions in the newly created process and take the | |
| 137 // appropriate action based on whether we find an extension using GCM. | |
| 138 content::BrowserContext* context = rph->GetBrowserContext(); | |
| 139 extensions::ExtensionRegistry* registry = | |
| 140 extensions::ExtensionRegistry::Get(context); | |
| 141 for (const std::string& extension_id : | |
| 142 extensions::ProcessMap::Get(context)->GetExtensionsInProcess(rph_id)) { | |
| 143 if (!registry->GetExtensionById(extension_id, | |
| 144 extensions::ExtensionRegistry::ENABLED) | |
| 145 ->permissions_data() | |
| 146 ->HasAPIPermission(extensions::APIPermission::kGcm)) { | |
| 147 continue; | |
| 148 } | |
| 149 | |
| 150 // This renderer has an extension that is using GCM. Make sure it is not | |
| 151 // frozen during suspend. | |
| 152 delegate_->SetShouldFreezeRenderer(rph->GetHandle(), false); | |
| 153 gcm_extension_processes_.insert(rph_id); | |
| 154 | |
| 155 // Watch to see if the renderer process or the RenderProcessHost is | |
| 156 // destroyed. | |
| 157 rph->AddObserver(this); | |
| 158 return; | |
| 159 } | |
| 160 | |
| 161 // We didn't find an extension in this RenderProcessHost that is using GCM so | |
| 162 // we can go ahead and freeze it on suspend. | |
| 163 delegate_->SetShouldFreezeRenderer(rph->GetHandle(), true); | |
| 164 } | 68 } |
| 165 | 69 |
| 166 } // namespace chromeos | 70 } // namespace chromeos |
| OLD | NEW |