diff options
Diffstat (limited to 'chromium/weblayer/browser/persistence')
7 files changed, 315 insertions, 18 deletions
diff --git a/chromium/weblayer/browser/persistence/browser_persistence_common.cc b/chromium/weblayer/browser/persistence/browser_persistence_common.cc index 76e2d4ce630..9ca03a353d2 100644 --- a/chromium/weblayer/browser/persistence/browser_persistence_common.cc +++ b/chromium/weblayer/browser/persistence/browser_persistence_common.cc @@ -9,11 +9,11 @@ #include "components/sessions/core/session_command.h" #include "components/sessions/core/session_service_commands.h" #include "components/sessions/core/session_types.h" -#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_url_handler.h" #include "content/public/browser/dom_storage_context.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/storage_partition.h" +#include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/browser_impl.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_impl.h" @@ -84,6 +84,7 @@ void ProcessRestoreCommands( DCHECK(entries.empty()); TabImpl* tab = browser->CreateTabForSessionRestore(std::move(web_contents), session_tab.guid); + tab->SetData(session_tab.data); if (!had_tabs && i == (windows[0])->selected_tab_index) browser->SetActiveTab(tab); @@ -142,6 +143,8 @@ BuildCommandsForTabConfiguration(const SessionID& browser_session_id, result.push_back(sessions::CreateSetTabGuidCommand(tab_id, tab->GetGuid())); + result.push_back(sessions::CreateSetTabDataCommand(tab_id, tab->GetData())); + return result; } diff --git a/chromium/weblayer/browser/persistence/browser_persister.cc b/chromium/weblayer/browser/persistence/browser_persister.cc index c7fe1a5c325..fac791261ab 100644 --- a/chromium/weblayer/browser/persistence/browser_persister.cc +++ b/chromium/weblayer/browser/persistence/browser_persister.cc @@ -18,13 +18,13 @@ #include "components/sessions/core/session_constants.h" #include "components/sessions/core/session_id.h" #include "components/sessions/core/session_types.h" -#include "content/public/browser/browser_context.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/restore_type.h" #include "content/public/browser/session_storage_namespace.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" +#include "weblayer/browser/browser_context_impl.h" #include "weblayer/browser/browser_impl.h" #include "weblayer/browser/persistence/browser_persistence_common.h" #include "weblayer/browser/profile_impl.h" @@ -105,8 +105,9 @@ void BrowserPersister::OnGeneratedNewCryptoKey( } void BrowserPersister::OnTabAdded(Tab* tab) { - content::WebContents* web_contents = - static_cast<TabImpl*>(tab)->web_contents(); + auto* tab_impl = static_cast<TabImpl*>(tab); + data_observer_.Add(tab_impl); + content::WebContents* web_contents = tab_impl->web_contents(); auto* tab_helper = sessions::SessionTabHelper::FromWebContents(web_contents); DCHECK(tab_helper); tab_helper->SetWindowID(browser_session_id_); @@ -130,10 +131,11 @@ void BrowserPersister::OnTabAdded(Tab* tab) { } void BrowserPersister::OnTabRemoved(Tab* tab, bool active_tab_changed) { + auto* tab_impl = static_cast<TabImpl*>(tab); + data_observer_.Remove(tab_impl); // Allow the associated sessionStorage to get deleted; it won't be needed // in the session restore. - content::WebContents* web_contents = - static_cast<TabImpl*>(tab)->web_contents(); + content::WebContents* web_contents = tab_impl->web_contents(); content::SessionStorageNamespace* session_storage_namespace = web_contents->GetController().GetDefaultSessionStorageNamespace(); session_storage_namespace->SetShouldPersist(false); @@ -161,6 +163,16 @@ void BrowserPersister::OnActiveTabChanged(Tab* tab) { browser_session_id_, index)); } +void BrowserPersister::OnDataChanged( + TabImpl* tab, + const std::map<std::string, std::string>& data) { + if (rebuild_on_next_save_) + return; + + ScheduleCommand( + sessions::CreateSetTabDataCommand(GetSessionIDForTab(tab), data)); +} + void BrowserPersister::SetTabUserAgentOverride( const SessionID& window_id, const SessionID& tab_id, diff --git a/chromium/weblayer/browser/persistence/browser_persister.h b/chromium/weblayer/browser/persistence/browser_persister.h index c157abec93e..a956914ad59 100644 --- a/chromium/weblayer/browser/persistence/browser_persister.h +++ b/chromium/weblayer/browser/persistence/browser_persister.h @@ -14,10 +14,12 @@ #include <vector> #include "base/macros.h" +#include "base/scoped_observer.h" #include "base/task/cancelable_task_tracker.h" #include "components/sessions/content/session_tab_helper_delegate.h" #include "components/sessions/core/command_storage_manager_delegate.h" #include "components/sessions/core/session_service_commands.h" +#include "weblayer/browser/tab_impl.h" #include "weblayer/public/browser_observer.h" class SessionID; @@ -29,7 +31,6 @@ class SessionCommand; namespace weblayer { class BrowserImpl; -class TabImpl; // BrowserPersister is responsible for maintaining the state of tabs in a // single Browser so that they can be restored at a later date. The state is @@ -40,7 +41,8 @@ class TabImpl; // current state. class BrowserPersister : public sessions::CommandStorageManagerDelegate, public sessions::SessionTabHelperDelegate, - public BrowserObserver { + public BrowserObserver, + public TabImpl::DataObserver { public: BrowserPersister(const base::FilePath& path, BrowserImpl* browser, @@ -72,6 +74,10 @@ class BrowserPersister : public sessions::CommandStorageManagerDelegate, void OnTabRemoved(Tab* tab, bool active_tab_changed) override; void OnActiveTabChanged(Tab* tab) override; + // TabImpl::DataObserver: + void OnDataChanged(TabImpl* tab, + const std::map<std::string, std::string>& data) override; + // sessions::SessionTabHelperDelegate: void SetTabUserAgentOverride(const SessionID& window_id, const SessionID& tab_id, @@ -127,6 +133,12 @@ class BrowserPersister : public sessions::CommandStorageManagerDelegate, std::vector<uint8_t> crypto_key_; + ScopedObserver<TabImpl, + TabImpl::DataObserver, + &TabImpl::AddDataObserver, + &TabImpl::RemoveDataObserver> + data_observer_{this}; + base::CancelableTaskTracker cancelable_task_tracker_; }; diff --git a/chromium/weblayer/browser/persistence/browser_persister_browsertest.cc b/chromium/weblayer/browser/persistence/browser_persister_browsertest.cc index 57933cd8cbe..23e30e3217c 100644 --- a/chromium/weblayer/browser/persistence/browser_persister_browsertest.cc +++ b/chromium/weblayer/browser/persistence/browser_persister_browsertest.cc @@ -6,16 +6,21 @@ #include "base/bind_helpers.h" #include "base/files/file_path.h" +#include "base/files/file_util.h" #include "base/guid.h" #include "base/path_service.h" #include "base/run_loop.h" +#include "base/test/bind_test_util.h" +#include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "components/sessions/core/command_storage_manager_test_helper.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/url_loader_interceptor.h" #include "net/base/filename_util.h" #include "net/test/embedded_test_server/embedded_test_server.h" +#include "testing/gmock/include/gmock/gmock.h" #include "weblayer/browser/browser_impl.h" +#include "weblayer/browser/persistence/browser_persister_file_utils.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_impl.h" #include "weblayer/common/weblayer_paths.h" @@ -40,6 +45,7 @@ class BrowserPersisterTestHelper { }; namespace { +using testing::UnorderedElementsAre; class OneShotNavigationObserver : public NavigationObserver { public: @@ -183,7 +189,7 @@ IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, SingleTab) { ASSERT_TRUE(embedded_test_server()->Start()); std::unique_ptr<BrowserImpl> browser = CreateBrowser(GetProfile(), "x"); - Tab* tab = browser->AddTab(Tab::Create(GetProfile())); + Tab* tab = browser->CreateTab(); const GURL url = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url, tab); ShutdownBrowserPersisterAndWait(browser.get()); @@ -208,7 +214,7 @@ IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, RestoresGuid) { ASSERT_TRUE(embedded_test_server()->Start()); std::unique_ptr<BrowserImpl> browser = CreateBrowser(GetProfile(), "x"); - Tab* tab = browser->AddTab(Tab::Create(GetProfile())); + Tab* tab = browser->CreateTab(); const std::string original_guid = tab->GetGuid(); EXPECT_FALSE(original_guid.empty()); EXPECT_TRUE(base::IsValidGUID(original_guid)); @@ -230,15 +236,72 @@ IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, RestoresGuid) { EXPECT_EQ(original_guid, browser->GetTabs()[0]->GetGuid()); } +IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, RestoresData) { + ASSERT_TRUE(embedded_test_server()->Start()); + + std::unique_ptr<BrowserImpl> browser = CreateBrowser(GetProfile(), "x"); + Tab* tab = browser->CreateTab(); + tab->SetData({{"abc", "efg"}}); + const GURL url = embedded_test_server()->GetURL("/simple_page.html"); + NavigateAndWaitForCompletion(url, tab); + ShutdownBrowserPersisterAndWait(browser.get()); + tab = nullptr; + browser.reset(); + + browser = CreateBrowser(GetProfile(), "x"); + // Should be no tabs while waiting for restore. + EXPECT_TRUE(browser->GetTabs().empty()); + // Wait for the restore and navigation to complete. + BrowserNavigationObserverImpl::WaitForNewTabToCompleteNavigation( + browser.get(), url); + + ASSERT_EQ(1u, browser->GetTabs().size()); + EXPECT_EQ(browser->GetTabs()[0], browser->GetActiveTab()); + EXPECT_THAT(browser->GetTabs()[0]->GetData(), + UnorderedElementsAre(std::make_pair("abc", "efg"))); +} + +IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, RestoresMostRecentData) { + ASSERT_TRUE(embedded_test_server()->Start()); + + std::unique_ptr<BrowserImpl> browser = CreateBrowser(GetProfile(), "x"); + Tab* tab = browser->CreateTab(); + tab->SetData({{"xxx", "xxx"}}); + const GURL url = embedded_test_server()->GetURL("/simple_page.html"); + NavigateAndWaitForCompletion(url, tab); + + // Make sure the data has been saved, then set different data on the tab. + BrowserPersisterTestHelper::GetCommandStorageManager( + browser->browser_persister()) + ->Save(); + tab->SetData({{"abc", "efg"}}); + + ShutdownBrowserPersisterAndWait(browser.get()); + tab = nullptr; + browser.reset(); + + browser = CreateBrowser(GetProfile(), "x"); + // Should be no tabs while waiting for restore. + EXPECT_TRUE(browser->GetTabs().empty()); + // Wait for the restore and navigation to complete. + BrowserNavigationObserverImpl::WaitForNewTabToCompleteNavigation( + browser.get(), url); + + ASSERT_EQ(1u, browser->GetTabs().size()); + EXPECT_EQ(browser->GetTabs()[0], browser->GetActiveTab()); + EXPECT_THAT(browser->GetTabs()[0]->GetData(), + UnorderedElementsAre(std::make_pair("abc", "efg"))); +} + IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, TwoTabs) { ASSERT_TRUE(embedded_test_server()->Start()); std::unique_ptr<BrowserImpl> browser = CreateBrowser(GetProfile(), "x"); - Tab* tab1 = browser->AddTab(Tab::Create(GetProfile())); + Tab* tab1 = browser->CreateTab(); const GURL url1 = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url1, tab1); - Tab* tab2 = browser->AddTab(Tab::Create(GetProfile())); + Tab* tab2 = browser->CreateTab(); const GURL url2 = embedded_test_server()->GetURL("/simple_page2.html"); NavigateAndWaitForCompletion(url2, tab2); browser->SetActiveTab(tab2); @@ -282,23 +345,23 @@ IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, MoveBetweenBrowsers) { // Create a browser with two tabs. std::unique_ptr<BrowserImpl> browser1 = CreateBrowser(GetProfile(), "x"); - Tab* tab1 = browser1->AddTab(Tab::Create(GetProfile())); + Tab* tab1 = browser1->CreateTab(); const GURL url1 = embedded_test_server()->GetURL("/simple_page.html"); NavigateAndWaitForCompletion(url1, tab1); - Tab* tab2 = browser1->AddTab(Tab::Create(GetProfile())); + Tab* tab2 = browser1->CreateTab(); const GURL url2 = embedded_test_server()->GetURL("/simple_page2.html"); NavigateAndWaitForCompletion(url2, tab2); browser1->SetActiveTab(tab2); // Create another browser with a single tab. std::unique_ptr<BrowserImpl> browser2 = CreateBrowser(GetProfile(), "y"); - Tab* tab3 = browser2->AddTab(Tab::Create(GetProfile())); + Tab* tab3 = browser2->CreateTab(); const GURL url3 = embedded_test_server()->GetURL("/simple_page3.html"); NavigateAndWaitForCompletion(url3, tab3); // Move |tab2| to |browser2|. - browser2->AddTab(browser1->RemoveTab(tab2)); + browser2->AddTab(tab2); browser2->SetActiveTab(tab2); ShutdownBrowserPersisterAndWait(browser1.get()); @@ -333,4 +396,81 @@ IN_PROC_BROWSER_TEST_F(BrowserPersisterTest, MoveBetweenBrowsers) { content::WaitForLoadStop(restored_tab_3->web_contents()); } +class BrowserPersisterTestWithTwoPersistedIds : public WebLayerBrowserTest { + public: + // WebLayerBrowserTest: + void SetUpOnMainThread() override { + WebLayerBrowserTest::SetUpOnMainThread(); + // Configure two browsers with ids 'x' and 'y'. + ASSERT_TRUE(embedded_test_server()->Start()); + std::unique_ptr<BrowserImpl> browser1 = CreateBrowser(GetProfile(), "x"); + const GURL url1 = embedded_test_server()->GetURL("/simple_page.html"); + NavigateAndWaitForCompletion(url1, browser1->CreateTab()); + + std::unique_ptr<BrowserImpl> browser2 = CreateBrowser(GetProfile(), "y"); + const GURL url2 = embedded_test_server()->GetURL("/simple_page3.html"); + NavigateAndWaitForCompletion(url2, browser2->CreateTab()); + + // Shut down the browsers. + ShutdownBrowserPersisterAndWait(browser1.get()); + browser1.reset(); + ShutdownBrowserPersisterAndWait(browser2.get()); + browser2.reset(); + } +}; + +IN_PROC_BROWSER_TEST_F(BrowserPersisterTestWithTwoPersistedIds, + GetBrowserPersistenceIds) { + { + // Create a file that has the name of a valid persistence file, but has + // invalid contents. + base::ScopedAllowBlockingForTesting allow_blocking; + base::WriteFile(BuildPathForBrowserPersister( + GetProfile()->GetBrowserPersisterDataBaseDir(), "z"), + "a bogus persistence file"); + } + + base::RunLoop run_loop; + base::flat_set<std::string> persistence_ids; + GetProfile()->GetBrowserPersistenceIds( + base::BindLambdaForTesting([&](base::flat_set<std::string> ids) { + persistence_ids = std::move(ids); + run_loop.Quit(); + })); + run_loop.Run(); + ASSERT_EQ(2u, persistence_ids.size()); + EXPECT_TRUE(persistence_ids.contains("x")); + EXPECT_TRUE(persistence_ids.contains("y")); +} + +IN_PROC_BROWSER_TEST_F(BrowserPersisterTestWithTwoPersistedIds, + RemoveBrowserPersistenceStorage) { + base::FilePath file_path1 = BuildPathForBrowserPersister( + GetProfile()->GetBrowserPersisterDataBaseDir(), "x"); + base::FilePath file_path2 = BuildPathForBrowserPersister( + GetProfile()->GetBrowserPersisterDataBaseDir(), "y"); + + { + base::ScopedAllowBlockingForTesting allow_blocking; + ASSERT_TRUE(base::PathExists(file_path1)); + ASSERT_TRUE(base::PathExists(file_path2)); + } + base::RunLoop run_loop; + base::flat_set<std::string> persistence_ids; + persistence_ids.insert("x"); + persistence_ids.insert("y"); + GetProfile()->RemoveBrowserPersistenceStorage( + base::BindLambdaForTesting([&](bool result) { + EXPECT_TRUE(result); + run_loop.Quit(); + }), + std::move(persistence_ids)); + run_loop.Run(); + { + base::ScopedAllowBlockingForTesting allow_blocking; + EXPECT_FALSE(base::PathExists(file_path1)); + EXPECT_FALSE(base::PathExists(file_path2)); + } +} + } // namespace weblayer diff --git a/chromium/weblayer/browser/persistence/browser_persister_file_utils.cc b/chromium/weblayer/browser/persistence/browser_persister_file_utils.cc new file mode 100644 index 00000000000..ead41f214f9 --- /dev/null +++ b/chromium/weblayer/browser/persistence/browser_persister_file_utils.cc @@ -0,0 +1,91 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "weblayer/browser/persistence/browser_persister_file_utils.h" + +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/stl_util.h" +#include "base/task/task_traits.h" +#include "base/task/thread_pool.h" +#include "components/base32/base32.h" +#include "components/sessions/core/command_storage_backend.h" +#include "content/public/browser/browser_thread.h" +#include "weblayer/browser/browser_impl.h" +#include "weblayer/browser/browser_list.h" +#include "weblayer/browser/profile_impl.h" + +namespace weblayer { +namespace { + +bool RemoveBrowserPersistenceStorageOnBackgroundThread( + const base::FilePath& path, + base::flat_set<std::string> ids) { + DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + bool all_succeeded = true; + for (const std::string& id : ids) { + DCHECK(!id.empty()); + base::FilePath persistence_path = BuildPathForBrowserPersister(path, id); + if (!base::DeleteFile(persistence_path, /* recurse */ false)) + all_succeeded = false; + } + return all_succeeded; +} + +} // namespace + +base::flat_set<std::string> GetBrowserPersistenceIdsOnBackgroundThread( + const base::FilePath& path) { + DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + base::flat_set<std::string> ids; + base::FilePath matching_path = base::FilePath().AppendASCII( + std::string(BrowserImpl::kPersistenceFilePrefix) + std::string("*")); + base::FileEnumerator iter(path, /* recursive */ false, + base::FileEnumerator::FILES, matching_path.value()); + for (base::FilePath name = iter.Next(); !name.empty(); name = iter.Next()) { + // The name is base32 encoded, which is ascii. + const std::string base_name = iter.GetInfo().GetName().MaybeAsASCII(); + if (base_name.size() <= base::size(BrowserImpl::kPersistenceFilePrefix)) + continue; + + const std::string encoded_id = + base_name.substr(base::size(BrowserImpl::kPersistenceFilePrefix) - 1); + const std::string decoded_id = base32::Base32Decode(encoded_id); + if (!decoded_id.empty() && + sessions::CommandStorageBackend::IsValidFile(name)) { + ids.insert(decoded_id); + } + } + return ids; +} + +base::FilePath BuildPathForBrowserPersister(const base::FilePath& base_path, + const std::string& browser_id) { + DCHECK(!browser_id.empty()); + const std::string encoded_name = base32::Base32Encode(browser_id); + return base_path.AppendASCII(BrowserImpl::kPersistenceFilePrefix + + encoded_name); +} + +void RemoveBrowserPersistenceStorageImpl( + ProfileImpl* profile, + base::OnceCallback<void(bool)> done_callback, + base::flat_set<std::string> ids) { + // Remove any ids that are actively in use. + for (BrowserImpl* browser : BrowserList::GetInstance()->browsers()) { + if (browser->profile() == profile) + ids.erase(browser->GetPersistenceId()); + } + + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, + {base::MayBlock(), base::TaskPriority::BEST_EFFORT, + base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, + base::BindOnce(&RemoveBrowserPersistenceStorageOnBackgroundThread, + profile->GetBrowserPersisterDataBaseDir(), std::move(ids)), + std::move(done_callback)); +} + +} // namespace weblayer diff --git a/chromium/weblayer/browser/persistence/browser_persister_file_utils.h b/chromium/weblayer/browser/persistence/browser_persister_file_utils.h new file mode 100644 index 00000000000..66cb58addc6 --- /dev/null +++ b/chromium/weblayer/browser/persistence/browser_persister_file_utils.h @@ -0,0 +1,39 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBLAYER_BROWSER_PERSISTENCE_BROWSER_PERSISTER_FILE_UTILS_H_ +#define WEBLAYER_BROWSER_PERSISTENCE_BROWSER_PERSISTER_FILE_UTILS_H_ + +#include <string> + +#include "base/callback_forward.h" +#include "base/containers/flat_set.h" + +namespace base { +class FilePath; +} + +namespace weblayer { + +class ProfileImpl; + +// Returns the set of known persistence ids for the profile at |path|. +base::flat_set<std::string> GetBrowserPersistenceIdsOnBackgroundThread( + const base::FilePath& path); + +// Returns the path to save persistence information. |base_path| is the base +// path of the profile, and |browser_id| the persistence id. +base::FilePath BuildPathForBrowserPersister(const base::FilePath& base_path, + const std::string& browser_id); + +// Implementation of RemoveBrowserPersistenceStorage(). Tries to remove all +// the persistence files for the set of browser persistence ids. +void RemoveBrowserPersistenceStorageImpl( + ProfileImpl* profile, + base::OnceCallback<void(bool)> done_callback, + base::flat_set<std::string> ids); + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_PERSISTENCE_BROWSER_PERSISTER_FILE_UTILS_H_ diff --git a/chromium/weblayer/browser/persistence/minimal_browser_persister_browsertest.cc b/chromium/weblayer/browser/persistence/minimal_browser_persister_browsertest.cc index 093fb4cd4f7..2d9ab733610 100644 --- a/chromium/weblayer/browser/persistence/minimal_browser_persister_browsertest.cc +++ b/chromium/weblayer/browser/persistence/minimal_browser_persister_browsertest.cc @@ -31,7 +31,7 @@ class MinimalBrowserPersisterTest : public WebLayerBrowserTest { WebLayerBrowserTest::SetUpOnMainThread(); ASSERT_TRUE(embedded_test_server()->Start()); browser_ = Browser::Create(GetProfile(), nullptr); - tab_ = static_cast<TabImpl*>(browser_->AddTab(Tab::Create(GetProfile()))); + tab_ = static_cast<TabImpl*>(browser_->CreateTab()); browser_->SetActiveTab(tab_); } void PostRunTestOnMainThread() override { @@ -83,7 +83,7 @@ IN_PROC_BROWSER_TEST_F(MinimalBrowserPersisterTest, SingleTab) { IN_PROC_BROWSER_TEST_F(MinimalBrowserPersisterTest, TwoTabs) { NavigateAndWaitForCompletion(url1(), tab_); - Tab* tab2 = browser_->AddTab(Tab::Create(GetProfile())); + Tab* tab2 = browser_->CreateTab(); NavigateAndWaitForCompletion(url2(), tab2); browser_->SetActiveTab(tab2); |