From 1a0ff23507566b794c4b85266dc0e0c22ad09746 Mon Sep 17 00:00:00 2001 From: Mugdha Lakhani Date: Thu, 22 Oct 2020 08:41:35 +0000 Subject: [PATCH] [WebLayer] Add Prerender API Expose an API to perform a prerender (implemented using NoStatePrefetch). A browsertest and a javatest have been added to verify the expected behavior. Bug: 1136091 Change-Id: I158b5b954fbbf2e3cb9821180d6afd6aa042abd2 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2470634 Commit-Queue: Mugdha Lakhani Reviewed-by: Scott Violet Reviewed-by: Ryan Sturm Reviewed-by: Colin Blundell Cr-Commit-Position: refs/heads/master@{#819762} --- .../prerender/browser/prerender_contents.cc | 3 +- weblayer/BUILD.gn | 3 + weblayer/browser/android/javatests/BUILD.gn | 1 + .../test/PrerenderControllerTest.java | 69 +++++++++++++++++++ weblayer/browser/java/BUILD.gn | 3 + .../PrerenderControllerImpl.java | 41 +++++++++++ .../weblayer_private/ProfileImpl.java | 17 +++++ .../interfaces/IPrerenderController.aidl | 10 +++ .../weblayer_private/interfaces/IProfile.aidl | 4 ++ .../no_state_prefetch_browsertest.cc | 40 +++++++++-- .../prerender_controller_impl.cc | 54 +++++++++++++++ .../prerender_controller_impl.h | 46 +++++++++++++ weblayer/browser/profile_impl.cc | 12 ++++ weblayer/browser/profile_impl.h | 4 ++ weblayer/public/java/BUILD.gn | 1 + .../weblayer/PrerenderController.java | 66 ++++++++++++++++++ .../java/org/chromium/weblayer/Profile.java | 22 ++++++ weblayer/public/prerender_controller.h | 31 +++++++++ weblayer/public/profile.h | 4 ++ 19 files changed, 425 insertions(+), 6 deletions(-) create mode 100644 weblayer/browser/android/javatests/src/org/chromium/weblayer/test/PrerenderControllerTest.java create mode 100644 weblayer/browser/java/org/chromium/weblayer_private/PrerenderControllerImpl.java create mode 100644 weblayer/browser/java/org/chromium/weblayer_private/interfaces/IPrerenderController.aidl create mode 100644 weblayer/browser/no_state_prefetch/prerender_controller_impl.cc create mode 100644 weblayer/browser/no_state_prefetch/prerender_controller_impl.h create mode 100644 weblayer/public/java/org/chromium/weblayer/PrerenderController.java create mode 100644 weblayer/public/prerender_controller.h diff --git a/components/prerender/browser/prerender_contents.cc b/components/prerender/browser/prerender_contents.cc index 93cd80fa82c1c5..a1d64cda2983f0 100644 --- a/components/prerender/browser/prerender_contents.cc +++ b/components/prerender/browser/prerender_contents.cc @@ -190,7 +190,8 @@ void PrerenderContents::StartPrerendering( DCHECK(!prerender_contents_); DCHECK_EQ(1U, alias_urls_.size()); - session_storage_namespace_id_ = session_storage_namespace->id(); + if (session_storage_namespace) + session_storage_namespace_id_ = session_storage_namespace->id(); bounds_ = bounds; DCHECK(load_start_time_.is_null()); diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn index be8d517723f953..d6a0145554e9fe 100644 --- a/weblayer/BUILD.gn +++ b/weblayer/BUILD.gn @@ -209,6 +209,8 @@ source_set("weblayer_lib_base") { "browser/navigation_impl.h", "browser/navigation_ui_data_impl.cc", "browser/navigation_ui_data_impl.h", + "browser/no_state_prefetch/prerender_controller_impl.cc", + "browser/no_state_prefetch/prerender_controller_impl.h", "browser/no_state_prefetch/prerender_link_manager_factory.cc", "browser/no_state_prefetch/prerender_link_manager_factory.h", "browser/no_state_prefetch/prerender_manager_delegate_impl.cc", @@ -325,6 +327,7 @@ source_set("weblayer_lib_base") { "public/navigation_controller.h", "public/navigation_observer.h", "public/new_tab_delegate.h", + "public/prerender_controller.h", "public/profile.h", "public/tab.h", "public/tab_observer.h", diff --git a/weblayer/browser/android/javatests/BUILD.gn b/weblayer/browser/android/javatests/BUILD.gn index 862d5bbc71f638..2d4b6a797e66e9 100644 --- a/weblayer/browser/android/javatests/BUILD.gn +++ b/weblayer/browser/android/javatests/BUILD.gn @@ -29,6 +29,7 @@ android_library("weblayer_java_tests") { "src/org/chromium/weblayer/test/NavigationTest.java", "src/org/chromium/weblayer/test/NewTabCallbackTest.java", "src/org/chromium/weblayer/test/OnTabRemovedTabListCallbackImpl.java", + "src/org/chromium/weblayer/test/PrerenderControllerTest.java", "src/org/chromium/weblayer/test/ProfileTest.java", "src/org/chromium/weblayer/test/RenderingTest.java", "src/org/chromium/weblayer/test/ScrollOffsetCallbackTest.java", diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/PrerenderControllerTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/PrerenderControllerTest.java new file mode 100644 index 00000000000000..3d4c005de6919d --- /dev/null +++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/PrerenderControllerTest.java @@ -0,0 +1,69 @@ +// Copyright 2020 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. + +package org.chromium.weblayer.test; + +import android.net.Uri; + +import androidx.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.util.CallbackHelper; +import org.chromium.base.test.util.Feature; +import org.chromium.content_public.browser.test.util.TestThreadUtils; +import org.chromium.net.test.util.TestWebServer; +import org.chromium.net.test.util.WebServer; +import org.chromium.weblayer.PrerenderController; +import org.chromium.weblayer.shell.InstrumentationActivity; + +import java.io.IOException; +import java.io.OutputStream; + +/** Tests verifying PrerenderController behavior. */ +@RunWith(WebLayerJUnit4ClassRunner.class) +public class PrerenderControllerTest { + private static final String DEFAULT_BODY = "TestPageHello World!"; + private CallbackHelper mPrerenderedPageFetched; + + private WebServer.RequestHandler mRequestHandler = new WebServer.RequestHandler() { + @Override + public void handleRequest(WebServer.HTTPRequest request, OutputStream stream) { + try { + if (request.getURI().contains("prerendered_page.html")) { + TestThreadUtils.runOnUiThreadBlocking( + () -> mPrerenderedPageFetched.notifyCalled()); + } + WebServer.writeResponse(stream, WebServer.STATUS_OK, DEFAULT_BODY.getBytes()); + } catch (IOException exception) { + Assert.fail(exception.getMessage() + + " \n while handling request: " + request.toString()); + } + } + }; + + @Rule + public InstrumentationActivityTestRule mActivityTestRule = + new InstrumentationActivityTestRule(); + + @Test + @SmallTest + @Feature({"WebLayer"}) + public void testAddingPrerender() throws Exception { + TestWebServer testServer = TestWebServer.start(); + testServer.setRequestHandler(mRequestHandler); + mPrerenderedPageFetched = new CallbackHelper(); + InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null); + TestThreadUtils.runOnUiThreadBlocking(() -> { + PrerenderController prerenderController = + activity.getBrowser().getProfile().getPrerenderController(); + prerenderController.schedulePrerender( + Uri.parse(testServer.getResponseUrl("/prerendered_page.html"))); + }); + mPrerenderedPageFetched.waitForFirst(); + } +} diff --git a/weblayer/browser/java/BUILD.gn b/weblayer/browser/java/BUILD.gn index 9b135686d9077c..d4bad3c94d9854 100644 --- a/weblayer/browser/java/BUILD.gn +++ b/weblayer/browser/java/BUILD.gn @@ -129,6 +129,7 @@ android_library("java") { "org/chromium/weblayer_private/NavigationImpl.java", "org/chromium/weblayer_private/NewTabCallbackProxy.java", "org/chromium/weblayer_private/PageInfoControllerDelegateImpl.java", + "org/chromium/weblayer_private/PrerenderControllerImpl.java", "org/chromium/weblayer_private/ProfileImpl.java", "org/chromium/weblayer_private/ProfileManager.java", "org/chromium/weblayer_private/RemoteFragmentImpl.java", @@ -331,6 +332,7 @@ generate_jni("jni") { "org/chromium/weblayer_private/NavigationControllerImpl.java", "org/chromium/weblayer_private/NavigationImpl.java", "org/chromium/weblayer_private/NewTabCallbackProxy.java", + "org/chromium/weblayer_private/PrerenderControllerImpl.java", "org/chromium/weblayer_private/ProfileImpl.java", "org/chromium/weblayer_private/TabCallbackProxy.java", "org/chromium/weblayer_private/TabImpl.java", @@ -432,6 +434,7 @@ android_aidl("aidl") { "org/chromium/weblayer_private/interfaces/INavigationController.aidl", "org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl", "org/chromium/weblayer_private/interfaces/IObjectWrapper.aidl", + "org/chromium/weblayer_private/interfaces/IPrerenderController.aidl", "org/chromium/weblayer_private/interfaces/IProfile.aidl", "org/chromium/weblayer_private/interfaces/IRemoteFragment.aidl", "org/chromium/weblayer_private/interfaces/IRemoteFragmentClient.aidl", diff --git a/weblayer/browser/java/org/chromium/weblayer_private/PrerenderControllerImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/PrerenderControllerImpl.java new file mode 100644 index 00000000000000..1f8089e54b88ec --- /dev/null +++ b/weblayer/browser/java/org/chromium/weblayer_private/PrerenderControllerImpl.java @@ -0,0 +1,41 @@ +// Copyright 2020 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. + +package org.chromium.weblayer_private; + +import org.chromium.base.LifetimeAssert; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.weblayer_private.interfaces.IPrerenderController; + +/** + * Implementation of {@link IPrerenderController}. + */ +@JNINamespace("weblayer") +public class PrerenderControllerImpl extends IPrerenderController.Stub { + private long mNativePrerenderController; + private final LifetimeAssert mLifetimeAssert = LifetimeAssert.create(this); + + void destroy() { + mNativePrerenderController = 0; + + // If mLifetimeAssert is GC'ed before this is called, it will throw an exception + // with a stack trace showing the stack during LifetimeAssert.create(). + LifetimeAssert.setSafeToGc(mLifetimeAssert, true); + } + + public PrerenderControllerImpl(long nativePrerenderController) { + mNativePrerenderController = nativePrerenderController; + } + + @Override + public void prerender(String url) { + PrerenderControllerImplJni.get().prerender(mNativePrerenderController, url); + } + + @NativeMethods() + interface Natives { + void prerender(long nativePrerenderControllerImpl, String url); + } +} diff --git a/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java index ee752c94eff015..d29d569d0ef769 100644 --- a/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java +++ b/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java @@ -21,6 +21,7 @@ import org.chromium.weblayer_private.interfaces.ICookieManager; import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient; import org.chromium.weblayer_private.interfaces.IObjectWrapper; +import org.chromium.weblayer_private.interfaces.IPrerenderController; import org.chromium.weblayer_private.interfaces.IProfile; import org.chromium.weblayer_private.interfaces.IUserIdentityCallbackClient; import org.chromium.weblayer_private.interfaces.ObjectWrapper; @@ -41,6 +42,7 @@ public final class ProfileImpl extends IProfile.Stub implements BrowserContextHa private final String mName; private long mNativeProfile; private CookieManagerImpl mCookieManager; + private PrerenderControllerImpl mPrerenderController; private Runnable mOnDestroyCallback; private boolean mBeingDeleted; private boolean mDownloadsInitialized; @@ -61,6 +63,8 @@ public static void enumerateAllProfileNames(ValueCallback callback) { mNativeProfile = ProfileImplJni.get().createProfile(name, ProfileImpl.this); mCookieManager = new CookieManagerImpl(ProfileImplJni.get().getCookieManager(mNativeProfile)); + mPrerenderController = new PrerenderControllerImpl( + ProfileImplJni.get().getPrerenderController(mNativeProfile)); mOnDestroyCallback = onDestroyCallback; mDownloadCallbackProxy = new DownloadCallbackProxy(mName, mNativeProfile); } @@ -75,6 +79,11 @@ private void destroyDependentJavaObjects() { mCookieManager.destroy(); mCookieManager = null; } + + if (mPrerenderController != null) { + mPrerenderController.destroy(); + mPrerenderController = null; + } } @Override @@ -185,6 +194,13 @@ public ICookieManager getCookieManager() { return mCookieManager; } + @Override + public IPrerenderController getPrerenderController() { + StrictModeWorkaround.apply(); + checkNotDestroyed(); + return mPrerenderController; + } + @Override public void getBrowserPersistenceIds(@NonNull IObjectWrapper callback) { StrictModeWorkaround.apply(); @@ -293,6 +309,7 @@ void clearBrowsingData(long nativeProfileImpl, @ImplBrowsingDataType int[] dataT long fromMillis, long toMillis, Runnable callback); void setDownloadDirectory(long nativeProfileImpl, String directory); long getCookieManager(long nativeProfileImpl); + long getPrerenderController(long nativeProfileImpl); void ensureBrowserContextInitialized(long nativeProfileImpl); void setBooleanSetting(long nativeProfileImpl, int type, boolean value); boolean getBooleanSetting(long nativeProfileImpl, int type); diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IPrerenderController.aidl b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IPrerenderController.aidl new file mode 100644 index 00000000000000..f5da9fe247ca4d --- /dev/null +++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IPrerenderController.aidl @@ -0,0 +1,10 @@ +// Copyright 2020 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. + +package org.chromium.weblayer_private.interfaces; + +interface IPrerenderController { + // Since 88 + void prerender(in String url) = 0; +} diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl index d647269aee785f..11fcde8708c8c9 100644 --- a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl +++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl @@ -8,6 +8,7 @@ import org.chromium.weblayer_private.interfaces.ICookieManager; import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient; import org.chromium.weblayer_private.interfaces.IUserIdentityCallbackClient; import org.chromium.weblayer_private.interfaces.IObjectWrapper; +import org.chromium.weblayer_private.interfaces.IPrerenderController; interface IProfile { void destroy() = 0; @@ -44,4 +45,7 @@ interface IProfile { // Added in Version 87. void setUserIdentityCallbackClient(IUserIdentityCallbackClient client) = 13; + + // Added in Version 88. + IPrerenderController getPrerenderController() = 15; } diff --git a/weblayer/browser/no_state_prefetch/no_state_prefetch_browsertest.cc b/weblayer/browser/no_state_prefetch/no_state_prefetch_browsertest.cc index 0916b1e07ba13d..6c20eb885e5734 100644 --- a/weblayer/browser/no_state_prefetch/no_state_prefetch_browsertest.cc +++ b/weblayer/browser/no_state_prefetch/no_state_prefetch_browsertest.cc @@ -20,6 +20,7 @@ #include "weblayer/browser/no_state_prefetch/prerender_manager_factory.h" #include "weblayer/browser/profile_impl.h" #include "weblayer/browser/tab_impl.h" +#include "weblayer/public/prerender_controller.h" #include "weblayer/shell/browser/shell.h" #include "weblayer/test/weblayer_browser_test.h" #include "weblayer/test/weblayer_browser_test_utils.h" @@ -68,15 +69,14 @@ class NoStatePrefetchBrowserTest : public WebLayerBrowserTest { std::unique_ptr HandleRequest( const net::test_server::HttpRequest& request) { if (request.GetURL().path().find("prerendered_page") != std::string::npos) { - if (prerendered_page_fetched_) - prerendered_page_fetched_->Quit(); + prerendered_page_fetched_->Quit(); + prerendered_page_was_fetched_ = true; } if (request.GetURL().path().find("prefetch.js") != std::string::npos) { script_fetched_ = true; auto iter = request.headers.find("Purpose"); purpose_header_value_ = iter->second; - if (script_resource_fetched_) - script_resource_fetched_->Quit(); + script_resource_fetched_->Quit(); } if (request.GetURL().path().find("prefetch_meta.js") != std::string::npos) { script_executed_ = true; @@ -103,6 +103,7 @@ class NoStatePrefetchBrowserTest : public WebLayerBrowserTest { std::unique_ptr prerendered_page_fetched_; std::unique_ptr script_resource_fetched_; + bool prerendered_page_was_fetched_ = false; bool script_fetched_ = false; bool script_executed_ = false; std::string purpose_header_value_; @@ -205,4 +206,33 @@ IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, LinkRelNextWithNSPDisabled) { prerendered_page_fetched_->Run(); } -} // namespace weblayer \ No newline at end of file +// Non-web initiated prerender succeeds and subsequent navigations reuse +// previously downloaded resources. +IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, ExternalPrerender) { + // std::unique_ptr controller = + // PrerenderControllerImpl::Create(shell()->browser()); + GetProfile()->GetPrerenderController()->Prerender( + GURL(https_server_->GetURL("/prerendered_page.html"))); + + script_resource_fetched_->Run(); + + // Navigate to the prerendered page and wait for its title to change. + script_fetched_ = false; + NavigateToPageAndWaitForTitleChange( + GURL(https_server_->GetURL("/prerendered_page.html")), + base::ASCIIToUTF16("Prefetch Page")); + EXPECT_FALSE(script_fetched_); +} + +// Non-web initiated prerender fails when the user has opted out. +IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, + ExternalPrerenderWhenOptedOut) { + GetProfile()->SetBooleanSetting(SettingType::NETWORK_PREDICTION_ENABLED, + false); + GetProfile()->GetPrerenderController()->Prerender( + GURL(https_server_->GetURL("/prerendered_page.html"))); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(prerendered_page_was_fetched_); +} + +} // namespace weblayer diff --git a/weblayer/browser/no_state_prefetch/prerender_controller_impl.cc b/weblayer/browser/no_state_prefetch/prerender_controller_impl.cc new file mode 100644 index 00000000000000..a5dd8f788a11db --- /dev/null +++ b/weblayer/browser/no_state_prefetch/prerender_controller_impl.cc @@ -0,0 +1,54 @@ +// Copyright 2020 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 "weblayer/browser/no_state_prefetch/prerender_controller_impl.h" + +#include "build/build_config.h" +#include "components/prerender/browser/prerender_handle.h" +#include "components/prerender/browser/prerender_manager.h" +#include "content/public/browser/browser_context.h" +#include "ui/gfx/geometry/rect.h" +#include "url/gurl.h" +#include "weblayer/browser/no_state_prefetch/prerender_manager_factory.h" + +#if defined(OS_ANDROID) +#include "base/android/jni_string.h" +#include "weblayer/browser/java/jni/PrerenderControllerImpl_jni.h" +#endif + +namespace weblayer { + +PrerenderControllerImpl::PrerenderControllerImpl( + content::BrowserContext* browser_context) + : browser_context_(browser_context) { + DCHECK(browser_context_); +} + +PrerenderControllerImpl::~PrerenderControllerImpl() = default; + +#if defined(OS_ANDROID) +void PrerenderControllerImpl::Prerender( + JNIEnv* env, + const base::android::JavaParamRef& url) { + Prerender(GURL(ConvertJavaStringToUTF8(url))); +} +#endif + +void PrerenderControllerImpl::Prerender(const GURL& url) { + auto* prerender_manager = + PrerenderManagerFactory::GetForBrowserContext(browser_context_); + DCHECK(prerender_manager); + + // The referrer parameter results in a header being set that lets the server + // serving the URL being prerendered see where the request originated. It's + // an optional header, it's okay to skip setting it here. + // SessionStorageNamespace isn't necessary for NoStatePrefetch, so it's okay + // to pass in a nullptr. + // PrerenderManager uses default bounds if the one provided is empty. + prerender_manager->AddPrerenderFromExternalRequest( + url, content::Referrer(), /* session_storage_namespace= */ nullptr, + /* bounds= */ gfx::Rect()); +} + +} // namespace weblayer diff --git a/weblayer/browser/no_state_prefetch/prerender_controller_impl.h b/weblayer/browser/no_state_prefetch/prerender_controller_impl.h new file mode 100644 index 00000000000000..eb368a9be2bce6 --- /dev/null +++ b/weblayer/browser/no_state_prefetch/prerender_controller_impl.h @@ -0,0 +1,46 @@ +// Copyright 2020 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 WEBLAYER_BROWSER_NO_STATE_PREFETCH_PRERENDER_CONTROLLER_IMPL_H_ +#define WEBLAYER_BROWSER_NO_STATE_PREFETCH_PRERENDER_CONTROLLER_IMPL_H_ + +#include + +#include "build/build_config.h" +#include "weblayer/public/prerender_controller.h" + +#if defined(OS_ANDROID) +#include "base/android/scoped_java_ref.h" +#endif + +class GURL; + +namespace content { +class BrowserContext; +} + +namespace weblayer { + +// Enables creation of a non-web initiated prerender request. +class PrerenderControllerImpl : public PrerenderController { + public: + explicit PrerenderControllerImpl(content::BrowserContext* browser_context); + ~PrerenderControllerImpl() override; + PrerenderControllerImpl(const PrerenderControllerImpl&) = delete; + PrerenderControllerImpl& operator=(const PrerenderControllerImpl&) = delete; + +#if defined(OS_ANDROID) + void Prerender(JNIEnv* env, const base::android::JavaParamRef& url); +#endif + + // PrerenderController + void Prerender(const GURL& url) override; + + private: + content::BrowserContext* browser_context_; +}; + +} // namespace weblayer + +#endif // WEBLAYER_BROWSER_NO_STATE_PREFETCH_PRERENDER_CONTROLLER_IMPL_H_ diff --git a/weblayer/browser/profile_impl.cc b/weblayer/browser/profile_impl.cc index d353f19cb3f8ba..6f4f93181dcb76 100644 --- a/weblayer/browser/profile_impl.cc +++ b/weblayer/browser/profile_impl.cc @@ -40,6 +40,7 @@ #include "weblayer/browser/cookie_manager_impl.h" #include "weblayer/browser/favicon/favicon_service_impl.h" #include "weblayer/browser/favicon/favicon_service_impl_factory.h" +#include "weblayer/browser/no_state_prefetch/prerender_controller_impl.h" #include "weblayer/browser/persistence/browser_persister_file_utils.h" #include "weblayer/browser/tab_impl.h" @@ -314,6 +315,13 @@ CookieManager* ProfileImpl::GetCookieManager() { return cookie_manager_.get(); } +PrerenderController* ProfileImpl::GetPrerenderController() { + if (!prerender_controller_) + prerender_controller_ = + std::make_unique(GetBrowserContext()); + return prerender_controller_.get(); +} + void ProfileImpl::GetBrowserPersistenceIds( base::OnceCallback)> callback) { DCHECK(!browser_context_->IsOffTheRecord()); @@ -511,6 +519,10 @@ jlong ProfileImpl::GetCookieManager(JNIEnv* env) { return reinterpret_cast(GetCookieManager()); } +jlong ProfileImpl::GetPrerenderController(JNIEnv* env) { + return reinterpret_cast(GetPrerenderController()); +} + void ProfileImpl::EnsureBrowserContextInitialized(JNIEnv* env) { content::BrowserContext::GetDownloadManager(GetBrowserContext()); } diff --git a/weblayer/browser/profile_impl.h b/weblayer/browser/profile_impl.h index 5707d850ac993f..07c5cd923b8239 100644 --- a/weblayer/browser/profile_impl.h +++ b/weblayer/browser/profile_impl.h @@ -30,6 +30,7 @@ class WebContents; namespace weblayer { class BrowserContextImpl; class CookieManagerImpl; +class PrerenderControllerImpl; class ProfileImpl : public Profile { public: @@ -91,6 +92,7 @@ class ProfileImpl : public Profile { void SetDownloadDirectory(const base::FilePath& directory) override; void SetDownloadDelegate(DownloadDelegate* delegate) override; CookieManager* GetCookieManager() override; + PrerenderController* GetPrerenderController() override; void GetBrowserPersistenceIds( base::OnceCallback)> callback) override; void RemoveBrowserPersistenceStorage( @@ -123,6 +125,7 @@ class ProfileImpl : public Profile { JNIEnv* env, const base::android::JavaParamRef& directory); jlong GetCookieManager(JNIEnv* env); + jlong GetPrerenderController(JNIEnv* env); void EnsureBrowserContextInitialized(JNIEnv* env); void SetBooleanSetting(JNIEnv* env, jint j_type, jboolean j_value); jboolean GetBooleanSetting(JNIEnv* env, jint j_type); @@ -176,6 +179,7 @@ class ProfileImpl : public Profile { std::unique_ptr locale_change_subscription_; std::unique_ptr cookie_manager_; + std::unique_ptr prerender_controller_; #if defined(OS_ANDROID) base::android::ScopedJavaGlobalRef java_profile_; diff --git a/weblayer/public/java/BUILD.gn b/weblayer/public/java/BUILD.gn index a76b3cf1421ac3..3252520f7e631c 100644 --- a/weblayer/public/java/BUILD.gn +++ b/weblayer/public/java/BUILD.gn @@ -81,6 +81,7 @@ android_library("java") { "org/chromium/weblayer/NewTabCallback.java", "org/chromium/weblayer/NewTabType.java", "org/chromium/weblayer/ObserverList.java", + "org/chromium/weblayer/PrerenderController.java", "org/chromium/weblayer/Profile.java", "org/chromium/weblayer/RemoteFragment.java", "org/chromium/weblayer/RemoteMediaService.java", diff --git a/weblayer/public/java/org/chromium/weblayer/PrerenderController.java b/weblayer/public/java/org/chromium/weblayer/PrerenderController.java new file mode 100644 index 00000000000000..78f9a6f9a0e237 --- /dev/null +++ b/weblayer/public/java/org/chromium/weblayer/PrerenderController.java @@ -0,0 +1,66 @@ +// Copyright 2020 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. + +package org.chromium.weblayer; + +import android.net.Uri; +import android.os.RemoteException; + +import androidx.annotation.NonNull; + +import org.chromium.weblayer_private.interfaces.APICallException; +import org.chromium.weblayer_private.interfaces.IPrerenderController; +import org.chromium.weblayer_private.interfaces.IProfile; + +/** + * PrerenderController enables prerendering of urls. + * + * Prerendering has the same effect as adding a link rel="prerender" resource hint to a web page. It + * is implemented using NoStatePrefetch and fetches resources needed for a url in advance, but does + * not execute Javascript or render any part of the page in advance. For more information on + * NoStatePrefetch, see https://developers.google.com/web/updates/2018/07/nostate-prefetch. + * + * @since 88 + */ +public class PrerenderController { + private final IPrerenderController mImpl; + + static PrerenderController create(IProfile profile) { + try { + return new PrerenderController(profile.getPrerenderController()); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + // Constructor for test mocking. + protected PrerenderController() { + mImpl = null; + } + + PrerenderController(IPrerenderController prerenderController) { + mImpl = prerenderController; + } + + /* + * Creates a prerender for the url provided. + * Prerendering here is implemented using NoStatePrefetch. We fetch resources and save them + * to the HTTP cache. All resources are cached according to their cache headers. For details, + * see https://developers.google.com/web/updates/2018/07/nostate-prefetch#implementation. + * + * On low end devices or when the device has too many renderers running and prerender is + * considered expensive, we do preconnect instead. Preconnect involves creating connections with + * the server without actually fetching any resources. For more information on preconnect, see + * https://www.chromium.org/developers/design-documents/network-stack/preconnect. + * @param uri The uri to prerender. + */ + public void schedulePrerender(@NonNull Uri uri) { + ThreadCheck.ensureOnUiThread(); + try { + mImpl.prerender(uri.toString()); + } catch (RemoteException exception) { + throw new APICallException(exception); + } + } +} diff --git a/weblayer/public/java/org/chromium/weblayer/Profile.java b/weblayer/public/java/org/chromium/weblayer/Profile.java index 1b05f31f5a085b..f29758e995e743 100644 --- a/weblayer/public/java/org/chromium/weblayer/Profile.java +++ b/weblayer/public/java/org/chromium/weblayer/Profile.java @@ -66,18 +66,25 @@ public static Collection getAllProfiles() { private IProfile mImpl; private DownloadCallbackClientImpl mDownloadCallbackClient; private final CookieManager mCookieManager; + private final PrerenderController mPrerenderController; // Constructor for test mocking. protected Profile() { mName = null; mImpl = null; mCookieManager = null; + mPrerenderController = null; } private Profile(String name, IProfile impl) { mName = name; mImpl = impl; mCookieManager = CookieManager.create(impl); + if (WebLayer.getSupportedMajorVersionInternal() >= 88) { + mPrerenderController = PrerenderController.create(impl); + } else { + mPrerenderController = null; + } sProfiles.put(name, this); } @@ -198,6 +205,21 @@ public CookieManager getCookieManager() { return mCookieManager; } + /** + * Gets the prerender controller for this profile. + * + * @since 88 + */ + @NonNull + public PrerenderController getPrerenderController() { + if (WebLayer.getSupportedMajorVersionInternal() < 88) { + throw new UnsupportedOperationException(); + } + + ThreadCheck.ensureOnUiThread(); + return mPrerenderController; + } + /** * Allows the embedder to set a boolean value for a specific setting, see {@link SettingType} * for more details and the possible options. diff --git a/weblayer/public/prerender_controller.h b/weblayer/public/prerender_controller.h new file mode 100644 index 00000000000000..96e5365bb35f42 --- /dev/null +++ b/weblayer/public/prerender_controller.h @@ -0,0 +1,31 @@ +// Copyright 2020 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 WEBLAYER_PUBLIC_PRERENDER_CONTROLLER_H_ +#define WEBLAYER_PUBLIC_PRERENDER_CONTROLLER_H_ + +#include + +class GURL; + +namespace weblayer { + +// PrerenderController enables prerendering of urls. +// Prerendering has the same effect as adding a link rel="prerender" resource +// hint to a web page. It is implemented using NoStatePrefetch and fetches +// resources needed for a url in advance, but does not execute Javascript or +// render any part of the page in advance. For more information on +// NoStatePrefetch, see +// https://developers.google.com/web/updates/2018/07/nostate-prefetch. +class PrerenderController { + public: + virtual void Prerender(const GURL& url) = 0; + + protected: + virtual ~PrerenderController() = default; +}; + +} // namespace weblayer + +#endif // WEBLAYER_PUBLIC_PRERENDER_CONTROLLER_H_ diff --git a/weblayer/public/profile.h b/weblayer/public/profile.h index e5d8b5417a576d..59d26c42c3a071 100644 --- a/weblayer/public/profile.h +++ b/weblayer/public/profile.h @@ -25,6 +25,7 @@ class GURL; namespace weblayer { class CookieManager; class DownloadDelegate; +class PrerenderController; // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.weblayer_private // GENERATED_JAVA_CLASS_NAME_OVERRIDE: ImplBrowsingDataType @@ -77,6 +78,9 @@ class Profile { // Gets the cookie manager for this profile. virtual CookieManager* GetCookieManager() = 0; + // Gets the prerender controller for this profile. + virtual PrerenderController* GetPrerenderController() = 0; + // Asynchronously fetches the set of known Browser persistence-ids. See // Browser::PersistenceInfo for more details on persistence-ids. virtual void GetBrowserPersistenceIds(