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 |
7 #include "base/bind.h" | 9 #include "base/bind.h" |
8 #include "base/logging.h" | 10 #include "base/logging.h" |
9 #include "base/message_loop/message_loop.h" | 11 #include "base/message_loop/message_loop.h" |
| 12 #include "base/process/process_handle.h" |
10 #include "chromeos/dbus/dbus_thread_manager.h" | 13 #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" |
11 | 25 |
12 namespace chromeos { | 26 namespace chromeos { |
13 | 27 |
14 RendererFreezer::RendererFreezer(scoped_ptr<RendererFreezer::Delegate> delegate) | 28 RendererFreezer::RendererFreezer(scoped_ptr<RendererFreezer::Delegate> delegate) |
15 : frozen_(false), | 29 : frozen_(false), |
16 delegate_(delegate.Pass()), | 30 delegate_(delegate.Pass()), |
17 weak_factory_(this) { | 31 weak_factory_(this) { |
18 if (delegate_->CanFreezeRenderers()) | 32 if (delegate_->CanFreezeRenderers()) { |
19 DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this); | 33 DBusThreadManager::Get() |
| 34 ->GetPowerManagerClient() |
| 35 ->SetRenderProcessManagerDelegate(weak_factory_.GetWeakPtr()); |
| 36 |
| 37 registrar_.Add( |
| 38 this, |
| 39 content::NOTIFICATION_RENDERER_PROCESS_CREATED, |
| 40 content::NotificationService::AllBrowserContextsAndSources()); |
| 41 } |
20 } | 42 } |
21 | 43 |
22 RendererFreezer::~RendererFreezer() { | 44 RendererFreezer::~RendererFreezer() { |
23 if (delegate_->CanFreezeRenderers()) | 45 for (int rph_id : gcm_extension_processes_) { |
24 DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this); | 46 content::RenderProcessHost* host = |
| 47 content::RenderProcessHost::FromID(rph_id); |
| 48 if (host) |
| 49 host->RemoveObserver(this); |
| 50 } |
25 } | 51 } |
26 | 52 |
27 void RendererFreezer::SuspendImminent() { | 53 void RendererFreezer::SuspendImminent() { |
28 // If there was already a callback pending, this will cancel it and create a | 54 if (delegate_->FreezeRenderers()) |
29 // new one. | 55 frozen_ = true; |
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()); | |
39 } | 56 } |
40 | 57 |
41 void RendererFreezer::SuspendDone(const base::TimeDelta& sleep_duration) { | 58 void RendererFreezer::SuspendDone() { |
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 | |
48 if (!frozen_) | 59 if (!frozen_) |
49 return; | 60 return; |
50 | 61 |
51 if (!delegate_->ThawRenderers()) { | 62 if (!delegate_->ThawRenderers()) { |
52 // We failed to write the thaw command and the renderers are still frozen. | 63 // We failed to write the thaw command and the renderers are still frozen. |
53 // We are in big trouble because none of the tabs will be responsive so | 64 // We are in big trouble because none of the tabs will be responsive so |
54 // let's crash the browser instead. | 65 // let's crash the browser instead. |
55 LOG(FATAL) << "Unable to thaw renderers."; | 66 LOG(FATAL) << "Unable to thaw renderers."; |
56 } | 67 } |
57 | 68 |
58 frozen_ = false; | 69 frozen_ = false; |
59 } | 70 } |
60 | 71 |
61 void RendererFreezer::OnReadyToSuspend( | 72 void RendererFreezer::Observe(int type, |
62 const base::Closure& power_manager_callback) { | 73 const content::NotificationSource& source, |
63 if (delegate_->FreezeRenderers()) | 74 const content::NotificationDetails& details) { |
64 frozen_ = true; | 75 switch (type) { |
| 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 } |
65 | 88 |
66 DCHECK(!power_manager_callback.is_null()); | 89 void RendererFreezer::RenderProcessExited(content::RenderProcessHost* host, |
67 power_manager_callback.Run(); | 90 base::TerminationStatus status, |
| 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); |
68 } | 164 } |
69 | 165 |
70 } // namespace chromeos | 166 } // namespace chromeos |
OLD | NEW |