OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.document; |
| 6 |
| 7 import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_LOW_END_D
EVICE; |
| 8 |
| 9 import android.app.Activity; |
| 10 import android.app.ActivityManager; |
| 11 import android.app.ActivityManager.RunningServiceInfo; |
| 12 import android.app.Instrumentation; |
| 13 import android.app.PendingIntent; |
| 14 import android.content.Context; |
| 15 import android.content.Intent; |
| 16 import android.net.Uri; |
| 17 import android.os.Build; |
| 18 import android.test.MoreAsserts; |
| 19 import android.test.suitebuilder.annotation.MediumTest; |
| 20 import android.view.ContextMenu; |
| 21 import android.view.View; |
| 22 |
| 23 import com.google.android.apps.chrome.R; |
| 24 |
| 25 import org.chromium.base.ApplicationStatus; |
| 26 import org.chromium.base.ThreadUtils; |
| 27 import org.chromium.base.test.util.MinAndroidSdkLevel; |
| 28 import org.chromium.base.test.util.Restriction; |
| 29 import org.chromium.base.test.util.UrlUtils; |
| 30 import org.chromium.chrome.browser.BookmarkUtils; |
| 31 import org.chromium.chrome.browser.ChromeActivity; |
| 32 import org.chromium.chrome.browser.ChromeMobileApplication; |
| 33 import org.chromium.chrome.browser.ChromeTabbedActivity; |
| 34 import org.chromium.chrome.browser.EmptyTabObserver; |
| 35 import org.chromium.chrome.browser.IntentHandler; |
| 36 import org.chromium.chrome.browser.Tab; |
| 37 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver; |
| 38 import org.chromium.chrome.browser.tabmodel.TabModel; |
| 39 import org.chromium.chrome.browser.tabmodel.TabModelUtils; |
| 40 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel; |
| 41 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel.Initializa
tionObserver; |
| 42 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelImpl; |
| 43 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelSelector; |
| 44 import org.chromium.chrome.browser.tabmodel.document.OffTheRecordDocumentTabMode
l; |
| 45 import org.chromium.chrome.test.MultiActivityTestBase; |
| 46 import org.chromium.chrome.test.util.ActivityUtils; |
| 47 import org.chromium.chrome.test.util.DisableInTabbedMode; |
| 48 import org.chromium.content.app.SandboxedProcessService; |
| 49 import org.chromium.content.browser.test.util.CallbackHelper; |
| 50 import org.chromium.content.browser.test.util.Criteria; |
| 51 import org.chromium.content.browser.test.util.CriteriaHelper; |
| 52 import org.chromium.content.browser.test.util.TouchCommon; |
| 53 import org.chromium.content_public.browser.LoadUrlParams; |
| 54 import org.chromium.ui.base.PageTransition; |
| 55 |
| 56 import java.lang.ref.WeakReference; |
| 57 import java.util.List; |
| 58 import java.util.concurrent.Callable; |
| 59 |
| 60 /** |
| 61 * Tests the interactions of the DocumentTabModel with Android's ActivityManager
. This test suite |
| 62 * actually fires Intents to start different DocumentActivities. |
| 63 * |
| 64 * Note that this is different instrumenting one DocumentActivity in particular,
which should be |
| 65 * tested using the DocumentActivityTestBase class. |
| 66 */ |
| 67 @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP) |
| 68 @DisableInTabbedMode |
| 69 public class DocumentModeTest extends MultiActivityTestBase { |
| 70 private static final String URL_1 = "data:text/html;charset=utf-8,Page%201"; |
| 71 private static final String URL_2 = "data:text/html;charset=utf-8,Page%202"; |
| 72 private static final String URL_3 = "data:text/html;charset=utf-8,Page%203"; |
| 73 private static final String URL_4 = "data:text/html;charset=utf-8,Page%204"; |
| 74 |
| 75 private static final String HTML_LINK = "<html><head><meta " |
| 76 + "name='viewport' content='width=device-width initial-scale=0.5, ma
ximum-scale=0.5'>" |
| 77 + "<style>body {margin: 0em;} div {width: 100%; height: 100%; backgr
ound: #011684;}" |
| 78 + "</style></head><body><a href='data:text/html;charset=utf-8,white'
target='_blank'>" |
| 79 + "<div></div></a></body></html>"; |
| 80 private static final float HTML_SCALE = 0.5f; |
| 81 |
| 82 private boolean mInitializationCompleted; |
| 83 |
| 84 private Context mContext; |
| 85 private String mUrl; |
| 86 private String mReferrer; |
| 87 |
| 88 private static class TestTabObserver extends EmptyTabObserver { |
| 89 private ContextMenu mContextMenu; |
| 90 |
| 91 @Override |
| 92 public void onContextMenuShown(Tab tab, ContextMenu menu) { |
| 93 mContextMenu = menu; |
| 94 } |
| 95 } |
| 96 |
| 97 private static void launchMainIntent(Context context) throws Exception { |
| 98 Intent intent = new Intent(Intent.ACTION_MAIN); |
| 99 intent.setPackage(context.getPackageName()); |
| 100 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 101 context.startActivity(intent); |
| 102 MultiActivityTestBase.waitUntilChromeInForeground(); |
| 103 } |
| 104 |
| 105 private static void fireViewIntent(Context context, Uri data) throws Excepti
on { |
| 106 Intent intent = new Intent(Intent.ACTION_VIEW, data); |
| 107 intent.setClassName(context.getPackageName(), ChromeLauncherActivity.cla
ss.getName()); |
| 108 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 109 context.startActivity(intent); |
| 110 MultiActivityTestBase.waitUntilChromeInForeground(); |
| 111 } |
| 112 |
| 113 /** |
| 114 * Launches three tabs via Intents with ACTION_VIEW. |
| 115 */ |
| 116 private int[] launchThreeTabs() throws Exception { |
| 117 int[] tabIds = new int[3]; |
| 118 tabIds[0] = launchViaViewIntent(false, URL_1); |
| 119 tabIds[1] = launchViaViewIntent(false, URL_2); |
| 120 tabIds[2] = launchViaViewIntent(false, URL_3); |
| 121 assertFalse(tabIds[0] == tabIds[1]); |
| 122 assertFalse(tabIds[0] == tabIds[2]); |
| 123 assertFalse(tabIds[1] == tabIds[2]); |
| 124 assertEquals(3, |
| 125 ChromeMobileApplication.getDocumentTabModelSelector().getModel(f
alse).getCount()); |
| 126 return tabIds; |
| 127 } |
| 128 |
| 129 @Override |
| 130 public void setUp() throws Exception { |
| 131 super.setUp(); |
| 132 mContext = getInstrumentation().getTargetContext(); |
| 133 } |
| 134 |
| 135 /** |
| 136 * Confirm that you can't start ChromeTabbedActivity while the user is runni
ng in Document mode. |
| 137 */ |
| 138 @MediumTest |
| 139 public void testDontStartTabbedActivityInDocumentMode() throws Exception { |
| 140 launchThreeTabs(); |
| 141 |
| 142 // Try launching a ChromeTabbedActivity. |
| 143 Runnable runnable = new Runnable() { |
| 144 @Override |
| 145 public void run() { |
| 146 Intent intent = new Intent(); |
| 147 intent.setAction(Intent.ACTION_VIEW); |
| 148 intent.setClassName(mContext, ChromeTabbedActivity.class.getName
()); |
| 149 intent.setData(Uri.parse(URL_1)); |
| 150 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTI
VITY_NEW_DOCUMENT); |
| 151 mContext.startActivity(intent); |
| 152 } |
| 153 }; |
| 154 ActivityUtils.waitForActivity(getInstrumentation(), ChromeTabbedActivity
.class, runnable); |
| 155 |
| 156 // ApplicationStatus should note that the ChromeTabbedActivity isn't run
ning anymore. |
| 157 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 158 @Override |
| 159 public boolean isSatisfied() { |
| 160 List<WeakReference<Activity>> activities = ApplicationStatus.get
RunningActivities(); |
| 161 for (WeakReference<Activity> activity : activities) { |
| 162 if (activity.get() instanceof ChromeTabbedActivity) return f
alse; |
| 163 } |
| 164 return true; |
| 165 } |
| 166 })); |
| 167 } |
| 168 |
| 169 /** |
| 170 * Confirm that firing an Intent without a properly formatted document://ID?
url scheme causes |
| 171 * the DocumentActivity to finish itself and hopefully not flat crash (thoug
h that'd be better |
| 172 * than letting the user live in both tabbed and document mode simultaneousl
y crbug.com/445136). |
| 173 */ |
| 174 @MediumTest |
| 175 public void testFireInvalidIntent() throws Exception { |
| 176 launchThreeTabs(); |
| 177 |
| 178 final DocumentTabModelSelector selector = |
| 179 ChromeMobileApplication.getDocumentTabModelSelector(); |
| 180 final Activity lastTrackedActivity = ApplicationStatus.getLastTrackedFoc
usedActivity(); |
| 181 |
| 182 // Send the user home, then fire an Intent with invalid data. |
| 183 MultiActivityTestBase.launchHomescreenIntent(mContext); |
| 184 Intent intent = new Intent(lastTrackedActivity.getIntent()); |
| 185 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW
_DOCUMENT); |
| 186 intent.setData(Uri.parse("toteslegitscheme://")); |
| 187 mContext.startActivity(intent); |
| 188 |
| 189 // A DocumentActivity gets started, but it should immediately call finis
hAndRemoveTask() |
| 190 // because of the broken Intent. |
| 191 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 192 @Override |
| 193 public boolean isSatisfied() { |
| 194 Activity activity = ApplicationStatus.getLastTrackedFocusedActiv
ity(); |
| 195 return activity != lastTrackedActivity; |
| 196 } |
| 197 })); |
| 198 |
| 199 // We shouldn't record that a new Tab exists. |
| 200 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 201 @Override |
| 202 public boolean isSatisfied() { |
| 203 return selector.getCurrentModel().getCount() == 3 |
| 204 && selector.getTotalTabCount() == 3; |
| 205 } |
| 206 })); |
| 207 } |
| 208 |
| 209 /** |
| 210 * Confirm that firing an Intent for a document that has an ID for an alread
y existing Tab kills |
| 211 * the original. |
| 212 */ |
| 213 @MediumTest |
| 214 public void testDuplicateTabIDsKillsOldActivities() throws Exception { |
| 215 launchThreeTabs(); |
| 216 |
| 217 final DocumentTabModelSelector selector = |
| 218 ChromeMobileApplication.getDocumentTabModelSelector(); |
| 219 final int lastTabId = selector.getCurrentTabId(); |
| 220 final Activity lastTrackedActivity = ApplicationStatus.getLastTrackedFoc
usedActivity(); |
| 221 |
| 222 // Send the user home, then fire an Intent with an old Tab ID and a new
URL. |
| 223 MultiActivityTestBase.launchHomescreenIntent(mContext); |
| 224 Intent intent = new Intent(lastTrackedActivity.getIntent()); |
| 225 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW
_DOCUMENT); |
| 226 intent.setData(Uri.parse("document://" + lastTabId + "?" + URL_4)); |
| 227 mContext.startActivity(intent); |
| 228 |
| 229 // Funnily enough, Android doesn't differentiate between URIs with diffe
rent queries when |
| 230 // refocusing Activities based on the Intent data. This means we can't
do a check to see |
| 231 // that the new Activity appears with URL_4 -- we just get a new instanc
e of URL_3. |
| 232 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 233 @Override |
| 234 public boolean isSatisfied() { |
| 235 Activity activity = ApplicationStatus.getLastTrackedFocusedActiv
ity(); |
| 236 if (!(activity instanceof ChromeActivity)) return false; |
| 237 |
| 238 ChromeActivity chromeActivity = (ChromeActivity) activity; |
| 239 Tab tab = chromeActivity.getActivityTab(); |
| 240 return tab != null && lastTrackedActivity != activity |
| 241 && !selector.isIncognitoSelected() |
| 242 && lastTabId == selector.getCurrentTabId(); |
| 243 } |
| 244 })); |
| 245 |
| 246 // Although we get a new DocumentActivity, the old one with the same tab
ID gets killed. |
| 247 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 248 @Override |
| 249 public boolean isSatisfied() { |
| 250 return selector.getCurrentModel().getCount() == 3 |
| 251 && selector.getTotalTabCount() == 3; |
| 252 } |
| 253 })); |
| 254 } |
| 255 |
| 256 /** |
| 257 * Confirm that firing a View Intent with a null URL acts like a Main Intent
. |
| 258 */ |
| 259 @MediumTest |
| 260 public void testRelaunchLatestTabWithInvalidViewIntent() throws Exception { |
| 261 launchThreeTabs(); |
| 262 |
| 263 final DocumentTabModelSelector selector = |
| 264 ChromeMobileApplication.getDocumentTabModelSelector(); |
| 265 final int lastTabId = selector.getCurrentTabId(); |
| 266 |
| 267 final Activity lastTrackedActivity = ApplicationStatus.getLastTrackedFoc
usedActivity(); |
| 268 |
| 269 // Send Chrome to the background, then bring it back. |
| 270 MultiActivityTestBase.launchHomescreenIntent(mContext); |
| 271 fireViewIntent(mContext, null); |
| 272 |
| 273 assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
| 274 @Override |
| 275 public boolean isSatisfied() { |
| 276 return lastTrackedActivity == ApplicationStatus.getLastTrackedFo
cusedActivity() |
| 277 && !selector.isIncognitoSelected() |
| 278 && lastTabId == selector.getCurrentTabId(); |
| 279 } |
| 280 })); |
| 281 |
| 282 assertEquals(3, selector.getCurrentModel().getCount()); |
| 283 assertEquals(3, selector.getTotalTabCount()); |
| 284 } |
| 285 |
| 286 /** |
| 287 * Confirm that clicking the Chrome icon (e.g. firing an Intent with ACTION_
MAIN) brings back |
| 288 * the last viewed Tab. |
| 289 */ |
| 290 @MediumTest |
| 291 public void testRelaunchLatestTab() throws Exception { |
| 292 launchThreeTabs(); |
| 293 |
| 294 final DocumentTabModelSelector selector = |
| 295 ChromeMobileApplication.getDocumentTabModelSelector(); |
| 296 final int lastTabId = selector.getCurrentTabId(); |
| 297 |
| 298 // Send Chrome to the background, then bring it back. |
| 299 MultiActivityTestBase.launchHomescreenIntent(mContext); |
| 300 launchMainIntent(mContext); |
| 301 |
| 302 assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
| 303 @Override |
| 304 public boolean isSatisfied() { |
| 305 return !selector.isIncognitoSelected() && lastTabId == selector.
getCurrentTabId(); |
| 306 } |
| 307 })); |
| 308 |
| 309 assertEquals(3, selector.getCurrentModel().getCount()); |
| 310 assertEquals(3, selector.getTotalTabCount()); |
| 311 } |
| 312 |
| 313 @MediumTest |
| 314 public void testReferrerExtra() throws Exception { |
| 315 Instrumentation.ActivityMonitor monitor = getInstrumentation().addMonito
r( |
| 316 DocumentActivity.class.getName(), null, false); |
| 317 launchMainIntent(mContext); |
| 318 |
| 319 // Wait for tab model to become initialized. |
| 320 assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
| 321 @Override |
| 322 public boolean isSatisfied() { |
| 323 return ChromeMobileApplication.isDocumentTabModelSelectorInitial
izedForTests(); |
| 324 } |
| 325 })); |
| 326 |
| 327 DocumentTabModelSelector selector = ChromeMobileApplication.getDocumentT
abModelSelector(); |
| 328 selector.addObserver(new EmptyTabModelSelectorObserver() { |
| 329 @Override |
| 330 public void onNewTabCreated(Tab tab) { |
| 331 tab.addObserver(new EmptyTabObserver() { |
| 332 @Override |
| 333 public void onLoadUrl(Tab tab, LoadUrlParams params, int loa
dType) { |
| 334 mUrl = params.getUrl(); |
| 335 if (params.getReferrer() != null) { |
| 336 mReferrer = params.getReferrer().getUrl(); |
| 337 } |
| 338 } |
| 339 }); |
| 340 } |
| 341 }); |
| 342 |
| 343 // Wait for document activity from the main intent to launch before firi
ng the next |
| 344 // intent. |
| 345 DocumentActivity doc = (DocumentActivity) monitor.waitForActivityWithTim
eout(500); |
| 346 assertNotNull("DocumentActivity did not start", doc); |
| 347 |
| 348 // Fire the Intent with EXTRA_REFERRER. |
| 349 Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(URL_1)); |
| 350 intent.setClassName(mContext.getPackageName(), ChromeLauncherActivity.cl
ass.getName()); |
| 351 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 352 intent.putExtra(Intent.EXTRA_REFERRER, Uri.parse(URL_2)); |
| 353 IntentHandler.addTrustedIntentExtras(intent, mContext); |
| 354 mContext.startActivity(intent); |
| 355 |
| 356 assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
| 357 @Override |
| 358 public boolean isSatisfied() { |
| 359 return URL_1.equals(mUrl); |
| 360 } |
| 361 })); |
| 362 |
| 363 assertEquals(URL_2, mReferrer); |
| 364 } |
| 365 |
| 366 /** |
| 367 * Confirm that setting the index brings the correct tab forward. |
| 368 */ |
| 369 @MediumTest |
| 370 public void testSetIndex() throws Exception { |
| 371 int[] tabIds = launchThreeTabs(); |
| 372 |
| 373 final DocumentTabModelSelector selector = |
| 374 ChromeMobileApplication.getDocumentTabModelSelector(); |
| 375 ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| 376 @Override |
| 377 public void run() { |
| 378 TabModelUtils.setIndex(selector.getCurrentModel(), 0); |
| 379 } |
| 380 }); |
| 381 |
| 382 assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
| 383 @Override |
| 384 public boolean isSatisfied() { |
| 385 return !selector.isIncognitoSelected() && selector.getCurrentMod
el().index() == 0; |
| 386 } |
| 387 })); |
| 388 |
| 389 assertEquals(3, selector.getCurrentModel().getCount()); |
| 390 assertEquals(3, selector.getTotalTabCount()); |
| 391 assertEquals(tabIds[0], selector.getCurrentTab().getId()); |
| 392 } |
| 393 |
| 394 /** Check that Intents that request reusing Tabs are honored. */ |
| 395 @MediumTest |
| 396 public void testReuseIntent() throws Exception { |
| 397 // Create a tab, then send the user back to the Home screen. |
| 398 int tabId = launchViaViewIntent(false, URL_1); |
| 399 assertTrue(ChromeMobileApplication.isDocumentTabModelSelectorInitialized
ForTests()); |
| 400 final DocumentTabModelSelector selector = |
| 401 ChromeMobileApplication.getDocumentTabModelSelector(); |
| 402 assertEquals(1, selector.getModel(false).getCount()); |
| 403 launchHomescreenIntent(mContext); |
| 404 |
| 405 // Fire an Intent to reuse the same tab as before. |
| 406 Runnable runnable = new Runnable() { |
| 407 @Override |
| 408 public void run() { |
| 409 Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(URL_1))
; |
| 410 intent.setClass(mContext, ChromeLauncherActivity.class); |
| 411 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 412 intent.putExtra(BookmarkUtils.REUSE_URL_MATCHING_TAB_ELSE_NEW_TA
B, true); |
| 413 mContext.startActivity(intent); |
| 414 } |
| 415 }; |
| 416 ActivityUtils.waitForActivity(getInstrumentation(), ChromeLauncherActivi
ty.class, runnable); |
| 417 waitUntilChromeInForeground(); |
| 418 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 419 @Override |
| 420 public boolean isSatisfied() { |
| 421 Activity lastActivity = ApplicationStatus.getLastTrackedFocusedA
ctivity(); |
| 422 return lastActivity instanceof DocumentActivity; |
| 423 } |
| 424 })); |
| 425 assertEquals(tabId, selector.getCurrentTabId()); |
| 426 assertFalse(selector.isIncognitoSelected()); |
| 427 |
| 428 // Create another tab. |
| 429 final int secondTabId = launchViaViewIntent(false, URL_2); |
| 430 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 431 @Override |
| 432 public boolean isSatisfied() { |
| 433 return selector.getModel(false).getCount() == 2 |
| 434 && selector.getCurrentTabId() == secondTabId; |
| 435 } |
| 436 })); |
| 437 } |
| 438 |
| 439 /** |
| 440 * Tests both ways of launching Incognito tabs: via an Intent, and via |
| 441 * {@ref ChromeLauncherActivity#launchDocumentInstance()}. |
| 442 */ |
| 443 @MediumTest |
| 444 public void testIncognitoLaunches() throws Exception { |
| 445 assertFalse(ChromeMobileApplication.isDocumentTabModelSelectorInitialize
dForTests()); |
| 446 |
| 447 // Make sure that an untrusted Intent can't launch an IncognitoDocumentA
ctivity. |
| 448 Instrumentation.ActivityMonitor monitor = getInstrumentation().addMonito
r( |
| 449 DocumentActivity.class.getName(), null, false); |
| 450 assertFalse(ChromeMobileApplication.isDocumentTabModelSelectorInitialize
dForTests()); |
| 451 assertEquals(0, ApplicationStatus.getRunningActivities().size()); |
| 452 Runnable runnable = new Runnable() { |
| 453 @Override |
| 454 public void run() { |
| 455 Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(URL_1))
; |
| 456 intent.setClass(mContext, ChromeLauncherActivity.class); |
| 457 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 458 intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, true
); |
| 459 mContext.startActivity(intent); |
| 460 } |
| 461 }; |
| 462 ActivityUtils.waitForActivity(getInstrumentation(), ChromeLauncherActivi
ty.class, runnable); |
| 463 DocumentActivity doc = (DocumentActivity) monitor.waitForActivityWithTim
eout(1000); |
| 464 assertNull(doc); |
| 465 |
| 466 // Create an Incognito tab via an Intent extra. |
| 467 final int firstId = launchViaViewIntent(true, URL_2); |
| 468 assertTrue(ChromeMobileApplication.isDocumentTabModelSelectorInitialized
ForTests()); |
| 469 final DocumentTabModelSelector selector = |
| 470 ChromeMobileApplication.getDocumentTabModelSelector(); |
| 471 final TabModel incognitoModel = selector.getModel(true); |
| 472 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 473 @Override |
| 474 public boolean isSatisfied() { |
| 475 return firstId == selector.getCurrentTabId() && selector.getTota
lTabCount() == 1; |
| 476 } |
| 477 })); |
| 478 assertEquals(incognitoModel, selector.getCurrentModel()); |
| 479 |
| 480 // Launch via ChromeLauncherActivity.launchInstance(). |
| 481 final int secondId = launchViaLaunchDocumentInstance(true, URL_3); |
| 482 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 483 @Override |
| 484 public boolean isSatisfied() { |
| 485 return secondId == selector.getCurrentTabId() && selector.getTot
alTabCount() == 2; |
| 486 } |
| 487 })); |
| 488 assertTrue(selector.isIncognitoSelected()); |
| 489 assertEquals(incognitoModel, selector.getCurrentModel()); |
| 490 assertEquals(secondId, TabModelUtils.getCurrentTabId(incognitoModel)); |
| 491 } |
| 492 |
| 493 /** |
| 494 * Confirm that the incognito tabs and TabModel are destroyed when the "clos
e all" notification |
| 495 * Intent is fired. |
| 496 */ |
| 497 @MediumTest |
| 498 public void testIncognitoNotificationClosesTabs() throws Exception { |
| 499 final int regularId = launchViaLaunchDocumentInstance(false, URL_1); |
| 500 final DocumentTabModelSelector selector = |
| 501 ChromeMobileApplication.getDocumentTabModelSelector(); |
| 502 assertFalse(selector.isIncognitoSelected()); |
| 503 |
| 504 final int incognitoId = launchViaLaunchDocumentInstance(true, URL_2); |
| 505 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 506 @Override |
| 507 public boolean isSatisfied() { |
| 508 return selector.isIncognitoSelected() && selector.getCurrentTabI
d() == incognitoId; |
| 509 } |
| 510 })); |
| 511 assertEquals(0, selector.getCurrentModel().index()); |
| 512 assertEquals(1, selector.getCurrentModel().getCount()); |
| 513 |
| 514 PendingIntent closeAllIntent = |
| 515 ChromeLauncherActivity.getRemoveAllIncognitoTabsIntent(mContext)
; |
| 516 closeAllIntent.send(); |
| 517 |
| 518 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 519 @Override |
| 520 public boolean isSatisfied() { |
| 521 return selector.getCurrentTabId() == regularId; |
| 522 } |
| 523 })); |
| 524 OffTheRecordDocumentTabModel tabModel = |
| 525 (OffTheRecordDocumentTabModel) selector.getModel(true); |
| 526 assertFalse(selector.isIncognitoSelected()); |
| 527 assertFalse(tabModel.isDocumentTabModelImplCreated()); |
| 528 } |
| 529 |
| 530 /** |
| 531 * Tests if a tab is covered by its child activity. |
| 532 */ |
| 533 @MediumTest |
| 534 public void testCoveredByChildActivity() throws Exception { |
| 535 final int tabId = launchViaLaunchDocumentInstance(false, URL_1); |
| 536 final DocumentTabModel model = |
| 537 ChromeMobileApplication.getDocumentTabModelSelector().getModelFo
rTabId(tabId); |
| 538 final Tab tab = model.getTabAt(0); |
| 539 assertTrue(tab instanceof DocumentTab); |
| 540 final DocumentTab documentTab = (DocumentTab) tab; |
| 541 |
| 542 // We need to wait until the UI for document tab is initialized. So we c
reate the |
| 543 // InitializationObserver and set its satisfied criteria the same as |
| 544 // DocumentActivity.mTabInitializationObserver. |
| 545 InitializationObserver observer = new InitializationObserver(model) { |
| 546 @Override |
| 547 public boolean isSatisfied(int currentState) { |
| 548 return currentState >= DocumentTabModelImpl.STATE_LOAD_TAB_S
TATE_BG_END |
| 549 || model.isTabStateReady(tabId); |
| 550 } |
| 551 |
| 552 @Override |
| 553 public boolean isCanceled() { |
| 554 return false; |
| 555 } |
| 556 |
| 557 @Override |
| 558 public void runImmediately() { |
| 559 // This observer is created before DocumentActivity.mTabInit
ializationObserver. |
| 560 // Postpone setting mInitializationCompleted afterwards. |
| 561 ThreadUtils.postOnUiThread(new Runnable() { |
| 562 @Override |
| 563 public void run() { |
| 564 mInitializationCompleted = true; |
| 565 } |
| 566 }); |
| 567 } |
| 568 }; |
| 569 observer.runWhenReady(); |
| 570 |
| 571 assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
| 572 @Override |
| 573 public boolean isSatisfied() { |
| 574 return mInitializationCompleted; |
| 575 } |
| 576 })); |
| 577 |
| 578 assertFalse(documentTab.isCoveredByChildActivity()); |
| 579 assertFalse(model.isCoveredByChildActivity(tabId)); |
| 580 |
| 581 documentTab.setCoveredByChildActivity(true); |
| 582 assertTrue(documentTab.isCoveredByChildActivity()); |
| 583 assertTrue(model.isCoveredByChildActivity(tabId)); |
| 584 |
| 585 documentTab.setCoveredByChildActivity(false); |
| 586 assertFalse(documentTab.isCoveredByChildActivity()); |
| 587 assertFalse(model.isCoveredByChildActivity(tabId)); |
| 588 } |
| 589 |
| 590 /** |
| 591 * Tests that tab ID is properly set when tabs change. |
| 592 */ |
| 593 @MediumTest |
| 594 public void testLastTabIdUpdates() throws Exception { |
| 595 launchLinkDocument(); |
| 596 |
| 597 final DocumentActivity firstActivity = |
| 598 (DocumentActivity) ApplicationStatus.getLastTrackedFocusedActivi
ty(); |
| 599 |
| 600 // Save the current tab ID. |
| 601 final DocumentTabModelSelector selector = |
| 602 ChromeMobileApplication.getDocumentTabModelSelector(); |
| 603 final TabModel tabModel = selector.getModel(false); |
| 604 final int firstTabId = selector.getCurrentTabId(); |
| 605 final int firstTabIndex = tabModel.index(); |
| 606 |
| 607 openLinkInBackgroundTab(); |
| 608 |
| 609 // Do a plain click to make the link open in a new foreground Document. |
| 610 Runnable fgTrigger = new Runnable() { |
| 611 @Override |
| 612 public void run() { |
| 613 ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| 614 @Override |
| 615 public void run() { |
| 616 View view = firstActivity.findViewById(android.R.id.cont
ent).getRootView(); |
| 617 TouchCommon.singleClickView(view); |
| 618 } |
| 619 }); |
| 620 } |
| 621 }; |
| 622 ActivityUtils.waitForActivity(getInstrumentation(), DocumentActivity.cla
ss, fgTrigger); |
| 623 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 624 @Override |
| 625 public boolean isSatisfied() { |
| 626 if (3 != tabModel.getCount()) return false; |
| 627 if (firstTabIndex == tabModel.index()) return false; |
| 628 if (firstTabId == selector.getCurrentTabId()) return false; |
| 629 return true; |
| 630 } |
| 631 })); |
| 632 MoreAsserts.assertNotEqual( |
| 633 firstActivity, ApplicationStatus.getLastTrackedFocusedActivity()
); |
| 634 } |
| 635 |
| 636 @Restriction(RESTRICTION_TYPE_LOW_END_DEVICE) |
| 637 @MediumTest |
| 638 public void testNewTabLoadLowEnd() throws Exception { |
| 639 launchLinkDocument(); |
| 640 |
| 641 final CallbackHelper tabCreatedCallback = new CallbackHelper(); |
| 642 final CallbackHelper tabLoadStartedCallback = new CallbackHelper(); |
| 643 |
| 644 final DocumentTabModelSelector selector = |
| 645 ChromeMobileApplication.getDocumentTabModelSelector(); |
| 646 selector.addObserver(new EmptyTabModelSelectorObserver() { |
| 647 @Override |
| 648 public void onNewTabCreated(final Tab newTab) { |
| 649 selector.removeObserver(this); |
| 650 tabCreatedCallback.notifyCalled(); |
| 651 |
| 652 assertFalse(newTab.getWebContents().isLoadingToDifferentDocument
()); |
| 653 |
| 654 newTab.addObserver(new EmptyTabObserver() { |
| 655 @Override |
| 656 public void onPageLoadStarted(Tab tab) { |
| 657 newTab.removeObserver(this); |
| 658 tabLoadStartedCallback.notifyCalled(); |
| 659 } |
| 660 }); |
| 661 } |
| 662 }); |
| 663 |
| 664 openLinkInBackgroundTab(); |
| 665 |
| 666 // Tab should be created, but shouldn't start loading until we switch to
it. |
| 667 assertEquals(1, tabCreatedCallback.getCallCount()); |
| 668 assertEquals(0, tabLoadStartedCallback.getCallCount()); |
| 669 |
| 670 TabModelUtils.setIndex(selector.getCurrentModel(), 1); |
| 671 tabLoadStartedCallback.waitForCallback(0); |
| 672 } |
| 673 |
| 674 /** |
| 675 * Tests that "Open in new tab" command doesn't create renderer per tab |
| 676 * on low end devices. |
| 677 */ |
| 678 @Restriction(RESTRICTION_TYPE_LOW_END_DEVICE) |
| 679 @MediumTest |
| 680 public void testNewTabRenderersLowEnd() throws Exception { |
| 681 launchLinkDocument(); |
| 682 |
| 683 // Ignore any side effects that the first background tab might produce. |
| 684 openLinkInBackgroundTab(); |
| 685 |
| 686 int rendererCountBefore = countRenderers(); |
| 687 |
| 688 final int newTabCount = 5; |
| 689 for (int i = 0; i != newTabCount; ++i) { |
| 690 openLinkInBackgroundTab(); |
| 691 } |
| 692 |
| 693 int rendererCountAfter = countRenderers(); |
| 694 |
| 695 assertEquals(rendererCountBefore, rendererCountAfter); |
| 696 } |
| 697 |
| 698 /** Starts a DocumentActivity by using firing a VIEW Intent. */ |
| 699 private int launchViaViewIntent(final boolean incognito, final String url) t
hrows Exception { |
| 700 // Fire the Intent and wait until Chrome is in the foreground. |
| 701 Runnable runnable = new Runnable() { |
| 702 @Override |
| 703 public void run() { |
| 704 Runnable runnable = new Runnable() { |
| 705 @Override |
| 706 public void run() { |
| 707 Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse
(URL_1)); |
| 708 intent.setClass(mContext, ChromeLauncherActivity.class); |
| 709 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 710 if (incognito) { |
| 711 intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNI
TO_TAB, true); |
| 712 IntentHandler.startActivityForTrustedIntent(intent,
mContext); |
| 713 } else { |
| 714 mContext.startActivity(intent); |
| 715 } |
| 716 } |
| 717 }; |
| 718 ActivityUtils.waitForActivity(getInstrumentation(), |
| 719 (incognito ? IncognitoDocumentActivity.class : DocumentA
ctivity.class), |
| 720 runnable); |
| 721 } |
| 722 }; |
| 723 return launchUrlViaRunnable(incognito, runnable); |
| 724 } |
| 725 |
| 726 /** Starts a DocumentActivity using {@ref ChromeLauncherActivity.launchDocum
entInstance().} */ |
| 727 private int launchViaLaunchDocumentInstance( |
| 728 final boolean incognito, final String url) throws Exception { |
| 729 Runnable runnable = new Runnable() { |
| 730 @Override |
| 731 public void run() { |
| 732 ChromeLauncherActivity.launchDocumentInstance(null, incognito, |
| 733 ChromeLauncherActivity.LAUNCH_MODE_FOREGROUND, url, |
| 734 DocumentMetricIds.STARTED_BY_UNKNOWN, PageTransition.LIN
K, false, null); |
| 735 } |
| 736 }; |
| 737 return launchUrlViaRunnable(incognito, runnable); |
| 738 } |
| 739 |
| 740 /** |
| 741 * Launches a DocumentActivity via the given Runnable. |
| 742 * Ideally this would use Observers, but we can't know that the DocumentTabM
odelSelector has |
| 743 * been created, and don't want to influence the test by accidentally creati
ng it during the |
| 744 * test suite runs. |
| 745 * @return ID of the Tab that was launched. |
| 746 */ |
| 747 private int launchUrlViaRunnable(final boolean incognito, final Runnable run
nable) |
| 748 throws Exception { |
| 749 final int tabCount = |
| 750 ChromeMobileApplication.isDocumentTabModelSelectorInitializedFor
Tests() |
| 751 ? ChromeMobileApplication.getDocumentTabModelSelector().getModel
(incognito) |
| 752 .getCount() : 0; |
| 753 final int tabId = |
| 754 ChromeMobileApplication.isDocumentTabModelSelectorInitializedFor
Tests() |
| 755 ? ChromeMobileApplication.getDocumentTabModelSelector().getCurre
ntTabId() |
| 756 : Tab.INVALID_TAB_ID; |
| 757 |
| 758 runnable.run(); |
| 759 assertTrue(ChromeMobileApplication.isDocumentTabModelSelectorInitialized
ForTests()); |
| 760 MultiActivityTestBase.waitUntilChromeInForeground(); |
| 761 |
| 762 // Wait until the selector is ready and the Tabs have been added to the
DocumentTabModel. |
| 763 assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
| 764 @Override |
| 765 public boolean isSatisfied() { |
| 766 if (!ChromeMobileApplication.isDocumentTabModelSelectorInitializ
edForTests()) { |
| 767 return false; |
| 768 } |
| 769 |
| 770 DocumentTabModelSelector selector = |
| 771 ChromeMobileApplication.getDocumentTabModelSelector(); |
| 772 if (selector.isIncognitoSelected() != incognito) return false; |
| 773 if (selector.getModel(incognito).getCount() != (tabCount + 1)) r
eturn false; |
| 774 if (selector.getCurrentTabId() == tabId) return false; |
| 775 return true; |
| 776 } |
| 777 })); |
| 778 |
| 779 return ChromeMobileApplication.getDocumentTabModelSelector().getCurrentT
abId(); |
| 780 } |
| 781 |
| 782 /** |
| 783 * Launches DocumentActivity with the special URL suitable for openLinkInBac
kgroundTab() |
| 784 * invocations. |
| 785 */ |
| 786 private void launchLinkDocument() throws Exception { |
| 787 // Load HTML that defines one gigantic link spanning the whole page. |
| 788 launchViaLaunchDocumentInstance(false, UrlUtils.encodeHtmlDataUri(HTML_L
INK)); |
| 789 final DocumentActivity activity = |
| 790 (DocumentActivity) ApplicationStatus.getLastTrackedFocusedActivi
ty(); |
| 791 assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
| 792 @Override |
| 793 public boolean isSatisfied() { |
| 794 return Float.compare( |
| 795 activity.getCurrentContentViewCore().getScale(), HTML_SC
ALE) == 0; |
| 796 } |
| 797 })); |
| 798 } |
| 799 |
| 800 /** |
| 801 * Long presses at the center of the page, selects "Open In New Tab" option |
| 802 * from the menu. |
| 803 */ |
| 804 private void openLinkInBackgroundTab() throws Exception { |
| 805 // Long press the center of the page, which should bring up the context
menu. |
| 806 final TestTabObserver observer = new TestTabObserver(); |
| 807 final DocumentActivity activity = |
| 808 (DocumentActivity) ApplicationStatus.getLastTrackedFocusedActivi
ty(); |
| 809 activity.getActivityTab().addObserver(observer); |
| 810 assertNull(observer.mContextMenu); |
| 811 final View view = ThreadUtils.runOnUiThreadBlocking(new Callable<View>()
{ |
| 812 @Override |
| 813 public View call() throws Exception { |
| 814 return activity.findViewById(android.R.id.content).getRootView()
; |
| 815 } |
| 816 }); |
| 817 TouchCommon.longPressView(view, view.getWidth() / 2, view.getHeight() /
2); |
| 818 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 819 @Override |
| 820 public boolean isSatisfied() { |
| 821 return observer.mContextMenu != null; |
| 822 } |
| 823 })); |
| 824 |
| 825 activity.getActivityTab().removeObserver(observer); |
| 826 |
| 827 // We expect tab to open in the background, i.e. tab index / id should |
| 828 // stay the same. |
| 829 final DocumentTabModelSelector selector = |
| 830 ChromeMobileApplication.getDocumentTabModelSelector(); |
| 831 final TabModel tabModel = selector.getModel(false); |
| 832 final int expectedTabCount = tabModel.getCount() + 1; |
| 833 final int expectedTabIndex = tabModel.index(); |
| 834 final int expectedTabId = selector.getCurrentTabId(); |
| 835 |
| 836 // Select the "open in new tab" option to open a tab in the background. |
| 837 ActivityUtils.waitForActivity(getInstrumentation(), DocumentActivity.cla
ss, |
| 838 new Runnable() { |
| 839 @Override |
| 840 public void run() { |
| 841 assertTrue(observer.mContextMenu.performIdentifierAction
( |
| 842 R.id.contextmenu_open_in_new_tab, 0)); |
| 843 } |
| 844 } |
| 845 ); |
| 846 |
| 847 assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
| 848 @Override |
| 849 public boolean isSatisfied() { |
| 850 if (expectedTabCount != tabModel.getCount()) return false; |
| 851 if (expectedTabIndex != tabModel.index()) return false; |
| 852 if (expectedTabId != selector.getCurrentTabId()) return false; |
| 853 return true; |
| 854 } |
| 855 })); |
| 856 |
| 857 assertEquals(activity, ApplicationStatus.getLastTrackedFocusedActivity()
); |
| 858 } |
| 859 |
| 860 /** |
| 861 * Returns the number of currently running renderer services. |
| 862 */ |
| 863 private int countRenderers() { |
| 864 ActivityManager activityManager = (ActivityManager) mContext.getSystemSe
rvice( |
| 865 Context.ACTIVITY_SERVICE); |
| 866 |
| 867 int rendererCount = 0; |
| 868 List<RunningServiceInfo> serviceInfos = activityManager.getRunningServic
es( |
| 869 Integer.MAX_VALUE); |
| 870 for (RunningServiceInfo serviceInfo : serviceInfos) { |
| 871 if (serviceInfo.service.getClassName().startsWith( |
| 872 SandboxedProcessService.class.getName())) { |
| 873 rendererCount++; |
| 874 } |
| 875 } |
| 876 |
| 877 return rendererCount; |
| 878 } |
| 879 } |
OLD | NEW |