0

[ChromeOS] Integrating OnTask pod with the OnTask SWA

This change integrates the OnTask pod with the OnTask SWA and ensures it
is initialized once the window tracker starts tracking the SWA window
at the onset of an OnTask session and destroyed at the end of the
session when the window is closed.

Bug: b:393194633
Change-Id: I8ca7d8176d8267766d1217a32bb78159078fa831
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6289666
Commit-Queue: Vignesh Shenvi <vshenvi@google.com>
Reviewed-by: Luke Cao <caott@google.com>
Reviewed-by: David Pennington <dpenning@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1423447}
This commit is contained in:
Vignesh Shenvi
2025-02-21 15:15:34 -08:00
committed by Chromium LUCI CQ
parent a9cca585d7
commit 4f11efa516
9 changed files with 445 additions and 1 deletions

@ -30,6 +30,7 @@ include_rules = [
"+chrome/browser/signin/identity_manager_factory.h",
"+chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h",
"+chrome/browser/ui/browser.h",
"+chrome/browser/ui/browser_commands.h",
"+chrome/browser/ui/browser_command_controller.h",
"+chrome/browser/ui/browser_list.h",
"+chrome/browser/ui/browser_list_observer.h",
@ -37,6 +38,7 @@ include_rules = [
"+chrome/browser/ui/browser_window.h",
"+chrome/browser/ui/exclusive_access/exclusive_access_manager.h",
"+chrome/browser/ui/exclusive_access/fullscreen_controller.h",
"+chrome/browser/ui/views/frame/browser_view.h",
"+chrome/browser/ui/tabs/tab_enums.h",
"+chrome/browser/ui/tabs/tab_strip_model_delegate.h",
"+chrome/browser/ui/tabs/tab_strip_model_observer.h",
@ -57,7 +59,6 @@ specific_include_rules = {
"+chrome/browser/content_settings/host_content_settings_map_factory.h",
],
"on_task_locked_session_navigation_throttle_interactive_ui_test.cc": [
"+chrome/browser/ui/browser_commands.h",
"+chrome/browser/ui/browser_navigator_params.h",
"+chrome/browser/ui/test/test_browser_closed_waiter.h",
"+chrome/test/base/interactive_test_utils.h",
@ -69,6 +70,9 @@ specific_include_rules = {
"+chrome/common/chrome_paths.h",
"+chrome/test/base/ui_test_utils.h"
],
"on_task_pod_controller_impl_browsertest.cc": [
"+chrome/browser/ui/tabs/tab_strip_model.h",
],
"on_task_session_manager_browsertest.cc": [
"+chrome/browser/ui/browser_commands.h",
"+chrome/test/base/ui_test_utils.h"

@ -16,6 +16,8 @@ static_library("on_task") {
"on_task_locked_session_navigation_throttle.h",
"on_task_locked_session_window_tracker.cc",
"on_task_locked_session_window_tracker.h",
"on_task_pod_controller_impl.cc",
"on_task_pod_controller_impl.h",
"on_task_system_web_app_manager_impl.cc",
"on_task_system_web_app_manager_impl.h",
]
@ -49,6 +51,7 @@ static_library("on_task") {
"//chromeos/ash/components/boca/on_task:activity",
"//chromeos/ash/components/browser_context_helper",
"//chromeos/strings:strings_grit",
"//chromeos/ui/frame",
"//components/keyed_service/content",
"//components/policy/core/browser",
"//components/policy/core/common",
@ -93,12 +96,14 @@ source_set("browser_tests") {
sources = [
"on_task_locked_session_navigation_throttle_interactive_ui_test.cc",
"on_task_locked_session_window_tracker_browsertest.cc",
"on_task_pod_controller_impl_browsertest.cc",
"on_task_session_manager_browsertest.cc",
"on_task_system_web_app_manager_impl_browsertest.cc",
]
deps = [
":on_task",
"//ash",
"//ash/webui/boca_ui",
"//base/test:test_support",
"//chrome/browser/ash/boca",

@ -9,6 +9,8 @@
#include <string>
#include <utility>
#include "ash/boca/on_task/on_task_pod_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
@ -19,6 +21,7 @@
#include "base/functional/bind.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chrome/browser/ash/boca/on_task/on_task_pod_controller_impl.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser.h"
@ -96,6 +99,10 @@ void LockedSessionWindowTracker::InitializeBrowserInfoForTracking(
}
browser_ = browser;
browser_->tab_strip_model()->AddObserver(this);
if (ash::features::IsBocaOnTaskPodEnabled()) {
on_task_pod_controller_ =
std::make_unique<ash::OnTaskPodControllerImpl>(browser_);
}
}
void LockedSessionWindowTracker::RefreshUrlBlocklist() {
@ -208,6 +215,7 @@ void LockedSessionWindowTracker::CleanupWindowTracker() {
if (on_task_blocklist_) {
on_task_blocklist_->CleanupBlocklist();
}
on_task_pod_controller_.reset();
browser_ = nullptr;
can_open_new_popup_ = true;
oauth_in_progress_ = false;
@ -257,6 +265,14 @@ void LockedSessionWindowTracker::SetNotificationManagerForTesting(
notifications_manager_ = std::move(notifications_manager);
}
ash::OnTaskPodController*
LockedSessionWindowTracker::GetOnTaskPodControllerForTesting() {
if (!on_task_pod_controller_) {
return nullptr;
}
return on_task_pod_controller_.get();
}
void LockedSessionWindowTracker::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,

@ -5,6 +5,8 @@
#ifndef CHROME_BROWSER_ASH_BOCA_ON_TASK_ON_TASK_LOCKED_SESSION_WINDOW_TRACKER_H_
#define CHROME_BROWSER_ASH_BOCA_ON_TASK_ON_TASK_LOCKED_SESSION_WINDOW_TRACKER_H_
#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
@ -26,6 +28,10 @@ namespace ash::boca {
class BocaWindowObserver;
}
namespace ash {
class OnTaskPodController;
}
// This class is used to track the windows and tabs that are opened in the
// user's OnTask locked session. Only one browser window is allowed at a time to
// be tracked. Attempting to track another browser while there is one already
@ -89,6 +95,8 @@ class LockedSessionWindowTracker : public KeyedService,
std::unique_ptr<ash::boca::OnTaskNotificationsManager>
notification_manager);
ash::OnTaskPodController* GetOnTaskPodControllerForTesting();
OnTaskBlocklist* on_task_blocklist();
Browser* browser();
@ -123,6 +131,7 @@ class LockedSessionWindowTracker : public KeyedService,
const std::unique_ptr<OnTaskBlocklist> on_task_blocklist_;
const bool is_consumer_profile_;
std::unique_ptr<ash::boca::OnTaskNotificationsManager> notifications_manager_;
std::unique_ptr<ash::OnTaskPodController> on_task_pod_controller_;
raw_ptr<Browser> browser_ = nullptr;
base::ObserverList<ash::boca::BocaWindowObserver> observers_;

@ -0,0 +1,98 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/boca/on_task/on_task_pod_controller_impl.h"
#include <memory>
#include <string>
#include "ash/boca/on_task/on_task_pod_view.h"
#include "ash/shell.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "chromeos/ui/frame/frame_header.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
// Internal name for the OnTask pod widget. Useful for debugging purposes.
constexpr char kOnTaskPodWidgetInternalName[] = "OnTaskPod";
// Creates a child widget for the specified parent window with some common
// characteristics.
std::unique_ptr<views::Widget> CreateChildWidget(
aura::Window* parent_window,
const std::string& widget_name,
std::unique_ptr<views::View> view) {
views::Widget::InitParams params(
views::Widget::InitParams::CLIENT_OWNS_WIDGET,
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.parent = parent_window;
params.name = widget_name;
params.activatable = views::Widget::InitParams::Activatable::kDefault;
params.opacity = views::Widget::InitParams::WindowOpacity::kOpaque;
auto widget = std::make_unique<views::Widget>();
widget->Init(std::move(params));
widget->SetContentsView(std::move(view));
widget->SetVisibilityAnimationTransition(views::Widget::ANIMATE_NONE);
return widget;
}
int GetFrameHeaderHeight(views::Widget* widget) {
auto* const frame_header = chromeos::FrameHeader::Get(widget);
return (frame_header && frame_header->view()->GetVisible())
? frame_header->GetHeaderHeight()
: 0;
}
} // namespace
OnTaskPodControllerImpl::OnTaskPodControllerImpl(Browser* browser)
: browser_(browser->AsWeakPtr()) {
aura::Window* const browser_window = browser_->window()->GetNativeWindow();
auto on_task_pod_view = std::make_unique<OnTaskPodView>(this);
pod_widget_ = CreateChildWidget(browser_window->GetToplevelWindow(),
kOnTaskPodWidgetInternalName,
std::move(on_task_pod_view));
pod_widget_->widget_delegate()->SetAccessibleTitle(
l10n_util::GetStringUTF16(IDS_ON_TASK_POD_ACCESSIBLE_NAME));
pod_widget_->SetBounds(CalculateWidgetBounds());
pod_widget_->Show();
}
OnTaskPodControllerImpl::~OnTaskPodControllerImpl() = default;
void OnTaskPodControllerImpl::ReloadCurrentPage() {
if (!browser_) {
return;
}
chrome::Reload(browser_.get(), WindowOpenDisposition::CURRENT_TAB);
}
const gfx::Rect OnTaskPodControllerImpl::CalculateWidgetBounds() {
const gfx::Rect parent_window_bounds =
pod_widget_->parent()->GetWindowBoundsInScreen();
const gfx::Size preferred_size =
pod_widget_->GetContentsView()->GetPreferredSize();
const int frame_header_height = GetFrameHeaderHeight(pod_widget_->parent());
const gfx::Point origin = gfx::Point(
parent_window_bounds.x(), parent_window_bounds.y() + frame_header_height);
return gfx::Rect(origin, preferred_size);
}
views::Widget* OnTaskPodControllerImpl::GetPodWidgetForTesting() {
if (!pod_widget_) {
return nullptr;
}
return pod_widget_.get();
}
} // namespace ash

@ -0,0 +1,48 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_ASH_BOCA_ON_TASK_ON_TASK_POD_CONTROLLER_IMPL_H_
#define CHROME_BROWSER_ASH_BOCA_ON_TASK_ON_TASK_POD_CONTROLLER_IMPL_H_
#include <memory>
#include "ash/boca/on_task/on_task_pod_controller.h"
#include "base/memory/weak_ptr.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/widget/widget.h"
class Browser;
namespace ash {
// OnTask pod controller implementation for the `OnTaskPodView`. This controller
// implementation also owns the widget that hosts the pod component view.
class OnTaskPodControllerImpl : public OnTaskPodController {
public:
explicit OnTaskPodControllerImpl(Browser* browser);
OnTaskPodControllerImpl(const OnTaskPodControllerImpl&) = delete;
OnTaskPodControllerImpl& operator=(const OnTaskPodControllerImpl) = delete;
~OnTaskPodControllerImpl() override;
// OnTaskPodController:
void ReloadCurrentPage() override;
// Component accessors used for testing purposes.
views::Widget* GetPodWidgetForTesting();
private:
// Calculates the OnTask pod widget bounds based on the snap location and
// the parent window frame header height.
const gfx::Rect CalculateWidgetBounds();
// Weak pointer for the Boca app instance that is being interacted with.
const base::WeakPtr<Browser> browser_;
// Pod widget that contains the `OnTaskPodView`.
std::unique_ptr<views::Widget> pod_widget_;
};
} // namespace ash
#endif // CHROME_BROWSER_ASH_BOCA_ON_TASK_ON_TASK_POD_CONTROLLER_IMPL_H_

@ -0,0 +1,260 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/boca/on_task/on_task_pod_controller_impl.h"
#include <vector>
#include "ash/boca/on_task/on_task_pod_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/webui/system_apps/public/system_web_app_type.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "chrome/browser/ash/boca/on_task/locked_session_window_tracker_factory.h"
#include "chrome/browser/ash/boca/on_task/on_task_locked_session_window_tracker.h"
#include "chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/ash/components/boca/proto/bundle.pb.h"
#include "components/sessions/core/session_id.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"
using ::testing::IsNull;
using ::testing::NotNull;
namespace ash {
namespace {
class OnTaskPodControllerImplBrowserTestBase : public InProcessBrowserTest {
protected:
void SetUpOnMainThread() override {
ash::SystemWebAppManager::Get(profile())->InstallSystemAppsForTesting();
system_web_app_manager_ =
std::make_unique<boca::OnTaskSystemWebAppManagerImpl>(profile());
host_resolver()->AddRule("*", "127.0.0.1");
InProcessBrowserTest::SetUpOnMainThread();
}
void TearDownOnMainThread() override {
system_web_app_manager_.reset();
InProcessBrowserTest::TearDownOnMainThread();
}
Browser* FindBocaSystemWebAppBrowser() {
return ash::FindSystemWebAppBrowser(profile(), ash::SystemWebAppType::BOCA);
}
Profile* profile() { return browser()->profile(); }
LockedSessionWindowTracker* window_tracker() {
return LockedSessionWindowTrackerFactory::GetInstance()
->GetForBrowserContext(profile());
}
ash::OnTaskPodControllerImpl* on_task_pod_controller() {
return static_cast<ash::OnTaskPodControllerImpl*>(
window_tracker()->GetOnTaskPodControllerForTesting());
}
boca::OnTaskSystemWebAppManagerImpl* system_web_app_manager() {
return system_web_app_manager_.get();
}
private:
std::unique_ptr<boca::OnTaskSystemWebAppManagerImpl> system_web_app_manager_;
};
class OnTaskPodControllerImplSetupBrowserTest
: public OnTaskPodControllerImplBrowserTestBase,
public ::testing::WithParamInterface<bool> {
protected:
OnTaskPodControllerImplSetupBrowserTest() {
std::vector<base::test::FeatureRef> enabled_features{
features::kBoca, features::kBocaConsumer};
std::vector<base::test::FeatureRef> disabled_features;
if (IsOnTaskPodEnabled()) {
enabled_features.push_back(features::kBocaOnTaskPod);
} else {
disabled_features.push_back(features::kBocaOnTaskPod);
}
scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
}
bool IsOnTaskPodEnabled() { return GetParam(); }
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_P(OnTaskPodControllerImplSetupBrowserTest,
PodSetupWithFeatureFlag) {
// Launch OnTask SWA.
base::test::TestFuture<bool> launch_future;
system_web_app_manager()->LaunchSystemWebAppAsync(
launch_future.GetCallback());
ASSERT_TRUE(launch_future.Get());
Browser* const boca_app_browser = FindBocaSystemWebAppBrowser();
ASSERT_THAT(boca_app_browser, NotNull());
ASSERT_TRUE(boca_app_browser->IsLockedForOnTask());
// Set up window tracker to track the app window. Verify that the pod is set
// up only when the feature flag is enabled.
const SessionID window_id = boca_app_browser->session_id();
ASSERT_TRUE(window_id.is_valid());
system_web_app_manager()->SetWindowTrackerForSystemWebAppWindow(
window_id, /*observers=*/{});
if (IsOnTaskPodEnabled()) {
ASSERT_THAT(on_task_pod_controller(), NotNull());
views::Widget* const pod_widget =
on_task_pod_controller()->GetPodWidgetForTesting();
ASSERT_THAT(pod_widget, NotNull());
EXPECT_TRUE(pod_widget->IsVisible());
EXPECT_TRUE(pod_widget->GetContentsView()->GetVisible());
} else {
EXPECT_THAT(on_task_pod_controller(), IsNull());
}
}
INSTANTIATE_TEST_SUITE_P(OnTaskPodControllerImplSetupBrowserTests,
OnTaskPodControllerImplSetupBrowserTest,
::testing::Bool());
class OnTaskPodControllerImplBrowserTest
: public OnTaskPodControllerImplBrowserTestBase {
protected:
OnTaskPodControllerImplBrowserTest() {
// Enable Boca and consumer experience for testing purposes. This is used
// to set up the Boca SWA for OnTask.
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{features::kBoca, features::kBocaConsumer,
features::kBocaOnTaskPod},
/*disabled_features=*/{});
}
void SetUpOnMainThread() override {
OnTaskPodControllerImplBrowserTestBase::SetUpOnMainThread();
embedded_test_server()->AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(embedded_test_server()->Start());
}
// Creates a new background tab with the specified url and navigation
// restrictions, and waits until the specified url has been loaded.
// Returns the newly created tab id.
SessionID CreateBackgroundTabAndWait(
SessionID window_id,
const GURL& url,
::boca::LockedNavigationOptions::NavigationType restriction_level) {
content::TestNavigationObserver navigation_observer(url);
navigation_observer.StartWatchingNewWebContents();
const SessionID tab_id =
system_web_app_manager()->CreateBackgroundTabWithUrl(window_id, url,
restriction_level);
navigation_observer.Wait();
return tab_id;
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(OnTaskPodControllerImplBrowserTest,
DestroyPodOnWindowClose) {
// Launch OnTask SWA.
base::test::TestFuture<bool> launch_future;
system_web_app_manager()->LaunchSystemWebAppAsync(
launch_future.GetCallback());
ASSERT_TRUE(launch_future.Get());
Browser* const boca_app_browser = FindBocaSystemWebAppBrowser();
ASSERT_THAT(boca_app_browser, NotNull());
ASSERT_TRUE(boca_app_browser->IsLockedForOnTask());
// Set up window tracker to track the app window. This is when the OnTask pod
// is set up.
const SessionID window_id = boca_app_browser->session_id();
ASSERT_TRUE(window_id.is_valid());
system_web_app_manager()->SetWindowTrackerForSystemWebAppWindow(
window_id, /*observers=*/{});
ASSERT_THAT(on_task_pod_controller(), NotNull());
boca_app_browser->window()->Close();
content::RunAllTasksUntilIdle();
EXPECT_THAT(on_task_pod_controller(), IsNull());
}
IN_PROC_BROWSER_TEST_F(OnTaskPodControllerImplBrowserTest,
DestroyPodOnWindowTrackerReset) {
// Launch OnTask SWA.
base::test::TestFuture<bool> launch_future;
system_web_app_manager()->LaunchSystemWebAppAsync(
launch_future.GetCallback());
ASSERT_TRUE(launch_future.Get());
Browser* const boca_app_browser = FindBocaSystemWebAppBrowser();
ASSERT_THAT(boca_app_browser, NotNull());
ASSERT_TRUE(boca_app_browser->IsLockedForOnTask());
// Set up window tracker to track the app window. This is when the OnTask pod
// is set up.
const SessionID window_id = boca_app_browser->session_id();
ASSERT_TRUE(window_id.is_valid());
system_web_app_manager()->SetWindowTrackerForSystemWebAppWindow(
window_id, /*observers=*/{});
ASSERT_THAT(on_task_pod_controller(), NotNull());
window_tracker()->InitializeBrowserInfoForTracking(nullptr);
EXPECT_THAT(on_task_pod_controller(), IsNull());
}
IN_PROC_BROWSER_TEST_F(OnTaskPodControllerImplBrowserTest, ReloadCurrentTab) {
// Launch OnTask SWA.
base::test::TestFuture<bool> launch_future;
system_web_app_manager()->LaunchSystemWebAppAsync(
launch_future.GetCallback());
ASSERT_TRUE(launch_future.Get());
Browser* const boca_app_browser = FindBocaSystemWebAppBrowser();
ASSERT_THAT(boca_app_browser, NotNull());
ASSERT_TRUE(boca_app_browser->IsLockedForOnTask());
// Set up window tracker to track the app window. This is when the OnTask pod
// is set up.
const SessionID window_id = boca_app_browser->session_id();
ASSERT_TRUE(window_id.is_valid());
system_web_app_manager()->SetWindowTrackerForSystemWebAppWindow(
window_id, /*observers=*/{});
ASSERT_THAT(on_task_pod_controller(), NotNull());
// Spawn a new tab for testing purposes.
auto* const tab_strip_model = boca_app_browser->tab_strip_model();
const GURL tab_url = embedded_test_server()->GetURL("/title1.html");
CreateBackgroundTabAndWait(
window_id, tab_url, ::boca::LockedNavigationOptions::DOMAIN_NAVIGATION);
ASSERT_EQ(tab_strip_model->count(), 2);
tab_strip_model->ActivateTabAt(1);
on_task_pod_controller()->ReloadCurrentPage();
content::WaitForLoadStop(tab_strip_model->GetActiveWebContents());
EXPECT_EQ(tab_strip_model->GetActiveWebContents()->GetLastCommittedURL(),
tab_url);
tab_strip_model->ActivateTabAt(0);
on_task_pod_controller()->ReloadCurrentPage();
content::WaitForLoadStop(tab_strip_model->GetActiveWebContents());
EXPECT_NE(tab_strip_model->GetActiveWebContents()->GetLastCommittedURL(),
tab_url);
}
} // namespace
} // namespace ash

@ -7012,6 +7012,9 @@
</message>
<!-- Strings for the OnTask pod -->
<message name="IDS_ON_TASK_POD_ACCESSIBLE_NAME" desc="Accessible name for the OnTask pod widget.">
OnTask Pod
</message>
<message name="IDS_ON_TASK_POD_RELOAD_ACCESSIBLE_NAME" desc="Accessible name for the Reload tab button in the OnTask pod.">
Reload tab
</message>

@ -0,0 +1 @@
9cece1f1738d4a2b5134fcd1dcb8838784bfbeee