Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(741)

Unified Diff: chrome/browser/cocoa/about_window_controller.mm

Issue 437053: In-application Keystone ticket promotion (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 11 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/cocoa/about_window_controller.mm
===================================================================
--- chrome/browser/cocoa/about_window_controller.mm (revision 33240)
+++ chrome/browser/cocoa/about_window_controller.mm (working copy)
@@ -2,16 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#import "chrome/browser/cocoa/about_window_controller.h"
+
#include "app/l10n_util_mac.h"
#include "app/resource_bundle.h"
#include "base/logging.h"
#include "base/mac_util.h"
#include "base/string_util.h"
#include "base/sys_string_conversions.h"
-#import "chrome/app/keystone_glue.h"
#include "chrome/browser/browser_list.h"
-#import "chrome/browser/cocoa/about_window_controller.h"
#import "chrome/browser/cocoa/background_tile_view.h"
+#import "chrome/browser/cocoa/keystone_glue.h"
#include "chrome/browser/cocoa/restart_browser.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
@@ -59,6 +60,15 @@
// Launches a check for available updates.
- (void)checkForUpdate;
+// Turns the update and promotion blocks on and off as needed based on whether
+// updates are possible and promotion is desired or required.
+- (void)adjustUpdateUIVisibility;
+
+// Maintains the update and promotion block visibility and window sizing.
+// This uses bool instead of BOOL for the convenience of the internal
+// implementation.
+- (void)setAllowsUpdate:(bool)update allowsPromotion:(bool)promotion;
+
// Notification callback, called with the status of asynchronous
// -checkForUpdate and -updateNow: operations.
- (void)updateStatus:(NSNotification*)notification;
@@ -106,11 +116,11 @@
// YES when an About box is currently showing the kAutoupdateInstallFailed
// status, or if no About box is visible, if the most recent About box to be
// closed was closed while showing this status. When an About box opens, if
-// the recent status is kAutoupdateInstallFailed and
-// recentShownInstallFailedStatus is NO, the failure needs to be shown instead
-// of launching a new update check. recentShownInstallFailedStatus is
+// the recent status is kAutoupdateInstallFailed or kAutoupdatePromoteFailed
+// and recentShownUserActionFailedStatus is NO, the failure needs to be shown
+// instead of launching a new update check. recentShownInstallFailedStatus is
// maintained by -updateStatus:.
-static BOOL recentShownInstallFailedStatus = NO;
+static BOOL recentShownUserActionFailedStatus = NO;
- (void)awakeFromNib {
NSBundle* bundle = mac_util::MainAppBundle();
@@ -146,7 +156,7 @@
// 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.
+ // this build doesn't have Keystone.
NSRect oldLegalRect = [legalBlock_ frame];
[legalText_ sizeToFit];
NSRect newRect = oldLegalRect;
@@ -154,19 +164,39 @@
[legalBlock_ setFrame:newRect];
CGFloat legalShift = newRect.size.height - oldLegalRect.size.height;
- KeystoneGlue* keystoneGlue = [KeystoneGlue defaultKeystoneGlue];
- CGFloat updateShift;
- if (keystoneGlue) {
+ NSRect backgroundFrame = [backgroundView_ frame];
+ backgroundFrame.origin.y += legalShift;
+ [backgroundView_ setFrame:backgroundFrame];
+
+ NSSize windowDelta = NSMakeSize(0.0, legalShift);
+ [GTMUILocalizerAndLayoutTweaker
+ resizeWindowWithoutAutoResizingSubViews:[self window]
+ delta:windowDelta];
+
+ windowHeight_ = [[self window] frame].size.height;
+
+ [self adjustUpdateUIVisibility];
+
+ // Don't do anything update-related if adjustUpdateUIVisibility decided that
+ // updates aren't possible.
+ if (![updateBlock_ isHidden]) {
+ KeystoneGlue* keystoneGlue = [KeystoneGlue defaultKeystoneGlue];
+ AutoupdateStatus recentStatus = [keystoneGlue recentStatus];
if ([keystoneGlue asyncOperationPending] ||
- ([keystoneGlue recentStatus] == kAutoupdateInstallFailed &&
- !recentShownInstallFailedStatus)) {
+ recentStatus == kAutoupdateRegisterFailed ||
+ ((recentStatus == kAutoupdateInstallFailed ||
+ recentStatus == kAutoupdatePromoteFailed) &&
+ !recentShownUserActionFailedStatus)) {
// If an asynchronous update operation is currently pending, such as a
// check for updates or an update installation attempt, set the status
// up correspondingly without launching a new update check.
//
- // If a previous update attempt was unsuccessful but no About box was
- // around to report the error, show it now, and allow another chance to
- // install the update.
+ // If registration failed, no other operations make sense, so just go
+ // straight to the error.
+ //
+ // If a previous update or promotion attempt was unsuccessful but no
+ // About box was around to report the error, show it now, and allow
+ // another chance to perform the action.
[self updateStatus:[keystoneGlue recentNotification]];
} else {
// Launch a new update check, even if one was already completed, because
@@ -174,34 +204,174 @@
// in the background since the last time an About box was displayed.
[self checkForUpdate];
}
+ }
- updateShift = 0.0;
+ [[self window] center];
+}
+
+- (void)windowWillClose:(NSNotification*)notification {
+ [self autorelease];
+}
+
+- (void)adjustUpdateUIVisibility {
+ bool allowUpdate;
+ bool allowPromotion;
+
+ KeystoneGlue* keystoneGlue = [KeystoneGlue defaultKeystoneGlue];
+ if (keystoneGlue && ![keystoneGlue isOnReadOnlyFilesystem]) {
+ AutoupdateStatus recentStatus = [keystoneGlue recentStatus];
+ if (recentStatus == kAutoupdateRegistering ||
+ recentStatus == kAutoupdateRegisterFailed ||
+ recentStatus == kAutoupdatePromoted) {
+ // Show the update block while registering so that there's a progress
+ // spinner, and if registration failed so that there's an error message.
+ // Show it following a promotion because updates should be possible
+ // after promotion successfully completes.
+ allowUpdate = true;
+
+ // Promotion isn't possible at this point.
+ allowPromotion = false;
+ } else if (recentStatus == kAutoupdatePromoteFailed) {
+ // TODO(mark): Add kAutoupdatePromoting to this block. KSRegistration
+ // currently handles the promotion synchronously, meaning that the main
+ // thread's loop doesn't spin, meaning that animations and other updates
+ // to the window won't occur until KSRegistration is done with
+ // promotion. This looks laggy and bad and probably qualifies as
+ // "jank." For now, there just won't be any visual feedback while
+ // promotion is in progress, but it should complete (or fail) very
+ // quickly. http://b/2290009.
+ //
+ // Also see the TODO for kAutoupdatePromoting in -updateStatus:version:.
+ //
+ // Show the update block so that there's some visual feedback that
+ // promotion is under way or that it's failed. Show the promotion block
+ // because the user either just clicked that button or because the user
+ // should be able to click it again.
+ allowUpdate = true;
+ allowPromotion = true;
+ } else {
+ // Show the update block only if a promotion is not absolutely required.
+ allowUpdate = ![keystoneGlue needsPromotion];
+
+ // Show the promotion block if promotion is a possibility.
+ allowPromotion = [keystoneGlue wantsPromotion];
+ }
} else {
- // Hide all the update UI
- [updateBlock_ setHidden:YES];
+ // There is no glue, or the application is on a read-only filesystem.
+ // Updates and promotions are impossible.
+ allowUpdate = false;
+ allowPromotion = false;
+ }
- // Figure out the amount being removed by taking out the update block
- // and its spacing.
- updateShift = NSMinY([legalBlock_ frame]) - NSMinY([updateBlock_ frame]);
+ [self setAllowsUpdate:allowUpdate allowsPromotion:allowPromotion];
+}
- NSRect legalFrame = [legalBlock_ frame];
- legalFrame.origin.y -= updateShift;
- [legalBlock_ setFrame:legalFrame];
+- (void)setAllowsUpdate:(bool)update allowsPromotion:(bool)promotion {
+ bool oldUpdate = ![updateBlock_ isHidden];
+ bool oldPromotion = ![promotionBlock_ isHidden];
+
+ if (promotion == oldPromotion && update == oldUpdate) {
+ return;
}
- NSRect backgroundFrame = [backgroundView_ frame];
- backgroundFrame.origin.y += legalShift - updateShift;
- [backgroundView_ setFrame:backgroundFrame];
+ NSRect updateFrame = [updateBlock_ frame];
+ NSRect promotionFrame = [promotionBlock_ frame];
- NSSize windowDelta = NSMakeSize(0.0, legalShift - updateShift);
+ // This routine assumes no space between the update and promotion blocks.
+ DCHECK_EQ(NSMinY(updateFrame), NSMaxY(promotionFrame));
- [GTMUILocalizerAndLayoutTweaker
- resizeWindowWithoutAutoResizingSubViews:[self window]
- delta:windowDelta];
-}
+ bool promotionOnly = !update && promotion;
+ bool oldPromotionOnly = !oldUpdate && oldPromotion;
+ if (promotionOnly != oldPromotionOnly) {
+ // The window is transitioning from having a promotion block and no update
+ // block to any other state, or the other way around. Move the promotion
+ // frame up so that its top edge is in the same position as the update
+ // frame's top edge, or move it back down to its original location.
+ promotionFrame.origin.y += (promotionOnly ? 1.0 : -1.0) *
+ NSHeight(updateFrame);
+ }
-- (void)windowWillClose:(NSNotification*)notification {
- [self autorelease];
+ CGFloat delta = 0.0;
+
+ if (update != oldUpdate) {
+ [updateBlock_ setHidden:!update];
+ delta += (update ? 1.0 : -1.0) * NSHeight(updateFrame);
+ }
+
+ if (promotion != oldPromotion) {
+ [promotionBlock_ setHidden:!promotion];
+ delta += (promotion ? 1.0 : -1.0) * NSHeight(promotionFrame);
+ }
+
+ NSRect legalFrame = [legalBlock_ frame];
+
+ bool updateOrPromotion = update || promotion;
+ bool oldUpdateOrPromotion = oldUpdate || oldPromotion;
+ if (updateOrPromotion != oldUpdateOrPromotion) {
+ // The window is transitioning from having an update or promotion block or
+ // both to not having either, or the other way around. Adjust delta to
+ // account for the space between the legal block and the update or
+ // promotion block. When the update and promotion blocks are not visible,
+ // this extra spacing is not used.
+ delta += (updateOrPromotion ? 1.0 : -1.0) *
+ (NSMinY(legalFrame) - NSMaxY(updateFrame));
+ }
+
+ // The promotion frame may have changed even if delta is 0, so always reset
+ // its frame.
+ promotionFrame.origin.y += delta;
+ [promotionBlock_ setFrame:promotionFrame];
+
+ if (delta) {
+ updateFrame.origin.y += delta;
+ [updateBlock_ setFrame:updateFrame];
+
+ legalFrame.origin.y += delta;
+ [legalBlock_ setFrame:legalFrame];
+
+ NSRect backgroundFrame = [backgroundView_ frame];
+ backgroundFrame.origin.y += delta;
+ [backgroundView_ setFrame:backgroundFrame];
+
+ // GTMUILocalizerAndLayoutTweaker resizes the window without any
+ // opportunity for animation. In order to animate, disable window
+ // updates, save the current frame, let GTMUILocalizerAndLayoutTweaker do
+ // its thing, save the desired frame, restore the original frame, and then
+ // animate.
+ NSWindow* window = [self window];
+ [window disableScreenUpdatesUntilFlush];
+
+ NSRect oldFrame = [window frame];
+
+ // GTMUILocalizerAndLayoutTweaker applies its delta to the window's
+ // current size (like oldFrame.size), but oldFrame isn't trustworthy if
+ // an animation is in progress. Set the window's frame to
+ // intermediateFrame, which is a frame of the size that an existing
+ // animation is animating to, so that GTM can apply the delta to the right
+ // size.
+ NSRect intermediateFrame = oldFrame;
+ intermediateFrame.origin.y -= intermediateFrame.size.height - windowHeight_;
+ intermediateFrame.size.height = windowHeight_;
+ [window setFrame:intermediateFrame display:NO];
+
+ NSSize windowDelta = NSMakeSize(0.0, delta);
+ [GTMUILocalizerAndLayoutTweaker
+ resizeWindowWithoutAutoResizingSubViews:window
+ delta:windowDelta];
+ [window setFrameTopLeftPoint:NSMakePoint(NSMinX(intermediateFrame),
+ NSMaxY(intermediateFrame))];
+ NSRect newFrame = [window frame];
+
+ windowHeight_ += delta;
+
+ if (![[self window] isVisible]) {
+ // Don't animate if the window isn't on screen yet.
+ [window setFrame:newFrame display:NO];
+ } else {
+ [window setFrame:oldFrame display:NO];
+ [window setFrame:newFrame display:YES animate:YES];
+ }
+ }
}
- (void)setUpdateThrobberMessage:(NSString*)message {
@@ -238,8 +408,6 @@
}
- (IBAction)updateNow:(id)sender {
- updateTriggered_ = YES;
-
[[KeystoneGlue defaultKeystoneGlue] installUpdate];
// Immediately, kAutoupdateStatusNotification will be posted, and
@@ -250,8 +418,23 @@
// installation attempt.
}
+- (IBAction)promoteUpdater:(id)sender {
+ [[KeystoneGlue defaultKeystoneGlue] promoteTicket];
+
+ // Immediately, kAutoupdateStatusNotification will be posted, and
+ // -updateStatus: will be called with status kAutoupdatePromoting.
+ //
+ // Upon completion, kAutoupdateStatusNotification will be posted, and
+ // -updateStatus: will be called with a status indicating a result of the
+ // installation attempt.
+ //
+ // If the promotion was successful, KeystoneGlue will re-register the ticket
+ // and -updateStatus: will be called again indicating first that
+ // registration is in progress and subsequently that it has completed.
+}
+
- (void)updateStatus:(NSNotification*)notification {
- recentShownInstallFailedStatus = NO;
+ recentShownUserActionFailedStatus = NO;
NSDictionary* dictionary = [notification userInfo];
AutoupdateStatus status = static_cast<AutoupdateStatus>(
@@ -260,14 +443,48 @@
// Don't assume |version| is a real string. It may be nil.
NSString* version = [dictionary objectForKey:kAutoupdateStatusVersion];
+ bool updateMessage = true;
bool throbber = false;
int imageID = 0;
NSString* message;
+ bool enableUpdateButton = false;
+ bool enablePromoteButton = true;
switch (status) {
+ case kAutoupdateRegistering:
+ // When registering, use the "checking" message. The check will be
+ // launched if appropriate immediately after registration.
+ throbber = true;
+ message = l10n_util::GetNSStringWithFixup(IDS_UPGRADE_CHECK_STARTED);
+ enablePromoteButton = false;
+
+ break;
+
+ case kAutoupdateRegistered:
+ // Once registered, the ability to update and promote is known.
+ [self adjustUpdateUIVisibility];
+
+ if (![updateBlock_ isHidden]) {
+ // If registration completes while the window is visible, go straight
+ // into an update check. Return immediately, this routine will be
+ // re-entered shortly with kAutoupdateChecking.
+ [self checkForUpdate];
+ return;
+ }
+
+ // Nothing actually failed, but updates aren't possible. The throbber
+ // and message are hidden, but they'll be reset to these dummy values
+ // just to get the throbber to stop spinning if it's running.
+ imageID = IDR_UPDATE_FAIL;
+ message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
+ IntToString16(status));
+
+ break;
+
case kAutoupdateChecking:
throbber = true;
message = l10n_util::GetNSStringWithFixup(IDS_UPGRADE_CHECK_STARTED);
+ enablePromoteButton = false;
break;
@@ -284,16 +501,14 @@
imageID = IDR_UPDATE_AVAILABLE;
message = l10n_util::GetNSStringFWithFixup(
IDS_UPGRADE_AVAILABLE, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
- [updateNowButton_ setEnabled:YES];
+ enableUpdateButton = true;
break;
case kAutoupdateInstalling:
- // Don't let anyone click "Update Now" twice.
- [updateNowButton_ setEnabled:NO];
-
throbber = true;
message = l10n_util::GetNSStringWithFixup(IDS_UPGRADE_STARTED);
+ enablePromoteButton = false;
break;
@@ -320,15 +535,80 @@
break;
+ case kAutoupdatePromoting:
+#if 1
+ // TODO(mark): See the TODO in -adjustUpdateUIVisibility for an
+ // explanation of why nothing can be done here at the moment. When
+ // KSRegistration handles promotion asynchronously, this dummy block can
+ // be replaced with the #else block. For now, just leave the messaging
+ // alone. http://b/2290009.
+ updateMessage = false;
+#else
+ // The visibility may be changing.
+ [self adjustUpdateUIVisibility];
+
+ // This is not a terminal state, and kAutoupdatePromoted or
+ // kAutoupdatePromoteFailed will follow. Use the throbber and
+ // "checking" message so that it looks like something's happening.
+ throbber = true;
+ message = l10n_util::GetNSStringWithFixup(IDS_UPGRADE_CHECK_STARTED);
+#endif
+
+ enablePromoteButton = false;
+
+ break;
+
+ case kAutoupdatePromoted:
+ // The visibility may be changing.
+ [self adjustUpdateUIVisibility];
+
+ if (![updateBlock_ isHidden]) {
+ // If promotion completes while the window is visible, go straight
+ // into an update check. Return immediately, this routine will be
+ // re-entered shortly with kAutoupdateChecking.
+ [self checkForUpdate];
+ return;
+ }
+
+ // Nothing actually failed, but updates aren't possible. The throbber
+ // and message are hidden, but they'll be reset to these dummy values
+ // just to get the throbber to stop spinning if it's running.
+ imageID = IDR_UPDATE_FAIL;
+ message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
+ IntToString16(status));
+
+ break;
+
+ case kAutoupdateRegisterFailed:
+ imageID = IDR_UPDATE_FAIL;
+ message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
+ IntToString16(status));
+ enablePromoteButton = false;
+
+ break;
+
+ case kAutoupdateCheckFailed:
+ imageID = IDR_UPDATE_FAIL;
+ message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
+ IntToString16(status));
+
+ break;
+
case kAutoupdateInstallFailed:
- recentShownInstallFailedStatus = YES;
+ recentShownUserActionFailedStatus = YES;
+ imageID = IDR_UPDATE_FAIL;
+ message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
+ IntToString16(status));
+
// Allow another chance.
- [updateNowButton_ setEnabled:YES];
+ enableUpdateButton = true;
- // Fall through.
+ break;
- case kAutoupdateCheckFailed:
+ case kAutoupdatePromoteFailed:
+ recentShownUserActionFailedStatus = YES;
+
imageID = IDR_UPDATE_FAIL;
message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
IntToString16(status));
@@ -337,15 +617,24 @@
default:
NOTREACHED();
+
return;
}
- if (throbber) {
- [self setUpdateThrobberMessage:message];
- } else {
- DCHECK_NE(imageID, 0);
- [self setUpdateImage:imageID message:message];
+ if (updateMessage) {
+ if (throbber) {
+ [self setUpdateThrobberMessage:message];
+ } else {
+ DCHECK_NE(imageID, 0);
+ [self setUpdateImage:imageID message:message];
+ }
}
+
+ // Note that these buttons may be hidden depending on what
+ // -adjustUpdateUIVisibility did. Their enabled/disabled status doesn't
+ // necessarily have anything to do with their visibility.
+ [updateNowButton_ setEnabled:enableUpdateButton];
+ [promoteButton_ setEnabled:enablePromoteButton];
}
- (BOOL)textView:(NSTextView *)aTextView
« no previous file with comments | « chrome/browser/cocoa/about_window_controller.h ('k') | chrome/browser/cocoa/about_window_controller_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698