Skip to content

Commit

Permalink
[Tab Content Manager] Fetch live layer only when requested
Browse files Browse the repository at this point in the history
Change the GetLiveLayer call to fetch via TabId rather than caching all
the ContentLayers in the TabContentManager.

This addresses this TODO which can be removed once the flag lands:
https://source.chromium.org/chromium/chromium/src/+/main:chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManagerHandler.java;drc=0c1da58afce2ab7b22754e0e6ae0b72631a27812;l=58

Unfortunately, unit testing for this is infeasible due to JNI
dependencies for TabContentManager and Tab in C++ that aren't feasible
to create. However, I was able to write a test to readback the GPU
buffer to confirm the live layer is attached to the Compositor via
a render test.

Bug: 1402843
Change-Id: I4079fa17059d35be8fa679f42026b9d04d6638f0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4247157
Reviewed-by: David Trainor <dtrainor@chromium.org>
Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
Code-Coverage: Findit <findit-for-me@appspot.gserviceaccount.com>
Cr-Commit-Position: refs/heads/main@{#1108683}
  • Loading branch information
ckitagawa-work authored and Chromium LUCI CQ committed Feb 23, 2023
1 parent a552aef commit 6feb2c9
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 6 deletions.
1 change: 1 addition & 0 deletions chrome/android/chrome_test_java_sources.gni
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/compositor/CompositorVisibilityTest.java",
"javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java",
"javatests/src/org/chromium/chrome/browser/compositor/layouts/MockLayoutHost.java",
"javatests/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManagerTest.java",
"javatests/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripTest.java",
"javatests/src/org/chromium/chrome/browser/contacts_picker/ContactsPickerLauncherTest.java",
"javatests/src/org/chromium/chrome/browser/content_settings/ContentSettingsObserverTest.java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,13 @@ public void destroy() {
}
}

@CalledByNative
private Tab getTabById(int tabId) {
if (mTabFinder == null) return null;

return mTabFinder.getTabById(tabId);
}

@CalledByNative
private long getNativePtr() {
return mNativeTabContentManager;
Expand All @@ -228,7 +235,7 @@ private long getNativePtr() {
* @param tab Tab whose cc layer will be attached.
*/
public void attachTab(Tab tab) {
if (mNativeTabContentManager == 0) return;
if (mNativeTabContentManager == 0 || sThumbnailCacheRefactor.isEnabled()) return;
TabContentManagerJni.get().attachTab(mNativeTabContentManager, tab, tab.getId());
}

Expand All @@ -237,7 +244,7 @@ public void attachTab(Tab tab) {
* @param tab Tab whose cc layer will be detached.
*/
public void detachTab(Tab tab) {
if (mNativeTabContentManager == 0) return;
if (mNativeTabContentManager == 0 || sThumbnailCacheRefactor.isEnabled()) return;
TabContentManagerJni.get().detachTab(mNativeTabContentManager, tab, tab.getId());
}

Expand Down Expand Up @@ -350,9 +357,7 @@ public void getTabThumbnailWithCallback(@NonNull int tabId, @Nullable Size thumb
getTabThumbnailFromDisk(tabId, thumbnailSize, (diskBitmap) -> {
if (diskBitmap != null) callback.onResult(diskBitmap);

if (mTabFinder == null) return;

Tab tab = mTabFinder.getTabById(tabId);
Tab tab = getTabById(tabId);
if (tab == null) return;

captureThumbnail(tab, writeBack, (bitmap) -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2023 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.compositor.layouts.content;

import android.graphics.Bitmap;
import android.os.Handler;
import android.view.PixelCopy;
import android.view.SurfaceView;

import androidx.test.filters.MediumTest;

import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.compositor.CompositorView;
import org.chromium.chrome.browser.compositor.CompositorViewHolder;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.test.util.RenderTestRule;

/**
* Tests for the {@link TabContentManager}.
* TODO(crbug.com/1402843): Tests are being added in the process of refactoring
* and optimizing the TabContentManager for modern usage. Add more tests here.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Batch(Batch.PER_CLASS)
@Features.EnableFeatures(ChromeFeatureList.THUMBNAIL_CACHE_REFACTOR)
public class TabContentManagerTest {
@ClassRule
public static ChromeTabbedActivityTestRule sActivityTestRule =
new ChromeTabbedActivityTestRule();

@Rule
public BlankCTATabInitialStateRule mBlankCTATabInitialStateRule =
new BlankCTATabInitialStateRule(sActivityTestRule, false);

@Rule
public RenderTestRule mRenderTestRule =
new RenderTestRule.Builder()
.setCorpus(RenderTestRule.Corpus.ANDROID_RENDER_TESTS_PUBLIC)
.setBugComponent(RenderTestRule.Component.UI_BROWSER_THUMBNAIL)
.setRevision(1)
.setDescription("Initial test creation")
.build();

/**
* With {@link ChromeFeatureList.THUMBNAIL_CACHE_REFACTOR} enabled the live layer is vended
* to the compositor via a pull mechanism rather than a push mechanism. Ensure the tab still
* draws its live layer.
*/
@Test
@MediumTest
@Feature({"RenderTest"})
public void testLiveLayerDraws() throws Exception {
final String testHttpsUrl1 =
sActivityTestRule.getTestServer().getURL("/chrome/test/data/android/test.html");
sActivityTestRule.loadUrlInNewTab(testHttpsUrl1);
mRenderTestRule.compareForResult(captureBitmap(), "contentViewTab1");

final String testHttpsUrl2 =
sActivityTestRule.getTestServer().getURL("/chrome/test/data/android/google.html");
sActivityTestRule.loadUrlInNewTab(testHttpsUrl2);
mRenderTestRule.compareForResult(captureBitmap(), "contentViewTab2");
}

/**
* Generate a bitmap of the {@link SurfaceView} using {@link PixelCopy} as the normal path for
* capturing bitmaps doesn't work for GPU accelerated content.
*/
private Bitmap captureBitmap() throws Exception {
CallbackHelper helper = new CallbackHelper();
Bitmap[] bitmapHolder = new Bitmap[1];
CompositorView compositorView =
((CompositorViewHolder) sActivityTestRule.getActivity().findViewById(
R.id.compositor_view_holder))
.getCompositorView();
Assert.assertNotNull(compositorView);
// Put the compositor view in a mode that supports readback (this is used by the magnifier
// normally). Note that this might fail if surface control isn't supported by the GPU under
// test.
TestThreadUtils.runOnUiThreadBlocking(
() -> { compositorView.onSelectionHandlesStateChanged(true); });
// TODO(crbug.com/1402843): It unfortunately may take time for the SurfaceView buffer to
// contain anything and there is no signal to listen to.
Thread.sleep(1000);
// Capture the surface using PixelCopy repeating until it works.
TestThreadUtils.runOnUiThreadBlocking(() -> {
SurfaceView surfaceView = (SurfaceView) compositorView.getActiveSurfaceView();
Assert.assertNotNull(surfaceView);
// Assume surface view size will be constant and only allocate the bitmap once.
bitmapHolder[0] = Bitmap.createBitmap(
surfaceView.getWidth(), surfaceView.getHeight(), Bitmap.Config.ARGB_8888);
captureBitmapInner(compositorView, bitmapHolder, helper, new Handler());
});
helper.waitForFirst();
Assert.assertNotNull(bitmapHolder[0]);
TestThreadUtils.runOnUiThreadBlocking(
() -> { compositorView.onSelectionHandlesStateChanged(false); });
return bitmapHolder[0];
}

private void captureBitmapInner(CompositorView compositorView, Bitmap[] bitmapHolder,
CallbackHelper helper, Handler handler) {
SurfaceView surfaceView = (SurfaceView) compositorView.getActiveSurfaceView();
Assert.assertNotNull(surfaceView);
PixelCopy.OnPixelCopyFinishedListener listener =
new PixelCopy.OnPixelCopyFinishedListener() {
@Override
public void onPixelCopyFinished(int copyResult) {
if (copyResult == PixelCopy.SUCCESS) {
helper.notifyCalled();
return;
}
// Backoff if the surface buffer isn't working yet. The test will time out
// if this takes too long.
handler.postDelayed(() -> {
captureBitmapInner(compositorView, bitmapHolder, helper, handler);
}, 500);
}
};
PixelCopy.request(surfaceView, bitmapHolder[0], listener, handler);
}
}
17 changes: 17 additions & 0 deletions chrome/browser/android/compositor/tab_content_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/cxx17_backports.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/field_trial_params.h"
Expand All @@ -24,6 +25,7 @@
#include "chrome/browser/android/compositor/layer/thumbnail_layer.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/flags/android/chrome_feature_list.h"
#include "chrome/browser/thumbnail/cc/features.h"
#include "chrome/browser/thumbnail/cc/thumbnail.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
Expand Down Expand Up @@ -152,6 +154,19 @@ void TabContentManager::SetUIResourceProvider(
}

scoped_refptr<cc::slim::Layer> TabContentManager::GetLiveLayer(int tab_id) {
if (base::FeatureList::IsEnabled(thumbnail::kThumbnailCacheRefactor)) {
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jobject> jtab = Java_TabContentManager_getTabById(
env, weak_java_tab_content_manager_.get(env), tab_id);
if (!jtab) {
return nullptr;
}
TabAndroid* tab = TabAndroid::GetNativeTab(env, jtab);
if (!tab) {
return nullptr;
}
return tab->GetContentLayer();
}
return live_layer_list_[tab_id];
}

Expand Down Expand Up @@ -191,6 +206,7 @@ scoped_refptr<ThumbnailLayer> TabContentManager::GetOrCreateStaticLayer(
void TabContentManager::AttachTab(JNIEnv* env,
const JavaParamRef<jobject>& jtab,
jint tab_id) {
DCHECK(!base::FeatureList::IsEnabled(thumbnail::kThumbnailCacheRefactor));
TabAndroid* tab = TabAndroid::GetNativeTab(env, jtab);
scoped_refptr<cc::slim::Layer> layer = tab->GetContentLayer();
if (!layer.get()) {
Expand All @@ -206,6 +222,7 @@ void TabContentManager::AttachTab(JNIEnv* env,
void TabContentManager::DetachTab(JNIEnv* env,
const JavaParamRef<jobject>& jtab,
jint tab_id) {
DCHECK(!base::FeatureList::IsEnabled(thumbnail::kThumbnailCacheRefactor));
scoped_refptr<cc::slim::Layer> current_layer = live_layer_list_[tab_id];
if (!current_layer.get()) {
// Empty cached layer should not exist but it is ok if it happens.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ public class RenderTestRule extends TestWatcher {
Component.UI_BROWSER_SHARING, Component.UI_BROWSER_SHOPPING,
Component.UI_BROWSER_SHOPPING_MERCHANT_TRUST,
Component.UI_BROWSER_SHOPPING_PRICE_TRACKING, Component.UI_BROWSER_TOOLBAR,
Component.UI_BROWSER_WEB_APP_INSTALLS, Component.UI_SETTINGS_PRIVACY})
Component.UI_BROWSER_THUMBNAIL, Component.UI_BROWSER_WEB_APP_INSTALLS,
Component.UI_SETTINGS_PRIVACY})
@Retention(RetentionPolicy.SOURCE)
public @interface Component {
String BLINK_CONTACTS = "Blink>Contacts";
Expand Down Expand Up @@ -177,6 +178,7 @@ public class RenderTestRule extends TestWatcher {
String UI_BROWSER_SHOPPING = "UI>Browser>Shopping";
String UI_BROWSER_SHOPPING_MERCHANT_TRUST = "UI>Browser>Shopping>MerchantTrust";
String UI_BROWSER_SHOPPING_PRICE_TRACKING = "UI>Browser>Shopping>PriceTracking";
String UI_BROWSER_THUMBNAIL = "UI>Browser>Thumbnail";
String UI_BROWSER_TOOLBAR = "UI>Browser>Toolbar";
String UI_BROWSER_WEB_APP_INSTALLS = "UI>Browser>WebAppInstalls";
String UI_SETTINGS_PRIVACY = "UI>Settings>Privacy";
Expand Down

0 comments on commit 6feb2c9

Please sign in to comment.