1// Copyright 2013 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/background_resource_provider.h"
6
7#include "base/i18n/rtl.h"
8#include "base/strings/string16.h"
9#include "base/strings/utf_string_conversions.h"
10#include "chrome/browser/background/background_contents_service.h"
11#include "chrome/browser/background/background_contents_service_factory.h"
12#include "chrome/browser/browser_process.h"
13#include "chrome/browser/chrome_notification_types.h"
14#include "chrome/browser/extensions/extension_service.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/profiles/profile_manager.h"
17#include "chrome/browser/tab_contents/background_contents.h"
18#include "chrome/browser/task_manager/renderer_resource.h"
19#include "chrome/browser/task_manager/resource_provider.h"
20#include "chrome/browser/task_manager/task_manager.h"
21#include "chrome/common/extensions/extension.h"
22#include "content/public/browser/notification_service.h"
23#include "content/public/browser/render_process_host.h"
24#include "content/public/browser/render_view_host.h"
25#include "content/public/browser/web_contents.h"
26#include "grit/generated_resources.h"
27#include "grit/theme_resources.h"
28#include "ui/base/l10n/l10n_util.h"
29#include "ui/base/resource/resource_bundle.h"
30#include "ui/gfx/image/image_skia.h"
31
32using content::RenderProcessHost;
33using content::RenderViewHost;
34using content::WebContents;
35using extensions::Extension;
36
37namespace task_manager {
38
39class BackgroundContentsResource : public RendererResource {
40 public:
41  BackgroundContentsResource(
42      BackgroundContents* background_contents,
43      const string16& application_name);
44  virtual ~BackgroundContentsResource();
45
46  // Resource methods:
47  virtual string16 GetTitle() const OVERRIDE;
48  virtual string16 GetProfileName() const OVERRIDE;
49  virtual gfx::ImageSkia GetIcon() const OVERRIDE;
50  virtual bool IsBackground() const OVERRIDE;
51
52  const string16& application_name() const { return application_name_; }
53 private:
54  BackgroundContents* background_contents_;
55
56  string16 application_name_;
57
58  // The icon painted for BackgroundContents.
59  // TODO(atwilson): Use the favicon when there's a way to get the favicon for
60  // BackgroundContents.
61  static gfx::ImageSkia* default_icon_;
62
63  DISALLOW_COPY_AND_ASSIGN(BackgroundContentsResource);
64};
65
66gfx::ImageSkia* BackgroundContentsResource::default_icon_ = NULL;
67
68// TODO(atwilson): http://crbug.com/116893
69// HACK: if the process handle is invalid, we use the current process's handle.
70// This preserves old behavior but is incorrect, and should be fixed.
71BackgroundContentsResource::BackgroundContentsResource(
72    BackgroundContents* background_contents,
73    const string16& application_name)
74    : RendererResource(
75          background_contents->web_contents()->GetRenderProcessHost()->
76              GetHandle() ?
77              background_contents->web_contents()->GetRenderProcessHost()->
78                  GetHandle() :
79              base::Process::Current().handle(),
80          background_contents->web_contents()->GetRenderViewHost()),
81      background_contents_(background_contents),
82      application_name_(application_name) {
83  // Just use the same icon that other extension resources do.
84  // TODO(atwilson): Use the favicon when that's available.
85  if (!default_icon_) {
86    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
87    default_icon_ = rb.GetImageSkiaNamed(IDR_PLUGINS_FAVICON);
88  }
89  // Ensure that the string has the appropriate direction markers (see comment
90  // in TabContentsResource::GetTitle()).
91  base::i18n::AdjustStringForLocaleDirection(&application_name_);
92}
93
94BackgroundContentsResource::~BackgroundContentsResource() {
95}
96
97string16 BackgroundContentsResource::GetTitle() const {
98  string16 title = application_name_;
99
100  if (title.empty()) {
101    // No title (can't locate the parent app for some reason) so just display
102    // the URL (properly forced to be LTR).
103    title = base::i18n::GetDisplayStringInLTRDirectionality(
104        UTF8ToUTF16(background_contents_->GetURL().spec()));
105  }
106  return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_BACKGROUND_PREFIX, title);
107}
108
109string16 BackgroundContentsResource::GetProfileName() const {
110  return string16();
111}
112
113gfx::ImageSkia BackgroundContentsResource::GetIcon() const {
114  return *default_icon_;
115}
116
117bool BackgroundContentsResource::IsBackground() const {
118  return true;
119}
120
121////////////////////////////////////////////////////////////////////////////////
122// BackgroundContentsResourceProvider class
123////////////////////////////////////////////////////////////////////////////////
124
125BackgroundContentsResourceProvider::
126    BackgroundContentsResourceProvider(TaskManager* task_manager)
127    : updating_(false),
128      task_manager_(task_manager) {
129}
130
131BackgroundContentsResourceProvider::~BackgroundContentsResourceProvider() {
132}
133
134Resource* BackgroundContentsResourceProvider::GetResource(
135    int origin_pid,
136    int render_process_host_id,
137    int routing_id) {
138  // If an origin PID was specified, the request is from a plugin, not the
139  // render view host process
140  if (origin_pid)
141    return NULL;
142
143  for (Resources::iterator i = resources_.begin(); i != resources_.end(); i++) {
144    WebContents* tab = i->first->web_contents();
145    if (tab->GetRenderProcessHost()->GetID() == render_process_host_id
146        && tab->GetRenderViewHost()->GetRoutingID() == routing_id) {
147      return i->second;
148    }
149  }
150
151  // Can happen if the page went away while a network request was being
152  // performed.
153  return NULL;
154}
155
156void BackgroundContentsResourceProvider::StartUpdating() {
157  DCHECK(!updating_);
158  updating_ = true;
159
160  // Add all the existing BackgroundContents from every profile, including
161  // incognito profiles.
162  ProfileManager* profile_manager = g_browser_process->profile_manager();
163  std::vector<Profile*> profiles(profile_manager->GetLoadedProfiles());
164  size_t num_default_profiles = profiles.size();
165  for (size_t i = 0; i < num_default_profiles; ++i) {
166    if (profiles[i]->HasOffTheRecordProfile()) {
167      profiles.push_back(profiles[i]->GetOffTheRecordProfile());
168    }
169  }
170  for (size_t i = 0; i < profiles.size(); ++i) {
171    BackgroundContentsService* background_contents_service =
172        BackgroundContentsServiceFactory::GetForProfile(profiles[i]);
173    std::vector<BackgroundContents*> contents =
174        background_contents_service->GetBackgroundContents();
175    ExtensionService* extension_service = profiles[i]->GetExtensionService();
176    for (std::vector<BackgroundContents*>::iterator iterator = contents.begin();
177         iterator != contents.end(); ++iterator) {
178      string16 application_name;
179      // Lookup the name from the parent extension.
180      if (extension_service) {
181        const string16& application_id =
182            background_contents_service->GetParentApplicationId(*iterator);
183        const Extension* extension = extension_service->GetExtensionById(
184            UTF16ToUTF8(application_id), false);
185        if (extension)
186          application_name = UTF8ToUTF16(extension->name());
187      }
188      Add(*iterator, application_name);
189    }
190  }
191
192  // Then we register for notifications to get new BackgroundContents.
193  registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
194                 content::NotificationService::AllBrowserContextsAndSources());
195  registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
196                 content::NotificationService::AllBrowserContextsAndSources());
197  registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
198                 content::NotificationService::AllBrowserContextsAndSources());
199}
200
201void BackgroundContentsResourceProvider::StopUpdating() {
202  DCHECK(updating_);
203  updating_ = false;
204
205  // Unregister for notifications
206  registrar_.Remove(
207      this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
208      content::NotificationService::AllBrowserContextsAndSources());
209  registrar_.Remove(
210      this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
211      content::NotificationService::AllBrowserContextsAndSources());
212  registrar_.Remove(
213      this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
214      content::NotificationService::AllBrowserContextsAndSources());
215
216  // Delete all the resources.
217  STLDeleteContainerPairSecondPointers(resources_.begin(), resources_.end());
218
219  resources_.clear();
220}
221
222void BackgroundContentsResourceProvider::AddToTaskManager(
223    BackgroundContents* background_contents,
224    const string16& application_name) {
225  BackgroundContentsResource* resource =
226      new BackgroundContentsResource(background_contents,
227                                                application_name);
228  resources_[background_contents] = resource;
229  task_manager_->AddResource(resource);
230}
231
232void BackgroundContentsResourceProvider::Add(
233    BackgroundContents* contents, const string16& application_name) {
234  if (!updating_)
235    return;
236
237  // TODO(atwilson): http://crbug.com/116893
238  // We should check that the process handle is valid here, but it won't
239  // be in the case of NOTIFICATION_BACKGROUND_CONTENTS_OPENED.
240
241  // Should never add the same BackgroundContents twice.
242  DCHECK(resources_.find(contents) == resources_.end());
243  AddToTaskManager(contents, application_name);
244}
245
246void BackgroundContentsResourceProvider::Remove(BackgroundContents* contents) {
247  if (!updating_)
248    return;
249  Resources::iterator iter = resources_.find(contents);
250  DCHECK(iter != resources_.end());
251
252  // Remove the resource from the Task Manager.
253  BackgroundContentsResource* resource = iter->second;
254  task_manager_->RemoveResource(resource);
255  // And from the provider.
256  resources_.erase(iter);
257  // Finally, delete the resource.
258  delete resource;
259}
260
261void BackgroundContentsResourceProvider::Observe(
262    int type,
263    const content::NotificationSource& source,
264    const content::NotificationDetails& details) {
265  switch (type) {
266    case chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED: {
267      // Get the name from the parent application. If no parent application is
268      // found, just pass an empty string - BackgroundContentsResource::GetTitle
269      // will display the URL instead in this case. This should never happen
270      // except in rare cases when an extension is being unloaded or chrome is
271      // exiting while the task manager is displayed.
272      string16 application_name;
273      ExtensionService* service =
274          content::Source<Profile>(source)->GetExtensionService();
275      if (service) {
276        std::string application_id = UTF16ToUTF8(
277            content::Details<BackgroundContentsOpenedDetails>(details)->
278                application_id);
279        const Extension* extension =
280            service->GetExtensionById(application_id, false);
281        // Extension can be NULL when running unit tests.
282        if (extension)
283          application_name = UTF8ToUTF16(extension->name());
284      }
285      Add(content::Details<BackgroundContentsOpenedDetails>(details)->contents,
286          application_name);
287      // Opening a new BackgroundContents needs to force the display to refresh
288      // (applications may now be considered "background" that weren't before).
289      task_manager_->ModelChanged();
290      break;
291    }
292    case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: {
293      BackgroundContents* contents =
294          content::Details<BackgroundContents>(details).ptr();
295      // Should never get a NAVIGATED before OPENED.
296      DCHECK(resources_.find(contents) != resources_.end());
297      // Preserve the application name.
298      string16 application_name(
299          resources_.find(contents)->second->application_name());
300      Remove(contents);
301      Add(contents, application_name);
302      break;
303    }
304    case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED:
305      Remove(content::Details<BackgroundContents>(details).ptr());
306      // Closing a BackgroundContents needs to force the display to refresh
307      // (applications may now be considered "foreground" that weren't before).
308      task_manager_->ModelChanged();
309      break;
310    default:
311      NOTREACHED() << "Unexpected notification.";
312      return;
313  }
314}
315
316}  // namespace task_manager
317