test_accessiblecaret_selection_mode.py (33634B)
1 # -*- coding: utf-8 -*- 2 # This Source Code Form is subject to the terms of the Mozilla Public 3 # License, v. 2.0. If a copy of the MPL was not distributed with this 4 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 6 import re 7 import sys 8 import os 9 import unittest 10 11 # Add this directory to the import path. 12 sys.path.append(os.path.dirname(__file__)) 13 14 from selection import ( 15 CaretActions, 16 SelectionManager, 17 ) 18 from marionette_driver.by import By 19 from marionette_driver.keys import Keys 20 from marionette_harness.marionette_test import ( 21 MarionetteTestCase, 22 SkipTest, 23 parameterized, 24 ) 25 26 27 class AccessibleCaretSelectionModeTestCase(MarionetteTestCase): 28 """Test cases for AccessibleCaret under selection mode.""" 29 30 # Element IDs. 31 _input_id = "input" 32 _input_padding_id = "input-padding" 33 _input_size_id = "input-size" 34 _textarea_id = "textarea" 35 _textarea2_id = "textarea2" 36 _textarea_disabled_id = "textarea-disabled" 37 _textarea_one_line_id = "textarea-one-line" 38 _textarea_rtl_id = "textarea-rtl" 39 _contenteditable_id = "contenteditable" 40 _contenteditable2_id = "contenteditable2" 41 _content_id = "content" 42 _content2_id = "content2" 43 _non_selectable_id = "non-selectable" 44 45 # Test html files. 46 _selection_html = "layout/test_carets_selection.html" 47 _multipleline_html = "layout/test_carets_multipleline.html" 48 _multiplerange_html = "layout/test_carets_multiplerange.html" 49 _longtext_html = "layout/test_carets_longtext.html" 50 _iframe_html = "layout/test_carets_iframe.html" 51 _iframe_scroll_html = "layout/test_carets_iframe_scroll.html" 52 _display_none_html = "layout/test_carets_display_none.html" 53 _svg_shapes_html = "layout/test_carets_svg_shapes.html" 54 _key_scroll_html = "layout/test_carets_key_scroll.html" 55 56 def setUp(self): 57 # Code to execute before every test is running. 58 super(AccessibleCaretSelectionModeTestCase, self).setUp() 59 self.carets_tested_pref = "layout.accessiblecaret.enabled" 60 self.prefs = { 61 "layout.word_select.eat_space_to_next_word": False, 62 self.carets_tested_pref: True, 63 # To disable transition, or the caret may not be the desired 64 # location yet, we cannot press a caret successfully. 65 "layout.accessiblecaret.transition-duration": "0.0", 66 # Enabled hapticfeedback on all platforms. The tests shouldn't crash 67 # on platforms without hapticfeedback support. 68 "layout.accessiblecaret.hapticfeedback": True, 69 } 70 self.marionette.set_prefs(self.prefs) 71 self.actions = CaretActions(self.marionette) 72 73 def tearDown(self): 74 self.marionette.actions.release() 75 super(AccessibleCaretSelectionModeTestCase, self).tearDown() 76 77 def open_test_html(self, test_html): 78 self.marionette.navigate(self.marionette.absolute_url(test_html)) 79 80 def word_offset(self, text, ordinal): 81 "Get the character offset of the ordinal-th word in text." 82 tokens = re.split(r"(\S+)", text) # both words and spaces 83 spaces = tokens[0::2] # collect spaces at odd indices 84 words = tokens[1::2] # collect word at even indices 85 86 if ordinal >= len(words): 87 raise IndexError( 88 "Only %d words in text, but got ordinal %d" % (len(words), ordinal) 89 ) 90 91 # Cursor position of the targeting word is behind the the first 92 # character in the word. For example, offset to 'def' in 'abc def' is 93 # between 'd' and 'e'. 94 offset = len(spaces[0]) + 1 95 offset += sum(len(words[i]) + len(spaces[i + 1]) for i in range(ordinal)) 96 return offset 97 98 def test_word_offset(self): 99 text = " " * 3 + "abc" + " " * 3 + "def" 100 101 self.assertTrue(self.word_offset(text, 0), 4) 102 self.assertTrue(self.word_offset(text, 1), 10) 103 with self.assertRaises(IndexError): 104 self.word_offset(text, 2) 105 106 def word_location(self, el, ordinal): 107 """Get the location (x, y) of the ordinal-th word in el. 108 109 The ordinal starts from 0. 110 111 Note: this function has a side effect which changes focus to the 112 target element el. 113 114 """ 115 sel = SelectionManager(el) 116 offset = self.word_offset(sel.content, ordinal) 117 118 # Move the blinking cursor to the word. 119 self.actions.click(element=el).perform() 120 sel.move_cursor_to_front() 121 sel.move_cursor_by_offset(offset) 122 x, y = sel.cursor_location() 123 124 return x, y 125 126 def rect_relative_to_window(self, el): 127 """Get element's bounding rectangle. 128 129 This function is similar to el.rect, but the coordinate is relative to 130 the top left corner of the window instead of the document. 131 132 """ 133 return self.marionette.execute_script( 134 """ 135 let rect = arguments[0].getBoundingClientRect(); 136 return {x: rect.x, y:rect.y, width: rect.width, height: rect.height}; 137 """, 138 script_args=[el], 139 ) 140 141 def long_press_on_location(self, el, x=None, y=None): 142 """Long press the location (x, y) to select a word. 143 144 If no (x, y) are given, it will be targeted at the center of the 145 element. On Windows, those spaces after the word will also be selected. 146 This function sends synthesized eMouseLongTap to gecko. 147 148 """ 149 rect = self.rect_relative_to_window(el) 150 target_x = rect["x"] + (x if x is not None else rect["width"] // 2) 151 target_y = rect["y"] + (y if y is not None else rect["height"] // 2) 152 153 self.marionette.execute_script( 154 """ 155 let utils = window.windowUtils; 156 utils.sendTouchEventToWindow('touchstart', [0], 157 [arguments[0]], [arguments[1]], 158 [1], [1], [0], [1], [0], [0], [0], 0); 159 window.synthesizeMouseEvent('mouselongtap', arguments[0], arguments[1], 160 {}, { toWindow: true }); 161 utils.sendTouchEventToWindow('touchend', [0], 162 [arguments[0]], [arguments[1]], 163 [1], [1], [0], [1], [0], [0], [0], 0); 164 """, 165 script_args=[target_x, target_y], 166 sandbox="system", 167 ) 168 169 def long_press_on_word(self, el, wordOrdinal): 170 x, y = self.word_location(el, wordOrdinal) 171 self.long_press_on_location(el, x, y) 172 173 def to_unix_line_ending(self, s): 174 """Changes all Windows/Mac line endings in s to UNIX line endings.""" 175 176 return s.replace("\r\n", "\n").replace("\r", "\n") 177 178 @parameterized(_input_id, el_id=_input_id) 179 @parameterized(_textarea_id, el_id=_textarea_id) 180 @parameterized(_textarea_disabled_id, el_id=_textarea_disabled_id) 181 @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id) 182 @parameterized(_contenteditable_id, el_id=_contenteditable_id) 183 @parameterized(_content_id, el_id=_content_id) 184 def test_long_press_to_select_a_word(self, el_id): 185 self.open_test_html(self._selection_html) 186 el = self.marionette.find_element(By.ID, el_id) 187 self._test_long_press_to_select_a_word(el) 188 189 def _test_long_press_to_select_a_word(self, el): 190 sel = SelectionManager(el) 191 original_content = sel.content 192 words = original_content.split() 193 self.assertTrue(len(words) >= 2, "Expect at least two words in the content.") 194 target_content = words[0] 195 196 # Goal: Select the first word. 197 self.long_press_on_word(el, 0) 198 199 # Ignore extra spaces selected after the word. 200 self.assertEqual(target_content, sel.selected_content) 201 202 @parameterized(_input_id, el_id=_input_id) 203 @parameterized(_textarea_id, el_id=_textarea_id) 204 @parameterized(_textarea_disabled_id, el_id=_textarea_disabled_id) 205 @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id) 206 @parameterized(_contenteditable_id, el_id=_contenteditable_id) 207 @parameterized(_content_id, el_id=_content_id) 208 def test_drag_carets(self, el_id): 209 self.open_test_html(self._selection_html) 210 el = self.marionette.find_element(By.ID, el_id) 211 sel = SelectionManager(el) 212 original_content = sel.content 213 words = original_content.split() 214 self.assertTrue(len(words) >= 1, "Expect at least one word in the content.") 215 216 # Goal: Select all text after the first word. 217 target_content = original_content[len(words[0]) :] 218 219 # Get the location of the carets at the end of the content for later 220 # use. 221 self.actions.click(element=el).perform() 222 sel.select_all() 223 end_caret_x, end_caret_y = sel.second_caret_location() 224 225 self.long_press_on_word(el, 0) 226 227 # Drag the second caret to the end of the content. 228 (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location() 229 self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform() 230 231 # Drag the first caret to the previous position of the second caret. 232 self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform() 233 234 self.assertEqual(target_content, sel.selected_content) 235 236 @parameterized(_input_id, el_id=_input_id) 237 @parameterized(_textarea_id, el_id=_textarea_id) 238 @parameterized(_textarea_disabled_id, el_id=_textarea_disabled_id) 239 @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id) 240 @parameterized(_contenteditable_id, el_id=_contenteditable_id) 241 @parameterized(_content_id, el_id=_content_id) 242 def test_drag_swappable_carets(self, el_id): 243 self.open_test_html(self._selection_html) 244 el = self.marionette.find_element(By.ID, el_id) 245 sel = SelectionManager(el) 246 original_content = sel.content 247 words = original_content.split() 248 self.assertTrue(len(words) >= 1, "Expect at least one word in the content.") 249 250 target_content1 = words[0] 251 target_content2 = original_content[len(words[0]) :] 252 253 # Get the location of the carets at the end of the content for later 254 # use. 255 self.actions.click(element=el).perform() 256 sel.select_all() 257 end_caret_x, end_caret_y = sel.second_caret_location() 258 259 self.long_press_on_word(el, 0) 260 261 # Drag the first caret to the end and back to where it was 262 # immediately. The selection range should not be collapsed. 263 caret1_x, caret1_y = sel.first_caret_location() 264 self.actions.flick(el, caret1_x, caret1_y, end_caret_x, end_caret_y).flick( 265 el, end_caret_x, end_caret_y, caret1_x, caret1_y 266 ).perform() 267 self.assertEqual(target_content1, sel.selected_content) 268 269 # Drag the first caret to the end. 270 caret1_x, caret1_y = sel.first_caret_location() 271 self.actions.flick(el, caret1_x, caret1_y, end_caret_x, end_caret_y).perform() 272 self.assertEqual(target_content2, sel.selected_content) 273 274 @parameterized(_input_id, el_id=_input_id) 275 # Bug 1855083: Skip textarea tests due to high frequent intermittent. 276 # @parameterized(_textarea_id, el_id=_textarea_id) 277 # @parameterized(_textarea_disabled_id, el_id=_textarea_disabled_id) 278 @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id) 279 @parameterized(_contenteditable_id, el_id=_contenteditable_id) 280 @parameterized(_content_id, el_id=_content_id) 281 def test_minimum_select_one_character(self, el_id): 282 self.open_test_html(self._selection_html) 283 el = self.marionette.find_element(By.ID, el_id) 284 self._test_minimum_select_one_character(el) 285 286 @parameterized(_textarea2_id, el_id=_textarea2_id) 287 @parameterized(_contenteditable2_id, el_id=_contenteditable2_id) 288 @parameterized(_content2_id, el_id=_content2_id) 289 def test_minimum_select_one_character2(self, el_id): 290 self.open_test_html(self._multipleline_html) 291 el = self.marionette.find_element(By.ID, el_id) 292 self._test_minimum_select_one_character(el) 293 294 def _test_minimum_select_one_character(self, el): 295 sel = SelectionManager(el) 296 original_content = sel.content 297 words = original_content.split() 298 self.assertTrue(len(words) >= 1, "Expect at least one word in the content.") 299 300 # Get the location of the carets at the end of the content for later 301 # use. 302 sel.select_all() 303 end_caret_x, end_caret_y = sel.second_caret_location() 304 self.actions.click(element=el).perform() 305 306 # Goal: Select the first character. 307 target_content = original_content[0] 308 309 self.long_press_on_word(el, 0) 310 311 # Drag the second caret to the end of the content. 312 (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location() 313 self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform() 314 315 # Drag the second caret to the position of the first caret. 316 (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location() 317 self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform() 318 319 self.assertEqual(target_content, sel.selected_content) 320 321 @parameterized( 322 _input_id + "_to_" + _textarea_id, el1_id=_input_id, el2_id=_textarea_id 323 ) 324 @parameterized( 325 _input_id + "_to_" + _contenteditable_id, 326 el1_id=_input_id, 327 el2_id=_contenteditable_id, 328 ) 329 @parameterized( 330 _input_id + "_to_" + _content_id, el1_id=_input_id, el2_id=_content_id 331 ) 332 @parameterized( 333 _textarea_id + "_to_" + _input_id, el1_id=_textarea_id, el2_id=_input_id 334 ) 335 @parameterized( 336 _textarea_id + "_to_" + _contenteditable_id, 337 el1_id=_textarea_id, 338 el2_id=_contenteditable_id, 339 ) 340 @parameterized( 341 _textarea_id + "_to_" + _content_id, el1_id=_textarea_id, el2_id=_content_id 342 ) 343 @parameterized( 344 _contenteditable_id + "_to_" + _input_id, 345 el1_id=_contenteditable_id, 346 el2_id=_input_id, 347 ) 348 @parameterized( 349 _contenteditable_id + "_to_" + _textarea_id, 350 el1_id=_contenteditable_id, 351 el2_id=_textarea_id, 352 ) 353 @parameterized( 354 _contenteditable_id + "_to_" + _content_id, 355 el1_id=_contenteditable_id, 356 el2_id=_content_id, 357 ) 358 @parameterized( 359 _content_id + "_to_" + _input_id, el1_id=_content_id, el2_id=_input_id 360 ) 361 @parameterized( 362 _content_id + "_to_" + _textarea_id, el1_id=_content_id, el2_id=_textarea_id 363 ) 364 @parameterized( 365 _content_id + "_to_" + _contenteditable_id, 366 el1_id=_content_id, 367 el2_id=_contenteditable_id, 368 ) 369 def test_long_press_changes_focus_from(self, el1_id, el2_id): 370 self.open_test_html(self._selection_html) 371 el1 = self.marionette.find_element(By.ID, el1_id) 372 el2 = self.marionette.find_element(By.ID, el2_id) 373 374 # Compute the content of the first word in el2. 375 sel = SelectionManager(el2) 376 original_content = sel.content 377 words = original_content.split() 378 target_content = words[0] 379 380 # Goal: Click to focus el1, and then select the first word on el2. 381 382 # We want to collect the location of the first word in el2 here 383 # since self.word_location() has the side effect which would 384 # change the focus. 385 x, y = self.word_location(el2, 0) 386 387 self.actions.click(element=el1).perform() 388 self.long_press_on_location(el2, x, y) 389 self.assertEqual(target_content, sel.selected_content) 390 391 @parameterized(_input_id, el_id=_input_id) 392 @parameterized(_textarea_id, el_id=_textarea_id) 393 @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id) 394 @parameterized(_contenteditable_id, el_id=_contenteditable_id) 395 def test_focus_not_changed_by_long_press_on_non_selectable(self, el_id): 396 self.open_test_html(self._selection_html) 397 el = self.marionette.find_element(By.ID, el_id) 398 non_selectable = self.marionette.find_element(By.ID, self._non_selectable_id) 399 400 # Goal: Focus remains on the editable element el after long pressing on 401 # the non-selectable element. 402 sel = SelectionManager(el) 403 self.long_press_on_word(el, 0) 404 self.long_press_on_location(non_selectable) 405 active_sel = SelectionManager(self.marionette.get_active_element()) 406 self.assertEqual(sel.content, active_sel.content) 407 408 @parameterized(_input_id, el_id=_input_id) 409 @parameterized(_textarea_id, el_id=_textarea_id) 410 @parameterized(_textarea_disabled_id, el_id=_textarea_disabled_id) 411 @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id) 412 @parameterized(_contenteditable_id, el_id=_contenteditable_id) 413 @parameterized(_content_id, el_id=_content_id) 414 def test_handle_tilt_when_carets_overlap_each_other(self, el_id): 415 """Test tilt handling when carets overlap to each other. 416 417 Let the two carets overlap each other. If they are set to tilted 418 successfully, click on the tilted carets should not cause the selection 419 to be collapsed and the carets should be draggable. 420 421 """ 422 self.open_test_html(self._selection_html) 423 el = self.marionette.find_element(By.ID, el_id) 424 sel = SelectionManager(el) 425 original_content = sel.content 426 words = original_content.split() 427 self.assertTrue(len(words) >= 1, "Expect at least one word in the content.") 428 429 # Goal: Select the first word. 430 self.long_press_on_word(el, 0) 431 target_content = sel.selected_content 432 433 # Drag the first caret to the position of the second caret to trigger 434 # carets overlapping. 435 (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location() 436 self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform() 437 438 # We make two hit tests targeting the left edge of the left tilted caret 439 # and the right edge of the right tilted caret. If either of the hits is 440 # missed, selection would be collapsed and both carets should not be 441 # draggable. 442 (caret3_x, caret3_y), (caret4_x, caret4_y) = sel.carets_location() 443 444 # The following values are from ua.css and all.js 445 caret_width = float(self.marionette.get_pref("layout.accessiblecaret.width")) 446 caret_margin_left = float( 447 self.marionette.get_pref("layout.accessiblecaret.margin-left") 448 ) 449 tilt_right_margin_left = 0.41 * caret_width 450 tilt_left_margin_left = -0.39 * caret_width 451 452 left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left 453 self.actions.move(el, left_caret_left_edge_x + 2, caret3_y).click().perform() 454 455 right_caret_right_edge_x = ( 456 caret4_x + caret_margin_left + tilt_right_margin_left + caret_width 457 ) 458 self.actions.move(el, right_caret_right_edge_x - 2, caret4_y).click().perform() 459 460 # Drag the first caret back to the initial selection, the first word. 461 self.actions.flick(el, caret3_x, caret3_y, caret1_x, caret1_y).perform() 462 463 self.assertEqual(target_content, sel.selected_content) 464 465 def test_drag_caret_over_non_selectable_field(self): 466 """Test dragging the caret over a non-selectable field. 467 468 The selected content should exclude non-selectable elements and the 469 second caret should appear in last range's position. 470 471 """ 472 self.open_test_html(self._multiplerange_html) 473 body = self.marionette.find_element(By.ID, "bd") 474 sel3 = self.marionette.find_element(By.ID, "sel3") 475 sel4 = self.marionette.find_element(By.ID, "sel4") 476 sel6 = self.marionette.find_element(By.ID, "sel6") 477 478 # Select target element and get target caret location 479 self.long_press_on_word(sel4, 3) 480 sel = SelectionManager(body) 481 end_caret_x, end_caret_y = sel.second_caret_location() 482 483 self.long_press_on_word(sel6, 0) 484 end_caret2_x, end_caret2_y = sel.second_caret_location() 485 486 # Select start element 487 self.long_press_on_word(sel3, 3) 488 489 # Drag end caret to target location 490 (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location() 491 self.actions.flick( 492 body, caret2_x, caret2_y, end_caret_x, end_caret_y, 1 493 ).perform() 494 self.assertEqual( 495 self.to_unix_line_ending(sel.selected_content.strip()), 496 "this 3\nuser can select this", 497 ) 498 499 (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location() 500 self.actions.flick( 501 body, caret2_x, caret2_y, end_caret2_x, end_caret2_y, 1 502 ).perform() 503 self.assertEqual( 504 self.to_unix_line_ending(sel.selected_content.strip()), 505 "this 3\nuser can select this 4\nuser can select this 5\nuser", 506 ) 507 508 # Drag first caret to target location 509 (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location() 510 self.actions.flick( 511 body, caret1_x, caret1_y, end_caret_x, end_caret_y, 1 512 ).perform() 513 self.assertEqual( 514 self.to_unix_line_ending(sel.selected_content.strip()), 515 "4\nuser can select this 5\nuser", 516 ) 517 518 def test_drag_swappable_caret_over_non_selectable_field(self): 519 self.open_test_html(self._multiplerange_html) 520 body = self.marionette.find_element(By.ID, "bd") 521 sel3 = self.marionette.find_element(By.ID, "sel3") 522 sel4 = self.marionette.find_element(By.ID, "sel4") 523 sel = SelectionManager(body) 524 525 self.long_press_on_word(sel4, 3) 526 ( 527 (end_caret1_x, end_caret1_y), 528 (end_caret2_x, end_caret2_y), 529 ) = sel.carets_location() 530 531 self.long_press_on_word(sel3, 3) 532 (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location() 533 534 # Drag the first caret down, which will across the second caret. 535 self.actions.flick( 536 body, caret1_x, caret1_y, end_caret1_x, end_caret1_y 537 ).perform() 538 self.assertEqual( 539 self.to_unix_line_ending(sel.selected_content.strip()), "3\nuser can select" 540 ) 541 542 # The old second caret becomes the first caret. Drag it down again. 543 self.actions.flick( 544 body, caret2_x, caret2_y, end_caret2_x, end_caret2_y 545 ).perform() 546 self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()), "this") 547 548 def test_drag_caret_to_beginning_of_a_line(self): 549 """Bug 1094056 550 Test caret visibility when caret is dragged to beginning of a line 551 """ 552 self.open_test_html(self._multiplerange_html) 553 body = self.marionette.find_element(By.ID, "bd") 554 sel1 = self.marionette.find_element(By.ID, "sel1") 555 sel2 = self.marionette.find_element(By.ID, "sel2") 556 557 # Select the first word in the second line 558 self.long_press_on_word(sel2, 0) 559 sel = SelectionManager(body) 560 ( 561 (start_caret_x, start_caret_y), 562 (end_caret_x, end_caret_y), 563 ) = sel.carets_location() 564 565 # Select target word in the first line 566 self.long_press_on_word(sel1, 2) 567 568 # Drag end caret to the beginning of the second line 569 (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location() 570 self.actions.flick( 571 body, caret2_x, caret2_y, start_caret_x, start_caret_y 572 ).perform() 573 574 # Drag end caret back to the target word 575 self.actions.flick( 576 body, start_caret_x, start_caret_y, caret2_x, caret2_y 577 ).perform() 578 579 self.assertEqual(self.to_unix_line_ending(sel.selected_content), "select") 580 581 def test_select_word_inside_an_iframe(self): 582 """Bug 1088552 583 The scroll offset in iframe should be taken into consideration properly. 584 In this test, we scroll content in the iframe to the bottom to cause a 585 huge offset. If we use the right coordinate system, selection should 586 work. Otherwise, it would be hard to trigger select word. 587 """ 588 self.open_test_html(self._iframe_html) 589 iframe = self.marionette.find_element(By.ID, "frame") 590 591 # switch to inner iframe and scroll to the bottom 592 self.marionette.switch_to_frame(iframe) 593 self.marionette.execute_script('document.getElementById("bd").scrollTop += 999') 594 595 # long press to select bottom text 596 body = self.marionette.find_element(By.ID, "bd") 597 sel = SelectionManager(body) 598 self._bottomtext = self.marionette.find_element(By.ID, "bottomtext") 599 self.long_press_on_location(self._bottomtext) 600 601 self.assertNotEqual(self.to_unix_line_ending(sel.selected_content), "") 602 603 def test_select_word_inside_an_unfocused_iframe(self): 604 """Bug 1306634: Test we can long press to select a word in an unfocused iframe.""" 605 self.open_test_html(self._iframe_html) 606 607 el = self.marionette.find_element(By.ID, self._input_id) 608 sel = SelectionManager(el) 609 610 # First, we select the first word in the input of the parent document. 611 el_first_word = sel.content.split()[0] # first world is "ABC" 612 self.long_press_on_word(el, 0) 613 self.assertEqual(el_first_word, sel.selected_content) 614 615 # Then, we long press on the center of the iframe. It should select a 616 # word inside of the document, not the placehoder in the parent 617 # document. 618 iframe = self.marionette.find_element(By.ID, "frame") 619 self.long_press_on_location(iframe) 620 self.marionette.switch_to_frame(iframe) 621 body = self.marionette.find_element(By.ID, "bd") 622 sel = SelectionManager(body) 623 self.assertNotEqual("", sel.selected_content) 624 625 def test_carets_initialized_in_display_none(self): 626 """Test AccessibleCaretEventHub is properly initialized on a <html> with 627 display: none. 628 629 """ 630 self.open_test_html(self._display_none_html) 631 html = self.marionette.find_element(By.ID, "html") 632 content = self.marionette.find_element(By.ID, "content") 633 634 # Remove 'display: none' from <html> 635 self.marionette.execute_script( 636 'arguments[0].style.display = "unset";', script_args=[html] 637 ) 638 639 # If AccessibleCaretEventHub is initialized successfully, select a word 640 # should work. 641 self._test_long_press_to_select_a_word(content) 642 643 @unittest.skip("Bug 1855083: High frequent intermittent.") 644 def test_long_press_to_select_when_partial_visible_word_is_selected(self): 645 self.open_test_html(self._selection_html) 646 el = self.marionette.find_element(By.ID, self._input_size_id) 647 sel = SelectionManager(el) 648 649 original_content = sel.content 650 words = original_content.split() 651 652 # We cannot use self.long_press_on_word() for the second long press 653 # on the first word because it has side effect that changes the 654 # cursor position. We need to save the location of the first word to 655 # be used later. 656 word0_x, word0_y = self.word_location(el, 0) 657 658 # Long press on the second word. 659 self.long_press_on_word(el, 1) 660 self.assertEqual(words[1], sel.selected_content) 661 662 # Long press on the first word. 663 self.long_press_on_location(el, word0_x, word0_y) 664 self.assertEqual(words[0], sel.selected_content) 665 666 # If the second caret is visible, it can be dragged to the position 667 # of the first caret. After that, selection will contain only the 668 # first character. 669 (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location() 670 self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform() 671 self.assertEqual(words[0][0], sel.selected_content) 672 673 @parameterized(_input_id, el_id=_input_id) 674 @parameterized(_input_padding_id, el_id=_input_padding_id) 675 @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id) 676 @parameterized(_contenteditable_id, el_id=_contenteditable_id) 677 def test_carets_not_jump_when_dragging_to_editable_content_boundary(self, el_id): 678 self.open_test_html(self._selection_html) 679 el = self.marionette.find_element(By.ID, el_id) 680 sel = SelectionManager(el) 681 original_content = sel.content 682 words = original_content.split() 683 self.assertTrue(len(words) >= 3, "Expect at least three words in the content.") 684 685 # Goal: the selection is not changed after dragging the caret on the 686 # Y-axis. 687 target_content = words[1] 688 689 self.long_press_on_word(el, 1) 690 (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location() 691 692 # Drag the first caret up by 40px. 693 self.actions.flick(el, caret1_x, caret1_y, caret1_x, caret1_y - 40).perform() 694 self.assertEqual(target_content, sel.selected_content) 695 696 # Drag the second caret down by 50px. 697 self.actions.flick(el, caret2_x, caret2_y, caret2_x, caret2_y + 50).perform() 698 self.assertEqual(target_content, sel.selected_content) 699 700 def test_carets_should_not_appear_when_long_pressing_svg_shapes(self): 701 self.open_test_html(self._svg_shapes_html) 702 703 rect = self.marionette.find_element(By.ID, "rect") 704 text = self.marionette.find_element(By.ID, "text") 705 706 sel = SelectionManager(text) 707 num_words_in_text = len(sel.content.split()) 708 709 # Goal: the carets should not appear when long-pressing on the 710 # unselectable SVG rect. 711 712 # Get the position of the end of last word in text, i.e. the 713 # position of the second caret when selecting the last word. 714 self.long_press_on_word(text, num_words_in_text - 1) 715 (_, _), (x2, y2) = sel.carets_location() 716 717 # Long press to select the unselectable SVG rect. 718 self.long_press_on_location(rect) 719 (_, _), (x, y) = sel.carets_location() 720 721 # Drag the second caret to (x2, y2). 722 self.actions.flick(text, x, y, x2, y2).perform() 723 724 # If the carets should appear on the rect, the selection will be 725 # extended to cover all the words in text. Assert this should not 726 # happen. 727 self.assertNotEqual(sel.content, sel.selected_content.strip()) 728 729 def test_select_word_scroll_then_drag_caret(self): 730 """Bug 1657256: Test select word, scroll page up , and then drag the second 731 caret down to cover "EEEEEE". 732 733 Note the selection should be extended to just cover "EEEEEE", not extend 734 to other lines below "EEEEEE". 735 736 """ 737 738 self.open_test_html(self._iframe_scroll_html) 739 iframe = self.marionette.find_element(By.ID, "iframe") 740 741 # Switch to the inner iframe. 742 self.marionette.switch_to_frame(iframe) 743 body = self.marionette.find_element(By.ID, "bd") 744 sel = SelectionManager(body) 745 746 # Select "EEEEEE" to get the y position of the second caret. This is the 747 # y position we are going to drag the caret to. 748 content2 = self.marionette.find_element(By.ID, self._content2_id) 749 self.long_press_on_word(content2, 0) 750 (_, _), (x, y2) = sel.carets_location() 751 752 # Step 1: Select "DDDDDD". 753 content = self.marionette.find_element(By.ID, self._content_id) 754 self.long_press_on_word(content, 0) 755 (_, _), (_, y1) = sel.carets_location() 756 757 # The y-axis offset of the second caret needed to extend the selection. 758 y_offset = y2 - y1 759 760 # Step 2: Scroll the page upwards by 40px. 761 scroll_offset = 40 762 self.marionette.execute_script( 763 "document.documentElement.scrollTop += arguments[0]", 764 script_args=[scroll_offset], 765 ) 766 767 # Step 3: Drag the second caret down. 768 self.actions.flick( 769 body, x, y1 - scroll_offset, x, y1 - scroll_offset + y_offset 770 ).perform() 771 772 self.assertEqual("DDDDDD EEEEEE", sel.selected_content) 773 774 @unittest.skip("Bug 1855083: High frequent intermittent.") 775 def test_carets_not_show_after_key_scroll_down_and_up(self): 776 self.open_test_html(self._key_scroll_html) 777 html = self.marionette.find_element(By.ID, "html") 778 sel = SelectionManager(html) 779 780 # Select "BBBBB" to get the position of the second caret. This is the 781 # position to which we are going to drag the caret in the step 3. 782 content2 = self.marionette.find_element(By.ID, self._content2_id) 783 self.long_press_on_word(content2, 0) 784 (_, _), (x2, y2) = sel.carets_location() 785 786 # Step 1: Select "AAAAA". 787 content = self.marionette.find_element(By.ID, self._content_id) 788 self.long_press_on_word(content, 0) 789 (_, _), (x1, y1) = sel.carets_location() 790 791 # Step 2: Scroll the page down and up. 792 # 793 # FIXME: The selection highlight on "AAAAA" often disappears after page 794 # up, which is the root cause of the intermittent. Longer pause between 795 # the page down and page up might help, but it's not a great solution. 796 self.actions.key_chain.send_keys(Keys.PAGE_DOWN).pause(1000).send_keys( 797 Keys.PAGE_UP 798 ).perform() 799 self.assertEqual("AAAAA", sel.selected_content) 800 801 # Step 3: The carets shouldn't show up after scrolling the page. We're 802 # attempting to drag the second caret down so that if the bug occurs, we 803 # can drag the second caret to extend the selection to "BBBBB". 804 self.actions.flick(html, x1, y1, x2, y2).perform() 805 self.assertNotEqual("AAAAA\nBBBBB", sel.selected_content)