Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / background / background_contents_service.cc
blob90a5107c13d2863ff74c5b3ce371540c16904799
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/message_loop/message_loop.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/prefs/pref_service.h"
15 #include "base/prefs/scoped_user_pref_update.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/time/time.h"
19 #include "base/values.h"
20 #include "chrome/browser/background/background_contents_service_factory.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/browser/chrome_notification_types.h"
23 #include "chrome/browser/extensions/extension_service.h"
24 #include "chrome/browser/notifications/desktop_notification_service.h"
25 #include "chrome/browser/notifications/notification.h"
26 #include "chrome/browser/notifications/notification_delegate.h"
27 #include "chrome/browser/notifications/notification_ui_manager.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/browser/profiles/profile_manager.h"
30 #include "chrome/browser/ui/browser.h"
31 #include "chrome/browser/ui/browser_finder.h"
32 #include "chrome/browser/ui/browser_tabstrip.h"
33 #include "chrome/browser/ui/host_desktop.h"
34 #include "chrome/common/extensions/extension_constants.h"
35 #include "chrome/common/pref_names.h"
36 #include "chrome/grit/generated_resources.h"
37 #include "content/public/browser/notification_service.h"
38 #include "content/public/browser/site_instance.h"
39 #include "content/public/browser/web_contents.h"
40 #include "extensions/browser/extension_host.h"
41 #include "extensions/browser/extension_registry.h"
42 #include "extensions/browser/extension_system.h"
43 #include "extensions/browser/image_loader.h"
44 #include "extensions/browser/notification_types.h"
45 #include "extensions/common/constants.h"
46 #include "extensions/common/extension.h"
47 #include "extensions/common/extension_icon_set.h"
48 #include "extensions/common/extension_set.h"
49 #include "extensions/common/manifest_handlers/background_info.h"
50 #include "extensions/common/manifest_handlers/icons_handler.h"
51 #include "extensions/grit/extensions_browser_resources.h"
52 #include "ipc/ipc_message.h"
53 #include "ui/base/l10n/l10n_util.h"
54 #include "ui/base/resource/resource_bundle.h"
55 #include "ui/gfx/image/image.h"
57 #if defined(ENABLE_NOTIFICATIONS)
58 #include "ui/message_center/message_center.h"
59 #endif
61 using content::SiteInstance;
62 using content::WebContents;
63 using extensions::BackgroundInfo;
64 using extensions::Extension;
65 using extensions::UnloadedExtensionInfo;
67 namespace {
69 const char kNotificationPrefix[] = "app.background.crashed.";
71 void CloseBalloon(const std::string& balloon_id, ProfileID profile_id) {
72 NotificationUIManager* notification_ui_manager =
73 g_browser_process->notification_ui_manager();
74 bool cancelled = notification_ui_manager->CancelById(balloon_id, profile_id);
75 if (cancelled) {
76 #if defined(ENABLE_NOTIFICATIONS)
77 // TODO(dewittj): Add this functionality to the notification UI manager's
78 // API.
79 g_browser_process->message_center()->SetVisibility(
80 message_center::VISIBILITY_TRANSIENT);
81 #endif
85 // Closes the crash notification balloon for the app/extension with this id.
86 void ScheduleCloseBalloon(const std::string& extension_id, Profile* profile) {
87 if (!base::MessageLoop::current()) // For unit_tests
88 return;
89 base::MessageLoop::current()->PostTask(
90 FROM_HERE,
91 base::Bind(&CloseBalloon,
92 kNotificationPrefix + extension_id,
93 NotificationUIManager::GetProfileID(profile)));
96 // Delegate for the app/extension crash notification balloon. Restarts the
97 // app/extension when the balloon is clicked.
98 class CrashNotificationDelegate : public NotificationDelegate {
99 public:
100 CrashNotificationDelegate(Profile* profile,
101 const Extension* extension)
102 : profile_(profile),
103 is_hosted_app_(extension->is_hosted_app()),
104 is_platform_app_(extension->is_platform_app()),
105 extension_id_(extension->id()) {
108 void Click() override {
109 // http://crbug.com/247790 involves a crash notification balloon being
110 // clicked while the extension isn't in the TERMINATED state. In that case,
111 // any of the "reload" methods called below can unload the extension, which
112 // indirectly destroys *this, invalidating all the member variables, so we
113 // copy the extension ID before using it.
114 std::string copied_extension_id = extension_id_;
115 if (is_hosted_app_) {
116 // There can be a race here: user clicks the balloon, and simultaneously
117 // reloads the sad tab for the app. So we check here to be safe before
118 // loading the background page.
119 BackgroundContentsService* service =
120 BackgroundContentsServiceFactory::GetForProfile(profile_);
121 if (!service->GetAppBackgroundContents(
122 base::ASCIIToUTF16(copied_extension_id))) {
123 service->LoadBackgroundContentsForExtension(profile_,
124 copied_extension_id);
126 } else if (is_platform_app_) {
127 apps::AppLoadService::Get(profile_)->
128 RestartApplication(copied_extension_id);
129 } else {
130 extensions::ExtensionSystem::Get(profile_)->extension_service()->
131 ReloadExtension(copied_extension_id);
134 // Closing the crash notification balloon for the app/extension here should
135 // be OK, but it causes a crash on Mac, see: http://crbug.com/78167
136 ScheduleCloseBalloon(copied_extension_id, profile_);
139 bool HasClickedListener() override { return true; }
141 std::string id() const override {
142 return kNotificationPrefix + extension_id_;
145 private:
146 ~CrashNotificationDelegate() override {}
148 Profile* profile_;
149 bool is_hosted_app_;
150 bool is_platform_app_;
151 std::string extension_id_;
153 DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
156 #if defined(ENABLE_NOTIFICATIONS)
157 void NotificationImageReady(
158 const std::string extension_name,
159 const base::string16 message,
160 scoped_refptr<CrashNotificationDelegate> delegate,
161 Profile* profile,
162 const gfx::Image& icon) {
163 gfx::Image notification_icon(icon);
164 if (notification_icon.IsEmpty()) {
165 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
166 notification_icon = rb.GetImageNamed(IDR_EXTENSION_DEFAULT_ICON);
169 // Origin URL must be different from the crashed extension to avoid the
170 // conflict. NotificationSystemObserver will cancel all notifications from
171 // the same origin when NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED.
172 Notification notification(GURL("chrome://extension-crash"),
173 base::string16(),
174 message,
175 notification_icon,
176 base::string16(),
177 delegate->id(),
178 delegate.get());
180 g_browser_process->notification_ui_manager()->Add(notification, profile);
182 #endif
184 // Show a popup notification balloon with a crash message for a given app/
185 // extension.
186 void ShowBalloon(const Extension* extension, Profile* profile) {
187 #if defined(ENABLE_NOTIFICATIONS)
188 const base::string16 message = l10n_util::GetStringFUTF16(
189 extension->is_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE :
190 IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE,
191 base::UTF8ToUTF16(extension->name()));
192 extension_misc::ExtensionIcons size(extension_misc::EXTENSION_ICON_LARGE);
193 extensions::ExtensionResource resource =
194 extensions::IconsInfo::GetIconResource(
195 extension, size, ExtensionIconSet::MATCH_SMALLER);
196 // We can't just load the image in the Observe method below because, despite
197 // what this method is called, it may call the callback synchronously.
198 // However, it's possible that the extension went away during the interim,
199 // so we'll bind all the pertinent data here.
200 extensions::ImageLoader::Get(profile)->LoadImageAsync(
201 extension,
202 resource,
203 gfx::Size(size, size),
204 base::Bind(
205 &NotificationImageReady,
206 extension->name(),
207 message,
208 make_scoped_refptr(new CrashNotificationDelegate(profile, extension)),
209 profile));
210 #endif
213 void ReloadExtension(const std::string& extension_id, Profile* profile) {
214 if (g_browser_process->IsShuttingDown() ||
215 !g_browser_process->profile_manager()->IsValidProfile(profile)) {
216 return;
219 extensions::ExtensionSystem* extension_system =
220 extensions::ExtensionSystem::Get(profile);
221 extensions::ExtensionRegistry* extension_registry =
222 extensions::ExtensionRegistry::Get(profile);
223 if (!extension_system || !extension_system->extension_service() ||
224 !extension_registry) {
225 return;
228 if (!extension_registry->GetExtensionById(
229 extension_id, extensions::ExtensionRegistry::TERMINATED)) {
230 // Either the app/extension was uninstalled by policy or it has since
231 // been restarted successfully by someone else (the user).
232 return;
234 extension_system->extension_service()->ReloadExtension(extension_id);
237 } // namespace
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:
246 // DictionaryValue {
247 // <appid_1>: { "url": <url1>, "name": <frame_name> },
248 // <appid_2>: { "url": <url2>, "name": <frame_name> },
249 // ... etc ...
250 // }
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,
258 const base::CommandLine* command_line)
259 : prefs_(NULL), extension_registry_observer_(this) {
260 // Don't load/store preferences if the parent profile is incognito.
261 if (!profile->IsOffTheRecord())
262 prefs_ = profile->GetPrefs();
264 // Listen for events to tell us when to load/unload persisted background
265 // contents.
266 StartObserving(profile);
269 BackgroundContentsService::~BackgroundContentsService() {
270 // BackgroundContents should be shutdown before we go away, as otherwise
271 // our browser process refcount will be off.
272 DCHECK(contents_map_.empty());
275 // static
276 void BackgroundContentsService::
277 SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(
278 int restart_delay_in_ms) {
279 restart_delay_in_ms_ = restart_delay_in_ms;
282 // static
283 std::string
284 BackgroundContentsService::GetNotificationDelegateIdForExtensionForTesting(
285 const std::string& extension_id) {
286 return kNotificationPrefix + extension_id;
289 // static
290 void BackgroundContentsService::ShowBalloonForTesting(
291 const extensions::Extension* extension,
292 Profile* profile) {
293 ShowBalloon(extension, profile);
296 std::vector<BackgroundContents*>
297 BackgroundContentsService::GetBackgroundContents() const
299 std::vector<BackgroundContents*> contents;
300 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
301 it != contents_map_.end(); ++it)
302 contents.push_back(it->second.contents);
303 return contents;
306 void BackgroundContentsService::StartObserving(Profile* profile) {
307 // On startup, load our background pages after extension-apps have loaded.
308 registrar_.Add(this,
309 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
310 content::Source<Profile>(profile));
312 // Track the lifecycle of all BackgroundContents in the system to allow us
313 // to store an up-to-date list of the urls. Start tracking contents when they
314 // have been opened via CreateBackgroundContents(), and stop tracking them
315 // when they are closed by script.
316 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED,
317 content::Source<Profile>(profile));
319 // Stop tracking BackgroundContents when they have been deleted (happens
320 // during shutdown or if the render process dies).
321 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
322 content::Source<Profile>(profile));
324 // Track when the BackgroundContents navigates to a new URL so we can update
325 // our persisted information as appropriate.
326 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
327 content::Source<Profile>(profile));
329 // Track when the extensions crash so that the user can be notified
330 // about it, and the crashed contents can be restarted.
331 registrar_.Add(this,
332 extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
333 content::Source<Profile>(profile));
334 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED,
335 content::Source<Profile>(profile));
337 // Listen for extension uninstall, load, unloaded notification.
338 extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile));
341 void BackgroundContentsService::Observe(
342 int type,
343 const content::NotificationSource& source,
344 const content::NotificationDetails& details) {
345 TRACE_EVENT0("browser,startup", "BackgroundContentsService::Observe");
346 switch (type) {
347 case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: {
348 SCOPED_UMA_HISTOGRAM_TIMER(
349 "Extensions.BackgroundContentsServiceStartupTime");
350 Profile* profile = content::Source<Profile>(source).ptr();
351 LoadBackgroundContentsFromManifests(profile);
352 LoadBackgroundContentsFromPrefs(profile);
353 SendChangeNotification(profile);
354 break;
356 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED:
357 BackgroundContentsShutdown(
358 content::Details<BackgroundContents>(details).ptr());
359 SendChangeNotification(content::Source<Profile>(source).ptr());
360 break;
361 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED:
362 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
363 UnregisterBackgroundContents(
364 content::Details<BackgroundContents>(details).ptr());
365 // CLOSED is always followed by a DELETED notification so we'll send our
366 // change notification there.
367 break;
368 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: {
369 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
371 // Do not register in the pref if the extension has a manifest-specified
372 // background page.
373 BackgroundContents* bgcontents =
374 content::Details<BackgroundContents>(details).ptr();
375 Profile* profile = content::Source<Profile>(source).ptr();
376 const base::string16& appid = GetParentApplicationId(bgcontents);
377 ExtensionService* extension_service =
378 extensions::ExtensionSystem::Get(profile)->extension_service();
379 // extension_service can be NULL when running tests.
380 if (extension_service) {
381 const Extension* extension = extension_service->GetExtensionById(
382 base::UTF16ToUTF8(appid), false);
383 if (extension && BackgroundInfo::HasBackgroundPage(extension))
384 break;
386 RegisterBackgroundContents(bgcontents);
387 break;
389 case extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED:
390 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED: {
391 Profile* profile = content::Source<Profile>(source).ptr();
392 const Extension* extension = NULL;
393 if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED) {
394 BackgroundContents* bg =
395 content::Details<BackgroundContents>(details).ptr();
396 std::string extension_id = base::UTF16ToASCII(
397 BackgroundContentsServiceFactory::GetForProfile(profile)->
398 GetParentApplicationId(bg));
399 extension =
400 extensions::ExtensionSystem::Get(profile)->extension_service()->
401 GetExtensionById(extension_id, false);
402 } else {
403 extensions::ExtensionHost* extension_host =
404 content::Details<extensions::ExtensionHost>(details).ptr();
405 extension = extension_host->extension();
407 if (!extension)
408 break;
410 const bool force_installed =
411 extensions::Manifest::IsComponentLocation(extension->location()) ||
412 extensions::Manifest::IsPolicyLocation(extension->location());
413 if (!force_installed) {
414 ShowBalloon(extension, profile);
415 } else {
416 // Restart the extension.
417 RestartForceInstalledExtensionOnCrash(extension, profile);
419 break;
422 default:
423 NOTREACHED();
424 break;
428 void BackgroundContentsService::OnExtensionLoaded(
429 content::BrowserContext* browser_context,
430 const extensions::Extension* extension) {
431 Profile* profile = Profile::FromBrowserContext(browser_context);
432 if (extension->is_hosted_app() &&
433 BackgroundInfo::HasBackgroundPage(extension)) {
434 // If there is a background page specified in the manifest for a hosted
435 // app, then blow away registered urls in the pref.
436 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id()));
438 ExtensionService* service =
439 extensions::ExtensionSystem::Get(browser_context)->extension_service();
440 if (service && service->is_ready()) {
441 // Now load the manifest-specified background page. If service isn't
442 // ready, then the background page will be loaded from the
443 // EXTENSIONS_READY callback.
444 LoadBackgroundContents(profile,
445 BackgroundInfo::GetBackgroundURL(extension),
446 base::ASCIIToUTF16("background"),
447 base::UTF8ToUTF16(extension->id()));
451 // Close the crash notification balloon for the app/extension, if any.
452 ScheduleCloseBalloon(extension->id(), profile);
453 SendChangeNotification(profile);
456 void BackgroundContentsService::OnExtensionUnloaded(
457 content::BrowserContext* browser_context,
458 const extensions::Extension* extension,
459 extensions::UnloadedExtensionInfo::Reason reason) {
460 switch (reason) {
461 case UnloadedExtensionInfo::REASON_DISABLE: // Fall through.
462 case UnloadedExtensionInfo::REASON_TERMINATE: // Fall through.
463 case UnloadedExtensionInfo::REASON_UNINSTALL: // Fall through.
464 case UnloadedExtensionInfo::REASON_BLACKLIST: // Fall through.
465 case UnloadedExtensionInfo::REASON_LOCK_ALL: // Fall through.
466 case UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN:
467 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id()));
468 SendChangeNotification(Profile::FromBrowserContext(browser_context));
469 return;
470 case UnloadedExtensionInfo::REASON_UPDATE: {
471 // If there is a manifest specified background page, then shut it down
472 // here, since if the updated extension still has the background page,
473 // then it will be loaded from LOADED callback. Otherwise, leave
474 // BackgroundContents in place.
475 // We don't call SendChangeNotification here - it will be generated
476 // from the LOADED callback.
477 if (BackgroundInfo::HasBackgroundPage(extension)) {
478 ShutdownAssociatedBackgroundContents(
479 base::ASCIIToUTF16(extension->id()));
481 return;
482 case UnloadedExtensionInfo::REASON_UNDEFINED:
483 // Fall through to undefined case.
484 break;
487 NOTREACHED() << "Undefined case " << reason;
488 return ShutdownAssociatedBackgroundContents(
489 base::ASCIIToUTF16(extension->id()));
492 void BackgroundContentsService::OnExtensionUninstalled(
493 content::BrowserContext* browser_context,
494 const extensions::Extension* extension,
495 extensions::UninstallReason reason) {
496 Profile* profile = Profile::FromBrowserContext(browser_context);
497 // Make sure the extension-crash balloons are removed when the extension is
498 // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed
499 // extension is unloaded immediately after the crash, not when user reloads or
500 // uninstalls the extension.
501 ScheduleCloseBalloon(extension->id(), profile);
504 void BackgroundContentsService::RestartForceInstalledExtensionOnCrash(
505 const Extension* extension,
506 Profile* profile) {
507 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
508 base::Bind(&ReloadExtension, extension->id(), profile),
509 base::TimeDelta::FromMilliseconds(restart_delay_in_ms_));
512 // Loads all background contents whose urls have been stored in prefs.
513 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
514 Profile* profile) {
515 if (!prefs_)
516 return;
517 const base::DictionaryValue* contents =
518 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
519 if (!contents)
520 return;
521 ExtensionService* extensions_service =
522 extensions::ExtensionSystem::Get(profile)->extension_service();
523 DCHECK(extensions_service);
524 for (base::DictionaryValue::Iterator it(*contents);
525 !it.IsAtEnd(); it.Advance()) {
526 // Check to make sure that the parent extension is still enabled.
527 const Extension* extension = extensions_service->
528 GetExtensionById(it.key(), false);
529 if (!extension) {
530 // We should never reach here - it should not be possible for an app
531 // to become uninstalled without the associated BackgroundContents being
532 // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a
533 // crash before we could save our prefs, or if the user deletes the
534 // extension files manually rather than uninstalling it.
535 NOTREACHED() << "No extension found for BackgroundContents - id = "
536 << it.key();
537 // Don't cancel out of our loop, just ignore this BackgroundContents and
538 // load the next one.
539 continue;
541 LoadBackgroundContentsFromDictionary(profile, it.key(), contents);
545 void BackgroundContentsService::SendChangeNotification(Profile* profile) {
546 content::NotificationService::current()->Notify(
547 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
548 content::Source<Profile>(profile),
549 content::Details<BackgroundContentsService>(this));
552 void BackgroundContentsService::LoadBackgroundContentsForExtension(
553 Profile* profile,
554 const std::string& extension_id) {
555 // First look if the manifest specifies a background page.
556 const Extension* extension =
557 extensions::ExtensionSystem::Get(profile)->extension_service()->
558 GetExtensionById(extension_id, false);
559 DCHECK(!extension || extension->is_hosted_app());
560 if (extension && BackgroundInfo::HasBackgroundPage(extension)) {
561 LoadBackgroundContents(profile,
562 BackgroundInfo::GetBackgroundURL(extension),
563 base::ASCIIToUTF16("background"),
564 base::UTF8ToUTF16(extension->id()));
565 return;
568 // Now look in the prefs.
569 if (!prefs_)
570 return;
571 const base::DictionaryValue* contents =
572 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
573 if (!contents)
574 return;
575 LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
578 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
579 Profile* profile,
580 const std::string& extension_id,
581 const base::DictionaryValue* contents) {
582 ExtensionService* extensions_service =
583 extensions::ExtensionSystem::Get(profile)->extension_service();
584 DCHECK(extensions_service);
586 const base::DictionaryValue* dict;
587 if (!contents->GetDictionaryWithoutPathExpansion(extension_id, &dict) ||
588 dict == NULL)
589 return;
591 base::string16 frame_name;
592 std::string url;
593 dict->GetString(kUrlKey, &url);
594 dict->GetString(kFrameNameKey, &frame_name);
595 LoadBackgroundContents(profile,
596 GURL(url),
597 frame_name,
598 base::UTF8ToUTF16(extension_id));
601 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
602 Profile* profile) {
603 for (const scoped_refptr<const extensions::Extension>& extension :
604 extensions::ExtensionRegistry::Get(profile)->enabled_extensions()) {
605 if (extension->is_hosted_app() &&
606 BackgroundInfo::HasBackgroundPage(extension.get())) {
607 LoadBackgroundContents(
608 profile, BackgroundInfo::GetBackgroundURL(extension.get()),
609 base::ASCIIToUTF16("background"), base::UTF8ToUTF16(extension->id()));
614 void BackgroundContentsService::LoadBackgroundContents(
615 Profile* profile,
616 const GURL& url,
617 const base::string16& frame_name,
618 const base::string16& application_id) {
619 // We are depending on the fact that we will initialize before any user
620 // actions or session restore can take place, so no BackgroundContents should
621 // be running yet for the passed application_id.
622 DCHECK(!GetAppBackgroundContents(application_id));
623 DCHECK(!application_id.empty());
624 DCHECK(url.is_valid());
625 DVLOG(1) << "Loading background content url: " << url;
627 BackgroundContents* contents = CreateBackgroundContents(
628 SiteInstance::CreateForURL(profile, url),
629 MSG_ROUTING_NONE,
630 MSG_ROUTING_NONE,
631 profile,
632 frame_name,
633 application_id,
634 std::string(),
635 NULL);
637 contents->CreateRenderViewSoon(url);
640 BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
641 SiteInstance* site,
642 int routing_id,
643 int main_frame_route_id,
644 Profile* profile,
645 const base::string16& frame_name,
646 const base::string16& application_id,
647 const std::string& partition_id,
648 content::SessionStorageNamespace* session_storage_namespace) {
649 BackgroundContents* contents = new BackgroundContents(
650 site, routing_id, main_frame_route_id, this, partition_id,
651 session_storage_namespace);
653 // Register the BackgroundContents internally, then send out a notification
654 // to external listeners.
655 BackgroundContentsOpenedDetails details = {contents,
656 frame_name,
657 application_id};
658 BackgroundContentsOpened(&details, profile);
659 content::NotificationService::current()->Notify(
660 chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
661 content::Source<Profile>(profile),
662 content::Details<BackgroundContentsOpenedDetails>(&details));
664 // A new background contents has been created - notify our listeners.
665 SendChangeNotification(profile);
666 return contents;
669 void BackgroundContentsService::RegisterBackgroundContents(
670 BackgroundContents* background_contents) {
671 DCHECK(IsTracked(background_contents));
672 if (!prefs_)
673 return;
675 // We store the first URL we receive for a given application. If there's
676 // already an entry for this application, no need to do anything.
677 // TODO(atwilson): Verify that this is the desired behavior based on developer
678 // feedback (http://crbug.com/47118).
679 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
680 base::DictionaryValue* pref = update.Get();
681 const base::string16& appid = GetParentApplicationId(background_contents);
682 base::DictionaryValue* current;
683 if (pref->GetDictionaryWithoutPathExpansion(base::UTF16ToUTF8(appid),
684 &current)) {
685 return;
688 // No entry for this application yet, so add one.
689 base::DictionaryValue* dict = new base::DictionaryValue();
690 dict->SetString(kUrlKey, background_contents->GetURL().spec());
691 dict->SetString(kFrameNameKey, contents_map_[appid].frame_name);
692 pref->SetWithoutPathExpansion(base::UTF16ToUTF8(appid), dict);
695 bool BackgroundContentsService::HasRegisteredBackgroundContents(
696 const base::string16& app_id) {
697 if (!prefs_)
698 return false;
699 std::string app = base::UTF16ToUTF8(app_id);
700 const base::DictionaryValue* contents =
701 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
702 return contents->HasKey(app);
705 void BackgroundContentsService::UnregisterBackgroundContents(
706 BackgroundContents* background_contents) {
707 if (!prefs_)
708 return;
709 DCHECK(IsTracked(background_contents));
710 const base::string16 appid = GetParentApplicationId(background_contents);
711 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
712 update.Get()->RemoveWithoutPathExpansion(base::UTF16ToUTF8(appid), NULL);
715 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
716 const base::string16& appid) {
717 BackgroundContents* contents = GetAppBackgroundContents(appid);
718 if (contents) {
719 UnregisterBackgroundContents(contents);
720 // Background contents destructor shuts down the renderer.
721 delete contents;
725 void BackgroundContentsService::BackgroundContentsOpened(
726 BackgroundContentsOpenedDetails* details,
727 Profile* profile) {
728 // Add the passed object to our list. Should not already be tracked.
729 DCHECK(!IsTracked(details->contents));
730 DCHECK(!details->application_id.empty());
731 contents_map_[details->application_id].contents = details->contents;
732 contents_map_[details->application_id].frame_name = details->frame_name;
734 ScheduleCloseBalloon(base::UTF16ToASCII(details->application_id), profile);
737 // Used by test code and debug checks to verify whether a given
738 // BackgroundContents is being tracked by this instance.
739 bool BackgroundContentsService::IsTracked(
740 BackgroundContents* background_contents) const {
741 return !GetParentApplicationId(background_contents).empty();
744 void BackgroundContentsService::BackgroundContentsShutdown(
745 BackgroundContents* background_contents) {
746 // Remove the passed object from our list.
747 DCHECK(IsTracked(background_contents));
748 base::string16 appid = GetParentApplicationId(background_contents);
749 contents_map_.erase(appid);
752 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
753 const base::string16& application_id) {
754 BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
755 return (it != contents_map_.end()) ? it->second.contents : NULL;
758 const base::string16& BackgroundContentsService::GetParentApplicationId(
759 BackgroundContents* contents) const {
760 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
761 it != contents_map_.end(); ++it) {
762 if (contents == it->second.contents)
763 return it->first;
765 return base::EmptyString16();
768 void BackgroundContentsService::AddWebContents(
769 WebContents* new_contents,
770 WindowOpenDisposition disposition,
771 const gfx::Rect& initial_rect,
772 bool user_gesture,
773 bool* was_blocked) {
774 Browser* browser = chrome::FindLastActiveWithProfile(
775 Profile::FromBrowserContext(new_contents->GetBrowserContext()),
776 chrome::GetActiveDesktop());
777 if (browser) {
778 chrome::AddWebContents(browser, NULL, new_contents, disposition,
779 initial_rect, user_gesture, was_blocked);