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