1 // Copyright (c) 2012 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/background/background_contents_service.h"
7 #include "apps/app_load_service.h"
8 #include "base/basictypes.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/prefs/scoped_user_pref_update.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/time/time.h"
18 #include "base/values.h"
19 #include "chrome/browser/background/background_contents_service_factory.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/extensions/extension_host.h"
23 #include "chrome/browser/extensions/extension_service.h"
24 #include "chrome/browser/extensions/image_loader.h"
25 #include "chrome/browser/notifications/desktop_notification_service.h"
26 #include "chrome/browser/notifications/notification.h"
27 #include "chrome/browser/notifications/notification_delegate.h"
28 #include "chrome/browser/notifications/notification_ui_manager.h"
29 #include "chrome/browser/profiles/profile.h"
30 #include "chrome/browser/profiles/profile_manager.h"
31 #include "chrome/browser/ui/browser.h"
32 #include "chrome/browser/ui/browser_finder.h"
33 #include "chrome/browser/ui/browser_tabstrip.h"
34 #include "chrome/browser/ui/host_desktop.h"
35 #include "chrome/common/chrome_switches.h"
36 #include "chrome/common/extensions/extension_constants.h"
37 #include "chrome/common/extensions/extension_icon_set.h"
38 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
39 #include "chrome/common/pref_names.h"
40 #include "content/public/browser/notification_service.h"
41 #include "content/public/browser/site_instance.h"
42 #include "content/public/browser/web_contents.h"
43 #include "extensions/browser/extension_system.h"
44 #include "extensions/common/extension.h"
45 #include "extensions/common/extension_set.h"
46 #include "extensions/common/manifest_handlers/background_info.h"
47 #include "grit/generated_resources.h"
48 #include "grit/theme_resources.h"
49 #include "ipc/ipc_message.h"
50 #include "ui/base/l10n/l10n_util.h"
51 #include "ui/base/resource/resource_bundle.h"
52 #include "ui/gfx/image/image.h"
54 #if defined(ENABLE_NOTIFICATIONS)
55 #include "ui/message_center/message_center.h"
56 #include "ui/message_center/message_center_util.h"
59 using content::SiteInstance
;
60 using content::WebContents
;
61 using extensions::BackgroundInfo
;
62 using extensions::Extension
;
63 using extensions::UnloadedExtensionInfo
;
67 const char kNotificationPrefix
[] = "app.background.crashed.";
69 void CloseBalloon(const std::string
& balloon_id
) {
70 NotificationUIManager
* notification_ui_manager
=
71 g_browser_process
->notification_ui_manager();
72 bool cancelled ALLOW_UNUSED
= notification_ui_manager
->CancelById(balloon_id
);
73 #if defined(ENABLE_NOTIFICATIONS)
74 if (cancelled
&& message_center::IsRichNotificationEnabled()) {
75 // TODO(dewittj): Add this functionality to the notification UI manager's
77 g_browser_process
->message_center()->SetVisibility(
78 message_center::VISIBILITY_TRANSIENT
);
83 // Closes the crash notification balloon for the app/extension with this id.
84 void ScheduleCloseBalloon(const std::string
& extension_id
) {
85 if (!base::MessageLoop::current()) // For unit_tests
87 base::MessageLoop::current()->PostTask(
88 FROM_HERE
, base::Bind(&CloseBalloon
, kNotificationPrefix
+ extension_id
));
91 // Delegate for the app/extension crash notification balloon. Restarts the
92 // app/extension when the balloon is clicked.
93 class CrashNotificationDelegate
: public NotificationDelegate
{
95 CrashNotificationDelegate(Profile
* profile
,
96 const Extension
* extension
)
98 is_hosted_app_(extension
->is_hosted_app()),
99 is_platform_app_(extension
->is_platform_app()),
100 extension_id_(extension
->id()) {
103 virtual void Display() OVERRIDE
{}
105 virtual void Error() OVERRIDE
{}
107 virtual void Close(bool by_user
) OVERRIDE
{}
109 virtual void Click() OVERRIDE
{
110 // http://crbug.com/247790 involves a crash notification balloon being
111 // clicked while the extension isn't in the TERMINATED state. In that case,
112 // any of the "reload" methods called below can unload the extension, which
113 // indirectly destroys *this, invalidating all the member variables, so we
114 // copy the extension ID before using it.
115 std::string copied_extension_id
= extension_id_
;
116 if (is_hosted_app_
) {
117 // There can be a race here: user clicks the balloon, and simultaneously
118 // reloads the sad tab for the app. So we check here to be safe before
119 // loading the background page.
120 BackgroundContentsService
* service
=
121 BackgroundContentsServiceFactory::GetForProfile(profile_
);
122 if (!service
->GetAppBackgroundContents(
123 base::ASCIIToUTF16(copied_extension_id
))) {
124 service
->LoadBackgroundContentsForExtension(profile_
,
125 copied_extension_id
);
127 } else if (is_platform_app_
) {
128 apps::AppLoadService::Get(profile_
)->
129 RestartApplication(copied_extension_id
);
131 extensions::ExtensionSystem::Get(profile_
)->extension_service()->
132 ReloadExtension(copied_extension_id
);
135 // Closing the crash notification balloon for the app/extension here should
136 // be OK, but it causes a crash on Mac, see: http://crbug.com/78167
137 ScheduleCloseBalloon(copied_extension_id
);
140 virtual bool HasClickedListener() OVERRIDE
{ return true; }
142 virtual std::string
id() const OVERRIDE
{
143 return kNotificationPrefix
+ extension_id_
;
146 virtual content::RenderViewHost
* GetRenderViewHost() const OVERRIDE
{
151 virtual ~CrashNotificationDelegate() {}
155 bool is_platform_app_
;
156 std::string extension_id_
;
158 DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate
);
161 #if defined(ENABLE_NOTIFICATIONS)
162 void NotificationImageReady(
163 const std::string extension_name
,
164 const base::string16 message
,
165 scoped_refptr
<CrashNotificationDelegate
> delegate
,
167 const gfx::Image
& icon
) {
168 gfx::Image
notification_icon(icon
);
169 if (icon
.IsEmpty()) {
170 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
171 notification_icon
= rb
.GetImageNamed(IDR_EXTENSION_DEFAULT_ICON
);
174 // Origin URL must be different from the crashed extension to avoid the
175 // conflict. NotificationSystemObserver will cancel all notifications from
176 // the same origin when NOTIFICATION_EXTENSION_UNLOADED.
177 // TODO(mukai, dewittj): remove this and switch to message center
179 DesktopNotificationService::AddIconNotification(
180 GURL() /* empty origin */,
190 // Show a popup notification balloon with a crash message for a given app/
192 void ShowBalloon(const Extension
* extension
, Profile
* profile
) {
193 #if defined(ENABLE_NOTIFICATIONS)
194 const base::string16 message
= l10n_util::GetStringFUTF16(
195 extension
->is_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE
:
196 IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE
,
197 base::UTF8ToUTF16(extension
->name()));
198 extension_misc::ExtensionIcons
size(extension_misc::EXTENSION_ICON_MEDIUM
);
199 extensions::ExtensionResource resource
=
200 extensions::IconsInfo::GetIconResource(
201 extension
, size
, ExtensionIconSet::MATCH_SMALLER
);
202 // We can't just load the image in the Observe method below because, despite
203 // what this method is called, it may call the callback synchronously.
204 // However, it's possible that the extension went away during the interim,
205 // so we'll bind all the pertinent data here.
206 extensions::ImageLoader::Get(profile
)->LoadImageAsync(
209 gfx::Size(size
, size
),
211 &NotificationImageReady
,
214 make_scoped_refptr(new CrashNotificationDelegate(profile
, extension
)),
219 void ReloadExtension(const std::string
& extension_id
, Profile
* profile
) {
220 if (g_browser_process
->IsShuttingDown() ||
221 !g_browser_process
->profile_manager()->IsValidProfile(profile
)) {
224 extensions::ExtensionSystem
* extension_system
=
225 extensions::ExtensionSystem::Get(profile
);
226 if (!extension_system
|| !extension_system
->extension_service())
228 if (!extension_system
->extension_service()->
229 GetTerminatedExtension(extension_id
)) {
230 // Either the app/extension was uninstalled by policy or it has since
231 // been restarted successfully by someone else (the user).
234 extension_system
->extension_service()->ReloadExtension(extension_id
);
239 // Keys for the information we store about individual BackgroundContents in
240 // prefs. There is one top-level DictionaryValue (stored at
241 // prefs::kRegisteredBackgroundContents). Information about each
242 // BackgroundContents is stored under that top-level DictionaryValue, keyed
243 // by the parent application ID for easy lookup.
245 // kRegisteredBackgroundContents:
247 // <appid_1>: { "url": <url1>, "name": <frame_name> },
248 // <appid_2>: { "url": <url2>, "name": <frame_name> },
251 const char kUrlKey
[] = "url";
252 const char kFrameNameKey
[] = "name";
254 int BackgroundContentsService::restart_delay_in_ms_
= 3000; // 3 seconds.
256 BackgroundContentsService::BackgroundContentsService(
257 Profile
* profile
, const CommandLine
* command_line
)
259 // Don't load/store preferences if the proper switch is not enabled, or if
260 // the parent profile is incognito.
261 if (!profile
->IsOffTheRecord() &&
262 !command_line
->HasSwitch(switches::kDisableRestoreBackgroundContents
))
263 prefs_
= profile
->GetPrefs();
265 // Listen for events to tell us when to load/unload persisted background
267 StartObserving(profile
);
270 BackgroundContentsService::~BackgroundContentsService() {
271 // BackgroundContents should be shutdown before we go away, as otherwise
272 // our browser process refcount will be off.
273 DCHECK(contents_map_
.empty());
277 void BackgroundContentsService::
278 SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(
279 int restart_delay_in_ms
) {
280 restart_delay_in_ms_
= restart_delay_in_ms
;
283 std::vector
<BackgroundContents
*>
284 BackgroundContentsService::GetBackgroundContents() const
286 std::vector
<BackgroundContents
*> contents
;
287 for (BackgroundContentsMap::const_iterator it
= contents_map_
.begin();
288 it
!= contents_map_
.end(); ++it
)
289 contents
.push_back(it
->second
.contents
);
293 void BackgroundContentsService::StartObserving(Profile
* profile
) {
294 // On startup, load our background pages after extension-apps have loaded.
295 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY
,
296 content::Source
<Profile
>(profile
));
298 // Track the lifecycle of all BackgroundContents in the system to allow us
299 // to store an up-to-date list of the urls. Start tracking contents when they
300 // have been opened via CreateBackgroundContents(), and stop tracking them
301 // when they are closed by script.
302 registrar_
.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED
,
303 content::Source
<Profile
>(profile
));
305 // Stop tracking BackgroundContents when they have been deleted (happens
306 // during shutdown or if the render process dies).
307 registrar_
.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED
,
308 content::Source
<Profile
>(profile
));
310 // Track when the BackgroundContents navigates to a new URL so we can update
311 // our persisted information as appropriate.
312 registrar_
.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED
,
313 content::Source
<Profile
>(profile
));
315 // Listen for new extension installs so that we can load any associated
317 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED
,
318 content::Source
<Profile
>(profile
));
320 // Track when the extensions crash so that the user can be notified
321 // about it, and the crashed contents can be restarted.
322 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED
,
323 content::Source
<Profile
>(profile
));
324 registrar_
.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED
,
325 content::Source
<Profile
>(profile
));
327 // Listen for extensions to be unloaded so we can shutdown associated
328 // BackgroundContents.
329 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED
,
330 content::Source
<Profile
>(profile
));
332 // Make sure the extension-crash balloons are removed when the extension is
333 // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed
334 // extension is unloaded immediately after the crash, not when user reloads or
335 // uninstalls the extension.
336 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED
,
337 content::Source
<Profile
>(profile
));
340 void BackgroundContentsService::Observe(
342 const content::NotificationSource
& source
,
343 const content::NotificationDetails
& details
) {
345 case chrome::NOTIFICATION_EXTENSIONS_READY
: {
346 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
347 LoadBackgroundContentsFromManifests(profile
);
348 LoadBackgroundContentsFromPrefs(profile
);
349 SendChangeNotification(profile
);
352 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED
:
353 BackgroundContentsShutdown(
354 content::Details
<BackgroundContents
>(details
).ptr());
355 SendChangeNotification(content::Source
<Profile
>(source
).ptr());
357 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED
:
358 DCHECK(IsTracked(content::Details
<BackgroundContents
>(details
).ptr()));
359 UnregisterBackgroundContents(
360 content::Details
<BackgroundContents
>(details
).ptr());
361 // CLOSED is always followed by a DELETED notification so we'll send our
362 // change notification there.
364 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED
: {
365 DCHECK(IsTracked(content::Details
<BackgroundContents
>(details
).ptr()));
367 // Do not register in the pref if the extension has a manifest-specified
369 BackgroundContents
* bgcontents
=
370 content::Details
<BackgroundContents
>(details
).ptr();
371 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
372 const base::string16
& appid
= GetParentApplicationId(bgcontents
);
373 ExtensionService
* extension_service
=
374 extensions::ExtensionSystem::Get(profile
)->extension_service();
375 // extension_service can be NULL when running tests.
376 if (extension_service
) {
377 const Extension
* extension
= extension_service
->GetExtensionById(
378 base::UTF16ToUTF8(appid
), false);
379 if (extension
&& BackgroundInfo::HasBackgroundPage(extension
))
382 RegisterBackgroundContents(bgcontents
);
385 case chrome::NOTIFICATION_EXTENSION_LOADED
: {
386 const Extension
* extension
=
387 content::Details
<const Extension
>(details
).ptr();
388 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
389 if (extension
->is_hosted_app() &&
390 BackgroundInfo::HasBackgroundPage(extension
)) {
391 // If there is a background page specified in the manifest for a hosted
392 // app, then blow away registered urls in the pref.
393 ShutdownAssociatedBackgroundContents(
394 base::ASCIIToUTF16(extension
->id()));
396 ExtensionService
* service
=
397 extensions::ExtensionSystem::Get(profile
)->extension_service();
398 if (service
&& service
->is_ready()) {
399 // Now load the manifest-specified background page. If service isn't
400 // ready, then the background page will be loaded from the
401 // EXTENSIONS_READY callback.
402 LoadBackgroundContents(profile
,
403 BackgroundInfo::GetBackgroundURL(extension
),
404 base::ASCIIToUTF16("background"),
405 base::UTF8ToUTF16(extension
->id()));
409 // Close the crash notification balloon for the app/extension, if any.
410 ScheduleCloseBalloon(extension
->id());
411 SendChangeNotification(profile
);
414 case chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED
:
415 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED
: {
416 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
417 const Extension
* extension
= NULL
;
418 if (type
== chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED
) {
419 BackgroundContents
* bg
=
420 content::Details
<BackgroundContents
>(details
).ptr();
421 std::string extension_id
= UTF16ToASCII(
422 BackgroundContentsServiceFactory::GetForProfile(profile
)->
423 GetParentApplicationId(bg
));
425 extensions::ExtensionSystem::Get(profile
)->extension_service()->
426 GetExtensionById(extension_id
, false);
428 extensions::ExtensionHost
* extension_host
=
429 content::Details
<extensions::ExtensionHost
>(details
).ptr();
430 extension
= extension_host
->extension();
435 const bool force_installed
=
436 extensions::Manifest::IsComponentLocation(extension
->location()) ||
437 extensions::Manifest::IsPolicyLocation(extension
->location());
438 if (!force_installed
) {
439 ShowBalloon(extension
, profile
);
441 // Restart the extension.
442 RestartForceInstalledExtensionOnCrash(extension
, profile
);
446 case chrome::NOTIFICATION_EXTENSION_UNLOADED
:
447 switch (content::Details
<UnloadedExtensionInfo
>(details
)->reason
) {
448 case UnloadedExtensionInfo::REASON_DISABLE
: // Fall through.
449 case UnloadedExtensionInfo::REASON_TERMINATE
: // Fall through.
450 case UnloadedExtensionInfo::REASON_UNINSTALL
: // Fall through.
451 case UnloadedExtensionInfo::REASON_BLACKLIST
:
452 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(
453 content::Details
<UnloadedExtensionInfo
>(details
)->
455 SendChangeNotification(content::Source
<Profile
>(source
).ptr());
457 case UnloadedExtensionInfo::REASON_UPDATE
: {
458 // If there is a manifest specified background page, then shut it down
459 // here, since if the updated extension still has the background page,
460 // then it will be loaded from LOADED callback. Otherwise, leave
461 // BackgroundContents in place.
462 // We don't call SendChangeNotification here - it will be generated
463 // from the LOADED callback.
464 const Extension
* extension
=
465 content::Details
<UnloadedExtensionInfo
>(details
)->extension
;
466 if (BackgroundInfo::HasBackgroundPage(extension
)) {
467 ShutdownAssociatedBackgroundContents(
468 base::ASCIIToUTF16(extension
->id()));
474 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(
475 content::Details
<UnloadedExtensionInfo
>(details
)->
481 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED
: {
482 // Close the crash notification balloon for the app/extension, if any.
483 ScheduleCloseBalloon(
484 content::Details
<const Extension
>(details
).ptr()->id());
494 void BackgroundContentsService::RestartForceInstalledExtensionOnCrash(
495 const Extension
* extension
,
497 base::MessageLoop::current()->PostDelayedTask(FROM_HERE
,
498 base::Bind(&ReloadExtension
, extension
->id(), profile
),
499 base::TimeDelta::FromMilliseconds(restart_delay_in_ms_
));
502 // Loads all background contents whose urls have been stored in prefs.
503 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
507 const base::DictionaryValue
* contents
=
508 prefs_
->GetDictionary(prefs::kRegisteredBackgroundContents
);
511 ExtensionService
* extensions_service
=
512 extensions::ExtensionSystem::Get(profile
)->extension_service();
513 DCHECK(extensions_service
);
514 for (base::DictionaryValue::Iterator
it(*contents
);
515 !it
.IsAtEnd(); it
.Advance()) {
516 // Check to make sure that the parent extension is still enabled.
517 const Extension
* extension
= extensions_service
->
518 GetExtensionById(it
.key(), false);
520 // We should never reach here - it should not be possible for an app
521 // to become uninstalled without the associated BackgroundContents being
522 // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a
523 // crash before we could save our prefs, or if the user deletes the
524 // extension files manually rather than uninstalling it.
525 NOTREACHED() << "No extension found for BackgroundContents - id = "
527 // Don't cancel out of our loop, just ignore this BackgroundContents and
528 // load the next one.
531 LoadBackgroundContentsFromDictionary(profile
, it
.key(), contents
);
535 void BackgroundContentsService::SendChangeNotification(Profile
* profile
) {
536 content::NotificationService::current()->Notify(
537 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED
,
538 content::Source
<Profile
>(profile
),
539 content::Details
<BackgroundContentsService
>(this));
542 void BackgroundContentsService::LoadBackgroundContentsForExtension(
544 const std::string
& extension_id
) {
545 // First look if the manifest specifies a background page.
546 const Extension
* extension
=
547 extensions::ExtensionSystem::Get(profile
)->extension_service()->
548 GetExtensionById(extension_id
, false);
549 DCHECK(!extension
|| extension
->is_hosted_app());
550 if (extension
&& BackgroundInfo::HasBackgroundPage(extension
)) {
551 LoadBackgroundContents(profile
,
552 BackgroundInfo::GetBackgroundURL(extension
),
553 base::ASCIIToUTF16("background"),
554 base::UTF8ToUTF16(extension
->id()));
558 // Now look in the prefs.
561 const base::DictionaryValue
* contents
=
562 prefs_
->GetDictionary(prefs::kRegisteredBackgroundContents
);
565 LoadBackgroundContentsFromDictionary(profile
, extension_id
, contents
);
568 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
570 const std::string
& extension_id
,
571 const base::DictionaryValue
* contents
) {
572 ExtensionService
* extensions_service
=
573 extensions::ExtensionSystem::Get(profile
)->extension_service();
574 DCHECK(extensions_service
);
576 const base::DictionaryValue
* dict
;
577 if (!contents
->GetDictionaryWithoutPathExpansion(extension_id
, &dict
) ||
581 base::string16 frame_name
;
583 dict
->GetString(kUrlKey
, &url
);
584 dict
->GetString(kFrameNameKey
, &frame_name
);
585 LoadBackgroundContents(profile
,
588 base::UTF8ToUTF16(extension_id
));
591 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
593 const extensions::ExtensionSet
* extensions
=
594 extensions::ExtensionSystem::Get(profile
)->
595 extension_service()->extensions();
596 for (extensions::ExtensionSet::const_iterator iter
= extensions
->begin();
597 iter
!= extensions
->end(); ++iter
) {
598 const Extension
* extension
= iter
->get();
599 if (extension
->is_hosted_app() &&
600 BackgroundInfo::HasBackgroundPage(extension
)) {
601 LoadBackgroundContents(profile
,
602 BackgroundInfo::GetBackgroundURL(extension
),
603 base::ASCIIToUTF16("background"),
604 base::UTF8ToUTF16(extension
->id()));
609 void BackgroundContentsService::LoadBackgroundContents(
612 const base::string16
& frame_name
,
613 const base::string16
& application_id
) {
614 // We are depending on the fact that we will initialize before any user
615 // actions or session restore can take place, so no BackgroundContents should
616 // be running yet for the passed application_id.
617 DCHECK(!GetAppBackgroundContents(application_id
));
618 DCHECK(!application_id
.empty());
619 DCHECK(url
.is_valid());
620 DVLOG(1) << "Loading background content url: " << url
;
622 BackgroundContents
* contents
= CreateBackgroundContents(
623 SiteInstance::CreateForURL(profile
, url
),
631 // TODO(atwilson): Create RenderViews asynchronously to avoid increasing
632 // startup latency (http://crbug.com/47236).
633 contents
->web_contents()->GetController().LoadURL(
634 url
, content::Referrer(), content::PAGE_TRANSITION_LINK
, std::string());
637 BackgroundContents
* BackgroundContentsService::CreateBackgroundContents(
641 const base::string16
& frame_name
,
642 const base::string16
& application_id
,
643 const std::string
& partition_id
,
644 content::SessionStorageNamespace
* session_storage_namespace
) {
645 BackgroundContents
* contents
= new BackgroundContents(
646 site
, routing_id
, this, partition_id
, session_storage_namespace
);
648 // Register the BackgroundContents internally, then send out a notification
649 // to external listeners.
650 BackgroundContentsOpenedDetails details
= {contents
,
653 BackgroundContentsOpened(&details
);
654 content::NotificationService::current()->Notify(
655 chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED
,
656 content::Source
<Profile
>(profile
),
657 content::Details
<BackgroundContentsOpenedDetails
>(&details
));
659 // A new background contents has been created - notify our listeners.
660 SendChangeNotification(profile
);
664 void BackgroundContentsService::RegisterBackgroundContents(
665 BackgroundContents
* background_contents
) {
666 DCHECK(IsTracked(background_contents
));
670 // We store the first URL we receive for a given application. If there's
671 // already an entry for this application, no need to do anything.
672 // TODO(atwilson): Verify that this is the desired behavior based on developer
673 // feedback (http://crbug.com/47118).
674 DictionaryPrefUpdate
update(prefs_
, prefs::kRegisteredBackgroundContents
);
675 base::DictionaryValue
* pref
= update
.Get();
676 const base::string16
& appid
= GetParentApplicationId(background_contents
);
677 base::DictionaryValue
* current
;
678 if (pref
->GetDictionaryWithoutPathExpansion(base::UTF16ToUTF8(appid
),
683 // No entry for this application yet, so add one.
684 base::DictionaryValue
* dict
= new base::DictionaryValue();
685 dict
->SetString(kUrlKey
, background_contents
->GetURL().spec());
686 dict
->SetString(kFrameNameKey
, contents_map_
[appid
].frame_name
);
687 pref
->SetWithoutPathExpansion(base::UTF16ToUTF8(appid
), dict
);
690 bool BackgroundContentsService::HasRegisteredBackgroundContents(
691 const base::string16
& app_id
) {
694 std::string app
= base::UTF16ToUTF8(app_id
);
695 const base::DictionaryValue
* contents
=
696 prefs_
->GetDictionary(prefs::kRegisteredBackgroundContents
);
697 return contents
->HasKey(app
);
700 void BackgroundContentsService::UnregisterBackgroundContents(
701 BackgroundContents
* background_contents
) {
704 DCHECK(IsTracked(background_contents
));
705 const base::string16 appid
= GetParentApplicationId(background_contents
);
706 DictionaryPrefUpdate
update(prefs_
, prefs::kRegisteredBackgroundContents
);
707 update
.Get()->RemoveWithoutPathExpansion(base::UTF16ToUTF8(appid
), NULL
);
710 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
711 const base::string16
& appid
) {
712 BackgroundContents
* contents
= GetAppBackgroundContents(appid
);
714 UnregisterBackgroundContents(contents
);
715 // Background contents destructor shuts down the renderer.
720 void BackgroundContentsService::BackgroundContentsOpened(
721 BackgroundContentsOpenedDetails
* details
) {
722 // Add the passed object to our list. Should not already be tracked.
723 DCHECK(!IsTracked(details
->contents
));
724 DCHECK(!details
->application_id
.empty());
725 contents_map_
[details
->application_id
].contents
= details
->contents
;
726 contents_map_
[details
->application_id
].frame_name
= details
->frame_name
;
728 ScheduleCloseBalloon(UTF16ToASCII(details
->application_id
));
731 // Used by test code and debug checks to verify whether a given
732 // BackgroundContents is being tracked by this instance.
733 bool BackgroundContentsService::IsTracked(
734 BackgroundContents
* background_contents
) const {
735 return !GetParentApplicationId(background_contents
).empty();
738 void BackgroundContentsService::BackgroundContentsShutdown(
739 BackgroundContents
* background_contents
) {
740 // Remove the passed object from our list.
741 DCHECK(IsTracked(background_contents
));
742 base::string16 appid
= GetParentApplicationId(background_contents
);
743 contents_map_
.erase(appid
);
746 BackgroundContents
* BackgroundContentsService::GetAppBackgroundContents(
747 const base::string16
& application_id
) {
748 BackgroundContentsMap::const_iterator it
= contents_map_
.find(application_id
);
749 return (it
!= contents_map_
.end()) ? it
->second
.contents
: NULL
;
752 const base::string16
& BackgroundContentsService::GetParentApplicationId(
753 BackgroundContents
* contents
) const {
754 for (BackgroundContentsMap::const_iterator it
= contents_map_
.begin();
755 it
!= contents_map_
.end(); ++it
) {
756 if (contents
== it
->second
.contents
)
759 return base::EmptyString16();
762 void BackgroundContentsService::AddWebContents(
763 WebContents
* new_contents
,
764 WindowOpenDisposition disposition
,
765 const gfx::Rect
& initial_pos
,
768 Browser
* browser
= chrome::FindLastActiveWithProfile(
769 Profile::FromBrowserContext(new_contents
->GetBrowserContext()),
770 chrome::GetActiveDesktop());
772 chrome::AddWebContents(browser
, NULL
, new_contents
, disposition
,
773 initial_pos
, user_gesture
, was_blocked
);