// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content_public.browser;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;
import org.chromium.base.UserDataHost;
import org.chromium.base.supplier.Supplier;
import org.chromium.content_public.browser.navigation_controller.LoadURLType;
import org.chromium.content_public.browser.navigation_controller.UserAgentOverrideOption;
import org.chromium.content_public.common.Referrer;
import org.chromium.content_public.common.ResourceRequestBody;
import org.chromium.ui.base.PageTransition;
import org.chromium.url.GURL;
import org.chromium.url.Origin;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* Holds parameters for NavigationController.LoadUrl. Parameters should match
* counterparts in NavigationController::LoadURLParams, including default
* values.
*/
@JNINamespace("content")
public class LoadUrlParams {
// Fields with counterparts in NavigationController::LoadURLParams.
private String mUrl;
private Origin mInitiatorOrigin;
private int mLoadUrlType;
private int mTransitionType;
private Referrer mReferrer;
private Map<String, String> mExtraHeaders;
@Nullable private UserDataHost mNavigationHandleUserDataHost;
private String mVerbatimHeaders;
private int mUaOverrideOption;
private ResourceRequestBody mPostData;
private String mBaseUrlForDataUrl;
private String mVirtualUrlForSpecialCases;
private String mDataUrlAsString;
private boolean mCanLoadLocalResources;
private boolean mIsRendererInitiated;
private boolean mShouldReplaceCurrentEntry;
private long mIntentReceivedTimestamp;
private long mInputStartTimestamp;
private boolean mHasUserGesture;
private boolean mShouldClearHistoryList;
@Nullable private AdditionalNavigationParams mAdditionalNavigationParams;
private Supplier<Long> mNavigationUIDataSupplier;
private boolean mIsPdf;
/**
* Creates an instance with default page transition type.
* @param url the url to be loaded
*/
public LoadUrlParams(String url) {
this(url, PageTransition.LINK);
}
/**
* Creates an instance with default page transition type.
* @param url the url to be loaded
*/
public LoadUrlParams(GURL url) {
this(url.getSpec(), PageTransition.LINK);
}
/**
* Creates an instance with the given page transition type.
* @param url the url to be loaded
* @param transitionType the PageTransitionType constant corresponding to the load
*/
public LoadUrlParams(GURL url, int transitionType) {
this(url.getSpec(), transitionType);
}
/**
* Creates an instance with the given page transition type.
* @param url the url to be loaded
* @param transitionType the PageTransitionType constant corresponding to the load
*/
public LoadUrlParams(String url, int transitionType) {
mUrl = url;
mTransitionType = transitionType;
// Initialize other fields to defaults matching defaults of the native
// NavigationController::LoadUrlParams.
mLoadUrlType = LoadURLType.DEFAULT;
mUaOverrideOption = UserAgentOverrideOption.INHERIT;
mPostData = null;
mBaseUrlForDataUrl = null;
mVirtualUrlForSpecialCases = null;
mDataUrlAsString = null;
}
/**
* Creates a new LoadUrlParams that is a copy of {@code other}.
* The user data host is intentionally not copied.
*/
public static LoadUrlParams copy(@NonNull LoadUrlParams other) {
LoadUrlParams copy = new LoadUrlParams(other.mUrl);
copy.mInitiatorOrigin = other.mInitiatorOrigin;
copy.mLoadUrlType = other.mLoadUrlType;
copy.mTransitionType = other.mTransitionType;
copy.mReferrer = other.mReferrer;
if (other.mExtraHeaders != null) {
copy.mExtraHeaders = new HashMap<>(other.mExtraHeaders);
}
// mNavigationHandleUserDataHost is intentionally not copied. We don't want any user data
// from one navigation to potentially affect a different navigation, and UserHandleData
// does not support deep copy.
copy.mVerbatimHeaders = other.mVerbatimHeaders;
copy.mUaOverrideOption = other.mUaOverrideOption;
copy.mPostData = other.mPostData;
copy.mBaseUrlForDataUrl = other.mBaseUrlForDataUrl;
copy.mVirtualUrlForSpecialCases = other.mVirtualUrlForSpecialCases;
copy.mDataUrlAsString = other.mDataUrlAsString;
copy.mCanLoadLocalResources = other.mCanLoadLocalResources;
copy.mIsRendererInitiated = other.mIsRendererInitiated;
copy.mShouldReplaceCurrentEntry = other.mShouldReplaceCurrentEntry;
copy.mIntentReceivedTimestamp = other.mIntentReceivedTimestamp;
copy.mInputStartTimestamp = other.mInputStartTimestamp;
copy.mHasUserGesture = other.mHasUserGesture;
copy.mShouldClearHistoryList = other.mShouldClearHistoryList;
copy.mAdditionalNavigationParams = other.mAdditionalNavigationParams;
return copy;
}
/**
* Helper method to create a LoadUrlParams object for data url.
* @param data Data to be loaded.
* @param mimeType Mime type of the data.
* @param isBase64Encoded True if the data is encoded in Base 64 format.
*/
public static LoadUrlParams createLoadDataParams(
String data, String mimeType, boolean isBase64Encoded) {
return createLoadDataParams(data, mimeType, isBase64Encoded, null);
}
/**
* Helper method to create a LoadUrlParams object for data url.
* @param data Data to be loaded.
* @param mimeType Mime type of the data.
* @param isBase64Encoded True if the data is encoded in Base 64 format.
* @param charset The character set for the data. Pass null if the mime type
* does not require a special charset.
*/
public static LoadUrlParams createLoadDataParams(
String data, String mimeType, boolean isBase64Encoded, String charset) {
LoadUrlParams params =
new LoadUrlParams(buildDataUri(data, mimeType, isBase64Encoded, charset));
params.setLoadType(LoadURLType.DATA);
params.setTransitionType(PageTransition.TYPED);
return params;
}
private static String buildDataUri(
String data, String mimeType, boolean isBase64Encoded, String charset) {
StringBuilder dataUrl = new StringBuilder("data:");
dataUrl.append(mimeType);
if (charset != null && !charset.isEmpty()) {
dataUrl.append(";charset=" + charset);
}
if (isBase64Encoded) {
dataUrl.append(";base64");
}
dataUrl.append(",");
dataUrl.append(data);
return dataUrl.toString();
}
/**
* Helper method to create a LoadUrlParams object for data url with base
* and virtual url.
* @param data Data to be loaded.
* @param mimeType Mime type of the data.
* @param isBase64Encoded True if the data is encoded in Base 64 format.
* @param baseUrl Base url of this data load. Note that for WebView compatibility,
* baseUrl and historyUrl are ignored if this is a data: url.
* Defaults to about:blank if null.
* @param historyUrl History url for this data load. Note that for WebView compatibility,
* this is ignored if baseUrl is a data: url. Defaults to about:blank
* if null.
*/
public static LoadUrlParams createLoadDataParamsWithBaseUrl(
String data,
String mimeType,
boolean isBase64Encoded,
String baseUrl,
String historyUrl) {
return createLoadDataParamsWithBaseUrl(
data, mimeType, isBase64Encoded, baseUrl, historyUrl, null);
}
/**
* Helper method to create a LoadUrlParams object for data url with base
* and virtual url.
* @param data Data to be loaded.
* @param mimeType Mime type of the data.
* @param isBase64Encoded True if the data is encoded in Base 64 format.
* @param baseUrl Base url of this data load. Note that for WebView compatibility,
* baseUrl and historyUrl are ignored if this is a data: url.
* Defaults to about:blank if null.
* @param historyUrl History url for this data load. Note that for WebView compatibility,
* this is ignored if baseUrl is a data: url. Defaults to about:blank
* if null.
* @param charset The character set for the data. Pass null if the mime type
* does not require a special charset.
*/
public static LoadUrlParams createLoadDataParamsWithBaseUrl(
String data,
String mimeType,
boolean isBase64Encoded,
String baseUrl,
String historyUrl,
String charset) {
LoadUrlParams params;
// For WebView compatibility, when the base URL has the 'data:'
// scheme, we treat it as a regular data URL load and skip setting
// baseUrl and historyUrl.
// TODO(joth): we should just append baseURL and historyURL here, and move the
// WebView specific transform up to a wrapper factory function in android_webview/.
if (baseUrl == null || !baseUrl.toLowerCase(Locale.US).startsWith("data:")) {
params = createLoadDataParams("", mimeType, isBase64Encoded, charset);
params.setBaseUrlForDataUrl(baseUrl != null ? baseUrl : "about:blank");
params.setVirtualUrlForSpecialCases(historyUrl != null ? historyUrl : "about:blank");
params.setDataUrlAsString(buildDataUri(data, mimeType, isBase64Encoded, charset));
} else {
params = createLoadDataParams(data, mimeType, isBase64Encoded, charset);
}
return params;
}
/**
* Helper method to create a LoadUrlParams object for an HTTP POST load.
* @param url URL of the load.
* @param postData Post data of the load. Can be null.
*/
@VisibleForTesting
public static LoadUrlParams createLoadHttpPostParams(String url, byte[] postData) {
LoadUrlParams params = new LoadUrlParams(url);
params.setLoadType(LoadURLType.HTTP_POST);
params.setTransitionType(PageTransition.TYPED);
params.setPostData(ResourceRequestBody.createFromBytes(postData));
return params;
}
/** Sets the url. */
public void setUrl(String url) {
mUrl = url;
}
/** Return the url. */
public String getUrl() {
return mUrl;
}
/** Sets the initiator origin. */
public void setInitiatorOrigin(@Nullable Origin initiatorOrigin) {
mInitiatorOrigin = initiatorOrigin;
}
/** Return the initiator origin. */
public @Nullable Origin getInitiatorOrigin() {
return mInitiatorOrigin;
}
/** Return the base url for a data url, otherwise null. */
public String getBaseUrl() {
return mBaseUrlForDataUrl;
}
/**
* Set load type of this load. Defaults to LoadURLType.DEFAULT.
* @param loadType One of LOAD_TYPE static constants above.
*/
public void setLoadType(int loadType) {
mLoadUrlType = loadType;
}
/**
* Set transition type of this load. Defaults to PageTransition.LINK.
* @param transitionType One of PAGE_TRANSITION static constants in ContentView.
*/
public void setTransitionType(int transitionType) {
mTransitionType = transitionType;
}
/** Return the transition type. */
public int getTransitionType() {
return mTransitionType;
}
/** Sets the referrer of this load. */
public void setReferrer(Referrer referrer) {
mReferrer = referrer;
}
/**
* @return the referrer of this load.
*/
public Referrer getReferrer() {
return mReferrer;
}
/**
* Set extra headers for this load.
* @param extraHeaders Extra HTTP headers for this load. Note that these
* headers will never overwrite existing ones set by Chromium.
*/
public void setExtraHeaders(Map<String, String> extraHeaders) {
mExtraHeaders = extraHeaders;
verifyHeaders();
}
/** Return the extra headers as a map. */
public Map<String, String> getExtraHeaders() {
return mExtraHeaders;
}
/**
* Returns the user data to be added to the navigation handle. Returns an empty but valid
* instance if there is no data.
*/
public UserDataHost getNavigationHandleUserData() {
if (mNavigationHandleUserDataHost == null) {
mNavigationHandleUserDataHost = new UserDataHost();
}
return mNavigationHandleUserDataHost;
}
/**
* Returns the user data to be added to the navigation handle. Clears out the existing user data
* host on each call. May return null if there is no data. This is not part of the content API.
*/
@Nullable
public UserDataHost takeNavigationHandleUserData() {
UserDataHost returnValue = mNavigationHandleUserDataHost;
mNavigationHandleUserDataHost = null;
return returnValue;
}
/**
* Return the extra headers as a single String separated by "\n", or null if no extra header is
* set. This form is suitable for passing to native
* NavigationController::LoadUrlParams::extra_headers. This will return the headers set in an
* exploded form through setExtraHeaders(). Embedders that work with extra headers in opaque
* collapsed form can use the setVerbatimHeaders() / getVerbatimHeaders() instead.
*/
public String getExtraHeadersString() {
return getExtraHeadersString("\n", false);
}
/**
* Return the extra headers as a single String separated by "\r\n", or null if no extra header
* is set. This form is suitable for passing to native
* net::HttpRequestHeaders::AddHeadersFromString.
*/
public String getExtraHttpRequestHeadersString() {
return getExtraHeadersString("\r\n", true);
}
private String getExtraHeadersString(String delimiter, boolean addTerminator) {
if (mExtraHeaders == null) return null;
StringBuilder headerBuilder = new StringBuilder();
for (Map.Entry<String, String> header : mExtraHeaders.entrySet()) {
if (headerBuilder.length() > 0) headerBuilder.append(delimiter);
// Header name should be lower case.
headerBuilder.append(header.getKey().toLowerCase(Locale.US));
headerBuilder.append(":");
headerBuilder.append(header.getValue());
}
if (addTerminator) headerBuilder.append(delimiter);
return headerBuilder.toString();
}
/**
* Sets the verbatim extra headers string. This is an alternative to storing the headers in
* a map (setExtraHeaders()) for the embedders that use collapsed headers strings.
*/
public void setVerbatimHeaders(String headers) {
mVerbatimHeaders = headers;
verifyHeaders();
}
private void verifyHeaders() {
// TODO(crbug.com/40761218): Merge extra and verbatim headers internally, and only
// expose one way to get headers, so users of this class don't miss headers.
if (mExtraHeaders != null && mVerbatimHeaders != null) {
// If both header types are set, ensure they're the same.
assert mVerbatimHeaders.equalsIgnoreCase(getExtraHeadersString());
}
}
/**
* @return the verbatim extra headers string
*/
public String getVerbatimHeaders() {
return mVerbatimHeaders;
}
/**
* Set user agent override option of this load. Defaults to UserAgentOverrideOption.INHERIT.
* @param uaOption One of UA_OVERRIDE static constants above.
*/
public void setOverrideUserAgent(int uaOption) {
mUaOverrideOption = uaOption;
}
/**
* Get user agent override option of this load. Defaults to UserAgentOverrideOption.INHERIT.
* @param uaOption One of UA_OVERRIDE static constants above.
*/
public int getUserAgentOverrideOption() {
return mUaOverrideOption;
}
/**
* Set the post data of this load, and if non-null, sets the load type to HTTP_POST.
* @param postData Post data for this http post load.
*/
public void setPostData(ResourceRequestBody postData) {
mPostData = postData;
if (postData != null) setLoadType(LoadURLType.HTTP_POST);
}
/**
* @return the data to be sent through POST
*/
public ResourceRequestBody getPostData() {
return mPostData;
}
/**
* Set the base url for data load. It is used both to resolve relative URLs
* and when applying JavaScript's same origin policy. It is ignored unless
* load type is LoadURLType.DATA.
* @param baseUrl The base url for this data load.
*/
public void setBaseUrlForDataUrl(String baseUrl) {
mBaseUrlForDataUrl = baseUrl;
}
/**
* Get the virtual url for data or pdf load. It is the url displayed to the user. It is ignored
* unless load type is LoadURLType.DATA or LoadURLType.PDF_ANDROID.
*
* @return The virtual url for this data or pdf load.
*/
public String getVirtualUrlForSpecialCases() {
return mVirtualUrlForSpecialCases;
}
/**
* Set the virtual url for data or pdf load. It is the url displayed to the user. It is ignored
* unless load type is LoadURLType.DATA or LoadURLType.PDF_ANDROID.
*
* @param virtualUrl The virtual url for this data or pdf load.
*/
public void setVirtualUrlForSpecialCases(String virtualUrl) {
mVirtualUrlForSpecialCases = virtualUrl;
}
/**
* Get the data for data load. This is then passed to the renderer as a string, not as a GURL
* object to circumvent GURL size restriction.
*
* @return The data url.
*/
public String getDataUrlAsString() {
return mDataUrlAsString;
}
/**
* Set the data for data load. This is then passed to the renderer as
* a string, not as a GURL object to circumvent GURL size restriction.
* @param url The data url.
*/
public void setDataUrlAsString(String url) {
mDataUrlAsString = url;
}
/**
* Set whether the load should be able to access local resources. This
* defaults to false.
*/
public void setCanLoadLocalResources(boolean canLoad) {
mCanLoadLocalResources = canLoad;
}
/**
* Get whether the load should be able to access local resources. This
* defaults to false.
*/
public boolean getCanLoadLocalResources() {
return mCanLoadLocalResources;
}
public int getLoadUrlType() {
return mLoadUrlType;
}
/**
* @param rendererInitiated Whether or not this load was initiated from a renderer.
*/
public void setIsRendererInitiated(boolean rendererInitiated) {
mIsRendererInitiated = rendererInitiated;
}
/**
* @return Whether or not this load was initiated from a renderer or not.
*/
public boolean getIsRendererInitiated() {
return mIsRendererInitiated;
}
/**
* @param shouldReplaceCurrentEntry Whether this navigation should replace
* the current navigation entry.
*/
public void setShouldReplaceCurrentEntry(boolean shouldReplaceCurrentEntry) {
mShouldReplaceCurrentEntry = shouldReplaceCurrentEntry;
}
/**
* @return Whether this navigation should replace the current navigation
* entry.
*/
public boolean getShouldReplaceCurrentEntry() {
return mShouldReplaceCurrentEntry;
}
/**
* @param intentReceivedTimestamp the timestamp at which Chrome received the intent that
* triggered this URL load, as returned by
* SystemClock.uptimeMillis.
*/
public void setIntentReceivedTimestamp(long intentReceivedTimestamp) {
mIntentReceivedTimestamp = intentReceivedTimestamp;
}
/**
* @return The timestamp at which Chrome received the intent that triggered this URL load.
*/
public long getIntentReceivedTimestamp() {
return mIntentReceivedTimestamp;
}
/**
* @param inputStartTimestamp the timestamp of the event in the location bar that triggered
* this URL load, as returned by SystemClock.uptimeMillis.
*/
public void setInputStartTimestamp(long inputStartTimestamp) {
mInputStartTimestamp = inputStartTimestamp;
}
/**
* @return The timestamp of the event in the location bar that triggered this URL load.
*/
public long getInputStartTimestamp() {
return mInputStartTimestamp;
}
/**
* Set whether the load is initiated by a user gesture.
*
* @param hasUserGesture True if load is initiated by user gesture, or false otherwise.
*/
public void setHasUserGesture(boolean hasUserGesture) {
mHasUserGesture = hasUserGesture;
}
/**
* @return Whether or not this load was initiated with a user gesture.
*/
public boolean getHasUserGesture() {
return mHasUserGesture;
}
/** Sets whether session history should be cleared once the navigation commits. */
public void setShouldClearHistoryList(boolean shouldClearHistoryList) {
mShouldClearHistoryList = shouldClearHistoryList;
}
/** Returns whether session history should be cleared once the navigation commits. */
public boolean getShouldClearHistoryList() {
return mShouldClearHistoryList;
}
/**
* Set the additional navigation params associated with the load.
*
* @param additionalNavigationParams Additional navigation params associated with the load.
*/
public void setAdditionalNavigationParams(
AdditionalNavigationParams additionalNavigationParams) {
mAdditionalNavigationParams = additionalNavigationParams;
}
/**
* @return The additional navigation params associated with the load.
*/
@Nullable
public AdditionalNavigationParams getAdditionalNavigationParams() {
return mAdditionalNavigationParams;
}
public boolean isBaseUrlDataScheme() {
// If there's no base url set, but this is a data load then
// treat the scheme as data:.
if (mBaseUrlForDataUrl == null && mLoadUrlType == LoadURLType.DATA) {
return true;
}
return LoadUrlParamsJni.get().isDataScheme(mBaseUrlForDataUrl);
}
/** Set the {@link NavigationUIData}. */
public void setNavigationUIDataSupplier(Supplier<Long> navigationUIDataSupplier) {
mNavigationUIDataSupplier = navigationUIDataSupplier;
}
/** Returns the supplier for {@link NavigationUIData} or null. */
public Supplier<Long> getNavigationUIDataSupplier() {
return mNavigationUIDataSupplier;
}
/**
* @return Whether the URL is a pdf file.
*/
public boolean getIsPdf() {
return mIsPdf;
}
/** Sets whether the URL is a pdf file. */
public void setIsPdf(boolean isPdf) {
mIsPdf = isPdf;
}
@NativeMethods
interface Natives {
/**
* Parses |url| as a GURL on the native side, and
* returns true if it's scheme is data:.
*/
boolean isDataScheme(String url);
}
}