chromium/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/WebContentsStateBridge.java

// Copyright 2020 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.chrome.browser.tab;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import org.jni_zero.NativeMethods;

import org.chromium.content_public.browser.WebContents;
import org.chromium.url.Origin;

import java.nio.ByteBuffer;

/**
 * Bridge into native serialization, deserialization, and management of {@link WebContentsState}.
 */
public class WebContentsStateBridge {
    /**
     * Creates a WebContents from the buffer.
     * @param isHidden Whether or not the tab initially starts hidden.
     * @return Pointer A WebContents object.
     */
    public static WebContents restoreContentsFromByteBuffer(
            WebContentsState webContentsState, boolean isHidden) {
        return WebContentsStateBridge.restoreContentsFromByteBuffer(
                webContentsState, isHidden, /* noRenderer= */ false);
    }

    /**
     * Creates a WebContents from the buffer.
     * @param isHidden Whether or not the tab initially starts hidden.
     * @param noRenderer Explicitly request to create without a renderer. If false a renderer may or
     *     may not be created.
     * @return Pointer A WebContents object.
     */
    public static WebContents restoreContentsFromByteBuffer(
            WebContentsState webContentsState, boolean isHidden, boolean noRenderer) {
        return WebContentsStateBridgeJni.get()
                .restoreContentsFromByteBuffer(
                        webContentsState.buffer(),
                        webContentsState.version(),
                        isHidden,
                        noRenderer);
    }

    /**
     * Deletes navigation entries from this buffer matching predicate.
     * @param predicate Handle for a deletion predicate interpreted by native code.
     * Only valid during this call frame.
     * @return WebContentsState A new state or null if nothing changed.
     */
    public static @Nullable WebContentsState deleteNavigationEntries(
            WebContentsState webContentsState, long predicate) {
        ByteBuffer newBuffer =
                WebContentsStateBridgeJni.get()
                        .deleteNavigationEntries(
                                webContentsState.buffer(), webContentsState.version(), predicate);
        return newWebContentsStateFromByteBuffer(newBuffer);
    }

    /**
     * Creates a WebContentsState for a tab that will be loaded lazily.
     *
     * @param title The title to display.
     * @param url URL that is pending.
     * @param referrerUrl URL for the referrer.
     * @param referrerPolicy Policy for the referrer.
     * @param initiatorOrigin Initiator of the navigation.
     * @param isIncognito Whether or not the state is meant to be incognito (e.g. encrypted).
     * @return ByteBuffer that represents a state representing a single pending URL.
     */
    public static ByteBuffer createSingleNavigationStateAsByteBuffer(
            String title,
            String url,
            String referrerUrl,
            int referrerPolicy,
            @Nullable Origin initiatorOrigin,
            boolean isIncognito) {
        return WebContentsStateBridgeJni.get()
                .createSingleNavigationStateAsByteBuffer(
                        title, url, referrerUrl, referrerPolicy, initiatorOrigin, isIncognito);
    }

    /**
     * Creates a WebContentsState for a tab that will be loaded lazily.
     *
     * @param webContentState The webContentsState to modify.
     * @param title The title to display.
     * @param url URL that is pending.
     * @param referrerUrl URL for the referrer.
     * @param referrerPolicy Policy for the referrer.
     * @param initiatorOrigin Initiator of the navigation.
     * @param isOffTheRecord Whether or not the state is meant to be off the record (e.g.
     *     encrypted).
     * @return ByteBuffer that represents a state with the pending navigation attached.
     */
    public static WebContentsState appendPendingNavigation(
            WebContentsState webContentsState,
            String title,
            String url,
            String referrerUrl,
            int referrerPolicy,
            @Nullable Origin initiatorOrigin,
            boolean isOffTheRecord) {
        ByteBuffer buffer =
                WebContentsStateBridgeJni.get()
                        .appendPendingNavigation(
                                webContentsState.buffer(),
                                webContentsState.version(),
                                title,
                                url,
                                referrerUrl,
                                referrerPolicy,
                                initiatorOrigin,
                                isOffTheRecord);
        return newWebContentsStateFromByteBuffer(buffer);
    }

    /**
     * Returns the WebContents' state as a ByteBuffer.
     *
     * @param webContents WebContents to pickle.
     * @return ByteBuffer containing the state of the WebContents.
     */
    public static ByteBuffer getContentsStateAsByteBuffer(WebContents webContents) {
        return WebContentsStateBridgeJni.get().getContentsStateAsByteBuffer(webContents);
    }

    /** @return Title currently being displayed in the saved state's current entry. */
    public static String getDisplayTitleFromState(WebContentsState contentsState) {
        return WebContentsStateBridgeJni.get()
                .getDisplayTitleFromByteBuffer(contentsState.buffer(), contentsState.version());
    }

    /** @return URL currently being displayed in the saved state's current entry. */
    public static String getVirtualUrlFromState(WebContentsState contentsState) {
        return WebContentsStateBridgeJni.get()
                .getVirtualUrlFromByteBuffer(contentsState.buffer(), contentsState.version());
    }

    private static @Nullable WebContentsState newWebContentsStateFromByteBuffer(
            @Nullable ByteBuffer buffer) {
        if (buffer == null) return null;
        WebContentsState newState = new WebContentsState(buffer);
        newState.setVersion(WebContentsState.CONTENTS_STATE_CURRENT_VERSION);
        return newState;
    }

    @NativeMethods
    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    public interface Natives {
        WebContents restoreContentsFromByteBuffer(
                ByteBuffer buffer,
                int savedStateVersion,
                boolean initiallyHidden,
                boolean noRenderer);

        ByteBuffer getContentsStateAsByteBuffer(WebContents webcontents);

        ByteBuffer deleteNavigationEntries(ByteBuffer state, int saveStateVersion, long predicate);

        ByteBuffer createSingleNavigationStateAsByteBuffer(
                String title,
                String url,
                String referrerUrl,
                int referrerPolicy,
                Origin initiatorOrigin,
                boolean isIncognito);

        ByteBuffer appendPendingNavigation(
                ByteBuffer buffer,
                int savedStateVersion,
                String title,
                String url,
                String referrerUrl,
                int referrerPolicy,
                Origin initiatorOrigin,
                boolean isIncognito);

        String getDisplayTitleFromByteBuffer(ByteBuffer state, int savedStateVersion);

        String getVirtualUrlFromByteBuffer(ByteBuffer state, int savedStateVersion);
    }
}