Move async same document navigation special-case to DocumentLoader

Normally, same document navigations are synchronous. However, if the
same document navigation is requested by a cross-origin document, we
perform it asynchronously to make it harder to leak url information
to the initiator. This moves that special-case from NavigationScheduler,
which is going away, to DocumentLoader and makes it an implementation
detail of DocumentLoader::CommitSameDocumentNavigation.

Bug: 914587
Change-Id: I78b2d49d23fec4ad2a83139cc4a4ce39f22de4de
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1584367
Reviewed-by: Dmitry Gozman <dgozman@chromium.org>
Commit-Queue: Nate Chapin <japhet@chromium.org>
Cr-Commit-Position: refs/heads/master@{#654919}
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 5d1a52a..0a312155 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -984,9 +984,24 @@
     }
   }
 
-  CommitSameDocumentNavigationInternal(url, frame_load_type, history_item,
-                                       client_redirect_policy, origin_document,
-                                       has_event, std::move(extra_data));
+  // If the requesting document is cross-origin, perform the navigation
+  // asynchronously to minimize the navigator's ability to execute timing
+  // attacks.
+  if (origin_document && !origin_document->GetSecurityOrigin()->CanAccess(
+                             frame_->GetDocument()->GetSecurityOrigin())) {
+    frame_->GetTaskRunner(TaskType::kInternalLoading)
+        ->PostTask(
+            FROM_HERE,
+            WTF::Bind(&DocumentLoader::CommitSameDocumentNavigationInternal,
+                      WrapWeakPersistent(this), url, frame_load_type,
+                      WrapPersistent(history_item), client_redirect_policy,
+                      WrapPersistent(origin_document), has_event,
+                      std::move(extra_data)));
+  } else {
+    CommitSameDocumentNavigationInternal(
+        url, frame_load_type, history_item, client_redirect_policy,
+        origin_document, has_event, std::move(extra_data));
+  }
   return mojom::CommitResult::Ok;
 }
 
@@ -998,6 +1013,11 @@
     Document* initiating_document,
     bool has_event,
     std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) {
+  // If this function was scheduled to run asynchronously, this DocumentLoader
+  // might have been detached before the task ran.
+  if (!frame_)
+    return;
+
   if (!IsBackForwardLoadType(frame_load_type)) {
     SetNavigationType(has_event ? kWebNavigationTypeLinkClicked
                                 : kWebNavigationTypeOther);
diff --git a/third_party/blink/renderer/core/loader/navigation_scheduler.cc b/third_party/blink/renderer/core/loader/navigation_scheduler.cc
index 51646494..2529329 100644
--- a/third_party/blink/renderer/core/loader/navigation_scheduler.cc
+++ b/third_party/blink/renderer/core/loader/navigation_scheduler.cc
@@ -250,23 +250,15 @@
     frame_load_type = WebFrameLoadType::kReplaceCurrentItem;
 
   base::TimeTicks input_timestamp = InputTimestamp();
-  // If the URL we're going to navigate to is the same as the current one,
-  // except for the fragment part, we don't need to schedule the location
-  // change. We'll skip this optimization for cross-origin navigations to
-  // minimize the navigator's ability to execute timing attacks.
-  if (origin_document->GetSecurityOrigin()->CanAccess(
-          frame_->GetDocument()->GetSecurityOrigin())) {
-    if (url.HasFragmentIdentifier() &&
-        EqualIgnoringFragmentIdentifier(frame_->GetDocument()->Url(), url)) {
-      FrameLoadRequest request(origin_document, ResourceRequest(url), "_self");
-      request.SetInputStartTime(input_timestamp);
-      if (frame_load_type == WebFrameLoadType::kReplaceCurrentItem) {
-        request.SetClientRedirectReason(
-            ClientNavigationReason::kFrameNavigation);
-      }
-      frame_->Loader().StartNavigation(request, frame_load_type);
-      return;
+  if (url.HasFragmentIdentifier() &&
+      EqualIgnoringFragmentIdentifier(frame_->GetDocument()->Url(), url)) {
+    FrameLoadRequest request(origin_document, ResourceRequest(url), "_self");
+    request.SetInputStartTime(input_timestamp);
+    if (frame_load_type == WebFrameLoadType::kReplaceCurrentItem) {
+      request.SetClientRedirectReason(ClientNavigationReason::kFrameNavigation);
     }
+    frame_->Loader().StartNavigation(request, frame_load_type);
+    return;
   }
 
   Schedule(ScheduledFrameNavigation::Create(origin_document, url,