diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentCaptureTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentCaptureTest.java
index ebb2742922636..0fad978e45365 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentCaptureTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentCaptureTest.java
@@ -11,6 +11,8 @@ import android.view.View;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SmallTest;
 
+import org.json.JSONArray;
+import org.json.JSONTokener;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -26,11 +28,13 @@ import org.chromium.components.content_capture.ContentCaptureConsumer;
 import org.chromium.components.content_capture.ContentCaptureData;
 import org.chromium.components.content_capture.ContentCaptureDataBase;
 import org.chromium.components.content_capture.ContentCaptureFrame;
+import org.chromium.components.content_capture.ContentCaptureTestSupport;
 import org.chromium.components.content_capture.FrameSession;
 import org.chromium.components.content_capture.OnscreenContentProvider;
 import org.chromium.components.content_capture.UrlAllowlist;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.util.TestWebServer;
+import org.chromium.url.GURL;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -55,6 +59,7 @@ public class AwContentCaptureTest {
         public static final int CONTENT_REMOVED = 3;
         public static final int SESSION_REMOVED = 4;
         public static final int TITLE_UPDATED = 5;
+        public static final int FAVICON_UPDATED = 6;
 
         public TestAwContentCaptureConsumer() {
             mCapturedContentIds = new HashSet<Long>();
@@ -113,6 +118,13 @@ public class AwContentCaptureTest {
             mCallbackHelper.notifyCalled();
         }
 
+        @Override
+        public void onFaviconUpdated(ContentCaptureFrame contentCaptureFrame) {
+            mFaviconUpdatedFrame = contentCaptureFrame;
+            mCallbacks.add(FAVICON_UPDATED);
+            mCallbackHelper.notifyCalled();
+        }
+
         @Override
         public boolean shouldCapture(String[] urls) {
             if (mUrlAllowlist == null) return true;
@@ -131,6 +143,10 @@ public class AwContentCaptureTest {
             return mUpdatedContent;
         }
 
+        public ContentCaptureFrame getFaviconUpdatedFrame() {
+            return mFaviconUpdatedFrame;
+        }
+
         public FrameSession getCurrentFrameSession() {
             return mCurrentFrameSession;
         }
@@ -191,6 +207,7 @@ public class AwContentCaptureTest {
         private volatile FrameSession mRemovedSession;
         private volatile long[] mRemovedIds;
         private volatile ContentCaptureFrame mTitleUpdatedFrame;
+        private volatile ContentCaptureFrame mFaviconUpdatedFrame;
         private volatile ArrayList<Integer> mCallbacks = new ArrayList<Integer>();
 
         private CallbackHelper mCallbackHelper = new CallbackHelper();
@@ -402,13 +419,13 @@ public class AwContentCaptureTest {
         ContentCaptureFrame c = data;
         Rect r = c.getBounds();
         session.add(ContentCaptureFrame.createContentCaptureFrame(
-                c.getId(), c.getUrl(), r.left, r.top, r.width(), r.height(), null));
+                c.getId(), c.getUrl(), r.left, r.top, r.width(), r.height(), null, null));
         return session;
     }
 
     private FrameSession createFrameSession(String url) {
         FrameSession session = new FrameSession(1);
-        session.add(ContentCaptureFrame.createContentCaptureFrame(0, url, 0, 0, 0, 0, null));
+        session.add(ContentCaptureFrame.createContentCaptureFrame(0, url, 0, 0, 0, 0, null, null));
         return session;
     }
 
@@ -830,4 +847,146 @@ public class AwContentCaptureTest {
             runScript("document.title='hello world'");
         }, toIntArray(TestAwContentCaptureConsumer.TITLE_UPDATED));
     }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testFaviconRetrievedAtFirstContentCapture() throws Throwable {
+        // Starts with a empty document, so no content shall be streamed.
+        final String response = "<html><head>"
+                + "<link rel=\"apple-touch-icon\" href=\"image.png\">"
+                + "</head><body>"
+                + "<p id='place_holder'></p>"
+                + "</body></html>";
+        final String url = mWebServer.setResponse(MAIN_FRAME_FILE, response, null);
+        int count = mContentsClient.getTouchIconHelper().getCallCount();
+        loadUrlSync(url);
+        // To simulate favicon being retrieved by WebContents before first Content is streamed,
+        // wait favicon being available in WebContents, then insert the text to document.
+        mContentsClient.getTouchIconHelper().waitForCallback(count);
+        Assert.assertEquals(1, mContentsClient.getTouchIconHelper().getTouchIconsCount());
+        runAndVerifyCallbacks(() -> {
+            runScript("document.getElementById('place_holder').innerHTML = 'world';");
+        }, toIntArray(TestAwContentCaptureConsumer.CONTENT_CAPTURED));
+        GURL gurl = new GURL(url);
+        String origin = gurl.getOrigin().getSpec();
+        // Blink attaches the default favicon if it is not specified in page.
+        final String expectedJson = String.format("["
+                        + "    {"
+                        + "        \"type\" : \"favicon\","
+                        + "        \"url\" : \"%sfavicon.ico\""
+                        + "    },"
+                        + "    {"
+                        + "        \"type\" : \"touch icon\","
+                        + "        \"url\" : \"%simage.png\""
+                        + "    }"
+                        + "]",
+                origin, origin);
+        verifyFaviconResult(expectedJson, mConsumer.getCapturedContent().getFavicon());
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testFaviconRetrievedAfterFirstContentCapture() throws Throwable {
+        final String response = "<html><head'>"
+                + "</head><body>"
+                + "<p id='place_holder'>world</p>"
+                + "</body></html>";
+        final String url = mWebServer.setResponse(MAIN_FRAME_FILE, response, null);
+        // Direct ContentCaptureReveiver and OnscreenContentProvider not to get the favicon
+        // from Webontents, because there is no way to control the time of favicon update.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { ContentCaptureTestSupport.disableGetFaviconFromWebContents(); });
+        runAndVerifyCallbacks(() -> {
+            loadUrlSync(url);
+        }, toIntArray(TestAwContentCaptureConsumer.CONTENT_CAPTURED));
+        GURL gurl = new GURL(url);
+        String origin = gurl.getOrigin().getSpec();
+        final String expectedJson = String.format("["
+                        + "    {"
+                        + "        \"type\" : \"favicon\","
+                        + "        \"url\" : \"%sfavicon.ico\""
+                        + "    },"
+                        + "    {"
+                        + "        \"type\" : \"touch icon\","
+                        + "        \"url\" : \"%simage.png\""
+                        + "    }"
+                        + "]",
+                origin, origin);
+        // Simulates favicon update by calling OnscreenContentProvider's test method.
+        runAndVerifyCallbacks(() -> {
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                ContentCaptureTestSupport.simulateDidUpdateFaviconURL(
+                        mAwContents.getWebContents(), expectedJson);
+            });
+        }, toIntArray(TestAwContentCaptureConsumer.FAVICON_UPDATED));
+        verifyFaviconResult(expectedJson, mConsumer.getFaviconUpdatedFrame().getFavicon());
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testFavicon() throws Throwable {
+        final String response = "<html><head>"
+                + "<link rel=icon href=mac.icns sizes=\"128x128 512x512 8192x8192 32768x32768\">"
+                + "</head><body>"
+                + "<p>world</p>"
+                + "</body></html>";
+        final String url = mWebServer.setResponse(MAIN_FRAME_FILE, response, null);
+
+        runAndVerifyCallbacks(() -> {
+            loadUrlSync(url);
+        }, toIntArray(TestAwContentCaptureConsumer.CONTENT_CAPTURED));
+        Long frameId = null;
+        Set<Long> capturedContentIds = null;
+        // Verify only on-screen content is captured.
+        verifyCapturedContent(null, frameId, url, null, toStringSet("world"), capturedContentIds,
+                mConsumer.getParentFrame(), mConsumer.getCapturedContent());
+        // The favicon could be from either first capture or FaviconUpdated callback.
+        String favicon = mConsumer.getCapturedContent().getFavicon();
+        if (favicon == null) {
+            // Update the title and verify the result.
+            runAndVerifyCallbacks(
+                    () -> {}, toIntArray(TestAwContentCaptureConsumer.FAVICON_UPDATED));
+            favicon = mConsumer.getFaviconUpdatedFrame().getFavicon();
+        }
+        GURL gurl = new GURL(url);
+        String origin = gurl.getOrigin().getSpec();
+        final String expectedJson = String.format("["
+                        + "     {"
+                        + "         \"sizes\" : "
+                        + "         ["
+                        + "             {"
+                        + "                 \"height\" : 128,"
+                        + "                 \"width\" : 128"
+                        + "             },"
+                        + "             {"
+                        + "                 \"height\" : 512,"
+                        + "                 \"width\" : 512"
+                        + "             },"
+                        + "             {"
+                        + "                 \"height\" : 8192,"
+                        + "                 \"width\" : 8192"
+                        + "             },"
+                        + "             {"
+                        + "                 \"height\" : 32768,"
+                        + "                 \"width\" : 32768"
+                        + "             }"
+                        + "         ],"
+                        + "         \"type\" : \"favicon\","
+                        + "         \"url\" : \"%smac.icns\""
+                        + "     }"
+                        + " ]",
+                origin);
+        verifyFaviconResult(expectedJson, favicon);
+    }
+
+    private static void verifyFaviconResult(String expectedJson, String resultJson)
+            throws Throwable {
+        JSONArray expectedResult = (JSONArray) new JSONTokener(expectedJson).nextValue();
+        JSONArray actualResult = (JSONArray) new JSONTokener(resultJson).nextValue();
+        Assert.assertEquals(String.format("Actual:%s\n Expected:\n%s\n", resultJson, expectedJson),
+                expectedResult.toString(), actualResult.toString());
+    }
 }
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 47363125effa4..e3f95de613f67 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -60,6 +60,7 @@ android_apk("webview_instrumentation_apk") {
     "//base:base_java",
     "//base:base_java_test_support",
     "//components/android_autofill/browser/test_support:component_autofill_provider_java_test_support",
+    "//components/content_capture/android/test_support:java",
     "//components/embedder_support/android:util_java",
     "//components/heap_profiling/multi_process:heap_profiling_java_test_support",
     "//components/policy/android:policy_java_test_support",
@@ -162,6 +163,7 @@ shared_library("libstandalonelibwebviewchromium") {
     "//android_webview/public",
     "//base",
     "//components/android_autofill/browser/test_support:component_autofill_provider_native_test_support",
+    "//components/content_capture/android/test_support",
     "//components/heap_profiling/multi_process:test_support",
     "//content/public/test/android:content_native_test_support",
     "//gpu/vulkan",
@@ -202,6 +204,7 @@ instrumentation_test_apk("webview_instrumentation_test_apk") {
     "//components/component_updater/android:component_provider_service_aidl_java",
     "//components/component_updater/android:embedded_component_loader_java",
     "//components/content_capture/android:java",
+    "//components/content_capture/android/test_support:java",
     "//components/embedder_support/android:util_java",
     "//components/embedder_support/android:web_contents_delegate_java",
     "//components/heap_profiling/multi_process:heap_profiling_java_test_support",
diff --git a/components/content_capture/android/DEPS b/components/content_capture/android/DEPS
index 9e21581717da5..a0fec6f0900fe 100644
--- a/components/content_capture/android/DEPS
+++ b/components/content_capture/android/DEPS
@@ -2,5 +2,6 @@ include_rules = [
   "+components/content_capture/android/jni_headers",
   "+content/public/android",
   "+content/public/browser",
+  "+third_party/blink/public/mojom/favicon",
   "+third_party/re2",
-]
\ No newline at end of file
+]
diff --git a/components/content_capture/android/java/src/org/chromium/components/content_capture/ContentCaptureConsumer.java b/components/content_capture/android/java/src/org/chromium/components/content_capture/ContentCaptureConsumer.java
index d9729917a6bc1..8edf08e1f25f6 100644
--- a/components/content_capture/android/java/src/org/chromium/components/content_capture/ContentCaptureConsumer.java
+++ b/components/content_capture/android/java/src/org/chromium/components/content_capture/ContentCaptureConsumer.java
@@ -44,6 +44,12 @@ public interface ContentCaptureConsumer {
      */
     void onTitleUpdated(ContentCaptureFrame mainFrame);
 
+    /**
+     * Invoked when the favicon is updated.
+     * @param mainFrame the frame whose favicon is updated.
+     */
+    void onFaviconUpdated(ContentCaptureFrame mainFrame);
+
     /**
      * @param urls
      * @return if the urls shall be captured.
diff --git a/components/content_capture/android/java/src/org/chromium/components/content_capture/ContentCaptureFrame.java b/components/content_capture/android/java/src/org/chromium/components/content_capture/ContentCaptureFrame.java
index b26e08d57d750..6d73875b75a0d 100644
--- a/components/content_capture/android/java/src/org/chromium/components/content_capture/ContentCaptureFrame.java
+++ b/components/content_capture/android/java/src/org/chromium/components/content_capture/ContentCaptureFrame.java
@@ -16,19 +16,21 @@ import org.chromium.base.annotations.CalledByNative;
 public class ContentCaptureFrame extends ContentCaptureDataBase {
     private final String mUrl;
     private final String mTitle;
+    private final String mFavicon;
 
     @CalledByNative
     @VisibleForTesting
-    public static ContentCaptureFrame createContentCaptureFrame(
-            long id, String value, int x, int y, int width, int height, String title) {
-        return new ContentCaptureFrame(id, value, x, y, width, height, title);
+    public static ContentCaptureFrame createContentCaptureFrame(long id, String value, int x, int y,
+            int width, int height, String title, String favicon) {
+        return new ContentCaptureFrame(id, value, x, y, width, height, title, favicon);
     }
 
-    private ContentCaptureFrame(
-            long id, String value, int x, int y, int width, int height, String title) {
+    private ContentCaptureFrame(long id, String value, int x, int y, int width, int height,
+            String title, String favicon) {
         super(id, new Rect(x, y, x + width, y + height));
         mUrl = value;
         mTitle = title;
+        mFavicon = favicon;
     }
 
     public String getUrl() {
@@ -39,6 +41,10 @@ public class ContentCaptureFrame extends ContentCaptureDataBase {
         return mTitle;
     }
 
+    public String getFavicon() {
+        return mFavicon;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(super.toString());
diff --git a/components/content_capture/android/java/src/org/chromium/components/content_capture/ExperimentContentCaptureConsumer.java b/components/content_capture/android/java/src/org/chromium/components/content_capture/ExperimentContentCaptureConsumer.java
index d5041253436ed..9b56128ebcbf3 100644
--- a/components/content_capture/android/java/src/org/chromium/components/content_capture/ExperimentContentCaptureConsumer.java
+++ b/components/content_capture/android/java/src/org/chromium/components/content_capture/ExperimentContentCaptureConsumer.java
@@ -43,6 +43,11 @@ public class ExperimentContentCaptureConsumer implements ContentCaptureConsumer
         if (sDump) Log.d(TAG, "onTitleUpdated");
     }
 
+    @Override
+    public void onFaviconUpdated(ContentCaptureFrame mainFrame) {
+        if (sDump) Log.d(TAG, "onFaviconUpdated");
+    }
+
     @Override
     public boolean shouldCapture(String[] urls) {
         return true;
diff --git a/components/content_capture/android/java/src/org/chromium/components/content_capture/OnscreenContentProvider.java b/components/content_capture/android/java/src/org/chromium/components/content_capture/OnscreenContentProvider.java
index a3896bef7d4de..b9d8f0c00292a 100644
--- a/components/content_capture/android/java/src/org/chromium/components/content_capture/OnscreenContentProvider.java
+++ b/components/content_capture/android/java/src/org/chromium/components/content_capture/OnscreenContentProvider.java
@@ -156,7 +156,18 @@ public class OnscreenContentProvider {
                 consumer.onTitleUpdated(mainFrame);
             }
         }
-        if (sDump.booleanValue()) Log.i(TAG, "Updated Title: %s", mainFrame);
+        if (sDump.booleanValue()) Log.i(TAG, "Updated Title: %s", mainFrame.getTitle());
+    }
+
+    @CalledByNative
+    private void didUpdateFavicon(ContentCaptureFrame mainFrame) {
+        String[] urls = buildUrls(null, mainFrame);
+        for (ContentCaptureConsumer consumer : mContentCaptureConsumers) {
+            if (consumer.shouldCapture(urls)) {
+                consumer.onFaviconUpdated(mainFrame);
+            }
+        }
+        if (sDump.booleanValue()) Log.i(TAG, "Updated Favicon: %s", mainFrame.getFavicon());
     }
 
     @CalledByNative
diff --git a/components/content_capture/android/java/src/org/chromium/components/content_capture/PlatformContentCaptureConsumer.java b/components/content_capture/android/java/src/org/chromium/components/content_capture/PlatformContentCaptureConsumer.java
index 3f2e1a68d6d25..373d68b69fc4b 100644
--- a/components/content_capture/android/java/src/org/chromium/components/content_capture/PlatformContentCaptureConsumer.java
+++ b/components/content_capture/android/java/src/org/chromium/components/content_capture/PlatformContentCaptureConsumer.java
@@ -82,6 +82,9 @@ public class PlatformContentCaptureConsumer implements ContentCaptureConsumer {
                 .executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
     }
 
+    @Override
+    public void onFaviconUpdated(ContentCaptureFrame mainFrame) {}
+
     @Override
     public void onContentRemoved(FrameSession frame, long[] removedIds) {
         if (frame.isEmpty() || mPlatformSession == null) return;
diff --git a/components/content_capture/android/junit/src/org/chromium/components/content_capture/PlatformAPIWrapperTest.java b/components/content_capture/android/junit/src/org/chromium/components/content_capture/PlatformAPIWrapperTest.java
index 6203847e27114..23eaca16092a4 100644
--- a/components/content_capture/android/junit/src/org/chromium/components/content_capture/PlatformAPIWrapperTest.java
+++ b/components/content_capture/android/junit/src/org/chromium/components/content_capture/PlatformAPIWrapperTest.java
@@ -291,7 +291,7 @@ public class PlatformAPIWrapperTest {
         FrameSession frameSession = new FrameSession(1);
         frameSession.add(ContentCaptureFrame.createContentCaptureFrame(MAIN_ID, MAIN_URL,
                 MAIN_FRAME_RECT.left, MAIN_FRAME_RECT.top, MAIN_FRAME_RECT.width(),
-                MAIN_FRAME_RECT.height(), MAIN_TITLE));
+                MAIN_FRAME_RECT.height(), MAIN_TITLE, null));
         return frameSession;
     }
 
@@ -300,7 +300,7 @@ public class PlatformAPIWrapperTest {
         frameSessionForRemoveTask.add(0,
                 ContentCaptureFrame.createContentCaptureFrame(CHILD_FRAME_ID, CHILD_URL,
                         CHILD_FRAME_RECT.left, CHILD_FRAME_RECT.top, CHILD_FRAME_RECT.width(),
-                        CHILD_FRAME_RECT.height(), CHILD_TITLE));
+                        CHILD_FRAME_RECT.height(), CHILD_TITLE, null));
         return frameSessionForRemoveTask;
     }
 
@@ -309,7 +309,7 @@ public class PlatformAPIWrapperTest {
         FrameSession frameSession = createFrameSession();
         ContentCaptureFrame data = ContentCaptureFrame.createContentCaptureFrame(CHILD_FRAME_ID,
                 CHILD_URL, CHILD_FRAME_RECT.left, CHILD_FRAME_RECT.top, CHILD_FRAME_RECT.width(),
-                CHILD_FRAME_RECT.height(), CHILD_TITLE);
+                CHILD_FRAME_RECT.height(), CHILD_TITLE, null);
         ContentCaptureData.createContentCaptureData(data, CHILD1_ID, CHILD1_TEXT, CHILD1_RECT.left,
                 CHILD1_RECT.top, CHILD1_RECT.width(), CHILD1_RECT.height());
         ContentCaptureData.createContentCaptureData(data, CHILD2_ID, CHILD2_TEXT, CHILD2_RECT.left,
@@ -321,7 +321,7 @@ public class PlatformAPIWrapperTest {
         // Modifies child2
         ContentCaptureFrame changeTextData = ContentCaptureFrame.createContentCaptureFrame(
                 CHILD_FRAME_ID, CHILD_URL, CHILD_FRAME_RECT.left, CHILD_FRAME_RECT.top,
-                CHILD_FRAME_RECT.width(), CHILD_FRAME_RECT.height(), CHILD_TITLE);
+                CHILD_FRAME_RECT.width(), CHILD_FRAME_RECT.height(), CHILD_TITLE, null);
         ContentCaptureData.createContentCaptureData(changeTextData, CHILD2_ID, CHILD2_NEW_TEXT,
                 CHILD2_RECT.left, CHILD2_RECT.top, CHILD2_RECT.width(), CHILD2_RECT.height());
         return new ContentUpdateTask(createFrameSession(), changeTextData, mRootPlatformSession);
@@ -340,7 +340,7 @@ public class PlatformAPIWrapperTest {
     private TitleUpdateTask createTitleUpdateTask() {
         ContentCaptureFrame mainFrame = ContentCaptureFrame.createContentCaptureFrame(MAIN_ID,
                 MAIN_URL, MAIN_FRAME_RECT.left, MAIN_FRAME_RECT.top, MAIN_FRAME_RECT.width(),
-                MAIN_FRAME_RECT.height(), UPDATED_MAIN_TITLE);
+                MAIN_FRAME_RECT.height(), UPDATED_MAIN_TITLE, null);
         return new TitleUpdateTask(mainFrame, mRootPlatformSession);
     }
 
diff --git a/components/content_capture/android/onscreen_content_provider_android.cc b/components/content_capture/android/onscreen_content_provider_android.cc
index f0cae24102853..d56fc0355d439 100644
--- a/components/content_capture/android/onscreen_content_provider_android.cc
+++ b/components/content_capture/android/onscreen_content_provider_android.cc
@@ -55,10 +55,14 @@ ScopedJavaLocalRef<jobject> ToJavaObjectOfContentCaptureFrame(
   if (!data.title.empty())
     jtitle = ConvertUTF16ToJavaString(env, data.title);
 
+  ScopedJavaLocalRef<jstring> jfavicon;
+  if (!data.favicon.empty())
+    jfavicon = ConvertUTF8ToJavaString(env, data.favicon);
+
   ScopedJavaLocalRef<jobject> jdata =
       Java_ContentCaptureFrame_createContentCaptureFrame(
           env, data.id, jurl, data.bounds.x(), data.bounds.y() + offset_y,
-          data.bounds.width(), data.bounds.height(), jtitle);
+          data.bounds.width(), data.bounds.height(), jtitle, jfavicon);
   if (jdata.is_null())
     return jdata;
   for (const auto& child : data.children) {
@@ -190,6 +194,22 @@ void OnscreenContentProviderAndroid::DidUpdateTitle(
   Java_OnscreenContentProvider_didUpdateTitle(env, java_ref_, jdata);
 }
 
+void OnscreenContentProviderAndroid::DidUpdateFavicon(
+    const ContentCaptureFrame& main_frame) {
+  JNIEnv* env = AttachCurrentThread();
+  DCHECK(java_ref_.obj());
+
+  auto* web_contents = GetWebContents();
+  DCHECK(web_contents);
+  const int offset_y = Java_OnscreenContentProvider_getOffsetY(
+      env, java_ref_, web_contents->GetJavaWebContents());
+  ScopedJavaLocalRef<jobject> jdata =
+      ToJavaObjectOfContentCaptureFrame(env, main_frame, offset_y);
+  if (jdata.is_null())
+    return;
+  Java_OnscreenContentProvider_didUpdateFavicon(env, java_ref_, jdata);
+}
+
 bool OnscreenContentProviderAndroid::ShouldCapture(const GURL& url) {
   // Capture all urls for experiment, the url will be checked
   // before the content is sent to the consumers.
diff --git a/components/content_capture/android/onscreen_content_provider_android.h b/components/content_capture/android/onscreen_content_provider_android.h
index 686b32ad4ccf2..586f161ad5426 100644
--- a/components/content_capture/android/onscreen_content_provider_android.h
+++ b/components/content_capture/android/onscreen_content_provider_android.h
@@ -32,6 +32,7 @@ class OnscreenContentProviderAndroid : public ContentCaptureConsumer {
                         const std::vector<int64_t>& data) override;
   void DidRemoveSession(const ContentCaptureSession& session) override;
   void DidUpdateTitle(const ContentCaptureFrame& main_frame) override;
+  void DidUpdateFavicon(const ContentCaptureFrame& main_frame) override;
   bool ShouldCapture(const GURL& url) override;
 
   base::android::ScopedJavaLocalRef<jobject> GetJavaObject();
diff --git a/components/content_capture/android/test_support/BUILD.gn b/components/content_capture/android/test_support/BUILD.gn
new file mode 100644
index 0000000000000..1e6eec4fd04e5
--- /dev/null
+++ b/components/content_capture/android/test_support/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2021 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.
+
+import("//build/config/android/config.gni")
+import("//build/config/android/rules.gni")
+
+testonly = true
+
+source_set("test_support") {
+  sources = [ "content_capture_test_support_android.cc" ]
+  deps = [
+    ":jni_headers",
+    "//components/content_capture/browser",
+  ]
+}
+
+android_library("java") {
+  deps = [
+    "//base:base_java",
+    "//content/public/android:content_java",
+    "//third_party/androidx:androidx_annotation_annotation_java",
+  ]
+  annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
+  sources = [ "java/src/org/chromium/components/content_capture/ContentCaptureTestSupport.java" ]
+}
+
+generate_jni("jni_headers") {
+  sources = [ "java/src/org/chromium/components/content_capture/ContentCaptureTestSupport.java" ]
+}
diff --git a/components/content_capture/android/test_support/content_capture_test_support_android.cc b/components/content_capture/android/test_support/content_capture_test_support_android.cc
new file mode 100644
index 0000000000000..3fbd2291205d3
--- /dev/null
+++ b/components/content_capture/android/test_support/content_capture_test_support_android.cc
@@ -0,0 +1,76 @@
+// Copyright 2021 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 "components/content_capture/android/test_support/jni_headers/ContentCaptureTestSupport_jni.h"
+
+#include "base/android/jni_string.h"
+#include "base/json/json_reader.h"
+#include "base/notreached.h"
+#include "base/values.h"
+#include "components/content_capture/browser/content_capture_receiver.h"
+#include "components/content_capture/browser/onscreen_content_provider.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace content_capture {
+
+namespace {
+blink::mojom::FaviconIconType ToType(std::string type) {
+  if (type == "favicon")
+    return blink::mojom::FaviconIconType::kFavicon;
+  else if (type == "touch icon")
+    return blink::mojom::FaviconIconType::kTouchIcon;
+  else if (type == "touch precomposed icon")
+    return blink::mojom::FaviconIconType::kTouchPrecomposedIcon;
+  NOTREACHED();
+  return blink::mojom::FaviconIconType::kInvalid;
+}
+
+}  // namespace
+
+static void JNI_ContentCaptureTestSupport_DisableGetFaviconFromWebContents(
+    JNIEnv* env) {
+  ContentCaptureReceiver::DisableGetFaviconFromWebContentsForTesting();
+}
+
+static void JNI_ContentCaptureTestSupport_SimulateDidUpdateFaviconURL(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& jwebContents,
+    const base::android::JavaParamRef<jstring>& jfaviconJson) {
+  content::WebContents* web_contents =
+      content::WebContents::FromJavaWebContents(jwebContents);
+  CHECK(web_contents);
+  OnscreenContentProvider* provider =
+      OnscreenContentProvider::FromWebContents(web_contents);
+  CHECK(provider);
+
+  std::string json = base::android::ConvertJavaStringToUTF8(env, jfaviconJson);
+  absl::optional<base::Value> root = base::JSONReader::Read(json);
+  CHECK(root);
+  CHECK(root->is_list());
+  std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
+  for (const base::Value& icon : root->GetList()) {
+    std::vector<gfx::Size> sizes;
+    // The sizes is optional.
+    if (auto* icon_sizes = icon.FindKey("sizes")) {
+      for (const base::Value& size : icon_sizes->GetList()) {
+        CHECK(size.FindKey("width"));
+        CHECK(size.FindKey("height"));
+        sizes.emplace_back(size.FindKey("width")->GetInt(),
+                           size.FindKey("height")->GetInt());
+      }
+    }
+    CHECK(icon.FindKey("url"));
+    CHECK(icon.FindKey("type"));
+    favicon_urls.push_back(blink::mojom::FaviconURL::New(
+        GURL(*icon.FindKey("url")->GetIfString()),
+        ToType(*icon.FindKey("type")->GetIfString()), sizes));
+  }
+  CHECK(!favicon_urls.empty());
+  provider->NotifyFaviconURLUpdatedForTesting(web_contents->GetMainFrame(),
+                                              favicon_urls);
+}
+
+}  // namespace content_capture
diff --git a/components/content_capture/android/test_support/java/src/org/chromium/components/content_capture/ContentCaptureTestSupport.java b/components/content_capture/android/test_support/java/src/org/chromium/components/content_capture/ContentCaptureTestSupport.java
new file mode 100644
index 0000000000000..602100a74c802
--- /dev/null
+++ b/components/content_capture/android/test_support/java/src/org/chromium/components/content_capture/ContentCaptureTestSupport.java
@@ -0,0 +1,28 @@
+// Copyright 2021 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.components.content_capture;
+
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.content_public.browser.WebContents;
+
+/**
+ * This is the test support class to help setup various test conditions.
+ */
+@JNINamespace("content_capture")
+public class ContentCaptureTestSupport {
+    public static void disableGetFaviconFromWebContents() {
+        ContentCaptureTestSupportJni.get().disableGetFaviconFromWebContents();
+    }
+
+    public static void simulateDidUpdateFaviconURL(WebContents webContents, String faviconJson) {
+        ContentCaptureTestSupportJni.get().simulateDidUpdateFaviconURL(webContents, faviconJson);
+    }
+
+    @NativeMethods
+    interface Natives {
+        void disableGetFaviconFromWebContents();
+        void simulateDidUpdateFaviconURL(WebContents webContents, String faviconJson);
+    }
+}
diff --git a/components/content_capture/browser/content_capture_consumer.h b/components/content_capture/browser/content_capture_consumer.h
index 9cf8b44fcce84..a21194f322b51 100644
--- a/components/content_capture/browser/content_capture_consumer.h
+++ b/components/content_capture/browser/content_capture_consumer.h
@@ -55,6 +55,8 @@ class ContentCaptureConsumer {
   virtual void DidRemoveSession(const ContentCaptureSession& session) = 0;
   // Invoked when the given |main_frame|'s title updated.
   virtual void DidUpdateTitle(const ContentCaptureFrame& main_frame) = 0;
+  // Invoked when the given |main_frame|'s favicon updated.
+  virtual void DidUpdateFavicon(const ContentCaptureFrame& main_frame) = 0;
 
   // Return if the |url| shall be captured. Even return false, the content might
   // still be streamed because of the other consumers require it. Consumer can
diff --git a/components/content_capture/browser/content_capture_receiver.cc b/components/content_capture/browser/content_capture_receiver.cc
index 4e5ac095e8d73..b9c6af181a420 100644
--- a/components/content_capture/browser/content_capture_receiver.cc
+++ b/components/content_capture/browser/content_capture_receiver.cc
@@ -216,10 +216,15 @@ void ContentCaptureReceiver::UpdateFaviconURL(
   if (!has_session_)
     return;
   frame_content_capture_data_.favicon = ToJSON(candidates);
+  auto* provider = GetOnscreenContentProvider(rfh_);
+  if (!provider)
+    return;
+  provider->DidUpdateFavicon(this);
 }
 
 void ContentCaptureReceiver::RetrieveFaviconURL() {
-  if (!rfh()->IsActive() || rfh()->GetMainFrame() != rfh()) {
+  if (!rfh()->IsActive() || rfh()->GetMainFrame() != rfh() ||
+      disable_get_favicon_from_web_contents_for_testing()) {
     frame_content_capture_data_.favicon = std::string();
   } else {
     frame_content_capture_data_.favicon = ToJSON(
@@ -267,4 +272,19 @@ const ContentCaptureFrame& ContentCaptureReceiver::GetContentCaptureFrame() {
   return frame_content_capture_data_;
 }
 
+// static
+bool
+    ContentCaptureReceiver::disable_get_favicon_from_web_contents_for_testing_ =
+        false;
+
+void ContentCaptureReceiver::DisableGetFaviconFromWebContentsForTesting() {
+  disable_get_favicon_from_web_contents_for_testing_ = true;
+}
+
+// static
+bool ContentCaptureReceiver::
+    disable_get_favicon_from_web_contents_for_testing() {
+  return disable_get_favicon_from_web_contents_for_testing_;
+}
+
 }  // namespace content_capture
diff --git a/components/content_capture/browser/content_capture_receiver.h b/components/content_capture/browser/content_capture_receiver.h
index 01398a8e03143..914fe9012558b 100644
--- a/components/content_capture/browser/content_capture_receiver.h
+++ b/components/content_capture/browser/content_capture_receiver.h
@@ -59,6 +59,9 @@ class ContentCaptureReceiver : public mojom::ContentCaptureReceiver {
   void UpdateFaviconURL(
       const std::vector<blink::mojom::FaviconURLPtr>& candidates);
 
+  static void DisableGetFaviconFromWebContentsForTesting();
+  static bool disable_get_favicon_from_web_contents_for_testing();
+
  private:
   FRIEND_TEST_ALL_PREFIXES(ContentCaptureReceiverTest, RenderFrameHostGone);
   FRIEND_TEST_ALL_PREFIXES(ContentCaptureReceiverTest, TitleUpdateTaskDelay);
@@ -104,6 +107,8 @@ class ContentCaptureReceiver : public mojom::ContentCaptureReceiver {
   // prevent running frequently.
   unsigned exponential_delay_ = 1;
 
+  static bool disable_get_favicon_from_web_contents_for_testing_;
+
   mojo::AssociatedRemote<mojom::ContentCaptureSender> content_capture_sender_;
   DISALLOW_COPY_AND_ASSIGN(ContentCaptureReceiver);
 };
diff --git a/components/content_capture/browser/content_capture_receiver_test.cc b/components/content_capture/browser/content_capture_receiver_test.cc
index 59c1606130207..4411a1088d72f 100644
--- a/components/content_capture/browser/content_capture_receiver_test.cc
+++ b/components/content_capture/browser/content_capture_receiver_test.cc
@@ -113,6 +113,8 @@ class ContentCaptureConsumerHelper : public ContentCaptureConsumer {
     updated_title_ = main_frame.title;
   }
 
+  void DidUpdateFavicon(const ContentCaptureFrame& main_frame) override {}
+
   bool ShouldCapture(const GURL& url) override { return false; }
 
   const ContentCaptureSession& parent_session() const {
diff --git a/components/content_capture/browser/onscreen_content_provider.cc b/components/content_capture/browser/onscreen_content_provider.cc
index 038ea01f777a0..1cea5371e28c2 100644
--- a/components/content_capture/browser/onscreen_content_provider.cc
+++ b/components/content_capture/browser/onscreen_content_provider.cc
@@ -217,6 +217,16 @@ void OnscreenContentProvider::DidUpdateTitle(
 void OnscreenContentProvider::DidUpdateFaviconURL(
     content::RenderFrameHost* render_frame_host,
     const std::vector<blink::mojom::FaviconURLPtr>& candidates) {
+  if (ContentCaptureReceiver::
+          disable_get_favicon_from_web_contents_for_testing()) {
+    return;
+  }
+  NotifyFaviconURLUpdated(render_frame_host, candidates);
+}
+
+void OnscreenContentProvider::NotifyFaviconURLUpdated(
+    content::RenderFrameHost* render_frame_host,
+    const std::vector<blink::mojom::FaviconURLPtr>& candidates) {
   // Only set the favicons for the mainframe.
   if (render_frame_host != web_contents()->GetMainFrame())
     return;
@@ -226,6 +236,18 @@ void OnscreenContentProvider::DidUpdateFaviconURL(
   }
 }
 
+void OnscreenContentProvider::DidUpdateFavicon(
+    ContentCaptureReceiver* content_capture_receiver) {
+  ContentCaptureSession session;
+  BuildContentCaptureSession(content_capture_receiver,
+                             /*ancestor_only=*/false, &session);
+
+  // Shall only update mainframe's title.
+  DCHECK(session.size() == 1);
+  for (auto* consumer : consumers_)
+    consumer->DidUpdateFavicon(*session.begin());
+}
+
 void OnscreenContentProvider::BuildContentCaptureSession(
     ContentCaptureReceiver* content_capture_receiver,
     bool ancestor_only,
diff --git a/components/content_capture/browser/onscreen_content_provider.h b/components/content_capture/browser/onscreen_content_provider.h
index 0887001a37603..e29753fdb9b6d 100644
--- a/components/content_capture/browser/onscreen_content_provider.h
+++ b/components/content_capture/browser/onscreen_content_provider.h
@@ -58,6 +58,7 @@ class OnscreenContentProvider : public content::WebContentsObserver,
                         const std::vector<int64_t>& data);
   void DidRemoveSession(ContentCaptureReceiver* content_capture_receiver);
   void DidUpdateTitle(ContentCaptureReceiver* content_capture_receiver);
+  void DidUpdateFavicon(ContentCaptureReceiver* content_capture_receiver);
 
   // content::WebContentsObserver:
   void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
@@ -75,6 +76,12 @@ class OnscreenContentProvider : public content::WebContentsObserver,
     return weak_ptr_factory_.GetWeakPtr();
   }
 
+  void NotifyFaviconURLUpdatedForTesting(
+      content::RenderFrameHost* render_frame_host,
+      const std::vector<blink::mojom::FaviconURLPtr>& candidates) {
+    NotifyFaviconURLUpdated(render_frame_host, candidates);
+  }
+
 #ifdef UNIT_TEST
   ContentCaptureReceiver* ContentCaptureReceiverForFrameForTesting(
       content::RenderFrameHost* render_frame_host) const {
@@ -110,6 +117,10 @@ class OnscreenContentProvider : public content::WebContentsObserver,
 
   bool ShouldCapture(const GURL& url);
 
+  void NotifyFaviconURLUpdated(
+      content::RenderFrameHost* render_frame_host,
+      const std::vector<blink::mojom::FaviconURLPtr>& candidates);
+
   std::map<content::RenderFrameHost*, std::unique_ptr<ContentCaptureReceiver>>
       frame_map_;
 
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/test/TestContentCaptureConsumer.java b/weblayer/browser/java/org/chromium/weblayer_private/test/TestContentCaptureConsumer.java
index 7cf1981e4e129..a4947dda3fcae 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/test/TestContentCaptureConsumer.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/test/TestContentCaptureConsumer.java
@@ -19,6 +19,7 @@ public class TestContentCaptureConsumer implements ContentCaptureConsumer {
     public static final int CONTENT_REMOVED = 3;
     public static final int SESSION_REMOVED = 4;
     public static final int TITLE_UPDATED = 5;
+    public static final int FAVICON_UPDATED = 6;
 
     public TestContentCaptureConsumer(Runnable onNewEvents, ArrayList<Integer> eventsObserved) {
         mOnNewEvents = onNewEvents;
@@ -56,6 +57,12 @@ public class TestContentCaptureConsumer implements ContentCaptureConsumer {
         mOnNewEvents.run();
     }
 
+    @Override
+    public void onFaviconUpdated(ContentCaptureFrame mainFrame) {
+        mEventsObserved.add(FAVICON_UPDATED);
+        mOnNewEvents.run();
+    }
+
     @Override
     public boolean shouldCapture(String[] urls) {
         return true;