0

[lensoverlay] Add message plumbing for rendering text fragments in PDF.

The parsing, searching, and rendering of the text fragments will take
place in follow-up CLs. This message is currently a no-op.

Change-Id: I2e1b2ce5eb5bd3bb638d1ceb8acd14a59a0d5684
Bug: 383575917
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6166811
Reviewed-by: Andy Phan <andyphan@chromium.org>
Commit-Queue: Juan Mojica <juanmojica@google.com>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1406772}
This commit is contained in:
Juan Mojica
2025-01-15 09:07:40 -08:00
committed by Chromium LUCI CQ
parent a5b4193af4
commit 812f0e219b
10 changed files with 127 additions and 0 deletions

@ -339,6 +339,13 @@ export class PluginController implements ContentController {
this.postMessage_({type: 'selectAll'});
}
highlightTextFragments(textFragments: string[]) {
this.postMessage_({
type: 'highlightTextFragments',
textFragments,
});
}
getSelectedText(): Promise<{selectedText: string}> {
return this.postMessageWithReply_({type: 'getSelectedText'});
}

@ -29,6 +29,8 @@ export enum ViewMode {
XYZ = 'xyz',
}
const FRAGMENT_DIRECTIVE_DELIMITER = ':~:';
type GetNamedDestinationCallback = (name: string) =>
Promise<NamedDestinationMessageData>;
@ -306,6 +308,26 @@ export class OpenPdfParamsParser {
return navpanes === '1';
}
/**
* Fetch text fragment directives that appear in the PDF URL if any.
*
* @param url that needs to be parsed.
* @return The text fragment directives or an empty array if they do not
* exist.
*/
getTextFragments(url: string): string[] {
const hash = new URL(url).hash;
// Handle the case of text directives included in the URL.
if (!hash.includes(FRAGMENT_DIRECTIVE_DELIMITER)) {
return [];
}
// Splitting the hash by the delimiter should always have at least two
// items since there was already a check for if the delimiter existed.
return new URLSearchParams(hash.split(FRAGMENT_DIRECTIVE_DELIMITER)[1])
.getAll('text');
}
/**
* Parse PDF url parameters. These parameters are mentioned in the url
* and specify actions to be performed when opening pdf files.

@ -741,6 +741,12 @@ export class PdfViewerElement extends PdfViewerBaseElement {
this.loadProgress_ = progress;
}
super.updateProgress(progress);
// Text fragment directives should be handled after the document is set to
// finished loading.
if (progress === 100) {
this.maybeRenderTextDirectiveHighlights_();
}
}
protected onErrorDialog_() {
@ -1268,6 +1274,18 @@ export class PdfViewerElement extends PdfViewerBaseElement {
}
// </if>
/**
* Sends a message to the PDF plugin to highlight the provided text
* directives if any.
*/
private maybeRenderTextDirectiveHighlights_() {
assert(this.paramsParser);
const textDirectives = this.paramsParser.getTextFragments(this.originalUrl);
if (textDirectives.length > 0) {
this.pluginController_.highlightTextFragments(textDirectives);
}
}
/**
* Saves the current PDF document to disk.
*/

@ -502,6 +502,40 @@ chrome.test.runTests([
chrome.test.assertEq(300, params.boundingBox.height);
chrome.test.assertEq(100, params.viewPosition);
chrome.test.succeed();
},
function testParamsGetTextFragments() {
const paramsParser = getParamsParser();
// Checking single text fragment.
let fragments = paramsParser.getTextFragments(`${URL}#:~:text=apples`);
chrome.test.assertEq(fragments.length, 1);
chrome.test.assertEq(fragments[0], 'apples');
// Checking multiple text fragments.
fragments = paramsParser.getTextFragments(
`${URL}#:~:text=apples&text=oranges&text=hello-,world,there,-world`);
chrome.test.assertEq(fragments.length, 3);
chrome.test.assertEq(fragments[0], 'apples');
chrome.test.assertEq(fragments[1], 'oranges');
chrome.test.assertEq(fragments[2], 'hello-,world,there,-world');
// Checking case where no text fragments are present.
fragments = paramsParser.getTextFragments(`${URL}#page=3`);
chrome.test.assertEq(fragments.length, 0);
// Check case where only delimiter is present.
fragments = paramsParser.getTextFragments(`${URL}#:~:`);
chrome.test.assertEq(fragments.length, 0);
// Check case where there are other viewport parameters before the text
// fragments.
fragments = paramsParser.getTextFragments(
`${URL}#page=3&view=FitBV:~:text=foo&text=bar`);
chrome.test.assertEq(fragments.length, 2);
chrome.test.assertEq(fragments[0], 'foo');
chrome.test.assertEq(fragments[1], 'bar');
chrome.test.succeed();
},
]);

@ -19,6 +19,7 @@
#include "base/compiler_specific.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/queue.h"
#include "base/containers/span.h"
#include "base/debug/crash_logging.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
@ -1595,6 +1596,8 @@ void PdfViewWebPlugin::OnMessage(const base::Value::Dict& message) {
&PdfViewWebPlugin::HandleGetPasswordCompleteMessage},
{"getSelectedText", &PdfViewWebPlugin::HandleGetSelectedTextMessage},
{"getThumbnail", &PdfViewWebPlugin::HandleGetThumbnailMessage},
{"highlightTextFragments",
&PdfViewWebPlugin::HandleHighlightTextFragmentsMessage},
{"print", &PdfViewWebPlugin::HandlePrintMessage},
{"loadPreviewPage", &PdfViewWebPlugin::HandleLoadPreviewPageMessage},
{"resetPrintPreviewMode",
@ -1703,6 +1706,17 @@ void PdfViewWebPlugin::HandleGetThumbnailMessage(
weak_factory_.GetWeakPtr(), std::move(reply), page_index));
}
void PdfViewWebPlugin::HandleHighlightTextFragmentsMessage(
const base::Value::Dict& message) {
const auto* text_fragment_value_list = message.FindList("textFragments");
std::vector<std::string> text_fragments;
text_fragments.reserve(text_fragment_value_list->size());
for (const base::Value& fragment : *text_fragment_value_list) {
text_fragments.push_back(fragment.GetString());
}
engine_->HighlightTextFragments(text_fragments);
}
void PdfViewWebPlugin::HandlePrintMessage(
const base::Value::Dict& /*message*/) {
Print();

@ -547,6 +547,7 @@ class PdfViewWebPlugin final : public PDFiumEngineClient,
void HandleGetPasswordCompleteMessage(const base::Value::Dict& message);
void HandleGetSelectedTextMessage(const base::Value::Dict& message);
void HandleGetThumbnailMessage(const base::Value::Dict& message);
void HandleHighlightTextFragmentsMessage(const base::Value::Dict& message);
void HandlePrintMessage(const base::Value::Dict& /*message*/);
void HandleRotateClockwiseMessage(const base::Value::Dict& /*message*/);
void HandleRotateCounterclockwiseMessage(

@ -1866,6 +1866,20 @@ TEST_F(PdfViewWebPluginTest, OnHasSearchifyText) {
plugin_->OnHasSearchifyText();
}
TEST_F(PdfViewWebPluginTest, HighlightTextFragments) {
base::Value::List fragments;
fragments.Append("hello-,world");
fragments.Append("world,-hello");
base::Value::Dict message;
message.Set("type", "highlightTextFragments");
message.Set("textFragments", std::move(fragments));
EXPECT_CALL(*engine_ptr_, HighlightTextFragments(
ElementsAre("hello-,world", "world,-hello")));
plugin_->OnMessage(message);
}
class PdfViewWebPluginWithDocInfoTest : public PdfViewWebPluginTest {
public:
void SetUp() override {

@ -992,6 +992,12 @@ void PDFiumEngine::SetFormHighlight(bool enable_form) {
KillFormFocus();
}
void PDFiumEngine::HighlightTextFragments(
base::span<const std::string> text_fragments) {
// TODO(crbug.com/383575917): Implement parsing, searching, and rendering of
// text fragments.
}
void PDFiumEngine::ClearTextSelection() {
SelectionChangeInvalidator selection_invalidator(this);
selection_.clear();

@ -492,6 +492,12 @@ class PDFiumEngine : public DocumentLoader::Client, public IFSDK_PAUSE {
// Sets whether form highlight should be enabled or cleared.
virtual void SetFormHighlight(bool enable_form);
// Attempts to render highlights for all of the fragments provided in
// `text_fragments`. If a fragment is not found, it is skipped and the
// engine will attempt to find and highlight the next fragment in the list.
virtual void HighlightTextFragments(
base::span<const std::string> text_fragments);
private:
// This helper class is used to detect the difference in selection between
// construction and destruction. At destruction, it invalidates all the

@ -128,6 +128,11 @@ class TestPDFiumEngine : public PDFiumEngine {
MOCK_METHOD(void, SetFormHighlight, (bool), (override));
MOCK_METHOD(void,
HighlightTextFragments,
(const base::span<const std::string>),
(override));
MOCK_METHOD(void, ClearTextSelection, (), (override));
protected: