Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / background / background_contents_service.cc
blobd437f321d9daaf234c81ccb065ff1a4224778d5e
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/notification.h"
28 #include "chrome/browser/notifications/notification_delegate.h"
29 #include "chrome/browser/notifications/notification_ui_manager.h"
30 #include "chrome/browser/profiles/profile.h"
31 #include "chrome/browser/profiles/profile_manager.h"
32 #include "chrome/browser/ui/browser.h"
33 #include "chrome/browser/ui/browser_finder.h"
34 #include "chrome/browser/ui/browser_tabstrip.h"
35 #include "chrome/browser/ui/host_desktop.h"
36 #include "chrome/common/extensions/extension_constants.h"
37 #include "chrome/common/pref_names.h"
38 #include "chrome/grit/generated_resources.h"
39 #include "content/public/browser/notification_service.h"
40 #include "content/public/browser/site_instance.h"
41 #include "content/public/browser/web_contents.h"
42 #include "extensions/browser/extension_host.h"
43 #include "extensions/browser/extension_registry.h"
44 #include "extensions/browser/extension_system.h"
45 #include "extensions/browser/image_loader.h"
46 #include "extensions/browser/notification_types.h"
47 #include "extensions/common/constants.h"
48 #include "extensions/common/extension.h"
49 #include "extensions/common/extension_icon_set.h"
50 #include "extensions/common/extension_set.h"
51 #include "extensions/common/manifest_handlers/background_info.h"
52 #include "extensions/common/manifest_handlers/icons_handler.h"
53 #include "extensions/grit/extensions_browser_resources.h"
54 #include "ipc/ipc_message.h"
55 #include "ui/base/l10n/l10n_util.h"
56 #include "ui/base/resource/resource_bundle.h"
57 #include "ui/gfx/image/image.h"
59 #if defined(ENABLE_NOTIFICATIONS)
60 #include "ui/message_center/message_center.h"
61 #endif
63 using content::SiteInstance;
64 using content::WebContents;
65 using extensions::BackgroundInfo;
66 using extensions::Extension;
67 using extensions::UnloadedExtensionInfo;
69 namespace {
71 const char kNotificationPrefix[] = "app.background.crashed.";
72 bool g_disable_close_balloon_for_testing = false;
74 void CloseBalloon(const std::string& balloon_id, ProfileID profile_id) {
75 NotificationUIManager* notification_ui_manager =
76 g_browser_process->notification_ui_manager();
77 bool cancelled = notification_ui_manager->CancelById(balloon_id, profile_id);
78 if (cancelled) {
79 #if defined(ENABLE_NOTIFICATIONS)
80 // TODO(dewittj): Add this functionality to the notification UI manager's
81 // API.
82 g_browser_process->message_center()->SetVisibility(
83 message_center::VISIBILITY_TRANSIENT);
84 #endif
88 // Closes the crash notification balloon for the app/extension with this id.
89 void ScheduleCloseBalloon(const std::string& extension_id, Profile* profile) {
90 if (g_disable_close_balloon_for_testing)
91 return;
92 base::ThreadTaskRunnerHandle::Get()->PostTask(
93 FROM_HERE, base::Bind(&CloseBalloon, kNotificationPrefix + extension_id,
94 NotificationUIManager::GetProfileID(profile)));
97 // Delegate for the app/extension crash notification balloon. Restarts the
98 // app/extension when the balloon is clicked.
99 class CrashNotificationDelegate : public NotificationDelegate {
100 public:
101 CrashNotificationDelegate(Profile* profile,
102 const Extension* extension)
103 : profile_(profile),
104 is_hosted_app_(extension->is_hosted_app()),
105 is_platform_app_(extension->is_platform_app()),
106 extension_id_(extension->id()) {
109 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);
130 } else {
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, profile_);
140 bool HasClickedListener() override { return true; }
142 std::string id() const override {
143 return kNotificationPrefix + extension_id_;
146 private:
147 ~CrashNotificationDelegate() override {}
149 Profile* profile_;
150 bool is_hosted_app_;
151 bool is_platform_app_;
152 std::string extension_id_;
154 DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
157 #if defined(ENABLE_NOTIFICATIONS)
158 void NotificationImageReady(
159 const std::string extension_name,
160 const base::string16 message,
161 scoped_refptr<CrashNotificationDelegate> delegate,
162 Profile* profile,
163 const gfx::Image& icon) {
164 gfx::Image notification_icon(icon);
165 if (notification_icon.IsEmpty()) {
166 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
167 notification_icon = rb.GetImageNamed(IDR_EXTENSION_DEFAULT_ICON);
170 // Origin URL must be different from the crashed extension to avoid the
171 // conflict. NotificationSystemObserver will cancel all notifications from
172 // the same origin when NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED.
173 Notification notification(GURL("chrome://extension-crash"),
174 base::string16(),
175 message,
176 notification_icon,
177 base::string16(),
178 delegate->id(),
179 delegate.get());
181 g_browser_process->notification_ui_manager()->Add(notification, profile);
183 #endif
185 // Show a popup notification balloon with a crash message for a given app/
186 // extension.
187 void ShowBalloon(const Extension* extension, Profile* profile) {
188 #if defined(ENABLE_NOTIFICATIONS)
189 const base::string16 message = l10n_util::GetStringFUTF16(
190 extension->is_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE :
191 IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE,
192 base::UTF8ToUTF16(extension->name()));
193 extension_misc::ExtensionIcons size(extension_misc::EXTENSION_ICON_LARGE);
194 extensions::ExtensionResource resource =
195 extensions::IconsInfo::GetIconResource(
196 extension, size, ExtensionIconSet::MATCH_SMALLER);
197 // We can't just load the image in the Observe method below because, despite
198 // what this method is called, it may call the callback synchronously.
199 // However, it's possible that the extension went away during the interim,
200 // so we'll bind all the pertinent data here.
201 extensions::ImageLoader::Get(profile)->LoadImageAsync(
202 extension,
203 resource,
204 gfx::Size(size, size),
205 base::Bind(
206 &NotificationImageReady,
207 extension->name(),
208 message,
209 make_scoped_refptr(new CrashNotificationDelegate(profile, extension)),
210 profile));
211 #endif
214 void ReloadExtension(const std::string& extension_id, Profile* profile) {
215 if (g_browser_process->IsShuttingDown() ||
216 !g_browser_process->profile_manager()->IsValidProfile(profile)) {
217 return;
220 extensions::ExtensionSystem* extension_system =
221 extensions::ExtensionSystem::Get(profile);
222 extensions::ExtensionRegistry* extension_registry =
223 extensions::ExtensionRegistry::Get(profile);
224 if (!extension_system || !extension_system->extension_service() ||
225 !extension_registry) {
226 return;
229 if (!extension_registry->GetExtensionById(
230 extension_id, extensions::ExtensionRegistry::TERMINATED)) {
231 // Either the app/extension was uninstalled by policy or it has since
232 // been restarted successfully by someone else (the user).
233 return;
235 extension_system->extension_service()->ReloadExtension(extension_id);
238 } // namespace
240 // Keys for the information we store about individual BackgroundContents in
241 // prefs. There is one top-level DictionaryValue (stored at
242 // prefs::kRegisteredBackgroundContents). Information about each
243 // BackgroundContents is stored under that top-level DictionaryValue, keyed
244 // by the parent application ID for easy lookup.
246 // kRegisteredBackgroundContents:
247 // DictionaryValue {
248 // <appid_1>: { "url": <url1>, "name": <frame_name> },
249 // <appid_2>: { "url": <url2>, "name": <frame_name> },
250 // ... etc ...
251 // }
252 const char kUrlKey[] = "url";
253 const char kFrameNameKey[] = "name";
255 int BackgroundContentsService::restart_delay_in_ms_ = 3000; // 3 seconds.
257 BackgroundContentsService::BackgroundContentsService(
258 Profile* profile,
259 const base::CommandLine* command_line)
260 : prefs_(NULL), extension_registry_observer_(this) {
261 // Don't load/store preferences if the parent profile is incognito.
262 if (!profile->IsOffTheRecord())
263 prefs_ = profile->GetPrefs();
265 // Listen for events to tell us when to load/unload persisted background
266 // contents.
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());
276 // static
277 void BackgroundContentsService::
278 SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(
279 int restart_delay_in_ms) {
280 restart_delay_in_ms_ = restart_delay_in_ms;
283 // static
284 std::string
285 BackgroundContentsService::GetNotificationDelegateIdForExtensionForTesting(
286 const std::string& extension_id) {
287 return kNotificationPrefix + extension_id;
290 // static
291 void BackgroundContentsService::ShowBalloonForTesting(
292 const extensions::Extension* extension,
293 Profile* profile) {
294 ShowBalloon(extension, profile);
297 // static
298 void BackgroundContentsService::DisableCloseBalloonForTesting(
299 bool disable_close_balloon_for_testing) {
300 g_disable_close_balloon_for_testing = disable_close_balloon_for_testing;
303 std::vector<BackgroundContents*>
304 BackgroundContentsService::GetBackgroundContents() const
306 std::vector<BackgroundContents*> contents;
307 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
308 it != contents_map_.end(); ++it)
309 contents.push_back(it->second.contents);
310 return contents;
313 void BackgroundContentsService::StartObserving(Profile* profile) {
314 // On startup, load our background pages after extension-apps have loaded.
315 registrar_.Add(this,
316 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
317 content::Source<Profile>(profile));
319 // Track the lifecycle of all BackgroundContents in the system to allow us
320 // to store an up-to-date list of the urls. Start tracking contents when they
321 // have been opened via CreateBackgroundContents(), and stop tracking them
322 // when they are closed by script.
323 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED,
324 content::Source<Profile>(profile));
326 // Stop tracking BackgroundContents when they have been deleted (happens
327 // during shutdown or if the render process dies).
328 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
329 content::Source<Profile>(profile));
331 // Track when the BackgroundContents navigates to a new URL so we can update
332 // our persisted information as appropriate.
333 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
334 content::Source<Profile>(profile));
336 // Track when the extensions crash so that the user can be notified
337 // about it, and the crashed contents can be restarted.
338 registrar_.Add(this,
339 extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
340 content::Source<Profile>(profile));
341 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED,
342 content::Source<Profile>(profile));
344 // Listen for extension uninstall, load, unloaded notification.
345 extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile));
348 void BackgroundContentsService::Observe(
349 int type,
350 const content::NotificationSource& source,
351 const content::NotificationDetails& details) {
352 TRACE_EVENT0("browser,startup", "BackgroundContentsService::Observe");
353 switch (type) {
354 case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: {
355 SCOPED_UMA_HISTOGRAM_TIMER(
356 "Extensions.BackgroundContentsServiceStartupTime");
357 Profile* profile = content::Source<Profile>(source).ptr();
358 LoadBackgroundContentsFromManifests(profile);
359 LoadBackgroundContentsFromPrefs(profile);
360 SendChangeNotification(profile);
361 break;
363 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED:
364 BackgroundContentsShutdown(
365 content::Details<BackgroundContents>(details).ptr());
366 SendChangeNotification(content::Source<Profile>(source).ptr());
367 break;
368 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED:
369 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
370 UnregisterBackgroundContents(
371 content::Details<BackgroundContents>(details).ptr());
372 // CLOSED is always followed by a DELETED notification so we'll send our
373 // change notification there.
374 break;
375 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: {
376 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
378 // Do not register in the pref if the extension has a manifest-specified
379 // background page.
380 BackgroundContents* bgcontents =
381 content::Details<BackgroundContents>(details).ptr();
382 Profile* profile = content::Source<Profile>(source).ptr();
383 const base::string16& appid = GetParentApplicationId(bgcontents);
384 ExtensionService* extension_service =
385 extensions::ExtensionSystem::Get(profile)->extension_service();
386 // extension_service can be NULL when running tests.
387 if (extension_service) {
388 const Extension* extension = extension_service->GetExtensionById(
389 base::UTF16ToUTF8(appid), false);
390 if (extension && BackgroundInfo::HasBackgroundPage(extension))
391 break;
393 RegisterBackgroundContents(bgcontents);
394 break;
396 case extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED:
397 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED: {
398 Profile* profile = content::Source<Profile>(source).ptr();
399 const Extension* extension = NULL;
400 if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED) {
401 BackgroundContents* bg =
402 content::Details<BackgroundContents>(details).ptr();
403 std::string extension_id = base::UTF16ToASCII(
404 BackgroundContentsServiceFactory::GetForProfile(profile)->
405 GetParentApplicationId(bg));
406 extension =
407 extensions::ExtensionSystem::Get(profile)->extension_service()->
408 GetExtensionById(extension_id, false);
409 } else {
410 extensions::ExtensionHost* extension_host =
411 content::Details<extensions::ExtensionHost>(details).ptr();
412 extension = extension_host->extension();
414 if (!extension)
415 break;
417 const bool force_installed =
418 extensions::Manifest::IsComponentLocation(extension->location()) ||
419 extensions::Manifest::IsPolicyLocation(extension->location());
420 if (!force_installed) {
421 ShowBalloon(extension, profile);
422 } else {
423 // Restart the extension.
424 RestartForceInstalledExtensionOnCrash(extension, profile);
426 break;
429 default:
430 NOTREACHED();
431 break;
435 void BackgroundContentsService::OnExtensionLoaded(
436 content::BrowserContext* browser_context,
437 const extensions::Extension* extension) {
438 Profile* profile = Profile::FromBrowserContext(browser_context);
439 if (extension->is_hosted_app() &&
440 BackgroundInfo::HasBackgroundPage(extension)) {
441 // If there is a background page specified in the manifest for a hosted
442 // app, then blow away registered urls in the pref.
443 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id()));
445 ExtensionService* service =
446 extensions::ExtensionSystem::Get(browser_context)->extension_service();
447 if (service && service->is_ready()) {
448 // Now load the manifest-specified background page. If service isn't
449 // ready, then the background page will be loaded from the
450 // EXTENSIONS_READY callback.
451 LoadBackgroundContents(profile,
452 BackgroundInfo::GetBackgroundURL(extension),
453 "background",
454 base::UTF8ToUTF16(extension->id()));
458 // Close the crash notification balloon for the app/extension, if any.
459 ScheduleCloseBalloon(extension->id(), profile);
460 SendChangeNotification(profile);
463 void BackgroundContentsService::OnExtensionUnloaded(
464 content::BrowserContext* browser_context,
465 const extensions::Extension* extension,
466 extensions::UnloadedExtensionInfo::Reason reason) {
467 switch (reason) {
468 case UnloadedExtensionInfo::REASON_DISABLE: // Fall through.
469 case UnloadedExtensionInfo::REASON_TERMINATE: // Fall through.
470 case UnloadedExtensionInfo::REASON_UNINSTALL: // Fall through.
471 case UnloadedExtensionInfo::REASON_BLACKLIST: // Fall through.
472 case UnloadedExtensionInfo::REASON_LOCK_ALL: // Fall through.
473 case UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN:
474 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id()));
475 SendChangeNotification(Profile::FromBrowserContext(browser_context));
476 return;
477 case UnloadedExtensionInfo::REASON_UPDATE: {
478 // If there is a manifest specified background page, then shut it down
479 // here, since if the updated extension still has the background page,
480 // then it will be loaded from LOADED callback. Otherwise, leave
481 // BackgroundContents in place.
482 // We don't call SendChangeNotification here - it will be generated
483 // from the LOADED callback.
484 if (BackgroundInfo::HasBackgroundPage(extension)) {
485 ShutdownAssociatedBackgroundContents(
486 base::ASCIIToUTF16(extension->id()));
488 return;
489 case UnloadedExtensionInfo::REASON_UNDEFINED:
490 // Fall through to undefined case.
491 break;
494 NOTREACHED() << "Undefined case " << reason;
495 return ShutdownAssociatedBackgroundContents(
496 base::ASCIIToUTF16(extension->id()));
499 void BackgroundContentsService::OnExtensionUninstalled(
500 content::BrowserContext* browser_context,
501 const extensions::Extension* extension,
502 extensions::UninstallReason reason) {
503 Profile* profile = Profile::FromBrowserContext(browser_context);
504 // Make sure the extension-crash balloons are removed when the extension is
505 // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed
506 // extension is unloaded immediately after the crash, not when user reloads or
507 // uninstalls the extension.
508 ScheduleCloseBalloon(extension->id(), profile);
511 void BackgroundContentsService::RestartForceInstalledExtensionOnCrash(
512 const Extension* extension,
513 Profile* profile) {
514 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
515 base::Bind(&ReloadExtension, extension->id(), profile),
516 base::TimeDelta::FromMilliseconds(restart_delay_in_ms_));
519 // Loads all background contents whose urls have been stored in prefs.
520 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
521 Profile* profile) {
522 if (!prefs_)
523 return;
524 const base::DictionaryValue* contents =
525 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
526 if (!contents)
527 return;
528 ExtensionService* extensions_service =
529 extensions::ExtensionSystem::Get(profile)->extension_service();
530 DCHECK(extensions_service);
531 for (base::DictionaryValue::Iterator it(*contents);
532 !it.IsAtEnd(); it.Advance()) {
533 // Check to make sure that the parent extension is still enabled.
534 const Extension* extension = extensions_service->
535 GetExtensionById(it.key(), false);
536 if (!extension) {
537 // We should never reach here - it should not be possible for an app
538 // to become uninstalled without the associated BackgroundContents being
539 // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a
540 // crash before we could save our prefs, or if the user deletes the
541 // extension files manually rather than uninstalling it.
542 NOTREACHED() << "No extension found for BackgroundContents - id = "
543 << it.key();
544 // Don't cancel out of our loop, just ignore this BackgroundContents and
545 // load the next one.
546 continue;
548 LoadBackgroundContentsFromDictionary(profile, it.key(), contents);
552 void BackgroundContentsService::SendChangeNotification(Profile* profile) {
553 content::NotificationService::current()->Notify(
554 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
555 content::Source<Profile>(profile),
556 content::Details<BackgroundContentsService>(this));
559 void BackgroundContentsService::LoadBackgroundContentsForExtension(
560 Profile* profile,
561 const std::string& extension_id) {
562 // First look if the manifest specifies a background page.
563 const Extension* extension =
564 extensions::ExtensionSystem::Get(profile)->extension_service()->
565 GetExtensionById(extension_id, false);
566 DCHECK(!extension || extension->is_hosted_app());
567 if (extension && BackgroundInfo::HasBackgroundPage(extension)) {
568 LoadBackgroundContents(profile,
569 BackgroundInfo::GetBackgroundURL(extension),
570 "background",
571 base::UTF8ToUTF16(extension->id()));
572 return;
575 // Now look in the prefs.
576 if (!prefs_)
577 return;
578 const base::DictionaryValue* contents =
579 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
580 if (!contents)
581 return;
582 LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
585 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
586 Profile* profile,
587 const std::string& extension_id,
588 const base::DictionaryValue* contents) {
589 ExtensionService* extensions_service =
590 extensions::ExtensionSystem::Get(profile)->extension_service();
591 DCHECK(extensions_service);
593 const base::DictionaryValue* dict;
594 if (!contents->GetDictionaryWithoutPathExpansion(extension_id, &dict) ||
595 dict == NULL)
596 return;
598 std::string frame_name;
599 std::string url;
600 dict->GetString(kUrlKey, &url);
601 dict->GetString(kFrameNameKey, &frame_name);
602 LoadBackgroundContents(profile,
603 GURL(url),
604 frame_name,
605 base::UTF8ToUTF16(extension_id));
608 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
609 Profile* profile) {
610 for (const scoped_refptr<const extensions::Extension>& extension :
611 extensions::ExtensionRegistry::Get(profile)->enabled_extensions()) {
612 if (extension->is_hosted_app() &&
613 BackgroundInfo::HasBackgroundPage(extension.get())) {
614 LoadBackgroundContents(
615 profile, BackgroundInfo::GetBackgroundURL(extension.get()),
616 "background", base::UTF8ToUTF16(extension->id()));
621 void BackgroundContentsService::LoadBackgroundContents(
622 Profile* profile,
623 const GURL& url,
624 const std::string& frame_name,
625 const base::string16& application_id) {
626 // We are depending on the fact that we will initialize before any user
627 // actions or session restore can take place, so no BackgroundContents should
628 // be running yet for the passed application_id.
629 DCHECK(!GetAppBackgroundContents(application_id));
630 DCHECK(!application_id.empty());
631 DCHECK(url.is_valid());
632 DVLOG(1) << "Loading background content url: " << url;
634 BackgroundContents* contents = CreateBackgroundContents(
635 SiteInstance::CreateForURL(profile, url),
636 MSG_ROUTING_NONE,
637 MSG_ROUTING_NONE,
638 profile,
639 frame_name,
640 application_id,
641 std::string(),
642 NULL);
644 contents->CreateRenderViewSoon(url);
647 BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
648 SiteInstance* site,
649 int routing_id,
650 int main_frame_route_id,
651 Profile* profile,
652 const std::string& frame_name,
653 const base::string16& application_id,
654 const std::string& partition_id,
655 content::SessionStorageNamespace* session_storage_namespace) {
656 BackgroundContents* contents = new BackgroundContents(
657 site, routing_id, main_frame_route_id, this, partition_id,
658 session_storage_namespace);
660 // Register the BackgroundContents internally, then send out a notification
661 // to external listeners.
662 BackgroundContentsOpenedDetails details = {contents,
663 frame_name,
664 application_id};
665 BackgroundContentsOpened(&details, profile);
666 content::NotificationService::current()->Notify(
667 chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
668 content::Source<Profile>(profile),
669 content::Details<BackgroundContentsOpenedDetails>(&details));
671 // A new background contents has been created - notify our listeners.
672 SendChangeNotification(profile);
673 return contents;
676 void BackgroundContentsService::RegisterBackgroundContents(
677 BackgroundContents* background_contents) {
678 DCHECK(IsTracked(background_contents));
679 if (!prefs_)
680 return;
682 // We store the first URL we receive for a given application. If there's
683 // already an entry for this application, no need to do anything.
684 // TODO(atwilson): Verify that this is the desired behavior based on developer
685 // feedback (http://crbug.com/47118).
686 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
687 base::DictionaryValue* pref = update.Get();
688 const base::string16& appid = GetParentApplicationId(background_contents);
689 base::DictionaryValue* current;
690 if (pref->GetDictionaryWithoutPathExpansion(base::UTF16ToUTF8(appid),
691 &current)) {
692 return;
695 // No entry for this application yet, so add one.
696 base::DictionaryValue* dict = new base::DictionaryValue();
697 dict->SetString(kUrlKey, background_contents->GetURL().spec());
698 dict->SetString(kFrameNameKey, contents_map_[appid].frame_name);
699 pref->SetWithoutPathExpansion(base::UTF16ToUTF8(appid), dict);
702 bool BackgroundContentsService::HasRegisteredBackgroundContents(
703 const base::string16& app_id) {
704 if (!prefs_)
705 return false;
706 std::string app = base::UTF16ToUTF8(app_id);
707 const base::DictionaryValue* contents =
708 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
709 return contents->HasKey(app);
712 void BackgroundContentsService::UnregisterBackgroundContents(
713 BackgroundContents* background_contents) {
714 if (!prefs_)
715 return;
716 DCHECK(IsTracked(background_contents));
717 const base::string16 appid = GetParentApplicationId(background_contents);
718 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
719 update.Get()->RemoveWithoutPathExpansion(base::UTF16ToUTF8(appid), NULL);
722 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
723 const base::string16& appid) {
724 BackgroundContents* contents = GetAppBackgroundContents(appid);
725 if (contents) {
726 UnregisterBackgroundContents(contents);
727 // Background contents destructor shuts down the renderer.
728 delete contents;
732 void BackgroundContentsService::BackgroundContentsOpened(
733 BackgroundContentsOpenedDetails* details,
734 Profile* profile) {
735 // Add the passed object to our list. Should not already be tracked.
736 DCHECK(!IsTracked(details->contents));
737 DCHECK(!details->application_id.empty());
738 contents_map_[details->application_id].contents = details->contents;
739 contents_map_[details->application_id].frame_name = details->frame_name;
741 ScheduleCloseBalloon(base::UTF16ToASCII(details->application_id), profile);
744 // Used by test code and debug checks to verify whether a given
745 // BackgroundContents is being tracked by this instance.
746 bool BackgroundContentsService::IsTracked(
747 BackgroundContents* background_contents) const {
748 return !GetParentApplicationId(background_contents).empty();
751 void BackgroundContentsService::BackgroundContentsShutdown(
752 BackgroundContents* background_contents) {
753 // Remove the passed object from our list.
754 DCHECK(IsTracked(background_contents));
755 base::string16 appid = GetParentApplicationId(background_contents);
756 contents_map_.erase(appid);
759 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
760 const base::string16& application_id) {
761 BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
762 return (it != contents_map_.end()) ? it->second.contents : NULL;
765 const base::string16& BackgroundContentsService::GetParentApplicationId(
766 BackgroundContents* contents) const {
767 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
768 it != contents_map_.end(); ++it) {
769 if (contents == it->second.contents)
770 return it->first;
772 return base::EmptyString16();
775 void BackgroundContentsService::AddWebContents(
776 WebContents* new_contents,
777 WindowOpenDisposition disposition,
778 const gfx::Rect& initial_rect,
779 bool user_gesture,
780 bool* was_blocked) {
781 Browser* browser = chrome::FindLastActiveWithProfile(
782 Profile::FromBrowserContext(new_contents->GetBrowserContext()),
783 chrome::GetActiveDesktop());
784 if (browser) {
785 chrome::AddWebContents(browser, NULL, new_contents, disposition,
786 initial_rect, user_gesture, was_blocked);