0

[unseasoned-pdf] Handle IME input.

Port applicable PepperWebPluginImpl and PepperPluginInstanceImpl logic
to PdfViewWebPlugin. This provides the same basic level of IME support
as the PPAPI-based PDF Viewer.

Bug: 1230168
Change-Id: I9611c48eb61e527421ee8cf9d0c073de48acabab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3184085
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: K. Moon <kmoon@chromium.org>
Cr-Commit-Position: refs/heads/main@{#925914}
This commit is contained in:
Lei Zhang
2021-09-28 20:30:58 +00:00
committed by Chromium LUCI CQ
parent 0d20cc78bc
commit a5482d7083
3 changed files with 230 additions and 0 deletions

@ -13,6 +13,7 @@
#include <vector>
#include "base/check_op.h"
#include "base/i18n/char_iterator.h"
#include "base/i18n/string_search.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
@ -69,6 +70,7 @@
#include "ui/base/cursor/cursor.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/display/screen_info.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/blink/blink_event_util.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/geometry/point.h"
@ -595,10 +597,40 @@ void PdfViewWebPlugin::RotateView(blink::WebPlugin::RotationType type) {
}
}
bool PdfViewWebPlugin::ShouldDispatchImeEventsToPlugin() {
return true;
}
blink::WebTextInputType PdfViewWebPlugin::GetPluginTextInputType() {
return text_input_type_;
}
gfx::Rect PdfViewWebPlugin::GetPluginCaretBounds() {
return caret_rect_;
}
void PdfViewWebPlugin::ImeSetCompositionForPlugin(
const blink::WebString& text,
const std::vector<ui::ImeTextSpan>& /*ime_text_spans*/,
const gfx::Range& /*replacement_range*/,
int /*selection_start*/,
int /*selection_end*/) {
composition_text_ = text;
}
void PdfViewWebPlugin::ImeCommitTextForPlugin(
const blink::WebString& text,
const std::vector<ui::ImeTextSpan>& /*ime_text_spans*/,
const gfx::Range& /*replacement_range*/,
int /*relative_cursor_pos*/) {
HandleImeCommit(text);
}
void PdfViewWebPlugin::ImeFinishComposingTextForPlugin(
bool /*keep_selection*/) {
HandleImeCommit(composition_text_);
}
void PdfViewWebPlugin::UpdateCursor(ui::mojom::CursorType new_cursor_type) {
set_cursor_type(new_cursor_type);
}
@ -612,6 +644,11 @@ void PdfViewWebPlugin::NotifySelectedFindResultChanged(int current_find_index) {
current_find_index + 1);
}
void PdfViewWebPlugin::CaretChanged(const gfx::Rect& caret_rect) {
caret_rect_ = gfx::ScaleToEnclosingRectSafe(
caret_rect + available_area().OffsetFromOrigin(), device_to_css_scale_);
}
void PdfViewWebPlugin::Alert(const std::string& message) {
container_wrapper_->Alert(blink::WebString::FromUTF8(message));
}
@ -957,6 +994,32 @@ bool PdfViewWebPlugin::Redo() {
return true;
}
void PdfViewWebPlugin::HandleImeCommit(const blink::WebString& text) {
if (text.IsEmpty())
return;
std::u16string text16 = text.Utf16();
composition_text_.Reset();
size_t i = 0;
for (base::i18n::UTF16CharIterator iterator(text16); iterator.Advance();) {
blink::WebKeyboardEvent char_event(blink::WebInputEvent::Type::kChar,
blink::WebInputEvent::kNoModifiers,
ui::EventTimeForNow());
char_event.windows_key_code = text16[i];
char_event.native_key_code = text16[i];
for (const size_t char_start = i; i < iterator.array_pos(); ++i) {
char_event.text[i - char_start] = text16[i];
char_event.unmodified_text[i - char_start] = text16[i];
}
blink::WebCoalescedInputEvent input_event(char_event, ui::LatencyInfo());
ui::Cursor dummy_cursor_info;
HandleInputEvent(input_event, &dummy_cursor_info);
}
}
void PdfViewWebPlugin::OnInvokePrintDialog(int32_t /*result*/) {
client_->Print(Container()->GetElement());
}

@ -198,11 +198,27 @@ class PdfViewWebPlugin final : public PdfViewPluginBase,
void StopFind() override;
bool CanRotateView() override;
void RotateView(blink::WebPlugin::RotationType type) override;
bool ShouldDispatchImeEventsToPlugin() override;
blink::WebTextInputType GetPluginTextInputType() override;
gfx::Rect GetPluginCaretBounds() override;
void ImeSetCompositionForPlugin(
const blink::WebString& text,
const std::vector<ui::ImeTextSpan>& ime_text_spans,
const gfx::Range& replacement_range,
int selection_start,
int selection_end) override;
void ImeCommitTextForPlugin(
const blink::WebString& text,
const std::vector<ui::ImeTextSpan>& ime_text_spans,
const gfx::Range& replacement_range,
int relative_cursor_pos) override;
void ImeFinishComposingTextForPlugin(bool keep_selection) override;
// PdfViewPluginBase:
void UpdateCursor(ui::mojom::CursorType new_cursor_type) override;
void NotifySelectedFindResultChanged(int current_find_index) override;
void CaretChanged(const gfx::Rect& caret_rect) override;
void Alert(const std::string& message) override;
bool Confirm(const std::string& message) override;
std::string Prompt(const std::string& question,
@ -302,6 +318,10 @@ class PdfViewWebPlugin final : public PdfViewPluginBase,
bool Undo();
bool Redo();
// Helper method for converting IME text to input events.
// TODO(crbug.com/1253665): Consider handling composition events.
void HandleImeCommit(const blink::WebString& text);
// Callback to print without re-entrancy issues. The callback prevents the
// invocation of printing in the middle of an event handler, which is risky;
// see crbug.com/66334.
@ -331,6 +351,10 @@ class PdfViewWebPlugin final : public PdfViewPluginBase,
blink::WebTextInputType text_input_type_ =
blink::WebTextInputType::kWebTextInputTypeNone;
gfx::Rect caret_rect_;
blink::WebString composition_text_;
// Whether the plugin element currently has focus.
bool has_focus_ = false;

@ -5,8 +5,10 @@
#include "pdf/pdf_view_web_plugin.h"
#include <memory>
#include <string>
#include <utility>
#include "base/strings/string_piece.h"
#include "cc/paint/paint_canvas.h"
#include "cc/test/pixel_comparator.h"
#include "cc/test/pixel_test_utils.h"
@ -18,6 +20,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/input/web_coalesced_input_event.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/input/web_keyboard_event.h"
#include "third_party/blink/public/common/input/web_mouse_event.h"
#include "third_party/blink/public/common/input/web_pointer_properties.h"
#include "third_party/blink/public/platform/web_string.h"
@ -30,6 +33,8 @@
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/cursor/cursor.h"
#include "ui/events/blink/blink_event_util.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
@ -83,6 +88,20 @@ MATCHER(SearchStringResultEq, "") {
return l.start_index == r.start_index && l.length == r.length;
}
MATCHER_P(IsExpectedImeKeyEvent, expected_text, "") {
if (arg.GetType() != blink::WebInputEvent::Type::kChar)
return false;
const auto& event = static_cast<const blink::WebKeyboardEvent&>(arg);
return event.GetModifiers() == blink::WebInputEvent::kNoModifiers &&
event.windows_key_code == expected_text[0] &&
event.native_key_code == expected_text[0] &&
event.dom_code == static_cast<int>(ui::DomCode::NONE) &&
event.dom_key == ui::DomKey::NONE && !event.is_system_key &&
!event.is_browser_shortcut && event.text == expected_text &&
event.unmodified_text == expected_text;
}
// Generates the expected `SkBitmap` with `paint_color` filled in the expected
// clipped area and `kDefaultColor` as the background color.
SkBitmap GenerateExpectedBitmapForPaint(const gfx::Rect& expected_clipped_rect,
@ -560,6 +579,106 @@ TEST_F(PdfViewWebPluginMouseEventsTest,
EXPECT_EQ(gfx::PointF(-20.0f, 0.0f), event->PositionInWidget());
}
class PdfViewWebPluginImeTest : public PdfViewWebPluginTest {
public:
class TestPDFiumEngineForIme : public TestPDFiumEngine {
public:
explicit TestPDFiumEngineForIme(PDFEngine::Client* client)
: TestPDFiumEngine(client) {}
// TestPDFiumEngine:
MOCK_METHOD(bool,
HandleInputEvent,
(const blink::WebInputEvent&),
(override));
};
std::unique_ptr<TestPDFiumEngine> CreateEngine() override {
return std::make_unique<TestPDFiumEngineForIme>(plugin_.get());
}
TestPDFiumEngineForIme* engine() {
return static_cast<TestPDFiumEngineForIme*>(engine_ptr_);
}
void TestImeSetCompositionForPlugin(const blink::WebString& text) {
EXPECT_CALL(*engine(), HandleInputEvent).Times(0);
plugin_->ImeSetCompositionForPlugin(text, std::vector<ui::ImeTextSpan>(),
gfx::Range(),
/*selection_start=*/0,
/*selection_end=*/0);
}
void TestImeFinishComposingTextForPlugin(
const blink::WebString& expected_text) {
InSequence sequence;
std::u16string expected_text16 = expected_text.Utf16();
if (expected_text16.size()) {
for (const auto& c : expected_text16) {
base::StringPiece16 expected_key(&c, 1);
EXPECT_CALL(*engine(),
HandleInputEvent(IsExpectedImeKeyEvent(expected_key)))
.WillOnce(Return(true));
}
} else {
EXPECT_CALL(*engine(), HandleInputEvent).Times(0);
}
plugin_->ImeFinishComposingTextForPlugin(false);
}
void TestImeCommitTextForPlugin(const blink::WebString& text) {
InSequence sequence;
std::u16string expected_text16 = text.Utf16();
if (expected_text16.size()) {
for (const auto& c : expected_text16) {
base::StringPiece16 event(&c, 1);
EXPECT_CALL(*engine(), HandleInputEvent(IsExpectedImeKeyEvent(event)))
.WillOnce(Return(true));
}
} else {
EXPECT_CALL(*engine(), HandleInputEvent).Times(0);
}
plugin_->ImeCommitTextForPlugin(text, std::vector<ui::ImeTextSpan>(),
gfx::Range(),
/*relative_cursor_pos=*/0);
}
};
TEST_F(PdfViewWebPluginImeTest, ImeSetCompositionAndFinishAscii) {
const blink::WebString text = blink::WebString::FromASCII("input");
TestImeSetCompositionForPlugin(text);
TestImeFinishComposingTextForPlugin(text);
}
TEST_F(PdfViewWebPluginImeTest, ImeSetCompositionAndFinishUnicode) {
const blink::WebString text = blink::WebString::FromUTF16(u"你好");
TestImeSetCompositionForPlugin(text);
TestImeFinishComposingTextForPlugin(text);
// Calling ImeFinishComposingTextForPlugin() again is a no-op.
TestImeFinishComposingTextForPlugin("");
}
TEST_F(PdfViewWebPluginImeTest, ImeSetCompositionAndFinishEmpty) {
const blink::WebString text;
TestImeSetCompositionForPlugin(text);
TestImeFinishComposingTextForPlugin(text);
}
TEST_F(PdfViewWebPluginImeTest, ImeCommitTextForPluginAscii) {
const blink::WebString text = blink::WebString::FromASCII("a b");
TestImeCommitTextForPlugin(text);
}
TEST_F(PdfViewWebPluginImeTest, ImeCommitTextForPluginUnicode) {
const blink::WebString text = blink::WebString::FromUTF16(u"さようなら");
TestImeCommitTextForPlugin(text);
}
TEST_F(PdfViewWebPluginImeTest, ImeCommitTextForPluginEmpty) {
const blink::WebString text;
TestImeCommitTextForPlugin(text);
}
TEST_F(PdfViewWebPluginTest, ChangeTextSelection) {
ASSERT_FALSE(plugin_->HasSelection());
ASSERT_TRUE(plugin_->SelectionAsText().IsEmpty());
@ -670,4 +789,28 @@ TEST_F(PdfViewWebPluginTest, UpdateFocus) {
plugin_->UpdateFocus(/*focused=*/true, blink::mojom::FocusType::kNone);
}
TEST_F(PdfViewWebPluginTest, ShouldDispatchImeEventsToPlugin) {
ASSERT_TRUE(plugin_->ShouldDispatchImeEventsToPlugin());
}
TEST_F(PdfViewWebPluginTest, CaretChangeUseZoomForDSFEnabled) {
EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
.WillRepeatedly(Return(true));
EXPECT_CALL(*engine_ptr_, ZoomUpdated(2.0f));
UpdatePluginGeometry(
/*device_scale=*/2.0f, /*window_rect=*/gfx::Rect(12, 24, 36, 48));
plugin_->CaretChanged(gfx::Rect(10, 20, 30, 40));
EXPECT_EQ(gfx::Rect(28, 20, 30, 40), plugin_->GetPluginCaretBounds());
}
TEST_F(PdfViewWebPluginTest, CaretChangeUseZoomForDSFDisabled) {
EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
.WillRepeatedly(Return(false));
EXPECT_CALL(*engine_ptr_, ZoomUpdated(2.0f));
UpdatePluginGeometry(
/*device_scale=*/2.0f, /*window_rect=*/gfx::Rect(12, 24, 36, 48));
plugin_->CaretChanged(gfx::Rect(10, 20, 30, 40));
EXPECT_EQ(gfx::Rect(23, 10, 15, 20), plugin_->GetPluginCaretBounds());
}
} // namespace chrome_pdf