This source file includes following definitions.
- CloseBalloon
- ScheduleCloseBalloon
- extension_id_
- Display
- Error
- Close
- Click
- HasClickedListener
- id
- GetRenderViewHost
- NotificationImageReady
- ShowBalloon
- ReloadExtension
- SetRestartDelayForForceInstalledAppsAndExtensionsForTesting
- GetNotificationIdForExtensionForTesting
- ShowBalloonForTesting
- GetBackgroundContents
- StartObserving
- Observe
- RestartForceInstalledExtensionOnCrash
- LoadBackgroundContentsFromPrefs
- SendChangeNotification
- LoadBackgroundContentsForExtension
- LoadBackgroundContentsFromDictionary
- LoadBackgroundContentsFromManifests
- LoadBackgroundContents
- CreateBackgroundContents
- RegisterBackgroundContents
- HasRegisteredBackgroundContents
- UnregisterBackgroundContents
- ShutdownAssociatedBackgroundContents
- BackgroundContentsOpened
- IsTracked
- BackgroundContentsShutdown
- GetAppBackgroundContents
- GetParentApplicationId
- AddWebContents
#include "chrome/browser/background/background_contents_service.h"
#include "apps/app_load_service.h"
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/message_loop/message_loop.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/background/background_contents_service_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/image_loader.h"
#include "chrome/browser/notifications/desktop_notification_service.h"
#include "chrome/browser/notifications/notification.h"
#include "chrome/browser/notifications/notification_delegate.h"
#include "chrome/browser/notifications/notification_ui_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/host_desktop.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_icon_set.h"
#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ipc/ipc_message.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
#if defined(ENABLE_NOTIFICATIONS)
#include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_util.h"
#endif
using content::SiteInstance;
using content::WebContents;
using extensions::BackgroundInfo;
using extensions::Extension;
using extensions::UnloadedExtensionInfo;
namespace {
const char kNotificationPrefix[] = "app.background.crashed.";
void CloseBalloon(const std::string& balloon_id) {
NotificationUIManager* notification_ui_manager =
g_browser_process->notification_ui_manager();
bool cancelled ALLOW_UNUSED = notification_ui_manager->CancelById(balloon_id);
#if defined(ENABLE_NOTIFICATIONS)
if (cancelled && message_center::IsRichNotificationEnabled()) {
g_browser_process->message_center()->SetVisibility(
message_center::VISIBILITY_TRANSIENT);
}
#endif
}
void ScheduleCloseBalloon(const std::string& extension_id) {
if (!base::MessageLoop::current())
return;
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(&CloseBalloon, kNotificationPrefix + extension_id));
}
class CrashNotificationDelegate : public NotificationDelegate {
public:
CrashNotificationDelegate(Profile* profile,
const Extension* extension)
: profile_(profile),
is_hosted_app_(extension->is_hosted_app()),
is_platform_app_(extension->is_platform_app()),
extension_id_(extension->id()) {
}
virtual void Display() OVERRIDE {}
virtual void Error() OVERRIDE {}
virtual void Close(bool by_user) OVERRIDE {}
virtual void Click() OVERRIDE {
std::string copied_extension_id = extension_id_;
if (is_hosted_app_) {
BackgroundContentsService* service =
BackgroundContentsServiceFactory::GetForProfile(profile_);
if (!service->GetAppBackgroundContents(
base::ASCIIToUTF16(copied_extension_id))) {
service->LoadBackgroundContentsForExtension(profile_,
copied_extension_id);
}
} else if (is_platform_app_) {
apps::AppLoadService::Get(profile_)->
RestartApplication(copied_extension_id);
} else {
extensions::ExtensionSystem::Get(profile_)->extension_service()->
ReloadExtension(copied_extension_id);
}
ScheduleCloseBalloon(copied_extension_id);
}
virtual bool HasClickedListener() OVERRIDE { return true; }
virtual std::string id() const OVERRIDE {
return kNotificationPrefix + extension_id_;
}
virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE {
return NULL;
}
private:
virtual ~CrashNotificationDelegate() {}
Profile* profile_;
bool is_hosted_app_;
bool is_platform_app_;
std::string extension_id_;
DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
};
#if defined(ENABLE_NOTIFICATIONS)
void NotificationImageReady(
const std::string extension_name,
const base::string16 message,
scoped_refptr<CrashNotificationDelegate> delegate,
Profile* profile,
const gfx::Image& icon) {
gfx::Image notification_icon(icon);
if (notification_icon.IsEmpty()) {
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
notification_icon = rb.GetImageNamed(IDR_EXTENSION_DEFAULT_ICON);
}
DesktopNotificationService::AddIconNotification(
GURL() ,
base::string16(),
message,
notification_icon,
base::string16(),
delegate.get(),
profile);
}
#endif
void ShowBalloon(const Extension* extension, Profile* profile) {
#if defined(ENABLE_NOTIFICATIONS)
const base::string16 message = l10n_util::GetStringFUTF16(
extension->is_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE :
IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE,
base::UTF8ToUTF16(extension->name()));
extension_misc::ExtensionIcons size(extension_misc::EXTENSION_ICON_MEDIUM);
extensions::ExtensionResource resource =
extensions::IconsInfo::GetIconResource(
extension, size, ExtensionIconSet::MATCH_SMALLER);
extensions::ImageLoader::Get(profile)->LoadImageAsync(
extension,
resource,
gfx::Size(size, size),
base::Bind(
&NotificationImageReady,
extension->name(),
message,
make_scoped_refptr(new CrashNotificationDelegate(profile, extension)),
profile));
#endif
}
void ReloadExtension(const std::string& extension_id, Profile* profile) {
if (g_browser_process->IsShuttingDown() ||
!g_browser_process->profile_manager()->IsValidProfile(profile)) {
return;
}
extensions::ExtensionSystem* extension_system =
extensions::ExtensionSystem::Get(profile);
extensions::ExtensionRegistry* extension_registry =
extensions::ExtensionRegistry::Get(profile);
if (!extension_system || !extension_system->extension_service() ||
!extension_registry) {
return;
}
if (!extension_registry->GetExtensionById(
extension_id, extensions::ExtensionRegistry::TERMINATED)) {
return;
}
extension_system->extension_service()->ReloadExtension(extension_id);
}
}
const char kUrlKey[] = "url";
const char kFrameNameKey[] = "name";
int BackgroundContentsService::restart_delay_in_ms_ = 3000;
BackgroundContentsService::BackgroundContentsService(
Profile* profile, const CommandLine* command_line)
: prefs_(NULL) {
if (!profile->IsOffTheRecord() &&
!command_line->HasSwitch(switches::kDisableRestoreBackgroundContents))
prefs_ = profile->GetPrefs();
StartObserving(profile);
}
BackgroundContentsService::~BackgroundContentsService() {
DCHECK(contents_map_.empty());
}
void BackgroundContentsService::
SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(
int restart_delay_in_ms) {
restart_delay_in_ms_ = restart_delay_in_ms;
}
std::string BackgroundContentsService::GetNotificationIdForExtensionForTesting(
const std::string& extension_id) {
return kNotificationPrefix + extension_id;
}
void BackgroundContentsService::ShowBalloonForTesting(
const extensions::Extension* extension,
Profile* profile) {
ShowBalloon(extension, profile);
}
std::vector<BackgroundContents*>
BackgroundContentsService::GetBackgroundContents() const
{
std::vector<BackgroundContents*> contents;
for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
it != contents_map_.end(); ++it)
contents.push_back(it->second.contents);
return contents;
}
void BackgroundContentsService::StartObserving(Profile* profile) {
registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
content::Source<Profile>(profile));
registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED,
content::Source<Profile>(profile));
registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
content::Source<Profile>(profile));
registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
content::Source<Profile>(profile));
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
content::Source<Profile>(profile));
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
content::Source<Profile>(profile));
registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED,
content::Source<Profile>(profile));
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
content::Source<Profile>(profile));
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
content::Source<Profile>(profile));
}
void BackgroundContentsService::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case chrome::NOTIFICATION_EXTENSIONS_READY: {
Profile* profile = content::Source<Profile>(source).ptr();
LoadBackgroundContentsFromManifests(profile);
LoadBackgroundContentsFromPrefs(profile);
SendChangeNotification(profile);
break;
}
case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED:
BackgroundContentsShutdown(
content::Details<BackgroundContents>(details).ptr());
SendChangeNotification(content::Source<Profile>(source).ptr());
break;
case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED:
DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
UnregisterBackgroundContents(
content::Details<BackgroundContents>(details).ptr());
break;
case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: {
DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
BackgroundContents* bgcontents =
content::Details<BackgroundContents>(details).ptr();
Profile* profile = content::Source<Profile>(source).ptr();
const base::string16& appid = GetParentApplicationId(bgcontents);
ExtensionService* extension_service =
extensions::ExtensionSystem::Get(profile)->extension_service();
if (extension_service) {
const Extension* extension = extension_service->GetExtensionById(
base::UTF16ToUTF8(appid), false);
if (extension && BackgroundInfo::HasBackgroundPage(extension))
break;
}
RegisterBackgroundContents(bgcontents);
break;
}
case chrome::NOTIFICATION_EXTENSION_LOADED: {
const Extension* extension =
content::Details<const Extension>(details).ptr();
Profile* profile = content::Source<Profile>(source).ptr();
if (extension->is_hosted_app() &&
BackgroundInfo::HasBackgroundPage(extension)) {
ShutdownAssociatedBackgroundContents(
base::ASCIIToUTF16(extension->id()));
ExtensionService* service =
extensions::ExtensionSystem::Get(profile)->extension_service();
if (service && service->is_ready()) {
LoadBackgroundContents(profile,
BackgroundInfo::GetBackgroundURL(extension),
base::ASCIIToUTF16("background"),
base::UTF8ToUTF16(extension->id()));
}
}
ScheduleCloseBalloon(extension->id());
SendChangeNotification(profile);
break;
}
case chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED:
case chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED: {
Profile* profile = content::Source<Profile>(source).ptr();
const Extension* extension = NULL;
if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED) {
BackgroundContents* bg =
content::Details<BackgroundContents>(details).ptr();
std::string extension_id = base::UTF16ToASCII(
BackgroundContentsServiceFactory::GetForProfile(profile)->
GetParentApplicationId(bg));
extension =
extensions::ExtensionSystem::Get(profile)->extension_service()->
GetExtensionById(extension_id, false);
} else {
extensions::ExtensionHost* extension_host =
content::Details<extensions::ExtensionHost>(details).ptr();
extension = extension_host->extension();
}
if (!extension)
break;
const bool force_installed =
extensions::Manifest::IsComponentLocation(extension->location()) ||
extensions::Manifest::IsPolicyLocation(extension->location());
if (!force_installed) {
ShowBalloon(extension, profile);
} else {
RestartForceInstalledExtensionOnCrash(extension, profile);
}
break;
}
case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED:
switch (content::Details<UnloadedExtensionInfo>(details)->reason) {
case UnloadedExtensionInfo::REASON_DISABLE:
case UnloadedExtensionInfo::REASON_TERMINATE:
case UnloadedExtensionInfo::REASON_UNINSTALL:
case UnloadedExtensionInfo::REASON_BLACKLIST:
ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(
content::Details<UnloadedExtensionInfo>(details)->
extension->id()));
SendChangeNotification(content::Source<Profile>(source).ptr());
break;
case UnloadedExtensionInfo::REASON_UPDATE: {
const Extension* extension =
content::Details<UnloadedExtensionInfo>(details)->extension;
if (BackgroundInfo::HasBackgroundPage(extension)) {
ShutdownAssociatedBackgroundContents(
base::ASCIIToUTF16(extension->id()));
}
break;
}
default:
NOTREACHED();
ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(
content::Details<UnloadedExtensionInfo>(details)->
extension->id()));
break;
}
break;
case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
ScheduleCloseBalloon(
content::Details<const Extension>(details).ptr()->id());
break;
}
default:
NOTREACHED();
break;
}
}
void BackgroundContentsService::RestartForceInstalledExtensionOnCrash(
const Extension* extension,
Profile* profile) {
base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
base::Bind(&ReloadExtension, extension->id(), profile),
base::TimeDelta::FromMilliseconds(restart_delay_in_ms_));
}
void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
Profile* profile) {
if (!prefs_)
return;
const base::DictionaryValue* contents =
prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
if (!contents)
return;
ExtensionService* extensions_service =
extensions::ExtensionSystem::Get(profile)->extension_service();
DCHECK(extensions_service);
for (base::DictionaryValue::Iterator it(*contents);
!it.IsAtEnd(); it.Advance()) {
const Extension* extension = extensions_service->
GetExtensionById(it.key(), false);
if (!extension) {
NOTREACHED() << "No extension found for BackgroundContents - id = "
<< it.key();
continue;
}
LoadBackgroundContentsFromDictionary(profile, it.key(), contents);
}
}
void BackgroundContentsService::SendChangeNotification(Profile* profile) {
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
content::Source<Profile>(profile),
content::Details<BackgroundContentsService>(this));
}
void BackgroundContentsService::LoadBackgroundContentsForExtension(
Profile* profile,
const std::string& extension_id) {
const Extension* extension =
extensions::ExtensionSystem::Get(profile)->extension_service()->
GetExtensionById(extension_id, false);
DCHECK(!extension || extension->is_hosted_app());
if (extension && BackgroundInfo::HasBackgroundPage(extension)) {
LoadBackgroundContents(profile,
BackgroundInfo::GetBackgroundURL(extension),
base::ASCIIToUTF16("background"),
base::UTF8ToUTF16(extension->id()));
return;
}
if (!prefs_)
return;
const base::DictionaryValue* contents =
prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
if (!contents)
return;
LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
}
void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
Profile* profile,
const std::string& extension_id,
const base::DictionaryValue* contents) {
ExtensionService* extensions_service =
extensions::ExtensionSystem::Get(profile)->extension_service();
DCHECK(extensions_service);
const base::DictionaryValue* dict;
if (!contents->GetDictionaryWithoutPathExpansion(extension_id, &dict) ||
dict == NULL)
return;
base::string16 frame_name;
std::string url;
dict->GetString(kUrlKey, &url);
dict->GetString(kFrameNameKey, &frame_name);
LoadBackgroundContents(profile,
GURL(url),
frame_name,
base::UTF8ToUTF16(extension_id));
}
void BackgroundContentsService::LoadBackgroundContentsFromManifests(
Profile* profile) {
const extensions::ExtensionSet* extensions =
extensions::ExtensionSystem::Get(profile)->
extension_service()->extensions();
for (extensions::ExtensionSet::const_iterator iter = extensions->begin();
iter != extensions->end(); ++iter) {
const Extension* extension = iter->get();
if (extension->is_hosted_app() &&
BackgroundInfo::HasBackgroundPage(extension)) {
LoadBackgroundContents(profile,
BackgroundInfo::GetBackgroundURL(extension),
base::ASCIIToUTF16("background"),
base::UTF8ToUTF16(extension->id()));
}
}
}
void BackgroundContentsService::LoadBackgroundContents(
Profile* profile,
const GURL& url,
const base::string16& frame_name,
const base::string16& application_id) {
DCHECK(!GetAppBackgroundContents(application_id));
DCHECK(!application_id.empty());
DCHECK(url.is_valid());
DVLOG(1) << "Loading background content url: " << url;
BackgroundContents* contents = CreateBackgroundContents(
SiteInstance::CreateForURL(profile, url),
MSG_ROUTING_NONE,
profile,
frame_name,
application_id,
std::string(),
NULL);
contents->web_contents()->GetController().LoadURL(
url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string());
}
BackgroundContents* BackgroundContentsService::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) {
BackgroundContents* contents = new BackgroundContents(
site, routing_id, this, partition_id, session_storage_namespace);
BackgroundContentsOpenedDetails details = {contents,
frame_name,
application_id};
BackgroundContentsOpened(&details);
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
content::Source<Profile>(profile),
content::Details<BackgroundContentsOpenedDetails>(&details));
SendChangeNotification(profile);
return contents;
}
void BackgroundContentsService::RegisterBackgroundContents(
BackgroundContents* background_contents) {
DCHECK(IsTracked(background_contents));
if (!prefs_)
return;
DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
base::DictionaryValue* pref = update.Get();
const base::string16& appid = GetParentApplicationId(background_contents);
base::DictionaryValue* current;
if (pref->GetDictionaryWithoutPathExpansion(base::UTF16ToUTF8(appid),
¤t)) {
return;
}
base::DictionaryValue* dict = new base::DictionaryValue();
dict->SetString(kUrlKey, background_contents->GetURL().spec());
dict->SetString(kFrameNameKey, contents_map_[appid].frame_name);
pref->SetWithoutPathExpansion(base::UTF16ToUTF8(appid), dict);
}
bool BackgroundContentsService::HasRegisteredBackgroundContents(
const base::string16& app_id) {
if (!prefs_)
return false;
std::string app = base::UTF16ToUTF8(app_id);
const base::DictionaryValue* contents =
prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
return contents->HasKey(app);
}
void BackgroundContentsService::UnregisterBackgroundContents(
BackgroundContents* background_contents) {
if (!prefs_)
return;
DCHECK(IsTracked(background_contents));
const base::string16 appid = GetParentApplicationId(background_contents);
DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
update.Get()->RemoveWithoutPathExpansion(base::UTF16ToUTF8(appid), NULL);
}
void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
const base::string16& appid) {
BackgroundContents* contents = GetAppBackgroundContents(appid);
if (contents) {
UnregisterBackgroundContents(contents);
delete contents;
}
}
void BackgroundContentsService::BackgroundContentsOpened(
BackgroundContentsOpenedDetails* details) {
DCHECK(!IsTracked(details->contents));
DCHECK(!details->application_id.empty());
contents_map_[details->application_id].contents = details->contents;
contents_map_[details->application_id].frame_name = details->frame_name;
ScheduleCloseBalloon(base::UTF16ToASCII(details->application_id));
}
bool BackgroundContentsService::IsTracked(
BackgroundContents* background_contents) const {
return !GetParentApplicationId(background_contents).empty();
}
void BackgroundContentsService::BackgroundContentsShutdown(
BackgroundContents* background_contents) {
DCHECK(IsTracked(background_contents));
base::string16 appid = GetParentApplicationId(background_contents);
contents_map_.erase(appid);
}
BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
const base::string16& application_id) {
BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
return (it != contents_map_.end()) ? it->second.contents : NULL;
}
const base::string16& BackgroundContentsService::GetParentApplicationId(
BackgroundContents* contents) const {
for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
it != contents_map_.end(); ++it) {
if (contents == it->second.contents)
return it->first;
}
return base::EmptyString16();
}
void BackgroundContentsService::AddWebContents(
WebContents* new_contents,
WindowOpenDisposition disposition,
const gfx::Rect& initial_pos,
bool user_gesture,
bool* was_blocked) {
Browser* browser = chrome::FindLastActiveWithProfile(
Profile::FromBrowserContext(new_contents->GetBrowserContext()),
chrome::GetActiveDesktop());
if (browser) {
chrome::AddWebContents(browser, NULL, new_contents, disposition,
initial_pos, user_gesture, was_blocked);
}
}