chromium/chrome/android/java/src/org/chromium/chrome/browser/tab/TabStateExtractor.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 org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.common.Referrer;

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

/** Extracts a {@link TabState} from a {@link Tab}. */
public class TabStateExtractor {
    private static Map<Integer, TabState> sTabStatesForTesting;

    /**
     * Returns an opaque "state" object that can be persisted to storage.
     * @param tab The {@link Tab} from which to extract the state.
     **/
    public static TabState from(Tab tab) {
        if (sTabStatesForTesting != null && sTabStatesForTesting.containsKey(tab.getId())) {
            return sTabStatesForTesting.get(tab.getId());
        }

        if (!tab.isInitialized()) return null;
        TabState tabState = new TabState();
        tabState.contentsState = getWebContentsState(tab);
        tabState.openerAppId = TabAssociatedApp.getAppId(tab);
        tabState.parentId = tab.getParentId();
        tabState.timestampMillis = tab.getTimestampMillis();
        tabState.tabLaunchTypeAtCreation = tab.getTabLaunchTypeAtCreation();
        // Don't save the actual default theme color because it could change on night mode state
        // changed.
        tabState.themeColor =
                tab.isThemingAllowed() && !tab.isNativePage()
                        ? tab.getThemeColor()
                        : TabState.UNSPECIFIED_THEME_COLOR;
        tabState.rootId = tab.getRootId();
        tabState.userAgent = tab.getUserAgent();
        tabState.lastNavigationCommittedTimestampMillis =
                tab.getLastNavigationCommittedTimestampMillis();
        tabState.tabGroupId = tab.getTabGroupId();
        return tabState;
    }

    /**
     * Returns an object representing the state of the Tab's WebContents.
     * @param tab The {@link Tab} from which to extract the WebContents state.
     **/
    public static WebContentsState getWebContentsState(Tab tab) {
        if (tab.getWebContentsState() != null) {
            return tab.getWebContentsState();
        }

        // Native call returns null when buffer allocation needed to serialize the state failed.
        ByteBuffer buffer = getWebContentsStateAsByteBuffer(tab);
        if (buffer == null) return null;

        WebContentsState state = new WebContentsState(buffer);
        state.setVersion(WebContentsState.CONTENTS_STATE_CURRENT_VERSION);
        return state;
    }

    /** Returns an ByteBuffer representing the state of the Tab's WebContents. */
    private static ByteBuffer getWebContentsStateAsByteBuffer(Tab tab) {
        LoadUrlParams pendingLoadParams = tab.getPendingLoadParams();
        if (pendingLoadParams == null) {
            return WebContentsStateBridge.getContentsStateAsByteBuffer(tab.getWebContents());
        } else {
            Referrer referrer = pendingLoadParams.getReferrer();
            return WebContentsStateBridge.createSingleNavigationStateAsByteBuffer(
                    tab.getTitle(),
                    pendingLoadParams.getUrl(),
                    referrer != null ? referrer.getUrl() : null,
                    // Policy will be ignored for null referrer url, 0 is just a placeholder.
                    referrer != null ? referrer.getPolicy() : 0,
                    pendingLoadParams.getInitiatorOrigin(),
                    tab.isIncognito());
        }
    }

    public static void setTabStateForTesting(int tabId, TabState tabState) {
        if (sTabStatesForTesting == null) {
            sTabStatesForTesting = new HashMap<>();
        }
        sTabStatesForTesting.put(tabId, tabState);
    }

    public static void resetTabStatesForTesting() {
        sTabStatesForTesting = null;
    }
}