| // Copyright 2012 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.weblayer_private; |
| |
| import android.content.Context; |
| import android.graphics.Color; |
| import android.graphics.SurfaceTexture; |
| import android.view.Surface; |
| import android.view.SurfaceHolder; |
| import android.view.SurfaceView; |
| import android.view.TextureView; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.webkit.ValueCallback; |
| import android.widget.FrameLayout; |
| |
| import androidx.annotation.IntDef; |
| |
| import org.chromium.base.annotations.CalledByNative; |
| import org.chromium.base.annotations.JNINamespace; |
| import org.chromium.base.annotations.NativeMethods; |
| import org.chromium.base.task.PostTask; |
| import org.chromium.content_public.browser.UiThreadTaskTraits; |
| import org.chromium.content_public.browser.WebContents; |
| import org.chromium.ui.base.WindowAndroid; |
| import org.chromium.ui.resources.ResourceManager; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| |
| /** |
| * This class manages the chromium compositor and the Surface that is used by |
| * the chromium compositor. Note it can be used to display only one WebContents. |
| * This allows switching between SurfaceView and TextureView as the source of |
| * the Surface used by chromium compositor, and attempts to make the switch |
| * visually seamless. |
| */ |
| @JNINamespace("weblayer") |
| public class ContentViewRenderView extends FrameLayout { |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({MODE_SURFACE_VIEW, MODE_SURFACE_VIEW}) |
| public @interface Mode {} |
| public static final int MODE_SURFACE_VIEW = 0; |
| public static final int MODE_TEXTURE_VIEW = 1; |
| |
| // This is mode that is requested by client. |
| private SurfaceData mRequested; |
| // This is the mode that last supplied the Surface to the compositor. |
| // This should generally be equal to |mRequested| except during transitions. |
| private SurfaceData mCurrent; |
| |
| // The native side of this object. |
| private long mNativeContentViewRenderView; |
| |
| private WindowAndroid mWindowAndroid; |
| private WebContents mWebContents; |
| |
| private int mBackgroundColor; |
| private int mWidth; |
| private int mHeight; |
| |
| // Common interface to listen to surface related events. |
| private interface SurfaceEventListener { |
| void surfaceCreated(); |
| void surfaceChanged( |
| Surface surface, boolean canBeUsedWithSurfaceControl, int width, int height); |
| // |cacheBackBuffer| will delay destroying the EGLSurface until after the next swap. |
| void surfaceDestroyed(boolean cacheBackBuffer); |
| } |
| |
| // Non-static implementation of SurfaceEventListener that forward calls to native Compositor. |
| // It is also responsible for updating |mRequested| and |mCurrent|. |
| private class SurfaceEventListenerImpl implements SurfaceEventListener { |
| private SurfaceData mSurfaceData; |
| |
| public void setRequestData(SurfaceData surfaceData) { |
| assert mSurfaceData == null; |
| mSurfaceData = surfaceData; |
| } |
| |
| @Override |
| public void surfaceCreated() { |
| assert mNativeContentViewRenderView != 0; |
| assert mSurfaceData == ContentViewRenderView.this.mRequested |
| || mSurfaceData == ContentViewRenderView.this.mCurrent; |
| if (ContentViewRenderView.this.mCurrent != null |
| && ContentViewRenderView.this.mCurrent != mSurfaceData) { |
| ContentViewRenderView.this.mCurrent.markForDestroy(true /* hasNextSurface */); |
| mSurfaceData.setSurfaceDataNeedsDestroy(ContentViewRenderView.this.mCurrent); |
| } |
| ContentViewRenderView.this.mCurrent = mSurfaceData; |
| ContentViewRenderViewJni.get().surfaceCreated(mNativeContentViewRenderView); |
| } |
| |
| @Override |
| public void surfaceChanged( |
| Surface surface, boolean canBeUsedWithSurfaceControl, int width, int height) { |
| assert mNativeContentViewRenderView != 0; |
| assert mSurfaceData == ContentViewRenderView.this.mCurrent; |
| ContentViewRenderViewJni.get().surfaceChanged(mNativeContentViewRenderView, |
| canBeUsedWithSurfaceControl, width, height, surface); |
| if (mWebContents != null) { |
| ContentViewRenderViewJni.get().onPhysicalBackingSizeChanged( |
| mNativeContentViewRenderView, mWebContents, width, height); |
| } |
| } |
| |
| @Override |
| public void surfaceDestroyed(boolean cacheBackBuffer) { |
| assert mNativeContentViewRenderView != 0; |
| assert mSurfaceData == ContentViewRenderView.this.mCurrent; |
| ContentViewRenderViewJni.get().surfaceDestroyed( |
| mNativeContentViewRenderView, cacheBackBuffer); |
| } |
| } |
| |
| // Abstract differences between SurfaceView and TextureView behind this class. |
| // Also responsible for holding and calling callbacks. |
| private static class SurfaceData implements SurfaceEventListener { |
| private class TextureViewWithInvalidate extends TextureView { |
| public TextureViewWithInvalidate(Context context) { |
| super(context); |
| } |
| |
| @Override |
| public void invalidate() { |
| // TextureView is invalidated when it receives a new frame from its SurfaceTexture. |
| // This is a safe place to indicate that this TextureView now has content and is |
| // ready to be shown. |
| super.invalidate(); |
| destroyPreviousData(); |
| } |
| } |
| |
| @Mode |
| private final int mMode; |
| private final SurfaceEventListener mListener; |
| private final FrameLayout mParent; |
| private final Runnable mEvict; |
| |
| private boolean mRanCallbacks; |
| private boolean mMarkedForDestroy; |
| private boolean mCachedSurfaceNeedsEviction; |
| |
| private boolean mNeedsOnSurfaceDestroyed; |
| |
| // During transitioning between two SurfaceData, there is a complicated series of calls to |
| // avoid visual artifacts. |
| // 1) Allocate new SurfaceData, and insert it into view hierarchy below the existing |
| // SurfaceData, so it is not yet showing. |
| // 2) When Surface is allocated by new View, swap chromium compositor to the |
| // new Surface. |markForDestroy| is called on the previous SurfaceData, and the two |
| // SurfaceDatas are linked through these two variables. |
| // Note at this point the existing view is still visible. |
| // 3) Wait until new SurfaceData decides that it has content and is ready to be shown |
| // * For TextureView, wait until TextureView.invalidate is called |
| // * For SurfaceView, wait for two swaps from the chromium compositor |
| // 4) New SurfaceData calls |destroy| on previous SurfaceData. |
| // * For TextureView, it is simply detached immediately from the view tree |
| // * For SurfaceView, to avoid flicker, move it to the back first before and wait |
| // two frames before detaching. |
| // 5) Previous SurfaceData runs callbacks on the new SurfaceData to signal the completion |
| // of the transition. |
| private SurfaceData mPrevSurfaceDataNeedsDestroy; |
| private SurfaceData mNextSurfaceDataNeedsRunCallback; |
| |
| private final SurfaceHolderCallback mSurfaceCallback; |
| private final SurfaceView mSurfaceView; |
| private int mNumSurfaceViewSwapsUntilVisible; |
| |
| private final TextureView mTextureView; |
| private final TextureViewSurfaceTextureListener mSurfaceTextureListener; |
| |
| private final ArrayList<ValueCallback<Boolean>> mModeCallbacks = new ArrayList<>(); |
| |
| public SurfaceData(@Mode int mode, FrameLayout parent, SurfaceEventListener listener, |
| int backgroundColor, Runnable evict) { |
| mMode = mode; |
| mListener = listener; |
| mParent = parent; |
| mEvict = evict; |
| if (mode == MODE_SURFACE_VIEW) { |
| mSurfaceView = new SurfaceView(parent.getContext()); |
| mSurfaceView.setZOrderMediaOverlay(true); |
| mSurfaceView.setBackgroundColor(backgroundColor); |
| |
| mSurfaceCallback = new SurfaceHolderCallback(this); |
| mSurfaceView.getHolder().addCallback(mSurfaceCallback); |
| mSurfaceView.setVisibility(View.VISIBLE); |
| |
| mTextureView = null; |
| mSurfaceTextureListener = null; |
| } else if (mode == MODE_TEXTURE_VIEW) { |
| mTextureView = new TextureViewWithInvalidate(parent.getContext()); |
| mSurfaceTextureListener = new TextureViewSurfaceTextureListener(this); |
| mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); |
| mTextureView.setVisibility(VISIBLE); |
| |
| mSurfaceView = null; |
| mSurfaceCallback = null; |
| } else { |
| throw new RuntimeException("Illegal mode: " + mode); |
| } |
| |
| // This postOnAnimation is to avoid manipulating the view tree inside layout or draw. |
| parent.postOnAnimation(() -> { |
| if (mMarkedForDestroy) return; |
| View view = (mMode == MODE_SURFACE_VIEW) ? mSurfaceView : mTextureView; |
| assert view != null; |
| // Always insert view for new surface below the existing view to avoid artifacts |
| // during surface swaps. Index 0 is the lowest child. |
| mParent.addView(view, 0, |
| new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, |
| FrameLayout.LayoutParams.MATCH_PARENT)); |
| mParent.invalidate(); |
| }); |
| } |
| |
| public void setSurfaceDataNeedsDestroy(SurfaceData surfaceData) { |
| assert !mMarkedForDestroy; |
| assert mPrevSurfaceDataNeedsDestroy == null; |
| mPrevSurfaceDataNeedsDestroy = surfaceData; |
| mPrevSurfaceDataNeedsDestroy.mNextSurfaceDataNeedsRunCallback = this; |
| } |
| |
| public @Mode int getMode() { |
| return mMode; |
| } |
| |
| public void addCallback(ValueCallback<Boolean> callback) { |
| assert !mMarkedForDestroy; |
| mModeCallbacks.add(callback); |
| if (mRanCallbacks) runCallbacks(); |
| } |
| |
| // Tearing down is separated into markForDestroy and destroy. After markForDestroy |
| // this class will is guaranteed to not issue any calls to its SurfaceEventListener. |
| public void markForDestroy(boolean hasNextSurface) { |
| if (mMarkedForDestroy) return; |
| mMarkedForDestroy = true; |
| |
| if (mNeedsOnSurfaceDestroyed) { |
| // SurfaceView being used with SurfaceControl need to cache the back buffer |
| // (EGLSurface). Otherwise the surface is destroyed immediate before the |
| // SurfaceView is detached. |
| mCachedSurfaceNeedsEviction = hasNextSurface && mMode == MODE_SURFACE_VIEW; |
| mListener.surfaceDestroyed(mCachedSurfaceNeedsEviction); |
| mNeedsOnSurfaceDestroyed = false; |
| } |
| |
| if (mMode == MODE_SURFACE_VIEW) { |
| mSurfaceView.getHolder().removeCallback(mSurfaceCallback); |
| } else if (mMode == MODE_TEXTURE_VIEW) { |
| mTextureView.setSurfaceTextureListener(null); |
| } else { |
| assert false; |
| } |
| } |
| |
| // Remove view from parent hierarchy. |
| public void destroy() { |
| assert mMarkedForDestroy; |
| runCallbacks(); |
| // This postOnAnimation is to avoid manipulating the view tree inside layout or draw. |
| mParent.postOnAnimation(() -> { |
| if (mMode == MODE_SURFACE_VIEW) { |
| // Detaching a SurfaceView causes a flicker because the SurfaceView tears down |
| // the Surface in SurfaceFlinger before removing its hole in the view tree. |
| // This is a complicated heuristics to avoid this. It first moves the |
| // SurfaceView behind the new View. Then wait two frames before detaching |
| // the SurfaceView. Waiting for a single frame still causes flickers on |
| // high end devices like Pixel 3. |
| moveChildToBackWithoutDetach(mParent, mSurfaceView); |
| mParent.postOnAnimation(() -> mParent.postOnAnimation(() -> { |
| mParent.removeView(mSurfaceView); |
| mParent.invalidate(); |
| if (mCachedSurfaceNeedsEviction) { |
| mEvict.run(); |
| mCachedSurfaceNeedsEviction = false; |
| } |
| runCallbackOnNextSurfaceData(); |
| })); |
| } else if (mMode == MODE_TEXTURE_VIEW) { |
| mParent.removeView(mTextureView); |
| runCallbackOnNextSurfaceData(); |
| } else { |
| assert false; |
| } |
| }); |
| } |
| |
| private static void moveChildToBackWithoutDetach(ViewGroup parent, View child) { |
| final int numberOfChildren = parent.getChildCount(); |
| final int childIndex = parent.indexOfChild(child); |
| if (childIndex <= 0) return; |
| for (int i = 0; i < childIndex; ++i) { |
| parent.bringChildToFront(parent.getChildAt(0)); |
| } |
| assert parent.indexOfChild(child) == 0; |
| for (int i = 0; i < numberOfChildren - childIndex - 1; ++i) { |
| parent.bringChildToFront(parent.getChildAt(1)); |
| } |
| parent.invalidate(); |
| } |
| |
| public void setBackgroundColor(int color) { |
| assert !mMarkedForDestroy; |
| if (mMode == MODE_SURFACE_VIEW) { |
| mSurfaceView.setBackgroundColor(color); |
| } |
| } |
| |
| /** @return true if should keep swapping frames */ |
| public boolean didSwapFrame() { |
| if (mSurfaceView != null && mSurfaceView.getBackground() != null) { |
| mSurfaceView.post(new Runnable() { |
| @Override |
| public void run() { |
| if (mSurfaceView != null) mSurfaceView.setBackgroundResource(0); |
| } |
| }); |
| } |
| if (mMode == MODE_SURFACE_VIEW) { |
| // We have no reliable signal for when to show a SurfaceView. This is a heuristic |
| // (used by chrome as well) is to wait for 2 swaps from the chromium comopsitor |
| // as a signal that the SurfaceView has content and is ready to be displayed. |
| if (mNumSurfaceViewSwapsUntilVisible > 0) { |
| mNumSurfaceViewSwapsUntilVisible--; |
| } |
| if (mNumSurfaceViewSwapsUntilVisible == 0) { |
| destroyPreviousData(); |
| } |
| return mNumSurfaceViewSwapsUntilVisible > 0; |
| } |
| return false; |
| } |
| |
| private void destroyPreviousData() { |
| if (mPrevSurfaceDataNeedsDestroy != null) { |
| mPrevSurfaceDataNeedsDestroy.destroy(); |
| mPrevSurfaceDataNeedsDestroy = null; |
| } |
| } |
| |
| @Override |
| public void surfaceCreated() { |
| if (mMarkedForDestroy) return; |
| |
| // On pre-M Android, layers start in the hidden state until a relayout happens. |
| // There is a bug that manifests itself when entering overlay mode on pre-M devices, |
| // where a relayout never happens. This bug is out of Chromium's control, but can be |
| // worked around by forcibly re-setting the visibility of the surface view. |
| // Otherwise, the screen stays black, and some tests fail. |
| if (mSurfaceView != null) { |
| mSurfaceView.setVisibility(mSurfaceView.getVisibility()); |
| } |
| mListener.surfaceCreated(); |
| |
| if (!mRanCallbacks && mPrevSurfaceDataNeedsDestroy == null) { |
| runCallbacks(); |
| } |
| |
| mNeedsOnSurfaceDestroyed = true; |
| } |
| |
| @Override |
| public void surfaceChanged( |
| Surface surface, boolean canBeUsedWithSurfaceControl, int width, int height) { |
| if (mMarkedForDestroy) return; |
| mListener.surfaceChanged(surface, canBeUsedWithSurfaceControl, width, height); |
| mNumSurfaceViewSwapsUntilVisible = 2; |
| } |
| |
| @Override |
| public void surfaceDestroyed(boolean cacheBackBuffer) { |
| if (mMarkedForDestroy) return; |
| assert mNeedsOnSurfaceDestroyed; |
| mListener.surfaceDestroyed(cacheBackBuffer); |
| mNeedsOnSurfaceDestroyed = false; |
| } |
| |
| private void runCallbacks() { |
| mRanCallbacks = true; |
| if (mModeCallbacks.isEmpty()) return; |
| // PostTask to avoid possible reentrancy problems with embedder code. |
| PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> { |
| ArrayList<ValueCallback<Boolean>> clone = |
| (ArrayList<ValueCallback<Boolean>>) mModeCallbacks.clone(); |
| mModeCallbacks.clear(); |
| for (ValueCallback<Boolean> run : clone) { |
| run.onReceiveValue(!mMarkedForDestroy); |
| } |
| }); |
| } |
| |
| private void runCallbackOnNextSurfaceData() { |
| if (mNextSurfaceDataNeedsRunCallback != null) { |
| mNextSurfaceDataNeedsRunCallback.runCallbacks(); |
| mNextSurfaceDataNeedsRunCallback = null; |
| } |
| } |
| } |
| |
| // Adapter for SurfaceHoolder.Callback. |
| private static class SurfaceHolderCallback implements SurfaceHolder.Callback { |
| private final SurfaceEventListener mListener; |
| |
| public SurfaceHolderCallback(SurfaceEventListener listener) { |
| mListener = listener; |
| } |
| |
| @Override |
| public void surfaceCreated(SurfaceHolder holder) { |
| mListener.surfaceCreated(); |
| } |
| |
| @Override |
| public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { |
| mListener.surfaceChanged(holder.getSurface(), true, width, height); |
| } |
| |
| @Override |
| public void surfaceDestroyed(SurfaceHolder holder) { |
| mListener.surfaceDestroyed(false /* cacheBackBuffer */); |
| } |
| } |
| |
| // Adapter for TextureView.SurfaceTextureListener. |
| private static class TextureViewSurfaceTextureListener |
| implements TextureView.SurfaceTextureListener { |
| private final SurfaceEventListener mListener; |
| |
| private SurfaceTexture mCurrentSurfaceTexture; |
| private Surface mCurrentSurface; |
| |
| public TextureViewSurfaceTextureListener(SurfaceEventListener listener) { |
| mListener = listener; |
| } |
| |
| @Override |
| public void onSurfaceTextureAvailable( |
| SurfaceTexture surfaceTexture, int width, int height) { |
| mListener.surfaceCreated(); |
| onSurfaceTextureSizeChanged(surfaceTexture, width, height); |
| } |
| |
| @Override |
| public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { |
| mListener.surfaceDestroyed(false /* cacheBackBuffer */); |
| return true; |
| } |
| |
| @Override |
| public void onSurfaceTextureSizeChanged( |
| SurfaceTexture surfaceTexture, int width, int height) { |
| if (mCurrentSurfaceTexture != surfaceTexture) { |
| mCurrentSurfaceTexture = surfaceTexture; |
| mCurrentSurface = new Surface(mCurrentSurfaceTexture); |
| } |
| mListener.surfaceChanged(mCurrentSurface, false, width, height); |
| } |
| |
| @Override |
| public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {} |
| } |
| |
| /** |
| * Constructs a new ContentViewRenderView. |
| * This should be called and the {@link ContentViewRenderView} should be added to the view |
| * hierarchy before the first draw to avoid a black flash that is seen every time a |
| * {@link SurfaceView} is added. |
| * @param context The context used to create this. |
| */ |
| public ContentViewRenderView(Context context) { |
| super(context); |
| setBackgroundColor(Color.WHITE); |
| } |
| |
| /** |
| * Initialization that requires native libraries should be done here. |
| * Native code should add/remove the layers to be rendered through the ContentViewLayerRenderer. |
| * @param rootWindow The {@link WindowAndroid} this render view should be linked to. |
| */ |
| public void onNativeLibraryLoaded(WindowAndroid rootWindow, @Mode int mode) { |
| assert rootWindow != null; |
| mNativeContentViewRenderView = |
| ContentViewRenderViewJni.get().init(ContentViewRenderView.this, rootWindow); |
| assert mNativeContentViewRenderView != 0; |
| mWindowAndroid = rootWindow; |
| requestMode(mode, (Boolean result) -> {}); |
| } |
| |
| public void requestMode(@Mode int mode, ValueCallback<Boolean> callback) { |
| assert mode == MODE_SURFACE_VIEW || mode == MODE_TEXTURE_VIEW; |
| assert callback != null; |
| if (mRequested != null && mRequested.getMode() != mode) { |
| if (mRequested != mCurrent) { |
| mRequested.markForDestroy(false /* hasNextSurface */); |
| mRequested.destroy(); |
| } |
| mRequested = null; |
| } |
| |
| if (mRequested == null) { |
| SurfaceEventListenerImpl listener = new SurfaceEventListenerImpl(); |
| mRequested = new SurfaceData( |
| mode, this, listener, mBackgroundColor, this::evictCachedSurface); |
| listener.setRequestData(mRequested); |
| } |
| assert mRequested.getMode() == mode; |
| mRequested.addCallback(callback); |
| } |
| |
| @Override |
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| mWidth = w; |
| mHeight = h; |
| if (mWebContents != null) mWebContents.setSize(w, h); |
| } |
| |
| /** |
| * View's method override to notify WindowAndroid about changes in its visibility. |
| */ |
| @Override |
| protected void onWindowVisibilityChanged(int visibility) { |
| super.onWindowVisibilityChanged(visibility); |
| |
| if (mWindowAndroid == null) return; |
| |
| if (visibility == View.GONE) { |
| mWindowAndroid.onVisibilityChanged(false); |
| } else if (visibility == View.VISIBLE) { |
| mWindowAndroid.onVisibilityChanged(true); |
| } |
| } |
| |
| /** |
| * Sets the background color of the surface / texture view. This method is necessary because |
| * the background color of ContentViewRenderView itself is covered by the background of |
| * SurfaceView. |
| * @param color The color of the background. |
| */ |
| @Override |
| public void setBackgroundColor(int color) { |
| super.setBackgroundColor(color); |
| mBackgroundColor = color; |
| if (mRequested != null) { |
| mRequested.setBackgroundColor(color); |
| } |
| if (mCurrent != null) { |
| mCurrent.setBackgroundColor(color); |
| } |
| } |
| |
| /** |
| * Should be called when the ContentViewRenderView is not needed anymore so its associated |
| * native resource can be freed. |
| */ |
| public void destroy() { |
| if (mRequested != null) { |
| mRequested.markForDestroy(false /* hasNextSurface */); |
| mRequested.destroy(); |
| if (mCurrent != null && mCurrent != mRequested) { |
| mCurrent.markForDestroy(false /* hasNextSurface */); |
| mCurrent.destroy(); |
| } |
| } |
| mRequested = null; |
| mCurrent = null; |
| |
| mWindowAndroid = null; |
| ContentViewRenderViewJni.get().destroy(mNativeContentViewRenderView); |
| mNativeContentViewRenderView = 0; |
| } |
| |
| public void setCurrentWebContents(WebContents webContents) { |
| assert mNativeContentViewRenderView != 0; |
| mWebContents = webContents; |
| |
| if (webContents != null) { |
| webContents.setSize(mWidth, mHeight); |
| ContentViewRenderViewJni.get().onPhysicalBackingSizeChanged( |
| mNativeContentViewRenderView, webContents, mWidth, mHeight); |
| } |
| ContentViewRenderViewJni.get().setCurrentWebContents( |
| mNativeContentViewRenderView, webContents); |
| } |
| |
| public ResourceManager getResourceManager() { |
| return ContentViewRenderViewJni.get().getResourceManager(mNativeContentViewRenderView); |
| } |
| |
| @CalledByNative |
| private boolean didSwapFrame() { |
| assert mCurrent != null; |
| return mCurrent.didSwapFrame(); |
| } |
| |
| private void evictCachedSurface() { |
| if (mNativeContentViewRenderView == 0) return; |
| ContentViewRenderViewJni.get().evictCachedSurface(mNativeContentViewRenderView); |
| } |
| |
| public long getNativeHandle() { |
| return mNativeContentViewRenderView; |
| } |
| |
| @NativeMethods |
| interface Natives { |
| long init(ContentViewRenderView caller, WindowAndroid rootWindow); |
| void destroy(long nativeContentViewRenderView); |
| void setCurrentWebContents(long nativeContentViewRenderView, WebContents webContents); |
| void onPhysicalBackingSizeChanged( |
| long nativeContentViewRenderView, WebContents webContents, int width, int height); |
| void surfaceCreated(long nativeContentViewRenderView); |
| void surfaceDestroyed(long nativeContentViewRenderView, boolean cacheBackBuffer); |
| void surfaceChanged(long nativeContentViewRenderView, boolean canBeUsedWithSurfaceControl, |
| int width, int height, Surface surface); |
| void evictCachedSurface(long nativeContentViewRenderView); |
| ResourceManager getResourceManager(long nativeContentViewRenderView); |
| } |
| } |