12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586 |
- // Copyright 2018 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 "components/sync_sessions/session_sync_bridge.h"
- #include <map>
- #include <utility>
- #include <vector>
- #include "base/callback_helpers.h"
- #include "base/run_loop.h"
- #include "base/test/bind.h"
- #include "base/test/mock_callback.h"
- #include "base/test/task_environment.h"
- #include "components/prefs/testing_pref_service.h"
- #include "components/sync/base/client_tag_hash.h"
- #include "components/sync/base/sync_prefs.h"
- #include "components/sync/engine/commit_and_get_updates_types.h"
- #include "components/sync/engine/data_type_activation_response.h"
- #include "components/sync/model/client_tag_based_model_type_processor.h"
- #include "components/sync/model/data_batch.h"
- #include "components/sync/model/data_type_activation_request.h"
- #include "components/sync/model/metadata_batch.h"
- #include "components/sync/model/metadata_change_list.h"
- #include "components/sync/model/model_type_sync_bridge.h"
- #include "components/sync/protocol/entity_metadata.pb.h"
- #include "components/sync/protocol/model_type_state.pb.h"
- #include "components/sync/protocol/session_specifics.pb.h"
- #include "components/sync/protocol/sync_enums.pb.h"
- #include "components/sync/test/mock_model_type_change_processor.h"
- #include "components/sync/test/model_type_store_test_util.h"
- #include "components/sync/test/test_matchers.h"
- #include "components/sync_sessions/mock_sync_sessions_client.h"
- #include "components/sync_sessions/session_sync_prefs.h"
- #include "components/sync_sessions/test_matchers.h"
- #include "components/sync_sessions/test_synced_window_delegates_getter.h"
- #include "testing/gmock/include/gmock/gmock.h"
- #include "testing/gtest/include/gtest/gtest.h"
- namespace sync_sessions {
- namespace {
- using sync_pb::SessionSpecifics;
- using syncer::CommitResponseDataList;
- using syncer::DataBatch;
- using syncer::EntityChangeList;
- using syncer::EntityData;
- using syncer::FailedCommitResponseDataList;
- using syncer::IsEmptyMetadataBatch;
- using syncer::MetadataBatch;
- using syncer::MockModelTypeChangeProcessor;
- using testing::_;
- using testing::AtLeast;
- using testing::ElementsAre;
- using testing::Eq;
- using testing::IsEmpty;
- using testing::IsNull;
- using testing::Not;
- using testing::NotNull;
- using testing::Pair;
- using testing::Pointee;
- using testing::Return;
- using testing::SaveArg;
- using testing::SizeIs;
- using testing::UnorderedElementsAre;
- using testing::WithArg;
- const char kLocalCacheGuid[] = "TestLocalCacheGuid";
- MATCHER_P(EntityDataHasSpecifics, session_specifics_matcher, "") {
- return session_specifics_matcher.MatchAndExplain(arg->specifics.session(),
- result_listener);
- }
- syncer::EntityData SpecificsToEntity(const sync_pb::SessionSpecifics& specifics,
- base::Time mtime = base::Time::Now()) {
- syncer::EntityData data;
- data.client_tag_hash = syncer::ClientTagHash::FromUnhashed(
- syncer::SESSIONS, SessionStore::GetClientTag(specifics));
- *data.specifics.mutable_session() = specifics;
- data.modification_time = mtime;
- return data;
- }
- syncer::UpdateResponseData SpecificsToUpdateResponse(
- const sync_pb::SessionSpecifics& specifics,
- base::Time mtime = base::Time::Now()) {
- syncer::UpdateResponseData data;
- data.entity = SpecificsToEntity(specifics, mtime);
- return data;
- }
- std::map<std::string, std::unique_ptr<EntityData>> BatchToEntityDataMap(
- std::unique_ptr<DataBatch> batch) {
- std::map<std::string, std::unique_ptr<EntityData>> storage_key_to_data;
- while (batch && batch->HasNext()) {
- storage_key_to_data.insert(batch->Next());
- }
- return storage_key_to_data;
- }
- syncer::UpdateResponseData CreateTombstone(const std::string& client_tag) {
- syncer::EntityData tombstone;
- tombstone.client_tag_hash =
- syncer::ClientTagHash::FromUnhashed(syncer::SESSIONS, client_tag);
- syncer::UpdateResponseData data;
- data.entity = std::move(tombstone);
- data.response_version = 2;
- return data;
- }
- syncer::CommitResponseData CreateSuccessResponse(
- const std::string& client_tag) {
- syncer::CommitResponseData response;
- response.client_tag_hash =
- syncer::ClientTagHash::FromUnhashed(syncer::SESSIONS, client_tag);
- response.sequence_number = 1;
- return response;
- }
- sync_pb::SessionSpecifics CreateHeaderSpecificsWithOneTab(
- const std::string& session_tag,
- int window_id,
- int tab_id) {
- sync_pb::SessionSpecifics specifics;
- specifics.set_session_tag(session_tag);
- specifics.mutable_header()->set_client_name("Some client name");
- specifics.mutable_header()->set_device_type(
- sync_pb::SyncEnums_DeviceType_TYPE_LINUX);
- sync_pb::SessionWindow* window = specifics.mutable_header()->add_window();
- window->set_browser_type(sync_pb::SyncEnums_BrowserType_TYPE_TABBED);
- window->set_window_id(window_id);
- window->add_tab(tab_id);
- return specifics;
- }
- sync_pb::SessionSpecifics CreateTabSpecifics(const std::string& session_tag,
- int window_id,
- int tab_id,
- int tab_node_id,
- const std::string& url) {
- sync_pb::SessionSpecifics specifics;
- specifics.set_session_tag(session_tag);
- specifics.set_tab_node_id(tab_node_id);
- specifics.mutable_tab()->add_navigation()->set_virtual_url(url);
- specifics.mutable_tab()->set_window_id(window_id);
- specifics.mutable_tab()->set_tab_id(tab_id);
- return specifics;
- }
- class SessionSyncBridgeTest : public ::testing::Test {
- protected:
- SessionSyncBridgeTest()
- : store_(syncer::ModelTypeStoreTestUtil::CreateInMemoryStoreForTest(
- syncer::SESSIONS)),
- session_sync_prefs_(&pref_service_) {
- SessionSyncPrefs::RegisterProfilePrefs(pref_service_.registry());
- ON_CALL(mock_sync_sessions_client_, GetSessionSyncPrefs())
- .WillByDefault(Return(&session_sync_prefs_));
- ON_CALL(mock_sync_sessions_client_, GetStoreFactory())
- .WillByDefault(
- Return(syncer::ModelTypeStoreTestUtil::FactoryForForwardingStore(
- store_.get())));
- ON_CALL(mock_sync_sessions_client_, GetSyncedWindowDelegatesGetter())
- .WillByDefault(Return(&window_getter_));
- ON_CALL(mock_sync_sessions_client_, GetLocalSessionEventRouter())
- .WillByDefault(Return(window_getter_.router()));
- // Even if we use NiceMock, let's be strict about errors and let tests
- // explicitly list them.
- EXPECT_CALL(mock_processor_, ReportError).Times(0);
- }
- ~SessionSyncBridgeTest() override = default;
- void InitializeBridge() {
- real_processor_ =
- std::make_unique<syncer::ClientTagBasedModelTypeProcessor>(
- syncer::SESSIONS, /*dump_stack=*/base::DoNothing());
- mock_processor_.DelegateCallsByDefaultTo(real_processor_.get());
- // Instantiate the bridge.
- bridge_ = std::make_unique<SessionSyncBridge>(
- mock_foreign_session_updated_cb_.Get(), &mock_sync_sessions_client_,
- mock_processor_.CreateForwardingProcessor());
- }
- void ShutdownBridge() {
- bridge_.reset();
- // The mock is still delegating to |real_processor_|, so we reset it too.
- ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_processor_));
- real_processor_.reset();
- }
- void StartSyncing(const std::vector<SessionSpecifics>& remote_data = {}) {
- syncer::DataTypeActivationRequest request;
- request.error_handler = base::DoNothing();
- request.cache_guid = kLocalCacheGuid;
- request.authenticated_account_id = CoreAccountId("SomeAccountId");
- base::RunLoop loop;
- real_processor_->OnSyncStarting(
- request,
- base::BindLambdaForTesting(
- [&loop](std::unique_ptr<syncer::DataTypeActivationResponse>) {
- loop.Quit();
- }));
- loop.Run();
- sync_pb::ModelTypeState state;
- state.set_initial_sync_done(true);
- state.set_cache_guid(request.cache_guid);
- state.mutable_progress_marker()->set_data_type_id(
- GetSpecificsFieldNumberFromModelType(syncer::SESSIONS));
- state.set_authenticated_account_id("SomeAccountId");
- syncer::UpdateResponseDataList initial_updates;
- for (const SessionSpecifics& specifics : remote_data) {
- initial_updates.push_back(SpecificsToUpdateResponse(specifics));
- }
- real_processor_->OnUpdateReceived(state, std::move(initial_updates));
- }
- std::map<std::string, std::unique_ptr<EntityData>> GetAllData() {
- base::RunLoop loop;
- std::unique_ptr<DataBatch> batch;
- bridge_->GetAllDataForDebugging(base::BindLambdaForTesting(
- [&loop, &batch](std::unique_ptr<DataBatch> input_batch) {
- batch = std::move(input_batch);
- loop.Quit();
- }));
- loop.Run();
- EXPECT_NE(nullptr, batch);
- return BatchToEntityDataMap(std::move(batch));
- }
- std::map<std::string, std::unique_ptr<EntityData>> GetData(
- const std::vector<std::string>& storage_keys) {
- base::RunLoop loop;
- std::unique_ptr<DataBatch> batch;
- bridge_->GetData(
- storage_keys,
- base::BindLambdaForTesting(
- [&loop, &batch](std::unique_ptr<DataBatch> input_batch) {
- batch = std::move(input_batch);
- loop.Quit();
- }));
- loop.Run();
- EXPECT_NE(nullptr, batch);
- return BatchToEntityDataMap(std::move(batch));
- }
- std::unique_ptr<EntityData> GetData(const std::string& storage_key) {
- std::map<std::string, std::unique_ptr<EntityData>> entity_data_map =
- GetData(std::vector<std::string>{storage_key});
- EXPECT_LE(entity_data_map.size(), 1U);
- if (entity_data_map.empty()) {
- return nullptr;
- }
- EXPECT_EQ(storage_key, entity_data_map.begin()->first);
- return std::move(entity_data_map.begin()->second);
- }
- void ResetWindows() { window_getter_.ResetWindows(); }
- TestSyncedWindowDelegate* AddWindow(
- int window_id,
- sync_pb::SyncEnums_BrowserType type =
- sync_pb::SyncEnums_BrowserType_TYPE_TABBED) {
- return window_getter_.AddWindow(type,
- SessionID::FromSerializedValue(window_id));
- }
- TestSyncedTabDelegate* AddTab(int window_id,
- const std::string& url,
- int tab_id = SessionID::NewUnique().id()) {
- TestSyncedTabDelegate* tab =
- window_getter_.AddTab(SessionID::FromSerializedValue(window_id),
- SessionID::FromSerializedValue(tab_id));
- tab->Navigate(url, base::Time::Now());
- return tab;
- }
- void CloseTab(int tab_id) {
- window_getter_.CloseTab(SessionID::FromSerializedValue(tab_id));
- }
- void SessionRestoreComplete() { window_getter_.SessionRestoreComplete(); }
- MockSyncSessionsClient& mock_sync_sessions_client() {
- return mock_sync_sessions_client_;
- }
- base::MockCallback<base::RepeatingClosure>&
- mock_foreign_session_updated_cb() {
- return mock_foreign_session_updated_cb_;
- }
- SessionSyncBridge* bridge() { return bridge_.get(); }
- syncer::MockModelTypeChangeProcessor& mock_processor() {
- return mock_processor_;
- }
- syncer::ClientTagBasedModelTypeProcessor* real_processor() {
- return real_processor_.get();
- }
- syncer::ModelTypeStore* underlying_store() { return store_.get(); }
- private:
- base::test::SingleThreadTaskEnvironment task_environment_;
- const std::unique_ptr<syncer::ModelTypeStore> store_;
- // Dependencies.
- TestingPrefServiceSimple pref_service_;
- SessionSyncPrefs session_sync_prefs_;
- testing::NiceMock<base::MockCallback<base::RepeatingClosure>>
- mock_foreign_session_updated_cb_;
- testing::NiceMock<MockSyncSessionsClient> mock_sync_sessions_client_;
- testing::NiceMock<MockModelTypeChangeProcessor> mock_processor_;
- TestSyncedWindowDelegatesGetter window_getter_;
- std::unique_ptr<SessionSyncBridge> bridge_;
- std::unique_ptr<syncer::ClientTagBasedModelTypeProcessor> real_processor_;
- };
- TEST_F(SessionSyncBridgeTest, ShouldCallModelReadyToSyncWhenSyncEnabled) {
- EXPECT_CALL(mock_processor(), ModelReadyToSync).Times(0);
- InitializeBridge();
- EXPECT_CALL(mock_processor(), ModelReadyToSync(IsEmptyMetadataBatch()));
- StartSyncing();
- }
- // Test that handling of local events (i.e. propagating the local state to
- // sync) does not start while a session restore is in progress.
- TEST_F(SessionSyncBridgeTest, ShouldDeferLocalEventDueToSessionRestore) {
- const int kWindowId = 1000001;
- const int kTabId1 = 1000002;
- const int kTabId2 = 1000003;
- // No notifications expected until OnSessionRestoreComplete().
- EXPECT_CALL(mock_processor(), Put).Times(0);
- AddWindow(kWindowId)->SetIsSessionRestoreInProgress(true);
- // Initial tab should be ignored (not exposed to processor) while session
- // restore is in progress.
- AddTab(kWindowId, "http://foo.com/", kTabId1);
- InitializeBridge();
- StartSyncing();
- EXPECT_THAT(GetAllData(),
- ElementsAre(Pair(
- _, EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid,
- /*window_ids=*/{},
- /*tab_ids=*/{})))));
- // Create the actual tab, which should be ignored because session restore
- // is in progress.
- AddTab(kWindowId, "http://bar.com/", kTabId2);
- EXPECT_THAT(GetAllData(), SizeIs(1));
- // OnSessionRestoreComplete() should issue three Put() calls, one updating the
- // header and one for each of the two added tabs.
- EXPECT_CALL(mock_processor(), Put).Times(3);
- SessionRestoreComplete();
- EXPECT_THAT(GetAllData(), SizeIs(3));
- }
- TEST_F(SessionSyncBridgeTest, ShouldCreateHeaderByDefault) {
- InitializeBridge();
- EXPECT_CALL(mock_processor(), ModelReadyToSync(IsEmptyMetadataBatch()));
- StartSyncing();
- EXPECT_THAT(GetAllData(), SizeIs(1));
- }
- // Tests that local windows and tabs that exist at the time the bridge is
- // started (e.g. after a Chrome restart) are properly exposed via the bridge's
- // GetData() and GetAllData() methods, as well as notified via Put().
- TEST_F(SessionSyncBridgeTest, ShouldExposeInitialLocalTabsToProcessor) {
- const int kWindowId = 1000001;
- const int kTabId1 = 1000002;
- const int kTabId2 = 1000003;
- AddWindow(kWindowId);
- AddTab(kWindowId, "http://foo.com/", kTabId1);
- AddTab(kWindowId, "http://bar.com/", kTabId2);
- InitializeBridge();
- const std::string header_storage_key =
- SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
- const std::string tab_storage_key1 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, 0);
- const std::string tab_storage_key2 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, 1);
- EXPECT_CALL(mock_processor(),
- Put(header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId}, {kTabId1, kTabId2})),
- _));
- EXPECT_CALL(mock_processor(),
- Put(tab_storage_key1,
- EntityDataHasSpecifics(
- MatchesTab(kLocalCacheGuid, kWindowId, kTabId1,
- /*tab_node_id=*/_, {"http://foo.com/"})),
- _));
- EXPECT_CALL(mock_processor(),
- Put(tab_storage_key2,
- EntityDataHasSpecifics(
- MatchesTab(kLocalCacheGuid, kWindowId, kTabId2,
- /*tab_node_id=*/_, {"http://bar.com/"})),
- _));
- StartSyncing();
- EXPECT_THAT(GetData(header_storage_key),
- EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, {kWindowId},
- {kTabId1, kTabId2})));
- EXPECT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId}, {kTabId1, kTabId2}))),
- Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId, kTabId1,
- /*tab_node_id=*/_, {"http://foo.com/"}))),
- Pair(tab_storage_key2,
- EntityDataHasSpecifics(
- MatchesTab(kLocalCacheGuid, kWindowId, kTabId2,
- /*tab_node_id=*/_, {"http://bar.com/"})))));
- }
- // Tests that the creation of a new tab while sync is enabled is propagated to:
- // 1) The processor, via Put().
- // 2) The in-memory representation exposed via GetData().
- // 3) The persisted store, exposed via GetAllData().
- TEST_F(SessionSyncBridgeTest, ShouldReportLocalTabCreation) {
- const int kWindowId = 1000001;
- const int kTabId1 = 1000002;
- const int kTabId2 = 1000003;
- AddWindow(kWindowId);
- AddTab(kWindowId, "http://foo.com/", kTabId1);
- InitializeBridge();
- StartSyncing();
- ASSERT_THAT(GetAllData(), SizeIs(2));
- EXPECT_CALL(mock_foreign_session_updated_cb(), Run()).Times(0);
- // Expectations for the processor.
- std::string header_storage_key;
- std::string tab_storage_key;
- // Tab creation triggers an update event due to the tab parented notification,
- // so the event handler issues two commits as well (one for tab creation, one
- // for tab update). During the first update, however, the tab is not syncable
- // and is hence skipped.
- testing::Expectation put_transient_header = EXPECT_CALL(
- mock_processor(), Put(_,
- EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId}, {kTabId1})),
- _));
- EXPECT_CALL(mock_processor(),
- Put(_,
- EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId}, {kTabId1, kTabId2})),
- _))
- .After(put_transient_header)
- .WillOnce(WithArg<0>(SaveArg<0>(&header_storage_key)));
- EXPECT_CALL(mock_processor(),
- Put(_,
- EntityDataHasSpecifics(
- MatchesTab(kLocalCacheGuid, kWindowId, kTabId2,
- /*tab_node_id=*/_, {"http://bar.com/"})),
- _))
- .WillOnce(WithArg<0>(SaveArg<0>(&tab_storage_key)));
- // Create the actual tab, now that we're syncing.
- AddTab(kWindowId, "http://bar.com/", kTabId2);
- ASSERT_THAT(header_storage_key,
- Eq(SessionStore::GetHeaderStorageKey(kLocalCacheGuid)));
- ASSERT_THAT(tab_storage_key, Not(IsEmpty()));
- // Verify the bridge's state exposed via the getters.
- EXPECT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId}, {kTabId1, kTabId2}))),
- Pair(_, EntityDataHasSpecifics(
- MatchesTab(kLocalCacheGuid, kWindowId, kTabId1,
- /*tab_node_id=*/_, {"http://foo.com/"}))),
- Pair(tab_storage_key, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId, kTabId2,
- /*tab_node_id=*/_, {"http://bar.com/"})))));
- EXPECT_THAT(GetData(header_storage_key),
- EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, {kWindowId},
- {kTabId1, kTabId2})));
- EXPECT_THAT(GetData(tab_storage_key),
- EntityDataHasSpecifics(
- MatchesTab(kLocalCacheGuid, kWindowId, kTabId2,
- /*tab_node_id=*/_, {"http://bar.com/"})));
- }
- TEST_F(SessionSyncBridgeTest, ShouldNotUpdatePlaceholderTabsDuringRestore) {
- const int kWindowId1 = 1000001;
- const int kWindowId2 = 1000002;
- const int kTabId1 = 1000003;
- const int kTabId2 = 1000004;
- // Zero is the first assigned tab node ID.
- const int kTabNodeId1 = 0;
- const int kTabNodeId2 = 1;
- AddWindow(kWindowId1);
- AddTab(kWindowId1, "http://foo.com/", kTabId1);
- AddTab(kWindowId1, "http://bar.com/", kTabId2);
- const std::string header_storage_key =
- SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
- const std::string tab_storage_key1 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId1);
- const std::string tab_storage_key2 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId2);
- InitializeBridge();
- StartSyncing();
- ASSERT_THAT(GetData(header_storage_key),
- EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId1}, {kTabId1, kTabId2})));
- ASSERT_THAT(
- GetData(tab_storage_key1),
- EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, kWindowId1, kTabId1,
- kTabNodeId1, {"http://foo.com/"})));
- ASSERT_THAT(
- GetData(tab_storage_key2),
- EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, kWindowId1, kTabId2,
- kTabNodeId2, {"http://bar.com/"})));
- ShutdownBridge();
- // Override tabs with placeholder tab delegates. Note that, on Android, tab
- // IDs are persisted by session restore across browser restarts.
- PlaceholderTabDelegate placeholder_tab1(
- SessionID::FromSerializedValue(kTabId1));
- PlaceholderTabDelegate placeholder_tab2(
- SessionID::FromSerializedValue(kTabId2));
- ResetWindows();
- TestSyncedWindowDelegate* window = AddWindow(kWindowId2);
- window->OverrideTabAt(0, &placeholder_tab1);
- window->OverrideTabAt(1, &placeholder_tab2);
- // When the bridge gets restarted, we only expect the header to be updated,
- // and placeholder tabs stay unchanged with a stale window ID.
- EXPECT_CALL(mock_processor(),
- Put(header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId2}, {kTabId1, kTabId2})),
- _));
- // Start the bridge again.
- InitializeBridge();
- StartSyncing();
- // Although we haven't notified the processor about the window-ID change, if
- // it hypothetically asked for these entities, the returned entities are
- // up-to-date.
- EXPECT_THAT(GetData(header_storage_key),
- EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId2}, {kTabId1, kTabId2})));
- EXPECT_THAT(
- GetData(tab_storage_key1),
- EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, kWindowId2, kTabId1,
- kTabNodeId1, {"http://foo.com/"})));
- EXPECT_THAT(
- GetData(tab_storage_key2),
- EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, kWindowId2, kTabId2,
- kTabNodeId2, {"http://bar.com/"})));
- EXPECT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId2}, {kTabId1, kTabId2}))),
- Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId2, kTabId1,
- kTabNodeId1, {"http://foo.com/"}))),
- Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId2, kTabId2,
- kTabNodeId2, {"http://bar.com/"})))));
- }
- TEST_F(SessionSyncBridgeTest,
- ShouldIgnoreUnsyncablePlaceholderTabDuringRestore) {
- const int kWindowId1 = 1000001;
- const int kWindowId2 = 1000002;
- const int kTabId1 = 1000002;
- const int kTabId2 = 1000003;
- // Zero is the first assigned tab node ID.
- const int kTabNodeId1 = 0;
- AddWindow(kWindowId1);
- AddTab(kWindowId1, "http://foo.com/", kTabId1);
- // Tab 2 is unsyncable because of the URL scheme.
- AddTab(kWindowId1, "about:blank", kTabId2);
- const std::string header_storage_key =
- SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
- const std::string tab_storage_key1 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId1);
- InitializeBridge();
- StartSyncing();
- ASSERT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key,
- EntityDataHasSpecifics(
- MatchesHeader(kLocalCacheGuid, {kWindowId1}, {kTabId1}))),
- Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId1, kTabId1,
- kTabNodeId1, {"http://foo.com/"})))));
- ShutdownBridge();
- // Override tabs with placeholder tab delegates. Note that, on Android, tab
- // IDs are persisted by session restore across browser restarts.
- PlaceholderTabDelegate placeholder_tab1(
- SessionID::FromSerializedValue(kTabId1));
- PlaceholderTabDelegate placeholder_tab2(
- SessionID::FromSerializedValue(kTabId2));
- ResetWindows();
- TestSyncedWindowDelegate* window = AddWindow(kWindowId2);
- window->OverrideTabAt(0, &placeholder_tab1);
- window->OverrideTabAt(1, &placeholder_tab2);
- // Start the bridge again.
- InitializeBridge();
- StartSyncing();
- EXPECT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key,
- EntityDataHasSpecifics(
- MatchesHeader(kLocalCacheGuid, {kWindowId2}, {kTabId1}))),
- Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId2, kTabId1,
- kTabNodeId1, {"http://foo.com/"})))));
- }
- // Ensure that tabbed windows from a previous session are preserved if no
- // windows are present on startup.
- TEST_F(SessionSyncBridgeTest, ShouldRestoreTabbedDataIfNoWindowsDuringStartup) {
- const int kWindowId1 = 1000001;
- const int kWindowId2 = 1000002;
- const int kTabNodeId = 0;
- AddWindow(kWindowId1);
- TestSyncedTabDelegate* tab = AddTab(kWindowId1, "http://foo.com/");
- const std::string header_storage_key =
- SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
- const std::string tab_storage_key =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId);
- InitializeBridge();
- StartSyncing();
- ASSERT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _))),
- Pair(tab_storage_key,
- EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, _, _, kTabNodeId, {"http://foo.com/"})))));
- ShutdownBridge();
- // Start the bridge with no local windows/tabs.
- ResetWindows();
- InitializeBridge();
- StartSyncing();
- EXPECT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _))),
- Pair(tab_storage_key,
- EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, _, _, kTabNodeId, {"http://foo.com/"})))));
- // Now actually resurrect the native data, which will end up having different
- // native ids, but the tab has the same sync id as before.
- EXPECT_CALL(
- mock_processor(),
- Put(header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _)), _));
- EXPECT_CALL(mock_processor(),
- Put(tab_storage_key,
- EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, /*window_id=*/_, /*tab_id=*/_,
- kTabNodeId, {"http://foo.com/", "http://bar.com/"})),
- _));
- AddWindow(kWindowId2)->OverrideTabAt(0, tab);
- tab->Navigate("http://bar.com/");
- }
- // Ensure that tabbed windows from a previous session are preserved if only
- // a custom tab is present at startup.
- TEST_F(SessionSyncBridgeTest, ShouldPreserveTabbedDataIfCustomTabOnlyFound) {
- const int kWindowId1 = 1000001;
- const int kWindowId2 = 1000002;
- AddWindow(kWindowId1);
- AddTab(kWindowId1, "http://foo.com/");
- InitializeBridge();
- StartSyncing();
- ASSERT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(_, EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _))),
- Pair(_, EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, _, _,
- /*tab_node_id=*/0,
- {"http://foo.com/"})))));
- ShutdownBridge();
- // Start the bridge with only a custom tab open.
- ResetWindows();
- AddWindow(kWindowId2, sync_pb::SyncEnums_BrowserType_TYPE_CUSTOM_TAB);
- AddTab(kWindowId2, "http://bar.com/");
- InitializeBridge();
- StartSyncing();
- // The previous session should be preserved, together with the new custom tab.
- EXPECT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(_, EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _))),
- Pair(_, EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, _, _,
- /*tab_node_id=*/0,
- {"http://foo.com/"}))),
- Pair(_, EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, _, _,
- /*tab_node_id=*/1,
- {"http://bar.com/"})))));
- }
- // Ensure that tabbed windows from a previous session are preserved and combined
- // with a custom tab that was newly found during startup.
- TEST_F(SessionSyncBridgeTest, ShouldPreserveTabbedDataIfNewCustomTabAlsoFound) {
- const int kWindowId1 = 1000001;
- const int kWindowId2 = 1000002;
- const int kTabId1 = 1000003;
- const int kTabId2 = 1000004;
- AddWindow(kWindowId1);
- AddTab(kWindowId1, "http://foo.com/", kTabId1);
- InitializeBridge();
- StartSyncing();
- ASSERT_THAT(GetAllData(),
- UnorderedElementsAre(
- Pair(_, EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId1}, {kTabId1}))),
- Pair(_, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId1, kTabId1,
- /*tab_node_id=*/0, {"http://foo.com/"})))));
- ShutdownBridge();
- // Start the bridge with an additional local custom tab.
- AddWindow(kWindowId2, sync_pb::SyncEnums_BrowserType_TYPE_CUSTOM_TAB);
- AddTab(kWindowId2, "http://bar.com/", kTabId2);
- InitializeBridge();
- StartSyncing();
- EXPECT_THAT(GetAllData(),
- UnorderedElementsAre(
- Pair(_, EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId1, kWindowId2},
- {kTabId1, kTabId2}))),
- Pair(_, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId1, kTabId1,
- /*tab_node_id=*/0, {"http://foo.com/"}))),
- Pair(_, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId2, kTabId2,
- /*tab_node_id=*/1, {"http://bar.com/"})))));
- }
- // Ensure that, in a scenario without prior sync data, encountering a custom
- // tab only (no tabbed window) starts syncing that tab.
- TEST_F(SessionSyncBridgeTest, ShouldAssociateIfCustomTabOnlyOnStartup) {
- const int kWindowId = 1000001;
- const int kTabId = 1000002;
- AddWindow(kWindowId, sync_pb::SyncEnums_BrowserType_TYPE_CUSTOM_TAB);
- AddTab(kWindowId, "http://foo.com/", kTabId);
- InitializeBridge();
- StartSyncing();
- EXPECT_THAT(GetAllData(),
- UnorderedElementsAre(
- Pair(_, EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId}, {kTabId}))),
- Pair(_, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId, kTabId,
- /*tab_node_id=*/0, {"http://foo.com/"})))));
- }
- // Ensure that all tabs are exposed in a scenario where only a custom tab
- // (without tabbed windows) was present during startup, and later tabbed windows
- // appear (browser started).
- TEST_F(SessionSyncBridgeTest, ShouldExposeTabbedWindowAfterCustomTabOnly) {
- const int kWindowId1 = 1000001;
- const int kWindowId2 = 1000002;
- const int kTabId1 = 1000003;
- const int kTabId2 = 1000004;
- AddWindow(kWindowId1, sync_pb::SyncEnums_BrowserType_TYPE_CUSTOM_TAB);
- AddTab(kWindowId1, "http://foo.com/", kTabId1);
- InitializeBridge();
- StartSyncing();
- ASSERT_THAT(GetAllData(),
- UnorderedElementsAre(
- Pair(_, EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId1}, {kTabId1}))),
- Pair(_, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId1, kTabId1,
- /*tab_node_id=*/0, {"http://foo.com/"})))));
- // Load the actual tabbed window, now that we're syncing.
- AddWindow(kWindowId2);
- AddTab(kWindowId2, "http://bar.com/", kTabId2);
- // The local change should be created and tracked correctly.
- EXPECT_THAT(GetAllData(),
- UnorderedElementsAre(
- Pair(_, EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId1, kWindowId2},
- {kTabId1, kTabId2}))),
- Pair(_, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId1, kTabId1,
- /*tab_node_id=*/0, {"http://foo.com/"}))),
- Pair(_, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId2, kTabId2,
- /*tab_node_id=*/1, {"http://bar.com/"})))));
- }
- TEST_F(SessionSyncBridgeTest, ShouldRecycleTabNodeAfterCommitCompleted) {
- const int kWindowId = 1000001;
- const int kTabId1 = 1000003;
- const int kTabId2 = 1000004;
- const int kTabId3 = 1000005;
- const int kTabId4 = 1000006;
- // Zero is the first assigned tab node ID.
- const int kTabNodeId1 = 0;
- const int kTabNodeId2 = 1;
- const int kTabNodeId3 = 2;
- AddWindow(kWindowId);
- TestSyncedTabDelegate* tab1 = AddTab(kWindowId, "http://foo.com/", kTabId1);
- const std::string header_storage_key =
- SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
- const std::string tab_storage_key1 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId1);
- const std::string tab_storage_key2 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId2);
- const std::string tab_storage_key3 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId3);
- const std::string tab_client_tag1 =
- SessionStore::GetTabClientTagForTest(kLocalCacheGuid, kTabNodeId1);
- const std::string tab_client_tag2 =
- SessionStore::GetTabClientTagForTest(kLocalCacheGuid, kTabNodeId2);
- const std::string tab_client_tag3 =
- SessionStore::GetTabClientTagForTest(kLocalCacheGuid, kTabNodeId3);
- InitializeBridge();
- StartSyncing();
- // Mimic a commit completing for the initial sync.
- ASSERT_TRUE(real_processor()->HasLocalChangesForTest());
- sync_pb::ModelTypeState state;
- state.set_initial_sync_done(true);
- real_processor()->OnCommitCompleted(
- state,
- {CreateSuccessResponse(kLocalCacheGuid),
- CreateSuccessResponse(tab_client_tag1)},
- /*error_response_list=*/FailedCommitResponseDataList());
- ASSERT_FALSE(real_processor()->HasLocalChangesForTest());
- // Open a second tab.
- AddTab(kWindowId, "http://bar.com/", kTabId2);
- ASSERT_TRUE(real_processor()->HasLocalChangesForTest());
- // Close |kTabId2| and force reassociation by navigating in the remaining open
- // tab, leading to a freed tab entity. However, while there are pending
- // changes to commit, the entity shouldn't be deleted (to prevent history
- // loss).
- EXPECT_CALL(mock_processor(), Delete).Times(0);
- CloseTab(kTabId2);
- tab1->Navigate("http://foo2.com/");
- EXPECT_TRUE(real_processor()->HasLocalChangesForTest());
- EXPECT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key,
- EntityDataHasSpecifics(
- MatchesHeader(kLocalCacheGuid, {kWindowId}, {kTabId1}))),
- Pair(tab_storage_key1,
- EntityDataHasSpecifics(
- MatchesTab(kLocalCacheGuid, kWindowId, kTabId1, kTabNodeId1,
- {"http://foo.com/", "http://foo2.com/"}))),
- Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId, kTabId2,
- kTabNodeId2, {"http://bar.com/"})))));
- // If a new tab is opened, the entity with unsynced changes should not be
- // recycled.
- AddTab(kWindowId, "http://baz.com/", kTabId3);
- EXPECT_THAT(GetAllData(), UnorderedElementsAre(Pair(header_storage_key, _),
- Pair(tab_storage_key1, _),
- Pair(tab_storage_key2, _),
- Pair(tab_storage_key3, _)));
- // Completing the commit for the previously closed tab should issue a
- // deletion. For that to trigger, we need to trigger the next association,
- // which we do by navigating in one of the open tabs.
- EXPECT_CALL(mock_processor(), Delete(tab_storage_key2, _));
- real_processor()->OnCommitCompleted(
- state, {CreateSuccessResponse(tab_client_tag2)},
- /*error_response_list=*/FailedCommitResponseDataList());
- tab1->Navigate("http://foo3.com/");
- EXPECT_THAT(GetAllData(), UnorderedElementsAre(Pair(header_storage_key, _),
- Pair(tab_storage_key1, _),
- Pair(tab_storage_key3, _)));
- // If yet anothertab is opened, the entity for the closed tab should be
- // recycled.
- AddTab(kWindowId, "http://qux.com/", kTabId4);
- EXPECT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key, _), Pair(tab_storage_key1, _),
- Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId, kTabId4,
- kTabNodeId2, {"http://qux.com/"}))),
- Pair(tab_storage_key3, _)));
- }
- TEST_F(SessionSyncBridgeTest, ShouldRestoreLocalSessionWithFreedTab) {
- const int kWindowId1 = 1000001;
- const int kWindowId2 = 1000002;
- const int kTabId1 = 1000003;
- const int kTabId2 = 1000004;
- const int kTabId3 = 1000005;
- // Zero is the first assigned tab node ID.
- const int kTabNodeId1 = 0;
- const int kTabNodeId2 = 1;
- const int kTabNodeId3 = 2;
- AddWindow(kWindowId1);
- TestSyncedTabDelegate* tab1 = AddTab(kWindowId1, "http://foo.com/", kTabId1);
- AddTab(kWindowId1, "http://bar.com/", kTabId2);
- const std::string header_storage_key =
- SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
- const std::string tab_storage_key1 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId1);
- const std::string tab_storage_key2 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId2);
- const std::string tab_storage_key3 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId3);
- InitializeBridge();
- StartSyncing();
- ASSERT_THAT(GetData(header_storage_key),
- EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, {kWindowId1}, {kTabId1, kTabId2})));
- // Close |kTabId2| and force reassociation by navigating in the remaining open
- // tab, leading to a freed tab entity.
- CloseTab(kTabId2);
- tab1->Navigate("http://foo2.com/");
- ASSERT_THAT(GetData(header_storage_key),
- EntityDataHasSpecifics(
- MatchesHeader(kLocalCacheGuid, {kWindowId1}, {kTabId1})));
- ShutdownBridge();
- ResetWindows();
- // The browser gets restarted with a new initial tab, for example because the
- // user chose "Continue where you left off".
- AddWindow(kWindowId2);
- AddTab(kWindowId2, "http://baz.com/", kTabId3);
- // Start the bridge again.
- InitializeBridge();
- StartSyncing();
- // Two tab nodes should be free at this point, because both tabs have been
- // closed. However, they are also unsynced (the commit hasn't completed),
- // which prevents their recycling, so a new tab node should be created.
- ASSERT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key,
- EntityDataHasSpecifics(
- MatchesHeader(kLocalCacheGuid, {kWindowId2}, {kTabId3}))),
- Pair(tab_storage_key1,
- EntityDataHasSpecifics(
- MatchesTab(kLocalCacheGuid, kWindowId1, kTabId1, kTabNodeId1,
- {"http://foo.com/", "http://foo2.com/"}))),
- Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId1, kTabId2,
- kTabNodeId2, {"http://bar.com/"}))),
- Pair(tab_storage_key3, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId2, kTabId3,
- kTabNodeId3, {"http://baz.com/"})))));
- }
- TEST_F(SessionSyncBridgeTest, ShouldDisableSyncAndReenable) {
- const int kWindowId = 1000001;
- const int kTabId = 1000002;
- AddWindow(kWindowId);
- AddTab(kWindowId, "http://foo.com/", kTabId);
- InitializeBridge();
- StartSyncing();
- const std::string header_storage_key =
- SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
- ASSERT_THAT(GetData(header_storage_key),
- EntityDataHasSpecifics(
- MatchesHeader(kLocalCacheGuid, {kWindowId}, {kTabId})));
- ASSERT_THAT(GetAllData(), Not(IsEmpty()));
- EXPECT_CALL(mock_processor(), ModelReadyToSync).Times(0);
- real_processor()->OnSyncStopping(syncer::CLEAR_METADATA);
- StartSyncing();
- ASSERT_THAT(GetData(header_storage_key),
- EntityDataHasSpecifics(
- MatchesHeader(kLocalCacheGuid, {kWindowId}, {kTabId})));
- }
- // Starting sync with no local data should just store the foreign entities in
- // the store and expose them via OpenTabsUIDelegate.
- TEST_F(SessionSyncBridgeTest, ShouldMergeForeignSession) {
- const std::string kForeignSessionTag = "foreignsessiontag";
- const int kForeignWindowId = 2000001;
- const int kForeignTabId = 2000002;
- const int kForeignTabNodeId = 2003;
- EXPECT_CALL(mock_processor(), UpdateStorageKey).Times(0);
- EXPECT_CALL(mock_processor(), Put).Times(0);
- InitializeBridge();
- const sync_pb::SessionSpecifics foreign_header =
- CreateHeaderSpecificsWithOneTab(kForeignSessionTag, kForeignWindowId,
- kForeignTabId);
- const sync_pb::SessionSpecifics foreign_tab =
- CreateTabSpecifics(kForeignSessionTag, kForeignWindowId, kForeignTabId,
- kForeignTabNodeId, "http://baz.com/");
- EXPECT_CALL(
- mock_processor(),
- Put(_, EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _)), _));
- EXPECT_CALL(mock_foreign_session_updated_cb(), Run());
- StartSyncing({foreign_header, foreign_tab});
- std::vector<const SyncedSession*> foreign_sessions;
- EXPECT_TRUE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
- &foreign_sessions));
- EXPECT_THAT(foreign_sessions,
- ElementsAre(MatchesSyncedSession(
- kForeignSessionTag,
- {{kForeignWindowId, std::vector<int>{kForeignTabId}}})));
- }
- TEST_F(SessionSyncBridgeTest, ShouldNotExposeForeignHeaderWithoutTabs) {
- const std::string kForeignSessionTag = "foreignsessiontag";
- const int kForeignWindowId = 2000001;
- const int kForeignTabId = 2000002;
- EXPECT_CALL(mock_processor(), UpdateStorageKey).Times(0);
- EXPECT_CALL(mock_processor(), Put).Times(0);
- InitializeBridge();
- const sync_pb::SessionSpecifics foreign_header =
- CreateHeaderSpecificsWithOneTab(kForeignSessionTag, kForeignWindowId,
- kForeignTabId);
- const std::string foreign_header_storage_key =
- SessionStore::GetHeaderStorageKey(kForeignSessionTag);
- EXPECT_CALL(
- mock_processor(),
- Put(_, EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _)), _));
- StartSyncing({foreign_header});
- ASSERT_THAT(GetData(foreign_header_storage_key), NotNull());
- std::vector<const SyncedSession*> foreign_sessions;
- EXPECT_FALSE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
- &foreign_sessions));
- // Restart bridge to verify the state doesn't change.
- ShutdownBridge();
- InitializeBridge();
- StartSyncing();
- ASSERT_THAT(GetData(foreign_header_storage_key), NotNull());
- EXPECT_FALSE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
- &foreign_sessions));
- }
- // Regression test for crbug.com/837517: Ensure that the bridge doesn't crash
- // and closed foreign tabs (|kForeignTabId2| in the test) are not exposed after
- // restarting the browser.
- TEST_F(SessionSyncBridgeTest, ShouldNotExposeClosedTabsAfterRestart) {
- const std::string kForeignSessionTag = "foreignsessiontag";
- const int kForeignWindowId = 2000001;
- const int kForeignTabId1 = 2000002;
- const int kForeignTabId2 = 2000003;
- const int kForeignTabNodeId1 = 2004;
- const int kForeignTabNodeId2 = 2005;
- // The header only lists a single tab |kForeignTabId1|, which becomes a mapped
- // tab.
- const sync_pb::SessionSpecifics foreign_header =
- CreateHeaderSpecificsWithOneTab(kForeignSessionTag, kForeignWindowId,
- kForeignTabId1);
- const sync_pb::SessionSpecifics foreign_tab1 =
- CreateTabSpecifics(kForeignSessionTag, kForeignWindowId, kForeignTabId1,
- kForeignTabNodeId1, "http://foo.com/");
- // |kForeignTabId2| is not present in the header, leading to an unmapped tab.
- const sync_pb::SessionSpecifics foreign_tab2 =
- CreateTabSpecifics(kForeignSessionTag, kForeignWindowId, kForeignTabId2,
- kForeignTabNodeId2, "http://bar.com/");
- InitializeBridge();
- StartSyncing({foreign_header, foreign_tab1, foreign_tab2});
- const std::string local_header_storage_key =
- SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
- const std::string foreign_header_storage_key =
- SessionStore::GetHeaderStorageKey(kForeignSessionTag);
- const std::string foreign_tab_storage_key1 =
- SessionStore::GetTabStorageKey(kForeignSessionTag, kForeignTabNodeId1);
- const std::string foreign_tab_storage_key2 =
- SessionStore::GetTabStorageKey(kForeignSessionTag, kForeignTabNodeId2);
- ASSERT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(local_header_storage_key, _),
- Pair(foreign_header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(
- kForeignSessionTag, {kForeignWindowId}, {kForeignTabId1}))),
- Pair(foreign_tab_storage_key1,
- EntityDataHasSpecifics(MatchesTab(
- kForeignSessionTag, kForeignWindowId, kForeignTabId1,
- kForeignTabNodeId1, {"http://foo.com/"}))),
- Pair(foreign_tab_storage_key2,
- EntityDataHasSpecifics(MatchesTab(
- kForeignSessionTag, kForeignWindowId, kForeignTabId2,
- kForeignTabNodeId2, {"http://bar.com/"})))));
- // Mimic a browser restart, which should restore the very same state (and not
- // crash!).
- ShutdownBridge();
- InitializeBridge();
- StartSyncing();
- EXPECT_THAT(GetAllData(),
- UnorderedElementsAre(Pair(local_header_storage_key, _),
- Pair(foreign_header_storage_key, _),
- Pair(foreign_tab_storage_key1, _),
- Pair(foreign_tab_storage_key2, _)));
- }
- TEST_F(SessionSyncBridgeTest, ShouldHandleRemoteDeletion) {
- const std::string kForeignSessionTag = "foreignsessiontag";
- const int kForeignWindowId = 2000001;
- const int kForeignTabId = 2000002;
- const int kForeignTabNodeId = 2003;
- InitializeBridge();
- const sync_pb::SessionSpecifics foreign_header =
- CreateHeaderSpecificsWithOneTab(kForeignSessionTag, kForeignWindowId,
- kForeignTabId);
- const sync_pb::SessionSpecifics foreign_tab =
- CreateTabSpecifics(kForeignSessionTag, kForeignWindowId, kForeignTabId,
- kForeignTabNodeId, "http://baz.com/");
- StartSyncing({foreign_header, foreign_tab});
- sync_pb::ModelTypeState state;
- state.set_initial_sync_done(true);
- // Mimic receiving a commit ack for the local header entity, to later be able
- // to verify HasLocalChangesForTest() without interferences from the local
- // session.
- ASSERT_TRUE(real_processor()->HasLocalChangesForTest());
- real_processor()->OnCommitCompleted(
- state, {CreateSuccessResponse(kLocalCacheGuid)},
- /*error_response_list=*/FailedCommitResponseDataList());
- ASSERT_FALSE(real_processor()->HasLocalChangesForTest());
- const sessions::SessionTab* foreign_session_tab = nullptr;
- ASSERT_TRUE(bridge()->GetOpenTabsUIDelegate()->GetForeignTab(
- kForeignSessionTag, SessionID::FromSerializedValue(kForeignTabId),
- &foreign_session_tab));
- ASSERT_THAT(foreign_session_tab, NotNull());
- std::vector<const SyncedSession*> foreign_sessions;
- ASSERT_TRUE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
- &foreign_sessions));
- ASSERT_THAT(foreign_sessions,
- ElementsAre(MatchesSyncedSession(
- kForeignSessionTag,
- {{kForeignWindowId, std::vector<int>{kForeignTabId}}})));
- ASSERT_TRUE(real_processor()->IsTrackingMetadata());
- ASSERT_TRUE(real_processor()->IsTrackingEntityForTest(
- SessionStore::GetHeaderStorageKey(kForeignSessionTag)));
- ASSERT_TRUE(real_processor()->IsTrackingEntityForTest(
- SessionStore::GetTabStorageKey(kForeignSessionTag, kForeignTabNodeId)));
- ASSERT_FALSE(real_processor()->HasLocalChangesForTest());
- // Mimic receiving a remote deletion of the foreign session.
- EXPECT_CALL(mock_foreign_session_updated_cb(), Run());
- syncer::UpdateResponseDataList updates;
- updates.push_back(
- CreateTombstone(SessionStore::GetClientTag(foreign_header)));
- real_processor()->OnUpdateReceived(state, std::move(updates));
- foreign_session_tab = nullptr;
- EXPECT_FALSE(bridge()->GetOpenTabsUIDelegate()->GetForeignTab(
- kForeignSessionTag, SessionID::FromSerializedValue(kForeignTabId),
- &foreign_session_tab));
- EXPECT_FALSE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
- &foreign_sessions));
- EXPECT_FALSE(real_processor()->HasLocalChangesForTest());
- const std::string header_storage_key =
- SessionStore::GetHeaderStorageKey(kForeignSessionTag);
- const std::string tab_storage_key =
- SessionStore::GetTabStorageKey(kForeignSessionTag, kForeignTabNodeId);
- EXPECT_FALSE(real_processor()->IsTrackingEntityForTest(header_storage_key));
- EXPECT_FALSE(real_processor()->IsTrackingEntityForTest(tab_storage_key));
- // Verify that both entities have been deleted from storage.
- {
- base::RunLoop loop;
- underlying_store()->ReadData(
- {header_storage_key, tab_storage_key},
- base::BindLambdaForTesting(
- [&](const absl::optional<syncer::ModelError>& error,
- std::unique_ptr<syncer::ModelTypeStore::RecordList>
- data_records,
- std::unique_ptr<syncer::ModelTypeStore::IdList>
- missing_id_list) {
- EXPECT_THAT(data_records, Pointee(IsEmpty()));
- EXPECT_THAT(
- missing_id_list,
- Pointee(ElementsAre(header_storage_key, tab_storage_key)));
- loop.Quit();
- }));
- loop.Run();
- }
- // Verify that the sync metadata for both entities have been deleted too.
- {
- base::RunLoop loop;
- underlying_store()->ReadAllMetadata(base::BindLambdaForTesting(
- [&](const absl::optional<syncer::ModelError>& error,
- std::unique_ptr<syncer::MetadataBatch> metadata_batch) {
- syncer::EntityMetadataMap entity_metadata_map =
- metadata_batch->TakeAllMetadata();
- EXPECT_EQ(0U, entity_metadata_map.count(header_storage_key));
- EXPECT_EQ(0U, entity_metadata_map.count(tab_storage_key));
- loop.Quit();
- }));
- loop.Run();
- }
- }
- TEST_F(SessionSyncBridgeTest, ShouldIgnoreRemoteDeletionOfLocalTab) {
- const int kWindowId1 = 1000001;
- const int kTabId1 = 1000002;
- const int kTabNodeId1 = 0;
- AddWindow(kWindowId1);
- AddTab(kWindowId1, "http://foo.com/", kTabId1);
- InitializeBridge();
- StartSyncing();
- const std::string header_storage_key =
- SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
- const std::string tab_storage_key1 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId1);
- const std::string tab_client_tag1 =
- SessionStore::GetTabClientTagForTest(kLocalCacheGuid, kTabNodeId1);
- ASSERT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _))),
- Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId1, kTabId1,
- kTabNodeId1, {"http://foo.com/"})))));
- ASSERT_TRUE(real_processor()->IsTrackingMetadata());
- ASSERT_TRUE(real_processor()->HasLocalChangesForTest());
- // Mimic receiving a commit ack for both the tab and the header entity,
- // because otherwise it will be treated as conflict, and then local wins.
- sync_pb::ModelTypeState state;
- state.set_initial_sync_done(true);
- real_processor()->OnCommitCompleted(
- state,
- {CreateSuccessResponse(tab_client_tag1),
- CreateSuccessResponse(kLocalCacheGuid)},
- /*error_response_list=*/FailedCommitResponseDataList());
- ASSERT_FALSE(real_processor()->HasLocalChangesForTest());
- // Mimic receiving a remote deletion of both entities.
- EXPECT_CALL(mock_processor(), Put).Times(0);
- syncer::UpdateResponseDataList updates;
- updates.push_back(CreateTombstone(kLocalCacheGuid));
- updates.push_back(CreateTombstone(tab_client_tag1));
- real_processor()->OnUpdateReceived(state, std::move(updates));
- // State should remain unchanged (deletions ignored).
- EXPECT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _))),
- Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId1, kTabId1,
- kTabNodeId1, {"http://foo.com/"})))));
- // Creating a new tab locally should trigger Put() calls for *all* entities
- // (because the local data was out of sync).
- const int kWindowId2 = 2000001;
- const int kTabId2 = 2000002;
- const int kTabNodeId2 = 1;
- const std::string tab_storage_key2 =
- SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId2);
- // Window creation already triggers a header update, which will be overriden
- // later below.
- testing::Expectation put_transient_header =
- EXPECT_CALL(mock_processor(), Put(header_storage_key, _, _));
- AddWindow(kWindowId2);
- // In the current implementation, some of the updates are reported to the
- // processor twice, but that's OK because the processor can detect it.
- EXPECT_CALL(mock_processor(),
- Put(header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, ElementsAre(kWindowId1, kWindowId2),
- ElementsAre(kTabId1, kTabId2))),
- _))
- .Times(2)
- .After(put_transient_header);
- EXPECT_CALL(mock_processor(), Put(tab_storage_key1,
- EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId1, kTabId1,
- kTabNodeId1, {"http://foo.com/"})),
- _));
- EXPECT_CALL(mock_processor(), Put(tab_storage_key2,
- EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId2, kTabId2,
- kTabNodeId2, {"http://bar.com/"})),
- _))
- .Times(2);
- AddTab(kWindowId2, "http://bar.com/", kTabId2);
- EXPECT_THAT(
- GetAllData(),
- UnorderedElementsAre(
- Pair(header_storage_key,
- EntityDataHasSpecifics(MatchesHeader(
- kLocalCacheGuid, ElementsAre(kWindowId1, kWindowId2),
- ElementsAre(kTabId1, kTabId2)))),
- Pair(tab_storage_key1,
- EntityDataHasSpecifics(
- MatchesTab(kLocalCacheGuid, /*window_id=*/_, /*tab_id=*/_,
- kTabNodeId1, {"http://foo.com/"}))),
- Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
- kLocalCacheGuid, kWindowId2, kTabId2,
- kTabNodeId2, {"http://bar.com/"})))));
- // Run until idle because PostTask() is used to invoke ResubmitLocalSession().
- base::RunLoop().RunUntilIdle();
- }
- // Verifies that a foreign session can be deleted by the user from the history
- // UI (via OpenTabsUIDelegate).
- TEST_F(SessionSyncBridgeTest, ShouldDeleteForeignSessionFromUI) {
- const std::string kForeignSessionTag = "foreignsessiontag";
- const int kForeignWindowId = 2000001;
- const int kForeignTabId = 2000002;
- const int kForeignTabNodeId = 2003;
- InitializeBridge();
- const sync_pb::SessionSpecifics foreign_header =
- CreateHeaderSpecificsWithOneTab(kForeignSessionTag, kForeignWindowId,
- kForeignTabId);
- const sync_pb::SessionSpecifics foreign_tab =
- CreateTabSpecifics(kForeignSessionTag, kForeignWindowId, kForeignTabId,
- kForeignTabNodeId, "http://baz.com/");
- StartSyncing({foreign_header, foreign_tab});
- const std::string foreign_header_storage_key =
- SessionStore::GetHeaderStorageKey(kForeignSessionTag);
- const std::string foreign_tab_storage_key =
- SessionStore::GetTabStorageKey(kForeignSessionTag, kForeignTabNodeId);
- // Test fixture expects the two foreign entities in the model as well as the
- // underlying store.
- ASSERT_THAT(GetData(foreign_header_storage_key), NotNull());
- ASSERT_THAT(GetData(foreign_tab_storage_key), NotNull());
- const sessions::SessionTab* foreign_session_tab = nullptr;
- ASSERT_TRUE(bridge()->GetOpenTabsUIDelegate()->GetForeignTab(
- kForeignSessionTag, SessionID::FromSerializedValue(kForeignTabId),
- &foreign_session_tab));
- ASSERT_THAT(foreign_session_tab, NotNull());
- std::vector<const SyncedSession*> foreign_sessions;
- ASSERT_TRUE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
- &foreign_sessions));
- ASSERT_THAT(foreign_sessions,
- ElementsAre(MatchesSyncedSession(
- kForeignSessionTag,
- {{kForeignWindowId, std::vector<int>{kForeignTabId}}})));
- ASSERT_TRUE(real_processor()->IsTrackingMetadata());
- // Mimic the user requesting a session deletion from the UI.
- EXPECT_CALL(mock_processor(), Delete(foreign_header_storage_key, _));
- EXPECT_CALL(mock_processor(), Delete(foreign_tab_storage_key, _));
- EXPECT_CALL(mock_foreign_session_updated_cb(), Run());
- bridge()->GetOpenTabsUIDelegate()->DeleteForeignSession(kForeignSessionTag);
- // Verify what gets exposed to the UI.
- foreign_session_tab = nullptr;
- EXPECT_FALSE(bridge()->GetOpenTabsUIDelegate()->GetForeignTab(
- kForeignSessionTag, SessionID::FromSerializedValue(kForeignTabId),
- &foreign_session_tab));
- EXPECT_FALSE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
- &foreign_sessions));
- // Verify store.
- EXPECT_THAT(GetData(foreign_header_storage_key), IsNull());
- EXPECT_THAT(GetData(foreign_tab_storage_key), IsNull());
- }
- // Verifies that attempts to delete the local session from the UI are ignored,
- // although the UI sholdn't really be offering that option.
- TEST_F(SessionSyncBridgeTest, ShouldIgnoreLocalSessionDeletionFromUI) {
- InitializeBridge();
- StartSyncing();
- EXPECT_CALL(mock_foreign_session_updated_cb(), Run()).Times(0);
- EXPECT_CALL(mock_processor(), Delete).Times(0);
- bridge()->GetOpenTabsUIDelegate()->DeleteForeignSession(kLocalCacheGuid);
- const SyncedSession* session = nullptr;
- EXPECT_TRUE(bridge()->GetOpenTabsUIDelegate()->GetLocalSession(&session));
- EXPECT_THAT(session, NotNull());
- EXPECT_THAT(GetData(SessionStore::GetHeaderStorageKey(kLocalCacheGuid)),
- NotNull());
- }
- // Verifies that receiving an empty update list does not broadcast a foreign
- // session change via the corresponding callback.
- TEST_F(SessionSyncBridgeTest, ShouldNotBroadcastUpdatesIfEmpty) {
- InitializeBridge();
- StartSyncing();
- EXPECT_CALL(mock_foreign_session_updated_cb(), Run()).Times(0);
- // Mimic receiving an empty list of remote updates.
- sync_pb::ModelTypeState state;
- state.set_initial_sync_done(true);
- real_processor()->OnUpdateReceived(state, {});
- }
- TEST_F(SessionSyncBridgeTest, ShouldDoGarbageCollection) {
- // We construct two identical sessions, one modified recently, one modified
- // more than |kStaleSessionThreshold| ago (14 days ago).
- const base::Time stale_mtime = base::Time::Now() - base::Days(15);
- const base::Time recent_mtime = base::Time::Now() - base::Days(13);
- const std::string kStaleSessionTag = "stalesessiontag";
- const std::string kRecentSessionTag = "recentsessiontag";
- const int kWindowId = 2000001;
- const int kTabId = 2000002;
- const int kTabNodeId = 2003;
- InitializeBridge();
- StartSyncing();
- // Construct a remote update.
- sync_pb::ModelTypeState state;
- state.set_initial_sync_done(true);
- syncer::UpdateResponseDataList updates;
- // Two entities belong to a recent session.
- updates.push_back(SpecificsToUpdateResponse(
- CreateHeaderSpecificsWithOneTab(kStaleSessionTag, kWindowId, kTabId),
- stale_mtime));
- updates.push_back(SpecificsToUpdateResponse(
- CreateTabSpecifics(kStaleSessionTag, kWindowId, kTabId, kTabNodeId,
- "http://baz.com/"),
- stale_mtime));
- updates.push_back(SpecificsToUpdateResponse(
- CreateHeaderSpecificsWithOneTab(kRecentSessionTag, kWindowId, kTabId),
- recent_mtime));
- updates.push_back(SpecificsToUpdateResponse(
- CreateTabSpecifics(kRecentSessionTag, kWindowId, kTabId, kTabNodeId,
- "http://baz.com/"),
- recent_mtime));
- // During garbage collection, we expect |kStaleSessionTag| to be deleted.
- EXPECT_CALL(mock_processor(),
- Delete(SessionStore::GetHeaderStorageKey(kStaleSessionTag), _));
- EXPECT_CALL(
- mock_processor(),
- Delete(SessionStore::GetTabStorageKey(kStaleSessionTag, kTabNodeId), _));
- EXPECT_CALL(mock_foreign_session_updated_cb(), Run()).Times(AtLeast(1));
- real_processor()->OnUpdateReceived(state, std::move(updates));
- }
- TEST_F(SessionSyncBridgeTest, ShouldReturnBrowserTypeInGetData) {
- const int kWindowId = 1000001;
- const int kTabId = 1000002;
- AddWindow(kWindowId, sync_pb::SyncEnums_BrowserType_TYPE_CUSTOM_TAB);
- AddTab(kWindowId, "http://foo.com/", kTabId);
- InitializeBridge();
- StartSyncing();
- std::unique_ptr<EntityData> tab_data = GetData(
- SessionStore::GetTabStorageKey(kLocalCacheGuid, /*tab_node_id=*/0));
- ASSERT_THAT(tab_data, NotNull());
- EXPECT_EQ(sync_pb::SyncEnums_BrowserType_TYPE_CUSTOM_TAB,
- tab_data->specifics.session().tab().browser_type());
- }
- } // namespace
- } // namespace sync_sessions
|