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.
4
5#include "chrome/browser/task_manager/web_contents_resource_provider.h"
6
7#include "base/bind.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_view_host.h"
23#include "content/public/browser/render_widget_host_iterator.h"
24#include "content/public/browser/site_instance.h"
25#include "content/public/browser/web_contents.h"
26#include "content/public/browser/web_contents_observer.h"
27#include "ui/base/l10n/l10n_util.h"
28#include "ui/gfx/image/image_skia.h"
29
30using content::RenderViewHost;
31using content::RenderFrameHost;
32using content::SiteInstance;
33using content::WebContents;
34
35namespace task_manager {
36
37// A resource for a process hosting out-of-process iframes.
38class SubframeResource : public RendererResource {
39 public:
40  explicit SubframeResource(WebContents* web_contents,
41                            SiteInstance* site_instance,
42                            RenderFrameHost* example_rfh);
43  virtual ~SubframeResource() {}
44
45  // Resource methods:
46  virtual Type GetType() const OVERRIDE;
47  virtual base::string16 GetTitle() const OVERRIDE;
48  virtual gfx::ImageSkia GetIcon() const OVERRIDE;
49  virtual WebContents* GetWebContents() const OVERRIDE;
50
51 private:
52  WebContents* web_contents_;
53  base::string16 title_;
54  DISALLOW_COPY_AND_ASSIGN(SubframeResource);
55};
56
57SubframeResource::SubframeResource(WebContents* web_contents,
58                                   SiteInstance* subframe_site_instance,
59                                   RenderFrameHost* example_rfh)
60    : RendererResource(subframe_site_instance->GetProcess()->GetHandle(),
61                       example_rfh->GetRenderViewHost()),
62      web_contents_(web_contents) {
63  int message_id = subframe_site_instance->GetBrowserContext()->IsOffTheRecord()
64                       ? IDS_TASK_MANAGER_SUBFRAME_INCOGNITO_PREFIX
65                       : IDS_TASK_MANAGER_SUBFRAME_PREFIX;
66  title_ = l10n_util::GetStringFUTF16(
67      message_id,
68      base::UTF8ToUTF16(subframe_site_instance->GetSiteURL().spec()));
69}
70
71Resource::Type SubframeResource::GetType() const {
72  return RENDERER;
73}
74
75base::string16 SubframeResource::GetTitle() const {
76  return title_;
77}
78
79gfx::ImageSkia SubframeResource::GetIcon() const {
80  return gfx::ImageSkia();
81}
82
83WebContents* SubframeResource::GetWebContents() const {
84  return web_contents_;
85}
86
87// Tracks changes to one WebContents, and manages task manager resources for
88// that WebContents, on behalf of a WebContentsResourceProvider.
89class TaskManagerWebContentsEntry : public content::WebContentsObserver {
90 public:
91  typedef std::multimap<SiteInstance*, RendererResource*> ResourceMap;
92  typedef std::pair<ResourceMap::iterator, ResourceMap::iterator> ResourceRange;
93
94  TaskManagerWebContentsEntry(WebContents* web_contents,
95                              WebContentsResourceProvider* provider)
96      : content::WebContentsObserver(web_contents),
97        provider_(provider),
98        main_frame_site_instance_(NULL) {}
99
100  virtual ~TaskManagerWebContentsEntry() {
101    for (ResourceMap::iterator j = resources_by_site_instance_.begin();
102         j != resources_by_site_instance_.end();) {
103      RendererResource* resource = j->second;
104
105      // Advance to next non-duplicate entry.
106      do {
107        ++j;
108      } while (j != resources_by_site_instance_.end() && resource == j->second);
109
110      delete resource;
111    }
112  }
113
114  // content::WebContentsObserver implementation.
115  virtual void RenderFrameDeleted(RenderFrameHost* render_frame_host) OVERRIDE {
116    ClearResourceForFrame(render_frame_host);
117  }
118
119  virtual void RenderFrameHostChanged(RenderFrameHost* old_host,
120                                      RenderFrameHost* new_host) OVERRIDE {
121    if (old_host)
122      ClearResourceForFrame(old_host);
123    CreateResourceForFrame(new_host);
124  }
125
126  virtual void RenderViewReady() OVERRIDE {
127    ClearAllResources();
128    CreateAllResources();
129  }
130
131  virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE {
132    ClearAllResources();
133  }
134
135  virtual void WebContentsDestroyed() OVERRIDE {
136    ClearAllResources();
137    provider_->DeleteEntry(web_contents(), this);  // Deletes |this|.
138  }
139
140  // Called by WebContentsResourceProvider.
141  RendererResource* GetResourceForSiteInstance(SiteInstance* site_instance) {
142    ResourceMap::iterator i = resources_by_site_instance_.find(site_instance);
143    if (i == resources_by_site_instance_.end())
144      return NULL;
145    return i->second;
146  }
147
148  void CreateAllResources() {
149    // We'll show one row per SiteInstance in the task manager.
150    DCHECK(web_contents()->GetMainFrame() != NULL);
151    web_contents()->ForEachFrame(
152        base::Bind(&TaskManagerWebContentsEntry::CreateResourceForFrame,
153                   base::Unretained(this)));
154  }
155
156  void ClearAllResources() {
157    for (ResourceMap::iterator j = resources_by_site_instance_.begin();
158         j != resources_by_site_instance_.end();) {
159      RendererResource* resource = j->second;
160
161      // Advance to next non-duplicate entry.
162      do {
163        ++j;
164      } while (j != resources_by_site_instance_.end() && resource == j->second);
165
166      // Remove the resource from the Task Manager.
167      task_manager()->RemoveResource(resource);
168      delete resource;
169    }
170    resources_by_site_instance_.clear();
171    tracked_frame_hosts_.clear();
172  }
173
174  void ClearResourceForFrame(RenderFrameHost* render_frame_host) {
175    SiteInstance* site_instance = render_frame_host->GetSiteInstance();
176    std::set<RenderFrameHost*>::iterator frame_set_iterator =
177        tracked_frame_hosts_.find(render_frame_host);
178    if (frame_set_iterator == tracked_frame_hosts_.end()) {
179      // We weren't tracking this RenderFrameHost.
180      return;
181    }
182    tracked_frame_hosts_.erase(frame_set_iterator);
183    ResourceRange resource_range =
184        resources_by_site_instance_.equal_range(site_instance);
185    if (resource_range.first == resource_range.second) {
186      NOTREACHED();
187      return;
188    }
189    RendererResource* resource = resource_range.first->second;
190    resources_by_site_instance_.erase(resource_range.first++);
191    if (resource_range.first == resource_range.second) {
192      // The removed entry was the sole remaining reference to that resource, so
193      // actually destroy it.
194      task_manager()->RemoveResource(resource);
195      delete resource;
196      if (site_instance == main_frame_site_instance_) {
197        main_frame_site_instance_ = NULL;
198      }
199    }
200  }
201
202  void CreateResourceForFrame(RenderFrameHost* render_frame_host) {
203    SiteInstance* site_instance = render_frame_host->GetSiteInstance();
204
205    DCHECK_EQ(0u, tracked_frame_hosts_.count(render_frame_host));
206    tracked_frame_hosts_.insert(render_frame_host);
207
208    ResourceRange existing_resource_range =
209        resources_by_site_instance_.equal_range(site_instance);
210    bool existing_resource =
211        (existing_resource_range.first != existing_resource_range.second);
212    bool is_main_frame = (render_frame_host == web_contents()->GetMainFrame());
213    bool site_instance_is_main = (site_instance == main_frame_site_instance_);
214    scoped_ptr<RendererResource> new_resource;
215    if (!existing_resource || (is_main_frame && !site_instance_is_main)) {
216      if (is_main_frame) {
217        new_resource = info()->MakeResource(web_contents());
218        main_frame_site_instance_ = site_instance;
219      } else {
220        new_resource.reset(new SubframeResource(
221            web_contents(), site_instance, render_frame_host));
222      }
223    }
224
225    if (existing_resource) {
226      RendererResource* old_resource = existing_resource_range.first->second;
227      if (!new_resource) {
228        resources_by_site_instance_.insert(
229            std::make_pair(site_instance, old_resource));
230      } else {
231        for (ResourceMap::iterator it = existing_resource_range.first;
232             it != existing_resource_range.second;
233             ++it) {
234          it->second = new_resource.get();
235        }
236        task_manager()->RemoveResource(old_resource);
237        delete old_resource;
238      }
239    }
240
241    if (new_resource) {
242      task_manager()->AddResource(new_resource.get());
243      resources_by_site_instance_.insert(
244          std::make_pair(site_instance, new_resource.release()));
245    }
246  }
247
248 private:
249  TaskManager* task_manager() { return provider_->task_manager(); }
250
251  WebContentsInformation* info() { return provider_->info(); }
252
253  WebContentsResourceProvider* const provider_;
254  std::set<RenderFrameHost*> tracked_frame_hosts_;
255  ResourceMap resources_by_site_instance_;
256  SiteInstance* main_frame_site_instance_;
257};
258
259////////////////////////////////////////////////////////////////////////////////
260// WebContentsResourceProvider class
261////////////////////////////////////////////////////////////////////////////////
262
263WebContentsResourceProvider::WebContentsResourceProvider(
264    TaskManager* task_manager,
265    scoped_ptr<WebContentsInformation> info)
266    : task_manager_(task_manager), info_(info.Pass()) {
267}
268
269WebContentsResourceProvider::~WebContentsResourceProvider() {}
270
271RendererResource* WebContentsResourceProvider::GetResource(int origin_pid,
272                                                           int child_id,
273                                                           int route_id) {
274  RenderFrameHost* rfh = RenderFrameHost::FromID(child_id, route_id);
275  WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
276
277  // If an origin PID was specified then the request originated in a plugin
278  // working on the WebContents's behalf, so ignore it.
279  if (origin_pid)
280    return NULL;
281
282  EntryMap::const_iterator web_contents_it = entries_.find(web_contents);
283
284  if (web_contents_it == entries_.end()) {
285    // Can happen if the tab was closed while a network request was being
286    // performed.
287    return NULL;
288  }
289
290  return web_contents_it->second->GetResourceForSiteInstance(
291      rfh->GetSiteInstance());
292}
293
294void WebContentsResourceProvider::StartUpdating() {
295  WebContentsInformation::NewWebContentsCallback new_web_contents_callback =
296      base::Bind(&WebContentsResourceProvider::OnWebContentsCreated, this);
297  info_->GetAll(new_web_contents_callback);
298  info_->StartObservingCreation(new_web_contents_callback);
299}
300
301void WebContentsResourceProvider::StopUpdating() {
302  info_->StopObservingCreation();
303
304  // Delete all entries; this dissassociates them from the WebContents too.
305  STLDeleteValues(&entries_);
306}
307
308void WebContentsResourceProvider::OnWebContentsCreated(
309    WebContents* web_contents) {
310  // Don't add dead tabs or tabs that haven't yet connected.
311  if (!web_contents->GetRenderProcessHost()->GetHandle() ||
312      !web_contents->WillNotifyDisconnection()) {
313    return;
314  }
315
316  DCHECK(info_->CheckOwnership(web_contents));
317  if (entries_.count(web_contents)) {
318    // The case may happen that we have added a WebContents as part of the
319    // iteration performed during StartUpdating() call but the notification that
320    // it has connected was not fired yet. So when the notification happens, we
321    // are already observing this WebContents and just ignore it.
322    return;
323  }
324  scoped_ptr<TaskManagerWebContentsEntry> entry(
325      new TaskManagerWebContentsEntry(web_contents, this));
326  entry->CreateAllResources();
327  entries_[web_contents] = entry.release();
328}
329
330void WebContentsResourceProvider::DeleteEntry(
331    WebContents* web_contents,
332    TaskManagerWebContentsEntry* entry) {
333  if (!entries_.erase(web_contents)) {
334    NOTREACHED();
335    return;
336  }
337  delete entry;  // Typically, this is our caller. Deletion is okay.
338}
339
340}  // namespace task_manager
341