#include "components/content_capture/browser/onscreen_content_provider.h"
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "components/content_capture/browser/content_capture_consumer.h"
#include "components/content_capture/browser/content_capture_receiver.h"
#include "components/content_capture/common/content_capture_features.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
namespace content_capture {
WEB_CONTENTS_USER_DATA_KEY_IMPL(OnscreenContentProvider);
OnscreenContentProvider::OnscreenContentProvider(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
content::WebContentsUserData<OnscreenContentProvider>(*web_contents) {
web_contents->ForEachRenderFrameHostWithAction(
[this](content::RenderFrameHost* render_frame_host) {
if (content::WebContents::FromRenderFrameHost(render_frame_host) !=
this->web_contents()) {
return content::RenderFrameHost::FrameIterationAction::kSkipChildren;
}
if (render_frame_host->IsRenderFrameLive()) {
RenderFrameCreated(render_frame_host);
}
return content::RenderFrameHost::FrameIterationAction::kContinue;
});
}
OnscreenContentProvider::~OnscreenContentProvider() = default;
OnscreenContentProvider* OnscreenContentProvider::Create(
content::WebContents* web_contents) {
DCHECK(!FromWebContents(web_contents));
CreateForWebContents(web_contents);
return FromWebContents(web_contents);
}
void OnscreenContentProvider::BindContentCaptureReceiver(
mojo::PendingAssociatedReceiver<mojom::ContentCaptureReceiver>
pending_receiver,
content::RenderFrameHost* render_frame_host) {
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
if (!web_contents)
return;
OnscreenContentProvider* manager =
OnscreenContentProvider::FromWebContents(web_contents);
if (!manager)
return;
auto* receiver = manager->ContentCaptureReceiverForFrame(render_frame_host);
if (receiver)
receiver->BindPendingReceiver(std::move(pending_receiver));
}
void OnscreenContentProvider::AddConsumer(ContentCaptureConsumer& consumer) {
consumers_.push_back(&consumer);
}
void OnscreenContentProvider::RemoveConsumer(ContentCaptureConsumer& consumer) {
for (auto it = consumers_.begin(); it != consumers_.end(); ++it) {
if (*it == &consumer) {
ContentCaptureSession session;
if (BuildContentCaptureSessionForMainFrame(&session)) {
consumer.DidRemoveSession(session);
}
consumers_.erase(it);
return;
}
}
NOTREACHED();
}
ContentCaptureReceiver* OnscreenContentProvider::ContentCaptureReceiverForFrame(
content::RenderFrameHost* render_frame_host) const {
auto mapping = frame_map_.find(render_frame_host->GetGlobalId());
return mapping == frame_map_.end() ? nullptr : mapping->second.get();
}
void OnscreenContentProvider::RenderFrameCreated(
content::RenderFrameHost* render_frame_host) {
if (ContentCaptureReceiverForFrame(render_frame_host))
return;
frame_map_.insert(std::make_pair(
render_frame_host->GetGlobalId(),
std::make_unique<ContentCaptureReceiver>(render_frame_host)));
}
void OnscreenContentProvider::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
if (auto* content_capture_receiver =
ContentCaptureReceiverForFrame(render_frame_host)) {
content_capture_receiver->RemoveSession();
}
frame_map_.erase(render_frame_host->GetGlobalId());
}
void OnscreenContentProvider::ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->IsSameDocument()) {
if (auto* rfh = content::RenderFrameHost::FromID(
navigation_handle->GetPreviousRenderFrameHostId())) {
if (auto* receiver = ContentCaptureReceiverForFrame(rfh)) {
receiver->RemoveSession();
}
}
}
if (auto* receiver = ContentCaptureReceiverForFrame(
navigation_handle->GetRenderFrameHost())) {
if (web_contents()->GetBrowserContext()->IsOffTheRecord() ||
!ShouldCapture(navigation_handle->GetURL())) {
receiver->StopCapture();
return;
}
receiver->StartCapture();
}
}
void OnscreenContentProvider::TitleWasSet(content::NavigationEntry* entry) {
if (auto* receiver = ContentCaptureReceiverForFrame(
web_contents()->GetPrimaryMainFrame())) {
receiver->SetTitle(web_contents()->GetTitle());
}
}
void OnscreenContentProvider::FlushCaptureContent(
ContentCaptureReceiver* content_capture_receiver,
const ContentCaptureFrame& data) {
ContentCaptureSession parent_session;
BuildContentCaptureSession(content_capture_receiver, true ,
&parent_session);
for (content_capture::ContentCaptureConsumer* consumer : consumers_) {
consumer->FlushCaptureContent(parent_session, data);
}
}
void OnscreenContentProvider::DidCaptureContent(
ContentCaptureReceiver* content_capture_receiver,
const ContentCaptureFrame& data) {
ContentCaptureSession parent_session;
BuildContentCaptureSession(content_capture_receiver, true ,
&parent_session);
for (content_capture::ContentCaptureConsumer* consumer : consumers_) {
consumer->DidCaptureContent(parent_session, data);
}
}
void OnscreenContentProvider::DidUpdateContent(
ContentCaptureReceiver* content_capture_receiver,
const ContentCaptureFrame& data) {
ContentCaptureSession parent_session;
BuildContentCaptureSession(content_capture_receiver, true ,
&parent_session);
for (content_capture::ContentCaptureConsumer* consumer : consumers_) {
consumer->DidUpdateContent(parent_session, data);
}
}
void OnscreenContentProvider::DidRemoveContent(
ContentCaptureReceiver* content_capture_receiver,
const std::vector<int64_t>& data) {
ContentCaptureSession session;
BuildContentCaptureSession(content_capture_receiver,
false , &session);
for (content_capture::ContentCaptureConsumer* consumer : consumers_) {
consumer->DidRemoveContent(session, data);
}
}
void OnscreenContentProvider::DidRemoveSession(
ContentCaptureReceiver* content_capture_receiver) {
ContentCaptureSession session;
if (!BuildContentCaptureSessionLastSeen(content_capture_receiver, &session))
return;
for (content_capture::ContentCaptureConsumer* consumer : consumers_) {
consumer->DidRemoveSession(session);
}
}
void OnscreenContentProvider::DidUpdateTitle(
ContentCaptureReceiver* content_capture_receiver) {
ContentCaptureSession session;
BuildContentCaptureSession(content_capture_receiver,
false, &session);
DCHECK(session.size() == 1);
for (content_capture::ContentCaptureConsumer* consumer : consumers_) {
consumer->DidUpdateTitle(*session.begin());
}
}
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) {
DCHECK(render_frame_host->IsInPrimaryMainFrame());
if (auto* receiver = ContentCaptureReceiverForFrame(render_frame_host)) {
receiver->UpdateFaviconURL(candidates);
}
}
void OnscreenContentProvider::DidUpdateFavicon(
ContentCaptureReceiver* content_capture_receiver) {
ContentCaptureSession session;
BuildContentCaptureSession(content_capture_receiver,
false, &session);
DCHECK(session.size() == 1);
for (content_capture::ContentCaptureConsumer* consumer : consumers_) {
consumer->DidUpdateFavicon(*session.begin());
}
}
void OnscreenContentProvider::DidUpdateSensitivityScore(
float sensitivity_score) {
if (!content_capture::features::ShouldSendMetadataForDataShare() ||
!ShouldCapture(web_contents()->GetLastCommittedURL())) {
return;
}
for (content_capture::ContentCaptureConsumer* consumer : consumers_) {
consumer->DidUpdateSensitivityScore(web_contents()->GetLastCommittedURL(),
sensitivity_score);
}
}
void OnscreenContentProvider::DidUpdateLanguageDetails(
const std::string& detected_language,
float language_confidence) {
if (!content_capture::features::ShouldSendMetadataForDataShare() ||
!ShouldCapture(web_contents()->GetLastCommittedURL())) {
return;
}
for (content_capture::ContentCaptureConsumer* consumer : consumers_) {
consumer->DidUpdateLanguageDetails(web_contents()->GetLastCommittedURL(),
detected_language, language_confidence);
}
}
void OnscreenContentProvider::ClearContentCaptureMetadata() {
if (!content_capture::features::ShouldSendMetadataForDataShare()) {
return;
}
for (content_capture::ContentCaptureConsumer* consumer : consumers_) {
consumer->ClearContentCaptureMetadata();
}
}
void OnscreenContentProvider::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
ClearContentCaptureMetadata();
}
void OnscreenContentProvider::BuildContentCaptureSession(
ContentCaptureReceiver* content_capture_receiver,
bool ancestor_only,
ContentCaptureSession* session) {
if (!ancestor_only)
session->push_back(content_capture_receiver->GetContentCaptureFrame());
content::RenderFrameHost* rfh =
content_capture_receiver->rfh()->GetParentOrOuterDocument();
while (rfh) {
ContentCaptureReceiver* receiver = ContentCaptureReceiverForFrame(rfh);
if (!receiver) {
RenderFrameCreated(rfh);
receiver = ContentCaptureReceiverForFrame(rfh);
DCHECK(receiver);
}
session->push_back(receiver->GetContentCaptureFrame());
rfh = receiver->rfh()->GetParentOrOuterDocument();
}
}
bool OnscreenContentProvider::BuildContentCaptureSessionLastSeen(
ContentCaptureReceiver* content_capture_receiver,
ContentCaptureSession* session) {
session->push_back(
content_capture_receiver->GetContentCaptureFrameLastSeen());
content::RenderFrameHost* rfh =
content_capture_receiver->rfh()->GetParentOrOuterDocument();
while (rfh) {
ContentCaptureReceiver* receiver = ContentCaptureReceiverForFrame(rfh);
if (!receiver)
return false;
session->push_back(receiver->GetContentCaptureFrameLastSeen());
rfh = receiver->rfh()->GetParentOrOuterDocument();
}
return true;
}
bool OnscreenContentProvider::BuildContentCaptureSessionForMainFrame(
ContentCaptureSession* session) {
if (auto* receiver = ContentCaptureReceiverForFrame(
web_contents()->GetPrimaryMainFrame())) {
session->push_back(receiver->GetContentCaptureFrame());
return true;
}
return false;
}
bool OnscreenContentProvider::ShouldCapture(const GURL& url) {
for (content_capture::ContentCaptureConsumer* consumer : consumers_) {
if (consumer->ShouldCapture(url))
return true;
}
return false;
}
}