No dual_mode on Win10+ shortcuts.
[chromium-blink-merge.git] / chrome / browser / background / background_contents_service.cc
blob323a081f943a572f0f567e36685538c0e223020c
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"
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/location.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/prefs/pref_service.h"
16 #include "base/prefs/scoped_user_pref_update.h"
17 #include "base/single_thread_task_runner.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/thread_task_runner_handle.h"
21 #include "base/time/time.h"
22 #include "base/values.h"
23 #include "chrome/browser/background/background_contents_service_factory.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/browser/chrome_notification_types.h"
26 #include "chrome/browser/extensions/extension_service.h"
27 #include "chrome/browser/notifications/desktop_notification_service.h"
28 #include "chrome/browser/notifications/notification.h"
29 #include "chrome/browser/notifications/notification_delegate.h"
30 #include "chrome/browser/notifications/notification_ui_manager.h"
31 #include "chrome/browser/profiles/profile.h"
32 #include "chrome/browser/profiles/profile_manager.h"
33 #include "chrome/browser/ui/browser.h"
34 #include "chrome/browser/ui/browser_finder.h"
35 #include "chrome/browser/ui/browser_tabstrip.h"
36 #include "chrome/browser/ui/host_desktop.h"
37 #include "chrome/common/extensions/extension_constants.h"
38 #include "chrome/common/pref_names.h"
39 #include "chrome/grit/generated_resources.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_host.h"
44 #include "extensions/browser/extension_registry.h"
45 #include "extensions/browser/extension_system.h"
46 #include "extensions/browser/image_loader.h"
47 #include "extensions/browser/notification_types.h"
48 #include "extensions/common/constants.h"
49 #include "extensions/common/extension.h"
50 #include "extensions/common/extension_icon_set.h"
51 #include "extensions/common/extension_set.h"
52 #include "extensions/common/manifest_handlers/background_info.h"
53 #include "extensions/common/manifest_handlers/icons_handler.h"
54 #include "extensions/grit/extensions_browser_resources.h"
55 #include "ipc/ipc_message.h"
56 #include "ui/base/l10n/l10n_util.h"
57 #include "ui/base/resource/resource_bundle.h"
58 #include "ui/gfx/image/image.h"
60 #if defined(ENABLE_NOTIFICATIONS)
61 #include "ui/message_center/message_center.h"
62 #endif
64 using content::SiteInstance;
65 using content::WebContents;
66 using extensions::BackgroundInfo;
67 using extensions::Extension;
68 using extensions::UnloadedExtensionInfo;
70 namespace {
72 const char kNotificationPrefix[] = "app.background.crashed.";
73 bool g_disable_close_balloon_for_testing = false;
75 void CloseBalloon(const std::string& balloon_id, ProfileID profile_id) {
76 NotificationUIManager* notification_ui_manager =
77 g_browser_process->notification_ui_manager();
78 bool cancelled = notification_ui_manager->CancelById(balloon_id, profile_id);
79 if (cancelled) {
80 #if defined(ENABLE_NOTIFICATIONS)
81 // TODO(dewittj): Add this functionality to the notification UI manager's
82 // API.
83 g_browser_process->message_center()->SetVisibility(
84 message_center::VISIBILITY_TRANSIENT);
85 #endif
89 // Closes the crash notification balloon for the app/extension with this id.
90 void ScheduleCloseBalloon(const std::string& extension_id, Profile* profile) {
91 if (g_disable_close_balloon_for_testing)
92 return;
93 base::ThreadTaskRunnerHandle::Get()->PostTask(
94 FROM_HERE, base::Bind(&CloseBalloon, kNotificationPrefix + extension_id,
95 NotificationUIManager::GetProfileID(profile)));
98 // Delegate for the app/extension crash notification balloon. Restarts the
99 // app/extension when the balloon is clicked.
100 class CrashNotificationDelegate : public NotificationDelegate {
101 public:
102 CrashNotificationDelegate(Profile* profile,
103 const Extension* extension)
104 : profile_(profile),
105 is_hosted_app_(extension->is_hosted_app()),
106 is_platform_app_(extension->is_platform_app()),
107 extension_id_(extension->id()) {
110 void Click() override {
111 // http://crbug.com/247790 involves a crash notification balloon being
112 // clicked while the extension isn't in the TERMINATED state. In that case,
113 // any of the "reload" methods called below can unload the extension, which
114 // indirectly destroys *this, invalidating all the member variables, so we
115 // copy the extension ID before using it.
116 std::string copied_extension_id = extension_id_;
117 if (is_hosted_app_) {
118 // There can be a race here: user clicks the balloon, and simultaneously
119 // reloads the sad tab for the app. So we check here to be safe before
120 // loading the background page.
121 BackgroundContentsService* service =
122 BackgroundContentsServiceFactory::GetForProfile(profile_);
123 if (!service->GetAppBackgroundContents(
124 base::ASCIIToUTF16(copied_extension_id))) {
125 service->LoadBackgroundContentsForExtension(profile_,
126 copied_extension_id);
128 } else if (is_platform_app_) {
129 apps::AppLoadService::Get(profile_)->
130 RestartApplication(copied_extension_id);
131 } else {
132 extensions::ExtensionSystem::Get(profile_)->extension_service()->
133 ReloadExtension(copied_extension_id);
136 // Closing the crash notification balloon for the app/extension here should
137 // be OK, but it causes a crash on Mac, see: http://crbug.com/78167
138 ScheduleCloseBalloon(copied_extension_id, profile_);
141 bool HasClickedListener() override { return true; }
143 std::string id() const override {
144 return kNotificationPrefix + extension_id_;
147 private:
148 ~CrashNotificationDelegate() override {}
150 Profile* profile_;
151 bool is_hosted_app_;
152 bool is_platform_app_;
153 std::string extension_id_;
155 DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
158 #if defined(ENABLE_NOTIFICATIONS)
159 void NotificationImageReady(
160 const std::string extension_name,
161 const base::string16 message,
162 scoped_refptr<CrashNotificationDelegate> delegate,
163 Profile* profile,
164 const gfx::Image& icon) {
165 gfx::Image notification_icon(icon);
166 if (notification_icon.IsEmpty()) {
167 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
168 notification_icon = rb.GetImageNamed(IDR_EXTENSION_DEFAULT_ICON);
171 // Origin URL must be different from the crashed extension to avoid the
172 // conflict. NotificationSystemObserver will cancel all notifications from
173 // the same origin when NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED.
174 Notification notification(GURL("chrome://extension-crash"),
175 base::string16(),
176 message,
177 notification_icon,
178 base::string16(),
179 delegate->id(),
180 delegate.get());
182 g_browser_process->notification_ui_manager()->Add(notification, profile);
184 #endif
186 // Show a popup notification balloon with a crash message for a given app/
187 // extension.
188 void ShowBalloon(const Extension* extension, Profile* profile) {
189 #if defined(ENABLE_NOTIFICATIONS)
190 const base::string16 message = l10n_util::GetStringFUTF16(
191 extension->is_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE :
192 IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE,
193 base::UTF8ToUTF16(extension->name()));
194 extension_misc::ExtensionIcons size(extension_misc::EXTENSION_ICON_LARGE);
195 extensions::ExtensionResource resource =
196 extensions::IconsInfo::GetIconResource(
197 extension, size, ExtensionIconSet::MATCH_SMALLER);
198 // We can't just load the image in the Observe method below because, despite
199 // what this method is called, it may call the callback synchronously.
200 // However, it's possible that the extension went away during the interim,
201 // so we'll bind all the pertinent data here.
202 extensions::ImageLoader::Get(profile)->LoadImageAsync(
203 extension,
204 resource,
205 gfx::Size(size, size),
206 base::Bind(
207 &NotificationImageReady,
208 extension->name(),
209 message,
210 make_scoped_refptr(new CrashNotificationDelegate(profile, extension)),
211 profile));
212 #endif
215 void ReloadExtension(const std::string& extension_id, Profile* profile) {
216 if (g_browser_process->IsShuttingDown() ||
217 !g_browser_process->profile_manager()->IsValidProfile(profile)) {
218 return;
221 extensions::ExtensionSystem* extension_system =
222 extensions::ExtensionSystem::Get(profile);
223 extensions::ExtensionRegistry* extension_registry =
224 extensions::ExtensionRegistry::Get(profile);
225 if (!extension_system || !extension_system->extension_service() ||
226 !extension_registry) {
227 return;
230 if (!extension_registry->GetExtensionById(
231 extension_id, extensions::ExtensionRegistry::TERMINATED)) {
232 // Either the app/extension was uninstalled by policy or it has since
233 // been restarted successfully by someone else (the user).
234 return;
236 extension_system->extension_service()->ReloadExtension(extension_id);
239 } // namespace
241 // Keys for the information we store about individual BackgroundContents in
242 // prefs. There is one top-level DictionaryValue (stored at
243 // prefs::kRegisteredBackgroundContents). Information about each
244 // BackgroundContents is stored under that top-level DictionaryValue, keyed
245 // by the parent application ID for easy lookup.
247 // kRegisteredBackgroundContents:
248 // DictionaryValue {
249 // <appid_1>: { "url": <url1>, "name": <frame_name> },
250 // <appid_2>: { "url": <url2>, "name": <frame_name> },
251 // ... etc ...
252 // }
253 const char kUrlKey[] = "url";
254 const char kFrameNameKey[] = "name";
256 int BackgroundContentsService::restart_delay_in_ms_ = 3000; // 3 seconds.
258 BackgroundContentsService::BackgroundContentsService(
259 Profile* profile,
260 const base::CommandLine* command_line)
261 : prefs_(NULL), extension_registry_observer_(this) {
262 // Don't load/store preferences if the parent profile is incognito.
263 if (!profile->IsOffTheRecord())
264 prefs_ = profile->GetPrefs();
266 // Listen for events to tell us when to load/unload persisted background
267 // contents.
268 StartObserving(profile);
271 BackgroundContentsService::~BackgroundContentsService() {
272 // BackgroundContents should be shutdown before we go away, as otherwise
273 // our browser process refcount will be off.
274 DCHECK(contents_map_.empty());
277 // static
278 void BackgroundContentsService::
279 SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(
280 int restart_delay_in_ms) {
281 restart_delay_in_ms_ = restart_delay_in_ms;
284 // static
285 std::string
286 BackgroundContentsService::GetNotificationDelegateIdForExtensionForTesting(
287 const std::string& extension_id) {
288 return kNotificationPrefix + extension_id;
291 // static
292 void BackgroundContentsService::ShowBalloonForTesting(
293 const extensions::Extension* extension,
294 Profile* profile) {
295 ShowBalloon(extension, profile);
298 // static
299 void BackgroundContentsService::DisableCloseBalloonForTesting(
300 bool disable_close_balloon_for_testing) {
301 g_disable_close_balloon_for_testing = disable_close_balloon_for_testing;
304 std::vector<BackgroundContents*>
305 BackgroundContentsService::GetBackgroundContents() const
307 std::vector<BackgroundContents*> contents;
308 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
309 it != contents_map_.end(); ++it)
310 contents.push_back(it->second.contents);
311 return contents;
314 void BackgroundContentsService::StartObserving(Profile* profile) {
315 // On startup, load our background pages after extension-apps have loaded.
316 registrar_.Add(this,
317 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
318 content::Source<Profile>(profile));
320 // Track the lifecycle of all BackgroundContents in the system to allow us
321 // to store an up-to-date list of the urls. Start tracking contents when they
322 // have been opened via CreateBackgroundContents(), and stop tracking them
323 // when they are closed by script.
324 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED,
325 content::Source<Profile>(profile));
327 // Stop tracking BackgroundContents when they have been deleted (happens
328 // during shutdown or if the render process dies).
329 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
330 content::Source<Profile>(profile));
332 // Track when the BackgroundContents navigates to a new URL so we can update
333 // our persisted information as appropriate.
334 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
335 content::Source<Profile>(profile));
337 // Track when the extensions crash so that the user can be notified
338 // about it, and the crashed contents can be restarted.
339 registrar_.Add(this,
340 extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
341 content::Source<Profile>(profile));
342 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED,
343 content::Source<Profile>(profile));
345 // Listen for extension uninstall, load, unloaded notification.
346 extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile));
349 void BackgroundContentsService::Observe(
350 int type,
351 const content::NotificationSource& source,
352 const content::NotificationDetails& details) {
353 TRACE_EVENT0("browser,startup", "BackgroundContentsService::Observe");
354 switch (type) {
355 case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: {
356 SCOPED_UMA_HISTOGRAM_TIMER(
357 "Extensions.BackgroundContentsServiceStartupTime");
358 Profile* profile = content::Source<Profile>(source).ptr();
359 LoadBackgroundContentsFromManifests(profile);
360 LoadBackgroundContentsFromPrefs(profile);
361 SendChangeNotification(profile);
362 break;
364 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED:
365 BackgroundContentsShutdown(
366 content::Details<BackgroundContents>(details).ptr());
367 SendChangeNotification(content::Source<Profile>(source).ptr());
368 break;
369 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED:
370 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
371 UnregisterBackgroundContents(
372 content::Details<BackgroundContents>(details).ptr());
373 // CLOSED is always followed by a DELETED notification so we'll send our
374 // change notification there.
375 break;
376 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: {
377 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
379 // Do not register in the pref if the extension has a manifest-specified
380 // background page.
381 BackgroundContents* bgcontents =
382 content::Details<BackgroundContents>(details).ptr();
383 Profile* profile = content::Source<Profile>(source).ptr();
384 const base::string16& appid = GetParentApplicationId(bgcontents);
385 ExtensionService* extension_service =
386 extensions::ExtensionSystem::Get(profile)->extension_service();
387 // extension_service can be NULL when running tests.
388 if (extension_service) {
389 const Extension* extension = extension_service->GetExtensionById(
390 base::UTF16ToUTF8(appid), false);
391 if (extension && BackgroundInfo::HasBackgroundPage(extension))
392 break;
394 RegisterBackgroundContents(bgcontents);
395 break;
397 case extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED:
398 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED: {
399 Profile* profile = content::Source<Profile>(source).ptr();
400 const Extension* extension = NULL;
401 if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED) {
402 BackgroundContents* bg =
403 content::Details<BackgroundContents>(details).ptr();
404 std::string extension_id = base::UTF16ToASCII(
405 BackgroundContentsServiceFactory::GetForProfile(profile)->
406 GetParentApplicationId(bg));
407 extension =
408 extensions::ExtensionSystem::Get(profile)->extension_service()->
409 GetExtensionById(extension_id, false);
410 } else {
411 extensions::ExtensionHost* extension_host =
412 content::Details<extensions::ExtensionHost>(details).ptr();
413 extension = extension_host->extension();
415 if (!extension)
416 break;
418 const bool force_installed =
419 extensions::Manifest::IsComponentLocation(extension->location()) ||
420 extensions::Manifest::IsPolicyLocation(extension->location());
421 if (!force_installed) {
422 ShowBalloon(extension, profile);
423 } else {
424 // Restart the extension.
425 RestartForceInstalledExtensionOnCrash(extension, profile);
427 break;
430 default:
431 NOTREACHED();
432 break;
436 void BackgroundContentsService::OnExtensionLoaded(
437 content::BrowserContext* browser_context,
438 const extensions::Extension* extension) {
439 Profile* profile = Profile::FromBrowserContext(browser_context);
440 if (extension->is_hosted_app() &&
441 BackgroundInfo::HasBackgroundPage(extension)) {
442 // If there is a background page specified in the manifest for a hosted
443 // app, then blow away registered urls in the pref.
444 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id()));
446 ExtensionService* service =
447 extensions::ExtensionSystem::Get(browser_context)->extension_service();
448 if (service && service->is_ready()) {
449 // Now load the manifest-specified background page. If service isn't
450 // ready, then the background page will be loaded from the
451 // EXTENSIONS_READY callback.
452 LoadBackgroundContents(profile,
453 BackgroundInfo::GetBackgroundURL(extension),
454 "background",
455 base::UTF8ToUTF16(extension->id()));
459 // Close the crash notification balloon for the app/extension, if any.
460 ScheduleCloseBalloon(extension->id(), profile);
461 SendChangeNotification(profile);
464 void BackgroundContentsService::OnExtensionUnloaded(
465 content::BrowserContext* browser_context,
466 const extensions::Extension* extension,
467 extensions::UnloadedExtensionInfo::Reason reason) {
468 switch (reason) {
469 case UnloadedExtensionInfo::REASON_DISABLE: // Fall through.
470 case UnloadedExtensionInfo::REASON_TERMINATE: // Fall through.
471 case UnloadedExtensionInfo::REASON_UNINSTALL: // Fall through.
472 case UnloadedExtensionInfo::REASON_BLACKLIST: // Fall through.
473 case UnloadedExtensionInfo::REASON_LOCK_ALL: // Fall through.
474 case UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN:
475 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id()));
476 SendChangeNotification(Profile::FromBrowserContext(browser_context));
477 return;
478 case UnloadedExtensionInfo::REASON_UPDATE: {
479 // If there is a manifest specified background page, then shut it down
480 // here, since if the updated extension still has the background page,
481 // then it will be loaded from LOADED callback. Otherwise, leave
482 // BackgroundContents in place.
483 // We don't call SendChangeNotification here - it will be generated
484 // from the LOADED callback.
485 if (BackgroundInfo::HasBackgroundPage(extension)) {
486 ShutdownAssociatedBackgroundContents(
487 base::ASCIIToUTF16(extension->id()));
489 return;
490 case UnloadedExtensionInfo::REASON_UNDEFINED:
491 // Fall through to undefined case.
492 break;
495 NOTREACHED() << "Undefined case " << reason;
496 return ShutdownAssociatedBackgroundContents(
497 base::ASCIIToUTF16(extension->id()));
500 void BackgroundContentsService::OnExtensionUninstalled(
501 content::BrowserContext* browser_context,
502 const extensions::Extension* extension,
503 extensions::UninstallReason reason) {
504 Profile* profile = Profile::FromBrowserContext(browser_context);
505 // Make sure the extension-crash balloons are removed when the extension is
506 // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed
507 // extension is unloaded immediately after the crash, not when user reloads or
508 // uninstalls the extension.
509 ScheduleCloseBalloon(extension->id(), profile);
512 void BackgroundContentsService::RestartForceInstalledExtensionOnCrash(
513 const Extension* extension,
514 Profile* profile) {
515 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
516 base::Bind(&ReloadExtension, extension->id(), profile),
517 base::TimeDelta::FromMilliseconds(restart_delay_in_ms_));
520 // Loads all background contents whose urls have been stored in prefs.
521 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
522 Profile* profile) {
523 if (!prefs_)
524 return;
525 const base::DictionaryValue* contents =
526 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
527 if (!contents)
528 return;
529 ExtensionService* extensions_service =
530 extensions::ExtensionSystem::Get(profile)->extension_service();
531 DCHECK(extensions_service);
532 for (base::DictionaryValue::Iterator it(*contents);
533 !it.IsAtEnd(); it.Advance()) {
534 // Check to make sure that the parent extension is still enabled.
535 const Extension* extension = extensions_service->
536 GetExtensionById(it.key(), false);
537 if (!extension) {
538 // We should never reach here - it should not be possible for an app
539 // to become uninstalled without the associated BackgroundContents being
540 // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a
541 // crash before we could save our prefs, or if the user deletes the
542 // extension files manually rather than uninstalling it.
543 NOTREACHED() << "No extension found for BackgroundContents - id = "
544 << it.key();
545 // Don't cancel out of our loop, just ignore this BackgroundContents and
546 // load the next one.
547 continue;
549 LoadBackgroundContentsFromDictionary(profile, it.key(), contents);
553 void BackgroundContentsService::SendChangeNotification(Profile* profile) {
554 content::NotificationService::current()->Notify(
555 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
556 content::Source<Profile>(profile),
557 content::Details<BackgroundContentsService>(this));
560 void BackgroundContentsService::LoadBackgroundContentsForExtension(
561 Profile* profile,
562 const std::string& extension_id) {
563 // First look if the manifest specifies a background page.
564 const Extension* extension =
565 extensions::ExtensionSystem::Get(profile)->extension_service()->
566 GetExtensionById(extension_id, false);
567 DCHECK(!extension || extension->is_hosted_app());
568 if (extension && BackgroundInfo::HasBackgroundPage(extension)) {
569 LoadBackgroundContents(profile,
570 BackgroundInfo::GetBackgroundURL(extension),
571 "background",
572 base::UTF8ToUTF16(extension->id()));
573 return;
576 // Now look in the prefs.
577 if (!prefs_)
578 return;
579 const base::DictionaryValue* contents =
580 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
581 if (!contents)
582 return;
583 LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
586 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
587 Profile* profile,
588 const std::string& extension_id,
589 const base::DictionaryValue* contents) {
590 ExtensionService* extensions_service =
591 extensions::ExtensionSystem::Get(profile)->extension_service();
592 DCHECK(extensions_service);
594 const base::DictionaryValue* dict;
595 if (!contents->GetDictionaryWithoutPathExpansion(extension_id, &dict) ||
596 dict == NULL)
597 return;
599 std::string frame_name;
600 std::string url;
601 dict->GetString(kUrlKey, &url);
602 dict->GetString(kFrameNameKey, &frame_name);
603 LoadBackgroundContents(profile,
604 GURL(url),
605 frame_name,
606 base::UTF8ToUTF16(extension_id));
609 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
610 Profile* profile) {
611 for (const scoped_refptr<const extensions::Extension>& extension :
612 extensions::ExtensionRegistry::Get(profile)->enabled_extensions()) {
613 if (extension->is_hosted_app() &&
614 BackgroundInfo::HasBackgroundPage(extension.get())) {
615 LoadBackgroundContents(
616 profile, BackgroundInfo::GetBackgroundURL(extension.get()),
617 "background", base::UTF8ToUTF16(extension->id()));
622 void BackgroundContentsService::LoadBackgroundContents(
623 Profile* profile,
624 const GURL& url,
625 const std::string& frame_name,
626 const base::string16& application_id) {
627 // We are depending on the fact that we will initialize before any user
628 // actions or session restore can take place, so no BackgroundContents should
629 // be running yet for the passed application_id.
630 DCHECK(!GetAppBackgroundContents(application_id));
631 DCHECK(!application_id.empty());
632 DCHECK(url.is_valid());
633 DVLOG(1) << "Loading background content url: " << url;
635 BackgroundContents* contents = CreateBackgroundContents(
636 SiteInstance::CreateForURL(profile, url),
637 MSG_ROUTING_NONE,
638 MSG_ROUTING_NONE,
639 profile,
640 frame_name,
641 application_id,
642 std::string(),
643 NULL);
645 contents->CreateRenderViewSoon(url);
648 BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
649 SiteInstance* site,
650 int routing_id,
651 int main_frame_route_id,
652 Profile* profile,
653 const std::string& frame_name,
654 const base::string16& application_id,
655 const std::string& partition_id,
656 content::SessionStorageNamespace* session_storage_namespace) {
657 BackgroundContents* contents = new BackgroundContents(
658 site, routing_id, main_frame_route_id, this, partition_id,
659 session_storage_namespace);
661 // Register the BackgroundContents internally, then send out a notification
662 // to external listeners.
663 BackgroundContentsOpenedDetails details = {contents,
664 frame_name,
665 application_id};
666 BackgroundContentsOpened(&details, profile);
667 content::NotificationService::current()->Notify(
668 chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
669 content::Source<Profile>(profile),
670 content::Details<BackgroundContentsOpenedDetails>(&details));
672 // A new background contents has been created - notify our listeners.
673 SendChangeNotification(profile);
674 return contents;
677 void BackgroundContentsService::RegisterBackgroundContents(
678 BackgroundContents* background_contents) {
679 DCHECK(IsTracked(background_contents));
680 if (!prefs_)
681 return;
683 // We store the first URL we receive for a given application. If there's
684 // already an entry for this application, no need to do anything.
685 // TODO(atwilson): Verify that this is the desired behavior based on developer
686 // feedback (http://crbug.com/47118).
687 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
688 base::DictionaryValue* pref = update.Get();
689 const base::string16& appid = GetParentApplicationId(background_contents);
690 base::DictionaryValue* current;
691 if (pref->GetDictionaryWithoutPathExpansion(base::UTF16ToUTF8(appid),
692 &current)) {
693 return;
696 // No entry for this application yet, so add one.
697 base::DictionaryValue* dict = new base::DictionaryValue();
698 dict->SetString(kUrlKey, background_contents->GetURL().spec());
699 dict->SetString(kFrameNameKey, contents_map_[appid].frame_name);
700 pref->SetWithoutPathExpansion(base::UTF16ToUTF8(appid), dict);
703 bool BackgroundContentsService::HasRegisteredBackgroundContents(
704 const base::string16& app_id) {
705 if (!prefs_)
706 return false;
707 std::string app = base::UTF16ToUTF8(app_id);
708 const base::DictionaryValue* contents =
709 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
710 return contents->HasKey(app);
713 void BackgroundContentsService::UnregisterBackgroundContents(
714 BackgroundContents* background_contents) {
715 if (!prefs_)
716 return;
717 DCHECK(IsTracked(background_contents));
718 const base::string16 appid = GetParentApplicationId(background_contents);
719 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
720 update.Get()->RemoveWithoutPathExpansion(base::UTF16ToUTF8(appid), NULL);
723 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
724 const base::string16& appid) {
725 BackgroundContents* contents = GetAppBackgroundContents(appid);
726 if (contents) {
727 UnregisterBackgroundContents(contents);
728 // Background contents destructor shuts down the renderer.
729 delete contents;
733 void BackgroundContentsService::BackgroundContentsOpened(
734 BackgroundContentsOpenedDetails* details,
735 Profile* profile) {
736 // Add the passed object to our list. Should not already be tracked.
737 DCHECK(!IsTracked(details->contents));
738 DCHECK(!details->application_id.empty());
739 contents_map_[details->application_id].contents = details->contents;
740 contents_map_[details->application_id].frame_name = details->frame_name;
742 ScheduleCloseBalloon(base::UTF16ToASCII(details->application_id), profile);
745 // Used by test code and debug checks to verify whether a given
746 // BackgroundContents is being tracked by this instance.
747 bool BackgroundContentsService::IsTracked(
748 BackgroundContents* background_contents) const {
749 return !GetParentApplicationId(background_contents).empty();
752 void BackgroundContentsService::BackgroundContentsShutdown(
753 BackgroundContents* background_contents) {
754 // Remove the passed object from our list.
755 DCHECK(IsTracked(background_contents));
756 base::string16 appid = GetParentApplicationId(background_contents);
757 contents_map_.erase(appid);
760 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
761 const base::string16& application_id) {
762 BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
763 return (it != contents_map_.end()) ? it->second.contents : NULL;
766 const base::string16& BackgroundContentsService::GetParentApplicationId(
767 BackgroundContents* contents) const {
768 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
769 it != contents_map_.end(); ++it) {
770 if (contents == it->second.contents)
771 return it->first;
773 return base::EmptyString16();
776 void BackgroundContentsService::AddWebContents(
777 WebContents* new_contents,
778 WindowOpenDisposition disposition,
779 const gfx::Rect& initial_rect,
780 bool user_gesture,
781 bool* was_blocked) {
782 Browser* browser = chrome::FindLastActiveWithProfile(
783 Profile::FromBrowserContext(new_contents->GetBrowserContext()),
784 chrome::GetActiveDesktop());
785 if (browser) {
786 chrome::AddWebContents(browser, NULL, new_contents, disposition,
787 initial_rect, user_gesture, was_blocked);