tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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)