chromium/chrome/android/junit/src/org/chromium/chrome/browser/tab/TabUnitTest.java

// Copyright 2019 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 static androidx.test.espresso.matcher.ViewMatchers.assertThat;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.app.Activity;
import android.content.Context;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewStructure;
import android.view.autofill.AutofillValue;

import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;

import org.chromium.base.Token;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.app.ChromeActivity;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.ui.native_page.NativePage;
import org.chromium.components.autofill.AutofillFeatures;
import org.chromium.components.autofill.AutofillProvider;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;

import java.lang.ref.WeakReference;

/** Tests for {@link Tab}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TabUnitTest {
    private static final int TAB1_ID = 456;
    private static final int TAB2_ID = 789;

    @Rule public JniMocker mocker = new JniMocker();

    @Mock private AutofillProvider mAutofillProvider;
    @Mock private Profile mProfile;
    @Mock private WindowAndroid mWindowAndroid;
    @Mock private LoadUrlParams mLoadUrlParams;
    @Mock private EmptyTabObserver mObserver;
    @Mock private Context mContext;
    @Mock private WeakReference<Context> mWeakReferenceContext;
    @Mock private WeakReference<Activity> mWeakReferenceActivity;
    @Mock private Activity mActivity;
    @Mock private NativePage mNativePage;
    @Mock private TabDelegateFactory mDelegateFactory;
    @Mock private TabWebContentsDelegateAndroid mTabWebContentsDelegateAndroid;
    @Mock private WebContents mWebContents;
    @Mock private View mNativePageView;
    @Mock private ChromeActivity mChromeActivity;
    @Mock TabImpl.Natives mNativeMock;

    private TabImpl mTab;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        doReturn(mWeakReferenceActivity).when(mWindowAndroid).getActivity();
        doReturn(mWeakReferenceContext).when(mWindowAndroid).getContext();
        doReturn(mActivity).when(mWeakReferenceActivity).get();
        doReturn(mContext).when(mWeakReferenceContext).get();
        doReturn(mContext).when(mContext).getApplicationContext();

        mTab =
                new TabImpl(TAB1_ID, mProfile, null) {
                    @Override
                    public boolean isInitialized() {
                        return true;
                    }
                };
        mTab.addObserver(mObserver);
        mTab.setAutofillProvider(mAutofillProvider);
    }

    @Test
    @SmallTest
    public void testSetRootIdWithChange() {
        TabStateAttributes.createForTab(mTab, TabCreationState.FROZEN_ON_RESTORE);
        assertThat(
                TabStateAttributes.from(mTab).getDirtinessState(),
                equalTo(TabStateAttributes.DirtinessState.CLEAN));
        assertThat(mTab.getRootId(), equalTo(TAB1_ID));

        mTab.setRootId(TAB2_ID);

        verify(mObserver).onRootIdChanged(mTab, TAB2_ID);

        assertThat(mTab.getRootId(), equalTo(TAB2_ID));
        assertThat(
                TabStateAttributes.from(mTab).getDirtinessState(),
                equalTo(TabStateAttributes.DirtinessState.DIRTY));
    }

    @Test
    @SmallTest
    public void testSetRootIdWithoutChange() {
        TabStateAttributes.createForTab(mTab, TabCreationState.FROZEN_ON_RESTORE);
        assertThat(
                TabStateAttributes.from(mTab).getDirtinessState(),
                equalTo(TabStateAttributes.DirtinessState.CLEAN));
        assertThat(mTab.getRootId(), equalTo(TAB1_ID));
        TabStateAttributes.from(mTab).clearTabStateDirtiness();

        mTab.setRootId(TAB1_ID);

        verify(mObserver, never()).onRootIdChanged(any(Tab.class), anyInt());
        assertThat(mTab.getRootId(), equalTo(TAB1_ID));
        assertThat(
                TabStateAttributes.from(mTab).getDirtinessState(),
                equalTo(TabStateAttributes.DirtinessState.CLEAN));
    }

    @Test
    @SmallTest
    public void testSetTabGroupIdWithChange() {
        TabStateAttributes.createForTab(mTab, TabCreationState.FROZEN_ON_RESTORE);
        assertThat(
                TabStateAttributes.from(mTab).getDirtinessState(),
                equalTo(TabStateAttributes.DirtinessState.CLEAN));
        assertNull(mTab.getTabGroupId());

        long tokenHigh = 0x1234567890L;
        long tokenLow = 0xABCDEF;
        Token token = new Token(tokenHigh, tokenLow);
        checkTabGroupIdChange(token);

        // Reverse field order so the token is unequal.
        token = new Token(tokenLow, tokenHigh);
        checkTabGroupIdChange(token);

        checkTabGroupIdChange(null);
    }

    private void checkTabGroupIdChange(@Nullable Token token) {
        mTab.setTabGroupId(token);

        verify(mObserver).onTabGroupIdChanged(mTab, token);

        TabStateAttributes attributes = TabStateAttributes.from(mTab);
        assertThat(mTab.getTabGroupId(), equalTo(token));
        assertThat(
                attributes.getDirtinessState(), equalTo(TabStateAttributes.DirtinessState.DIRTY));

        attributes.clearTabStateDirtiness();
    }

    @Test
    @SmallTest
    public void testSetTabGroupIdWithoutChange() {
        TabStateAttributes.createForTab(mTab, TabCreationState.FROZEN_ON_RESTORE);
        assertThat(
                TabStateAttributes.from(mTab).getDirtinessState(),
                equalTo(TabStateAttributes.DirtinessState.CLEAN));
        assertNull(mTab.getTabGroupId());
        TabStateAttributes.from(mTab).clearTabStateDirtiness();

        mTab.setTabGroupId(null);

        verify(mObserver, never()).onTabGroupIdChanged(any(Tab.class), any());
        assertNull(mTab.getTabGroupId());
        assertThat(
                TabStateAttributes.from(mTab).getDirtinessState(),
                equalTo(TabStateAttributes.DirtinessState.CLEAN));
    }

    @Test
    @SmallTest
    public void testFreezeDetachedNativePage() {
        mocker.mock(TabImplJni.TEST_HOOKS, mNativeMock);

        doReturn(mTabWebContentsDelegateAndroid)
                .when(mDelegateFactory)
                .createWebContentsDelegate(any(Tab.class));
        doReturn(mNativePage)
                .when(mDelegateFactory)
                .createNativePage(any(String.class), any(), any(Tab.class), any());
        doReturn(false).when(mNativePage).isFrozen();
        doReturn(mNativePageView).when(mNativePage).getView();
        doReturn(mWindowAndroid).when(mWebContents).getTopLevelNativeWindow();
        doReturn(mChromeActivity).when(mWeakReferenceContext).get();

        mTab =
                new TabImpl(TAB1_ID, mProfile, null) {
                    @Override
                    public WindowAndroid getWindowAndroid() {
                        return mWindowAndroid;
                    }

                    @Override
                    void updateWindowAndroid(WindowAndroid windowAndroid) {}

                    @Override
                    public WebContents getWebContents() {
                        return mWebContents;
                    }

                    @Override
                    public boolean isNativePage() {
                        return true;
                    }

                    @Override
                    void pushNativePageStateToNavigationEntry() {}
                };
        mTab.updateAttachment(mWindowAndroid, mDelegateFactory);

        // A valid, non-null NativeFrozenPage object should be instantiated when a Tab is
        // told to freeze its native page in a currently detached state.
        assertEquals(mTab.getNativePage(), mNativePage);
        mTab.freezeNativePage();
        assertNotEquals(mTab.getNativePage(), mNativePage);
    }

    @Test
    @SmallTest
    public void testMaybeLoadNativePage_nullOrEmptyUrl() {
        mTab.updateAttachment(mWindowAndroid, mDelegateFactory);
        assertFalse(
                mTab.maybeShowNativePage(
                        (String) null, /* forceReload= */ false, /* pdfInfo= */ null));
        assertFalse(mTab.maybeShowNativePage("", /* forceReload= */ false, /* pdfInfo= */ null));
    }

    @Test
    @SmallTest
    @DisableFeatures({AutofillFeatures.AUTOFILL_VIRTUAL_VIEW_STRUCTURE_ANDROID})
    public void testAutofillUnavailable() {
        assertFalse(mTab.providesAutofillStructure());
        mTab.setAutofillProvider(null);

        mTab.onProvideAutofillVirtualStructure(mock(ViewStructure.class), 0);
        verify(mAutofillProvider, never()).onProvideAutoFillVirtualStructure(any(), anyInt());

        mTab.autofill(new SparseArray<AutofillValue>());
        verify(mAutofillProvider, never()).autofill(any());
    }

    @Test
    @SmallTest
    @EnableFeatures({AutofillFeatures.AUTOFILL_VIRTUAL_VIEW_STRUCTURE_ANDROID})
    public void testAutofillRequestsHandledByProvider() {
        assertTrue(mTab.providesAutofillStructure());

        ViewStructure structure = mock(ViewStructure.class);
        mTab.onProvideAutofillVirtualStructure(
                structure, View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS);
        verify(mAutofillProvider)
                .onProvideAutoFillVirtualStructure(
                        structure, View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS);

        SparseArray<AutofillValue> values = new SparseArray<AutofillValue>();
        mTab.autofill(values);
        verify(mAutofillProvider).autofill(values);
    }

    @Test
    @SmallTest
    public void testDefaultInvalidTimestamp() {
        Tab tab = new TabImpl(1, mProfile, TabLaunchType.FROM_LINK);
        assertThat(tab.getTimestampMillis(), equalTo(TabImpl.INVALID_TIMESTAMP));
    }
}