• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/background_contents_service.h"
6 
7 #include "base/basictypes.h"
8 #include "base/command_line.h"
9 #include "base/string_util.h"
10 #include "base/utf_string_conversions.h"
11 #include "base/values.h"
12 #include "chrome/browser/background_contents_service_factory.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/extensions/extension_host.h"
15 #include "chrome/browser/extensions/extension_service.h"
16 #include "chrome/browser/notifications/desktop_notification_service.h"
17 #include "chrome/browser/notifications/notification.h"
18 #include "chrome/browser/notifications/notification_ui_manager.h"
19 #include "chrome/browser/prefs/pref_service.h"
20 #include "chrome/browser/prefs/scoped_user_pref_update.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_list.h"
24 #include "chrome/common/chrome_switches.h"
25 #include "chrome/common/extensions/extension.h"
26 #include "chrome/common/pref_names.h"
27 #include "content/browser/renderer_host/render_view_host.h"
28 #include "content/browser/site_instance.h"
29 #include "content/browser/tab_contents/tab_contents.h"
30 #include "content/common/notification_service.h"
31 #include "content/common/notification_type.h"
32 #include "grit/generated_resources.h"
33 #include "ui/base/l10n/l10n_util.h"
34 
35 namespace {
36 
37 const char kNotificationPrefix[] = "app.background.crashed.";
38 
CloseBalloon(const std::string id)39 void CloseBalloon(const std::string id) {
40   g_browser_process->notification_ui_manager()->CancelById(id);
41 }
42 
ScheduleCloseBalloon(const std::string & extension_id)43 void ScheduleCloseBalloon(const std::string& extension_id) {
44   MessageLoop::current()->PostTask(FROM_HERE,
45       NewRunnableFunction(&CloseBalloon,
46           kNotificationPrefix + extension_id));
47 }
48 
49 class CrashNotificationDelegate : public NotificationDelegate {
50  public:
CrashNotificationDelegate(Profile * profile,const Extension * extension)51   CrashNotificationDelegate(Profile* profile, const Extension* extension)
52       : profile_(profile),
53         is_hosted_app_(extension->is_hosted_app()),
54         extension_id_(extension->id()) {
55   }
56 
~CrashNotificationDelegate()57   ~CrashNotificationDelegate() {
58   }
59 
Display()60   void Display() {}
61 
Error()62   void Error() {}
63 
Close(bool by_user)64   void Close(bool by_user) {}
65 
Click()66   void Click() {
67     if (is_hosted_app_) {
68       BackgroundContentsServiceFactory::GetForProfile(profile_)->
69           LoadBackgroundContentsForExtension(profile_, extension_id_);
70     } else {
71       profile_->GetExtensionService()->ReloadExtension(extension_id_);
72     }
73 
74     // Closing the balloon here should be OK, but it causes a crash on Mac
75     // http://crbug.com/78167
76     ScheduleCloseBalloon(extension_id_);
77   }
78 
id() const79   std::string id() const {
80     return kNotificationPrefix + extension_id_;
81   }
82 
83  private:
84   Profile* profile_;
85   bool is_hosted_app_;
86   std::string extension_id_;
87 
88   DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
89 };
90 
ShowBalloon(const Extension * extension,Profile * profile)91 void ShowBalloon(const Extension* extension, Profile* profile) {
92   string16 message = l10n_util::GetStringFUTF16(
93       extension->is_hosted_app() ?  IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE :
94       IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE,
95       UTF8ToUTF16(extension->name()));
96   string16 content_url = DesktopNotificationService::CreateDataUrl(
97       extension->GetIconURL(Extension::EXTENSION_ICON_SMALLISH,
98                             ExtensionIconSet::MATCH_BIGGER),
99       string16(), message, WebKit::WebTextDirectionDefault);
100   Notification notification(
101       extension->url(), GURL(content_url), string16(), string16(),
102       new CrashNotificationDelegate(profile, extension));
103   g_browser_process->notification_ui_manager()->Add(notification, profile);
104 }
105 
106 }
107 
108 // Keys for the information we store about individual BackgroundContents in
109 // prefs. There is one top-level DictionaryValue (stored at
110 // prefs::kRegisteredBackgroundContents). Information about each
111 // BackgroundContents is stored under that top-level DictionaryValue, keyed
112 // by the parent application ID for easy lookup.
113 //
114 // kRegisteredBackgroundContents:
115 //    DictionaryValue {
116 //       <appid_1>: { "url": <url1>, "name": <frame_name> },
117 //       <appid_2>: { "url": <url2>, "name": <frame_name> },
118 //         ... etc ...
119 //    }
120 const char kUrlKey[] = "url";
121 const char kFrameNameKey[] = "name";
122 
BackgroundContentsService(Profile * profile,const CommandLine * command_line)123 BackgroundContentsService::BackgroundContentsService(
124     Profile* profile, const CommandLine* command_line)
125     : prefs_(NULL) {
126   // Don't load/store preferences if the proper switch is not enabled, or if
127   // the parent profile is incognito.
128   if (!profile->IsOffTheRecord() &&
129       !command_line->HasSwitch(switches::kDisableRestoreBackgroundContents))
130     prefs_ = profile->GetPrefs();
131 
132   // Listen for events to tell us when to load/unload persisted background
133   // contents.
134   StartObserving(profile);
135 }
136 
~BackgroundContentsService()137 BackgroundContentsService::~BackgroundContentsService() {
138   // BackgroundContents should be shutdown before we go away, as otherwise
139   // our browser process refcount will be off.
140   DCHECK(contents_map_.empty());
141 }
142 
143 std::vector<BackgroundContents*>
GetBackgroundContents() const144 BackgroundContentsService::GetBackgroundContents() const
145 {
146   std::vector<BackgroundContents*> contents;
147   for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
148        it != contents_map_.end(); ++it)
149     contents.push_back(it->second.contents);
150   return contents;
151 }
152 
StartObserving(Profile * profile)153 void BackgroundContentsService::StartObserving(Profile* profile) {
154   // On startup, load our background pages after extension-apps have loaded.
155   registrar_.Add(this, NotificationType::EXTENSIONS_READY,
156                  Source<Profile>(profile));
157 
158   // Track the lifecycle of all BackgroundContents in the system to allow us
159   // to store an up-to-date list of the urls. Start tracking contents when they
160   // have been opened via CreateBackgroundContents(), and stop tracking them
161   // when they are closed by script.
162   registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_CLOSED,
163                  Source<Profile>(profile));
164 
165   // Stop tracking BackgroundContents when they have been deleted (happens
166   // during shutdown or if the render process dies).
167   registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_DELETED,
168                  Source<Profile>(profile));
169 
170   // Track when the BackgroundContents navigates to a new URL so we can update
171   // our persisted information as appropriate.
172   registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_NAVIGATED,
173                  Source<Profile>(profile));
174 
175   // Listen for new extension installs so that we can load any associated
176   // background page.
177   registrar_.Add(this, NotificationType::EXTENSION_LOADED,
178                  Source<Profile>(profile));
179 
180   // Track when the extensions crash so that the user can be notified
181   // about it, and the crashed contents can be restarted.
182   registrar_.Add(this, NotificationType::EXTENSION_PROCESS_TERMINATED,
183                  Source<Profile>(profile));
184   registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_TERMINATED,
185                  Source<Profile>(profile));
186 
187   // Listen for extensions to be unloaded so we can shutdown associated
188   // BackgroundContents.
189   registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
190                  Source<Profile>(profile));
191 
192   // Make sure the extension-crash balloons are removed when the extension is
193   // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed
194   // extension is unloaded immediately after the crash, not when user reloads or
195   // uninstalls the extension.
196   registrar_.Add(this, NotificationType::EXTENSION_UNINSTALLED,
197                  Source<Profile>(profile));
198 }
199 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)200 void BackgroundContentsService::Observe(NotificationType type,
201                                         const NotificationSource& source,
202                                         const NotificationDetails& details) {
203   switch (type.value) {
204     case NotificationType::EXTENSIONS_READY:
205       LoadBackgroundContentsFromManifests(Source<Profile>(source).ptr());
206       LoadBackgroundContentsFromPrefs(Source<Profile>(source).ptr());
207       break;
208     case NotificationType::BACKGROUND_CONTENTS_DELETED:
209       BackgroundContentsShutdown(Details<BackgroundContents>(details).ptr());
210       break;
211     case NotificationType::BACKGROUND_CONTENTS_CLOSED:
212       DCHECK(IsTracked(Details<BackgroundContents>(details).ptr()));
213       UnregisterBackgroundContents(Details<BackgroundContents>(details).ptr());
214       break;
215     case NotificationType::BACKGROUND_CONTENTS_NAVIGATED: {
216       DCHECK(IsTracked(Details<BackgroundContents>(details).ptr()));
217 
218       // Do not register in the pref if the extension has a manifest-specified
219       // background page.
220       BackgroundContents* bgcontents =
221           Details<BackgroundContents>(details).ptr();
222       Profile* profile = Source<Profile>(source).ptr();
223       const string16& appid = GetParentApplicationId(bgcontents);
224       ExtensionService* extension_service = profile->GetExtensionService();
225       // extension_service can be NULL when running tests.
226       if (extension_service) {
227         const Extension* extension =
228             extension_service->GetExtensionById(UTF16ToUTF8(appid), false);
229         if (extension && extension->background_url().is_valid())
230           break;
231       }
232       RegisterBackgroundContents(Details<BackgroundContents>(details).ptr());
233       break;
234     }
235     case NotificationType::EXTENSION_LOADED: {
236       const Extension* extension = Details<const Extension>(details).ptr();
237       Profile* profile = Source<Profile>(source).ptr();
238       if (extension->is_hosted_app() &&
239           extension->background_url().is_valid()) {
240         // If there is a background page specified in the manifest for a hosted
241         // app, then blow away registered urls in the pref.
242         ShutdownAssociatedBackgroundContents(ASCIIToUTF16(extension->id()));
243 
244         ExtensionService* service = profile->GetExtensionService();
245         if (service && service->is_ready()) {
246           // Now load the manifest-specified background page. If service isn't
247           // ready, then the background page will be loaded from the
248           // EXTENSIONS_READY callback.
249           LoadBackgroundContents(profile, extension->background_url(),
250               ASCIIToUTF16("background"), UTF8ToUTF16(extension->id()));
251         }
252       }
253 
254       // Remove any "This extension has crashed" balloons.
255       ScheduleCloseBalloon(extension->id());
256       break;
257     }
258     case NotificationType::EXTENSION_PROCESS_TERMINATED:
259     case NotificationType::BACKGROUND_CONTENTS_TERMINATED: {
260       Profile* profile = Source<Profile>(source).ptr();
261       const Extension* extension = NULL;
262       if (type.value == NotificationType::BACKGROUND_CONTENTS_TERMINATED) {
263         BackgroundContents* bg =
264             Details<BackgroundContents>(details).ptr();
265         std::string extension_id = UTF16ToASCII(
266             BackgroundContentsServiceFactory::GetForProfile(profile)->
267                 GetParentApplicationId(bg));
268         extension =
269           profile->GetExtensionService()->GetExtensionById(extension_id, false);
270       } else {
271         ExtensionHost* extension_host = Details<ExtensionHost>(details).ptr();
272         extension = extension_host->extension();
273       }
274       if (!extension)
275         break;
276 
277       // When an extension crashes, EXTENSION_PROCESS_TERMINATED is followed by
278       // an EXTENSION_UNLOADED notification. This UNLOADED signal causes all the
279       // notifications for this extension to be cancelled by
280       // DesktopNotificationService. For this reason, instead of showing the
281       // balloon right now, we schedule it to show a little later.
282       MessageLoop::current()->PostTask(FROM_HERE,
283           NewRunnableFunction(&ShowBalloon, extension, profile));
284       break;
285     }
286     case NotificationType::EXTENSION_UNLOADED:
287       switch (Details<UnloadedExtensionInfo>(details)->reason) {
288         case UnloadedExtensionInfo::DISABLE:  // Intentionally fall through.
289         case UnloadedExtensionInfo::UNINSTALL:
290           ShutdownAssociatedBackgroundContents(
291               ASCIIToUTF16(
292                   Details<UnloadedExtensionInfo>(details)->extension->id()));
293           break;
294         case UnloadedExtensionInfo::UPDATE: {
295           // If there is a manifest specified background page, then shut it down
296           // here, since if the updated extension still has the background page,
297           // then it will be loaded from LOADED callback. Otherwise, leave
298           // BackgroundContents in place.
299           const Extension* extension =
300               Details<UnloadedExtensionInfo>(details)->extension;
301           if (extension->background_url().is_valid())
302             ShutdownAssociatedBackgroundContents(ASCIIToUTF16(extension->id()));
303           break;
304         }
305         default:
306           NOTREACHED();
307           ShutdownAssociatedBackgroundContents(
308               ASCIIToUTF16(
309                   Details<UnloadedExtensionInfo>(details)->extension->id()));
310           break;
311       }
312       break;
313 
314     case NotificationType::EXTENSION_UNINSTALLED: {
315       // Remove any "This extension has crashed" balloons.
316       const UninstalledExtensionInfo* uninstalled_extension =
317           Details<const UninstalledExtensionInfo>(details).ptr();
318       ScheduleCloseBalloon(uninstalled_extension->extension_id);
319       break;
320     }
321 
322     default:
323       NOTREACHED();
324       break;
325   }
326 }
327 
328 // Loads all background contents whose urls have been stored in prefs.
LoadBackgroundContentsFromPrefs(Profile * profile)329 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
330     Profile* profile) {
331   if (!prefs_)
332     return;
333   const DictionaryValue* contents =
334       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
335   if (!contents)
336     return;
337   ExtensionService* extensions_service = profile->GetExtensionService();
338   DCHECK(extensions_service);
339   for (DictionaryValue::key_iterator it = contents->begin_keys();
340        it != contents->end_keys(); ++it) {
341     // Check to make sure that the parent extension is still enabled.
342     const Extension* extension = extensions_service->GetExtensionById(
343         *it, false);
344     if (!extension) {
345       // We should never reach here - it should not be possible for an app
346       // to become uninstalled without the associated BackgroundContents being
347       // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a
348       // crash before we could save our prefs.
349       NOTREACHED() << "No extension found for BackgroundContents - id = "
350                    << *it;
351       return;
352     }
353     LoadBackgroundContentsFromDictionary(profile, *it, contents);
354   }
355 }
356 
LoadBackgroundContentsForExtension(Profile * profile,const std::string & extension_id)357 void BackgroundContentsService::LoadBackgroundContentsForExtension(
358     Profile* profile,
359     const std::string& extension_id) {
360   // First look if the manifest specifies a background page.
361   const Extension* extension =
362       profile->GetExtensionService()->GetExtensionById(extension_id, false);
363   DCHECK(!extension || extension->is_hosted_app());
364   if (extension && extension->background_url().is_valid()) {
365     LoadBackgroundContents(profile,
366                            extension->background_url(),
367                            ASCIIToUTF16("background"),
368                            UTF8ToUTF16(extension->id()));
369     return;
370   }
371 
372   // Now look in the prefs.
373   if (!prefs_)
374     return;
375   const DictionaryValue* contents =
376       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
377   if (!contents)
378     return;
379   LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
380 }
381 
LoadBackgroundContentsFromDictionary(Profile * profile,const std::string & extension_id,const DictionaryValue * contents)382 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
383     Profile* profile,
384     const std::string& extension_id,
385     const DictionaryValue* contents) {
386   ExtensionService* extensions_service = profile->GetExtensionService();
387   DCHECK(extensions_service);
388 
389   DictionaryValue* dict;
390   contents->GetDictionaryWithoutPathExpansion(extension_id, &dict);
391   if (dict == NULL)
392     return;
393   string16 frame_name;
394   std::string url;
395   dict->GetString(kUrlKey, &url);
396   dict->GetString(kFrameNameKey, &frame_name);
397   LoadBackgroundContents(profile,
398                          GURL(url),
399                          frame_name,
400                          UTF8ToUTF16(extension_id));
401 }
402 
LoadBackgroundContentsFromManifests(Profile * profile)403 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
404     Profile* profile) {
405   const ExtensionList* extensions =
406       profile->GetExtensionService()->extensions();
407   ExtensionList::const_iterator iter = extensions->begin();
408   for (; iter != extensions->end(); ++iter) {
409     const Extension* extension = *iter;
410     if (extension->is_hosted_app() &&
411         extension->background_url().is_valid()) {
412       LoadBackgroundContents(profile,
413                              extension->background_url(),
414                              ASCIIToUTF16("background"),
415                              UTF8ToUTF16(extension->id()));
416     }
417   }
418 }
419 
LoadBackgroundContents(Profile * profile,const GURL & url,const string16 & frame_name,const string16 & application_id)420 void BackgroundContentsService::LoadBackgroundContents(
421     Profile* profile,
422     const GURL& url,
423     const string16& frame_name,
424     const string16& application_id) {
425   // We are depending on the fact that we will initialize before any user
426   // actions or session restore can take place, so no BackgroundContents should
427   // be running yet for the passed application_id.
428   DCHECK(!GetAppBackgroundContents(application_id));
429   DCHECK(!application_id.empty());
430   DCHECK(url.is_valid());
431   DVLOG(1) << "Loading background content url: " << url;
432 
433   BackgroundContents* contents = CreateBackgroundContents(
434       SiteInstance::CreateSiteInstanceForURL(profile, url),
435       MSG_ROUTING_NONE,
436       profile,
437       frame_name,
438       application_id);
439 
440   RenderViewHost* render_view_host = contents->render_view_host();
441   // TODO(atwilson): Create RenderViews asynchronously to avoid increasing
442   // startup latency (http://crbug.com/47236).
443   render_view_host->CreateRenderView(frame_name);
444   render_view_host->NavigateToURL(url);
445 }
446 
CreateBackgroundContents(SiteInstance * site,int routing_id,Profile * profile,const string16 & frame_name,const string16 & application_id)447 BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
448     SiteInstance* site,
449     int routing_id,
450     Profile* profile,
451     const string16& frame_name,
452     const string16& application_id) {
453   BackgroundContents* contents = new BackgroundContents(site, routing_id, this);
454 
455   // Register the BackgroundContents internally, then send out a notification
456   // to external listeners.
457   BackgroundContentsOpenedDetails details = {contents,
458                                              frame_name,
459                                              application_id};
460   BackgroundContentsOpened(&details);
461   NotificationService::current()->Notify(
462       NotificationType::BACKGROUND_CONTENTS_OPENED,
463       Source<Profile>(profile),
464       Details<BackgroundContentsOpenedDetails>(&details));
465   return contents;
466 }
467 
RegisterBackgroundContents(BackgroundContents * background_contents)468 void BackgroundContentsService::RegisterBackgroundContents(
469     BackgroundContents* background_contents) {
470   DCHECK(IsTracked(background_contents));
471   if (!prefs_)
472     return;
473 
474   // We store the first URL we receive for a given application. If there's
475   // already an entry for this application, no need to do anything.
476   // TODO(atwilson): Verify that this is the desired behavior based on developer
477   // feedback (http://crbug.com/47118).
478   DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
479   DictionaryValue* pref = update.Get();
480   const string16& appid = GetParentApplicationId(background_contents);
481   DictionaryValue* current;
482   if (pref->GetDictionaryWithoutPathExpansion(UTF16ToUTF8(appid), &current))
483     return;
484 
485   // No entry for this application yet, so add one.
486   DictionaryValue* dict = new DictionaryValue();
487   dict->SetString(kUrlKey, background_contents->GetURL().spec());
488   dict->SetString(kFrameNameKey, contents_map_[appid].frame_name);
489   pref->SetWithoutPathExpansion(UTF16ToUTF8(appid), dict);
490   prefs_->ScheduleSavePersistentPrefs();
491 }
492 
UnregisterBackgroundContents(BackgroundContents * background_contents)493 void BackgroundContentsService::UnregisterBackgroundContents(
494     BackgroundContents* background_contents) {
495   if (!prefs_)
496     return;
497   DCHECK(IsTracked(background_contents));
498   const string16 appid = GetParentApplicationId(background_contents);
499   DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
500   update.Get()->RemoveWithoutPathExpansion(UTF16ToUTF8(appid), NULL);
501   prefs_->ScheduleSavePersistentPrefs();
502 }
503 
ShutdownAssociatedBackgroundContents(const string16 & appid)504 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
505     const string16& appid) {
506   BackgroundContents* contents = GetAppBackgroundContents(appid);
507   if (contents) {
508     UnregisterBackgroundContents(contents);
509     // Background contents destructor shuts down the renderer.
510     delete contents;
511   }
512 }
513 
BackgroundContentsOpened(BackgroundContentsOpenedDetails * details)514 void BackgroundContentsService::BackgroundContentsOpened(
515     BackgroundContentsOpenedDetails* details) {
516   // Add the passed object to our list. Should not already be tracked.
517   DCHECK(!IsTracked(details->contents));
518   DCHECK(!details->application_id.empty());
519   contents_map_[details->application_id].contents = details->contents;
520   contents_map_[details->application_id].frame_name = details->frame_name;
521 }
522 
523 // Used by test code and debug checks to verify whether a given
524 // BackgroundContents is being tracked by this instance.
IsTracked(BackgroundContents * background_contents) const525 bool BackgroundContentsService::IsTracked(
526     BackgroundContents* background_contents) const {
527   return !GetParentApplicationId(background_contents).empty();
528 }
529 
BackgroundContentsShutdown(BackgroundContents * background_contents)530 void BackgroundContentsService::BackgroundContentsShutdown(
531     BackgroundContents* background_contents) {
532   // Remove the passed object from our list.
533   DCHECK(IsTracked(background_contents));
534   string16 appid = GetParentApplicationId(background_contents);
535   contents_map_.erase(appid);
536 }
537 
GetAppBackgroundContents(const string16 & application_id)538 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
539     const string16& application_id) {
540   BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
541   return (it != contents_map_.end()) ? it->second.contents : NULL;
542 }
543 
GetParentApplicationId(BackgroundContents * contents) const544 const string16& BackgroundContentsService::GetParentApplicationId(
545     BackgroundContents* contents) const {
546   for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
547        it != contents_map_.end(); ++it) {
548     if (contents == it->second.contents)
549       return it->first;
550   }
551   return EmptyString16();
552 }
553 
554 // static
RegisterUserPrefs(PrefService * prefs)555 void BackgroundContentsService::RegisterUserPrefs(PrefService* prefs) {
556   prefs->RegisterDictionaryPref(prefs::kRegisteredBackgroundContents);
557 }
558 
AddTabContents(TabContents * new_contents,WindowOpenDisposition disposition,const gfx::Rect & initial_pos,bool user_gesture)559 void BackgroundContentsService::AddTabContents(
560     TabContents* new_contents,
561     WindowOpenDisposition disposition,
562     const gfx::Rect& initial_pos,
563     bool user_gesture) {
564   Browser* browser = BrowserList::GetLastActiveWithProfile(
565       new_contents->profile());
566   if (!browser)
567     return;
568   browser->AddTabContents(new_contents, disposition, initial_pos, user_gesture);
569 }
570