1 // Copyright 2019 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.weblayer.test;
6 
7 import static org.junit.Assert.assertEquals;
8 import static org.junit.Assert.assertFalse;
9 import static org.junit.Assert.assertNotEquals;
10 import static org.junit.Assert.assertNotNull;
11 import static org.junit.Assert.assertTrue;
12 
13 import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
14 
15 import android.net.Uri;
16 import android.os.SystemClock;
17 import android.support.test.InstrumentationRegistry;
18 import android.webkit.WebResourceResponse;
19 
20 import androidx.test.filters.SmallTest;
21 
22 import org.hamcrest.Matchers;
23 import org.junit.Assert;
24 import org.junit.Before;
25 import org.junit.Rule;
26 import org.junit.Test;
27 import org.junit.runner.RunWith;
28 
29 import org.chromium.base.test.util.CallbackHelper;
30 import org.chromium.base.test.util.Criteria;
31 import org.chromium.base.test.util.CriteriaHelper;
32 import org.chromium.content_public.browser.test.util.TestThreadUtils;
33 import org.chromium.net.test.util.TestWebServer;
34 import org.chromium.weblayer.Browser;
35 import org.chromium.weblayer.LoadError;
36 import org.chromium.weblayer.NavigateParams;
37 import org.chromium.weblayer.Navigation;
38 import org.chromium.weblayer.NavigationCallback;
39 import org.chromium.weblayer.NavigationController;
40 import org.chromium.weblayer.NavigationState;
41 import org.chromium.weblayer.Tab;
42 import org.chromium.weblayer.TabCallback;
43 import org.chromium.weblayer.TabListCallback;
44 import org.chromium.weblayer.WebLayer;
45 import org.chromium.weblayer.shell.InstrumentationActivity;
46 
47 import java.io.ByteArrayInputStream;
48 import java.io.InputStream;
49 import java.nio.charset.StandardCharsets;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collections;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.concurrent.TimeoutException;
57 import java.util.concurrent.atomic.AtomicReference;
58 
59 /**
60  * Example test that just starts the weblayer shell.
61  */
62 @RunWith(WebLayerJUnit4ClassRunner.class)
63 public class NavigationTest {
64     @Rule
65     public InstrumentationActivityTestRule mActivityTestRule =
66             new InstrumentationActivityTestRule();
67 
68     // URLs used for base tests.
69     private static final String URL1 = "data:text,foo";
70     private static final String URL2 = "data:text,bar";
71     private static final String URL3 = "data:text,baz";
72     private static final String URL4 = "data:text,bat";
73     private static final String STREAM_URL = "https://doesntreallyexist123.com/bar";
74     private static final String STREAM_HTML = "<html>foobar</html>";
75     private static final String STREAM_INNER_BODY = "foobar";
76 
77     private static boolean sShouldTrackPageInitiated;
78 
79     private static class Callback extends NavigationCallback {
80         public static class NavigationCallbackHelper extends CallbackHelper {
81             private Uri mUri;
82             private boolean mIsSameDocument;
83             private int mHttpStatusCode;
84             private List<Uri> mRedirectChain;
85             private @LoadError int mLoadError;
86             private @NavigationState int mNavigationState;
87             private boolean mIsPageInitiatedNavigation;
88 
notifyCalled(Navigation navigation)89             public void notifyCalled(Navigation navigation) {
90                 mUri = navigation.getUri();
91                 mIsSameDocument = navigation.isSameDocument();
92                 mHttpStatusCode = navigation.getHttpStatusCode();
93                 mRedirectChain = navigation.getRedirectChain();
94                 mLoadError = navigation.getLoadError();
95                 mNavigationState = navigation.getState();
96                 if (sShouldTrackPageInitiated) {
97                     mIsPageInitiatedNavigation = navigation.isPageInitiated();
98                 }
99                 notifyCalled();
100             }
101 
assertCalledWith(int currentCallCount, String uri)102             public void assertCalledWith(int currentCallCount, String uri) throws TimeoutException {
103                 waitForCallback(currentCallCount);
104                 assertEquals(mUri.toString(), uri);
105             }
106 
assertCalledWith(int currentCallCount, String uri, boolean isSameDocument)107             public void assertCalledWith(int currentCallCount, String uri, boolean isSameDocument)
108                     throws TimeoutException {
109                 waitForCallback(currentCallCount);
110                 assertEquals(mUri.toString(), uri);
111                 assertEquals(mIsSameDocument, isSameDocument);
112             }
113 
assertCalledWith(int currentCallCount, List<Uri> redirectChain)114             public void assertCalledWith(int currentCallCount, List<Uri> redirectChain)
115                     throws TimeoutException {
116                 waitForCallback(currentCallCount);
117                 assertEquals(mRedirectChain, redirectChain);
118             }
119 
assertCalledWith(int currentCallCount, String uri, @LoadError int loadError)120             public void assertCalledWith(int currentCallCount, String uri, @LoadError int loadError)
121                     throws TimeoutException {
122                 waitForCallback(currentCallCount);
123                 assertEquals(mUri.toString(), uri);
124                 assertEquals(mLoadError, loadError);
125             }
126 
getHttpStatusCode()127             public int getHttpStatusCode() {
128                 return mHttpStatusCode;
129             }
130 
131             @NavigationState
getNavigationState()132             public int getNavigationState() {
133                 return mNavigationState;
134             }
135 
isPageInitiated()136             public boolean isPageInitiated() {
137                 assert sShouldTrackPageInitiated;
138                 return mIsPageInitiatedNavigation;
139             }
140         }
141 
142         public static class UriCallbackHelper extends CallbackHelper {
143             private Uri mUri;
144 
notifyCalled(Uri uri)145             public void notifyCalled(Uri uri) {
146                 mUri = uri;
147                 notifyCalled();
148             }
149 
getUri()150             public Uri getUri() {
151                 return mUri;
152             }
153         }
154 
155         public static class NavigationCallbackValueRecorder {
156             private List<String> mObservedValues =
157                     Collections.synchronizedList(new ArrayList<String>());
158 
recordValue(String parameter)159             public void recordValue(String parameter) {
160                 mObservedValues.add(parameter);
161             }
162 
getObservedValues()163             public List<String> getObservedValues() {
164                 return mObservedValues;
165             }
166 
waitUntilValueObserved(String expectation)167             public void waitUntilValueObserved(String expectation) {
168                 CriteriaHelper.pollInstrumentationThread(
169                         () -> Criteria.checkThat(expectation, Matchers.isIn(mObservedValues)));
170             }
171         }
172 
173         public static class FirstContentfulPaintCallbackHelper extends CallbackHelper {
174             private long mNavigationStartMillis;
175             private long mFirstContentfulPaintMs;
176 
notifyCalled(long navigationStartMillis, long firstContentfulPaintMs)177             public void notifyCalled(long navigationStartMillis, long firstContentfulPaintMs) {
178                 mNavigationStartMillis = navigationStartMillis;
179                 mFirstContentfulPaintMs = firstContentfulPaintMs;
180                 notifyCalled();
181             }
182 
getNavigationStartMillis()183             public long getNavigationStartMillis() {
184                 return mNavigationStartMillis;
185             }
186 
getFirstContentfulPaintMs()187             public long getFirstContentfulPaintMs() {
188                 return mFirstContentfulPaintMs;
189             }
190         }
191 
192         public static class LargestContentfulPaintCallbackHelper extends CallbackHelper {
193             private long mNavigationStartMillis;
194             private long mLargestContentfulPaintMs;
195 
notifyCalled(long navigationStartMillis, long largestContentfulPaintMs)196             public void notifyCalled(long navigationStartMillis, long largestContentfulPaintMs) {
197                 mNavigationStartMillis = navigationStartMillis;
198                 mLargestContentfulPaintMs = largestContentfulPaintMs;
199                 notifyCalled();
200             }
201 
getNavigationStartMillis()202             public long getNavigationStartMillis() {
203                 return mNavigationStartMillis;
204             }
205 
getLargestContentfulPaintMs()206             public long getLargestContentfulPaintMs() {
207                 return mLargestContentfulPaintMs;
208             }
209         }
210 
211         public NavigationCallbackHelper onStartedCallback = new NavigationCallbackHelper();
212         public NavigationCallbackHelper onRedirectedCallback = new NavigationCallbackHelper();
213         public NavigationCallbackHelper onReadyToCommitCallback = new NavigationCallbackHelper();
214         public NavigationCallbackHelper onCompletedCallback = new NavigationCallbackHelper();
215         public NavigationCallbackHelper onFailedCallback = new NavigationCallbackHelper();
216         public NavigationCallbackValueRecorder loadStateChangedCallback =
217                 new NavigationCallbackValueRecorder();
218         public NavigationCallbackValueRecorder loadProgressChangedCallback =
219                 new NavigationCallbackValueRecorder();
220         public CallbackHelper onFirstContentfulPaintCallback = new CallbackHelper();
221         public FirstContentfulPaintCallbackHelper onFirstContentfulPaint2Callback =
222                 new FirstContentfulPaintCallbackHelper();
223         public LargestContentfulPaintCallbackHelper onLargestContentfulPaintCallback =
224                 new LargestContentfulPaintCallbackHelper();
225         public UriCallbackHelper onOldPageNoLongerRenderedCallback = new UriCallbackHelper();
226 
227         @Override
onNavigationStarted(Navigation navigation)228         public void onNavigationStarted(Navigation navigation) {
229             onStartedCallback.notifyCalled(navigation);
230         }
231 
232         @Override
onNavigationRedirected(Navigation navigation)233         public void onNavigationRedirected(Navigation navigation) {
234             onRedirectedCallback.notifyCalled(navigation);
235         }
236 
237         @Override
onReadyToCommitNavigation(Navigation navigation)238         public void onReadyToCommitNavigation(Navigation navigation) {
239             onReadyToCommitCallback.notifyCalled(navigation);
240         }
241 
242         @Override
onNavigationCompleted(Navigation navigation)243         public void onNavigationCompleted(Navigation navigation) {
244             onCompletedCallback.notifyCalled(navigation);
245         }
246 
247         @Override
onNavigationFailed(Navigation navigation)248         public void onNavigationFailed(Navigation navigation) {
249             onFailedCallback.notifyCalled(navigation);
250         }
251 
252         @Override
onFirstContentfulPaint()253         public void onFirstContentfulPaint() {
254             onFirstContentfulPaintCallback.notifyCalled();
255         }
256 
257         @Override
onFirstContentfulPaint( long navigationStartMillis, long firstContentfulPaintMs)258         public void onFirstContentfulPaint(
259                 long navigationStartMillis, long firstContentfulPaintMs) {
260             onFirstContentfulPaint2Callback.notifyCalled(
261                     navigationStartMillis, firstContentfulPaintMs);
262         }
263 
264         @Override
onLargestContentfulPaint( long navigationStartMillis, long largestContentfulPaintMs)265         public void onLargestContentfulPaint(
266                 long navigationStartMillis, long largestContentfulPaintMs) {
267             onLargestContentfulPaintCallback.notifyCalled(
268                     navigationStartMillis, largestContentfulPaintMs);
269         }
270 
271         @Override
onOldPageNoLongerRendered(Uri newNavigationUri)272         public void onOldPageNoLongerRendered(Uri newNavigationUri) {
273             onOldPageNoLongerRenderedCallback.notifyCalled(newNavigationUri);
274         }
275 
276         @Override
onLoadStateChanged(boolean isLoading, boolean toDifferentDocument)277         public void onLoadStateChanged(boolean isLoading, boolean toDifferentDocument) {
278             loadStateChangedCallback.recordValue(
279                     Boolean.toString(isLoading) + " " + Boolean.toString(toDifferentDocument));
280         }
281 
282         @Override
onLoadProgressChanged(double progress)283         public void onLoadProgressChanged(double progress) {
284             loadProgressChangedCallback.recordValue(
285                     progress == 1 ? "load complete" : "load started");
286         }
287     }
288 
289     private final Callback mCallback = new Callback();
290 
291     @Before
setUp()292     public void setUp() throws Throwable {
293         TestThreadUtils.runOnUiThreadBlocking(() -> {
294             sShouldTrackPageInitiated =
295                     WebLayer.getSupportedMajorVersion(
296                             InstrumentationRegistry.getTargetContext().getApplicationContext())
297                     >= 86;
298         });
299     }
300 
301     @Test
302     @SmallTest
testNavigationEvents()303     public void testNavigationEvents() throws Exception {
304         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
305 
306         setNavigationCallback(activity);
307         int curStartedCount = mCallback.onStartedCallback.getCallCount();
308         int curCommittedCount = mCallback.onReadyToCommitCallback.getCallCount();
309         int curCompletedCount = mCallback.onCompletedCallback.getCallCount();
310         int curOnFirstContentfulPaintCount =
311                 mCallback.onFirstContentfulPaintCallback.getCallCount();
312 
313         mActivityTestRule.navigateAndWait(URL2);
314 
315         mCallback.onStartedCallback.assertCalledWith(curStartedCount, URL2);
316         mCallback.onReadyToCommitCallback.assertCalledWith(curCommittedCount, URL2);
317         mCallback.onCompletedCallback.assertCalledWith(curCompletedCount, URL2);
318         mCallback.onFirstContentfulPaintCallback.waitForCallback(curOnFirstContentfulPaintCount);
319         assertEquals(mCallback.onCompletedCallback.getHttpStatusCode(), 200);
320     }
321 
322     @MinWebLayerVersion(85)
323     @Test
324     @SmallTest
testOldPageNoLongerRendered()325     public void testOldPageNoLongerRendered() throws Exception {
326         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
327         setNavigationCallback(activity);
328 
329         int renderedCount = mCallback.onOldPageNoLongerRenderedCallback.getCallCount();
330         mActivityTestRule.navigateAndWait(URL2);
331         mCallback.onOldPageNoLongerRenderedCallback.waitForCallback(renderedCount);
332         assertEquals(Uri.parse(URL2), mCallback.onOldPageNoLongerRenderedCallback.getUri());
333     }
334 
335     @Test
336     @SmallTest
testLoadStateUpdates()337     public void testLoadStateUpdates() throws Exception {
338         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
339         setNavigationCallback(activity);
340         mActivityTestRule.navigateAndWait(URL1);
341 
342         /* Wait until the NavigationCallback is notified of load completion. */
343         mCallback.loadStateChangedCallback.waitUntilValueObserved("false false");
344         mCallback.loadProgressChangedCallback.waitUntilValueObserved("load complete");
345 
346         /* Verify that the NavigationCallback was notified of load progress /before/ load
347          * completion.
348          */
349         int finishStateIndex =
350                 mCallback.loadStateChangedCallback.getObservedValues().indexOf("false false");
351         int finishProgressIndex =
352                 mCallback.loadProgressChangedCallback.getObservedValues().indexOf("load complete");
353         int startStateIndex =
354                 mCallback.loadStateChangedCallback.getObservedValues().lastIndexOf("true true");
355         int startProgressIndex =
356                 mCallback.loadProgressChangedCallback.getObservedValues().lastIndexOf(
357                         "load started");
358 
359         assertNotEquals(startStateIndex, -1);
360         assertNotEquals(startProgressIndex, -1);
361         assertNotEquals(finishStateIndex, -1);
362         assertNotEquals(finishProgressIndex, -1);
363 
364         assertTrue(startStateIndex < finishStateIndex);
365         assertTrue(startProgressIndex < finishProgressIndex);
366     }
367 
368     @Test
369     @SmallTest
370     public void testReplace() throws Exception {
371         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
372         setNavigationCallback(activity);
373 
374         final NavigateParams params =
375                 new NavigateParams.Builder().setShouldReplaceCurrentEntry(true).build();
376         navigateAndWaitForCompletion(URL2,
377                 ()
378                         -> activity.getTab().getNavigationController().navigate(
379                                 Uri.parse(URL2), params));
380         runOnUiThreadBlocking(() -> {
381             NavigationController navigationController = activity.getTab().getNavigationController();
382             assertFalse(navigationController.canGoForward());
383             assertFalse(navigationController.canGoBack());
384             assertEquals(1, navigationController.getNavigationListSize());
385         });
386 
387         // Verify getter works as expected.
388         assertTrue(params.getShouldReplaceCurrentEntry());
389 
390         // Verify that a default NavigateParams does not replace.
391         final NavigateParams params2 = new NavigateParams();
392         navigateAndWaitForCompletion(URL3,
393                 ()
394                         -> activity.getTab().getNavigationController().navigate(
395                                 Uri.parse(URL3), params2));
396         runOnUiThreadBlocking(() -> {
397             NavigationController navigationController = activity.getTab().getNavigationController();
398             assertFalse(navigationController.canGoForward());
399             assertTrue(navigationController.canGoBack());
400             assertEquals(2, navigationController.getNavigationListSize());
401         });
402     }
403 
404     @Test
405     @SmallTest
testGoBackAndForward()406     public void testGoBackAndForward() throws Exception {
407         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
408         setNavigationCallback(activity);
409 
410         mActivityTestRule.navigateAndWait(URL2);
411         mActivityTestRule.navigateAndWait(URL3);
412 
413         NavigationController navigationController =
414                 runOnUiThreadBlocking(() -> activity.getTab().getNavigationController());
415 
416         navigateAndWaitForCompletion(URL2, () -> {
417             assertTrue(navigationController.canGoBack());
418             navigationController.goBack();
419         });
420 
421         navigateAndWaitForCompletion(URL1, () -> {
422             assertTrue(navigationController.canGoBack());
423             navigationController.goBack();
424         });
425 
426         navigateAndWaitForCompletion(URL2, () -> {
427             assertFalse(navigationController.canGoBack());
428             assertTrue(navigationController.canGoForward());
429             navigationController.goForward();
430         });
431 
432         navigateAndWaitForCompletion(URL3, () -> {
433             assertTrue(navigationController.canGoForward());
434             navigationController.goForward();
435         });
436 
437         runOnUiThreadBlocking(() -> { assertFalse(navigationController.canGoForward()); });
438     }
439 
440     @Test
441     @SmallTest
testGoToIndex()442     public void testGoToIndex() throws Exception {
443         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
444         setNavigationCallback(activity);
445 
446         mActivityTestRule.navigateAndWait(URL2);
447         mActivityTestRule.navigateAndWait(URL3);
448         mActivityTestRule.navigateAndWait(URL4);
449 
450         // Navigate back to the 2nd url.
451         assertEquals(URL2, goToIndexAndReturnUrl(activity.getTab(), 1));
452 
453         // Navigate forwards to the 4th url.
454         assertEquals(URL4, goToIndexAndReturnUrl(activity.getTab(), 3));
455     }
456 
457     @Test
458     @SmallTest
testGetNavigationEntryTitle()459     public void testGetNavigationEntryTitle() throws Exception {
460         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(
461                 "data:text/html,<head><title>Page A</title></head>");
462         setNavigationCallback(activity);
463 
464         mActivityTestRule.navigateAndWait("data:text/html,<head><title>Page B</title></head>");
465         mActivityTestRule.navigateAndWait("data:text/html,<head><title>Page C</title></head>");
466 
467         runOnUiThreadBlocking(() -> {
468             NavigationController navigationController = activity.getTab().getNavigationController();
469             assertEquals("Page A", navigationController.getNavigationEntryTitle(0));
470             assertEquals("Page B", navigationController.getNavigationEntryTitle(1));
471             assertEquals("Page C", navigationController.getNavigationEntryTitle(2));
472         });
473     }
474 
475     @Test
476     @SmallTest
testSameDocument()477     public void testSameDocument() throws Exception {
478         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
479         setNavigationCallback(activity);
480 
481         int curCompletedCount = mCallback.onCompletedCallback.getCallCount();
482 
483         mActivityTestRule.executeScriptSync(
484                 "history.pushState(null, '', '#bar');", true /* useSeparateIsolate */);
485 
486         mCallback.onCompletedCallback.assertCalledWith(
487                 curCompletedCount, "data:text,foo#bar", true);
488     }
489 
490     @Test
491     @SmallTest
testReload()492     public void testReload() throws Exception {
493         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
494         setNavigationCallback(activity);
495 
496         navigateAndWaitForCompletion(
497                 URL1, () -> { activity.getTab().getNavigationController().reload(); });
498     }
499 
500     @Test
501     @SmallTest
testStop()502     public void testStop() throws Exception {
503         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
504         setNavigationCallback(activity);
505 
506         int curFailedCount = mCallback.onFailedCallback.getCallCount();
507 
508         runOnUiThreadBlocking(() -> {
509             NavigationController navigationController = activity.getTab().getNavigationController();
510             navigationController.registerNavigationCallback(new NavigationCallback() {
511                 @Override
512                 public void onNavigationStarted(Navigation navigation) {
513                     navigationController.stop();
514                 }
515             });
516             navigationController.navigate(Uri.parse(URL2));
517         });
518 
519         mCallback.onFailedCallback.assertCalledWith(curFailedCount, URL2);
520     }
521 
522     @Test
523     @SmallTest
testRedirect()524     public void testRedirect() throws Exception {
525         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
526         setNavigationCallback(activity);
527 
528         int curRedirectedCount = mCallback.onRedirectedCallback.getCallCount();
529 
530         String finalUrl = mActivityTestRule.getTestServer().getURL("/echo");
531         String url = mActivityTestRule.getTestServer().getURL("/server-redirect?" + finalUrl);
532         navigateAndWaitForCompletion(finalUrl,
533                 () -> { activity.getTab().getNavigationController().navigate(Uri.parse(url)); });
534 
535         mCallback.onRedirectedCallback.assertCalledWith(
536                 curRedirectedCount, Arrays.asList(Uri.parse(url), Uri.parse(finalUrl)));
537     }
538 
539     @Test
540     @SmallTest
testNavigationList()541     public void testNavigationList() throws Exception {
542         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
543         setNavigationCallback(activity);
544 
545         mActivityTestRule.navigateAndWait(URL2);
546         mActivityTestRule.navigateAndWait(URL3);
547 
548         NavigationController navigationController =
549                 runOnUiThreadBlocking(() -> activity.getTab().getNavigationController());
550 
551         runOnUiThreadBlocking(() -> {
552             assertEquals(3, navigationController.getNavigationListSize());
553             assertEquals(2, navigationController.getNavigationListCurrentIndex());
554             assertEquals(URL1, navigationController.getNavigationEntryDisplayUri(0).toString());
555             assertEquals(URL2, navigationController.getNavigationEntryDisplayUri(1).toString());
556             assertEquals(URL3, navigationController.getNavigationEntryDisplayUri(2).toString());
557         });
558 
559         navigateAndWaitForCompletion(URL2, () -> { navigationController.goBack(); });
560 
561         runOnUiThreadBlocking(() -> {
562             assertEquals(3, navigationController.getNavigationListSize());
563             assertEquals(1, navigationController.getNavigationListCurrentIndex());
564         });
565     }
566 
567     @Test
568     @SmallTest
testLoadError()569     public void testLoadError() throws Exception {
570         String url = mActivityTestRule.getTestDataURL("non_existent.html");
571 
572         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
573         setNavigationCallback(activity);
574 
575         int curFailedCount = mCallback.onFailedCallback.getCallCount();
576 
577         // navigateAndWait() expects a success code, so it won't work here.
578         runOnUiThreadBlocking(
579                 () -> { activity.getTab().getNavigationController().navigate(Uri.parse(url)); });
580 
581         mCallback.onFailedCallback.assertCalledWith(
582                 curFailedCount, url, LoadError.HTTP_CLIENT_ERROR);
583         assertEquals(mCallback.onFailedCallback.getHttpStatusCode(), 404);
584         assertEquals(mCallback.onFailedCallback.getNavigationState(), NavigationState.FAILED);
585     }
586 
587     @Test
588     @SmallTest
testRepostConfirmation()589     public void testRepostConfirmation() throws Exception {
590         // Load a page with a form.
591         InstrumentationActivity activity =
592                 mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestDataURL("form.html"));
593         assertNotNull(activity);
594         setNavigationCallback(activity);
595 
596         // Touch the page; this should submit the form.
597         int currentCallCount = mCallback.onCompletedCallback.getCallCount();
598         EventUtils.simulateTouchCenterOfView(activity.getWindow().getDecorView());
599         String targetUrl = mActivityTestRule.getTestDataURL("simple_page.html");
600         mCallback.onCompletedCallback.assertCalledWith(currentCallCount, targetUrl);
601 
602         // Make sure a tab modal shows after we attempt a reload.
603         Boolean isTabModalShowingResult[] = new Boolean[1];
604         CallbackHelper callbackHelper = new CallbackHelper();
605         runOnUiThreadBlocking(() -> {
606             Tab tab = activity.getTab();
607             TabCallback callback = new TabCallback() {
608                 @Override
609                 public void onTabModalStateChanged(boolean isTabModalShowing) {
610                     isTabModalShowingResult[0] = isTabModalShowing;
611                     callbackHelper.notifyCalled();
612                 }
613             };
614             tab.registerTabCallback(callback);
615             tab.getNavigationController().reload();
616         });
617 
618         callbackHelper.waitForFirst();
619         assertTrue(isTabModalShowingResult[0]);
620     }
621 
setNavigationCallback(InstrumentationActivity activity)622     private void setNavigationCallback(InstrumentationActivity activity) {
623         runOnUiThreadBlocking(
624                 ()
625                         -> activity.getTab().getNavigationController().registerNavigationCallback(
626                                 mCallback));
627     }
628 
registerNavigationCallback(NavigationCallback callback)629     private void registerNavigationCallback(NavigationCallback callback) {
630         runOnUiThreadBlocking(()
631                                       -> mActivityTestRule.getActivity()
632                                                  .getTab()
633                                                  .getNavigationController()
634                                                  .registerNavigationCallback(callback));
635     }
636 
unregisterNavigationCallback(NavigationCallback callback)637     private void unregisterNavigationCallback(NavigationCallback callback) {
638         runOnUiThreadBlocking(()
639                                       -> mActivityTestRule.getActivity()
640                                                  .getTab()
641                                                  .getNavigationController()
642                                                  .unregisterNavigationCallback(callback));
643     }
644 
navigateAndWaitForCompletion(String expectedUrl, Runnable navigateRunnable)645     private void navigateAndWaitForCompletion(String expectedUrl, Runnable navigateRunnable)
646             throws Exception {
647         int currentCallCount = mCallback.onCompletedCallback.getCallCount();
648         runOnUiThreadBlocking(navigateRunnable);
649         mCallback.onCompletedCallback.assertCalledWith(currentCallCount, expectedUrl);
650     }
651 
goToIndexAndReturnUrl(Tab tab, int index)652     private String goToIndexAndReturnUrl(Tab tab, int index) throws Exception {
653         NavigationController navigationController =
654                 runOnUiThreadBlocking(() -> tab.getNavigationController());
655 
656         final BoundedCountDownLatch navigationComplete = new BoundedCountDownLatch(1);
657         final AtomicReference<String> navigationUrl = new AtomicReference<String>();
658         NavigationCallback navigationCallback = new NavigationCallback() {
659             @Override
660             public void onNavigationCompleted(Navigation navigation) {
661                 navigationComplete.countDown();
662                 navigationUrl.set(navigation.getUri().toString());
663             }
664         };
665 
666         runOnUiThreadBlocking(() -> {
667             navigationController.registerNavigationCallback(navigationCallback);
668             navigationController.goToIndex(index);
669         });
670 
671         navigationComplete.timedAwait();
672 
673         runOnUiThreadBlocking(
674                 () -> { navigationController.unregisterNavigationCallback(navigationCallback); });
675 
676         return navigationUrl.get();
677     }
678 
679     @Test
680     @SmallTest
testStopFromOnNavigationStarted()681     public void testStopFromOnNavigationStarted() throws Exception {
682         final InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
683         final BoundedCountDownLatch doneLatch = new BoundedCountDownLatch(1);
684         NavigationCallback navigationCallback = new NavigationCallback() {
685             @Override
686             public void onNavigationStarted(Navigation navigation) {
687                 activity.getTab().getNavigationController().stop();
688                 doneLatch.countDown();
689             }
690         };
691         runOnUiThreadBlocking(() -> {
692             NavigationController controller = activity.getTab().getNavigationController();
693             controller.registerNavigationCallback(navigationCallback);
694             controller.navigate(Uri.parse(URL1));
695         });
696         doneLatch.timedAwait();
697     }
698 
699     // NavigationCallback implementation that sets a header in either start or redirect.
700     private static final class HeaderSetter extends NavigationCallback {
701         private final String mName;
702         private final String mValue;
703         private final boolean mInStart;
704         public boolean mGotIllegalArgumentException;
705 
HeaderSetter(String name, String value, boolean inStart)706         HeaderSetter(String name, String value, boolean inStart) {
707             mName = name;
708             mValue = value;
709             mInStart = inStart;
710         }
711 
712         @Override
onNavigationStarted(Navigation navigation)713         public void onNavigationStarted(Navigation navigation) {
714             if (mInStart) applyHeader(navigation);
715         }
716 
717         @Override
onNavigationRedirected(Navigation navigation)718         public void onNavigationRedirected(Navigation navigation) {
719             if (!mInStart) applyHeader(navigation);
720         }
721 
applyHeader(Navigation navigation)722         private void applyHeader(Navigation navigation) {
723             try {
724                 navigation.setRequestHeader(mName, mValue);
725             } catch (IllegalArgumentException e) {
726                 mGotIllegalArgumentException = true;
727             }
728         }
729     }
730 
731     @Test
732     @SmallTest
testSetRequestHeaderInStart()733     public void testSetRequestHeaderInStart() throws Exception {
734         TestWebServer testServer = TestWebServer.start();
735         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
736         String headerName = "header";
737         String headerValue = "value";
738         HeaderSetter setter = new HeaderSetter(headerName, headerValue, true);
739         registerNavigationCallback(setter);
740         String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
741         mActivityTestRule.navigateAndWait(url);
742         assertFalse(setter.mGotIllegalArgumentException);
743         assertEquals(headerValue, testServer.getLastRequest("/ok.html").headerValue(headerName));
744     }
745 
746     @Test
747     @SmallTest
testSetRequestHeaderInRedirect()748     public void testSetRequestHeaderInRedirect() throws Exception {
749         TestWebServer testServer = TestWebServer.start();
750         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
751         String headerName = "header";
752         String headerValue = "value";
753         HeaderSetter setter = new HeaderSetter(headerName, headerValue, false);
754         registerNavigationCallback(setter);
755         // The destination of the redirect.
756         String finalUrl = testServer.setResponse("/ok.html", "<html>ok</html>", null);
757         // The url that redirects to |finalUrl|.
758         String redirectingUrl = testServer.setRedirect("/redirect.html", finalUrl);
759         Tab tab = mActivityTestRule.getActivity().getTab();
760         NavigationWaiter waiter = new NavigationWaiter(finalUrl, tab, false, false);
761         TestThreadUtils.runOnUiThreadBlocking(
762                 () -> { tab.getNavigationController().navigate(Uri.parse(redirectingUrl)); });
763         waiter.waitForNavigation();
764         assertFalse(setter.mGotIllegalArgumentException);
765         assertEquals(headerValue, testServer.getLastRequest("/ok.html").headerValue(headerName));
766     }
767 
768     @Test
769     @SmallTest
testSetRequestHeaderThrowsExceptionInCompleted()770     public void testSetRequestHeaderThrowsExceptionInCompleted() throws Exception {
771         mActivityTestRule.launchShellWithUrl(null);
772         boolean gotCompleted[] = new boolean[1];
773         NavigationCallback navigationCallback = new NavigationCallback() {
774             @Override
775             public void onNavigationCompleted(Navigation navigation) {
776                 gotCompleted[0] = true;
777                 boolean gotException = false;
778                 try {
779                     navigation.setRequestHeader("name", "value");
780                 } catch (IllegalStateException e) {
781                     gotException = true;
782                 }
783                 assertTrue(gotException);
784             }
785         };
786         registerNavigationCallback(navigationCallback);
787         mActivityTestRule.navigateAndWait(URL1);
788         assertTrue(gotCompleted[0]);
789     }
790 
791     @Test
792     @SmallTest
testSetRequestHeaderThrowsExceptionWithInvalidValue()793     public void testSetRequestHeaderThrowsExceptionWithInvalidValue() throws Exception {
794         mActivityTestRule.launchShellWithUrl(null);
795         HeaderSetter setter = new HeaderSetter("name", "\0", true);
796         registerNavigationCallback(setter);
797         mActivityTestRule.navigateAndWait(URL1);
798         assertTrue(setter.mGotIllegalArgumentException);
799     }
800 
801     // NavigationCallback implementation that sets the user-agent string in onNavigationStarted().
802     private static final class UserAgentSetter extends NavigationCallback {
803         private final String mValue;
804         public boolean mGotIllegalStateException;
805 
UserAgentSetter(String value)806         UserAgentSetter(String value) {
807             mValue = value;
808         }
809 
810         @Override
onNavigationStarted(Navigation navigation)811         public void onNavigationStarted(Navigation navigation) {
812             try {
813                 navigation.setUserAgentString(mValue);
814             } catch (IllegalStateException e) {
815                 mGotIllegalStateException = true;
816             }
817         }
818     }
819 
820     @Test
821     @SmallTest
822     @MinWebLayerVersion(84)
testSetUserAgentString()823     public void testSetUserAgentString() throws Exception {
824         TestWebServer testServer = TestWebServer.start();
825         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
826         String customUserAgent = "custom-ua";
827         UserAgentSetter setter = new UserAgentSetter(customUserAgent);
828         registerNavigationCallback(setter);
829         String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
830         mActivityTestRule.navigateAndWait(url);
831         String actualUserAgent = testServer.getLastRequest("/ok.html").headerValue("User-Agent");
832         assertEquals(customUserAgent, actualUserAgent);
833     }
834 
835     @Test
836     @SmallTest
837     @MinWebLayerVersion(88)
testCantUsePerNavigationAndDesktopMode()838     public void testCantUsePerNavigationAndDesktopMode() throws Exception {
839         TestWebServer testServer = TestWebServer.start();
840         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
841         UserAgentSetter setter = new UserAgentSetter("foo");
842         registerNavigationCallback(setter);
843         String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
844         runOnUiThreadBlocking(() -> { activity.getTab().setDesktopUserAgentEnabled(true); });
845         mActivityTestRule.navigateAndWait(url);
846         assertTrue(setter.mGotIllegalStateException);
847     }
848 
849     @Test
850     @SmallTest
851     @MinWebLayerVersion(88)
testDesktopMode()852     public void testDesktopMode() throws Exception {
853         TestWebServer testServer = TestWebServer.start();
854         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
855         String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
856         runOnUiThreadBlocking(() -> { activity.getTab().setDesktopUserAgentEnabled(true); });
857         mActivityTestRule.navigateAndWait(url);
858         String actualUserAgent = testServer.getLastRequest("/ok.html").headerValue("User-Agent");
859         assertFalse(actualUserAgent.contains("Android"));
860     }
861 
862     @Test
863     @SmallTest
864     @MinWebLayerVersion(88)
testDesktopModeSticks()865     public void testDesktopModeSticks() throws Exception {
866         TestWebServer testServer = TestWebServer.start();
867         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
868         String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
869         String url2 = testServer.setResponse("/ok2.html", "<html>ok</html>", null);
870         runOnUiThreadBlocking(() -> { activity.getTab().setDesktopUserAgentEnabled(true); });
871         mActivityTestRule.navigateAndWait(url);
872         mActivityTestRule.navigateAndWait(url2);
873         String actualUserAgent = testServer.getLastRequest("/ok2.html").headerValue("User-Agent");
874         assertFalse(actualUserAgent.contains("Android"));
875     }
876 
877     @Test
878     @SmallTest
879     @MinWebLayerVersion(88)
testDesktopModeGetter()880     public void testDesktopModeGetter() throws Exception {
881         TestWebServer testServer = TestWebServer.start();
882         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
883         setNavigationCallback(activity);
884 
885         UserAgentSetter setter = new UserAgentSetter("foo");
886         registerNavigationCallback(setter);
887         mActivityTestRule.navigateAndWait(URL1);
888         unregisterNavigationCallback(setter);
889         runOnUiThreadBlocking(
890                 () -> { assertFalse(activity.getTab().isDesktopUserAgentEnabled()); });
891 
892         runOnUiThreadBlocking(() -> { activity.getTab().setDesktopUserAgentEnabled(true); });
893         mActivityTestRule.navigateAndWait(URL2);
894         runOnUiThreadBlocking(() -> { assertTrue(activity.getTab().isDesktopUserAgentEnabled()); });
895 
896         navigateAndWaitForCompletion(
897                 URL1, () -> activity.getTab().getNavigationController().goBack());
898         runOnUiThreadBlocking(
899                 () -> { assertFalse(activity.getTab().isDesktopUserAgentEnabled()); });
900     }
901 
902     @Test
903     @SmallTest
904     @MinWebLayerVersion(85)
testSkippedNavigationEntry()905     public void testSkippedNavigationEntry() throws Exception {
906         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
907         setNavigationCallback(activity);
908 
909         int curCompletedCount = mCallback.onCompletedCallback.getCallCount();
910         mActivityTestRule.executeScriptSync(
911                 "history.pushState(null, '', '#foo');", true /* useSeparateIsolate */);
912         mCallback.onCompletedCallback.assertCalledWith(curCompletedCount, URL1 + "#foo", true);
913 
914         curCompletedCount = mCallback.onCompletedCallback.getCallCount();
915         mActivityTestRule.executeScriptSync(
916                 "history.pushState(null, '', '#bar');", true /* useSeparateIsolate */);
917         mCallback.onCompletedCallback.assertCalledWith(curCompletedCount, URL1 + "#bar", true);
918 
919         runOnUiThreadBlocking(() -> {
920             NavigationController navigationController = activity.getTab().getNavigationController();
921             int currentIndex = navigationController.getNavigationListCurrentIndex();
922             // Should skip the two previous same document entries, but not the most recent.
923             assertFalse(navigationController.isNavigationEntrySkippable(currentIndex));
924             assertTrue(navigationController.isNavigationEntrySkippable(currentIndex - 1));
925             assertTrue(navigationController.isNavigationEntrySkippable(currentIndex - 2));
926         });
927     }
928 
929     @Test
930     @SmallTest
931     @MinWebLayerVersion(85)
testIndexOutOfBounds()932     public void testIndexOutOfBounds() throws Exception {
933         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
934         runOnUiThreadBlocking(() -> {
935             NavigationController controller = activity.getTab().getNavigationController();
936             assertIndexOutOfBoundsException(() -> controller.goBack());
937             assertIndexOutOfBoundsException(() -> controller.goForward());
938             assertIndexOutOfBoundsException(() -> controller.goToIndex(10));
939             assertIndexOutOfBoundsException(() -> controller.getNavigationEntryDisplayUri(10));
940             assertIndexOutOfBoundsException(() -> controller.getNavigationEntryTitle(10));
941             assertIndexOutOfBoundsException(() -> controller.isNavigationEntrySkippable(10));
942         });
943     }
944 
assertIndexOutOfBoundsException(Runnable runnable)945     private static void assertIndexOutOfBoundsException(Runnable runnable) {
946         try {
947             runnable.run();
948         } catch (IndexOutOfBoundsException e) {
949             // Expected exception.
950             return;
951         }
952         Assert.fail("Expected IndexOutOfBoundsException.");
953     }
954 
955     @Test
956     @SmallTest
957     @MinWebLayerVersion(86)
testPageInitiated()958     public void testPageInitiated() throws Exception {
959         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
960         setNavigationCallback(activity);
961         String initialUrl = mActivityTestRule.getTestDataURL("simple_page4.html");
962         mActivityTestRule.navigateAndWait(initialUrl);
963         String refreshUrl = mActivityTestRule.getTestDataURL("simple_page.html");
964         mCallback.onCompletedCallback.assertCalledWith(
965                 mCallback.onCompletedCallback.getCallCount(), refreshUrl);
966         assertTrue(mCallback.onCompletedCallback.isPageInitiated());
967     }
968 
969     @Test
970     @SmallTest
971     @MinWebLayerVersion(86)
testPageInitiatedFromClient()972     public void testPageInitiatedFromClient() throws Exception {
973         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
974         setNavigationCallback(activity);
975         mActivityTestRule.navigateAndWait(URL2);
976         assertFalse(mCallback.onStartedCallback.isPageInitiated());
977     }
978 
979     // Verifies the following sequence doesn't crash:
980     // 1. create a new background tab.
981     // 2. show modal dialog.
982     // 3. destroy tab with modal dialog.
983     // 4. switch to background tab created in step 1.
984     // This is a regression test for https://crbug.com/1121388.
985     @Test
986     @SmallTest
987     @MinWebLayerVersion(85)
testDestroyTabWithModalDialog()988     public void testDestroyTabWithModalDialog() throws Exception {
989         // Load a page with a form.
990         InstrumentationActivity activity =
991                 mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestDataURL("form.html"));
992         assertNotNull(activity);
993         setNavigationCallback(activity);
994 
995         // Touch the page; this should submit the form.
996         int currentCallCount = mCallback.onCompletedCallback.getCallCount();
997         EventUtils.simulateTouchCenterOfView(activity.getWindow().getDecorView());
998         String targetUrl = mActivityTestRule.getTestDataURL("simple_page.html");
999         mCallback.onCompletedCallback.assertCalledWith(currentCallCount, targetUrl);
1000 
1001         Tab secondTab = runOnUiThreadBlocking(() -> activity.getTab().getBrowser().createTab());
1002         // Make sure a tab modal shows after we attempt a reload.
1003         Boolean isTabModalShowingResult[] = new Boolean[1];
1004         CallbackHelper callbackHelper = new CallbackHelper();
1005         runOnUiThreadBlocking(() -> {
1006             Tab tab = activity.getTab();
1007             Browser browser = tab.getBrowser();
1008             TabCallback callback = new TabCallback() {
1009                 @Override
1010                 public void onTabModalStateChanged(boolean isTabModalShowing) {
1011                     tab.unregisterTabCallback(this);
1012                     isTabModalShowingResult[0] = isTabModalShowing;
1013                     callbackHelper.notifyCalled();
1014                 }
1015             };
1016             tab.registerTabCallback(callback);
1017 
1018             browser.registerTabListCallback(new TabListCallback() {
1019                 @Override
1020                 public void onTabRemoved(Tab tab) {
1021                     browser.unregisterTabListCallback(this);
1022                     browser.setActiveTab(secondTab);
1023                 }
1024             });
1025             tab.getNavigationController().reload();
1026         });
1027 
1028         callbackHelper.waitForFirst();
1029         runOnUiThreadBlocking(() -> {
1030             Tab tab = activity.getTab();
1031             tab.getBrowser().destroyTab(tab);
1032         });
1033     }
1034 
1035     /**
1036      * This test verifies calling destroyTab() from within onNavigationFailed doesn't crash.
1037      */
1038     @Test
1039     @SmallTest
1040     @MinWebLayerVersion(86)
testDestroyTabInNavigationFailed()1041     public void testDestroyTabInNavigationFailed() throws Throwable {
1042         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
1043         CallbackHelper callbackHelper = new CallbackHelper();
1044         runOnUiThreadBlocking(() -> {
1045             NavigationController navigationController = activity.getTab().getNavigationController();
1046             navigationController.registerNavigationCallback(new NavigationCallback() {
1047                 @Override
1048                 public void onNavigationFailed(Navigation navigation) {
1049                     navigationController.unregisterNavigationCallback(this);
1050                     Tab tab = activity.getTab();
1051                     tab.getBrowser().destroyTab(tab);
1052                     callbackHelper.notifyCalled();
1053                 }
1054             });
1055         });
1056         TestThreadUtils.runOnUiThreadBlocking(() -> {
1057             activity.getTab().getNavigationController().navigate(
1058                     Uri.parse("http://localhost:7/non_existent"));
1059         });
1060         callbackHelper.waitForFirst();
1061     }
1062 
navigateToStream(InstrumentationActivity activity, String mimeType, String cacheControl)1063     private void navigateToStream(InstrumentationActivity activity, String mimeType,
1064             String cacheControl) throws Exception {
1065         int curOnFirstContentfulPaintCount =
1066                 mCallback.onFirstContentfulPaintCallback.getCallCount();
1067         InputStream stream = new ByteArrayInputStream(STREAM_HTML.getBytes(StandardCharsets.UTF_8));
1068         WebResourceResponse response = new WebResourceResponse(mimeType, "UTF-8", stream);
1069         if (cacheControl != null) {
1070             Map<String, String> headers = new HashMap<>();
1071             headers.put("Cache-Control", cacheControl);
1072             response.setResponseHeaders(headers);
1073         }
1074 
1075         final NavigateParams params = new NavigateParams.Builder().setResponse(response).build();
1076         navigateAndWaitForCompletion(STREAM_URL,
1077                 ()
1078                         -> activity.getTab().getNavigationController().navigate(
1079                                 Uri.parse(STREAM_URL), params));
1080         mCallback.onFirstContentfulPaintCallback.waitForCallback(curOnFirstContentfulPaintCount);
1081     }
1082 
assertStreamContent()1083     private void assertStreamContent() throws Exception {
1084         assertEquals(STREAM_INNER_BODY,
1085                 mActivityTestRule.executeScriptAndExtractString("document.body.innerText"));
1086     }
1087 
1088     @Test
1089     @SmallTest
1090     @MinWebLayerVersion(87)
testWebResponse()1091     public void testWebResponse() throws Exception {
1092         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
1093         // The code asserts that when InputStreams are used that the stock URL bar is not visible.
1094         TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
1095         setNavigationCallback(activity);
1096 
1097         navigateToStream(activity, "text/html", null);
1098         assertStreamContent();
1099     }
1100 
1101     @Test
1102     @SmallTest
1103     @MinWebLayerVersion(87)
testWebResponseMimeSniff()1104     public void testWebResponseMimeSniff() throws Exception {
1105         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
1106         TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
1107         setNavigationCallback(activity);
1108 
1109         navigateToStream(activity, "", null);
1110         assertStreamContent();
1111     }
1112 
1113     @Test
1114     @SmallTest
1115     @MinWebLayerVersion(87)
testWebResponseNoCacheControl()1116     public void testWebResponseNoCacheControl() throws Exception {
1117         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
1118         TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
1119         setNavigationCallback(activity);
1120 
1121         navigateToStream(activity, "text/html", null);
1122 
1123         mActivityTestRule.navigateAndWait(URL1);
1124 
1125         int curFailedCount = mCallback.onFailedCallback.getCallCount();
1126         runOnUiThreadBlocking(() -> { activity.getTab().getNavigationController().goBack(); });
1127         mCallback.onFailedCallback.assertCalledWith(
1128                 curFailedCount, STREAM_URL, LoadError.CONNECTIVITY_ERROR);
1129     }
1130 
1131     @Test
1132     @SmallTest
1133     @MinWebLayerVersion(87)
testWebResponseCached()1134     public void testWebResponseCached() throws Exception {
1135         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
1136         TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
1137         setNavigationCallback(activity);
1138 
1139         navigateToStream(activity, "text/html", "private, max-age=60");
1140 
1141         // Now check that the data can be reused from the cache if it had the correct headers.
1142         mActivityTestRule.navigateAndWait(URL1);
1143         int curOnFirstContentfulPaintCount =
1144                 mCallback.onFirstContentfulPaintCallback.getCallCount();
1145         navigateAndWaitForCompletion(
1146                 STREAM_URL, () -> { activity.getTab().getNavigationController().goBack(); });
1147         mCallback.onFirstContentfulPaintCallback.waitForCallback(curOnFirstContentfulPaintCount);
1148         assertStreamContent();
1149     }
1150 
1151     @Test
1152     @SmallTest
1153     @MinWebLayerVersion(87)
testWebResponseCachedWithSniffedMimeType()1154     public void testWebResponseCachedWithSniffedMimeType() throws Exception {
1155         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
1156         TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
1157         setNavigationCallback(activity);
1158 
1159         navigateToStream(activity, "", "private, max-age=60");
1160 
1161         mActivityTestRule.navigateAndWait(URL1);
1162 
1163         int curOnFirstContentfulPaintCount =
1164                 mCallback.onFirstContentfulPaintCallback.getCallCount();
1165         navigateAndWaitForCompletion(
1166                 STREAM_URL, () -> { activity.getTab().getNavigationController().goBack(); });
1167         mCallback.onFirstContentfulPaintCallback.waitForCallback(curOnFirstContentfulPaintCount);
1168         assertStreamContent();
1169     }
1170 
1171     @Test
1172     @SmallTest
1173     @MinWebLayerVersion(87)
testWebResponseNoStore()1174     public void testWebResponseNoStore() throws Exception {
1175         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
1176         TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
1177         setNavigationCallback(activity);
1178 
1179         navigateToStream(activity, "text/html", "no-store");
1180 
1181         mActivityTestRule.navigateAndWait(URL1);
1182 
1183         int curFailedCount = mCallback.onFailedCallback.getCallCount();
1184         runOnUiThreadBlocking(() -> { activity.getTab().getNavigationController().goBack(); });
1185         mCallback.onFailedCallback.assertCalledWith(
1186                 curFailedCount, STREAM_URL, LoadError.CONNECTIVITY_ERROR);
1187     }
1188 
1189     @Test
1190     @SmallTest
1191     @MinWebLayerVersion(87)
testWebResponseExpired()1192     public void testWebResponseExpired() throws Exception {
1193         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(URL1);
1194         TestThreadUtils.runOnUiThreadBlocking(() -> { activity.getBrowser().setTopView(null); });
1195         setNavigationCallback(activity);
1196 
1197         navigateToStream(activity, "text/html", "private, max-age=2");
1198 
1199         Thread.sleep(5000);
1200 
1201         mActivityTestRule.navigateAndWait(URL1);
1202 
1203         int curFailedCount = mCallback.onFailedCallback.getCallCount();
1204         runOnUiThreadBlocking(() -> { activity.getTab().getNavigationController().goBack(); });
1205         mCallback.onFailedCallback.assertCalledWith(
1206                 curFailedCount, STREAM_URL, LoadError.CONNECTIVITY_ERROR);
1207     }
1208 
1209     @MinWebLayerVersion(88)
1210     @Test
1211     @SmallTest
testOnFirstContentfulPaintTiming()1212     public void testOnFirstContentfulPaintTiming() throws Exception {
1213         long activityStartTimeMs = SystemClock.uptimeMillis();
1214 
1215         TestWebServer testServer = TestWebServer.start();
1216         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
1217         setNavigationCallback(activity);
1218         String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
1219 
1220         int count = mCallback.onFirstContentfulPaint2Callback.getCallCount();
1221         mActivityTestRule.navigateAndWait(url);
1222         mCallback.onFirstContentfulPaint2Callback.waitForCallback(count);
1223 
1224         long navigationStart = mCallback.onFirstContentfulPaint2Callback.getNavigationStartMillis();
1225         long current = SystemClock.uptimeMillis();
1226         Assert.assertTrue(navigationStart <= current);
1227         Assert.assertTrue(navigationStart >= activityStartTimeMs);
1228 
1229         long firstContentfulPaint =
1230                 mCallback.onFirstContentfulPaint2Callback.getFirstContentfulPaintMs();
1231         Assert.assertTrue(firstContentfulPaint <= (current - navigationStart));
1232     }
1233 
1234     @MinWebLayerVersion(88)
1235     @Test
1236     @SmallTest
testOnLargestContentfulPaintTiming()1237     public void testOnLargestContentfulPaintTiming() throws Exception {
1238         long activityStartTimeMs = SystemClock.uptimeMillis();
1239 
1240         TestWebServer testServer = TestWebServer.start();
1241         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
1242         setNavigationCallback(activity);
1243         String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
1244 
1245         int count = mCallback.onLargestContentfulPaintCallback.getCallCount();
1246         mActivityTestRule.navigateAndWait(url);
1247 
1248         // Navigate to a new page, as metrics like LCP are only reported at the end of the page load
1249         // lifetime.
1250         mActivityTestRule.navigateAndWait("about:blank");
1251         mCallback.onLargestContentfulPaintCallback.waitForCallback(count);
1252 
1253         long navigationStart =
1254                 mCallback.onLargestContentfulPaintCallback.getNavigationStartMillis();
1255         long current = SystemClock.uptimeMillis();
1256         Assert.assertTrue(navigationStart <= current);
1257         Assert.assertTrue(navigationStart >= activityStartTimeMs);
1258 
1259         long largestContentfulPaint =
1260                 mCallback.onLargestContentfulPaintCallback.getLargestContentfulPaintMs();
1261         Assert.assertTrue(largestContentfulPaint <= (current - navigationStart));
1262     }
1263 }
1264