#include "chrome/browser/ui/lens/lens_search_contextualization_controller.h"
#include "base/functional/bind.h"
#include "base/strings/string_split.h"
#include "base/task/bind_post_task.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/content_extraction/inner_html.h"
#include "chrome/browser/ui/lens/lens_overlay_controller.h"
#include "chrome/browser/ui/lens/lens_overlay_image_helper.h"
#include "chrome/browser/ui/lens/lens_overlay_proto_converter.h"
#include "chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.h"
#include "chrome/browser/ui/lens/lens_search_controller.h"
#include "chrome/browser/ui/lens/lens_search_feature_flag_utils.h"
#include "chrome/browser/ui/lens/lens_searchbox_controller.h"
#include "chrome/browser/ui/lens/lens_session_metrics_logger.h"
#include "components/content_extraction/content/browser/inner_text.h"
#include "components/lens/lens_features.h"
#include "components/tabs/public/tab_interface.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "components/zoom/zoom_controller.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "pdf/buildflags.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "ui/gfx/skia_util.h"
#if BUILDFLAG(ENABLE_PDF)
#include "components/pdf/browser/pdf_document_helper.h"
#include "pdf/mojom/pdf.mojom.h"
#endif
namespace {
constexpr float kByteChangeTolerancePercent = 0.01;
constexpr int kMaxDomTextLengthForOcrSimilarity = 50 * 1000 * 1000;
std::string TrimNonAlphaNumeric(const std::string& text) {
if (text.empty()) {
return text;
}
size_t first_alphanum_index =
std::find_if(text.begin(), text.end(), ::isalnum) - text.begin();
if (first_alphanum_index == text.length()) {
return "";
}
size_t last_alphanum_index =
std::find_if(text.rbegin(), text.rend(), ::isalnum) - text.rbegin();
last_alphanum_index = text.length() - 1 - last_alphanum_index;
return text.substr(first_alphanum_index,
last_alphanum_index - first_alphanum_index + 1);
}
double CalculateWordOverlapSimilarity(std::string dom_text,
lens::mojom::TextPtr ocr_text) {
std::vector<std::string> dom_words = base::SplitString(
dom_text, " \t\r\n<>", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
std::map<std::string, int> dom_words_map;
for (std::string& word : dom_words) {
std::string processed_word = TrimNonAlphaNumeric(base::ToLowerASCII(word));
if (!processed_word.empty()) {
dom_words_map[processed_word]++;
}
}
double overlap_count = 0;
double total_ocr_words = 0;
if (ocr_text && ocr_text->text_layout &&
ocr_text->text_layout->paragraphs.size() > 0) {
for (const auto& paragraph : ocr_text->text_layout->paragraphs) {
if (paragraph && paragraph->lines.size() > 0) {
for (const auto& line : paragraph->lines) {
if (line && line->words.size() > 0) {
for (const auto& word : line->words) {
if (word) {
std::string processed_word =
TrimNonAlphaNumeric(base::ToLowerASCII(word->plain_text));
if (processed_word.empty()) {
continue;
}
auto word_iterator = dom_words_map.find(processed_word);
if (word_iterator != dom_words_map.end() &&
word_iterator->second > 0) {
overlap_count++;
word_iterator->second--;
}
total_ocr_words++;
}
}
}
}
}
}
}
return total_ocr_words == 0 ? 0.0 : overlap_count / total_ocr_words;
}
bool IsProtectedPageFeatureEnabled() {
return lens::features::IsLensSearchProtectedPageEnabled() &&
lens::IsLensOverlayContextualSearchboxEnabled() &&
lens::features::UseApcAsContext();
}
}
namespace lens {
LensSearchContextualizationController::LensSearchContextualizationController(
LensSearchController* lens_search_controller)
: lens_search_controller_(lens_search_controller) {}
LensSearchContextualizationController::
~LensSearchContextualizationController() = default;
void LensSearchContextualizationController::StartContextualization(
lens::LensOverlayInvocationSource invocation_source,
OnPageContextUpdatedCallback callback) {
CHECK(state_ == State::kOff);
state_ = State::kInitializing;
invocation_source_ = invocation_source;
StartScreenshotFlow(base::BindOnce(
&LensSearchContextualizationController::OnScreenshotTakenForContextual,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void LensSearchContextualizationController::GetPageContextualization(
PageContentRetrievedCallback callback) {
if (!lens::IsLensOverlayContextualSearchboxEnabled()) {
std::move(callback).Run({}, lens::MimeType::kUnknown,
std::nullopt);
return;
}
is_page_context_eligible_ = true;
#if BUILDFLAG(ENABLE_PDF)
pdf::PDFDocumentHelper* pdf_helper =
pdf::PDFDocumentHelper::MaybeGetForWebContents(
lens_search_controller_->GetTabInterface()->GetContents());
if (pdf_helper) {
MaybeGetPdfBytes(pdf_helper, std::move(callback));
return;
}
#endif
std::vector<lens::PageContent> page_contents;
auto* render_frame_host = lens_search_controller_->GetTabInterface()
->GetContents()
->GetPrimaryMainFrame();
if (!render_frame_host || (!lens::features::UseInnerTextAsContext() &&
!lens::features::UseApcAsContext())) {
std::move(callback).Run(page_contents, lens::MimeType::kUnknown,
std::nullopt);
return;
}
MaybeGetInnerText(page_contents, render_frame_host, std::move(callback));
}
void LensSearchContextualizationController::TryUpdatePageContextualization(
OnPageContextUpdatedCallback callback) {
if (state_ == State::kInitializing) {
return;
}
if (state_ == State::kOff) {
viewport_screenshot_ = lens_search_controller_->lens_overlay_controller()
->initial_screenshot();
state_ = State::kActive;
}
CHECK(state_ == State::kActive);
if (GetQueryController()->IsPageContentUploadInProgress()) {
std::move(callback).Run();
return;
}
on_page_context_updated_callback_ = std::move(callback);
GetPageContextualization(base::BindOnce(
&LensSearchContextualizationController::UpdatePageContextualization,
weak_ptr_factory_.GetWeakPtr()));
}
#if BUILDFLAG(ENABLE_PDF)
void LensSearchContextualizationController::
FetchVisiblePageIndexAndGetPartialPdfText(
uint32_t page_count,
PdfPartialPageTextRetrievedCallback callback) {
pdf::PDFDocumentHelper* pdf_helper =
pdf::PDFDocumentHelper::MaybeGetForWebContents(
lens_search_controller_->GetTabInterface()->GetContents());
if (!pdf_helper ||
lens::features::GetLensOverlayPdfSuggestCharacterTarget() == 0 ||
page_count == 0) {
return;
}
CHECK(callback);
pdf_partial_page_text_retrieved_callback_ = std::move(callback);
pdf_pages_text_.clear();
pdf_helper->GetPageText(
0,
base::BindOnce(
&LensSearchContextualizationController::GetPartialPdfTextCallback,
weak_ptr_factory_.GetWeakPtr(), 0, page_count,
0));
}
#endif
void LensSearchContextualizationController::ResetState() {
on_page_context_updated_callback_.Reset();
is_page_context_eligible_ = false;
ocr_dom_similarity_recorded_in_session_ = false;
page_contents_.clear();
primary_content_type_ = lens::MimeType::kUnknown;
viewport_screenshot_.reset();
last_retrieved_most_visible_page_ = std::nullopt;
pdf_partial_page_text_retrieved_callback_.Reset();
pdf_pages_text_.clear();
page_context_eligibility_callback_.Reset();
pending_context_eligibility_params_.reset();
state_ = State::kOff;
}
void LensSearchContextualizationController::SetPageContent(
std::vector<lens::PageContent> page_contents,
lens::MimeType primary_content_type) {
page_contents_ = page_contents;
primary_content_type_ = primary_content_type;
}
void LensSearchContextualizationController::RecordDocumentMetrics(
std::optional<uint32_t> page_count) {
std::set<lens::MimeType> retrieved_content_types;
if (page_contents_.empty()) {
lens::RecordDocumentSizeBytes(lens::MimeType::kUnknown, 0);
} else {
for (const auto& page_content : page_contents_) {
lens::RecordDocumentSizeBytes(page_content.content_type_,
page_content.bytes_.size());
retrieved_content_types.insert(page_content.content_type_);
}
}
if (page_count.has_value() && primary_content_type_ == lens::MimeType::kPdf) {
lens::RecordPdfPageCount(page_count.value());
return;
}
auto* render_frame_host = lens_search_controller_->GetTabInterface()
->GetContents()
->GetPrimaryMainFrame();
if (!retrieved_content_types.contains(lens::MimeType::kPlainText)) {
content_extraction::GetInnerText(
*render_frame_host, std::nullopt,
base::BindOnce(
&LensSearchContextualizationController::RecordInnerTextSize,
weak_ptr_factory_.GetWeakPtr()));
}
TryCalculateAndRecordOcrDomSimilarity();
}
void LensSearchContextualizationController::
TryCalculateAndRecordOcrDomSimilarity() {
if (!text_ || page_contents_.empty() ||
ocr_dom_similarity_recorded_in_session_) {
return;
}
ocr_dom_similarity_recorded_in_session_ = true;
const auto& page_content_bytes = page_contents_.front().bytes_;
const auto primary_content_type = primary_content_type_;
bool is_dom = primary_content_type == lens::MimeType::kHtml ||
primary_content_type == lens::MimeType::kPlainText ||
primary_content_type == lens::MimeType::kAnnotatedPageContent;
bool is_dom_too_large =
page_content_bytes.size() > kMaxDomTextLengthForOcrSimilarity;
bool is_english = text_->content_language == "en";
if (!is_dom || is_dom_too_large || !is_english) {
text_.reset();
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskPriority::BEST_EFFORT},
base::BindOnce(
&CalculateWordOverlapSimilarity,
std::string(page_content_bytes.begin(), page_content_bytes.end()),
text_.Clone()),
base::BindOnce(&lens::RecordOcrDomSimilarity));
}
void LensSearchContextualizationController::SetText(lens::mojom::TextPtr text) {
text_ = std::move(text);
}
void LensSearchContextualizationController::UpdatePageContextualization(
std::vector<lens::PageContent> page_contents,
lens::MimeType primary_content_type,
std::optional<uint32_t> page_count) {
if (state_ == State::kOff) {
return;
}
if (!lens::IsLensOverlayContextualSearchboxEnabled()) {
std::move(on_page_context_updated_callback_).Run();
return;
}
if (!is_page_context_eligible_) {
std::move(on_page_context_updated_callback_).Run();
return;
}
if (!lens::features::UpdateViewportEachQueryEnabled() ||
lens_search_controller_->lens_overlay_controller()->IsOverlayShowing()) {
UpdatePageContextualizationPart2(page_contents, primary_content_type,
page_count, SkBitmap());
return;
}
CaptureScreenshot(base::BindOnce(
&LensSearchContextualizationController::UpdatePageContextualizationPart2,
weak_ptr_factory_.GetWeakPtr(), page_contents, primary_content_type,
page_count));
}
void LensSearchContextualizationController::UpdatePageContextualizationPart2(
std::vector<lens::PageContent> page_contents,
lens::MimeType primary_content_type,
std::optional<uint32_t> page_count,
const SkBitmap& bitmap) {
if (state_ == State::kOff || !on_page_context_updated_callback_) {
return;
}
#if BUILDFLAG(ENABLE_PDF)
pdf::PDFDocumentHelper* pdf_helper =
pdf::PDFDocumentHelper::MaybeGetForWebContents(
lens_search_controller_->GetTabInterface()->GetContents());
if (pdf_helper) {
pdf_helper->GetMostVisiblePageIndex(base::BindOnce(
&LensSearchContextualizationController::UpdatePageContext,
weak_ptr_factory_.GetWeakPtr(), page_contents, primary_content_type,
page_count, bitmap));
return;
}
#endif
UpdatePageContext(page_contents, primary_content_type, page_count, bitmap,
std::nullopt);
}
void LensSearchContextualizationController::UpdatePageContext(
std::vector<lens::PageContent> page_contents,
lens::MimeType primary_content_type,
std::optional<uint32_t> page_count,
const SkBitmap& bitmap,
std::optional<uint32_t> most_visible_page) {
if (state_ == State::kOff) {
return;
}
bool sending_bitmap = false;
if (!bitmap.drawsNothing() &&
(viewport_screenshot_.drawsNothing() ||
!gfx::BitmapsAreEqual(viewport_screenshot_, bitmap))) {
viewport_screenshot_ = bitmap;
sending_bitmap = true;
if (!lens_search_controller_->lens_overlay_controller()
->IsOverlayShowing() &&
!lens_search_controller_->lens_overlay_controller()
->IsOverlayInitializing()) {
lens_search_controller_->lens_overlay_controller()->ClearAllSelections();
lens_search_controller_->HandleThumbnailCreatedBitmap(bitmap);
}
}
last_retrieved_most_visible_page_ = most_visible_page;
auto old_page_content_it = std::ranges::find_if(
page_contents_, [&primary_content_type](const auto& page_content) {
return page_content.content_type_ == primary_content_type;
});
auto new_page_content_it = std::ranges::find_if(
page_contents, [&primary_content_type](const auto& page_content) {
return page_content.content_type_ == primary_content_type;
});
const lens::PageContent* old_page_content =
old_page_content_it != page_contents_.end() ? &(*old_page_content_it)
: nullptr;
const lens::PageContent* new_page_content =
new_page_content_it != page_contents.end() ? &(*new_page_content_it)
: nullptr;
if (primary_content_type_ == primary_content_type && old_page_content &&
new_page_content) {
const float old_size = old_page_content->bytes_.size();
const float new_size = new_page_content->bytes_.size();
const float percent_changed = abs((new_size - old_size) / old_size);
if (percent_changed < kByteChangeTolerancePercent) {
if (!sending_bitmap) {
GetQueryController()->MaybeRestartQueryFlow();
if (on_page_context_updated_callback_) {
std::move(on_page_context_updated_callback_).Run();
}
return;
}
GetQueryController()->SendUpdatedPageContent(
std::nullopt, std::nullopt, std::nullopt, std::nullopt,
last_retrieved_most_visible_page_,
sending_bitmap ? bitmap : SkBitmap());
if (on_page_context_updated_callback_) {
std::move(on_page_context_updated_callback_).Run();
}
return;
}
}
GetQueryController()->ResetPageContentData();
page_contents_ = page_contents;
primary_content_type_ = primary_content_type;
if (!new_page_content || new_page_content->bytes_.empty()) {
lens_search_controller_->lens_overlay_side_panel_coordinator()
->SuppressGhostLoader();
}
#if BUILDFLAG(ENABLE_PDF)
if (new_page_content &&
new_page_content->content_type_ == lens::MimeType::kPdf) {
FetchVisiblePageIndexAndGetPartialPdfText(
page_count.value_or(0),
base::BindOnce(&LensSearchContextualizationController::
OnPdfPartialPageTextRetrieved,
weak_ptr_factory_.GetWeakPtr()));
}
#endif
GetQueryController()->SendUpdatedPageContent(
page_contents_, primary_content_type_,
lens_search_controller_->GetPageURL(),
lens_search_controller_->GetPageTitle(),
last_retrieved_most_visible_page_, sending_bitmap ? bitmap : SkBitmap());
RecordDocumentMetrics(page_count.value_or(0));
lens_search_controller_->lens_session_metrics_logger()
->OnFollowUpPageContentRetrieved(primary_content_type);
if (on_page_context_updated_callback_) {
std::move(on_page_context_updated_callback_).Run();
}
}
void LensSearchContextualizationController::MaybeGetInnerText(
std::vector<lens::PageContent> page_contents,
content::RenderFrameHost* render_frame_host,
PageContentRetrievedCallback callback) {
if (!lens::features::UseInnerTextAsContext()) {
MaybeGetAnnotatedPageContent(page_contents, render_frame_host,
std::move(callback));
return;
}
content_extraction::GetInnerText(
*render_frame_host, std::nullopt,
base::BindOnce(
&LensSearchContextualizationController::OnInnerTextReceived,
weak_ptr_factory_.GetWeakPtr(), page_contents, render_frame_host,
std::move(callback)));
}
void LensSearchContextualizationController::OnInnerTextReceived(
std::vector<lens::PageContent> page_contents,
content::RenderFrameHost* render_frame_host,
PageContentRetrievedCallback callback,
std::unique_ptr<content_extraction::InnerTextResult> result) {
const bool was_successful =
result && result->inner_text.size() <=
lens::features::GetLensOverlayFileUploadLimitBytes();
page_contents.emplace_back(
was_successful
? std::vector<uint8_t>(result->inner_text.begin(),
result->inner_text.end())
: std::vector<uint8_t>{},
lens::MimeType::kPlainText);
MaybeGetAnnotatedPageContent(page_contents, render_frame_host,
std::move(callback));
}
void LensSearchContextualizationController::MaybeGetAnnotatedPageContent(
std::vector<lens::PageContent> page_contents,
content::RenderFrameHost* render_frame_host,
PageContentRetrievedCallback callback) {
if (!lens::features::UseApcAsContext()) {
auto primary_content_type = lens::features::UseInnerTextAsContext()
? lens::MimeType::kPlainText
: lens::MimeType::kHtml;
std::move(callback).Run(page_contents, primary_content_type, std::nullopt);
return;
}
blink::mojom::AIPageContentOptionsPtr ai_page_content_options =
optimization_guide::DefaultAIPageContentOptions(
true);
ai_page_content_options->max_meta_elements = 20;
optimization_guide::GetAIPageContent(
lens_search_controller_->GetTabInterface()->GetContents(),
std::move(ai_page_content_options),
base::BindOnce(&LensSearchContextualizationController::
OnAnnotatedPageContentReceived,
weak_ptr_factory_.GetWeakPtr(), page_contents,
std::move(callback)));
}
void LensSearchContextualizationController::OnAnnotatedPageContentReceived(
std::vector<lens::PageContent> page_contents,
PageContentRetrievedCallback callback,
optimization_guide::AIPageContentResultOrError result) {
const auto& tab_url = lens_search_controller_->GetTabInterface()
->GetContents()
->GetLastCommittedURL();
if (result.has_value()) {
std::vector<optimization_guide::FrameMetadata> frame_metadata_structs =
optimization_guide::GetFrameMetadataFromPageContent(result.value());
IsPageContextEligible(
tab_url, std::move(frame_metadata_structs),
base::BindOnce(&LensSearchContextualizationController::
OnPageContextEligibilityFetched,
weak_ptr_factory_.GetWeakPtr(), std::move(page_contents),
std::move(callback), std::move(result.value())));
return;
}
IsPageContextEligible(
tab_url, {},
base::BindOnce(&LensSearchContextualizationController::
OnPageContextEligibilityFetched,
weak_ptr_factory_.GetWeakPtr(), std::move(page_contents),
std::move(callback), std::nullopt));
}
void LensSearchContextualizationController::OnPageContextEligibilityFetched(
std::vector<lens::PageContent> page_contents,
PageContentRetrievedCallback callback,
std::optional<optimization_guide::AIPageContentResult> result,
bool is_page_context_eligible) {
if (!is_page_context_eligible) {
is_page_context_eligible_ = false;
lens_search_controller_->lens_overlay_side_panel_coordinator()
->SetShowProtectedErrorPage(true);
page_contents.clear();
} else if (result) {
std::string serialized_apc;
result->proto.SerializeToString(&serialized_apc);
page_contents.emplace_back(
std::vector<uint8_t>(serialized_apc.begin(), serialized_apc.end()),
lens::MimeType::kAnnotatedPageContent);
}
std::move(callback).Run(page_contents, lens::MimeType::kAnnotatedPageContent,
std::nullopt);
}
#if BUILDFLAG(ENABLE_PDF)
void LensSearchContextualizationController::MaybeGetPdfBytes(
pdf::PDFDocumentHelper* pdf_helper,
PageContentRetrievedCallback callback) {
CHECK(pdf_helper);
pdf_helper->GetPdfBytes(
lens::features::GetLensOverlayFileUploadLimitBytes(),
base::BindOnce(&LensSearchContextualizationController::OnPdfBytesReceived,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void LensSearchContextualizationController::OnPdfBytesReceived(
PageContentRetrievedCallback callback,
pdf::mojom::PdfListener::GetPdfBytesStatus status,
const std::vector<uint8_t>& bytes,
uint32_t page_count) {
if (status != pdf::mojom::PdfListener::GetPdfBytesStatus::kSuccess ||
page_count == 0) {
std::move(callback).Run(
{lens::PageContent({}, lens::MimeType::kPdf)},
lens::MimeType::kPdf, page_count);
return;
}
std::move(callback).Run({lens::PageContent(bytes, lens::MimeType::kPdf)},
lens::MimeType::kPdf, page_count);
}
void LensSearchContextualizationController::GetPartialPdfTextCallback(
uint32_t page_index,
uint32_t total_page_count,
uint32_t total_characters_retrieved,
const std::u16string& page_text) {
CHECK_GE(total_page_count, 1u);
CHECK_LT(page_index, total_page_count);
CHECK_EQ(pdf_pages_text_.size(), page_index);
pdf_pages_text_.push_back(page_text);
base::CheckedNumeric<uint32_t> total_characters_retrieved_check =
total_characters_retrieved;
total_characters_retrieved_check += page_text.size();
total_characters_retrieved = total_characters_retrieved_check.ValueOrDefault(
std::numeric_limits<uint32_t>::max());
pdf::PDFDocumentHelper* pdf_helper =
pdf::PDFDocumentHelper::MaybeGetForWebContents(
lens_search_controller_->GetTabInterface()->GetContents());
if (!pdf_helper ||
total_characters_retrieved >=
lens::features::GetLensOverlayPdfSuggestCharacterTarget() ||
page_index + 1 >= total_page_count) {
std::move(pdf_partial_page_text_retrieved_callback_).Run(pdf_pages_text_);
if (!lens_search_controller_->should_route_to_contextual_tasks()) {
GetQueryController()->SendPartialPageContentRequest(pdf_pages_text_);
}
return;
}
pdf_helper->GetPageText(
page_index + 1,
base::BindOnce(
&LensSearchContextualizationController::GetPartialPdfTextCallback,
weak_ptr_factory_.GetWeakPtr(), page_index + 1, total_page_count,
total_characters_retrieved));
}
void LensSearchContextualizationController::OnPdfPartialPageTextRetrieved(
std::vector<std::u16string> pdf_pages_text) {
pdf_pages_text_ = std::move(pdf_pages_text);
}
#endif
bool LensSearchContextualizationController::IsScreenshotPossible(
content::RenderWidgetHostView* view) {
return view && view->IsSurfaceAvailableForCopy();
}
void LensSearchContextualizationController::StartScreenshotFlow(
OnScreenshotTakenCallback callback) {
content::RenderWidgetHostView* view =
lens_search_controller_->GetTabInterface()
->GetContents()
->GetPrimaryMainFrame()
->GetRenderViewHost()
->GetWidget()
->GetView();
if (!IsScreenshotPossible(view)) {
std::move(callback).Run(SkBitmap(), {}, std::nullopt);
return;
}
view->CopyFromSurface(
gfx::Rect(), gfx::Size(),
base::BindPostTask(
base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(&LensSearchContextualizationController::
FetchViewportImageBoundingBoxes,
weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
}
void LensSearchContextualizationController::CaptureScreenshot(
base::OnceCallback<void(const SkBitmap&)> callback) {
content::RenderWidgetHostView* view =
lens_search_controller_->GetTabInterface()
->GetContents()
->GetPrimaryMainFrame()
->GetRenderViewHost()
->GetWidget()
->GetView();
if (!IsScreenshotPossible(view)) {
std::move(callback).Run(SkBitmap());
return;
}
view->CopyFromSurface(
gfx::Rect(), gfx::Size(),
base::BindPostTask(
base::SequencedTaskRunner::GetCurrentDefault(),
base::BindOnce(&LensSearchContextualizationController::
OnScreenshotCapturedForUpdate,
weak_ptr_factory_.GetWeakPtr(),
++screenshot_attempt_id_, std::move(callback))));
}
void LensSearchContextualizationController::OnScreenshotCapturedForUpdate(
int attempt_id,
base::OnceCallback<void(const SkBitmap&)> callback,
const viz::CopyOutputBitmapWithMetadata& result) {
if (attempt_id != screenshot_attempt_id_) {
return;
}
std::move(callback).Run(result.bitmap);
}
void LensSearchContextualizationController::DidCaptureScreenshot(
mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame>
chrome_render_frame,
int attempt_id,
const SkBitmap& bitmap,
const std::vector<gfx::Rect>& bounds,
OnScreenshotTakenCallback callback,
std::optional<uint32_t> pdf_current_page) {
if (screenshot_attempt_id_ != attempt_id) {
return;
}
if (bitmap.drawsNothing()) {
std::move(callback).Run(SkBitmap(), {}, std::nullopt);
lens_search_controller_->CloseLensSync(
lens::LensOverlayDismissalSource::kErrorScreenshotCreationFailed);
return;
}
std::move(callback).Run(bitmap, bounds, pdf_current_page);
}
void LensSearchContextualizationController::OnScreenshotTakenForContextual(
OnPageContextUpdatedCallback callback,
const SkBitmap& bitmap,
const std::vector<gfx::Rect>& all_bounds,
std::optional<uint32_t> pdf_current_page) {
const auto& tab_url = lens_search_controller_->GetTabInterface()
->GetContents()
->GetLastCommittedURL();
IsPageContextEligible(
tab_url, {},
base::BindOnce(&LensSearchContextualizationController::
OnInitialPageContextEligibilityFetched,
weak_ptr_factory_.GetWeakPtr(), bitmap, all_bounds,
pdf_current_page, std::move(callback)));
}
void LensSearchContextualizationController::IsPageContextEligible(
const GURL& main_frame_url,
std::vector<optimization_guide::FrameMetadata> frame_metadata,
LensSearchPageContextEligibilityCallback callback) {
if (!IsProtectedPageFeatureEnabled()) {
std::move(callback).Run(true);
return;
}
if (!page_context_eligibility_) {
if (has_page_context_eligibility_api_loaded_) {
std::move(callback).Run(false);
return;
}
pending_context_eligibility_params_.emplace(main_frame_url,
std::move(frame_metadata));
page_context_eligibility_callback_ = std::move(callback);
return;
}
std::move(callback).Run(optimization_guide::IsPageContextEligible(
main_frame_url.GetHost(), main_frame_url.GetPath(),
std::move(frame_metadata), page_context_eligibility_));
}
void LensSearchContextualizationController::CreatePageContextEligibilityAPI() {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
base::BindOnce(&optimization_guide::PageContextEligibility::Get),
base::BindOnce(&LensSearchContextualizationController::
OnPageContextEligibilityAPILoaded,
weak_ptr_factory_.GetWeakPtr()));
}
bool LensSearchContextualizationController::GetCurrentPageContextEligibility() {
if (!IsProtectedPageFeatureEnabled()) {
return true;
}
return is_page_context_eligible_ && has_page_context_eligibility_api_loaded_;
}
LensSearchContextualizationController::PageContextEligibilityParams::
PageContextEligibilityParams(
const GURL& main_frame_url,
std::vector<optimization_guide::FrameMetadata> frame_metadata)
: main_frame_url(main_frame_url),
frame_metadata(std::move(frame_metadata)) {}
LensSearchContextualizationController::PageContextEligibilityParams::
~PageContextEligibilityParams() = default;
void LensSearchContextualizationController::OnPageContextEligibilityAPILoaded(
optimization_guide::PageContextEligibility* page_context_eligibility) {
page_context_eligibility_ = page_context_eligibility;
has_page_context_eligibility_api_loaded_ = true;
if (page_context_eligibility_callback_ &&
pending_context_eligibility_params_) {
std::move(page_context_eligibility_callback_)
.Run(optimization_guide::IsPageContextEligible(
pending_context_eligibility_params_->main_frame_url.GetHost(),
pending_context_eligibility_params_->main_frame_url.GetPath(),
std::move(pending_context_eligibility_params_->frame_metadata),
page_context_eligibility_));
pending_context_eligibility_params_.reset();
}
}
void LensSearchContextualizationController::
OnInitialPageContextEligibilityFetched(
const SkBitmap& bitmap,
const std::vector<gfx::Rect>& all_bounds,
std::optional<uint32_t> pdf_current_page,
OnPageContextUpdatedCallback callback,
bool is_page_context_eligible) {
auto bitmap_to_send = bitmap;
auto page_url = lens_search_controller_->GetPageURL();
auto page_title = lens_search_controller_->GetPageTitle();
if (!is_page_context_eligible) {
is_page_context_eligible_ = false;
lens_search_controller_->lens_overlay_side_panel_coordinator()
->SetShowProtectedErrorPage(true);
bitmap_to_send = SkBitmap();
page_url = GURL();
page_title = "";
}
viewport_screenshot_ = bitmap_to_send;
page_url_ = page_url;
page_title_ = page_title;
GetQueryController()->StartQueryFlow(
viewport_screenshot_, page_url_, page_title_,
ConvertSignificantRegionBoxes(all_bounds),
std::vector<lens::PageContent>(), lens::MimeType::kUnknown,
pdf_current_page, GetUiScaleFactor(), base::TimeTicks::Now());
lens_search_controller_->HandleThumbnailCreatedBitmap(bitmap_to_send);
state_ = State::kActive;
TryUpdatePageContextualization(std::move(callback));
}
void LensSearchContextualizationController::FetchViewportImageBoundingBoxes(
OnScreenshotTakenCallback callback,
const viz::CopyOutputBitmapWithMetadata& result) {
const SkBitmap& bitmap = result.bitmap;
content::RenderFrameHost* render_frame_host =
lens_search_controller_->GetTabInterface()
->GetContents()
->GetPrimaryMainFrame();
mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> chrome_render_frame;
render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
&chrome_render_frame);
auto* frame = chrome_render_frame.get();
frame->RequestBoundsHintForAllImages(base::BindOnce(
&LensSearchContextualizationController::GetPdfCurrentPage,
weak_ptr_factory_.GetWeakPtr(), std::move(chrome_render_frame),
++screenshot_attempt_id_, bitmap, std::move(callback)));
}
void LensSearchContextualizationController::GetPdfCurrentPage(
mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame>
chrome_render_frame,
int attempt_id,
const SkBitmap& bitmap,
OnScreenshotTakenCallback callback,
const std::vector<gfx::Rect>& bounds) {
#if BUILDFLAG(ENABLE_PDF)
pdf::PDFDocumentHelper* pdf_helper =
pdf::PDFDocumentHelper::MaybeGetForWebContents(
lens_search_controller_->GetTabInterface()->GetContents());
if (pdf_helper) {
pdf_helper->GetMostVisiblePageIndex(base::BindOnce(
&LensSearchContextualizationController::DidCaptureScreenshot,
weak_ptr_factory_.GetWeakPtr(), std::move(chrome_render_frame),
attempt_id, bitmap, bounds, std::move(callback)));
return;
}
#endif
DidCaptureScreenshot(std::move(chrome_render_frame), attempt_id, bitmap,
bounds, std::move(callback),
std::nullopt);
}
void LensSearchContextualizationController::RecordInnerTextSize(
std::unique_ptr<content_extraction::InnerTextResult> result) {
if (!result) {
return;
}
lens::RecordDocumentSizeBytes(lens::MimeType::kPlainText,
result->inner_text.size());
}
std::vector<lens::mojom::CenterRotatedBoxPtr>
LensSearchContextualizationController::ConvertSignificantRegionBoxes(
const std::vector<gfx::Rect>& all_bounds) {
std::vector<lens::mojom::CenterRotatedBoxPtr> significant_region_boxes;
int max_regions = lens::features::GetLensOverlayMaxSignificantRegions();
if (max_regions == 0) {
return significant_region_boxes;
}
content::RenderFrameHost* render_frame_host =
lens_search_controller_->GetTabInterface()
->GetContents()
->GetPrimaryMainFrame();
auto view_bounds = render_frame_host->GetView()->GetViewBounds();
for (auto& image_bounds : all_bounds) {
if (image_bounds.width() * image_bounds.height() >=
lens::features::GetLensOverlaySignificantRegionMinArea()) {
significant_region_boxes.emplace_back(
lens::GetCenterRotatedBoxFromTabViewAndImageBounds(
view_bounds, view_bounds, image_bounds));
}
}
std::erase_if(significant_region_boxes, [](const auto& box) {
return box->box.height() == 0 || box->box.width() == 0;
});
std::sort(significant_region_boxes.begin(), significant_region_boxes.end(),
[](const auto& box1, const auto& box2) {
return box1->box.height() * box1->box.width() >
box2->box.height() * box2->box.width();
});
if (max_regions > 0 && significant_region_boxes.size() >
static_cast<unsigned long>(max_regions)) {
significant_region_boxes.resize(max_regions);
}
return significant_region_boxes;
}
float LensSearchContextualizationController::GetUiScaleFactor() {
int device_scale_factor = lens_search_controller_->GetTabInterface()
->GetContents()
->GetRenderWidgetHostView()
->GetDeviceScaleFactor();
float page_scale_factor =
zoom::ZoomController::FromWebContents(
lens_search_controller_->GetTabInterface()->GetContents())
->GetZoomPercent() /
100.0f;
return device_scale_factor * page_scale_factor;
}
lens::LensOverlayQueryController*
LensSearchContextualizationController::GetQueryController() {
auto* query_controller =
lens_search_controller_->lens_overlay_query_controller();
CHECK(query_controller);
return query_controller;
}
lens::LensSearchboxController*
LensSearchContextualizationController::GetSearchboxController() {
auto* searchbox_controller =
lens_search_controller_->lens_searchbox_controller();
CHECK(searchbox_controller);
return searchbox_controller;
}
}