Commit e978d2c0 authored by Evan Stade's avatar Evan Stade Committed by Commit Bot

Fetch splash image earlier during web app installs.

This changes ShortcutHelper to download the splash image for a webapp
while the webapp is being installed, rather than after. If the splash
image is downloaded before the webapp installation process finishes,
the splash image will be stored in a local map until it can be
transferred to the WebappDataStorage object.

This simplifies the code and fixes a potential UAF.

Bug: 1147913
Change-Id: I9405ee9cc304eaf8b9ff2bbaca17c1c63cfd6e65
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2533031
Commit-Queue: Evan Stade <estade@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#826837}
parent de0d2b8f
...@@ -36,7 +36,6 @@ import org.chromium.base.Log; ...@@ -36,7 +36,6 @@ import org.chromium.base.Log;
import org.chromium.base.StrictModeContext; import org.chromium.base.StrictModeContext;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.task.AsyncTask; import org.chromium.base.task.AsyncTask;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider; import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
...@@ -55,8 +54,10 @@ import org.chromium.ui.base.ViewUtils; ...@@ -55,8 +54,10 @@ import org.chromium.ui.base.ViewUtils;
import org.chromium.ui.widget.Toast; import org.chromium.ui.widget.Toast;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
...@@ -147,6 +148,12 @@ public class ShortcutHelper { ...@@ -147,6 +148,12 @@ public class ShortcutHelper {
private static ShortcutManager sShortcutManager; private static ShortcutManager sShortcutManager;
// Holds splash images for web apps that are currently being installed. After installation is
// complete, the image associated with the web app will be moved to the appropriate {@link
// WebappDataStorage}.
@VisibleForTesting
public static Map<String, Bitmap> sSplashImageMap = new HashMap<>();
/** Helper for generating home screen shortcuts. */ /** Helper for generating home screen shortcuts. */
public static class Delegate { public static class Delegate {
/** /**
...@@ -195,8 +202,7 @@ public class ShortcutHelper { ...@@ -195,8 +202,7 @@ public class ShortcutHelper {
/** /**
* Adds home screen shortcut which opens in a {@link WebappActivity}. Creates web app * Adds home screen shortcut which opens in a {@link WebappActivity}. Creates web app
* home screen shortcut and registers web app asynchronously. Calls * home screen shortcut and registers web app asynchronously.
* ShortcutHelper::OnWebappDataStored() when done.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
@CalledByNative @CalledByNative
...@@ -204,7 +210,7 @@ public class ShortcutHelper { ...@@ -204,7 +210,7 @@ public class ShortcutHelper {
final String userTitle, final String name, final String shortName, final String iconUrl, final String userTitle, final String name, final String shortName, final String iconUrl,
final Bitmap icon, boolean isIconAdaptive, @WebDisplayMode final int displayMode, final Bitmap icon, boolean isIconAdaptive, @WebDisplayMode final int displayMode,
final int orientation, final int source, final long themeColor, final int orientation, final int source, final long themeColor,
final long backgroundColor, final long callbackPointer) { final long backgroundColor) {
new AsyncTask<Intent>() { new AsyncTask<Intent>() {
@Override @Override
protected Intent doInBackground() { protected Intent doInBackground() {
...@@ -225,18 +231,20 @@ public class ShortcutHelper { ...@@ -225,18 +231,20 @@ public class ShortcutHelper {
protected void onPostExecute(final Intent resultIntent) { protected void onPostExecute(final Intent resultIntent) {
sDelegate.addShortcutToHomescreen(userTitle, icon, isIconAdaptive, resultIntent); sDelegate.addShortcutToHomescreen(userTitle, icon, isIconAdaptive, resultIntent);
// Store the webapp data so that it is accessible without the intent. Once this // Store the webapp data so that it is accessible without the intent.
// process is complete, call back to native code to start the splash image
// download.
WebappRegistry.getInstance().register(id, storage -> { WebappRegistry.getInstance().register(id, storage -> {
BrowserServicesIntentDataProvider intentDataProvider = BrowserServicesIntentDataProvider intentDataProvider =
WebappIntentDataProviderFactory.create(resultIntent); WebappIntentDataProviderFactory.create(resultIntent);
assert intentDataProvider != null; assert intentDataProvider != null;
if (intentDataProvider != null) { if (intentDataProvider != null) {
storage.updateFromWebappIntentDataProvider(intentDataProvider); storage.updateFromWebappIntentDataProvider(intentDataProvider);
if (callbackPointer != 0) { }
ShortcutHelperJni.get().onWebappDataStored(callbackPointer);
} // If the image is not yet downloaded (i.e. |splashImage| is null), it will be
// stored later when native calls storeWebappSplashImage().
Bitmap splashImage = sSplashImageMap.remove(id);
if (splashImage != null) {
storeWebappSplashImage(id, splashImage);
} }
}); });
if (shouldShowToastWhenAddingShortcut()) { if (shouldShowToastWhenAddingShortcut()) {
...@@ -329,7 +337,10 @@ public class ShortcutHelper { ...@@ -329,7 +337,10 @@ public class ShortcutHelper {
@CalledByNative @CalledByNative
private static void storeWebappSplashImage(final String id, final Bitmap splashImage) { private static void storeWebappSplashImage(final String id, final Bitmap splashImage) {
final WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(id); final WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(id);
if (storage != null) { if (storage == null) {
// The app is not installed yet; put it in this map for now.
sSplashImageMap.put(id, splashImage);
} else {
new AsyncTask<String>() { new AsyncTask<String>() {
@Override @Override
protected String doInBackground() { protected String doInBackground() {
...@@ -812,9 +823,4 @@ public class ShortcutHelper { ...@@ -812,9 +823,4 @@ public class ShortcutHelper {
storage.setShouldForceUpdate(true); storage.setShouldForceUpdate(true);
} }
} }
@NativeMethods
interface Natives {
void onWebappDataStored(long callbackPointer);
}
} }
...@@ -311,6 +311,8 @@ public class AddToHomescreenTest { ...@@ -311,6 +311,8 @@ public class AddToHomescreenTest {
Criteria.checkThat(dataStorageFactory.mSplashImage, Matchers.notNullValue()); Criteria.checkThat(dataStorageFactory.mSplashImage, Matchers.notNullValue());
}); });
Assert.assertTrue(ShortcutHelper.sSplashImageMap.isEmpty());
// Test that bitmap sizes match expectations. // Test that bitmap sizes match expectations.
int idealSize = mActivity.getResources().getDimensionPixelSize( int idealSize = mActivity.getResources().getDimensionPixelSize(
R.dimen.webapp_splash_image_size_ideal); R.dimen.webapp_splash_image_size_ideal);
......
...@@ -74,15 +74,11 @@ void GetIconSizes() { ...@@ -74,15 +74,11 @@ void GetIconSizes() {
} }
// Adds a shortcut which opens in a fullscreen window to the launcher. // Adds a shortcut which opens in a fullscreen window to the launcher.
// |splash_image_callback| will be invoked once the Java-side operation has void AddWebappWithSkBitmap(content::WebContents* web_contents,
// completed. This is necessary as Java will asynchronously create and const ShortcutInfo& info,
// populate a WebappDataStorage object for standalone-capable sites. This must
// exist before the splash image can be stored.
void AddWebappWithSkBitmap(const ShortcutInfo& info,
const std::string& webapp_id, const std::string& webapp_id,
const SkBitmap& icon_bitmap, const SkBitmap& icon_bitmap,
bool is_icon_maskable, bool is_icon_maskable) {
base::OnceClosure splash_image_callback) {
// Send the data to the Java side to create the shortcut. // Send the data to the Java side to create the shortcut.
JNIEnv* env = base::android::AttachCurrentThread(); JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jstring> java_webapp_id = ScopedJavaLocalRef<jstring> java_webapp_id =
...@@ -104,25 +100,23 @@ void AddWebappWithSkBitmap(const ShortcutInfo& info, ...@@ -104,25 +100,23 @@ void AddWebappWithSkBitmap(const ShortcutInfo& info,
if (!icon_bitmap.drawsNothing()) if (!icon_bitmap.drawsNothing())
java_bitmap = gfx::ConvertToJavaBitmap(icon_bitmap); java_bitmap = gfx::ConvertToJavaBitmap(icon_bitmap);
// The callback will need to be run after shortcut creation completes in order
// to download the splash image and save it to the WebappDataStorage. Create a
// copy of the callback here and send the pointer to Java, which will send it
// back once the asynchronous shortcut creation process finishes.
uintptr_t callback_pointer = reinterpret_cast<uintptr_t>(
new base::OnceClosure(std::move(splash_image_callback)));
Java_ShortcutHelper_addWebapp( Java_ShortcutHelper_addWebapp(
env, java_webapp_id, java_url, java_scope_url, java_user_title, java_name, env, java_webapp_id, java_url, java_scope_url, java_user_title, java_name,
java_short_name, java_best_primary_icon_url, java_bitmap, java_short_name, java_best_primary_icon_url, java_bitmap,
is_icon_maskable, static_cast<int>(info.display), is_icon_maskable, static_cast<int>(info.display),
static_cast<int>(info.orientation), info.source, static_cast<int>(info.orientation), info.source,
OptionalSkColorToJavaColor(info.theme_color), OptionalSkColorToJavaColor(info.theme_color),
OptionalSkColorToJavaColor(info.background_color), callback_pointer); OptionalSkColorToJavaColor(info.background_color));
// Start downloading the splash image in parallel with the app install.
content::ManifestIconDownloader::Download(
web_contents, info.splash_image_url, info.ideal_splash_image_size_in_px,
info.minimum_splash_image_size_in_px,
base::BindOnce(&ShortcutHelper::StoreWebappSplashImage, webapp_id));
} }
// Adds a shortcut which opens in a browser tab to the launcher. // Adds a shortcut which opens in a browser tab to the launcher.
void AddShortcutWithSkBitmap(content::WebContents* web_contents, void AddShortcutWithSkBitmap(const ShortcutInfo& info,
const ShortcutInfo& info,
const std::string& id, const std::string& id,
const SkBitmap& icon_bitmap, const SkBitmap& icon_bitmap,
bool is_icon_maskable) { bool is_icon_maskable) {
...@@ -180,16 +174,11 @@ void ShortcutHelper::AddToLauncherWithSkBitmap( ...@@ -180,16 +174,11 @@ void ShortcutHelper::AddToLauncherWithSkBitmap(
if (info.display == blink::mojom::DisplayMode::kStandalone || if (info.display == blink::mojom::DisplayMode::kStandalone ||
info.display == blink::mojom::DisplayMode::kFullscreen || info.display == blink::mojom::DisplayMode::kFullscreen ||
info.display == blink::mojom::DisplayMode::kMinimalUi) { info.display == blink::mojom::DisplayMode::kMinimalUi) {
AddWebappWithSkBitmap( AddWebappWithSkBitmap(web_contents, info, webapp_id, icon_bitmap,
info, webapp_id, icon_bitmap, is_icon_maskable, is_icon_maskable);
base::BindOnce(&ShortcutHelper::FetchSplashScreenImage, web_contents,
info.splash_image_url,
info.ideal_splash_image_size_in_px,
info.minimum_splash_image_size_in_px, webapp_id));
return; return;
} }
AddShortcutWithSkBitmap(web_contents, info, webapp_id, icon_bitmap, AddShortcutWithSkBitmap(info, webapp_id, icon_bitmap, is_icon_maskable);
is_icon_maskable);
} }
void ShortcutHelper::ShowWebApkInstallInProgressToast() { void ShortcutHelper::ShowWebApkInstallInProgressToast() {
...@@ -233,21 +222,6 @@ int ShortcutHelper::GetIdealShortcutIconSizeInPx() { ...@@ -233,21 +222,6 @@ int ShortcutHelper::GetIdealShortcutIconSizeInPx() {
return g_ideal_shortcut_icon_size; return g_ideal_shortcut_icon_size;
} }
// static
void ShortcutHelper::FetchSplashScreenImage(
content::WebContents* web_contents,
const GURL& image_url,
const int ideal_splash_image_size_in_px,
const int minimum_splash_image_size_in_px,
const std::string& webapp_id) {
// This is a fire and forget task. It is not vital for the splash screen image
// to be downloaded so if the downloader returns false there is no fallback.
content::ManifestIconDownloader::Download(
web_contents, image_url, ideal_splash_image_size_in_px,
minimum_splash_image_size_in_px,
base::BindOnce(&ShortcutHelper::StoreWebappSplashImage, webapp_id));
}
// static // static
void ShortcutHelper::StoreWebappSplashImage(const std::string& webapp_id, void ShortcutHelper::StoreWebappSplashImage(const std::string& webapp_id,
const SkBitmap& splash_image) { const SkBitmap& splash_image) {
...@@ -382,19 +356,3 @@ bool ShortcutHelper::DoesAndroidSupportMaskableIcons() { ...@@ -382,19 +356,3 @@ bool ShortcutHelper::DoesAndroidSupportMaskableIcons() {
void ShortcutHelper::SetIdealShortcutSizeForTesting(int size) { void ShortcutHelper::SetIdealShortcutSizeForTesting(int size) {
g_ideal_shortcut_icon_size = size; g_ideal_shortcut_icon_size = size;
} }
// Callback used by Java when the shortcut has been created.
// |splash_image_callback| is a pointer to a base::OnceClosure allocated in
// AddShortcutWithSkBitmap, so reinterpret_cast it back and run it.
//
// This callback should only ever be called when the shortcut was for a
// webapp-capable site; otherwise, |splash_image_callback| will have never been
// allocated and doesn't need to be run or deleted.
void JNI_ShortcutHelper_OnWebappDataStored(JNIEnv* env,
jlong jsplash_image_callback) {
DCHECK(jsplash_image_callback);
base::OnceClosure* splash_image_callback =
reinterpret_cast<base::OnceClosure*>(jsplash_image_callback);
std::move(*splash_image_callback).Run();
delete splash_image_callback;
}
...@@ -66,16 +66,6 @@ class ShortcutHelper { ...@@ -66,16 +66,6 @@ class ShortcutHelper {
// Returns the ideal size for a shortcut icon of a WebAPK. // Returns the ideal size for a shortcut icon of a WebAPK.
static int GetIdealShortcutIconSizeInPx(); static int GetIdealShortcutIconSizeInPx();
// Fetches the splash screen image and stores it inside the WebappDataStorage
// of the webapp. The WebappDataStorage object *must* have been previously
// created by AddToLauncherWithSkBitmap(); this method should be passed as a
// closure to that method.
static void FetchSplashScreenImage(content::WebContents* web_contents,
const GURL& image_url,
const int ideal_splash_image_size_in_px,
const int minimum_splash_image_size_in_px,
const std::string& webapp_id);
// Stores the webapp splash screen in the WebappDataStorage associated with // Stores the webapp splash screen in the WebappDataStorage associated with
// |webapp_id|. // |webapp_id|.
static void StoreWebappSplashImage(const std::string& webapp_id, static void StoreWebappSplashImage(const std::string& webapp_id,
......
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