Commit a7acf81c authored by Julian Watson's avatar Julian Watson Committed by Commit Bot

crostini: disable export import buttons when operation in progress

BUG=985192

Change-Id: I15da8fd02164d3c2089c4dddc24b48171577cde8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1728171Reviewed-by: default avatarDan Beam <dbeam@chromium.org>
Reviewed-by: default avatarJoel Hockey <joelhockey@chromium.org>
Commit-Queue: Julian Watson <juwa@google.com>
Cr-Commit-Position: refs/heads/master@{#683408}
parent bb43a6ea
...@@ -175,6 +175,9 @@ void CrostiniExportImport::Start( ...@@ -175,6 +175,9 @@ void CrostiniExportImport::Start(
return; return;
} else { } else {
notifications_.emplace_hint(it, container_id, notification); notifications_.emplace_hint(it, container_id, notification);
for (auto& observer : observers_) {
observer.OnCrostiniExportImportOperationStatusChanged(true);
}
} }
switch (type) { switch (type) {
...@@ -504,6 +507,9 @@ CrostiniExportImportNotification& CrostiniExportImport::RemoveNotification( ...@@ -504,6 +507,9 @@ CrostiniExportImportNotification& CrostiniExportImport::RemoveNotification(
DCHECK(it != notifications_.end()); DCHECK(it != notifications_.end());
auto& notification = *it->second; auto& notification = *it->second;
notifications_.erase(it); notifications_.erase(it);
for (auto& observer : observers_) {
observer.OnCrostiniExportImportOperationStatusChanged(false);
}
return notification; return notification;
} }
...@@ -532,6 +538,11 @@ void CrostiniExportImport::CancelOperation(ExportImportType type, ...@@ -532,6 +538,11 @@ void CrostiniExportImport::CancelOperation(ExportImportType type,
} }
} }
bool CrostiniExportImport::GetExportImportOperationStatus() const {
ContainerId id(kCrostiniDefaultVmName, kCrostiniDefaultContainerName);
return notifications_.find(id) != notifications_.end();
}
CrostiniExportImportNotification* CrostiniExportImportNotification*
CrostiniExportImport::GetNotificationForTesting(ContainerId container_id) { CrostiniExportImport::GetNotificationForTesting(ContainerId container_id) {
auto it = notifications_.find(container_id); auto it = notifications_.find(container_id);
......
...@@ -60,11 +60,25 @@ class CrostiniExportImport : public KeyedService, ...@@ -60,11 +60,25 @@ class CrostiniExportImport : public KeyedService,
public crostini::ExportContainerProgressObserver, public crostini::ExportContainerProgressObserver,
public crostini::ImportContainerProgressObserver { public crostini::ImportContainerProgressObserver {
public: public:
class Observer : public base::CheckedObserver {
public:
// Called immediately before operation begins with |in_progress|=true, and
// again immediately after the operation completes with |in_progress|=false.
virtual void OnCrostiniExportImportOperationStatusChanged(
bool in_progress) = 0;
};
static CrostiniExportImport* GetForProfile(Profile* profile); static CrostiniExportImport* GetForProfile(Profile* profile);
explicit CrostiniExportImport(Profile* profile); explicit CrostiniExportImport(Profile* profile);
~CrostiniExportImport() override; ~CrostiniExportImport() override;
void AddObserver(Observer* observer) { observers_.AddObserver(observer); }
void RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
// KeyedService: // KeyedService:
void Shutdown() override; void Shutdown() override;
...@@ -82,9 +96,12 @@ class CrostiniExportImport : public KeyedService, ...@@ -82,9 +96,12 @@ class CrostiniExportImport : public KeyedService,
base::FilePath path, base::FilePath path,
CrostiniManager::CrostiniResultCallback callback); CrostiniManager::CrostiniResultCallback callback);
// Cancel currently running export/import // Cancel currently running export/import.
void CancelOperation(ExportImportType type, ContainerId id); void CancelOperation(ExportImportType type, ContainerId id);
// Whether an export or import is currently in progress.
bool GetExportImportOperationStatus() const;
CrostiniExportImportNotification* GetNotificationForTesting( CrostiniExportImportNotification* GetNotificationForTesting(
ContainerId container_id); ContainerId container_id);
...@@ -173,6 +190,7 @@ class CrostiniExportImport : public KeyedService, ...@@ -173,6 +190,7 @@ class CrostiniExportImport : public KeyedService,
// Notifications must have unique-per-profile identifiers. // Notifications must have unique-per-profile identifiers.
// A non-static member on a profile-keyed-service will suffice. // A non-static member on a profile-keyed-service will suffice.
int next_notification_id_; int next_notification_id_;
base::ObserverList<Observer> observers_;
// weak_ptr_factory_ should always be last member. // weak_ptr_factory_ should always be last member.
base::WeakPtrFactory<CrostiniExportImport> weak_ptr_factory_; base::WeakPtrFactory<CrostiniExportImport> weak_ptr_factory_;
......
...@@ -24,6 +24,7 @@ js_library("crostini_browser_proxy") { ...@@ -24,6 +24,7 @@ js_library("crostini_browser_proxy") {
js_library("crostini_export_import") { js_library("crostini_export_import") {
deps = [ deps = [
":crostini_browser_proxy", ":crostini_browser_proxy",
"//ui/webui/resources/js:web_ui_listener_behavior",
] ]
} }
......
...@@ -51,14 +51,26 @@ cr.define('settings', function() { ...@@ -51,14 +51,26 @@ cr.define('settings', function() {
*/ */
removeCrostiniSharedPath(vmName, path) {} removeCrostiniSharedPath(vmName, path) {}
/* Request chrome send a crostini-installer-status-changed event with the /**
current installer status */ * Request chrome send a crostini-installer-status-changed event with the
* current installer status
*/
requestCrostiniInstallerStatus() {} requestCrostiniInstallerStatus() {}
/* Export crostini container. */ /**
* Request chrome send a crostini-export-import-operation-status-changed
* event with the current operation status
*/
requestCrostiniExportImportOperationStatus() {}
/**
* Export crostini container.
*/
exportCrostiniContainer() {} exportCrostiniContainer() {}
/* Import crostini container. */ /**
* Import crostini container.
*/
importCrostiniContainer() {} importCrostiniContainer() {}
} }
...@@ -99,6 +111,11 @@ cr.define('settings', function() { ...@@ -99,6 +111,11 @@ cr.define('settings', function() {
chrome.send('requestCrostiniInstallerStatus'); chrome.send('requestCrostiniInstallerStatus');
} }
/** @override */
requestCrostiniExportImportOperationStatus() {
chrome.send('requestCrostiniExportImportOperationStatus');
}
/** @override */ /** @override */
exportCrostiniContainer() { exportCrostiniContainer() {
chrome.send('exportCrostiniContainer'); chrome.send('exportCrostiniContainer');
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> <link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
<link rel="import" href="chrome://resources/html/i18n_behavior.html"> <link rel="import" href="chrome://resources/html/i18n_behavior.html">
<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
<link rel="import" href="crostini_browser_proxy.html"> <link rel="import" href="crostini_browser_proxy.html">
<link rel="import" href="crostini_import_confirmation_dialog.html"> <link rel="import" href="crostini_import_confirmation_dialog.html">
<link rel="import" href="../i18n_setup.html"> <link rel="import" href="../i18n_setup.html">
...@@ -15,18 +16,18 @@ ...@@ -15,18 +16,18 @@
<div id="exportCrostiniLabel" class="start secondary"> <div id="exportCrostiniLabel" class="start secondary">
$i18n{crostiniExportLabel} $i18n{crostiniExportLabel}
</div> </div>
<cr-button on-click="onExportClick_" <cr-button on-click="onExportClick_" disabled="[[!enableButtons_]]"
aria-labelledby="exportCrostiniLabel"> aria-labelledby="exportCrostiniLabel">
$i18n{crostiniExport} $i18n{crostiniExport}
</cr-button> </cr-button>
</div> </div>
<div id="import" class="list-item"> <div id="import" class="list-item">
<div id="importCrostiniLabel" class="start secondary"> <div id="importCrostiniLabel" class="start secondary">
$i18n{crostiniImportLabel} $i18n{crostiniImportLabel}
</div> </div>
<cr-button on-click="onImportClick_" <cr-button on-click="onImportClick_" disabled="[[!enableButtons_]]"
aria-labelledby="importCrostiniLabel"> aria-labelledby="importCrostiniLabel">
$i18n{crostiniImport} $i18n{crostiniImport}
</cr-button> </cr-button>
</div> </div>
</div> </div>
......
...@@ -11,12 +11,33 @@ ...@@ -11,12 +11,33 @@
Polymer({ Polymer({
is: 'settings-crostini-export-import', is: 'settings-crostini-export-import',
behaviors: [WebUIListenerBehavior],
properties: { properties: {
/** @private */ /** @private */
showImportConfirmationDialog_: { showImportConfirmationDialog_: {
type: Boolean, type: Boolean,
value: false, value: false,
}, },
/**
* Whether the export import buttons should be enabled. Initially false
* until status has been confirmed.
* @private {boolean}
*/
enableButtons_: {
type: Boolean,
value: false,
},
},
attached: function() {
this.addWebUIListener(
'crostini-export-import-operation-status-changed', inProgress => {
this.enableButtons_ = !inProgress;
});
settings.CrostiniBrowserProxyImpl.getInstance()
.requestCrostiniExportImportOperationStatus();
}, },
/** @private */ /** @private */
......
...@@ -6,12 +6,9 @@ ...@@ -6,12 +6,9 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector>
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "chrome/browser/chromeos/crostini/crostini_export_import.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h" #include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/chromeos/file_manager/path_util.h" #include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/chromeos/guest_os/guest_os_share_path.h" #include "chrome/browser/chromeos/guest_os/guest_os_share_path.h"
...@@ -67,6 +64,11 @@ void CrostiniHandler::RegisterMessages() { ...@@ -67,6 +64,11 @@ void CrostiniHandler::RegisterMessages() {
base::BindRepeating( base::BindRepeating(
&CrostiniHandler::HandleCrostiniInstallerStatusRequest, &CrostiniHandler::HandleCrostiniInstallerStatusRequest,
weak_ptr_factory_.GetWeakPtr())); weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"requestCrostiniExportImportOperationStatus",
base::BindRepeating(
&CrostiniHandler::HandleCrostiniExportImportOperationStatusRequest,
weak_ptr_factory_.GetWeakPtr()));
} }
void CrostiniHandler::OnJavascriptAllowed() { void CrostiniHandler::OnJavascriptAllowed() {
...@@ -75,6 +77,7 @@ void CrostiniHandler::OnJavascriptAllowed() { ...@@ -75,6 +77,7 @@ void CrostiniHandler::OnJavascriptAllowed() {
if (chromeos::CrosUsbDetector::Get()) { if (chromeos::CrosUsbDetector::Get()) {
chromeos::CrosUsbDetector::Get()->AddUsbDeviceObserver(this); chromeos::CrosUsbDetector::Get()->AddUsbDeviceObserver(this);
} }
crostini::CrostiniExportImport::GetForProfile(profile_)->AddObserver(this);
} }
void CrostiniHandler::OnJavascriptDisallowed() { void CrostiniHandler::OnJavascriptDisallowed() {
...@@ -86,6 +89,7 @@ void CrostiniHandler::OnJavascriptDisallowed() { ...@@ -86,6 +89,7 @@ void CrostiniHandler::OnJavascriptDisallowed() {
if (chromeos::CrosUsbDetector::Get()) { if (chromeos::CrosUsbDetector::Get()) {
chromeos::CrosUsbDetector::Get()->RemoveUsbDeviceObserver(this); chromeos::CrosUsbDetector::Get()->RemoveUsbDeviceObserver(this);
} }
crostini::CrostiniExportImport::GetForProfile(profile_)->RemoveObserver(this);
} }
void CrostiniHandler::HandleRequestCrostiniInstallerView( void CrostiniHandler::HandleRequestCrostiniInstallerView(
...@@ -225,6 +229,15 @@ void CrostiniHandler::HandleCrostiniInstallerStatusRequest( ...@@ -225,6 +229,15 @@ void CrostiniHandler::HandleCrostiniInstallerStatusRequest(
OnCrostiniInstallerViewStatusChanged(status); OnCrostiniInstallerViewStatusChanged(status);
} }
void CrostiniHandler::HandleCrostiniExportImportOperationStatusRequest(
const base::ListValue* args) {
AllowJavascript();
CHECK_EQ(0U, args->GetSize());
bool in_progress = crostini::CrostiniExportImport::GetForProfile(profile_)
->GetExportImportOperationStatus();
OnCrostiniExportImportOperationStatusChanged(in_progress);
}
void CrostiniHandler::OnCrostiniInstallerViewStatusChanged(bool status) { void CrostiniHandler::OnCrostiniInstallerViewStatusChanged(bool status) {
// It's technically possible for this to be called before Javascript is // It's technically possible for this to be called before Javascript is
// enabled, in which case we must not call FireWebUIListener // enabled, in which case we must not call FireWebUIListener
...@@ -234,5 +247,12 @@ void CrostiniHandler::OnCrostiniInstallerViewStatusChanged(bool status) { ...@@ -234,5 +247,12 @@ void CrostiniHandler::OnCrostiniInstallerViewStatusChanged(bool status) {
} }
} }
void CrostiniHandler::OnCrostiniExportImportOperationStatusChanged(
bool in_progress) {
// Other side listens with cr.addWebUIListener
FireWebUIListener("crostini-export-import-operation-status-changed",
base::Value(in_progress));
}
} // namespace settings } // namespace settings
} // namespace chromeos } // namespace chromeos
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <vector> #include <vector>
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "chrome/browser/chromeos/crostini/crostini_export_import.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h" #include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/usb/cros_usb_detector.h" #include "chrome/browser/chromeos/usb/cros_usb_detector.h"
#include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h" #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
...@@ -23,6 +24,7 @@ namespace settings { ...@@ -23,6 +24,7 @@ namespace settings {
class CrostiniHandler : public ::settings::SettingsPageUIHandler, class CrostiniHandler : public ::settings::SettingsPageUIHandler,
public crostini::InstallerViewStatusObserver, public crostini::InstallerViewStatusObserver,
public crostini::CrostiniExportImport::Observer,
public chromeos::CrosUsbDeviceObserver { public chromeos::CrosUsbDeviceObserver {
public: public:
explicit CrostiniHandler(Profile* profile); explicit CrostiniHandler(Profile* profile);
...@@ -56,6 +58,11 @@ class CrostiniHandler : public ::settings::SettingsPageUIHandler, ...@@ -56,6 +58,11 @@ class CrostiniHandler : public ::settings::SettingsPageUIHandler,
void HandleCrostiniInstallerStatusRequest(const base::ListValue* args); void HandleCrostiniInstallerStatusRequest(const base::ListValue* args);
// Handle the CrostiniInstallerView opening/closing. // Handle the CrostiniInstallerView opening/closing.
void OnCrostiniInstallerViewStatusChanged(bool open) override; void OnCrostiniInstallerViewStatusChanged(bool open) override;
// Handle a request for the CrostiniExportImport operation status.
void HandleCrostiniExportImportOperationStatusRequest(
const base::ListValue* args);
// CrostiniExportImport::Observer:
void OnCrostiniExportImportOperationStatusChanged(bool in_progress) override;
Profile* profile_; Profile* profile_;
// weak_ptr_factory_ should always be last member. // weak_ptr_factory_ should always be last member.
......
...@@ -151,6 +151,33 @@ suite('CrostiniPageTests', function() { ...@@ -151,6 +151,33 @@ suite('CrostiniPageTests', function() {
}); });
}); });
test('ExportImportButtonsGetDisabledOnOperationStatus', function() {
assertTrue(!!subpage.$$('#crostini-export-import'));
subpage.$$('#crostini-export-import').click();
return flushAsync()
.then(() => {
subpage = crostiniPage.$$('settings-crostini-export-import');
assertFalse(subpage.$$('#export cr-button').disabled);
assertFalse(subpage.$$('#import cr-button').disabled);
cr.webUIListenerCallback(
'crostini-export-import-operation-status-changed', true);
return flushAsync();
})
.then(() => {
subpage = crostiniPage.$$('settings-crostini-export-import');
assertTrue(subpage.$$('#export cr-button').disabled);
assertTrue(subpage.$$('#import cr-button').disabled);
cr.webUIListenerCallback(
'crostini-export-import-operation-status-changed', false);
return flushAsync();
})
.then(() => {
subpage = crostiniPage.$$('settings-crostini-export-import');
assertFalse(subpage.$$('#export cr-button').disabled);
assertFalse(subpage.$$('#import cr-button').disabled);
});
});
test('Remove', function() { test('Remove', function() {
assertTrue(!!subpage.$$('#remove cr-button')); assertTrue(!!subpage.$$('#remove cr-button'));
subpage.$$('#remove cr-button').click(); subpage.$$('#remove cr-button').click();
......
...@@ -55,6 +55,12 @@ class TestCrostiniBrowserProxy extends TestBrowserProxy { ...@@ -55,6 +55,12 @@ class TestCrostiniBrowserProxy extends TestBrowserProxy {
cr.webUIListenerCallback('crostini-installer-status-changed', false); cr.webUIListenerCallback('crostini-installer-status-changed', false);
} }
/** @override */
requestCrostiniExportImportOperationStatus() {
cr.webUIListenerCallback(
'crostini-export-import-operation-status-changed', false);
}
/** override */ /** override */
exportCrostiniContainer() { exportCrostiniContainer() {
this.methodCalled('exportCrostiniContainer'); this.methodCalled('exportCrostiniContainer');
......
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