diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc index a807204436e7c..775deff2c1b23 100644 --- a/chrome/browser/pdf/pdf_extension_test.cc +++ b/chrome/browser/pdf/pdf_extension_test.cc @@ -104,6 +104,15 @@ #include "chrome/browser/ui/views/location_bar/zoom_bubble_view.h" #endif +#if defined(TOOLKIT_VIEWS) && defined(USE_AURA) +#include "ui/events/base_event_utils.h" +#include "ui/events/event.h" +#include "ui/events/gesture_event_details.h" +#include "ui/events/types/event_type.h" +#include "ui/views/touchui/touch_selection_menu_views.h" +#include "ui/views/widget/any_widget_observer.h" +#endif // defined(TOOLKIT_VIEWS) && defined(USE_AURA) + using content::WebContents; using extensions::ExtensionsAPIClient; using guest_view::GuestViewManager; @@ -2189,6 +2198,48 @@ IN_PROC_BROWSER_TEST_F(PDFExtensionHitTestTest, ContextMenuCoordinates) { // UntrustworthyContextMenuParams. } +#if defined(TOOLKIT_VIEWS) && defined(USE_AURA) +// On text selection, a touch selection menu should pop up. On clicking ellipsis +// icon on the menu, the context menu should open up. +IN_PROC_BROWSER_TEST_F(PDFExtensionTest, + ContextMenuOpensFromTouchSelectionMenu) { + const GURL url = embedded_test_server()->GetURL("/pdf/text_large.pdf"); + WebContents* const guest_contents = LoadPdfGetGuestContents(url); + ASSERT_TRUE(guest_contents); + + views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{}, + "TouchSelectionMenuViews"); + gfx::Point text_selection_position(10, 10); + ConvertPageCoordToScreenCoord(guest_contents, &text_selection_position); + content::SimulateTouchEventAt(GetActiveWebContents(), ui::ET_TOUCH_PRESSED, + text_selection_position); + bool success = false; + ASSERT_TRUE(content::ExecuteScriptAndExtractBool( + GetActiveWebContents(), + "window.addEventListener('message', function(event) {" + " if (event.data.type == 'touchSelectionOccurred')" + " window.domAutomationController.send(true);" + "});", + &success)); + ASSERT_TRUE(success); + content::SimulateTouchEventAt(GetActiveWebContents(), ui::ET_TOUCH_RELEASED, + text_selection_position); + views::Widget* widget = waiter.WaitIfNeededAndGet(); + ASSERT_TRUE(widget); + views::TouchSelectionMenuViews* menu = + static_cast<views::TouchSelectionMenuViews*>(widget->GetContentsView()); + ASSERT_TRUE(menu); + views::View* ellipsis_button = menu->GetViewByID( + views::TouchSelectionMenuViews::ButtonViewId::kEllipsisButton); + ASSERT_TRUE(ellipsis_button); + ContextMenuWaiter context_menu_observer; + ui::GestureEvent tap(0, 0, 0, ui::EventTimeForNow(), + ui::GestureEventDetails(ui::ET_GESTURE_TAP)); + ellipsis_button->OnGestureEvent(&tap); + context_menu_observer.WaitForMenuOpenAndClose(); +} +#endif // defined(TOOLKIT_VIEWS) && defined(USE_AURA) + // The plugin document and the mime handler should both use the same background // color. IN_PROC_BROWSER_TEST_F(PDFExtensionTest, BackgroundColor) { diff --git a/chrome/browser/resources/pdf/pdf_viewer.js b/chrome/browser/resources/pdf/pdf_viewer.js index 296c0fc0ccec1..05faebd067273 100644 --- a/chrome/browser/resources/pdf/pdf_viewer.js +++ b/chrome/browser/resources/pdf/pdf_viewer.js @@ -1278,6 +1278,11 @@ export class PDFViewer { this.isFormFieldFocused_ = /** @type {{ focused: boolean }} */ (data).focused; return; + case 'touchSelectionOccurred': + this.sendScriptingMessage_({ + type: 'touchSelectionOccurred', + }); + return; } assertNotReached('Unknown message type received: ' + data.type); } diff --git a/chrome/browser/ui/views/touch_selection_menu_chromeos.cc b/chrome/browser/ui/views/touch_selection_menu_chromeos.cc index ffc9d189121ba..b4d85f99ade8c 100644 --- a/chrome/browser/ui/views/touch_selection_menu_chromeos.cc +++ b/chrome/browser/ui/views/touch_selection_menu_chromeos.cc @@ -51,8 +51,9 @@ void TouchSelectionMenuChromeOS::SetActionsForTesting( void TouchSelectionMenuChromeOS::CreateButtons() { if (action_) { - views::LabelButton* button = CreateButton(base::UTF8ToUTF16(action_->title), - kSmartTextSelectionActionTag); + views::LabelButton* button = + CreateButton(base::UTF8ToUTF16(action_->title)); + button->set_tag(kSmartTextSelectionActionTag); if (action_->bitmap_icon) { gfx::ImageSkia original( diff --git a/chrome/test/data/pdf/text_large.in b/chrome/test/data/pdf/text_large.in new file mode 100644 index 0000000000000..98caf0078108e --- /dev/null +++ b/chrome/test/data/pdf/text_large.in @@ -0,0 +1,45 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /MediaBox [0 0 612 792] + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Resources << + /Font << + /F1 4 0 R + >> + >> + /Contents 5 0 R +>> +endobj +{{object 4 0}} << + /Type /Font + /Subtype /Type1 + /BaseFont /Helvetica +>> +endobj +{{object 5 0}} << + {{streamlen}} +>> +stream +BT +/F1 100 Tf +0 706 Td +(LargeText) Tj +ET +endstream +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF \ No newline at end of file diff --git a/chrome/test/data/pdf/text_large.pdf b/chrome/test/data/pdf/text_large.pdf new file mode 100644 index 0000000000000..658036bc765ce --- /dev/null +++ b/chrome/test/data/pdf/text_large.pdf @@ -0,0 +1,57 @@ +%PDF-1.7 +%��� +1 0 obj << + /Type /Catalog + /Pages 2 0 R +>> +endobj +2 0 obj << + /Type /Pages + /MediaBox [0 0 612 792] + /Count 1 + /Kids [3 0 R] +>> +endobj +3 0 obj << + /Type /Page + /Parent 2 0 R + /Resources << + /Font << + /F1 4 0 R + >> + >> + /Contents 5 0 R +>> +endobj +4 0 obj << + /Type /Font + /Subtype /Type1 + /BaseFont /Helvetica +>> +endobj +5 0 obj << + /Length 41 +>> +stream +BT +/F1 100 Tf +0 706 Td +(LargeText) Tj +ET +endstream +endobj +xref +0 6 +0000000000 65535 f +0000000015 00000 n +0000000068 00000 n +0000000157 00000 n +0000000283 00000 n +0000000359 00000 n +trailer << + /Root 1 0 R + /Size 6 +>> +startxref +451 +%%EOF \ No newline at end of file diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc index fda511a564b46..d5fed68d721bf 100644 --- a/content/public/test/browser_test_utils.cc +++ b/content/public/test/browser_test_utils.cc @@ -1147,8 +1147,10 @@ void SimulateTapWithModifiersAt(WebContents* web_contents, } #if defined(USE_AURA) -void SimulateTouchPressAt(WebContents* web_contents, const gfx::Point& point) { - ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, point, base::TimeTicks(), +void SimulateTouchEventAt(WebContents* web_contents, + ui::EventType event_type, + const gfx::Point& point) { + ui::TouchEvent touch(event_type, point, base::TimeTicks(), ui::PointerDetails(ui::EventPointerType::kTouch, 0)); static_cast<RenderWidgetHostViewAura*>( web_contents->GetRenderWidgetHostView()) diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h index fac48d0459e48..73885a067c247 100644 --- a/content/public/test/browser_test_utils.h +++ b/content/public/test/browser_test_utils.h @@ -52,6 +52,7 @@ #include "ui/events/keycodes/dom/dom_code.h" #include "ui/events/keycodes/dom/dom_key.h" #include "ui/events/keycodes/keyboard_codes.h" +#include "ui/events/types/event_type.h" #include "url/gurl.h" #if defined(OS_WIN) @@ -287,8 +288,10 @@ void SimulateTouchGestureAt(WebContents* web_contents, blink::WebInputEvent::Type type); #if defined(USE_AURA) -// Generates a TouchStart at |point|. -void SimulateTouchPressAt(WebContents* web_contents, const gfx::Point& point); +// Generates a TouchEvent of |event_type| at |point|. +void SimulateTouchEventAt(WebContents* web_contents, + ui::EventType event_type, + const gfx::Point& point); void SimulateLongTapAt(WebContents* web_contents, const gfx::Point& point); #endif diff --git a/pdf/out_of_process_instance.cc b/pdf/out_of_process_instance.cc index 9c44da580b2bd..0ae0e1637bbd4 100644 --- a/pdf/out_of_process_instance.cc +++ b/pdf/out_of_process_instance.cc @@ -117,6 +117,8 @@ constexpr char kJSDataToSave[] = "dataToSave"; constexpr char kJSHasUnsavedChanges[] = "hasUnsavedChanges"; // Consume save token (Plugin -> Page) constexpr char kJSConsumeSaveTokenType[] = "consumeSaveToken"; +// Notify when touch selection occurs (Plugin -> Page) +constexpr char kJSTouchSelectionOccurredType[] = "touchSelectionOccurred"; // Go to page (Plugin -> Page) constexpr char kJSGoToPageType[] = "goToPage"; constexpr char kJSPageNumber[] = "page"; @@ -1444,6 +1446,12 @@ void OutOfProcessInstance::NotifySelectedFindResultChanged( SelectedFindResultChanged(current_find_index); } +void OutOfProcessInstance::NotifyTouchSelectionOccurred() { + pp::VarDictionary message; + message.Set(kType, kJSTouchSelectionOccurredType); + PostMessage(message); +} + void OutOfProcessInstance::GetDocumentPassword( pp::CompletionCallbackWithOutput<pp::Var> callback) { if (password_callback_) { diff --git a/pdf/out_of_process_instance.h b/pdf/out_of_process_instance.h index db8f4b476e453..e8c00636d7a01 100644 --- a/pdf/out_of_process_instance.h +++ b/pdf/out_of_process_instance.h @@ -111,6 +111,7 @@ class OutOfProcessInstance : public pp::Instance, void UpdateTickMarks(const std::vector<pp::Rect>& tickmarks) override; void NotifyNumberOfFindResultsChanged(int total, bool final_result) override; void NotifySelectedFindResultChanged(int current_find_index) override; + void NotifyTouchSelectionOccurred() override; void GetDocumentPassword( pp::CompletionCallbackWithOutput<pp::Var> callback) override; void Beep() override; diff --git a/pdf/pdf_engine.h b/pdf/pdf_engine.h index 514e82ea49c19..33dbe762bd186 100644 --- a/pdf/pdf_engine.h +++ b/pdf/pdf_engine.h @@ -176,6 +176,8 @@ class PDFEngine { // Updates the index of the currently selected search item. virtual void NotifySelectedFindResultChanged(int current_find_index) {} + virtual void NotifyTouchSelectionOccurred() {} + // Prompts the user for a password to open this document. The callback is // called when the password is retrieved. virtual void GetDocumentPassword( diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc index be349bbb95d40..7d29a52bb735a 100644 --- a/pdf/pdfium/pdfium_engine.cc +++ b/pdf/pdfium/pdfium_engine.cc @@ -1083,6 +1083,9 @@ void PDFiumEngine::OnMultipleClick(int click_count, selection_.push_back(PDFiumRange(pages_[page_index].get(), start_index, end_index - start_index)); + + if (handling_long_press_) + client_->NotifyTouchSelectionOccurred(); } bool PDFiumEngine::OnLeftMouseDown(const pp::MouseInputEvent& event) { @@ -2330,6 +2333,7 @@ void PDFiumEngine::SetGrayscale(bool grayscale) { } void PDFiumEngine::HandleLongPress(const pp::TouchInputEvent& event) { + base::AutoReset<bool> handling_long_press_guard(&handling_long_press_, true); pp::FloatPoint fp = event.GetTouchByIndex(PP_TOUCHLIST_TYPE_TARGETTOUCHES, 0).position(); pp::Point point; diff --git a/pdf/pdfium/pdfium_engine.h b/pdf/pdfium/pdfium_engine.h index 593a51d318fb6..90a6305333b5a 100644 --- a/pdf/pdfium/pdfium_engine.h +++ b/pdf/pdfium/pdfium_engine.h @@ -702,6 +702,9 @@ class PDFiumEngine : public PDFEngine, // Timer for touch long press detection. base::OneShotTimer touch_timer_; + // Set to true when handling long touch press. + bool handling_long_press_ = false; + // The focus item type for the currently focused object. FocusElementType focus_item_type_ = FocusElementType::kNone; diff --git a/ui/views/touchui/touch_selection_menu_views.cc b/ui/views/touchui/touch_selection_menu_views.cc index 8b014f1386d0c..6cc5d4b6ce49a 100644 --- a/ui/views/touchui/touch_selection_menu_views.cc +++ b/ui/views/touchui/touch_selection_menu_views.cc @@ -36,7 +36,6 @@ struct MenuCommand { }; constexpr int kSpacingBetweenButtons = 2; -constexpr int kEllipsesButtonTag = -1; } // namespace @@ -126,18 +125,21 @@ void TouchSelectionMenuViews::CreateButtons() { if (!client_->IsCommandIdEnabled(command.command_id)) continue; - Button* button = CreateButton(l10n_util::GetStringUTF16(command.message_id), - command.command_id); + Button* button = + CreateButton(l10n_util::GetStringUTF16(command.message_id)); + button->set_tag(command.command_id); AddChildView(button); } - // Finally, add ellipses button. - AddChildView(CreateButton(base::ASCIIToUTF16("..."), kEllipsesButtonTag)); + // Finally, add ellipsis button. + LabelButton* ellipsis_button = CreateButton(base::ASCIIToUTF16("...")); + ellipsis_button->SetID(ButtonViewId::kEllipsisButton); + AddChildView(ellipsis_button); InvalidateLayout(); } -LabelButton* TouchSelectionMenuViews::CreateButton(const base::string16& title, - int tag) { +LabelButton* TouchSelectionMenuViews::CreateButton( + const base::string16& title) { base::string16 label = gfx::RemoveAcceleratorChar(title, '&', nullptr, nullptr); LabelButton* button = new LabelButton(this, label, style::CONTEXT_TOUCH_MENU); @@ -145,7 +147,6 @@ LabelButton* TouchSelectionMenuViews::CreateButton(const base::string16& title, button->SetMinSize(kMenuButtonMinSize); button->SetFocusForPlatform(); button->SetHorizontalAlignment(gfx::ALIGN_CENTER); - button->set_tag(tag); return button; } @@ -180,7 +181,7 @@ void TouchSelectionMenuViews::WindowClosing() { void TouchSelectionMenuViews::ButtonPressed(Button* sender, const ui::Event& event) { CloseMenu(); - if (sender->tag() != kEllipsesButtonTag) + if (sender->GetID() != ButtonViewId::kEllipsisButton) client_->ExecuteCommand(sender->tag(), event.flags()); else client_->RunContextMenu(); diff --git a/ui/views/touchui/touch_selection_menu_views.h b/ui/views/touchui/touch_selection_menu_views.h index ad1819ee56a41..77121939b1834 100644 --- a/ui/views/touchui/touch_selection_menu_views.h +++ b/ui/views/touchui/touch_selection_menu_views.h @@ -24,6 +24,8 @@ class VIEWS_EXPORT TouchSelectionMenuViews : public BubbleDialogDelegateView, public: METADATA_HEADER(TouchSelectionMenuViews); + enum ButtonViewId : int { kEllipsisButton = 1 }; + TouchSelectionMenuViews(TouchSelectionMenuRunnerViews* owner, ui::TouchSelectionMenuClient* client, aura::Window* context); @@ -45,7 +47,7 @@ class VIEWS_EXPORT TouchSelectionMenuViews : public BubbleDialogDelegateView, virtual void CreateButtons(); // Helper method to create a single button. - LabelButton* CreateButton(const base::string16& title, int tag); + LabelButton* CreateButton(const base::string16& title); // ButtonListener: void ButtonPressed(Button* sender, const ui::Event& event) override;