diff options
author | ryanmyers <ryanmyers@chromium.org> | 2016-03-24 18:21:16 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-03-25 01:22:55 +0000 |
commit | fffe8c740df30ab0529e05000a6fce138075023a (patch) | |
tree | c4a8b7d804b388314b362b110fe71970e9402ef4 | |
parent | aa5f0bcbe659641c6902d69a0ca33e8eb785533d (diff) | |
download | chromium_src-fffe8c740df30ab0529e05000a6fce138075023a.zip chromium_src-fffe8c740df30ab0529e05000a6fce138075023a.tar.gz chromium_src-fffe8c740df30ab0529e05000a6fce138075023a.tar.bz2 |
Add viewing of error messages from Keystone upon self-update failure.
With newer versions of Keystone's Registration Framework, errors in the
update process (visible on stderr from agent/ksadmin) are supplied to
Chrome in the userInfo dictionary of notifications. Pull this out if
present, and display it in chrome://help underneath the error code.
Example output: https://screenshot.googleplex.com/uH59OwVp5Xi
BUG=512609
Review URL: https://codereview.chromium.org/1769703002
Cr-Commit-Position: refs/heads/master@{#383217}
-rw-r--r-- | chrome/app/generated_resources.grd | 9 | ||||
-rw-r--r-- | chrome/browser/mac/keystone_glue.h | 4 | ||||
-rw-r--r-- | chrome/browser/mac/keystone_glue.mm | 167 | ||||
-rw-r--r-- | chrome/browser/mac/keystone_registration.h | 2 | ||||
-rw-r--r-- | chrome/browser/mac/keystone_registration.mm | 2 | ||||
-rw-r--r-- | chrome/browser/ui/webui/help/version_updater_mac.mm | 32 |
6 files changed, 163 insertions, 53 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 57e2273..169b3cc 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -8049,6 +8049,12 @@ I don't think this site should be blocked! <message name="IDS_PROMOTE_AUTHENTICATION_PROMPT" desc="The prompt displayed in the authentication dialog when setting up automatic updates for all users. The system will add a sentence asking for an administrator's name and password. Mac-only."> <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> will set up automatic updates for all users of this computer. </message> + <message name="IDS_PROMOTE_PREFLIGHT_LAUNCH_ERROR" desc="Status label shown when an administrator user requested automatic updates for all users, but the OS failed to start the pre-flight process. Mac-only."> + Failed to set up automatic updates for all users (preflight launch error: <ph name="ERROR_NUMBER">$1<ex>1</ex></ph>) + </message> + <message name="IDS_PROMOTE_PREFLIGHT_SCRIPT_ERROR" desc="Status label shown when an administrator user requested automatic updates for all users, but the pre-flight process reported an error. Mac-only."> + Failed to set up automatic updates for all users (preflight execution error: <ph name="ERROR_NUMBER">$1<ex>1</ex></ph>) + </message> </if> <!-- About Chrome View --> @@ -8064,6 +8070,9 @@ I don't think this site should be blocked! <message name="IDS_UPGRADE_ERROR" desc="Status label: Error occurred during upgrade"> Update failed (error: <ph name="ERROR_NUMBER">$1<ex>1</ex></ph>) </message> + <message name="IDS_UPGRADE_ERROR_DETAILS" desc="Status label optionally shown when an error occured during upgrade. The upgrade daemon may optionally send supplemental error messages; if they're available, we append them after this message in an HTML pre block."> + Error details: + </message> </if> <if expr="is_win or chromeos"> <message name="IDS_UPGRADE_DISABLED_BY_POLICY" desc="Status label: Upgrades disabled by policy"> diff --git a/chrome/browser/mac/keystone_glue.h b/chrome/browser/mac/keystone_glue.h index 912a1d9..ff0de4a 100644 --- a/chrome/browser/mac/keystone_glue.h +++ b/chrome/browser/mac/keystone_glue.h @@ -44,10 +44,12 @@ enum AutoupdateStatus { // the notification. Its userInfo dictionary will contain an AutoupdateStatus // value as an intValue at key kAutoupdateStatusStatus. If a version is // available (see AutoupdateStatus), it will be present at key -// kAutoupdateStatusVersion. +// kAutoupdateStatusVersion. If any error messages were supplied by Keystone, +// they will be present at key kAutoupdateStatusErrorMessages. extern NSString* const kAutoupdateStatusNotification; extern NSString* const kAutoupdateStatusStatus; extern NSString* const kAutoupdateStatusVersion; +extern NSString* const kAutoupdateStatusErrorMessages; namespace { diff --git a/chrome/browser/mac/keystone_glue.mm b/chrome/browser/mac/keystone_glue.mm index c6333e7..79621ff 100644 --- a/chrome/browser/mac/keystone_glue.mm +++ b/chrome/browser/mac/keystone_glue.mm @@ -19,6 +19,7 @@ #include "base/mac/mac_logging.h" #include "base/mac/scoped_nsautorelease_pool.h" #include "base/memory/ref_counted.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/sys_string_conversions.h" #include "base/threading/worker_pool.h" #include "build/build_config.h" @@ -127,7 +128,9 @@ class PerformBridge : public base::RefCountedThreadSafe<PerformBridge> { // Called when an update check or update installation is complete. Posts the // kAutoupdateStatusNotification notification to the default notification // center. -- (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version; +- (void)updateStatus:(AutoupdateStatus)status + version:(NSString*)version + error:(NSString*)error; // Returns the version of the currently-installed application on disk. - (NSString*)currentlyInstalledVersion; @@ -135,7 +138,7 @@ class PerformBridge : public base::RefCountedThreadSafe<PerformBridge> { // These three methods are used to determine the version of the application // currently installed on disk, compare that to the currently-running version, // decide whether any updates have been installed, and call -// -updateStatus:version:. +// -updateStatus:version:error:. // // In order to check the version on disk, the installed application's // Info.plist dictionary must be read; in order to see changes as updates are @@ -153,8 +156,8 @@ class PerformBridge : public base::RefCountedThreadSafe<PerformBridge> { // CFBundleShortVersionString key, and performs // -determineUpdateStatusForVersion: on the main thread. // -determineUpdateStatusForVersion: does the actual comparison of the version -// on disk with the running version and calls -updateStatus:version: with the -// results of its analysis. +// on disk with the running version and calls -updateStatus:version:error: with +// the results of its analysis. - (void)determineUpdateStatusAsync; - (void)determineUpdateStatus; - (void)determineUpdateStatusForVersion:(NSString*)version; @@ -208,6 +211,7 @@ class PerformBridge : public base::RefCountedThreadSafe<PerformBridge> { NSString* const kAutoupdateStatusNotification = @"AutoupdateStatusNotification"; NSString* const kAutoupdateStatusStatus = @"status"; NSString* const kAutoupdateStatusVersion = @"version"; +NSString* const kAutoupdateStatusErrorMessages = @"errormessages"; namespace { @@ -289,21 +293,25 @@ NSString* const kVersionKey = @"KSVersion"; NSBundle* appBundle = base::mac::OuterBundle(); NSDictionary* infoDictionary = [self infoDictionary]; - NSString* productID = [infoDictionary objectForKey:@"KSProductID"]; + NSString* productID = base::mac::ObjCCast<NSString>( + [infoDictionary objectForKey:@"KSProductID"]); if (productID == nil) { productID = [appBundle bundleIdentifier]; } NSString* appPath = [appBundle bundlePath]; - NSString* url = [infoDictionary objectForKey:@"KSUpdateURL"]; - NSString* version = [infoDictionary objectForKey:kVersionKey]; + NSString* url = base::mac::ObjCCast<NSString>( + [infoDictionary objectForKey:@"KSUpdateURL"]); + NSString* version = base::mac::ObjCCast<NSString>( + [infoDictionary objectForKey:kVersionKey]); if (!productID || !appPath || !url || !version) { // If parameters required for Keystone are missing, don't use it. return; } - NSString* channel = [infoDictionary objectForKey:kChannelKey]; + NSString* channel = base::mac::ObjCCast<NSString>( + [infoDictionary objectForKey:kChannelKey]); // The stable channel has no tag. If updating to stable, remove the // dev and beta tags since we've been "promoted". if (channel == nil) @@ -365,13 +373,15 @@ NSString* const kVersionKey = @"KSVersion"; // User NSDictionary* infoDictionary = [self infoDictionary]; - NSString* appBundleBrandID = [infoDictionary objectForKey:kBrandKey]; + NSString* appBundleBrandID = base::mac::ObjCCast<NSString>( + [infoDictionary objectForKey:kBrandKey]); NSString* storedBrandID = nil; if ([fm fileExistsAtPath:userBrandFile]) { NSDictionary* storedBrandDict = [NSDictionary dictionaryWithContentsOfFile:userBrandFile]; - storedBrandID = [storedBrandDict objectForKey:kBrandKey]; + storedBrandID = base::mac::ObjCCast<NSString>( + [storedBrandDict objectForKey:kBrandKey]); } if ((appBundleBrandID != nil) && @@ -494,8 +504,8 @@ NSString* const kVersionKey = @"KSVersion"; } - (void)setRegistrationActive { - if (!registration_) - return; + DCHECK(registration_); + registrationActive_ = YES; // Should never have zero profiles. Do not report this value. @@ -536,12 +546,16 @@ NSString* const kVersionKey = @"KSVersion"; } - (void)registerWithKeystone { - [self updateStatus:kAutoupdateRegistering version:nil]; + DCHECK(registration_); + + [self updateStatus:kAutoupdateRegistering version:nil error:nil]; NSDictionary* parameters = [self keystoneParameters]; BOOL result = [registration_ registerWithParameters:parameters]; if (!result) { - [self updateStatus:kAutoupdateRegisterFailed version:nil]; + // TODO: If Keystone ever makes a variant of this API with a withError: + // parameter, include the error message here in the call to updateStatus:. + [self updateStatus:kAutoupdateRegisterFailed version:nil error:nil]; return; } @@ -562,15 +576,26 @@ NSString* const kVersionKey = @"KSVersion"; - (void)registrationComplete:(NSNotification*)notification { NSDictionary* userInfo = [notification userInfo]; - if ([[userInfo objectForKey:ksr::KSRegistrationStatusKey] boolValue]) { + NSNumber* status = base::mac::ObjCCast<NSNumber>( + [userInfo objectForKey:ksr::KSRegistrationStatusKey]); + NSString* errorMessages = base::mac::ObjCCast<NSString>( + [userInfo objectForKey:ksr::KSRegistrationUpdateCheckRawErrorMessagesKey]); + + if ([status boolValue]) { if ([self isSystemTicketDoomed]) { - [self updateStatus:kAutoupdateNeedsPromotion version:nil]; + [self updateStatus:kAutoupdateNeedsPromotion + version:nil + error:errorMessages]; } else { - [self updateStatus:kAutoupdateRegistered version:nil]; + [self updateStatus:kAutoupdateRegistered + version:nil + error:errorMessages]; } } else { // Dump registration_? - [self updateStatus:kAutoupdateRegisterFailed version:nil]; + [self updateStatus:kAutoupdateRegisterFailed + version:nil + error:errorMessages]; } } @@ -583,14 +608,14 @@ NSString* const kVersionKey = @"KSVersion"; } - (void)checkForUpdate { - DCHECK(![self asyncOperationPending]); + DCHECK(registration_); - if (!registration_) { - [self updateStatus:kAutoupdateCheckFailed version:nil]; + if ([self asyncOperationPending]) { + // Update check already in process; return without doing anything. return; } - [self updateStatus:kAutoupdateChecking version:nil]; + [self updateStatus:kAutoupdateChecking version:nil error:nil]; // All checks from inside Chrome are considered user-initiated, because they // only happen following a user action, such as visiting the about page. @@ -604,15 +629,25 @@ NSString* const kVersionKey = @"KSVersion"; - (void)checkForUpdateComplete:(NSNotification*)notification { NSDictionary* userInfo = [notification userInfo]; - - if ([[userInfo objectForKey:ksr::KSRegistrationUpdateCheckErrorKey] - boolValue]) { - [self updateStatus:kAutoupdateCheckFailed version:nil]; - } else if ([[userInfo objectForKey:ksr::KSRegistrationStatusKey] boolValue]) { + NSNumber* error = base::mac::ObjCCast<NSNumber>( + [userInfo objectForKey:ksr::KSRegistrationUpdateCheckErrorKey]); + NSNumber* status = base::mac::ObjCCast<NSNumber>( + [userInfo objectForKey:ksr::KSRegistrationStatusKey]); + NSString* errorMessages = base::mac::ObjCCast<NSString>( + [userInfo objectForKey:ksr::KSRegistrationUpdateCheckRawErrorMessagesKey]); + + if ([error boolValue]) { + [self updateStatus:kAutoupdateCheckFailed + version:nil + error:errorMessages]; + } else if ([status boolValue]) { // If an update is known to be available, go straight to // -updateStatus:version:. It doesn't matter what's currently on disk. - NSString* version = [userInfo objectForKey:ksr::KSRegistrationVersionKey]; - [self updateStatus:kAutoupdateAvailable version:version]; + NSString* version = base::mac::ObjCCast<NSString>( + [userInfo objectForKey:ksr::KSRegistrationVersionKey]); + [self updateStatus:kAutoupdateAvailable + version:version + error:errorMessages]; } else { // If no updates are available, check what's on disk, because an update // may have already been installed. This check happens on another thread, @@ -622,14 +657,14 @@ NSString* const kVersionKey = @"KSVersion"; } - (void)installUpdate { - DCHECK(![self asyncOperationPending]); + DCHECK(registration_); - if (!registration_) { - [self updateStatus:kAutoupdateInstallFailed version:nil]; + if ([self asyncOperationPending]) { + // Update check already in process; return without doing anything. return; } - [self updateStatus:kAutoupdateInstalling version:nil]; + [self updateStatus:kAutoupdateInstalling version:nil error:nil]; [registration_ startUpdate]; @@ -639,14 +674,19 @@ NSString* const kVersionKey = @"KSVersion"; - (void)installUpdateComplete:(NSNotification*)notification { NSDictionary* userInfo = [notification userInfo]; + NSNumber* successfulInstall = base::mac::ObjCCast<NSNumber>( + [userInfo objectForKey:ksr::KSUpdateCheckSuccessfullyInstalledKey]); + NSString* errorMessages = base::mac::ObjCCast<NSString>( + [userInfo objectForKey:ksr::KSRegistrationUpdateCheckRawErrorMessagesKey]); // http://crbug.com/160308 and b/7517358: when using system Keystone and on // a user ticket, KSUpdateCheckSuccessfulKey will be NO even when an update // was installed correctly, so don't check it. It should be redudnant when // KSUpdateCheckSuccessfullyInstalledKey is checked. - if (![[userInfo objectForKey:ksr::KSUpdateCheckSuccessfullyInstalledKey] - intValue]) { - [self updateStatus:kAutoupdateInstallFailed version:nil]; + if (![successfulInstall intValue]) { + [self updateStatus:kAutoupdateInstallFailed + version:nil + error:errorMessages]; } else { updateSuccessfullyInstalled_ = YES; @@ -660,7 +700,8 @@ NSString* const kVersionKey = @"KSVersion"; NSString* appInfoPlistPath = [self appInfoPlistPath]; NSDictionary* infoPlist = [NSDictionary dictionaryWithContentsOfFile:appInfoPlistPath]; - return [infoPlist objectForKey:@"CFBundleShortVersionString"]; + return base::mac::ObjCCast<NSString>( + [infoPlist objectForKey:@"CFBundleShortVersionString"]); } // Runs on the main thread. @@ -709,10 +750,12 @@ NSString* const kVersionKey = @"KSVersion"; } } - [self updateStatus:status version:version]; + [self updateStatus:status version:version error:nil]; } -- (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version { +- (void)updateStatus:(AutoupdateStatus)status + version:(NSString*)version + error:(NSString*)error { NSNumber* statusNumber = [NSNumber numberWithInt:status]; NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithObject:statusNumber @@ -720,6 +763,9 @@ NSString* const kVersionKey = @"KSVersion"; if (version) { [dictionary setObject:version forKey:kAutoupdateStatusVersion]; } + if (error) { + [dictionary setObject:version forKey:kAutoupdateStatusErrorMessages]; + } NSNotification* notification = [NSNotification notificationWithName:kAutoupdateStatusNotification @@ -736,8 +782,9 @@ NSString* const kVersionKey = @"KSVersion"; - (AutoupdateStatus)recentStatus { NSDictionary* dictionary = [recentNotification_ userInfo]; - return static_cast<AutoupdateStatus>( - [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]); + NSNumber* status = base::mac::ObjCCastStrict<NSNumber>( + [dictionary objectForKey:kAutoupdateStatusStatus]); + return static_cast<AutoupdateStatus>([status intValue]); } - (BOOL)asyncOperationPending { @@ -749,6 +796,7 @@ NSString* const kVersionKey = @"KSVersion"; } - (BOOL)isUserTicket { + DCHECK(registration_); return [registration_ ticketType] == ksr::kKSRegistrationUserTicket; } @@ -856,6 +904,8 @@ NSString* const kVersionKey = @"KSVersion"; - (void)promoteTicketWithAuthorization:(AuthorizationRef)authorization_arg synchronous:(BOOL)synchronous { + DCHECK(registration_); + base::mac::ScopedAuthorizationRef authorization(authorization_arg); authorization_arg = NULL; @@ -873,7 +923,7 @@ NSString* const kVersionKey = @"KSVersion"; synchronousPromotion_ = synchronous; - [self updateStatus:kAutoupdatePromoting version:nil]; + [self updateStatus:kAutoupdatePromoting version:nil error:nil]; // TODO(mark): Remove when able! // @@ -920,14 +970,24 @@ NSString* const kVersionKey = @"KSVersion"; NULL, // pipe &exit_status); if (status != errAuthorizationSuccess) { - OSSTATUS_LOG(ERROR, status) - << "AuthorizationExecuteWithPrivileges preflight"; - [self updateStatus:kAutoupdatePromoteFailed version:nil]; + // It's possible to get an OS-provided error string for this return code + // using base::mac::DescriptionFromOSStatus, but most of those strings are + // not useful/actionable for users, so we stick with the error code instead. + NSString* errorMessage = + l10n_util::GetNSStringFWithFixup(IDS_PROMOTE_PREFLIGHT_LAUNCH_ERROR, + base::IntToString16(status)); + [self updateStatus:kAutoupdatePromoteFailed + version:nil + error:errorMessage]; return; } if (exit_status != 0) { - LOG(ERROR) << "keystone_promote_preflight status " << exit_status; - [self updateStatus:kAutoupdatePromoteFailed version:nil]; + NSString* errorMessage = + l10n_util::GetNSStringFWithFixup(IDS_PROMOTE_PREFLIGHT_SCRIPT_ERROR, + base::IntToString16(status)); + [self updateStatus:kAutoupdatePromoteFailed + version:nil + error:errorMessage]; return; } @@ -951,7 +1011,9 @@ NSString* const kVersionKey = @"KSVersion"; if (![registration_ promoteWithParameters:parameters authorization:authorization_]) { - [self updateStatus:kAutoupdatePromoteFailed version:nil]; + // TODO: If Keystone ever makes a variant of this API with a withError: + // parameter, include the error message here in the call to updateStatus:. + [self updateStatus:kAutoupdatePromoteFailed version:nil error:nil]; authorization_.reset(); return; } @@ -968,7 +1030,10 @@ NSString* const kVersionKey = @"KSVersion"; - (void)promotionComplete:(NSNotification*)notification { NSDictionary* userInfo = [notification userInfo]; - if ([[userInfo objectForKey:ksr::KSRegistrationStatusKey] boolValue]) { + NSNumber* status = base::mac::ObjCCast<NSNumber>( + [userInfo objectForKey:ksr::KSRegistrationStatusKey]); + + if ([status boolValue]) { if (synchronousPromotion_) { // Short-circuit: if performing a synchronous promotion, the promotion // came from the installer, which already set the permissions properly. @@ -980,7 +1045,7 @@ NSString* const kVersionKey = @"KSVersion"; } } else { authorization_.reset(); - [self updateStatus:kAutoupdatePromoteFailed version:nil]; + [self updateStatus:kAutoupdatePromoteFailed version:nil error:nil]; } if (synchronousPromotion_) { @@ -1036,7 +1101,7 @@ NSString* const kVersionKey = @"KSVersion"; - (void)changePermissionsForPromotionComplete { authorization_.reset(); - [self updateStatus:kAutoupdatePromoted version:nil]; + [self updateStatus:kAutoupdatePromoted version:nil error:nil]; } - (void)setAppPath:(NSString*)appPath { diff --git a/chrome/browser/mac/keystone_registration.h b/chrome/browser/mac/keystone_registration.h index 72e52ed..1771ca6 100644 --- a/chrome/browser/mac/keystone_registration.h +++ b/chrome/browser/mac/keystone_registration.h @@ -43,6 +43,8 @@ extern NSString* KSRegistrationPromotionDidCompleteNotification; extern NSString* KSRegistrationCheckForUpdateNotification; extern NSString* KSRegistrationStatusKey; extern NSString* KSRegistrationUpdateCheckErrorKey; +extern NSString* KSRegistrationUpdateCheckRawResultsKey; +extern NSString* KSRegistrationUpdateCheckRawErrorMessagesKey; extern NSString* KSRegistrationStartUpdateNotification; extern NSString* KSUpdateCheckSuccessfulKey; diff --git a/chrome/browser/mac/keystone_registration.mm b/chrome/browser/mac/keystone_registration.mm index 82e2659..4ff70c3 100644 --- a/chrome/browser/mac/keystone_registration.mm +++ b/chrome/browser/mac/keystone_registration.mm @@ -31,6 +31,8 @@ NSString* KSRegistrationCheckForUpdateNotification = @"KSRegistrationCheckForUpdateNotification"; NSString* KSRegistrationStatusKey = @"Status"; NSString* KSRegistrationUpdateCheckErrorKey = @"Error"; +NSString* KSRegistrationUpdateCheckRawResultsKey = @"RawResults"; +NSString* KSRegistrationUpdateCheckRawErrorMessagesKey = @"RawErrorMessages"; NSString* KSRegistrationStartUpdateNotification = @"KSRegistrationStartUpdateNotification"; diff --git a/chrome/browser/ui/webui/help/version_updater_mac.mm b/chrome/browser/ui/webui/help/version_updater_mac.mm index be9c08f..2d0e42e 100644 --- a/chrome/browser/ui/webui/help/version_updater_mac.mm +++ b/chrome/browser/ui/webui/help/version_updater_mac.mm @@ -6,11 +6,17 @@ #include "base/bind.h" #include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" #include "chrome/browser/lifetime/application_lifetime.h" #import "chrome/browser/mac/keystone_glue.h" #include "chrome/browser/obsolete_system/obsolete_system.h" #include "chrome/grit/chromium_strings.h" #include "chrome/grit/generated_resources.h" +#include "net/base/escape.h" #include "ui/base/l10n/l10n_util.h" // KeystoneObserver is a simple notification observer for Keystone status @@ -131,7 +137,11 @@ void VersionUpdaterMac::RelaunchBrowser() const { void VersionUpdaterMac::UpdateStatus(NSDictionary* dictionary) { AutoupdateStatus keystone_status = static_cast<AutoupdateStatus>( - [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]); + [base::mac::ObjCCastStrict<NSNumber>( + [dictionary objectForKey:kAutoupdateStatusStatus]) intValue]); + std::string error_messages = base::SysNSStringToUTF8( + base::mac::ObjCCastStrict<NSString>( + [dictionary objectForKey:kAutoupdateStatusErrorMessages])); bool enable_promote_button = true; base::string16 message; @@ -211,6 +221,26 @@ void VersionUpdaterMac::UpdateStatus(NSDictionary* dictionary) { NOTREACHED(); return; } + + // If there are any detailed error messages being passed along by Keystone, + // log them. If we have an error to display, include the detail messages + // below the error in a <pre> block. Don't bother displaying detail messages + // on a success/in-progress/indeterminate status. + if (!error_messages.empty()) { + VLOG(1) << "Update error messages: " << error_messages; + + if (status == FAILED) { + if (!message.empty()) { + message += base::UTF8ToUTF16("<br/><br/>"); + } + + message += l10n_util::GetStringUTF16(IDS_UPGRADE_ERROR_DETAILS); + message += base::UTF8ToUTF16("<br/><pre>"); + message += base::UTF8ToUTF16(net::EscapeForHTML(error_messages)); + message += base::UTF8ToUTF16("</pre>"); + } + } + if (!status_callback_.is_null()) status_callback_.Run(status, 0, message); |