[Prerender] Restore request priorities when swapped in

Requests from prerendered contents have a IDLE priority, in order not to slow
down visible pages.
However, when the prerendered contents become visible, the request priorities
were not reset back to their correct values, leading to bad prerendering
performance.

In this CL, the priority management for prerender requests is moved to the
PrerenderResourceThrottle/PrerendereContents.
The original priorities are stored in the throttle, and restored when the
prerender contents swaps in.
A new public function is added to ResourceDispatcherHost to update a request
priority, and its implementation reuses the existing code that updates the image
priorities.

The prerender contents keeps a list of all the network resources that were
started while the prerender is hidden. If this proves to be too large, pruning
the list when responses are received should be doable.

BUG=705955

Review-Url: https://codereview.chromium.org/2807163002
Cr-Commit-Position: refs/heads/master@{#464728}
diff --git a/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc b/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc
index ef5d5c4..cc36f635 100644
--- a/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc
+++ b/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc
@@ -443,27 +443,6 @@
     safe_browsing_->OnResourceRequest(request);
 
   const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
-
-// The lowering of request priority causes issues with scheduling, since
-// content::ResourceScheduler uses it to delay and throttle requests. This is
-// disabled only on Android, as the prerenders are not likely to compete with
-// page loads there.
-// See https://crbug.com/652746 for details.
-// TODO(lizeb,droger): Fix the issue on all platforms.
-#if !defined(OS_ANDROID)
-  bool is_prerendering =
-      info->GetVisibilityState() == blink::kWebPageVisibilityStatePrerender;
-  if (is_prerendering) {
-    // Requests with the IGNORE_LIMITS flag set (i.e., sync XHRs)
-    // should remain at MAXIMUM_PRIORITY.
-    if (request->load_flags() & net::LOAD_IGNORE_LIMITS) {
-      DCHECK_EQ(request->priority(), net::MAXIMUM_PRIORITY);
-    } else {
-      request->SetPriority(net::IDLE);
-    }
-  }
-#endif  // OS_ANDROID
-
   ProfileIOData* io_data = ProfileIOData::FromResourceContext(
       resource_context);
 
diff --git a/chrome/browser/prerender/prerender_browsertest.cc b/chrome/browser/prerender/prerender_browsertest.cc
index e12a573..fdac9ca 100644
--- a/chrome/browser/prerender/prerender_browsertest.cc
+++ b/chrome/browser/prerender/prerender_browsertest.cc
@@ -143,7 +143,7 @@
 using net::NetworkChangeNotifier;
 using prerender::test_utils::RequestCounter;
 using prerender::test_utils::CreateCountingInterceptorOnIO;
-using prerender::test_utils::CreateHangingFirstRequestInterceptorOnIO;
+using prerender::test_utils::CreateHangingFirstRequestInterceptor;
 using prerender::test_utils::CreateMockInterceptorOnIO;
 using prerender::test_utils::TestPrerender;
 using prerender::test_utils::TestPrerenderContents;
@@ -166,6 +166,8 @@
 
 namespace {
 
+const char kPrefetchJpeg[] = "/prerender/image.jpeg";
+
 class FaviconUpdateWatcher : public favicon::FaviconDriverObserver {
  public:
   explicit FaviconUpdateWatcher(content::WebContents* web_contents)
@@ -539,6 +541,17 @@
       dest_url, false /* started_in_foreground */);
 }
 
+// Helper function, to allow passing a UI closure to
+// CreateHangingFirstRequestInterceptor() instead of a IO callback.
+base::Callback<void(net::URLRequest*)> GetIOCallbackFromUIClosure(
+    base::Closure ui_closure) {
+  auto lambda = [](base::Closure closure, net::URLRequest*) {
+    content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
+                                     closure);
+  };
+  return base::Bind(lambda, ui_closure);
+}
+
 }  // namespace
 
 class PrerenderBrowserTest : public test_utils::PrerenderInProcessBrowserTest {
@@ -1302,10 +1315,9 @@
   base::FilePath file(GetTestPath("prerender_page.html"));
 
   base::RunLoop prerender_start_loop;
-  BrowserThread::PostTask(
-      BrowserThread::IO, FROM_HERE,
-      base::Bind(&CreateHangingFirstRequestInterceptorOnIO, kNoCommitUrl, file,
-                 prerender_start_loop.QuitClosure()));
+  CreateHangingFirstRequestInterceptor(
+      kNoCommitUrl, file,
+      GetIOCallbackFromUIClosure(prerender_start_loop.QuitClosure()));
   DisableJavascriptCalls();
   PrerenderTestURL(kNoCommitUrl,
                    FINAL_STATUS_NAVIGATION_UNCOMMITTED,
@@ -1330,10 +1342,9 @@
   base::FilePath file(GetTestPath("prerender_page.html"));
 
   base::RunLoop prerender_start_loop;
-  BrowserThread::PostTask(
-      BrowserThread::IO, FROM_HERE,
-      base::Bind(&CreateHangingFirstRequestInterceptorOnIO, kNoCommitUrl, file,
-                 prerender_start_loop.QuitClosure()));
+  CreateHangingFirstRequestInterceptor(
+      kNoCommitUrl, file,
+      GetIOCallbackFromUIClosure(prerender_start_loop.QuitClosure()));
   DisableJavascriptCalls();
   PrerenderTestURL(CreateClientRedirect(kNoCommitUrl.spec()),
                    FINAL_STATUS_APP_TERMINATING, 1);
@@ -2109,7 +2120,7 @@
 // Checks that prerendering a JPG works correctly.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderImageJpeg) {
   DisableJavascriptCalls();
-  PrerenderTestURL("/prerender/image.jpeg", FINAL_STATUS_USED, 1);
+  PrerenderTestURL(kPrefetchJpeg, FINAL_STATUS_USED, 1);
   NavigateToDestURL();
 }
 
@@ -2181,7 +2192,7 @@
   https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME);
   https_server.ServeFilesFromSourceDirectory("chrome/test/data");
   ASSERT_TRUE(https_server.Start());
-  GURL https_url = https_server.GetURL("/prerender/image.jpeg");
+  GURL https_url = https_server.GetURL(kPrefetchJpeg);
   base::StringPairs replacement_text;
   replacement_text.push_back(
       std::make_pair("REPLACE_WITH_IMAGE_URL", https_url.spec()));
@@ -2293,7 +2304,7 @@
   https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
   https_server.ServeFilesFromSourceDirectory("chrome/test/data");
   ASSERT_TRUE(https_server.Start());
-  GURL https_url = https_server.GetURL("/prerender/image.jpeg");
+  GURL https_url = https_server.GetURL(kPrefetchJpeg);
   base::StringPairs replacement_text;
   replacement_text.push_back(
       std::make_pair("REPLACE_WITH_IMAGE_URL", https_url.spec()));
@@ -2367,7 +2378,7 @@
 
 // Ensures that we do not prerender pages which have a malware subresource.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderSafeBrowsingSubresource) {
-  GURL image_url = embedded_test_server()->GetURL("/prerender/image.jpeg");
+  GURL image_url = embedded_test_server()->GetURL(kPrefetchJpeg);
   GetFakeSafeBrowsingDatabaseManager()->SetThreatTypeForUrl(
       image_url, safe_browsing::SB_THREAT_TYPE_URL_MALWARE);
   base::StringPairs replacement_text;
@@ -2491,11 +2502,8 @@
   const GURL hang_url("http://unload-url.test");
   base::FilePath empty_file = ui_test_utils::GetTestFilePath(
       base::FilePath(), base::FilePath(FILE_PATH_LITERAL("empty.html")));
-  BrowserThread::PostTask(
-      BrowserThread::IO, FROM_HERE,
-      base::Bind(&CreateHangingFirstRequestInterceptorOnIO,
-                 hang_url, empty_file,
-                 base::Closure()));
+  CreateHangingFirstRequestInterceptor(
+      hang_url, empty_file, base::Callback<void(net::URLRequest*)>());
 
   set_loader_path("/prerender/prerender_loader_with_unload.html");
   PrerenderTestURL("/prerender/prerender_page.html", FINAL_STATUS_USED, 1);
@@ -3291,6 +3299,122 @@
   EXPECT_EQ(0, done_counter.count());
 }
 
+// Checks that the requests from a prerender are IDLE priority before the swap
+// (except on Android), but normal priority after the swap.
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ResourcePriority) {
+  GURL before_swap_url = embedded_test_server()->GetURL(kPrefetchJpeg);
+  GURL after_swap_url = embedded_test_server()->GetURL("/prerender/image.png");
+  GURL main_page_url =
+      GetURLWithReplacement("/prerender/prerender_with_image.html",
+                            "REPLACE_WITH_IMAGE_URL", kPrefetchJpeg);
+
+  // Setup request interceptors for subresources.
+  auto get_priority_lambda = [](net::RequestPriority* out_priority,
+                                net::URLRequest* request) {
+    *out_priority = request->priority();
+  };
+  RequestCounter before_swap_counter;
+  net::RequestPriority before_swap_priority = net::THROTTLED;
+  InterceptRequestAndCount(
+      before_swap_url, &before_swap_counter,
+      base::Bind(get_priority_lambda, base::Unretained(&before_swap_priority)));
+  RequestCounter after_swap_counter;
+  net::RequestPriority after_swap_priority = net::THROTTLED;
+  InterceptRequestAndCount(
+      after_swap_url, &after_swap_counter,
+      base::Bind(get_priority_lambda, base::Unretained(&after_swap_priority)));
+
+  // Start the prerender.
+  PrerenderTestURL(main_page_url, FINAL_STATUS_USED, 1);
+
+  // Check priority before swap.
+  before_swap_counter.WaitForCount(1);
+#if defined(OS_ANDROID)
+  EXPECT_GT(before_swap_priority, net::IDLE);
+#else
+  EXPECT_EQ(net::IDLE, before_swap_priority);
+#endif
+
+  // Swap.
+  NavigateToDestURL();
+
+  // Check priority after swap.
+  GetActiveWebContents()->GetMainFrame()->ExecuteJavaScriptForTests(
+      base::ASCIIToUTF16(
+          "var img=new Image(); img.src='/prerender/image.png'"));
+  after_swap_counter.WaitForCount(1);
+  EXPECT_NE(net::IDLE, after_swap_priority);
+}
+
+// Checks that a request started before the swap gets its original priority back
+// after the swap.
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ResourcePriorityOverlappingSwap) {
+  GURL image_url = embedded_test_server()->GetURL(kPrefetchJpeg);
+  GURL main_page_url =
+      GetURLWithReplacement("/prerender/prerender_with_image.html",
+                            "REPLACE_WITH_IMAGE_URL", kPrefetchJpeg);
+
+  // Setup request interceptors for subresources.
+  net::URLRequest* url_request = nullptr;
+  net::RequestPriority priority = net::THROTTLED;
+  base::RunLoop wait_loop;
+  auto io_lambda = [](net::URLRequest** out_request,
+                      net::RequestPriority* out_priority, base::Closure closure,
+                      net::URLRequest* request) {
+    if (out_request)
+      *out_request = request;
+    content::BrowserThread::PostTask(
+        content::BrowserThread::UI, FROM_HERE,
+        base::Bind(
+            [](net::RequestPriority priority,
+               net::RequestPriority* out_priority, base::Closure closure) {
+              *out_priority = priority;
+              closure.Run();
+            },
+            request->priority(), base::Unretained(out_priority), closure));
+  };
+
+  CreateHangingFirstRequestInterceptor(
+      image_url, base::FilePath(),
+      base::Bind(io_lambda, base::Unretained(&url_request),
+                 base::Unretained(&priority), wait_loop.QuitClosure()));
+
+  // The prerender will hang on the image resource, can't run the usual checks.
+  DisableLoadEventCheck();
+  DisableJavascriptCalls();
+  // Start the prerender.
+  PrerenderTestURL(main_page_url, FINAL_STATUS_USED, 0);
+
+// Check priority before swap.
+#if defined(OS_ANDROID)
+  if (priority <= net::IDLE)
+    wait_loop.Run();
+  EXPECT_GT(priority, net::IDLE);
+#else
+  if (priority != net::IDLE)
+    wait_loop.Run();
+  EXPECT_EQ(net::IDLE, priority);
+#endif
+
+  // Swap. Cannot use NavigateToDestURL, because it waits for the load to
+  // complete, but the resource is still hung.
+  current_browser()->OpenURL(content::OpenURLParams(
+      dest_url(), Referrer(), WindowOpenDisposition::CURRENT_TAB,
+      ui::PAGE_TRANSITION_TYPED, false));
+
+  // Check priority after swap. The test may timeout in case of failure.
+  priority = net::THROTTLED;
+  do {
+    base::RunLoop loop;
+    content::BrowserThread::PostTask(
+        content::BrowserThread::IO, FROM_HERE,
+        base::Bind(io_lambda, nullptr, base::Unretained(&priority),
+                   loop.QuitClosure(), base::Unretained(url_request)));
+    loop.Run();
+  } while (priority <= net::IDLE);
+  EXPECT_GT(priority, net::IDLE);
+}
+
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, FirstContentfulPaintTimingSimple) {
   GetPrerenderManager()->DisablePageLoadMetricsObserverForTesting();
   base::SimpleTestTickClock* clock = OverridePrerenderManagerTimeTicks();
@@ -3324,10 +3448,9 @@
 
   GURL url = embedded_test_server()->GetURL("/prerender/prerender_page.html");
   base::RunLoop hanging_request_waiter;
-  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
-                          base::Bind(&CreateHangingFirstRequestInterceptorOnIO,
-                                     url, GetTestPath("prerender_page.html"),
-                                     hanging_request_waiter.QuitClosure()));
+  CreateHangingFirstRequestInterceptor(
+      url, GetTestPath("prerender_page.html"),
+      GetIOCallbackFromUIClosure(hanging_request_waiter.QuitClosure()));
   // As this load will be canceled, it is not waited for, and hence no
   // javascript is executed.
   DisableJavascriptCalls();
@@ -3414,10 +3537,9 @@
       base::FilePath(FILE_PATH_LITERAL("prerender/prerender_page.html")));
 
   base::RunLoop prerender_start_loop;
-  BrowserThread::PostTask(
-      BrowserThread::IO, FROM_HERE,
-      base::Bind(&CreateHangingFirstRequestInterceptorOnIO, url, url_file,
-                 prerender_start_loop.QuitClosure()));
+  CreateHangingFirstRequestInterceptor(
+      url, url_file,
+      GetIOCallbackFromUIClosure(prerender_start_loop.QuitClosure()));
   // As this load is uncommitted, it is not waited for, and hence no
   // javascript is executed.
   DisableJavascriptCalls();
@@ -3537,10 +3659,9 @@
       base::FilePath(FILE_PATH_LITERAL("prerender/prerender_page.html")));
 
   base::RunLoop prerender_start_loop;
-  BrowserThread::PostTask(
-      BrowserThread::IO, FROM_HERE,
-      base::Bind(&CreateHangingFirstRequestInterceptorOnIO, url, url_file,
-                 prerender_start_loop.QuitClosure()));
+  CreateHangingFirstRequestInterceptor(
+      url, url_file,
+      GetIOCallbackFromUIClosure(prerender_start_loop.QuitClosure()));
   // As this load is uncommitted, it is not waited for, and hence no
   // javascript is executed.
   DisableJavascriptCalls();
diff --git a/chrome/browser/prerender/prerender_contents.cc b/chrome/browser/prerender/prerender_contents.cc
index 8fa33154..f533a2b 100644
--- a/chrome/browser/prerender/prerender_contents.cc
+++ b/chrome/browser/prerender/prerender_contents.cc
@@ -70,7 +70,12 @@
 };
 
 void ResumeThrottles(
-    std::vector<base::WeakPtr<PrerenderResourceThrottle> > throttles) {
+    std::vector<base::WeakPtr<PrerenderResourceThrottle>> throttles,
+    std::vector<base::WeakPtr<PrerenderResourceThrottle>> idle_resources) {
+  for (auto resource : idle_resources) {
+    if (resource)
+      resource->ResetResourcePriority();
+  }
   for (size_t i = 0; i < throttles.size(); i++) {
     if (throttles[i])
       throttles[i]->ResumeHandler();
@@ -757,10 +762,10 @@
   NotifyPrerenderStop();
 
   BrowserThread::PostTask(
-      BrowserThread::IO,
-      FROM_HERE,
-      base::Bind(&ResumeThrottles, resource_throttles_));
+      BrowserThread::IO, FROM_HERE,
+      base::Bind(&ResumeThrottles, resource_throttles_, idle_resources_));
   resource_throttles_.clear();
+  idle_resources_.clear();
 }
 
 void PrerenderContents::CancelPrerenderForPrinting() {
@@ -778,6 +783,11 @@
   resource_throttles_.push_back(throttle);
 }
 
+void PrerenderContents::AddIdleResource(
+    const base::WeakPtr<PrerenderResourceThrottle>& throttle) {
+  idle_resources_.push_back(throttle);
+}
+
 void PrerenderContents::AddNetworkBytes(int64_t bytes) {
   network_bytes_ += bytes;
   for (Observer& observer : observer_list_)
diff --git a/chrome/browser/prerender/prerender_contents.h b/chrome/browser/prerender/prerender_contents.h
index 082fa62d..3648fefd 100644
--- a/chrome/browser/prerender/prerender_contents.h
+++ b/chrome/browser/prerender/prerender_contents.h
@@ -235,6 +235,12 @@
   void AddResourceThrottle(
       const base::WeakPtr<PrerenderResourceThrottle>& throttle);
 
+  // Called when a PrerenderResourceThrottle changes a resource priority to
+  // net::IDLE. The resources are reset back to their original priorities when
+  // the prerender contents is swapped in.
+  void AddIdleResource(
+      const base::WeakPtr<PrerenderResourceThrottle>& throttle);
+
   // Increments the number of bytes fetched over the network for this prerender.
   void AddNetworkBytes(int64_t bytes);
 
@@ -372,6 +378,8 @@
   // Resources that are throttled, pending a prerender use. Can only access a
   // throttle on the IO thread.
   std::vector<base::WeakPtr<PrerenderResourceThrottle> > resource_throttles_;
+  // Resources for which the priority was lowered to net::IDLE.
+  std::vector<base::WeakPtr<PrerenderResourceThrottle>> idle_resources_;
 
   // A running tally of the number of bytes this prerender has caused to be
   // transferred over the network for resources.  Updated with AddNetworkBytes.
diff --git a/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc b/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc
index ecd1846..34ac7f9 100644
--- a/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc
+++ b/chrome/browser/prerender/prerender_nostate_prefetch_browsertest.cc
@@ -352,10 +352,8 @@
   base::FilePath first_path = ui_test_utils::GetTestFilePath(
       base::FilePath(), base::FilePath().AppendASCII(kPrefetchPage));
 
-  content::BrowserThread::PostTask(
-      content::BrowserThread::IO, FROM_HERE,
-      base::Bind(&test_utils::CreateHangingFirstRequestInterceptorOnIO,
-                 first_url, first_path, base::Closure()));
+  test_utils::CreateHangingFirstRequestInterceptor(
+      first_url, first_path, base::Callback<void(net::URLRequest*)>());
 
   // Start the first prefetch directly instead of via PrefetchFromFile for the
   // first prefetch to avoid the wait on prerender stop.
diff --git a/chrome/browser/prerender/prerender_resource_throttle.cc b/chrome/browser/prerender/prerender_resource_throttle.cc
index afd908f..b6ed41fd 100644
--- a/chrome/browser/prerender/prerender_resource_throttle.cc
+++ b/chrome/browser/prerender/prerender_resource_throttle.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/prerender/prerender_manager.h"
 #include "chrome/browser/prerender/prerender_util.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/resource_dispatcher_host.h"
 #include "content/public/browser/web_contents.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_response_headers.h"
@@ -85,7 +86,29 @@
 PrerenderResourceThrottle::PrerenderResourceThrottle(net::URLRequest* request)
     : request_(request),
       load_flags_(net::LOAD_NORMAL),
-      prerender_throttle_info_(new PrerenderThrottleInfo()) {}
+      prerender_throttle_info_(new PrerenderThrottleInfo()) {
+// Priorities for prerendering requests are lowered, to avoid competing with
+// other page loads, except on Android where this is less likely to be a
+// problem. In some cases, this may negatively impact the performance of
+// prerendering, see https://crbug.com/652746 for details.
+#if !defined(OS_ANDROID)
+  // Requests with the IGNORE_LIMITS flag set (i.e., sync XHRs)
+  // should remain at MAXIMUM_PRIORITY.
+  if (request_->load_flags() & net::LOAD_IGNORE_LIMITS) {
+    DCHECK_EQ(request_->priority(), net::MAXIMUM_PRIORITY);
+  } else if (request_->priority() != net::IDLE) {
+    original_request_priority_ = request_->priority();
+    // In practice, the resource scheduler does not know about the request yet,
+    // and it falls back to calling request_->SetPriority(), so it would be
+    // possible to do just that here. It is cleaner and more robust to go
+    // through the resource dispatcher host though.
+    if (content::ResourceDispatcherHost::Get()) {
+      content::ResourceDispatcherHost::Get()->ReprioritizeRequest(request_,
+                                                                  net::IDLE);
+    }
+  }
+#endif  // OS_ANDROID
+}
 
 PrerenderResourceThrottle::~PrerenderResourceThrottle() {}
 
@@ -94,6 +117,7 @@
   const content::ResourceRequestInfo* info =
       content::ResourceRequestInfo::ForRequest(request_);
   *defer = true;
+
   BrowserThread::PostTask(
       BrowserThread::UI, FROM_HERE,
       base::Bind(&PrerenderResourceThrottle::WillStartRequestOnUI, AsWeakPtr(),
@@ -149,6 +173,16 @@
   Resume();
 }
 
+void PrerenderResourceThrottle::ResetResourcePriority() {
+  if (!original_request_priority_)
+    return;
+
+  if (content::ResourceDispatcherHost::Get()) {
+    content::ResourceDispatcherHost::Get()->ReprioritizeRequest(
+        request_, original_request_priority_.value());
+  }
+}
+
 // static
 void PrerenderResourceThrottle::WillStartRequestOnUI(
     const base::WeakPtr<PrerenderResourceThrottle>& throttle,
@@ -202,9 +236,16 @@
       // Delay icon fetching until the contents are getting swapped in
       // to conserve network usage in mobile devices.
       prerender_contents->AddResourceThrottle(throttle);
+
+      // No need to call AddIdleResource() on Android.
       return;
 #endif
     }
+
+#if !defined(OS_ANDROID)
+    if (!cancel)
+      prerender_contents->AddIdleResource(throttle);
+#endif
   }
 
   BrowserThread::PostTask(
diff --git a/chrome/browser/prerender/prerender_resource_throttle.h b/chrome/browser/prerender/prerender_resource_throttle.h
index a9f853e..0302ec5 100644
--- a/chrome/browser/prerender/prerender_resource_throttle.h
+++ b/chrome/browser/prerender/prerender_resource_throttle.h
@@ -10,10 +10,12 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "chrome/common/prerender_types.h"
 #include "content/public/browser/resource_request_info.h"
 #include "content/public/browser/resource_throttle.h"
 #include "content/public/common/resource_type.h"
+#include "net/base/request_priority.h"
 
 class GURL;
 
@@ -50,6 +52,9 @@
   // May only be called if currently throttling the resource.
   void ResumeHandler();
 
+  // Resets the resource priority back to its original value.
+  void ResetResourcePriority();
+
   static void OverridePrerenderContentsForTesting(PrerenderContents* contents);
 
  private:
@@ -90,6 +95,11 @@
   net::URLRequest* request_;
   int load_flags_;  // Load flags to be OR'ed with the existing request flags.
 
+  // The throttle changes most request priorities to IDLE during prerendering.
+  // The priority is reset back to the original priority when prerendering is
+  // finished.
+  base::Optional<net::RequestPriority> original_request_priority_;
+
   scoped_refptr<PrerenderThrottleInfo> prerender_throttle_info_;
 
   DISALLOW_COPY_AND_ASSIGN(PrerenderResourceThrottle);
diff --git a/chrome/browser/prerender/prerender_test_utils.cc b/chrome/browser/prerender/prerender_test_utils.cc
index 9ee543a..15f46ca 100644
--- a/chrome/browser/prerender/prerender_test_utils.cc
+++ b/chrome/browser/prerender/prerender_test_utils.cc
@@ -177,12 +177,10 @@
 
 class HangingFirstRequestInterceptor : public net::URLRequestInterceptor {
  public:
-  HangingFirstRequestInterceptor(const base::FilePath& file,
-                                 base::Closure callback)
-      : file_(file),
-        callback_(callback),
-        first_run_(true) {
-  }
+  HangingFirstRequestInterceptor(
+      const base::FilePath& file,
+      base::Callback<void(net::URLRequest*)> callback)
+      : file_(file), callback_(callback), first_run_(true) {}
   ~HangingFirstRequestInterceptor() override {}
 
   net::URLRequestJob* MaybeInterceptRequest(
@@ -190,10 +188,8 @@
       net::NetworkDelegate* network_delegate) const override {
     if (first_run_) {
       first_run_ = false;
-      if (!callback_.is_null()) {
-        BrowserThread::PostTask(
-            BrowserThread::UI, FROM_HERE, callback_);
-      }
+      if (!callback_.is_null())
+        callback_.Run(request);
       return new HangingURLRequestJob(request, network_delegate);
     }
     return new net::URLRequestMockHTTPJob(
@@ -206,7 +202,7 @@
 
  private:
   base::FilePath file_;
-  base::Closure callback_;
+  base::Callback<void(net::URLRequest*)> callback_;
   mutable bool first_run_;
 };
 
@@ -251,6 +247,17 @@
   void FinishedProcessingCheck() override { NOTREACHED(); }
 };
 
+void CreateHangingFirstRequestInterceptorOnIO(
+    const GURL& url,
+    const base::FilePath& file,
+    base::Callback<void(net::URLRequest*)> callback_io) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+  std::unique_ptr<net::URLRequestInterceptor> interceptor(
+      new HangingFirstRequestInterceptor(file, callback_io));
+  net::URLRequestFilter::GetInstance()->AddUrlInterceptor(
+      url, std::move(interceptor));
+}
+
 }  // namespace
 
 RequestCounter::RequestCounter() : count_(0), expected_count_(-1) {}
@@ -813,13 +820,15 @@
                file, content::BrowserThread::GetBlockingPool()));
 }
 
-void CreateHangingFirstRequestInterceptorOnIO(
-    const GURL& url, const base::FilePath& file, base::Closure callback) {
-  CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
-  std::unique_ptr<net::URLRequestInterceptor> interceptor(
-      new HangingFirstRequestInterceptor(file, callback));
-  net::URLRequestFilter::GetInstance()->AddUrlInterceptor(
-      url, std::move(interceptor));
+void CreateHangingFirstRequestInterceptor(
+    const GURL& url,
+    const base::FilePath& file,
+    base::Callback<void(net::URLRequest*)> callback_io) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  content::BrowserThread::PostTask(
+      content::BrowserThread::IO, FROM_HERE,
+      base::Bind(&CreateHangingFirstRequestInterceptorOnIO, url, file,
+                 callback_io));
 }
 
 }  // namespace test_utils
diff --git a/chrome/browser/prerender/prerender_test_utils.h b/chrome/browser/prerender/prerender_test_utils.h
index cdc266b..55d889b 100644
--- a/chrome/browser/prerender/prerender_test_utils.h
+++ b/chrome/browser/prerender/prerender_test_utils.h
@@ -433,10 +433,12 @@
 void CreateMockInterceptorOnIO(const GURL& url, const base::FilePath& file);
 
 // Makes |url| never respond on the first load, and then with the contents of
-// |file| afterwards. When the first load has been scheduled, runs |callback| on
-// the UI thread.
-void CreateHangingFirstRequestInterceptorOnIO(
-    const GURL& url, const base::FilePath& file, base::Closure callback);
+// |file| afterwards. When the first load has been scheduled, runs |callback_io|
+// on the IO thread.
+void CreateHangingFirstRequestInterceptor(
+    const GURL& url,
+    const base::FilePath& file,
+    base::Callback<void(net::URLRequest*)> callback_io);
 
 }  // namespace test_utils
 
diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc
index 90ed1ce..ccd64e4 100644
--- a/content/browser/loader/resource_dispatcher_host_impl.cc
+++ b/content/browser/loader/resource_dispatcher_host_impl.cc
@@ -498,6 +498,12 @@
   http_header_interceptor_map_[http_header] = interceptor_info;
 }
 
+void ResourceDispatcherHostImpl::ReprioritizeRequest(
+    net::URLRequest* request,
+    net::RequestPriority priority) {
+  scheduler_->ReprioritizeRequest(request, priority);
+}
+
 void ResourceDispatcherHostImpl::Shutdown() {
   DCHECK(main_thread_task_runner_->BelongsToCurrentThread());
   io_thread_task_runner_->PostTask(
diff --git a/content/browser/loader/resource_dispatcher_host_impl.h b/content/browser/loader/resource_dispatcher_host_impl.h
index cbb6e11..20679dd9 100644
--- a/content/browser/loader/resource_dispatcher_host_impl.h
+++ b/content/browser/loader/resource_dispatcher_host_impl.h
@@ -108,6 +108,8 @@
   void RegisterInterceptor(const std::string& http_header,
                            const std::string& starts_with,
                            const InterceptorCallback& interceptor) override;
+  void ReprioritizeRequest(net::URLRequest* request,
+                           net::RequestPriority priority) override;
 
   // Puts the resource dispatcher host in an inactive state (unable to begin
   // new requests).  Cancels all pending requests.
diff --git a/content/browser/loader/resource_scheduler.cc b/content/browser/loader/resource_scheduler.cc
index e1dcb0a..d653c724 100644
--- a/content/browser/loader/resource_scheduler.cc
+++ b/content/browser/loader/resource_scheduler.cc
@@ -237,8 +237,9 @@
   }
 
   static ScheduledResourceRequest* ForRequest(net::URLRequest* request) {
-    return static_cast<UnownedPointer*>(request->GetUserData(kUserDataKey))
-        ->get();
+    UnownedPointer* pointer =
+        static_cast<UnownedPointer*>(request->GetUserData(kUserDataKey));
+    return pointer ? pointer->get() : nullptr;
   }
 
   // Starts the request. If |start_mode| is START_ASYNC, the request will not
@@ -1080,6 +1081,17 @@
                               new_priority_params);
 }
 
+void ResourceScheduler::ReprioritizeRequest(net::URLRequest* request,
+                                            net::RequestPriority new_priority) {
+  int current_intra_priority = 0;
+  auto* existing_request = ScheduledResourceRequest::ForRequest(request);
+  if (existing_request) {
+    current_intra_priority =
+        existing_request->get_request_priority_params().intra_priority;
+  }
+  ReprioritizeRequest(request, new_priority, current_intra_priority);
+}
+
 ResourceScheduler::ClientId ResourceScheduler::MakeClientId(
     int child_id, int route_id) {
   return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id;
diff --git a/content/browser/loader/resource_scheduler.h b/content/browser/loader/resource_scheduler.h
index bcb3213a3..71014ce 100644
--- a/content/browser/loader/resource_scheduler.h
+++ b/content/browser/loader/resource_scheduler.h
@@ -97,11 +97,16 @@
   // Returns true if at least one client is currently loading.
   bool HasLoadingClients() const;
 
-  // Update the priority for |request|. Modifies request->priority(), and may
+  // Updates the priority for |request|. Modifies request->priority(), and may
   // start the request loading if it wasn't already started.
+  // If the scheduler does not know about the request, |new_priority| is set but
+  // |intra_priority_value| is ignored.
   void ReprioritizeRequest(net::URLRequest* request,
                            net::RequestPriority new_priority,
                            int intra_priority_value);
+  // Same as above, but keeps the existing intra priority value.
+  void ReprioritizeRequest(net::URLRequest* request,
+                           net::RequestPriority new_priority);
 
  private:
   // Returns the maximum number of delayable requests to all be in-flight at
diff --git a/content/public/browser/resource_dispatcher_host.h b/content/public/browser/resource_dispatcher_host.h
index 4838b48..6f2f26f 100644
--- a/content/public/browser/resource_dispatcher_host.h
+++ b/content/public/browser/resource_dispatcher_host.h
@@ -12,6 +12,7 @@
 
 #include "base/callback_forward.h"
 #include "content/common/content_export.h"
+#include "net/base/request_priority.h"
 
 namespace net {
 class URLRequest;
@@ -84,6 +85,11 @@
                                    const std::string& starts_with,
                                    const InterceptorCallback& interceptor) = 0;
 
+  // Updates the priority for |request|. Modifies request->priority(), and may
+  // start the request loading if it wasn't already started.
+  virtual void ReprioritizeRequest(net::URLRequest* request,
+                                   net::RequestPriority priority) = 0;
+
  protected:
   virtual ~ResourceDispatcherHost() {}
 };