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/task_manager/web_contents_resource_provider.h"
8 #include "base/bind_helpers.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/browser_process.h"
11 #include "chrome/browser/prerender/prerender_manager.h"
12 #include "chrome/browser/prerender/prerender_manager_factory.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/profiles/profile_manager.h"
15 #include "chrome/browser/task_manager/renderer_resource.h"
16 #include "chrome/browser/task_manager/task_manager.h"
17 #include "chrome/browser/task_manager/task_manager_util.h"
18 #include "chrome/browser/task_manager/web_contents_information.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "content/public/browser/render_frame_host.h"
21 #include "content/public/browser/render_process_host.h"
22 #include "content/public/browser/render_process_host_observer.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/render_widget_host_iterator.h"
25 #include "content/public/browser/site_instance.h"
26 #include "content/public/browser/web_contents.h"
27 #include "content/public/browser/web_contents_observer.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/gfx/image/image_skia.h"
31 using content::RenderFrameHost
;
32 using content::RenderProcessHost
;
33 using content::RenderViewHost
;
34 using content::SiteInstance
;
35 using content::WebContents
;
37 namespace task_manager
{
39 // A resource for a process hosting out-of-process iframes.
40 class SubframeResource
: public RendererResource
{
42 explicit SubframeResource(WebContents
* web_contents
,
43 SiteInstance
* site_instance
,
44 RenderFrameHost
* example_rfh
);
45 ~SubframeResource() override
{}
48 Type
GetType() const override
;
49 base::string16
GetTitle() const override
;
50 gfx::ImageSkia
GetIcon() const override
;
51 WebContents
* GetWebContents() const override
;
54 WebContents
* web_contents_
;
55 base::string16 title_
;
56 DISALLOW_COPY_AND_ASSIGN(SubframeResource
);
59 SubframeResource::SubframeResource(WebContents
* web_contents
,
60 SiteInstance
* subframe_site_instance
,
61 RenderFrameHost
* example_rfh
)
62 : RendererResource(subframe_site_instance
->GetProcess()->GetHandle(),
63 example_rfh
->GetRenderViewHost()),
64 web_contents_(web_contents
) {
65 int message_id
= subframe_site_instance
->GetBrowserContext()->IsOffTheRecord()
66 ? IDS_TASK_MANAGER_SUBFRAME_INCOGNITO_PREFIX
67 : IDS_TASK_MANAGER_SUBFRAME_PREFIX
;
68 title_
= l10n_util::GetStringFUTF16(
70 base::UTF8ToUTF16(subframe_site_instance
->GetSiteURL().spec()));
73 Resource::Type
SubframeResource::GetType() const {
77 base::string16
SubframeResource::GetTitle() const {
81 gfx::ImageSkia
SubframeResource::GetIcon() const {
82 return gfx::ImageSkia();
85 WebContents
* SubframeResource::GetWebContents() const {
89 // Tracks changes to one WebContents, and manages task manager resources for
90 // that WebContents, on behalf of a WebContentsResourceProvider.
91 class TaskManagerWebContentsEntry
: public content::WebContentsObserver
,
92 public content::RenderProcessHostObserver
{
94 typedef std::multimap
<SiteInstance
*, RendererResource
*> ResourceMap
;
95 typedef std::pair
<ResourceMap::iterator
, ResourceMap::iterator
> ResourceRange
;
97 TaskManagerWebContentsEntry(WebContents
* web_contents
,
98 WebContentsResourceProvider
* provider
)
99 : content::WebContentsObserver(web_contents
),
101 main_frame_site_instance_(NULL
) {}
103 ~TaskManagerWebContentsEntry() override
{ ClearAllResources(false); }
105 // content::WebContentsObserver implementation.
106 void RenderFrameDeleted(RenderFrameHost
* render_frame_host
) override
{
107 ClearResourceForFrame(render_frame_host
);
110 void RenderFrameHostChanged(RenderFrameHost
* old_host
,
111 RenderFrameHost
* new_host
) override
{
113 ClearResourceForFrame(old_host
);
114 CreateResourceForFrame(new_host
);
117 void RenderViewReady() override
{
118 ClearAllResources(true);
119 CreateAllResources();
122 void WebContentsDestroyed() override
{
123 ClearAllResources(true);
124 provider_
->DeleteEntry(web_contents(), this); // Deletes |this|.
127 // content::RenderProcessHostObserver implementation.
128 void RenderProcessExited(RenderProcessHost
* process_host
,
129 base::TerminationStatus status
,
130 int exit_code
) override
{
131 ClearResourcesForProcess(process_host
);
134 void RenderProcessHostDestroyed(RenderProcessHost
* process_host
) override
{
135 tracked_process_hosts_
.erase(process_host
);
138 // Called by WebContentsResourceProvider.
139 RendererResource
* GetResourceForSiteInstance(SiteInstance
* site_instance
) {
140 ResourceMap::iterator i
= resources_by_site_instance_
.find(site_instance
);
141 if (i
== resources_by_site_instance_
.end())
146 void CreateAllResources() {
147 // We'll show one row per SiteInstance in the task manager.
148 DCHECK(web_contents()->GetMainFrame() != NULL
);
149 web_contents()->ForEachFrame(
150 base::Bind(&TaskManagerWebContentsEntry::CreateResourceForFrame
,
151 base::Unretained(this)));
154 void ClearAllResources(bool update_task_manager
) {
155 RendererResource
* last_resource
= NULL
;
156 for (auto& x
: resources_by_site_instance_
) {
157 RendererResource
* resource
= x
.second
;
158 if (resource
== last_resource
)
159 continue; // Skip multiset duplicates.
160 if (update_task_manager
)
161 task_manager()->RemoveResource(resource
);
163 last_resource
= resource
;
165 resources_by_site_instance_
.clear();
167 RenderProcessHost
* last_process_host
= NULL
;
168 for (RenderProcessHost
* process_host
: tracked_process_hosts_
) {
169 if (last_process_host
== process_host
)
170 continue; // Skip multiset duplicates.
171 process_host
->RemoveObserver(this);
172 last_process_host
= process_host
;
174 tracked_process_hosts_
.clear();
175 tracked_frame_hosts_
.clear();
178 void ClearResourceForFrame(RenderFrameHost
* render_frame_host
) {
179 SiteInstance
* site_instance
= render_frame_host
->GetSiteInstance();
180 std::set
<RenderFrameHost
*>::iterator frame_set_iterator
=
181 tracked_frame_hosts_
.find(render_frame_host
);
182 if (frame_set_iterator
== tracked_frame_hosts_
.end()) {
183 // We weren't tracking this RenderFrameHost.
186 tracked_frame_hosts_
.erase(frame_set_iterator
);
187 ResourceRange resource_range
=
188 resources_by_site_instance_
.equal_range(site_instance
);
189 if (resource_range
.first
== resource_range
.second
) {
193 RendererResource
* resource
= resource_range
.first
->second
;
194 resources_by_site_instance_
.erase(resource_range
.first
++);
195 if (resource_range
.first
== resource_range
.second
) {
196 // The removed entry was the sole remaining reference to that resource, so
197 // actually destroy it.
198 task_manager()->RemoveResource(resource
);
200 DecrementProcessWatch(site_instance
->GetProcess());
201 if (site_instance
== main_frame_site_instance_
) {
202 main_frame_site_instance_
= NULL
;
207 void ClearResourcesForProcess(RenderProcessHost
* crashed_process
) {
208 std::vector
<RenderFrameHost
*> frame_hosts_to_delete
;
209 for (RenderFrameHost
* frame_host
: tracked_frame_hosts_
) {
210 if (frame_host
->GetProcess() == crashed_process
) {
211 frame_hosts_to_delete
.push_back(frame_host
);
214 for (RenderFrameHost
* frame_host
: frame_hosts_to_delete
) {
215 ClearResourceForFrame(frame_host
);
219 void CreateResourceForFrame(RenderFrameHost
* render_frame_host
) {
220 SiteInstance
* site_instance
= render_frame_host
->GetSiteInstance();
222 DCHECK_EQ(0u, tracked_frame_hosts_
.count(render_frame_host
));
224 if (!site_instance
->GetProcess()->HasConnection())
227 tracked_frame_hosts_
.insert(render_frame_host
);
229 ResourceRange existing_resource_range
=
230 resources_by_site_instance_
.equal_range(site_instance
);
231 bool existing_resource
=
232 (existing_resource_range
.first
!= existing_resource_range
.second
);
233 bool is_main_frame
= (render_frame_host
== web_contents()->GetMainFrame());
234 bool site_instance_is_main
= (site_instance
== main_frame_site_instance_
);
235 scoped_ptr
<RendererResource
> new_resource
;
236 if (!existing_resource
|| (is_main_frame
&& !site_instance_is_main
)) {
238 new_resource
= info()->MakeResource(web_contents());
239 main_frame_site_instance_
= site_instance
;
241 new_resource
.reset(new SubframeResource(
242 web_contents(), site_instance
, render_frame_host
));
246 if (existing_resource
) {
247 RendererResource
* old_resource
= existing_resource_range
.first
->second
;
249 resources_by_site_instance_
.insert(
250 std::make_pair(site_instance
, old_resource
));
252 for (ResourceMap::iterator it
= existing_resource_range
.first
;
253 it
!= existing_resource_range
.second
;
255 it
->second
= new_resource
.get();
257 task_manager()->RemoveResource(old_resource
);
259 DecrementProcessWatch(site_instance
->GetProcess());
264 task_manager()->AddResource(new_resource
.get());
265 resources_by_site_instance_
.insert(
266 std::make_pair(site_instance
, new_resource
.release()));
267 IncrementProcessWatch(site_instance
->GetProcess());
271 // Add ourself as an observer of |process|, if we aren't already. Must be
272 // balanced by a call to DecrementProcessWatch().
273 // TODO(nick): Move away from RenderProcessHostObserver once
274 // WebContentsObserver supports per-frame process death notices.
275 void IncrementProcessWatch(RenderProcessHost
* process
) {
276 auto range
= tracked_process_hosts_
.equal_range(process
);
277 if (range
.first
== range
.second
) {
278 process
->AddObserver(this);
280 tracked_process_hosts_
.insert(range
.first
, process
);
283 void DecrementProcessWatch(RenderProcessHost
* process
) {
284 auto range
= tracked_process_hosts_
.equal_range(process
);
285 if (range
.first
== range
.second
) {
290 auto element
= range
.first
++;
291 if (range
.first
== range
.second
) {
292 process
->RemoveObserver(this);
294 tracked_process_hosts_
.erase(element
, range
.first
);
298 TaskManager
* task_manager() { return provider_
->task_manager(); }
300 WebContentsInformation
* info() { return provider_
->info(); }
302 WebContentsResourceProvider
* const provider_
;
304 // Every RenderFrameHost that we're watching.
305 std::set
<RenderFrameHost
*> tracked_frame_hosts_
;
307 // The set of processes we're currently observing. There is one entry here per
308 // RendererResource we create. A multimap because we may request observation
309 // more than once, say if two resources happen to share a process.
310 std::multiset
<RenderProcessHost
*> tracked_process_hosts_
;
312 // Maps SiteInstances to the RendererResources. A multimap, this contains one
313 // entry per tracked RenderFrameHost, so we can tell when we're done reusing.
314 ResourceMap resources_by_site_instance_
;
316 // The site instance of the main frame.
317 SiteInstance
* main_frame_site_instance_
;
320 ////////////////////////////////////////////////////////////////////////////////
321 // WebContentsResourceProvider class
322 ////////////////////////////////////////////////////////////////////////////////
324 WebContentsResourceProvider::WebContentsResourceProvider(
325 TaskManager
* task_manager
,
326 scoped_ptr
<WebContentsInformation
> info
)
327 : task_manager_(task_manager
), info_(info
.Pass()) {
330 WebContentsResourceProvider::~WebContentsResourceProvider() {}
332 RendererResource
* WebContentsResourceProvider::GetResource(int origin_pid
,
335 RenderFrameHost
* rfh
= RenderFrameHost::FromID(child_id
, route_id
);
336 WebContents
* web_contents
= WebContents::FromRenderFrameHost(rfh
);
338 // If an origin PID was specified then the request originated in a plugin
339 // working on the WebContents's behalf, so ignore it.
343 EntryMap::const_iterator web_contents_it
= entries_
.find(web_contents
);
345 if (web_contents_it
== entries_
.end()) {
346 // Can happen if the tab was closed while a network request was being
351 return web_contents_it
->second
->GetResourceForSiteInstance(
352 rfh
->GetSiteInstance());
355 void WebContentsResourceProvider::StartUpdating() {
356 WebContentsInformation::NewWebContentsCallback new_web_contents_callback
=
357 base::Bind(&WebContentsResourceProvider::OnWebContentsCreated
, this);
358 info_
->GetAll(new_web_contents_callback
);
359 info_
->StartObservingCreation(new_web_contents_callback
);
362 void WebContentsResourceProvider::StopUpdating() {
363 info_
->StopObservingCreation();
365 // Delete all entries; this dissassociates them from the WebContents too.
366 STLDeleteValues(&entries_
);
369 void WebContentsResourceProvider::OnWebContentsCreated(
370 WebContents
* web_contents
) {
371 // Don't add dead tabs or tabs that haven't yet connected.
372 if (!web_contents
->GetRenderProcessHost()->GetHandle() ||
373 !web_contents
->WillNotifyDisconnection()) {
377 DCHECK(info_
->CheckOwnership(web_contents
));
378 if (entries_
.count(web_contents
)) {
379 // The case may happen that we have added a WebContents as part of the
380 // iteration performed during StartUpdating() call but the notification that
381 // it has connected was not fired yet. So when the notification happens, we
382 // are already observing this WebContents and just ignore it.
385 scoped_ptr
<TaskManagerWebContentsEntry
> entry(
386 new TaskManagerWebContentsEntry(web_contents
, this));
387 entry
->CreateAllResources();
388 entries_
[web_contents
] = entry
.release();
391 void WebContentsResourceProvider::DeleteEntry(
392 WebContents
* web_contents
,
393 TaskManagerWebContentsEntry
* entry
) {
394 if (!entries_
.erase(web_contents
)) {
398 delete entry
; // Typically, this is our caller. Deletion is okay.
401 } // namespace task_manager