// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview 'passwords-export-dialog' is the dialog that allows exporting
* passwords.
*/
(function() {
'use strict';
/**
* The states of the export passwords dialog.
* @enum {string}
*/
const States = {
START: 'START',
IN_PROGRESS: 'IN_PROGRESS',
ERROR: 'ERROR',
};
const ProgressStatus = chrome.passwordsPrivate.ExportProgressStatus;
/**
* The amount of time (ms) between the start of the export and the moment we
* start showing the progress bar.
* @type {number}
*/
const progressBarDelayMs = 100;
/**
* The minimum amount of time (ms) that the progress bar will be visible.
* @type {number}
*/
const progressBarBlockMs = 1000;
Polymer({
is: 'passwords-export-dialog',
behaviors: [I18nBehavior],
properties: {
/** The error that occurred while exporting. */
exportErrorMessage: String,
/** @private */
showStartDialog_: Boolean,
/** @private */
showProgressDialog_: Boolean,
/** @private */
showErrorDialog_: Boolean,
//
/** @type settings.BlockingRequestManager */
tokenRequestManager: Object
//
},
listeners: {
'cancel': 'close',
},
/**
* The interface for callbacks to the browser.
* Defined in passwords_section.js
* @type {PasswordManagerProxy}
* @private
*/
passwordManager_: null,
/** @private {function(!PasswordManagerProxy.PasswordExportProgress):void} */
onPasswordsFileExportProgressListener_: null,
/**
* The task that will display the progress bar, if the export doesn't finish
* quickly. This is null, unless the task is currently scheduled.
* @private {?number}
*/
progressTaskToken_: null,
/**
* The task that will display the completion of the export, if any. We display
* the progress bar for at least |progressBarBlockMs|, therefore, if export
* finishes earlier, we cache the result in |delayedProgress_| and this task
* will consume it. This is null, unless the task is currently scheduled.
* @private {?number}
*/
delayedCompletionToken_: null,
/**
* We display the progress bar for at least |progressBarBlockMs|. If progress
* is achieved earlier, we store the update here and consume it later.
* @private {?PasswordManagerProxy.PasswordExportProgress}
*/
delayedProgress_: null,
/** @override */
attached: function() {
this.passwordManager_ = PasswordManagerImpl.getInstance();
this.switchToDialog_(States.START);
this.onPasswordsFileExportProgressListener_ =
this.onPasswordsFileExportProgress_.bind(this);
// If export started on a different tab and is still in progress, display a
// busy UI.
this.passwordManager_.requestExportProgressStatus(status => {
if (status == ProgressStatus.IN_PROGRESS) {
this.switchToDialog_(States.IN_PROGRESS);
}
});
this.passwordManager_.addPasswordsFileExportProgressListener(
this.onPasswordsFileExportProgressListener_);
},
/**
* Handles an export progress event by changing the visible dialog or caching
* the event for later consumption.
* @param {!PasswordManagerProxy.PasswordExportProgress} progress
* @private
*/
onPasswordsFileExportProgress_(progress) {
// If Chrome has already started displaying the progress bar
// (|progressTaskToken_ is null) and hasn't completed its minimum display
// time (|delayedCompletionToken_| is not null) progress should be cached
// for consumption when the blocking time ends.
const progressBlocked =
!this.progressTaskToken_ && this.delayedCompletionToken_;
if (!progressBlocked) {
clearTimeout(this.progressTaskToken_);
this.progressTaskToken_ = null;
this.processProgress_(progress);
} else {
this.delayedProgress_ = progress;
}
},
/**
* Displays the progress bar and suspends further UI updates for
* |progressBarBlockMs|.
* @private
*/
progressTask_() {
this.progressTaskToken_ = null;
this.switchToDialog_(States.IN_PROGRESS);
this.delayedCompletionToken_ =
setTimeout(this.delayedCompletionTask_.bind(this), progressBarBlockMs);
},
/**
* Unblocks progress after showing the progress bar for |progressBarBlock|ms
* and processes any progress that was delayed.
* @private
*/
delayedCompletionTask_() {
this.delayedCompletionToken_ = null;
if (this.delayedProgress_) {
this.processProgress_(this.delayedProgress_);
this.delayedProgress_ = null;
}
},
/** Closes the dialog. */
close: function() {
clearTimeout(this.progressTaskToken_);
clearTimeout(this.delayedCompletionToken_);
this.progressTaskToken_ = null;
this.delayedCompletionToken_ = null;
this.passwordManager_.removePasswordsFileExportProgressListener(
this.onPasswordsFileExportProgressListener_);
this.showStartDialog_ = false;
this.showProgressDialog_ = false;
this.showErrorDialog_ = false;
// Need to allow for the dialogs to be removed from the DOM before firing
// the close event. Otherwise the handler will not be able to set focus.
this.async(() => this.fire('passwords-export-dialog-close'));
},
/** @private */
onExportTap_: function() {
//
this.tokenRequestManager.request(this.exportPasswords_.bind(this));
//
//
this.exportPasswords_();
//
},
/**
* Tells the PasswordsPrivate API to export saved passwords in a .csv pending
* security checks.
* @private
*/
exportPasswords_: function() {
this.passwordManager_.exportPasswords(() => {
if (chrome.runtime.lastError &&
chrome.runtime.lastError.message == 'in-progress') {
// Exporting was started by a different call to exportPasswords() and is
// is still in progress. This UI needs to be updated to the current
// status.
this.switchToDialog_(States.IN_PROGRESS);
}
});
},
/**
* Prepares and displays the appropriate view (with delay, if necessary).
* @param {!PasswordManagerProxy.PasswordExportProgress} progress
* @private
*/
processProgress_(progress) {
if (progress.status == ProgressStatus.IN_PROGRESS) {
this.progressTaskToken_ =
setTimeout(this.progressTask_.bind(this), progressBarDelayMs);
return;
}
if (progress.status == ProgressStatus.SUCCEEDED) {
this.close();
return;
}
if (progress.status == ProgressStatus.FAILED_WRITE_FAILED) {
this.exportErrorMessage =
this.i18n('exportPasswordsFailTitle', progress.folderName);
this.switchToDialog_(States.ERROR);
return;
}
},
/**
* Opens the specified dialog and hides the others.
* @param {!States} state the dialog to open.
* @private
*/
switchToDialog_(state) {
this.showStartDialog_ = state == States.START;
this.showProgressDialog_ = state == States.IN_PROGRESS;
this.showErrorDialog_ = state == States.ERROR;
},
/**
* Handler for tapping the 'cancel' button. Should just dismiss the dialog.
* @private
*/
onCancelButtonTap_: function() {
this.close();
},
/**
* Handler for tapping the 'cancel' button on the progress dialog. It should
* cancel the export and dismiss the dialog.
* @private
*/
onCancelProgressButtonTap_: function() {
this.passwordManager_.cancelExportPasswords();
this.close();
},
});
})();