summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'chrome')
-rw-r--r--chrome/app/keystone_glue.h90
-rw-r--r--chrome/app/keystone_glue.mm283
-rw-r--r--chrome/app/keystone_glue_unittest.mm76
-rw-r--r--chrome/browser/app_controller_mac.mm23
-rw-r--r--chrome/browser/cocoa/about_window_controller.h30
-rw-r--r--chrome/browser/cocoa/about_window_controller.mm554
-rw-r--r--chrome/browser/cocoa/about_window_controller_unittest.mm74
-rw-r--r--chrome/browser/cocoa/restart_browser.mm2
8 files changed, 699 insertions, 433 deletions
diff --git a/chrome/app/keystone_glue.h b/chrome/app/keystone_glue.h
index 9a8eb01..f8d2a53 100644
--- a/chrome/app/keystone_glue.h
+++ b/chrome/app/keystone_glue.h
@@ -6,27 +6,30 @@
#define CHROME_APP_KEYSTONE_GLUE_H_
#import <Foundation/Foundation.h>
-
-// Objects which request callbacks from KeystoneGlue (e.g. information
-// on update availability) should implement this protocol. All callbacks
-// require the caller to be spinning in the runloop to happen.
-@protocol KeystoneGlueCallbacks
-
-// Callback when a checkForUpdate completes.
-// |latestVersion| may be nil if not returned from the server.
-// |latestVersion| is not a localizable string.
-- (void)upToDateCheckCompleted:(BOOL)upToDate
- latestVersion:(NSString*)latestVersion;
-
-// Callback when a startUpdate completes.
-// |successful| tells if the *check* was successful. This does not
-// necessarily mean updates installed successfully.
-// |installs| tells the number of updates that installed successfully
-// (typically 0 or 1).
-- (void)updateCompleted:(BOOL)successful installs:(int)installs;
-
-@end // protocol KeystoneGlueCallbacks
-
+#import <base/scoped_nsobject.h>
+
+// Possible outcomes of -checkForUpdate and -installUpdate. A version may
+// accompany some of these, but beware: a version is never required. For
+// statuses that can be accompanied by a version, the comment indicates what
+// version is referenced.
+enum AutoupdateStatus {
+ kAutoupdateCurrent = 0, // version of the running application
+ kAutoupdateAvailable, // version of the update that is available
+ kAutoupdateInstalled, // version of the update that was installed
+ kAutoupdateCheckFailed, // no version
+ kAutoupdateInstallFailed // no version
+};
+
+// kAutoupdateStatusNotification is the name of the notification posted when
+// -checkForUpdate and -installUpdate complete. This notification will be
+// sent with with its sender object set to the KeystoneGlue instance sending
+// 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.
+extern const NSString* const kAutoupdateStatusNotification;
+extern const NSString* const kAutoupdateStatusStatus;
+extern const NSString* const kAutoupdateStatusVersion;
// KeystoneGlue is an adapter around the KSRegistration class, allowing it to
// be used without linking directly against its containing KeystoneRegistration
@@ -55,10 +58,11 @@
KSRegistration* registration_; // strong
NSTimer* timer_; // strong
- // Data for callbacks, all strong. Deallocated (if needed) in a
- // NSNotificationCenter callback.
- NSObject<KeystoneGlueCallbacks>* startTarget_;
- NSObject<KeystoneGlueCallbacks>* checkTarget_;
+ // The most recent kAutoupdateStatusNotification notification posted.
+ scoped_nsobject<NSNotification> recentNotification_;
+
+ // YES if an update was ever successfully installed by -installUpdate.
+ BOOL updateSuccessfullyInstalled_;
}
// Return the default Keystone Glue object.
@@ -68,21 +72,27 @@
// with Keystone, and set up periodic activity pings.
- (void)registerWithKeystone;
-// Check if updates are available.
-// upToDateCheckCompleted:: called on target when done.
-// Return NO if we could not start the check.
-- (BOOL)checkForUpdate:(NSObject<KeystoneGlueCallbacks>*)target;
+// -checkForUpdate launches a check for updates, and -installUpdate begins
+// installing an available update. For each, status will be communicated via
+// a kAutoupdateStatusNotification notification, and will also be available
+// through -recentUpdateStatus.
+- (void)checkForUpdate;
+- (void)installUpdate;
+
+// Accessor for recentNotification_. Returns an autoreleased NSNotification.
+- (NSNotification*)recentNotification;
-// Start an update.
-// updateCompleted:: called on target when done.
-// This cannot be cancelled.
-// Return NO if we could not start the check.
-- (BOOL)startUpdate:(NSObject<KeystoneGlueCallbacks>*)target;
+// Clears the saved recentNotification_.
+- (void)clearRecentNotification;
-@end // KeystoneGlue
+@end // @interface KeystoneGlue
+@interface KeystoneGlue(ExposedForTesting)
-@interface KeystoneGlue (ExposedForTesting)
+// Release the shared instance. Use this in tests to reset the shared
+// instance in case strange things are done to it for testing purposes. Never
+// call this from non-test code.
++ (void)releaseDefaultKeystoneGlue;
// Load any params we need for configuring Keystone.
- (void)loadParameters;
@@ -94,11 +104,11 @@
- (void)stopTimer;
// Called when a checkForUpdate: notification completes.
-- (void)checkComplete:(NSNotification *)notification;
+- (void)checkForUpdateComplete:(NSNotification*)notification;
-// Called when a startUpdate: notification completes.
-- (void)startUpdateComplete:(NSNotification *)notification;
+// Called when an installUpdate: notification completes.
+- (void)installUpdateComplete:(NSNotification*)notification;
-@end // KeystoneGlue (ExposedForTesting)
+@end // @interface KeystoneGlue(ExposedForTesting)
#endif // CHROME_APP_KEYSTONE_GLUE_H_
diff --git a/chrome/app/keystone_glue.mm b/chrome/app/keystone_glue.mm
index 736972c..689cfdb 100644
--- a/chrome/app/keystone_glue.mm
+++ b/chrome/app/keystone_glue.mm
@@ -2,16 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "base/mac_util.h"
#import "chrome/app/keystone_glue.h"
-@interface KeystoneGlue(Private)
-
-// Called periodically to announce activity by pinging the Keystone server.
-- (void)markActive:(NSTimer*)timer;
-
-@end
-
+#include "base/logging.h"
+#include "base/mac_util.h"
+#import "base/worker_pool_mac.h"
+#include "chrome/common/chrome_constants.h"
// Provide declarations of the Keystone registration bits needed here. From
// KSRegistration.h.
@@ -31,7 +27,9 @@ NSString *KSRegistrationRemoveExistingTag = @"";
#define KSRegistrationPreserveExistingTag nil
@interface KSRegistration : NSObject
+
+ (id)registrationWithProductID:(NSString*)productID;
+
// Older API
- (BOOL)registerWithVersion:(NSString*)version
existenceCheckerType:(KSExistenceCheckerType)xctype
@@ -43,20 +41,65 @@ NSString *KSRegistrationRemoveExistingTag = @"";
existenceCheckerString:(NSString*)xc
serverURLString:(NSString*)serverURLString
preserveTTToken:(BOOL)preserveToken
- tag:(NSString *)tag;
+ tag:(NSString*)tag;
+
- (void)setActive;
- (void)checkForUpdate;
- (void)startUpdate;
-@end
+@end // @interface KSRegistration
+
+@interface KeystoneGlue(Private)
+
+// Called periodically to announce activity by pinging the Keystone server.
+- (void)markActive:(NSTimer*)timer;
+
+// 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;
+
+// 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:.
+//
+// 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
+// applied, the dictionary must be read each time, bypassing any caches such
+// as the one that NSBundle might be maintaining. Reading files can be a
+// blocking operation, and blocking operations are to be avoided on the main
+// thread. I'm not quite sure what jank means, but I bet that a blocked main
+// thread would cause some of it.
+//
+// -determineUpdateStatusAsync is called on the main thread to initiate the
+// operation. It performs initial set-up work that must be done on the main
+// thread and arranges for -determineUpdateStatusAtPath: to be called on a
+// work queue thread managed by NSOperationQueue.
+// -determineUpdateStatusAtPath: then reads the Info.plist, gets the version
+// from the 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.
+- (void)determineUpdateStatusAsync;
+- (void)determineUpdateStatusAtPath:(NSString*)appPath;
+- (void)determineUpdateStatusForVersion:(NSString*)version;
+
+@end // @interface KeystoneGlue(Private)
+
+const NSString* const kAutoupdateStatusNotification =
+ @"AutoupdateStatusNotification";
+const NSString* const kAutoupdateStatusStatus = @"status";
+const NSString* const kAutoupdateStatusVersion = @"version";
@implementation KeystoneGlue
-+ (id)defaultKeystoneGlue {
- // TODO(jrg): use base::SingletonObjC<KeystoneGlue>
- static KeystoneGlue* sDefaultKeystoneGlue = nil; // leaked
+// TODO(jrg): use base::SingletonObjC<KeystoneGlue>
+static KeystoneGlue* sDefaultKeystoneGlue = nil; // leaked
- if (sDefaultKeystoneGlue == nil) {
++ (id)defaultKeystoneGlue {
+ if (!sDefaultKeystoneGlue) {
sDefaultKeystoneGlue = [[KeystoneGlue alloc] init];
[sDefaultKeystoneGlue loadParameters];
if (![sDefaultKeystoneGlue loadKeystoneRegistration]) {
@@ -67,6 +110,29 @@ NSString *KSRegistrationRemoveExistingTag = @"";
return sDefaultKeystoneGlue;
}
++ (void)releaseDefaultKeystoneGlue {
+ [sDefaultKeystoneGlue release];
+ sDefaultKeystoneGlue = nil;
+}
+
+- (id)init {
+ if ((self = [super init])) {
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+
+ [center addObserver:self
+ selector:@selector(checkForUpdateComplete:)
+ name:KSRegistrationCheckForUpdateNotification
+ object:nil];
+
+ [center addObserver:self
+ selector:@selector(installUpdateComplete:)
+ name:KSRegistrationStartUpdateNotification
+ object:nil];
+ }
+
+ return self;
+}
+
- (void)dealloc {
[url_ release];
[productID_ release];
@@ -174,64 +240,155 @@ NSString *KSRegistrationRemoveExistingTag = @"";
[ksr setActive];
}
-- (void)checkComplete:(NSNotification *)notification {
- NSDictionary *userInfo = [notification userInfo];
- BOOL updatesAvailable = [[userInfo objectForKey:KSRegistrationStatusKey]
- boolValue];
- NSString *latestVersion = [userInfo objectForKey:KSRegistrationVersionKey];
+- (void)checkForUpdate {
+ if (!registration_) {
+ [self updateStatus:kAutoupdateCheckFailed version:nil];
+ return;
+ }
- [checkTarget_ upToDateCheckCompleted:updatesAvailable
- latestVersion:latestVersion];
- [checkTarget_ release];
- checkTarget_ = nil;
+ [registration_ checkForUpdate];
- NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
- [center removeObserver:self
- name:KSRegistrationCheckForUpdateNotification
- object:nil];
+ // Upon completion, KSRegistrationCheckForUpdateNotification will be posted,
+ // and -checkForUpdateComplete: will be called.
}
-- (BOOL)checkForUpdate:(NSObject<KeystoneGlueCallbacks>*)target {
- if (registration_ == nil)
- return NO;
- NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
- [center addObserver:self
- selector:@selector(checkComplete:)
- name:KSRegistrationCheckForUpdateNotification
- object:nil];
- checkTarget_ = [target retain];
- [registration_ checkForUpdate];
- return YES;
+- (void)checkForUpdateComplete:(NSNotification*)notification {
+ NSDictionary* userInfo = [notification userInfo];
+ BOOL updatesAvailable =
+ [[userInfo objectForKey:KSRegistrationStatusKey] boolValue];
+
+ if (updatesAvailable) {
+ // 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:KSRegistrationVersionKey];
+ [self updateStatus:kAutoupdateAvailable version:version];
+ } 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,
+ // and -updateStatus:version: will be called on the main thread when done.
+ [self determineUpdateStatusAsync];
+ }
}
-- (void)startUpdateComplete:(NSNotification *)notification {
- NSDictionary *userInfo = [notification userInfo];
- BOOL checkSuccessful = [[userInfo objectForKey:KSUpdateCheckSuccessfulKey]
- boolValue];
- int installs = [[userInfo objectForKey:KSUpdateCheckSuccessfullyInstalledKey]
- intValue];
+- (void)installUpdate {
+ if (!registration_) {
+ [self updateStatus:kAutoupdateInstallFailed version:nil];
+ return;
+ }
- [startTarget_ updateCompleted:checkSuccessful installs:installs];
- [startTarget_ release];
- startTarget_ = nil;
+ [registration_ startUpdate];
- NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
- [center removeObserver:self
- name:KSRegistrationStartUpdateNotification
- object:nil];
+ // Upon completion, KSRegistrationStartUpdateNotification will be posted,
+ // and -installUpdateComplete: will be called.
}
-- (BOOL)startUpdate:(NSObject<KeystoneGlueCallbacks>*)target {
- if (registration_ == nil)
- return NO;
- NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
- [center addObserver:self
- selector:@selector(startUpdateComplete:)
- name:KSRegistrationStartUpdateNotification
- object:nil];
- startTarget_ = [target retain];
- [registration_ startUpdate];
- return YES;
+- (void)installUpdateComplete:(NSNotification*)notification {
+ NSDictionary* userInfo = [notification userInfo];
+ BOOL checkSuccessful =
+ [[userInfo objectForKey:KSUpdateCheckSuccessfulKey] boolValue];
+ int installs =
+ [[userInfo objectForKey:KSUpdateCheckSuccessfullyInstalledKey] intValue];
+
+ if (!checkSuccessful || !installs) {
+ [self updateStatus:kAutoupdateInstallFailed version:nil];
+ } else {
+ updateSuccessfullyInstalled_ = YES;
+
+ // Nothing in the notification dictionary reports the version that was
+ // installed. Figure it out based on what's on disk.
+ [self determineUpdateStatusAsync];
+ }
+}
+
+// Runs on the main thread.
+- (void)determineUpdateStatusAsync {
+ // NSBundle is not documented as being thread-safe. Do NSBundle operations
+ // on the main thread before jumping over to a NSOperationQueue-managed
+ // thread to do blocking file input.
+ DCHECK([NSThread isMainThread]);
+
+ SEL selector = @selector(determineUpdateStatusAtPath:);
+ NSString* appPath = [[NSBundle mainBundle] bundlePath];
+ NSInvocationOperation* operation =
+ [[[NSInvocationOperation alloc] initWithTarget:self
+ selector:selector
+ object:appPath] autorelease];
+
+ NSOperationQueue* operationQueue = [WorkerPoolObjC sharedOperationQueue];
+ [operationQueue addOperation:operation];
+}
+
+// Runs on a thread managed by NSOperationQueue.
+- (void)determineUpdateStatusAtPath:(NSString*)appPath {
+ DCHECK(![NSThread isMainThread]);
+
+ NSString* appInfoPlistPath =
+ [[appPath stringByAppendingPathComponent:@"Contents"]
+ stringByAppendingPathComponent:@"Info.plist"];
+ NSDictionary* infoPlist =
+ [NSDictionary dictionaryWithContentsOfFile:appInfoPlistPath];
+ NSString* version = [infoPlist objectForKey:@"CFBundleShortVersionString"];
+
+ [self performSelectorOnMainThread:@selector(determineUpdateStatusForVersion:)
+ withObject:version
+ waitUntilDone:NO];
+}
+
+// Runs on the main thread.
+- (void)determineUpdateStatusForVersion:(NSString*)version {
+ DCHECK([NSThread isMainThread]);
+
+ AutoupdateStatus status;
+ if (updateSuccessfullyInstalled_) {
+ // If an update was successfully installed and this object saw it happen,
+ // then don't even bother comparing versions.
+ status = kAutoupdateInstalled;
+ } else {
+ NSString* currentVersion =
+ [NSString stringWithUTF8String:chrome::kChromeVersion];
+ if (!version) {
+ // If the version on disk could not be determined, assume that
+ // whatever's running is current.
+ version = currentVersion;
+ status = kAutoupdateCurrent;
+ } else if ([version isEqualToString:currentVersion]) {
+ status = kAutoupdateCurrent;
+ } else {
+ // If the version on disk doesn't match what's currently running, an
+ // update must have been applied in the background, without this app's
+ // direct participation. Leave updateSuccessfullyInstalled_ alone
+ // because there's no direct knowledge of what actually happened.
+ status = kAutoupdateInstalled;
+ }
+ }
+
+ [self updateStatus:status version:version];
+}
+
+- (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version {
+ NSNumber* statusNumber = [NSNumber numberWithInt:status];
+ NSMutableDictionary* dictionary =
+ [NSMutableDictionary dictionaryWithObject:statusNumber
+ forKey:kAutoupdateStatusStatus];
+ if (version) {
+ [dictionary setObject:version forKey:kAutoupdateStatusVersion];
+ }
+
+ NSNotification* notification =
+ [NSNotification notificationWithName:kAutoupdateStatusNotification
+ object:self
+ userInfo:dictionary];
+ recentNotification_.reset([notification retain]);
+
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+}
+
+- (NSNotification*)recentNotification {
+ return [[recentNotification_ retain] autorelease];
+}
+
+- (void)clearRecentNotification {
+ recentNotification_.reset(nil);
}
-@end
+@end // @implementation KeystoneGlue
diff --git a/chrome/app/keystone_glue_unittest.mm b/chrome/app/keystone_glue_unittest.mm
index 614bdbc..90a2f40 100644
--- a/chrome/app/keystone_glue_unittest.mm
+++ b/chrome/app/keystone_glue_unittest.mm
@@ -14,18 +14,38 @@
@implementation FakeGlueRegistration
-- (void)checkForUpdate { }
-- (void)startUpdate { }
+
+// Send the notifications that a real KeystoneGlue object would send.
+
+- (void)checkForUpdate {
+ NSNumber* yesNumber = [NSNumber numberWithBool:YES];
+ NSString* statusKey = @"Status";
+ NSDictionary* dictionary = [NSDictionary dictionaryWithObject:yesNumber
+ forKey:statusKey];
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center postNotificationName:@"KSRegistrationCheckForUpdateNotification"
+ object:nil
+ userInfo:dictionary];
+}
+
+- (void)startUpdate {
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center postNotificationName:@"KSRegistrationStartUpdateNotification"
+ object:nil];
+}
+
@end
-@interface FakeKeystoneGlue : KeystoneGlue<KeystoneGlueCallbacks> {
+@interface FakeKeystoneGlue : KeystoneGlue {
@public
BOOL upToDate_;
NSString *latestVersion_;
BOOL successful_;
int installs_;
}
+
+- (void)fakeAboutWindowCallback:(NSNotification*)notification;
@end
@@ -38,10 +58,23 @@
latestVersion_ = @"foo bar";
successful_ = YES;
installs_ = 1010101010;
+
+ // Set up an observer that takes the notification that the About window
+ // listens for.
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center addObserver:self
+ selector:@selector(fakeAboutWindowCallback:)
+ name:kAutoupdateStatusNotification
+ object:nil];
}
return self;
}
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
// For mocking
- (NSDictionary*)infoDictionary {
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
@@ -69,21 +102,24 @@
return timer_ ? YES : NO;
}
-- (void)upToDateCheckCompleted:(BOOL)upToDate
- latestVersion:(NSString*)latestVersion {
- upToDate_ = upToDate;
- latestVersion_ = latestVersion;
-}
-
-- (void)updateCompleted:(BOOL)successful installs:(int)installs {
- successful_ = successful;
- installs_ = installs;
-}
-
- (void)addFakeRegistration {
registration_ = [[FakeGlueRegistration alloc] init];
}
+- (void)fakeAboutWindowCallback:(NSNotification*)notification {
+ NSDictionary* dictionary = [notification userInfo];
+ AutoupdateStatus status = static_cast<AutoupdateStatus>(
+ [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]);
+
+ if (status == kAutoupdateAvailable) {
+ upToDate_ = NO;
+ latestVersion_ = [dictionary objectForKey:kAutoupdateStatusVersion];
+ } else if (status == kAutoupdateInstallFailed) {
+ successful_ = NO;
+ installs_ = 0;
+ }
+}
+
// Confirm we look like callbacks with nil NSNotifications
- (BOOL)confirmCallbacks {
return (!upToDate_ &&
@@ -114,12 +150,16 @@ TEST_F(KeystoneGlueTest, BasicGlobalCreate) {
Method loadMethod_ = class_getInstanceMethod([KeystoneGlue class], lks);
method_setImplementation(loadMethod_, newLoadImp_);
+ // Dump any existing KeystoneGlue shared instance so that a new one can be
+ // created with the mocked methods.
+ [KeystoneGlue releaseDefaultKeystoneGlue];
KeystoneGlue *glue = [KeystoneGlue defaultKeystoneGlue];
ASSERT_TRUE(glue);
// Fix back up the class to the way we found it.
method_setImplementation(infoMethod_, oldInfoImp_);
method_setImplementation(loadMethod_, oldLoadImp_);
+ [KeystoneGlue releaseDefaultKeystoneGlue];
}
TEST_F(KeystoneGlueTest, BasicUse) {
@@ -136,14 +176,10 @@ TEST_F(KeystoneGlueTest, BasicUse) {
ASSERT_TRUE([glue hasATimer]);
[glue stopTimer];
- ASSERT_TRUE(![glue checkForUpdate:glue] && ![glue startUpdate:glue]);
-
// Brief exercise of callbacks
[glue addFakeRegistration];
- ASSERT_TRUE([glue checkForUpdate:glue]);
- [glue checkComplete:nil];
- ASSERT_TRUE([glue startUpdate:glue]);
- [glue startUpdateComplete:nil];
+ [glue checkForUpdate];
+ [glue installUpdate];
ASSERT_TRUE([glue confirmCallbacks]);
}
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 434f61e..db96e05 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -665,26 +665,17 @@
// window controller.
- (void)aboutWindowClosed:(NSNotification*)notify {
[[NSNotificationCenter defaultCenter]
- removeObserver:self
- name:kUserClosedAboutNotification
- object:aboutController_.get()];
- aboutController_.reset(NULL);
+ removeObserver:self
+ name:kUserClosedAboutNotification
+ object:aboutController_.get()];
+ aboutController_.reset(nil);
}
- (IBAction)orderFrontStandardAboutPanel:(id)sender {
- // Otherwise bring up our special dialog (e.g. with an auto-update button).
if (!aboutController_) {
aboutController_.reset([[AboutWindowController alloc]
- initWithProfile:[self defaultProfile]]);
- if (!aboutController_) {
- // If we get here something is wacky. I managed to do it when
- // testing by explicitly forcing an auto-update to an older
- // version then trying to open the about box again (missing
- // nib). This shouldn't be possible in general but let's try
- // hard to not do nothing.
- [NSApp orderFrontStandardAboutPanel:sender];
- return;
- }
+ initWithProfile:[self defaultProfile]]);
+
// Watch for a notification of when it goes away so that we can destroy
// the controller.
[[NSNotificationCenter defaultCenter]
@@ -693,8 +684,10 @@
name:kUserClosedAboutNotification
object:aboutController_.get()];
}
+
if (![[aboutController_ window] isVisible])
[[aboutController_ window] center];
+
[aboutController_ showWindow:self];
}
diff --git a/chrome/browser/cocoa/about_window_controller.h b/chrome/browser/cocoa/about_window_controller.h
index d3117bb..23e76e0 100644
--- a/chrome/browser/cocoa/about_window_controller.h
+++ b/chrome/browser/cocoa/about_window_controller.h
@@ -5,22 +5,17 @@
#ifndef CHROME_BROWSER_COCOA_ABOUT_WINDOW_CONTROLLER_H_
#define CHROME_BROWSER_COCOA_ABOUT_WINDOW_CONTROLLER_H_
-#import <Cocoa/Cocoa.h>
-#include "base/scoped_nsobject.h"
-#import "chrome/app/keystone_glue.h"
+#import <AppKit/AppKit.h>
@class BackgroundTileView;
class Profile;
-// Returns an NSAttributedString that contains the locale specific legal text.
-NSAttributedString* BuildAboutWindowLegalTextBlock();
+// kUserClosedAboutNotification is the name of the notification posted when
+// the About window is closed.
+extern const NSString* const kUserClosedAboutNotification;
-// A window controller that handles the branded (Chrome.app) about
-// window. The branded about window has a few features beyond the
-// standard Cocoa about panel. For example, opening the about window
-// will check to see if this version is current and tell the user.
-// There is also an "update me now" button with a progress spinner.
-@interface AboutWindowController : NSWindowController<KeystoneGlueCallbacks> {
+// A window controller that handles the About box.
+@interface AboutWindowController : NSWindowController {
@private
IBOutlet NSTextField* version_;
IBOutlet BackgroundTileView* backgroundView_;
@@ -35,9 +30,6 @@ NSAttributedString* BuildAboutWindowLegalTextBlock();
BOOL updateTriggered_; // Has an update ever been triggered?
Profile* profile_; // Weak, probably the default profile.
-
- // The version we got told about by Keystone
- scoped_nsobject<NSString> newVersionAvailable_;
}
// Initialize the controller with the given profile, but does not show it.
@@ -47,17 +39,17 @@ NSAttributedString* BuildAboutWindowLegalTextBlock();
// Trigger an update right now, as initiated by a button.
- (IBAction)updateNow:(id)sender;
-@end
+@end // @interface AboutWindowController
+@interface AboutWindowController(JustForTesting)
-@interface AboutWindowController (JustForTesting)
- (NSTextView*)legalText;
- (NSButton*)updateButton;
- (NSTextField*)updateText;
-@end
+// Returns an NSAttributedString that contains locale-specific legal text.
++ (NSAttributedString*)legalTextBlock;
-// NSNotification sent when the about window is closed.
-extern NSString* const kUserClosedAboutNotification;
+@end // @interface AboutWindowController(JustForTesting)
#endif // CHROME_BROWSER_COCOA_ABOUT_WINDOW_CONTROLLER_H_
diff --git a/chrome/browser/cocoa/about_window_controller.mm b/chrome/browser/cocoa/about_window_controller.mm
index 936dc8a..1fb2496 100644
--- a/chrome/browser/cocoa/about_window_controller.mm
+++ b/chrome/browser/cocoa/about_window_controller.mm
@@ -4,7 +4,6 @@
#include "app/l10n_util_mac.h"
#include "app/resource_bundle.h"
-#include "base/file_version_info.h"
#include "base/logging.h"
#include "base/mac_util.h"
#include "base/string_util.h"
@@ -20,23 +19,8 @@
#include "grit/locale_settings.h"
#include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
-NSString* const kUserClosedAboutNotification =
- @"kUserClosedAboutNotification";
-
-@interface AboutWindowController (Private)
-- (KeystoneGlue*)defaultKeystoneGlue;
-- (void)startProgressMessageID:(uint32_t)messageID;
-- (void)startProgressMessage:(NSString*)message;
-- (void)stopProgressMessage:(NSString*)message imageID:(uint32_t)imageID;
-@end
-
namespace {
-// Keystone doesn't give us error numbers on some results, so we just make
-// our own for reporting in the UI.
-const int kUpdateInstallFailed = 128;
-const int kUpdateInstallFailedToStart = 129;
-
void AttributedStringAppendString(NSMutableAttributedString* attr_str,
NSString* str) {
// You might think doing [[attr_str mutableString] appendString:str] would
@@ -49,7 +33,6 @@ void AttributedStringAppendString(NSMutableAttributedString* attr_str,
void AttributedStringAppendHyperlink(NSMutableAttributedString* attr_str,
NSString* text, NSString* url_str) {
-
// Figure out the range of the text we're adding and add the text.
NSRange range = NSMakeRange([attr_str length], [text length]);
AttributedStringAppendString(attr_str, text);
@@ -71,7 +54,287 @@ void AttributedStringAppendHyperlink(NSMutableAttributedString* attr_str,
} // namespace
-NSAttributedString* BuildAboutWindowLegalTextBlock() {
+@interface AboutWindowController(Private)
+
+// Launches a check for available updates.
+- (void)checkForUpdate;
+
+// Notification callback, called with the status of asynchronous
+// -checkForUpdate and -updateNow: operations.
+- (void)updateStatus:(NSNotification*)notification;
+
+// These methods maintain the image (or throbber) and text displayed regarding
+// update status. -setUpdateThrobberMessage: starts a progress throbber and
+// sets the text. -setUpdateImage:message: displays an image and sets the
+// text.
+- (void)setUpdateThrobberMessage:(NSString*)message;
+- (void)setUpdateImage:(int)imageID message:(NSString*)message;
+
+@end // @interface AboutWindowController(Private)
+
+const NSString* const kUserClosedAboutNotification =
+ @"UserClosedAboutNotification";
+
+@implementation AboutWindowController
+
+- (id)initWithProfile:(Profile*)profile {
+ NSString* nibPath = [mac_util::MainAppBundle() pathForResource:@"About"
+ ofType:@"nib"];
+ if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
+ profile_ = profile;
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center addObserver:self
+ selector:@selector(updateStatus:)
+ name:kAutoupdateStatusNotification
+ object:nil];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+- (void)awakeFromNib {
+ NSBundle* bundle = mac_util::MainAppBundle();
+ NSString* chromeVersion =
+ [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
+
+#if defined(GOOGLE_CHROME_BUILD)
+ NSString* version = chromeVersion;
+#else // GOOGLE_CHROME_BUILD
+ // The format string is not localized, but this is how the displayed version
+ // is built on Windows too.
+ NSString* svnRevision = [bundle objectForInfoDictionaryKey:@"SVNRevision"];
+ NSString* version =
+ [NSString stringWithFormat:@"%@ (%@)", chromeVersion, svnRevision];
+#endif // GOOGLE_CHROME_BUILD
+
+ [version_ setStringValue:version];
+
+ // Put the two images into the UI.
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ NSImage* backgroundImage = rb.GetNSImageNamed(IDR_ABOUT_BACKGROUND_COLOR);
+ DCHECK(backgroundImage);
+ [backgroundView_ setTileImage:backgroundImage];
+ NSImage* logoImage = rb.GetNSImageNamed(IDR_ABOUT_BACKGROUND);
+ DCHECK(logoImage);
+ [logoView_ setImage:logoImage];
+
+ [[legalText_ textStorage] setAttributedString:[[self class] legalTextBlock]];
+
+ // Resize our text view now so that the |updateShift| below is set
+ // correctly. The About box has its controls manually positioned, so we need
+ // to calculate how much larger (or smaller) our text box is and store that
+ // difference in |legalShift|. We do something similar with |updateShift|
+ // below, which is either 0, or the amount of space to offset the window size
+ // because the view that contains the update button has been removed because
+ // this build doesn't have KeyStone.
+ NSRect oldLegalRect = [legalBlock_ frame];
+ [legalText_ sizeToFit];
+ NSRect newRect = oldLegalRect;
+ newRect.size.height = [legalText_ frame].size.height;
+ [legalBlock_ setFrame:newRect];
+ CGFloat legalShift = newRect.size.height - oldLegalRect.size.height;
+
+ KeystoneGlue* keystoneGlue = [KeystoneGlue defaultKeystoneGlue];
+ CGFloat updateShift;
+ if (keystoneGlue) {
+ NSNotification* recentNotification = [keystoneGlue recentNotification];
+ NSDictionary* recentDictionary = [recentNotification userInfo];
+ AutoupdateStatus recentStatus = static_cast<AutoupdateStatus>(
+ [[recentDictionary objectForKey:kAutoupdateStatusStatus] intValue]);
+ if (recentStatus == kAutoupdateInstallFailed) {
+ // A previous update attempt was unsuccessful, but no About box was
+ // around to report status. Use the saved notification to set up the
+ // About box with the error message, and to allow another chance to
+ // install the update.
+ [self updateStatus:recentNotification];
+ } else {
+ [self checkForUpdate];
+ }
+
+ updateShift = 0.0;
+ } else {
+ // Hide all the update UI
+ [updateBlock_ setHidden:YES];
+
+ // Figure out the amount being removed by taking out the update block
+ // and its spacing.
+ updateShift = NSMinY([legalBlock_ frame]) - NSMinY([updateBlock_ frame]);
+
+ NSRect legalFrame = [legalBlock_ frame];
+ legalFrame.origin.y -= updateShift;
+ [legalBlock_ setFrame:legalFrame];
+ }
+
+ NSRect backgroundFrame = [backgroundView_ frame];
+ backgroundFrame.origin.y += legalShift - updateShift;
+ [backgroundView_ setFrame:backgroundFrame];
+
+ NSSize windowDelta = NSMakeSize(0.0, legalShift - updateShift);
+
+ [GTMUILocalizerAndLayoutTweaker
+ resizeWindowWithoutAutoResizingSubViews:[self window]
+ delta:windowDelta];
+}
+
+- (void)windowWillClose:(NSNotification*)notification {
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center postNotificationName:kUserClosedAboutNotification object:self];
+}
+
+- (void)setUpdateThrobberMessage:(NSString*)message {
+ [updateStatusIndicator_ setHidden:YES];
+
+ [spinner_ setHidden:NO];
+ [spinner_ startAnimation:self];
+
+ [updateText_ setStringValue:message];
+}
+
+- (void)setUpdateImage:(int)imageID message:(NSString*)message {
+ [spinner_ stopAnimation:self];
+ [spinner_ setHidden:YES];
+
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ NSImage* statusImage = rb.GetNSImageNamed(imageID);
+ DCHECK(statusImage);
+ [updateStatusIndicator_ setImage:statusImage];
+ [updateStatusIndicator_ setHidden:NO];
+
+ [updateText_ setStringValue:message];
+}
+
+- (void)checkForUpdate {
+ [self setUpdateThrobberMessage:
+ l10n_util::GetNSStringWithFixup(IDS_UPGRADE_CHECK_STARTED)];
+ [[KeystoneGlue defaultKeystoneGlue] checkForUpdate];
+
+ // Upon completion, kAutoupdateStatusNotification will be posted, and
+ // -updateStatus: will be called.
+}
+
+- (IBAction)updateNow:(id)sender {
+ updateTriggered_ = YES;
+
+ // Don't let someone click "Update Now" twice!
+ [updateNowButton_ setEnabled:NO];
+ [self setUpdateThrobberMessage:
+ l10n_util::GetNSStringWithFixup(IDS_UPGRADE_STARTED)];
+ [[KeystoneGlue defaultKeystoneGlue] installUpdate];
+
+ // Upon completion, kAutoupdateStatusNotification will be posted, and
+ // -updateStatus: will be called.
+}
+
+- (void)updateStatus:(NSNotification*)notification {
+ NSDictionary* dictionary = [notification userInfo];
+ AutoupdateStatus status = static_cast<AutoupdateStatus>(
+ [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]);
+
+ // Don't assume |version| is a real string. It may be nil.
+ NSString* version = [dictionary objectForKey:kAutoupdateStatusVersion];
+
+ int imageID;
+ NSString* message;
+
+ switch (status) {
+ case kAutoupdateCurrent:
+ imageID = IDR_UPDATE_UPTODATE;
+ message = l10n_util::GetNSStringFWithFixup(
+ IDS_UPGRADE_ALREADY_UP_TO_DATE,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
+ base::SysNSStringToUTF16(version));
+
+ break;
+
+ case kAutoupdateAvailable:
+ imageID = IDR_UPDATE_AVAILABLE;
+ message = l10n_util::GetNSStringFWithFixup(
+ IDS_UPGRADE_AVAILABLE, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
+ [updateNowButton_ setEnabled:YES];
+
+ break;
+
+ case kAutoupdateInstalled:
+ {
+ imageID = IDR_UPDATE_UPTODATE;
+ string16 productName = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
+ if (version) {
+ message = l10n_util::GetNSStringFWithFixup(
+ IDS_UPGRADE_SUCCESSFUL,
+ productName,
+ base::SysNSStringToUTF16(version));
+ } else {
+ message = l10n_util::GetNSStringFWithFixup(
+ IDS_UPGRADE_SUCCESSFUL_NOVERSION, productName);
+ }
+
+ // TODO(mark): Turn the button in the dialog into a restart button
+ // instead of springing this sheet or dialog.
+ NSWindow* window = [self window];
+ NSWindow* restartDialogParent = [window isVisible] ? window : nil;
+ restart_browser::RequestRestart(restartDialogParent);
+ }
+
+ break;
+
+ case kAutoupdateInstallFailed:
+ // Allow another chance.
+ [updateNowButton_ setEnabled:YES];
+
+ // Fall through.
+
+ case kAutoupdateCheckFailed:
+ // TODO(mark): Keystone doesn't currently indicate when a check for
+ // updates failed. Fix that.
+ imageID = IDR_UPDATE_FAIL;
+ message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
+ IntToString16(status));
+
+ break;
+
+ default:
+ NOTREACHED();
+ return;
+ }
+
+ [self setUpdateImage:imageID message:message];
+
+ // Since the update status is now displayed in an About box, the saved state
+ // can be cleared. If the About box is closed and then reopened, this will
+ // let it start out with a clean slate and not be affected by past failures.
+ [[KeystoneGlue defaultKeystoneGlue] clearRecentNotification];
+}
+
+- (BOOL)textView:(NSTextView *)aTextView
+ clickedOnLink:(id)link
+ atIndex:(NSUInteger)charIndex {
+ // We always create a new window, so there's no need to try to re-use
+ // an existing one just to pass in the NEW_WINDOW disposition.
+ Browser* browser = Browser::Create(profile_);
+ if (browser) {
+ browser->OpenURL(GURL([link UTF8String]), GURL(), NEW_WINDOW,
+ PageTransition::LINK);
+ }
+ return YES;
+}
+
+- (NSTextView*)legalText {
+ return legalText_;
+}
+
+- (NSButton*)updateButton {
+ return updateNowButton_;
+}
+
+- (NSTextField*)updateText {
+ return updateText_;
+}
+
++ (NSAttributedString*)legalTextBlock {
// Windows builds this up in a very complex way, we're just trying to model
// it the best we can to get all the information in (they actually do it
// but created Labels and Links that they carefully place to make it appear
@@ -82,7 +345,8 @@ NSAttributedString* BuildAboutWindowLegalTextBlock() {
[[[NSMutableAttributedString alloc] init] autorelease];
[legal_block beginEditing];
- NSString* copyright = l10n_util::GetNSString(IDS_ABOUT_VERSION_COPYRIGHT);
+ NSString* copyright =
+ l10n_util::GetNSStringWithFixup(IDS_ABOUT_VERSION_COPYRIGHT);
AttributedStringAppendString(legal_block, copyright);
// These are the markers directly in IDS_ABOUT_VERSION_LICENSE
@@ -97,7 +361,8 @@ NSAttributedString* BuildAboutWindowLegalTextBlock() {
// Now fetch the license string and deal with the markers
- NSString* license = l10n_util::GetNSString(IDS_ABOUT_VERSION_LICENSE);
+ NSString* license =
+ l10n_util::GetNSStringWithFixup(IDS_ABOUT_VERSION_LICENSE);
NSRange begin_chr = [license rangeOfString:kBeginLinkChr];
NSRange begin_oss = [license rangeOfString:kBeginLinkOss];
@@ -166,7 +431,8 @@ NSAttributedString* BuildAboutWindowLegalTextBlock() {
&url_offsets);
DCHECK_EQ(url_offsets.size(), 1U);
NSString* about_terms = base::SysWideToNSString(w_about_terms);
- NSString* terms_link_text = l10n_util::GetNSString(IDS_TERMS_OF_SERVICE);
+ NSString* terms_link_text =
+ l10n_util::GetNSStringWithFixup(IDS_TERMS_OF_SERVICE);
AttributedStringAppendString(legal_block, @"\n\n");
sub_str = [about_terms substringToIndex:url_offsets[0]];
@@ -174,7 +440,7 @@ NSAttributedString* BuildAboutWindowLegalTextBlock() {
AttributedStringAppendHyperlink(legal_block, terms_link_text, kTOS);
sub_str = [about_terms substringFromIndex:url_offsets[0]];
AttributedStringAppendString(legal_block, sub_str);
-#endif // defined(GOOGLE_CHROME_BUILD)
+#endif // GOOGLE_CHROME_BUILD
// We need to explicitly select Lucida Grande because once we click on
// the NSTextView, it changes to Helvetica 12 otherwise.
@@ -187,246 +453,4 @@ NSAttributedString* BuildAboutWindowLegalTextBlock() {
return legal_block;
}
-@implementation AboutWindowController
-
-- (id)initWithProfile:(Profile*)profile {
- NSString* nibpath = [mac_util::MainAppBundle() pathForResource:@"About"
- ofType:@"nib"];
- self = [super initWithWindowNibPath:nibpath owner:self];
- if (self) {
- profile_ = profile;
- }
- return self;
-}
-
-- (void)awakeFromNib {
- // Set our current version.
- scoped_ptr<FileVersionInfo> version_info(
- FileVersionInfo::CreateFileVersionInfoForCurrentModule());
- std::wstring version(version_info->product_version());
-#if !defined(GOOGLE_CHROME_BUILD)
- // Yes, Windows does this raw since it is only in Chromium builds
- // src/chrome/browser/views/about_chrome_view.cc AboutChromeView::Init()
- version += L" (";
- version += version_info->last_change();
- version += L")";
-#endif
- NSString* nsversion = base::SysWideToNSString(version);
- [version_ setStringValue:nsversion];
-
- // Put the two images into the ui
- ResourceBundle& rb = ResourceBundle::GetSharedInstance();
- NSImage* backgroundImage = rb.GetNSImageNamed(IDR_ABOUT_BACKGROUND_COLOR);
- DCHECK(backgroundImage);
- [backgroundView_ setTileImage:backgroundImage];
- NSImage* logoImage = rb.GetNSImageNamed(IDR_ABOUT_BACKGROUND);
- DCHECK(logoImage);
- [logoView_ setImage:logoImage];
-
- [[legalText_ textStorage]
- setAttributedString:BuildAboutWindowLegalTextBlock()];
-
- // Resize our text view now so that the |updateShift| below is set
- // correctly. The about box has its controls manually positioned, so we need
- // to calculate how much larger (or smaller) our text box is and store that
- // difference in |legalShift|. We do something similar with |updateShift|
- // below, which is either 0, or the amount of space to offset the window size
- // because the view that contains the update button has been removed because
- // this build doesn't have KeyStone.
- NSRect oldLegalRect = [legalBlock_ frame];
- [legalText_ sizeToFit];
- NSRect newRect = oldLegalRect;
- newRect.size.height = [legalText_ frame].size.height;
- [legalBlock_ setFrame:newRect];
- CGFloat legalShift = newRect.size.height - oldLegalRect.size.height;
-
- KeystoneGlue* keystone = [self defaultKeystoneGlue];
- CGFloat updateShift = 0.0;
- if (keystone) {
- // Initiate an update check.
- if ([keystone checkForUpdate:self]) {
- [self startProgressMessageID:IDS_UPGRADE_CHECK_STARTED];
- }
- } else {
- // Hide all the update UI
- [updateBlock_ setHidden:YES];
- // Figure out the amount we're removing by taking about the update block
- // (and it's spacing).
- updateShift = NSMinY([legalBlock_ frame]) - NSMinY([updateBlock_ frame]);
- }
-
- // Adjust the sizes/locations.
- NSRect rect = [legalBlock_ frame];
- rect.origin.y -= updateShift;
- [legalBlock_ setFrame:rect];
-
- rect = [backgroundView_ frame];
- rect.origin.y = rect.origin.y - updateShift + legalShift;
- [backgroundView_ setFrame:rect];
-
- NSSize windowDelta = NSMakeSize(0, (legalShift - updateShift));
- [GTMUILocalizerAndLayoutTweaker
- resizeWindowWithoutAutoResizingSubViews:[self window]
- delta:windowDelta];
-}
-
-- (KeystoneGlue*)defaultKeystoneGlue {
- return [KeystoneGlue defaultKeystoneGlue];
-}
-
-- (void)startProgressMessageID:(uint32_t)messageID {
- NSString* message = l10n_util::GetNSStringWithFixup(messageID);
- [self startProgressMessage:message];
-}
-
-- (void)startProgressMessage:(NSString*)message {
- [updateStatusIndicator_ setHidden:YES];
- [spinner_ setHidden:NO];
- [spinner_ startAnimation:self];
-
- [updateText_ setStringValue:message];
-}
-
-- (void)stopProgressMessage:(NSString*)message imageID:(uint32_t)imageID {
- [spinner_ stopAnimation:self];
- [spinner_ setHidden:YES];
- if (imageID) {
- [updateStatusIndicator_ setHidden:NO];
- ResourceBundle& rb = ResourceBundle::GetSharedInstance();
- NSImage* statusImage = rb.GetNSImageNamed(imageID);
- DCHECK(statusImage);
- [updateStatusIndicator_ setImage:statusImage];
- }
-
- [updateText_ setStringValue:message];
-}
-
-// Callback from KeystoneGlue; implementation of KeystoneGlueCallbacks protocol.
-// Warning: latest version may be nil if not set in server config.
-- (void)upToDateCheckCompleted:(BOOL)updatesAvailable
- latestVersion:(NSString*)latestVersion {
- uint32_t imageID;
- NSString* message;
- if (updatesAvailable) {
- newVersionAvailable_.reset([latestVersion copy]);
-
- // Window UI doesn't put the version number in the string.
- imageID = IDR_UPDATE_AVAILABLE;
- message =
- l10n_util::GetNSStringF(IDS_UPGRADE_AVAILABLE,
- l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
- [updateNowButton_ setEnabled:YES];
- } else {
- // NOTE: This is can be a lie, Keystone does not provide us with an error if
- // it was not able to reach the server. So we can't completely map to the
- // Windows UI.
-
- // Keystone does not provide the version number when we are up to date so to
- // maintain the UI, we just go fetch our version and call it good.
- scoped_ptr<FileVersionInfo> version_info(
- FileVersionInfo::CreateFileVersionInfoForCurrentModule());
- std::wstring version(version_info->product_version());
-
- // TODO: We really should check to see if what is on disk is newer then what
- // is running and report it as such. (Windows has some messages that can
- // help with this.) http://crbug.com/13165
-
- imageID = IDR_UPDATE_UPTODATE;
- message =
- l10n_util::GetNSStringF(IDS_UPGRADE_ALREADY_UP_TO_DATE,
- l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
- WideToUTF16(version));
- }
- [self stopProgressMessage:message imageID:imageID];
-}
-
-- (void)windowWillClose:(NSNotification*)notification {
- // If an update has ever been triggered, we force reuse of the same About Box.
- // This gives us 2 things:
- // 1. If an update is ongoing and the window was closed we would have
- // no way of getting status.
- // 2. If we have a "Please restart" message we want it to stay there.
- if (updateTriggered_)
- return;
-
- [[NSNotificationCenter defaultCenter]
- postNotificationName:kUserClosedAboutNotification
- object:self];
-}
-
-// Callback from KeystoneGlue; implementation of KeystoneGlueCallbacks protocol.
-- (void)updateCompleted:(BOOL)successful installs:(int)installs {
- uint32_t imageID;
- NSString* message;
- if (successful && installs) {
- imageID = IDR_UPDATE_UPTODATE;
- if ([newVersionAvailable_.get() length]) {
- message =
- l10n_util::GetNSStringF(IDS_UPGRADE_SUCCESSFUL,
- l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
- base::SysNSStringToUTF16(
- newVersionAvailable_.get()));
- } else {
- message =
- l10n_util::GetNSStringF(IDS_UPGRADE_SUCCESSFUL_NOVERSION,
- l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
- }
-
- // Tell the user to restart their browser.
- restart_browser::RequestRestart(nil);
-
- } else {
- imageID = IDR_UPDATE_FAIL;
- message =
- l10n_util::GetNSStringF(IDS_UPGRADE_ERROR,
- IntToString16(kUpdateInstallFailed));
-
- // Allow a second chance.
- [updateNowButton_ setEnabled:YES];
- }
-
- [self stopProgressMessage:message imageID:imageID];
-}
-
-- (IBAction)updateNow:(id)sender {
- updateTriggered_ = YES;
-
- // Don't let someone click "Update Now" twice!
- [updateNowButton_ setEnabled:NO];
- if ([[self defaultKeystoneGlue] startUpdate:self]) {
- // Clear any previous error message from the throbber area.
- [self startProgressMessageID:IDS_UPGRADE_STARTED];
- } else {
- NSString* message =
- l10n_util::GetNSStringF(IDS_UPGRADE_ERROR,
- IntToString16(kUpdateInstallFailedToStart));
- [self stopProgressMessage:message imageID:IDR_UPDATE_FAIL];
- }
-}
-
-- (BOOL)textView:(NSTextView *)aTextView
- clickedOnLink:(id)link
- atIndex:(NSUInteger)charIndex {
- // We always create a new window, so there's no need to try to re-use
- // an existing one just to pass in the NEW_WINDOW disposition.
- Browser* browser = Browser::Create(profile_);
- if (browser)
- browser->OpenURL(GURL([link UTF8String]), GURL(), NEW_WINDOW,
- PageTransition::LINK);
- return YES;
-}
-
-- (NSTextView*)legalText {
- return legalText_;
-}
-
-- (NSButton*)updateButton {
- return updateNowButton_;
-}
-
-- (NSTextField*)updateText {
- return updateText_;
-}
-
-@end
-
+@end // @implementation AboutWindowController
diff --git a/chrome/browser/cocoa/about_window_controller_unittest.mm b/chrome/browser/cocoa/about_window_controller_unittest.mm
index 3117b08..c7b65fe 100644
--- a/chrome/browser/cocoa/about_window_controller_unittest.mm
+++ b/chrome/browser/cocoa/about_window_controller_unittest.mm
@@ -5,6 +5,7 @@
#import <Cocoa/Cocoa.h>
#import "base/scoped_nsobject.h"
+#import "chrome/app/keystone_glue.h"
#import "chrome/browser/cocoa/about_window_controller.h"
#include "chrome/browser/cocoa/browser_test_helper.h"
#include "chrome/browser/cocoa/cocoa_test_helper.h"
@@ -13,6 +14,23 @@
namespace {
+void PostAutoupdateStatusNotification(AutoupdateStatus status,
+ NSString* version) {
+ NSNumber* statusNumber = [NSNumber numberWithInt:status];
+ NSMutableDictionary* dictionary =
+ [NSMutableDictionary dictionaryWithObjects:&statusNumber
+ forKeys:&kAutoupdateStatusStatus
+ count:1];
+ if (version) {
+ [dictionary setObject:version forKey:kAutoupdateStatusVersion];
+ }
+
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center postNotificationName:kAutoupdateStatusNotification
+ object:nil
+ userInfo:dictionary];
+}
+
class AboutWindowControllerTest : public PlatformTest {
public:
virtual void SetUp() {
@@ -27,7 +45,7 @@ class AboutWindowControllerTest : public PlatformTest {
};
TEST_F(AboutWindowControllerTest, TestCopyright) {
- NSString* text = [BuildAboutWindowLegalTextBlock() string];
+ NSString* text = [[AboutWindowController legalTextBlock] string];
// Make sure we have the word "Copyright" in it, which is present in all
// locales.
@@ -36,7 +54,7 @@ TEST_F(AboutWindowControllerTest, TestCopyright) {
}
TEST_F(AboutWindowControllerTest, RemovesLinkAnchors) {
- NSString* text = [BuildAboutWindowLegalTextBlock() string];
+ NSString* text = [[AboutWindowController legalTextBlock] string];
// Make sure that we removed the "BEGIN_LINK" and "END_LINK" anchors.
NSRange range = [text rangeOfString:@"BEGIN_LINK"];
@@ -47,7 +65,7 @@ TEST_F(AboutWindowControllerTest, RemovesLinkAnchors) {
}
TEST_F(AboutWindowControllerTest, AwakeNibSetsString) {
- NSAttributedString* legal_text = BuildAboutWindowLegalTextBlock();
+ NSAttributedString* legal_text = [AboutWindowController legalTextBlock];
NSAttributedString* text_storage =
[[about_window_controller_ legalText] textStorage];
@@ -60,7 +78,7 @@ TEST_F(AboutWindowControllerTest, TestButton) {
// Not enabled until we know if updates are available.
ASSERT_FALSE([button isEnabled]);
- [about_window_controller_ upToDateCheckCompleted:YES latestVersion:nil];
+ PostAutoupdateStatusNotification(kAutoupdateAvailable, nil);
ASSERT_TRUE([button isEnabled]);
// Make sure the button is hooked up
@@ -75,16 +93,52 @@ TEST_F(AboutWindowControllerTest, TestButton) {
// Doesn't confirm correctness, but does confirm something happens.
TEST_F(AboutWindowControllerTest, TestCallbacks) {
NSString *lastText = [[about_window_controller_ updateText]
- stringValue];
- [about_window_controller_ upToDateCheckCompleted:NO latestVersion:@"foo"];
+ stringValue];
+ PostAutoupdateStatusNotification(kAutoupdateCurrent, @"foo");
+ ASSERT_FALSE([lastText isEqual:[[about_window_controller_ updateText]
+ stringValue]]);
+
+ lastText = [[about_window_controller_ updateText] stringValue];
+ PostAutoupdateStatusNotification(kAutoupdateCurrent, @"foo");
+ ASSERT_TRUE([lastText isEqual:[[about_window_controller_ updateText]
+ stringValue]]);
+
+ lastText = [[about_window_controller_ updateText] stringValue];
+ PostAutoupdateStatusNotification(kAutoupdateCurrent, @"bar");
+ ASSERT_FALSE([lastText isEqual:[[about_window_controller_ updateText]
+ stringValue]]);
+
+ lastText = [[about_window_controller_ updateText] stringValue];
+ PostAutoupdateStatusNotification(kAutoupdateAvailable, nil);
ASSERT_FALSE([lastText isEqual:[[about_window_controller_ updateText]
- stringValue]]);
+ stringValue]]);
lastText = [[about_window_controller_ updateText] stringValue];
- [about_window_controller_ updateCompleted:NO installs:0];
+ PostAutoupdateStatusNotification(kAutoupdateCheckFailed, nil);
+ ASSERT_FALSE([lastText isEqual:[[about_window_controller_ updateText]
+ stringValue]]);
+
+#if 0
+ // TODO(mark): The kAutoupdateInstalled portion of the test is disabled
+ // because it leaks restart dialogs. If the About box is revised to use
+ // a button within the box to advise a restart instead of popping dialogs,
+ // these tests should be enabled.
+
+ lastText = [[about_window_controller_ updateText] stringValue];
+ PostAutoupdateStatusNotification(kAutoupdateInstalled, @"ver");
+ ASSERT_FALSE([lastText isEqual:[[about_window_controller_ updateText]
+ stringValue]]);
+
+ lastText = [[about_window_controller_ updateText] stringValue];
+ PostAutoupdateStatusNotification(kAutoupdateInstalled, nil);
+ ASSERT_FALSE([lastText isEqual:[[about_window_controller_ updateText]
+ stringValue]]);
+#endif
+
+ lastText = [[about_window_controller_ updateText] stringValue];
+ PostAutoupdateStatusNotification(kAutoupdateInstallFailed, nil);
ASSERT_FALSE([lastText isEqual:[[about_window_controller_
- updateText] stringValue]]);
+ updateText] stringValue]]);
}
} // namespace
-
diff --git a/chrome/browser/cocoa/restart_browser.mm b/chrome/browser/cocoa/restart_browser.mm
index 9a17f04..726fe65 100644
--- a/chrome/browser/cocoa/restart_browser.mm
+++ b/chrome/browser/cocoa/restart_browser.mm
@@ -53,7 +53,7 @@ void RequestRestart(NSWindow* parent) {
RestartHelper* helper = [[RestartHelper alloc] init];
NSAlert* alert = [helper alert];
- [alert setAlertStyle:NSCriticalAlertStyle];
+ [alert setAlertStyle:NSInformationalAlertStyle];
[alert setMessageText:title];
[alert setInformativeText:text];
[alert addButtonWithTitle:okBtn];