Skip to content

Commit de85c12

Browse files
committed
feat(tests): add DaoBackToOpenerBrowserTest to validate back-to-opener functionality in tab navigation
1 parent 65f1d23 commit de85c12

1 file changed

Lines changed: 210 additions & 0 deletions

File tree

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

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "base/files/file_path.h"
66
#include "base/files/file_util.h"
77
#include "base/files/scoped_temp_dir.h"
8+
#include "base/run_loop.h"
89
#include "base/strings/strcat.h"
910
#include "base/strings/string_util.h"
1011
#include "base/strings/utf_string_conversions.h"
@@ -31,6 +32,7 @@
3132
#include "content/public/browser/web_contents.h"
3233
#include "content/public/test/browser_test.h"
3334
#include "content/public/test/browser_test_utils.h"
35+
#include "content/public/test/test_navigation_observer.h"
3436
#include "ui/base/hit_test.h"
3537
#include "dao/browser/agent/dao_agent_memory_service.h"
3638
#include "dao/browser/agent/dao_agent_memory_service_factory.h"
@@ -2249,5 +2251,213 @@ IN_PROC_BROWSER_TEST_F(DaoAgentMemoryServiceConversationTest, StatsAvailable) {
22492251
EXPECT_GE(stats.Get().episode_count, 0);
22502252
}
22512253

2254+
// =============================================================================
2255+
// DaoBackToOpenerBrowserTest
2256+
// =============================================================================
2257+
//
2258+
// These tests pin down the back-to-opener semantics that Dao's address-bar
2259+
// Back button is expected to honor once it routes through chrome::GoBack /
2260+
// chrome::CanGoBack. The feature flag (tabs::kBackToOpener) is enabled by
2261+
// default in our patched build, so we exercise the public command surface
2262+
// without re-enabling it here.
2263+
2264+
class DaoBackToOpenerBrowserTest : public InProcessBrowserTest {
2265+
public:
2266+
DaoBackToOpenerBrowserTest() = default;
2267+
2268+
void SetUpOnMainThread() override {
2269+
InProcessBrowserTest::SetUpOnMainThread();
2270+
host_resolver()->AddRule("*", "127.0.0.1");
2271+
embedded_test_server()->ServeFilesFromSourceDirectory("chrome/test/data");
2272+
ASSERT_TRUE(embedded_test_server()->Start());
2273+
}
2274+
2275+
// Navigate the active tab to the opener fixture and click the link to spawn
2276+
// a child tab in the same browser. Returns the destination WebContents.
2277+
content::WebContents* OpenChildFromOpener(GURL* opener_url_out = nullptr) {
2278+
GURL opener_url =
2279+
embedded_test_server()->GetURL("/back_to_opener_opener.html");
2280+
if (opener_url_out) {
2281+
*opener_url_out = opener_url;
2282+
}
2283+
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), opener_url));
2284+
2285+
content::WebContents* opener_contents =
2286+
browser()->tab_strip_model()->GetActiveWebContents();
2287+
EXPECT_TRUE(content::WaitForLoadStop(opener_contents));
2288+
2289+
ui_test_utils::TabAddedWaiter tab_waiter(browser());
2290+
EXPECT_TRUE(content::ExecJs(opener_contents,
2291+
"document.getElementById('link').click();"));
2292+
content::WebContents* dest_contents = tab_waiter.Wait();
2293+
EXPECT_NE(nullptr, dest_contents);
2294+
EXPECT_NE(dest_contents, opener_contents);
2295+
EXPECT_TRUE(content::WaitForLoadStop(dest_contents));
2296+
return dest_contents;
2297+
}
2298+
};
2299+
2300+
// 1. Clicking back in a child tab whose in-tab history is empty should close
2301+
// the child tab and re-activate the opener tab.
2302+
IN_PROC_BROWSER_TEST_F(DaoBackToOpenerBrowserTest,
2303+
BackClosesChildAndActivatesParent) {
2304+
TabStripModel* tab_strip = browser()->tab_strip_model();
2305+
content::WebContents* opener_contents = tab_strip->GetActiveWebContents();
2306+
const int opener_index = tab_strip->GetIndexOfWebContents(opener_contents);
2307+
2308+
content::WebContents* dest_contents = OpenChildFromOpener();
2309+
ASSERT_NE(nullptr, dest_contents);
2310+
ASSERT_EQ(2, tab_strip->count());
2311+
ASSERT_EQ(dest_contents, tab_strip->GetActiveWebContents());
2312+
2313+
// Back-to-opener should be available even though the child tab itself has
2314+
// no in-tab back history.
2315+
EXPECT_FALSE(dest_contents->GetController().CanGoBack());
2316+
EXPECT_TRUE(chrome::CanGoBack(browser()));
2317+
2318+
content::WebContentsDestroyedWatcher close_watcher(dest_contents);
2319+
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
2320+
close_watcher.Wait();
2321+
2322+
// Child closed, opener focused.
2323+
EXPECT_EQ(1, tab_strip->count());
2324+
EXPECT_EQ(opener_index,
2325+
tab_strip->GetIndexOfWebContents(opener_contents));
2326+
EXPECT_EQ(opener_contents, tab_strip->GetActiveWebContents());
2327+
}
2328+
2329+
// 2. If the opener tab navigates away after spawning the child, back-to-opener
2330+
// should no longer be available from the child (chrome::CanGoBack returns
2331+
// false when the child has no in-tab history).
2332+
IN_PROC_BROWSER_TEST_F(DaoBackToOpenerBrowserTest,
2333+
ParentNavigatedAwayDisablesBack) {
2334+
TabStripModel* tab_strip = browser()->tab_strip_model();
2335+
content::WebContents* opener_contents = tab_strip->GetActiveWebContents();
2336+
const int opener_index = tab_strip->GetIndexOfWebContents(opener_contents);
2337+
2338+
content::WebContents* dest_contents = OpenChildFromOpener();
2339+
ASSERT_NE(nullptr, dest_contents);
2340+
ASSERT_EQ(2, tab_strip->count());
2341+
2342+
// Switch back to the opener and navigate it elsewhere.
2343+
tab_strip->ActivateTabAt(opener_index);
2344+
ASSERT_EQ(opener_contents, tab_strip->GetActiveWebContents());
2345+
GURL other_url = embedded_test_server()->GetURL("/title2.html");
2346+
{
2347+
content::TestNavigationObserver nav_observer(opener_contents);
2348+
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), other_url));
2349+
nav_observer.Wait();
2350+
}
2351+
2352+
// Re-focus the child tab. Its own history is empty, and the opener has
2353+
// moved on, so chrome::CanGoBack should now report false.
2354+
const int dest_index = tab_strip->GetIndexOfWebContents(dest_contents);
2355+
ASSERT_NE(TabStripModel::kNoTab, dest_index);
2356+
tab_strip->ActivateTabAt(dest_index);
2357+
ASSERT_EQ(dest_contents, tab_strip->GetActiveWebContents());
2358+
2359+
EXPECT_FALSE(dest_contents->GetController().CanGoBack());
2360+
EXPECT_FALSE(chrome::CanGoBack(browser()));
2361+
}
2362+
2363+
// 3. Closing the opener tab should also disable back-to-opener for the
2364+
// orphaned child tab.
2365+
IN_PROC_BROWSER_TEST_F(DaoBackToOpenerBrowserTest,
2366+
ParentClosedDisablesBack) {
2367+
TabStripModel* tab_strip = browser()->tab_strip_model();
2368+
content::WebContents* opener_contents = tab_strip->GetActiveWebContents();
2369+
const int opener_index = tab_strip->GetIndexOfWebContents(opener_contents);
2370+
2371+
content::WebContents* dest_contents = OpenChildFromOpener();
2372+
ASSERT_NE(nullptr, dest_contents);
2373+
ASSERT_EQ(2, tab_strip->count());
2374+
2375+
// Close the opener.
2376+
{
2377+
content::WebContentsDestroyedWatcher destroyed_watcher(opener_contents);
2378+
tab_strip->CloseWebContentsAt(opener_index,
2379+
TabCloseTypes::CLOSE_USER_GESTURE);
2380+
destroyed_watcher.Wait();
2381+
}
2382+
ASSERT_EQ(1, tab_strip->count());
2383+
ASSERT_EQ(dest_contents, tab_strip->GetActiveWebContents());
2384+
2385+
// With the opener gone and no in-tab history, back must be disabled.
2386+
EXPECT_FALSE(dest_contents->GetController().CanGoBack());
2387+
EXPECT_FALSE(chrome::CanGoBack(browser()));
2388+
}
2389+
2390+
// 4. When a child tab has its own in-tab navigation history, the regular
2391+
// in-tab Back semantics should take precedence: clicking back navigates the
2392+
// child instead of closing it.
2393+
IN_PROC_BROWSER_TEST_F(DaoBackToOpenerBrowserTest,
2394+
InTabHistoryTakesPrecedence) {
2395+
TabStripModel* tab_strip = browser()->tab_strip_model();
2396+
2397+
content::WebContents* dest_contents = OpenChildFromOpener();
2398+
ASSERT_NE(nullptr, dest_contents);
2399+
ASSERT_EQ(2, tab_strip->count());
2400+
const int dest_index = tab_strip->GetIndexOfWebContents(dest_contents);
2401+
2402+
// Drive an in-tab navigation in the child to grow its history.
2403+
GURL second_url = embedded_test_server()->GetURL("/title2.html");
2404+
{
2405+
content::TestNavigationObserver nav_observer(dest_contents);
2406+
ASSERT_TRUE(content::ExecJs(
2407+
dest_contents,
2408+
content::JsReplace("window.location.href = $1;", second_url)));
2409+
nav_observer.Wait();
2410+
}
2411+
EXPECT_TRUE(dest_contents->GetController().CanGoBack());
2412+
EXPECT_TRUE(chrome::CanGoBack(browser()));
2413+
2414+
// chrome::GoBack should navigate the child, not close it.
2415+
{
2416+
content::TestNavigationObserver nav_observer(dest_contents);
2417+
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
2418+
nav_observer.Wait();
2419+
}
2420+
2421+
// Tab is still open and is still the active tab.
2422+
EXPECT_EQ(2, tab_strip->count());
2423+
EXPECT_EQ(dest_index, tab_strip->GetIndexOfWebContents(dest_contents));
2424+
EXPECT_EQ(dest_contents, tab_strip->GetActiveWebContents());
2425+
}
2426+
2427+
// 5. Pinned tabs should not participate in back-to-opener: a pinned child
2428+
// tab with no in-tab history should report CanGoBack == false.
2429+
IN_PROC_BROWSER_TEST_F(DaoBackToOpenerBrowserTest,
2430+
PinnedChildDoesNotGoBack) {
2431+
TabStripModel* tab_strip = browser()->tab_strip_model();
2432+
2433+
content::WebContents* dest_contents = OpenChildFromOpener();
2434+
ASSERT_NE(nullptr, dest_contents);
2435+
ASSERT_EQ(2, tab_strip->count());
2436+
2437+
int dest_index = tab_strip->GetIndexOfWebContents(dest_contents);
2438+
ASSERT_NE(TabStripModel::kNoTab, dest_index);
2439+
2440+
// Pin the destination tab. Pinning may reorder; refresh the index and
2441+
// re-activate it.
2442+
dest_index = tab_strip->SetTabPinned(dest_index, true);
2443+
ASSERT_NE(TabStripModel::kNoTab, dest_index);
2444+
tab_strip->ActivateTabAt(dest_index);
2445+
ASSERT_EQ(dest_contents, tab_strip->GetActiveWebContents());
2446+
ASSERT_TRUE(tab_strip->IsTabPinned(dest_index));
2447+
2448+
// No in-tab history and pinned: chrome::CanGoBack must be false. Clicking
2449+
// back must NOT close the pinned tab.
2450+
EXPECT_FALSE(dest_contents->GetController().CanGoBack());
2451+
EXPECT_FALSE(chrome::CanGoBack(browser()));
2452+
2453+
// Even if invoked anyway, the tab count must stay at 2 (defense in depth).
2454+
const int tab_count_before = tab_strip->count();
2455+
if (chrome::CanGoBack(browser())) {
2456+
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
2457+
base::RunLoop().RunUntilIdle();
2458+
}
2459+
EXPECT_EQ(tab_count_before, tab_strip->count());
2460+
}
2461+
22522462
} // namespace
22532463
} // namespace dao

0 commit comments

Comments
 (0)