Commit b1e143c7 authored by pkotwicz's avatar pkotwicz Committed by Commit bot

Update WebAPKs even if the WebAPK start URL has no Web Manifest part 2/3

The class name ManifestUpgradeDetector does not make sense in the context of
the new ManifestUpgradeDetector#Callback. The new callback can be called up to
twice:
- Once after the initial page load. (Regardless of whether the page uses the
correct Web Manifest)
- Once after a page which uses the correct Web Manifest has finished loading.
If a Web Developer removes the Web Manifest from their site, the second call is
never done.

This CL:
- moves the check for "whether the fetched Web Manifest requires a WebAPK
upgrade" into WebApkUpdateManager.
- merges the manifest fetching parts of ManifestUpgradeDetector and
ManifestUpgradeDetectorFetcher into a new class WebApkUpdateDataFetcher

BUG=639536

Review-Url: https://codereview.chromium.org/2460253002
Cr-Commit-Position: refs/heads/master@{#437641}
parent 9c5e2946
......@@ -397,6 +397,7 @@ android_library("chrome_test_java") {
"//base:base_java",
"//base:base_java_test_support",
"//chrome/android:chrome_java",
"//chrome/android/webapk/libs/client:client_java",
"//chrome/android/webapk/libs/common:common_java",
"//chrome/android/webapk/libs/runtime_library:webapk_service_aidl_java",
"//chrome/test/android:chrome_java_test_support",
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.webapps;
import android.text.TextUtils;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.util.UrlUtilities;
import java.util.Map;
/**
* This class checks whether the WebAPK needs to be re-installed and sends a request to re-install
* the WebAPK if it needs to be re-installed.
*/
public class ManifestUpgradeDetector implements ManifestUpgradeDetectorFetcher.Callback {
/** ManifestUpgradeDetector callback. */
public interface Callback {
/**
* Called when the Web Manifest for the initial URL load has been fetched (successfully or
* unsuccessfully).
* TODO(pkotwicz): Add calls to {@link #onFinishedFetchingWebManifestForInitialUrl()}.
* @param needsUpgrade Whether the WebAPK should be updated because the Web Manifest has
* changed. False if the Web Manifest could not be fetched.
* @param info The fetched Web Manifest data. Null if the initial URL does not point
* to a Web Manifest.
* @param bestIconUrl The icon URL in {@link WebApkInfo#iconUrlToMurmur2HashMap()} best
* suited for use as the launcher icon on this device.
*/
void onFinishedFetchingWebManifestForInitialUrl(
boolean needsUpgrade, WebApkInfo info, String bestIconUrl);
/**
* Called when the Web Manifest has been successfully fetched (including on the initial URL
* load).
* @param needsUpgrade Whether the WebAPK should be updated because the Web Manifest has
* changed.
* @param info The fetched Web Manifest data.
* @param bestIconUrl The icon URL in {@link WebApkInfo#iconUrlToMurmur2HashMap()} best
* suited for use as the launcher icon on this device.
*/
void onGotManifestData(boolean needsUpgrade, WebApkInfo info, String bestIconUrl);
}
private static final String TAG = "cr_UpgradeDetector";
/** The WebAPK's tab. */
private final Tab mTab;
/**
* Web Manifest data at time that the WebAPK was generated.
*/
private WebApkInfo mInfo;
/**
* Fetches the WebAPK's Web Manifest from the web.
*/
private ManifestUpgradeDetectorFetcher mFetcher;
private Callback mCallback;
/**
* Creates an instance of {@link ManifestUpgradeDetector}.
*
* @param tab WebAPK's tab.
* @param webappInfo Parameters used for generating the WebAPK. Extracted from WebAPK's Android
* manifest.
* @param info Web Manifest data at the time that the WebAPK was generated.
* @param callback Called once it has been determined whether the WebAPK needs to be upgraded.
*/
public ManifestUpgradeDetector(Tab tab, WebApkInfo info, Callback callback) {
mTab = tab;
mInfo = info;
mCallback = callback;
}
/**
* Starts fetching the web manifest resources.
*/
public boolean start() {
if (mFetcher != null) return false;
mFetcher = createFetcher();
return mFetcher.start(mTab, mInfo, this);
}
/**
* Creates ManifestUpgradeDataFetcher.
*/
protected ManifestUpgradeDetectorFetcher createFetcher() {
return new ManifestUpgradeDetectorFetcher();
}
/**
* Puts the object in a state where it is safe to be destroyed.
*/
public void destroy() {
if (mFetcher != null) {
mFetcher.destroy();
}
mFetcher = null;
}
/**
* Called when the updated Web Manifest has been fetched.
*/
@Override
public void onGotManifestData(WebApkInfo fetchedInfo, String bestIconUrl) {
mFetcher.destroy();
mFetcher = null;
// TODO(hanxi): crbug.com/627824. Validate whether the new fetched data is
// WebAPK-compatible.
boolean upgrade = needsUpgrade(fetchedInfo, bestIconUrl);
mCallback.onGotManifestData(upgrade, fetchedInfo, bestIconUrl);
}
/**
* Checks whether the WebAPK needs to be upgraded provided the fetched manifest data.
*/
private boolean needsUpgrade(WebApkInfo fetchedInfo, String bestIconUrl) {
// We should have computed the Murmur2 hash for the bitmap at the best icon URL for
// {@link fetchedInfo} (but not the other icon URLs.)
String fetchedBestIconMurmur2Hash = fetchedInfo.iconUrlToMurmur2HashMap().get(bestIconUrl);
String bestIconMurmur2Hash =
findMurmur2HashForUrlIgnoringFragment(mInfo.iconUrlToMurmur2HashMap(), bestIconUrl);
return !TextUtils.equals(bestIconMurmur2Hash, fetchedBestIconMurmur2Hash)
|| !urlsMatchIgnoringFragments(
mInfo.scopeUri().toString(), fetchedInfo.scopeUri().toString())
|| !urlsMatchIgnoringFragments(
mInfo.manifestStartUrl(), fetchedInfo.manifestStartUrl())
|| !TextUtils.equals(mInfo.shortName(), fetchedInfo.shortName())
|| !TextUtils.equals(mInfo.name(), fetchedInfo.name())
|| mInfo.backgroundColor() != fetchedInfo.backgroundColor()
|| mInfo.themeColor() != fetchedInfo.themeColor()
|| mInfo.orientation() != fetchedInfo.orientation()
|| mInfo.displayMode() != fetchedInfo.displayMode();
}
/**
* Returns the Murmur2 hash for entry in {@link iconUrlToMurmur2HashMap} whose canonical
* representation, ignoring fragments, matches {@link iconUrlToMatch}.
*/
private String findMurmur2HashForUrlIgnoringFragment(
Map<String, String> iconUrlToMurmur2HashMap, String iconUrlToMatch) {
for (Map.Entry<String, String> entry : iconUrlToMurmur2HashMap.entrySet()) {
if (urlsMatchIgnoringFragments(entry.getKey(), iconUrlToMatch)) {
return entry.getValue();
}
}
return null;
}
/**
* Returns whether the URLs match ignoring fragments. Canonicalizes the URLs prior to doing the
* comparison.
*/
protected boolean urlsMatchIgnoringFragments(String url1, String url2) {
return UrlUtilities.urlsMatchIgnoringFragments(url1, url2);
}
}
......@@ -18,50 +18,56 @@ import java.util.HashMap;
* Downloads the Web Manifest if the web site still uses the {@link manifestUrl} passed to the
* constructor.
*/
public class ManifestUpgradeDetectorFetcher extends EmptyTabObserver {
public class WebApkUpdateDataFetcher extends EmptyTabObserver {
/** Observes fetching of the Web Manifest. */
public interface Observer {
/**
* Called when the Web Manifest for the initial URL load has been fetched (successfully or
* unsuccessfully).
* TODO(pkotwicz): Add calls to {@link #onFinishedFetchingWebManifestForInitialUrl()}.
* @param fetchedInfo The fetched Web Manifest data. Null if the initial URL does not point
* to a Web Manifest.
* @param bestIconUrl The icon URL in {@link fetchedInfo#iconUrlToMurmur2HashMap()} best
* suited for use as the launcher icon on this device.
*/
void onFinishedFetchingWebManifestForInitialUrl(WebApkInfo fetchedInfo, String bestIconUrl);
/**
* Called once the Web Manifest has been downloaded.
*/
public interface Callback {
/**
* @param fetchedInfo The fetched Web Manifest data.
* @param bestIconUrl Icon URL in {@link data} which is best suited for use as the launcher
* icon on this device.
* Called when the Web Manifest has been successfully fetched (including on the initial URL
* load).
* @param fetchedInfo The fetched Web Manifest data.
* @param bestIconUrl The icon URL in {@link fetchedInfo#iconUrlToMurmur2HashMap()} best
* suited for use as the launcher icon on this device.
*/
void onGotManifestData(WebApkInfo fetchedInfo, String bestIconUrl);
}
/**
* Pointer to the native side ManifestUpgradeDetectorFetcher. The Java side owns the native side
* ManifestUpgradeDetectorFetcher.
* Pointer to the native side WebApkUpdateDataFetcher. The Java side owns the native side
* WebApkUpdateDataFetcher.
*/
private long mNativePointer;
/** The tab that is being observed. */
private Tab mTab;
/**
* Web Manifest data at time that the WebAPK was generated.
*/
/** Web Manifest data at the time that the WebAPK was generated. */
private WebApkInfo mOldInfo;
private Callback mCallback;
private Observer mObserver;
/**
* Starts fetching the web manifest resources.
* @param callback Called once the Web Manifest has been downloaded.
*/
public boolean start(Tab tab, WebApkInfo oldInfo, Callback callback) {
/** Starts observing page loads in order to fetch the Web Manifest after each page load. */
public boolean start(Tab tab, WebApkInfo oldInfo, Observer observer) {
if (tab.getWebContents() == null || TextUtils.isEmpty(oldInfo.manifestUrl())) {
return false;
}
mTab = tab;
mOldInfo = oldInfo;
mNativePointer = nativeInitialize(mOldInfo.scopeUri().toString(), mOldInfo.manifestUrl());
mCallback = callback;
mObserver = observer;
mTab.addObserver(this);
mNativePointer = nativeInitialize(mOldInfo.scopeUri().toString(), mOldInfo.manifestUrl());
nativeStart(mNativePointer, mTab.getWebContents());
return true;
}
......@@ -87,7 +93,7 @@ public class ManifestUpgradeDetectorFetcher extends EmptyTabObserver {
}
/**
* Updates which WebContents the native ManifestUpgradeDetectorFetcher is monitoring.
* Updates which WebContents the native WebApkUpdateDataFetcher is monitoring.
*/
private void updatePointers() {
nativeReplaceWebContents(mNativePointer, mTab.getWebContents());
......@@ -112,13 +118,12 @@ public class ManifestUpgradeDetectorFetcher extends EmptyTabObserver {
mOldInfo.source(), themeColor, backgroundColor, mOldInfo.webApkPackageName(),
mOldInfo.shellApkVersion(), mOldInfo.manifestUrl(), startUrl,
iconUrlToMurmur2HashMap);
mCallback.onGotManifestData(info, bestIconUrl);
mObserver.onGotManifestData(info, bestIconUrl);
}
private native long nativeInitialize(String scope, String webManifestUrl);
private native void nativeReplaceWebContents(
long nativeManifestUpgradeDetectorFetcher, WebContents webContents);
private native void nativeDestroy(long nativeManifestUpgradeDetectorFetcher);
private native void nativeStart(
long nativeManifestUpgradeDetectorFetcher, WebContents webContents);
long nativeWebApkUpdateDataFetcher, WebContents webContents);
private native void nativeDestroy(long nativeWebApkUpdateDataFetcher);
private native void nativeStart(long nativeWebApkUpdateDataFetcher, WebContents webContents);
}
......@@ -9,6 +9,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.provider.Settings;
import android.text.TextUtils;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
......@@ -16,15 +17,17 @@ import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.util.UrlUtilities;
import org.chromium.webapk.lib.client.WebApkVersion;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* WebApkUpdateManager manages when to check for updates to the WebAPK's Web Manifest, and sends
* an update request to the WebAPK Server when an update is needed.
*/
public class WebApkUpdateManager implements ManifestUpgradeDetector.Callback {
public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
private static final String TAG = "WebApkUpdateManager";
/** Number of milliseconds between checks for whether the WebAPK's Web Manifest has changed. */
......@@ -44,7 +47,7 @@ public class WebApkUpdateManager implements ManifestUpgradeDetector.Callback {
*/
private boolean mPreviousUpdateSucceeded;
private ManifestUpgradeDetector mUpgradeDetector;
private WebApkUpdateDataFetcher mFetcher;
/**
* Checks whether the WebAPK's Web Manifest has changed. Requests an updated WebAPK if the
......@@ -60,27 +63,28 @@ public class WebApkUpdateManager implements ManifestUpgradeDetector.Callback {
if (!shouldCheckIfWebManifestUpdated(storage, mInfo, mPreviousUpdateSucceeded)) return;
mUpgradeDetector = buildManifestUpgradeDetector(tab, mInfo);
mUpgradeDetector.start();
mFetcher = buildFetcher();
mFetcher.start(tab, mInfo, this);
}
public void destroy() {
destroyUpgradeDetector();
destroyFetcher();
}
@Override
public void onFinishedFetchingWebManifestForInitialUrl(
boolean needsUpgrade, WebApkInfo info, String bestIconUrl) {
onGotManifestData(needsUpgrade, info, bestIconUrl);
WebApkInfo fetchedInfo, String bestIconUrl) {
onGotManifestData(fetchedInfo, bestIconUrl);
}
@Override
public void onGotManifestData(boolean needsUpgrade, WebApkInfo info, String bestIconUrl) {
public void onGotManifestData(WebApkInfo fetchedInfo, String bestIconUrl) {
WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(mInfo.id());
storage.updateTimeOfLastCheckForUpdatedWebManifest();
boolean gotManifest = (info != null);
needsUpgrade |= isShellApkVersionOutOfDate(mInfo);
boolean gotManifest = (fetchedInfo != null);
boolean needsUpgrade = isShellApkVersionOutOfDate(mInfo)
|| (gotManifest && needsUpdate(mInfo, fetchedInfo, bestIconUrl));
Log.v(TAG, "Got Manifest: " + gotManifest);
Log.v(TAG, "WebAPK upgrade needed: " + needsUpgrade);
......@@ -97,7 +101,7 @@ public class WebApkUpdateManager implements ManifestUpgradeDetector.Callback {
// Web Manifests as the user navigates. For instance, the WebAPK's start_url might not
// point to a Web Manifest because start_url redirects to the WebAPK's main page.
if (gotManifest || needsUpgrade) {
destroyUpgradeDetector();
destroyFetcher();
}
if (!needsUpgrade) {
......@@ -111,8 +115,8 @@ public class WebApkUpdateManager implements ManifestUpgradeDetector.Callback {
// {@link onBuiltWebApk} being called.
recordUpdate(storage, false);
if (info != null) {
updateAsync(info, bestIconUrl);
if (fetchedInfo != null) {
updateAsync(fetchedInfo, bestIconUrl);
return;
}
......@@ -122,10 +126,10 @@ public class WebApkUpdateManager implements ManifestUpgradeDetector.Callback {
}
/**
* Builds {@link ManifestUpgradeDetector}. In a separate function for the sake of tests.
* Builds {@link WebApkUpdateDataFetcher}. In a separate function for the sake of tests.
*/
protected ManifestUpgradeDetector buildManifestUpgradeDetector(Tab tab, WebApkInfo info) {
return new ManifestUpgradeDetector(tab, info, this);
protected WebApkUpdateDataFetcher buildFetcher() {
return new WebApkUpdateDataFetcher();
}
/**
......@@ -142,13 +146,13 @@ public class WebApkUpdateManager implements ManifestUpgradeDetector.Callback {
}
/**
* Destroys {@link mUpgradeDetector}. In a separate function for the sake of tests.
* Destroys {@link mFetcher}. In a separate function for the sake of tests.
*/
protected void destroyUpgradeDetector() {
if (mUpgradeDetector == null) return;
protected void destroyFetcher() {
if (mFetcher == null) return;
mUpgradeDetector.destroy();
mUpgradeDetector = null;
mFetcher.destroy();
mFetcher = null;
}
/** Returns the current time. In a separate function for the sake of testing. */
......@@ -201,6 +205,11 @@ public class WebApkUpdateManager implements ManifestUpgradeDetector.Callback {
*/
private boolean shouldCheckIfWebManifestUpdated(
WebappDataStorage storage, WebApkInfo info, boolean previousUpdateSucceeded) {
if (CommandLine.getInstance().hasSwitch(
ChromeSwitches.CHECK_FOR_WEB_MANIFEST_UPDATE_ON_STARTUP)) {
return true;
}
// Updating WebAPKs requires "installation from unknown sources" to be enabled. It is
// confusing for a user to see a dialog asking them to enable "installation from unknown
// sources" when they are in the middle of using the WebAPK (as opposed to after requesting
......@@ -209,11 +218,6 @@ public class WebApkUpdateManager implements ManifestUpgradeDetector.Callback {
return false;
}
if (CommandLine.getInstance().hasSwitch(
ChromeSwitches.CHECK_FOR_WEB_MANIFEST_UPDATE_ON_STARTUP)) {
return true;
}
if (isShellApkVersionOutOfDate(info)) return true;
long now = currentTimeMillis();
......@@ -252,6 +256,55 @@ public class WebApkUpdateManager implements ManifestUpgradeDetector.Callback {
}
}
/**
* Checks whether the WebAPK needs to be updated.
* @param info Meta data from WebAPK's Android Manifest.
* @param fetchedInfo Fetched data for Web Manifest.
* @param bestFetchedIconUrl The icon URL in {@link fetchedInfo#iconUrlToMurmur2HashMap()} best
* suited for use as the launcher icon on this device.
*/
private boolean needsUpdate(WebApkInfo info, WebApkInfo fetchedInfo, String bestIconUrl) {
// We should have computed the Murmur2 hash for the bitmap at the best icon URL for
// {@link fetchedInfo} (but not the other icon URLs.)
String fetchedBestIconMurmur2Hash = fetchedInfo.iconUrlToMurmur2HashMap().get(bestIconUrl);
String bestIconMurmur2Hash =
findMurmur2HashForUrlIgnoringFragment(mInfo.iconUrlToMurmur2HashMap(), bestIconUrl);
return !TextUtils.equals(bestIconMurmur2Hash, fetchedBestIconMurmur2Hash)
|| !urlsMatchIgnoringFragments(
mInfo.scopeUri().toString(), fetchedInfo.scopeUri().toString())
|| !urlsMatchIgnoringFragments(
mInfo.manifestStartUrl(), fetchedInfo.manifestStartUrl())
|| !TextUtils.equals(mInfo.shortName(), fetchedInfo.shortName())
|| !TextUtils.equals(mInfo.name(), fetchedInfo.name())
|| mInfo.backgroundColor() != fetchedInfo.backgroundColor()
|| mInfo.themeColor() != fetchedInfo.themeColor()
|| mInfo.orientation() != fetchedInfo.orientation()
|| mInfo.displayMode() != fetchedInfo.displayMode();
}
/**
* Returns the Murmur2 hash for entry in {@link iconUrlToMurmur2HashMap} whose canonical
* representation, ignoring fragments, matches {@link iconUrlToMatch}.
*/
private String findMurmur2HashForUrlIgnoringFragment(
Map<String, String> iconUrlToMurmur2HashMap, String iconUrlToMatch) {
for (Map.Entry<String, String> entry : iconUrlToMurmur2HashMap.entrySet()) {
if (urlsMatchIgnoringFragments(entry.getKey(), iconUrlToMatch)) {
return entry.getValue();
}
}
return null;
}
/**
* Returns whether the urls match ignoring fragments. Canonicalizes the URLs prior to doing the
* comparison.
*/
protected boolean urlsMatchIgnoringFragments(String url1, String url2) {
return UrlUtilities.urlsMatchIgnoringFragments(url1, url2);
}
/**
* Called after either a request to update the WebAPK has been sent or the update process
* fails.
......
......@@ -1030,8 +1030,6 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/webapps/ChromeWebApkHost.java",
"java/src/org/chromium/chrome/browser/webapps/FullScreenActivity.java",
"java/src/org/chromium/chrome/browser/webapps/FullScreenDelegateFactory.java",
"java/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetector.java",
"java/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorFetcher.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkActivity0.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkActivity1.java",
......@@ -1046,6 +1044,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/webapps/WebApkInfo.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkInstaller.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkManagedActivity.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java",
"java/src/org/chromium/chrome/browser/webapps/WebApkVersionManager.java",
"java/src/org/chromium/chrome/browser/webapps/WebappActivity.java",
......@@ -1442,8 +1441,8 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialogTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/AddToHomescreenManagerTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/TestFetchStorageCallback.java",
"javatests/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorFetcherTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcherTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/WebappActivityTestBase.java",
"javatests/src/org/chromium/chrome/browser/webapps/WebappAuthenticatorTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/WebappDirectoryManagerTest.java",
......@@ -1520,7 +1519,6 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/snackbar/SnackbarCollectionUnitTest.java",
"junit/src/org/chromium/chrome/browser/superviseduser/SupervisedUserContentProviderUnitTest.java",
"junit/src/org/chromium/chrome/browser/tabstate/TabStateUnitTest.java",
"junit/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorTest.java",
"junit/src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java",
"junit/src/org/chromium/chrome/browser/webapps/WebappRegistryTest.java",
"junit/src/org/chromium/chrome/browser/webapps/WebApkInfoTest.java",
......
......@@ -19,9 +19,9 @@ import org.chromium.net.test.EmbeddedTestServer;
import java.util.HashMap;
/**
* Tests the ManifestUpgradeDetectorFetcher.
* Tests the WebApkUpdateDataFetcher.
*/
public class ManifestUpgradeDetectorFetcherTest extends ChromeTabbedActivityTestBase {
public class WebApkUpdateDataFetcherTest extends ChromeTabbedActivityTestBase {
private static final String WEB_MANIFEST_URL1 = "/chrome/test/data/banners/manifest.json";
// Name for Web Manifest at {@link WEB_MANIFEST_URL1}.
......@@ -48,10 +48,19 @@ public class ManifestUpgradeDetectorFetcherTest extends ChromeTabbedActivityTest
// CallbackHelper which blocks until the {@link ManifestUpgradeDetectorFetcher.Callback}
// callback is called.
private static class CallbackWaiter
extends CallbackHelper implements ManifestUpgradeDetectorFetcher.Callback {
extends CallbackHelper implements WebApkUpdateDataFetcher.Observer {
private String mName;
private String mBestIconMurmur2Hash;
@Override
public void onFinishedFetchingWebManifestForInitialUrl(
WebApkInfo fetchedInfo, String bestIconUrl) {
assertNull(mName);
mName = fetchedInfo.name();
mBestIconMurmur2Hash = fetchedInfo.iconUrlToMurmur2HashMap().get(bestIconUrl);
notifyCalled();
}
@Override
public void onGotManifestData(WebApkInfo fetchedInfo, String bestIconUrl) {
assertNull(mName);
......@@ -89,31 +98,30 @@ public class ManifestUpgradeDetectorFetcherTest extends ChromeTabbedActivityTest
}
/**
* Starts a ManifestUpgradeDetectorFetcher. Calls {@link callback} once the fetcher is done.
* Starts a WebApkUpdateDataFetcher. Calls {@link callback} once the fetcher is done.
*/
private void startManifestUpgradeDetectorFetcher(final String scopeUrl,
final String manifestUrl, final ManifestUpgradeDetectorFetcher.Callback callback) {
final ManifestUpgradeDetectorFetcher fetcher = new ManifestUpgradeDetectorFetcher();
private void startWebApkUpdateDataFetcher(final String scopeUrl,
final String manifestUrl, final WebApkUpdateDataFetcher.Observer observer) {
final WebApkUpdateDataFetcher fetcher = new WebApkUpdateDataFetcher();
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
WebApkInfo oldInfo = WebApkInfo.create("", "", scopeUrl, null, null, null, -1, -1,
-1, -1, -1, "random.package", -1, manifestUrl, null,
new HashMap<String, String>());
fetcher.start(mTab, oldInfo, callback);
fetcher.start(mTab, oldInfo, observer);
}
});
}
/**
* Test starting ManifestUpgradeDetectorFetcher while a page with the desired manifest URL is
* loading.
* Test starting WebApkUpdateDataFetcher while a page with the desired manifest URL is loading.
*/
@MediumTest
@Feature({"WebApk"})
public void testLaunchWithDesiredManifestUrl() throws Exception {
CallbackWaiter waiter = new CallbackWaiter();
startManifestUpgradeDetectorFetcher(mTestServer.getURL(WEB_MANIFEST_SCOPE),
startWebApkUpdateDataFetcher(mTestServer.getURL(WEB_MANIFEST_SCOPE),
mTestServer.getURL(WEB_MANIFEST_URL1), waiter);
WebappTestPage.navigateToPageWithServiceWorkerAndManifest(
......@@ -124,16 +132,16 @@ public class ManifestUpgradeDetectorFetcherTest extends ChromeTabbedActivityTest
}
/**
* Test starting ManifestUpgradeDetectorFetcher on page which uses a different manifest URL than
* the ManifestUpgradeDetectorFetcher is looking for. Check that the callback is only called
* once the user navigates to a page which uses the desired manifest URL.
* Test starting WebApkUpdateDataFetcher on page which uses a different manifest URL than the
* ManifestUpgradeDetectorFetcher is looking for. Check that the callback is only called once
* the user navigates to a page which uses the desired manifest URL.
*/
@MediumTest
@Feature({"Webapps"})
@RetryOnFailure
public void testLaunchWithDifferentManifestUrl() throws Exception {
CallbackWaiter waiter = new CallbackWaiter();
startManifestUpgradeDetectorFetcher(mTestServer.getURL(WEB_MANIFEST_SCOPE),
startWebApkUpdateDataFetcher(mTestServer.getURL(WEB_MANIFEST_SCOPE),
mTestServer.getURL(WEB_MANIFEST_URL2), waiter);
WebappTestPage.navigateToPageWithServiceWorkerAndManifest(
......@@ -153,7 +161,7 @@ public class ManifestUpgradeDetectorFetcherTest extends ChromeTabbedActivityTest
@Feature({"Webapps"})
public void testLargeIconMurmur2Hash() throws Exception {
CallbackWaiter waiter = new CallbackWaiter();
startManifestUpgradeDetectorFetcher(mTestServer.getURL(WEB_MANIFEST_SCOPE),
startWebApkUpdateDataFetcher(mTestServer.getURL(WEB_MANIFEST_SCOPE),
mTestServer.getURL(WEB_MANIFEST_WITH_LONG_ICON_MURMUR2_HASH), waiter);
WebappTestPage.navigateToPageWithServiceWorkerAndManifest(
mTestServer, mTab, WEB_MANIFEST_WITH_LONG_ICON_MURMUR2_HASH);
......
......@@ -9,22 +9,28 @@ import android.test.suitebuilder.annotation.MediumTest;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.blink_public.platform.WebDisplayMode;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeTabbedActivityTestBase;
import org.chromium.chrome.test.util.browser.WebappTestPage;
import org.chromium.content_public.common.ScreenOrientationValues;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.webapk.lib.client.WebApkVersion;
import java.util.HashMap;
import java.util.Map;
/**
* Tests ManifestUpgradeDetector. This class contains tests which cannot be done as JUnit tests.
* Tests WebApkUpdateManager. This class contains tests which cannot be done as JUnit tests.
*/
public class ManifestUpgradeDetectorTest extends ChromeTabbedActivityTestBase {
@CommandLineFlags.Add(ChromeSwitches.CHECK_FOR_WEB_MANIFEST_UPDATE_ON_STARTUP)
public class WebApkUpdateManagerTest extends ChromeTabbedActivityTestBase {
private static final String WEBAPK_PACKAGE = "test.package";
private static final String WEBAPK_ID = "webapk_id";
private static final String WEBAPK_MANIFEST_URL =
"/chrome/test/data/banners/manifest_one_icon.json";
......@@ -44,29 +50,38 @@ public class ManifestUpgradeDetectorTest extends ChromeTabbedActivityTestBase {
private EmbeddedTestServer mTestServer;
private Tab mTab;
// CallbackHelper which blocks until the {@link ManifestUpgradeDetector.Callback} callback is
// called.
private static class CallbackWaiter
extends CallbackHelper implements ManifestUpgradeDetector.Callback {
private String mName;
private boolean mNeedsUpgrade;
/**
* Subclass of {@link WebApkUpdateManager} which notifies the {@link CallbackHelper} passed to
* the constructor when it has been determined whether an update is needed.
*/
private static class TestWebApkUpdateManager extends WebApkUpdateManager {
private CallbackHelper mWaiter;
private boolean mNeedsUpdate = false;
public TestWebApkUpdateManager(CallbackHelper waiter) {
mWaiter = waiter;
}
@Override
public void onFinishedFetchingWebManifestForInitialUrl(
boolean needsUpgrade, WebApkInfo info, String bestIconUrl) {}
WebApkInfo fetchedInfo, String bestIconUrl) {
super.onFinishedFetchingWebManifestForInitialUrl(fetchedInfo, bestIconUrl);
mWaiter.notifyCalled();
}
public void onGotManifestData(boolean needsUpgrade, WebApkInfo info, String bestIconUrl) {
mName = info.name();
mNeedsUpgrade = needsUpgrade;
notifyCalled();
@Override
public void onGotManifestData(WebApkInfo fetchedInfo, String bestIconUrl) {
super.onGotManifestData(fetchedInfo, bestIconUrl);
mWaiter.notifyCalled();
}
public String name() {
return mName;
@Override
protected void updateAsync(WebApkInfo fetchedInfo, String bestIconUrl) {
mNeedsUpdate = true;
}
public boolean needsUpgrade() {
return mNeedsUpgrade;
public boolean needsUpdate() {
return mNeedsUpdate;
}
}
......@@ -108,6 +123,10 @@ public class ManifestUpgradeDetectorTest extends ChromeTabbedActivityTestBase {
Context context = getInstrumentation().getTargetContext();
mTestServer = EmbeddedTestServer.createAndStartServer(context);
mTab = getActivity().getActivityTab();
TestFetchStorageCallback callback = new TestFetchStorageCallback();
WebappRegistry.getInstance().register(WEBAPK_ID, callback);
callback.waitForCallback(0);
}
@Override
......@@ -121,24 +140,26 @@ public class ManifestUpgradeDetectorTest extends ChromeTabbedActivityTestBase {
startMainActivityOnBlankPage();
}
/**
* Starts a ManifestUpgradeDetector. Calls {@link callback} once the detector has fetched the
* Web Manifest and determined whether the WebAPK needs to be upgraded.
*/
private void startManifestUpgradeDetector(
CreationData creationData, final ManifestUpgradeDetector.Callback callback) {
WebApkInfo info = WebApkInfo.create("", "", creationData.scope, null, creationData.name,
creationData.shortName, creationData.displayMode, creationData.orientation, 0,
creationData.themeColor, creationData.backgroundColor, "", 0,
creationData.manifestUrl, creationData.startUrl,
creationData.iconUrlToMurmur2HashMap);
final ManifestUpgradeDetector detector = new ManifestUpgradeDetector(mTab, info, callback);
/** Checks whether a WebAPK update is needed. */
private boolean checkUpdateNeeded(final CreationData creationData) throws Exception {
CallbackHelper waiter = new CallbackHelper();
final TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(waiter);
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
detector.start();
WebApkInfo info = WebApkInfo.create(WEBAPK_ID, "", creationData.scope, null,
creationData.name, creationData.shortName, creationData.displayMode,
creationData.orientation, 0, creationData.themeColor,
creationData.backgroundColor, "", WebApkVersion.CURRENT_SHELL_APK_VERSION,
creationData.manifestUrl, creationData.startUrl,
creationData.iconUrlToMurmur2HashMap);
updateManager.updateIfNeeded(mTab, info);
}
});
waiter.waitForCallback(0);
return updateManager.needsUpdate();
}
/**
......@@ -150,20 +171,14 @@ public class ManifestUpgradeDetectorTest extends ChromeTabbedActivityTestBase {
@MediumTest
@Feature({"WebApk"})
public void testCanonicalUrlsIdenticalShouldNotUpgrade() throws Exception {
CallbackWaiter waiter = new CallbackWaiter();
// URL canonicalization should replace "%74" with 't'.
CreationData creationData = defaultCreationData(mTestServer);
creationData.startUrl = mTestServer.getURL(
"/chrome/test/data/banners/manifest_%74est_page.html");
startManifestUpgradeDetector(creationData, waiter);
WebappTestPage.navigateToPageWithServiceWorkerAndManifest(
mTestServer, mTab, WEBAPK_MANIFEST_URL);
waiter.waitForCallback(0);
assertEquals(WEBAPK_NAME, waiter.name());
assertFalse(waiter.needsUpgrade());
assertFalse(checkUpdateNeeded(creationData));
}
/**
......@@ -172,19 +187,13 @@ public class ManifestUpgradeDetectorTest extends ChromeTabbedActivityTestBase {
@MediumTest
@Feature({"WebApk"})
public void testCanonicalUrlsDifferentShouldUpgrade() throws Exception {
CallbackWaiter waiter = new CallbackWaiter();
// URL canonicalization should replace "%62" with 'b'.
CreationData creationData = defaultCreationData(mTestServer);
creationData.startUrl = mTestServer.getURL(
"/chrome/test/data/banners/manifest_%62est_page.html");
startManifestUpgradeDetector(creationData, waiter);
WebappTestPage.navigateToPageWithServiceWorkerAndManifest(
mTestServer, mTab, WEBAPK_MANIFEST_URL);
waiter.waitForCallback(0);
assertEquals(WEBAPK_NAME, waiter.name());
assertTrue(waiter.needsUpgrade());
assertTrue(checkUpdateNeeded(creationData));
}
}
......@@ -3231,14 +3231,14 @@ split_static_library("browser") {
"android/web_contents_factory.h",
"android/webapk/chrome_webapk_host.cc",
"android/webapk/chrome_webapk_host.h",
"android/webapk/manifest_upgrade_detector_fetcher.cc",
"android/webapk/manifest_upgrade_detector_fetcher.h",
"android/webapk/webapk_icon_hasher.cc",
"android/webapk/webapk_icon_hasher.h",
"android/webapk/webapk_installer.cc",
"android/webapk/webapk_installer.h",
"android/webapk/webapk_metrics.cc",
"android/webapk/webapk_metrics.h",
"android/webapk/webapk_update_data_fetcher.cc",
"android/webapk/webapk_update_data_fetcher.h",
"android/webapk/webapk_update_manager.cc",
"android/webapk/webapk_update_manager.h",
"android/webapk/webapk_web_manifest_checker.cc",
......@@ -4028,8 +4028,8 @@ if (android_java_ui) {
"../android/java/src/org/chromium/chrome/browser/util/UrlUtilities.java",
"../android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenManager.java",
"../android/java/src/org/chromium/chrome/browser/webapps/ChromeWebApkHost.java",
"../android/java/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorFetcher.java",
"../android/java/src/org/chromium/chrome/browser/webapps/WebApkInstaller.java",
"../android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java",
"../android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java",
"../android/java/src/org/chromium/chrome/browser/webapps/WebappRegistry.java",
]
......
......@@ -102,8 +102,8 @@
#include "chrome/browser/android/voice_search_tab_helper.h"
#include "chrome/browser/android/warmup_manager.h"
#include "chrome/browser/android/web_contents_factory.h"
#include "chrome/browser/android/webapk/manifest_upgrade_detector_fetcher.h"
#include "chrome/browser/android/webapk/webapk_installer.h"
#include "chrome/browser/android/webapk/webapk_update_data_fetcher.h"
#include "chrome/browser/android/webapk/webapk_update_manager.h"
#include "chrome/browser/android/webapps/add_to_homescreen_manager.h"
#include "chrome/browser/autofill/android/personal_data_manager_android.h"
......@@ -311,8 +311,6 @@ static base::android::RegistrationMethod kChromeRegisteredMethods[] = {
{"LayerTitleCache", RegisterLayerTitleCache},
{"SpecialLocaleHandler", RegisterSpecialLocaleHandler},
{"LogoBridge", RegisterLogoBridge},
{"ManifestUpgradeDetectorFetcher",
ManifestUpgradeDetectorFetcher::Register},
{"MediaDrmCredentialManager",
MediaDrmCredentialManager::RegisterMediaDrmCredentialManager},
{"MostVisitedSites", MostVisitedSitesBridge::Register},
......@@ -417,6 +415,7 @@ static base::android::RegistrationMethod kChromeRegisteredMethods[] = {
{"WarmupManager", RegisterWarmupManager},
{"WebApkInstaller", WebApkInstaller::Register},
{"WebApkUpdateManager", WebApkUpdateManager::Register},
{"WebApkUpdateDataFetcher", WebApkUpdateDataFetcher::Register},
{"WebContentsFactory", RegisterWebContentsFactory},
{"WebsitePreferenceBridge", RegisterWebsitePreferenceBridge},
{"WebsiteSettingsPopupAndroid",
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/android/webapk/manifest_upgrade_detector_fetcher.h"
#include "chrome/browser/android/webapk/webapk_update_data_fetcher.h"
#include <jni.h>
#include <vector>
......@@ -16,7 +16,7 @@
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/manifest.h"
#include "jni/ManifestUpgradeDetectorFetcher_jni.h"
#include "jni/WebApkUpdateDataFetcher_jni.h"
#include "third_party/smhasher/src/MurmurHash2.h"
#include "ui/gfx/android/java_bitmap.h"
#include "ui/gfx/codec/png_codec.h"
......@@ -42,12 +42,12 @@ jlong Initialize(JNIEnv* env,
GURL scope(base::android::ConvertJavaStringToUTF8(env, java_scope_url));
GURL web_manifest_url(base::android::ConvertJavaStringToUTF8(
env, java_web_manifest_url));
ManifestUpgradeDetectorFetcher* fetcher =
new ManifestUpgradeDetectorFetcher(env, obj, scope, web_manifest_url);
WebApkUpdateDataFetcher* fetcher =
new WebApkUpdateDataFetcher(env, obj, scope, web_manifest_url);
return reinterpret_cast<intptr_t>(fetcher);
}
ManifestUpgradeDetectorFetcher::ManifestUpgradeDetectorFetcher(
WebApkUpdateDataFetcher::WebApkUpdateDataFetcher(
JNIEnv* env,
jobject obj,
const GURL& scope,
......@@ -60,15 +60,15 @@ ManifestUpgradeDetectorFetcher::ManifestUpgradeDetectorFetcher(
java_ref_.Reset(env, obj);
}
ManifestUpgradeDetectorFetcher::~ManifestUpgradeDetectorFetcher() {
WebApkUpdateDataFetcher::~WebApkUpdateDataFetcher() {
}
// static
bool ManifestUpgradeDetectorFetcher::Register(JNIEnv* env) {
bool WebApkUpdateDataFetcher::Register(JNIEnv* env) {
return RegisterNativesImpl(env);
}
void ManifestUpgradeDetectorFetcher::ReplaceWebContents(
void WebApkUpdateDataFetcher::ReplaceWebContents(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& java_web_contents) {
......@@ -77,12 +77,12 @@ void ManifestUpgradeDetectorFetcher::ReplaceWebContents(
content::WebContentsObserver::Observe(web_contents);
}
void ManifestUpgradeDetectorFetcher::Destroy(JNIEnv* env,
const JavaParamRef<jobject>& obj) {
void WebApkUpdateDataFetcher::Destroy(JNIEnv* env,
const JavaParamRef<jobject>& obj) {
delete this;
}
void ManifestUpgradeDetectorFetcher::Start(
void WebApkUpdateDataFetcher::Start(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& java_web_contents) {
......@@ -91,11 +91,11 @@ void ManifestUpgradeDetectorFetcher::Start(
FetchInstallableData();
}
void ManifestUpgradeDetectorFetcher::DidStopLoading() {
void WebApkUpdateDataFetcher::DidStopLoading() {
FetchInstallableData();
}
void ManifestUpgradeDetectorFetcher::FetchInstallableData() {
void WebApkUpdateDataFetcher::FetchInstallableData() {
GURL url = web_contents()->GetLastCommittedURL();
// DidStopLoading() can be called multiple times for a single URL. Only fetch
......@@ -119,11 +119,11 @@ void ManifestUpgradeDetectorFetcher::FetchInstallableData() {
InstallableManager::FromWebContents(web_contents());
installable_manager->GetData(
params,
base::Bind(&ManifestUpgradeDetectorFetcher::OnDidGetInstallableData,
base::Bind(&WebApkUpdateDataFetcher::OnDidGetInstallableData,
weak_ptr_factory_.GetWeakPtr()));
}
void ManifestUpgradeDetectorFetcher::OnDidGetInstallableData(
void WebApkUpdateDataFetcher::OnDidGetInstallableData(
const InstallableData& data) {
// If the manifest is empty, it means the current WebContents doesn't
// associate with a Web Manifest. In such case, we ignore the empty manifest
......@@ -154,11 +154,11 @@ void ManifestUpgradeDetectorFetcher::OnDidGetInstallableData(
icon_hasher_->DownloadAndComputeMurmur2Hash(
profile->GetRequestContext(),
data.icon_url,
base::Bind(&ManifestUpgradeDetectorFetcher::OnGotIconMurmur2Hash,
base::Bind(&WebApkUpdateDataFetcher::OnGotIconMurmur2Hash,
weak_ptr_factory_.GetWeakPtr()));
}
void ManifestUpgradeDetectorFetcher::OnGotIconMurmur2Hash(
void WebApkUpdateDataFetcher::OnGotIconMurmur2Hash(
const std::string& best_icon_murmur2_hash) {
icon_hasher_.reset();
......@@ -171,7 +171,7 @@ void ManifestUpgradeDetectorFetcher::OnGotIconMurmur2Hash(
OnDataAvailable(info_, best_icon_murmur2_hash, best_icon_);
}
void ManifestUpgradeDetectorFetcher::OnDataAvailable(
void WebApkUpdateDataFetcher::OnDataAvailable(
const ShortcutInfo& info,
const std::string& best_icon_murmur2_hash,
const SkBitmap& best_icon_bitmap) {
......@@ -195,7 +195,7 @@ void ManifestUpgradeDetectorFetcher::OnDataAvailable(
ScopedJavaLocalRef<jobjectArray> java_icon_urls =
base::android::ToJavaArrayOfStrings(env, info.icon_urls);
Java_ManifestUpgradeDetectorFetcher_onDataAvailable(
Java_WebApkUpdateDataFetcher_onDataAvailable(
env, java_ref_, java_url, java_scope, java_name, java_short_name,
java_best_icon_url, java_best_icon_murmur2_hash, java_best_bitmap,
java_icon_urls, info.display, info.orientation, info.theme_color,
......
......@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_ANDROID_WEBAPK_MANIFEST_UPGRADE_DETECTOR_FETCHER_H_
#define CHROME_BROWSER_ANDROID_WEBAPK_MANIFEST_UPGRADE_DETECTOR_FETCHER_H_
#ifndef CHROME_BROWSER_ANDROID_WEBAPK_WEBAPK_UPDATE_DATA_FETCHER_H_
#define CHROME_BROWSER_ANDROID_WEBAPK_WEBAPK_UPDATE_DATA_FETCHER_H_
#include "base/android/jni_android.h"
#include "base/android/jni_weak_ref.h"
......@@ -22,15 +22,15 @@ class GURL;
struct InstallableData;
class WebApkIconHasher;
// ManifestUpgradeDetectorFetcher is the C++ counterpart of
// org.chromium.chrome.browser's ManifestUpgradeDetectorFetcher in Java. It is
// created via a JNI (Initialize) call and MUST BE DESTROYED via Destroy().
class ManifestUpgradeDetectorFetcher : public content::WebContentsObserver {
// WebApkUpdateDataFetcher is the C++ counterpart of
// org.chromium.chrome.browser's WebApkUpdateDataFetcher in Java. It is created
// via a JNI (Initialize) call and MUST BE DESTROYED via Destroy().
class WebApkUpdateDataFetcher : public content::WebContentsObserver {
public:
ManifestUpgradeDetectorFetcher(JNIEnv* env,
jobject obj,
const GURL& scope,
const GURL& web_manifest_url);
WebApkUpdateDataFetcher(JNIEnv* env,
jobject obj,
const GURL& scope,
const GURL& web_manifest_url);
// Replaces the WebContents that is being observed.
void ReplaceWebContents(
......@@ -50,7 +50,7 @@ class ManifestUpgradeDetectorFetcher : public content::WebContentsObserver {
static bool Register(JNIEnv* env);
private:
~ManifestUpgradeDetectorFetcher() override;
~WebApkUpdateDataFetcher() override;
// content::WebContentsObserver:
void DidStopLoading() override;
......@@ -87,9 +87,9 @@ class ManifestUpgradeDetectorFetcher : public content::WebContentsObserver {
ShortcutInfo info_;
SkBitmap best_icon_;
base::WeakPtrFactory<ManifestUpgradeDetectorFetcher> weak_ptr_factory_;
base::WeakPtrFactory<WebApkUpdateDataFetcher> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(ManifestUpgradeDetectorFetcher);
DISALLOW_COPY_AND_ASSIGN(WebApkUpdateDataFetcher);
};
#endif // CHROME_BROWSER_ANDROID_WEBAPK_MANIFEST_UPGRADE_DETECTOR_FETCHER_H_
#endif // CHROME_BROWSER_ANDROID_WEBAPK_WEBAPK_UPDATE_DATA_FETCHER_H_
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