File: two_client_history_sync_test.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (388 lines) | stat: -rw-r--r-- 15,672 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/uuid.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history/web_history_service_factory.h"
#include "chrome/browser/sync/test/integration/bookmarks_helper.h"
#include "chrome/browser/sync/test/integration/history_helper.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "components/history/content/browser/history_context_helper.h"
#include "components/history/core/browser/browsing_history_driver.h"
#include "components/history/core/browser/browsing_history_service.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/history_types.h"
#include "components/sync/base/features.h"
#include "components/sync/protocol/history_specifics.pb.h"
#include "components/sync/service/sync_service_impl.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"

#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#endif

using history_helper::HasVisitDuration;
using history_helper::UrlIs;
using history_helper::VisitRowHasDuration;
using testing::AllOf;
using testing::IsEmpty;
using testing::SizeIs;
using testing::UnorderedElementsAre;

namespace {

class TestBrowsingHistoryDriver : public history::BrowsingHistoryDriver {
 public:
  explicit TestBrowsingHistoryDriver(history::WebHistoryService* web_history)
      : web_history_(web_history) {}
  ~TestBrowsingHistoryDriver() override = default;

  bool AllowHistoryDeletions() override { return true; }
  history::WebHistoryService* GetWebHistoryService() override {
    return web_history_;
  }

  void OnRemoveVisits(
      const std::vector<history::ExpireHistoryArgs>& expire_list) override {}
  bool ShouldHideWebHistoryUrl(const GURL& url) override { return false; }
  void ShouldShowNoticeAboutOtherFormsOfBrowsingHistory(
      const syncer::SyncService* sync_service,
      history::WebHistoryService* history_service,
      base::OnceCallback<void(bool)> callback) override {}

 private:
  const raw_ptr<history::WebHistoryService> web_history_;
};

class TwoClientHistorySyncTest : public SyncTest {
 public:
  TwoClientHistorySyncTest() : SyncTest(TWO_CLIENT) {
    // TODO(crbug.com/40248833): Use HTTPS URLs in tests to avoid having to
    // disable this feature.
    features_.InitAndDisableFeature(features::kHttpsUpgrades);
  }
  ~TwoClientHistorySyncTest() override = default;

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");

    ASSERT_TRUE(embedded_test_server()->Start());

    SyncTest::SetUpOnMainThread();
  }

  bool SetupClients() override {
    if (!SyncTest::SetupClients()) {
      return false;
    }

    // SyncTest doesn't create any tabs in the profiles/browsers it creates.
    // Create an "empty" tab here, so that NavigateToURL() will have a non-null
    // WebContents to navigate in.
    for (int i = 0; i < num_clients(); ++i) {
      if (!AddTabAtIndexToBrowser(GetBrowser(i), 0, GURL("about:blank"),
                                  ui::PAGE_TRANSITION_AUTO_TOPLEVEL)) {
        return false;
      }
    }

    return true;
  }

  void NavigateToURL(
      int profile_index,
      const GURL& url,
      ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED) {
    content::NavigationController::LoadURLParams params(url);
    params.transition_type = transition;
    content::NavigateToURLBlockUntilNavigationsComplete(
        GetActiveWebContents(profile_index), params, 1);

    // Ensure the navigation succeeded (i.e. whatever test URL was passed in was
    // actually valid).
    ASSERT_EQ(200, GetActiveWebContents(profile_index)
                       ->GetController()
                       .GetLastCommittedEntry()
                       ->GetHttpStatusCode());
  }

  [[nodiscard]] bool WaitForServerHistory(
      testing::Matcher<std::vector<sync_pb::HistorySpecifics>> matcher) {
    return history_helper::ServerHistoryMatchChecker(matcher).Wait();
  }

  [[nodiscard]] bool WaitForLocalHistory(
      int profile_index,
      const std::map<GURL, testing::Matcher<std::vector<history::VisitRow>>>&
          matchers) {
    return history_helper::LocalHistoryMatchChecker(
               profile_index, GetSyncService(profile_index), matchers)
        .Wait();
  }

  content::WebContents* GetActiveWebContents(int profile_index) {
    // Note: chrome_test_utils::GetActiveWebContents() doesn't work, since it
    // uses the profile created by InProcessBrowserTest, not the profiles from
    // SyncTest.
    return GetBrowser(profile_index)->tab_strip_model()->GetActiveWebContents();
  }

 private:
  base::test::ScopedFeatureList features_;
};

// Very simple test; its main reason for existence is that it's the only E2E
// test covering history.
IN_PROC_BROWSER_TEST_F(TwoClientHistorySyncTest, E2E_ENABLED(SyncsUrl)) {
  ASSERT_TRUE(ResetSyncForPrimaryAccount());
  // Use a randomized URL to prevent test collisions.
  const std::u16string kHistoryUrl = base::ASCIIToUTF16(base::StringPrintf(
      "http://www.add-history.google.com/%s",
      base::Uuid::GenerateRandomV4().AsLowercaseString().c_str()));
  ASSERT_TRUE(SetupSync());

  // Populate one client with a URL, wait for it to sync to the other.
  GURL new_url(kHistoryUrl);
  history_helper::AddUrlToHistory(0, new_url);
  EXPECT_TRUE(WaitForLocalHistory(1, {{new_url, SizeIs(1)}}));
}

IN_PROC_BROWSER_TEST_F(TwoClientHistorySyncTest, SyncsVisitForBookmarkedUrl) {
  GURL bookmark_url("http://www.bookmark.google.com/");
  GURL bookmark_icon_url("http://www.bookmark.google.com/favicon.ico");
  ASSERT_TRUE(SetupClients());
  // Create a bookmark.
  const bookmarks::BookmarkNode* node = bookmarks_helper::AddURL(
      0, bookmarks_helper::IndexedURLTitle(0), bookmark_url);
  bookmarks_helper::SetFavicon(0, node, bookmark_icon_url,
                               bookmarks_helper::CreateFavicon(SK_ColorWHITE),
                               bookmarks_helper::FROM_UI);
  ASSERT_TRUE(SetupSync());

  // A row in the DB for client 1 should have been created as a result of
  // syncing the bookmark.
  history::URLRow row;
  ASSERT_TRUE(history_helper::GetUrlFromClient(1, bookmark_url, &row));

  // Now, add a visit for client 0 to the bookmark URL and sync it over - this
  // should not cause a crash.
  history_helper::AddUrlToHistory(0, bookmark_url);

  ASSERT_TRUE(WaitForLocalHistory(0, {{bookmark_url, SizeIs(1)}}));
  EXPECT_TRUE(WaitForLocalHistory(1, {{bookmark_url, SizeIs(1)}}));
}

IN_PROC_BROWSER_TEST_F(TwoClientHistorySyncTest, SyncsUrlDeletion) {
  ASSERT_TRUE(SetupSync());

  // Navigate to two URLs on the first client.
  GURL url1 =
      embedded_test_server()->GetURL("synced1.com", "/sync/simple.html");
  NavigateToURL(0, url1);
  GURL url2 =
      embedded_test_server()->GetURL("synced2.com", "/sync/simple.html");
  NavigateToURL(0, url2);

  // Wait for the visits to arrive on the second client.
  ASSERT_TRUE(WaitForLocalHistory(1, {{url1, SizeIs(1)}, {url2, SizeIs(1)}}));

  // Delete the first URL on the first client.
  history::HistoryService* history_service =
      HistoryServiceFactory::GetForProfile(GetProfile(0),
                                           ServiceAccessType::EXPLICIT_ACCESS);
  history::WebHistoryService* web_history_service =
      WebHistoryServiceFactory::GetForProfile(GetProfile(0));
  history_service->DeleteLocalAndRemoteUrl(web_history_service, url1);

  // Wait for the deletion to apply to the second client: The first URL should
  // be gone, but the second one should remain.
  EXPECT_TRUE(WaitForLocalHistory(1, {{url1, IsEmpty()}, {url2, SizeIs(1)}}));
}

IN_PROC_BROWSER_TEST_F(TwoClientHistorySyncTest, SyncsTimeRangeDeletion) {
  ASSERT_TRUE(SetupSync());

  // Navigate to three URLs on the first client.
  GURL url1 =
      embedded_test_server()->GetURL("synced1.com", "/sync/simple.html");
  NavigateToURL(0, url1);
  GURL url2 =
      embedded_test_server()->GetURL("synced2.com", "/sync/simple.html");
  NavigateToURL(0, url2);
  GURL url3 =
      embedded_test_server()->GetURL("synced3.com", "/sync/simple.html");
  NavigateToURL(0, url3);

  // Wait for the visits to arrive on the second client.
  ASSERT_TRUE(WaitForLocalHistory(
      1, {{url1, SizeIs(1)}, {url2, SizeIs(1)}, {url3, SizeIs(1)}}));

  // Get the visit timestamps.
  history::VisitVector visits1 =
      history_helper::GetVisitsForURLFromClient(/*index=*/0, url1);
  ASSERT_EQ(visits1.size(), 1u);
  base::Time time1 = visits1[0].visit_time;
  history::VisitVector visits2 =
      history_helper::GetVisitsForURLFromClient(/*index=*/0, url2);
  ASSERT_EQ(visits2.size(), 1u);
  base::Time time2 = visits2[0].visit_time;
  history::VisitVector visits3 =
      history_helper::GetVisitsForURLFromClient(/*index=*/0, url3);
  ASSERT_EQ(visits3.size(), 1u);
  base::Time time3 = visits3[0].visit_time;

  // Delete a time range that covers exactly the second visit.
  base::Time begin = time1 + (time2 - time1) / 2;
  ASSERT_LT(time1, begin);
  ASSERT_LT(begin, time2);
  base::Time end = time2 + (time3 - time2) / 2;
  ASSERT_LT(time2, end);
  ASSERT_LT(end, time3);

  history::HistoryService* history_service =
      HistoryServiceFactory::GetForProfile(GetProfile(0),
                                           ServiceAccessType::EXPLICIT_ACCESS);
  history::WebHistoryService* web_history_service =
      WebHistoryServiceFactory::GetForProfile(GetProfile(0));
  base::CancelableTaskTracker task_tracker;
  history_service->DeleteLocalAndRemoteHistoryBetween(
      web_history_service, begin, end, history::kNoAppIdFilter,
      base::DoNothing(), &task_tracker);

  // Wait for the deletion to apply to the second client: The second URL should
  // be gone, but the first and third should remain.
  EXPECT_TRUE(WaitForLocalHistory(
      1, {{url1, SizeIs(1)}, {url2, IsEmpty()}, {url3, SizeIs(1)}}));
}

IN_PROC_BROWSER_TEST_F(TwoClientHistorySyncTest, SyncsVisitsDeletion) {
  ASSERT_TRUE(SetupSync());

  // Navigate to two URLs on the first client.
  GURL url1 =
      embedded_test_server()->GetURL("synced1.com", "/sync/simple.html");
  NavigateToURL(0, url1);
  GURL url2 =
      embedded_test_server()->GetURL("synced2.com", "/sync/simple.html");
  NavigateToURL(0, url2);

  // Navigate to the URLs again, so there are two visits per URL.
  NavigateToURL(0, url1);
  NavigateToURL(0, url2);

  // Wait for the visits to arrive on the second client.
  ASSERT_TRUE(WaitForLocalHistory(1, {{url1, SizeIs(2)}, {url2, SizeIs(2)}}));

  // Get the visit timestamps corresponding to the first URL.
  history::VisitVector visits1 =
      history_helper::GetVisitsForURLFromClient(/*index=*/0, url1);
  ASSERT_EQ(visits1.size(), 2u);
  base::Time time1a = visits1[0].visit_time;
  base::Time time1b = visits1[1].visit_time;

  // Specify deletions for both of the first URL's visits.
  // Logic similar to `BrowsingHistoryHandler::HandleRemoveVisits()`.
  history::BrowsingHistoryService::HistoryEntry entry1;
  entry1.url = url1;
  entry1.all_timestamps.insert(time1a);
  entry1.all_timestamps.insert(time1b);
  std::vector<history::BrowsingHistoryService::HistoryEntry> items_to_remove;
  items_to_remove.push_back(entry1);

  // Apply the deletions. This requires creating a BrowsingHistoryService
  // instance.
  history::HistoryService* history_service =
      HistoryServiceFactory::GetForProfile(GetProfile(0),
                                           ServiceAccessType::EXPLICIT_ACCESS);
  history::WebHistoryService* web_history_service =
      WebHistoryServiceFactory::GetForProfile(GetProfile(0));
  TestBrowsingHistoryDriver driver(web_history_service);
  history::BrowsingHistoryService browsing_history_service(
      &driver, history_service, GetSyncService(0));

  browsing_history_service.RemoveVisits(items_to_remove);
  // Note that this API applies deletions to all matching visits on the same
  // day, so it's hard to test the deletion of only *some* of a URL's visits.

  // Wait for the deletions to apply to the second client: Both visits to the
  // first URL should be gone, but the second URL's visits should remain.
  EXPECT_TRUE(WaitForLocalHistory(1, {{url1, IsEmpty()}, {url2, SizeIs(2)}}));
}

IN_PROC_BROWSER_TEST_F(TwoClientHistorySyncTest,
                       DoesNotSyncBrowsingTopicsEligibility) {
  ASSERT_TRUE(SetupSync());

  // Navigate to some URL.
  GURL url1 =
      embedded_test_server()->GetURL("synced1.com", "/sync/simple.html");
  NavigateToURL(0, url1);

  // (Hackily) mark the just-added history entry as eligible for browsing
  // topics. This field should *not* be synced.
  history::HistoryService* history_service =
      HistoryServiceFactory::GetForProfile(GetProfile(0),
                                           ServiceAccessType::EXPLICIT_ACCESS);

  history::ContextID context_id =
      history::ContextIDForWebContents(GetActiveWebContents(0));
  int nav_entry_id = GetActiveWebContents(0)
                         ->GetController()
                         .GetLastCommittedEntry()
                         ->GetUniqueID();

  history_service->SetBrowsingTopicsAllowed(context_id, nav_entry_id, url1);

  // Navigate somewhere else, to "complete" the first visit and populate its
  // duration.
  GURL url2 =
      embedded_test_server()->GetURL("synced2.com", "/sync/simple.html");
  NavigateToURL(0, url2);

  // Ensure the visit arrived on the server, including the duration. The
  // browsing-topics-allowed bit should *not* be here, but there's no real way
  // to check for its absence on the server. Instead, we'll check that on the
  // second client, below.
  EXPECT_TRUE(WaitForServerHistory(UnorderedElementsAre(
      AllOf(UrlIs(url1.spec()), HasVisitDuration()), UrlIs(url2))));

  // Wait for the visit to arrive on the second client.
  EXPECT_TRUE(WaitForLocalHistory(
      1, {{url1, UnorderedElementsAre(VisitRowHasDuration())}}));

  // Finally, check that the local visit (on the first client) has the
  // browsing-topics-allowed bit set, but the synced visit (on the second
  // client) does not.
  std::vector<history::AnnotatedVisit> local_visits =
      history_helper::GetAnnotatedVisitsForURLFromClient(0, url1);
  ASSERT_EQ(local_visits.size(), 1u);
  history::AnnotatedVisit local_visit = local_visits[0];
  EXPECT_TRUE(local_visit.content_annotations.annotation_flags &
              history::VisitContentAnnotationFlag::kBrowsingTopicsEligible);

  std::vector<history::AnnotatedVisit> synced_visits =
      history_helper::GetAnnotatedVisitsForURLFromClient(1, url1);
  ASSERT_EQ(synced_visits.size(), 1u);
  history::AnnotatedVisit synced_visit = synced_visits[0];
  EXPECT_FALSE(synced_visit.content_annotations.annotation_flags &
               history::VisitContentAnnotationFlag::kBrowsingTopicsEligible);
  // Just as a sanity check: Other visit fields *did* arrive.
  EXPECT_FALSE(synced_visit.visit_row.visit_duration.is_zero());
}

}  // namespace