Commit 082af3c7 authored by Ioana Pandele's avatar Ioana Pandele Committed by Commit Bot

Add progress indicator for password export

This is an alert with the title "Preparing passwords..." displayed after the reauthentication
step is finished, and while passwords are being serialized and written to the temporary file.
The alert offers the user the option to cancel the exporting operation.

If the user cancels, the exporter will wait for the async tasks to finish, perform necessary
clean-up and then become available again.

Bug: 789122
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: I8dd6b9a094e055eef15f5580e3d887e5cf979408
Reviewed-on: https://chromium-review.googlesource.com/897607Reviewed-by: default avatarSylvain Defresne <sdefresne@chromium.org>
Commit-Queue: Ioana Pandele <ioanap@chromium.org>
Cr-Commit-Position: refs/heads/master@{#534046}
parent 3180cc44
...@@ -1072,6 +1072,9 @@ Handoff must also be enabled in the General section of Settings, and your device ...@@ -1072,6 +1072,9 @@ Handoff must also be enabled in the General section of Settings, and your device
<message name="IDS_IOS_EXPORT_PASSWORDS_ALERT_MESSAGE" desc="The message of the alert displayed as a warning to the user who tapped on the button to export passwords. [iOS only]"> <message name="IDS_IOS_EXPORT_PASSWORDS_ALERT_MESSAGE" desc="The message of the alert displayed as a warning to the user who tapped on the button to export passwords. [iOS only]">
Your passwords will be visible to anyone who can see the exported file. Your passwords will be visible to anyone who can see the exported file.
</message> </message>
<message name="IDS_IOS_EXPORT_PASSWORDS_PREPARING_ALERT_TITLE" desc="The title of the alert displayed to indicate that the passwords are being prepared for export. [iOS only]">
Preparing Passwords...
</message>
<message name="IDS_IOS_EXPORT_PASSWORDS_FAILED_ALERT_TITLE" desc="Title of the alert informing the user that exporting passwords has failed. [iOS only]"> <message name="IDS_IOS_EXPORT_PASSWORDS_FAILED_ALERT_TITLE" desc="Title of the alert informing the user that exporting passwords has failed. [iOS only]">
Can't Export Passwords Can't Export Passwords
</message> </message>
......
...@@ -20,6 +20,12 @@ enum class WriteToURLStatus { ...@@ -20,6 +20,12 @@ enum class WriteToURLStatus {
UNKNOWN_ERROR, UNKNOWN_ERROR,
}; };
enum class ExportState {
IDLE,
ONGOING,
CANCELLING,
};
@protocol ReauthenticationProtocol; @protocol ReauthenticationProtocol;
@protocol FileWriterProtocol<NSObject> @protocol FileWriterProtocol<NSObject>
...@@ -48,6 +54,10 @@ enum class WriteToURLStatus { ...@@ -48,6 +54,10 @@ enum class WriteToURLStatus {
// in order to export passwords. // in order to export passwords.
- (void)showSetPasscodeDialog; - (void)showSetPasscodeDialog;
// Displays an alert which informs the user that the passwords are being
// prepared to be exported and gives them the option of cancelling the export.
- (void)showPreparingPasswordsAlert;
// Displays an alert detailing an error that has occured during export. // Displays an alert detailing an error that has occured during export.
- (void)showExportErrorAlertWithLocalizedReason:(NSString*)errorReason; - (void)showExportErrorAlertWithLocalizedReason:(NSString*)errorReason;
...@@ -60,6 +70,9 @@ enum class WriteToURLStatus { ...@@ -60,6 +70,9 @@ enum class WriteToURLStatus {
NSArray* returnedItems, NSArray* returnedItems,
NSError* activityError))completionHandler; NSError* activityError))completionHandler;
// Enables or disables the export button based on the export state.
- (void)updateExportPasswordsButton;
@end @end
/** Class handling all the operations necessary to export passwords.*/ /** Class handling all the operations necessary to export passwords.*/
...@@ -80,8 +93,11 @@ enum class WriteToURLStatus { ...@@ -80,8 +93,11 @@ enum class WriteToURLStatus {
- (void)startExportFlow: - (void)startExportFlow:
(std::vector<std::unique_ptr<autofill::PasswordForm>>)passwords; (std::vector<std::unique_ptr<autofill::PasswordForm>>)passwords;
// Whether an export operation is already in progress. // Called when the user cancels the export operation.
@property(nonatomic, assign, readonly) BOOL isExporting; - (void)cancelExport;
// State of the export operation.
@property(nonatomic, readonly, assign) ExportState exportState;
@end @end
......
...@@ -104,16 +104,15 @@ enum class ReauthenticationStatus { ...@@ -104,16 +104,15 @@ enum class ReauthenticationStatus {
@property(nonatomic, assign) BOOL serializingFinished; @property(nonatomic, assign) BOOL serializingFinished;
// String containing serialized password forms. // String containing serialized password forms.
@property(nonatomic, copy) NSString* serializedPasswords; @property(nonatomic, copy) NSString* serializedPasswords;
// Whether an export operation is ongoing. This is a readwrite property // The exporter state.
// corresponding to the public readonly property. @property(nonatomic, assign) ExportState exportState;
@property(nonatomic, assign) BOOL isExporting;
@end @end
@implementation PasswordExporter @implementation PasswordExporter
// Public synthesized properties // Public synthesized properties
@synthesize isExporting = _isExporting; @synthesize exportState = _exportState;
// Private synthesized properties // Private synthesized properties
@synthesize reauthenticationStatus = _reauthenticationStatus; @synthesize reauthenticationStatus = _reauthenticationStatus;
...@@ -142,8 +141,11 @@ enum class ReauthenticationStatus { ...@@ -142,8 +141,11 @@ enum class ReauthenticationStatus {
- (void)startExportFlow: - (void)startExportFlow:
(std::vector<std::unique_ptr<autofill::PasswordForm>>)passwords { (std::vector<std::unique_ptr<autofill::PasswordForm>>)passwords {
DCHECK(!passwords.empty());
DCHECK(self.exportState == ExportState::IDLE);
if ([_weakReauthenticationModule canAttemptReauth]) { if ([_weakReauthenticationModule canAttemptReauth]) {
self.isExporting = YES; self.exportState = ExportState::ONGOING;
[_weakDelegate updateExportPasswordsButton];
[self serializePasswords:std::move(passwords)]; [self serializePasswords:std::move(passwords)];
[self startReauthentication]; [self startReauthentication];
} else { } else {
...@@ -151,6 +153,10 @@ enum class ReauthenticationStatus { ...@@ -151,6 +153,10 @@ enum class ReauthenticationStatus {
} }
} }
- (void)cancelExport {
self.exportState = ExportState::CANCELLING;
}
#pragma mark - Private methods #pragma mark - Private methods
- (void)showExportErrorAlertWithLocalizedReason:(NSString*)errorReason { - (void)showExportErrorAlertWithLocalizedReason:(NSString*)errorReason {
...@@ -183,6 +189,7 @@ enum class ReauthenticationStatus { ...@@ -183,6 +189,7 @@ enum class ReauthenticationStatus {
return; return;
if (success) { if (success) {
strongSelf.reauthenticationStatus = ReauthenticationStatus::SUCCESSFUL; strongSelf.reauthenticationStatus = ReauthenticationStatus::SUCCESSFUL;
[strongSelf showPreparingPasswordsAlert];
} else { } else {
strongSelf.reauthenticationStatus = ReauthenticationStatus::FAILED; strongSelf.reauthenticationStatus = ReauthenticationStatus::FAILED;
} }
...@@ -196,6 +203,10 @@ enum class ReauthenticationStatus { ...@@ -196,6 +203,10 @@ enum class ReauthenticationStatus {
handler:onReauthenticationFinished]; handler:onReauthenticationFinished];
} }
- (void)showPreparingPasswordsAlert {
[_weakDelegate showPreparingPasswordsAlert];
}
- (void)tryExporting { - (void)tryExporting {
if (!self.serializingFinished) if (!self.serializingFinished)
return; return;
...@@ -217,10 +228,15 @@ enum class ReauthenticationStatus { ...@@ -217,10 +228,15 @@ enum class ReauthenticationStatus {
self.serializingFinished = NO; self.serializingFinished = NO;
self.serializedPasswords = nil; self.serializedPasswords = nil;
self.reauthenticationStatus = ReauthenticationStatus::PENDING; self.reauthenticationStatus = ReauthenticationStatus::PENDING;
self.isExporting = NO; self.exportState = ExportState::IDLE;
[_weakDelegate updateExportPasswordsButton];
} }
- (void)writePasswordsToFile { - (void)writePasswordsToFile {
if (self.exportState == ExportState::CANCELLING) {
[self resetExportState];
return;
}
NSURL* tempPasswordsFileURL = NSURL* tempPasswordsFileURL =
[[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES] [[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES]
URLByAppendingPathComponent:_tempPasswordsFileName URLByAppendingPathComponent:_tempPasswordsFileName
...@@ -232,6 +248,10 @@ enum class ReauthenticationStatus { ...@@ -232,6 +248,10 @@ enum class ReauthenticationStatus {
if (!strongSelf) { if (!strongSelf) {
return; return;
} }
if (strongSelf.exportState == ExportState::CANCELLING) {
[strongSelf resetExportState];
return;
}
switch (status) { switch (status) {
case WriteToURLStatus::SUCCESS: case WriteToURLStatus::SUCCESS:
[strongSelf showActivityView]; [strongSelf showActivityView];
...@@ -284,7 +304,12 @@ enum class ReauthenticationStatus { ...@@ -284,7 +304,12 @@ enum class ReauthenticationStatus {
[[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES] [[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES]
URLByAppendingPathComponent:_tempPasswordsFileName URLByAppendingPathComponent:_tempPasswordsFileName
isDirectory:NO]; isDirectory:NO];
if (self.exportState == ExportState::CANCELLING) {
// Initiate cleanup. Once the file is deleted, the export state will be
// reset;
[self deleteTemporaryFile:passwordsTempFileURL];
return;
}
__weak PasswordExporter* weakSelf = self; __weak PasswordExporter* weakSelf = self;
[_weakDelegate [_weakDelegate
showActivityViewWithActivityItems:@[ passwordsTempFileURL ] showActivityViewWithActivityItems:@[ passwordsTempFileURL ]
......
...@@ -168,6 +168,9 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -168,6 +168,9 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
// Boolean containing whether the export button and functionality are enabled // Boolean containing whether the export button and functionality are enabled
// or not. // or not.
BOOL exportEnabled_; BOOL exportEnabled_;
// Alert informing the user that passwords are being prepared for
// export.
UIAlertController* preparingPasswordsAlert_;
} }
// Kick off async request to get logins from password store. // Kick off async request to get logins from password store.
...@@ -194,9 +197,12 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -194,9 +197,12 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
browserState_ = browserState; browserState_ = browserState;
reauthenticationModule_ = [[ReauthenticationModule alloc] reauthenticationModule_ = [[ReauthenticationModule alloc]
initWithSuccessfulReauthTimeAccessor:self]; initWithSuccessfulReauthTimeAccessor:self];
if (base::FeatureList::IsEnabled(
password_manager::features::kPasswordExport)) {
passwordExporter_ = [[PasswordExporter alloc] passwordExporter_ = [[PasswordExporter alloc]
initWithReauthenticationModule:reauthenticationModule_ initWithReauthenticationModule:reauthenticationModule_
delegate:self]; delegate:self];
}
self.title = l10n_util::GetNSString(IDS_IOS_SAVE_PASSWORDS); self.title = l10n_util::GetNSString(IDS_IOS_SAVE_PASSWORDS);
self.collectionViewAccessibilityIdentifier = self.collectionViewAccessibilityIdentifier =
@"SavePasswordsCollectionViewController"; @"SavePasswordsCollectionViewController";
...@@ -279,7 +285,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -279,7 +285,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
exportPasswordsItem_ = [self exportPasswordsItem]; exportPasswordsItem_ = [self exportPasswordsItem];
[model addItem:exportPasswordsItem_ [model addItem:exportPasswordsItem_
toSectionWithIdentifier:SectionIdentifierExportPasswordsButton]; toSectionWithIdentifier:SectionIdentifierExportPasswordsButton];
[self updateExportPasswordsItem]; [self updateExportPasswordsButton];
} }
} }
...@@ -484,22 +490,24 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -484,22 +490,24 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
[self reloadData]; [self reloadData];
} }
- (void)updateExportPasswordsItem { - (void)updateExportPasswordsButton {
if (savedForms_.empty()) { if (!exportPasswordsItem_)
return;
if (!savedForms_.empty() &&
self.passwordExporter.exportState == ExportState::IDLE) {
exportPasswordsItem_.textColor = [[MDCPalette greyPalette] tint900];
exportPasswordsItem_.accessibilityTraits = UIAccessibilityTraitButton;
[self reconfigureCellsForItems:@[ exportPasswordsItem_ ]];
exportEnabled_ = YES;
} else {
exportPasswordsItem_.textColor = [[MDCPalette greyPalette] tint500]; exportPasswordsItem_.textColor = [[MDCPalette greyPalette] tint500];
exportPasswordsItem_.accessibilityTraits = UIAccessibilityTraitNotEnabled; exportPasswordsItem_.accessibilityTraits = UIAccessibilityTraitNotEnabled;
[self reconfigureCellsForItems:@[ exportPasswordsItem_ ]]; [self reconfigureCellsForItems:@[ exportPasswordsItem_ ]];
exportEnabled_ = NO; exportEnabled_ = NO;
} else {
exportEnabled_ = YES;
} }
} }
- (void)startPasswordsExportFlow { - (void)startPasswordsExportFlow {
// TODO(crbug.com/789122): Consider disabling the button while another export
// operation is in progress.
if (self.passwordExporter.isExporting)
return;
UIAlertController* exportConfirmation = [UIAlertController UIAlertController* exportConfirmation = [UIAlertController
alertControllerWithTitle:nil alertControllerWithTitle:nil
message:l10n_util::GetNSString( message:l10n_util::GetNSString(
...@@ -524,6 +532,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -524,6 +532,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
[strongSelf.passwordExporter [strongSelf.passwordExporter
startExportFlow:CopyOf(strongSelf->savedForms_)]; startExportFlow:CopyOf(strongSelf->savedForms_)];
}]; }];
[exportConfirmation addAction:exportAction]; [exportConfirmation addAction:exportAction];
[self presentViewController:exportConfirmation animated:YES completion:nil]; [self presentViewController:exportConfirmation animated:YES completion:nil];
...@@ -696,7 +705,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -696,7 +705,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
[strongSelf updateEditButton]; [strongSelf updateEditButton];
if (base::FeatureList::IsEnabled( if (base::FeatureList::IsEnabled(
password_manager::features::kPasswordExport)) { password_manager::features::kPasswordExport)) {
[strongSelf updateExportPasswordsItem]; [strongSelf updateExportPasswordsButton];
} }
}]; }];
} }
...@@ -775,6 +784,26 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -775,6 +784,26 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
[self presentViewController:alertController animated:YES completion:nil]; [self presentViewController:alertController animated:YES completion:nil];
} }
- (void)showPreparingPasswordsAlert {
preparingPasswordsAlert_ = [UIAlertController
alertControllerWithTitle:
l10n_util::GetNSString(IDS_IOS_EXPORT_PASSWORDS_PREPARING_ALERT_TITLE)
message:nil
preferredStyle:UIAlertControllerStyleAlert];
__weak SavePasswordsCollectionViewController* weakSelf = self;
UIAlertAction* cancelAction =
[UIAlertAction actionWithTitle:l10n_util::GetNSString(
IDS_IOS_EXPORT_PASSWORDS_CANCEL_BUTTON)
style:UIAlertActionStyleCancel
handler:^(UIAlertAction*) {
[weakSelf.passwordExporter cancelExport];
}];
[preparingPasswordsAlert_ addAction:cancelAction];
[self presentViewController:preparingPasswordsAlert_
animated:YES
completion:nil];
}
- (void)showExportErrorAlertWithLocalizedReason:(NSString*)localizedReason { - (void)showExportErrorAlertWithLocalizedReason:(NSString*)localizedReason {
UIAlertController* alertController = [UIAlertController UIAlertController* alertController = [UIAlertController
alertControllerWithTitle:l10n_util::GetNSString( alertControllerWithTitle:l10n_util::GetNSString(
...@@ -786,7 +815,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -786,7 +815,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
style:UIAlertActionStyleDefault style:UIAlertActionStyleDefault
handler:nil]; handler:nil];
[alertController addAction:okAction]; [alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil]; [self presentViewController:alertController];
} }
- (void)showActivityViewWithActivityItems:(NSArray*)activityItems - (void)showActivityViewWithActivityItems:(NSArray*)activityItems
...@@ -826,9 +855,23 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -826,9 +855,23 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
activityViewController.popoverPresentationController activityViewController.popoverPresentationController
.permittedArrowDirections = .permittedArrowDirections =
UIPopoverArrowDirectionDown | UIPopoverArrowDirectionDown; UIPopoverArrowDirectionDown | UIPopoverArrowDirectionDown;
[self presentViewController:activityViewController
[self presentViewController:activityViewController];
}
- (void)presentViewController:(UIViewController*)viewController {
if (self.presentedViewController == preparingPasswordsAlert_ &&
!preparingPasswordsAlert_.beingDismissed) {
__weak SavePasswordsCollectionViewController* weakSelf = self;
[self dismissViewControllerAnimated:YES
completion:^() {
[weakSelf presentViewController:viewController
animated:YES animated:YES
completion:nil]; completion:nil];
}];
} else {
[self presentViewController:viewController animated:YES completion:nil];
}
} }
#pragma mark Helper methods #pragma mark Helper methods
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment