1 // Copyright 2017 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.android_webview.test;
6 
7 import static org.junit.Assert.assertNotEquals;
8 
9 import android.content.Context;
10 import android.content.ContextWrapper;
11 import android.content.SharedPreferences;
12 import android.graphics.Bitmap;
13 import android.graphics.Color;
14 import android.net.Uri;
15 import android.support.test.InstrumentationRegistry;
16 import android.view.ViewGroup;
17 
18 import androidx.test.filters.SmallTest;
19 
20 import org.hamcrest.Matchers;
21 import org.junit.After;
22 import org.junit.Assert;
23 import org.junit.Before;
24 import org.junit.Rule;
25 import org.junit.Test;
26 import org.junit.runner.RunWith;
27 
28 import org.chromium.android_webview.AwBrowserContext;
29 import org.chromium.android_webview.AwContents;
30 import org.chromium.android_webview.AwContents.DependencyFactory;
31 import org.chromium.android_webview.AwContents.InternalAccessDelegate;
32 import org.chromium.android_webview.AwContents.NativeDrawFunctorFactory;
33 import org.chromium.android_webview.AwContentsClient;
34 import org.chromium.android_webview.AwContentsStatics;
35 import org.chromium.android_webview.AwSettings;
36 import org.chromium.android_webview.SafeBrowsingAction;
37 import org.chromium.android_webview.WebviewErrorCode;
38 import org.chromium.android_webview.common.AwSwitches;
39 import org.chromium.android_webview.safe_browsing.AwSafeBrowsingConfigHelper;
40 import org.chromium.android_webview.safe_browsing.AwSafeBrowsingConversionHelper;
41 import org.chromium.android_webview.safe_browsing.AwSafeBrowsingResponse;
42 import org.chromium.android_webview.test.TestAwContentsClient.OnReceivedError2Helper;
43 import org.chromium.android_webview.test.util.GraphicsTestUtils;
44 import org.chromium.base.BuildInfo;
45 import org.chromium.base.Callback;
46 import org.chromium.base.ThreadUtils;
47 import org.chromium.base.task.PostTask;
48 import org.chromium.base.test.util.CallbackHelper;
49 import org.chromium.base.test.util.CommandLineFlags;
50 import org.chromium.base.test.util.Criteria;
51 import org.chromium.base.test.util.CriteriaHelper;
52 import org.chromium.base.test.util.CriteriaNotSatisfiedException;
53 import org.chromium.base.test.util.DisabledTest;
54 import org.chromium.base.test.util.Feature;
55 import org.chromium.base.test.util.InMemorySharedPreferences;
56 import org.chromium.components.safe_browsing.SafeBrowsingApiBridge;
57 import org.chromium.components.safe_browsing.SafeBrowsingApiHandler;
58 import org.chromium.content_public.browser.UiThreadTaskTraits;
59 import org.chromium.content_public.browser.test.util.TestThreadUtils;
60 import org.chromium.content_public.common.ContentUrlConstants;
61 import org.chromium.net.test.EmbeddedTestServer;
62 import org.chromium.url.GURL;
63 
64 import java.util.ArrayList;
65 import java.util.Arrays;
66 
67 /**
68  * Test suite for SafeBrowsing.
69  *
70  * Ensures that interstitials can be successfully created for malicous pages.
71  */
72 @RunWith(AwJUnit4ClassRunner.class)
73 public class SafeBrowsingTest {
74     @Rule
75     public AwActivityTestRule mActivityTestRule = new AwActivityTestRule() {
76 
77         /**
78          * Creates a special BrowserContext that has a safebrowsing api handler which always says
79          * sites are malicious
80          */
81         @Override
82         public AwBrowserContext createAwBrowserContextOnUiThread(InMemorySharedPreferences prefs) {
83             return new MockAwBrowserContext(prefs);
84         }
85     };
86 
87     private SafeBrowsingContentsClient mContentsClient;
88     private AwTestContainerView mContainerView;
89     private MockAwContents mAwContents;
90 
91     private EmbeddedTestServer mTestServer;
92 
93     // Used to check which thread a callback is invoked on.
94     private volatile boolean mOnUiThread;
95 
96     // Used to verify the getSafeBrowsingPrivacyPolicyUrl() API.
97     private volatile Uri mPrivacyPolicyUrl;
98 
99     // These colors correspond to the body.background attribute in GREEN_HTML_PATH, SAFE_HTML_PATH,
100     // MALWARE_HTML_PATH, IFRAME_HTML_PATH, etc. They should only be changed if those values are
101     // changed as well
102     private static final int GREEN_PAGE_BACKGROUND_COLOR = Color.rgb(0, 255, 0);
103     private static final int SAFE_PAGE_BACKGROUND_COLOR = Color.rgb(0, 0, 255);
104     private static final int PHISHING_PAGE_BACKGROUND_COLOR = Color.rgb(0, 0, 255);
105     private static final int MALWARE_PAGE_BACKGROUND_COLOR = Color.rgb(0, 0, 255);
106     private static final int UNWANTED_SOFTWARE_PAGE_BACKGROUND_COLOR = Color.rgb(0, 0, 255);
107     private static final int BILLING_PAGE_BACKGROUND_COLOR = Color.rgb(0, 0, 255);
108     private static final int IFRAME_EMBEDDER_BACKGROUND_COLOR = Color.rgb(10, 10, 10);
109 
110     private static final String RESOURCE_PATH = "/android_webview/test/data";
111 
112     // A blank green page
113     private static final String GREEN_HTML_PATH = RESOURCE_PATH + "/green.html";
114 
115     // Blank blue pages
116     private static final String SAFE_HTML_PATH = RESOURCE_PATH + "/safe.html";
117     private static final String PHISHING_HTML_PATH = RESOURCE_PATH + "/phishing.html";
118     private static final String MALWARE_HTML_PATH = RESOURCE_PATH + "/malware.html";
119     private static final String MALWARE_WITH_IMAGE_HTML_PATH =
120             RESOURCE_PATH + "/malware_with_image.html";
121     private static final String UNWANTED_SOFTWARE_HTML_PATH =
122             RESOURCE_PATH + "/unwanted_software.html";
123     private static final String BILLING_HTML_PATH = RESOURCE_PATH + "/billing.html";
124 
125     // A gray page with an iframe to MALWARE_HTML_PATH
126     private static final String IFRAME_HTML_PATH = RESOURCE_PATH + "/iframe.html";
127 
128     // These URLs will be CTS-tested and should not be changed.
129     private static final String WEB_UI_MALWARE_URL = "chrome://safe-browsing/match?type=malware";
130     private static final String WEB_UI_PHISHING_URL = "chrome://safe-browsing/match?type=phishing";
131     private static final String WEB_UI_HOST = "safe-browsing";
132 
133     /**
134      * A fake SafeBrowsingApiHandler which treats URLs ending in MALWARE_HTML_PATH as malicious URLs
135      * that should be blocked.
136      */
137     public static class MockSafeBrowsingApiHandler implements SafeBrowsingApiHandler {
138         private Observer mObserver;
139         private static final String SAFE_METADATA = "{}";
140 
141         // These codes are defined in "safebrowsing.proto".
142         private static final int PHISHING_CODE = 5;
143         private static final int MALWARE_CODE = 4;
144         private static final int UNWANTED_SOFTWARE_CODE = 3;
145         private static final int BILLING_CODE = 15;
146 
147         // Mock time it takes for a lookup request to complete.
148         private static final long CHECK_DELTA_US = 10;
149 
150         @Override
init(Observer result)151         public boolean init(Observer result) {
152             mObserver = result;
153             return true;
154         }
155 
buildMetadataFromCode(int code)156         private String buildMetadataFromCode(int code) {
157             return "{\"matches\":[{\"threat_type\":\"" + code + "\"}]}";
158         }
159 
160         @Override
startUriLookup(final long callbackId, String uri, int[] threatsOfInterest)161         public void startUriLookup(final long callbackId, String uri, int[] threatsOfInterest) {
162             final String metadata;
163             Arrays.sort(threatsOfInterest);
164 
165             if (uri.endsWith(PHISHING_HTML_PATH)
166                     && Arrays.binarySearch(threatsOfInterest, PHISHING_CODE) >= 0) {
167                 metadata = buildMetadataFromCode(PHISHING_CODE);
168             } else if (uri.endsWith(MALWARE_HTML_PATH)
169                     && Arrays.binarySearch(threatsOfInterest, MALWARE_CODE) >= 0) {
170                 metadata = buildMetadataFromCode(MALWARE_CODE);
171             } else if (uri.endsWith(UNWANTED_SOFTWARE_HTML_PATH)
172                     && Arrays.binarySearch(threatsOfInterest, UNWANTED_SOFTWARE_CODE) >= 0) {
173                 metadata = buildMetadataFromCode(UNWANTED_SOFTWARE_CODE);
174             } else if (uri.endsWith(BILLING_HTML_PATH)
175                     && Arrays.binarySearch(threatsOfInterest, BILLING_CODE) >= 0) {
176                 metadata = buildMetadataFromCode(BILLING_CODE);
177             } else {
178                 metadata = SAFE_METADATA;
179             }
180 
181             // clang-format off
182             PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
183                     (Runnable) () -> mObserver.onUrlCheckDone(
184                         callbackId, SafeBrowsingResult.SUCCESS, metadata, CHECK_DELTA_US));
185             // clang-format on
186         }
187 
188         @Override
startAllowlistLookup(final String uri, int threatType)189         public boolean startAllowlistLookup(final String uri, int threatType) {
190             return false;
191         }
192     }
193 
194     /**
195      * A fake AwBrowserContext which loads the MockSafeBrowsingApiHandler instead of the real one.
196      */
197     private static class MockAwBrowserContext extends AwBrowserContext {
MockAwBrowserContext(SharedPreferences sharedPreferences)198         public MockAwBrowserContext(SharedPreferences sharedPreferences) {
199             super(sharedPreferences, 0, true);
200             SafeBrowsingApiBridge.setSafeBrowsingHandlerType(MockSafeBrowsingApiHandler.class);
201         }
202     }
203 
204     private static class MockAwContents extends TestAwContents {
205         private boolean mCanShowInterstitial;
206         private boolean mCanShowBigInterstitial;
207 
MockAwContents(AwBrowserContext browserContext, ViewGroup containerView, Context context, InternalAccessDelegate internalAccessAdapter, NativeDrawFunctorFactory nativeDrawFunctorFactory, AwContentsClient contentsClient, AwSettings settings, DependencyFactory dependencyFactory)208         public MockAwContents(AwBrowserContext browserContext, ViewGroup containerView,
209                 Context context, InternalAccessDelegate internalAccessAdapter,
210                 NativeDrawFunctorFactory nativeDrawFunctorFactory, AwContentsClient contentsClient,
211                 AwSettings settings, DependencyFactory dependencyFactory) {
212             super(browserContext, containerView, context, internalAccessAdapter,
213                     nativeDrawFunctorFactory, contentsClient, settings, dependencyFactory);
214             mCanShowInterstitial = true;
215             mCanShowBigInterstitial = true;
216         }
217 
setCanShowInterstitial(boolean able)218         public void setCanShowInterstitial(boolean able) {
219             mCanShowInterstitial = able;
220         }
221 
setCanShowBigInterstitial(boolean able)222         public void setCanShowBigInterstitial(boolean able) {
223             mCanShowBigInterstitial = able;
224         }
225 
226         @Override
canShowInterstitial()227         protected boolean canShowInterstitial() {
228             return mCanShowInterstitial;
229         }
230 
231         @Override
canShowBigInterstitial()232         protected boolean canShowBigInterstitial() {
233             return mCanShowBigInterstitial;
234         }
235     }
236 
237     /**
238      * An AwContentsClient with customizable behavior for onSafeBrowsingHit().
239      */
240     private static class SafeBrowsingContentsClient extends TestAwContentsClient {
241         private AwWebResourceRequest mLastRequest;
242         private int mLastThreatType;
243         private int mAction = SafeBrowsingAction.SHOW_INTERSTITIAL;
244         private int mOnSafeBrowsingHitCount;
245         private boolean mReporting = true;
246 
247         @Override
onSafeBrowsingHit(AwWebResourceRequest request, int threatType, Callback<AwSafeBrowsingResponse> callback)248         public void onSafeBrowsingHit(AwWebResourceRequest request, int threatType,
249                 Callback<AwSafeBrowsingResponse> callback) {
250             mLastRequest = request;
251             mLastThreatType = threatType;
252             mOnSafeBrowsingHitCount++;
253             callback.onResult(new AwSafeBrowsingResponse(mAction, mReporting));
254         }
255 
getLastRequest()256         public AwWebResourceRequest getLastRequest() {
257             return mLastRequest;
258         }
259 
getLastThreatType()260         public int getLastThreatType() {
261             return mLastThreatType;
262         }
263 
getOnSafeBrowsingHitCount()264         public int getOnSafeBrowsingHitCount() {
265             return mOnSafeBrowsingHitCount;
266         }
267 
setSafeBrowsingAction(int action)268         public void setSafeBrowsingAction(int action) {
269             mAction = action;
270         }
271 
setReporting(boolean value)272         public void setReporting(boolean value) {
273             mReporting = value;
274         }
275     }
276 
277     private static class SafeBrowsingDependencyFactory
278             extends AwActivityTestRule.TestDependencyFactory {
279         @Override
createAwContents(AwBrowserContext browserContext, ViewGroup containerView, Context context, InternalAccessDelegate internalAccessAdapter, NativeDrawFunctorFactory nativeDrawFunctorFactory, AwContentsClient contentsClient, AwSettings settings, DependencyFactory dependencyFactory)280         public AwContents createAwContents(AwBrowserContext browserContext, ViewGroup containerView,
281                 Context context, InternalAccessDelegate internalAccessAdapter,
282                 NativeDrawFunctorFactory nativeDrawFunctorFactory, AwContentsClient contentsClient,
283                 AwSettings settings, DependencyFactory dependencyFactory) {
284             return new MockAwContents(browserContext, containerView, context, internalAccessAdapter,
285                     nativeDrawFunctorFactory, contentsClient, settings, dependencyFactory);
286         }
287     }
288 
289     private static class JavaScriptHelper extends CallbackHelper {
290         private String mValue;
291 
setValue(String s)292         public void setValue(String s) {
293             mValue = s;
294         }
295 
getValue()296         public String getValue() {
297             return mValue;
298         }
299     }
300 
301     private static class AllowlistHelper extends CallbackHelper implements Callback<Boolean> {
302         public boolean success;
303 
304         @Override
onResult(Boolean success)305         public void onResult(Boolean success) {
306             this.success = success;
307             notifyCalled();
308         }
309     }
310 
311     @Before
setUp()312     public void setUp() {
313         mContentsClient = new SafeBrowsingContentsClient();
314         mContainerView = mActivityTestRule.createAwTestContainerViewOnMainSync(
315                 mContentsClient, false, new SafeBrowsingDependencyFactory());
316         mAwContents = (MockAwContents) mContainerView.getAwContents();
317 
318         mTestServer = EmbeddedTestServer.createAndStartServer(
319                 InstrumentationRegistry.getInstrumentation().getContext());
320 
321         // Need to configure user opt-in, otherwise WebView won't perform Safe Browsing checks.
322         AwSafeBrowsingConfigHelper.setSafeBrowsingUserOptIn(true);
323     }
324 
325     @After
tearDown()326     public void tearDown() {
327         mTestServer.stopAndDestroyServer();
328     }
329 
getPageColor()330     private int getPageColor() {
331         Bitmap bitmap = GraphicsTestUtils.drawAwContentsOnUiThread(
332                 mAwContents, mContainerView.getWidth(), mContainerView.getHeight());
333         return bitmap.getPixel(0, 0);
334     }
335 
loadGreenPage()336     private void loadGreenPage() throws Exception {
337         mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
338                 mTestServer.getURL(GREEN_HTML_PATH));
339 
340         // Make sure we actually wait for the page to be visible
341         mActivityTestRule.waitForVisualStateCallback(mAwContents);
342     }
343 
evaluateJavaScriptOnInterstitialOnUiThread( final String script, final Callback<String> callback)344     private void evaluateJavaScriptOnInterstitialOnUiThread(
345             final String script, final Callback<String> callback) {
346         PostTask.runOrPostTask(
347                 UiThreadTaskTraits.DEFAULT, () -> mAwContents.evaluateJavaScript(script, callback));
348     }
349 
evaluateJavaScriptOnInterstitialOnUiThreadSync(final String script)350     private String evaluateJavaScriptOnInterstitialOnUiThreadSync(final String script)
351             throws Exception {
352         final JavaScriptHelper helper = new JavaScriptHelper();
353         final Callback<String> callback = value -> {
354             helper.setValue(value);
355             helper.notifyCalled();
356         };
357         final int count = helper.getCallCount();
358         evaluateJavaScriptOnInterstitialOnUiThread(script, callback);
359         helper.waitForCallback(count);
360         return helper.getValue();
361     }
362 
waitForInterstitialDomToLoad()363     private void waitForInterstitialDomToLoad() {
364         final String script = "document.readyState;";
365         final String expected = "\"complete\"";
366 
367         CriteriaHelper.pollInstrumentationThread(() -> {
368             try {
369                 Criteria.checkThat(evaluateJavaScriptOnInterstitialOnUiThreadSync(script),
370                         Matchers.is(expected));
371             } catch (Exception e) {
372                 throw new RuntimeException(e);
373             }
374         });
375     }
376 
clickBackToSafety()377     private void clickBackToSafety() {
378         clickLinkById("primary-button");
379     }
380 
clickVisitUnsafePageQuietInterstitial()381     private void clickVisitUnsafePageQuietInterstitial() {
382         clickLinkById("details-link");
383         clickLinkById("proceed-link");
384     }
385 
clickVisitUnsafePage()386     private void clickVisitUnsafePage() {
387         clickLinkById("details-button");
388         clickLinkById("proceed-link");
389     }
390 
clickLinkById(String id)391     private void clickLinkById(String id) {
392         final String script = "document.getElementById('" + id + "').click();";
393         evaluateJavaScriptOnInterstitialOnUiThread(script, null);
394     }
395 
loadPathAndWaitForInterstitial(final String path)396     private void loadPathAndWaitForInterstitial(final String path) throws Exception {
397         final String responseUrl = mTestServer.getURL(path);
398         mActivityTestRule.loadUrlAsync(mAwContents, responseUrl);
399         // Subresource triggered interstitials will trigger after the page containing the
400         // subresource has loaded (and displayed), so we first wait for the interstitial to be
401         // triggered, then for a visual state callback to allow the interstitial to render.
402         CriteriaHelper.pollUiThread(() -> mAwContents.isDisplayingInterstitialForTesting());
403         // Wait for the interstitial to actually render.
404         mActivityTestRule.waitForVisualStateCallback(mAwContents);
405     }
406 
assertTargetPageHasLoaded(int pageColor)407     private void assertTargetPageHasLoaded(int pageColor) throws Exception {
408         mActivityTestRule.waitForVisualStateCallback(mAwContents);
409         Assert.assertEquals("Target page should be visible", colorToString(pageColor),
410                 colorToString(GraphicsTestUtils.getPixelColorAtCenterOfView(
411                         mAwContents, mContainerView)));
412     }
413 
assertGreenPageShowing()414     private void assertGreenPageShowing() {
415         Assert.assertEquals("Original page should be showing",
416                 colorToString(GREEN_PAGE_BACKGROUND_COLOR),
417                 colorToString(GraphicsTestUtils.getPixelColorAtCenterOfView(
418                         mAwContents, mContainerView)));
419     }
420 
assertGreenPageNotShowing()421     private void assertGreenPageNotShowing() {
422         assertNotEquals("Original page should not be showing",
423                 colorToString(GREEN_PAGE_BACKGROUND_COLOR),
424                 colorToString(GraphicsTestUtils.getPixelColorAtCenterOfView(
425                         mAwContents, mContainerView)));
426     }
427 
assertTargetPageNotShowing(int pageColor)428     private void assertTargetPageNotShowing(int pageColor) {
429         assertNotEquals("Target page should not be showing", colorToString(pageColor),
430                 colorToString(GraphicsTestUtils.getPixelColorAtCenterOfView(
431                         mAwContents, mContainerView)));
432     }
433 
434     /**
435      * Converts a color from the confusing integer representation to a more readable string
436      * respresentation. There is a 1:1 mapping between integer and string representations, so it's
437      * valid to compare strings directly. The string representation is better for assert output.
438      *
439      * @param color integer representation of the color
440      * @return a String representation of the color in RGBA format
441      */
colorToString(int color)442     private String colorToString(int color) {
443         return "(" + Color.red(color) + "," + Color.green(color) + "," + Color.blue(color) + ","
444                 + Color.alpha(color) + ")";
445     }
446 
447     @Test
448     @SmallTest
449     @Feature({"AndroidWebView"})
testSafeBrowsingGetterAndSetter()450     public void testSafeBrowsingGetterAndSetter() throws Throwable {
451         Assert.assertTrue("Getter API should follow manifest tag by default",
452                 mActivityTestRule.getAwSettingsOnUiThread(mAwContents).getSafeBrowsingEnabled());
453         mActivityTestRule.getAwSettingsOnUiThread(mAwContents).setSafeBrowsingEnabled(false);
454         Assert.assertFalse("setSafeBrowsingEnabled(false) should change the getter",
455                 mActivityTestRule.getAwSettingsOnUiThread(mAwContents).getSafeBrowsingEnabled());
456         mActivityTestRule.getAwSettingsOnUiThread(mAwContents).setSafeBrowsingEnabled(true);
457         Assert.assertTrue("setSafeBrowsingEnabled(true) should change the getter",
458                 mActivityTestRule.getAwSettingsOnUiThread(mAwContents).getSafeBrowsingEnabled());
459         AwSafeBrowsingConfigHelper.setSafeBrowsingUserOptIn(false);
460         Assert.assertTrue("Getter API should ignore user opt-out",
461                 mActivityTestRule.getAwSettingsOnUiThread(mAwContents).getSafeBrowsingEnabled());
462     }
463 
464     @Test
465     @SmallTest
466     @Feature({"AndroidWebView"})
testSafeBrowsingDoesNotBlockSafePages()467     public void testSafeBrowsingDoesNotBlockSafePages() throws Throwable {
468         loadGreenPage();
469         final String responseUrl = mTestServer.getURL(SAFE_HTML_PATH);
470         mActivityTestRule.loadUrlSync(
471                 mAwContents, mContentsClient.getOnPageFinishedHelper(), responseUrl);
472         assertTargetPageHasLoaded(SAFE_PAGE_BACKGROUND_COLOR);
473     }
474 
475     @Test
476     @SmallTest
477     @Feature({"AndroidWebView"})
testSafeBrowsingBlocksUnwantedSoftwarePages()478     public void testSafeBrowsingBlocksUnwantedSoftwarePages() throws Throwable {
479         loadGreenPage();
480         loadPathAndWaitForInterstitial(UNWANTED_SOFTWARE_HTML_PATH);
481         assertGreenPageNotShowing();
482         assertTargetPageNotShowing(UNWANTED_SOFTWARE_PAGE_BACKGROUND_COLOR);
483         // Assume that we are rendering the interstitial, since we see neither the previous page nor
484         // the target page
485     }
486 
487     @Test
488     @SmallTest
489     @Feature({"AndroidWebView"})
testSafeBrowsingBlocksBillingPages()490     public void testSafeBrowsingBlocksBillingPages() throws Throwable {
491         loadGreenPage();
492         loadPathAndWaitForInterstitial(BILLING_HTML_PATH);
493         assertGreenPageNotShowing();
494         assertTargetPageNotShowing(BILLING_PAGE_BACKGROUND_COLOR);
495         // Assume that we are rendering the interstitial, since we see neither the previous page nor
496         // the target page
497     }
498 
499     @Test
500     @SmallTest
501     @Feature({"AndroidWebView"})
testSafeBrowsingOnSafeBrowsingHitBillingCode()502     public void testSafeBrowsingOnSafeBrowsingHitBillingCode() throws Throwable {
503         loadGreenPage();
504         loadPathAndWaitForInterstitial(BILLING_HTML_PATH);
505 
506         // Check onSafeBrowsingHit arguments
507         final String responseUrl = mTestServer.getURL(BILLING_HTML_PATH);
508         Assert.assertEquals(responseUrl, mContentsClient.getLastRequest().url);
509         // The expectedCode intentionally depends on targetSdk (and is disconnected from SDK_INT).
510         // This is for backwards compatibility with apps with a lower targetSdk.
511         int expectedCode = BuildInfo.targetsAtLeastQ()
512                 ? AwSafeBrowsingConversionHelper.SAFE_BROWSING_THREAT_BILLING
513                 : AwSafeBrowsingConversionHelper.SAFE_BROWSING_THREAT_UNKNOWN;
514         Assert.assertEquals(expectedCode, mContentsClient.getLastThreatType());
515     }
516 
517     @Test
518     @SmallTest
519     @Feature({"AndroidWebView"})
testSafeBrowsingBlocksPhishingPages()520     public void testSafeBrowsingBlocksPhishingPages() throws Throwable {
521         loadGreenPage();
522         loadPathAndWaitForInterstitial(PHISHING_HTML_PATH);
523         assertGreenPageNotShowing();
524         assertTargetPageNotShowing(PHISHING_PAGE_BACKGROUND_COLOR);
525         // Assume that we are rendering the interstitial, since we see neither the previous page nor
526         // the target page
527     }
528 
529     @Test
530     @SmallTest
531     @Feature({"AndroidWebView"})
testSafeBrowsingAllowlistedUnsafePagesDontShowInterstitial()532     public void testSafeBrowsingAllowlistedUnsafePagesDontShowInterstitial() throws Throwable {
533         loadGreenPage();
534         final String responseUrl = mTestServer.getURL(MALWARE_HTML_PATH);
535         verifyAllowlistRule(Uri.parse(responseUrl).getHost(), true);
536         mActivityTestRule.loadUrlSync(
537                 mAwContents, mContentsClient.getOnPageFinishedHelper(), responseUrl);
538         assertTargetPageHasLoaded(MALWARE_PAGE_BACKGROUND_COLOR);
539     }
540 
541     @Test
542     @SmallTest
543     @Feature({"AndroidWebView"})
testSafeBrowsingAllowlistHardcodedWebUiPages()544     public void testSafeBrowsingAllowlistHardcodedWebUiPages() throws Throwable {
545         loadGreenPage();
546         verifyAllowlistRule(WEB_UI_HOST, true);
547         mActivityTestRule.loadUrlSync(
548                 mAwContents, mContentsClient.getOnPageFinishedHelper(), WEB_UI_MALWARE_URL);
549         mActivityTestRule.loadUrlSync(
550                 mAwContents, mContentsClient.getOnPageFinishedHelper(), WEB_UI_PHISHING_URL);
551 
552         // Assume the pages are allowed, since we successfully loaded them.
553     }
554 
555     @Test
556     @SmallTest
557     @Feature({"AndroidWebView"})
testSafeBrowsingAllowlistHardcodedWebUiPageBackToSafety()558     public void testSafeBrowsingAllowlistHardcodedWebUiPageBackToSafety() throws Throwable {
559         mContentsClient.setSafeBrowsingAction(SafeBrowsingAction.BACK_TO_SAFETY);
560 
561         loadGreenPage();
562         OnReceivedError2Helper errorHelper = mContentsClient.getOnReceivedError2Helper();
563         int errorCount = errorHelper.getCallCount();
564         mActivityTestRule.loadUrlSync(
565                 mAwContents, mContentsClient.getOnPageFinishedHelper(), WEB_UI_MALWARE_URL);
566         errorHelper.waitForCallback(errorCount);
567         Assert.assertEquals(
568                 WebviewErrorCode.ERROR_UNSAFE_RESOURCE, errorHelper.getError().errorCode);
569         Assert.assertEquals("Network error is for the malicious page", WEB_UI_MALWARE_URL,
570                 errorHelper.getRequest().url);
571 
572         assertGreenPageShowing();
573 
574         // Check onSafeBrowsingHit arguments
575         Assert.assertEquals(WEB_UI_MALWARE_URL, mContentsClient.getLastRequest().url);
576         Assert.assertEquals(AwSafeBrowsingConversionHelper.SAFE_BROWSING_THREAT_MALWARE,
577                 mContentsClient.getLastThreatType());
578     }
579 
580     @Test
581     @SmallTest
582     @Feature({"AndroidWebView"})
testCallbackCalledOnSafeBrowsingBadAllowlistRule()583     public void testCallbackCalledOnSafeBrowsingBadAllowlistRule() throws Throwable {
584         verifyAllowlistRule("http://www.google.com", false);
585     }
586 
587     @Test
588     @SmallTest
589     @Feature({"AndroidWebView"})
testCallbackCalledOnSafeBrowsingGoodAllowlistRule()590     public void testCallbackCalledOnSafeBrowsingGoodAllowlistRule() throws Throwable {
591         verifyAllowlistRule("www.google.com", true);
592     }
593 
verifyAllowlistRule(final String rule, boolean expected)594     private void verifyAllowlistRule(final String rule, boolean expected) throws Throwable {
595         final AllowlistHelper helper = new AllowlistHelper();
596         final int count = helper.getCallCount();
597         TestThreadUtils.runOnUiThreadBlocking(() -> {
598             ArrayList<String> s = new ArrayList<String>();
599             s.add(rule);
600             AwContentsStatics.setSafeBrowsingAllowlist(s, helper);
601         });
602         helper.waitForCallback(count);
603         Assert.assertEquals(expected, helper.success);
604     }
605 
606     @Test
607     @SmallTest
608     @Feature({"AndroidWebView"})
testSafeBrowsingShowsInterstitialForMainFrame()609     public void testSafeBrowsingShowsInterstitialForMainFrame() throws Throwable {
610         loadGreenPage();
611         loadPathAndWaitForInterstitial(MALWARE_HTML_PATH);
612         assertGreenPageNotShowing();
613         assertTargetPageNotShowing(MALWARE_PAGE_BACKGROUND_COLOR);
614         // Assume that we are rendering the interstitial, since we see neither the previous page
615         // nor the target page
616     }
617 
618     @Test
619     @DisabledTest(message = "Wait for interstitial is flaky. crbug.com/1107540")
620     @SmallTest
621     @Feature({"AndroidWebView"})
testSafeBrowsingShowsInterstitialForSubresource()622     public void testSafeBrowsingShowsInterstitialForSubresource() throws Throwable {
623         loadGreenPage();
624         loadPathAndWaitForInterstitial(IFRAME_HTML_PATH);
625         assertGreenPageNotShowing();
626         assertTargetPageNotShowing(IFRAME_EMBEDDER_BACKGROUND_COLOR);
627         // Assume that we are rendering the interstitial, since we see neither the previous page
628         // nor the target page
629     }
630 
631     @Test
632     @SmallTest
633     @Feature({"AndroidWebView"})
testSafeBrowsingProceedThroughInterstitialForMainFrame()634     public void testSafeBrowsingProceedThroughInterstitialForMainFrame() throws Throwable {
635         int pageFinishedCount = mContentsClient.getOnPageFinishedHelper().getCallCount();
636         loadPathAndWaitForInterstitial(MALWARE_HTML_PATH);
637         waitForInterstitialDomToLoad();
638         int onSafeBrowsingCountBeforeClick = mContentsClient.getOnSafeBrowsingHitCount();
639         clickVisitUnsafePage();
640         mContentsClient.getOnPageFinishedHelper().waitForCallback(pageFinishedCount);
641         assertTargetPageHasLoaded(MALWARE_PAGE_BACKGROUND_COLOR);
642         // Check there is not an extra onSafeBrowsingHit call after proceeding.
643         Assert.assertEquals(
644                 onSafeBrowsingCountBeforeClick, mContentsClient.getOnSafeBrowsingHitCount());
645     }
646 
647     @Test
648     @DisabledTest(message = "Wait for interstitial is flaky. crbug.com/1107540")
649     @SmallTest
650     @Feature({"AndroidWebView"})
testSafeBrowsingProceedThroughInterstitialForSubresource()651     public void testSafeBrowsingProceedThroughInterstitialForSubresource() throws Throwable {
652         int pageFinishedCount = mContentsClient.getOnPageFinishedHelper().getCallCount();
653         loadPathAndWaitForInterstitial(IFRAME_HTML_PATH);
654         waitForInterstitialDomToLoad();
655         clickVisitUnsafePage();
656         // For subresources, the initial site finishes loading before the interstitial is shown,
657         // causing an extra onPageFinished call if committed interstitials are enabled (since the
658         // proceed action triggers a reload).
659         mContentsClient.getOnPageFinishedHelper().waitForCallback(pageFinishedCount, 2);
660         assertTargetPageHasLoaded(IFRAME_EMBEDDER_BACKGROUND_COLOR);
661     }
662 
663     @Test
664     @SmallTest
665     @Feature({"AndroidWebView"})
testSafeBrowsingDontProceedCausesNetworkErrorForMainFrame()666     public void testSafeBrowsingDontProceedCausesNetworkErrorForMainFrame() throws Throwable {
667         loadGreenPage();
668         loadPathAndWaitForInterstitial(MALWARE_HTML_PATH);
669         OnReceivedError2Helper errorHelper = mContentsClient.getOnReceivedError2Helper();
670         int errorCount = errorHelper.getCallCount();
671         waitForInterstitialDomToLoad();
672         clickBackToSafety();
673         errorHelper.waitForCallback(errorCount);
674         Assert.assertEquals(
675                 WebviewErrorCode.ERROR_UNSAFE_RESOURCE, errorHelper.getError().errorCode);
676         final String responseUrl = mTestServer.getURL(MALWARE_HTML_PATH);
677         Assert.assertEquals("Network error is for the malicious page", responseUrl,
678                 errorHelper.getRequest().url);
679     }
680 
681     @Test
682     @SmallTest
683     @Feature({"AndroidWebView"})
testSafeBrowsingDontProceedNavigatesBackForMainFrame()684     public void testSafeBrowsingDontProceedNavigatesBackForMainFrame() throws Throwable {
685         loadGreenPage();
686         loadPathAndWaitForInterstitial(MALWARE_HTML_PATH);
687         waitForInterstitialDomToLoad();
688         OnReceivedError2Helper errorHelper = mContentsClient.getOnReceivedError2Helper();
689         int errorCount = errorHelper.getCallCount();
690         clickBackToSafety();
691         errorHelper.waitForCallback(errorCount);
692         mActivityTestRule.waitForVisualStateCallback(mAwContents);
693         assertTargetPageNotShowing(MALWARE_PAGE_BACKGROUND_COLOR);
694         assertGreenPageShowing();
695     }
696 
697     @Test
698     @DisabledTest(message = "Wait for interstitial is flaky. crbug.com/1107540")
699     @SmallTest
700     @Feature({"AndroidWebView"})
testSafeBrowsingDontProceedNavigatesBackForSubResource()701     public void testSafeBrowsingDontProceedNavigatesBackForSubResource() throws Throwable {
702         loadGreenPage();
703         loadPathAndWaitForInterstitial(IFRAME_HTML_PATH);
704         waitForInterstitialDomToLoad();
705         OnReceivedError2Helper errorHelper = mContentsClient.getOnReceivedError2Helper();
706         int errorCount = errorHelper.getCallCount();
707         clickBackToSafety();
708         errorHelper.waitForCallback(errorCount);
709         mActivityTestRule.waitForVisualStateCallback(mAwContents);
710         assertTargetPageNotShowing(IFRAME_EMBEDDER_BACKGROUND_COLOR);
711         assertGreenPageShowing();
712     }
713 
714     @Test
715     @SmallTest
716     @Feature({"AndroidWebView"})
testSafeBrowsingCanBeDisabledPerWebview()717     public void testSafeBrowsingCanBeDisabledPerWebview() throws Throwable {
718         mActivityTestRule.getAwSettingsOnUiThread(mAwContents).setSafeBrowsingEnabled(false);
719 
720         final String responseUrl = mTestServer.getURL(MALWARE_HTML_PATH);
721         mActivityTestRule.loadUrlSync(
722                 mAwContents, mContentsClient.getOnPageFinishedHelper(), responseUrl);
723         assertTargetPageHasLoaded(MALWARE_PAGE_BACKGROUND_COLOR);
724     }
725 
726     @Test
727     @SmallTest
728     @Feature({"AndroidWebView"})
testSafeBrowsingCanBeDisabledPerWebview_withImage()729     public void testSafeBrowsingCanBeDisabledPerWebview_withImage() throws Throwable {
730         // In particular this test checks that there is no crash when network service
731         // is enabled, safebrowsing is disabled and the RendererURLLoaderThrottle
732         // attempts to check a url through loading an image (crbug.com/889479).
733         mActivityTestRule.getAwSettingsOnUiThread(mAwContents).setSafeBrowsingEnabled(false);
734 
735         final String responseUrl = mTestServer.getURL(MALWARE_WITH_IMAGE_HTML_PATH);
736         mActivityTestRule.loadUrlSync(
737                 mAwContents, mContentsClient.getOnPageFinishedHelper(), responseUrl);
738         assertTargetPageHasLoaded(MALWARE_PAGE_BACKGROUND_COLOR);
739     }
740 
741     @Test
742     @SmallTest
743     @Feature({"AndroidWebView"})
744     @CommandLineFlags.Add(AwSwitches.WEBVIEW_DISABLE_SAFEBROWSING_SUPPORT)
testSafeBrowsingCanBeEnabledPerWebview()745     public void testSafeBrowsingCanBeEnabledPerWebview() throws Throwable {
746         final String responseUrl = mTestServer.getURL(MALWARE_HTML_PATH);
747         mActivityTestRule.loadUrlSync(
748                 mAwContents, mContentsClient.getOnPageFinishedHelper(), responseUrl);
749         assertTargetPageHasLoaded(MALWARE_PAGE_BACKGROUND_COLOR);
750 
751         mActivityTestRule.getAwSettingsOnUiThread(mAwContents).setSafeBrowsingEnabled(true);
752 
753         loadGreenPage();
754         loadPathAndWaitForInterstitial(MALWARE_HTML_PATH);
755         assertGreenPageNotShowing();
756         assertTargetPageNotShowing(MALWARE_PAGE_BACKGROUND_COLOR);
757     }
758 
759     @Test
760     @SmallTest
761     @Feature({"AndroidWebView"})
testSafeBrowsingShowsNetworkErrorForInvisibleViews()762     public void testSafeBrowsingShowsNetworkErrorForInvisibleViews() throws Throwable {
763         mAwContents.setCanShowInterstitial(false);
764         mAwContents.setCanShowBigInterstitial(false);
765         final String responseUrl = mTestServer.getURL(MALWARE_HTML_PATH);
766         OnReceivedError2Helper errorHelper = mContentsClient.getOnReceivedError2Helper();
767         int errorCount = errorHelper.getCallCount();
768         mActivityTestRule.loadUrlAsync(mAwContents, responseUrl);
769         errorHelper.waitForCallback(errorCount);
770         Assert.assertEquals(
771                 WebviewErrorCode.ERROR_UNSAFE_RESOURCE, errorHelper.getError().errorCode);
772         Assert.assertEquals("Network error is for the malicious page", responseUrl,
773                 errorHelper.getRequest().url);
774     }
775 
776     @Test
777     @SmallTest
778     @Feature({"AndroidWebView"})
testSafeBrowsingShowsQuietInterstitialForOddSizedViews()779     public void testSafeBrowsingShowsQuietInterstitialForOddSizedViews() throws Throwable {
780         mAwContents.setCanShowBigInterstitial(false);
781         loadGreenPage();
782         loadPathAndWaitForInterstitial(MALWARE_HTML_PATH);
783         assertGreenPageNotShowing();
784         assertTargetPageNotShowing(MALWARE_PAGE_BACKGROUND_COLOR);
785     }
786 
787     @Test
788     @SmallTest
789     @Feature({"AndroidWebView"})
testSafeBrowsingCanShowQuietPhishingInterstitial()790     public void testSafeBrowsingCanShowQuietPhishingInterstitial() throws Throwable {
791         mAwContents.setCanShowBigInterstitial(false);
792         loadGreenPage();
793         loadPathAndWaitForInterstitial(PHISHING_HTML_PATH);
794         assertGreenPageNotShowing();
795         assertTargetPageNotShowing(PHISHING_PAGE_BACKGROUND_COLOR);
796     }
797 
798     @Test
799     @SmallTest
800     @Feature({"AndroidWebView"})
testSafeBrowsingCanShowQuietUnwantedSoftwareInterstitial()801     public void testSafeBrowsingCanShowQuietUnwantedSoftwareInterstitial() throws Throwable {
802         mAwContents.setCanShowBigInterstitial(false);
803         loadGreenPage();
804         loadPathAndWaitForInterstitial(UNWANTED_SOFTWARE_HTML_PATH);
805         assertGreenPageNotShowing();
806         assertTargetPageNotShowing(UNWANTED_SOFTWARE_PAGE_BACKGROUND_COLOR);
807     }
808 
809     @Test
810     @SmallTest
811     @Feature({"AndroidWebView"})
testSafeBrowsingCanShowQuietBillingInterstitial()812     public void testSafeBrowsingCanShowQuietBillingInterstitial() throws Throwable {
813         mAwContents.setCanShowBigInterstitial(false);
814         loadGreenPage();
815         loadPathAndWaitForInterstitial(BILLING_HTML_PATH);
816         assertGreenPageNotShowing();
817         assertTargetPageNotShowing(BILLING_PAGE_BACKGROUND_COLOR);
818     }
819 
820     @Test
821     @SmallTest
822     @Feature({"AndroidWebView"})
testSafeBrowsingProceedQuietInterstitial()823     public void testSafeBrowsingProceedQuietInterstitial() throws Throwable {
824         mAwContents.setCanShowBigInterstitial(false);
825         int pageFinishedCount = mContentsClient.getOnPageFinishedHelper().getCallCount();
826         loadPathAndWaitForInterstitial(PHISHING_HTML_PATH);
827         waitForInterstitialDomToLoad();
828         clickVisitUnsafePageQuietInterstitial();
829         mContentsClient.getOnPageFinishedHelper().waitForCallback(pageFinishedCount);
830         assertTargetPageHasLoaded(PHISHING_PAGE_BACKGROUND_COLOR);
831     }
832 
833     @Test
834     @SmallTest
835     @Feature({"AndroidWebView"})
testSafeBrowsingOnSafeBrowsingHitShowInterstitial()836     public void testSafeBrowsingOnSafeBrowsingHitShowInterstitial() throws Throwable {
837         mContentsClient.setSafeBrowsingAction(SafeBrowsingAction.SHOW_INTERSTITIAL);
838 
839         loadGreenPage();
840         loadPathAndWaitForInterstitial(PHISHING_HTML_PATH);
841         assertGreenPageNotShowing();
842         assertTargetPageNotShowing(PHISHING_PAGE_BACKGROUND_COLOR);
843         // Assume that we are rendering the interstitial, since we see neither the previous page nor
844         // the target page
845 
846         // Check onSafeBrowsingHit arguments
847         final String responseUrl = mTestServer.getURL(PHISHING_HTML_PATH);
848         Assert.assertEquals(responseUrl, mContentsClient.getLastRequest().url);
849         Assert.assertEquals(AwSafeBrowsingConversionHelper.SAFE_BROWSING_THREAT_PHISHING,
850                 mContentsClient.getLastThreatType());
851     }
852 
853     @Test
854     @SmallTest
855     @Feature({"AndroidWebView"})
testSafeBrowsingOnSafeBrowsingHitProceed()856     public void testSafeBrowsingOnSafeBrowsingHitProceed() throws Throwable {
857         mContentsClient.setSafeBrowsingAction(SafeBrowsingAction.PROCEED);
858 
859         loadGreenPage();
860         final String responseUrl = mTestServer.getURL(PHISHING_HTML_PATH);
861         mActivityTestRule.loadUrlSync(
862                 mAwContents, mContentsClient.getOnPageFinishedHelper(), responseUrl);
863         mActivityTestRule.waitForVisualStateCallback(mAwContents);
864         assertTargetPageHasLoaded(PHISHING_PAGE_BACKGROUND_COLOR);
865 
866         // Check onSafeBrowsingHit arguments
867         Assert.assertEquals(responseUrl, mContentsClient.getLastRequest().url);
868         Assert.assertEquals(AwSafeBrowsingConversionHelper.SAFE_BROWSING_THREAT_PHISHING,
869                 mContentsClient.getLastThreatType());
870     }
871 
872     @Test
873     @SmallTest
874     @Feature({"AndroidWebView"})
testSafeBrowsingOnSafeBrowsingHitBackToSafety()875     public void testSafeBrowsingOnSafeBrowsingHitBackToSafety() throws Throwable {
876         mContentsClient.setSafeBrowsingAction(SafeBrowsingAction.BACK_TO_SAFETY);
877 
878         loadGreenPage();
879         final String responseUrl = mTestServer.getURL(MALWARE_HTML_PATH);
880         OnReceivedError2Helper errorHelper = mContentsClient.getOnReceivedError2Helper();
881         int errorCount = errorHelper.getCallCount();
882         mActivityTestRule.loadUrlAsync(mAwContents, responseUrl);
883         errorHelper.waitForCallback(errorCount);
884         Assert.assertEquals(
885                 WebviewErrorCode.ERROR_UNSAFE_RESOURCE, errorHelper.getError().errorCode);
886         Assert.assertEquals("Network error is for the malicious page", responseUrl,
887                 errorHelper.getRequest().url);
888 
889         assertGreenPageShowing();
890 
891         // Check onSafeBrowsingHit arguments
892         Assert.assertEquals(responseUrl, mContentsClient.getLastRequest().url);
893         Assert.assertEquals(AwSafeBrowsingConversionHelper.SAFE_BROWSING_THREAT_MALWARE,
894                 mContentsClient.getLastThreatType());
895     }
896 
897     @Test
898     @SmallTest
899     @Feature({"AndroidWebView"})
testSafeBrowsingOnSafeBrowsingHitForSubresourceNoPreviousPage()900     public void testSafeBrowsingOnSafeBrowsingHitForSubresourceNoPreviousPage() throws Throwable {
901         mContentsClient.setSafeBrowsingAction(SafeBrowsingAction.BACK_TO_SAFETY);
902         final String responseUrl = mTestServer.getURL(IFRAME_HTML_PATH);
903         final String subresourceUrl = mTestServer.getURL(MALWARE_HTML_PATH);
904         int pageFinishedCount = mContentsClient.getOnPageFinishedHelper().getCallCount();
905         mActivityTestRule.loadUrlAsync(mAwContents, responseUrl);
906 
907         // We'll successfully load IFRAME_HTML_PATH, and will soon call onSafeBrowsingHit
908         mContentsClient.getOnPageFinishedHelper().waitForCallback(pageFinishedCount);
909 
910         final GURL aboutBlank = new GURL(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
911 
912         // Wait for the onSafeBrowsingHit to call BACK_TO_SAFETY and navigate back
913         mActivityTestRule.pollUiThread(() -> aboutBlank.equals(mAwContents.getUrl()));
914 
915         // Check onSafeBrowsingHit arguments
916         Assert.assertFalse(mContentsClient.getLastRequest().isMainFrame);
917         Assert.assertEquals(subresourceUrl, mContentsClient.getLastRequest().url);
918         Assert.assertEquals(AwSafeBrowsingConversionHelper.SAFE_BROWSING_THREAT_MALWARE,
919                 mContentsClient.getLastThreatType());
920     }
921 
922     @Test
923     @SmallTest
924     @Feature({"AndroidWebView"})
testSafeBrowsingOnSafeBrowsingHitForSubresource()925     public void testSafeBrowsingOnSafeBrowsingHitForSubresource() throws Throwable {
926         mContentsClient.setSafeBrowsingAction(SafeBrowsingAction.BACK_TO_SAFETY);
927         loadGreenPage();
928         final String responseUrl = mTestServer.getURL(IFRAME_HTML_PATH);
929         final String subresourceUrl = mTestServer.getURL(MALWARE_HTML_PATH);
930         int pageFinishedCount = mContentsClient.getOnPageFinishedHelper().getCallCount();
931         mActivityTestRule.loadUrlAsync(mAwContents, responseUrl);
932 
933         // We'll successfully load IFRAME_HTML_PATH, and will soon call onSafeBrowsingHit
934         mContentsClient.getOnPageFinishedHelper().waitForCallback(pageFinishedCount);
935 
936         // Wait for the onSafeBrowsingHit to call BACK_TO_SAFETY and navigate back
937         // clang-format off
938         mActivityTestRule.pollUiThread(() -> colorToString(GREEN_PAGE_BACKGROUND_COLOR).equals(
939                 colorToString(GraphicsTestUtils.getPixelColorAtCenterOfView(mAwContents,
940                         mContainerView))));
941         // clang-format on
942 
943         // Check onSafeBrowsingHit arguments
944         Assert.assertFalse(mContentsClient.getLastRequest().isMainFrame);
945         Assert.assertEquals(subresourceUrl, mContentsClient.getLastRequest().url);
946         Assert.assertEquals(AwSafeBrowsingConversionHelper.SAFE_BROWSING_THREAT_MALWARE,
947                 mContentsClient.getLastThreatType());
948 
949         mContentsClient.setSafeBrowsingAction(SafeBrowsingAction.PROCEED);
950 
951         mActivityTestRule.loadUrlSync(
952                 mAwContents, mContentsClient.getOnPageFinishedHelper(), responseUrl);
953         mActivityTestRule.waitForVisualStateCallback(mAwContents);
954         assertTargetPageHasLoaded(IFRAME_EMBEDDER_BACKGROUND_COLOR);
955 
956         Assert.assertFalse(mContentsClient.getLastRequest().isMainFrame);
957         Assert.assertEquals(subresourceUrl, mContentsClient.getLastRequest().url);
958         Assert.assertEquals(AwSafeBrowsingConversionHelper.SAFE_BROWSING_THREAT_MALWARE,
959                 mContentsClient.getLastThreatType());
960     }
961 
962     @Test
963     @SmallTest
964     @Feature({"AndroidWebView"})
testSafeBrowsingOnSafeBrowsingHitHideReportingCheckbox()965     public void testSafeBrowsingOnSafeBrowsingHitHideReportingCheckbox() throws Throwable {
966         mContentsClient.setReporting(false);
967         loadGreenPage();
968         loadPathAndWaitForInterstitial(PHISHING_HTML_PATH);
969         waitForInterstitialDomToLoad();
970 
971         Assert.assertFalse(getVisibilityByIdOnInterstitial("extended-reporting-opt-in"));
972     }
973 
974     @Test
975     @SmallTest
976     @Feature({"AndroidWebView"})
testSafeBrowsingReportingCheckboxVisibleByDefault()977     public void testSafeBrowsingReportingCheckboxVisibleByDefault() throws Throwable {
978         loadGreenPage();
979         loadPathAndWaitForInterstitial(PHISHING_HTML_PATH);
980         waitForInterstitialDomToLoad();
981 
982         Assert.assertTrue(getVisibilityByIdOnInterstitial("extended-reporting-opt-in"));
983     }
984 
985     /**
986      * @return whether {@code domNodeId} is visible on the interstitial page.
987      * @throws Exception if the node cannot be found in the interstitial DOM or unable to evaluate
988      * JS.
989      */
getVisibilityByIdOnInterstitial(String domNodeId)990     private boolean getVisibilityByIdOnInterstitial(String domNodeId) throws Exception {
991         // clang-format off
992         final String script =
993                   "(function isNodeVisible(node) {"
994                 + "  if (!node) return 'node not found';"
995                 + "  return !node.classList.contains('hidden');"
996                 + "})(document.getElementById('" + domNodeId + "'))";
997         // clang-format on
998 
999         String value = evaluateJavaScriptOnInterstitialOnUiThreadSync(script);
1000 
1001         if (value.equals("true")) {
1002             return true;
1003         } else if (value.equals("false")) {
1004             return false;
1005         } else {
1006             throw new Exception("Node not found");
1007         }
1008     }
1009 
1010     @Test
1011     @SmallTest
1012     @Feature({"AndroidWebView"})
testSafeBrowsingUserOptOutOverridesManifest()1013     public void testSafeBrowsingUserOptOutOverridesManifest() throws Throwable {
1014         AwSafeBrowsingConfigHelper.setSafeBrowsingUserOptIn(false);
1015         loadGreenPage();
1016         final String responseUrl = mTestServer.getURL(MALWARE_HTML_PATH);
1017         mActivityTestRule.loadUrlSync(
1018                 mAwContents, mContentsClient.getOnPageFinishedHelper(), responseUrl);
1019         assertTargetPageHasLoaded(MALWARE_PAGE_BACKGROUND_COLOR);
1020     }
1021 
1022     @Test
1023     @SmallTest
1024     @Feature({"AndroidWebView"})
testSafeBrowsingUserOptOutOverridesPerWebView()1025     public void testSafeBrowsingUserOptOutOverridesPerWebView() throws Throwable {
1026         AwSafeBrowsingConfigHelper.setSafeBrowsingUserOptIn(false);
1027         mActivityTestRule.getAwSettingsOnUiThread(mAwContents).setSafeBrowsingEnabled(true);
1028         loadGreenPage();
1029         final String responseUrl = mTestServer.getURL(MALWARE_HTML_PATH);
1030         mActivityTestRule.loadUrlSync(
1031                 mAwContents, mContentsClient.getOnPageFinishedHelper(), responseUrl);
1032         assertTargetPageHasLoaded(MALWARE_PAGE_BACKGROUND_COLOR);
1033     }
1034 
1035     @Test
1036     @SmallTest
1037     @Feature({"AndroidWebView"})
testSafeBrowsingHardcodedMalwareUrl()1038     public void testSafeBrowsingHardcodedMalwareUrl() throws Throwable {
1039         loadGreenPage();
1040         mActivityTestRule.loadUrlAsync(mAwContents, WEB_UI_MALWARE_URL);
1041         // Wait for the interstitial to actually render.
1042         mActivityTestRule.waitForVisualStateCallback(mAwContents);
1043         waitForInterstitialDomToLoad();
1044     }
1045 
1046     @Test
1047     @SmallTest
1048     @Feature({"AndroidWebView"})
testSafeBrowsingHardcodedPhishingUrl()1049     public void testSafeBrowsingHardcodedPhishingUrl() throws Throwable {
1050         loadGreenPage();
1051         mActivityTestRule.loadUrlAsync(mAwContents, WEB_UI_PHISHING_URL);
1052         // Wait for the interstitial to actually render.
1053         mActivityTestRule.waitForVisualStateCallback(mAwContents);
1054         waitForInterstitialDomToLoad();
1055     }
1056 
1057     @Test
1058     @SmallTest
1059     @Feature({"AndroidWebView"})
testSafeBrowsingHardcodedUrlsIgnoreUserOptOut()1060     public void testSafeBrowsingHardcodedUrlsIgnoreUserOptOut() throws Throwable {
1061         AwSafeBrowsingConfigHelper.setSafeBrowsingUserOptIn(false);
1062         loadGreenPage();
1063         mActivityTestRule.loadUrlAsync(mAwContents, WEB_UI_MALWARE_URL);
1064         // Wait for the interstitial to actually render.
1065         mActivityTestRule.waitForVisualStateCallback(mAwContents);
1066         waitForInterstitialDomToLoad();
1067     }
1068 
1069     @Test
1070     @SmallTest
1071     @Feature({"AndroidWebView"})
testSafeBrowsingHardcodedUrlsRespectPerWebviewToggle()1072     public void testSafeBrowsingHardcodedUrlsRespectPerWebviewToggle() throws Throwable {
1073         mActivityTestRule.getAwSettingsOnUiThread(mAwContents).setSafeBrowsingEnabled(false);
1074         mActivityTestRule.loadUrlSync(
1075                 mAwContents, mContentsClient.getOnPageFinishedHelper(), WEB_UI_MALWARE_URL);
1076         // If we get here, it means the navigation was not blocked by an interstitial.
1077     }
1078 
1079     @Test
1080     @SmallTest
1081     @Feature({"AndroidWebView"})
testSafeBrowsingClickLearnMoreLink()1082     public void testSafeBrowsingClickLearnMoreLink() throws Throwable {
1083         loadInterstitialAndClickLink(PHISHING_HTML_PATH, "learn-more-link",
1084                 appendLocale("https://support.google.com/chrome/?p=cpn_safe_browsing_wv"));
1085     }
1086 
1087     @Test
1088     @SmallTest
1089     @Feature({"AndroidWebView"})
testSafeBrowsingClickReportErrorLink()1090     public void testSafeBrowsingClickReportErrorLink() throws Throwable {
1091         // Only phishing interstitials have the report-error-link
1092         final String reportErrorUrl =
1093                 Uri.parse("https://safebrowsing.google.com/safebrowsing/report_error/")
1094                         .buildUpon()
1095                         .appendQueryParameter(
1096                                 "url", mTestServer.getURL(PHISHING_HTML_PATH).toString())
1097                         .appendQueryParameter("hl", getSafeBrowsingLocaleOnUiThreadForTesting())
1098                         .toString();
1099         loadInterstitialAndClickLink(PHISHING_HTML_PATH, "report-error-link", reportErrorUrl);
1100     }
1101 
appendLocale(String url)1102     private String appendLocale(String url) throws Exception {
1103         return Uri.parse(url)
1104                 .buildUpon()
1105                 .appendQueryParameter("hl", getSafeBrowsingLocaleOnUiThreadForTesting())
1106                 .toString();
1107     }
1108 
getSafeBrowsingLocaleOnUiThreadForTesting()1109     private String getSafeBrowsingLocaleOnUiThreadForTesting() throws Exception {
1110         return TestThreadUtils.runOnUiThreadBlocking(
1111                 () -> AwContents.getSafeBrowsingLocaleForTesting());
1112     }
1113 
1114     @Test
1115     @SmallTest
1116     @Feature({"AndroidWebView"})
testSafeBrowsingClickDiagnosticLink()1117     public void testSafeBrowsingClickDiagnosticLink() throws Throwable {
1118         final String responseUrl = mTestServer.getURL(MALWARE_HTML_PATH);
1119         final String diagnosticUrl =
1120                 Uri.parse("https://transparencyreport.google.com/safe-browsing/search")
1121                         .buildUpon()
1122                         .appendQueryParameter("url", responseUrl)
1123                         .appendQueryParameter("hl", getSafeBrowsingLocaleOnUiThreadForTesting())
1124                         .toString();
1125         loadInterstitialAndClickLink(MALWARE_HTML_PATH, "diagnostic-link", diagnosticUrl);
1126     }
1127 
1128     @Test
1129     @SmallTest
1130     @Feature({"AndroidWebView"})
testSafeBrowsingClickWhitePaperLink()1131     public void testSafeBrowsingClickWhitePaperLink() throws Throwable {
1132         final String whitepaperUrl =
1133                 Uri.parse("https://www.google.com/chrome/browser/privacy/whitepaper.html")
1134                         .buildUpon()
1135                         .appendQueryParameter("hl", getSafeBrowsingLocaleOnUiThreadForTesting())
1136                         .fragment("extendedreport")
1137                         .toString();
1138         loadInterstitialAndClickLink(PHISHING_HTML_PATH, "whitepaper-link", whitepaperUrl);
1139     }
1140 
1141     @Test
1142     @SmallTest
1143     @Feature({"AndroidWebView"})
testSafeBrowsingClickPrivacyPolicy()1144     public void testSafeBrowsingClickPrivacyPolicy() throws Throwable {
1145         final String privacyPolicyUrl =
1146                 Uri.parse("https://www.google.com/chrome/browser/privacy/")
1147                         .buildUpon()
1148                         .appendQueryParameter("hl", getSafeBrowsingLocaleOnUiThreadForTesting())
1149                         .fragment("safe-browsing-policies")
1150                         .toString();
1151         loadInterstitialAndClickLink(PHISHING_HTML_PATH, "privacy-link", privacyPolicyUrl);
1152     }
1153 
loadInterstitialAndClickLink(String path, String linkId, String linkUrl)1154     private void loadInterstitialAndClickLink(String path, String linkId, String linkUrl)
1155             throws Exception {
1156         loadPathAndWaitForInterstitial(path);
1157         waitForInterstitialDomToLoad();
1158         int pageFinishedCount = mContentsClient.getOnPageFinishedHelper().getCallCount();
1159         clickLinkById(linkId);
1160         mContentsClient.getOnPageFinishedHelper().waitForCallback(pageFinishedCount);
1161         // Some click tests involve URLs that redirect and mAwContents.getUrl() sometimes
1162         // returns the post-redirect URL, so we instead check with ShouldInterceptRequest.
1163         AwContentsClient.AwWebResourceRequest requestsForUrl =
1164                 mContentsClient.getShouldInterceptRequestHelper().getRequestsForUrl(linkUrl);
1165         // Make sure the URL was seen for a main frame navigation.
1166         Assert.assertTrue(requestsForUrl.isMainFrame);
1167     }
1168 
1169     @Test
1170     @SmallTest
1171     @Feature({"AndroidWebView"})
testInitSafeBrowsingCallbackOnUIThread()1172     public void testInitSafeBrowsingCallbackOnUIThread() throws Throwable {
1173         Context ctx = InstrumentationRegistry.getInstrumentation()
1174                               .getTargetContext()
1175                               .getApplicationContext();
1176         CallbackHelper helper = new CallbackHelper();
1177         int count = helper.getCallCount();
1178         mOnUiThread = false;
1179         AwContentsStatics.initSafeBrowsing(ctx, b -> {
1180             mOnUiThread = ThreadUtils.runningOnUiThread();
1181             helper.notifyCalled();
1182         });
1183         helper.waitForCallback(count);
1184         // Don't run the assert on the callback's thread, since the test runner loses the stack
1185         // trace unless on the instrumentation thread.
1186         Assert.assertTrue("Callback should run on UI Thread", mOnUiThread);
1187     }
1188 
1189     @Test
1190     @SmallTest
1191     @Feature({"AndroidWebView"})
testInitSafeBrowsingUsesAppContext()1192     public void testInitSafeBrowsingUsesAppContext() throws Throwable {
1193         MockContext ctx =
1194                 new MockContext(InstrumentationRegistry.getInstrumentation().getTargetContext());
1195         CallbackHelper helper = new CallbackHelper();
1196         int count = helper.getCallCount();
1197 
1198         AwContentsStatics.initSafeBrowsing(ctx, b -> helper.notifyCalled());
1199         helper.waitForCallback(count);
1200         Assert.assertTrue(
1201                 "Should only use application context", ctx.wasGetApplicationContextCalled());
1202     }
1203 
1204     private static class MockContext extends ContextWrapper {
1205         private boolean mGetApplicationContextWasCalled;
1206 
MockContext(Context context)1207         public MockContext(Context context) {
1208             super(context);
1209         }
1210 
1211         @Override
getApplicationContext()1212         public Context getApplicationContext() {
1213             mGetApplicationContextWasCalled = true;
1214             return super.getApplicationContext();
1215         }
1216 
wasGetApplicationContextCalled()1217         public boolean wasGetApplicationContextCalled() {
1218             return mGetApplicationContextWasCalled;
1219         }
1220     }
1221 
1222     @Test
1223     @SmallTest
1224     @Feature({"AndroidWebView"})
testGetSafeBrowsingPrivacyPolicyUrl()1225     public void testGetSafeBrowsingPrivacyPolicyUrl() throws Throwable {
1226         final Uri privacyPolicyUrl =
1227                 Uri.parse("https://www.google.com/chrome/browser/privacy/")
1228                         .buildUpon()
1229                         .appendQueryParameter("hl", getSafeBrowsingLocaleOnUiThreadForTesting())
1230                         .fragment("safe-browsing-policies")
1231                         .build();
1232         TestThreadUtils.runOnUiThreadBlocking(
1233                 () -> { mPrivacyPolicyUrl = AwContentsStatics.getSafeBrowsingPrivacyPolicyUrl(); });
1234         Assert.assertEquals(privacyPolicyUrl, this.mPrivacyPolicyUrl);
1235         Assert.assertNotNull(this.mPrivacyPolicyUrl);
1236     }
1237 
1238     @Test
1239     @SmallTest
1240     @Feature({"AndroidWebView"})
testDestroyWebViewWithInterstitialShowing()1241     public void testDestroyWebViewWithInterstitialShowing() throws Throwable {
1242         loadPathAndWaitForInterstitial(MALWARE_HTML_PATH);
1243         destroyOnMainSync();
1244         // As long as we've reached this line without crashing, there should be no bug.
1245     }
1246 
destroyOnMainSync()1247     private void destroyOnMainSync() {
1248         // The AwActivityTestRule method invokes AwContents#destroy() on the main thread, but
1249         // Awcontents#destroy() posts an asynchronous task itself to destroy natives. Therefore, we
1250         // still need to wait for the real work to actually finish.
1251         mActivityTestRule.destroyAwContentsOnMainSync(mAwContents);
1252         CriteriaHelper.pollUiThread(() -> {
1253             try {
1254                 int awContentsCount = TestThreadUtils.runOnUiThreadBlocking(
1255                         () -> AwContents.getNativeInstanceCount());
1256                 Criteria.checkThat(awContentsCount, Matchers.is(0));
1257             } catch (Exception e) {
1258                 throw new CriteriaNotSatisfiedException(e);
1259             }
1260         });
1261     }
1262 }
1263