Skip to content

Commit 6132861

Browse files
committed
feat: implement DaoLoadProgressView and DaoLoadProgressController for enhanced loading feedback in the browser UI
1 parent 125ebbd commit 6132861

9 files changed

Lines changed: 731 additions & 26 deletions

src/dao/browser/ui/views/dao_browser_browsertest.cc

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include "base/test/scoped_feature_list.h"
1515
#include "base/test/test_future.h"
1616
#include "base/threading/thread_restrictions.h"
17+
#include "base/timer/timer.h"
18+
#include "content/public/browser/page_navigator.h"
1719
#include "base/command_line.h"
1820
#include "chrome/browser/prefs/session_startup_pref.h"
1921
#include "chrome/browser/profiles/profile.h"
@@ -62,6 +64,7 @@
6264
#include "dao/browser/ui/views/dao_control_center_button.h"
6365
#include "dao/browser/ui/views/dao_control_center_popup.h"
6466
#include "dao/browser/ui/views/dao_corner_overlay_view.h"
67+
#include "dao/browser/ui/views/dao_load_progress_view.h"
6568
#include "dao/browser/ui/views/dao_tab_commands.h"
6669
#include "dao/browser/ui/views/dao_tab_identity.h"
6770
#include "dao/browser/ui/views/dao_toast_view.h"
@@ -2557,5 +2560,175 @@ IN_PROC_BROWSER_TEST_F(DaoAgentWorkspaceBrowserTest,
25572560
EXPECT_TRUE(base::DirectoryExists(stage));
25582561
}
25592562

2563+
// =============================================================================
2564+
// DaoLoadProgressBrowserTest
2565+
// =============================================================================
2566+
2567+
class DaoLoadProgressBrowserTest : public InProcessBrowserTest {
2568+
public:
2569+
void SetUpOnMainThread() override {
2570+
InProcessBrowserTest::SetUpOnMainThread();
2571+
ASSERT_TRUE(embedded_test_server()->Start());
2572+
}
2573+
};
2574+
2575+
IN_PROC_BROWSER_TEST_F(DaoLoadProgressBrowserTest, ViewExists) {
2576+
auto* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
2577+
ASSERT_TRUE(browser_view);
2578+
auto* progress = browser_view->dao_load_progress();
2579+
ASSERT_TRUE(progress);
2580+
// The view exists; its visible state depends on whether the active tab is
2581+
// currently loading at this moment (NTP can still be loading at test start),
2582+
// so we don't assert on opacity here. End-to-end load behavior is covered
2583+
// by tests added in Task 7.
2584+
ASSERT_TRUE(progress->layer());
2585+
}
2586+
2587+
IN_PROC_BROWSER_TEST_F(DaoLoadProgressBrowserTest,
2588+
StartLoadingMakesBarVisible) {
2589+
auto* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
2590+
auto* progress = browser_view->dao_load_progress();
2591+
ASSERT_TRUE(progress);
2592+
2593+
progress->StartLoading();
2594+
EXPECT_TRUE(progress->is_loading_for_testing());
2595+
EXPECT_GT(progress->layer()->opacity(), 0.0f);
2596+
EXPECT_EQ(progress->displayed_progress_for_testing(), 0.0);
2597+
}
2598+
2599+
IN_PROC_BROWSER_TEST_F(DaoLoadProgressBrowserTest, RealLoadShowsThenHides) {
2600+
auto* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
2601+
auto* progress = browser_view->dao_load_progress();
2602+
ASSERT_TRUE(progress);
2603+
2604+
const GURL url = embedded_test_server()->GetURL("/title1.html");
2605+
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
2606+
2607+
// After NavigateToURL returns, DidStopLoading has fired and the controller
2608+
// begins the Completing/FadingOut sequence. Pump the message loop until the
2609+
// layer opacity falls back to ~0 (or 1s elapses, in which case the EXPECT
2610+
// below catches the failure).
2611+
base::RunLoop loop;
2612+
base::OneShotTimer timeout;
2613+
base::RepeatingTimer poller;
2614+
timeout.Start(FROM_HERE, base::Seconds(2),
2615+
base::BindLambdaForTesting([&]() { loop.Quit(); }));
2616+
poller.Start(FROM_HERE, base::Milliseconds(50),
2617+
base::BindLambdaForTesting([&]() {
2618+
if (progress->layer()->opacity() <= 0.01f) {
2619+
loop.Quit();
2620+
}
2621+
}));
2622+
loop.Run();
2623+
poller.Stop();
2624+
timeout.Stop();
2625+
2626+
EXPECT_LE(progress->layer()->opacity(), 0.01f);
2627+
EXPECT_FALSE(progress->is_loading_for_testing());
2628+
}
2629+
2630+
IN_PROC_BROWSER_TEST_F(DaoLoadProgressBrowserTest,
2631+
SwitchingToFinishedTabHidesBar) {
2632+
auto* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
2633+
auto* progress = browser_view->dao_load_progress();
2634+
ASSERT_TRUE(progress);
2635+
2636+
// Tab 0: load and finish.
2637+
const GURL url_a = embedded_test_server()->GetURL("/title1.html");
2638+
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_a));
2639+
2640+
// Tab 1: open and load. AddTabAtIndex waits for the navigation to complete
2641+
// via an internal NavigationObserver, so on return the controller will have
2642+
// received FinishLoading() and the bar is either still fading out or already
2643+
// hidden.
2644+
const GURL url_b = embedded_test_server()->GetURL("/title2.html");
2645+
ASSERT_TRUE(AddTabAtIndex(1, url_b, ui::PAGE_TRANSITION_TYPED));
2646+
2647+
// Poll until the in-flight fade-out animation has settled (opacity <= 0.01).
2648+
// This avoids relying on a fixed wall-clock delay.
2649+
{
2650+
base::RunLoop loop;
2651+
base::RepeatingTimer poller;
2652+
base::OneShotTimer timeout;
2653+
timeout.Start(FROM_HERE, base::Seconds(2),
2654+
base::BindLambdaForTesting([&]() { loop.Quit(); }));
2655+
poller.Start(FROM_HERE, base::Milliseconds(50),
2656+
base::BindLambdaForTesting([&]() {
2657+
if (progress->layer()->opacity() <= 0.01f) {
2658+
loop.Quit();
2659+
}
2660+
}));
2661+
loop.Run();
2662+
poller.Stop();
2663+
timeout.Stop();
2664+
}
2665+
2666+
browser()->tab_strip_model()->ActivateTabAt(0);
2667+
// Allow the controller to react to the tab switch.
2668+
base::RunLoop().RunUntilIdle();
2669+
EXPECT_LE(progress->layer()->opacity(), 0.01f);
2670+
}
2671+
2672+
IN_PROC_BROWSER_TEST_F(DaoLoadProgressBrowserTest, StopCommandHidesBar) {
2673+
auto* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
2674+
auto* progress = browser_view->dao_load_progress();
2675+
ASSERT_TRUE(progress);
2676+
2677+
// Start a navigation, then immediately stop. We don't await NavigateToURL
2678+
// — instead we kick off a slow navigation and call Stop before it
2679+
// completes.
2680+
const GURL url = embedded_test_server()->GetURL("/slow?2");
2681+
auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
2682+
content::TestNavigationManager nav_manager(web_contents, url);
2683+
content::OpenURLParams open_params(
2684+
url, content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
2685+
ui::PAGE_TRANSITION_TYPED, /*is_renderer_initiated=*/false);
2686+
browser()->OpenURL(open_params, /*navigation_handle_callback=*/{});
2687+
2688+
// Wait until the navigation actually starts on the network side — this is
2689+
// a reliable signal that DidStartLoading has been dispatched to observers.
2690+
ASSERT_TRUE(nav_manager.WaitForRequestStart());
2691+
EXPECT_TRUE(progress->is_loading_for_testing());
2692+
2693+
chrome::Stop(browser());
2694+
// Pump until DidStopLoading + fade-out completes.
2695+
base::RunLoop run_loop;
2696+
base::OneShotTimer timer;
2697+
timer.Start(FROM_HERE, base::Milliseconds(800), run_loop.QuitClosure());
2698+
run_loop.Run();
2699+
2700+
EXPECT_LE(progress->layer()->opacity(), 0.01f);
2701+
EXPECT_FALSE(progress->is_loading_for_testing());
2702+
}
2703+
2704+
IN_PROC_BROWSER_TEST_F(DaoLoadProgressBrowserTest,
2705+
LayoutFollowsSidebarCollapse) {
2706+
auto* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
2707+
auto* progress = browser_view->dao_load_progress();
2708+
auto* sidebar = browser_view->dao_sidebar();
2709+
ASSERT_TRUE(progress);
2710+
ASSERT_TRUE(sidebar);
2711+
2712+
// Force a layout pass with expanded sidebar.
2713+
ASSERT_FALSE(sidebar->collapsed());
2714+
browser_view->DeprecatedLayoutImmediately();
2715+
const int expanded_x = progress->bounds().x();
2716+
const int expanded_w = progress->bounds().width();
2717+
2718+
// Collapse the sidebar and re-layout.
2719+
sidebar->ToggleCollapsed();
2720+
// The sidebar collapse animation runs ~250ms. Pump and wait.
2721+
{
2722+
base::RunLoop run_loop;
2723+
base::OneShotTimer timer;
2724+
timer.Start(FROM_HERE, base::Milliseconds(500), run_loop.QuitClosure());
2725+
run_loop.Run();
2726+
}
2727+
browser_view->DeprecatedLayoutImmediately();
2728+
2729+
EXPECT_NE(progress->bounds().x(), expanded_x);
2730+
EXPECT_NE(progress->bounds().width(), expanded_w);
2731+
}
2732+
25602733
} // namespace
25612734
} // namespace dao
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2026 Dao Browser Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "dao/browser/ui/views/dao_load_progress_controller.h"
6+
7+
#include "chrome/browser/ui/tabs/tab_strip_model.h"
8+
#include "content/public/browser/web_contents.h"
9+
#include "dao/browser/ui/views/dao_load_progress_view.h"
10+
11+
namespace dao {
12+
13+
DaoLoadProgressController::DaoLoadProgressController(
14+
TabStripModel* tab_strip_model,
15+
DaoLoadProgressView* view)
16+
: tab_strip_model_(tab_strip_model), view_(view) {
17+
tab_strip_model_->AddObserver(this);
18+
AttachToWebContents(tab_strip_model_->GetActiveWebContents());
19+
}
20+
21+
DaoLoadProgressController::~DaoLoadProgressController() {
22+
if (tab_strip_model_) {
23+
tab_strip_model_->RemoveObserver(this);
24+
}
25+
// WebContentsObserver auto-detaches in its destructor.
26+
}
27+
28+
void DaoLoadProgressController::OnTabStripModelChanged(
29+
TabStripModel* tab_strip_model,
30+
const TabStripModelChange& change,
31+
const TabStripSelectionChange& selection) {
32+
if (selection.active_tab_changed()) {
33+
AttachToWebContents(selection.new_contents);
34+
}
35+
}
36+
37+
void DaoLoadProgressController::AttachToWebContents(
38+
content::WebContents* new_contents) {
39+
// Detach from previous (no-op if same).
40+
Observe(new_contents);
41+
42+
if (!new_contents) {
43+
view_->HideImmediately();
44+
return;
45+
}
46+
47+
if (new_contents->IsLoading()) {
48+
// Sync the view to the new tab's current progress without animating.
49+
view_->StartLoading();
50+
view_->SetTargetProgress(new_contents->GetLoadProgress(),
51+
/*animate=*/false);
52+
} else {
53+
view_->HideImmediately();
54+
}
55+
}
56+
57+
void DaoLoadProgressController::LoadProgressChanged(double progress) {
58+
view_->SetTargetProgress(progress, /*animate=*/true);
59+
}
60+
61+
void DaoLoadProgressController::DidStartLoading() {
62+
view_->StartLoading();
63+
}
64+
65+
void DaoLoadProgressController::DidStopLoading() {
66+
view_->FinishLoading();
67+
}
68+
69+
void DaoLoadProgressController::WebContentsDestroyed() {
70+
view_->HideImmediately();
71+
}
72+
73+
} // namespace dao
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2026 Dao Browser Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef DAO_BROWSER_UI_VIEWS_DAO_LOAD_PROGRESS_CONTROLLER_H_
6+
#define DAO_BROWSER_UI_VIEWS_DAO_LOAD_PROGRESS_CONTROLLER_H_
7+
8+
#include "base/memory/raw_ptr.h"
9+
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
10+
#include "content/public/browser/web_contents_observer.h"
11+
12+
class TabStripModel;
13+
14+
namespace content {
15+
class WebContents;
16+
}
17+
18+
namespace dao {
19+
20+
class DaoLoadProgressView;
21+
22+
// Owns the plumbing that drives DaoLoadProgressView from the active tab's
23+
// real load events.
24+
class DaoLoadProgressController : public TabStripModelObserver,
25+
public content::WebContentsObserver {
26+
public:
27+
DaoLoadProgressController(TabStripModel* tab_strip_model,
28+
DaoLoadProgressView* view);
29+
DaoLoadProgressController(const DaoLoadProgressController&) = delete;
30+
DaoLoadProgressController& operator=(const DaoLoadProgressController&) =
31+
delete;
32+
~DaoLoadProgressController() override;
33+
34+
// TabStripModelObserver:
35+
void OnTabStripModelChanged(
36+
TabStripModel* tab_strip_model,
37+
const TabStripModelChange& change,
38+
const TabStripSelectionChange& selection) override;
39+
40+
// content::WebContentsObserver:
41+
void LoadProgressChanged(double progress) override;
42+
void DidStartLoading() override;
43+
void DidStopLoading() override;
44+
void WebContentsDestroyed() override;
45+
46+
private:
47+
// Re-point at `new_contents`; resync the view to its current state.
48+
void AttachToWebContents(content::WebContents* new_contents);
49+
50+
raw_ptr<TabStripModel> tab_strip_model_;
51+
raw_ptr<DaoLoadProgressView> view_;
52+
};
53+
54+
} // namespace dao
55+
56+
#endif // DAO_BROWSER_UI_VIEWS_DAO_LOAD_PROGRESS_CONTROLLER_H_

0 commit comments

Comments
 (0)