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