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
3 // found in the LICENSE file.
5 #include "chrome/browser/chromeos/power/renderer_freezer.h"
10 #include "base/logging.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/process/process_handle.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/chromeos/login/lock/screen_locker.h"
15 #include "chrome/browser/chromeos/login/lock/screen_locker_delegate.h"
16 #include "chromeos/dbus/dbus_thread_manager.h"
17 #include "content/public/browser/notification_details.h"
18 #include "content/public/browser/notification_service.h"
19 #include "content/public/browser/notification_source.h"
20 #include "content/public/browser/notification_types.h"
21 #include "content/public/browser/render_process_host.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/browser/web_ui.h"
24 #include "extensions/browser/extension_registry.h"
25 #include "extensions/browser/notification_types.h"
26 #include "extensions/browser/process_map.h"
27 #include "extensions/common/extension.h"
28 #include "extensions/common/permissions/api_permission.h"
29 #include "extensions/common/permissions/permissions_data.h"
33 RendererFreezer::RendererFreezer(scoped_ptr
<RendererFreezer::Delegate
> delegate
)
34 : delegate_(delegate
.Pass()),
36 delegate_
->CheckCanFreezeRenderers(
37 base::Bind(&RendererFreezer::OnCheckCanFreezeRenderersComplete
,
38 weak_factory_
.GetWeakPtr()));
41 RendererFreezer::~RendererFreezer() {
42 for (int rph_id
: gcm_extension_processes_
) {
43 content::RenderProcessHost
* host
=
44 content::RenderProcessHost::FromID(rph_id
);
46 host
->RemoveObserver(this);
50 void RendererFreezer::SuspendImminent() {
51 // All the delegate's operations are asynchronous so they may not complete
52 // before the system suspends. This is ok since the renderers only need to be
53 // frozen in dark resume. As long as they do get frozen soon after we enter
54 // dark resume, there shouldn't be a problem.
55 delegate_
->FreezeRenderers();
58 void RendererFreezer::SuspendDone() {
59 delegate_
->ThawRenderers(base::Bind(&RendererFreezer::OnThawRenderersComplete
,
60 weak_factory_
.GetWeakPtr()));
63 void RendererFreezer::Observe(int type
,
64 const content::NotificationSource
& source
,
65 const content::NotificationDetails
& details
) {
67 case chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED
: {
68 OnScreenLockStateChanged(
69 content::Source
<chromeos::ScreenLocker
>(source
).ptr(),
70 *(content::Details
<bool>(details
).ptr()));
73 case content::NOTIFICATION_RENDERER_PROCESS_CREATED
: {
74 content::RenderProcessHost
* process
=
75 content::Source
<content::RenderProcessHost
>(source
).ptr();
76 OnRenderProcessCreated(process
);
86 void RendererFreezer::RenderProcessExited(content::RenderProcessHost
* host
,
87 base::TerminationStatus status
,
89 auto it
= gcm_extension_processes_
.find(host
->GetID());
90 if (it
== gcm_extension_processes_
.end()) {
91 LOG(ERROR
) << "Received unrequested RenderProcessExited message";
94 gcm_extension_processes_
.erase(it
);
96 // When this function is called, the renderer process has died but the
97 // RenderProcessHost will not be destroyed. If a new renderer process is
98 // created for this RPH, registering as an observer again will trigger a
99 // warning about duplicate observers. To prevent this we just stop observing
100 // this RPH until another renderer process is created for it.
101 host
->RemoveObserver(this);
104 void RendererFreezer::RenderProcessHostDestroyed(
105 content::RenderProcessHost
* host
) {
106 auto it
= gcm_extension_processes_
.find(host
->GetID());
107 if (it
== gcm_extension_processes_
.end()) {
108 LOG(ERROR
) << "Received unrequested RenderProcessHostDestroyed message";
112 gcm_extension_processes_
.erase(it
);
115 void RendererFreezer::OnCheckCanFreezeRenderersComplete(bool can_freeze
) {
119 DBusThreadManager::Get()
120 ->GetPowerManagerClient()
121 ->SetRenderProcessManagerDelegate(weak_factory_
.GetWeakPtr());
123 registrar_
.Add(this, chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED
,
124 content::NotificationService::AllBrowserContextsAndSources());
127 content::NOTIFICATION_RENDERER_PROCESS_CREATED
,
128 content::NotificationService::AllBrowserContextsAndSources());
131 void RendererFreezer::OnThawRenderersComplete(bool success
) {
135 // We failed to write the thaw command and the renderers are still frozen. We
136 // are in big trouble because none of the tabs will be responsive so let's
137 // crash the browser instead.
138 LOG(FATAL
) << "Unable to thaw renderers.";
141 void RendererFreezer::OnScreenLockStateChanged(chromeos::ScreenLocker
* locker
,
143 // The ScreenLocker class sends NOTIFICATION_SCREEN_LOCK_STATE_CHANGED when
144 // the lock screen becomes ready, resulting in this code running synchronously
145 // to mark the screen locker renderer to remain unfrozen during a suspend
146 // request. Since this happens before the PowerManagerClient calls
147 // RendererFreezer::SuspendImminent(), it is guaranteed that the screen locker
148 // renderer will not be frozen at any point.
150 delegate_
->SetShouldFreezeRenderer(locker
->delegate()
151 ->GetAssociatedWebUI()
153 ->GetRenderProcessHost()
159 void RendererFreezer::OnRenderProcessCreated(content::RenderProcessHost
* rph
) {
160 const int rph_id
= rph
->GetID();
162 if (gcm_extension_processes_
.find(rph_id
) != gcm_extension_processes_
.end()) {
163 LOG(ERROR
) << "Received duplicate notifications about the creation of a "
164 << "RenderProcessHost with id " << rph_id
;
168 // According to extensions::ProcessMap, extensions and renderers have a
169 // many-to-many relationship. Specifically, a hosted app can appear in many
170 // renderers while any other kind of extension can be running in "split mode"
171 // if there is an incognito window open and so could appear in two renderers.
173 // We don't care about hosted apps because they cannot use GCM so we only need
174 // to worry about extensions in "split mode". Luckily for us this function is
175 // called any time a new renderer process is created so we don't really need
176 // to care whether we are currently in an incognito context. We just need to
177 // iterate over all the extensions in the newly created process and take the
178 // appropriate action based on whether we find an extension using GCM.
179 content::BrowserContext
* context
= rph
->GetBrowserContext();
180 extensions::ExtensionRegistry
* registry
=
181 extensions::ExtensionRegistry::Get(context
);
182 for (const std::string
& extension_id
:
183 extensions::ProcessMap::Get(context
)->GetExtensionsInProcess(rph_id
)) {
184 const extensions::Extension
* extension
= registry
->GetExtensionById(
185 extension_id
, extensions::ExtensionRegistry::ENABLED
);
187 !extension
->permissions_data()->HasAPIPermission(
188 extensions::APIPermission::kGcm
)) {
192 // This renderer has an extension that is using GCM. Make sure it is not
193 // frozen during suspend.
194 delegate_
->SetShouldFreezeRenderer(rph
->GetHandle(), false);
195 gcm_extension_processes_
.insert(rph_id
);
197 // Watch to see if the renderer process or the RenderProcessHost is
199 rph
->AddObserver(this);
203 // We didn't find an extension in this RenderProcessHost that is using GCM so
204 // we can go ahead and freeze it on suspend.
205 delegate_
->SetShouldFreezeRenderer(rph
->GetHandle(), true);
208 } // namespace chromeos