Index: chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java |
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java |
index 83646723b829140df357fbf683bc80289cfd8dd5..3ee9a35621bf3cfbc253f5354ee8c945198e2d51 100644 |
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java |
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java |
@@ -32,6 +32,7 @@ import android.support.customtabs.CustomTabsService; |
import android.support.customtabs.CustomTabsServiceConnection; |
import android.support.customtabs.CustomTabsSession; |
import android.support.customtabs.CustomTabsSessionToken; |
+import android.support.test.filters.LargeTest; |
import android.support.test.filters.MediumTest; |
import android.support.test.filters.SmallTest; |
import android.text.TextUtils; |
@@ -49,6 +50,7 @@ import org.chromium.base.ApplicationStatus.ActivityStateListener; |
import org.chromium.base.ObserverList.RewindableIterator; |
import org.chromium.base.PathUtils; |
import org.chromium.base.ThreadUtils; |
+import org.chromium.base.annotations.SuppressFBWarnings; |
import org.chromium.base.library_loader.LibraryLoader; |
import org.chromium.base.library_loader.LibraryProcessType; |
import org.chromium.base.test.util.CallbackHelper; |
@@ -68,6 +70,7 @@ import org.chromium.chrome.browser.appmenu.AppMenuHandler; |
import org.chromium.chrome.browser.document.ChromeLauncherActivity; |
import org.chromium.chrome.browser.firstrun.FirstRunStatus; |
import org.chromium.chrome.browser.metrics.PageLoadMetrics; |
+import org.chromium.chrome.browser.preferences.Preferences; |
import org.chromium.chrome.browser.prerender.ExternalPrerenderHandler; |
import org.chromium.chrome.browser.profiles.Profile; |
import org.chromium.chrome.browser.tab.EmptyTabObserver; |
@@ -79,6 +82,8 @@ import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; |
import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
import org.chromium.chrome.browser.toolbar.CustomTabToolbar; |
import org.chromium.chrome.browser.util.ColorUtils; |
+import org.chromium.chrome.test.util.ApplicationTestUtils; |
+import org.chromium.chrome.test.util.ApplicationTestUtils.ActivityCloser; |
import org.chromium.chrome.test.util.ChromeRestriction; |
import org.chromium.chrome.test.util.browser.LocationSettingsTestUtil; |
import org.chromium.chrome.test.util.browser.contextmenu.ContextMenuUtils; |
@@ -94,7 +99,10 @@ import org.chromium.net.test.EmbeddedTestServer; |
import org.chromium.net.test.util.TestWebServer; |
import org.chromium.ui.mojom.WindowOpenDisposition; |
+import java.lang.ref.WeakReference; |
import java.util.ArrayList; |
+import java.util.Collections; |
+import java.util.List; |
import java.util.concurrent.Callable; |
import java.util.concurrent.TimeoutException; |
import java.util.concurrent.atomic.AtomicBoolean; |
@@ -2350,6 +2358,144 @@ public class CustomTabActivityTest extends CustomTabActivityTestBase { |
assertEquals(testUrl, tab.getUrl()); |
} |
+ @LargeTest |
+ public void testClosedActivitiesNotLeaked() throws Exception { |
+ openCloseActivities(true /* closeFrontToBack */, new ActivityCloser<CustomTabActivity>() { |
+ @Override |
+ public void close(CustomTabActivity activity) { |
+ CustomTabToolbar toolbar = (CustomTabToolbar) activity.findViewById(R.id.toolbar); |
+ toolbar.getCloseButtonForTest().performClick(); |
+ } |
+ }); |
+ } |
+ |
+ @LargeTest |
+ public void testClosedBackActivitiesNotLeaked() throws Exception { |
+ openCloseActivities(true /* closeFrontToBack */, new ActivityCloser<CustomTabActivity>() { |
+ @Override |
+ public void close(CustomTabActivity activity) { |
+ activity.onBackPressed(); |
+ } |
+ }); |
+ } |
+ |
+ @LargeTest |
+ public void testFinishedActivitiesNotLeaked() throws Exception { |
+ openCloseActivities(true /* closeFrontToBack */, new ActivityCloser<CustomTabActivity>() { |
+ @Override |
+ public void close(CustomTabActivity activity) { |
+ activity.finish(); |
+ } |
+ }); |
+ } |
+ |
+ @LargeTest |
+ public void testStoppedFinishedActivitiesNotLeaked() throws Exception { |
+ openCloseActivities(false /* closeFrontToBack */, new ActivityCloser<CustomTabActivity>() { |
+ @Override |
+ public void close(CustomTabActivity activity) { |
+ activity.finish(); |
+ } |
+ }); |
+ } |
+ |
+ /** |
+ * Opens several activities, closes them, and checks that activities are GCed. |
+ * |
+ * @param closeFrontToBack Whether to close activities starting from the |
+ * last opened one (front), or from the first opened one (back). |
+ * Essentially this param controls whether activities are closed |
+ * in the active (true) or stopped (false) state. |
+ * @param activityCloser Activity closing logic. |
+ */ |
+ @SuppressFBWarnings("DM_GC") |
+ private void openCloseActivities(boolean closeFrontToBack, |
+ final ActivityCloser<CustomTabActivity> activityCloser) throws Exception { |
+ final int activityCount = 5; |
+ |
+ final List<WeakReference<Activity>> activityRefs = new ArrayList<>(); |
+ |
+ // Something in ApplicationTestUtils.closeActivity() call below retains last closed |
+ // activity (in a local reference). I couldn't figure out what it is, so to avoid |
+ // the problem we start one extra activity so that it's closed last. We don't care |
+ // what that extra activity is, as long it's not CustomTabActivity. Note that we |
+ // need to start extra activity before / after CCT activities according to |
+ // closeFrontToBack flag. |
+ Runnable extraActivityStarter = new Runnable() { |
+ @Override |
+ public void run() { |
+ Intent intent = new Intent(); |
+ intent.setClass(getInstrumentation().getTargetContext(), Preferences.class); |
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
+ activityRefs.add( |
+ new WeakReference<>(getInstrumentation().startActivitySync(intent))); |
+ } |
+ }; |
+ ActivityCloser<Activity> extraActivityCloser = new ActivityCloser<Activity>() { |
+ @Override |
+ public void close(Activity activity) { |
+ activity.finish(); |
+ } |
+ }; |
+ |
+ // We're closing front to back, so extra activity should be started first. |
+ if (closeFrontToBack) extraActivityStarter.run(); |
+ |
+ for (int i = 0; i != activityCount; ++i) { |
+ // Don't use startActivityCompletely() because it retains the started |
+ // activity in several member variables. |
+ CustomTabActivity activity = |
+ launchCustomTabActivityCompletely(createMinimalCustomTabIntent()); |
+ waitForCustomTab(activity); |
+ activityRefs.add(new WeakReference<Activity>(activity)); |
+ } |
+ |
+ // We're closing back to front, so extra activity should be started last. |
+ if (!closeFrontToBack) extraActivityStarter.run(); |
+ |
+ // Make sure that the first activity we need to close is at the beginning. |
+ if (closeFrontToBack) Collections.reverse(activityRefs); |
+ |
+ for (WeakReference<Activity> ref : activityRefs) { |
+ Activity activity = ref.get(); |
+ // Activity can be null here if "Don't keep activities" developer option |
+ // is turned on. |
+ if (activity != null) { |
+ if (activity instanceof CustomTabActivity) { |
+ ApplicationTestUtils.closeActivity( |
+ (CustomTabActivity) activity, activityCloser); |
+ } else { |
+ // Must be an extra activity started above. |
+ ApplicationTestUtils.closeActivity(activity, extraActivityCloser); |
+ } |
+ } |
+ } |
+ |
+ // GC the activities. Note that System.gc() is a no-op unless it is followed |
+ // by or following a call to System.runFinalization(). |
+ System.gc(); |
+ System.runFinalization(); |
+ System.gc(); |
+ |
+ for (int i = 0; i != activityCount; ++i) { |
+ WeakReference<Activity> ref = activityRefs.get(i); |
+ |
+ // If this fails: something retains activities! It's probably a leak! |
+ // In order to find the culprit: |
+ // * Repro locally |
+ // * Comment out the assert |
+ // * Add Thread.sleep() for a minute or so |
+ // * While it's sleeping, open DDMS and take an HPROF dump |
+ // * Use ahat tool (go/ahat) to open the dump |
+ // * Go to 'allocations' page |
+ // * Find 'SeparateTaskCustomTabActivity' row |
+ // * Click on the number, *not* on the class name |
+ // * You will see a list of live SeparateTaskCustomTabActivity objects - |
+ // click on entries and inspect their 'Sample Path from GC Root'. |
+ assertNull("Activity is leaked", ref.get()); |
+ } |
+ } |
+ |
/** Maybe prerenders a URL with a referrer, then launch it with another one. */ |
private void maybeSpeculateAndLaunchWithReferrers(String url, int speculationMode, |
String speculationReferrer, String launchReferrer) throws Exception { |