First part of fetching a favicon
This change adds a new class (favicon_caller) which wraps the calls to the API large_icon_service in order to request and fetch a favicon for a particular URL. The implementation is similar to content_suggestions_service.cc.
The website_parent_approval package now exposes new helper methods for Java, that utilise favicon_caller in order to fetch a favicon. These new JNI methods will be consumed by WebsiteParentApproval in a follow up change.
Bug: 1340912
Change-Id: I6457b560c51f31cd54ae8b715d98881f60b3e1c5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3810619
Reviewed-by: Nohemi Fernandez <[email protected]>
Commit-Queue: Anthi Orfanou <[email protected]>
Reviewed-by: Rainhard Findling <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1043425}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 9384821..96c298358 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3255,6 +3255,8 @@
"ssl/chrome_security_state_model_delegate.h",
"ssl/known_interception_disclosure_infobar.cc",
"ssl/known_interception_disclosure_infobar.h",
+ "supervised_user/android/favicon_fetcher.cc",
+ "supervised_user/android/favicon_fetcher.h",
"sync/android/sync_service_android_bridge.cc",
"sync/android/sync_service_android_bridge.h",
"sync/glue/synced_tab_delegate_android.cc",
diff --git a/chrome/browser/supervised_user/android/favicon_fetcher.cc b/chrome/browser/supervised_user/android/favicon_fetcher.cc
new file mode 100644
index 0000000..73e0890
--- /dev/null
+++ b/chrome/browser/supervised_user/android/favicon_fetcher.cc
@@ -0,0 +1,120 @@
+// Copyright 2022 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.
+
+#include "chrome/browser/supervised_user/android/favicon_fetcher.h"
+
+#include <cstddef>
+#include <memory>
+
+#include "base/android/callback_android.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "chrome/browser/favicon/large_icon_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/favicon/core/large_icon_service.h"
+#include "components/favicon_base/favicon_callback.h"
+#include "components/favicon_base/favicon_types.h"
+#include "content/public/browser/browser_context.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "ui/gfx/android/java_bitmap.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_skia_rep.h"
+#include "url/gurl.h"
+
+FaviconFetcher::FaviconFetcher(Profile* profile)
+ : large_icon_service_(
+ LargeIconServiceFactory::GetForBrowserContext(profile)) {}
+
+void FaviconFetcher::OnFaviconDownloaded(
+ const GURL& url,
+ const base::android::ScopedJavaGlobalRef<jobject>& callback,
+ FaviconDimensions faviconDimensions,
+ favicon_base::GoogleFaviconServerRequestStatus status) {
+ if (status == favicon_base::GoogleFaviconServerRequestStatus::SUCCESS) {
+ FetchFavicon(url, false, faviconDimensions.min_source_size_in_pixel,
+ faviconDimensions.desired_size_in_pixel, std::move(callback));
+ } else {
+ LOG(WARNING)
+ << "Unable to obtain a favicon image with the required specs for "
+ << url.host();
+ delete this;
+ }
+}
+
+void FaviconFetcher::OnGetFaviconFromCacheFinished(
+ const GURL& url,
+ bool continue_to_server,
+ const base::android::ScopedJavaGlobalRef<jobject>& callback,
+ FaviconDimensions faviconDimensions,
+ const favicon_base::LargeIconImageResult& image_result) {
+ if (!image_result.image.IsEmpty()) {
+ SkBitmap faviconBitmap =
+ image_result.image.AsImageSkia().GetRepresentation(1.0f).GetBitmap();
+ // Return the image to the caller by executing the callback and destroy this
+ // instance.
+ base::android::RunObjectCallbackAndroid(
+ callback, gfx::ConvertToJavaBitmap(faviconBitmap));
+ delete this;
+ return;
+ }
+
+ // Try to fetch the favicon from a Google favicon server.
+ if (continue_to_server) {
+ net::NetworkTrafficAnnotationTag traffic_annotation =
+ net::DefineNetworkTrafficAnnotation("favicon_fetcher_get_favicon", R"(
+ semantics {
+ sender: "Favicon"
+ description:
+ "Sends a request to a Google server to retrieve a favicon bitmap "
+ "for a URL that a supervised user has requested approval to "
+ "access from their parents."
+ trigger:
+ "A request can be sent if Chrome does not have a favicon for a "
+ "particular page that supervised users are requesting approval to "
+ "access from their parents."
+ data: "Page URL and desired icon size."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting: "This feature cannot be disabled by settings."
+ policy_exception_justification: "Not implemented."
+ }
+ comments: "No policy is necessary as the request cannot be turned off via settings."
+ "A request for a favicon will always be sent for supervised users."
+ )");
+ large_icon_service_
+ ->GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
+ url,
+ /*may_page_url_be_private=*/true,
+ /*should_trim_page_url_path=*/false, traffic_annotation,
+ base::BindOnce(&FaviconFetcher::OnFaviconDownloaded,
+ base::Unretained(this), url, std::move(callback),
+ faviconDimensions));
+ } else {
+ delete this;
+ }
+}
+
+void FaviconFetcher::FetchFavicon(
+ const GURL& url,
+ bool continue_to_server,
+ int min_source_side_size_in_pixel,
+ int desired_side_size_in_pixel,
+ const base::android::ScopedJavaGlobalRef<jobject>& callback) {
+ FaviconDimensions faviconDimensions;
+ faviconDimensions.min_source_size_in_pixel = min_source_side_size_in_pixel;
+ faviconDimensions.desired_size_in_pixel = desired_side_size_in_pixel;
+
+ large_icon_service_->GetLargeIconImageOrFallbackStyleForPageUrl(
+ url, faviconDimensions.min_source_size_in_pixel,
+ faviconDimensions.desired_size_in_pixel,
+ base::BindOnce(&FaviconFetcher::OnGetFaviconFromCacheFinished,
+ base::Unretained(this), url, continue_to_server,
+ std::move(callback), faviconDimensions),
+ &task_tracker_);
+}
diff --git a/chrome/browser/supervised_user/android/favicon_fetcher.h b/chrome/browser/supervised_user/android/favicon_fetcher.h
new file mode 100644
index 0000000..66810c94
--- /dev/null
+++ b/chrome/browser/supervised_user/android/favicon_fetcher.h
@@ -0,0 +1,68 @@
+// Copyright 2022 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.
+
+#ifndef CHROME_BROWSER_SUPERVISED_USER_ANDROID_FAVICON_FETCHER_H_
+#define CHROME_BROWSER_SUPERVISED_USER_ANDROID_FAVICON_FETCHER_H_
+
+#include <string>
+#include "base/memory/raw_ptr.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/favicon/core/large_icon_service.h"
+#include "components/favicon_base/favicon_callback.h"
+#include "components/favicon_base/favicon_types.h"
+#include "ui/gfx/image/image.h"
+#include "url/gurl.h"
+
+class FaviconFetcher {
+ public:
+ explicit FaviconFetcher(Profile* profile);
+
+ FaviconFetcher() = delete;
+
+ FaviconFetcher(const FaviconFetcher&) = delete;
+
+ ~FaviconFetcher() = default;
+
+ // Initiates a request to fetch a favicon for a specific url.
+ // Wraps the calls to a service that obtains the favicon and returns
+ // through the provided callback. The FaviconFetcher will delete
+ // itself upon callback completion.
+ void FetchFavicon(
+ const GURL& url,
+ bool continue_to_server,
+ int min_source_size_in_pixel,
+ int desired_size_in_pixel,
+ const base::android::ScopedJavaGlobalRef<jobject>& callback);
+
+ private:
+ // Wrapper for favicon specs.
+ struct FaviconDimensions {
+ int min_source_size_in_pixel;
+ // Set to zero to return icon without rescaling.
+ int desired_size_in_pixel;
+ };
+
+ // Provides access to the status of a request for fetching a favicon.
+ void OnFaviconDownloaded(
+ const GURL& url,
+ const base::android::ScopedJavaGlobalRef<jobject>& callback,
+ FaviconDimensions faviconDimensions,
+ favicon_base::GoogleFaviconServerRequestStatus status);
+
+ // Returns the downloaded favicon through the provided callback.
+ // If the favicon is not present in cache, requests it from google servers.
+ void OnGetFaviconFromCacheFinished(
+ const GURL& url,
+ bool continue_to_server,
+ const base::android::ScopedJavaGlobalRef<jobject>& callback,
+ FaviconDimensions faviconDimensions,
+ const favicon_base::LargeIconImageResult& image_result);
+
+ // Required for execution of icon fetching.
+ raw_ptr<favicon::LargeIconService> large_icon_service_;
+ base::CancelableTaskTracker task_tracker_;
+};
+
+#endif // CHROME_BROWSER_SUPERVISED_USER_ANDROID_FAVICON_CALLER_H_
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java
index a3b8f89..b3a31d4 100644
--- a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java
+++ b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java
@@ -4,6 +4,9 @@
package org.chromium.chrome.browser.supervised_user;
+import android.graphics.Bitmap;
+
+import org.chromium.base.Callback;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.chrome.browser.supervised_user.website_approval.WebsiteApprovalCoordinator;
@@ -14,6 +17,25 @@
* Requests approval from a parent of a supervised user to unblock navigation to a given URL.
*/
class WebsiteParentApproval {
+ // Favicon default specs
+ private static final int FAVICON_MIN_SOURCE_SIZE_PIXEL = 16;
+ private static final int FAVICON_DESIRED_SIZE_PIXEL = 32;
+
+ /**
+ * Wrapper class used to store a fetched favicon.
+ */
+ private static final class FaviconHelper {
+ Bitmap mFavicon;
+
+ public void setFavicon(Bitmap favicon) {
+ mFavicon = favicon;
+ }
+
+ public Bitmap getFavicon() {
+ return mFavicon;
+ }
+ }
+
/**
* Whether or not local (i.e. on-device) approval is supported.
*
@@ -42,13 +64,17 @@
private static void requestLocalApproval(WindowAndroid windowAndroid, GURL url) {
// First ask the parent to authenticate.
ParentAuthDelegate delegate = new ParentAuthDelegateImpl();
+ FaviconHelper faviconHelper = new FaviconHelper();
delegate.requestLocalAuth(windowAndroid, url,
- (success) -> { onParentAuthComplete(success, windowAndroid, url); });
+ (success) -> { onParentAuthComplete(success, windowAndroid, url, faviconHelper); });
+
+ WebsiteParentApprovalJni.get().fetchFavicon(url, FAVICON_MIN_SOURCE_SIZE_PIXEL,
+ FAVICON_DESIRED_SIZE_PIXEL, (Bitmap favicon) -> faviconHelper.setFavicon(favicon));
}
/** Displays the screen giving the parent the option to approve or deny the website.*/
private static void onParentAuthComplete(
- boolean success, WindowAndroid windowAndroid, GURL url) {
+ boolean success, WindowAndroid windowAndroid, GURL url, FaviconHelper faviconHelper) {
if (!success) {
WebsiteParentApprovalJni.get().onCompletion(false);
return;
@@ -69,11 +95,15 @@
WebsiteParentApprovalJni.get().onCompletion(false);
}
});
+ // TODO(crbug.com/1340912): consume faviconHelper to set the favicon.
websiteApprovalUi.show();
}
@NativeMethods
interface Natives {
void onCompletion(boolean success);
+
+ void fetchFavicon(GURL url, int minSourceSizePixel, int desiredSizePixel,
+ Callback<Bitmap> onFaviconFetched);
}
}
diff --git a/chrome/browser/supervised_user/android/website_parent_approval.cc b/chrome/browser/supervised_user/android/website_parent_approval.cc
index a36d954..7ff7dfc 100644
--- a/chrome/browser/supervised_user/android/website_parent_approval.cc
+++ b/chrome/browser/supervised_user/android/website_parent_approval.cc
@@ -7,10 +7,15 @@
#include <jni.h>
#include <memory>
+#include "base/android/callback_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
+#include "base/callback.h"
#include "base/no_destructor.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/supervised_user/android/favicon_fetcher.h"
#include "chrome/browser/supervised_user/jni_headers/WebsiteParentApproval_jni.h"
#include "chrome/browser/supervised_user/web_approvals_manager.h"
#include "content/public/browser/web_contents.h"
@@ -35,6 +40,7 @@
base::android::AttachCurrentThread());
}
+// static
void WebsiteParentApproval::RequestLocalApproval(
content::WebContents* web_contents,
const GURL& url,
@@ -57,3 +63,21 @@
DCHECK(cb != nullptr);
std::move(*cb).Run(jboolean);
}
+
+// Triggers the asynchronous favicon request for a provided url.
+// Returns it via the provided callback.
+static void JNI_WebsiteParentApproval_FetchFavicon(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& j_url,
+ jint min_source_size_in_pixel,
+ jint desired_size_in_pixel,
+ const base::android::JavaParamRef<jobject>& on_favicon_fetched_callback) {
+ GURL url = *(url::GURLAndroid::ToNativeGURL(env, j_url));
+
+ FaviconFetcher* faviconFetcher =
+ new FaviconFetcher(ProfileManager::GetActiveUserProfile());
+
+ faviconFetcher->FetchFavicon(
+ url, true, min_source_size_in_pixel, desired_size_in_pixel,
+ base::android::ScopedJavaGlobalRef(on_favicon_fetched_callback));
+}
\ No newline at end of file
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 4a87c6e..2f3261d 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -388,4 +388,5 @@
<item id="chrome_commerce_subscriptions_get" added_in_milestone="105" content_hash_code="02f49372" os_list="windows,android,linux,chromeos" file_path="components/commerce/core/subscriptions/subscriptions_server_proxy.cc" />
<item id="cryptauth_get_my_devices" added_in_milestone="106" type="partial" second_id="oauth2_api_call_flow" content_hash_code="04b33199" os_list="chromeos" file_path="ash/services/device_sync/cryptauth_device_manager_impl.cc" />
<item id="chrome_commerce_waa_fetcher" added_in_milestone="107" content_hash_code="016d45fc" os_list="linux,windows,android,chromeos" file_path="components/commerce/core/account_checker.cc" />
+ <item id="favicon_fetcher_get_favicon" added_in_milestone="106" content_hash_code="0640ce11" os_list="android" file_path="chrome/browser/supervised_user/android/favicon_fetcher.cc" />
</annotations>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index 89e3706..8c15fa17 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -467,6 +467,7 @@
<sender name="Favicon">
<annotation id="content_suggestion_get_favicon"/>
<annotation id="icon_catcher_get_large_icon"/>
+ <annotation id="favicon_fetcher_get_favicon"/>
</sender>
<sender name="Images">
<annotation id="image_annotation"/>