| // Copyright 2015 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.android_webview.test; |
| |
| import android.content.Context; |
| import android.content.ContextWrapper; |
| import android.test.suitebuilder.annotation.LargeTest; |
| import android.test.suitebuilder.annotation.SmallTest; |
| |
| import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout; |
| |
| import org.chromium.android_webview.AwContents; |
| import org.chromium.base.annotations.SuppressFBWarnings; |
| import org.chromium.base.test.util.Feature; |
| import org.chromium.content.browser.test.util.Criteria; |
| import org.chromium.content.browser.test.util.CriteriaHelper; |
| |
| import java.util.concurrent.Callable; |
| |
| /** |
| * AwContents garbage collection tests. Most apps relies on WebView being |
| * garbage collected to release memory. These tests ensure that nothing |
| * accidentally prevents AwContents from garbage collected, leading to leaks. |
| * See crbug.com/544098 for why @DisableHardwareAccelerationForTest is needed. |
| */ |
| public class AwContentsGarbageCollectionTest extends AwTestBase { |
| // The system retains a strong ref to the last focused view (in InputMethodManager) |
| // so allow for 1 'leaked' instance. |
| private static final int MAX_IDLE_INSTANCES = 1; |
| |
| private TestDependencyFactory mOverridenFactory; |
| |
| @Override |
| public void tearDown() throws Exception { |
| mOverridenFactory = null; |
| super.tearDown(); |
| } |
| |
| @Override |
| protected TestDependencyFactory createTestDependencyFactory() { |
| if (mOverridenFactory == null) { |
| return new TestDependencyFactory(); |
| } else { |
| return mOverridenFactory; |
| } |
| } |
| |
| @SuppressFBWarnings("URF_UNREAD_FIELD") |
| private static class StrongRefTestContext extends ContextWrapper { |
| private AwContents mAwContents; |
| public void setAwContentsStrongRef(AwContents awContents) { |
| mAwContents = awContents; |
| } |
| |
| public StrongRefTestContext(Context context) { |
| super(context); |
| } |
| } |
| |
| private static class GcTestDependencyFactory extends TestDependencyFactory { |
| private StrongRefTestContext mContext; |
| |
| public GcTestDependencyFactory(StrongRefTestContext context) { |
| mContext = context; |
| } |
| |
| @Override |
| public AwTestContainerView createAwTestContainerView( |
| AwTestRunnerActivity activity, boolean allowHardwareAcceleration) { |
| if (activity != mContext.getBaseContext()) fail(); |
| return new AwTestContainerView(mContext, allowHardwareAcceleration); |
| } |
| } |
| |
| @SuppressFBWarnings("URF_UNREAD_FIELD") |
| private static class StrongRefTestAwContentsClient extends TestAwContentsClient { |
| private AwContents mAwContentsStrongRef; |
| public void setAwContentsStrongRef(AwContents awContents) { |
| mAwContentsStrongRef = awContents; |
| } |
| } |
| |
| @DisableHardwareAccelerationForTest |
| @SmallTest |
| @Feature({"AndroidWebView"}) |
| public void testCreateAndGcOneTime() throws Throwable { |
| gcAndCheckAllAwContentsDestroyed(); |
| |
| TestAwContentsClient client = new TestAwContentsClient(); |
| AwTestContainerView containerViews[] = new AwTestContainerView[MAX_IDLE_INSTANCES + 1]; |
| for (int i = 0; i < containerViews.length; i++) { |
| containerViews[i] = createAwTestContainerViewOnMainSync(client); |
| loadUrlAsync(containerViews[i].getAwContents(), "about:blank"); |
| } |
| |
| containerViews = null; |
| removeAllViews(); |
| gcAndCheckAllAwContentsDestroyed(); |
| } |
| |
| @DisableHardwareAccelerationForTest |
| @SmallTest |
| @Feature({"AndroidWebView"}) |
| public void testReferenceFromClient() throws Throwable { |
| gcAndCheckAllAwContentsDestroyed(); |
| |
| AwTestContainerView containerViews[] = new AwTestContainerView[MAX_IDLE_INSTANCES + 1]; |
| for (int i = 0; i < containerViews.length; i++) { |
| StrongRefTestAwContentsClient client = new StrongRefTestAwContentsClient(); |
| containerViews[i] = createAwTestContainerViewOnMainSync(client); |
| loadUrlAsync(containerViews[i].getAwContents(), "about:blank"); |
| } |
| |
| containerViews = null; |
| removeAllViews(); |
| gcAndCheckAllAwContentsDestroyed(); |
| } |
| |
| @DisableHardwareAccelerationForTest |
| @SmallTest |
| @Feature({"AndroidWebView"}) |
| public void testReferenceFromContext() throws Throwable { |
| gcAndCheckAllAwContentsDestroyed(); |
| |
| TestAwContentsClient client = new TestAwContentsClient(); |
| AwTestContainerView containerViews[] = new AwTestContainerView[MAX_IDLE_INSTANCES + 1]; |
| for (int i = 0; i < containerViews.length; i++) { |
| StrongRefTestContext context = new StrongRefTestContext(getActivity()); |
| mOverridenFactory = new GcTestDependencyFactory(context); |
| containerViews[i] = createAwTestContainerViewOnMainSync(client); |
| mOverridenFactory = null; |
| loadUrlAsync(containerViews[i].getAwContents(), "about:blank"); |
| } |
| |
| containerViews = null; |
| removeAllViews(); |
| gcAndCheckAllAwContentsDestroyed(); |
| } |
| |
| @DisableHardwareAccelerationForTest |
| @LargeTest |
| @Feature({"AndroidWebView"}) |
| public void testCreateAndGcManyTimes() throws Throwable { |
| gcAndCheckAllAwContentsDestroyed(); |
| |
| final int concurrentInstances = 4; |
| final int repetitions = 16; |
| |
| for (int i = 0; i < repetitions; ++i) { |
| for (int j = 0; j < concurrentInstances; ++j) { |
| StrongRefTestAwContentsClient client = new StrongRefTestAwContentsClient(); |
| StrongRefTestContext context = new StrongRefTestContext(getActivity()); |
| mOverridenFactory = new GcTestDependencyFactory(context); |
| AwTestContainerView view = createAwTestContainerViewOnMainSync(client); |
| mOverridenFactory = null; |
| // Embedding app can hold onto a strong ref to the WebView from either |
| // WebViewClient or WebChromeClient. That should not prevent WebView from |
| // gc-ed. We simulate that behavior by making the equivalent change here, |
| // have AwContentsClient hold a strong ref to the AwContents object. |
| client.setAwContentsStrongRef(view.getAwContents()); |
| context.setAwContentsStrongRef(view.getAwContents()); |
| loadUrlAsync(view.getAwContents(), "about:blank"); |
| } |
| assertTrue(AwContents.getNativeInstanceCount() >= concurrentInstances); |
| assertTrue(AwContents.getNativeInstanceCount() <= (i + 1) * concurrentInstances); |
| removeAllViews(); |
| } |
| |
| gcAndCheckAllAwContentsDestroyed(); |
| } |
| |
| private void removeAllViews() throws Throwable { |
| runTestOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| getActivity().removeAllViews(); |
| } |
| }); |
| } |
| |
| private void gcAndCheckAllAwContentsDestroyed() throws InterruptedException { |
| Runtime.getRuntime().gc(); |
| |
| Criteria criteria = new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| try { |
| return runTestOnUiThreadAndGetResult(new Callable<Boolean>() { |
| @Override |
| public Boolean call() { |
| int count = AwContents.getNativeInstanceCount(); |
| return count <= MAX_IDLE_INSTANCES; |
| } |
| }); |
| } catch (Exception e) { |
| return false; |
| } |
| } |
| }; |
| |
| // Depending on a single gc call can make this test flaky. It's possible |
| // that the WebView still has transient references during load so it does not get |
| // gc-ed in the one gc-call above. Instead call gc again if exit criteria fails to |
| // catch this case. |
| final long timeoutBetweenGcMs = scaleTimeout(1000); |
| for (int i = 0; i < 15; ++i) { |
| try { |
| CriteriaHelper.pollForCriteria(criteria, timeoutBetweenGcMs, CHECK_INTERVAL); |
| break; |
| } catch (AssertionError e) { |
| Runtime.getRuntime().gc(); |
| } |
| } |
| |
| assertTrue(criteria.isSatisfied()); |
| } |
| } |
| |