File: navigation_predictor_browsertest.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 (861 lines) | stat: -rw-r--r-- 35,266 bytes parent folder | download | duplicates (6)
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
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <tuple>

#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/navigation_predictor/navigation_predictor.h"
#include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h"
#include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service_factory.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/search_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_manager.h"
#include "components/search_engines/template_url_service.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/base/features.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/embedded_test_server_connection_listener.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
#include "url/url_constants.h"

namespace {

class NavigationPredictorBrowserTest
    : public subresource_filter::SubresourceFilterBrowserTest {
 public:
  NavigationPredictorBrowserTest() {
    // Report all anchors to avoid non-deterministic behavior.
    std::map<std::string, std::string> params;
    params["random_anchor_sampling_period"] = "1";
    params["traffic_client_enabled_percent"] = "100";

    feature_list_.InitAndEnableFeatureWithParameters(
        blink::features::kNavigationPredictor, params);

    NavigationPredictor::DisableRendererMetricSendingDelayForTesting();
  }

  NavigationPredictorBrowserTest(const NavigationPredictorBrowserTest&) =
      delete;
  NavigationPredictorBrowserTest& operator=(
      const NavigationPredictorBrowserTest&) = delete;

  void SetUp() override {
    https_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    https_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    https_server_->ServeFilesFromSourceDirectory(
        "chrome/test/data/navigation_predictor");
    ASSERT_TRUE(https_server_->Start());

    http_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTP);
    http_server_->ServeFilesFromSourceDirectory(
        "chrome/test/data/navigation_predictor");
    ASSERT_TRUE(http_server_->Start());

    subresource_filter::SubresourceFilterBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    subresource_filter::SubresourceFilterBrowserTest::SetUpOnMainThread();
    host_resolver()->ClearRules();
    host_resolver()->AddRule("a.test", "127.0.0.1");
    host_resolver()->AddRule("b.test", "127.0.0.1");
    ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  }

  const GURL GetTestURL(const char* file) const {
    return https_server_->GetURL("a.test", file);
  }

  const GURL GetTestURL(const char* hostname, const char* file) const {
    return https_server_->GetURL(hostname, file);
  }

  const GURL GetHttpTestURL(const char* hostname, const char* file) const {
    return http_server_->GetURL(hostname, file);
  }

  // Wait until at least |num_links| are reported as having entered the viewport
  // in UKM.
  void WaitLinkEnteredViewport(size_t num_links,
                               bool requires_polling = false) {
    EnsureLayout();

    const char* entry_name =
        ukm::builders::NavigationPredictorAnchorElementMetrics::kEntryName;

    while (ukm_recorder_->GetEntriesByName(entry_name).size() < num_links) {
      if (requires_polling) {
        // We need to poll for the condition to become true instead of using
        // `TestUkmRecorder::SetOnAddEntryCallback` if multiple lifecycle
        // updates are needed to get the next report.
        EnsureLayout();
      } else {
        base::RunLoop run_loop;
        ukm_recorder_->SetOnAddEntryCallback(entry_name,
                                             run_loop.QuitClosure());
        EnsureLayout();
        run_loop.Run();
      }
    }
  }

  void ResetUKM() {
    ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  }

 private:
  void EnsureLayout() {
    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    content::RenderFrameHost* primary_rfh = web_contents->GetPrimaryMainFrame();
    if (primary_rfh->IsRenderFrameLive()) {
      EXPECT_EQ(true, EvalJsAfterLifecycleUpdate(primary_rfh, "", "true"));
      EXPECT_EQ(true, EvalJsAfterLifecycleUpdate(primary_rfh, "", "true"));
    }
  }

  std::unique_ptr<net::EmbeddedTestServer> http_server_;
  std::unique_ptr<net::EmbeddedTestServer> https_server_;
  std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
  base::test::ScopedFeatureList feature_list_;
};

class TestObserver : public NavigationPredictorKeyedService::Observer {
 public:
  TestObserver() = default;

  TestObserver(const TestObserver&) = delete;
  TestObserver& operator=(const TestObserver&) = delete;

  ~TestObserver() override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  }

  std::optional<NavigationPredictorKeyedService::Prediction> last_prediction()
      const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return last_prediction_;
  }

  size_t count_predictions() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return count_predictions_;
  }

  // Waits until the count if received notifications is at least
  // |expected_notifications_count|.
  void WaitUntilNotificationsCountReached(size_t expected_notifications_count) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    // Ensure that |wait_loop_| is null implying there is no ongoing wait.
    ASSERT_FALSE(wait_loop_);

    while (count_predictions_ < expected_notifications_count) {
      expected_notifications_count_ = expected_notifications_count;
      wait_loop_ = std::make_unique<base::RunLoop>();
      wait_loop_->Run();
      wait_loop_.reset();
    }
  }

 private:
  void OnPredictionUpdated(
      const NavigationPredictorKeyedService::Prediction& prediction) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    ++count_predictions_;
    last_prediction_ = prediction;
    if (wait_loop_ && count_predictions_ >= expected_notifications_count_) {
      wait_loop_->Quit();
    }
  }

  // Count of prediction notifications received so far.
  size_t count_predictions_ = 0u;

  // last prediction received.
  std::optional<NavigationPredictorKeyedService::Prediction> last_prediction_;

  // If |wait_loop_| is non-null, then it quits as soon as count of received
  // notifications are at least |expected_notifications_count_|.
  std::unique_ptr<base::RunLoop> wait_loop_;
  std::optional<size_t> expected_notifications_count_;

  SEQUENCE_CHECKER(sequence_checker_);
};

IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, Pipeline) {
  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  const GURL& url = GetTestURL("/simple_page_with_anchors.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WaitLinkEnteredViewport(1);

  // Force recording NavigationPredictorPageLinkMetrics UKM.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));

  using PageLinkEntry = ukm::builders::NavigationPredictorPageLinkMetrics;
  auto entries = test_ukm_recorder->GetEntriesByName(PageLinkEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  auto* entry = entries[0].get();
  auto get_metric = [&](auto name) {
    return *test_ukm_recorder->GetEntryMetric(entry, name);
  };
  EXPECT_EQ(5, get_metric(PageLinkEntry::kNumberOfAnchors_TotalName));
  EXPECT_EQ(0, get_metric(PageLinkEntry::kNumberOfAnchors_ContainsImageName));
  EXPECT_EQ(0, get_metric(PageLinkEntry::kNumberOfAnchors_InIframeName));
  EXPECT_EQ(3, get_metric(PageLinkEntry::kNumberOfAnchors_SameHostName));
  EXPECT_EQ(0, get_metric(PageLinkEntry::kNumberOfAnchors_URLIncrementedName));

  // Same document anchor element should be removed.
  using AnchorEntry = ukm::builders::NavigationPredictorAnchorElementMetrics;
  entries = test_ukm_recorder->GetEntriesByName(AnchorEntry::kEntryName);
  EXPECT_EQ(2u, entries.size());
}

// Test that no metrics are recorded in off-the-record profiles.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, PipelineOffTheRecord) {
  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  const GURL& url = GetTestURL("/simple_page_with_anchors.html");
  Browser* incognito = CreateIncognitoBrowser();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito, url));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(
      content::ExecJs(incognito->tab_strip_model()->GetActiveWebContents(),
                      "document.getElementById('google').click();"));
  base::RunLoop().RunUntilIdle();

  auto entries = test_ukm_recorder->GetMergedEntriesByName(
      ukm::builders::NavigationPredictorAnchorElementMetrics::kEntryName);
  EXPECT_EQ(0u, entries.size());
  entries = test_ukm_recorder->GetMergedEntriesByName(
      ukm::builders::NavigationPredictorPageLinkMetrics::kEntryName);
  EXPECT_EQ(0u, entries.size());
  entries = test_ukm_recorder->GetMergedEntriesByName(
      ukm::builders::NavigationPredictorPageLinkClick::kEntryName);
  EXPECT_EQ(0u, entries.size());
}

// Test that the browser does not process anchor element metrics from an http
// web page on page load.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, PipelineHttp) {
  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  // We don't use localhost for this test, as http localhost is trusted.
  const GURL& url = GetHttpTestURL("a.test", "/simple_page_with_anchors.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  base::RunLoop().RunUntilIdle();

  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver click_nav_observer(web_contents);
  EXPECT_TRUE(content::ExecJs(web_contents,
                              "document.getElementById('google').click();"));
  click_nav_observer.Wait();
  base::RunLoop().RunUntilIdle();

  auto entries = test_ukm_recorder->GetMergedEntriesByName(
      ukm::builders::NavigationPredictorAnchorElementMetrics::kEntryName);
  EXPECT_EQ(0u, entries.size());
  entries = test_ukm_recorder->GetMergedEntriesByName(
      ukm::builders::NavigationPredictorPageLinkMetrics::kEntryName);
  EXPECT_EQ(0u, entries.size());
  entries = test_ukm_recorder->GetMergedEntriesByName(
      ukm::builders::NavigationPredictorPageLinkClick::kEntryName);
  EXPECT_EQ(0u, entries.size());
}

// Make sure AnchorsData gets cleared between navigations.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, MultipleNavigations) {
  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  const GURL& url = GetTestURL("/simple_page_with_anchors.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WaitLinkEnteredViewport(1);
  using AnchorEntry = ukm::builders::NavigationPredictorAnchorElementMetrics;
  size_t num_links_in_viewport =
      test_ukm_recorder->GetEntriesByName(AnchorEntry::kEntryName).size();

  // Load the same URL again. The UKM record from the previous load should get
  // flushed.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  // Wait until layout has happened: at least one new link entered viewport
  // since the last page load.
  WaitLinkEnteredViewport(num_links_in_viewport + 1);

  using PageLinkEntry = ukm::builders::NavigationPredictorPageLinkMetrics;
  auto entries = test_ukm_recorder->GetEntriesByName(PageLinkEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  auto* entry = entries[0].get();
  auto get_metric = [&](auto name) {
    return *test_ukm_recorder->GetEntryMetric(entry, name);
  };
  EXPECT_EQ(5, get_metric(PageLinkEntry::kNumberOfAnchors_TotalName));

  // Force recording NavigationPredictorPageLinkMetrics UKM.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));

  // If we correctly reset AnchorsData, the number of anchors should still be 5
  // (and not 10).
  entries = test_ukm_recorder->GetEntriesByName(PageLinkEntry::kEntryName);
  EXPECT_EQ(2u, entries.size());
  entry = entries[1];
  EXPECT_EQ(5, get_metric(PageLinkEntry::kNumberOfAnchors_TotalName));
}

// Tests that anchors from iframes are reported.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, PageWithIframe) {
  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  const GURL& url = GetTestURL("/page_with_anchors_and_iframe.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  // Wait until all links have entered the viewport. In particular this forces
  // the iframe to load.
  WaitLinkEnteredViewport(7);

  // Force recording NavigationPredictorPageLinkMetrics UKM.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));

  using PageLinkEntry = ukm::builders::NavigationPredictorPageLinkMetrics;
  auto entries = test_ukm_recorder->GetEntriesByName(PageLinkEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  auto* entry = entries[0].get();
  auto get_metric = [&](auto name) {
    return *test_ukm_recorder->GetEntryMetric(entry, name);
  };
  EXPECT_EQ(7, get_metric(PageLinkEntry::kNumberOfAnchors_TotalName));
  EXPECT_EQ(1, get_metric(PageLinkEntry::kNumberOfAnchors_ContainsImageName));
  EXPECT_EQ(3, get_metric(PageLinkEntry::kNumberOfAnchors_InIframeName));
  EXPECT_EQ(3, get_metric(PageLinkEntry::kNumberOfAnchors_SameHostName));
  EXPECT_EQ(0, get_metric(PageLinkEntry::kNumberOfAnchors_URLIncrementedName));

  // Anchors in same-origin iframes should be reported as entering the viewport.
  using AnchorEntry = ukm::builders::NavigationPredictorAnchorElementMetrics;
  entries = test_ukm_recorder->GetEntriesByName(AnchorEntry::kEntryName);
  EXPECT_EQ(7u, entries.size());
}

// Tests parameterized on whether site isolation is enabled, to ensure that the
// metrics calculations in the renderer don't change based on the process model.
class NavigationPredictorSiteIsolationBrowserTest
    : public NavigationPredictorBrowserTest,
      public ::testing::WithParamInterface<bool> {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    NavigationPredictorBrowserTest::SetUpCommandLine(command_line);
    if (SiteIsolationEnabled()) {
      content::IsolateAllSitesForTesting(command_line);
    } else {
      command_line->RemoveSwitch(switches::kSitePerProcess);
      command_line->AppendSwitch(switches::kDisableSiteIsolation);
    }
  }

  bool SiteIsolationEnabled() const { return GetParam(); }
};

INSTANTIATE_TEST_SUITE_P(All,
                         NavigationPredictorSiteIsolationBrowserTest,
                         testing::Bool());

// Tests cross-origin iframe. For now we don't log cross-origin links, so this
// test just makes sure the iframe is ignored and the browser doesn't crash.
IN_PROC_BROWSER_TEST_P(NavigationPredictorSiteIsolationBrowserTest,
                       PageWithCrossOriginIframe) {
  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  const GURL& url =
      GetTestURL("/page_with_anchors_and_cross_origin_iframe.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  const GURL& iframe_url =
      GetTestURL("b.test", "/iframe_simple_page_with_anchors.html");
  EXPECT_TRUE(content::NavigateIframeToURL(
      browser()->tab_strip_model()->GetActiveWebContents(), "crossFrame",
      iframe_url));
  WaitLinkEnteredViewport(1);

  // Force recording NavigationPredictorPageLinkMetrics UKM.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));

  using PageLinkEntry = ukm::builders::NavigationPredictorPageLinkMetrics;
  auto entries = test_ukm_recorder->GetEntriesByName(PageLinkEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  auto* entry = entries[0].get();
  auto get_metric = [&](auto name) {
    return *test_ukm_recorder->GetEntryMetric(entry, name);
  };
  EXPECT_EQ(4, get_metric(PageLinkEntry::kNumberOfAnchors_TotalName));
  EXPECT_EQ(0, get_metric(PageLinkEntry::kNumberOfAnchors_ContainsImageName));
  EXPECT_EQ(0, get_metric(PageLinkEntry::kNumberOfAnchors_InIframeName));
  EXPECT_EQ(3, get_metric(PageLinkEntry::kNumberOfAnchors_SameHostName));
  EXPECT_EQ(0, get_metric(PageLinkEntry::kNumberOfAnchors_URLIncrementedName));

  // Same anchors in iframes should be reported as entering the viewport.
  using AnchorEntry = ukm::builders::NavigationPredictorAnchorElementMetrics;
  entries = test_ukm_recorder->GetEntriesByName(AnchorEntry::kEntryName);
  EXPECT_EQ(4u, entries.size());
}

// Tests a frame hierarchy of A(B(A)). The cross-origin iframe B should be
// ignored, but the same-origin iframe A should be included even though its
// parent is cross-origin.
IN_PROC_BROWSER_TEST_P(NavigationPredictorSiteIsolationBrowserTest,
                       PageWithSameOriginIframeInCrossOriginIframe) {
  // TODO(crbug.com/41492823): Flaky timeouts on mac, linux rel, and cros rel.
#if BUILDFLAG(IS_MAC) || \
    ((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && defined(NDEBUG))
  if (SiteIsolationEnabled()) {
    GTEST_SKIP() << "Flaky. https://crbug.com/41492823";
  }
#endif

  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  const GURL url =
      GetTestURL("a.test", "/page_with_anchor_and_cross_origin_iframe_b.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  // The links in the same-origin iframe won't be reported until the next
  // lifecycle update of the main frame, which WaitLinkEnteredViewport triggers.
  // Given that this could race with the processing of the links in the iframe
  // document, we may need to trigger updates multiple times.
  const bool requires_polling = true;

  WaitLinkEnteredViewport(4, requires_polling);

  // Force recording NavigationPredictorPageLinkMetrics UKM.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));

  using PageLinkEntry = ukm::builders::NavigationPredictorPageLinkMetrics;
  auto entries = test_ukm_recorder->GetEntriesByName(PageLinkEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  auto* entry = entries[0].get();
  auto get_metric = [&](auto name) {
    return *test_ukm_recorder->GetEntryMetric(entry, name);
  };
  EXPECT_EQ(4, get_metric(PageLinkEntry::kNumberOfAnchors_TotalName));
  EXPECT_EQ(1, get_metric(PageLinkEntry::kNumberOfAnchors_ContainsImageName));
  EXPECT_EQ(3, get_metric(PageLinkEntry::kNumberOfAnchors_InIframeName));
  EXPECT_EQ(1, get_metric(PageLinkEntry::kNumberOfAnchors_SameHostName));
  EXPECT_EQ(0, get_metric(PageLinkEntry::kNumberOfAnchors_URLIncrementedName));

  using AnchorEntry = ukm::builders::NavigationPredictorAnchorElementMetrics;
  entries = test_ukm_recorder->GetEntriesByName(AnchorEntry::kEntryName);
  EXPECT_EQ(4u, entries.size());
}

// Inject link into the viewport after some time test reporting of
// NavigationStartToEnteredViewportMs.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest,
                       NavigationStartToEnteredViewportMs) {
  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GetTestURL("/dynamically_inserted_anchor.html")));
  WaitLinkEnteredViewport(1);

  using AnchorEntry = ukm::builders::NavigationPredictorAnchorElementMetrics;
  auto entries = test_ukm_recorder->GetEntriesByName(AnchorEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  uint64_t time_ms = *test_ukm_recorder->GetEntryMetric(
      entries[0], AnchorEntry::kNavigationStartToLinkLoggedMsName);
  EXPECT_LT(0u, time_ms);
  // To avoid making the test flaky we allow this value to be up to 100s.
  // This still tests for cases where the unsigned integer overflows or if we
  // fail to subtract navigation start from the timestamp when the link entered
  // the viewport.
  EXPECT_GT(100000u, time_ms);
}

// Simulate a click at the anchor element.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, ClickAnchorElement) {
  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  const GURL& url = GetTestURL("/simple_page_with_anchors.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WaitLinkEnteredViewport(1);

  EXPECT_TRUE(
      content::ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
                      "document.getElementById('google').click();"));
  base::RunLoop().RunUntilIdle();

  // Navigate to another page.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL("/1.html")));
  base::RunLoop().RunUntilIdle();

  // Make sure the click has been logged.
  auto entries = test_ukm_recorder->GetMergedEntriesByName(
      ukm::builders::NavigationPredictorPageLinkClick::kEntryName);
  EXPECT_EQ(1u, entries.size());
}

class NavigationPredictorBrowserTestWithDefaultPredictorEnabled
    : public NavigationPredictorBrowserTest {
 public:
  NavigationPredictorBrowserTestWithDefaultPredictorEnabled() {
    feature_list_.InitAndEnableFeatureWithParameters(
        blink::features::kNavigationPredictor, {});
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Simulate a click at the anchor element in off-the-record profile.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest,
                       ClickAnchorElementOffTheRecord) {
  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  const GURL& url = GetTestURL("/simple_page_with_anchors.html");

  Browser* incognito = CreateIncognitoBrowser();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(incognito, url));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(
      content::ExecJs(incognito->tab_strip_model()->GetActiveWebContents(),
                      "document.getElementById('google').click();"));
  content::WaitForLoadStop(
      incognito->tab_strip_model()->GetActiveWebContents());

  auto entries = test_ukm_recorder->GetMergedEntriesByName(
      ukm::builders::PageLoad::kEntryName);
  EXPECT_EQ(1u, entries.size());

  // Make sure no click has been logged.
  entries = test_ukm_recorder->GetMergedEntriesByName(
      ukm::builders::NavigationPredictorPageLinkClick::kEntryName);
  EXPECT_EQ(0u, entries.size());
}

// Tests that the browser counts anchors from anywhere on the page.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest,
                       ViewportOnlyAndUrlIncrementByOne) {
  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  const GURL& url = GetTestURL("/long_page_with_anchors-1.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WaitLinkEnteredViewport(1);

  // Force recording NavigationPredictorPageLinkMetrics UKM.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));

  // Make sure no click has been logged.
  using UkmEntry = ukm::builders::NavigationPredictorPageLinkMetrics;
  auto entries = test_ukm_recorder->GetEntriesByName(UkmEntry::kEntryName);
  EXPECT_EQ(1u, entries.size());
  auto* entry = entries[0].get();
  auto get_metric = [&](auto name) {
    return *test_ukm_recorder->GetEntryMetric(entry, name);
  };
  EXPECT_EQ(3, get_metric(UkmEntry::kNumberOfAnchors_TotalName));
  EXPECT_EQ(0, get_metric(UkmEntry::kNumberOfAnchors_ContainsImageName));
  EXPECT_EQ(0, get_metric(UkmEntry::kNumberOfAnchors_InIframeName));
  EXPECT_EQ(1, get_metric(UkmEntry::kNumberOfAnchors_SameHostName));
  EXPECT_EQ(1, get_metric(UkmEntry::kNumberOfAnchors_URLIncrementedName));
}

// Test that anchors are dispated to the single observer, except for anchors
// linking to the same page (e.g. fragment links).
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, SingleObserver) {
  TestObserver observer;

  NavigationPredictorKeyedService* service =
      NavigationPredictorKeyedServiceFactory::GetForProfile(
          browser()->profile());
  EXPECT_NE(nullptr, service);
  service->AddObserver(&observer);

  const GURL& url = GetTestURL("/simple_page_with_anchors.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WaitLinkEnteredViewport(1);
  observer.WaitUntilNotificationsCountReached(1);

  service->RemoveObserver(&observer);

  EXPECT_EQ(1u, observer.count_predictions());
  EXPECT_EQ(url, observer.last_prediction()->source_document_url());
  EXPECT_THAT(observer.last_prediction()->sorted_predicted_urls(),
              ::testing::UnorderedElementsAre("https://google.com/",
                                              "https://example.com/"));

  // Doing another navigation after removing the observer should not cause a
  // crash.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WaitLinkEnteredViewport(1);
  EXPECT_EQ(1u, observer.count_predictions());
}

// Test that anchors are dispatched to the single observer, including
// anchors outside the viewport. Reactive prefetch relies on anchors from
// outside the viewport to be included since hints are only requested at onload
// predictions after that point are ignored.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest,
                       SingleObserverPastViewport) {
  TestObserver observer;

  NavigationPredictorKeyedService* service =
      NavigationPredictorKeyedServiceFactory::GetForProfile(
          browser()->profile());
  EXPECT_NE(nullptr, service);
  service->AddObserver(&observer);

  const GURL& url = GetTestURL("/long_page_with_anchors-1.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WaitLinkEnteredViewport(1);
  observer.WaitUntilNotificationsCountReached(1);

  service->RemoveObserver(&observer);

  EXPECT_EQ(1u, observer.count_predictions());
  EXPECT_EQ(url, observer.last_prediction()->source_document_url());
  EXPECT_THAT(observer.last_prediction()->sorted_predicted_urls(),
              ::testing::UnorderedElementsAre(
                  "https://google.com/", "https://example2.com/",
                  GetTestURL("/long_page_with_anchors-2.html")));

  // Doing another navigation after removing the observer should not cause a
  // crash.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WaitLinkEnteredViewport(1);
  EXPECT_EQ(1u, observer.count_predictions());
}

// Same as NavigationScoreSingleObserver test but with more than one observer.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, TwoObservers) {
  TestObserver observer_1;
  TestObserver observer_2;

  NavigationPredictorKeyedService* service =
      NavigationPredictorKeyedServiceFactory::GetForProfile(
          browser()->profile());
  service->AddObserver(&observer_1);
  service->AddObserver(&observer_2);

  const GURL& url = GetTestURL("/simple_page_with_anchors.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WaitLinkEnteredViewport(1);
  observer_1.WaitUntilNotificationsCountReached(1);
  observer_2.WaitUntilNotificationsCountReached(1);

  service->RemoveObserver(&observer_1);

  EXPECT_EQ(1u, observer_1.count_predictions());
  EXPECT_EQ(url, observer_1.last_prediction()->source_document_url());
  EXPECT_EQ(2u, observer_1.last_prediction()->sorted_predicted_urls().size());
  EXPECT_THAT(observer_1.last_prediction()->sorted_predicted_urls(),
              ::testing::UnorderedElementsAre("https://google.com/",
                                              "https://example.com/"));
  EXPECT_EQ(1u, observer_2.count_predictions());
  EXPECT_EQ(url, observer_2.last_prediction()->source_document_url());

  // Only |observer_2| should get the notification since |observer_1| has
  // been removed from receiving the notifications.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WaitLinkEnteredViewport(1);
  observer_2.WaitUntilNotificationsCountReached(2);
  EXPECT_EQ(1u, observer_1.count_predictions());
  EXPECT_EQ(2u, observer_2.count_predictions());
  EXPECT_THAT(observer_2.last_prediction()->sorted_predicted_urls(),
              ::testing::UnorderedElementsAre("https://google.com/",
                                              "https://example.com/"));
}

// Test that the navigation predictor keyed service is null for incognito
// profiles.
IN_PROC_BROWSER_TEST_F(NavigationPredictorBrowserTest, Incognito) {
  Browser* incognito = CreateIncognitoBrowser();
  NavigationPredictorKeyedService* incognito_service =
      NavigationPredictorKeyedServiceFactory::GetForProfile(
          incognito->profile());
  EXPECT_EQ(nullptr, incognito_service);
}

class NavigationPredictorMPArchBrowserTest
    : public NavigationPredictorBrowserTest {
 public:
  NavigationPredictorMPArchBrowserTest() = default;
  ~NavigationPredictorMPArchBrowserTest() override = default;
  NavigationPredictorMPArchBrowserTest(
      const NavigationPredictorMPArchBrowserTest&) = delete;

  NavigationPredictorMPArchBrowserTest& operator=(
      const NavigationPredictorMPArchBrowserTest&) = delete;

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    test_server_.AddDefaultHandlers(GetChromeTestDataDir());
    test_server_.ServeFilesFromSourceDirectory(
        "chrome/test/data/navigation_predictor");
    test_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
    ASSERT_TRUE(test_server_.Start());
  }

  content::WebContents* GetWebContents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  net::EmbeddedTestServer* test_server() { return &test_server_; }

 private:
  net::EmbeddedTestServer test_server_{net::EmbeddedTestServer::TYPE_HTTPS};
};

class NavigationPredictorPrerenderBrowserTest
    : public NavigationPredictorMPArchBrowserTest {
 public:
  NavigationPredictorPrerenderBrowserTest()
      : prerender_test_helper_(base::BindRepeating(
            &NavigationPredictorPrerenderBrowserTest::GetWebContents,
            base::Unretained(this))) {}
  ~NavigationPredictorPrerenderBrowserTest() override = default;
  NavigationPredictorPrerenderBrowserTest(
      const NavigationPredictorPrerenderBrowserTest&) = delete;

  NavigationPredictorPrerenderBrowserTest& operator=(
      const NavigationPredictorPrerenderBrowserTest&) = delete;

  void SetUp() override {
    prerender_test_helper_.RegisterServerRequestMonitor(test_server());
    NavigationPredictorMPArchBrowserTest::SetUp();
  }

  content::test::PrerenderTestHelper& prerender_test_helper() {
    return prerender_test_helper_;
  }

 private:
  content::test::PrerenderTestHelper prerender_test_helper_;
};

// Test that prerendering doesn't create a predictor object and doesn't affect
// the primary page's behavior.
IN_PROC_BROWSER_TEST_F(NavigationPredictorPrerenderBrowserTest,
                       PrerenderingDontCreatePredictor) {
  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  // Navigate to an initial page.
  const GURL& url = test_server()->GetURL("/simple_page_with_anchors.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WaitLinkEnteredViewport(1);

  using AnchorEntry = ukm::builders::NavigationPredictorAnchorElementMetrics;
  auto anchor_entries =
      test_ukm_recorder->GetEntriesByName(AnchorEntry::kEntryName);
  EXPECT_EQ(2u, anchor_entries.size());

  // Start prerendering. This shouldn't create a NavigationPredictor instance.
  // If it happens, the constructor of NavigationPredictor is called for the
  // non-primary page and the DCHECK there should fail.
  content::FrameTreeNodeId host_id = prerender_test_helper().AddPrerender(url);
  content::test::PrerenderHostObserver host_observer(*GetWebContents(),
                                                     host_id);
  EXPECT_FALSE(host_observer.was_activated());

  // Make sure the prerendering doesn't log any anchors.
  anchor_entries = test_ukm_recorder->GetEntriesByName(AnchorEntry::kEntryName);
  EXPECT_EQ(2u, anchor_entries.size());

  ResetUKM();

  // Activate the prerendered frame.
  prerender_test_helper().NavigatePrimaryPage(url);
  EXPECT_TRUE(host_observer.was_activated());
  WaitLinkEnteredViewport(1);

  // Make sure the activating logs anchors correctly.
  anchor_entries = test_ukm_recorder->GetEntriesByName(AnchorEntry::kEntryName);
  EXPECT_EQ(4u, anchor_entries.size());
}

class NavigationPredictorFencedFrameBrowserTest
    : public NavigationPredictorMPArchBrowserTest {
 public:
  NavigationPredictorFencedFrameBrowserTest() = default;
  ~NavigationPredictorFencedFrameBrowserTest() override = default;
  NavigationPredictorFencedFrameBrowserTest(
      const NavigationPredictorFencedFrameBrowserTest&) = delete;

  NavigationPredictorFencedFrameBrowserTest& operator=(
      const NavigationPredictorFencedFrameBrowserTest&) = delete;

  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
    return fenced_frame_helper_;
  }

 private:
  content::test::FencedFrameTestHelper fenced_frame_helper_;
};

IN_PROC_BROWSER_TEST_F(NavigationPredictorFencedFrameBrowserTest,
                       EnsureFencedFrameDoesNotCreateNavigationPredictor) {
  auto test_ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  ResetUKM();

  // Navigate to an initial page.
  const GURL& url = test_server()->GetURL("/simple_page_with_anchors.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WaitLinkEnteredViewport(1);

  using AnchorEntry = ukm::builders::NavigationPredictorAnchorElementMetrics;
  auto anchor_entries =
      test_ukm_recorder->GetEntriesByName(AnchorEntry::kEntryName);
  EXPECT_EQ(2u, anchor_entries.size());

  // Create a fenced frame.
  const GURL& fenced_frame_url =
      test_server()->GetURL("/fenced_frames/simple_page_with_anchors.html");
  std::ignore = fenced_frame_test_helper().CreateFencedFrame(
      web_contents()->GetPrimaryMainFrame(), fenced_frame_url);

  // Make sure the fenced frame doesn't log any anchors.
  anchor_entries = test_ukm_recorder->GetEntriesByName(AnchorEntry::kEntryName);
  EXPECT_EQ(2u, anchor_entries.size());
}

}  // namespace