Skip to content

Commit

Permalink
[WebLayer] Add Prerender API
Browse files Browse the repository at this point in the history
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 <nator@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: Ryan Sturm <ryansturm@chromium.org>
Reviewed-by: Colin Blundell <blundell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#819762}
  • Loading branch information
Mugdha Lakhani authored and Commit Bot committed Oct 22, 2020
1 parent c93a3aa commit 1a0ff23
Show file tree
Hide file tree
Showing 19 changed files with 425 additions and 6 deletions.
3 changes: 2 additions & 1 deletion components/prerender/browser/prerender_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
3 changes: 3 additions & 0 deletions weblayer/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions weblayer/browser/android/javatests/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = "<html><title>TestPage</title>Hello World!</html>";
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();
}
}
3 changes: 3 additions & 0 deletions weblayer/browser/java/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -61,6 +63,8 @@ public static void enumerateAllProfileNames(ValueCallback<String[]> 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);
}
Expand All @@ -75,6 +79,11 @@ private void destroyDependentJavaObjects() {
mCookieManager.destroy();
mCookieManager = null;
}

if (mPrerenderController != null) {
mPrerenderController.destroy();
mPrerenderController = null;
}
}

@Override
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -44,4 +45,7 @@ interface IProfile {

// Added in Version 87.
void setUserIdentityCallbackClient(IUserIdentityCallbackClient client) = 13;

// Added in Version 88.
IPrerenderController getPrerenderController() = 15;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -68,15 +69,14 @@ class NoStatePrefetchBrowserTest : public WebLayerBrowserTest {
std::unique_ptr<net::test_server::HttpResponse> 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;
Expand All @@ -103,6 +103,7 @@ class NoStatePrefetchBrowserTest : public WebLayerBrowserTest {

std::unique_ptr<base::RunLoop> prerendered_page_fetched_;
std::unique_ptr<base::RunLoop> script_resource_fetched_;
bool prerendered_page_was_fetched_ = false;
bool script_fetched_ = false;
bool script_executed_ = false;
std::string purpose_header_value_;
Expand Down Expand Up @@ -205,4 +206,33 @@ IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, LinkRelNextWithNSPDisabled) {
prerendered_page_fetched_->Run();
}

} // namespace weblayer
// Non-web initiated prerender succeeds and subsequent navigations reuse
// previously downloaded resources.
IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, ExternalPrerender) {
// std::unique_ptr<PrerenderControllerImpl> 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
54 changes: 54 additions & 0 deletions weblayer/browser/no_state_prefetch/prerender_controller_impl.cc
Original file line number Diff line number Diff line change
@@ -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<jstring>& 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
Loading

0 comments on commit 1a0ff23

Please sign in to comment.