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 |