[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:

committed by
Chromium LUCI CQ

parent
a5b4193af4
commit
812f0e219b
@ -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:
|
||||
|
Reference in New Issue
Block a user