Commit c41f13cf authored by Mehran Mahmoudi's avatar Mehran Mahmoudi Committed by Commit Bot

[Tab View] Add TabViewManager and TabViewProvider

This CL implements a new system for managing custom Views for Tab.
The TabViewManager class acts as the entry point for all users who want
to display a View on top of Tab's Content view.

TabViewManager knows the priority of all its users and always displays
the TabViewProvider with the highest priority.

Internal design doc: http://go/tab-view-clients

Bug: 1073948
Change-Id: I5d5d0384b512d6722dd0357295de5542b9b77306
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2159248Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarPatrick Noland <pnoland@chromium.org>
Reviewed-by: default avatarMehran Mahmoudi <mahmoudi@chromium.org>
Reviewed-by: default avatarJinsuk Kim <jinsukkim@chromium.org>
Commit-Queue: Mehran Mahmoudi <mahmoudi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#763351}
parent cfb1a244
......@@ -1598,6 +1598,8 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/tab/TabUma.java",
"java/src/org/chromium/chrome/browser/tab/TabUtils.java",
"java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java",
"java/src/org/chromium/chrome/browser/tab/TabViewManager.java",
"java/src/org/chromium/chrome/browser/tab/TabViewProvider.java",
"java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java",
"java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java",
"java/src/org/chromium/chrome/browser/tab/TabWebContentsUserData.java",
......
......@@ -211,6 +211,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelperTest.java",
"junit/src/org/chromium/chrome/browser/tab/TabUnitTest.java",
"junit/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegateTest.java",
"junit/src/org/chromium/chrome/browser/tab/TabViewManagerTest.java",
"junit/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorImplTest.java",
"junit/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorProfileSupplierTest.java",
"junit/src/org/chromium/chrome/browser/tabstate/TabStateUnitTest.java",
......
......@@ -34,12 +34,11 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.flags.BooleanCachedFieldTrialParameter;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.tab.SadTab;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabViewManager;
import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
import org.chromium.chrome.browser.ui.native_page.FrozenNativePage;
import org.chromium.chrome.browser.ui.native_page.NativePage;
import org.chromium.chrome.browser.usage_stats.SuspendedTab;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.display.DisplayAndroid;
......@@ -280,7 +279,7 @@ public class TabContentManager {
View viewToDraw = null;
if (isNativeViewShowing) {
viewToDraw = tab.getContentView();
viewToDraw = tab.getView();
} else if (!(nativePage instanceof FrozenNativePage)) {
viewToDraw = nativePage.getView();
}
......@@ -644,7 +643,7 @@ public class TabContentManager {
}
private boolean isNativeViewShowing(Tab tab) {
return tab != null && (SadTab.isShowing(tab) || SuspendedTab.isShowing(tab));
return tab != null && TabViewManager.get(tab).getCurrentTabViewProvider() != null;
}
@NativeMethods
......
......@@ -4,13 +4,16 @@
package org.chromium.chrome.browser.paint_preview;
import android.view.View;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.lifecycle.Destroyable;
import org.chromium.chrome.browser.paint_preview.services.PaintPreviewDemoService;
import org.chromium.chrome.browser.paint_preview.services.PaintPreviewDemoServiceFactory;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabThemeColorHelper;
import org.chromium.chrome.browser.tab.TabViewManager;
import org.chromium.chrome.browser.tab.TabViewProvider;
import org.chromium.components.paintpreview.player.PlayerManager;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.UiThreadTaskTraits;
......@@ -21,7 +24,7 @@ import org.chromium.url.GURL;
* Responsible for displaying the Paint Preview demo. When displaying, the Paint Preview will
* overlay the associated {@link Tab}'s content view.
*/
public class PaintPreviewDemoManager implements Destroyable {
public class PaintPreviewDemoManager implements TabViewProvider {
private Tab mTab;
private PaintPreviewDemoService mPaintPreviewDemoService;
private PlayerManager mPlayerManager;
......@@ -66,28 +69,22 @@ public class PaintPreviewDemoManager implements Destroyable {
.show();
return;
}
mTab.getContentView().addView(mPlayerManager.getView());
Toast.makeText(mTab.getContext(), R.string.paint_preview_demo_playback_start,
Toast.LENGTH_LONG)
.show();
TabViewManager.get(mTab).addTabViewProvider(this);
}
public void removePaintPreviewDemo() {
void removePaintPreviewDemo() {
if (mTab == null || mPlayerManager == null) {
return;
}
mTab.getContentView().removeView(mPlayerManager.getView());
TabViewManager.get(mTab).removeTabViewProvider(this);
mPaintPreviewDemoService.cleanUpForTabId(mTab.getId());
mPlayerManager = null;
Toast.makeText(
mTab.getContext(), R.string.paint_preview_demo_playback_end, Toast.LENGTH_LONG)
.show();
}
public boolean isShowingPaintPreviewDemo() {
boolean isShowingPaintPreviewDemo() {
return mPlayerManager != null
&& mPlayerManager.getView().getParent() == mTab.getContentView();
&& TabViewManager.get(mTab).getCurrentTabViewProvider() == this;
}
private void onLinkClicked(GURL url) {
......@@ -96,10 +93,33 @@ public class PaintPreviewDemoManager implements Destroyable {
mTab.loadUrl(new LoadUrlParams(url.getSpec()));
}
@Override
public void destroy() {
if (mPaintPreviewDemoService != null) {
mPaintPreviewDemoService.cleanUpForTabId(mTab.getId());
}
}
@Override
public int getTabViewProviderType() {
return Type.PAINT_PREVIEW;
}
@Override
public View getView() {
return mPlayerManager == null ? null : mPlayerManager.getView();
}
@Override
public void onShown() {
Toast.makeText(mTab.getContext(), R.string.paint_preview_demo_playback_start,
Toast.LENGTH_LONG)
.show();
}
@Override
public void onHidden() {
Toast.makeText(
mTab.getContext(), R.string.paint_preview_demo_playback_end, Toast.LENGTH_LONG)
.show();
}
}
......@@ -12,9 +12,8 @@ import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
......@@ -35,7 +34,7 @@ import org.chromium.ui.widget.ChromeBulletSpan;
* Represent the sad tab displayed in place of a crashed renderer. Instantiated on the first
* |show()| request from a Tab, and destroyed together with it.
*/
public class SadTab extends EmptyTabObserver implements UserData {
public class SadTab extends EmptyTabObserver implements UserData, TabViewProvider {
private static final Class<SadTab> USER_DATA_KEY = SadTab.class;
private final TabImpl mTab;
......@@ -113,10 +112,7 @@ public class SadTab extends EmptyTabObserver implements UserData {
suggestionAction, buttonAction, showSendFeedbackView, mTab.isIncognito());
mSadTabSuccessiveRefreshCounter++;
// Show the sad tab inside ContentView.
mTab.getContentView().addView(mView,
new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mTab.notifyContentChanged();
TabViewManager.get(mTab).addTabViewProvider(this);
}
/**
......@@ -124,10 +120,7 @@ public class SadTab extends EmptyTabObserver implements UserData {
*/
@VisibleForTesting
public void removeIfPresent() {
if (isShowing()) {
mTab.getContentView().removeView(mView);
mTab.notifyContentChanged();
}
TabViewManager.get(mTab).removeTabViewProvider(this);
mView = null;
}
......@@ -135,7 +128,7 @@ public class SadTab extends EmptyTabObserver implements UserData {
* @return Whether or not the sad tab is showing.
*/
public boolean isShowing() {
return mView != null && mView.getParent() == mTab.getContentView();
return mView != null && TabViewManager.get(mTab).getCurrentTabViewProvider() == this;
}
// TabObserver
......@@ -181,6 +174,8 @@ public class SadTab extends EmptyTabObserver implements UserData {
LayoutInflater inflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View sadTabView = inflater.inflate(R.layout.sad_tab, null);
sadTabView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
TextView titleText = (TextView) sadTabView.findViewById(R.id.sad_tab_title);
int titleTextId =
......@@ -299,4 +294,14 @@ public class SadTab extends EmptyTabObserver implements UserData {
public static void initForTesting(Tab tab, SadTab sadTab) {
tab.getUserDataHost().setUserData(USER_DATA_KEY, sadTab);
}
@Override
public int getTabViewProviderType() {
return Type.SAD_TAB;
}
@Override
public View getView() {
return mView;
}
}
......@@ -90,6 +90,9 @@ public class TabImpl implements Tab, TabObscuringHandler.Observer {
/** The parent view of the ContentView and the InfoBarContainer. */
private ContentView mContentView;
/** The view provided by {@link TabViewManager} to be shown on top of Content view. */
private View mCustomView;
/** A list of Tab observers. These are used to broadcast Tab events to listeners. */
private final ObserverList<TabObserver> mObservers = new ObserverList<>();
......@@ -334,6 +337,14 @@ public class TabImpl implements Tab, TabObscuringHandler.Observer {
}
}
/**
* Sets a custom {@link View} for this {@link Tab} that replaces Content view.
*/
void setCustomView(@Nullable View view) {
mCustomView = view;
notifyContentChanged();
}
@Override
public ContentView getContentView() {
return mContentView;
......@@ -341,7 +352,11 @@ public class TabImpl implements Tab, TabObscuringHandler.Observer {
@Override
public View getView() {
return mNativePage != null ? mNativePage.getView() : mContentView;
if (mCustomView != null) return mCustomView;
if (mNativePage != null) return mNativePage.getView();
return mContentView;
}
@Override
......@@ -790,7 +805,7 @@ public class TabImpl implements Tab, TabObscuringHandler.Observer {
WebContentsAccessibility wcax = getWebContentsAccessibility(getWebContents());
if (wcax != null) {
boolean isWebContentObscured = isObscured || SadTab.isShowing(this);
boolean isWebContentObscured = isObscured || mCustomView != null;
wcax.setObscuredByAnotherView(isWebContentObscured);
}
}
......
// 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.chrome.browser.tab;
import android.util.SparseIntArray;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.UserData;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
* TODO(crbug.com/1074348) Make this an interface and move it to //chrome/browser/tab.
* This class is responsible for displaying custom {@link View}s on top of {@link Tab}'s Content
* view. Users that want to display a custom {@link View} should:
* 1. Implement {@link TabViewProvider}
* 2. Add an entry to {@link TabViewProvider.Type}
* 3. Add their {@link TabViewProvider.Type} to {@link #PRIORITIZED_TAB_VIEW_PROVIDER_TYPES}
* with an appropriate priority. In order to find the right priority, please consider the
* existing entries in the array and determine where the new feature fits relative to them.
* 4. Use {@link #addTabViewProvider} and {@link #removeTabViewProvider} to add and remove their
* {@link TabViewProvider}
*/
public class TabViewManager implements UserData, Comparator<TabViewProvider> {
/**
* A prioritized list of all {@link TabViewProvider.Type}s, from most important to least
* important. The {@link TabViewProvider} with the highest priority will always be shown first,
* regardless of its insertion time relative to other {@link TabViewProvider}s.
*/
@VisibleForTesting
@TabViewProvider.Type
static final int[] PRIORITIZED_TAB_VIEW_PROVIDER_TYPES = new int[] {
TabViewProvider.Type.SUSPENDED_TAB,
TabViewProvider.Type.PAINT_PREVIEW,
TabViewProvider.Type.SAD_TAB};
/**
* A lookup table for {@link #PRIORITIZED_TAB_VIEW_PROVIDER_TYPES}. This is initialized in the
* following static block and doesn't need to be manually updated.
*/
private static final SparseIntArray TAB_VIEW_PROVIDER_PRIORITY_LOOKUP = new SparseIntArray();
static {
for (int i = 0; i < PRIORITIZED_TAB_VIEW_PROVIDER_TYPES.length; i++) {
TAB_VIEW_PROVIDER_PRIORITY_LOOKUP.put(PRIORITIZED_TAB_VIEW_PROVIDER_TYPES[i], i);
}
}
private static final Class<TabViewManager> USER_DATA_KEY = TabViewManager.class;
private PriorityQueue<TabViewProvider> mTabViewProviders;
private TabImpl mTab;
public static TabViewManager get(Tab tab) {
if (tab.getUserDataHost().getUserData(USER_DATA_KEY) == null) {
tab.getUserDataHost().setUserData(USER_DATA_KEY, new TabViewManager(tab));
}
return tab.getUserDataHost().getUserData(USER_DATA_KEY);
}
@VisibleForTesting
TabViewManager(Tab tab) {
mTab = (TabImpl) tab;
mTabViewProviders = new PriorityQueue<>(PRIORITIZED_TAB_VIEW_PROVIDER_TYPES.length, this);
}
/**
* @return The {@link TabViewProvider} that is being currently displayed.
*/
public @Nullable TabViewProvider getCurrentTabViewProvider() {
return mTabViewProviders.peek();
}
/**
* Adds a {@link TabViewProvider} to be shown in the {@link Tab} associated with this {@link
* TabViewManager}. If the given {@link TabViewProvider} has the highest priority, it will be
* shown immediately. Otherwise, it will be shown after other {@link TabViewProvider}s with
* higher priorities are removed.
*/
public void addTabViewProvider(TabViewProvider tabViewProvider) {
if (mTabViewProviders.contains(tabViewProvider)) return;
TabViewProvider currentTabViewProvider = mTabViewProviders.peek();
mTabViewProviders.add(tabViewProvider);
updateCurrentTabViewProvider(currentTabViewProvider);
}
/**
* Remove the given {@link TabViewProvider} from the {@link Tab} associated with this {@link
* TabViewManager}. If the given {@link TabViewProvider} is currently shown, the next available
* {@link TabViewProvider} with the highest priority will be shown. If there are no other {@link
* TabViewProvider}s, {@link Tab}'s Content view will be shown.
*/
public void removeTabViewProvider(TabViewProvider tabViewProvider) {
TabViewProvider currentTabViewProvider = mTabViewProviders.peek();
mTabViewProviders.remove(tabViewProvider);
updateCurrentTabViewProvider(currentTabViewProvider);
}
private void updateCurrentTabViewProvider(TabViewProvider previousTabViewProvider) {
if (mTab == null) return;
TabViewProvider currentTabViewProvider = mTabViewProviders.peek();
if (currentTabViewProvider != previousTabViewProvider) {
View view = null;
if (currentTabViewProvider != null) {
view = currentTabViewProvider.getView();
assert view != null;
}
mTab.setCustomView(view);
if (previousTabViewProvider != null) previousTabViewProvider.onHidden();
if (currentTabViewProvider != null) currentTabViewProvider.onShown();
}
}
/**
* Compares two {@link TabViewProvider}s based on their priority in
* {@link #PRIORITIZED_TAB_VIEW_PROVIDER_TYPES}. Do not edit the logic here when you add a new
* {@link TabViewProvider.Type}. Instead, simply add your new {@link TabViewProvider.Type} to
* {@link #PRIORITIZED_TAB_VIEW_PROVIDER_TYPES}.
*/
@Override
public int compare(TabViewProvider tvp1, TabViewProvider tvp2) {
int tvp1Priority = TAB_VIEW_PROVIDER_PRIORITY_LOOKUP.get(tvp1.getTabViewProviderType());
int tvp2Priority = TAB_VIEW_PROVIDER_PRIORITY_LOOKUP.get(tvp2.getTabViewProviderType());
return tvp1Priority - tvp2Priority;
}
@Override
public void destroy() {
mTab.setCustomView(null);
TabViewProvider currentTabViewProvider = mTabViewProviders.peek();
if (currentTabViewProvider != null) currentTabViewProvider.onHidden();
mTabViewProviders.clear();
mTab = null;
}
}
// 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.chrome.browser.tab;
import android.view.View;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* TODO(crbug.com/1074348) Move this interface to //chrome/browser/tab.
* An interface that provides a {@link View} to be shown in a {@link Tab}.
* Refer to the Javadoc on {@link TabViewManager} to learn how to add a new {@link TabViewProvider}
* to a {@link Tab}.
*/
public interface TabViewProvider {
/**
* Represents each {@link TabViewProvider} implementer. Please note that the integer values
* bear no ordering or prioritization meaning.
*/
@IntDef({Type.SUSPENDED_TAB, Type.SAD_TAB, Type.PAINT_PREVIEW})
@Retention(RetentionPolicy.SOURCE)
@interface Type {
int SUSPENDED_TAB = 0;
int SAD_TAB = 1;
int PAINT_PREVIEW = 2;
}
/**
* @return The {@link Type} associated with this {@link TabViewProvider}.
*/
@Type
int getTabViewProviderType();
/**
* @return The {@link View} that {@link Tab} is supposed to show.
*/
View getView();
/**
* Called when the {@link View} provided by {@link #getView()} is provided to {@link Tab}.
* */
default void onShown() {}
/**
* Called when the {@link View} provided by {@link #getView()} is removed from {@link Tab}.
* */
default void onHidden() {}
}
......@@ -206,7 +206,14 @@ public class TabWebContentsObserver extends TabWebContentsUserData {
}
} else {
rendererCrashStatus = TAB_RENDERER_CRASH_STATUS_SHOWN_IN_FOREGROUND_APP;
SadTab.from(mTab).show();
// TODO(crbug.com/1074078): Remove the Handler and call SadTab directly when
// WebContentsObserverProxy observers' iterator concurrency issue is fixed.
// Showing the SadTab will cause the content view hosting WebContents to lose focus.
// Post the show in order to avoid immediately triggering
// {@link WebContentsObserver#onWebContentsLostFocus}. This will ensure all
// observers in {@link WebContentsObserverProxy} receive callbacks for
// {@link WebContentsObserver#renderProcessGone} first.
(new Handler()).post(SadTab.from(mTab)::show);
// This is necessary to correlate histogram data with stability counts.
RecordHistogram.recordBooleanHistogram("Stability.Android.RendererCrash", true);
}
......
......@@ -10,9 +10,7 @@ import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.view.ViewGroup.LayoutParams;
import android.widget.TextView;
import androidx.annotation.Nullable;
......@@ -28,6 +26,8 @@ import org.chromium.chrome.browser.media.MediaCaptureDevicesDispatcherAndroid;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.chrome.browser.tab.TabViewManager;
import org.chromium.chrome.browser.tab.TabViewProvider;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsAccessibility;
import org.chromium.ui.base.WindowAndroid;
......@@ -36,7 +36,7 @@ import org.chromium.ui.base.WindowAndroid;
* Represents the suspension page presented when a user tries to visit a site whose fully-qualified
* domain name (FQDN) has been suspended via Digital Wellbeing.
*/
public class SuspendedTab extends EmptyTabObserver implements UserData {
public class SuspendedTab extends EmptyTabObserver implements UserData, TabViewProvider {
private static final String DIGITAL_WELLBEING_SITE_DETAILS_ACTION =
"org.chromium.chrome.browser.usage_stats.action.SHOW_WEBSITE_DETAILS";
private static final String EXTRA_FQDN_NAME =
......@@ -147,7 +147,7 @@ public class SuspendedTab extends EmptyTabObserver implements UserData {
@VisibleForTesting
boolean isViewAttached() {
return mView != null && mView.getParent() == mTab.getContentView();
return mView != null && TabViewManager.get(mTab).getCurrentTabViewProvider() == this;
}
private View createView() {
......@@ -155,20 +155,16 @@ public class SuspendedTab extends EmptyTabObserver implements UserData {
LayoutInflater inflater = LayoutInflater.from(context);
View suspendedTabView = inflater.inflate(R.layout.suspended_tab, null);
suspendedTabView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
return suspendedTabView;
}
private void attachView() {
assert mView == null;
ViewGroup parent = mTab.getContentView();
// getContentView() will return null if the tab doesn't have a WebContents, which is
// possible in some situations, e.g. if the renderer crashes.
if (parent == null) return;
mView = createView();
parent.addView(mView,
new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
TabViewManager.get(mTab).addTabViewProvider(this);
updateFqdnText();
}
......@@ -201,10 +197,8 @@ public class SuspendedTab extends EmptyTabObserver implements UserData {
}
private void removeViewIfPresent() {
if (isViewAttached()) {
mTab.getContentView().removeView(mView);
mView = null;
}
TabViewManager.get(mTab).removeTabViewProvider(this);
mView = null;
}
// TabObserver implementation.
......@@ -222,4 +216,14 @@ public class SuspendedTab extends EmptyTabObserver implements UserData {
public void destroy() {
mTab.removeObserver(this);
}
@Override
public int getTabViewProviderType() {
return Type.SUSPENDED_TAB;
}
@Override
public View getView() {
return mView;
}
}
......@@ -4,6 +4,7 @@
package org.chromium.chrome.browser.tab;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.widget.Button;
......@@ -44,6 +45,7 @@ public class SadTabTest {
}
private static boolean isShowingSadTab(Tab tab) {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
try {
return TestThreadUtils.runOnUiThreadBlocking(() -> SadTab.isShowing(tab));
} catch (ExecutionException e) {
......@@ -193,7 +195,12 @@ public class SadTabTest {
* doesn't exist.
*/
private static Button getSadTabButton(Tab tab) {
return (Button) tab.getContentView().findViewById(R.id.sad_tab_button);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
try {
return TestThreadUtils.runOnUiThreadBlocking(
() -> tab.getView().findViewById(R.id.sad_tab_button));
} catch (ExecutionException e) {
return null;
}
}
}
// 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.chrome.browser.tab;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.view.View;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.base.test.BaseRobolectricTestRunner;
/**
* Unit tests for the {@link TabViewManager} class.
*/
@RunWith(BaseRobolectricTestRunner.class)
public class TabViewManagerTest {
@Mock
private TabImpl mTab;
@Mock
private TabViewProvider mTabViewProvider0;
@Mock
private TabViewProvider mTabViewProvider1;
@Mock
private TabViewProvider mTabViewProvider2;
@Mock
private View mTabView0;
@Mock
private View mTabView1;
@Mock
private View mTabView2;
private TabViewManager mTabViewManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mTabViewManager = new TabViewManager(mTab);
when(mTabViewProvider0.getTabViewProviderType())
.thenReturn(TabViewManager.PRIORITIZED_TAB_VIEW_PROVIDER_TYPES[0]);
when(mTabViewProvider1.getTabViewProviderType())
.thenReturn(TabViewManager.PRIORITIZED_TAB_VIEW_PROVIDER_TYPES[1]);
when(mTabViewProvider2.getTabViewProviderType())
.thenReturn(TabViewManager.PRIORITIZED_TAB_VIEW_PROVIDER_TYPES[2]);
when(mTabViewProvider0.getView()).thenReturn(mTabView0);
when(mTabViewProvider1.getView()).thenReturn(mTabView1);
when(mTabViewProvider2.getView()).thenReturn(mTabView2);
}
/**
* Verifies that the {@link TabViewProvider} with the highest priority is always
* showing after each call to {@link TabViewManager#addTabViewProvider}.
*/
@Test
public void testAddTabViewProvider() {
mTabViewManager.addTabViewProvider(mTabViewProvider1);
Assert.assertEquals("TabViewProvider with the highest priority should be shown", mTabViewProvider1,
mTabViewManager.getCurrentTabViewProvider());
verify(mTab).setCustomView(mTabView1);
verifyTabViewProviderOnShownCalled(mTabViewProvider1, 1);
verifyTabViewProviderOnHiddenCalled(mTabViewProvider1, 0);
mTabViewManager.addTabViewProvider(mTabViewProvider2);
Assert.assertEquals("TabViewProvider with the highest priority should be shown", mTabViewProvider1,
mTabViewManager.getCurrentTabViewProvider());
verify(mTab).setCustomView(mTabView1);
verifyTabViewProviderOnShownCalled(mTabViewProvider1, 1);
verifyTabViewProviderOnShownCalled(mTabViewProvider2, 0);
verifyTabViewProviderOnHiddenCalled(mTabViewProvider1, 0);
verifyTabViewProviderOnHiddenCalled(mTabViewProvider2, 0);
mTabViewManager.addTabViewProvider(mTabViewProvider0);
Assert.assertEquals("TabViewProvider with the highest priority should be shown", mTabViewProvider0,
mTabViewManager.getCurrentTabViewProvider());
verify(mTab).setCustomView(mTabView0);
verifyTabViewProviderOnShownCalled(mTabViewProvider0, 1);
verifyTabViewProviderOnShownCalled(mTabViewProvider1, 1);
verifyTabViewProviderOnShownCalled(mTabViewProvider2, 0);
verifyTabViewProviderOnHiddenCalled(mTabViewProvider0, 0);
verifyTabViewProviderOnHiddenCalled(mTabViewProvider1, 1);
verifyTabViewProviderOnHiddenCalled(mTabViewProvider2, 0);
}
/**
* Verifies that the {@link TabViewProvider} with the highest priority is always
* showing after each call to {@link TabViewManager#removeTabViewProvider}.
*/
@Test
public void testRemoveTabViewProvider() {
mTabViewManager.addTabViewProvider(mTabViewProvider0);
mTabViewManager.addTabViewProvider(mTabViewProvider2);
mTabViewManager.addTabViewProvider(mTabViewProvider1);
Assert.assertEquals("TabViewProvider with the highest priority should be shown", mTabViewProvider0,
mTabViewManager.getCurrentTabViewProvider());
mTabViewManager.removeTabViewProvider(mTabViewProvider0);
Assert.assertEquals("TabViewProvider with the highest priority should be shown", mTabViewProvider1,
mTabViewManager.getCurrentTabViewProvider());
verify(mTab).setCustomView(mTabView1);
verifyTabViewProviderOnShownCalled(mTabViewProvider0, 1);
verifyTabViewProviderOnShownCalled(mTabViewProvider1, 1);
verifyTabViewProviderOnShownCalled(mTabViewProvider2, 0);
verifyTabViewProviderOnHiddenCalled(mTabViewProvider0, 1);
verifyTabViewProviderOnHiddenCalled(mTabViewProvider1, 0);
verifyTabViewProviderOnHiddenCalled(mTabViewProvider2, 0);
mTabViewManager.removeTabViewProvider(mTabViewProvider2);
Assert.assertEquals("TabViewProvider with the highest priority should be shown", mTabViewProvider1,
mTabViewManager.getCurrentTabViewProvider());
verify(mTab).setCustomView(mTabView1);
verifyTabViewProviderOnShownCalled(mTabViewProvider1, 1);
verifyTabViewProviderOnShownCalled(mTabViewProvider2, 0);
verifyTabViewProviderOnHiddenCalled(mTabViewProvider1, 0);
verifyTabViewProviderOnHiddenCalled(mTabViewProvider2, 0);
mTabViewManager.removeTabViewProvider(mTabViewProvider1);
Assert.assertNull("No TabViewProvider should be shown", mTabViewManager.getCurrentTabViewProvider());
verify(mTab).setCustomView(null);
verifyTabViewProviderOnShownCalled(mTabViewProvider1, 1);
verifyTabViewProviderOnHiddenCalled(mTabViewProvider1, 1);
}
private void verifyTabViewProviderOnShownCalled(TabViewProvider mockTabViewProvider, int numberOfCalls) {
String description = "onShown() should have been called " + numberOfCalls + " times on TabViewProvider type "
+ mockTabViewProvider.getTabViewProviderType();
verify(mockTabViewProvider, times(numberOfCalls).description(description)).onShown();
}
private void verifyTabViewProviderOnHiddenCalled(TabViewProvider mockTabViewProvider, int numberOfCalls) {
String description = "onHidden() should have been called " + numberOfCalls + " times on TabViewProvider type "
+ mockTabViewProvider.getTabViewProviderType();
verify(mockTabViewProvider, times(numberOfCalls).description(description)).onHidden();
}
}
......@@ -26,6 +26,7 @@ import org.mockito.ArgumentMatcher;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.chromium.base.Promise;
......@@ -95,6 +96,8 @@ public final class PageViewObserverTest {
doReturn(false).when(mTab).isIncognito();
doReturn(null).when(mTab).getUrlString();
doReturn(mChromeActivity).when(mTab).getActivity();
doReturn(Robolectric.buildActivity(Activity.class).get()).when(mTab).getContext();
doReturn(Robolectric.buildActivity(Activity.class).get()).when(mTab2).getContext();
doReturn(true).when(mTab).isInitialized();
doReturn(true).when(mTab2).isInitialized();
doReturn(Arrays.asList(mTabModel)).when(mTabModelSelector).getModels();
......
......@@ -8,6 +8,7 @@ import android.content.Context;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting;
......@@ -42,6 +43,8 @@ public class PlayerManager {
mDelegate = new PlayerCompositorDelegateImpl(nativePaintPreviewServiceProvider, url,
directoryKey, this::onCompositorReady, linkClickHandler);
mHostView = new FrameLayout(mContext);
mHostView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mHostView.setBackgroundColor(backgroundColor);
mViewReadyCallback = viewReadyCallback;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment