0

Hook up "Select all" context menu item in interactive PDF forms.

To do this, plumb a HasEditableText() call through PPAPI to the PDF
plugin, where it uses the FORM_GetFocusedText() PDFium API to check if
there is text to select. Enable the context menu item only if there is
text to select. Then hook up the "Select all" command to send a
synthesized ctrl + a keyboard event to the plugin.

BUG=753216

Change-Id: Ideaf19ff5e848a428e15f191e66587a0c8fe05b0
Reviewed-on: https://chromium-review.googlesource.com/640560
Reviewed-by: Bill Budge <bbudge@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Kent Tamura <tkent@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Reviewed-by: dsinclair <dsinclair@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/master@{#551652}
This commit is contained in:
Lei Zhang
2018-04-18 13:24:28 +00:00
committed by Commit Bot
parent 8e1ea4951a
commit d46818b1c5
19 changed files with 135 additions and 12 deletions

@ -7,6 +7,8 @@
#include <stdint.h>
#include <string>
#include "base/process/process_handle.h"
#include "base/strings/string16.h"
#include "content/common/content_export.h"
@ -138,9 +140,16 @@ class PepperPluginInstance {
// Returns true if the plugin text can be edited.
virtual bool CanEditText() = 0;
// Returns true if the plugin has editable text. i.e. The editable text field
// is non-empty. Assumes CanEditText() returns true.
virtual bool HasEditableText() = 0;
// Replaces the plugin's selected text, if any, with |text|. Assumes
// CanEditText() returns true.
virtual void ReplaceSelection(const std::string& text) = 0;
// Issues a select all command.
virtual void SelectAll() = 0;
};
} // namespace content

@ -89,6 +89,12 @@ bool FakePepperPluginInstance::CanEditText() {
return false;
}
bool FakePepperPluginInstance::HasEditableText() {
return false;
}
void FakePepperPluginInstance::ReplaceSelection(const std::string& text) {}
void FakePepperPluginInstance::SelectAll() {}
} // namespace content

@ -7,6 +7,8 @@
#include <stdint.h>
#include <string>
#include "content/public/renderer/pepper_plugin_instance.h"
#include "url/gurl.h"
@ -48,7 +50,9 @@ class FakePepperPluginInstance : public PepperPluginInstance {
void SetSelectionBounds(const gfx::PointF& base,
const gfx::PointF& extent) override;
bool CanEditText() override;
bool HasEditableText() override;
void ReplaceSelection(const std::string& text) override;
void SelectAll() override;
private:
GURL gurl_;

@ -125,6 +125,7 @@
#include "third_party/blink/public/web/web_view.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "ui/events/blink/blink_event_util.h"
#include "ui/events/blink/web_input_event.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_rep.h"
@ -1517,15 +1518,46 @@ void PepperPluginInstanceImpl::SetSelectionBounds(const gfx::PointF& base,
bool PepperPluginInstanceImpl::CanEditText() {
if (!LoadPdfInterface())
return false;
// No reference to |this| on the stack. Do not do any more work after this.
// See NOTE above.
return PP_ToBool(plugin_pdf_interface_->CanEditText(pp_instance()));
}
bool PepperPluginInstanceImpl::HasEditableText() {
if (!LoadPdfInterface())
return false;
// No reference to |this| on the stack. Do not do any more work after this.
// See NOTE above.
return PP_ToBool(plugin_pdf_interface_->HasEditableText(pp_instance()));
}
void PepperPluginInstanceImpl::ReplaceSelection(const std::string& text) {
if (!LoadPdfInterface())
return;
// No reference to |this| on the stack. Do not do any more work after this.
// See NOTE above.
plugin_pdf_interface_->ReplaceSelection(pp_instance(), text.c_str());
}
void PepperPluginInstanceImpl::SelectAll() {
if (!LoadPdfInterface())
return;
#if defined(OS_MACOSX)
static const ui::EventFlags kPlatformModifier = ui::EF_COMMAND_DOWN;
#else
static const ui::EventFlags kPlatformModifier = ui::EF_CONTROL_DOWN;
#endif
// Synthesize a ctrl + a key event to send to the plugin and let it sort out
// the event. See also https://crbug.com/739529.
ui::KeyEvent event(L'A', ui::VKEY_A, kPlatformModifier);
WebCursorInfo dummy_cursor_info;
// No reference to |this| on the stack. Do not do any more work after this.
// See NOTE above.
HandleInputEvent(MakeWebKeyboardEvent(event), &dummy_cursor_info);
}
void PepperPluginInstanceImpl::RequestSurroundingText(
size_t desired_number_of_characters) {
// Keep a reference on the stack. See NOTE above.

@ -417,7 +417,9 @@ class CONTENT_EXPORT PepperPluginInstanceImpl
void SetSelectionBounds(const gfx::PointF& base,
const gfx::PointF& extent) override;
bool CanEditText() override;
bool HasEditableText() override;
void ReplaceSelection(const std::string& text) override;
void SelectAll() override;
// PPB_Instance_API implementation.
PP_Bool BindGraphics(PP_Instance instance, PP_Resource device) override;

@ -294,6 +294,10 @@ bool PepperWebPluginImpl::CanEditText() const {
return instance_ && instance_->CanEditText();
}
bool PepperWebPluginImpl::HasEditableText() const {
return instance_ && instance_->HasEditableText();
}
bool PepperWebPluginImpl::ExecuteEditCommand(const blink::WebString& name) {
return ExecuteEditCommand(name, WebString());
}
@ -313,6 +317,7 @@ bool PepperWebPluginImpl::ExecuteEditCommand(const blink::WebString& name,
instance_->ReplaceSelection("");
return true;
}
// If the clipboard contains something other than text (e.g. an image),
// WebClipboard::ReadPlainText() returns an empty string. The empty string is
// then pasted, replacing any selected text. This behavior is consistent with
@ -328,6 +333,15 @@ bool PepperWebPluginImpl::ExecuteEditCommand(const blink::WebString& name,
instance_->ReplaceSelection(text.Utf8());
return true;
}
if (name == "SelectAll") {
if (!CanEditText())
return false;
instance_->SelectAll();
return true;
}
return false;
}

@ -61,6 +61,7 @@ class PepperWebPluginImpl : public blink::WebPlugin {
blink::WebString SelectionAsText() const override;
blink::WebString SelectionAsMarkup() const override;
bool CanEditText() const override;
bool HasEditableText() const override;
bool ExecuteEditCommand(const blink::WebString& name) override;
bool ExecuteEditCommand(const blink::WebString& name,
const blink::WebString& value) override;

@ -267,6 +267,15 @@ PP_Bool CanEditText(PP_Instance instance) {
return PP_FromBool(obj_instance->CanEditText());
}
PP_Bool HasEditableText(PP_Instance instance) {
void* object = pp::Instance::GetPerInstanceObject(instance, kPPPPdfInterface);
if (!object)
return PP_FALSE;
auto* obj_instance = static_cast<OutOfProcessInstance*>(object);
return PP_FromBool(obj_instance->HasEditableText());
}
void ReplaceSelection(PP_Instance instance, const char* text) {
void* object = pp::Instance::GetPerInstanceObject(instance, kPPPPdfInterface);
if (object) {
@ -278,7 +287,8 @@ void ReplaceSelection(PP_Instance instance, const char* text) {
const PPP_Pdf ppp_private = {
&GetLinkAtPosition, &Transform, &GetPrintPresetOptionsFromDocument,
&EnableAccessibility, &SetCaretPosition, &MoveRangeSelectionExtent,
&SetSelectionBounds, &CanEditText, &ReplaceSelection,
&SetSelectionBounds, &CanEditText, &HasEditableText,
&ReplaceSelection,
};
int ExtractPrintPreviewPageIndex(base::StringPiece src_url) {
@ -980,6 +990,10 @@ bool OutOfProcessInstance::CanEditText() {
return engine_->CanEditText();
}
bool OutOfProcessInstance::HasEditableText() {
return engine_->HasEditableText();
}
void OutOfProcessInstance::ReplaceSelection(const std::string& text) {
engine_->ReplaceSelection(text);
}

@ -82,6 +82,7 @@ class OutOfProcessInstance : public pp::Instance,
void SetSelectionBounds(const pp::FloatPoint& base,
const pp::FloatPoint& extent);
bool CanEditText();
bool HasEditableText();
void ReplaceSelection(const std::string& text);
void FlushCallback(int32_t result);

@ -315,9 +315,11 @@ class PDFEngine {
virtual void RotateClockwise() = 0;
virtual void RotateCounterclockwise() = 0;
virtual std::string GetSelectedText() = 0;
// Returns true if focus is within an editable form text area, and false
// otherwise.
// Returns true if focus is within an editable form text area.
virtual bool CanEditText() = 0;
// Returns true if focus is within an editable form text area and the text
// area has text.
virtual bool HasEditableText() = 0;
// Replace selected text within an editable form text area with another
// string. If there is no selected text, append the replacement text after the
// current caret position.

@ -2699,6 +2699,15 @@ bool PDFiumEngine::CanEditText() {
return editable_form_text_area_;
}
bool PDFiumEngine::HasEditableText() {
DCHECK(CanEditText());
if (last_page_mouse_down_ == -1)
return false;
FPDF_PAGE page = pages_[last_page_mouse_down_]->GetPage();
// If the return value is 2, that corresponds to "\0\0".
return FORM_GetFocusedText(form_, page, nullptr, 0) > 2;
}
void PDFiumEngine::ReplaceSelection(const std::string& text) {
DCHECK(CanEditText());
if (last_page_mouse_down_ != -1) {

@ -74,6 +74,7 @@ class PDFiumEngine : public PDFEngine,
void RotateCounterclockwise() override;
std::string GetSelectedText() override;
bool CanEditText() override;
bool HasEditableText() override;
void ReplaceSelection(const std::string& text) override;
std::string GetLinkAtPosition(const pp::Point& point) override;
bool HasPermission(DocumentPermission permission) const override;

@ -73,11 +73,16 @@ struct PPP_Pdf_1_1 {
void (*SetSelectionBounds)(PP_Instance instance,
const struct PP_FloatPoint* base,
const struct PP_FloatPoint* extent);
// Return true if plugin text can be edited. For the PDF plugin, this
// is when focus is within an editable form text area (a form text field
// or user-editable form combobox text field).
// Return true if plugin text can be edited. i.e. When focus is within an
// editable form text area (a form text field or user-editable form combobox
// text field.
PP_Bool (*CanEditText)(PP_Instance instance);
// Return true if plugin has editable text. i.e. When the focused editable
// field has content.
PP_Bool (*HasEditableText)(PP_Instance instance);
// Replace the plugin's selected text (if focus is in an editable text area).
// If there is no selected text, append the replacement text after the current
// caret position.

@ -817,6 +817,9 @@ IPC_MESSAGE_ROUTED3(PpapiMsg_PPPPdf_SetSelectionBounds,
IPC_SYNC_MESSAGE_ROUTED1_1(PpapiMsg_PPPPdf_CanEditText,
PP_Instance /* instance */,
PP_Bool /* result */)
IPC_SYNC_MESSAGE_ROUTED1_1(PpapiMsg_PPPPdf_HasEditableText,
PP_Instance /* instance */,
PP_Bool /* result */)
IPC_MESSAGE_ROUTED2(PpapiMsg_PPPPdf_ReplaceSelection,
PP_Instance /* instance */,
std::string /* text */)

@ -69,6 +69,13 @@ PP_Bool CanEditText(PP_Instance instance) {
return ret;
}
PP_Bool HasEditableText(PP_Instance instance) {
PP_Bool ret = PP_FALSE;
HostDispatcher::GetForInstance(instance)->Send(
new PpapiMsg_PPPPdf_HasEditableText(API_ID_PPP_PDF, instance, &ret));
return ret;
}
void ReplaceSelection(PP_Instance instance, const char* text) {
HostDispatcher::GetForInstance(instance)->Send(
new PpapiMsg_PPPPdf_ReplaceSelection(API_ID_PPP_PDF, instance, text));
@ -77,7 +84,8 @@ void ReplaceSelection(PP_Instance instance, const char* text) {
const PPP_Pdf ppp_pdf_interface = {
&GetLinkAtPosition, &Transform, &GetPrintPresetOptionsFromDocument,
&EnableAccessibility, &SetCaretPosition, &MoveRangeSelectionExtent,
&SetSelectionBounds, &CanEditText, &ReplaceSelection,
&SetSelectionBounds, &CanEditText, &HasEditableText,
&ReplaceSelection,
};
#else
// The NaCl plugin doesn't need the host side interface - stub it out.
@ -121,6 +129,8 @@ bool PPP_Pdf_Proxy::OnMessageReceived(const IPC::Message& msg) {
IPC_MESSAGE_HANDLER(PpapiMsg_PPPPdf_SetSelectionBounds,
OnPluginMsgSetSelectionBounds)
IPC_MESSAGE_HANDLER(PpapiMsg_PPPPdf_CanEditText, OnPluginMsgCanEditText)
IPC_MESSAGE_HANDLER(PpapiMsg_PPPPdf_HasEditableText,
OnPluginMsgHasEditableText)
IPC_MESSAGE_HANDLER(PpapiMsg_PPPPdf_ReplaceSelection,
OnPluginMsgReplaceSelection)
IPC_MESSAGE_UNHANDLED(handled = false)
@ -177,6 +187,12 @@ void PPP_Pdf_Proxy::OnPluginMsgCanEditText(PP_Instance instance,
CallWhileUnlocked(ppp_pdf_->CanEditText, instance));
}
void PPP_Pdf_Proxy::OnPluginMsgHasEditableText(PP_Instance instance,
PP_Bool* result) {
*result = PP_FromBool(ppp_pdf_ &&
CallWhileUnlocked(ppp_pdf_->HasEditableText, instance));
}
void PPP_Pdf_Proxy::OnPluginMsgReplaceSelection(PP_Instance instance,
const std::string& text) {
if (ppp_pdf_)

@ -5,6 +5,8 @@
#ifndef PPAPI_PROXY_PPP_PDF_PROXY_H_
#define PPAPI_PROXY_PPP_PDF_PROXY_H_
#include <string>
#include "base/macros.h"
#include "ppapi/c/private/ppp_pdf.h"
#include "ppapi/proxy/interface_proxy.h"
@ -38,6 +40,7 @@ class PPP_Pdf_Proxy : public InterfaceProxy {
const PP_FloatPoint& base,
const PP_FloatPoint& extent);
void OnPluginMsgCanEditText(PP_Instance instance, PP_Bool* result);
void OnPluginMsgHasEditableText(PP_Instance instance, PP_Bool* result);
void OnPluginMsgReplaceSelection(PP_Instance instance,
const std::string& text);

@ -161,6 +161,7 @@ class WebPlugin {
virtual WebString SelectionAsMarkup() const { return WebString(); }
virtual bool CanEditText() const { return false; }
virtual bool HasEditableText() const { return false; }
virtual bool ExecuteEditCommand(const WebString& name) { return false; }
virtual bool ExecuteEditCommand(const WebString& name,
@ -254,4 +255,4 @@ class WebPlugin {
} // namespace blink
#endif
#endif // THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_PLUGIN_H_

@ -417,7 +417,7 @@ bool WebPluginContainerImpl::ExecuteEditCommand(const WebString& name,
// static
bool WebPluginContainerImpl::SupportsCommand(const WebString& name) {
return name == "Copy" || name == "Cut" || name == "Paste" ||
name == "PasteAndMatchStyle";
name == "PasteAndMatchStyle" || name == "SelectAll";
}
WebElement WebPluginContainerImpl::GetElement() {

@ -394,9 +394,9 @@ bool ContextMenuController::ShowContextMenu(const ContextMenu* default_menu,
if (!!(data.edit_flags & WebContextMenuData::kCanCopy))
data.edit_flags |= WebContextMenuData::kCanCut;
data.edit_flags |= WebContextMenuData::kCanPaste;
// TODO(bug 753216): Implement "SelectAll" command and enable when
// focus is within an editable text area.
data.edit_flags &= ~WebContextMenuData::kCanSelectAll;
if (plugin->HasEditableText())
data.edit_flags |= WebContextMenuData::kCanSelectAll;
}
// Disable translation for plugins.
data.edit_flags &= ~WebContextMenuData::kCanTranslate;