Commit 57d16703 authored by Joel Hockey's avatar Joel Hockey Committed by Commit Bot

Support ShowCrostiniUpdateComponentView in Terminal System App

Terminal must have dedicated logic to invoke
ShowCrostiniUpdateComponentView.

Most crostini apps start crostini first and show a
spinner whilst this is happening before launching the app.
The crostini startup logic for this case detects
when to show the update component view.

Terminal System App launches immediately so that it can
show the status of crostini starting, so it requires
extra logic.

TerminalSource dummy files have been extended to include
terminal.html and terminal.js which invokes
chrome.terminalPrivate.openTerminalProcess to
test this logic.

Change-Id: I68ec7b769e038947d107024e36312e9c0689f862
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2079719
Commit-Queue: Joel Hockey <joelhockey@chromium.org>
Reviewed-by: default avatarNicholas Verne <nverne@chromium.org>
Cr-Commit-Position: refs/heads/master@{#745359}
parent 8cd4b4a2
...@@ -47,11 +47,8 @@ std::string MoveForward(int i) { ...@@ -47,11 +47,8 @@ std::string MoveForward(int i) {
CrostiniStartupStatus::CrostiniStartupStatus( CrostiniStartupStatus::CrostiniStartupStatus(
base::RepeatingCallback<void(const std::string&)> print, base::RepeatingCallback<void(const std::string&)> print,
bool verbose, bool verbose)
base::OnceClosure callback) : print_(std::move(print)), verbose_(verbose) {
: print_(std::move(print)),
verbose_(verbose),
callback_(std::move(callback)) {
// Initialize Progress // Initialize Progress
Print(base::StringPrintf("%s%s[%s] ", kCursorHide, kColor5Purple, Print(base::StringPrintf("%s%s[%s] ", kCursorHide, kColor5Purple,
std::string(kMaxStage, ' ').c_str())); std::string(kMaxStage, ' ').c_str()));
...@@ -62,8 +59,6 @@ CrostiniStartupStatus::~CrostiniStartupStatus() = default; ...@@ -62,8 +59,6 @@ CrostiniStartupStatus::~CrostiniStartupStatus() = default;
void CrostiniStartupStatus::OnCrostiniRestarted( void CrostiniStartupStatus::OnCrostiniRestarted(
crostini::CrostiniResult result) { crostini::CrostiniResult result) {
if (result != crostini::CrostiniResult::SUCCESS) { if (result != crostini::CrostiniResult::SUCCESS) {
LOG(ERROR) << "Error starting crostini for terminal: "
<< static_cast<int>(result);
PrintAfterStage( PrintAfterStage(
kColor1RedBright, kColor1RedBright,
base::StringPrintf("Error starting penguin container: %d %s\r\n", base::StringPrintf("Error starting penguin container: %d %s\r\n",
...@@ -77,8 +72,6 @@ void CrostiniStartupStatus::OnCrostiniRestarted( ...@@ -77,8 +72,6 @@ void CrostiniStartupStatus::OnCrostiniRestarted(
} }
Print( Print(
base::StringPrintf("\r%s%s%s", kEraseInLine, kColor0Normal, kCursorShow)); base::StringPrintf("\r%s%s%s", kEraseInLine, kColor0Normal, kCursorShow));
std::move(callback_).Run();
delete this;
} }
void CrostiniStartupStatus::ShowProgressAtInterval() { void CrostiniStartupStatus::ShowProgressAtInterval() {
......
...@@ -20,14 +20,13 @@ class CrostiniStartupStatus ...@@ -20,14 +20,13 @@ class CrostiniStartupStatus
: public crostini::CrostiniManager::RestartObserver { : public crostini::CrostiniManager::RestartObserver {
public: public:
CrostiniStartupStatus(base::RepeatingCallback<void(const std::string&)> print, CrostiniStartupStatus(base::RepeatingCallback<void(const std::string&)> print,
bool verbose, bool verbose);
base::OnceClosure callback);
~CrostiniStartupStatus() override; ~CrostiniStartupStatus() override;
// Updates the progress spinner every 300ms. // Updates the progress spinner every 300ms.
void ShowProgressAtInterval(); void ShowProgressAtInterval();
// Deletes this object when called. // Called when startup is complete.
void OnCrostiniRestarted(crostini::CrostiniResult result); void OnCrostiniRestarted(crostini::CrostiniResult result);
private: private:
...@@ -45,7 +44,6 @@ class CrostiniStartupStatus ...@@ -45,7 +44,6 @@ class CrostiniStartupStatus
base::RepeatingCallback<void(const std::string& output)> print_; base::RepeatingCallback<void(const std::string& output)> print_;
const bool verbose_; const bool verbose_;
base::OnceClosure callback_;
int spinner_index_ = 0; int spinner_index_ = 0;
int stage_index_ = 0; int stage_index_ = 0;
int end_of_line_index_ = 0; int end_of_line_index_ = 0;
......
...@@ -27,9 +27,7 @@ class CrostiniStartupStatusTest : public testing::Test { ...@@ -27,9 +27,7 @@ class CrostiniStartupStatusTest : public testing::Test {
return new CrostiniStartupStatus( return new CrostiniStartupStatus(
base::BindRepeating(&CrostiniStartupStatusTest::Print, base::BindRepeating(&CrostiniStartupStatusTest::Print,
base::Unretained(this)), base::Unretained(this)),
verbose, verbose);
base::BindOnce(&CrostiniStartupStatusTest::Done,
base::Unretained(this)));
} }
void SetUp() override {} void SetUp() override {}
...@@ -44,8 +42,6 @@ TEST_F(CrostiniStartupStatusTest, TestNotVerbose) { ...@@ -44,8 +42,6 @@ TEST_F(CrostiniStartupStatusTest, TestNotVerbose) {
startup_status->OnStageStarted(InstallerState::kInstallImageLoader); startup_status->OnStageStarted(InstallerState::kInstallImageLoader);
startup_status->OnCrostiniRestarted(crostini::CrostiniResult::SUCCESS); startup_status->OnCrostiniRestarted(crostini::CrostiniResult::SUCCESS);
EXPECT_TRUE(done_);
EXPECT_EQ(output_.size(), 2u); EXPECT_EQ(output_.size(), 2u);
// Hide cursor, init progress. // Hide cursor, init progress.
EXPECT_EQ(output_[0], "\x1b[?25l\x1b[35m[ ] "); EXPECT_EQ(output_[0], "\x1b[?25l\x1b[35m[ ] ");
...@@ -58,7 +54,6 @@ TEST_F(CrostiniStartupStatusTest, TestVerbose) { ...@@ -58,7 +54,6 @@ TEST_F(CrostiniStartupStatusTest, TestVerbose) {
startup_status->OnStageStarted(InstallerState::kStart); startup_status->OnStageStarted(InstallerState::kStart);
startup_status->OnStageStarted(InstallerState::kInstallImageLoader); startup_status->OnStageStarted(InstallerState::kInstallImageLoader);
startup_status->OnCrostiniRestarted(crostini::CrostiniResult::SUCCESS); startup_status->OnCrostiniRestarted(crostini::CrostiniResult::SUCCESS);
EXPECT_TRUE(done_);
EXPECT_EQ(output_.size(), 6u); EXPECT_EQ(output_.size(), 6u);
// Hide cursor, init progress. // Hide cursor, init progress.
......
...@@ -14,11 +14,14 @@ ...@@ -14,11 +14,14 @@
#include "base/json/json_writer.h" #include "base/json/json_writer.h"
#include "base/lazy_instance.h" #include "base/lazy_instance.h"
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h" #include "base/system/sys_info.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "base/values.h" #include "base/values.h"
#include "chrome/browser/chromeos/crostini/crostini_features.h" #include "chrome/browser/chromeos/crostini/crostini_features.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/crostini/crostini_pref_names.h" #include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/extensions/api/terminal/crostini_startup_status.h" #include "chrome/browser/extensions/api/terminal/crostini_startup_status.h"
#include "chrome/browser/extensions/api/terminal/terminal_extension_helper.h" #include "chrome/browser/extensions/api/terminal/terminal_extension_helper.h"
#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service.h"
...@@ -226,26 +229,27 @@ TerminalPrivateOpenTerminalProcessFunction::Run() { ...@@ -226,26 +229,27 @@ TerminalPrivateOpenTerminalProcessFunction::Run() {
crostini::kCrostiniDefaultContainerName); crostini::kCrostiniDefaultContainerName);
std::string startup_id = params_args.GetSwitchValueASCII(kSwitchStartupId); std::string startup_id = params_args.GetSwitchValueASCII(kSwitchStartupId);
auto open_process =
base::BindOnce(&TerminalPrivateOpenTerminalProcessFunction::OpenProcess,
this, user_id_hash, tab_id, vmshell_cmd.argv());
auto* mgr = crostini::CrostiniManager::GetForProfile(profile); auto* mgr = crostini::CrostiniManager::GetForProfile(profile);
bool verbose = bool verbose =
!mgr->GetContainerInfo(crostini::kCrostiniDefaultVmName, !mgr->GetContainerInfo(crostini::kCrostiniDefaultVmName,
crostini::kCrostiniDefaultContainerName) crostini::kCrostiniDefaultContainerName)
.has_value(); .has_value();
auto* observer = new CrostiniStartupStatus( auto observer = std::make_unique<CrostiniStartupStatus>(
base::BindRepeating(&NotifyProcessOutput, browser_context(), tab_id, base::BindRepeating(&NotifyProcessOutput, browser_context(), tab_id,
startup_id, startup_id,
api::terminal_private::ToString( api::terminal_private::ToString(
api::terminal_private::OUTPUT_TYPE_STDOUT)), api::terminal_private::OUTPUT_TYPE_STDOUT)),
verbose, std::move(open_process)); verbose);
// Save copy of pointer for RestartObserver before moving object.
CrostiniStartupStatus* observer_ptr = observer.get();
observer->ShowProgressAtInterval(); observer->ShowProgressAtInterval();
mgr->RestartCrostini( mgr->RestartCrostini(
vm_name, container_name, vm_name, container_name,
base::BindOnce(&CrostiniStartupStatus::OnCrostiniRestarted, base::BindOnce(
base::Unretained(observer)), &TerminalPrivateOpenTerminalProcessFunction::OnCrostiniRestarted,
observer); this, std::move(observer), user_id_hash, tab_id,
vmshell_cmd.argv()),
observer_ptr);
} else { } else {
// command=[unrecognized]. // command=[unrecognized].
return RespondNow(Error("Invalid process name: " + params->process_name)); return RespondNow(Error("Invalid process name: " + params->process_name));
...@@ -253,6 +257,31 @@ TerminalPrivateOpenTerminalProcessFunction::Run() { ...@@ -253,6 +257,31 @@ TerminalPrivateOpenTerminalProcessFunction::Run() {
return RespondLater(); return RespondLater();
} }
void TerminalPrivateOpenTerminalProcessFunction::OnCrostiniRestarted(
std::unique_ptr<CrostiniStartupStatus> startup_status,
const std::string& user_id_hash,
int tab_id,
const std::vector<std::string>& arguments,
crostini::CrostiniResult result) {
startup_status->OnCrostiniRestarted(result);
if (result == crostini::CrostiniResult::SUCCESS) {
OpenProcess(user_id_hash, tab_id, arguments);
} else {
const std::string msg =
base::StringPrintf("Error starting crostini for terminal: %d", result);
LOG(ERROR) << msg;
Respond(Error(msg));
// Special handling to update terminal component.
if (result == crostini::CrostiniResult::OFFLINE_WHEN_UPGRADE_REQUIRED ||
result == crostini::CrostiniResult::LOAD_COMPONENT_FAILED) {
ShowCrostiniUpdateComponentView(
Profile::FromBrowserContext(browser_context()),
crostini::CrostiniUISurface::kAppList);
}
}
}
void TerminalPrivateOpenTerminalProcessFunction::OpenProcess( void TerminalPrivateOpenTerminalProcessFunction::OpenProcess(
const std::string& user_id_hash, const std::string& user_id_hash,
int tab_id, int tab_id,
......
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
#ifndef CHROME_BROWSER_EXTENSIONS_API_TERMINAL_TERMINAL_PRIVATE_API_H_ #ifndef CHROME_BROWSER_EXTENSIONS_API_TERMINAL_TERMINAL_PRIVATE_API_H_
#define CHROME_BROWSER_EXTENSIONS_API_TERMINAL_TERMINAL_PRIVATE_API_H_ #define CHROME_BROWSER_EXTENSIONS_API_TERMINAL_TERMINAL_PRIVATE_API_H_
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include "chrome/browser/chromeos/crostini/crostini_simple_types.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "extensions/browser/browser_context_keyed_api_factory.h" #include "extensions/browser/browser_context_keyed_api_factory.h"
#include "extensions/browser/extension_function.h" #include "extensions/browser/extension_function.h"
...@@ -17,6 +19,8 @@ class PrefChangeRegistrar; ...@@ -17,6 +19,8 @@ class PrefChangeRegistrar;
namespace extensions { namespace extensions {
class CrostiniStartupStatus;
class TerminalPrivateAPI : public BrowserContextKeyedAPI { class TerminalPrivateAPI : public BrowserContextKeyedAPI {
public: public:
explicit TerminalPrivateAPI(content::BrowserContext* context); explicit TerminalPrivateAPI(content::BrowserContext* context);
...@@ -50,6 +54,14 @@ class TerminalPrivateOpenTerminalProcessFunction : public ExtensionFunction { ...@@ -50,6 +54,14 @@ class TerminalPrivateOpenTerminalProcessFunction : public ExtensionFunction {
ExtensionFunction::ResponseAction Run() override; ExtensionFunction::ResponseAction Run() override;
private: private:
// Callback for when starting crostini is complete.
void OnCrostiniRestarted(
std::unique_ptr<CrostiniStartupStatus> startup_status,
const std::string& user_id_hash,
int tab_id,
const std::vector<std::string>& arguments,
crostini::CrostiniResult result);
using ProcessOutputCallback = using ProcessOutputCallback =
base::Callback<void(const std::string& terminal_id, base::Callback<void(const std::string& terminal_id,
const std::string& output_type, const std::string& output_type,
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "chrome/browser/ui/views/crostini/crostini_update_component_view.h" #include "chrome/browser/ui/views/crostini/crostini_update_component_view.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_base.h" #include "base/metrics/histogram_base.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h" #include "base/test/metrics/histogram_tester.h"
...@@ -12,13 +13,36 @@ ...@@ -12,13 +13,36 @@
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/views/crostini/crostini_browser_test_util.h" #include "chrome/browser/ui/views/crostini/crostini_browser_test_util.h"
#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
#include "chrome/browser/web_applications/system_web_app_manager.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/common/chrome_features.h" #include "chrome/common/chrome_features.h"
#include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_concierge_client.h" #include "chromeos/dbus/fake_concierge_client.h"
#include "components/crx_file/id_util.h" #include "components/crx_file/id_util.h"
#include "content/public/browser/web_contents_observer.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
class LoadFinishedWaiter : public content::WebContentsObserver {
public:
explicit LoadFinishedWaiter(content::WebContents* contents)
: content::WebContentsObserver(contents) {}
~LoadFinishedWaiter() override = default;
void Wait() { run_loop_.Run(); }
// content::WebContentsObserver:
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) override {
run_loop_.Quit();
}
private:
base::RunLoop run_loop_;
};
class CrostiniUpdateComponentViewBrowserTest class CrostiniUpdateComponentViewBrowserTest
: public CrostiniDialogBrowserTest { : public CrostiniDialogBrowserTest {
public: public:
...@@ -99,13 +123,20 @@ IN_PROC_BROWSER_TEST_F(CrostiniUpdateComponentViewBrowserTest, ...@@ -99,13 +123,20 @@ IN_PROC_BROWSER_TEST_F(CrostiniUpdateComponentViewBrowserTest,
ExpectNoView(); ExpectNoView();
UnregisterTermina(); UnregisterTermina();
crostini::LaunchCrostiniApp(browser()->profile(), crostini::LaunchCrostiniApp(browser()->profile(), crostini::GetTerminalId(),
crostini::kCrostiniTerminalId, 0); 0);
ExpectNoView(); ExpectNoView();
} }
IN_PROC_BROWSER_TEST_F(CrostiniUpdateComponentViewBrowserTest, IN_PROC_BROWSER_TEST_F(CrostiniUpdateComponentViewBrowserTest,
LaunchAppOffline_UpgradeNeeded) { LaunchAppOffline_UpgradeNeeded) {
// Ensure Terminal System App is installed if enabled.
if (base::FeatureList::IsEnabled(features::kTerminalSystemApp)) {
web_app::WebAppProvider::Get(browser()->profile())
->system_web_app_manager()
.InstallSystemAppsForTesting();
}
base::HistogramTester histogram_tester; base::HistogramTester histogram_tester;
SetConnectionType(network::mojom::ConnectionType::CONNECTION_NONE); SetConnectionType(network::mojom::ConnectionType::CONNECTION_NONE);
crostini::CrostiniManager::GetForProfile(browser()->profile()) crostini::CrostiniManager::GetForProfile(browser()->profile())
...@@ -114,8 +145,18 @@ IN_PROC_BROWSER_TEST_F(CrostiniUpdateComponentViewBrowserTest, ...@@ -114,8 +145,18 @@ IN_PROC_BROWSER_TEST_F(CrostiniUpdateComponentViewBrowserTest,
ExpectNoView(); ExpectNoView();
UnregisterTermina(); UnregisterTermina();
crostini::LaunchCrostiniApp(browser()->profile(), crostini::LaunchCrostiniApp(browser()->profile(), crostini::GetTerminalId(),
crostini::kCrostiniTerminalId, 0); 0);
// For Terminal System App, we must wait for browser to load.
if (base::FeatureList::IsEnabled(features::kTerminalSystemApp)) {
Browser* terminal_browser = web_app::FindSystemWebAppBrowser(
browser()->profile(), web_app::SystemAppType::TERMINAL);
CHECK_NE(nullptr, terminal_browser);
LoadFinishedWaiter(terminal_browser->tab_strip_model()->GetWebContentsAt(0))
.Wait();
}
ExpectView(); ExpectView();
ActiveView()->AcceptDialog(); ActiveView()->AcceptDialog();
......
...@@ -58,10 +58,14 @@ void ReadFile(const std::string& relative_path, ...@@ -58,10 +58,14 @@ void ReadFile(const std::string& relative_path,
{"manifest.json", R"({ {"manifest.json", R"({
"name": "Test Terminal", "name": "Test Terminal",
"icons": [{ "src": "/icon.svg", "sizes": "any" }], "icons": [{ "src": "/icon.svg", "sizes": "any" }],
"start_url": "/html/pwa.html"})"}, "start_url": "/html/terminal.html"})"},
{"icon.svg", {"icon.svg",
"<svg xmlns='http://www.w3.org/2000/svg'><rect " "<svg xmlns='http://www.w3.org/2000/svg'><rect "
"fill='red'/></svg>"}, "fill='red'/></svg>"},
{"html/terminal.html", "<script src='/js/terminal.js'></script>"},
{"js/terminal.js",
"chrome.terminalPrivate.openTerminalProcess("
"'vmshell', [], () => {})"},
}); });
auto it = kTestFiles->find(relative_path); auto it = kTestFiles->find(relative_path);
if (it != kTestFiles->end()) { if (it != kTestFiles->end()) {
......
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