session_sync_bridge_unittest.cc 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/sync_sessions/session_sync_bridge.h"
  5. #include <map>
  6. #include <utility>
  7. #include <vector>
  8. #include "base/callback_helpers.h"
  9. #include "base/run_loop.h"
  10. #include "base/test/bind.h"
  11. #include "base/test/mock_callback.h"
  12. #include "base/test/task_environment.h"
  13. #include "components/prefs/testing_pref_service.h"
  14. #include "components/sync/base/client_tag_hash.h"
  15. #include "components/sync/base/sync_prefs.h"
  16. #include "components/sync/engine/commit_and_get_updates_types.h"
  17. #include "components/sync/engine/data_type_activation_response.h"
  18. #include "components/sync/model/client_tag_based_model_type_processor.h"
  19. #include "components/sync/model/data_batch.h"
  20. #include "components/sync/model/data_type_activation_request.h"
  21. #include "components/sync/model/metadata_batch.h"
  22. #include "components/sync/model/metadata_change_list.h"
  23. #include "components/sync/model/model_type_sync_bridge.h"
  24. #include "components/sync/protocol/entity_metadata.pb.h"
  25. #include "components/sync/protocol/model_type_state.pb.h"
  26. #include "components/sync/protocol/session_specifics.pb.h"
  27. #include "components/sync/protocol/sync_enums.pb.h"
  28. #include "components/sync/test/mock_model_type_change_processor.h"
  29. #include "components/sync/test/model_type_store_test_util.h"
  30. #include "components/sync/test/test_matchers.h"
  31. #include "components/sync_sessions/mock_sync_sessions_client.h"
  32. #include "components/sync_sessions/session_sync_prefs.h"
  33. #include "components/sync_sessions/test_matchers.h"
  34. #include "components/sync_sessions/test_synced_window_delegates_getter.h"
  35. #include "testing/gmock/include/gmock/gmock.h"
  36. #include "testing/gtest/include/gtest/gtest.h"
  37. namespace sync_sessions {
  38. namespace {
  39. using sync_pb::SessionSpecifics;
  40. using syncer::CommitResponseDataList;
  41. using syncer::DataBatch;
  42. using syncer::EntityChangeList;
  43. using syncer::EntityData;
  44. using syncer::FailedCommitResponseDataList;
  45. using syncer::IsEmptyMetadataBatch;
  46. using syncer::MetadataBatch;
  47. using syncer::MockModelTypeChangeProcessor;
  48. using testing::_;
  49. using testing::AtLeast;
  50. using testing::ElementsAre;
  51. using testing::Eq;
  52. using testing::IsEmpty;
  53. using testing::IsNull;
  54. using testing::Not;
  55. using testing::NotNull;
  56. using testing::Pair;
  57. using testing::Pointee;
  58. using testing::Return;
  59. using testing::SaveArg;
  60. using testing::SizeIs;
  61. using testing::UnorderedElementsAre;
  62. using testing::WithArg;
  63. const char kLocalCacheGuid[] = "TestLocalCacheGuid";
  64. MATCHER_P(EntityDataHasSpecifics, session_specifics_matcher, "") {
  65. return session_specifics_matcher.MatchAndExplain(arg->specifics.session(),
  66. result_listener);
  67. }
  68. syncer::EntityData SpecificsToEntity(const sync_pb::SessionSpecifics& specifics,
  69. base::Time mtime = base::Time::Now()) {
  70. syncer::EntityData data;
  71. data.client_tag_hash = syncer::ClientTagHash::FromUnhashed(
  72. syncer::SESSIONS, SessionStore::GetClientTag(specifics));
  73. *data.specifics.mutable_session() = specifics;
  74. data.modification_time = mtime;
  75. return data;
  76. }
  77. syncer::UpdateResponseData SpecificsToUpdateResponse(
  78. const sync_pb::SessionSpecifics& specifics,
  79. base::Time mtime = base::Time::Now()) {
  80. syncer::UpdateResponseData data;
  81. data.entity = SpecificsToEntity(specifics, mtime);
  82. return data;
  83. }
  84. std::map<std::string, std::unique_ptr<EntityData>> BatchToEntityDataMap(
  85. std::unique_ptr<DataBatch> batch) {
  86. std::map<std::string, std::unique_ptr<EntityData>> storage_key_to_data;
  87. while (batch && batch->HasNext()) {
  88. storage_key_to_data.insert(batch->Next());
  89. }
  90. return storage_key_to_data;
  91. }
  92. syncer::UpdateResponseData CreateTombstone(const std::string& client_tag) {
  93. syncer::EntityData tombstone;
  94. tombstone.client_tag_hash =
  95. syncer::ClientTagHash::FromUnhashed(syncer::SESSIONS, client_tag);
  96. syncer::UpdateResponseData data;
  97. data.entity = std::move(tombstone);
  98. data.response_version = 2;
  99. return data;
  100. }
  101. syncer::CommitResponseData CreateSuccessResponse(
  102. const std::string& client_tag) {
  103. syncer::CommitResponseData response;
  104. response.client_tag_hash =
  105. syncer::ClientTagHash::FromUnhashed(syncer::SESSIONS, client_tag);
  106. response.sequence_number = 1;
  107. return response;
  108. }
  109. sync_pb::SessionSpecifics CreateHeaderSpecificsWithOneTab(
  110. const std::string& session_tag,
  111. int window_id,
  112. int tab_id) {
  113. sync_pb::SessionSpecifics specifics;
  114. specifics.set_session_tag(session_tag);
  115. specifics.mutable_header()->set_client_name("Some client name");
  116. specifics.mutable_header()->set_device_type(
  117. sync_pb::SyncEnums_DeviceType_TYPE_LINUX);
  118. sync_pb::SessionWindow* window = specifics.mutable_header()->add_window();
  119. window->set_browser_type(sync_pb::SyncEnums_BrowserType_TYPE_TABBED);
  120. window->set_window_id(window_id);
  121. window->add_tab(tab_id);
  122. return specifics;
  123. }
  124. sync_pb::SessionSpecifics CreateTabSpecifics(const std::string& session_tag,
  125. int window_id,
  126. int tab_id,
  127. int tab_node_id,
  128. const std::string& url) {
  129. sync_pb::SessionSpecifics specifics;
  130. specifics.set_session_tag(session_tag);
  131. specifics.set_tab_node_id(tab_node_id);
  132. specifics.mutable_tab()->add_navigation()->set_virtual_url(url);
  133. specifics.mutable_tab()->set_window_id(window_id);
  134. specifics.mutable_tab()->set_tab_id(tab_id);
  135. return specifics;
  136. }
  137. class SessionSyncBridgeTest : public ::testing::Test {
  138. protected:
  139. SessionSyncBridgeTest()
  140. : store_(syncer::ModelTypeStoreTestUtil::CreateInMemoryStoreForTest(
  141. syncer::SESSIONS)),
  142. session_sync_prefs_(&pref_service_) {
  143. SessionSyncPrefs::RegisterProfilePrefs(pref_service_.registry());
  144. ON_CALL(mock_sync_sessions_client_, GetSessionSyncPrefs())
  145. .WillByDefault(Return(&session_sync_prefs_));
  146. ON_CALL(mock_sync_sessions_client_, GetStoreFactory())
  147. .WillByDefault(
  148. Return(syncer::ModelTypeStoreTestUtil::FactoryForForwardingStore(
  149. store_.get())));
  150. ON_CALL(mock_sync_sessions_client_, GetSyncedWindowDelegatesGetter())
  151. .WillByDefault(Return(&window_getter_));
  152. ON_CALL(mock_sync_sessions_client_, GetLocalSessionEventRouter())
  153. .WillByDefault(Return(window_getter_.router()));
  154. // Even if we use NiceMock, let's be strict about errors and let tests
  155. // explicitly list them.
  156. EXPECT_CALL(mock_processor_, ReportError).Times(0);
  157. }
  158. ~SessionSyncBridgeTest() override = default;
  159. void InitializeBridge() {
  160. real_processor_ =
  161. std::make_unique<syncer::ClientTagBasedModelTypeProcessor>(
  162. syncer::SESSIONS, /*dump_stack=*/base::DoNothing());
  163. mock_processor_.DelegateCallsByDefaultTo(real_processor_.get());
  164. // Instantiate the bridge.
  165. bridge_ = std::make_unique<SessionSyncBridge>(
  166. mock_foreign_session_updated_cb_.Get(), &mock_sync_sessions_client_,
  167. mock_processor_.CreateForwardingProcessor());
  168. }
  169. void ShutdownBridge() {
  170. bridge_.reset();
  171. // The mock is still delegating to |real_processor_|, so we reset it too.
  172. ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_processor_));
  173. real_processor_.reset();
  174. }
  175. void StartSyncing(const std::vector<SessionSpecifics>& remote_data = {}) {
  176. syncer::DataTypeActivationRequest request;
  177. request.error_handler = base::DoNothing();
  178. request.cache_guid = kLocalCacheGuid;
  179. request.authenticated_account_id = CoreAccountId("SomeAccountId");
  180. base::RunLoop loop;
  181. real_processor_->OnSyncStarting(
  182. request,
  183. base::BindLambdaForTesting(
  184. [&loop](std::unique_ptr<syncer::DataTypeActivationResponse>) {
  185. loop.Quit();
  186. }));
  187. loop.Run();
  188. sync_pb::ModelTypeState state;
  189. state.set_initial_sync_done(true);
  190. state.set_cache_guid(request.cache_guid);
  191. state.mutable_progress_marker()->set_data_type_id(
  192. GetSpecificsFieldNumberFromModelType(syncer::SESSIONS));
  193. state.set_authenticated_account_id("SomeAccountId");
  194. syncer::UpdateResponseDataList initial_updates;
  195. for (const SessionSpecifics& specifics : remote_data) {
  196. initial_updates.push_back(SpecificsToUpdateResponse(specifics));
  197. }
  198. real_processor_->OnUpdateReceived(state, std::move(initial_updates));
  199. }
  200. std::map<std::string, std::unique_ptr<EntityData>> GetAllData() {
  201. base::RunLoop loop;
  202. std::unique_ptr<DataBatch> batch;
  203. bridge_->GetAllDataForDebugging(base::BindLambdaForTesting(
  204. [&loop, &batch](std::unique_ptr<DataBatch> input_batch) {
  205. batch = std::move(input_batch);
  206. loop.Quit();
  207. }));
  208. loop.Run();
  209. EXPECT_NE(nullptr, batch);
  210. return BatchToEntityDataMap(std::move(batch));
  211. }
  212. std::map<std::string, std::unique_ptr<EntityData>> GetData(
  213. const std::vector<std::string>& storage_keys) {
  214. base::RunLoop loop;
  215. std::unique_ptr<DataBatch> batch;
  216. bridge_->GetData(
  217. storage_keys,
  218. base::BindLambdaForTesting(
  219. [&loop, &batch](std::unique_ptr<DataBatch> input_batch) {
  220. batch = std::move(input_batch);
  221. loop.Quit();
  222. }));
  223. loop.Run();
  224. EXPECT_NE(nullptr, batch);
  225. return BatchToEntityDataMap(std::move(batch));
  226. }
  227. std::unique_ptr<EntityData> GetData(const std::string& storage_key) {
  228. std::map<std::string, std::unique_ptr<EntityData>> entity_data_map =
  229. GetData(std::vector<std::string>{storage_key});
  230. EXPECT_LE(entity_data_map.size(), 1U);
  231. if (entity_data_map.empty()) {
  232. return nullptr;
  233. }
  234. EXPECT_EQ(storage_key, entity_data_map.begin()->first);
  235. return std::move(entity_data_map.begin()->second);
  236. }
  237. void ResetWindows() { window_getter_.ResetWindows(); }
  238. TestSyncedWindowDelegate* AddWindow(
  239. int window_id,
  240. sync_pb::SyncEnums_BrowserType type =
  241. sync_pb::SyncEnums_BrowserType_TYPE_TABBED) {
  242. return window_getter_.AddWindow(type,
  243. SessionID::FromSerializedValue(window_id));
  244. }
  245. TestSyncedTabDelegate* AddTab(int window_id,
  246. const std::string& url,
  247. int tab_id = SessionID::NewUnique().id()) {
  248. TestSyncedTabDelegate* tab =
  249. window_getter_.AddTab(SessionID::FromSerializedValue(window_id),
  250. SessionID::FromSerializedValue(tab_id));
  251. tab->Navigate(url, base::Time::Now());
  252. return tab;
  253. }
  254. void CloseTab(int tab_id) {
  255. window_getter_.CloseTab(SessionID::FromSerializedValue(tab_id));
  256. }
  257. void SessionRestoreComplete() { window_getter_.SessionRestoreComplete(); }
  258. MockSyncSessionsClient& mock_sync_sessions_client() {
  259. return mock_sync_sessions_client_;
  260. }
  261. base::MockCallback<base::RepeatingClosure>&
  262. mock_foreign_session_updated_cb() {
  263. return mock_foreign_session_updated_cb_;
  264. }
  265. SessionSyncBridge* bridge() { return bridge_.get(); }
  266. syncer::MockModelTypeChangeProcessor& mock_processor() {
  267. return mock_processor_;
  268. }
  269. syncer::ClientTagBasedModelTypeProcessor* real_processor() {
  270. return real_processor_.get();
  271. }
  272. syncer::ModelTypeStore* underlying_store() { return store_.get(); }
  273. private:
  274. base::test::SingleThreadTaskEnvironment task_environment_;
  275. const std::unique_ptr<syncer::ModelTypeStore> store_;
  276. // Dependencies.
  277. TestingPrefServiceSimple pref_service_;
  278. SessionSyncPrefs session_sync_prefs_;
  279. testing::NiceMock<base::MockCallback<base::RepeatingClosure>>
  280. mock_foreign_session_updated_cb_;
  281. testing::NiceMock<MockSyncSessionsClient> mock_sync_sessions_client_;
  282. testing::NiceMock<MockModelTypeChangeProcessor> mock_processor_;
  283. TestSyncedWindowDelegatesGetter window_getter_;
  284. std::unique_ptr<SessionSyncBridge> bridge_;
  285. std::unique_ptr<syncer::ClientTagBasedModelTypeProcessor> real_processor_;
  286. };
  287. TEST_F(SessionSyncBridgeTest, ShouldCallModelReadyToSyncWhenSyncEnabled) {
  288. EXPECT_CALL(mock_processor(), ModelReadyToSync).Times(0);
  289. InitializeBridge();
  290. EXPECT_CALL(mock_processor(), ModelReadyToSync(IsEmptyMetadataBatch()));
  291. StartSyncing();
  292. }
  293. // Test that handling of local events (i.e. propagating the local state to
  294. // sync) does not start while a session restore is in progress.
  295. TEST_F(SessionSyncBridgeTest, ShouldDeferLocalEventDueToSessionRestore) {
  296. const int kWindowId = 1000001;
  297. const int kTabId1 = 1000002;
  298. const int kTabId2 = 1000003;
  299. // No notifications expected until OnSessionRestoreComplete().
  300. EXPECT_CALL(mock_processor(), Put).Times(0);
  301. AddWindow(kWindowId)->SetIsSessionRestoreInProgress(true);
  302. // Initial tab should be ignored (not exposed to processor) while session
  303. // restore is in progress.
  304. AddTab(kWindowId, "http://foo.com/", kTabId1);
  305. InitializeBridge();
  306. StartSyncing();
  307. EXPECT_THAT(GetAllData(),
  308. ElementsAre(Pair(
  309. _, EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid,
  310. /*window_ids=*/{},
  311. /*tab_ids=*/{})))));
  312. // Create the actual tab, which should be ignored because session restore
  313. // is in progress.
  314. AddTab(kWindowId, "http://bar.com/", kTabId2);
  315. EXPECT_THAT(GetAllData(), SizeIs(1));
  316. // OnSessionRestoreComplete() should issue three Put() calls, one updating the
  317. // header and one for each of the two added tabs.
  318. EXPECT_CALL(mock_processor(), Put).Times(3);
  319. SessionRestoreComplete();
  320. EXPECT_THAT(GetAllData(), SizeIs(3));
  321. }
  322. TEST_F(SessionSyncBridgeTest, ShouldCreateHeaderByDefault) {
  323. InitializeBridge();
  324. EXPECT_CALL(mock_processor(), ModelReadyToSync(IsEmptyMetadataBatch()));
  325. StartSyncing();
  326. EXPECT_THAT(GetAllData(), SizeIs(1));
  327. }
  328. // Tests that local windows and tabs that exist at the time the bridge is
  329. // started (e.g. after a Chrome restart) are properly exposed via the bridge's
  330. // GetData() and GetAllData() methods, as well as notified via Put().
  331. TEST_F(SessionSyncBridgeTest, ShouldExposeInitialLocalTabsToProcessor) {
  332. const int kWindowId = 1000001;
  333. const int kTabId1 = 1000002;
  334. const int kTabId2 = 1000003;
  335. AddWindow(kWindowId);
  336. AddTab(kWindowId, "http://foo.com/", kTabId1);
  337. AddTab(kWindowId, "http://bar.com/", kTabId2);
  338. InitializeBridge();
  339. const std::string header_storage_key =
  340. SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
  341. const std::string tab_storage_key1 =
  342. SessionStore::GetTabStorageKey(kLocalCacheGuid, 0);
  343. const std::string tab_storage_key2 =
  344. SessionStore::GetTabStorageKey(kLocalCacheGuid, 1);
  345. EXPECT_CALL(mock_processor(),
  346. Put(header_storage_key,
  347. EntityDataHasSpecifics(MatchesHeader(
  348. kLocalCacheGuid, {kWindowId}, {kTabId1, kTabId2})),
  349. _));
  350. EXPECT_CALL(mock_processor(),
  351. Put(tab_storage_key1,
  352. EntityDataHasSpecifics(
  353. MatchesTab(kLocalCacheGuid, kWindowId, kTabId1,
  354. /*tab_node_id=*/_, {"http://foo.com/"})),
  355. _));
  356. EXPECT_CALL(mock_processor(),
  357. Put(tab_storage_key2,
  358. EntityDataHasSpecifics(
  359. MatchesTab(kLocalCacheGuid, kWindowId, kTabId2,
  360. /*tab_node_id=*/_, {"http://bar.com/"})),
  361. _));
  362. StartSyncing();
  363. EXPECT_THAT(GetData(header_storage_key),
  364. EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, {kWindowId},
  365. {kTabId1, kTabId2})));
  366. EXPECT_THAT(
  367. GetAllData(),
  368. UnorderedElementsAre(
  369. Pair(header_storage_key,
  370. EntityDataHasSpecifics(MatchesHeader(
  371. kLocalCacheGuid, {kWindowId}, {kTabId1, kTabId2}))),
  372. Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
  373. kLocalCacheGuid, kWindowId, kTabId1,
  374. /*tab_node_id=*/_, {"http://foo.com/"}))),
  375. Pair(tab_storage_key2,
  376. EntityDataHasSpecifics(
  377. MatchesTab(kLocalCacheGuid, kWindowId, kTabId2,
  378. /*tab_node_id=*/_, {"http://bar.com/"})))));
  379. }
  380. // Tests that the creation of a new tab while sync is enabled is propagated to:
  381. // 1) The processor, via Put().
  382. // 2) The in-memory representation exposed via GetData().
  383. // 3) The persisted store, exposed via GetAllData().
  384. TEST_F(SessionSyncBridgeTest, ShouldReportLocalTabCreation) {
  385. const int kWindowId = 1000001;
  386. const int kTabId1 = 1000002;
  387. const int kTabId2 = 1000003;
  388. AddWindow(kWindowId);
  389. AddTab(kWindowId, "http://foo.com/", kTabId1);
  390. InitializeBridge();
  391. StartSyncing();
  392. ASSERT_THAT(GetAllData(), SizeIs(2));
  393. EXPECT_CALL(mock_foreign_session_updated_cb(), Run()).Times(0);
  394. // Expectations for the processor.
  395. std::string header_storage_key;
  396. std::string tab_storage_key;
  397. // Tab creation triggers an update event due to the tab parented notification,
  398. // so the event handler issues two commits as well (one for tab creation, one
  399. // for tab update). During the first update, however, the tab is not syncable
  400. // and is hence skipped.
  401. testing::Expectation put_transient_header = EXPECT_CALL(
  402. mock_processor(), Put(_,
  403. EntityDataHasSpecifics(MatchesHeader(
  404. kLocalCacheGuid, {kWindowId}, {kTabId1})),
  405. _));
  406. EXPECT_CALL(mock_processor(),
  407. Put(_,
  408. EntityDataHasSpecifics(MatchesHeader(
  409. kLocalCacheGuid, {kWindowId}, {kTabId1, kTabId2})),
  410. _))
  411. .After(put_transient_header)
  412. .WillOnce(WithArg<0>(SaveArg<0>(&header_storage_key)));
  413. EXPECT_CALL(mock_processor(),
  414. Put(_,
  415. EntityDataHasSpecifics(
  416. MatchesTab(kLocalCacheGuid, kWindowId, kTabId2,
  417. /*tab_node_id=*/_, {"http://bar.com/"})),
  418. _))
  419. .WillOnce(WithArg<0>(SaveArg<0>(&tab_storage_key)));
  420. // Create the actual tab, now that we're syncing.
  421. AddTab(kWindowId, "http://bar.com/", kTabId2);
  422. ASSERT_THAT(header_storage_key,
  423. Eq(SessionStore::GetHeaderStorageKey(kLocalCacheGuid)));
  424. ASSERT_THAT(tab_storage_key, Not(IsEmpty()));
  425. // Verify the bridge's state exposed via the getters.
  426. EXPECT_THAT(
  427. GetAllData(),
  428. UnorderedElementsAre(
  429. Pair(header_storage_key,
  430. EntityDataHasSpecifics(MatchesHeader(
  431. kLocalCacheGuid, {kWindowId}, {kTabId1, kTabId2}))),
  432. Pair(_, EntityDataHasSpecifics(
  433. MatchesTab(kLocalCacheGuid, kWindowId, kTabId1,
  434. /*tab_node_id=*/_, {"http://foo.com/"}))),
  435. Pair(tab_storage_key, EntityDataHasSpecifics(MatchesTab(
  436. kLocalCacheGuid, kWindowId, kTabId2,
  437. /*tab_node_id=*/_, {"http://bar.com/"})))));
  438. EXPECT_THAT(GetData(header_storage_key),
  439. EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, {kWindowId},
  440. {kTabId1, kTabId2})));
  441. EXPECT_THAT(GetData(tab_storage_key),
  442. EntityDataHasSpecifics(
  443. MatchesTab(kLocalCacheGuid, kWindowId, kTabId2,
  444. /*tab_node_id=*/_, {"http://bar.com/"})));
  445. }
  446. TEST_F(SessionSyncBridgeTest, ShouldNotUpdatePlaceholderTabsDuringRestore) {
  447. const int kWindowId1 = 1000001;
  448. const int kWindowId2 = 1000002;
  449. const int kTabId1 = 1000003;
  450. const int kTabId2 = 1000004;
  451. // Zero is the first assigned tab node ID.
  452. const int kTabNodeId1 = 0;
  453. const int kTabNodeId2 = 1;
  454. AddWindow(kWindowId1);
  455. AddTab(kWindowId1, "http://foo.com/", kTabId1);
  456. AddTab(kWindowId1, "http://bar.com/", kTabId2);
  457. const std::string header_storage_key =
  458. SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
  459. const std::string tab_storage_key1 =
  460. SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId1);
  461. const std::string tab_storage_key2 =
  462. SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId2);
  463. InitializeBridge();
  464. StartSyncing();
  465. ASSERT_THAT(GetData(header_storage_key),
  466. EntityDataHasSpecifics(MatchesHeader(
  467. kLocalCacheGuid, {kWindowId1}, {kTabId1, kTabId2})));
  468. ASSERT_THAT(
  469. GetData(tab_storage_key1),
  470. EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, kWindowId1, kTabId1,
  471. kTabNodeId1, {"http://foo.com/"})));
  472. ASSERT_THAT(
  473. GetData(tab_storage_key2),
  474. EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, kWindowId1, kTabId2,
  475. kTabNodeId2, {"http://bar.com/"})));
  476. ShutdownBridge();
  477. // Override tabs with placeholder tab delegates. Note that, on Android, tab
  478. // IDs are persisted by session restore across browser restarts.
  479. PlaceholderTabDelegate placeholder_tab1(
  480. SessionID::FromSerializedValue(kTabId1));
  481. PlaceholderTabDelegate placeholder_tab2(
  482. SessionID::FromSerializedValue(kTabId2));
  483. ResetWindows();
  484. TestSyncedWindowDelegate* window = AddWindow(kWindowId2);
  485. window->OverrideTabAt(0, &placeholder_tab1);
  486. window->OverrideTabAt(1, &placeholder_tab2);
  487. // When the bridge gets restarted, we only expect the header to be updated,
  488. // and placeholder tabs stay unchanged with a stale window ID.
  489. EXPECT_CALL(mock_processor(),
  490. Put(header_storage_key,
  491. EntityDataHasSpecifics(MatchesHeader(
  492. kLocalCacheGuid, {kWindowId2}, {kTabId1, kTabId2})),
  493. _));
  494. // Start the bridge again.
  495. InitializeBridge();
  496. StartSyncing();
  497. // Although we haven't notified the processor about the window-ID change, if
  498. // it hypothetically asked for these entities, the returned entities are
  499. // up-to-date.
  500. EXPECT_THAT(GetData(header_storage_key),
  501. EntityDataHasSpecifics(MatchesHeader(
  502. kLocalCacheGuid, {kWindowId2}, {kTabId1, kTabId2})));
  503. EXPECT_THAT(
  504. GetData(tab_storage_key1),
  505. EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, kWindowId2, kTabId1,
  506. kTabNodeId1, {"http://foo.com/"})));
  507. EXPECT_THAT(
  508. GetData(tab_storage_key2),
  509. EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, kWindowId2, kTabId2,
  510. kTabNodeId2, {"http://bar.com/"})));
  511. EXPECT_THAT(
  512. GetAllData(),
  513. UnorderedElementsAre(
  514. Pair(header_storage_key,
  515. EntityDataHasSpecifics(MatchesHeader(
  516. kLocalCacheGuid, {kWindowId2}, {kTabId1, kTabId2}))),
  517. Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
  518. kLocalCacheGuid, kWindowId2, kTabId1,
  519. kTabNodeId1, {"http://foo.com/"}))),
  520. Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
  521. kLocalCacheGuid, kWindowId2, kTabId2,
  522. kTabNodeId2, {"http://bar.com/"})))));
  523. }
  524. TEST_F(SessionSyncBridgeTest,
  525. ShouldIgnoreUnsyncablePlaceholderTabDuringRestore) {
  526. const int kWindowId1 = 1000001;
  527. const int kWindowId2 = 1000002;
  528. const int kTabId1 = 1000002;
  529. const int kTabId2 = 1000003;
  530. // Zero is the first assigned tab node ID.
  531. const int kTabNodeId1 = 0;
  532. AddWindow(kWindowId1);
  533. AddTab(kWindowId1, "http://foo.com/", kTabId1);
  534. // Tab 2 is unsyncable because of the URL scheme.
  535. AddTab(kWindowId1, "about:blank", kTabId2);
  536. const std::string header_storage_key =
  537. SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
  538. const std::string tab_storage_key1 =
  539. SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId1);
  540. InitializeBridge();
  541. StartSyncing();
  542. ASSERT_THAT(
  543. GetAllData(),
  544. UnorderedElementsAre(
  545. Pair(header_storage_key,
  546. EntityDataHasSpecifics(
  547. MatchesHeader(kLocalCacheGuid, {kWindowId1}, {kTabId1}))),
  548. Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
  549. kLocalCacheGuid, kWindowId1, kTabId1,
  550. kTabNodeId1, {"http://foo.com/"})))));
  551. ShutdownBridge();
  552. // Override tabs with placeholder tab delegates. Note that, on Android, tab
  553. // IDs are persisted by session restore across browser restarts.
  554. PlaceholderTabDelegate placeholder_tab1(
  555. SessionID::FromSerializedValue(kTabId1));
  556. PlaceholderTabDelegate placeholder_tab2(
  557. SessionID::FromSerializedValue(kTabId2));
  558. ResetWindows();
  559. TestSyncedWindowDelegate* window = AddWindow(kWindowId2);
  560. window->OverrideTabAt(0, &placeholder_tab1);
  561. window->OverrideTabAt(1, &placeholder_tab2);
  562. // Start the bridge again.
  563. InitializeBridge();
  564. StartSyncing();
  565. EXPECT_THAT(
  566. GetAllData(),
  567. UnorderedElementsAre(
  568. Pair(header_storage_key,
  569. EntityDataHasSpecifics(
  570. MatchesHeader(kLocalCacheGuid, {kWindowId2}, {kTabId1}))),
  571. Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
  572. kLocalCacheGuid, kWindowId2, kTabId1,
  573. kTabNodeId1, {"http://foo.com/"})))));
  574. }
  575. // Ensure that tabbed windows from a previous session are preserved if no
  576. // windows are present on startup.
  577. TEST_F(SessionSyncBridgeTest, ShouldRestoreTabbedDataIfNoWindowsDuringStartup) {
  578. const int kWindowId1 = 1000001;
  579. const int kWindowId2 = 1000002;
  580. const int kTabNodeId = 0;
  581. AddWindow(kWindowId1);
  582. TestSyncedTabDelegate* tab = AddTab(kWindowId1, "http://foo.com/");
  583. const std::string header_storage_key =
  584. SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
  585. const std::string tab_storage_key =
  586. SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId);
  587. InitializeBridge();
  588. StartSyncing();
  589. ASSERT_THAT(
  590. GetAllData(),
  591. UnorderedElementsAre(
  592. Pair(header_storage_key,
  593. EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _))),
  594. Pair(tab_storage_key,
  595. EntityDataHasSpecifics(MatchesTab(
  596. kLocalCacheGuid, _, _, kTabNodeId, {"http://foo.com/"})))));
  597. ShutdownBridge();
  598. // Start the bridge with no local windows/tabs.
  599. ResetWindows();
  600. InitializeBridge();
  601. StartSyncing();
  602. EXPECT_THAT(
  603. GetAllData(),
  604. UnorderedElementsAre(
  605. Pair(header_storage_key,
  606. EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _))),
  607. Pair(tab_storage_key,
  608. EntityDataHasSpecifics(MatchesTab(
  609. kLocalCacheGuid, _, _, kTabNodeId, {"http://foo.com/"})))));
  610. // Now actually resurrect the native data, which will end up having different
  611. // native ids, but the tab has the same sync id as before.
  612. EXPECT_CALL(
  613. mock_processor(),
  614. Put(header_storage_key,
  615. EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _)), _));
  616. EXPECT_CALL(mock_processor(),
  617. Put(tab_storage_key,
  618. EntityDataHasSpecifics(MatchesTab(
  619. kLocalCacheGuid, /*window_id=*/_, /*tab_id=*/_,
  620. kTabNodeId, {"http://foo.com/", "http://bar.com/"})),
  621. _));
  622. AddWindow(kWindowId2)->OverrideTabAt(0, tab);
  623. tab->Navigate("http://bar.com/");
  624. }
  625. // Ensure that tabbed windows from a previous session are preserved if only
  626. // a custom tab is present at startup.
  627. TEST_F(SessionSyncBridgeTest, ShouldPreserveTabbedDataIfCustomTabOnlyFound) {
  628. const int kWindowId1 = 1000001;
  629. const int kWindowId2 = 1000002;
  630. AddWindow(kWindowId1);
  631. AddTab(kWindowId1, "http://foo.com/");
  632. InitializeBridge();
  633. StartSyncing();
  634. ASSERT_THAT(
  635. GetAllData(),
  636. UnorderedElementsAre(
  637. Pair(_, EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _))),
  638. Pair(_, EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, _, _,
  639. /*tab_node_id=*/0,
  640. {"http://foo.com/"})))));
  641. ShutdownBridge();
  642. // Start the bridge with only a custom tab open.
  643. ResetWindows();
  644. AddWindow(kWindowId2, sync_pb::SyncEnums_BrowserType_TYPE_CUSTOM_TAB);
  645. AddTab(kWindowId2, "http://bar.com/");
  646. InitializeBridge();
  647. StartSyncing();
  648. // The previous session should be preserved, together with the new custom tab.
  649. EXPECT_THAT(
  650. GetAllData(),
  651. UnorderedElementsAre(
  652. Pair(_, EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _))),
  653. Pair(_, EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, _, _,
  654. /*tab_node_id=*/0,
  655. {"http://foo.com/"}))),
  656. Pair(_, EntityDataHasSpecifics(MatchesTab(kLocalCacheGuid, _, _,
  657. /*tab_node_id=*/1,
  658. {"http://bar.com/"})))));
  659. }
  660. // Ensure that tabbed windows from a previous session are preserved and combined
  661. // with a custom tab that was newly found during startup.
  662. TEST_F(SessionSyncBridgeTest, ShouldPreserveTabbedDataIfNewCustomTabAlsoFound) {
  663. const int kWindowId1 = 1000001;
  664. const int kWindowId2 = 1000002;
  665. const int kTabId1 = 1000003;
  666. const int kTabId2 = 1000004;
  667. AddWindow(kWindowId1);
  668. AddTab(kWindowId1, "http://foo.com/", kTabId1);
  669. InitializeBridge();
  670. StartSyncing();
  671. ASSERT_THAT(GetAllData(),
  672. UnorderedElementsAre(
  673. Pair(_, EntityDataHasSpecifics(MatchesHeader(
  674. kLocalCacheGuid, {kWindowId1}, {kTabId1}))),
  675. Pair(_, EntityDataHasSpecifics(MatchesTab(
  676. kLocalCacheGuid, kWindowId1, kTabId1,
  677. /*tab_node_id=*/0, {"http://foo.com/"})))));
  678. ShutdownBridge();
  679. // Start the bridge with an additional local custom tab.
  680. AddWindow(kWindowId2, sync_pb::SyncEnums_BrowserType_TYPE_CUSTOM_TAB);
  681. AddTab(kWindowId2, "http://bar.com/", kTabId2);
  682. InitializeBridge();
  683. StartSyncing();
  684. EXPECT_THAT(GetAllData(),
  685. UnorderedElementsAre(
  686. Pair(_, EntityDataHasSpecifics(MatchesHeader(
  687. kLocalCacheGuid, {kWindowId1, kWindowId2},
  688. {kTabId1, kTabId2}))),
  689. Pair(_, EntityDataHasSpecifics(MatchesTab(
  690. kLocalCacheGuid, kWindowId1, kTabId1,
  691. /*tab_node_id=*/0, {"http://foo.com/"}))),
  692. Pair(_, EntityDataHasSpecifics(MatchesTab(
  693. kLocalCacheGuid, kWindowId2, kTabId2,
  694. /*tab_node_id=*/1, {"http://bar.com/"})))));
  695. }
  696. // Ensure that, in a scenario without prior sync data, encountering a custom
  697. // tab only (no tabbed window) starts syncing that tab.
  698. TEST_F(SessionSyncBridgeTest, ShouldAssociateIfCustomTabOnlyOnStartup) {
  699. const int kWindowId = 1000001;
  700. const int kTabId = 1000002;
  701. AddWindow(kWindowId, sync_pb::SyncEnums_BrowserType_TYPE_CUSTOM_TAB);
  702. AddTab(kWindowId, "http://foo.com/", kTabId);
  703. InitializeBridge();
  704. StartSyncing();
  705. EXPECT_THAT(GetAllData(),
  706. UnorderedElementsAre(
  707. Pair(_, EntityDataHasSpecifics(MatchesHeader(
  708. kLocalCacheGuid, {kWindowId}, {kTabId}))),
  709. Pair(_, EntityDataHasSpecifics(MatchesTab(
  710. kLocalCacheGuid, kWindowId, kTabId,
  711. /*tab_node_id=*/0, {"http://foo.com/"})))));
  712. }
  713. // Ensure that all tabs are exposed in a scenario where only a custom tab
  714. // (without tabbed windows) was present during startup, and later tabbed windows
  715. // appear (browser started).
  716. TEST_F(SessionSyncBridgeTest, ShouldExposeTabbedWindowAfterCustomTabOnly) {
  717. const int kWindowId1 = 1000001;
  718. const int kWindowId2 = 1000002;
  719. const int kTabId1 = 1000003;
  720. const int kTabId2 = 1000004;
  721. AddWindow(kWindowId1, sync_pb::SyncEnums_BrowserType_TYPE_CUSTOM_TAB);
  722. AddTab(kWindowId1, "http://foo.com/", kTabId1);
  723. InitializeBridge();
  724. StartSyncing();
  725. ASSERT_THAT(GetAllData(),
  726. UnorderedElementsAre(
  727. Pair(_, EntityDataHasSpecifics(MatchesHeader(
  728. kLocalCacheGuid, {kWindowId1}, {kTabId1}))),
  729. Pair(_, EntityDataHasSpecifics(MatchesTab(
  730. kLocalCacheGuid, kWindowId1, kTabId1,
  731. /*tab_node_id=*/0, {"http://foo.com/"})))));
  732. // Load the actual tabbed window, now that we're syncing.
  733. AddWindow(kWindowId2);
  734. AddTab(kWindowId2, "http://bar.com/", kTabId2);
  735. // The local change should be created and tracked correctly.
  736. EXPECT_THAT(GetAllData(),
  737. UnorderedElementsAre(
  738. Pair(_, EntityDataHasSpecifics(MatchesHeader(
  739. kLocalCacheGuid, {kWindowId1, kWindowId2},
  740. {kTabId1, kTabId2}))),
  741. Pair(_, EntityDataHasSpecifics(MatchesTab(
  742. kLocalCacheGuid, kWindowId1, kTabId1,
  743. /*tab_node_id=*/0, {"http://foo.com/"}))),
  744. Pair(_, EntityDataHasSpecifics(MatchesTab(
  745. kLocalCacheGuid, kWindowId2, kTabId2,
  746. /*tab_node_id=*/1, {"http://bar.com/"})))));
  747. }
  748. TEST_F(SessionSyncBridgeTest, ShouldRecycleTabNodeAfterCommitCompleted) {
  749. const int kWindowId = 1000001;
  750. const int kTabId1 = 1000003;
  751. const int kTabId2 = 1000004;
  752. const int kTabId3 = 1000005;
  753. const int kTabId4 = 1000006;
  754. // Zero is the first assigned tab node ID.
  755. const int kTabNodeId1 = 0;
  756. const int kTabNodeId2 = 1;
  757. const int kTabNodeId3 = 2;
  758. AddWindow(kWindowId);
  759. TestSyncedTabDelegate* tab1 = AddTab(kWindowId, "http://foo.com/", kTabId1);
  760. const std::string header_storage_key =
  761. SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
  762. const std::string tab_storage_key1 =
  763. SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId1);
  764. const std::string tab_storage_key2 =
  765. SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId2);
  766. const std::string tab_storage_key3 =
  767. SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId3);
  768. const std::string tab_client_tag1 =
  769. SessionStore::GetTabClientTagForTest(kLocalCacheGuid, kTabNodeId1);
  770. const std::string tab_client_tag2 =
  771. SessionStore::GetTabClientTagForTest(kLocalCacheGuid, kTabNodeId2);
  772. const std::string tab_client_tag3 =
  773. SessionStore::GetTabClientTagForTest(kLocalCacheGuid, kTabNodeId3);
  774. InitializeBridge();
  775. StartSyncing();
  776. // Mimic a commit completing for the initial sync.
  777. ASSERT_TRUE(real_processor()->HasLocalChangesForTest());
  778. sync_pb::ModelTypeState state;
  779. state.set_initial_sync_done(true);
  780. real_processor()->OnCommitCompleted(
  781. state,
  782. {CreateSuccessResponse(kLocalCacheGuid),
  783. CreateSuccessResponse(tab_client_tag1)},
  784. /*error_response_list=*/FailedCommitResponseDataList());
  785. ASSERT_FALSE(real_processor()->HasLocalChangesForTest());
  786. // Open a second tab.
  787. AddTab(kWindowId, "http://bar.com/", kTabId2);
  788. ASSERT_TRUE(real_processor()->HasLocalChangesForTest());
  789. // Close |kTabId2| and force reassociation by navigating in the remaining open
  790. // tab, leading to a freed tab entity. However, while there are pending
  791. // changes to commit, the entity shouldn't be deleted (to prevent history
  792. // loss).
  793. EXPECT_CALL(mock_processor(), Delete).Times(0);
  794. CloseTab(kTabId2);
  795. tab1->Navigate("http://foo2.com/");
  796. EXPECT_TRUE(real_processor()->HasLocalChangesForTest());
  797. EXPECT_THAT(
  798. GetAllData(),
  799. UnorderedElementsAre(
  800. Pair(header_storage_key,
  801. EntityDataHasSpecifics(
  802. MatchesHeader(kLocalCacheGuid, {kWindowId}, {kTabId1}))),
  803. Pair(tab_storage_key1,
  804. EntityDataHasSpecifics(
  805. MatchesTab(kLocalCacheGuid, kWindowId, kTabId1, kTabNodeId1,
  806. {"http://foo.com/", "http://foo2.com/"}))),
  807. Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
  808. kLocalCacheGuid, kWindowId, kTabId2,
  809. kTabNodeId2, {"http://bar.com/"})))));
  810. // If a new tab is opened, the entity with unsynced changes should not be
  811. // recycled.
  812. AddTab(kWindowId, "http://baz.com/", kTabId3);
  813. EXPECT_THAT(GetAllData(), UnorderedElementsAre(Pair(header_storage_key, _),
  814. Pair(tab_storage_key1, _),
  815. Pair(tab_storage_key2, _),
  816. Pair(tab_storage_key3, _)));
  817. // Completing the commit for the previously closed tab should issue a
  818. // deletion. For that to trigger, we need to trigger the next association,
  819. // which we do by navigating in one of the open tabs.
  820. EXPECT_CALL(mock_processor(), Delete(tab_storage_key2, _));
  821. real_processor()->OnCommitCompleted(
  822. state, {CreateSuccessResponse(tab_client_tag2)},
  823. /*error_response_list=*/FailedCommitResponseDataList());
  824. tab1->Navigate("http://foo3.com/");
  825. EXPECT_THAT(GetAllData(), UnorderedElementsAre(Pair(header_storage_key, _),
  826. Pair(tab_storage_key1, _),
  827. Pair(tab_storage_key3, _)));
  828. // If yet anothertab is opened, the entity for the closed tab should be
  829. // recycled.
  830. AddTab(kWindowId, "http://qux.com/", kTabId4);
  831. EXPECT_THAT(
  832. GetAllData(),
  833. UnorderedElementsAre(
  834. Pair(header_storage_key, _), Pair(tab_storage_key1, _),
  835. Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
  836. kLocalCacheGuid, kWindowId, kTabId4,
  837. kTabNodeId2, {"http://qux.com/"}))),
  838. Pair(tab_storage_key3, _)));
  839. }
  840. TEST_F(SessionSyncBridgeTest, ShouldRestoreLocalSessionWithFreedTab) {
  841. const int kWindowId1 = 1000001;
  842. const int kWindowId2 = 1000002;
  843. const int kTabId1 = 1000003;
  844. const int kTabId2 = 1000004;
  845. const int kTabId3 = 1000005;
  846. // Zero is the first assigned tab node ID.
  847. const int kTabNodeId1 = 0;
  848. const int kTabNodeId2 = 1;
  849. const int kTabNodeId3 = 2;
  850. AddWindow(kWindowId1);
  851. TestSyncedTabDelegate* tab1 = AddTab(kWindowId1, "http://foo.com/", kTabId1);
  852. AddTab(kWindowId1, "http://bar.com/", kTabId2);
  853. const std::string header_storage_key =
  854. SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
  855. const std::string tab_storage_key1 =
  856. SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId1);
  857. const std::string tab_storage_key2 =
  858. SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId2);
  859. const std::string tab_storage_key3 =
  860. SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId3);
  861. InitializeBridge();
  862. StartSyncing();
  863. ASSERT_THAT(GetData(header_storage_key),
  864. EntityDataHasSpecifics(MatchesHeader(
  865. kLocalCacheGuid, {kWindowId1}, {kTabId1, kTabId2})));
  866. // Close |kTabId2| and force reassociation by navigating in the remaining open
  867. // tab, leading to a freed tab entity.
  868. CloseTab(kTabId2);
  869. tab1->Navigate("http://foo2.com/");
  870. ASSERT_THAT(GetData(header_storage_key),
  871. EntityDataHasSpecifics(
  872. MatchesHeader(kLocalCacheGuid, {kWindowId1}, {kTabId1})));
  873. ShutdownBridge();
  874. ResetWindows();
  875. // The browser gets restarted with a new initial tab, for example because the
  876. // user chose "Continue where you left off".
  877. AddWindow(kWindowId2);
  878. AddTab(kWindowId2, "http://baz.com/", kTabId3);
  879. // Start the bridge again.
  880. InitializeBridge();
  881. StartSyncing();
  882. // Two tab nodes should be free at this point, because both tabs have been
  883. // closed. However, they are also unsynced (the commit hasn't completed),
  884. // which prevents their recycling, so a new tab node should be created.
  885. ASSERT_THAT(
  886. GetAllData(),
  887. UnorderedElementsAre(
  888. Pair(header_storage_key,
  889. EntityDataHasSpecifics(
  890. MatchesHeader(kLocalCacheGuid, {kWindowId2}, {kTabId3}))),
  891. Pair(tab_storage_key1,
  892. EntityDataHasSpecifics(
  893. MatchesTab(kLocalCacheGuid, kWindowId1, kTabId1, kTabNodeId1,
  894. {"http://foo.com/", "http://foo2.com/"}))),
  895. Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
  896. kLocalCacheGuid, kWindowId1, kTabId2,
  897. kTabNodeId2, {"http://bar.com/"}))),
  898. Pair(tab_storage_key3, EntityDataHasSpecifics(MatchesTab(
  899. kLocalCacheGuid, kWindowId2, kTabId3,
  900. kTabNodeId3, {"http://baz.com/"})))));
  901. }
  902. TEST_F(SessionSyncBridgeTest, ShouldDisableSyncAndReenable) {
  903. const int kWindowId = 1000001;
  904. const int kTabId = 1000002;
  905. AddWindow(kWindowId);
  906. AddTab(kWindowId, "http://foo.com/", kTabId);
  907. InitializeBridge();
  908. StartSyncing();
  909. const std::string header_storage_key =
  910. SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
  911. ASSERT_THAT(GetData(header_storage_key),
  912. EntityDataHasSpecifics(
  913. MatchesHeader(kLocalCacheGuid, {kWindowId}, {kTabId})));
  914. ASSERT_THAT(GetAllData(), Not(IsEmpty()));
  915. EXPECT_CALL(mock_processor(), ModelReadyToSync).Times(0);
  916. real_processor()->OnSyncStopping(syncer::CLEAR_METADATA);
  917. StartSyncing();
  918. ASSERT_THAT(GetData(header_storage_key),
  919. EntityDataHasSpecifics(
  920. MatchesHeader(kLocalCacheGuid, {kWindowId}, {kTabId})));
  921. }
  922. // Starting sync with no local data should just store the foreign entities in
  923. // the store and expose them via OpenTabsUIDelegate.
  924. TEST_F(SessionSyncBridgeTest, ShouldMergeForeignSession) {
  925. const std::string kForeignSessionTag = "foreignsessiontag";
  926. const int kForeignWindowId = 2000001;
  927. const int kForeignTabId = 2000002;
  928. const int kForeignTabNodeId = 2003;
  929. EXPECT_CALL(mock_processor(), UpdateStorageKey).Times(0);
  930. EXPECT_CALL(mock_processor(), Put).Times(0);
  931. InitializeBridge();
  932. const sync_pb::SessionSpecifics foreign_header =
  933. CreateHeaderSpecificsWithOneTab(kForeignSessionTag, kForeignWindowId,
  934. kForeignTabId);
  935. const sync_pb::SessionSpecifics foreign_tab =
  936. CreateTabSpecifics(kForeignSessionTag, kForeignWindowId, kForeignTabId,
  937. kForeignTabNodeId, "http://baz.com/");
  938. EXPECT_CALL(
  939. mock_processor(),
  940. Put(_, EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _)), _));
  941. EXPECT_CALL(mock_foreign_session_updated_cb(), Run());
  942. StartSyncing({foreign_header, foreign_tab});
  943. std::vector<const SyncedSession*> foreign_sessions;
  944. EXPECT_TRUE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
  945. &foreign_sessions));
  946. EXPECT_THAT(foreign_sessions,
  947. ElementsAre(MatchesSyncedSession(
  948. kForeignSessionTag,
  949. {{kForeignWindowId, std::vector<int>{kForeignTabId}}})));
  950. }
  951. TEST_F(SessionSyncBridgeTest, ShouldNotExposeForeignHeaderWithoutTabs) {
  952. const std::string kForeignSessionTag = "foreignsessiontag";
  953. const int kForeignWindowId = 2000001;
  954. const int kForeignTabId = 2000002;
  955. EXPECT_CALL(mock_processor(), UpdateStorageKey).Times(0);
  956. EXPECT_CALL(mock_processor(), Put).Times(0);
  957. InitializeBridge();
  958. const sync_pb::SessionSpecifics foreign_header =
  959. CreateHeaderSpecificsWithOneTab(kForeignSessionTag, kForeignWindowId,
  960. kForeignTabId);
  961. const std::string foreign_header_storage_key =
  962. SessionStore::GetHeaderStorageKey(kForeignSessionTag);
  963. EXPECT_CALL(
  964. mock_processor(),
  965. Put(_, EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _)), _));
  966. StartSyncing({foreign_header});
  967. ASSERT_THAT(GetData(foreign_header_storage_key), NotNull());
  968. std::vector<const SyncedSession*> foreign_sessions;
  969. EXPECT_FALSE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
  970. &foreign_sessions));
  971. // Restart bridge to verify the state doesn't change.
  972. ShutdownBridge();
  973. InitializeBridge();
  974. StartSyncing();
  975. ASSERT_THAT(GetData(foreign_header_storage_key), NotNull());
  976. EXPECT_FALSE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
  977. &foreign_sessions));
  978. }
  979. // Regression test for crbug.com/837517: Ensure that the bridge doesn't crash
  980. // and closed foreign tabs (|kForeignTabId2| in the test) are not exposed after
  981. // restarting the browser.
  982. TEST_F(SessionSyncBridgeTest, ShouldNotExposeClosedTabsAfterRestart) {
  983. const std::string kForeignSessionTag = "foreignsessiontag";
  984. const int kForeignWindowId = 2000001;
  985. const int kForeignTabId1 = 2000002;
  986. const int kForeignTabId2 = 2000003;
  987. const int kForeignTabNodeId1 = 2004;
  988. const int kForeignTabNodeId2 = 2005;
  989. // The header only lists a single tab |kForeignTabId1|, which becomes a mapped
  990. // tab.
  991. const sync_pb::SessionSpecifics foreign_header =
  992. CreateHeaderSpecificsWithOneTab(kForeignSessionTag, kForeignWindowId,
  993. kForeignTabId1);
  994. const sync_pb::SessionSpecifics foreign_tab1 =
  995. CreateTabSpecifics(kForeignSessionTag, kForeignWindowId, kForeignTabId1,
  996. kForeignTabNodeId1, "http://foo.com/");
  997. // |kForeignTabId2| is not present in the header, leading to an unmapped tab.
  998. const sync_pb::SessionSpecifics foreign_tab2 =
  999. CreateTabSpecifics(kForeignSessionTag, kForeignWindowId, kForeignTabId2,
  1000. kForeignTabNodeId2, "http://bar.com/");
  1001. InitializeBridge();
  1002. StartSyncing({foreign_header, foreign_tab1, foreign_tab2});
  1003. const std::string local_header_storage_key =
  1004. SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
  1005. const std::string foreign_header_storage_key =
  1006. SessionStore::GetHeaderStorageKey(kForeignSessionTag);
  1007. const std::string foreign_tab_storage_key1 =
  1008. SessionStore::GetTabStorageKey(kForeignSessionTag, kForeignTabNodeId1);
  1009. const std::string foreign_tab_storage_key2 =
  1010. SessionStore::GetTabStorageKey(kForeignSessionTag, kForeignTabNodeId2);
  1011. ASSERT_THAT(
  1012. GetAllData(),
  1013. UnorderedElementsAre(
  1014. Pair(local_header_storage_key, _),
  1015. Pair(foreign_header_storage_key,
  1016. EntityDataHasSpecifics(MatchesHeader(
  1017. kForeignSessionTag, {kForeignWindowId}, {kForeignTabId1}))),
  1018. Pair(foreign_tab_storage_key1,
  1019. EntityDataHasSpecifics(MatchesTab(
  1020. kForeignSessionTag, kForeignWindowId, kForeignTabId1,
  1021. kForeignTabNodeId1, {"http://foo.com/"}))),
  1022. Pair(foreign_tab_storage_key2,
  1023. EntityDataHasSpecifics(MatchesTab(
  1024. kForeignSessionTag, kForeignWindowId, kForeignTabId2,
  1025. kForeignTabNodeId2, {"http://bar.com/"})))));
  1026. // Mimic a browser restart, which should restore the very same state (and not
  1027. // crash!).
  1028. ShutdownBridge();
  1029. InitializeBridge();
  1030. StartSyncing();
  1031. EXPECT_THAT(GetAllData(),
  1032. UnorderedElementsAre(Pair(local_header_storage_key, _),
  1033. Pair(foreign_header_storage_key, _),
  1034. Pair(foreign_tab_storage_key1, _),
  1035. Pair(foreign_tab_storage_key2, _)));
  1036. }
  1037. TEST_F(SessionSyncBridgeTest, ShouldHandleRemoteDeletion) {
  1038. const std::string kForeignSessionTag = "foreignsessiontag";
  1039. const int kForeignWindowId = 2000001;
  1040. const int kForeignTabId = 2000002;
  1041. const int kForeignTabNodeId = 2003;
  1042. InitializeBridge();
  1043. const sync_pb::SessionSpecifics foreign_header =
  1044. CreateHeaderSpecificsWithOneTab(kForeignSessionTag, kForeignWindowId,
  1045. kForeignTabId);
  1046. const sync_pb::SessionSpecifics foreign_tab =
  1047. CreateTabSpecifics(kForeignSessionTag, kForeignWindowId, kForeignTabId,
  1048. kForeignTabNodeId, "http://baz.com/");
  1049. StartSyncing({foreign_header, foreign_tab});
  1050. sync_pb::ModelTypeState state;
  1051. state.set_initial_sync_done(true);
  1052. // Mimic receiving a commit ack for the local header entity, to later be able
  1053. // to verify HasLocalChangesForTest() without interferences from the local
  1054. // session.
  1055. ASSERT_TRUE(real_processor()->HasLocalChangesForTest());
  1056. real_processor()->OnCommitCompleted(
  1057. state, {CreateSuccessResponse(kLocalCacheGuid)},
  1058. /*error_response_list=*/FailedCommitResponseDataList());
  1059. ASSERT_FALSE(real_processor()->HasLocalChangesForTest());
  1060. const sessions::SessionTab* foreign_session_tab = nullptr;
  1061. ASSERT_TRUE(bridge()->GetOpenTabsUIDelegate()->GetForeignTab(
  1062. kForeignSessionTag, SessionID::FromSerializedValue(kForeignTabId),
  1063. &foreign_session_tab));
  1064. ASSERT_THAT(foreign_session_tab, NotNull());
  1065. std::vector<const SyncedSession*> foreign_sessions;
  1066. ASSERT_TRUE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
  1067. &foreign_sessions));
  1068. ASSERT_THAT(foreign_sessions,
  1069. ElementsAre(MatchesSyncedSession(
  1070. kForeignSessionTag,
  1071. {{kForeignWindowId, std::vector<int>{kForeignTabId}}})));
  1072. ASSERT_TRUE(real_processor()->IsTrackingMetadata());
  1073. ASSERT_TRUE(real_processor()->IsTrackingEntityForTest(
  1074. SessionStore::GetHeaderStorageKey(kForeignSessionTag)));
  1075. ASSERT_TRUE(real_processor()->IsTrackingEntityForTest(
  1076. SessionStore::GetTabStorageKey(kForeignSessionTag, kForeignTabNodeId)));
  1077. ASSERT_FALSE(real_processor()->HasLocalChangesForTest());
  1078. // Mimic receiving a remote deletion of the foreign session.
  1079. EXPECT_CALL(mock_foreign_session_updated_cb(), Run());
  1080. syncer::UpdateResponseDataList updates;
  1081. updates.push_back(
  1082. CreateTombstone(SessionStore::GetClientTag(foreign_header)));
  1083. real_processor()->OnUpdateReceived(state, std::move(updates));
  1084. foreign_session_tab = nullptr;
  1085. EXPECT_FALSE(bridge()->GetOpenTabsUIDelegate()->GetForeignTab(
  1086. kForeignSessionTag, SessionID::FromSerializedValue(kForeignTabId),
  1087. &foreign_session_tab));
  1088. EXPECT_FALSE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
  1089. &foreign_sessions));
  1090. EXPECT_FALSE(real_processor()->HasLocalChangesForTest());
  1091. const std::string header_storage_key =
  1092. SessionStore::GetHeaderStorageKey(kForeignSessionTag);
  1093. const std::string tab_storage_key =
  1094. SessionStore::GetTabStorageKey(kForeignSessionTag, kForeignTabNodeId);
  1095. EXPECT_FALSE(real_processor()->IsTrackingEntityForTest(header_storage_key));
  1096. EXPECT_FALSE(real_processor()->IsTrackingEntityForTest(tab_storage_key));
  1097. // Verify that both entities have been deleted from storage.
  1098. {
  1099. base::RunLoop loop;
  1100. underlying_store()->ReadData(
  1101. {header_storage_key, tab_storage_key},
  1102. base::BindLambdaForTesting(
  1103. [&](const absl::optional<syncer::ModelError>& error,
  1104. std::unique_ptr<syncer::ModelTypeStore::RecordList>
  1105. data_records,
  1106. std::unique_ptr<syncer::ModelTypeStore::IdList>
  1107. missing_id_list) {
  1108. EXPECT_THAT(data_records, Pointee(IsEmpty()));
  1109. EXPECT_THAT(
  1110. missing_id_list,
  1111. Pointee(ElementsAre(header_storage_key, tab_storage_key)));
  1112. loop.Quit();
  1113. }));
  1114. loop.Run();
  1115. }
  1116. // Verify that the sync metadata for both entities have been deleted too.
  1117. {
  1118. base::RunLoop loop;
  1119. underlying_store()->ReadAllMetadata(base::BindLambdaForTesting(
  1120. [&](const absl::optional<syncer::ModelError>& error,
  1121. std::unique_ptr<syncer::MetadataBatch> metadata_batch) {
  1122. syncer::EntityMetadataMap entity_metadata_map =
  1123. metadata_batch->TakeAllMetadata();
  1124. EXPECT_EQ(0U, entity_metadata_map.count(header_storage_key));
  1125. EXPECT_EQ(0U, entity_metadata_map.count(tab_storage_key));
  1126. loop.Quit();
  1127. }));
  1128. loop.Run();
  1129. }
  1130. }
  1131. TEST_F(SessionSyncBridgeTest, ShouldIgnoreRemoteDeletionOfLocalTab) {
  1132. const int kWindowId1 = 1000001;
  1133. const int kTabId1 = 1000002;
  1134. const int kTabNodeId1 = 0;
  1135. AddWindow(kWindowId1);
  1136. AddTab(kWindowId1, "http://foo.com/", kTabId1);
  1137. InitializeBridge();
  1138. StartSyncing();
  1139. const std::string header_storage_key =
  1140. SessionStore::GetHeaderStorageKey(kLocalCacheGuid);
  1141. const std::string tab_storage_key1 =
  1142. SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId1);
  1143. const std::string tab_client_tag1 =
  1144. SessionStore::GetTabClientTagForTest(kLocalCacheGuid, kTabNodeId1);
  1145. ASSERT_THAT(
  1146. GetAllData(),
  1147. UnorderedElementsAre(
  1148. Pair(header_storage_key,
  1149. EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _))),
  1150. Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
  1151. kLocalCacheGuid, kWindowId1, kTabId1,
  1152. kTabNodeId1, {"http://foo.com/"})))));
  1153. ASSERT_TRUE(real_processor()->IsTrackingMetadata());
  1154. ASSERT_TRUE(real_processor()->HasLocalChangesForTest());
  1155. // Mimic receiving a commit ack for both the tab and the header entity,
  1156. // because otherwise it will be treated as conflict, and then local wins.
  1157. sync_pb::ModelTypeState state;
  1158. state.set_initial_sync_done(true);
  1159. real_processor()->OnCommitCompleted(
  1160. state,
  1161. {CreateSuccessResponse(tab_client_tag1),
  1162. CreateSuccessResponse(kLocalCacheGuid)},
  1163. /*error_response_list=*/FailedCommitResponseDataList());
  1164. ASSERT_FALSE(real_processor()->HasLocalChangesForTest());
  1165. // Mimic receiving a remote deletion of both entities.
  1166. EXPECT_CALL(mock_processor(), Put).Times(0);
  1167. syncer::UpdateResponseDataList updates;
  1168. updates.push_back(CreateTombstone(kLocalCacheGuid));
  1169. updates.push_back(CreateTombstone(tab_client_tag1));
  1170. real_processor()->OnUpdateReceived(state, std::move(updates));
  1171. // State should remain unchanged (deletions ignored).
  1172. EXPECT_THAT(
  1173. GetAllData(),
  1174. UnorderedElementsAre(
  1175. Pair(header_storage_key,
  1176. EntityDataHasSpecifics(MatchesHeader(kLocalCacheGuid, _, _))),
  1177. Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
  1178. kLocalCacheGuid, kWindowId1, kTabId1,
  1179. kTabNodeId1, {"http://foo.com/"})))));
  1180. // Creating a new tab locally should trigger Put() calls for *all* entities
  1181. // (because the local data was out of sync).
  1182. const int kWindowId2 = 2000001;
  1183. const int kTabId2 = 2000002;
  1184. const int kTabNodeId2 = 1;
  1185. const std::string tab_storage_key2 =
  1186. SessionStore::GetTabStorageKey(kLocalCacheGuid, kTabNodeId2);
  1187. // Window creation already triggers a header update, which will be overriden
  1188. // later below.
  1189. testing::Expectation put_transient_header =
  1190. EXPECT_CALL(mock_processor(), Put(header_storage_key, _, _));
  1191. AddWindow(kWindowId2);
  1192. // In the current implementation, some of the updates are reported to the
  1193. // processor twice, but that's OK because the processor can detect it.
  1194. EXPECT_CALL(mock_processor(),
  1195. Put(header_storage_key,
  1196. EntityDataHasSpecifics(MatchesHeader(
  1197. kLocalCacheGuid, ElementsAre(kWindowId1, kWindowId2),
  1198. ElementsAre(kTabId1, kTabId2))),
  1199. _))
  1200. .Times(2)
  1201. .After(put_transient_header);
  1202. EXPECT_CALL(mock_processor(), Put(tab_storage_key1,
  1203. EntityDataHasSpecifics(MatchesTab(
  1204. kLocalCacheGuid, kWindowId1, kTabId1,
  1205. kTabNodeId1, {"http://foo.com/"})),
  1206. _));
  1207. EXPECT_CALL(mock_processor(), Put(tab_storage_key2,
  1208. EntityDataHasSpecifics(MatchesTab(
  1209. kLocalCacheGuid, kWindowId2, kTabId2,
  1210. kTabNodeId2, {"http://bar.com/"})),
  1211. _))
  1212. .Times(2);
  1213. AddTab(kWindowId2, "http://bar.com/", kTabId2);
  1214. EXPECT_THAT(
  1215. GetAllData(),
  1216. UnorderedElementsAre(
  1217. Pair(header_storage_key,
  1218. EntityDataHasSpecifics(MatchesHeader(
  1219. kLocalCacheGuid, ElementsAre(kWindowId1, kWindowId2),
  1220. ElementsAre(kTabId1, kTabId2)))),
  1221. Pair(tab_storage_key1,
  1222. EntityDataHasSpecifics(
  1223. MatchesTab(kLocalCacheGuid, /*window_id=*/_, /*tab_id=*/_,
  1224. kTabNodeId1, {"http://foo.com/"}))),
  1225. Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
  1226. kLocalCacheGuid, kWindowId2, kTabId2,
  1227. kTabNodeId2, {"http://bar.com/"})))));
  1228. // Run until idle because PostTask() is used to invoke ResubmitLocalSession().
  1229. base::RunLoop().RunUntilIdle();
  1230. }
  1231. // Verifies that a foreign session can be deleted by the user from the history
  1232. // UI (via OpenTabsUIDelegate).
  1233. TEST_F(SessionSyncBridgeTest, ShouldDeleteForeignSessionFromUI) {
  1234. const std::string kForeignSessionTag = "foreignsessiontag";
  1235. const int kForeignWindowId = 2000001;
  1236. const int kForeignTabId = 2000002;
  1237. const int kForeignTabNodeId = 2003;
  1238. InitializeBridge();
  1239. const sync_pb::SessionSpecifics foreign_header =
  1240. CreateHeaderSpecificsWithOneTab(kForeignSessionTag, kForeignWindowId,
  1241. kForeignTabId);
  1242. const sync_pb::SessionSpecifics foreign_tab =
  1243. CreateTabSpecifics(kForeignSessionTag, kForeignWindowId, kForeignTabId,
  1244. kForeignTabNodeId, "http://baz.com/");
  1245. StartSyncing({foreign_header, foreign_tab});
  1246. const std::string foreign_header_storage_key =
  1247. SessionStore::GetHeaderStorageKey(kForeignSessionTag);
  1248. const std::string foreign_tab_storage_key =
  1249. SessionStore::GetTabStorageKey(kForeignSessionTag, kForeignTabNodeId);
  1250. // Test fixture expects the two foreign entities in the model as well as the
  1251. // underlying store.
  1252. ASSERT_THAT(GetData(foreign_header_storage_key), NotNull());
  1253. ASSERT_THAT(GetData(foreign_tab_storage_key), NotNull());
  1254. const sessions::SessionTab* foreign_session_tab = nullptr;
  1255. ASSERT_TRUE(bridge()->GetOpenTabsUIDelegate()->GetForeignTab(
  1256. kForeignSessionTag, SessionID::FromSerializedValue(kForeignTabId),
  1257. &foreign_session_tab));
  1258. ASSERT_THAT(foreign_session_tab, NotNull());
  1259. std::vector<const SyncedSession*> foreign_sessions;
  1260. ASSERT_TRUE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
  1261. &foreign_sessions));
  1262. ASSERT_THAT(foreign_sessions,
  1263. ElementsAre(MatchesSyncedSession(
  1264. kForeignSessionTag,
  1265. {{kForeignWindowId, std::vector<int>{kForeignTabId}}})));
  1266. ASSERT_TRUE(real_processor()->IsTrackingMetadata());
  1267. // Mimic the user requesting a session deletion from the UI.
  1268. EXPECT_CALL(mock_processor(), Delete(foreign_header_storage_key, _));
  1269. EXPECT_CALL(mock_processor(), Delete(foreign_tab_storage_key, _));
  1270. EXPECT_CALL(mock_foreign_session_updated_cb(), Run());
  1271. bridge()->GetOpenTabsUIDelegate()->DeleteForeignSession(kForeignSessionTag);
  1272. // Verify what gets exposed to the UI.
  1273. foreign_session_tab = nullptr;
  1274. EXPECT_FALSE(bridge()->GetOpenTabsUIDelegate()->GetForeignTab(
  1275. kForeignSessionTag, SessionID::FromSerializedValue(kForeignTabId),
  1276. &foreign_session_tab));
  1277. EXPECT_FALSE(bridge()->GetOpenTabsUIDelegate()->GetAllForeignSessions(
  1278. &foreign_sessions));
  1279. // Verify store.
  1280. EXPECT_THAT(GetData(foreign_header_storage_key), IsNull());
  1281. EXPECT_THAT(GetData(foreign_tab_storage_key), IsNull());
  1282. }
  1283. // Verifies that attempts to delete the local session from the UI are ignored,
  1284. // although the UI sholdn't really be offering that option.
  1285. TEST_F(SessionSyncBridgeTest, ShouldIgnoreLocalSessionDeletionFromUI) {
  1286. InitializeBridge();
  1287. StartSyncing();
  1288. EXPECT_CALL(mock_foreign_session_updated_cb(), Run()).Times(0);
  1289. EXPECT_CALL(mock_processor(), Delete).Times(0);
  1290. bridge()->GetOpenTabsUIDelegate()->DeleteForeignSession(kLocalCacheGuid);
  1291. const SyncedSession* session = nullptr;
  1292. EXPECT_TRUE(bridge()->GetOpenTabsUIDelegate()->GetLocalSession(&session));
  1293. EXPECT_THAT(session, NotNull());
  1294. EXPECT_THAT(GetData(SessionStore::GetHeaderStorageKey(kLocalCacheGuid)),
  1295. NotNull());
  1296. }
  1297. // Verifies that receiving an empty update list does not broadcast a foreign
  1298. // session change via the corresponding callback.
  1299. TEST_F(SessionSyncBridgeTest, ShouldNotBroadcastUpdatesIfEmpty) {
  1300. InitializeBridge();
  1301. StartSyncing();
  1302. EXPECT_CALL(mock_foreign_session_updated_cb(), Run()).Times(0);
  1303. // Mimic receiving an empty list of remote updates.
  1304. sync_pb::ModelTypeState state;
  1305. state.set_initial_sync_done(true);
  1306. real_processor()->OnUpdateReceived(state, {});
  1307. }
  1308. TEST_F(SessionSyncBridgeTest, ShouldDoGarbageCollection) {
  1309. // We construct two identical sessions, one modified recently, one modified
  1310. // more than |kStaleSessionThreshold| ago (14 days ago).
  1311. const base::Time stale_mtime = base::Time::Now() - base::Days(15);
  1312. const base::Time recent_mtime = base::Time::Now() - base::Days(13);
  1313. const std::string kStaleSessionTag = "stalesessiontag";
  1314. const std::string kRecentSessionTag = "recentsessiontag";
  1315. const int kWindowId = 2000001;
  1316. const int kTabId = 2000002;
  1317. const int kTabNodeId = 2003;
  1318. InitializeBridge();
  1319. StartSyncing();
  1320. // Construct a remote update.
  1321. sync_pb::ModelTypeState state;
  1322. state.set_initial_sync_done(true);
  1323. syncer::UpdateResponseDataList updates;
  1324. // Two entities belong to a recent session.
  1325. updates.push_back(SpecificsToUpdateResponse(
  1326. CreateHeaderSpecificsWithOneTab(kStaleSessionTag, kWindowId, kTabId),
  1327. stale_mtime));
  1328. updates.push_back(SpecificsToUpdateResponse(
  1329. CreateTabSpecifics(kStaleSessionTag, kWindowId, kTabId, kTabNodeId,
  1330. "http://baz.com/"),
  1331. stale_mtime));
  1332. updates.push_back(SpecificsToUpdateResponse(
  1333. CreateHeaderSpecificsWithOneTab(kRecentSessionTag, kWindowId, kTabId),
  1334. recent_mtime));
  1335. updates.push_back(SpecificsToUpdateResponse(
  1336. CreateTabSpecifics(kRecentSessionTag, kWindowId, kTabId, kTabNodeId,
  1337. "http://baz.com/"),
  1338. recent_mtime));
  1339. // During garbage collection, we expect |kStaleSessionTag| to be deleted.
  1340. EXPECT_CALL(mock_processor(),
  1341. Delete(SessionStore::GetHeaderStorageKey(kStaleSessionTag), _));
  1342. EXPECT_CALL(
  1343. mock_processor(),
  1344. Delete(SessionStore::GetTabStorageKey(kStaleSessionTag, kTabNodeId), _));
  1345. EXPECT_CALL(mock_foreign_session_updated_cb(), Run()).Times(AtLeast(1));
  1346. real_processor()->OnUpdateReceived(state, std::move(updates));
  1347. }
  1348. TEST_F(SessionSyncBridgeTest, ShouldReturnBrowserTypeInGetData) {
  1349. const int kWindowId = 1000001;
  1350. const int kTabId = 1000002;
  1351. AddWindow(kWindowId, sync_pb::SyncEnums_BrowserType_TYPE_CUSTOM_TAB);
  1352. AddTab(kWindowId, "http://foo.com/", kTabId);
  1353. InitializeBridge();
  1354. StartSyncing();
  1355. std::unique_ptr<EntityData> tab_data = GetData(
  1356. SessionStore::GetTabStorageKey(kLocalCacheGuid, /*tab_node_id=*/0));
  1357. ASSERT_THAT(tab_data, NotNull());
  1358. EXPECT_EQ(sync_pb::SyncEnums_BrowserType_TYPE_CUSTOM_TAB,
  1359. tab_data->specifics.session().tab().browser_type());
  1360. }
  1361. } // namespace
  1362. } // namespace sync_sessions