Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2011 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_contents_service.h"
      6 
      7 #include "base/basictypes.h"
      8 #include "base/command_line.h"
      9 #include "base/string_util.h"
     10 #include "base/utf_string_conversions.h"
     11 #include "base/values.h"
     12 #include "chrome/browser/background_contents_service_factory.h"
     13 #include "chrome/browser/browser_process.h"
     14 #include "chrome/browser/extensions/extension_host.h"
     15 #include "chrome/browser/extensions/extension_service.h"
     16 #include "chrome/browser/notifications/desktop_notification_service.h"
     17 #include "chrome/browser/notifications/notification.h"
     18 #include "chrome/browser/notifications/notification_ui_manager.h"
     19 #include "chrome/browser/prefs/pref_service.h"
     20 #include "chrome/browser/prefs/scoped_user_pref_update.h"
     21 #include "chrome/browser/profiles/profile.h"
     22 #include "chrome/browser/ui/browser.h"
     23 #include "chrome/browser/ui/browser_list.h"
     24 #include "chrome/common/chrome_switches.h"
     25 #include "chrome/common/extensions/extension.h"
     26 #include "chrome/common/pref_names.h"
     27 #include "content/browser/renderer_host/render_view_host.h"
     28 #include "content/browser/site_instance.h"
     29 #include "content/browser/tab_contents/tab_contents.h"
     30 #include "content/common/notification_service.h"
     31 #include "content/common/notification_type.h"
     32 #include "grit/generated_resources.h"
     33 #include "ui/base/l10n/l10n_util.h"
     34 
     35 namespace {
     36 
     37 const char kNotificationPrefix[] = "app.background.crashed.";
     38 
     39 void CloseBalloon(const std::string id) {
     40   g_browser_process->notification_ui_manager()->CancelById(id);
     41 }
     42 
     43 void ScheduleCloseBalloon(const std::string& extension_id) {
     44   MessageLoop::current()->PostTask(FROM_HERE,
     45       NewRunnableFunction(&CloseBalloon,
     46           kNotificationPrefix + extension_id));
     47 }
     48 
     49 class CrashNotificationDelegate : public NotificationDelegate {
     50  public:
     51   CrashNotificationDelegate(Profile* profile, const Extension* extension)
     52       : profile_(profile),
     53         is_hosted_app_(extension->is_hosted_app()),
     54         extension_id_(extension->id()) {
     55   }
     56 
     57   ~CrashNotificationDelegate() {
     58   }
     59 
     60   void Display() {}
     61 
     62   void Error() {}
     63 
     64   void Close(bool by_user) {}
     65 
     66   void Click() {
     67     if (is_hosted_app_) {
     68       BackgroundContentsServiceFactory::GetForProfile(profile_)->
     69           LoadBackgroundContentsForExtension(profile_, extension_id_);
     70     } else {
     71       profile_->GetExtensionService()->ReloadExtension(extension_id_);
     72     }
     73 
     74     // Closing the balloon here should be OK, but it causes a crash on Mac
     75     // http://crbug.com/78167
     76     ScheduleCloseBalloon(extension_id_);
     77   }
     78 
     79   std::string id() const {
     80     return kNotificationPrefix + extension_id_;
     81   }
     82 
     83  private:
     84   Profile* profile_;
     85   bool is_hosted_app_;
     86   std::string extension_id_;
     87 
     88   DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
     89 };
     90 
     91 void ShowBalloon(const Extension* extension, Profile* profile) {
     92   string16 message = l10n_util::GetStringFUTF16(
     93       extension->is_hosted_app() ?  IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE :
     94       IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE,
     95       UTF8ToUTF16(extension->name()));
     96   string16 content_url = DesktopNotificationService::CreateDataUrl(
     97       extension->GetIconURL(Extension::EXTENSION_ICON_SMALLISH,
     98                             ExtensionIconSet::MATCH_BIGGER),
     99       string16(), message, WebKit::WebTextDirectionDefault);
    100   Notification notification(
    101       extension->url(), GURL(content_url), string16(), string16(),
    102       new CrashNotificationDelegate(profile, extension));
    103   g_browser_process->notification_ui_manager()->Add(notification, profile);
    104 }
    105 
    106 }
    107 
    108 // Keys for the information we store about individual BackgroundContents in
    109 // prefs. There is one top-level DictionaryValue (stored at
    110 // prefs::kRegisteredBackgroundContents). Information about each
    111 // BackgroundContents is stored under that top-level DictionaryValue, keyed
    112 // by the parent application ID for easy lookup.
    113 //
    114 // kRegisteredBackgroundContents:
    115 //    DictionaryValue {
    116 //       <appid_1>: { "url": <url1>, "name": <frame_name> },
    117 //       <appid_2>: { "url": <url2>, "name": <frame_name> },
    118 //         ... etc ...
    119 //    }
    120 const char kUrlKey[] = "url";
    121 const char kFrameNameKey[] = "name";
    122 
    123 BackgroundContentsService::BackgroundContentsService(
    124     Profile* profile, const CommandLine* command_line)
    125     : prefs_(NULL) {
    126   // Don't load/store preferences if the proper switch is not enabled, or if
    127   // the parent profile is incognito.
    128   if (!profile->IsOffTheRecord() &&
    129       !command_line->HasSwitch(switches::kDisableRestoreBackgroundContents))
    130     prefs_ = profile->GetPrefs();
    131 
    132   // Listen for events to tell us when to load/unload persisted background
    133   // contents.
    134   StartObserving(profile);
    135 }
    136 
    137 BackgroundContentsService::~BackgroundContentsService() {
    138   // BackgroundContents should be shutdown before we go away, as otherwise
    139   // our browser process refcount will be off.
    140   DCHECK(contents_map_.empty());
    141 }
    142 
    143 std::vector<BackgroundContents*>
    144 BackgroundContentsService::GetBackgroundContents() const
    145 {
    146   std::vector<BackgroundContents*> contents;
    147   for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
    148        it != contents_map_.end(); ++it)
    149     contents.push_back(it->second.contents);
    150   return contents;
    151 }
    152 
    153 void BackgroundContentsService::StartObserving(Profile* profile) {
    154   // On startup, load our background pages after extension-apps have loaded.
    155   registrar_.Add(this, NotificationType::EXTENSIONS_READY,
    156                  Source<Profile>(profile));
    157 
    158   // Track the lifecycle of all BackgroundContents in the system to allow us
    159   // to store an up-to-date list of the urls. Start tracking contents when they
    160   // have been opened via CreateBackgroundContents(), and stop tracking them
    161   // when they are closed by script.
    162   registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_CLOSED,
    163                  Source<Profile>(profile));
    164 
    165   // Stop tracking BackgroundContents when they have been deleted (happens
    166   // during shutdown or if the render process dies).
    167   registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_DELETED,
    168                  Source<Profile>(profile));
    169 
    170   // Track when the BackgroundContents navigates to a new URL so we can update
    171   // our persisted information as appropriate.
    172   registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_NAVIGATED,
    173                  Source<Profile>(profile));
    174 
    175   // Listen for new extension installs so that we can load any associated
    176   // background page.
    177   registrar_.Add(this, NotificationType::EXTENSION_LOADED,
    178                  Source<Profile>(profile));
    179 
    180   // Track when the extensions crash so that the user can be notified
    181   // about it, and the crashed contents can be restarted.
    182   registrar_.Add(this, NotificationType::EXTENSION_PROCESS_TERMINATED,
    183                  Source<Profile>(profile));
    184   registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_TERMINATED,
    185                  Source<Profile>(profile));
    186 
    187   // Listen for extensions to be unloaded so we can shutdown associated
    188   // BackgroundContents.
    189   registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
    190                  Source<Profile>(profile));
    191 
    192   // Make sure the extension-crash balloons are removed when the extension is
    193   // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed
    194   // extension is unloaded immediately after the crash, not when user reloads or
    195   // uninstalls the extension.
    196   registrar_.Add(this, NotificationType::EXTENSION_UNINSTALLED,
    197                  Source<Profile>(profile));
    198 }
    199 
    200 void BackgroundContentsService::Observe(NotificationType type,
    201                                         const NotificationSource& source,
    202                                         const NotificationDetails& details) {
    203   switch (type.value) {
    204     case NotificationType::EXTENSIONS_READY:
    205       LoadBackgroundContentsFromManifests(Source<Profile>(source).ptr());
    206       LoadBackgroundContentsFromPrefs(Source<Profile>(source).ptr());
    207       break;
    208     case NotificationType::BACKGROUND_CONTENTS_DELETED:
    209       BackgroundContentsShutdown(Details<BackgroundContents>(details).ptr());
    210       break;
    211     case NotificationType::BACKGROUND_CONTENTS_CLOSED:
    212       DCHECK(IsTracked(Details<BackgroundContents>(details).ptr()));
    213       UnregisterBackgroundContents(Details<BackgroundContents>(details).ptr());
    214       break;
    215     case NotificationType::BACKGROUND_CONTENTS_NAVIGATED: {
    216       DCHECK(IsTracked(Details<BackgroundContents>(details).ptr()));
    217 
    218       // Do not register in the pref if the extension has a manifest-specified
    219       // background page.
    220       BackgroundContents* bgcontents =
    221           Details<BackgroundContents>(details).ptr();
    222       Profile* profile = Source<Profile>(source).ptr();
    223       const string16& appid = GetParentApplicationId(bgcontents);
    224       ExtensionService* extension_service = profile->GetExtensionService();
    225       // extension_service can be NULL when running tests.
    226       if (extension_service) {
    227         const Extension* extension =
    228             extension_service->GetExtensionById(UTF16ToUTF8(appid), false);
    229         if (extension && extension->background_url().is_valid())
    230           break;
    231       }
    232       RegisterBackgroundContents(Details<BackgroundContents>(details).ptr());
    233       break;
    234     }
    235     case NotificationType::EXTENSION_LOADED: {
    236       const Extension* extension = Details<const Extension>(details).ptr();
    237       Profile* profile = Source<Profile>(source).ptr();
    238       if (extension->is_hosted_app() &&
    239           extension->background_url().is_valid()) {
    240         // If there is a background page specified in the manifest for a hosted
    241         // app, then blow away registered urls in the pref.
    242         ShutdownAssociatedBackgroundContents(ASCIIToUTF16(extension->id()));
    243 
    244         ExtensionService* service = profile->GetExtensionService();
    245         if (service && service->is_ready()) {
    246           // Now load the manifest-specified background page. If service isn't
    247           // ready, then the background page will be loaded from the
    248           // EXTENSIONS_READY callback.
    249           LoadBackgroundContents(profile, extension->background_url(),
    250               ASCIIToUTF16("background"), UTF8ToUTF16(extension->id()));
    251         }
    252       }
    253 
    254       // Remove any "This extension has crashed" balloons.
    255       ScheduleCloseBalloon(extension->id());
    256       break;
    257     }
    258     case NotificationType::EXTENSION_PROCESS_TERMINATED:
    259     case NotificationType::BACKGROUND_CONTENTS_TERMINATED: {
    260       Profile* profile = Source<Profile>(source).ptr();
    261       const Extension* extension = NULL;
    262       if (type.value == NotificationType::BACKGROUND_CONTENTS_TERMINATED) {
    263         BackgroundContents* bg =
    264             Details<BackgroundContents>(details).ptr();
    265         std::string extension_id = UTF16ToASCII(
    266             BackgroundContentsServiceFactory::GetForProfile(profile)->
    267                 GetParentApplicationId(bg));
    268         extension =
    269           profile->GetExtensionService()->GetExtensionById(extension_id, false);
    270       } else {
    271         ExtensionHost* extension_host = Details<ExtensionHost>(details).ptr();
    272         extension = extension_host->extension();
    273       }
    274       if (!extension)
    275         break;
    276 
    277       // When an extension crashes, EXTENSION_PROCESS_TERMINATED is followed by
    278       // an EXTENSION_UNLOADED notification. This UNLOADED signal causes all the
    279       // notifications for this extension to be cancelled by
    280       // DesktopNotificationService. For this reason, instead of showing the
    281       // balloon right now, we schedule it to show a little later.
    282       MessageLoop::current()->PostTask(FROM_HERE,
    283           NewRunnableFunction(&ShowBalloon, extension, profile));
    284       break;
    285     }
    286     case NotificationType::EXTENSION_UNLOADED:
    287       switch (Details<UnloadedExtensionInfo>(details)->reason) {
    288         case UnloadedExtensionInfo::DISABLE:  // Intentionally fall through.
    289         case UnloadedExtensionInfo::UNINSTALL:
    290           ShutdownAssociatedBackgroundContents(
    291               ASCIIToUTF16(
    292                   Details<UnloadedExtensionInfo>(details)->extension->id()));
    293           break;
    294         case UnloadedExtensionInfo::UPDATE: {
    295           // If there is a manifest specified background page, then shut it down
    296           // here, since if the updated extension still has the background page,
    297           // then it will be loaded from LOADED callback. Otherwise, leave
    298           // BackgroundContents in place.
    299           const Extension* extension =
    300               Details<UnloadedExtensionInfo>(details)->extension;
    301           if (extension->background_url().is_valid())
    302             ShutdownAssociatedBackgroundContents(ASCIIToUTF16(extension->id()));
    303           break;
    304         }
    305         default:
    306           NOTREACHED();
    307           ShutdownAssociatedBackgroundContents(
    308               ASCIIToUTF16(
    309                   Details<UnloadedExtensionInfo>(details)->extension->id()));
    310           break;
    311       }
    312       break;
    313 
    314     case NotificationType::EXTENSION_UNINSTALLED: {
    315       // Remove any "This extension has crashed" balloons.
    316       const UninstalledExtensionInfo* uninstalled_extension =
    317           Details<const UninstalledExtensionInfo>(details).ptr();
    318       ScheduleCloseBalloon(uninstalled_extension->extension_id);
    319       break;
    320     }
    321 
    322     default:
    323       NOTREACHED();
    324       break;
    325   }
    326 }
    327 
    328 // Loads all background contents whose urls have been stored in prefs.
    329 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
    330     Profile* profile) {
    331   if (!prefs_)
    332     return;
    333   const DictionaryValue* contents =
    334       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
    335   if (!contents)
    336     return;
    337   ExtensionService* extensions_service = profile->GetExtensionService();
    338   DCHECK(extensions_service);
    339   for (DictionaryValue::key_iterator it = contents->begin_keys();
    340        it != contents->end_keys(); ++it) {
    341     // Check to make sure that the parent extension is still enabled.
    342     const Extension* extension = extensions_service->GetExtensionById(
    343         *it, false);
    344     if (!extension) {
    345       // We should never reach here - it should not be possible for an app
    346       // to become uninstalled without the associated BackgroundContents being
    347       // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a
    348       // crash before we could save our prefs.
    349       NOTREACHED() << "No extension found for BackgroundContents - id = "
    350                    << *it;
    351       return;
    352     }
    353     LoadBackgroundContentsFromDictionary(profile, *it, contents);
    354   }
    355 }
    356 
    357 void BackgroundContentsService::LoadBackgroundContentsForExtension(
    358     Profile* profile,
    359     const std::string& extension_id) {
    360   // First look if the manifest specifies a background page.
    361   const Extension* extension =
    362       profile->GetExtensionService()->GetExtensionById(extension_id, false);
    363   DCHECK(!extension || extension->is_hosted_app());
    364   if (extension && extension->background_url().is_valid()) {
    365     LoadBackgroundContents(profile,
    366                            extension->background_url(),
    367                            ASCIIToUTF16("background"),
    368                            UTF8ToUTF16(extension->id()));
    369     return;
    370   }
    371 
    372   // Now look in the prefs.
    373   if (!prefs_)
    374     return;
    375   const DictionaryValue* contents =
    376       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
    377   if (!contents)
    378     return;
    379   LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
    380 }
    381 
    382 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
    383     Profile* profile,
    384     const std::string& extension_id,
    385     const DictionaryValue* contents) {
    386   ExtensionService* extensions_service = profile->GetExtensionService();
    387   DCHECK(extensions_service);
    388 
    389   DictionaryValue* dict;
    390   contents->GetDictionaryWithoutPathExpansion(extension_id, &dict);
    391   if (dict == NULL)
    392     return;
    393   string16 frame_name;
    394   std::string url;
    395   dict->GetString(kUrlKey, &url);
    396   dict->GetString(kFrameNameKey, &frame_name);
    397   LoadBackgroundContents(profile,
    398                          GURL(url),
    399                          frame_name,
    400                          UTF8ToUTF16(extension_id));
    401 }
    402 
    403 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
    404     Profile* profile) {
    405   const ExtensionList* extensions =
    406       profile->GetExtensionService()->extensions();
    407   ExtensionList::const_iterator iter = extensions->begin();
    408   for (; iter != extensions->end(); ++iter) {
    409     const Extension* extension = *iter;
    410     if (extension->is_hosted_app() &&
    411         extension->background_url().is_valid()) {
    412       LoadBackgroundContents(profile,
    413                              extension->background_url(),
    414                              ASCIIToUTF16("background"),
    415                              UTF8ToUTF16(extension->id()));
    416     }
    417   }
    418 }
    419 
    420 void BackgroundContentsService::LoadBackgroundContents(
    421     Profile* profile,
    422     const GURL& url,
    423     const string16& frame_name,
    424     const string16& application_id) {
    425   // We are depending on the fact that we will initialize before any user
    426   // actions or session restore can take place, so no BackgroundContents should
    427   // be running yet for the passed application_id.
    428   DCHECK(!GetAppBackgroundContents(application_id));
    429   DCHECK(!application_id.empty());
    430   DCHECK(url.is_valid());
    431   DVLOG(1) << "Loading background content url: " << url;
    432 
    433   BackgroundContents* contents = CreateBackgroundContents(
    434       SiteInstance::CreateSiteInstanceForURL(profile, url),
    435       MSG_ROUTING_NONE,
    436       profile,
    437       frame_name,
    438       application_id);
    439 
    440   RenderViewHost* render_view_host = contents->render_view_host();
    441   // TODO(atwilson): Create RenderViews asynchronously to avoid increasing
    442   // startup latency (http://crbug.com/47236).
    443   render_view_host->CreateRenderView(frame_name);
    444   render_view_host->NavigateToURL(url);
    445 }
    446 
    447 BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
    448     SiteInstance* site,
    449     int routing_id,
    450     Profile* profile,
    451     const string16& frame_name,
    452     const string16& application_id) {
    453   BackgroundContents* contents = new BackgroundContents(site, routing_id, this);
    454 
    455   // Register the BackgroundContents internally, then send out a notification
    456   // to external listeners.
    457   BackgroundContentsOpenedDetails details = {contents,
    458                                              frame_name,
    459                                              application_id};
    460   BackgroundContentsOpened(&details);
    461   NotificationService::current()->Notify(
    462       NotificationType::BACKGROUND_CONTENTS_OPENED,
    463       Source<Profile>(profile),
    464       Details<BackgroundContentsOpenedDetails>(&details));
    465   return contents;
    466 }
    467 
    468 void BackgroundContentsService::RegisterBackgroundContents(
    469     BackgroundContents* background_contents) {
    470   DCHECK(IsTracked(background_contents));
    471   if (!prefs_)
    472     return;
    473 
    474   // We store the first URL we receive for a given application. If there's
    475   // already an entry for this application, no need to do anything.
    476   // TODO(atwilson): Verify that this is the desired behavior based on developer
    477   // feedback (http://crbug.com/47118).
    478   DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
    479   DictionaryValue* pref = update.Get();
    480   const string16& appid = GetParentApplicationId(background_contents);
    481   DictionaryValue* current;
    482   if (pref->GetDictionaryWithoutPathExpansion(UTF16ToUTF8(appid), &current))
    483     return;
    484 
    485   // No entry for this application yet, so add one.
    486   DictionaryValue* dict = new DictionaryValue();
    487   dict->SetString(kUrlKey, background_contents->GetURL().spec());
    488   dict->SetString(kFrameNameKey, contents_map_[appid].frame_name);
    489   pref->SetWithoutPathExpansion(UTF16ToUTF8(appid), dict);
    490   prefs_->ScheduleSavePersistentPrefs();
    491 }
    492 
    493 void BackgroundContentsService::UnregisterBackgroundContents(
    494     BackgroundContents* background_contents) {
    495   if (!prefs_)
    496     return;
    497   DCHECK(IsTracked(background_contents));
    498   const string16 appid = GetParentApplicationId(background_contents);
    499   DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
    500   update.Get()->RemoveWithoutPathExpansion(UTF16ToUTF8(appid), NULL);
    501   prefs_->ScheduleSavePersistentPrefs();
    502 }
    503 
    504 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
    505     const string16& appid) {
    506   BackgroundContents* contents = GetAppBackgroundContents(appid);
    507   if (contents) {
    508     UnregisterBackgroundContents(contents);
    509     // Background contents destructor shuts down the renderer.
    510     delete contents;
    511   }
    512 }
    513 
    514 void BackgroundContentsService::BackgroundContentsOpened(
    515     BackgroundContentsOpenedDetails* details) {
    516   // Add the passed object to our list. Should not already be tracked.
    517   DCHECK(!IsTracked(details->contents));
    518   DCHECK(!details->application_id.empty());
    519   contents_map_[details->application_id].contents = details->contents;
    520   contents_map_[details->application_id].frame_name = details->frame_name;
    521 }
    522 
    523 // Used by test code and debug checks to verify whether a given
    524 // BackgroundContents is being tracked by this instance.
    525 bool BackgroundContentsService::IsTracked(
    526     BackgroundContents* background_contents) const {
    527   return !GetParentApplicationId(background_contents).empty();
    528 }
    529 
    530 void BackgroundContentsService::BackgroundContentsShutdown(
    531     BackgroundContents* background_contents) {
    532   // Remove the passed object from our list.
    533   DCHECK(IsTracked(background_contents));
    534   string16 appid = GetParentApplicationId(background_contents);
    535   contents_map_.erase(appid);
    536 }
    537 
    538 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
    539     const string16& application_id) {
    540   BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
    541   return (it != contents_map_.end()) ? it->second.contents : NULL;
    542 }
    543 
    544 const string16& BackgroundContentsService::GetParentApplicationId(
    545     BackgroundContents* contents) const {
    546   for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
    547        it != contents_map_.end(); ++it) {
    548     if (contents == it->second.contents)
    549       return it->first;
    550   }
    551   return EmptyString16();
    552 }
    553 
    554 // static
    555 void BackgroundContentsService::RegisterUserPrefs(PrefService* prefs) {
    556   prefs->RegisterDictionaryPref(prefs::kRegisteredBackgroundContents);
    557 }
    558 
    559 void BackgroundContentsService::AddTabContents(
    560     TabContents* new_contents,
    561     WindowOpenDisposition disposition,
    562     const gfx::Rect& initial_pos,
    563     bool user_gesture) {
    564   Browser* browser = BrowserList::GetLastActiveWithProfile(
    565       new_contents->profile());
    566   if (!browser)
    567     return;
    568   browser->AddTabContents(new_contents, disposition, initial_pos, user_gesture);
    569 }
    570