1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/page/context_menu_controller.h"
6 
7 #include "base/optional.h"
8 #include "build/build_config.h"
9 #include "testing/gtest/include/gtest/gtest.h"
10 #include "third_party/blink/public/common/context_menu_data/edit_flags.h"
11 #include "third_party/blink/public/common/input/web_keyboard_event.h"
12 #include "third_party/blink/public/common/input/web_menu_source_type.h"
13 #include "third_party/blink/public/platform/web_rect.h"
14 #include "third_party/blink/public/web/web_context_menu_data.h"
15 #include "third_party/blink/renderer/core/dom/xml_document.h"
16 #include "third_party/blink/renderer/core/editing/frame_selection.h"
17 #include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
18 #include "third_party/blink/renderer/core/frame/web_frame_widget_base.h"
19 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
20 #include "third_party/blink/renderer/core/geometry/dom_rect.h"
21 #include "third_party/blink/renderer/core/html/html_document.h"
22 #include "third_party/blink/renderer/core/html/media/html_video_element.h"
23 #include "third_party/blink/renderer/core/input/context_menu_allowed_scope.h"
24 #include "third_party/blink/renderer/core/page/context_menu_controller.h"
25 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
26 #include "third_party/blink/renderer/platform/heap/heap.h"
27 #include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
28 #include "third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h"
29 #include "third_party/blink/renderer/platform/testing/empty_web_media_player.h"
30 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
31 #include "third_party/blink/renderer/platform/wtf/casting.h"
32 
33 using testing::Return;
34 
35 namespace blink {
36 
37 namespace {
38 
39 class MockWebMediaPlayerForContextMenu : public EmptyWebMediaPlayer {
40  public:
41   MOCK_CONST_METHOD0(Duration, double());
42   MOCK_CONST_METHOD0(HasAudio, bool());
43   MOCK_CONST_METHOD0(HasVideo, bool());
44 
GetVideoSurfaceLayerMode() const45   SurfaceLayerMode GetVideoSurfaceLayerMode() const override {
46     return SurfaceLayerMode::kAlways;
47   }
48 };
49 
50 class TestWebFrameClientImpl : public frame_test_helpers::TestWebFrameClient {
51  public:
ShowContextMenu(const WebContextMenuData & data,const base::Optional<gfx::Point> &)52   void ShowContextMenu(const WebContextMenuData& data,
53                        const base::Optional<gfx::Point>&) override {
54     context_menu_data_ = data;
55   }
56 
CreateMediaPlayer(const WebMediaPlayerSource &,WebMediaPlayerClient *,blink::MediaInspectorContext *,WebMediaPlayerEncryptedMediaClient *,WebContentDecryptionModule *,const WebString & sink_id)57   WebMediaPlayer* CreateMediaPlayer(const WebMediaPlayerSource&,
58                                     WebMediaPlayerClient*,
59                                     blink::MediaInspectorContext*,
60                                     WebMediaPlayerEncryptedMediaClient*,
61                                     WebContentDecryptionModule*,
62                                     const WebString& sink_id) override {
63     return new MockWebMediaPlayerForContextMenu();
64   }
65 
GetContextMenuData() const66   const WebContextMenuData& GetContextMenuData() const {
67     return context_menu_data_;
68   }
69 
70  private:
71   WebContextMenuData context_menu_data_;
72 };
73 
74 }  // anonymous namespace
75 
76 class ContextMenuControllerTest : public testing::Test {
77  public:
SetUp()78   void SetUp() override {
79     web_view_helper_.Initialize(&web_frame_client_);
80 
81     WebLocalFrameImpl* local_main_frame = web_view_helper_.LocalMainFrame();
82     local_main_frame->ViewImpl()->MainFrameViewWidget()->Resize(
83         gfx::Size(640, 480));
84     local_main_frame->ViewImpl()->MainFrameWidget()->UpdateAllLifecyclePhases(
85         DocumentUpdateReason::kTest);
86   }
87 
ShowContextMenu(const PhysicalOffset & location,WebMenuSourceType source)88   bool ShowContextMenu(const PhysicalOffset& location,
89                        WebMenuSourceType source) {
90     return web_view_helper_.GetWebView()
91         ->GetPage()
92         ->GetContextMenuController()
93         .ShowContextMenu(GetDocument()->GetFrame(), location, source);
94   }
95 
ShowContextMenuForElement(Element * element,WebMenuSourceType source)96   bool ShowContextMenuForElement(Element* element, WebMenuSourceType source) {
97     const DOMRect* rect = element->getBoundingClientRect();
98     PhysicalOffset location(LayoutUnit((rect->left() + rect->right()) / 2),
99                             LayoutUnit((rect->top() + rect->bottom()) / 2));
100     ContextMenuAllowedScope context_menu_allowed_scope;
101     return ShowContextMenu(location, source);
102   }
103 
GetDocument()104   Document* GetDocument() {
105     return static_cast<Document*>(
106         web_view_helper_.LocalMainFrame()->GetDocument());
107   }
108 
GetWebView()109   WebView* GetWebView() { return web_view_helper_.GetWebView(); }
GetPage()110   Page* GetPage() { return web_view_helper_.GetWebView()->GetPage(); }
LocalMainFrame()111   WebLocalFrameImpl* LocalMainFrame() {
112     return web_view_helper_.LocalMainFrame();
113   }
LoadAhem()114   void LoadAhem() { web_view_helper_.LoadAhem(); }
115 
GetWebFrameClient() const116   const TestWebFrameClientImpl& GetWebFrameClient() const {
117     return web_frame_client_;
118   }
119 
DurationChanged(HTMLVideoElement * video)120   void DurationChanged(HTMLVideoElement* video) { video->DurationChanged(); }
121 
SetReadyState(HTMLVideoElement * video,HTMLMediaElement::ReadyState state)122   void SetReadyState(HTMLVideoElement* video,
123                      HTMLMediaElement::ReadyState state) {
124     video->SetReadyState(state);
125   }
126 
127  private:
128   TestWebFrameClientImpl web_frame_client_;
129   frame_test_helpers::WebViewHelper web_view_helper_;
130 };
131 
TEST_F(ContextMenuControllerTest,VideoNotLoaded)132 TEST_F(ContextMenuControllerTest, VideoNotLoaded) {
133   ContextMenuAllowedScope context_menu_allowed_scope;
134   HitTestResult hit_test_result;
135   const char video_url[] = "https://example.com/foo.webm";
136 
137   // Make sure Picture-in-Picture is enabled.
138   GetDocument()->GetSettings()->SetPictureInPictureEnabled(true);
139 
140   // Setup video element.
141   Persistent<HTMLVideoElement> video =
142       MakeGarbageCollected<HTMLVideoElement>(*GetDocument());
143   video->SetSrc(video_url);
144   GetDocument()->body()->AppendChild(video);
145   test::RunPendingTasks();
146   SetReadyState(video.Get(), HTMLMediaElement::kHaveNothing);
147   test::RunPendingTasks();
148 
149   EXPECT_CALL(*static_cast<MockWebMediaPlayerForContextMenu*>(
150                   video->GetWebMediaPlayer()),
151               HasVideo())
152       .WillRepeatedly(Return(false));
153 
154   DOMRect* rect = video->getBoundingClientRect();
155   PhysicalOffset location(LayoutUnit((rect->left() + rect->right()) / 2),
156                           LayoutUnit((rect->top() + rect->bottom()) / 2));
157   EXPECT_TRUE(ShowContextMenu(location, kMenuSourceMouse));
158 
159   // Context menu info are sent to the WebLocalFrameClient.
160   WebContextMenuData context_menu_data =
161       GetWebFrameClient().GetContextMenuData();
162   EXPECT_EQ(ContextMenuDataMediaType::kVideo, context_menu_data.media_type);
163   EXPECT_EQ(video_url, context_menu_data.src_url.GetString());
164 
165   const Vector<std::pair<WebContextMenuData::MediaFlags, bool>>
166       expected_media_flags = {
167           {WebContextMenuData::kMediaInError, false},
168           {WebContextMenuData::kMediaPaused, true},
169           {WebContextMenuData::kMediaMuted, false},
170           {WebContextMenuData::kMediaLoop, false},
171           {WebContextMenuData::kMediaCanSave, true},
172           {WebContextMenuData::kMediaHasAudio, false},
173           {WebContextMenuData::kMediaCanToggleControls, false},
174           {WebContextMenuData::kMediaControls, false},
175           {WebContextMenuData::kMediaCanPrint, false},
176           {WebContextMenuData::kMediaCanRotate, false},
177           {WebContextMenuData::kMediaCanPictureInPicture, false},
178           {WebContextMenuData::kMediaPictureInPicture, false},
179           {WebContextMenuData::kMediaCanLoop, true},
180       };
181 
182   for (const auto& expected_media_flag : expected_media_flags) {
183     EXPECT_EQ(expected_media_flag.second,
184               !!(context_menu_data.media_flags & expected_media_flag.first))
185         << "Flag 0x" << std::hex << expected_media_flag.first;
186   }
187 }
188 
TEST_F(ContextMenuControllerTest,VideoWithAudioOnly)189 TEST_F(ContextMenuControllerTest, VideoWithAudioOnly) {
190   ContextMenuAllowedScope context_menu_allowed_scope;
191   HitTestResult hit_test_result;
192   const char video_url[] = "https://example.com/foo.webm";
193 
194   // Make sure Picture-in-Picture is enabled.
195   GetDocument()->GetSettings()->SetPictureInPictureEnabled(true);
196 
197   // Setup video element.
198   Persistent<HTMLVideoElement> video =
199       MakeGarbageCollected<HTMLVideoElement>(*GetDocument());
200   video->SetSrc(video_url);
201   GetDocument()->body()->AppendChild(video);
202   test::RunPendingTasks();
203   SetReadyState(video.Get(), HTMLMediaElement::kHaveNothing);
204   test::RunPendingTasks();
205 
206   EXPECT_CALL(*static_cast<MockWebMediaPlayerForContextMenu*>(
207                   video->GetWebMediaPlayer()),
208               HasVideo())
209       .WillRepeatedly(Return(false));
210   EXPECT_CALL(*static_cast<MockWebMediaPlayerForContextMenu*>(
211                   video->GetWebMediaPlayer()),
212               HasAudio())
213       .WillRepeatedly(Return(true));
214 
215   DOMRect* rect = video->getBoundingClientRect();
216   PhysicalOffset location(LayoutUnit((rect->left() + rect->right()) / 2),
217                           LayoutUnit((rect->top() + rect->bottom()) / 2));
218   EXPECT_TRUE(ShowContextMenu(location, kMenuSourceMouse));
219 
220   // Context menu info are sent to the WebLocalFrameClient.
221   WebContextMenuData context_menu_data =
222       GetWebFrameClient().GetContextMenuData();
223   EXPECT_EQ(ContextMenuDataMediaType::kAudio, context_menu_data.media_type);
224   EXPECT_EQ(video_url, context_menu_data.src_url.GetString());
225 
226   const Vector<std::pair<WebContextMenuData::MediaFlags, bool>>
227       expected_media_flags = {
228           {WebContextMenuData::kMediaInError, false},
229           {WebContextMenuData::kMediaPaused, true},
230           {WebContextMenuData::kMediaMuted, false},
231           {WebContextMenuData::kMediaLoop, false},
232           {WebContextMenuData::kMediaCanSave, true},
233           {WebContextMenuData::kMediaHasAudio, true},
234           {WebContextMenuData::kMediaCanToggleControls, false},
235           {WebContextMenuData::kMediaControls, false},
236           {WebContextMenuData::kMediaCanPrint, false},
237           {WebContextMenuData::kMediaCanRotate, false},
238           {WebContextMenuData::kMediaCanPictureInPicture, false},
239           {WebContextMenuData::kMediaPictureInPicture, false},
240           {WebContextMenuData::kMediaCanLoop, true},
241       };
242 
243   for (const auto& expected_media_flag : expected_media_flags) {
244     EXPECT_EQ(expected_media_flag.second,
245               !!(context_menu_data.media_flags & expected_media_flag.first))
246         << "Flag 0x" << std::hex << expected_media_flag.first;
247   }
248 }
249 
TEST_F(ContextMenuControllerTest,PictureInPictureEnabledVideoLoaded)250 TEST_F(ContextMenuControllerTest, PictureInPictureEnabledVideoLoaded) {
251   // Make sure Picture-in-Picture is enabled.
252   GetDocument()->GetSettings()->SetPictureInPictureEnabled(true);
253 
254   ContextMenuAllowedScope context_menu_allowed_scope;
255   HitTestResult hit_test_result;
256   const char video_url[] = "https://example.com/foo.webm";
257 
258   // Setup video element.
259   Persistent<HTMLVideoElement> video =
260       MakeGarbageCollected<HTMLVideoElement>(*GetDocument());
261   video->SetSrc(video_url);
262   GetDocument()->body()->AppendChild(video);
263   test::RunPendingTasks();
264   SetReadyState(video.Get(), HTMLMediaElement::kHaveMetadata);
265   test::RunPendingTasks();
266 
267   EXPECT_CALL(*static_cast<MockWebMediaPlayerForContextMenu*>(
268                   video->GetWebMediaPlayer()),
269               HasVideo())
270       .WillRepeatedly(Return(true));
271 
272   DOMRect* rect = video->getBoundingClientRect();
273   PhysicalOffset location(LayoutUnit((rect->left() + rect->right()) / 2),
274                           LayoutUnit((rect->top() + rect->bottom()) / 2));
275   EXPECT_TRUE(ShowContextMenu(location, kMenuSourceMouse));
276 
277   // Context menu info are sent to the WebLocalFrameClient.
278   WebContextMenuData context_menu_data =
279       GetWebFrameClient().GetContextMenuData();
280   EXPECT_EQ(ContextMenuDataMediaType::kVideo, context_menu_data.media_type);
281   EXPECT_EQ(video_url, context_menu_data.src_url.GetString());
282 
283   const Vector<std::pair<WebContextMenuData::MediaFlags, bool>>
284       expected_media_flags = {
285           {WebContextMenuData::kMediaInError, false},
286           {WebContextMenuData::kMediaPaused, true},
287           {WebContextMenuData::kMediaMuted, false},
288           {WebContextMenuData::kMediaLoop, false},
289           {WebContextMenuData::kMediaCanSave, true},
290           {WebContextMenuData::kMediaHasAudio, false},
291           {WebContextMenuData::kMediaCanToggleControls, true},
292           {WebContextMenuData::kMediaControls, false},
293           {WebContextMenuData::kMediaCanPrint, false},
294           {WebContextMenuData::kMediaCanRotate, false},
295           {WebContextMenuData::kMediaCanPictureInPicture, true},
296           {WebContextMenuData::kMediaPictureInPicture, false},
297           {WebContextMenuData::kMediaCanLoop, true},
298       };
299 
300   for (const auto& expected_media_flag : expected_media_flags) {
301     EXPECT_EQ(expected_media_flag.second,
302               !!(context_menu_data.media_flags & expected_media_flag.first))
303         << "Flag 0x" << std::hex << expected_media_flag.first;
304   }
305 }
306 
TEST_F(ContextMenuControllerTest,PictureInPictureDisabledVideoLoaded)307 TEST_F(ContextMenuControllerTest, PictureInPictureDisabledVideoLoaded) {
308   // Make sure Picture-in-Picture is disabled.
309   GetDocument()->GetSettings()->SetPictureInPictureEnabled(false);
310 
311   ContextMenuAllowedScope context_menu_allowed_scope;
312   HitTestResult hit_test_result;
313   const char video_url[] = "https://example.com/foo.webm";
314 
315   // Setup video element.
316   Persistent<HTMLVideoElement> video =
317       MakeGarbageCollected<HTMLVideoElement>(*GetDocument());
318   video->SetSrc(video_url);
319   GetDocument()->body()->AppendChild(video);
320   test::RunPendingTasks();
321   SetReadyState(video.Get(), HTMLMediaElement::kHaveMetadata);
322   test::RunPendingTasks();
323 
324   EXPECT_CALL(*static_cast<MockWebMediaPlayerForContextMenu*>(
325                   video->GetWebMediaPlayer()),
326               HasVideo())
327       .WillRepeatedly(Return(true));
328 
329   DOMRect* rect = video->getBoundingClientRect();
330   PhysicalOffset location(LayoutUnit((rect->left() + rect->right()) / 2),
331                           LayoutUnit((rect->top() + rect->bottom()) / 2));
332   EXPECT_TRUE(ShowContextMenu(location, kMenuSourceMouse));
333 
334   // Context menu info are sent to the WebLocalFrameClient.
335   WebContextMenuData context_menu_data =
336       GetWebFrameClient().GetContextMenuData();
337   EXPECT_EQ(ContextMenuDataMediaType::kVideo, context_menu_data.media_type);
338   EXPECT_EQ(video_url, context_menu_data.src_url.GetString());
339 
340   const Vector<std::pair<WebContextMenuData::MediaFlags, bool>>
341       expected_media_flags = {
342           {WebContextMenuData::kMediaInError, false},
343           {WebContextMenuData::kMediaPaused, true},
344           {WebContextMenuData::kMediaMuted, false},
345           {WebContextMenuData::kMediaLoop, false},
346           {WebContextMenuData::kMediaCanSave, true},
347           {WebContextMenuData::kMediaHasAudio, false},
348           {WebContextMenuData::kMediaCanToggleControls, true},
349           {WebContextMenuData::kMediaControls, false},
350           {WebContextMenuData::kMediaCanPrint, false},
351           {WebContextMenuData::kMediaCanRotate, false},
352           {WebContextMenuData::kMediaCanPictureInPicture, false},
353           {WebContextMenuData::kMediaPictureInPicture, false},
354           {WebContextMenuData::kMediaCanLoop, true},
355       };
356 
357   for (const auto& expected_media_flag : expected_media_flags) {
358     EXPECT_EQ(expected_media_flag.second,
359               !!(context_menu_data.media_flags & expected_media_flag.first))
360         << "Flag 0x" << std::hex << expected_media_flag.first;
361   }
362 }
363 
TEST_F(ContextMenuControllerTest,MediaStreamVideoLoaded)364 TEST_F(ContextMenuControllerTest, MediaStreamVideoLoaded) {
365   // Make sure Picture-in-Picture is enabled.
366   GetDocument()->GetSettings()->SetPictureInPictureEnabled(true);
367 
368   ContextMenuAllowedScope context_menu_allowed_scope;
369   HitTestResult hit_test_result;
370 
371   // Setup video element.
372   Persistent<HTMLVideoElement> video =
373       MakeGarbageCollected<HTMLVideoElement>(*GetDocument());
374   MediaStreamComponentVector dummy_components;
375   auto* media_stream_descriptor = MakeGarbageCollected<MediaStreamDescriptor>(
376       dummy_components, dummy_components);
377   video->SetSrcObject(media_stream_descriptor);
378   GetDocument()->body()->AppendChild(video);
379   test::RunPendingTasks();
380   SetReadyState(video.Get(), HTMLMediaElement::kHaveMetadata);
381   test::RunPendingTasks();
382 
383   EXPECT_CALL(*static_cast<MockWebMediaPlayerForContextMenu*>(
384                   video->GetWebMediaPlayer()),
385               HasVideo())
386       .WillRepeatedly(Return(true));
387 
388   DOMRect* rect = video->getBoundingClientRect();
389   PhysicalOffset location(LayoutUnit((rect->left() + rect->right()) / 2),
390                           LayoutUnit((rect->top() + rect->bottom()) / 2));
391   EXPECT_TRUE(ShowContextMenu(location, kMenuSourceMouse));
392 
393   // Context menu info are sent to the WebLocalFrameClient.
394   WebContextMenuData context_menu_data =
395       GetWebFrameClient().GetContextMenuData();
396   EXPECT_EQ(ContextMenuDataMediaType::kVideo, context_menu_data.media_type);
397 
398   const Vector<std::pair<WebContextMenuData::MediaFlags, bool>>
399       expected_media_flags = {
400           {WebContextMenuData::kMediaInError, false},
401           {WebContextMenuData::kMediaPaused, true},
402           {WebContextMenuData::kMediaMuted, false},
403           {WebContextMenuData::kMediaLoop, false},
404           {WebContextMenuData::kMediaCanSave, false},
405           {WebContextMenuData::kMediaHasAudio, false},
406           {WebContextMenuData::kMediaCanToggleControls, true},
407           {WebContextMenuData::kMediaControls, false},
408           {WebContextMenuData::kMediaCanPrint, false},
409           {WebContextMenuData::kMediaCanRotate, false},
410           {WebContextMenuData::kMediaCanPictureInPicture, true},
411           {WebContextMenuData::kMediaPictureInPicture, false},
412           {WebContextMenuData::kMediaCanLoop, false},
413       };
414 
415   for (const auto& expected_media_flag : expected_media_flags) {
416     EXPECT_EQ(expected_media_flag.second,
417               !!(context_menu_data.media_flags & expected_media_flag.first))
418         << "Flag 0x" << std::hex << expected_media_flag.first;
419   }
420 }
421 
TEST_F(ContextMenuControllerTest,InfiniteDurationVideoLoaded)422 TEST_F(ContextMenuControllerTest, InfiniteDurationVideoLoaded) {
423   // Make sure Picture-in-Picture is enabled.
424   GetDocument()->GetSettings()->SetPictureInPictureEnabled(true);
425 
426   ContextMenuAllowedScope context_menu_allowed_scope;
427   HitTestResult hit_test_result;
428   const char video_url[] = "https://example.com/foo.webm";
429 
430   // Setup video element.
431   Persistent<HTMLVideoElement> video =
432       MakeGarbageCollected<HTMLVideoElement>(*GetDocument());
433   video->SetSrc(video_url);
434   GetDocument()->body()->AppendChild(video);
435   test::RunPendingTasks();
436   SetReadyState(video.Get(), HTMLMediaElement::kHaveMetadata);
437   test::RunPendingTasks();
438 
439   EXPECT_CALL(*static_cast<MockWebMediaPlayerForContextMenu*>(
440                   video->GetWebMediaPlayer()),
441               HasVideo())
442       .WillRepeatedly(Return(true));
443 
444   EXPECT_CALL(*static_cast<MockWebMediaPlayerForContextMenu*>(
445                   video->GetWebMediaPlayer()),
446               Duration())
447       .WillRepeatedly(Return(std::numeric_limits<double>::infinity()));
448   DurationChanged(video.Get());
449 
450   DOMRect* rect = video->getBoundingClientRect();
451   PhysicalOffset location(LayoutUnit((rect->left() + rect->right()) / 2),
452                           LayoutUnit((rect->top() + rect->bottom()) / 2));
453   EXPECT_TRUE(ShowContextMenu(location, kMenuSourceMouse));
454 
455   // Context menu info are sent to the WebLocalFrameClient.
456   WebContextMenuData context_menu_data =
457       GetWebFrameClient().GetContextMenuData();
458   EXPECT_EQ(ContextMenuDataMediaType::kVideo, context_menu_data.media_type);
459   EXPECT_EQ(video_url, context_menu_data.src_url.GetString());
460 
461   const Vector<std::pair<WebContextMenuData::MediaFlags, bool>>
462       expected_media_flags = {
463           {WebContextMenuData::kMediaInError, false},
464           {WebContextMenuData::kMediaPaused, true},
465           {WebContextMenuData::kMediaMuted, false},
466           {WebContextMenuData::kMediaLoop, false},
467           {WebContextMenuData::kMediaCanSave, false},
468           {WebContextMenuData::kMediaHasAudio, false},
469           {WebContextMenuData::kMediaCanToggleControls, true},
470           {WebContextMenuData::kMediaControls, false},
471           {WebContextMenuData::kMediaCanPrint, false},
472           {WebContextMenuData::kMediaCanRotate, false},
473           {WebContextMenuData::kMediaCanPictureInPicture, true},
474           {WebContextMenuData::kMediaPictureInPicture, false},
475           {WebContextMenuData::kMediaCanLoop, false},
476       };
477 
478   for (const auto& expected_media_flag : expected_media_flags) {
479     EXPECT_EQ(expected_media_flag.second,
480               !!(context_menu_data.media_flags & expected_media_flag.first))
481         << "Flag 0x" << std::hex << expected_media_flag.first;
482   }
483 }
484 
TEST_F(ContextMenuControllerTest,EditingActionsEnabledInSVGDocument)485 TEST_F(ContextMenuControllerTest, EditingActionsEnabledInSVGDocument) {
486   frame_test_helpers::LoadFrame(LocalMainFrame(), R"SVG(data:image/svg+xml,
487     <svg xmlns='http://www.w3.org/2000/svg'
488          xmlns:h='http://www.w3.org/1999/xhtml'
489          font-family='Ahem'>
490       <text y='20' id='t'>Copyable text</text>
491       <foreignObject y='30' width='100' height='200'>
492         <h:div id='e' style='width: 100px; height: 50px'
493                contenteditable='true'>
494           XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
495         </h:div>
496       </foreignObject>
497     </svg>
498   )SVG");
499   LoadAhem();
500 
501   Document* document = GetDocument();
502   ASSERT_TRUE(document->IsSVGDocument());
503 
504   Element* text_element = document->getElementById("t");
505   document->UpdateStyleAndLayout(DocumentUpdateReason::kTest);
506   FrameSelection& selection = document->GetFrame()->Selection();
507 
508   // <text> element
509   selection.SelectSubString(*text_element, 4, 8);
510   EXPECT_TRUE(ShowContextMenuForElement(text_element, kMenuSourceMouse));
511 
512   WebContextMenuData context_menu_data =
513       GetWebFrameClient().GetContextMenuData();
514   EXPECT_EQ(context_menu_data.media_type, ContextMenuDataMediaType::kNone);
515   EXPECT_EQ(context_menu_data.edit_flags, ContextMenuDataEditFlags::kCanCopy);
516   EXPECT_EQ(context_menu_data.selected_text, "able tex");
517 
518   // <div contenteditable=true>
519   Element* editable_element = document->getElementById("e");
520   selection.SelectSubString(*editable_element, 0, 42);
521   EXPECT_TRUE(ShowContextMenuForElement(editable_element, kMenuSourceMouse));
522 
523   context_menu_data = GetWebFrameClient().GetContextMenuData();
524   EXPECT_EQ(context_menu_data.media_type, ContextMenuDataMediaType::kNone);
525   EXPECT_EQ(context_menu_data.edit_flags,
526             ContextMenuDataEditFlags::kCanCut |
527                 ContextMenuDataEditFlags::kCanCopy |
528                 ContextMenuDataEditFlags::kCanPaste |
529                 ContextMenuDataEditFlags::kCanDelete |
530                 ContextMenuDataEditFlags::kCanEditRichly);
531 }
532 
TEST_F(ContextMenuControllerTest,EditingActionsEnabledInXMLDocument)533 TEST_F(ContextMenuControllerTest, EditingActionsEnabledInXMLDocument) {
534   frame_test_helpers::LoadFrame(LocalMainFrame(), R"XML(data:text/xml,
535     <root>
536       <style xmlns="http://www.w3.org/1999/xhtml">
537         root { color: blue; }
538       </style>
539       <text id="t">Blue text</text>
540     </root>
541   )XML");
542 
543   Document* document = GetDocument();
544   ASSERT_TRUE(IsA<XMLDocument>(document));
545   ASSERT_FALSE(IsA<HTMLDocument>(document));
546 
547   Element* text_element = document->getElementById("t");
548   document->UpdateStyleAndLayout(DocumentUpdateReason::kTest);
549   FrameSelection& selection = document->GetFrame()->Selection();
550 
551   selection.SelectAll();
552   EXPECT_TRUE(ShowContextMenuForElement(text_element, kMenuSourceMouse));
553 
554   WebContextMenuData context_menu_data =
555       GetWebFrameClient().GetContextMenuData();
556   EXPECT_EQ(context_menu_data.media_type, ContextMenuDataMediaType::kNone);
557   EXPECT_EQ(context_menu_data.edit_flags, ContextMenuDataEditFlags::kCanCopy);
558   EXPECT_EQ(context_menu_data.selected_text, "Blue text");
559 }
560 
561 TEST_F(ContextMenuControllerTest, ShowNonLocatedContextMenuEvent) {
562   GetDocument()->documentElement()->setInnerHTML(
563       "<input id='sample' type='text' size='5' value='Sample Input Text'>");
564 
565   Document* document = GetDocument();
566   Element* input_element = document->getElementById("sample");
567   document->UpdateStyleAndLayout(DocumentUpdateReason::kTest);
568 
569   // Select the 'Sample' of |input|.
570   DOMRect* rect = input_element->getBoundingClientRect();
571   WebGestureEvent gesture_event(
572       WebInputEvent::Type::kGestureLongPress, WebInputEvent::kNoModifiers,
573       base::TimeTicks::Now(), WebGestureDevice::kTouchscreen);
574   gesture_event.SetPositionInWidget(gfx::PointF(rect->left(), rect->top()));
575   GetWebView()->MainFrameWidget()->HandleInputEvent(
576       WebCoalescedInputEvent(gesture_event, ui::LatencyInfo()));
577 
578   WebContextMenuData context_menu_data =
579       GetWebFrameClient().GetContextMenuData();
580   EXPECT_EQ(context_menu_data.selected_text, "Sample");
581 
582   // Adjust the selection from the start of |input| to the middle.
583   gfx::Point middle_point((rect->left() + rect->right()) / 2,
584                           (rect->top() + rect->bottom()) / 2);
585   LocalMainFrame()->MoveRangeSelectionExtent(middle_point);
586   LocalMainFrame()->LocalRootFrameWidget()->ShowContextMenu(
587       ui::mojom::MenuSourceType::TOUCH_HANDLE, middle_point);
588 
589   context_menu_data = GetWebFrameClient().GetContextMenuData();
590   EXPECT_NE(context_menu_data.selected_text, "");
591 
592   // Scroll the value of |input| to end.
593   input_element->setScrollLeft(input_element->scrollWidth());
594 
595   // Select all the value of |input| to ensure the start of selection is
596   // invisible.
597   LocalMainFrame()->MoveRangeSelectionExtent(
598       gfx::Point(rect->right(), rect->bottom()));
599   LocalMainFrame()->LocalRootFrameWidget()->ShowContextMenu(
600       ui::mojom::MenuSourceType::TOUCH_HANDLE,
601       gfx::Point(rect->right() / 2, rect->bottom() / 2));
602 
603   context_menu_data = GetWebFrameClient().GetContextMenuData();
604   EXPECT_EQ(context_menu_data.selected_text, "Sample Input Text");
605 }
606 
607 #if !defined(OS_MAC)
608 // Mac has no way to open a context menu based on a keyboard event.
609 TEST_F(ContextMenuControllerTest,
610        ValidateNonLocatedContextMenuOnLargeImageElement) {
611   GetDocument()->documentElement()->setInnerHTML(
612       "<img src=\"http://example.test/cat.jpg\" id=\"sample_image\" "
613       "width=\"200\" height=\"10000\" tabindex=\"-1\" />");
614 
615   Document* document = GetDocument();
616   Element* image_element = document->getElementById("sample_image");
617   // Set focus on the image element.
618   image_element->focus();
619   document->UpdateStyleAndLayout(DocumentUpdateReason::kTest);
620 
621   // Simulate Shift + F10 key event.
622   WebKeyboardEvent key_event(WebInputEvent::Type::kRawKeyDown,
623                              WebInputEvent::kShiftKey,
624                              WebInputEvent::GetStaticTimeStampForTests());
625 
626   key_event.windows_key_code = ui::VKEY_F10;
627   GetWebView()->MainFrameWidget()->HandleInputEvent(
628       WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
629   key_event.SetType(WebInputEvent::Type::kKeyUp);
630   GetWebView()->MainFrameWidget()->HandleInputEvent(
631       WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
632 
633   WebContextMenuData context_menu_data =
634       GetWebFrameClient().GetContextMenuData();
635   EXPECT_EQ(context_menu_data.media_type, ContextMenuDataMediaType::kImage);
636 }
637 #endif
638 
TEST_F(ContextMenuControllerTest,SelectionRectClipped)639 TEST_F(ContextMenuControllerTest, SelectionRectClipped) {
640   GetDocument()->documentElement()->setInnerHTML(
641       "<textarea id='text-area' cols=6 rows=2>Sample editable text</textarea>");
642 
643   Document* document = GetDocument();
644   Element* editable_element = document->getElementById("text-area");
645   document->UpdateStyleAndLayout(DocumentUpdateReason::kTest);
646   FrameSelection& selection = document->GetFrame()->Selection();
647 
648   // Select the 'Sample' of |textarea|.
649   DOMRect* rect = editable_element->getBoundingClientRect();
650   WebGestureEvent gesture_event(
651       WebInputEvent::Type::kGestureLongPress, WebInputEvent::kNoModifiers,
652       base::TimeTicks::Now(), WebGestureDevice::kTouchscreen);
653   gesture_event.SetPositionInWidget(gfx::PointF(rect->left(), rect->top()));
654   GetWebView()->MainFrameWidget()->HandleInputEvent(
655       WebCoalescedInputEvent(gesture_event, ui::LatencyInfo()));
656 
657   WebContextMenuData context_menu_data =
658       GetWebFrameClient().GetContextMenuData();
659   EXPECT_EQ(context_menu_data.selected_text, "Sample");
660 
661   // The selection rect is not clipped.
662   IntRect anchor, focus;
663   selection.ComputeAbsoluteBounds(anchor, focus);
664   anchor = document->GetFrame()->View()->FrameToViewport(anchor);
665   focus = document->GetFrame()->View()->FrameToViewport(focus);
666   int left = std::min(focus.X(), anchor.X());
667   int top = std::min(focus.Y(), anchor.Y());
668   int right = std::max(focus.MaxX(), anchor.MaxX());
669   int bottom = std::max(focus.MaxY(), anchor.MaxY());
670   WebRect selection_rect(left, top, right - left, bottom - top);
671   EXPECT_EQ(context_menu_data.selection_rect, selection_rect);
672 
673   // Select all the content of |textarea|.
674   selection.SelectAll();
675   EXPECT_TRUE(ShowContextMenuForElement(editable_element, kMenuSourceMouse));
676 
677   context_menu_data = GetWebFrameClient().GetContextMenuData();
678   EXPECT_EQ(context_menu_data.selected_text, "Sample editable text");
679 
680   // The selection rect is clipped by the editable box.
681   IntRect clip_bound = editable_element->VisibleBoundsInVisualViewport();
682   selection.ComputeAbsoluteBounds(anchor, focus);
683   anchor = document->GetFrame()->View()->FrameToViewport(anchor);
684   focus = document->GetFrame()->View()->FrameToViewport(focus);
685   left = std::max(clip_bound.X(), std::min(focus.X(), anchor.X()));
686   top = std::max(clip_bound.Y(), std::min(focus.Y(), anchor.Y()));
687   right = std::min(clip_bound.MaxX(), std::max(focus.MaxX(), anchor.MaxX()));
688   bottom = std::min(clip_bound.MaxY(), std::max(focus.MaxY(), anchor.MaxY()));
689   selection_rect = WebRect(left, top, right - left, bottom - top);
690   EXPECT_EQ(context_menu_data.selection_rect, selection_rect);
691 }
692 
693 }  // namespace blink
694