Commit 98a6a05c authored by Ella Ge's avatar Ella Ge Committed by Commit Bot

TWA Quality enforcement on failing digital assert link

This CL adds the TWA quality enforcement for the launching URL failing
the digital assert link verification.

Bug: 1109609
Change-Id: I459015c0a70d4c00a5c3ec4f6f4506ff914c0552
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2339228Reviewed-by: default avatarPeter Conn <peconn@chromium.org>
Commit-Queue: Ella Ge <eirage@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798653}
parent 4b2c40ae
...@@ -24,6 +24,8 @@ import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar; ...@@ -24,6 +24,8 @@ import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar;
import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar.CustomTabTabObserver; import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar.CustomTabTabObserver;
import org.chromium.chrome.browser.dependency_injection.ActivityScope; import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.browser.NavigationHandle; import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.net.NetError; import org.chromium.net.NetError;
...@@ -44,7 +46,7 @@ import javax.inject.Inject; ...@@ -44,7 +46,7 @@ import javax.inject.Inject;
* will crash. We should hold web apps to the same standard. * will crash. We should hold web apps to the same standard.
*/ */
@ActivityScope @ActivityScope
public class QualityEnforcer { public class QualityEnforcer implements NativeInitObserver {
@VisibleForTesting @VisibleForTesting
static final String NOTIFY = "quality_enforcement.notify"; static final String NOTIFY = "quality_enforcement.notify";
@VisibleForTesting @VisibleForTesting
...@@ -58,6 +60,7 @@ public class QualityEnforcer { ...@@ -58,6 +60,7 @@ public class QualityEnforcer {
private final CustomTabsConnection mConnection; private final CustomTabsConnection mConnection;
private final CustomTabsSessionToken mSessionToken; private final CustomTabsSessionToken mSessionToken;
private final ClientPackageNameProvider mClientPackageNameProvider; private final ClientPackageNameProvider mClientPackageNameProvider;
private final BrowserServicesIntentDataProvider mIntentDataProvider;
private final TrustedWebActivityUmaRecorder mUmaRecorder; private final TrustedWebActivityUmaRecorder mUmaRecorder;
private boolean mOriginVerified; private boolean mOriginVerified;
...@@ -104,19 +107,33 @@ public class QualityEnforcer { ...@@ -104,19 +107,33 @@ public class QualityEnforcer {
}; };
@Inject @Inject
public QualityEnforcer(ChromeActivity<?> activity, TabObserverRegistrar tabObserverRegistrar, public QualityEnforcer(ChromeActivity<?> activity,
ActivityLifecycleDispatcher lifecycleDispatcher,
TabObserverRegistrar tabObserverRegistrar,
BrowserServicesIntentDataProvider intentDataProvider, CustomTabsConnection connection, BrowserServicesIntentDataProvider intentDataProvider, CustomTabsConnection connection,
Verifier verifier, ClientPackageNameProvider clientPackageNameProvider, Verifier verifier, ClientPackageNameProvider clientPackageNameProvider,
TrustedWebActivityUmaRecorder umaRecorder) { TrustedWebActivityUmaRecorder umaRecorder) {
mActivity = activity; mActivity = activity;
mVerifier = verifier; mVerifier = verifier;
mConnection = connection;
mSessionToken = intentDataProvider.getSession(); mSessionToken = intentDataProvider.getSession();
mIntentDataProvider = intentDataProvider;
mConnection = connection;
mClientPackageNameProvider = clientPackageNameProvider; mClientPackageNameProvider = clientPackageNameProvider;
mUmaRecorder = umaRecorder; mUmaRecorder = umaRecorder;
// Initialize the value to true before the first navigation. // Initialize the value to true before the first navigation.
mOriginVerified = true; mOriginVerified = true;
tabObserverRegistrar.registerActivityTabObserver(mTabObserver); tabObserverRegistrar.registerActivityTabObserver(mTabObserver);
lifecycleDispatcher.register(this);
}
@Override
public void onFinishNativeInitialization() {
String url = mIntentDataProvider.getUrlToLoad();
mVerifier.verify(url).then((verified) -> {
if (!verified) {
trigger(ViolationType.DIGITAL_ASSERTLINKS, mIntentDataProvider.getUrlToLoad(), 0);
}
});
} }
private void trigger(@ViolationType int type, String url, int httpStatusCode) { private void trigger(@ViolationType int type, String url, int httpStatusCode) {
...@@ -160,14 +177,20 @@ public class QualityEnforcer { ...@@ -160,14 +177,20 @@ public class QualityEnforcer {
/* Get the localized string for toast message. */ /* Get the localized string for toast message. */
private String getToastMessage(@ViolationType int type, String url, int httpStatusCode) { private String getToastMessage(@ViolationType int type, String url, int httpStatusCode) {
if (type == ViolationType.ERROR_404 || type == ViolationType.ERROR_5XX) { switch (type) {
return ContextUtils.getApplicationContext().getString( case ViolationType.ERROR_404:
R.string.twa_quality_enforcement_violation_error, httpStatusCode, url); case ViolationType.ERROR_5XX:
} else if (type == ViolationType.UNAVAILABLE_OFFLINE) { return ContextUtils.getApplicationContext().getString(
return ContextUtils.getApplicationContext().getString( R.string.twa_quality_enforcement_violation_error, httpStatusCode, url);
R.string.twa_quality_enforcement_violation_offline, url); case ViolationType.UNAVAILABLE_OFFLINE:
return ContextUtils.getApplicationContext().getString(
R.string.twa_quality_enforcement_violation_offline, url);
case ViolationType.DIGITAL_ASSERTLINKS:
return ContextUtils.getApplicationContext().getString(
R.string.twa_quality_enforcement_violation_assert_link, url);
default:
return "";
} }
return "";
} }
/* /*
...@@ -175,11 +198,16 @@ public class QualityEnforcer { ...@@ -175,11 +198,16 @@ public class QualityEnforcer {
* the toast because this is used in TWA's crash message. * the toast because this is used in TWA's crash message.
*/ */
private String toTwaCrashMessage(@ViolationType int type, String url, int httpStatusCode) { private String toTwaCrashMessage(@ViolationType int type, String url, int httpStatusCode) {
if (type == ViolationType.ERROR_404 || type == ViolationType.ERROR_5XX) { switch (type) {
return httpStatusCode + " on " + url; case ViolationType.ERROR_404:
} else if (type == ViolationType.UNAVAILABLE_OFFLINE) { case ViolationType.ERROR_5XX:
return "Page unavailable offline: " + url; return httpStatusCode + " on " + url;
case ViolationType.UNAVAILABLE_OFFLINE:
return "Page unavailable offline: " + url;
case ViolationType.DIGITAL_ASSERTLINKS:
return "Digital assert links verification failed on " + url;
default:
return "";
} }
return "";
} }
} }
...@@ -119,7 +119,30 @@ public class QualityEnforcerTest { ...@@ -119,7 +119,30 @@ public class QualityEnforcerTest {
assertEquals(mErrorMessage, "Page unavailable offline: https://example.com/"); assertEquals(mErrorMessage, "Page unavailable offline: https://example.com/");
} }
@Test
@MediumTest
public void notifiedDigitalAssertLinkFailed() throws TimeoutException {
launchNotVerify(mTestPage);
mCallbackHelper.waitForFirst();
}
public void launch(String testPage) throws TimeoutException { public void launch(String testPage) throws TimeoutException {
Intent intent = createTrustedWebActivityIntentWithCallback(testPage);
spoofVerification(PACKAGE_NAME, testPage);
createSession(intent, PACKAGE_NAME);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
}
public void launchNotVerify(String testPage) throws TimeoutException {
Intent intent = createTrustedWebActivityIntentWithCallback(testPage);
createSession(intent, PACKAGE_NAME);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
}
private Intent createTrustedWebActivityIntentWithCallback(String testPage)
throws TimeoutException {
CustomTabsSession session = CustomTabsTestUtils.bindWithCallback(mCallback).session; CustomTabsSession session = CustomTabsTestUtils.bindWithCallback(mCallback).session;
Intent intent = new CustomTabsIntent.Builder(session).build().intent; Intent intent = new CustomTabsIntent.Builder(session).build().intent;
intent.setComponent(new ComponentName( intent.setComponent(new ComponentName(
...@@ -128,10 +151,6 @@ public class QualityEnforcerTest { ...@@ -128,10 +151,6 @@ public class QualityEnforcerTest {
intent.setData(Uri.parse(testPage)); intent.setData(Uri.parse(testPage));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, true); intent.putExtra(TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, true);
return intent;
spoofVerification(PACKAGE_NAME, testPage);
createSession(intent, PACKAGE_NAME);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
} }
} }
...@@ -39,6 +39,7 @@ import org.chromium.chrome.browser.customtabs.CustomTabsConnection; ...@@ -39,6 +39,7 @@ import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar; import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar;
import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar.CustomTabTabObserver; import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar.CustomTabTabObserver;
import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.util.browser.Features; import org.chromium.chrome.test.util.browser.Features;
import org.chromium.chrome.test.util.browser.Features.DisableFeatures; import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
...@@ -63,6 +64,8 @@ public class QualityEnforcerUnitTest { ...@@ -63,6 +64,8 @@ public class QualityEnforcerUnitTest {
@Mock @Mock
private ChromeActivity mActivity; private ChromeActivity mActivity;
@Mock @Mock
ActivityLifecycleDispatcher mLifecycleDispatcher;
@Mock
private CustomTabIntentDataProvider mIntentDataProvider; private CustomTabIntentDataProvider mIntentDataProvider;
@Mock @Mock
private CustomTabsConnection mCustomTabsConnection; private CustomTabsConnection mCustomTabsConnection;
...@@ -93,9 +96,9 @@ public class QualityEnforcerUnitTest { ...@@ -93,9 +96,9 @@ public class QualityEnforcerUnitTest {
when(mVerifier.verify(TRUSTED_ORIGIN_PAGE)).thenReturn(Promise.fulfilled(true)); when(mVerifier.verify(TRUSTED_ORIGIN_PAGE)).thenReturn(Promise.fulfilled(true));
when(mVerifier.verify(UNTRUSTED_PAGE)).thenReturn(Promise.fulfilled(false)); when(mVerifier.verify(UNTRUSTED_PAGE)).thenReturn(Promise.fulfilled(false));
mQualityEnforcer = mQualityEnforcer = new QualityEnforcer(mActivity, mLifecycleDispatcher,
new QualityEnforcer(mActivity, mTabObserverRegistrar, mIntentDataProvider, mTabObserverRegistrar, mIntentDataProvider, mCustomTabsConnection, mVerifier,
mCustomTabsConnection, mVerifier, mClientPackageNameProvider, mUmaRecorder); mClientPackageNameProvider, mUmaRecorder);
} }
@Test @Test
...@@ -160,7 +163,30 @@ public class QualityEnforcerUnitTest { ...@@ -160,7 +163,30 @@ public class QualityEnforcerUnitTest {
@Test @Test
public void trigger_offline() { public void trigger_offline() {
navigateToUrlInternet(TRUSTED_ORIGIN_PAGE); navigateToUrlInternet(TRUSTED_ORIGIN_PAGE);
verifyTriggeredOffline(); Assert.assertEquals(
ContextUtils.getApplicationContext().getString(
R.string.twa_quality_enforcement_violation_offline, TRUSTED_ORIGIN_PAGE),
ShadowToast.getTextOfLatestToast());
verifyNotifyClientApp();
}
@Test
public void notTrigger_digitalAssertLinkPass() {
when(mIntentDataProvider.getUrlToLoad()).thenReturn(TRUSTED_ORIGIN_PAGE);
mQualityEnforcer.onFinishNativeInitialization();
verifyNotTriggered();
}
@Test
public void trigger_digitalAssertLinkFailed() {
when(mIntentDataProvider.getUrlToLoad()).thenReturn(UNTRUSTED_PAGE);
mQualityEnforcer.onFinishNativeInitialization();
Assert.assertEquals(
ContextUtils.getApplicationContext().getString(
R.string.twa_quality_enforcement_violation_assert_link, UNTRUSTED_PAGE),
ShadowToast.getTextOfLatestToast());
verifyNotifyClientApp();
} }
private void verifyTriggered404() { private void verifyTriggered404() {
...@@ -168,15 +194,10 @@ public class QualityEnforcerUnitTest { ...@@ -168,15 +194,10 @@ public class QualityEnforcerUnitTest {
R.string.twa_quality_enforcement_violation_error, R.string.twa_quality_enforcement_violation_error,
HTTP_ERROR_NOT_FOUND, TRUSTED_ORIGIN_PAGE), HTTP_ERROR_NOT_FOUND, TRUSTED_ORIGIN_PAGE),
ShadowToast.getTextOfLatestToast()); ShadowToast.getTextOfLatestToast());
verify(mCustomTabsConnection) verifyNotifyClientApp();
.sendExtraCallbackWithResult(any(), eq(QualityEnforcer.NOTIFY), any());
} }
private void verifyTriggeredOffline() { private void verifyNotifyClientApp() {
Assert.assertEquals(
ContextUtils.getApplicationContext().getString(
R.string.twa_quality_enforcement_violation_offline, TRUSTED_ORIGIN_PAGE),
ShadowToast.getTextOfLatestToast());
verify(mCustomTabsConnection) verify(mCustomTabsConnection)
.sendExtraCallbackWithResult(any(), eq(QualityEnforcer.NOTIFY), any()); .sendExtraCallbackWithResult(any(), eq(QualityEnforcer.NOTIFY), any());
} }
......
...@@ -3354,6 +3354,9 @@ To change this setting, <ph name="BEGIN_LINK">&lt;resetlink&gt;</ph>reset sync<p ...@@ -3354,6 +3354,9 @@ To change this setting, <ph name="BEGIN_LINK">&lt;resetlink&gt;</ph>reset sync<p
<message name="IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_ERROR" desc="Text shown on a toast when TWA violation 404."> <message name="IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_ERROR" desc="Text shown on a toast when TWA violation 404.">
<ph name="ERROR_CODE">%1$s<ex>404</ex></ph> on <ph name="VIOLATED_URL">%2$s<ex>https://example.com/</ex></ph> <ph name="ERROR_CODE">%1$s<ex>404</ex></ph> on <ph name="VIOLATED_URL">%2$s<ex>https://example.com/</ex></ph>
</message> </message>
<message name="IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_ASSERT_LINK" desc="Text shown on a toast when TWA violate the quality enforcement criteria: digital assert link verification failed.">
Digital assert links verification failed on <ph name="VIOLATED_URL">%1$s<ex>https://example.com/</ex></ph>
</message>
<message name="IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_OFFLINE" desc="Text shown on a toast when TWA violate the quality enforcement criteria: page unavailable offline."> <message name="IDS_TWA_QUALITY_ENFORCEMENT_VIOLATION_OFFLINE" desc="Text shown on a toast when TWA violate the quality enforcement criteria: page unavailable offline.">
Page unavailable offline: <ph name="VIOLATED_URL">%1$s<ex>https://example.com/</ex></ph> Page unavailable offline: <ph name="VIOLATED_URL">%1$s<ex>https://example.com/</ex></ph>
</message> </message>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment