0

ContentCapture: Update favicon to consumer

This patch forwards favicon to ContentCaptureConsumer, also adds
new test support class to help on variours test cases.

Bug: 1168827
Change-Id: I8ee228876061a834c6a816af917e489e904dd796
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2990495
Reviewed-by: Xianzhu Wang <wangxianzhu@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Reviewed-by: Colin Blundell <blundell@chromium.org>
Commit-Queue: Michael Bai <michaelbai@chromium.org>
Cr-Commit-Position: refs/heads/master@{#896994}
This commit is contained in:
Michael Bai
2021-06-29 17:37:13 +00:00
committed by Chromium LUCI CQ
parent e604a20d92
commit 343ed83408
21 changed files with 434 additions and 16 deletions

@ -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());
}
}

@ -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",

@ -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",
]
]

@ -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.

@ -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());

@ -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;

@ -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

@ -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;

@ -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);
}

@ -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.

@ -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();

@ -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" ]
}

@ -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

@ -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);
}
}

@ -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

@ -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

@ -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);
};

@ -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 {

@ -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,

@ -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_;

@ -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;