From 2978fad99a9479da4e271f3ee2f313e366a6919b Mon Sep 17 00:00:00 2001 From: Jacob Stanley Date: Sat, 26 Mar 2022 01:34:23 +0000 Subject: [PATCH] Add browser side of anchor element interaction preloading feature. Adds mojo interface along with browser service that will trigger preconnect on PointerDown events forwarded by the renderer. This also improves testing in the renderer by intercepting mojo message. Full end-to-end browser tests will be added next. Bug: 1297312 Change-Id: I59ca4fcf6bda7f09b834c3f5f87c7a3b451f65a4 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3537709 Reviewed-by: Max Curran Reviewed-by: danakj Reviewed-by: David Bokan Commit-Queue: Jacob Stanley Cr-Commit-Position: refs/heads/main@{#985601} --- chrome/browser/BUILD.gn | 2 + .../chrome_browser_interface_binders.cc | 8 + .../anchor_element_preloader.cc | 39 ++++ .../anchor_element_preloader.h | 28 +++ third_party/blink/public/mojom/BUILD.gn | 1 + .../anchor_element_interaction_host.mojom | 16 ++ third_party/blink/renderer/core/BUILD.gn | 2 +- .../blink/renderer/core/dom/document.cc | 7 + .../blink/renderer/core/dom/document.h | 2 + .../anchor_element_interaction_tracker.cc | 29 --- .../frame/anchor_element_listener_test.cc | 126 ----------- .../blink/renderer/core/frame/build.gni | 4 - .../blink/renderer/core/frame/local_frame.cc | 6 - .../blink/renderer/core/frame/local_frame.h | 2 - .../loader/anchor_element_interaction_test.cc | 195 ++++++++++++++++++ .../anchor_element_interaction_tracker.cc | 52 +++++ .../anchor_element_interaction_tracker.h | 12 +- .../anchor_element_listener.cc | 20 +- .../anchor_element_listener.h | 21 +- .../blink/renderer/core/loader/build.gni | 4 + 20 files changed, 390 insertions(+), 186 deletions(-) create mode 100644 chrome/browser/navigation_predictor/anchor_element_preloader.cc create mode 100644 chrome/browser/navigation_predictor/anchor_element_preloader.h create mode 100644 third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom delete mode 100644 third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.cc delete mode 100644 third_party/blink/renderer/core/frame/anchor_element_listener_test.cc create mode 100644 third_party/blink/renderer/core/loader/anchor_element_interaction_test.cc create mode 100644 third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.cc rename third_party/blink/renderer/core/{frame => loader}/anchor_element_interaction_tracker.h (66%) rename third_party/blink/renderer/core/{frame => loader}/anchor_element_listener.cc (68%) rename third_party/blink/renderer/core/{frame => loader}/anchor_element_listener.h (54%) diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index db6fc6ae990f7e..30281f61876cf1 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn @@ -808,6 +808,8 @@ static_library("browser") { "metrics/variations/chrome_variations_service_client.cc", "metrics/variations/chrome_variations_service_client.h", "native_window_notification_source.h", + "navigation_predictor/anchor_element_preloader.cc", + "navigation_predictor/anchor_element_preloader.h", "navigation_predictor/navigation_predictor.cc", "navigation_predictor/navigation_predictor.h", "navigation_predictor/navigation_predictor_features.cc", diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc index f4650bd80bf407..1b561979474fbb 100644 --- a/chrome/browser/chrome_browser_interface_binders.cc +++ b/chrome/browser/chrome_browser_interface_binders.cc @@ -16,6 +16,7 @@ #include "chrome/browser/dom_distiller/dom_distiller_service_factory.h" #include "chrome/browser/media/history/media_history_store.mojom.h" #include "chrome/browser/media/media_engagement_score_details.mojom.h" +#include "chrome/browser/navigation_predictor/anchor_element_preloader.h" #include "chrome/browser/navigation_predictor/navigation_predictor.h" #include "chrome/browser/password_manager/chrome_password_manager_client.h" #include "chrome/browser/predictors/network_hints_handler_impl.h" @@ -78,6 +79,7 @@ #include "services/image_annotation/public/mojom/image_annotation.mojom.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/mojom/credentialmanagement/credential_manager.mojom.h" +#include "third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom.h" #include "third_party/blink/public/mojom/loader/navigation_predictor.mojom.h" #include "third_party/blink/public/mojom/payments/payment_credential.mojom.h" #include "third_party/blink/public/mojom/payments/payment_request.mojom.h" @@ -629,6 +631,12 @@ void PopulateChromeFrameBinders( map->Add( base::BindRepeating(&NavigationPredictor::Create)); + if (base::FeatureList::IsEnabled( + blink::features::kAnchorElementInteraction)) { + map->Add( + base::BindRepeating(&AnchorElementPreloader::Create)); + } + map->Add( base::BindRepeating(&BindDistillabilityService)); diff --git a/chrome/browser/navigation_predictor/anchor_element_preloader.cc b/chrome/browser/navigation_predictor/anchor_element_preloader.cc new file mode 100644 index 00000000000000..550ca3f69273af --- /dev/null +++ b/chrome/browser/navigation_predictor/anchor_element_preloader.cc @@ -0,0 +1,39 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/navigation_predictor/anchor_element_preloader.h" +#include "chrome/browser/predictors/loading_predictor.h" +#include "chrome/browser/predictors/loading_predictor_factory.h" +#include "content/public/browser/browser_context.h" + +AnchorElementPreloader::AnchorElementPreloader( + content::RenderFrameHost* render_frame_host, + mojo::PendingReceiver receiver) + : content::DocumentService( + render_frame_host, + std::move(receiver)) {} + +void AnchorElementPreloader::Create( + content::RenderFrameHost* render_frame_host, + mojo::PendingReceiver + receiver) { + // The object is bound to the lifetime of the |render_frame_host| and the mojo + // connection. See DocumentService for details. + new AnchorElementPreloader(render_frame_host, std::move(receiver)); +} + +void AnchorElementPreloader::OnPointerDown(const GURL& target) { + auto* loading_predictor = predictors::LoadingPredictorFactory::GetForProfile( + Profile::FromBrowserContext(render_frame_host()->GetBrowserContext())); + + if (!loading_predictor) { + return; + } + + net::SchemefulSite schemeful_site(target); + net::NetworkIsolationKey network_isolation_key(schemeful_site, + schemeful_site); + loading_predictor->PreconnectURLIfAllowed(target, /*allow_credentials=*/true, + network_isolation_key); +} diff --git a/chrome/browser/navigation_predictor/anchor_element_preloader.h b/chrome/browser/navigation_predictor/anchor_element_preloader.h new file mode 100644 index 00000000000000..a6962c597d75c7 --- /dev/null +++ b/chrome/browser/navigation_predictor/anchor_element_preloader.h @@ -0,0 +1,28 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_NAVIGATION_PREDICTOR_ANCHOR_ELEMENT_PRELOADER_H_ +#define CHROME_BROWSER_NAVIGATION_PREDICTOR_ANCHOR_ELEMENT_PRELOADER_H_ + +#include "content/public/browser/document_service.h" +#include "third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom.h" + +class AnchorElementPreloader + : content::DocumentService { + public: + static void Create( + content::RenderFrameHost* render_frame_host, + mojo::PendingReceiver + receiver); + + private: + AnchorElementPreloader( + content::RenderFrameHost* render_frame_host, + mojo::PendingReceiver + receiver); + // Preconnects to the given URL `target`. + void OnPointerDown(const GURL& target) override; +}; + +#endif // CHROME_BROWSER_NAVIGATION_PREDICTOR_ANCHOR_ELEMENT_PRELOADER_H_ diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn index 83d68e186f14c4..23cc3eea04aa1c 100644 --- a/third_party/blink/public/mojom/BUILD.gn +++ b/third_party/blink/public/mojom/BUILD.gn @@ -102,6 +102,7 @@ mojom("mojom_platform") { "keyboard_lock/keyboard_lock.mojom", "leak_detector/leak_detector.mojom", "link_to_text/link_to_text.mojom", + "loader/anchor_element_interaction_host.mojom", "loader/code_cache.mojom", "loader/content_security_notifier.mojom", "loader/fetch_client_settings_object.mojom", diff --git a/third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom b/third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom new file mode 100644 index 00000000000000..a28eab1863f845 --- /dev/null +++ b/third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom @@ -0,0 +1,16 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module blink.mojom; + +import "url/mojom/url.mojom"; + +// Interface for sending the URL from the renderer side to browser process. +interface AnchorElementInteractionHost { + // On PointerDown events for anchor elements that are part + // of the HTTP family, the renderer calls OnPointerDown to pass + // the URL to the browser process. In the browser process, the URL gets + // preresolved and preconnected. + OnPointerDown(url.mojom.Url target); +}; \ No newline at end of file diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn index 5c2f8dc539551a..6fd98e6aa33c4d 100644 --- a/third_party/blink/renderer/core/BUILD.gn +++ b/third_party/blink/renderer/core/BUILD.gn @@ -1282,7 +1282,6 @@ source_set("unit_tests") { "fragment_directive/text_fragment_selector_generator_test.cc", "fragment_directive/text_fragment_selector_test.cc", "frame/ad_tracker_test.cc", - "frame/anchor_element_listener_test.cc", "frame/attribution_response_parsing_test.cc", "frame/attribution_src_loader_test.cc", "frame/browser_controls_test.cc", @@ -1346,6 +1345,7 @@ source_set("unit_tests") { "inspector/protocol_unittest.cc", "intersection_observer/intersection_observer_test.cc", "loader/alternate_signed_exchange_resource_info_test.cc", + "loader/anchor_element_interaction_test.cc", "loader/base_fetch_context_test.cc", "loader/cookie_jar_unittest.cc", "loader/document_load_timing_test.cc", diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc index e4853bde41fb0c..79c69597da5110 100644 --- a/third_party/blink/renderer/core/dom/document.cc +++ b/third_party/blink/renderer/core/dom/document.cc @@ -249,6 +249,7 @@ #include "third_party/blink/renderer/core/layout/layout_object_factory.h" #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/text_autosizer.h" +#include "third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h" #include "third_party/blink/renderer/core/loader/cookie_jar.h" #include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/core/loader/frame_fetch_context.h" @@ -2723,6 +2724,11 @@ void Document::Initialize() { autosizer->UpdatePageInfo(); GetFrame()->DidAttachDocument(); + if (AnchorElementInteractionTracker::IsFeatureEnabled() && + !GetFrame()->IsProvisional()) { + anchor_element_interaction_tracker_ = + MakeGarbageCollected(*this); + } lifecycle_.AdvanceTo(DocumentLifecycle::kStyleClean); if (View()) @@ -7943,6 +7949,7 @@ void Document::Trace(Visitor* visitor) const { visitor->Trace(meta_theme_color_elements_); visitor->Trace(unassociated_listed_elements_); visitor->Trace(intrinsic_size_observer_); + visitor->Trace(anchor_element_interaction_tracker_); Supplementable::Trace(visitor); TreeScope::Trace(visitor); ContainerNode::Trace(visitor); diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h index 8bbfac2928bf52..ad9038ecc824b7 100644 --- a/third_party/blink/renderer/core/dom/document.h +++ b/third_party/blink/renderer/core/dom/document.h @@ -110,6 +110,7 @@ enum class CSPDisposition : int32_t; namespace blink { +class AnchorElementInteractionTracker; class AnimationClock; class AXContext; class AXObjectCache; @@ -2119,6 +2120,7 @@ class CORE_EXPORT Document : public ContainerNode, Member document_element_; UserActionElementSet user_action_elements_; Member root_scroller_controller_; + Member anchor_element_interaction_tracker_; double overscroll_accumulated_delta_x_ = 0; double overscroll_accumulated_delta_y_ = 0; diff --git a/third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.cc b/third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.cc deleted file mode 100644 index 9fd54000ee217a..00000000000000 --- a/third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.cc +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2022 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.h" -#include "third_party/blink/public/common/features.h" -#include "third_party/blink/renderer/core/dom/document.h" -#include "third_party/blink/renderer/core/event_type_names.h" -#include "third_party/blink/renderer/core/frame/anchor_element_listener.h" - -namespace blink { - -AnchorElementInteractionTracker::AnchorElementInteractionTracker( - Document& document) { - anchor_element_listener_ = MakeGarbageCollected(); - document.addEventListener(event_type_names::kPointerdown, - anchor_element_listener_, true); -} - -void AnchorElementInteractionTracker::Trace(Visitor* visitor) const { - visitor->Trace(anchor_element_listener_); -} - -// static -bool AnchorElementInteractionTracker::IsFeatureEnabled() { - return base::FeatureList::IsEnabled(features::kAnchorElementInteraction); -} - -} // namespace blink diff --git a/third_party/blink/renderer/core/frame/anchor_element_listener_test.cc b/third_party/blink/renderer/core/frame/anchor_element_listener_test.cc deleted file mode 100644 index d617e9e8929732..00000000000000 --- a/third_party/blink/renderer/core/frame/anchor_element_listener_test.cc +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2022 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "third_party/blink/renderer/core/frame/anchor_element_listener.h" -#include "third_party/blink/renderer/core/dom/element.h" -#include "third_party/blink/renderer/core/html/html_anchor_element.h" -#include "third_party/blink/renderer/core/testing/sim/sim_request.h" -#include "third_party/blink/renderer/core/testing/sim/sim_test.h" - -namespace blink { - -class AnchorElementListenerTest : public SimTest {}; - -TEST_F(AnchorElementListenerTest, ValidHref) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete( - "example"); - auto* anchor_element_listener_ = - MakeGarbageCollected(); - auto* anchor_element = - DynamicTo(GetDocument().getElementById("anchor1")); - KURL URL = - anchor_element_listener_->GetHrefEligibleForPreloading(*anchor_element); - KURL expected_url = KURL("https://anchor1.com/"); - EXPECT_FALSE(URL.IsEmpty()); - EXPECT_EQ(expected_url, URL); -} - -TEST_F(AnchorElementListenerTest, InvalidHref) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete("example"); - auto* anchor_element_listener_ = - MakeGarbageCollected(); - auto* anchor_element = - DynamicTo(GetDocument().getElementById("anchor1")); - EXPECT_TRUE( - anchor_element_listener_->GetHrefEligibleForPreloading(*anchor_element) - .IsEmpty()); -} - -TEST_F(AnchorElementListenerTest, OneAnchorElementCheck) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete( - "example"); - auto* anchor_element_listener_ = - MakeGarbageCollected(); - auto* element = GetDocument().getElementById("anchor1"); - auto* anchor_element = - anchor_element_listener_->FirstAnchorElementIncludingSelf(element); - auto* expected_anchor = - DynamicTo(GetDocument().getElementById("anchor1")); - EXPECT_EQ(expected_anchor, anchor_element); -} - -TEST_F(AnchorElementListenerTest, NestedAnchorElementCheck) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete( - ""); - auto* anchor_element_listener_ = - MakeGarbageCollected(); - auto* element = GetDocument().getElementById("anchor2"); - auto* anchor_element = - anchor_element_listener_->FirstAnchorElementIncludingSelf(element); - auto* expected_anchor = - DynamicTo(GetDocument().getElementById("anchor2")); - EXPECT_EQ(expected_anchor, anchor_element); -} - -TEST_F(AnchorElementListenerTest, NestedDivAnchorElementCheck) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete( - "
"); - auto* anchor_element_listener_ = - MakeGarbageCollected(); - auto* element = GetDocument().getElementById("div1id"); - auto* anchor_element = - anchor_element_listener_->FirstAnchorElementIncludingSelf(element); - auto* expected_anchor = - DynamicTo(GetDocument().getElementById("anchor1")); - EXPECT_EQ(expected_anchor, anchor_element); -} - -TEST_F(AnchorElementListenerTest, MultipleNestedAnchorElementCheck) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete( - "

"); - auto* anchor_element_listener_ = - MakeGarbageCollected(); - auto* element = GetDocument().getElementById("div2id"); - auto* anchor_element = - anchor_element_listener_->FirstAnchorElementIncludingSelf(element); - auto* expected_anchor = - DynamicTo(GetDocument().getElementById("anchor1")); - EXPECT_EQ(expected_anchor, anchor_element); -} - -TEST_F(AnchorElementListenerTest, NoAnchorElementCheck) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete("
"); - auto* anchor_element_listener_ = - MakeGarbageCollected(); - auto* element = GetDocument().getElementById("div1id"); - auto* anchor_element = - anchor_element_listener_->FirstAnchorElementIncludingSelf(element); - EXPECT_EQ(nullptr, anchor_element); -} - -} // namespace blink diff --git a/third_party/blink/renderer/core/frame/build.gni b/third_party/blink/renderer/core/frame/build.gni index e94c900dcce3c7..00d49ab8429278 100644 --- a/third_party/blink/renderer/core/frame/build.gni +++ b/third_party/blink/renderer/core/frame/build.gni @@ -5,10 +5,6 @@ blink_core_sources_frame = [ "ad_tracker.cc", "ad_tracker.h", - "anchor_element_listener.cc", - "anchor_element_listener.h", - "anchor_element_interaction_tracker.cc", - "anchor_element_interaction_tracker.h", "attribution_reporting.cc", "attribution_reporting.h", "attribution_response_parsing.cc", diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc index 85849e45352bb8..ae445a52314d85 100644 --- a/third_party/blink/renderer/core/frame/local_frame.cc +++ b/third_party/blink/renderer/core/frame/local_frame.cc @@ -109,7 +109,6 @@ #include "third_party/blink/renderer/core/fileapi/public_url_manager.h" #include "third_party/blink/renderer/core/fragment_directive/text_fragment_handler.h" #include "third_party/blink/renderer/core/frame/ad_tracker.h" -#include "third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.h" #include "third_party/blink/renderer/core/frame/attribution_src_loader.h" #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" #include "third_party/blink/renderer/core/frame/event_handler_registry.h" @@ -398,7 +397,6 @@ void LocalFrame::Trace(Visitor* visitor) const { visitor->Trace(console_); visitor->Trace(smooth_scroll_sequencer_); visitor->Trace(content_capture_manager_); - visitor->Trace(anchor_element_interaction_tracker_); visitor->Trace(system_clipboard_); visitor->Trace(virtual_keyboard_overlay_changed_observers_); visitor->Trace(pause_handle_receivers_); @@ -711,10 +709,6 @@ void LocalFrame::DidAttachDocument() { // appendChild()), the drag source will detach and stop firing drag events // even after the frame reattaches. GetEventHandler().Clear(); - if (AnchorElementInteractionTracker::IsFeatureEnabled() && !IsProvisional()) { - anchor_element_interaction_tracker_ = - MakeGarbageCollected(*document); - } Selection().DidAttachDocument(document); notified_color_scheme_ = false; } diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h index 3ba2ab1d596598..e2a7640fc18a75 100644 --- a/third_party/blink/renderer/core/frame/local_frame.h +++ b/third_party/blink/renderer/core/frame/local_frame.h @@ -99,7 +99,6 @@ namespace blink { class AdTracker; class AttributionSrcLoader; -class AnchorElementInteractionTracker; class AssociatedInterfaceProvider; class BrowserInterfaceBrokerProxy; class Color; @@ -876,7 +875,6 @@ class CORE_EXPORT LocalFrame final // use the instance owned by their local root. Member smooth_scroll_sequencer_; Member content_capture_manager_; - Member anchor_element_interaction_tracker_; InterfaceRegistry* const interface_registry_; diff --git a/third_party/blink/renderer/core/loader/anchor_element_interaction_test.cc b/third_party/blink/renderer/core/loader/anchor_element_interaction_test.cc new file mode 100644 index 00000000000000..2082e437b33f3a --- /dev/null +++ b/third_party/blink/renderer/core/loader/anchor_element_interaction_test.cc @@ -0,0 +1,195 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include "base/run_loop.h" +#include "base/test/scoped_feature_list.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "third_party/blink/public/common/features.h" +#include "third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom-blink.h" +#include "third_party/blink/renderer/core/dom/element.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/html/html_anchor_element.h" +#include "third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h" +#include "third_party/blink/renderer/core/loader/anchor_element_listener.h" +#include "third_party/blink/renderer/core/testing/sim/sim_request.h" +#include "third_party/blink/renderer/core/testing/sim/sim_test.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" + +namespace blink { + +class MockAnchorElementInteractionHost + : public mojom::blink::AnchorElementInteractionHost { + public: + explicit MockAnchorElementInteractionHost( + mojo::PendingReceiver + pending_receiver) { + receiver_.Bind(std::move(pending_receiver)); + } + + absl::optional url_received_ = absl::nullopt; + + private: + void OnPointerDown(const KURL& target) override { url_received_ = target; } + + private: + mojo::Receiver receiver_{this}; +}; + +class AnchorElementInteractionTest : public SimTest { + public: + protected: + void SetUp() override { + SimTest::SetUp(); + + feature_list_.InitAndEnableFeature(features::kAnchorElementInteraction); + + MainFrame().GetFrame()->GetBrowserInterfaceBroker().SetBinderForTesting( + mojom::blink::AnchorElementInteractionHost::Name_, + WTF::BindRepeating(&AnchorElementInteractionTest::Bind, + WTF::Unretained(this))); + } + + void TearDown() override { + MainFrame().GetFrame()->GetBrowserInterfaceBroker().SetBinderForTesting( + mojom::blink::AnchorElementInteractionHost::Name_, {}); + hosts_.clear(); + SimTest::TearDown(); + } + + void Bind(mojo::ScopedMessagePipeHandle message_pipe_handle) { + auto host = std::make_unique( + mojo::PendingReceiver( + std::move(message_pipe_handle))); + hosts_.push_back(std::move(host)); + } + + base::test::ScopedFeatureList feature_list_; + std::vector> hosts_; +}; + +TEST_F(AnchorElementInteractionTest, InvalidHref) { + String source("https://example.com/p1"); + SimRequest main_resource(source, "text/html"); + LoadURL(source); + main_resource.Complete(R"HTML( + example + + )HTML"); + base::RunLoop().RunUntilIdle(); + absl::optional expected_null_url = absl::nullopt; + EXPECT_EQ(1u, hosts_.size()); + absl::optional url_received = hosts_[0]->url_received_; + EXPECT_FALSE(url_received.has_value()); + EXPECT_EQ(expected_null_url, url_received); +} + +TEST_F(AnchorElementInteractionTest, NestedAnchorElementCheck) { + String source("https://example.com/p1"); + SimRequest main_resource(source, "text/html"); + LoadURL(source); + main_resource.Complete(R"HTML( + + + )HTML"); + base::RunLoop().RunUntilIdle(); + KURL expected_url = KURL("https://anchor2.com/"); + EXPECT_EQ(1u, hosts_.size()); + absl::optional url_received = hosts_[0]->url_received_; + EXPECT_TRUE(url_received.has_value()); + EXPECT_EQ(expected_url, url_received); +} + +TEST_F(AnchorElementInteractionTest, NestedDivAnchorElementCheck) { + String source("https://example.com/p1"); + SimRequest main_resource(source, "text/html"); + LoadURL(source); + main_resource.Complete(R"HTML( +
+ + )HTML"); + base::RunLoop().RunUntilIdle(); + KURL expected_url = KURL("https://anchor1.com/"); + EXPECT_EQ(1u, hosts_.size()); + absl::optional url_received = hosts_[0]->url_received_; + EXPECT_TRUE(url_received.has_value()); + EXPECT_EQ(expected_url, url_received); +} + +TEST_F(AnchorElementInteractionTest, MultipleNestedAnchorElementCheck) { + String source("https://example.com/p1"); + SimRequest main_resource(source, "text/html"); + LoadURL(source); + main_resource.Complete(R"HTML( +

+ + )HTML"); + base::RunLoop().RunUntilIdle(); + KURL expected_url = KURL("https://anchor1.com/"); + EXPECT_EQ(1u, hosts_.size()); + absl::optional url_received = hosts_[0]->url_received_; + EXPECT_TRUE(url_received.has_value()); + EXPECT_EQ(expected_url, url_received); +} + +TEST_F(AnchorElementInteractionTest, NoAnchorElementCheck) { + String source("https://example.com/p1"); + SimRequest main_resource(source, "text/html"); + LoadURL(source); + main_resource.Complete(R"HTML( +
+ + )HTML"); + base::RunLoop().RunUntilIdle(); + absl::optional expected_null_url = absl::nullopt; + EXPECT_EQ(1u, hosts_.size()); + absl::optional url_received = hosts_[0]->url_received_; + EXPECT_FALSE(url_received.has_value()); + EXPECT_EQ(expected_null_url, url_received); +} + +TEST_F(AnchorElementInteractionTest, OneAnchorElementCheck) { + String source("https://example.com/p1"); + SimRequest main_resource(source, "text/html"); + LoadURL(source); + main_resource.Complete(R"HTML( + foo + + )HTML"); + base::RunLoop().RunUntilIdle(); + KURL expected_url = KURL("https://anchor1.com/"); + EXPECT_EQ(1u, hosts_.size()); + absl::optional url_received = hosts_[0]->url_received_; + EXPECT_TRUE(url_received.has_value()); + EXPECT_EQ(expected_url, url_received); +} + +} // namespace blink diff --git a/third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.cc b/third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.cc new file mode 100644 index 00000000000000..fb9d05308d2e6a --- /dev/null +++ b/third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.cc @@ -0,0 +1,52 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h" +#include "base/memory/weak_ptr.h" +#include "third_party/blink/public/common/browser_interface_broker_proxy.h" +#include "third_party/blink/public/common/features.h" +#include "third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom-blink.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/event_type_names.h" +#include "third_party/blink/renderer/core/execution_context/execution_context.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/loader/anchor_element_listener.h" +#include "third_party/blink/renderer/platform/heap/persistent.h" + +namespace blink { + +AnchorElementInteractionTracker::AnchorElementInteractionTracker( + Document& document) + : interaction_host_(document.GetExecutionContext()) { + base::RepeatingCallback callback = + WTF::BindRepeating(&AnchorElementInteractionTracker::OnPointerDown, + WrapWeakPersistent(this)); + + anchor_element_listener_ = + MakeGarbageCollected(callback); + + document.addEventListener(event_type_names::kPointerdown, + anchor_element_listener_, true); + + document.GetFrame()->GetBrowserInterfaceBroker().GetInterface( + interaction_host_.BindNewPipeAndPassReceiver( + document.GetExecutionContext()->GetTaskRunner( + TaskType::kInternalDefault))); +} + +void AnchorElementInteractionTracker::Trace(Visitor* visitor) const { + visitor->Trace(anchor_element_listener_); + visitor->Trace(interaction_host_); +} + +// static +bool AnchorElementInteractionTracker::IsFeatureEnabled() { + return base::FeatureList::IsEnabled(features::kAnchorElementInteraction); +} + +void AnchorElementInteractionTracker::OnPointerDown(const KURL& url) { + interaction_host_->OnPointerDown(url); +} + +} // namespace blink diff --git a/third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.h b/third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h similarity index 66% rename from third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.h rename to third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h index 4db763362518ec..44d8d097bfcc88 100644 --- a/third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.h +++ b/third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h @@ -2,16 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_ANCHOR_ELEMENT_INTERACTION_TRACKER_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_ANCHOR_ELEMENT_INTERACTION_TRACKER_H_ +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_ANCHOR_ELEMENT_INTERACTION_TRACKER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_ANCHOR_ELEMENT_INTERACTION_TRACKER_H_ +#include "third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom-blink.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h" #include "third_party/blink/renderer/platform/heap/member.h" +#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h" namespace blink { class AnchorElementListener; class Document; +class KURL; // Creates an event listener for mousedown events anywhere on a document. // If there is one, the listener will retrieve the valid href from the anchor @@ -26,12 +29,15 @@ class AnchorElementInteractionTracker static bool IsFeatureEnabled(); + void OnPointerDown(const KURL& url); + void Trace(Visitor* visitor) const; private: Member anchor_element_listener_; + HeapMojoRemote interaction_host_; }; } // namespace blink -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_ANCHOR_ELEMENT_INTERACTION_TRACKER_H_ +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_ANCHOR_ELEMENT_INTERACTION_TRACKER_H_ diff --git a/third_party/blink/renderer/core/frame/anchor_element_listener.cc b/third_party/blink/renderer/core/loader/anchor_element_listener.cc similarity index 68% rename from third_party/blink/renderer/core/frame/anchor_element_listener.cc rename to third_party/blink/renderer/core/loader/anchor_element_listener.cc index 5dcc8ef1e7704a..d866b4e713225b 100644 --- a/third_party/blink/renderer/core/frame/anchor_element_listener.cc +++ b/third_party/blink/renderer/core/loader/anchor_element_listener.cc @@ -2,13 +2,23 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "third_party/blink/renderer/core/frame/anchor_element_listener.h" +#include "third_party/blink/renderer/core/loader/anchor_element_listener.h" #include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/events/pointer_event.h" #include "third_party/blink/renderer/core/html/html_anchor_element.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h" +namespace { +constexpr const int16_t kMainEventButtonValue = 0; +constexpr const int16_t kAuxiliaryEventButtonValue = 1; +} // namespace + namespace blink { +AnchorElementListener::AnchorElementListener( + base::RepeatingCallback callback) + : tracker_callback_(std::move(callback)) {} + void AnchorElementListener::Invoke(ExecutionContext* execution_context, Event* event) { if (!event->target()) { @@ -20,6 +30,11 @@ void AnchorElementListener::Invoke(ExecutionContext* execution_context, if (!event->target()->ToNode()->IsHTMLElement()) { return; } + // TODO(crbug.com/1297312): Check if user changed the default mouse settings + if (DynamicTo(event)->button() != kMainEventButtonValue && + DynamicTo(event)->button() != kAuxiliaryEventButtonValue) { + return; + } Node* node = event->srcElement()->ToNode(); HTMLAnchorElement* html_anchor_element = FirstAnchorElementIncludingSelf(node); @@ -30,8 +45,7 @@ void AnchorElementListener::Invoke(ExecutionContext* execution_context, if (anchor_url.IsEmpty()) { return; } - // TODO(crbug.com/1297312): send URL back up to the tracker. Tracker will then - // communicate with with the browser process to preload. + tracker_callback_.Run(anchor_url); } HTMLAnchorElement* AnchorElementListener::FirstAnchorElementIncludingSelf( diff --git a/third_party/blink/renderer/core/frame/anchor_element_listener.h b/third_party/blink/renderer/core/loader/anchor_element_listener.h similarity index 54% rename from third_party/blink/renderer/core/frame/anchor_element_listener.h rename to third_party/blink/renderer/core/loader/anchor_element_listener.h index f2ed8206d21741..dac0832b7e688b 100644 --- a/third_party/blink/renderer/core/frame/anchor_element_listener.h +++ b/third_party/blink/renderer/core/loader/anchor_element_listener.h @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_ANCHOR_ELEMENT_LISTENER_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_ANCHOR_ELEMENT_LISTENER_H_ +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_ANCHOR_ELEMENT_LISTENER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_ANCHOR_ELEMENT_LISTENER_H_ +#include "base/callback.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/dom/events/native_event_listener.h" @@ -14,11 +15,15 @@ class Node; class Event; class HTMLAnchorElement; class KURL; +class ExecutionContext; // Listens for kPointerdown events, and checks to see if an anchor // element is clicked with a valid href to be eligible for preloading. class CORE_EXPORT AnchorElementListener : public NativeEventListener { public: + explicit AnchorElementListener( + base::RepeatingCallback callback); + void Invoke(ExecutionContext* execution_context, Event* event) override; private: @@ -29,17 +34,9 @@ class CORE_EXPORT AnchorElementListener : public NativeEventListener { KURL GetHrefEligibleForPreloading( const HTMLAnchorElement& html_anchor_element); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, ValidHref); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, InvalidHref); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, OneAnchorElementCheck); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, NestedAnchorElementCheck); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, - NestedDivAnchorElementCheck); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, - MultipleNestedAnchorElementCheck); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, NoAnchorElementCheck); + base::RepeatingCallback tracker_callback_; }; } // namespace blink -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_ANCHOR_ELEMENT_LISTENER_H_ +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_ANCHOR_ELEMENT_LISTENER_H_ diff --git a/third_party/blink/renderer/core/loader/build.gni b/third_party/blink/renderer/core/loader/build.gni index 5be28c5961abbe..a0428b04699ad3 100644 --- a/third_party/blink/renderer/core/loader/build.gni +++ b/third_party/blink/renderer/core/loader/build.gni @@ -5,6 +5,10 @@ blink_core_sources_loader = [ "alternate_signed_exchange_resource_info.cc", "alternate_signed_exchange_resource_info.h", + "anchor_element_listener.cc", + "anchor_element_listener.h", + "anchor_element_interaction_tracker.cc", + "anchor_element_interaction_tracker.h", "back_forward_cache_loader_helper_impl.cc", "back_forward_cache_loader_helper_impl.h", "base_fetch_context.cc",