blink: splice fragment identifier into canonical URL for sharing

If:
* The document has a non-empty canonical URL,
* The document's canonical URL has no fragment identifier,
* The document's URL has a fragment identifier,

then include the document URL's fragment identifier in the canonical
URL for sharing purposes. This allows sharing URLs that reference
a specific anchor within the page. For example, when visiting a mobile
Wikipedia page, this allows the user to share a reference to a specific
section even though the page's canonical URL references the page as a
whole.

Note that this change applies only to fragment identifiers, not to
fragment directives (like text highlight directives) - those are
consumed in Document::SetURL and not part of the document's URL.

Fixed: 1038187
Change-Id: Ie7f562ebf74735ccc271e41c488012a952118d9b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3237449
Commit-Queue: Elly Fong-Jones <ellyjones@chromium.org>
Reviewed-by: sebsg <sebsg@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/main@{#934606}
diff --git a/third_party/blink/renderer/core/exported/web_document.cc b/third_party/blink/renderer/core/exported/web_document.cc
index 79c2557..f3e93676 100644
--- a/third_party/blink/renderer/core/exported/web_document.cc
+++ b/third_party/blink/renderer/core/exported/web_document.cc
@@ -278,7 +278,11 @@
   HTMLLinkElement* link_element = document->LinkCanonical();
   if (!link_element)
     return WebURL();
-  return link_element->Href();
+  KURL canon_url = link_element->Href();
+  KURL doc_url = document->Url();
+  if (doc_url.HasFragmentIdentifier() && !canon_url.HasFragmentIdentifier())
+    canon_url.SetFragmentIdentifier(doc_url.FragmentIdentifier());
+  return canon_url;
 }
 
 WebDistillabilityFeatures WebDocument::DistillabilityFeatures() {
diff --git a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
index 16d874f4..dbf61a85 100644
--- a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
@@ -1082,13 +1082,20 @@
 
 void LocalFrameMojoHandler::GetCanonicalUrlForSharing(
     GetCanonicalUrlForSharingCallback callback) {
-  KURL canonical_url;
+  KURL canon_url;
   HTMLLinkElement* link_element = GetDocument()->LinkCanonical();
-  if (link_element)
-    canonical_url = link_element->Href();
-  std::move(callback).Run(canonical_url.IsNull()
-                              ? absl::nullopt
-                              : absl::make_optional(canonical_url));
+  if (link_element) {
+    canon_url = link_element->Href();
+    KURL doc_url = GetDocument()->Url();
+    // When sharing links to pages, the fragment identifier often serves to mark a specific place
+    // within the page that the user wishes to point the recipient to. Canonical URLs generally
+    // don't and can't contain this state, so try to match user expectations a little more closely
+    // here by splicing the fragment identifier (if there is one) into the shared URL.
+    if (doc_url.HasFragmentIdentifier() && !canon_url.HasFragmentIdentifier())
+      canon_url.SetFragmentIdentifier(doc_url.FragmentIdentifier());
+  }
+  std::move(callback).Run(canon_url.IsNull() ? absl::nullopt
+                                             : absl::make_optional(canon_url));
 }
 
 void LocalFrameMojoHandler::AnimateDoubleTapZoom(const gfx::Point& point,
diff --git a/third_party/blink/renderer/core/frame/web_frame_test.cc b/third_party/blink/renderer/core/frame/web_frame_test.cc
index 977c14f..e1b0fd7 100644
--- a/third_party/blink/renderer/core/frame/web_frame_test.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_test.cc
@@ -13369,6 +13369,34 @@
             frame->GetDocument().CanonicalUrlForSharing());
 }
 
+TEST_F(WebFrameTest, GetCanonicalURLWithCanonicalFragment) {
+  frame_test_helpers::WebViewHelper web_view_helper;
+  web_view_helper.Initialize();
+  WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
+  frame_test_helpers::LoadHTMLString(
+      frame, R"(
+    <head>
+      <link rel="canonical" href="https://example.com/canonical.html#a1">
+    </head>)",
+      ToKURL("https://example.com/test_page.html#a2"));
+  EXPECT_EQ(WebURL(ToKURL("https://example.com/canonical.html#a1")),
+            frame->GetDocument().CanonicalUrlForSharing());
+}
+
+TEST_F(WebFrameTest, GetCanonicalURLWithDocumentFragment) {
+  frame_test_helpers::WebViewHelper web_view_helper;
+  web_view_helper.Initialize();
+  WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
+  frame_test_helpers::LoadHTMLString(
+      frame, R"(
+    <head>
+      <link rel="canonical" href="https://example.com/canonical.html">
+    </head>)",
+      ToKURL("https://example.com/test_page.html#a2"));
+  EXPECT_EQ(WebURL(ToKURL("https://example.com/canonical.html#a2")),
+            frame->GetDocument().CanonicalUrlForSharing());
+}
+
 TEST_F(WebFrameSimTest, EnterFullscreenResetScrollAndScaleState) {
   UseAndroidSettings();
   WebView().MainFrameViewWidget()->Resize(gfx::Size(490, 500));