def chain_flick(marionette, wait_for_condition, expected1, expected2):
    testAction = marionette.absolute_url("testAction.html")
    marionette.navigate(testAction)
    button = marionette.find_element(By.ID, "button1")
    action = Actions(marionette)
    action.flick(button, 0, 0, 0, 200).perform()
    wait_for_condition_else_raise(marionette, wait_for_condition, expected1,"return document.getElementById('button1').innerHTML;")
    wait_for_condition_else_raise(marionette, wait_for_condition, expected2,"return document.getElementById('buttonFlick').innerHTML;")
Esempio n. 2
0
 def _flick_to_image(self, direction):
     image = self.marionette.find_element(*self._current_image_locator)
     action = Actions(self.marionette)
     x_start = (image.size['width'] / 100) * (direction == 'next' and 90 or 10)
     x_end = (image.size['width'] / 100) * (direction == 'next' and 10 or 90)
     y_start = image.size['height'] / 4
     y_end = image.size['height'] / 4
     action.flick(image, x_start, y_start, x_end, y_end, 200).perform()
     Wait(self.marionette).until(
         lambda m: abs(image.location['x']) >= image.size['width'])
def chain_flick(marionette, wait_for_condition, expected1, expected2):
    testAction = marionette.absolute_url("testAction.html")
    marionette.navigate(testAction)
    button = marionette.find_element(By.ID, "button1")
    action = Actions(marionette)
    action.flick(button, 0, 0, 0, 200).perform()
    wait_for_condition_else_raise(
        marionette, wait_for_condition, expected1,
        "return document.getElementById('button1').innerHTML;")
    wait_for_condition_else_raise(
        marionette, wait_for_condition, expected2,
        "return document.getElementById('buttonFlick').innerHTML;")
Esempio n. 4
0
 def _flick_to_image(self, direction):
     image = self.marionette.find_element(*self._current_image_locator)
     action = Actions(self.marionette)
     x_start = (image.size['width'] / 100) * (direction == 'next' and 90
                                              or 10)
     x_end = (image.size['width'] / 100) * (direction == 'next' and 10
                                            or 90)
     y_start = image.size['height'] / 4
     y_end = image.size['height'] / 4
     action.flick(image, x_start, y_start, x_end, y_end, 200).perform()
     Wait(self.marionette).until(
         lambda m: abs(image.location['x']) >= image.size['width'])
Esempio n. 5
0
 def _flick_to_image(self, direction):
     image = self.marionette.find_element(*self._current_image_locator)
     action = Actions(self.marionette)
     x_start = (image.size['width'] / 100) * (direction == 'next' and 90 or 10)
     x_end = (image.size['width'] / 100) * (direction == 'next' and 10 or 90)
     y_start = image.size['height'] / 4
     y_end = image.size['height'] / 4
     action.flick(image, x_start, y_start, x_end, y_end, 200).perform()
     Wait(self.marionette).until(
         lambda m: abs(image.location['x']) >= image.size['width'])
     # Workaround for bug 1161441, the transitionend event is not firing in this
     # case with a Marionette flick action, as opposed to a manual flick action
     self.marionette.execute_script("""
           arguments[0].dispatchEvent(new CustomEvent("transitionend"));
         """, [self.current_image_frame])
Esempio n. 6
0
 def _flick_to_image(self, direction):
     image = self.marionette.find_element(*self._current_image_locator)
     action = Actions(self.marionette)
     x_start = (image.size['width'] / 100) * (direction == 'next' and 90
                                              or 10)
     x_end = (image.size['width'] / 100) * (direction == 'next' and 10
                                            or 90)
     y_start = image.size['height'] / 4
     y_end = image.size['height'] / 4
     action.flick(image, x_start, y_start, x_end, y_end, 200).perform()
     Wait(self.marionette).until(
         lambda m: abs(image.location['x']) >= image.size['width'])
     # Workaround for bug 1161441, the transitionend event is not firing in this
     # case with a Marionette flick action, as opposed to a manual flick action
     self.marionette.execute_script(
         """
           arguments[0].dispatchEvent(new CustomEvent("transitionend"));
         """, [self.current_image_frame])
Esempio n. 7
0
    def _flick_to_month(self, direction):
        """Flick current monthly calendar to next or previous month.

        @param direction: flick to next month if direction='next', else flick to previous month
        """
        action = Actions(self.marionette)

        month = self.marionette.find_element(
            *self._current_monthly_calendar_locator)
        month_year = self.current_month_year

        x_start = (month.size['width'] / 100) * (direction == 'next' and 90 or 10)
        x_end = (month.size['width'] / 100) * (direction == 'next' and 10 or 90)
        y_start = month.size['height'] / 4
        y_end = month.size['height'] / 4

        action.flick(month, x_start, y_start, x_end, y_end, 200).perform()

        Wait(self.marionette).until(lambda m: self.current_month_year != month_year)
Esempio n. 8
0
    def _flick_to_month(self, direction):
        """Flick current monthly calendar to next or previous month.

        @param direction: flick to next month if direction='next', else flick to previous month
        """
        action = Actions(self.marionette)

        month = self.marionette.find_element(
            *self._current_monthly_calendar_locator)
        month_year = self.current_month_year

        x_start = (month.size['width'] / 100) * (direction == 'next' and 90
                                                 or 10)
        x_end = (month.size['width'] / 100) * (direction == 'next' and 10
                                               or 90)
        y_start = month.size['height'] / 4
        y_end = month.size['height'] / 4

        action.flick(month, x_start, y_start, x_end, y_end, 200).perform()

        Wait(self.marionette).until(
            lambda m: self.current_month_year != month_year)
class CommonCaretsTestCase(object):
    '''Common test cases for a selection with a two carets.

    To run these test cases, a subclass must inherit from both this class and
    MarionetteTestCase.

    '''
    _long_press_time = 1  # 1 second
    _input_selector = (By.ID, 'input')
    _textarea_selector = (By.ID, 'textarea')
    _textarea_rtl_selector = (By.ID, 'textarea_rtl')
    _contenteditable_selector = (By.ID, 'contenteditable')
    _content_selector = (By.ID, 'content')
    _textarea2_selector = (By.ID, 'textarea2')
    _contenteditable2_selector = (By.ID, 'contenteditable2')
    _content2_selector = (By.ID, 'content2')

    def setUp(self):
        # Code to execute before a tests are run.
        super(CommonCaretsTestCase, self).setUp()
        self.actions = Actions(self.marionette)

        # The carets to be tested.
        self.carets_tested_pref = None

        # The carets to be disabled in this test suite.
        self.carets_disabled_pref = None

    def set_pref(self, pref_name, value):
        '''Set a preference to value.

        For example:
        >>> set_pref('layout.accessiblecaret.enabled', True)

        '''
        pref_name = repr(pref_name)
        if isinstance(value, bool):
            value = 'true' if value else 'false'
            func = 'setBoolPref'
        elif isinstance(value, int):
            value = str(value)
            func = 'setIntPref'
        else:
            value = repr(value)
            func = 'setCharPref'

        with self.marionette.using_context('chrome'):
            script = 'Services.prefs.%s(%s, %s)' % (func, pref_name, value)
            self.marionette.execute_script(script)

    def open_test_html(self, enabled=True):
        '''Open html for testing and locate elements, and enable/disable touch
        caret.'''
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html = self.marionette.absolute_url('test_selectioncarets.html')
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(*self._input_selector)
        self._textarea = self.marionette.find_element(*self._textarea_selector)
        self._textarea_rtl = self.marionette.find_element(
            *self._textarea_rtl_selector)
        self._contenteditable = self.marionette.find_element(
            *self._contenteditable_selector)
        self._content = self.marionette.find_element(*self._content_selector)

    def open_test_html2(self, enabled=True):
        '''Open html for testing and locate elements, and enable/disable touch
        caret.'''
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html2 = self.marionette.absolute_url(
            'test_selectioncarets_multipleline.html')
        self.marionette.navigate(test_html2)

        self._textarea2 = self.marionette.find_element(
            *self._textarea2_selector)
        self._contenteditable2 = self.marionette.find_element(
            *self._contenteditable2_selector)
        self._content2 = self.marionette.find_element(*self._content2_selector)

    def _first_word_location(self, el):
        '''Get the location (x, y) of the first word in el.

        Note: this function has a side effect which changes focus to the
        target element el.

        '''
        sel = SelectionManager(el)

        # Move caret behind the first character to get the location of the first
        # word.
        el.tap()
        sel.move_caret_to_front()
        sel.move_caret_by_offset(1)

        return sel.caret_location()

    def _long_press_to_select(self, el, x, y):
        '''Long press the location (x, y) to select a word.

        SelectionCarets should appear. On Windows, those spaces after the
        word will also be selected.

        '''
        long_press_without_contextmenu(self.marionette, el,
                                       self._long_press_time, x, y)

    def _test_long_press_to_select_a_word(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(
            len(words) >= 2, 'Expect at least two words in the content.')
        target_content = words[0]

        # Goal: Select the first word.
        x, y = self._first_word_location(el)
        self._long_press_to_select(el, x, y)

        # Ignore extra spaces selected after the word.
        assertFunc(target_content, sel.selected_content.rstrip())

    def _test_move_selection_carets(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(
            len(words) >= 1, 'Expect at least one word in the content.')

        # Goal: Select all text after the first word.
        target_content = original_content[len(words[0]):]

        # Get the location of the selection carets at the end of the content for
        # later use.
        el.tap()
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        x, y = self._first_word_location(el)
        self._long_press_to_select(el, x, y)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x,
                           end_caret_y).perform()

        # Move the left caret to the previous position of the right caret.
        self.actions.flick(el, caret1_x, caret1_y, caret2_x,
                           caret2_y).perform()

        # Ignore extra spaces at the beginning of the content in comparison.
        assertFunc(target_content.lstrip(), sel.selected_content.lstrip())

    def _test_minimum_select_one_character(self,
                                           el,
                                           assertFunc,
                                           x=None,
                                           y=None):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(
            len(words) >= 1, 'Expect at least one word in the content.')

        # Get the location of the selection carets at the end of the content for
        # later use.
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()
        el.tap()

        # Goal: Select the first character.
        target_content = original_content[0]

        if x and y:
            # If we got x and y from the arguments, use it as a hint of the
            # location of the first word
            pass
        else:
            x, y = self._first_word_location(el)
        self._long_press_to_select(el, x, y)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x,
                           end_caret_y).perform()

        # Move the right caret to the position of the left caret.
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x,
                           caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    def _test_focus_obtained_by_long_press(self, el1, el2):
        '''Test the focus could be changed from el1 to el2 by long press.

        If the focus is changed to e2 successfully, SelectionCarets should
        appear and could be dragged.

        '''
        # Goal: Tap to focus el1, and then select the first character on
        # el2.

        # We want to collect the location of the first word in el2 here
        # since self._first_word_location() has the side effect which would
        # change the focus.
        x, y = self._first_word_location(el2)
        el1.tap()
        self._test_minimum_select_one_character(el2,
                                                self.assertEqual,
                                                x=x,
                                                y=y)

    def _test_handle_tilt_when_carets_overlap_to_each_other(
            self, el, assertFunc):
        '''Test tilt handling when carets overlap to each other.

        Let SelectionCarets overlap to each other. If SelectionCarets are set
        to tilted successfully, tapping the tilted carets should not cause the
        selection to be collapsed and the carets should be draggable.
        '''

        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(
            len(words) >= 1, 'Expect at least one word in the content.')

        # Goal: Select the first word.
        x, y = self._first_word_location(el)
        self._long_press_to_select(el, x, y)
        target_content = sel.selected_content

        # Move the left caret to the position of the right caret to trigger
        # carets overlapping.
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret1_x, caret1_y, caret2_x,
                           caret2_y).perform()

        # We make two hit tests targeting the left edge of the left tilted caret
        # and the right edge of the right tilted caret. If either of the hits is
        # missed, selection would be collapsed and both carets should not be
        # draggable.
        (caret3_x, caret3_y), (caret4_x,
                               caret4_y) = sel.selection_carets_location()

        # The following values are from ua.css.
        caret_width = 44
        caret_margin_left = -23
        tilt_right_margin_left = 18
        tilt_left_margin_left = -17

        left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
        el.tap(left_caret_left_edge_x + 2, caret3_y)

        right_caret_right_edge_x = (caret4_x + caret_margin_left +
                                    tilt_right_margin_left + caret_width)
        el.tap(right_caret_right_edge_x - 2, caret4_y)

        # Drag the left caret back to the initial selection, the first word.
        self.actions.flick(el, caret3_x, caret3_y, caret1_x,
                           caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    ########################################################################
    # <input> test cases with selection carets enabled
    ########################################################################
    def test_input_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._input, self.assertEqual)

    def test_input_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._input, self.assertEqual)

    def test_input_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._input, self.assertEqual)

    def test_input_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._input)

    def test_input_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable,
                                                self._input)

    def test_input_focus_obtained_by_long_press_from_content_non_editable(
            self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._input)

    def test_input_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(
            self._input, self.assertEqual)

    ########################################################################
    # <input> test cases with selection carets disabled
    ########################################################################
    def test_input_long_press_to_select_a_word_disabled(self):
        self.open_test_html(enabled=False)
        self._test_long_press_to_select_a_word(self._input,
                                               self.assertNotEqual)

    def test_input_move_selection_carets_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_selection_carets(self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with selection carets enabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._textarea,
                                               self.assertEqual)

    def test_textarea_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._textarea, self.assertEqual)

    def test_textarea_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._textarea,
                                                self.assertEqual)

    def test_textarea_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable,
                                                self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_content_non_editable(
            self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._textarea)

    def test_textarea_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(
            self._textarea, self.assertEqual)

    ########################################################################
    # <textarea> test cases with selection carets disabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word_disabled(self):
        self.open_test_html(enabled=False)
        self._test_long_press_to_select_a_word(self._textarea,
                                               self.assertNotEqual)

    def test_textarea_move_selection_carets_disable(self):
        self.open_test_html(enabled=False)
        self._test_move_selection_carets(self._textarea, self.assertNotEqual)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets enabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._textarea_rtl,
                                               self.assertEqual)

    def test_textarea_rtl_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._textarea_rtl,
                                                self.assertEqual)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets disabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word_disabled(self):
        self.open_test_html(enabled=False)
        self._test_long_press_to_select_a_word(self._textarea_rtl,
                                               self.assertNotEqual)

    def test_textarea_rtl_move_selection_carets_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_selection_carets(self._textarea_rtl,
                                         self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with selection carets enabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._contenteditable,
                                               self.assertEqual)

    def test_contenteditable_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._contenteditable,
                                         self.assertEqual)

    def test_contenteditable_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._contenteditable,
                                                self.assertEqual)

    def test_contenteditable_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input,
                                                self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea,
                                                self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_content_non_editable(
            self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content,
                                                self._contenteditable)

    def test_contenteditable_handle_tilt_when_carets_overlap_to_each_other(
            self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(
            self._contenteditable, self.assertEqual)

    ########################################################################
    # <div> contenteditable test cases with selection carets disabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word_disabled(self):
        self.open_test_html(enabled=False)
        self._test_long_press_to_select_a_word(self._contenteditable,
                                               self.assertNotEqual)

    def test_contenteditable_move_selection_carets_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_selection_carets(self._contenteditable,
                                         self.assertNotEqual)

    ########################################################################
    # <div> non-editable test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._content, self.assertEqual)

    def test_content_non_editable_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._content, self.assertEqual)

    def test_content_non_editable_minimum_select_one_character_by_selection(
            self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._content,
                                                self.assertEqual)

    def test_content_non_editable_focus_obtained_by_long_press_from_input(
            self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_textarea(
            self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_contenteditable(
            self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable,
                                                self._content)

    def test_content_non_editable_handle_tilt_when_carets_overlap_to_each_other(
            self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(
            self._content, self.assertEqual)

    ########################################################################
    # <textarea> (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_textarea2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._textarea2,
                                                self.assertEqual)

    ########################################################################
    # <div> contenteditable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_contenteditable2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._contenteditable2,
                                                self.assertEqual)

    ########################################################################
    # <div> non-editable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._content2,
                                                self.assertEqual)
Esempio n. 10
0
class AccessibleCaretCursorModeTestCase(MarionetteTestCase):
    '''Test cases for AccessibleCaret under cursor mode.

    We call the blinking cursor (nsCaret) as cursor, and call AccessibleCaret as
    caret for short.

    '''
    # Element IDs.
    _input_id = 'input'
    _input_padding_id = 'input-padding'
    _textarea_id = 'textarea'
    _textarea_one_line_id = 'textarea-one-line'
    _contenteditable_id = 'contenteditable'

    # Test html files.
    _cursor_html = 'test_carets_cursor.html'

    def setUp(self):
        # Code to execute before every test is running.
        super(AccessibleCaretCursorModeTestCase, self).setUp()
        self.caret_tested_pref = 'layout.accessiblecaret.enabled'
        self.hide_carets_for_mouse = 'layout.accessiblecaret.hide_carets_for_mouse_input'
        self.prefs = {
            self.caret_tested_pref: True,
            self.hide_carets_for_mouse: False,
        }
        self.marionette.set_prefs(self.prefs)
        self.actions = Actions(self.marionette)

    def open_test_html(self, test_html):
        self.marionette.navigate(self.marionette.absolute_url(test_html))

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_move_cursor_to_the_right_by_one_character(self, el_id):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content
        target_content = target_content[:1] + content_to_add + target_content[
            1:]

        # Get first caret (x, y) at position 1 and 2.
        el.tap()
        sel.move_cursor_to_front()
        cursor0_x, cursor0_y = sel.cursor_location()
        first_caret0_x, first_caret0_y = sel.first_caret_location()
        sel.move_cursor_by_offset(1)
        first_caret1_x, first_caret1_y = sel.first_caret_location()

        # Tap the front of the input to make first caret appear.
        el.tap(cursor0_x, cursor0_y)

        # Move first caret.
        self.actions.flick(el, first_caret0_x, first_caret0_y, first_caret1_x,
                           first_caret1_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertEqual(target_content, sel.content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_move_cursor_to_end_by_dragging_caret_to_bottom_right_corner(
            self, el_id):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content + content_to_add

        # Tap the front of the input to make first caret appear.
        el.tap()
        sel.move_cursor_to_front()
        el.tap(*sel.cursor_location())

        # Move first caret to the bottom-right corner of the element.
        src_x, src_y = sel.first_caret_location()
        dest_x, dest_y = el.size['width'], el.size['height']
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertEqual(target_content, sel.content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_move_cursor_to_front_by_dragging_caret_to_front(self, el_id):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = content_to_add + sel.content

        # Get first caret location at the front.
        el.tap()
        sel.move_cursor_to_front()
        dest_x, dest_y = sel.first_caret_location()

        # Tap to make first caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_cursor_to_end()
        sel.move_cursor_by_offset(1, backward=True)
        el.tap(*sel.cursor_location())
        src_x, src_y = sel.first_caret_location()

        # Move first caret to the front of the input box.
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertEqual(target_content, sel.content)

    def test_caret_not_appear_when_typing_in_scrollable_content(self):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, self._input_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content + string.ascii_letters

        el.tap()
        sel.move_cursor_to_end()

        # Insert a long string to the end of the <input>, which triggers
        # ScrollPositionChanged event.
        el.send_keys(string.ascii_letters)

        # The caret should not be visible. If it does appear wrongly due to the
        # ScrollPositionChanged event, we can drag it to the front of the
        # <input> to change the cursor position.
        src_x, src_y = sel.first_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        # The content should not be inserted at the front of the <input>.
        el.send_keys(content_to_add)

        self.assertNotEqual(non_target_content, sel.content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_input_padding_id, el_id=_input_padding_id)
    @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_caret_not_jump_when_dragging_to_editable_content_boundary(
            self, el_id):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = sel.content + content_to_add

        # Goal: the cursor position is not changed after dragging the caret down
        # on the Y-axis.
        el.tap()
        sel.move_cursor_to_front()
        el.tap(*sel.cursor_location())
        x, y = sel.first_caret_location()

        # Drag the caret down by 50px, and insert '!'.
        self.actions.flick(el, x, y, x, y + 50).perform()
        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertNotEqual(non_target_content, sel.content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_input_padding_id, el_id=_input_padding_id)
    @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_caret_not_jump_to_front_when_dragging_up_to_editable_content_boundary(
            self, el_id):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Goal: the cursor position is not changed after dragging the caret down
        # on the Y-axis.
        el.tap()
        sel.move_cursor_to_end()
        sel.move_cursor_by_offset(1, backward=True)
        el.tap(*sel.cursor_location())
        x, y = sel.first_caret_location()

        # Drag the caret up by 50px, and insert '!'.
        self.actions.flick(el, x, y, x, y - 50).perform()
        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertNotEqual(non_target_content, sel.content)

    def test_drag_caret_from_front_to_end_across_columns(self):
        self.open_test_html('test_carets_columns.html')
        el = self.marionette.find_element(By.ID, 'columns-inner')
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content + content_to_add

        # Goal: the cursor position can be changed by dragging the caret from
        # the front to the end of the content.

        # Tap to make the cursor appear.
        before_image_1 = self.marionette.find_element(By.ID, 'before-image-1')
        before_image_1.tap()

        # Tap the front of the content to make first caret appear.
        sel.move_cursor_to_front()
        el.tap(*sel.cursor_location())
        src_x, src_y = sel.first_caret_location()
        dest_x, dest_y = el.size['width'], el.size['height']

        # Drag the first caret to the bottom-right corner of the element.
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertEqual(target_content, sel.content)

    def test_move_cursor_to_front_by_dragging_caret_to_front_br_element(self):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, self._contenteditable_id)
        sel = SelectionManager(el)
        content_to_add_1 = '!'
        content_to_add_2 = '\n\n'
        target_content = content_to_add_1 + content_to_add_2 + sel.content

        # Goal: the cursor position can be changed by dragging the caret from
        # the end of the content to the front br element. Because we cannot get
        # caret location if it's on a br element, we need to get the first caret
        # location then adding the new lines.

        # Get first caret location at the front.
        el.tap()
        sel.move_cursor_to_front()
        dest_x, dest_y = sel.first_caret_location()

        # Append new line to the front of the content.
        el.send_keys(content_to_add_2)

        # Tap to make first caret appear.
        el.tap()
        sel.move_cursor_to_end()
        sel.move_cursor_by_offset(1, backward=True)
        el.tap(*sel.cursor_location())
        src_x, src_y = sel.first_caret_location()

        # Move first caret to the front of the input box.
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add_1).key_up(
            content_to_add_1).perform()
        self.assertEqual(target_content, sel.content)
class SelectionCaretsTest(MarionetteTestCase):
    _long_press_time = 1  # 1 second
    _input_selector = (By.ID, "input")
    _textarea_selector = (By.ID, "textarea")
    _textarea_rtl_selector = (By.ID, "textarea_rtl")
    _contenteditable_selector = (By.ID, "contenteditable")
    _content_selector = (By.ID, "content")
    _textarea2_selector = (By.ID, "textarea2")
    _contenteditable2_selector = (By.ID, "contenteditable2")
    _content2_selector = (By.ID, "content2")

    def setUp(self):
        # Code to execute before a tests are run.
        MarionetteTestCase.setUp(self)
        self.actions = Actions(self.marionette)

    def openTestHtml(self, enabled=True):
        """Open html for testing and locate elements, and enable/disable touch
        caret."""
        self.marionette.execute_script(
            'SpecialPowers.setBoolPref("selectioncaret.enabled", %s);' % ("true" if enabled else "false")
        )

        test_html = self.marionette.absolute_url("test_selectioncarets.html")
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(*self._input_selector)
        self._textarea = self.marionette.find_element(*self._textarea_selector)
        self._textarea_rtl = self.marionette.find_element(*self._textarea_rtl_selector)
        self._contenteditable = self.marionette.find_element(*self._contenteditable_selector)
        self._content = self.marionette.find_element(*self._content_selector)

    def openTestHtml2(self, enabled=True):
        """Open html for testing and locate elements, and enable/disable touch
        caret."""
        self.marionette.execute_script(
            'SpecialPowers.setBoolPref("selectioncaret.enabled", %s);' % ("true" if enabled else "false")
        )

        test_html2 = self.marionette.absolute_url("test_selectioncarets_multipleline.html")
        self.marionette.navigate(test_html2)

        self._textarea2 = self.marionette.find_element(*self._textarea2_selector)
        self._contenteditable2 = self.marionette.find_element(*self._contenteditable2_selector)
        self._content2 = self.marionette.find_element(*self._content2_selector)

    def _first_word_location(self, el):
        """Get the location (x, y) of the first word in el.

        Note: this function has a side effect which changes focus to the
        target element el.

        """
        sel = SelectionManager(el)

        # Move caret behind the first character to get the location of the first
        # word.
        el.tap()
        sel.move_caret_to_front()
        sel.move_caret_by_offset(1)

        return sel.caret_location()

    def _long_press_to_select(self, el, x, y):
        """Long press the location (x, y) to select a word.

        SelectionCarets should appear. On Windows, those spaces after the
        word will also be selected.

        """
        long_press_without_contextmenu(self.marionette, el, self._long_press_time, x, y)

    def _test_long_press_to_select_a_word(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 2, "Expect at least two words in the content.")
        target_content = words[0]

        # Goal: Select the first word.
        x, y = self._first_word_location(el)
        self._long_press_to_select(el, x, y)

        # Ignore extra spaces selected after the word.
        assertFunc(target_content, sel.selected_content.rstrip())

    def _test_move_selection_carets(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")

        # Goal: Select all text after the first word.
        target_content = original_content[len(words[0]) :]

        # Get the location of the selection carets at the end of the content for
        # later use.
        el.tap()
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        x, y = self._first_word_location(el)
        self._long_press_to_select(el, x, y)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Move the left caret to the previous position of the right caret.
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        # Ignore extra spaces at the beginning of the content in comparison.
        assertFunc(target_content.lstrip(), sel.selected_content.lstrip())

    def _test_minimum_select_one_character(self, el, assertFunc, x=None, y=None):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")

        # Get the location of the selection carets at the end of the content for
        # later use.
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()
        el.tap()

        # Goal: Select the first character.
        target_content = original_content[0]

        if x and y:
            # If we got x and y from the arguments, use it as a hint of the
            # location of the first word
            pass
        else:
            x, y = self._first_word_location(el)
        self._long_press_to_select(el, x, y)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Move the right caret to the position of the left caret.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    def _test_focus_obtained_by_long_press(self, el1, el2):
        """Test the focus could be changed from el1 to el2 by long press.

        If the focus is changed to e2 successfully, SelectionCarets should
        appear and could be dragged.

        """
        # Goal: Tap to focus el1, and then select the first character on
        # el2.

        # We want to collect the location of the first word in el2 here
        # since self._first_word_location() has the side effect which would
        # change the focus.
        x, y = self._first_word_location(el2)
        el1.tap()
        self._test_minimum_select_one_character(el2, self.assertEqual, x=x, y=y)

    def _test_handle_tilt_when_carets_overlap_to_each_other(self, el, assertFunc):
        """Test tilt handling when carets overlap to each other.

        Let SelectionCarets overlap to each other. If SelectionCarets are set
        to tilted successfully, tapping the tilted carets should not cause the
        selection to be collapsed and the carets should be draggable.
        """

        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")

        # Goal: Select the first word.
        x, y = self._first_word_location(el)
        self._long_press_to_select(el, x, y)
        target_content = sel.selected_content

        # Move the left caret to the position of the right caret to trigger
        # carets overlapping.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        # We make two hit tests targeting the left edge of the left tilted caret
        # and the right edge of the right tilted caret. If either of the hits is
        # missed, selection would be collapsed and both carets should not be
        # draggable.
        (caret3_x, caret3_y), (caret4_x, caret4_y) = sel.selection_carets_location()

        # The following values are from ua.css.
        caret_width = 44
        caret_margin_left = -23
        tilt_right_margin_left = 18
        tilt_left_margin_left = -17

        left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
        el.tap(ceil(left_caret_left_edge_x), caret3_y)

        right_caret_right_edge_x = caret4_x + caret_margin_left + tilt_right_margin_left + caret_width
        el.tap(floor(right_caret_right_edge_x), caret4_y)

        # Drag the left caret back to the initial selection, the first word.
        self.actions.flick(el, caret3_x, caret3_y, caret1_x, caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    ########################################################################
    # <input> test cases with selection carets enabled
    ########################################################################
    def test_input_long_press_to_select_a_word(self):
        self.openTestHtml(enabled=True)
        self._test_long_press_to_select_a_word(self._input, self.assertEqual)

    def test_input_move_selection_carets(self):
        self.openTestHtml(enabled=True)
        self._test_move_selection_carets(self._input, self.assertEqual)

    def test_input_minimum_select_one_character(self):
        self.openTestHtml(enabled=True)
        self._test_minimum_select_one_character(self._input, self.assertEqual)

    def test_input_focus_obtained_by_long_press_from_textarea(self):
        self.openTestHtml(enabled=True)
        self._test_focus_obtained_by_long_press(self._textarea, self._input)

    def test_input_focus_obtained_by_long_press_from_contenteditable(self):
        self.openTestHtml(enabled=True)
        self._test_focus_obtained_by_long_press(self._contenteditable, self._input)

    def test_input_focus_obtained_by_long_press_from_content_non_editable(self):
        self.openTestHtml(enabled=True)
        self._test_focus_obtained_by_long_press(self._content, self._input)

    def test_input_handle_tilt_when_carets_overlap_to_each_other(self):
        self.openTestHtml(enabled=True)
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._input, self.assertEqual)

    ########################################################################
    # <input> test cases with selection carets disabled
    ########################################################################
    def test_input_long_press_to_select_a_word_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_long_press_to_select_a_word(self._input, self.assertNotEqual)

    def test_input_move_selection_carets_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_selection_carets(self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with selection carets enabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word(self):
        self.openTestHtml(enabled=True)
        self._test_long_press_to_select_a_word(self._textarea, self.assertEqual)

    def test_textarea_move_selection_carets(self):
        self.openTestHtml(enabled=True)
        self._test_move_selection_carets(self._textarea, self.assertEqual)

    def test_textarea_minimum_select_one_character(self):
        self.openTestHtml(enabled=True)
        self._test_minimum_select_one_character(self._textarea, self.assertEqual)

    def test_textarea_focus_obtained_by_long_press_from_input(self):
        self.openTestHtml(enabled=True)
        self._test_focus_obtained_by_long_press(self._input, self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_contenteditable(self):
        self.openTestHtml(enabled=True)
        self._test_focus_obtained_by_long_press(self._contenteditable, self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_content_non_editable(self):
        self.openTestHtml(enabled=True)
        self._test_focus_obtained_by_long_press(self._content, self._textarea)

    def test_textarea_handle_tilt_when_carets_overlap_to_each_other(self):
        self.openTestHtml(enabled=True)
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._textarea, self.assertEqual)

    ########################################################################
    # <textarea> test cases with selection carets disabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_long_press_to_select_a_word(self._textarea, self.assertNotEqual)

    def test_textarea_move_selection_carets_disable(self):
        self.openTestHtml(enabled=False)
        self._test_move_selection_carets(self._textarea, self.assertNotEqual)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets enabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word(self):
        self.openTestHtml(enabled=True)
        self._test_long_press_to_select_a_word(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_move_selection_carets(self):
        self.openTestHtml(enabled=True)
        self._test_move_selection_carets(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_minimum_select_one_character(self):
        self.openTestHtml(enabled=True)
        self._test_minimum_select_one_character(self._textarea_rtl, self.assertEqual)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets disabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_long_press_to_select_a_word(self._textarea_rtl, self.assertNotEqual)

    def test_textarea_rtl_move_selection_carets_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_selection_carets(self._textarea_rtl, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with selection carets enabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word(self):
        self.openTestHtml(enabled=True)
        self._test_long_press_to_select_a_word(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_selection_carets(self):
        self.openTestHtml(enabled=True)
        self._test_move_selection_carets(self._contenteditable, self.assertEqual)

    def test_contenteditable_minimum_select_one_character(self):
        self.openTestHtml(enabled=True)
        self._test_minimum_select_one_character(self._contenteditable, self.assertEqual)

    def test_contenteditable_focus_obtained_by_long_press_from_input(self):
        self.openTestHtml(enabled=True)
        self._test_focus_obtained_by_long_press(self._input, self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_textarea(self):
        self.openTestHtml(enabled=True)
        self._test_focus_obtained_by_long_press(self._textarea, self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_content_non_editable(self):
        self.openTestHtml(enabled=True)
        self._test_focus_obtained_by_long_press(self._content, self._contenteditable)

    def test_contenteditable_handle_tilt_when_carets_overlap_to_each_other(self):
        self.openTestHtml(enabled=True)
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._contenteditable, self.assertEqual)

    ########################################################################
    # <div> contenteditable test cases with selection carets disabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_long_press_to_select_a_word(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_move_selection_carets_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_selection_carets(self._contenteditable, self.assertNotEqual)

    ########################################################################
    # <div> non-editable test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable_long_press_to_select_a_word(self):
        self.openTestHtml(enabled=True)
        self._test_long_press_to_select_a_word(self._content, self.assertEqual)

    def test_content_non_editable_move_selection_carets(self):
        self.openTestHtml(enabled=True)
        self._test_move_selection_carets(self._content, self.assertEqual)

    def test_content_non_editable_minimum_select_one_character_by_selection(self):
        self.openTestHtml(enabled=True)
        self._test_minimum_select_one_character(self._content, self.assertEqual)

    def test_content_non_editable_focus_obtained_by_long_press_from_input(self):
        self.openTestHtml(enabled=True)
        self._test_focus_obtained_by_long_press(self._input, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_textarea(self):
        self.openTestHtml(enabled=True)
        self._test_focus_obtained_by_long_press(self._textarea, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_contenteditable(self):
        self.openTestHtml(enabled=True)
        self._test_focus_obtained_by_long_press(self._contenteditable, self._content)

    def test_content_non_editable_handle_tilt_when_carets_overlap_to_each_other(self):
        self.openTestHtml(enabled=True)
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._content, self.assertEqual)

    ########################################################################
    # <textarea> (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_textarea2_minimum_select_one_character(self):
        self.openTestHtml2(enabled=True)
        self._test_minimum_select_one_character(self._textarea2, self.assertEqual)

    ########################################################################
    # <div> contenteditable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_contenteditable2_minimum_select_one_character(self):
        self.openTestHtml2(enabled=True)
        self._test_minimum_select_one_character(self._contenteditable2, self.assertEqual)

    ########################################################################
    # <div> non-editable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable2_minimum_select_one_character(self):
        self.openTestHtml2(enabled=True)
        self._test_minimum_select_one_character(self._content2, self.assertEqual)
Esempio n. 12
0
class Loop(object):
    """Object representing the Loop application.
    """
    def __init__(self, parent):
        self.apps = parent.apps
        self.data_layer = parent.data_layer
        self.parent = parent
        self.marionette = parent.marionette
        self.UTILS = parent.UTILS
        self.actions = Actions(self.marionette)
        self.browser = Browser(self.parent)
        self.app_name = "Firefox Hello"
        self.market_url = "https://owd.tid.es/B3lg1r89n/market/appList.html"
        self.persistent_directory = "/data/local/storage/persistent"
        self.loop_dir = self.UTILS.general.get_config_variable(
            "install_dir", "loop")

    def launch(self):
        """
        Launch the app.
        """
        self.app = self.apps.launch(self.app_name)
        self.UTILS.element.waitForNotElements(
            DOM.GLOBAL.loading_overlay,
            self.__class__.__name__ + " app - loading overlay")
        return self.app

    def is_installed(self):
        return self.apps.is_app_installed(self.app_name)

    def install(self):
        via = self.UTILS.general.get_config_variable("install_via", "loop")
        if via == "Grunt":
            self.install_via_grunt()
        elif via == "Market":
            self.install_via_marketplace()
        else:
            self.UTILS.test.test(False, "Not valid way to install Loop")

    def install_via_grunt(self, version="1.1"):
        self.UTILS.reporting.logResult('info', 'Installing via grunt....')
        c1 = 'cd {}'.format(self.loop_dir)
        c2 = 'git checkout {}'.format(version)
        c3 = 'git fetch && git merge origin/{}'.format(version)
        c4 = 'grunt build'

        with open(os.devnull, 'w') as DEVNULL:
            result = subprocess32.check_output("; ".join([c1, c2, c3, c4]),
                                               shell=True,
                                               stderr=DEVNULL)

        self.marionette.switch_to_frame()
        msg = "{} installed".format(self.app_name)
        installed_app_msg = (DOM.GLOBAL.system_banner_msg[0],
                             DOM.GLOBAL.system_banner_msg[1].format(msg))
        self.UTILS.element.waitForElements(installed_app_msg,
                                           "App installed",
                                           timeout=30)

        install_ok_msg = "Done, without errors."
        self.UTILS.reporting.logResult(
            'info', "Result of this test script: {}".format(result))
        self.UTILS.test.test(install_ok_msg in result,
                             "Install via grunt is OK")

    def install_via_marketplace(self):
        self.UTILS.reporting.logResult('info',
                                       'Installing via marketplace....')
        # Make sure we install the latest version
        self.update_and_publish()

        self.browser.launch()
        time.sleep(1)
        self.browser.open_url(self.market_url)

        loop_link = self.UTILS.element.getElement(
            ('xpath', '//p[contains(text(), "{}")]'.format(self.app_name)),
            "App link")
        loop_link.tap()

        self.marionette.switch_to_frame()
        install_ok = self.UTILS.element.getElement(DOM.GLOBAL.app_install_ok,
                                                   "Install button")
        install_ok.tap()

        msg = "{} installed".format(self.app_name)
        installed_app_msg = (DOM.GLOBAL.system_banner_msg[0],
                             DOM.GLOBAL.system_banner_msg[1].format(msg))
        self.UTILS.element.waitForElements(installed_app_msg,
                                           "App installed",
                                           timeout=30)

    def reinstall(self):
        self.uninstall()
        time.sleep(2)
        self.install()

    def uninstall(self):
        self.UTILS.reporting.logResult('info', "uninstalling.........")
        self.apps.uninstall(self.app_name)
        self.parent.wait_for_condition(
            lambda m: not self.apps.is_app_installed(self.app_name),
            timeout=20,
            message="{} is not installed".format(self.app_name))

    def update_and_publish(self):
        self.publish_loop_dir = self.UTILS.general.get_config_variable(
            "aux_files", "loop")

        c1 = 'cd {}'.fomrat(self.publish_loop_dir)
        c2 = './publish_app.sh {}'.format(self.loop_dir)
        with open(os.devnull, 'w') as DEVNULL:
            result = subprocess32.check_output("; ".join([c1, c2]),
                                               shell=True,
                                               stderr=DEVNULL)

        chops = result.split("\n")
        self.UTILS.reporting.logResult('info', "result: {}".format(chops))
        self.UTILS.test.test("And all done, hopefully." in chops,
                             "The script to publish an app is OK", True)

    def _fill_fxa_field(self, field_locator, text):
        """ Auxiliary method to fill "Firefox account login" fields
        """
        self.UTILS.reporting.logResult(
            'info',
            '[firefox_login] Filling fxa field with text: {}'.format(text))
        self.parent.wait_for_element_displayed(*field_locator)
        fxa_input = self.marionette.find_element(*field_locator)
        fxa_input.send_keys(text)
        time.sleep(2)

        self.parent.wait_for_condition(
            lambda m: m.find_element(*DOM.Loop.ffox_account_login_next
                                     ).get_attribute("disabled") != "disabled")
        next_btn = self.marionette.find_element(
            *DOM.Loop.ffox_account_login_next)
        next_btn.tap()
        self.parent.wait_for_element_not_displayed(
            *DOM.Loop.ffox_account_login_overlay)

    def tap_on_firefox_login_button(self):
        ffox_btn = self.marionette.find_element(
            *DOM.Loop.wizard_login_ffox_account)
        self.UTILS.element.simulateClick(ffox_btn)

    def _tap_on_phone_login_button(self):
        phone_btn = self.marionette.find_element(
            *DOM.Loop.wizard_login_phone_number)
        self.UTILS.element.simulateClick(phone_btn)

    def _get_mobile_id_options(self):
        return self.marionette.find_elements(*DOM.Loop.mobile_id_sim_list_item)

    def wizard_or_login(self):
        """ Checks if we have to skip the Wizard, log in, or if we're already at the main screen of Loop

            For the first two scenarios, it returns True.
            If we are already inside Loop, it returns False.
        """
        # TODO: switch try-except code -> first check login instead of wizard
        #      see if it works, when the wizard is first
        try:
            self.parent.wait_for_element_displayed(*DOM.Loop.wizard_header)
            self.UTILS.reporting.logResult('info', '[wizard_or_login] Wizard')
            self.skip_wizard()
            return True
        except:
            self.UTILS.reporting.logResult('info', '[wizard_or_login] Login')
            try:
                self.parent.wait_for_element_displayed(*DOM.Loop.wizard_login)
                return True
            except:
                self.UTILS.reporting.logResult('info',
                                               '[wizard_or_login] Loop')
                try:
                    self.parent.wait_for_element_displayed(
                        *DOM.Loop.app_header)
                    return False
                except:
                    self.UTILS.test.test(False, "Ooops. Something went wrong",
                                         True)

    def get_wizard_steps(self):
        """ Returns the number of steps of the wizard
        """
        return len(
            self.marionette.find_elements(*DOM.Loop.wizard_slideshow_step))

    def skip_wizard(self):
        """ Skips first time use wizard by flicking the screen
        """
        time.sleep(1)
        wizard_steps = self.get_wizard_steps()

        current_frame = self.apps.displayed_app.frame
        x_start = current_frame.size['width'] // 2
        x_end = x_start // 4
        y_start = current_frame.size['height'] // 2

        for i in range(wizard_steps):
            self.actions.flick(current_frame,
                               x_start,
                               y_start,
                               x_end,
                               y_start,
                               duration=600).perform()
            time.sleep(1)

        self.marionette.switch_to_frame(self.apps.displayed_app.frame_id)
        self.parent.wait_for_element_displayed(DOM.Loop.wizard_login[0],
                                               DOM.Loop.wizard_login[1],
                                               timeout=10)

    def firefox_login(self, email, password, is_wrong=False):
        """ Logs in using Firefox account
        """
        self.tap_on_firefox_login_button()

        self.UTILS.iframe.switchToFrame(*DOM.Loop.ffox_account_frame_locator)
        self.parent.wait_for_element_displayed(
            DOM.Loop.ffox_account_login_title[0],
            DOM.Loop.ffox_account_login_title[1],
            timeout=20)

        self._fill_fxa_field(DOM.Loop.ffox_account_login_mail, email)
        self._fill_fxa_field(DOM.Loop.ffox_account_login_pass, password)

        if not is_wrong:
            done_btn = self.marionette.find_element(
                *DOM.Loop.ffox_account_login_done)
            done_btn.tap()

    def phone_login_auto(self, phone_number, option_number=1):
        """Wrapper to log in using phone number, either the already selected or entering it manually"""
        try:
            self.UTILS.reporting.info("Trying phone login using Mobile ID")
            self.phone_login()
        except:
            self.UTILS.reporting.info(
                "Mobile ID login failed, falling back to manual")
            self.UTILS.iframe.switchToFrame(*DOM.Loop.mobile_id_frame_locator)
            self.marionette.find_element('id', 'header').tap(25, 25)
            self.UTILS.iframe.switchToFrame(*DOM.Loop.frame_locator)
            self.phone_login_manually(phone_number)

    @retry(3,
           context=("OWDTestToolkit.apps.loop", "Loop"),
           aux_func_name="retry_phone_login")
    def phone_login(self, option_number=1):
        """ Logs in using mobile id
        """
        self._tap_on_phone_login_button()
        self.UTILS.iframe.switchToFrame(*DOM.Loop.mobile_id_frame_locator)
        self.parent.wait_for_element_not_displayed(
            *DOM.Loop.ffox_account_login_overlay)

        mobile_id_header = ("xpath",
                            DOM.GLOBAL.app_head_specific.format(
                                _("Mobile ID")))
        self.parent.wait_for_element_displayed(*mobile_id_header)

        options = self._get_mobile_id_options()
        if len(options) > 1:
            # Option number refers to the SIM number (1 or 2), not to the position in the array
            options[option_number - 1].tap()

        allow_button = self.marionette.find_element(
            *DOM.Loop.mobile_id_allow_button)
        allow_button.tap()

        try:
            self.parent.wait_for_element_displayed(
                DOM.Loop.mobile_id_verified_button[0],
                DOM.Loop.mobile_id_verified_button[1],
                timeout=30)
            verified_button = self.marionette.find_element(
                *DOM.Loop.mobile_id_verified_button)
            self.UTILS.element.simulateClick(verified_button)
        except:
            self.parent.wait_for_condition(
                lambda m: "state-sending" in m.find_element(
                    *DOM.Loop.mobile_id_allow_button).get_attribute("class"),
                timeout=5,
                message="Button is still sending")
            # Make @retry do its work
            raise

        self.apps.switch_to_displayed_app()

    @retry(3,
           context=("OWDTestToolkit.apps.loop", "Loop"),
           aux_func_name="retry_phone_login")
    def phone_login_manually(self, phone_number_without_prefix):
        """
        Logs in using mobile id, but instead of using the automatically provided by the app
        selecting the manual option
        @phone_number_without_prefix str Phone number to register into Loop
        NOTE: for the shake of simplicity, we assume the prefix is the spanish one (+34) by default
        """

        self._tap_on_phone_login_button()
        self.UTILS.iframe.switchToFrame(*DOM.Loop.mobile_id_frame_locator)
        self.parent.wait_for_element_not_displayed(
            *DOM.Loop.ffox_account_login_overlay)

        mobile_id_header = ("xpath",
                            DOM.GLOBAL.app_head_specific.format(
                                _("Mobile ID")))
        self.parent.wait_for_element_displayed(*mobile_id_header)

        # Manually!
        manually_link = self.marionette.find_element(
            *DOM.Loop.mobile_id_add_phone_number)
        manually_link.tap()

        self.parent.wait_for_element_displayed(
            *DOM.Loop.mobile_id_add_phone_number_number)
        phone_input = self.marionette.find_element(
            *DOM.Loop.mobile_id_add_phone_number_number)
        phone_input.send_keys(phone_number_without_prefix)

        # NOTE: before you virtually kill me, I cannot take this duplicated code into another
        # separated method due to the @reply decorator. Just letting you know :).
        allow_button = self.marionette.find_element(
            *DOM.Loop.mobile_id_allow_button)
        allow_button.tap()

        try:
            self.parent.wait_for_element_displayed(
                DOM.Loop.mobile_id_verified_button[0],
                DOM.Loop.mobile_id_verified_button[1],
                timeout=30)
            verified_button = self.marionette.find_element(
                *DOM.Loop.mobile_id_verified_button)
            self.UTILS.element.simulateClick(verified_button)
        except:
            self.parent.wait_for_condition(
                lambda m: "state-sending" in m.find_element(
                    *DOM.Loop.mobile_id_allow_button).get_attribute("class"),
                timeout=5,
                message="Button is still sending")
            # Make @retry do its work
            raise

        self.apps.switch_to_displayed_app()

    @retry(5,
           context=("OWDTestToolkit.apps.loop", "Loop"),
           aux_func_name="retry_ffox_login")
    def allow_permission_ffox_login(self):
        """ Allows Loop to read our contacts

        This method checks whether is necessary to allow extra permissions for loop or not ater
        loggin in with Firefox accounts

        Also, since this is is the last step before connecting to the Loop server, it checks
        that no error has been raised. If that happens, it retries the connection up to 5 times.
        """
        self.marionette.switch_to_frame()
        try:
            self.UTILS.reporting.debug("Looking for permission panel....")
            self.parent.wait_for_element_displayed(
                DOM.GLOBAL.app_permission_dialog[0],
                DOM.GLOBAL.app_permission_dialog[1],
                timeout=10)
        except:
            try:
                self.UTILS.reporting.debug(
                    "Now looking for permission Loop main view....")
                self.apps.switch_to_displayed_app()
                self.parent.wait_for_element_displayed(*DOM.Loop.app_header)
                return
            except:
                self.UTILS.reporting.debug("And Now looking for error....")
                self.marionette.switch_to_frame()
                self.parent.wait_for_element_displayed(
                    *DOM.GLOBAL.modal_dialog_alert_title)
                self.UTILS.reporting.logResult('info', "Error connecting...")
                # Make @retry do its work
                raise

        msg_text = self.marionette.find_element(
            *DOM.GLOBAL.app_permission_msg).text
        self.UTILS.test.test(self.app_name in msg_text, "Permissions for loop")

        allow_btn = self.marionette.find_element(
            *DOM.GLOBAL.app_permission_btn_yes)
        self.UTILS.element.simulateClick(allow_btn)

        self.apps.switch_to_displayed_app()

    def allow_permission_phone_login(self):
        """ Allows Loop to read our contacts

        This method checks whether is necessary to allow extra permissions for loop or not ater
        loggin in with Mobile ID

        Also, since this is is the last step before connecting to the Loop server, it checks
        that no error has been raised. If that happens, it retries the connection up to 5 times.
        """
        self.marionette.switch_to_frame()
        try:
            self.UTILS.reporting.debug("Looking for permission panel....")
            self.parent.wait_for_element_displayed(
                DOM.GLOBAL.app_permission_dialog[0],
                DOM.GLOBAL.app_permission_dialog[1],
                timeout=10)
        except:
            self.UTILS.reporting.debug(
                "Now looking for permission Loop main view....")
            self.apps.switch_to_displayed_app()
            self.parent.wait_for_element_displayed(*DOM.Loop.app_header)
            return

        msg_text = self.marionette.find_element(
            *DOM.GLOBAL.app_permission_msg).text
        self.UTILS.test.test(self.app_name in msg_text, "Permissions for loop")

        allow_btn = self.marionette.find_element(
            *DOM.GLOBAL.app_permission_btn_yes)
        self.UTILS.element.simulateClick(allow_btn)

        self.apps.switch_to_displayed_app()

    def retry_ffox_login(self):
        """ Retry Ffox account login if it has failed.

        This method is called as the aux_func for our brand new retry decorator
        """

        self.UTILS.reporting.logResult('info', "Retrying FxA login...")
        self.apps.switch_to_displayed_app()
        time.sleep(2)

        self.parent.wait_for_element_displayed(*DOM.Loop.error_screen_ok)
        ok_btn = self.marionette.find_element(*DOM.Loop.error_screen_ok)
        self.UTILS.element.simulateClick(ok_btn)

        self.parent.wait_for_element_displayed(*DOM.Loop.wizard_login)
        self.tap_on_firefox_login_button()

    def retry_phone_login(self):
        """ Retry phone login if it has failed
        """
        self.UTILS.reporting.logResult('info', "Retrying phone login...")
        self.parent.wait_for_element_displayed(*DOM.Loop.mobile_id_back)
        back_btn = self.marionette.find_element(*DOM.Loop.mobile_id_back)
        self.UTILS.element.simulateClick(back_btn)

        self.apps.switch_to_displayed_app()
        time.sleep(2)
        self._tap_on_phone_login_button()

    def open_settings(self):
        """ Open settings panel from call log 
        """
        self.parent.wait_for_element_displayed(*DOM.Loop.open_settings_btn)
        settings_btn = self.marionette.find_element(
            *DOM.Loop.open_settings_btn)
        self.UTILS.element.simulateClick(settings_btn)
        self.parent.wait_for_element_displayed(*DOM.Loop.settings_panel_header)

    def logout(self, confirm=True):
        """ This methods logs us out from Loop.

        It assumes we already are in the Loop Settings panel
        """
        try:
            self.parent.wait_for_element_displayed(*DOM.Loop.settings_logout)
        except:
            self.UTILS.reporting.logResult('info', "Already logged out")
            return
        logout_btn = self.marionette.find_element(*DOM.Loop.settings_logout)
        self.UTILS.element.simulateClick(logout_btn)

        self.parent.wait_for_element_displayed(*DOM.Loop.form_confirm_logout)
        if confirm:
            confirm_btn = self.marionette.find_element(
                *DOM.Loop.form_confirm_logout)
            self.UTILS.element.simulateClick(confirm_btn)
            self.parent.wait_for_element_not_displayed(
                *DOM.Loop.loading_overlay)
            self.parent.wait_for_element_displayed(*DOM.Loop.wizard_login)
        else:
            cancel_btn = self.marionette.find_element(
                *DOM.Loop.form_confirm_cancel)
            self.parent.wait_for_element_displayed(
                *DOM.Loop.settings_panel_header)

    def switch_to_urls(self):
        self.parent.wait_for_element_displayed(
            *DOM.Loop.call_log_shared_links_tab)
        tab = self.marionette.find_element(*DOM.Loop.call_log_shared_links_tab)
        tab.tap()
        self.parent.wait_for_condition(
            lambda m: tab.get_attribute("aria-selected") == "true",
            timeout=10,
            message="Checking that 'Shared links' is selected")

    def _get_number_of_urls(self, locator):
        try:
            entries = self.marionette.find_elements(*locator)
            return len(entries)
        except:
            return 0

    def get_number_of_all_urls(self):
        return self._get_number_of_urls(DOM.Loop.shared_links_entry)

    def get_number_of_available_urls(self):
        return self._get_number_of_urls(DOM.Loop.shared_links_entry_available)

    def get_number_of_revoked_urls(self):
        return self._get_number_of_urls(DOM.Loop.shared_links_entry_revoked)

    def _select_action(self, action):
        elem = (DOM.Loop.form_action_action[0],
                DOM.Loop.form_action_action[1].format(action))
        self.parent.wait_for_element_displayed(*elem)
        self.marionette.find_element(*elem).tap()

    def _confirm(self, decission):
        elem = DOM.Loop.form_confirm_delete if decission else DOM.Loop.form_confirm_cancel
        self.parent.wait_for_element_displayed(*elem)
        self.marionette.find_element(*elem).tap()

    def _is_entry_revoked(self, entry):
        try:
            entry.find_element(*DOM.Loop.shared_links_entry_revoked_nested)
            return True
        except Exception, e:
            self.UTILS.reporting.logResult('info',
                                           "What happened here? {}".format(e))
            return False
class AccessibleCaretSelectionModeTestCase(MarionetteTestCase):
    '''Test cases for AccessibleCaret under selection mode.'''
    # Element IDs.
    _input_id = 'input'
    _input_padding_id = 'input-padding'
    _input_size_id = 'input-size'
    _textarea_id = 'textarea'
    _textarea2_id = 'textarea2'
    _textarea_one_line_id = 'textarea-one-line'
    _textarea_rtl_id = 'textarea-rtl'
    _contenteditable_id = 'contenteditable'
    _contenteditable2_id = 'contenteditable2'
    _content_id = 'content'
    _content2_id = 'content2'
    _non_selectable_id = 'non-selectable'

    # Test html files.
    _selection_html = 'test_carets_selection.html'
    _multipleline_html = 'test_carets_multipleline.html'
    _multiplerange_html = 'test_carets_multiplerange.html'
    _longtext_html = 'test_carets_longtext.html'
    _iframe_html = 'test_carets_iframe.html'
    _display_none_html = 'test_carets_display_none.html'

    def setUp(self):
        # Code to execute before every test is running.
        super(AccessibleCaretSelectionModeTestCase, self).setUp()
        self.carets_tested_pref = 'layout.accessiblecaret.enabled'
        self.prefs = {
            'layout.word_select.eat_space_to_next_word': False,
            self.carets_tested_pref: True,
        }
        self.marionette.set_prefs(self.prefs)
        self.actions = Actions(self.marionette)

    def open_test_html(self, test_html):
        self.marionette.navigate(self.marionette.absolute_url(test_html))

    def word_offset(self, text, ordinal):
        'Get the character offset of the ordinal-th word in text.'
        tokens = re.split(r'(\S+)', text)         # both words and spaces
        spaces = tokens[0::2]                     # collect spaces at odd indices
        words = tokens[1::2]                      # collect word at even indices

        if ordinal >= len(words):
            raise IndexError('Only %d words in text, but got ordinal %d' %
                             (len(words), ordinal))

        # Cursor position of the targeting word is behind the the first
        # character in the word. For example, offset to 'def' in 'abc def' is
        # between 'd' and 'e'.
        offset = len(spaces[0]) + 1
        offset += sum(len(words[i]) + len(spaces[i + 1]) for i in range(ordinal))
        return offset

    def test_word_offset(self):
        text = ' ' * 3 + 'abc' + ' ' * 3 + 'def'

        self.assertTrue(self.word_offset(text, 0), 4)
        self.assertTrue(self.word_offset(text, 1), 10)
        with self.assertRaises(IndexError):
            self.word_offset(text, 2)

    def word_location(self, el, ordinal):
        '''Get the location (x, y) of the ordinal-th word in el.

        The ordinal starts from 0.

        Note: this function has a side effect which changes focus to the
        target element el.

        '''
        sel = SelectionManager(el)
        offset = self.word_offset(sel.content, ordinal)

        # Move the blinking cursor to the word.
        el.tap()
        sel.move_cursor_to_front()
        sel.move_cursor_by_offset(offset)
        x, y = sel.cursor_location()

        return x, y

    def rect_relative_to_window(self, el):
        '''Get element's bounding rectangle.

        This function is similar to el.rect, but the coordinate is relative to
        the top left corner of the window instead of the document.

        '''
        return self.marionette.execute_script('''
            let rect = arguments[0].getBoundingClientRect();
            return {x: rect.x, y:rect.y, width: rect.width, height: rect.height};
            ''', script_args=[el])

    def long_press_on_location(self, el, x=None, y=None):
        '''Long press the location (x, y) to select a word.

        If no (x, y) are given, it will be targeted at the center of the
        element. On Windows, those spaces after the word will also be selected.
        This function sends synthesized eMouseLongTap to gecko.

        '''
        rect = self.rect_relative_to_window(el)
        target_x = rect['x'] + (x if x is not None else rect['width'] // 2)
        target_y = rect['y'] + (y if y is not None else rect['height'] // 2)

        self.marionette.execute_script('''
            let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
            utils.sendTouchEventToWindow('touchstart', [0],
                                         [arguments[0]], [arguments[1]],
                                         [1], [1], [0], [1], 1, 0);
            utils.sendMouseEventToWindow('mouselongtap', arguments[0], arguments[1],
                                          0, 1, 0);
            utils.sendTouchEventToWindow('touchend', [0],
                                         [arguments[0]], [arguments[1]],
                                         [1], [1], [0], [1], 1, 0);
            ''', script_args=[target_x, target_y], sandbox='system')

    def long_press_on_word(self, el, wordOrdinal):
        x, y = self.word_location(el, wordOrdinal)
        self.long_press_on_location(el, x, y)

    def to_unix_line_ending(self, s):
        """Changes all Windows/Mac line endings in s to UNIX line endings."""

        return s.replace('\r\n', '\n').replace('\r', '\n')

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    @parameterized(_content_id, el_id=_content_id)
    def test_long_press_to_select_a_word(self, el_id):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        self._test_long_press_to_select_a_word(el)

    def _test_long_press_to_select_a_word(self, el):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 2, 'Expect at least two words in the content.')
        target_content = words[0]

        # Goal: Select the first word.
        self.long_press_on_word(el, 0)

        # Ignore extra spaces selected after the word.
        self.assertEqual(target_content, sel.selected_content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    @parameterized(_content_id, el_id=_content_id)
    def test_drag_carets(self, el_id):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        # Goal: Select all text after the first word.
        target_content = original_content[len(words[0]):]

        # Get the location of the carets at the end of the content for later
        # use.
        el.tap()
        sel.select_all()
        end_caret_x, end_caret_y = sel.second_caret_location()

        self.long_press_on_word(el, 0)

        # Drag the second caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Drag the first caret to the previous position of the second caret.
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        self.assertEqual(target_content, sel.selected_content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    @parameterized(_content_id, el_id=_content_id)
    def test_drag_swappable_carets(self, el_id):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        target_content1 = words[0]
        target_content2 = original_content[len(words[0]):]

        # Get the location of the carets at the end of the content for later
        # use.
        el.tap()
        sel.select_all()
        end_caret_x, end_caret_y = sel.second_caret_location()

        self.long_press_on_word(el, 0)

        # Drag the first caret to the end and back to where it was
        # immediately. The selection range should not be collapsed.
        caret1_x, caret1_y = sel.first_caret_location()
        self.actions.flick(el, caret1_x, caret1_y, end_caret_x, end_caret_y)\
                    .flick(el, end_caret_x, end_caret_y, caret1_x, caret1_y).perform()
        self.assertEqual(target_content1, sel.selected_content)

        # Drag the first caret to the end.
        caret1_x, caret1_y = sel.first_caret_location()
        self.actions.flick(el, caret1_x, caret1_y, end_caret_x, end_caret_y).perform()
        self.assertEqual(target_content2, sel.selected_content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    @parameterized(_content_id, el_id=_content_id)
    def test_minimum_select_one_character(self, el_id):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        self._test_minimum_select_one_character(el)

    @parameterized(_textarea2_id, el_id=_textarea2_id)
    @parameterized(_contenteditable2_id, el_id=_contenteditable2_id)
    @parameterized(_content2_id, el_id=_content2_id)
    def test_minimum_select_one_character2(self, el_id):
        self.open_test_html(self._multipleline_html)
        el = self.marionette.find_element(By.ID, el_id)
        self._test_minimum_select_one_character(el)

    def _test_minimum_select_one_character(self, el):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        # Get the location of the carets at the end of the content for later
        # use.
        sel.select_all()
        end_caret_x, end_caret_y = sel.second_caret_location()
        el.tap()

        # Goal: Select the first character.
        target_content = original_content[0]

        self.long_press_on_word(el, 0)

        # Drag the second caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Drag the second caret to the position of the first caret.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()

        self.assertEqual(target_content, sel.selected_content)

    @parameterized(_input_id + '_to_' + _textarea_id,
                   el1_id=_input_id, el2_id=_textarea_id)
    @parameterized(_input_id + '_to_' + _contenteditable_id,
                   el1_id=_input_id, el2_id=_contenteditable_id)
    @parameterized(_input_id + '_to_' + _content_id,
                   el1_id=_input_id, el2_id=_content_id)
    @parameterized(_textarea_id + '_to_' + _input_id,
                   el1_id=_textarea_id, el2_id=_input_id)
    @parameterized(_textarea_id + '_to_' + _contenteditable_id,
                   el1_id=_textarea_id, el2_id=_contenteditable_id)
    @parameterized(_textarea_id + '_to_' + _content_id,
                   el1_id=_textarea_id, el2_id=_content_id)
    @parameterized(_contenteditable_id + '_to_' + _input_id,
                   el1_id=_contenteditable_id, el2_id=_input_id)
    @parameterized(_contenteditable_id + '_to_' + _textarea_id,
                   el1_id=_contenteditable_id, el2_id=_textarea_id)
    @parameterized(_contenteditable_id + '_to_' + _content_id,
                   el1_id=_contenteditable_id, el2_id=_content_id)
    @parameterized(_content_id + '_to_' + _input_id,
                   el1_id=_content_id, el2_id=_input_id)
    @parameterized(_content_id + '_to_' + _textarea_id,
                   el1_id=_content_id, el2_id=_textarea_id)
    @parameterized(_content_id + '_to_' + _contenteditable_id,
                   el1_id=_content_id, el2_id=_contenteditable_id)
    def test_long_press_changes_focus_from(self, el1_id, el2_id):
        self.open_test_html(self._selection_html)
        el1 = self.marionette.find_element(By.ID, el1_id)
        el2 = self.marionette.find_element(By.ID, el2_id)

        # Compute the content of the first word in el2.
        sel = SelectionManager(el2)
        original_content = sel.content
        words = original_content.split()
        target_content = words[0]

        # Goal: Tap to focus el1, and then select the first word on el2.

        # We want to collect the location of the first word in el2 here
        # since self.word_location() has the side effect which would
        # change the focus.
        x, y = self.word_location(el2, 0)

        el1.tap()
        self.long_press_on_location(el2, x, y)
        self.assertEqual(target_content, sel.selected_content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_focus_not_changed_by_long_press_on_non_selectable(self, el_id):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        non_selectable = self.marionette.find_element(By.ID, self._non_selectable_id)

        # Goal: Focus remains on the editable element el after long pressing on
        # the non-selectable element.
        sel = SelectionManager(el)
        self.long_press_on_word(el, 0)
        self.long_press_on_location(non_selectable)
        active_sel = SelectionManager(self.marionette.get_active_element())
        self.assertEqual(sel.content, active_sel.content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    @parameterized(_content_id, el_id=_content_id)
    def test_handle_tilt_when_carets_overlap_each_other(self, el_id):
        '''Test tilt handling when carets overlap to each other.

        Let the two carets overlap each other. If they are set to tilted
        successfully, tapping the tilted carets should not cause the selection
        to be collapsed and the carets should be draggable.

        '''
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        # Goal: Select the first word.
        self.long_press_on_word(el, 0)
        target_content = sel.selected_content

        # Drag the first caret to the position of the second caret to trigger
        # carets overlapping.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        # We make two hit tests targeting the left edge of the left tilted caret
        # and the right edge of the right tilted caret. If either of the hits is
        # missed, selection would be collapsed and both carets should not be
        # draggable.
        (caret3_x, caret3_y), (caret4_x, caret4_y) = sel.carets_location()

        # The following values are from ua.css and all.js
        caret_width = float(self.marionette.get_pref('layout.accessiblecaret.width'))
        caret_margin_left = float(self.marionette.get_pref('layout.accessiblecaret.margin-left'))
        tilt_right_margin_left = 0.41 * caret_width
        tilt_left_margin_left = -0.39 * caret_width

        left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
        el.tap(left_caret_left_edge_x + 2, caret3_y)

        right_caret_right_edge_x = (caret4_x + caret_margin_left +
                                    tilt_right_margin_left + caret_width)
        el.tap(right_caret_right_edge_x - 2, caret4_y)

        # Drag the first caret back to the initial selection, the first word.
        self.actions.flick(el, caret3_x, caret3_y, caret1_x, caret1_y).perform()

        self.assertEqual(target_content, sel.selected_content)

    def test_drag_caret_over_non_selectable_field(self):
        '''Test dragging the caret over a non-selectable field.

        The selected content should exclude non-selectable elements and the
        second caret should appear in last range's position.

        '''
        self.open_test_html(self._multiplerange_html)
        body = self.marionette.find_element(By.ID, 'bd')
        sel3 = self.marionette.find_element(By.ID, 'sel3')
        sel4 = self.marionette.find_element(By.ID, 'sel4')
        sel6 = self.marionette.find_element(By.ID, 'sel6')

        # Select target element and get target caret location
        self.long_press_on_word(sel4, 3)
        sel = SelectionManager(body)
        end_caret_x, end_caret_y = sel.second_caret_location()

        self.long_press_on_word(sel6, 0)
        end_caret2_x, end_caret2_y = sel.second_caret_location()

        # Select start element
        self.long_press_on_word(sel3, 3)

        # Drag end caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(body, caret2_x, caret2_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
                         'this 3\nuser can select this')

        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(body, caret2_x, caret2_y, end_caret2_x, end_caret2_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
                         'this 3\nuser can select this 4\nuser can select this 5\nuser')

        # Drag first caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(body, caret1_x, caret1_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
                         '4\nuser can select this 5\nuser')

    def test_drag_swappable_caret_over_non_selectable_field(self):
        self.open_test_html(self._multiplerange_html)
        body = self.marionette.find_element(By.ID, 'bd')
        sel3 = self.marionette.find_element(By.ID, 'sel3')
        sel4 = self.marionette.find_element(By.ID, 'sel4')
        sel = SelectionManager(body)

        self.long_press_on_word(sel4, 3)
        (end_caret1_x, end_caret1_y), (end_caret2_x, end_caret2_y) = sel.carets_location()

        self.long_press_on_word(sel3, 3)
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()

        # Drag the first caret down, which will across the second caret.
        self.actions.flick(body, caret1_x, caret1_y, end_caret1_x, end_caret1_y).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
                         '3\nuser can select')

        # The old second caret becomes the first caret. Drag it down again.
        self.actions.flick(body, caret2_x, caret2_y, end_caret2_x, end_caret2_y).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
                         'this')

    def test_drag_caret_to_beginning_of_a_line(self):
        '''Bug 1094056
        Test caret visibility when caret is dragged to beginning of a line
        '''
        self.open_test_html(self._multiplerange_html)
        body = self.marionette.find_element(By.ID, 'bd')
        sel1 = self.marionette.find_element(By.ID, 'sel1')
        sel2 = self.marionette.find_element(By.ID, 'sel2')

        # Select the first word in the second line
        self.long_press_on_word(sel2, 0)
        sel = SelectionManager(body)
        (start_caret_x, start_caret_y), (end_caret_x, end_caret_y) = sel.carets_location()

        # Select target word in the first line
        self.long_press_on_word(sel1, 2)

        # Drag end caret to the beginning of the second line
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(body, caret2_x, caret2_y, start_caret_x, start_caret_y).perform()

        # Drag end caret back to the target word
        self.actions.flick(body, start_caret_x, start_caret_y, caret2_x, caret2_y).perform()

        self.assertEqual(self.to_unix_line_ending(sel.selected_content), 'select')

    @skip_if_not_rotatable
    def test_caret_position_after_changing_orientation_of_device(self):
        '''Bug 1094072
        If positions of carets are updated correctly, they should be draggable.
        '''
        self.open_test_html(self._longtext_html)
        body = self.marionette.find_element(By.ID, 'bd')
        longtext = self.marionette.find_element(By.ID, 'longtext')

        # Select word in portrait mode, then change to landscape mode
        self.marionette.set_orientation('portrait')
        self.long_press_on_word(longtext, 12)
        sel = SelectionManager(body)
        (p_start_caret_x, p_start_caret_y), (p_end_caret_x, p_end_caret_y) = sel.carets_location()
        self.marionette.set_orientation('landscape')
        (l_start_caret_x, l_start_caret_y), (l_end_caret_x, l_end_caret_y) = sel.carets_location()

        # Drag end caret to the start caret to change the selected content
        self.actions.flick(body, l_end_caret_x, l_end_caret_y,
                           l_start_caret_x, l_start_caret_y).perform()

        # Change orientation back to portrait mode to prevent affecting
        # other tests
        self.marionette.set_orientation('portrait')

        self.assertEqual(self.to_unix_line_ending(sel.selected_content), 'o')

    def test_select_word_inside_an_iframe(self):
        '''Bug 1088552
        The scroll offset in iframe should be taken into consideration properly.
        In this test, we scroll content in the iframe to the bottom to cause a
        huge offset. If we use the right coordinate system, selection should
        work. Otherwise, it would be hard to trigger select word.
        '''
        self.open_test_html(self._iframe_html)
        iframe = self.marionette.find_element(By.ID, 'frame')

        # switch to inner iframe and scroll to the bottom
        self.marionette.switch_to_frame(iframe)
        self.marionette.execute_script(
            'document.getElementById("bd").scrollTop += 999')

        # long press to select bottom text
        body = self.marionette.find_element(By.ID, 'bd')
        sel = SelectionManager(body)
        self._bottomtext = self.marionette.find_element(By.ID, 'bottomtext')
        self.long_press_on_location(self._bottomtext)

        self.assertNotEqual(self.to_unix_line_ending(sel.selected_content), '')

    def test_carets_initialized_in_display_none(self):
        '''Test AccessibleCaretEventHub is properly initialized on a <html> with
        display: none.

        '''
        self.open_test_html(self._display_none_html)
        html = self.marionette.find_element(By.ID, 'html')
        content = self.marionette.find_element(By.ID, 'content')

        # Remove 'display: none' from <html>
        self.marionette.execute_script(
            'arguments[0].style.display = "unset";',
            script_args=[html]
        )

        # If AccessibleCaretEventHub is initialized successfully, select a word
        # should work.
        self._test_long_press_to_select_a_word(content)

    def test_long_press_to_select_when_partial_visible_word_is_selected(self):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, self._input_size_id)
        sel = SelectionManager(el)

        original_content = sel.content
        words = original_content.split()

        # We cannot use self.long_press_on_word() for the second long press
        # on the first word because it has side effect that changes the
        # cursor position. We need to save the location of the first word to
        # be used later.
        word0_x, word0_y = self.word_location(el, 0)

        # Long press on the second word.
        self.long_press_on_word(el, 1)
        self.assertEqual(words[1], sel.selected_content)

        # Long press on the first word.
        self.long_press_on_location(el, word0_x, word0_y)
        self.assertEqual(words[0], sel.selected_content)

        # If the second caret is visible, it can be dragged to the position
        # of the first caret. After that, selection will contain only the
        # first character.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()
        self.assertEqual(words[0][0], sel.selected_content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_input_padding_id, el_id=_input_padding_id)
    @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_carets_not_jump_when_dragging_to_editable_content_boundary(self, el_id):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 3, 'Expect at least three words in the content.')

        # Goal: the selection is not changed after dragging the caret on the
        # Y-axis.
        target_content = words[1]

        self.long_press_on_word(el, 1)
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()

        # Drag the first caret up by 50px.
        self.actions.flick(el, caret1_x, caret1_y, caret1_x, caret1_y - 50).perform()
        self.assertEqual(target_content, sel.selected_content)

        # Drag the second caret down by 50px.
        self.actions.flick(el, caret2_x, caret2_y, caret2_x, caret2_y + 50).perform()
        self.assertEqual(target_content, sel.selected_content)
Esempio n. 14
0
class TouchCaretTest(MarionetteTestCase):
    _input_selector = (By.ID, 'input')
    _textarea_selector = (By.ID, 'textarea')
    _contenteditable_selector = (By.ID, 'contenteditable')
    _large_expiration_time = 3000 * 20  # 60 seconds

    def setUp(self):
        # Code to execute before a test is being run.
        MarionetteTestCase.setUp(self)
        self.actions = Actions(self.marionette)
        self.original_expiration_time = self.expiration_time

    def tearDown(self):
        # Code to execute after a test is being run.
        self.expiration_time = self.original_expiration_time
        MarionetteTestCase.tearDown(self)

    @property
    def expiration_time(self):
        'Return touch caret expiration time in milliseconds.'
        return self.marionette.execute_script(
            'return SpecialPowers.getIntPref("touchcaret.expiration.time");')

    @expiration_time.setter
    def expiration_time(self, expiration_time):
        'Set touch caret expiration time in milliseconds.'
        self.marionette.execute_script(
            'SpecialPowers.setIntPref("touchcaret.expiration.time", arguments[0]);',
            script_args=[expiration_time])

    def openTestHtml(self, enabled=True, expiration_time=None):
        '''Open html for testing and locate elements, enable/disable touch caret, and
        set touch caret expiration time in milliseconds).

        '''
        self.marionette.execute_async_script(
            'SpecialPowers.pushPrefEnv({"set": [["touchcaret.enabled", %s]]}, marionetteScriptFinished);' %
            ('true' if enabled else 'false'))

        # Set a larger expiration time to avoid intermittent test failures.
        if expiration_time is not None:
            self.expiration_time = expiration_time

        test_html = self.marionette.absolute_url('test_touchcaret.html')
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(*self._input_selector)
        self._textarea = self.marionette.find_element(*self._textarea_selector)
        self._contenteditable = self.marionette.find_element(*self._contenteditable_selector)

    def _test_move_caret_to_the_right_by_one_character(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content
        target_content = target_content[:1] + content_to_add + target_content[1:]

        # Get touch caret (x, y) at position 1 and 2.
        el.tap()
        sel.move_caret_to_front()
        caret0_x, caret0_y = sel.caret_location()
        touch_caret0_x, touch_caret0_y = sel.touch_caret_location()
        sel.move_caret_by_offset(1)
        touch_caret1_x, touch_caret1_y = sel.touch_caret_location()

        # Tap the front of the input to make touch caret appear.
        el.tap(caret0_x, caret0_y)

        # Move touch caret
        self.actions.flick(el, touch_caret0_x, touch_caret0_y,
                           touch_caret1_x, touch_caret1_y).perform()

        el.send_keys(content_to_add)
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content + content_to_add

        # Tap the front of the input to make touch caret appear.
        el.tap()
        sel.move_caret_to_front()
        el.tap(*sel.caret_location())

        # Move touch caret to the bottom-right corner of the element.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = el.size['width'], el.size['height']
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        el.send_keys(content_to_add)
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = content_to_add + sel.content

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Move touch caret to the top-left corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        el.send_keys(content_to_add)
        assertFunc(target_content, sel.content)

    def _test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Get touch caret expiration time in millisecond, and convert it to second.
        timeout = self.expiration_time / 1000.0

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Wait until touch caret disappears, then pretend to move it to the
        # top-left corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.wait(timeout).flick(el, src_x, src_y, dest_x, dest_y).perform()

        el.send_keys(content_to_add)
        assertFunc(non_target_content, sel.content)

    def _test_touch_caret_hides_after_receiving_wheel_event(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Send an arbitrary scroll-down-10px wheel event to the center of the
        # input box to hide touch caret. Then pretend to move it to the top-left
        # corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        el_center_x, el_center_y = el.rect['x'], el.rect['y']
        self.marionette.execute_script(
            '''var utils = SpecialPowers.getDOMWindowUtils(window);
            utils.sendWheelEvent(arguments[0], arguments[1],
                                 0, 10, 0, WheelEvent.DOM_DELTA_PIXEL,
                                 0, 0, 0, 0);''',
            script_args=[el_center_x, el_center_y]
        )
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        el.send_keys(content_to_add)
        assertFunc(non_target_content, sel.content)


    ########################################################################
    # <input> test cases with touch caret enabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_the_right_by_one_character(self._input, self.assertEqual)

    def test_input_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._input, self.assertEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._input, self.assertEqual)

    def test_input_touch_caret_timeout(self):
        self.openTestHtml(enabled=True)
        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._input, self.assertNotEqual)

    def test_input_touch_caret_hides_after_receiving_wheel_event(self):
        self.openTestHtml(enabled=True, expiration_time=0)
        self._test_touch_caret_hides_after_receiving_wheel_event(self._input, self.assertNotEqual)

    ########################################################################
    # <input> test cases with touch caret disabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_caret_to_the_right_by_one_character(self._input, self.assertNotEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret enabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._textarea, self.assertEqual)

    def test_textarea_touch_caret_timeout(self):
        self.openTestHtml(enabled=True)
        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._textarea, self.assertNotEqual)

    def test_textarea_touch_caret_hides_after_receiving_wheel_event(self):
        self.openTestHtml(enabled=True, expiration_time=0)
        self._test_touch_caret_hides_after_receiving_wheel_event(self._textarea, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret disabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertNotEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._textarea, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with touch caret enabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._contenteditable, self.assertEqual)

    def test_contenteditable_touch_caret_timeout(self):
        self.openTestHtml(enabled=True)
        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_touch_caret_hides_after_receiving_wheel_event(self):
        self.openTestHtml(enabled=True, expiration_time=0)
        self._test_touch_caret_hides_after_receiving_wheel_event(self._contenteditable, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with touch caret disabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._contenteditable, self.assertNotEqual)
Esempio n. 15
0
class Calendar(object):

    def __init__(self, parent):
        self.apps = parent.apps
        self.data_layer = parent.data_layer
        self.parent = parent
        self.marionette = parent.marionette
        self.UTILS = parent.UTILS
        self.actions = Actions(self.marionette)

    def launch(self):
        self.app = self.apps.launch(self.__class__.__name__)
        self.UTILS.element.waitForNotElements(DOM.GLOBAL.loading_overlay, self.__class__.__name__ + " app - loading overlay")
        return self.app

    def moveDayViewBy(self, num):
        """
        Switches to week view, then moves 'p_num' weeks in the future or past (if the p_num is
        positive or negative) relative to today.
        """
        self.UTILS.reporting.logResult("info", "<b>Adjusting day view by {} screens ...</b>".format(num))
        self.setView("day")
        self.setView("today")

        if num == 0:
            return
        """
        Set the y-coordinate offset, depending on which
        direction we need to flick the display.
        """
        numMoves = num
        if numMoves > 0:
            _moveEl = 1
        else:
            _moveEl = 2
            numMoves = numMoves * -1

        # Now move to the desired screen.
        for i in range(numMoves):
            # Flick the screen to move it (tricky to find the element we can flick!).
            _el = self.marionette.find_elements(*DOM.Calendar.dview_events)

            _num = 0
            for i in range(len(_el)):
                if _el[i].is_displayed():
                    _num = i
                    break

            x_pos1 = 0
            x_pos2 = 0
            if _moveEl == 1:
                x_pos1 = _el[_num].size["width"]
            if _moveEl == 2:
                x_pos2 = _el[_num].size["width"]
            self.actions.flick(_el[_num], x_pos1, 0, x_pos2, 0).perform()

        time.sleep(0.3)

        # Check this is the expected day.
        _new_epoch = int(time.time()) + (num * 24 * 60 * 60)
        _new_now = self.UTILS.date_and_time.getDateTimeFromEpochSecs(_new_epoch)
        _expected_str = "{} {}, {}".format(_new_now.month_name[:3], _new_now.mday, _new_now.day_name)

        x = self.UTILS.element.getElement(DOM.Calendar.current_view_header, "Current view header")
        self.UTILS.test.test(x.text == _expected_str, "Header is '<b>%s</b>' (it was '%s')." % (_expected_str, x.text))

        x = self.UTILS.debug.screenShotOnErr()
        self.UTILS.reporting.logResult("info", "Day view screen after moving {} pages: ".format(num), x)

    def getEventPreview(self, p_view, p_hour24, p_title, p_location=False):
        """
        Return object for an event in month / week or day view.
        The tag identifiers aren't consistent, so set them here.
        <type>: (<event preview identifier>, <event title identifier>)
        """
        event_view = {
            "month": (DOM.Calendar.view_events_block_m % p_hour24, DOM.Calendar.view_events_title_month),
            "week": (DOM.Calendar.view_events_block_w % p_hour24, DOM.Calendar.view_events_title_week),
            "day": (DOM.Calendar.view_events_block_d % p_hour24, DOM.Calendar.view_events_title_day)
        }

        viewStr = event_view[p_view]
        """
        Switch to the desired view.
        For the life of me I can't get 'wait_for_element' ... to work in day view, so I'm
        just waiting a few seconds then checking with .is_displayed() instead.
        """
        self.setView(p_view)
        time.sleep(2)

        # Start by getting the parent element objects, which could contain event details.
        event_objects = self.UTILS.element.getElements(('xpath', viewStr[0]), "'" + p_view + "' event details list",
                                               False, 20, False)
        if not event_objects:
            return False

        if len(event_objects) <= 0:
            return False

        for event_object in event_objects:
            if p_title in event_object.text:
                return event_object

        # If we get to here we failed to return the element we're after.
        return False

    def createEvent(self):
        x = self.UTILS.element.getElement(DOM.Calendar.add_event_btn, "Add event button")
        x.tap()

        title = ("xpath", "//input[@name='title']")
        where = ("xpath", "//input[@name='location']")
        # allday checkbox, True False (use tap())
        allday = ("xpath", "//li[@class='allday']")

        x = self.UTILS.element.getElement(title, "Title", True, 10)
        x.send_keys("hello title")

        x = self.UTILS.element.getElement(where, "Where")
        x.send_keys("hello where")

        x = self.UTILS.element.getElement(allday, "All day")
        x.tap()

        self.marionette.execute_script("document.getElementById('start-date-locale').click()")

    def changeDay(self, numDays, viewType):
        """
        Changes the calendar day to a different day relative to 'today' - <b>uses the
        month view to do this, then switches back to whichever
        view you want (month, week, day)</b>.<br>
        <b>numDays</b> is a number (can be negative to go back, i.e. -5,-2,1,3,5 etc...).<br>
        <b>viewType</b> is the calendar view to return to (today / day / week / month)<br>
        Returns a modified DateTime object from <i>UTILS.getDateTimeFromEpochSecs()</i>.
        """
        self.setView("month")
        self.setView("today")

        now_secs = time.time()
        now_diff = int(now_secs) + (86400 * numDays)
        now_today = self.UTILS.date_and_time.getDateTimeFromEpochSecs(now_secs)
        new_today = self.UTILS.date_and_time.getDateTimeFromEpochSecs(now_diff)

        # Switch to month view and tap this day, then switch back to our view.
        if now_today.mon != new_today.mon:
            x = new_today.mon - now_today.mon
            self.moveMonthViewBy(x)

        el_id_str = "d-%s-%s-%s" % (new_today.year, new_today.mon - 1, new_today.mday)
        x = self.UTILS.element.getElement(("xpath", "//li[@data-date='{}']".format(el_id_str)),
                                  "Cell for day {}/{}/{}".format(new_today.mday, new_today.mon, new_today.year))
        x.tap()
        self.setView(viewType.lower())
        return new_today

    def moveMonthViewBy(self, num):
        """
        Switches to month view, then moves 'num' months in the future or past (if the num is
        positive or negative) relative to today.
        """
        self.UTILS.reporting.logResult("info", "<b>Adjusting month view by {} months ...</b>".format(num))
        self.setView("month")
        self.setView("today")

        if num == 0:
            return
        """
        Set the y-coordinate offset, depending on which
        direction we need to flick the display.
        """
        numMoves = num
        x2 = 0
        if numMoves > 0:
            el_num = -1
            x2 = -500
        if numMoves < 0:
            el_num = 0
            x2 = 500
            numMoves = numMoves * -1

        now = time.localtime(int(time.time()))
        month = now.tm_mon
        year = now.tm_year

        for i in range(numMoves):
            # Flick the display to show the date we're aiming for.
            el = self.marionette.find_elements(*DOM.Calendar.mview_first_row_for_flick)[el_num]
            self.actions.flick(el, 0, 0, x2, 0).perform()

            # Increment the month and year so we keep track of what's expected.
            if num < 0:
                month = month - 1
            else:
                month = month + 1

            if month <= 0:
                month = 12
                year = year - 1
            elif month >= 13:
                month = 1
                year = year + 1

        time.sleep(0.3)

        # Work out what the header should now be.
        month_names = ["January", "February", "March", "April", "May", "June",
                        "July", "August", "September", "October", "November", "December"]

        expect = "{} {}".format(month_names[month - 1], year)
        actual = self.UTILS.element.getElement(DOM.Calendar.current_view_header, "Header").text

        self.UTILS.test.test(expect.lower() in actual.lower(), "Expecting header to contain '{}' (received '{}')"
                        .format(expect, actual))

    def moveWeekViewBy(self, num):
        """
        Switches to week view, then moves 'num' weeks in the future or past (if the num is
        positive or negative) relative to today.
        """
        self.UTILS.reporting.logResult("info", "<b>Adjusting week view by {} screens ...</b>".format(num))
        self.setView("week")
        self.setView("today")

        if num == 0:
            return
        """
        Set the y-coordinate offset, depending on which
        direction we need to flick the display.
        """
        numMoves = num
        x2 = 0
        if numMoves > 0:
            el = -1
            x2 = -500
        if numMoves < 0:
            el = 0
            x2 = 500
            numMoves = numMoves * -1
        """
        Keep track of how many days we're adjusting the display by (so we can check
        we're looking at the correct display at the end).
        """
        days_offset = 0
        now_epoch = int(time.time())
        now = self.UTILS.date_and_time.getDateTimeFromEpochSecs(now_epoch)
        now_str = "{} {}".format(now.day_name[:3].upper(), now.mday)

        displayed_days = self.UTILS.element.getElements(DOM.Calendar.wview_active_days, "Active days")
        startpos = 0
        for i in range(len(displayed_days)):
            x = displayed_days[i].text
            if x:
                if now_str in x:
                    startpos = i - 1
                    break

        if num < 0:
            days_offset = startpos
        else:
            days_offset = len(displayed_days) - startpos - 2

        # Now move to the desired screen.
        for i in range(numMoves):

            # Flick the screen to move it.
            self.actions.flick(displayed_days[el], 0, 0, x2, 0).perform()

            # Get the count of days we're adjusting (so we can check later).
            displayed_days = self.UTILS.element.getElements(DOM.Calendar.wview_active_days, "Active days")
            days_offset = days_offset + len(displayed_days)

        time.sleep(0.3)
        """
        Work out what the display should now be:
        1. Today + days_offset should be displayed.
        2. Header should be month + year, now + days_offset should be in active days.
        """
        if num < 0:
            new_epoch = now_epoch - (days_offset * 24 * 60 * 60)
        else:
            new_epoch = now_epoch + (days_offset * 24 * 60 * 60)

        new_now = self.UTILS.date_and_time.getDateTimeFromEpochSecs(new_epoch)

        new_now_str = "{} {}".format(new_now.day_name[:3].upper(), new_now.mday)

        x = self.UTILS.element.getElements(DOM.Calendar.wview_active_days, "Active days")
        boolOK = False
        for i in range(len(x)):
            x = displayed_days[i].text
            if x:
                if new_now_str in x:
                    boolOK = True
                    break

        self.UTILS.test.test(boolOK, "The column for date '<b>{}</b>' displayed.".format(new_now_str))

        x = self.UTILS.element.getElement(DOM.Calendar.current_view_header, "Current view header")
        self.UTILS.test.test(new_now.month_name in x.text, "'<b>{}</b>' is in header ('{}')."
                        .format(new_now.month_name, x.text))
        self.UTILS.test.test(str(new_now.year) in x.text, "'<b>{}</b>' is in header ('{}').".format(new_now.year, x.text))

        x = self.UTILS.debug.screenShotOnErr()
        self.UTILS.reporting.logResult("info", "Week view screen after moving {} pages: ".format(num), x)

    def setView(self, typ):
        """
        Set to view typ (today / day / week / month).
        """
        x = self.UTILS.element.getElement((DOM.Calendar.view_type[0], DOM.Calendar.view_type[1] % typ.lower()),
                                  "'" + typ + "' view type selector")
        x.tap()

        if typ.lower() != 'today':
            viewTypes = {"month": DOM.Calendar.mview_container,
                         "week": DOM.Calendar.wview_container,
                         "day": DOM.Calendar.dview_container}

            self.UTILS.element.waitForElements(viewTypes[typ], "Container for '{}' view".format(typ))
        else:
            time.sleep(0.5)
class CommonCaretsTestCase(object):
    '''Common test cases for a selection with a two carets.

    To run these test cases, a subclass must inherit from both this class and
    MarionetteTestCase.

    '''
    _input_selector = (By.ID, 'input')
    _textarea_selector = (By.ID, 'textarea')
    _textarea_rtl_selector = (By.ID, 'textarea_rtl')
    _contenteditable_selector = (By.ID, 'contenteditable')
    _content_selector = (By.ID, 'content')
    _textarea2_selector = (By.ID, 'textarea2')
    _contenteditable2_selector = (By.ID, 'contenteditable2')
    _content2_selector = (By.ID, 'content2')

    def setUp(self):
        # Code to execute before a tests are run.
        super(CommonCaretsTestCase, self).setUp()
        self.actions = Actions(self.marionette)

        # The carets to be tested.
        self.carets_tested_pref = None

        # The carets to be disabled in this test suite.
        self.carets_disabled_pref = None

    def set_pref(self, pref_name, value):
        '''Set a preference to value.

        For example:
        >>> set_pref('layout.accessiblecaret.enabled', True)

        '''
        pref_name = repr(pref_name)
        if isinstance(value, bool):
            value = 'true' if value else 'false'
            func = 'setBoolPref'
        elif isinstance(value, int):
            value = str(value)
            func = 'setIntPref'
        else:
            value = repr(value)
            func = 'setCharPref'

        with self.marionette.using_context('chrome'):
            script = 'Services.prefs.%s(%s, %s)' % (func, pref_name, value)
            self.marionette.execute_script(script)

    def open_test_html(self, enabled=True):
        '''Open html for testing and locate elements, and enable/disable touch
        caret.'''
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html = self.marionette.absolute_url('test_selectioncarets.html')
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(*self._input_selector)
        self._textarea = self.marionette.find_element(*self._textarea_selector)
        self._textarea_rtl = self.marionette.find_element(*self._textarea_rtl_selector)
        self._contenteditable = self.marionette.find_element(*self._contenteditable_selector)
        self._content = self.marionette.find_element(*self._content_selector)

    def open_test_html2(self, enabled=True):
        '''Open html for testing and locate elements, and enable/disable touch
        caret.'''
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html2 = self.marionette.absolute_url('test_selectioncarets_multipleline.html')
        self.marionette.navigate(test_html2)

        self._textarea2 = self.marionette.find_element(*self._textarea2_selector)
        self._contenteditable2 = self.marionette.find_element(*self._contenteditable2_selector)
        self._content2 = self.marionette.find_element(*self._content2_selector)

    def open_test_html_multirange(self, enabled=True):
        'Open html for testing and enable selectioncaret and non-editable support'
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html = self.marionette.absolute_url('test_selectioncarets_multiplerange.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._sel1 = self.marionette.find_element(By.ID, 'sel1')
        self._sel2 = self.marionette.find_element(By.ID, 'sel2')
        self._sel3 = self.marionette.find_element(By.ID, 'sel3')
        self._sel4 = self.marionette.find_element(By.ID, 'sel4')
        self._sel6 = self.marionette.find_element(By.ID, 'sel6')
        self._nonsel1 = self.marionette.find_element(By.ID, 'nonsel1')

    def open_test_html_long_text(self, enabled=True):
        'Open html for testing and enable selectioncaret'
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html = self.marionette.absolute_url('test_selectioncarets_longtext.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._longtext = self.marionette.find_element(By.ID, 'longtext')

    def open_test_html_iframe(self, enabled=True):
        'Open html for testing and enable selectioncaret'
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html = self.marionette.absolute_url('test_selectioncarets_iframe.html')
        self.marionette.navigate(test_html)

        self._iframe = self.marionette.find_element(By.ID, 'frame')

    def word_location(self, el, ordinal):
        '''Get the location (x, y) of the ordinal-th word in el.

        The ordinal starts from 0.

        Note: this function has a side effect which changes focus to the
        target element el.

        '''
        sel = SelectionManager(el)
        tokens = re.split(r'(\S+)', sel.content)  # both words and spaces
        words = tokens[0::2]                      # collect words at even indices
        spaces = tokens[1::2]                     # collect spaces at odd indices
        self.assertTrue(ordinal < len(words),
                        'Expect at least %d words in the content.' % ordinal)

        # Cursor position of the targeting word is behind the the first
        # character in the word. For example, offset to 'def' in 'abc def' is
        # between 'd' and 'e'.
        offset = sum(len(words[i]) + len(spaces[i]) for i in range(ordinal)) + 1

        # Move caret to the word.
        el.tap()
        sel.move_caret_to_front()
        sel.move_caret_by_offset(offset)
        x, y = sel.caret_location()

        return x, y

    def rect_relative_to_window(self, el):
        '''Get element's bounding rectangle.

        This function is similar to el.rect, but the coordinate is relative to
        the top left corner of the window instead of the document.

        '''
        return self.marionette.execute_script('''
            let rect = arguments[0].getBoundingClientRect();
            return {x: rect.x, y:rect.y, width: rect.width, height: rect.height};
            ''', script_args=[el])

    def long_press_on_location(self, el, x=None, y=None):
        '''Long press the location (x, y) to select a word.

        If no (x, y) are given, it will be targeted at the center of the
        element. On Windows, those spaces after the word will also be selected.
        This function sends synthesized eMouseLongTap to gecko.

        '''
        rect = self.rect_relative_to_window(el)
        target_x = rect['x'] + (x if x is not None else rect['width'] // 2)
        target_y = rect['y'] + (y if y is not None else rect['height'] // 2)

        self.marionette.execute_script('''
            let Ci = Components.interfaces;
            let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
            utils.sendTouchEventToWindow('touchstart', [0],
                                         [arguments[0]], [arguments[1]],
                                         [1], [1], [0], [1], 1, 0);
            utils.sendMouseEventToWindow('mouselongtap', arguments[0], arguments[1],
                                          0, 1, 0);
            utils.sendTouchEventToWindow('touchend', [0],
                                         [arguments[0]], [arguments[1]],
                                         [1], [1], [0], [1], 1, 0);
            ''', script_args=[target_x, target_y], sandbox='system')

    def long_press_on_word(self, el, wordOrdinal):
        x, y = self.word_location(el, wordOrdinal)
        self.long_press_on_location(el, x, y)

    def to_unix_line_ending(self, s):
        """Changes all Windows/Mac line endings in s to UNIX line endings."""

        return s.replace('\r\n', '\n').replace('\r', '\n')

    def _test_long_press_to_select_a_word(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 2, 'Expect at least two words in the content.')
        target_content = words[0]

        # Goal: Select the first word.
        self.long_press_on_word(el, 0)

        # Ignore extra spaces selected after the word.
        assertFunc(target_content, sel.selected_content.rstrip())

    def _test_move_selection_carets(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        # Goal: Select all text after the first word.
        target_content = original_content[len(words[0]):]

        # Get the location of the selection carets at the end of the content for
        # later use.
        el.tap()
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        self.long_press_on_word(el, 0)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Move the left caret to the previous position of the right caret.
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        # Ignore extra spaces at the beginning of the content in comparison.
        assertFunc(target_content.lstrip(), sel.selected_content.lstrip())

    def _test_minimum_select_one_character(self, el, assertFunc,
                                           x=None, y=None):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        # Get the location of the selection carets at the end of the content for
        # later use.
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()
        el.tap()

        # Goal: Select the first character.
        target_content = original_content[0]

        if x and y:
            # If we got x and y from the arguments, use it as a hint of the
            # location of the first word
            pass
        else:
            x, y = self.word_location(el, 0)
        self.long_press_on_location(el, x, y)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Move the right caret to the position of the left caret.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    def _test_focus_obtained_by_long_press(self, el1, el2):
        '''Test the focus could be changed from el1 to el2 by long press.

        If the focus is changed to e2 successfully, SelectionCarets should
        appear and could be dragged.

        '''
        # Goal: Tap to focus el1, and then select the first character on
        # el2.

        # We want to collect the location of the first word in el2 here
        # since self.word_location() has the side effect which would
        # change the focus.
        x, y = self.word_location(el2, 0)
        el1.tap()
        self._test_minimum_select_one_character(el2, self.assertEqual,
                                                x=x, y=y)

    def _test_handle_tilt_when_carets_overlap_to_each_other(self, el, assertFunc):
        '''Test tilt handling when carets overlap to each other.

        Let SelectionCarets overlap to each other. If SelectionCarets are set
        to tilted successfully, tapping the tilted carets should not cause the
        selection to be collapsed and the carets should be draggable.
        '''

        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        # Goal: Select the first word.
        self.long_press_on_word(el, 0)
        target_content = sel.selected_content

        # Move the left caret to the position of the right caret to trigger
        # carets overlapping.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        # We make two hit tests targeting the left edge of the left tilted caret
        # and the right edge of the right tilted caret. If either of the hits is
        # missed, selection would be collapsed and both carets should not be
        # draggable.
        (caret3_x, caret3_y), (caret4_x, caret4_y) = sel.selection_carets_location()

        # The following values are from ua.css.
        caret_width = 44
        caret_margin_left = -23
        tilt_right_margin_left = 18
        tilt_left_margin_left = -17

        left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
        el.tap(left_caret_left_edge_x + 2, caret3_y)

        right_caret_right_edge_x = (caret4_x + caret_margin_left +
                                    tilt_right_margin_left + caret_width)
        el.tap(right_caret_right_edge_x - 2, caret4_y)

        # Drag the left caret back to the initial selection, the first word.
        self.actions.flick(el, caret3_x, caret3_y, caret1_x, caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    def test_long_press_to_select_non_selectable_word(self):
        '''Testing long press on non selectable field.
        We should not select anything when long press on non selectable fields.'''

        self.open_test_html_multirange()
        halfY = self._nonsel1.size['height'] / 2
        self.long_press_on_location(self._nonsel1, 0, halfY)
        sel = SelectionManager(self._nonsel1)
        range_count = sel.range_count()
        self.assertEqual(range_count, 0)

    def test_drag_caret_over_non_selectable_field(self):
        '''Testing drag caret over non selectable field.
        So that the selected content should exclude non selectable field and
        end selection caret should appear in last range's position.'''
        self.open_test_html_multirange()

        # Select target element and get target caret location
        self.long_press_on_word(self._sel4, 3)
        sel = SelectionManager(self._body)
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        self.long_press_on_word(self._sel6, 0)
        (_, _), (end_caret2_x, end_caret2_y) = sel.selection_carets_location()

        # Select start element
        self.long_press_on_word(self._sel3, 3)

        # Drag end caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
                         'this 3\nuser can select this')

        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret2_x, end_caret2_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
                         'this 3\nuser can select this 4\nuser can select this 5\nuser')

        # Drag first caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret1_x, caret1_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
                         '4\nuser can select this 5\nuser')

    def test_drag_caret_to_beginning_of_a_line(self):
        '''Bug 1094056
        Test caret visibility when caret is dragged to beginning of a line
        '''
        self.open_test_html_multirange()

        # Select the first word in the second line
        self.long_press_on_word(self._sel2, 0)
        sel = SelectionManager(self._body)
        (start_caret_x, start_caret_y), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        # Select target word in the first line
        self.long_press_on_word(self._sel1, 2)

        # Drag end caret to the beginning of the second line
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, start_caret_x, start_caret_y).perform()

        # Drag end caret back to the target word
        self.actions.flick(self._body, start_caret_x, start_caret_y, caret2_x, caret2_y).perform()

        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()), 'select')

    @skip_if_not_rotatable
    def test_caret_position_after_changing_orientation_of_device(self):
        '''Bug 1094072
        If positions of carets are updated correctly, they should be draggable.
        '''
        self.open_test_html_long_text()

        # Select word in portrait mode, then change to landscape mode
        self.marionette.set_orientation('portrait')
        self.long_press_on_word(self._longtext, 12)
        sel = SelectionManager(self._body)
        (p_start_caret_x, p_start_caret_y), (p_end_caret_x, p_end_caret_y) = sel.selection_carets_location()
        self.marionette.set_orientation('landscape')
        (l_start_caret_x, l_start_caret_y), (l_end_caret_x, l_end_caret_y) = sel.selection_carets_location()

        # Drag end caret to the start caret to change the selected content
        self.actions.flick(self._body, l_end_caret_x, l_end_caret_y, l_start_caret_x, l_start_caret_y).perform()

        # Change orientation back to portrait mode to prevent affecting
        # other tests
        self.marionette.set_orientation('portrait')

        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()), 'o')

    def test_select_word_inside_an_iframe(self):
        '''Bug 1088552
        The scroll offset in iframe should be taken into consideration properly.
        In this test, we scroll content in the iframe to the bottom to cause a
        huge offset. If we use the right coordinate system, selection should
        work. Otherwise, it would be hard to trigger select word.
        '''
        self.open_test_html_iframe()

        # switch to inner iframe and scroll to the bottom
        self.marionette.switch_to_frame(self._iframe)
        self.marionette.execute_script(
            'document.getElementById("bd").scrollTop += 999')

        # long press to select bottom text
        self._body = self.marionette.find_element(By.ID, 'bd')
        sel = SelectionManager(self._body)
        self._bottomtext = self.marionette.find_element(By.ID, 'bottomtext')
        self.long_press_on_location(self._bottomtext)

        self.assertNotEqual(self.to_unix_line_ending(sel.selected_content.strip()), '')

    ########################################################################
    # <input> test cases with selection carets enabled
    ########################################################################
    def test_input_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._input, self.assertEqual)

    def test_input_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._input, self.assertEqual)

    def test_input_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._input, self.assertEqual)

    def test_input_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._input)

    def test_input_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable, self._input)

    def test_input_focus_obtained_by_long_press_from_content_non_editable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._input)

    def test_input_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._input, self.assertEqual)

    ########################################################################
    # <input> test cases with selection carets disabled
    ########################################################################
    def test_input_long_press_to_select_a_word_disabled(self):
        self.open_test_html(enabled=False)
        self._test_long_press_to_select_a_word(self._input, self.assertNotEqual)

    def test_input_move_selection_carets_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_selection_carets(self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with selection carets enabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._textarea, self.assertEqual)

    def test_textarea_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._textarea, self.assertEqual)

    def test_textarea_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._textarea, self.assertEqual)

    def test_textarea_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable, self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_content_non_editable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._textarea)

    def test_textarea_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._textarea, self.assertEqual)

    ########################################################################
    # <textarea> test cases with selection carets disabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word_disabled(self):
        self.open_test_html(enabled=False)
        self._test_long_press_to_select_a_word(self._textarea, self.assertNotEqual)

    def test_textarea_move_selection_carets_disable(self):
        self.open_test_html(enabled=False)
        self._test_move_selection_carets(self._textarea, self.assertNotEqual)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets enabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._textarea_rtl, self.assertEqual)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets disabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word_disabled(self):
        self.open_test_html(enabled=False)
        self._test_long_press_to_select_a_word(self._textarea_rtl, self.assertNotEqual)

    def test_textarea_rtl_move_selection_carets_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_selection_carets(self._textarea_rtl, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with selection carets enabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._contenteditable, self.assertEqual)

    def test_contenteditable_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._contenteditable, self.assertEqual)

    def test_contenteditable_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_content_non_editable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._contenteditable)

    def test_contenteditable_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._contenteditable, self.assertEqual)

    ########################################################################
    # <div> contenteditable test cases with selection carets disabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word_disabled(self):
        self.open_test_html(enabled=False)
        self._test_long_press_to_select_a_word(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_move_selection_carets_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_selection_carets(self._contenteditable, self.assertNotEqual)

    ########################################################################
    # <div> non-editable test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._content, self.assertEqual)

    def test_content_non_editable_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._content, self.assertEqual)

    def test_content_non_editable_minimum_select_one_character_by_selection(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._content, self.assertEqual)

    def test_content_non_editable_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable, self._content)

    def test_content_non_editable_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._content, self.assertEqual)

    ########################################################################
    # <textarea> (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_textarea2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._textarea2, self.assertEqual)

    ########################################################################
    # <div> contenteditable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_contenteditable2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._contenteditable2, self.assertEqual)

    ########################################################################
    # <div> non-editable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._content2, self.assertEqual)
class AccessibleCaretCursorModeTestCase(MarionetteTestCase):
    '''Test cases for AccessibleCaret under cursor mode.

    We call the blinking cursor (nsCaret) as cursor, and call AccessibleCaret as
    caret for short.

    '''
    # Element IDs.
    _input_id = 'input'
    _input_padding_id = 'input-padding'
    _textarea_id = 'textarea'
    _textarea_one_line_id = 'textarea-one-line'
    _contenteditable_id = 'contenteditable'

    # Test html files.
    _cursor_html = 'test_carets_cursor.html'

    def setUp(self):
        # Code to execute before every test is running.
        super(AccessibleCaretCursorModeTestCase, self).setUp()
        self.caret_tested_pref = 'layout.accessiblecaret.enabled'
        self.caret_timeout_ms_pref = 'layout.accessiblecaret.timeout_ms'
        self.hide_carets_for_mouse = 'layout.accessiblecaret.hide_carets_for_mouse_input'
        self.prefs = {
            self.caret_tested_pref: True,
            self.caret_timeout_ms_pref: 0,
            self.hide_carets_for_mouse: False,
        }
        self.marionette.set_prefs(self.prefs)
        self.actions = Actions(self.marionette)

    def open_test_html(self, test_html):
        self.marionette.navigate(self.marionette.absolute_url(test_html))

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_move_cursor_to_the_right_by_one_character(self, el_id):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content
        target_content = target_content[:1] + content_to_add + target_content[1:]

        # Get first caret (x, y) at position 1 and 2.
        el.tap()
        sel.move_cursor_to_front()
        cursor0_x, cursor0_y = sel.cursor_location()
        first_caret0_x, first_caret0_y = sel.first_caret_location()
        sel.move_cursor_by_offset(1)
        first_caret1_x, first_caret1_y = sel.first_caret_location()

        # Tap the front of the input to make first caret appear.
        el.tap(cursor0_x, cursor0_y)

        # Move first caret.
        self.actions.flick(el, first_caret0_x, first_caret0_y,
                           first_caret1_x, first_caret1_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertEqual(target_content, sel.content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_move_cursor_to_end_by_dragging_caret_to_bottom_right_corner(self, el_id):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content + content_to_add

        # Tap the front of the input to make first caret appear.
        el.tap()
        sel.move_cursor_to_front()
        el.tap(*sel.cursor_location())

        # Move first caret to the bottom-right corner of the element.
        src_x, src_y = sel.first_caret_location()
        dest_x, dest_y = el.size['width'], el.size['height']
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertEqual(target_content, sel.content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_move_cursor_to_front_by_dragging_caret_to_front(self, el_id):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = content_to_add + sel.content

        # Get first caret location at the front.
        el.tap()
        sel.move_cursor_to_front()
        dest_x, dest_y = sel.first_caret_location()

        # Tap to make first caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_cursor_to_end()
        sel.move_cursor_by_offset(1, backward=True)
        el.tap(*sel.cursor_location())
        src_x, src_y = sel.first_caret_location()

        # Move first caret to the front of the input box.
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertEqual(target_content, sel.content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_dragging_caret_to_top_left_corner_after_timeout(self, el_id):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Set caret timeout to be 1 second.
        timeout = 1
        self.marionette.set_pref(self.caret_timeout_ms_pref, timeout * 1000)

        # Set a 3x timeout margin to prevent intermittent test failures.
        timeout *= 3

        # Tap to make first caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_cursor_to_end()
        sel.move_cursor_by_offset(1, backward=True)
        el.tap(*sel.cursor_location())

        # Wait until first caret disappears, then pretend to move it to the
        # top-left corner of the input box.
        src_x, src_y = sel.first_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.wait(timeout).flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertNotEqual(non_target_content, sel.content)

    def test_caret_not_appear_when_typing_in_scrollable_content(self):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, self._input_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content + string.ascii_letters + content_to_add

        el.tap()
        sel.move_cursor_to_end()

        # Insert a long string to the end of the <input>, which triggers
        # ScrollPositionChanged event.
        el.send_keys(string.ascii_letters)

        # The caret should not be visible. If it does appear wrongly due to the
        # ScrollPositionChanged event, we can drag it to the front of the
        # <input> to change the cursor position.
        src_x, src_y = sel.first_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        # The content should be inserted at the end of the <input>.
        el.send_keys(content_to_add)

        self.assertEqual(target_content, sel.content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_input_padding_id, el_id=_input_padding_id)
    @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_caret_not_jump_when_dragging_to_editable_content_boundary(self, el_id):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = sel.content + content_to_add

        # Goal: the cursor position is not changed after dragging the caret down
        # on the Y-axis.
        el.tap()
        sel.move_cursor_to_front()
        el.tap(*sel.cursor_location())
        x, y = sel.first_caret_location()

        # Drag the caret down by 50px, and insert '!'.
        self.actions.flick(el, x, y, x, y + 50).perform()
        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertNotEqual(non_target_content, sel.content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_input_padding_id, el_id=_input_padding_id)
    @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_caret_not_jump_to_front_when_dragging_up_to_editable_content_boundary(self, el_id):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Goal: the cursor position is not changed after dragging the caret down
        # on the Y-axis.
        el.tap()
        sel.move_cursor_to_end()
        sel.move_cursor_by_offset(1, backward=True)
        el.tap(*sel.cursor_location())
        x, y = sel.first_caret_location()

        # Drag the caret up by 50px, and insert '!'.
        self.actions.flick(el, x, y, x, y - 50).perform()
        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertNotEqual(non_target_content, sel.content)

    def test_drag_caret_from_front_to_end_across_columns(self):
        self.open_test_html('test_carets_columns.html')
        el = self.marionette.find_element(By.ID, 'columns-inner')
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content + content_to_add

        # Goal: the cursor position can be changed by dragging the caret from
        # the front to the end of the content.

        # Tap to make the cursor appear.
        before_image_1 = self.marionette.find_element(By.ID, 'before-image-1')
        before_image_1.tap()

        # Tap the front of the content to make first caret appear.
        sel.move_cursor_to_front()
        el.tap(*sel.cursor_location())
        src_x, src_y = sel.first_caret_location()
        dest_x, dest_y = el.size['width'], el.size['height']

        # Drag the first caret to the bottom-right corner of the element.
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertEqual(target_content, sel.content)

    def test_move_cursor_to_front_by_dragging_caret_to_front_br_element(self):
        self.open_test_html(self._cursor_html)
        el = self.marionette.find_element(By.ID, self._contenteditable_id)
        sel = SelectionManager(el)
        content_to_add_1 = '!'
        content_to_add_2 = '\n\n'
        target_content = content_to_add_1 + content_to_add_2 + sel.content

        # Goal: the cursor position can be changed by dragging the caret from
        # the end of the content to the front br element. Because we cannot get
        # caret location if it's on a br element, we need to get the first caret
        # location then adding the new lines.

        # Get first caret location at the front.
        el.tap()
        sel.move_cursor_to_front()
        dest_x, dest_y = sel.first_caret_location()

        # Append new line to the front of the content.
        el.send_keys(content_to_add_2);

        # Tap to make first caret appear.
        el.tap()
        sel.move_cursor_to_end()
        sel.move_cursor_by_offset(1, backward=True)
        el.tap(*sel.cursor_location())
        src_x, src_y = sel.first_caret_location()

        # Move first caret to the front of the input box.
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add_1).key_up(content_to_add_1).perform()
        self.assertEqual(target_content, sel.content)
Esempio n. 18
0
class CommonCaretTestCase(object):
    '''Common test cases for a collapsed selection with a single caret.

    To run these test cases, a subclass must inherit from both this class and
    MarionetteTestCase.

    '''
    _input_selector = (By.ID, 'input')
    _textarea_selector = (By.ID, 'textarea')
    _contenteditable_selector = (By.ID, 'contenteditable')

    def setUp(self):
        # Code to execute before a test is being run.
        super(CommonCaretTestCase, self).setUp()
        self.actions = Actions(self.marionette)

        # The caret to be tested.
        self.caret_tested_pref = None

        # The caret to be disabled in this test suite.
        self.caret_disabled_pref = None
        self.caret_timeout_ms_pref = None

    def set_pref(self, pref_name, value):
        '''Set a preference to value.

        For example:
        >>> set_pref('layout.accessiblecaret.enabled', True)

        '''
        pref_name = repr(pref_name)
        if isinstance(value, bool):
            value = 'true' if value else 'false'
            func = 'setBoolPref'
        elif isinstance(value, int):
            value = str(value)
            func = 'setIntPref'
        else:
            value = repr(value)
            func = 'setCharPref'

        with self.marionette.using_context('chrome'):
            script = 'Services.prefs.%s(%s, %s)' % (func, pref_name, value)
            self.marionette.execute_script(script)

    def timeout_ms(self):
        'Return touch caret expiration time in milliseconds.'
        with self.marionette.using_context('chrome'):
            return self.marionette.execute_script(
                'return Services.prefs.getIntPref("%s");' % self.caret_timeout_ms_pref)

    def open_test_html(self, enabled=True, timeout_ms=0):
        '''Open html for testing and locate elements, enable/disable touch caret, and
        set touch caret expiration time in milliseconds).

        '''
        self.set_pref(self.caret_tested_pref, enabled)
        self.set_pref(self.caret_disabled_pref, False)
        self.set_pref(self.caret_timeout_ms_pref, timeout_ms)

        test_html = self.marionette.absolute_url('test_touchcaret.html')
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(*self._input_selector)
        self._textarea = self.marionette.find_element(*self._textarea_selector)
        self._contenteditable = self.marionette.find_element(*self._contenteditable_selector)

    def _test_move_caret_to_the_right_by_one_character(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content
        target_content = target_content[:1] + content_to_add + target_content[1:]

        # Get touch caret (x, y) at position 1 and 2.
        el.tap()
        sel.move_caret_to_front()
        caret0_x, caret0_y = sel.caret_location()
        touch_caret0_x, touch_caret0_y = sel.touch_caret_location()
        sel.move_caret_by_offset(1)
        touch_caret1_x, touch_caret1_y = sel.touch_caret_location()

        # Tap the front of the input to make touch caret appear.
        el.tap(caret0_x, caret0_y)

        # Move touch caret
        self.actions.flick(el, touch_caret0_x, touch_caret0_y,
                           touch_caret1_x, touch_caret1_y).perform()

        el.send_keys(content_to_add)
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content + content_to_add

        # Tap the front of the input to make touch caret appear.
        el.tap()
        sel.move_caret_to_front()
        el.tap(*sel.caret_location())

        # Move touch caret to the bottom-right corner of the element.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = el.size['width'], el.size['height']
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        el.send_keys(content_to_add)
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = content_to_add + sel.content

        # Get touch caret location at the front.
        el.tap()
        sel.move_caret_to_front()
        dest_x, dest_y = sel.touch_caret_location()

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())
        src_x, src_y = sel.touch_caret_location()

        # Move touch caret to the front of the input box.
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        el.send_keys(content_to_add)
        assertFunc(target_content, sel.content)

    def _test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Get touch caret expiration time in millisecond, and convert it to second.
        timeout = self.timeout_ms() / 1000.0

        # Set a 3x timeout margin to prevent intermittent test failures.
        timeout *= 3

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Wait until touch caret disappears, then pretend to move it to the
        # top-left corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.wait(timeout).flick(el, src_x, src_y, dest_x, dest_y).perform()

        el.send_keys(content_to_add)
        assertFunc(non_target_content, sel.content)

    def _test_touch_caret_hides_after_receiving_wheel_event(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Send an arbitrary scroll-down-10px wheel event to the center of the
        # input box to hide touch caret. Then pretend to move it to the top-left
        # corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        el_center_x, el_center_y = el.rect['x'], el.rect['y']
        self.marionette.execute_script(
            '''
            var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                              .getInterface(Components.interfaces.nsIDOMWindowUtils);
            utils.sendWheelEvent(arguments[0], arguments[1],
                                 0, 10, 0, WheelEvent.DOM_DELTA_PIXEL,
                                 0, 0, 0, 0);
            ''',
            script_args=[el_center_x, el_center_y],
            sandbox='system'
        )
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        el.send_keys(content_to_add)
        assertFunc(non_target_content, sel.content)

    ########################################################################
    # <input> test cases with touch caret enabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(self._input, self.assertEqual)

    def test_input_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._input, self.assertEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._input, self.assertEqual)

    def test_input_touch_caret_timeout(self):
        self.open_test_html(timeout_ms=1000)
        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._input, self.assertNotEqual)

    def test_input_touch_caret_hides_after_receiving_wheel_event(self):
        self.open_test_html()
        self._test_touch_caret_hides_after_receiving_wheel_event(self._input, self.assertNotEqual)

    ########################################################################
    # <input> test cases with touch caret disabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_caret_to_the_right_by_one_character(self._input, self.assertNotEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret enabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._textarea, self.assertEqual)

    def test_textarea_touch_caret_timeout(self):
        self.open_test_html(timeout_ms=1000)
        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._textarea, self.assertNotEqual)

    def test_textarea_touch_caret_hides_after_receiving_wheel_event(self):
        self.open_test_html()
        self._test_touch_caret_hides_after_receiving_wheel_event(self._textarea, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret disabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertNotEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._textarea, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with touch caret enabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._contenteditable, self.assertEqual)

    def test_contenteditable_touch_caret_timeout(self):
        self.open_test_html(timeout_ms=1000)
        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_touch_caret_hides_after_receiving_wheel_event(self):
        self.open_test_html()
        self._test_touch_caret_hides_after_receiving_wheel_event(self._contenteditable, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with touch caret disabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._contenteditable, self.assertNotEqual)
class CommonCaretsTestCase2(object):
    '''Common test cases for a selection with a two carets.

    To run these test cases, a subclass must inherit from both this class and
    MarionetteTestCase.

    '''
    _long_press_time = 1        # 1 second

    def setUp(self):
        # Code to execute before a tests are run.
        MarionetteTestCase.setUp(self)
        self.actions = Actions(self.marionette)

        # The carets to be tested.
        self.carets_tested_pref = None

        # The carets to be disabled in this test suite.
        self.carets_disabled_pref = None

    def set_pref(self, pref_name, value):
        '''Set a preference to value.

        For example:
        >>> set_pref('layout.accessiblecaret.enabled', True)

        '''
        pref_name = repr(pref_name)
        if isinstance(value, bool):
            value = 'true' if value else 'false'
            func = 'setBoolPref'
        elif isinstance(value, int):
            value = str(value)
            func = 'setIntPref'
        else:
            value = repr(value)
            func = 'setCharPref'

        with self.marionette.using_context('chrome'):
            script = 'Services.prefs.%s(%s, %s)' % (func, pref_name, value)
            self.marionette.execute_script(script)

    def open_test_html(self, enabled=True):
        'Open html for testing and enable selectioncaret and non-editable support'
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html = self.marionette.absolute_url('test_selectioncarets_multiplerange.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._sel1 = self.marionette.find_element(By.ID, 'sel1')
        self._sel2 = self.marionette.find_element(By.ID, 'sel2')
        self._sel3 = self.marionette.find_element(By.ID, 'sel3')
        self._sel4 = self.marionette.find_element(By.ID, 'sel4')
        self._sel6 = self.marionette.find_element(By.ID, 'sel6')
        self._nonsel1 = self.marionette.find_element(By.ID, 'nonsel1')

    def open_test_html_long_text(self, enabled=True):
        'Open html for testing and enable selectioncaret'
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html = self.marionette.absolute_url('test_selectioncarets_longtext.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._longtext = self.marionette.find_element(By.ID, 'longtext')

    def open_test_html_iframe(self, enabled=True):
        'Open html for testing and enable selectioncaret'
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html = self.marionette.absolute_url('test_selectioncarets_iframe.html')
        self.marionette.navigate(test_html)

        self._iframe = self.marionette.find_element(By.ID, 'frame')

    def _long_press_to_select_word(self, el, wordOrdinal):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(wordOrdinal < len(words),
            'Expect at least %d words in the content.' % wordOrdinal)

        # Calc offset
        offset = 0
        for i in range(wordOrdinal):
            offset += (len(words[i]) + 1)

        # Move caret inside the word.
        el.tap()
        sel.move_caret_to_front()
        sel.move_caret_by_offset(offset)
        x, y = sel.caret_location()

        # Long press the caret position. Selection carets should appear, and the
        # word will be selected. On Windows, those spaces after the word
        # will also be selected.
        long_press_without_contextmenu(self.marionette, el, self._long_press_time, x, y)

    def _to_unix_line_ending(self, s):
        """Changes all Windows/Mac line endings in s to UNIX line endings."""

        return s.replace('\r\n', '\n').replace('\r', '\n')

    def test_long_press_to_select_non_selectable_word(self):
        '''Testing long press on non selectable field.
        We should not select anything when long press on non selectable fields.'''

        self.open_test_html()
        halfY = self._nonsel1.size['height'] / 2
        long_press_without_contextmenu(self.marionette, self._nonsel1, self._long_press_time, 0, halfY)
        sel = SelectionManager(self._nonsel1)
        range_count = sel.range_count()
        self.assertEqual(range_count, 0)

    def test_drag_caret_over_non_selectable_field(self):
        '''Testing drag caret over non selectable field.
        So that the selected content should exclude non selectable field and
        end selection caret should appear in last range's position.'''
        self.open_test_html()

        # Select target element and get target caret location
        self._long_press_to_select_word(self._sel4, 3)
        sel = SelectionManager(self._body)
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        self._long_press_to_select_word(self._sel6, 0)
        (_, _), (end_caret2_x, end_caret2_y) = sel.selection_carets_location()

        # Select start element
        self._long_press_to_select_word(self._sel3, 3)

        # Drag end caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()),
            'this 3\nuser can select this')

        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret2_x, end_caret2_y, 1).perform()
        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()),
            'this 3\nuser can select this 4\nuser can select this 5\nuser')

        # Drag first caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret1_x, caret1_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()),
            '4\nuser can select this 5\nuser')

    def test_drag_caret_to_beginning_of_a_line(self):
        '''Bug 1094056
        Test caret visibility when caret is dragged to beginning of a line
        '''
        self.open_test_html()

        # Select the first word in the second line
        self._long_press_to_select_word(self._sel2, 0)
        sel = SelectionManager(self._body)
        (start_caret_x, start_caret_y), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        # Select target word in the first line
        self._long_press_to_select_word(self._sel1, 2)

        # Drag end caret to the beginning of the second line
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, start_caret_x, start_caret_y).perform()

        # Drag end caret back to the target word
        self.actions.flick(self._body, start_caret_x, start_caret_y, caret2_x, caret2_y).perform()

        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()), 'select')

    @skip_if_not_rotatable
    def test_caret_position_after_changing_orientation_of_device(self):
        '''Bug 1094072
        If positions of carets are updated correctly, they should be draggable.
        '''
        self.open_test_html_long_text()

        # Select word in portrait mode, then change to landscape mode
        self.marionette.set_orientation('portrait')
        self._long_press_to_select_word(self._longtext, 12)
        sel = SelectionManager(self._body)
        (p_start_caret_x, p_start_caret_y), (p_end_caret_x, p_end_caret_y) = sel.selection_carets_location()
        self.marionette.set_orientation('landscape')
        (l_start_caret_x, l_start_caret_y), (l_end_caret_x, l_end_caret_y) = sel.selection_carets_location()

        # Drag end caret to the start caret to change the selected content
        self.actions.flick(self._body, l_end_caret_x, l_end_caret_y, l_start_caret_x, l_start_caret_y).perform()

        # Change orientation back to portrait mode to prevent affecting
        # other tests
        self.marionette.set_orientation('portrait')

        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()), 'o')

    def test_select_word_inside_an_iframe(self):
        '''Bug 1088552
        The scroll offset in iframe should be taken into consideration properly.
        In this test, we scroll content in the iframe to the bottom to cause a
        huge offset. If we use the right coordinate system, selection should
        work. Otherwise, it would be hard to trigger select word.
        '''
        self.open_test_html_iframe()

        # switch to inner iframe and scroll to the bottom
        self.marionette.switch_to_frame(self._iframe)
        self.marionette.execute_script(
         'document.getElementById("bd").scrollTop += 999')

        # long press to select bottom text
        self._body = self.marionette.find_element(By.ID, 'bd')
        sel = SelectionManager(self._body)
        self._bottomtext = self.marionette.find_element(By.ID, 'bottomtext')
        long_press_without_contextmenu(self.marionette, self._bottomtext, self._long_press_time)

        self.assertNotEqual(self._to_unix_line_ending(sel.selected_content.strip()), '')
class AccessibleCaretSelectionModeTestCase(MarionetteTestCase):
    """Test cases for AccessibleCaret under selection mode."""

    # Element IDs.
    _input_id = "input"
    _input_padding_id = "input-padding"
    _textarea_id = "textarea"
    _textarea2_id = "textarea2"
    _textarea_one_line_id = "textarea-one-line"
    _textarea_rtl_id = "textarea-rtl"
    _contenteditable_id = "contenteditable"
    _contenteditable2_id = "contenteditable2"
    _content_id = "content"
    _content2_id = "content2"
    _non_selectable_id = "non-selectable"

    # Test html files.
    _selection_html = "test_carets_selection.html"
    _multipleline_html = "test_carets_multipleline.html"
    _multiplerange_html = "test_carets_multiplerange.html"
    _longtext_html = "test_carets_longtext.html"
    _iframe_html = "test_carets_iframe.html"
    _display_none_html = "test_carets_display_none.html"

    def setUp(self):
        # Code to execute before every test is running.
        super(AccessibleCaretSelectionModeTestCase, self).setUp()
        self.carets_tested_pref = "layout.accessiblecaret.enabled"
        self.prefs = {"layout.word_select.eat_space_to_next_word": False, self.carets_tested_pref: True}
        self.marionette.set_prefs(self.prefs)
        self.actions = Actions(self.marionette)

    def open_test_html(self, test_html):
        self.marionette.navigate(self.marionette.absolute_url(test_html))

    def word_offset(self, text, ordinal):
        "Get the character offset of the ordinal-th word in text."
        tokens = re.split(r"(\S+)", text)  # both words and spaces
        spaces = tokens[0::2]  # collect spaces at odd indices
        words = tokens[1::2]  # collect word at even indices

        if ordinal >= len(words):
            raise IndexError("Only %d words in text, but got ordinal %d" % (len(words), ordinal))

        # Cursor position of the targeting word is behind the the first
        # character in the word. For example, offset to 'def' in 'abc def' is
        # between 'd' and 'e'.
        offset = len(spaces[0]) + 1
        offset += sum(len(words[i]) + len(spaces[i + 1]) for i in range(ordinal))
        return offset

    def test_word_offset(self):
        text = " " * 3 + "abc" + " " * 3 + "def"

        self.assertTrue(self.word_offset(text, 0), 4)
        self.assertTrue(self.word_offset(text, 1), 10)
        with self.assertRaises(IndexError):
            self.word_offset(text, 2)

    def word_location(self, el, ordinal):
        """Get the location (x, y) of the ordinal-th word in el.

        The ordinal starts from 0.

        Note: this function has a side effect which changes focus to the
        target element el.

        """
        sel = SelectionManager(el)
        offset = self.word_offset(sel.content, ordinal)

        # Move the blinking cursor to the word.
        el.tap()
        sel.move_cursor_to_front()
        sel.move_cursor_by_offset(offset)
        x, y = sel.cursor_location()

        return x, y

    def rect_relative_to_window(self, el):
        """Get element's bounding rectangle.

        This function is similar to el.rect, but the coordinate is relative to
        the top left corner of the window instead of the document.

        """
        return self.marionette.execute_script(
            """
            let rect = arguments[0].getBoundingClientRect();
            return {x: rect.x, y:rect.y, width: rect.width, height: rect.height};
            """,
            script_args=[el],
        )

    def long_press_on_location(self, el, x=None, y=None):
        """Long press the location (x, y) to select a word.

        If no (x, y) are given, it will be targeted at the center of the
        element. On Windows, those spaces after the word will also be selected.
        This function sends synthesized eMouseLongTap to gecko.

        """
        rect = self.rect_relative_to_window(el)
        target_x = rect["x"] + (x if x is not None else rect["width"] // 2)
        target_y = rect["y"] + (y if y is not None else rect["height"] // 2)

        self.marionette.execute_script(
            """
            let Ci = Components.interfaces;
            let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
            utils.sendTouchEventToWindow('touchstart', [0],
                                         [arguments[0]], [arguments[1]],
                                         [1], [1], [0], [1], 1, 0);
            utils.sendMouseEventToWindow('mouselongtap', arguments[0], arguments[1],
                                          0, 1, 0);
            utils.sendTouchEventToWindow('touchend', [0],
                                         [arguments[0]], [arguments[1]],
                                         [1], [1], [0], [1], 1, 0);
            """,
            script_args=[target_x, target_y],
            sandbox="system",
        )

    def long_press_on_word(self, el, wordOrdinal):
        x, y = self.word_location(el, wordOrdinal)
        self.long_press_on_location(el, x, y)

    def to_unix_line_ending(self, s):
        """Changes all Windows/Mac line endings in s to UNIX line endings."""

        return s.replace("\r\n", "\n").replace("\r", "\n")

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    @parameterized(_content_id, el_id=_content_id)
    def test_long_press_to_select_a_word(self, el_id):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        self._test_long_press_to_select_a_word(el)

    def _test_long_press_to_select_a_word(self, el):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 2, "Expect at least two words in the content.")
        target_content = words[0]

        # Goal: Select the first word.
        self.long_press_on_word(el, 0)

        # Ignore extra spaces selected after the word.
        self.assertEqual(target_content, sel.selected_content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    @parameterized(_content_id, el_id=_content_id)
    def test_drag_carets(self, el_id):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")

        # Goal: Select all text after the first word.
        target_content = original_content[len(words[0]) :]

        # Get the location of the carets at the end of the content for later
        # use.
        el.tap()
        sel.select_all()
        end_caret_x, end_caret_y = sel.second_caret_location()

        self.long_press_on_word(el, 0)

        # Drag the second caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Drag the first caret to the previous position of the second caret.
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        self.assertEqual(target_content, sel.selected_content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    @parameterized(_content_id, el_id=_content_id)
    def test_drag_swappable_carets(self, el_id):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")

        target_content1 = words[0]
        target_content2 = original_content[len(words[0]) :]

        # Get the location of the carets at the end of the content for later
        # use.
        el.tap()
        sel.select_all()
        end_caret_x, end_caret_y = sel.second_caret_location()

        self.long_press_on_word(el, 0)

        # Drag the first caret to the end and back to where it was
        # immediately. The selection range should not be collapsed.
        caret1_x, caret1_y = sel.first_caret_location()
        self.actions.flick(el, caret1_x, caret1_y, end_caret_x, end_caret_y).flick(
            el, end_caret_x, end_caret_y, caret1_x, caret1_y
        ).perform()
        self.assertEqual(target_content1, sel.selected_content)

        # Drag the first caret to the end.
        caret1_x, caret1_y = sel.first_caret_location()
        self.actions.flick(el, caret1_x, caret1_y, end_caret_x, end_caret_y).perform()
        self.assertEqual(target_content2, sel.selected_content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    @parameterized(_content_id, el_id=_content_id)
    def test_minimum_select_one_character(self, el_id):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        self._test_minimum_select_one_character(el)

    @parameterized(_textarea2_id, el_id=_textarea2_id)
    @parameterized(_contenteditable2_id, el_id=_contenteditable2_id)
    @parameterized(_content2_id, el_id=_content2_id)
    def test_minimum_select_one_character2(self, el_id):
        self.open_test_html(self._multipleline_html)
        el = self.marionette.find_element(By.ID, el_id)
        self._test_minimum_select_one_character(el)

    def _test_minimum_select_one_character(self, el, x=None, y=None):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")

        # Get the location of the carets at the end of the content for later
        # use.
        sel.select_all()
        end_caret_x, end_caret_y = sel.second_caret_location()
        el.tap()

        # Goal: Select the first character.
        target_content = original_content[0]

        if x and y:
            # If we got x and y from the arguments, use it as a hint of the
            # location of the first word
            pass
        else:
            x, y = self.word_location(el, 0)
        self.long_press_on_location(el, x, y)

        # Drag the second caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Drag the second caret to the position of the first caret.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()

        self.assertEqual(target_content, sel.selected_content)

    @parameterized(_input_id + "_to_" + _textarea_id, el1_id=_input_id, el2_id=_textarea_id)
    @parameterized(_input_id + "_to_" + _contenteditable_id, el1_id=_input_id, el2_id=_contenteditable_id)
    @parameterized(_input_id + "_to_" + _content_id, el1_id=_input_id, el2_id=_content_id)
    @parameterized(_textarea_id + "_to_" + _input_id, el1_id=_textarea_id, el2_id=_input_id)
    @parameterized(_textarea_id + "_to_" + _contenteditable_id, el1_id=_textarea_id, el2_id=_contenteditable_id)
    @parameterized(_textarea_id + "_to_" + _content_id, el1_id=_textarea_id, el2_id=_content_id)
    @parameterized(_contenteditable_id + "_to_" + _input_id, el1_id=_contenteditable_id, el2_id=_input_id)
    @parameterized(_contenteditable_id + "_to_" + _textarea_id, el1_id=_contenteditable_id, el2_id=_textarea_id)
    @parameterized(_contenteditable_id + "_to_" + _content_id, el1_id=_contenteditable_id, el2_id=_content_id)
    @parameterized(_content_id + "_to_" + _input_id, el1_id=_content_id, el2_id=_input_id)
    @parameterized(_content_id + "_to_" + _textarea_id, el1_id=_content_id, el2_id=_textarea_id)
    @parameterized(_content_id + "_to_" + _contenteditable_id, el1_id=_content_id, el2_id=_contenteditable_id)
    def test_long_press_changes_focus_from(self, el1_id, el2_id):
        """Test the focus could be changed from el1 to el2 by long press.

        If the focus is changed to e2 successfully, the carets should appear and
        could be dragged.

        """
        # Goal: Tap to focus el1, and then select the first character on
        # el2.

        # We want to collect the location of the first word in el2 here
        # since self.word_location() has the side effect which would
        # change the focus.
        self.open_test_html(self._selection_html)
        el1 = self.marionette.find_element(By.ID, el1_id)
        el2 = self.marionette.find_element(By.ID, el2_id)
        x, y = self.word_location(el2, 0)
        el1.tap()
        self._test_minimum_select_one_character(el2, x=x, y=y)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_focus_not_changed_by_long_press_on_non_selectable(self, el_id):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        non_selectable = self.marionette.find_element(By.ID, self._non_selectable_id)

        # Goal: Focus remains on the editable element el after long pressing on
        # the non-selectable element.
        sel = SelectionManager(el)
        self.long_press_on_word(el, 0)
        self.long_press_on_location(non_selectable)
        active_sel = SelectionManager(self.marionette.get_active_element())
        self.assertEqual(sel.content, active_sel.content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_textarea_id, el_id=_textarea_id)
    @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    @parameterized(_content_id, el_id=_content_id)
    def test_handle_tilt_when_carets_overlap_each_other(self, el_id):
        """Test tilt handling when carets overlap to each other.

        Let the two carets overlap each other. If they are set to tilted
        successfully, tapping the tilted carets should not cause the selection
        to be collapsed and the carets should be draggable.

        """
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")

        # Goal: Select the first word.
        self.long_press_on_word(el, 0)
        target_content = sel.selected_content

        # Drag the first caret to the position of the second caret to trigger
        # carets overlapping.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        # We make two hit tests targeting the left edge of the left tilted caret
        # and the right edge of the right tilted caret. If either of the hits is
        # missed, selection would be collapsed and both carets should not be
        # draggable.
        (caret3_x, caret3_y), (caret4_x, caret4_y) = sel.carets_location()

        # The following values are from ua.css and all.js
        caret_width = float(self.marionette.get_pref("layout.accessiblecaret.width"))
        caret_margin_left = float(self.marionette.get_pref("layout.accessiblecaret.margin-left"))
        tilt_right_margin_left = 0.41 * caret_width
        tilt_left_margin_left = -0.39 * caret_width

        left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
        el.tap(left_caret_left_edge_x + 2, caret3_y)

        right_caret_right_edge_x = caret4_x + caret_margin_left + tilt_right_margin_left + caret_width
        el.tap(right_caret_right_edge_x - 2, caret4_y)

        # Drag the first caret back to the initial selection, the first word.
        self.actions.flick(el, caret3_x, caret3_y, caret1_x, caret1_y).perform()

        self.assertEqual(target_content, sel.selected_content)

    def test_drag_caret_over_non_selectable_field(self):
        """Test dragging the caret over a non-selectable field.

        The selected content should exclude non-selectable elements and the
        second caret should appear in last range's position.

        """
        self.open_test_html(self._multiplerange_html)
        body = self.marionette.find_element(By.ID, "bd")
        sel3 = self.marionette.find_element(By.ID, "sel3")
        sel4 = self.marionette.find_element(By.ID, "sel4")
        sel6 = self.marionette.find_element(By.ID, "sel6")

        # Select target element and get target caret location
        self.long_press_on_word(sel4, 3)
        sel = SelectionManager(body)
        end_caret_x, end_caret_y = sel.second_caret_location()

        self.long_press_on_word(sel6, 0)
        end_caret2_x, end_caret2_y = sel.second_caret_location()

        # Select start element
        self.long_press_on_word(sel3, 3)

        # Drag end caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(body, caret2_x, caret2_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()), "this 3\nuser can select this")

        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(body, caret2_x, caret2_y, end_caret2_x, end_caret2_y, 1).perform()
        self.assertEqual(
            self.to_unix_line_ending(sel.selected_content.strip()),
            "this 3\nuser can select this 4\nuser can select this 5\nuser",
        )

        # Drag first caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(body, caret1_x, caret1_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()), "4\nuser can select this 5\nuser")

    def test_drag_swappable_caret_over_non_selectable_field(self):
        self.open_test_html(self._multiplerange_html)
        body = self.marionette.find_element(By.ID, "bd")
        sel3 = self.marionette.find_element(By.ID, "sel3")
        sel4 = self.marionette.find_element(By.ID, "sel4")
        sel = SelectionManager(body)

        self.long_press_on_word(sel4, 3)
        (end_caret1_x, end_caret1_y), (end_caret2_x, end_caret2_y) = sel.carets_location()

        self.long_press_on_word(sel3, 3)
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()

        # Drag the first caret down, which will across the second caret.
        self.actions.flick(body, caret1_x, caret1_y, end_caret1_x, end_caret1_y).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()), "3\nuser can select")

        # The old second caret becomes the first caret. Drag it down again.
        self.actions.flick(body, caret2_x, caret2_y, end_caret2_x, end_caret2_y).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()), "this")

    def test_drag_caret_to_beginning_of_a_line(self):
        """Bug 1094056
        Test caret visibility when caret is dragged to beginning of a line
        """
        self.open_test_html(self._multiplerange_html)
        body = self.marionette.find_element(By.ID, "bd")
        sel1 = self.marionette.find_element(By.ID, "sel1")
        sel2 = self.marionette.find_element(By.ID, "sel2")

        # Select the first word in the second line
        self.long_press_on_word(sel2, 0)
        sel = SelectionManager(body)
        (start_caret_x, start_caret_y), (end_caret_x, end_caret_y) = sel.carets_location()

        # Select target word in the first line
        self.long_press_on_word(sel1, 2)

        # Drag end caret to the beginning of the second line
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(body, caret2_x, caret2_y, start_caret_x, start_caret_y).perform()

        # Drag end caret back to the target word
        self.actions.flick(body, start_caret_x, start_caret_y, caret2_x, caret2_y).perform()

        self.assertEqual(self.to_unix_line_ending(sel.selected_content), "select")

    @skip_if_not_rotatable
    def test_caret_position_after_changing_orientation_of_device(self):
        """Bug 1094072
        If positions of carets are updated correctly, they should be draggable.
        """
        self.open_test_html(self._longtext_html)
        body = self.marionette.find_element(By.ID, "bd")
        longtext = self.marionette.find_element(By.ID, "longtext")

        # Select word in portrait mode, then change to landscape mode
        self.marionette.set_orientation("portrait")
        self.long_press_on_word(longtext, 12)
        sel = SelectionManager(body)
        (p_start_caret_x, p_start_caret_y), (p_end_caret_x, p_end_caret_y) = sel.carets_location()
        self.marionette.set_orientation("landscape")
        (l_start_caret_x, l_start_caret_y), (l_end_caret_x, l_end_caret_y) = sel.carets_location()

        # Drag end caret to the start caret to change the selected content
        self.actions.flick(body, l_end_caret_x, l_end_caret_y, l_start_caret_x, l_start_caret_y).perform()

        # Change orientation back to portrait mode to prevent affecting
        # other tests
        self.marionette.set_orientation("portrait")

        self.assertEqual(self.to_unix_line_ending(sel.selected_content), "o")

    def test_select_word_inside_an_iframe(self):
        """Bug 1088552
        The scroll offset in iframe should be taken into consideration properly.
        In this test, we scroll content in the iframe to the bottom to cause a
        huge offset. If we use the right coordinate system, selection should
        work. Otherwise, it would be hard to trigger select word.
        """
        self.open_test_html(self._iframe_html)
        iframe = self.marionette.find_element(By.ID, "frame")

        # switch to inner iframe and scroll to the bottom
        self.marionette.switch_to_frame(iframe)
        self.marionette.execute_script('document.getElementById("bd").scrollTop += 999')

        # long press to select bottom text
        body = self.marionette.find_element(By.ID, "bd")
        sel = SelectionManager(body)
        self._bottomtext = self.marionette.find_element(By.ID, "bottomtext")
        self.long_press_on_location(self._bottomtext)

        self.assertNotEqual(self.to_unix_line_ending(sel.selected_content), "")

    def test_carets_initialized_in_display_none(self):
        """Test AccessibleCaretEventHub is properly initialized on a <html> with
        display: none.

        """
        self.open_test_html(self._display_none_html)
        html = self.marionette.find_element(By.ID, "html")
        content = self.marionette.find_element(By.ID, "content")

        # Remove 'display: none' from <html>
        self.marionette.execute_script('arguments[0].style.display = "unset";', script_args=[html])

        # If AccessibleCaretEventHub is initialized successfully, select a word
        # should work.
        self._test_long_press_to_select_a_word(content)

    def test_long_press_to_select_when_partial_visible_word_is_selected(self):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, self._input_id)
        sel = SelectionManager(el)

        # To successfully select the second word while the first word is being
        # selected, use sufficient spaces between 'a' and 'b' to avoid the
        # second caret covers on the second word.
        original_content = "aaaaaaaa          bbbbbbbb"
        el.clear()
        el.send_keys(original_content)
        words = original_content.split()

        # We cannot use self.long_press_on_word() directly since it has will
        # change the cursor position which affects this test. We have to store
        # the position of word 0 and word 1 before long-pressing to select the
        # word.
        word0_x, word0_y = self.word_location(el, 0)
        word1_x, word1_y = self.word_location(el, 1)

        self.long_press_on_location(el, word0_x, word0_y)
        self.assertEqual(words[0], sel.selected_content)

        self.long_press_on_location(el, word1_x, word1_y)
        self.assertEqual(words[1], sel.selected_content)

        self.long_press_on_location(el, word0_x, word0_y)
        self.assertEqual(words[0], sel.selected_content)

        # If the second carets is visible, it can be dragged to the position of
        # the first caret. After that, selection will contain only the first
        # character.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()
        self.assertEqual(words[0][0], sel.selected_content)

    @parameterized(_input_id, el_id=_input_id)
    @parameterized(_input_padding_id, el_id=_input_padding_id)
    @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
    @parameterized(_contenteditable_id, el_id=_contenteditable_id)
    def test_carets_not_jump_when_dragging_to_editable_content_boundary(self, el_id):
        self.open_test_html(self._selection_html)
        el = self.marionette.find_element(By.ID, el_id)
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 3, "Expect at least three words in the content.")

        # Goal: the selection is not changed after dragging the caret on the
        # Y-axis.
        target_content = words[1]

        self.long_press_on_word(el, 1)
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()

        # Drag the first caret up by 50px.
        self.actions.flick(el, caret1_x, caret1_y, caret1_x, caret1_y - 50).perform()
        self.assertEqual(target_content, sel.selected_content)

        # Drag the second caret down by 50px.
        self.actions.flick(el, caret2_x, caret2_y, caret2_x, caret2_y + 50).perform()
        self.assertEqual(target_content, sel.selected_content)
Esempio n. 21
0
class Loop(object):

    """Object representing the Loop application.
    """

    def __init__(self, parent):
        self.apps = parent.apps
        self.data_layer = parent.data_layer
        self.parent = parent
        self.marionette = parent.marionette
        self.UTILS = parent.UTILS
        self.actions = Actions(self.marionette)
        self.browser = Browser(self.parent)
        self.app_name = "Firefox Hello"
        self.market_url = "https://owd.tid.es/B3lg1r89n/market/appList.html"
        self.persistent_directory = "/data/local/storage/persistent"
        self.loop_dir = self.UTILS.general.get_config_variable("install_dir", "loop")

    def launch(self):
        """
        Launch the app.
        """
        self.app = self.apps.launch(self.app_name)
        self.UTILS.element.waitForNotElements(DOM.GLOBAL.loading_overlay,
                                              self.__class__.__name__ + " app - loading overlay")
        return self.app

    def is_installed(self):
        return self.apps.is_app_installed(self.app_name)

    def install(self):
        via = self.UTILS.general.get_config_variable("install_via", "loop")
        if via == "Grunt":
            self.install_via_grunt()
        elif via == "Market":
            self.install_via_marketplace()
        else:
            self.UTILS.test.test(False, "Not valid way to install Loop")

    def install_via_grunt(self, version="1.1"):
        self.UTILS.reporting.logResult('info', 'Installing via grunt....')
        c1 = 'cd {}'.format(self.loop_dir)
        c2 = 'git checkout {}'.format(version)
        c3 = 'git fetch && git merge origin/{}'.format(version)
        c4 = 'grunt build'

        with open(os.devnull, 'w') as DEVNULL:
            result = subprocess32.check_output("; ".join([c1, c2, c3, c4]), shell=True, stderr=DEVNULL)

        self.marionette.switch_to_frame()
        msg = "{} installed".format(self.app_name)
        installed_app_msg = (DOM.GLOBAL.system_banner_msg[0], DOM.GLOBAL.system_banner_msg[1].format(msg))
        self.UTILS.element.waitForElements(installed_app_msg, "App installed", timeout=30)

        install_ok_msg = "Done, without errors."
        self.UTILS.reporting.logResult('info', "Result of this test script: {}".format(result))
        self.UTILS.test.test(install_ok_msg in result, "Install via grunt is OK")

    def install_via_marketplace(self):
        self.UTILS.reporting.logResult('info', 'Installing via marketplace....')
        # Make sure we install the latest version
        self.update_and_publish()

        self.browser.launch()
        time.sleep(1)
        self.browser.open_url(self.market_url)

        loop_link = self.UTILS.element.getElement(
            ('xpath', '//p[contains(text(), "{}")]'.format(self.app_name)), "App link")
        loop_link.tap()

        self.marionette.switch_to_frame()
        install_ok = self.UTILS.element.getElement(DOM.GLOBAL.app_install_ok, "Install button")
        install_ok.tap()

        msg = "{} installed".format(self.app_name)
        installed_app_msg = (DOM.GLOBAL.system_banner_msg[0], DOM.GLOBAL.system_banner_msg[1].format(msg))
        self.UTILS.element.waitForElements(installed_app_msg, "App installed", timeout=30)

    def reinstall(self):
        self.uninstall()
        time.sleep(2)
        self.install()

    def uninstall(self):
        self.UTILS.reporting.logResult('info', "uninstalling.........")
        self.apps.uninstall(self.app_name)
        self.parent.wait_for_condition(lambda m: not self.apps.is_app_installed(
            self.app_name), timeout=20, message="{} is not installed".format(self.app_name))

    def update_and_publish(self):
        self.publish_loop_dir = self.UTILS.general.get_config_variable("aux_files", "loop")

        c1 = 'cd {}'.fomrat(self.publish_loop_dir)
        c2 = './publish_app.sh {}'.format(self.loop_dir)
        with open(os.devnull, 'w') as DEVNULL:
            result = subprocess32.check_output("; ".join([c1, c2]), shell=True, stderr=DEVNULL)

        chops = result.split("\n")
        self.UTILS.reporting.logResult('info', "result: {}".format(chops))
        self.UTILS.test.test("And all done, hopefully." in chops, "The script to publish an app is OK", True)

    def _fill_fxa_field(self, field_locator, text):
        """ Auxiliary method to fill "Firefox account login" fields
        """
        self.UTILS.reporting.logResult('info', '[firefox_login] Filling fxa field with text: {}'.format(text))
        self.parent.wait_for_element_displayed(*field_locator)
        fxa_input = self.marionette.find_element(*field_locator)
        fxa_input.send_keys(text)
        time.sleep(2)

        self.parent.wait_for_condition(
            lambda m: m.find_element(*DOM.Loop.ffox_account_login_next).get_attribute("disabled") != "disabled")
        next_btn = self.marionette.find_element(*DOM.Loop.ffox_account_login_next)
        next_btn.tap()
        self.parent.wait_for_element_not_displayed(*DOM.Loop.ffox_account_login_overlay)

    def tap_on_firefox_login_button(self):
        ffox_btn = self.marionette.find_element(*DOM.Loop.wizard_login_ffox_account)
        self.UTILS.element.simulateClick(ffox_btn)

    def _tap_on_phone_login_button(self):
        phone_btn = self.marionette.find_element(*DOM.Loop.wizard_login_phone_number)
        self.UTILS.element.simulateClick(phone_btn)

    def _get_mobile_id_options(self):
        return self.marionette.find_elements(*DOM.Loop.mobile_id_sim_list_item)

    def wizard_or_login(self):
        """ Checks if we have to skip the Wizard, log in, or if we're already at the main screen of Loop

            For the first two scenarios, it returns True.
            If we are already inside Loop, it returns False.
        """
        # TODO: switch try-except code -> first check login instead of wizard
        #      see if it works, when the wizard is first
        try:
            self.parent.wait_for_element_displayed(*DOM.Loop.wizard_header)
            self.UTILS.reporting.logResult('info', '[wizard_or_login] Wizard')
            self.skip_wizard()
            return True
        except:
            self.UTILS.reporting.logResult('info', '[wizard_or_login] Login')
            try:
                self.parent.wait_for_element_displayed(*DOM.Loop.wizard_login)
                return True
            except:
                self.UTILS.reporting.logResult('info', '[wizard_or_login] Loop')
                try:
                    self.parent.wait_for_element_displayed(*DOM.Loop.app_header)
                    return False
                except:
                    self.UTILS.test.test(False, "Ooops. Something went wrong", True)

    def get_wizard_steps(self):
        """ Returns the number of steps of the wizard
        """
        return len(self.marionette.find_elements(*DOM.Loop.wizard_slideshow_step))

    def skip_wizard(self):
        """ Skips first time use wizard by flicking the screen
        """
        time.sleep(1)
        wizard_steps = self.get_wizard_steps()

        current_frame = self.apps.displayed_app.frame
        x_start = current_frame.size['width'] // 2
        x_end = x_start // 4
        y_start = current_frame.size['height'] // 2

        for i in range(wizard_steps):
            self.actions.flick(current_frame, x_start, y_start, x_end, y_start, duration=600).perform()
            time.sleep(1)

        self.marionette.switch_to_frame(self.apps.displayed_app.frame_id)
        self.parent.wait_for_element_displayed(DOM.Loop.wizard_login[0], DOM.Loop.wizard_login[1], timeout=10)

    def firefox_login(self, email, password, is_wrong=False):
        """ Logs in using Firefox account
        """
        self.tap_on_firefox_login_button()

        self.UTILS.iframe.switchToFrame(*DOM.Loop.ffox_account_frame_locator)
        self.parent.wait_for_element_displayed(
            DOM.Loop.ffox_account_login_title[0], DOM.Loop.ffox_account_login_title[1], timeout=20)

        self._fill_fxa_field(DOM.Loop.ffox_account_login_mail, email)
        self._fill_fxa_field(DOM.Loop.ffox_account_login_pass, password)

        if not is_wrong:
            done_btn = self.marionette.find_element(*DOM.Loop.ffox_account_login_done)
            done_btn.tap()

    def phone_login_auto(self, phone_number, option_number=1):
        """Wrapper to log in using phone number, either the already selected or entering it manually"""
        try:
            self.UTILS.reporting.info("Trying phone login using Mobile ID")
            self.phone_login()
        except:
            self.UTILS.reporting.info("Mobile ID login failed, falling back to manual")
            self.UTILS.iframe.switchToFrame(*DOM.Loop.mobile_id_frame_locator)
            self.marionette.find_element('id', 'header').tap(25, 25)
            self.UTILS.iframe.switchToFrame(*DOM.Loop.frame_locator)
            self.phone_login_manually(phone_number)

    @retry(3, context=("OWDTestToolkit.apps.loop", "Loop"), aux_func_name="retry_phone_login")
    def phone_login(self, option_number=1):
        """ Logs in using mobile id
        """
        self._tap_on_phone_login_button()
        self.UTILS.iframe.switchToFrame(*DOM.Loop.mobile_id_frame_locator)
        self.parent.wait_for_element_not_displayed(*DOM.Loop.ffox_account_login_overlay)

        mobile_id_header = ("xpath", DOM.GLOBAL.app_head_specific.format(_("Mobile ID")))
        self.parent.wait_for_element_displayed(*mobile_id_header)

        options = self._get_mobile_id_options()
        if len(options) > 1:
            # Option number refers to the SIM number (1 or 2), not to the position in the array
            options[option_number - 1].tap()

        allow_button = self.marionette.find_element(*DOM.Loop.mobile_id_allow_button)
        allow_button.tap()

        try:
            self.parent.wait_for_element_displayed(
                DOM.Loop.mobile_id_verified_button[0], DOM.Loop.mobile_id_verified_button[1], timeout=30)
            verified_button = self.marionette.find_element(*DOM.Loop.mobile_id_verified_button)
            self.UTILS.element.simulateClick(verified_button)
        except:
            self.parent.wait_for_condition(lambda m: "state-sending" in m.find_element(
                *DOM.Loop.mobile_id_allow_button).get_attribute("class"), timeout=5, message="Button is still sending")
            # Make @retry do its work
            raise

        self.apps.switch_to_displayed_app()

    @retry(3, context=("OWDTestToolkit.apps.loop", "Loop"), aux_func_name="retry_phone_login")
    def phone_login_manually(self, phone_number_without_prefix):
        """
        Logs in using mobile id, but instead of using the automatically provided by the app
        selecting the manual option
        @phone_number_without_prefix str Phone number to register into Loop
        NOTE: for the shake of simplicity, we assume the prefix is the spanish one (+34) by default
        """

        self._tap_on_phone_login_button()
        self.UTILS.iframe.switchToFrame(*DOM.Loop.mobile_id_frame_locator)
        self.parent.wait_for_element_not_displayed(*DOM.Loop.ffox_account_login_overlay)

        mobile_id_header = ("xpath", DOM.GLOBAL.app_head_specific.format(_("Mobile ID")))
        self.parent.wait_for_element_displayed(*mobile_id_header)

        # Manually!
        manually_link = self.marionette.find_element(*DOM.Loop.mobile_id_add_phone_number)
        manually_link.tap()

        self.parent.wait_for_element_displayed(*DOM.Loop.mobile_id_add_phone_number_number)
        phone_input = self.marionette.find_element(*DOM.Loop.mobile_id_add_phone_number_number)
        phone_input.send_keys(phone_number_without_prefix)

        # NOTE: before you virtually kill me, I cannot take this duplicated code into another
        # separated method due to the @reply decorator. Just letting you know :).
        allow_button = self.marionette.find_element(*DOM.Loop.mobile_id_allow_button)
        allow_button.tap()

        try:
            self.parent.wait_for_element_displayed(
                DOM.Loop.mobile_id_verified_button[0], DOM.Loop.mobile_id_verified_button[1], timeout=30)
            verified_button = self.marionette.find_element(*DOM.Loop.mobile_id_verified_button)
            self.UTILS.element.simulateClick(verified_button)
        except:
            self.parent.wait_for_condition(lambda m: "state-sending" in m.find_element(
                *DOM.Loop.mobile_id_allow_button).get_attribute("class"), timeout=5, message="Button is still sending")
            # Make @retry do its work
            raise

        self.apps.switch_to_displayed_app()

    @retry(5, context=("OWDTestToolkit.apps.loop", "Loop"), aux_func_name="retry_ffox_login")
    def allow_permission_ffox_login(self):
        """ Allows Loop to read our contacts

        This method checks whether is necessary to allow extra permissions for loop or not ater
        loggin in with Firefox accounts

        Also, since this is is the last step before connecting to the Loop server, it checks
        that no error has been raised. If that happens, it retries the connection up to 5 times.
        """
        self.marionette.switch_to_frame()
        try:
            self.UTILS.reporting.debug("Looking for permission panel....")
            self.parent.wait_for_element_displayed(
                DOM.GLOBAL.app_permission_dialog[0], DOM.GLOBAL.app_permission_dialog[1], timeout=10)
        except:
            try:
                self.UTILS.reporting.debug("Now looking for permission Loop main view....")
                self.apps.switch_to_displayed_app()
                self.parent.wait_for_element_displayed(*DOM.Loop.app_header)
                return
            except:
                self.UTILS.reporting.debug("And Now looking for error....")
                self.marionette.switch_to_frame()
                self.parent.wait_for_element_displayed(*DOM.GLOBAL.modal_dialog_alert_title)
                self.UTILS.reporting.logResult('info', "Error connecting...")
                # Make @retry do its work
                raise

        msg_text = self.marionette.find_element(*DOM.GLOBAL.app_permission_msg).text
        self.UTILS.test.test(self.app_name in msg_text, "Permissions for loop")

        allow_btn = self.marionette.find_element(*DOM.GLOBAL.app_permission_btn_yes)
        self.UTILS.element.simulateClick(allow_btn)

        self.apps.switch_to_displayed_app()

    def allow_permission_phone_login(self):
        """ Allows Loop to read our contacts

        This method checks whether is necessary to allow extra permissions for loop or not ater
        loggin in with Mobile ID

        Also, since this is is the last step before connecting to the Loop server, it checks
        that no error has been raised. If that happens, it retries the connection up to 5 times.
        """
        self.marionette.switch_to_frame()
        try:
            self.UTILS.reporting.debug("Looking for permission panel....")
            self.parent.wait_for_element_displayed(
                DOM.GLOBAL.app_permission_dialog[0], DOM.GLOBAL.app_permission_dialog[1], timeout=10)
        except:
            self.UTILS.reporting.debug("Now looking for permission Loop main view....")
            self.apps.switch_to_displayed_app()
            self.parent.wait_for_element_displayed(*DOM.Loop.app_header)
            return

        msg_text = self.marionette.find_element(*DOM.GLOBAL.app_permission_msg).text
        self.UTILS.test.test(self.app_name in msg_text, "Permissions for loop")

        allow_btn = self.marionette.find_element(*DOM.GLOBAL.app_permission_btn_yes)
        self.UTILS.element.simulateClick(allow_btn)

        self.apps.switch_to_displayed_app()

    def retry_ffox_login(self):
        """ Retry Ffox account login if it has failed.

        This method is called as the aux_func for our brand new retry decorator
        """

        self.UTILS.reporting.logResult('info', "Retrying FxA login...")
        self.apps.switch_to_displayed_app()
        time.sleep(2)

        self.parent.wait_for_element_displayed(*DOM.Loop.error_screen_ok)
        ok_btn = self.marionette.find_element(*DOM.Loop.error_screen_ok)
        self.UTILS.element.simulateClick(ok_btn)

        self.parent.wait_for_element_displayed(*DOM.Loop.wizard_login)
        self.tap_on_firefox_login_button()

    def retry_phone_login(self):
        """ Retry phone login if it has failed
        """
        self.UTILS.reporting.logResult('info', "Retrying phone login...")
        self.parent.wait_for_element_displayed(*DOM.Loop.mobile_id_back)
        back_btn = self.marionette.find_element(*DOM.Loop.mobile_id_back)
        self.UTILS.element.simulateClick(back_btn)

        self.apps.switch_to_displayed_app()
        time.sleep(2)
        self._tap_on_phone_login_button()

    def open_settings(self):
        """ Open settings panel from call log 
        """
        self.parent.wait_for_element_displayed(*DOM.Loop.open_settings_btn)
        settings_btn = self.marionette.find_element(*DOM.Loop.open_settings_btn)
        self.UTILS.element.simulateClick(settings_btn)
        self.parent.wait_for_element_displayed(*DOM.Loop.settings_panel_header)

    def logout(self, confirm=True):
        """ This methods logs us out from Loop.

        It assumes we already are in the Loop Settings panel
        """
        try:
            self.parent.wait_for_element_displayed(*DOM.Loop.settings_logout)
        except:
            self.UTILS.reporting.logResult('info', "Already logged out")
            return
        logout_btn = self.marionette.find_element(*DOM.Loop.settings_logout)
        self.UTILS.element.simulateClick(logout_btn)

        self.parent.wait_for_element_displayed(*DOM.Loop.form_confirm_logout)
        if confirm:
            confirm_btn = self.marionette.find_element(*DOM.Loop.form_confirm_logout)
            self.UTILS.element.simulateClick(confirm_btn)
            self.parent.wait_for_element_not_displayed(*DOM.Loop.loading_overlay)
            self.parent.wait_for_element_displayed(*DOM.Loop.wizard_login)
        else:
            cancel_btn = self.marionette.find_element(*DOM.Loop.form_confirm_cancel)
            self.parent.wait_for_element_displayed(*DOM.Loop.settings_panel_header)

    def switch_to_urls(self):
        self.parent.wait_for_element_displayed(*DOM.Loop.call_log_shared_links_tab)
        tab = self.marionette.find_element(*DOM.Loop.call_log_shared_links_tab)
        tab.tap()
        self.parent.wait_for_condition(lambda m: tab.get_attribute(
            "aria-selected") == "true", timeout=10, message="Checking that 'Shared links' is selected")

    def _get_number_of_urls(self, locator):
        try:
            entries = self.marionette.find_elements(*locator)
            return len(entries)
        except:
            return 0

    def get_number_of_all_urls(self):
        return self._get_number_of_urls(DOM.Loop.shared_links_entry)

    def get_number_of_available_urls(self):
        return self._get_number_of_urls(DOM.Loop.shared_links_entry_available)

    def get_number_of_revoked_urls(self):
        return self._get_number_of_urls(DOM.Loop.shared_links_entry_revoked)

    def _select_action(self, action):
        elem = (DOM.Loop.form_action_action[0], DOM.Loop.form_action_action[1].format(action))
        self.parent.wait_for_element_displayed(*elem)
        self.marionette.find_element(*elem).tap()

    def _confirm(self, decission):
        elem = DOM.Loop.form_confirm_delete if decission else DOM.Loop.form_confirm_cancel
        self.parent.wait_for_element_displayed(*elem)
        self.marionette.find_element(*elem).tap()

    def _is_entry_revoked(self, entry):
        try:
            entry.find_element(*DOM.Loop.shared_links_entry_revoked_nested)
            return True
        except Exception, e:
            self.UTILS.reporting.logResult('info', "What happened here? {}".format(e))
            return False
class AccessibleCaretCursorModeTestCase(MarionetteTestCase):
    '''Test cases for AccessibleCaret under cursor mode, aka touch caret.

    '''

    def setUp(self):
        # Code to execute before a test is being run.
        super(AccessibleCaretCursorModeTestCase, self).setUp()
        self.caret_tested_pref = 'layout.accessiblecaret.enabled'
        self.caret_timeout_ms_pref = 'layout.accessiblecaret.timeout_ms'
        self.prefs = {
            self.caret_tested_pref: True,
            self.caret_timeout_ms_pref: 0,
        }
        self.marionette.set_prefs(self.prefs)
        self.actions = Actions(self.marionette)

    def timeout_ms(self):
        'Return touch caret expiration time in milliseconds.'
        return self.marionette.get_pref(self.caret_timeout_ms_pref)

    def open_test_html(self):
        'Open html for testing and locate elements.'
        test_html = self.marionette.absolute_url('test_touchcaret.html')
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(By.ID, 'input')
        self._textarea = self.marionette.find_element(By.ID, 'textarea')
        self._contenteditable = self.marionette.find_element(By.ID, 'contenteditable')

    def _test_move_caret_to_the_right_by_one_character(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content
        target_content = target_content[:1] + content_to_add + target_content[1:]

        # Get touch caret (x, y) at position 1 and 2.
        el.tap()
        sel.move_caret_to_front()
        caret0_x, caret0_y = sel.caret_location()
        touch_caret0_x, touch_caret0_y = sel.touch_caret_location()
        sel.move_caret_by_offset(1)
        touch_caret1_x, touch_caret1_y = sel.touch_caret_location()

        # Tap the front of the input to make touch caret appear.
        el.tap(caret0_x, caret0_y)

        # Move touch caret
        self.actions.flick(el, touch_caret0_x, touch_caret0_y,
                           touch_caret1_x, touch_caret1_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content + content_to_add

        # Tap the front of the input to make touch caret appear.
        el.tap()
        sel.move_caret_to_front()
        el.tap(*sel.caret_location())

        # Move touch caret to the bottom-right corner of the element.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = el.size['width'], el.size['height']
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = content_to_add + sel.content

        # Get touch caret location at the front.
        el.tap()
        sel.move_caret_to_front()
        dest_x, dest_y = sel.touch_caret_location()

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())
        src_x, src_y = sel.touch_caret_location()

        # Move touch caret to the front of the input box.
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(target_content, sel.content)

    def _test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Get touch caret expiration time in millisecond, and convert it to second.
        timeout = self.timeout_ms() / 1000.0

        # Set a 3x timeout margin to prevent intermittent test failures.
        timeout *= 3

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Wait until touch caret disappears, then pretend to move it to the
        # top-left corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.wait(timeout).flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(non_target_content, sel.content)

    def _test_touch_caret_hides_after_receiving_wheel_event(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Send an arbitrary scroll-down-10px wheel event to the center of the
        # input box to hide touch caret. Then pretend to move it to the top-left
        # corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        el_center_x, el_center_y = el.rect['x'], el.rect['y']
        self.marionette.execute_script(
            '''
            var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                              .getInterface(Components.interfaces.nsIDOMWindowUtils);
            utils.sendWheelEvent(arguments[0], arguments[1],
                                 0, 10, 0, WheelEvent.DOM_DELTA_PIXEL,
                                 0, 0, 0, 0);
            ''',
            script_args=[el_center_x, el_center_y],
            sandbox='system'
        )
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(non_target_content, sel.content)

    def _test_caret_not_appear_when_typing_in_scrollable_content(self, el, assertFunc):
        sel = SelectionManager(el)

        content_to_add = '!'
        target_content = sel.content + string.ascii_letters + content_to_add

        el.tap()
        sel.move_caret_to_end()

        # Insert a long string to the end of the <input>, which triggers
        # ScrollPositionChanged event.
        el.send_keys(string.ascii_letters)

        # The caret should not be visible. If it does appear wrongly due to the
        # ScrollPositionChanged event, we can drag it to the front of the
        # <input> to change the cursor position.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        # The content should be inserted at the end of the <input>.
        el.send_keys(content_to_add)

        assertFunc(target_content, sel.content)

    ########################################################################
    # <input> test cases with touch caret enabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(self._input, self.assertEqual)

    def test_input_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._input, self.assertEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._input, self.assertEqual)

    def test_input_caret_not_appear_when_typing_in_scrollable_content(self):
        self.open_test_html()
        self._test_caret_not_appear_when_typing_in_scrollable_content(self._input, self.assertEqual)

    def test_input_touch_caret_timeout(self):
        with self.marionette.using_prefs({self.caret_timeout_ms_pref: 1000}):
            self.open_test_html()
            self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._input, self.assertNotEqual)

    ########################################################################
    # <input> test cases with touch caret disabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_the_right_by_one_character(self._input, self.assertNotEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret enabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._textarea, self.assertEqual)

    def test_textarea_touch_caret_timeout(self):
        with self.marionette.using_prefs({self.caret_timeout_ms_pref: 1000}):
            self.open_test_html()
            self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._textarea, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret disabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertNotEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._textarea, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with touch caret enabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._contenteditable, self.assertEqual)

    def test_contenteditable_touch_caret_timeout(self):
        with self.marionette.using_prefs({self.caret_timeout_ms_pref: 1000}):
            self.open_test_html()
            self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._contenteditable, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with touch caret disabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._contenteditable, self.assertNotEqual)

    def test_caret_does_not_jump_when_dragging_to_editable_content_boundary(self):
        self.open_test_html()
        el = self._input
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = sel.content + content_to_add

        # Goal: the cursor position does not being changed after dragging the
        # caret down on the Y-axis.
        el.tap()
        sel.move_caret_to_front()
        el.tap(*sel.caret_location())
        x, y = sel.touch_caret_location()

        # Drag the caret down by 50px, and insert '!'.
        self.actions.flick(el, x, y, x, y + 50).perform()
        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertNotEqual(non_target_content, sel.content)
class CommonCaretsTestCase2(object):
    '''Common test cases for a selection with a two carets.

    To run these test cases, a subclass must inherit from both this class and
    MarionetteTestCase.

    '''
    _long_press_time = 1  # 1 second

    def setUp(self):
        # Code to execute before a tests are run.
        MarionetteTestCase.setUp(self)
        self.actions = Actions(self.marionette)

        # The carets to be tested.
        self.carets_tested_pref = None

        # The carets to be disabled in this test suite.
        self.carets_disabled_pref = None

    def set_pref(self, pref_name, value):
        '''Set a preference to value.

        For example:
        >>> set_pref('layout.accessiblecaret.enabled', True)

        '''
        pref_name = repr(pref_name)
        if isinstance(value, bool):
            value = 'true' if value else 'false'
        elif isinstance(value, int):
            value = str(value)
        else:
            value = repr(value)

        script = '''SpecialPowers.pushPrefEnv({"set": [[%s, %s]]}, marionetteScriptFinished);''' % (
            pref_name, value)

        self.marionette.execute_async_script(script)

    def open_test_html(self, enabled=True):
        'Open html for testing and enable selectioncaret and non-editable support'
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html = self.marionette.absolute_url(
            'test_selectioncarets_multiplerange.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._sel1 = self.marionette.find_element(By.ID, 'sel1')
        self._sel2 = self.marionette.find_element(By.ID, 'sel2')
        self._sel3 = self.marionette.find_element(By.ID, 'sel3')
        self._sel4 = self.marionette.find_element(By.ID, 'sel4')
        self._sel6 = self.marionette.find_element(By.ID, 'sel6')
        self._nonsel1 = self.marionette.find_element(By.ID, 'nonsel1')

    def open_test_html_long_text(self, enabled=True):
        'Open html for testing and enable selectioncaret'
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html = self.marionette.absolute_url(
            'test_selectioncarets_longtext.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._longtext = self.marionette.find_element(By.ID, 'longtext')

    def open_test_html_iframe(self, enabled=True):
        'Open html for testing and enable selectioncaret'
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html = self.marionette.absolute_url(
            'test_selectioncarets_iframe.html')
        self.marionette.navigate(test_html)

        self._iframe = self.marionette.find_element(By.ID, 'frame')

    def _long_press_to_select_word(self, el, wordOrdinal):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(
            wordOrdinal < len(words),
            'Expect at least %d words in the content.' % wordOrdinal)

        # Calc offset
        offset = 0
        for i in range(wordOrdinal):
            offset += (len(words[i]) + 1)

        # Move caret inside the word.
        el.tap()
        sel.move_caret_to_front()
        sel.move_caret_by_offset(offset)
        x, y = sel.caret_location()

        # Long press the caret position. Selection carets should appear, and the
        # word will be selected. On Windows, those spaces after the word
        # will also be selected.
        long_press_without_contextmenu(self.marionette, el,
                                       self._long_press_time, x, y)

    def _to_unix_line_ending(self, s):
        """Changes all Windows/Mac line endings in s to UNIX line endings."""

        return s.replace('\r\n', '\n').replace('\r', '\n')

    def test_long_press_to_select_non_selectable_word(self):
        '''Testing long press on non selectable field.
        We should not select anything when long press on non selectable fields.'''

        self.open_test_html()
        halfY = self._nonsel1.size['height'] / 2
        long_press_without_contextmenu(self.marionette, self._nonsel1,
                                       self._long_press_time, 0, halfY)
        sel = SelectionManager(self._nonsel1)
        range_count = sel.range_count()
        self.assertEqual(range_count, 0)

    def test_drag_caret_over_non_selectable_field(self):
        '''Testing drag caret over non selectable field.
        So that the selected content should exclude non selectable field and
        end selection caret should appear in last range's position.'''
        self.open_test_html()

        # Select target element and get target caret location
        self._long_press_to_select_word(self._sel4, 3)
        sel = SelectionManager(self._body)
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        self._long_press_to_select_word(self._sel6, 0)
        (_, _), (end_caret2_x, end_caret2_y) = sel.selection_carets_location()

        # Select start element
        self._long_press_to_select_word(self._sel3, 3)

        # Drag end caret to target location
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret_x,
                           end_caret_y, 1).perform()
        self.assertEqual(
            self._to_unix_line_ending(sel.selected_content.strip()),
            'this 3\nuser can select this')

        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret2_x,
                           end_caret2_y, 1).perform()
        self.assertEqual(
            self._to_unix_line_ending(sel.selected_content.strip()),
            'this 3\nuser can select this 4\nuser can select this 5\nuser')

        # Drag first caret to target location
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret1_x, caret1_y, end_caret_x,
                           end_caret_y, 1).perform()
        self.assertEqual(
            self._to_unix_line_ending(sel.selected_content.strip()),
            '4\nuser can select this 5\nuser')

    def test_drag_caret_to_beginning_of_a_line(self):
        '''Bug 1094056
        Test caret visibility when caret is dragged to beginning of a line
        '''
        self.open_test_html()

        # Select the first word in the second line
        self._long_press_to_select_word(self._sel2, 0)
        sel = SelectionManager(self._body)
        (start_caret_x,
         start_caret_y), (end_caret_x,
                          end_caret_y) = sel.selection_carets_location()

        # Select target word in the first line
        self._long_press_to_select_word(self._sel1, 2)

        # Drag end caret to the beginning of the second line
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, start_caret_x,
                           start_caret_y).perform()

        # Drag end caret back to the target word
        self.actions.flick(self._body, start_caret_x, start_caret_y, caret2_x,
                           caret2_y).perform()

        self.assertEqual(
            self._to_unix_line_ending(sel.selected_content.strip()), 'select')

    @skip_if_not_rotatable
    def test_caret_position_after_changing_orientation_of_device(self):
        '''Bug 1094072
        If positions of carets are updated correctly, they should be draggable.
        '''
        self.open_test_html_long_text()

        # Select word in portrait mode, then change to landscape mode
        self.marionette.set_orientation('portrait')
        self._long_press_to_select_word(self._longtext, 12)
        sel = SelectionManager(self._body)
        (p_start_caret_x,
         p_start_caret_y), (p_end_caret_x,
                            p_end_caret_y) = sel.selection_carets_location()
        self.marionette.set_orientation('landscape')
        (l_start_caret_x,
         l_start_caret_y), (l_end_caret_x,
                            l_end_caret_y) = sel.selection_carets_location()

        # Drag end caret to the start caret to change the selected content
        self.actions.flick(self._body, l_end_caret_x, l_end_caret_y,
                           l_start_caret_x, l_start_caret_y).perform()

        # Change orientation back to portrait mode to prevent affecting
        # other tests
        self.marionette.set_orientation('portrait')

        self.assertEqual(
            self._to_unix_line_ending(sel.selected_content.strip()), 'o')

    def test_select_word_inside_an_iframe(self):
        '''Bug 1088552
        The scroll offset in iframe should be taken into consideration properly.
        In this test, we scroll content in the iframe to the bottom to cause a
        huge offset. If we use the right coordinate system, selection should
        work. Otherwise, it would be hard to trigger select word.
        '''
        self.open_test_html_iframe()

        # switch to inner iframe and scroll to the bottom
        self.marionette.switch_to_frame(self._iframe)
        self.marionette.execute_script(
            'document.getElementById("bd").scrollTop += 999')

        # long press to select bottom text
        self._body = self.marionette.find_element(By.ID, 'bd')
        sel = SelectionManager(self._body)
        self._bottomtext = self.marionette.find_element(By.ID, 'bottomtext')
        long_press_without_contextmenu(self.marionette, self._bottomtext,
                                       self._long_press_time)

        self.assertNotEqual(
            self._to_unix_line_ending(sel.selected_content.strip()), '')
Esempio n. 24
0
class CommonCaretsTestCase(object):
    '''Common test cases for a selection with a two carets.

    To run these test cases, a subclass must inherit from both this class and
    MarionetteTestCase.

    '''
    _long_press_time = 1        # 1 second
    _input_selector = (By.ID, 'input')
    _textarea_selector = (By.ID, 'textarea')
    _textarea_rtl_selector = (By.ID, 'textarea_rtl')
    _contenteditable_selector = (By.ID, 'contenteditable')
    _content_selector = (By.ID, 'content')
    _textarea2_selector = (By.ID, 'textarea2')
    _contenteditable2_selector = (By.ID, 'contenteditable2')
    _content2_selector = (By.ID, 'content2')

    def setUp(self):
        # Code to execute before a tests are run.
        super(CommonCaretsTestCase, self).setUp()
        self.actions = Actions(self.marionette)

        # The carets to be tested.
        self.carets_tested_pref = None

        # The carets to be disabled in this test suite.
        self.carets_disabled_pref = None

    def set_pref(self, pref_name, value):
        '''Set a preference to value.

        For example:
        >>> set_pref('layout.accessiblecaret.enabled', True)

        '''
        pref_name = repr(pref_name)
        if isinstance(value, bool):
            value = 'true' if value else 'false'
            func = 'setBoolPref'
        elif isinstance(value, int):
            value = str(value)
            func = 'setIntPref'
        else:
            value = repr(value)
            func = 'setCharPref'

        with self.marionette.using_context('chrome'):
            script = 'Services.prefs.%s(%s, %s)' % (func, pref_name, value)
            self.marionette.execute_script(script)

    def open_test_html(self, enabled=True):
        '''Open html for testing and locate elements, and enable/disable touch
        caret.'''
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html = self.marionette.absolute_url('test_selectioncarets.html')
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(*self._input_selector)
        self._textarea = self.marionette.find_element(*self._textarea_selector)
        self._textarea_rtl = self.marionette.find_element(*self._textarea_rtl_selector)
        self._contenteditable = self.marionette.find_element(*self._contenteditable_selector)
        self._content = self.marionette.find_element(*self._content_selector)

    def open_test_html2(self, enabled=True):
        '''Open html for testing and locate elements, and enable/disable touch
        caret.'''
        self.set_pref(self.carets_tested_pref, enabled)
        self.set_pref(self.carets_disabled_pref, False)

        test_html2 = self.marionette.absolute_url('test_selectioncarets_multipleline.html')
        self.marionette.navigate(test_html2)

        self._textarea2 = self.marionette.find_element(*self._textarea2_selector)
        self._contenteditable2 = self.marionette.find_element(*self._contenteditable2_selector)
        self._content2 = self.marionette.find_element(*self._content2_selector)

    def _first_word_location(self, el):
        '''Get the location (x, y) of the first word in el.

        Note: this function has a side effect which changes focus to the
        target element el.

        '''
        sel = SelectionManager(el)

        # Move caret behind the first character to get the location of the first
        # word.
        el.tap()
        sel.move_caret_to_front()
        sel.move_caret_by_offset(1)

        return sel.caret_location()

    def _long_press_to_select(self, el, x, y):
        '''Long press the location (x, y) to select a word.

        SelectionCarets should appear. On Windows, those spaces after the
        word will also be selected.

        '''
        long_press_without_contextmenu(self.marionette, el, self._long_press_time,
                                       x, y)

    def _test_long_press_to_select_a_word(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 2, 'Expect at least two words in the content.')
        target_content = words[0]

        # Goal: Select the first word.
        x, y = self._first_word_location(el)
        self._long_press_to_select(el, x, y)

        # Ignore extra spaces selected after the word.
        assertFunc(target_content, sel.selected_content.rstrip())

    def _test_move_selection_carets(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        # Goal: Select all text after the first word.
        target_content = original_content[len(words[0]):]

        # Get the location of the selection carets at the end of the content for
        # later use.
        el.tap()
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        x, y = self._first_word_location(el)
        self._long_press_to_select(el, x, y)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Move the left caret to the previous position of the right caret.
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        # Ignore extra spaces at the beginning of the content in comparison.
        assertFunc(target_content.lstrip(), sel.selected_content.lstrip())

    def _test_minimum_select_one_character(self, el, assertFunc,
                                           x=None, y=None):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        # Get the location of the selection carets at the end of the content for
        # later use.
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()
        el.tap()

        # Goal: Select the first character.
        target_content = original_content[0]

        if x and y:
            # If we got x and y from the arguments, use it as a hint of the
            # location of the first word
            pass
        else:
            x, y = self._first_word_location(el)
        self._long_press_to_select(el, x, y)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Move the right caret to the position of the left caret.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    def _test_focus_obtained_by_long_press(self, el1, el2):
        '''Test the focus could be changed from el1 to el2 by long press.

        If the focus is changed to e2 successfully, SelectionCarets should
        appear and could be dragged.

        '''
        # Goal: Tap to focus el1, and then select the first character on
        # el2.

        # We want to collect the location of the first word in el2 here
        # since self._first_word_location() has the side effect which would
        # change the focus.
        x, y = self._first_word_location(el2)
        el1.tap()
        self._test_minimum_select_one_character(el2, self.assertEqual,
                                                x=x, y=y)

    def _test_handle_tilt_when_carets_overlap_to_each_other(self, el, assertFunc):
        '''Test tilt handling when carets overlap to each other.

        Let SelectionCarets overlap to each other. If SelectionCarets are set
        to tilted successfully, tapping the tilted carets should not cause the
        selection to be collapsed and the carets should be draggable.
        '''

        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        # Goal: Select the first word.
        x, y = self._first_word_location(el)
        self._long_press_to_select(el, x, y)
        target_content = sel.selected_content

        # Move the left caret to the position of the right caret to trigger
        # carets overlapping.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        # We make two hit tests targeting the left edge of the left tilted caret
        # and the right edge of the right tilted caret. If either of the hits is
        # missed, selection would be collapsed and both carets should not be
        # draggable.
        (caret3_x, caret3_y), (caret4_x, caret4_y) = sel.selection_carets_location()

        # The following values are from ua.css.
        caret_width = 44
        caret_margin_left = -23
        tilt_right_margin_left = 18
        tilt_left_margin_left = -17

        left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
        el.tap(left_caret_left_edge_x + 2, caret3_y)

        right_caret_right_edge_x = (caret4_x + caret_margin_left +
                                    tilt_right_margin_left + caret_width)
        el.tap(right_caret_right_edge_x - 2, caret4_y)

        # Drag the left caret back to the initial selection, the first word.
        self.actions.flick(el, caret3_x, caret3_y, caret1_x, caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    ########################################################################
    # <input> test cases with selection carets enabled
    ########################################################################
    def test_input_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._input, self.assertEqual)

    def test_input_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._input, self.assertEqual)

    def test_input_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._input, self.assertEqual)

    def test_input_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._input)

    def test_input_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable, self._input)

    def test_input_focus_obtained_by_long_press_from_content_non_editable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._input)

    def test_input_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._input, self.assertEqual)

    ########################################################################
    # <input> test cases with selection carets disabled
    ########################################################################
    def test_input_long_press_to_select_a_word_disabled(self):
        self.open_test_html(enabled=False)
        self._test_long_press_to_select_a_word(self._input, self.assertNotEqual)

    def test_input_move_selection_carets_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_selection_carets(self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with selection carets enabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._textarea, self.assertEqual)

    def test_textarea_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._textarea, self.assertEqual)

    def test_textarea_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._textarea, self.assertEqual)

    def test_textarea_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable, self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_content_non_editable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._textarea)

    def test_textarea_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._textarea, self.assertEqual)

    ########################################################################
    # <textarea> test cases with selection carets disabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word_disabled(self):
        self.open_test_html(enabled=False)
        self._test_long_press_to_select_a_word(self._textarea, self.assertNotEqual)

    def test_textarea_move_selection_carets_disable(self):
        self.open_test_html(enabled=False)
        self._test_move_selection_carets(self._textarea, self.assertNotEqual)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets enabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._textarea_rtl, self.assertEqual)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets disabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word_disabled(self):
        self.open_test_html(enabled=False)
        self._test_long_press_to_select_a_word(self._textarea_rtl, self.assertNotEqual)

    def test_textarea_rtl_move_selection_carets_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_selection_carets(self._textarea_rtl, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with selection carets enabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._contenteditable, self.assertEqual)

    def test_contenteditable_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._contenteditable, self.assertEqual)

    def test_contenteditable_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_content_non_editable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._contenteditable)

    def test_contenteditable_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._contenteditable, self.assertEqual)

    ########################################################################
    # <div> contenteditable test cases with selection carets disabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word_disabled(self):
        self.open_test_html(enabled=False)
        self._test_long_press_to_select_a_word(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_move_selection_carets_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_selection_carets(self._contenteditable, self.assertNotEqual)

    ########################################################################
    # <div> non-editable test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._content, self.assertEqual)

    def test_content_non_editable_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._content, self.assertEqual)

    def test_content_non_editable_minimum_select_one_character_by_selection(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._content, self.assertEqual)

    def test_content_non_editable_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable, self._content)

    def test_content_non_editable_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._content, self.assertEqual)

    ########################################################################
    # <textarea> (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_textarea2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._textarea2, self.assertEqual)

    ########################################################################
    # <div> contenteditable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_contenteditable2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._contenteditable2, self.assertEqual)

    ########################################################################
    # <div> non-editable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._content2, self.assertEqual)
Esempio n. 25
0
class CommonCaretsTestCase(object):
    """Common test cases for a selection with a two carets.

    To run these test cases, a subclass must inherit from both this class and
    MarionetteTestCase.

    """

    def setUp(self):
        # Code to execute before a tests are run.
        super(CommonCaretsTestCase, self).setUp()
        self.actions = Actions(self.marionette)

    def open_test_html(self):
        "Open html for testing and locate elements."
        test_html = self.marionette.absolute_url("test_selectioncarets.html")
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(By.ID, "input")
        self._textarea = self.marionette.find_element(By.ID, "textarea")
        self._textarea_rtl = self.marionette.find_element(By.ID, "textarea_rtl")
        self._contenteditable = self.marionette.find_element(By.ID, "contenteditable")
        self._content = self.marionette.find_element(By.ID, "content")
        self._non_selectable = self.marionette.find_element(By.ID, "non_selectable")

    def open_test_html2(self):
        "Open html for testing and locate elements."
        test_html2 = self.marionette.absolute_url("test_selectioncarets_multipleline.html")
        self.marionette.navigate(test_html2)

        self._textarea2 = self.marionette.find_element(By.ID, "textarea2")
        self._contenteditable2 = self.marionette.find_element(By.ID, "contenteditable2")
        self._content2 = self.marionette.find_element(By.ID, "content2")

    def open_test_html_multirange(self):
        "Open html for testing non-editable support."
        test_html = self.marionette.absolute_url("test_selectioncarets_multiplerange.html")
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, "bd")
        self._sel1 = self.marionette.find_element(By.ID, "sel1")
        self._sel2 = self.marionette.find_element(By.ID, "sel2")
        self._sel3 = self.marionette.find_element(By.ID, "sel3")
        self._sel4 = self.marionette.find_element(By.ID, "sel4")
        self._sel6 = self.marionette.find_element(By.ID, "sel6")
        self._nonsel1 = self.marionette.find_element(By.ID, "nonsel1")

    def open_test_html_long_text(self):
        "Open html for testing long text."
        test_html = self.marionette.absolute_url("test_selectioncarets_longtext.html")
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, "bd")
        self._longtext = self.marionette.find_element(By.ID, "longtext")

    def open_test_html_iframe(self):
        "Open html for testing iframe."
        test_html = self.marionette.absolute_url("test_selectioncarets_iframe.html")
        self.marionette.navigate(test_html)

        self._iframe = self.marionette.find_element(By.ID, "frame")

    def open_test_html_display_none(self):
        "Open html for testing html with display: none."
        test_html = self.marionette.absolute_url("test_carets_display_none.html")
        self.marionette.navigate(test_html)

        self._html = self.marionette.find_element(By.ID, "html")
        self._content = self.marionette.find_element(By.ID, "content")

    def word_offset(self, text, ordinal):
        "Get the character offset of the ordinal-th word in text."
        tokens = re.split(r"(\S+)", text)  # both words and spaces
        spaces = tokens[0::2]  # collect spaces at odd indices
        words = tokens[1::2]  # collect word at even indices

        if ordinal >= len(words):
            raise IndexError("Only %d words in text, but got ordinal %d" % (len(words), ordinal))

        # Cursor position of the targeting word is behind the the first
        # character in the word. For example, offset to 'def' in 'abc def' is
        # between 'd' and 'e'.
        offset = len(spaces[0]) + 1
        offset += sum(len(words[i]) + len(spaces[i + 1]) for i in range(ordinal))
        return offset

    def test_word_offset(self):
        text = " " * 3 + "abc" + " " * 3 + "def"

        self.assertTrue(self.word_offset(text, 0), 4)
        self.assertTrue(self.word_offset(text, 1), 10)
        with self.assertRaises(IndexError):
            self.word_offset(text, 2)

    def word_location(self, el, ordinal):
        """Get the location (x, y) of the ordinal-th word in el.

        The ordinal starts from 0.

        Note: this function has a side effect which changes focus to the
        target element el.

        """
        sel = SelectionManager(el)
        offset = self.word_offset(sel.content, ordinal)

        # Move caret to the word.
        el.tap()
        sel.move_caret_to_front()
        sel.move_caret_by_offset(offset)
        x, y = sel.caret_location()

        return x, y

    def rect_relative_to_window(self, el):
        """Get element's bounding rectangle.

        This function is similar to el.rect, but the coordinate is relative to
        the top left corner of the window instead of the document.

        """
        return self.marionette.execute_script(
            """
            let rect = arguments[0].getBoundingClientRect();
            return {x: rect.x, y:rect.y, width: rect.width, height: rect.height};
            """,
            script_args=[el],
        )

    def long_press_on_location(self, el, x=None, y=None):
        """Long press the location (x, y) to select a word.

        If no (x, y) are given, it will be targeted at the center of the
        element. On Windows, those spaces after the word will also be selected.
        This function sends synthesized eMouseLongTap to gecko.

        """
        rect = self.rect_relative_to_window(el)
        target_x = rect["x"] + (x if x is not None else rect["width"] // 2)
        target_y = rect["y"] + (y if y is not None else rect["height"] // 2)

        self.marionette.execute_script(
            """
            let Ci = Components.interfaces;
            let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
            utils.sendTouchEventToWindow('touchstart', [0],
                                         [arguments[0]], [arguments[1]],
                                         [1], [1], [0], [1], 1, 0);
            utils.sendMouseEventToWindow('mouselongtap', arguments[0], arguments[1],
                                          0, 1, 0);
            utils.sendTouchEventToWindow('touchend', [0],
                                         [arguments[0]], [arguments[1]],
                                         [1], [1], [0], [1], 1, 0);
            """,
            script_args=[target_x, target_y],
            sandbox="system",
        )

    def long_press_on_word(self, el, wordOrdinal):
        x, y = self.word_location(el, wordOrdinal)
        self.long_press_on_location(el, x, y)

    def to_unix_line_ending(self, s):
        """Changes all Windows/Mac line endings in s to UNIX line endings."""

        return s.replace("\r\n", "\n").replace("\r", "\n")

    def _test_long_press_to_select_a_word(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 2, "Expect at least two words in the content.")
        target_content = words[0]

        # Goal: Select the first word.
        self.long_press_on_word(el, 0)

        # Ignore extra spaces selected after the word.
        assertFunc(target_content, sel.selected_content)

    def _test_move_selection_carets(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")

        # Goal: Select all text after the first word.
        target_content = original_content[len(words[0]) :]

        # Get the location of the selection carets at the end of the content for
        # later use.
        el.tap()
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        self.long_press_on_word(el, 0)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Move the left caret to the previous position of the right caret.
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        assertFunc(target_content, sel.selected_content)

    def _test_minimum_select_one_character(self, el, assertFunc, x=None, y=None):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")

        # Get the location of the selection carets at the end of the content for
        # later use.
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()
        el.tap()

        # Goal: Select the first character.
        target_content = original_content[0]

        if x and y:
            # If we got x and y from the arguments, use it as a hint of the
            # location of the first word
            pass
        else:
            x, y = self.word_location(el, 0)
        self.long_press_on_location(el, x, y)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Move the right caret to the position of the left caret.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    def _test_focus_obtained_by_long_press(self, el1, el2):
        """Test the focus could be changed from el1 to el2 by long press.

        If the focus is changed to e2 successfully, SelectionCarets should
        appear and could be dragged.

        """
        # Goal: Tap to focus el1, and then select the first character on
        # el2.

        # We want to collect the location of the first word in el2 here
        # since self.word_location() has the side effect which would
        # change the focus.
        x, y = self.word_location(el2, 0)
        el1.tap()
        self._test_minimum_select_one_character(el2, self.assertEqual, x=x, y=y)

    def _test_focus_not_being_changed_by_long_press_on_non_selectable(self, el):
        # Goal: Focus remains on the editable element el after long pressing on
        # the non-selectable element.
        sel = SelectionManager(el)
        self.long_press_on_word(el, 0)
        self.long_press_on_location(self._non_selectable)
        active_sel = SelectionManager(self.marionette.get_active_element())
        self.assertEqual(sel.content, active_sel.content)

    def _test_handle_tilt_when_carets_overlap_to_each_other(self, el, assertFunc):
        """Test tilt handling when carets overlap to each other.

        Let SelectionCarets overlap to each other. If SelectionCarets are set
        to tilted successfully, tapping the tilted carets should not cause the
        selection to be collapsed and the carets should be draggable.
        """

        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")

        # Goal: Select the first word.
        self.long_press_on_word(el, 0)
        target_content = sel.selected_content

        # Move the left caret to the position of the right caret to trigger
        # carets overlapping.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        # We make two hit tests targeting the left edge of the left tilted caret
        # and the right edge of the right tilted caret. If either of the hits is
        # missed, selection would be collapsed and both carets should not be
        # draggable.
        (caret3_x, caret3_y), (caret4_x, caret4_y) = sel.selection_carets_location()

        # The following values are from ua.css and all.js
        if self.carets_tested_pref == "selectioncaret.enabled":
            caret_width = 44
            caret_margin_left = -23
            tilt_right_margin_left = 18
            tilt_left_margin_left = -17
        elif self.carets_tested_pref == "layout.accessiblecaret.enabled":
            caret_width = float(self.marionette.get_pref("layout.accessiblecaret.width"))
            caret_margin_left = float(self.marionette.get_pref("layout.accessiblecaret.margin-left"))
            tilt_right_margin_left = 0.41 * caret_width
            tilt_left_margin_left = -0.39 * caret_width

        left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
        el.tap(left_caret_left_edge_x + 2, caret3_y)

        right_caret_right_edge_x = caret4_x + caret_margin_left + tilt_right_margin_left + caret_width
        el.tap(right_caret_right_edge_x - 2, caret4_y)

        # Drag the left caret back to the initial selection, the first word.
        self.actions.flick(el, caret3_x, caret3_y, caret1_x, caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    def test_long_press_to_select_non_selectable_word(self):
        """Testing long press on non selectable field.
        We should not select anything when long press on non selectable fields."""

        self.open_test_html_multirange()
        halfY = self._nonsel1.size["height"] / 2
        self.long_press_on_location(self._nonsel1, 0, halfY)
        sel = SelectionManager(self._nonsel1)
        range_count = sel.range_count()
        self.assertEqual(range_count, 0)

    def test_drag_caret_over_non_selectable_field(self):
        """Testing drag caret over non selectable field.
        So that the selected content should exclude non selectable field and
        end selection caret should appear in last range's position."""
        self.open_test_html_multirange()

        # Select target element and get target caret location
        self.long_press_on_word(self._sel4, 3)
        sel = SelectionManager(self._body)
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        self.long_press_on_word(self._sel6, 0)
        (_, _), (end_caret2_x, end_caret2_y) = sel.selection_carets_location()

        # Select start element
        self.long_press_on_word(self._sel3, 3)

        # Drag end caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()), "this 3\nuser can select this")

        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret2_x, end_caret2_y, 1).perform()
        self.assertEqual(
            self.to_unix_line_ending(sel.selected_content.strip()),
            "this 3\nuser can select this 4\nuser can select this 5\nuser",
        )

        # Drag first caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret1_x, caret1_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()), "4\nuser can select this 5\nuser")

    def test_drag_caret_to_beginning_of_a_line(self):
        """Bug 1094056
        Test caret visibility when caret is dragged to beginning of a line
        """
        self.open_test_html_multirange()

        # Select the first word in the second line
        self.long_press_on_word(self._sel2, 0)
        sel = SelectionManager(self._body)
        (start_caret_x, start_caret_y), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        # Select target word in the first line
        self.long_press_on_word(self._sel1, 2)

        # Drag end caret to the beginning of the second line
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, start_caret_x, start_caret_y).perform()

        # Drag end caret back to the target word
        self.actions.flick(self._body, start_caret_x, start_caret_y, caret2_x, caret2_y).perform()

        self.assertEqual(self.to_unix_line_ending(sel.selected_content), "select")

    @skip_if_not_rotatable
    def test_caret_position_after_changing_orientation_of_device(self):
        """Bug 1094072
        If positions of carets are updated correctly, they should be draggable.
        """
        self.open_test_html_long_text()

        # Select word in portrait mode, then change to landscape mode
        self.marionette.set_orientation("portrait")
        self.long_press_on_word(self._longtext, 12)
        sel = SelectionManager(self._body)
        (p_start_caret_x, p_start_caret_y), (p_end_caret_x, p_end_caret_y) = sel.selection_carets_location()
        self.marionette.set_orientation("landscape")
        (l_start_caret_x, l_start_caret_y), (l_end_caret_x, l_end_caret_y) = sel.selection_carets_location()

        # Drag end caret to the start caret to change the selected content
        self.actions.flick(self._body, l_end_caret_x, l_end_caret_y, l_start_caret_x, l_start_caret_y).perform()

        # Change orientation back to portrait mode to prevent affecting
        # other tests
        self.marionette.set_orientation("portrait")

        self.assertEqual(self.to_unix_line_ending(sel.selected_content), "o")

    def test_select_word_inside_an_iframe(self):
        """Bug 1088552
        The scroll offset in iframe should be taken into consideration properly.
        In this test, we scroll content in the iframe to the bottom to cause a
        huge offset. If we use the right coordinate system, selection should
        work. Otherwise, it would be hard to trigger select word.
        """
        self.open_test_html_iframe()

        # switch to inner iframe and scroll to the bottom
        self.marionette.switch_to_frame(self._iframe)
        self.marionette.execute_script('document.getElementById("bd").scrollTop += 999')

        # long press to select bottom text
        self._body = self.marionette.find_element(By.ID, "bd")
        sel = SelectionManager(self._body)
        self._bottomtext = self.marionette.find_element(By.ID, "bottomtext")
        self.long_press_on_location(self._bottomtext)

        self.assertNotEqual(self.to_unix_line_ending(sel.selected_content), "")

    def test_carets_initialized_in_display_none(self):
        """Test AccessibleCaretEventHub is properly initialized on a <html> with
        display: none.

        """
        self.open_test_html_display_none()

        # Remove 'display: none' from <html>
        self.marionette.execute_script('arguments[0].style.display = "unset";', script_args=[self._html])

        # If AccessibleCaretEventHub is initialized successfully, select a word
        # should work.
        self._test_long_press_to_select_a_word(self._content, self.assertEqual)

    ########################################################################
    # <input> test cases with selection carets enabled
    ########################################################################
    def test_input_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._input, self.assertEqual)

    def test_input_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._input, self.assertEqual)

    def test_input_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._input, self.assertEqual)

    def test_input_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._input)

    def test_input_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable, self._input)

    def test_input_focus_obtained_by_long_press_from_content_non_editable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._input)

    def test_input_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._input, self.assertEqual)

    def test_input_focus_not_changed_by_long_press_on_non_selectable(self):
        self.open_test_html()
        self._test_focus_not_being_changed_by_long_press_on_non_selectable(self._input)

    ########################################################################
    # <input> test cases with selection carets disabled
    ########################################################################
    def test_input_long_press_to_select_a_word_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_long_press_to_select_a_word(self._input, self.assertNotEqual)

    def test_input_move_selection_carets_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_move_selection_carets(self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with selection carets enabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._textarea, self.assertEqual)

    def test_textarea_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._textarea, self.assertEqual)

    def test_textarea_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._textarea, self.assertEqual)

    def test_textarea_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable, self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_content_non_editable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._textarea)

    def test_textarea_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._textarea, self.assertEqual)

    def test_textarea_focus_not_changed_by_long_press_on_non_selectable(self):
        self.open_test_html()
        self._test_focus_not_being_changed_by_long_press_on_non_selectable(self._textarea)

    ########################################################################
    # <textarea> test cases with selection carets disabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_long_press_to_select_a_word(self._textarea, self.assertNotEqual)

    def test_textarea_move_selection_carets_disable(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_move_selection_carets(self._textarea, self.assertNotEqual)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets enabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_focus_not_changed_by_long_press_on_non_selectable(self):
        self.open_test_html()
        self._test_focus_not_being_changed_by_long_press_on_non_selectable(self._textarea_rtl)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets disabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_long_press_to_select_a_word(self._textarea_rtl, self.assertNotEqual)

    def test_textarea_rtl_move_selection_carets_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_move_selection_carets(self._textarea_rtl, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with selection carets enabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._contenteditable, self.assertEqual)

    def test_contenteditable_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._contenteditable, self.assertEqual)

    def test_contenteditable_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_content_non_editable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._contenteditable)

    def test_contenteditable_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._contenteditable, self.assertEqual)

    def test_contenteditable_focus_not_changed_by_long_press_on_non_selectable(self):
        self.open_test_html()
        self._test_focus_not_being_changed_by_long_press_on_non_selectable(self._contenteditable)

    ########################################################################
    # <div> contenteditable test cases with selection carets disabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_long_press_to_select_a_word(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_move_selection_carets_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_move_selection_carets(self._contenteditable, self.assertNotEqual)

    ########################################################################
    # <div> non-editable test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._content, self.assertEqual)

    def test_content_non_editable_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._content, self.assertEqual)

    def test_content_non_editable_minimum_select_one_character_by_selection(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._content, self.assertEqual)

    def test_content_non_editable_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable, self._content)

    def test_content_non_editable_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._content, self.assertEqual)

    ########################################################################
    # <textarea> (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_textarea2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._textarea2, self.assertEqual)

    ########################################################################
    # <div> contenteditable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_contenteditable2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._contenteditable2, self.assertEqual)

    ########################################################################
    # <div> non-editable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._content2, self.assertEqual)
Esempio n. 26
0
class element(object):

    def __init__(self, parent):
        self.parent = parent
        self.marionette = parent.marionette
        self.actions = Actions(self.marionette)

    def getElement(self, elem, msg, is_displayed=True, timeout=5, stop_on_error=True):
        """
        Returns an element, or False it it's not found
        """
        x = self.getElements(elem, msg, is_displayed, timeout, stop_on_error)
        if x:
            # We're expecting ONE element back (it has different methods if it's one).
            return x[0]
        else:
            return False

    def getElements(self, elem, msg, is_displayed=True, timeout=5, stop_on_error=True):
        """
        Returns a list of matching elements, or False if none are found
        """
        boolEl = self.waitForElements(elem, msg, is_displayed, timeout, stop_on_error)

        if boolEl:
            el = self.marionette.find_elements(*elem)
            return el
        else:
            return False

    def headerCheck(self, value):
        """
        Returns the header that matches a string.
        NOTE: ALL headers in this iframe return true for ".is_displayed()"!
        """
        is_ok = False
        try:
            self.parent.parent.wait_for_element_present(*DOM.GLOBAL.app_head, timeout=1)
            headerNames = self.marionette.find_elements(*DOM.GLOBAL.app_head)
            for i in headerNames:
                if i.text == value:
                    if i.is_displayed():
                        is_ok = True
                        break
        except:
            is_ok = False

        self.parent.test.test(is_ok, "Header is \"" + value + "\".")
        return is_ok

    def move_scroller(self, scroller, forward=True):
        """Move the scroller one item forward or backwards.
        """
        x_pos = scroller.size['width'] / 2
        y_start = scroller.size['height'] / 2

        y_end = -scroller.size['height'] if forward else scroller.size['height']
        self.actions.flick(scroller, x_pos, y_start, x_pos, y_end, 100)
        self.actions.perform()

    def set_scroller_val(self, scroller_elem, number):
        """
        Set the numeric value of a scroller (only works with numbers right now).
        """
        current_value = int(scroller_elem.find_element(*DOM.GLOBAL.scroller_curr_val).text)

        # Now flick the scroller as many times as required
        n = int(number)
        while n != current_value:
            # Do we need to go forwards or backwards?
            if n > int(current_value):
                self.move_scroller(scroller_elem, True)
            if n < int(current_value):
                self.move_scroller(scroller_elem, False)

            # Get the new 'current_value'.
            current_value = int(scroller_elem.find_element(*DOM.GLOBAL.scroller_curr_val).text)

    # From gaiatest Clock -> regions -> alarm.py
    def _flick_menu_up(self, locator):
        self.parent.parent.wait_for_element_displayed(*self._current_element(*locator), timeout=2)
        current_element = self.marionette.find_element(*self._current_element(*locator))
        next_element = self.marionette.find_element(*self._next_element(*locator))

        # TODO: update this with more accurate Actions
        action = Actions(self.marionette)
        action.press(next_element)
        action.move(current_element)
        action.release()
        action.perform()

    def _flick_menu_down(self, locator):
        self.parent.parent.wait_for_element_displayed(*self._current_element(*locator), timeout=2)
        current_element = self.marionette.find_element(*self._current_element(*locator))
        next_element = self.marionette.find_element(*self._next_element(*locator))

        # TODO: update this with more accurate Actions
        action = Actions(self.marionette)
        action.press(current_element)
        action.move(next_element)
        action.release()
        action.perform()

    def _current_element(self, method, target):
        self.parent.reporting.debug("*** Finding current element for target {}".format(target))
        return (method, '{}.picker-unit.selected'.format(target))

    def _next_element(self, method, target):
        self.parent.reporting.debug("*** Finding next element for target {}".format(target))
        return (method, '{}.picker-unit.selected + div'.format(target))

    def simulateClick(self, element):
        self.marionette.execute_script("""
            /**
            * Helper method to simulate clicks on iFrames which is not currently
            *  working in the Marionette JS Runner.
            * @param {Marionette.Element} element The element to simulate the click on.
            **/

            var event = new MouseEvent('click', {
             'view': window,
             'bubbles': true,
             'cancelable': true
             });
            arguments[0].dispatchEvent(event);
        """, script_args=[element])

    def simulateClickAtPos(self, element, posX, posY):
        self.parent.reporting.logResult('info', 'Simulating click....')
        self.marionette.execute_script("""
            /**
            * Helper method to simulate clicks on iFrames which is not currently
            *  working in the Marionette JS Runner.
            * @param {Marionette.Element} element The element to simulate the click on.
            **/

            var event = document.createEvent('MouseEvents');
            event.initMouseEvent(
            'click', true, true, window, 0,
            0, 0, arguments[1], arguments[2], false, false,
            false, false, 0, null
            );
            arguments[0].dispatchEvent(event);
        """, script_args=[element, posX, posY])

    def waitForElements(self, elem, msg, is_displayed=True, timeout=5, stop_on_error=True):
        """
        Waits for an element to be displayed and captures the error if not
        """
        is_ok = True
        msg = u"" + msg
        try:
            if is_displayed:
                msg = u"{} displayed within {} seconds.|{}".format(msg, timeout, elem)
                self.parent.parent.wait_for_element_displayed(*elem, timeout=timeout)
            else:
                msg = u"{} present within {} seconds.|{}".format(msg, timeout, elem)
                self.parent.parent.wait_for_element_present(*elem, timeout=timeout)
        except Exception:
            is_ok = False

        self.parent.test.test(is_ok, msg, stop_on_error)

        return is_ok

    def waitForNotElements(self, elem, msg, is_displayed=True, timeout=5, stop_on_error=True):
        """
        Waits for an element to be displayed and captures the error if not
        """
        is_ok = True
        try:
            if is_displayed:
                msg = "{} no longer displayed within {} seconds.|{}".format(msg, timeout, elem)
                self.parent.parent.wait_for_element_not_displayed(*elem, timeout=timeout)
            else:
                msg = "{} no longer present within {} seconds.|{}".format(msg, timeout, elem)
                self.parent.parent.wait_for_element_not_present(*elem, timeout=timeout)
        except:
            is_ok = False

        self.parent.test.test(is_ok, msg, stop_on_error)
        return is_ok

    def getElementByXpath(self, path):
        """
        Use this function when normal getElement did not work
        """
        return self.marionette.execute_script("""
            return document.evaluate(arguments[0], document, null, 9, null).singleNodeValue;
        """, script_args=[path])

    def getParent(self, element):
        """
        Gets the element's parent. Can be called recursively
        """
        return self.marionette.execute_script("""
            return arguments[0].parentNode;
        """, script_args=[element])

    def getChildren(self, element):
        """
        Gets the element's children
        """
        return self.marionette.execute_script("""
            return arguments[0].children;
        """, script_args=[element])

    def find_nested(self, context, css_selector):
        return self.marionette.execute_script("""
            return arguments[0].querySelector(arguments[1])
        """, script_args=[context, css_selector])

    def get_css_value(self, element, css_property):
        """
        Gets the value of a certain css property.
        """
        return self.marionette.execute_script("""
            function getStyle (el, styleProp) {
                if (el.currentStyle)
                    var y = x.currentStyle[styleProp];
                else if (window.getComputedStyle)
                    var y = document.defaultView.getComputedStyle(el,null)
                                                .getPropertyValue(styleProp);
                return y;
            }
            return getStyle(arguments[0], arguments[1])
        """, script_args=[element, css_property])

    def is_ellipsis_active(self, element):
        """
        Checks whether a certain element is really ellipsed when its content
        overflows its width
        """
        return self.marionette.execute_script("""
            function isEllipsisActive(element) {
                return (element.offsetWidth < element.scrollWidth);
            }
            return isEllipsisActive(arguments[0])
        """, script_args=[element])

    def scroll_into_view(self, element):
        self.marionette.execute_script("""
            arguments[0].scrollIntoView();
        """, script_args=[element])

    def perform_action_over_element(self, locator, action, position=None):
        script = """
            var _locatorMap = {
                "id": document.getElementById,
                "class name": document.getElementsByClassName,
                "css selector": document.querySelector,
                "xpath": function () {
                            return document.evaluate(arguments[0], document, null, 9, null).singleNodeValue
                        },
                "tag name": document.getElementsByTagName
            };

            // TODO - Add more events here
            var _actionMap = {
                "click":    new MouseEvent('click', {
                                'view': window,
                                'bubbles': true,
                                'cancelable': true
                            }), //HTMLElement.prototype.click
            }

            var location_method = arguments[0][0];
            var locator         = arguments[0][1];
            var action          = arguments[1];
            var position        = arguments[2];

            if (position) {
                var element = _locatorMap[location_method].call(document, locator)[position];
            } else {
                if ((locator === "class name") || (locator === "tag name")) {
                    var e = 'JavaScriptException: InvalidParametersException: '
                    var msg = 'If using "class name" or "tag name", it is mandatory to specify a position'
                    throw  e + msg
                }
                var element = _locatorMap[location_method].call(document, locator);
            }

            if (element) {
                if (_actionMap.hasOwnProperty(action)) {
                    element.dispatchEvent(_actionMap[action])
                } else {
                    var e = 'JavaScriptException: InvalidParametersException: '
                    var msg = 'Specified action <' + action + '> not supported';
                    throw  e + msg
                }
            }
        """
        self.marionette.execute_script(script, script_args=[list(locator), action, position])
class SelectionCaretsMultipleRangeTest(MarionetteTestCase):
    _long_press_time = 1        # 1 second

    def setUp(self):
        # Code to execute before a tests are run.
        MarionetteTestCase.setUp(self)
        self.actions = Actions(self.marionette)

    def openTestHtml(self, enabled=True):
        # Open html for testing and enable selectioncaret and
        # non-editable support
        self.marionette.execute_async_script(
            'SpecialPowers.pushPrefEnv({"set": [["selectioncaret.enabled", %s],["selectioncaret.noneditable", %s]]}, marionetteScriptFinished);' %
            ( ('true' if enabled else 'false'),  ('true' if enabled else 'false')))

        test_html = self.marionette.absolute_url('test_selectioncarets_multiplerange.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._sel1 = self.marionette.find_element(By.ID, 'sel1')
        self._sel2 = self.marionette.find_element(By.ID, 'sel2')
        self._sel3 = self.marionette.find_element(By.ID, 'sel3')
        self._sel4 = self.marionette.find_element(By.ID, 'sel4')
        self._sel6 = self.marionette.find_element(By.ID, 'sel6')
        self._nonsel1 = self.marionette.find_element(By.ID, 'nonsel1')

    def openTestHtmlLongText(self, enabled=True):
        # Open html for testing and enable selectioncaret
        self.marionette.execute_script(
            'SpecialPowers.setBoolPref("selectioncaret.enabled", %s);' %
            ('true' if enabled else 'false'))

        test_html = self.marionette.absolute_url('test_selectioncarets_longtext.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._longtext = self.marionette.find_element(By.ID, 'longtext')

    def openTestHtmlIframe(self, enabled=True):
        # Open html for testing and enable selectioncaret
        self.marionette.execute_script(
            'SpecialPowers.setBoolPref("selectioncaret.enabled", %s);' %
            ('true' if enabled else 'false'))

        test_html = self.marionette.absolute_url('test_selectioncarets_iframe.html')
        self.marionette.navigate(test_html)

        self._iframe = self.marionette.find_element(By.ID, 'frame')

    def _long_press_to_select_word(self, el, wordOrdinal):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(wordOrdinal < len(words),
            'Expect at least %d words in the content.' % wordOrdinal)

        # Calc offset
        offset = 0
        for i in range(wordOrdinal):
            offset += (len(words[i]) + 1)

        # Move caret inside the word.
        el.tap()
        sel.move_caret_to_front()
        sel.move_caret_by_offset(offset)
        x, y = sel.caret_location()

        # Long press the caret position. Selection carets should appear, and the
        # word will be selected. On Windows, those spaces after the word
        # will also be selected.
        long_press_without_contextmenu(self.marionette, el, self._long_press_time, x, y)

    def _to_unix_line_ending(self, s):
        """Changes all Windows/Mac line endings in s to UNIX line endings."""

        return s.replace('\r\n', '\n').replace('\r', '\n')

    def test_long_press_to_select_non_selectable_word(self):
        '''Testing long press on non selectable field.
        We should not select anything when long press on non selectable fields.'''

        self.openTestHtml(enabled=True)
        halfY = self._nonsel1.size['height'] / 2
        long_press_without_contextmenu(self.marionette, self._nonsel1, self._long_press_time, 0, halfY)
        sel = SelectionManager(self._nonsel1)
        range_count = sel.range_count()
        self.assertEqual(range_count, 0)

    def test_drag_caret_over_non_selectable_field(self):
        '''Testing drag caret over non selectable field.
        So that the selected content should exclude non selectable field and
        end selection caret should appear in last range's position.'''
        self.openTestHtml(enabled=True)

        # Select target element and get target caret location
        self._long_press_to_select_word(self._sel4, 3)
        sel = SelectionManager(self._body)
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        self._long_press_to_select_word(self._sel6, 0)
        (_, _), (end_caret2_x, end_caret2_y) = sel.selection_carets_location()

        # Select start element
        self._long_press_to_select_word(self._sel3, 3)

        # Drag end caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()),
            'this 3\nuser can select this')

        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret2_x, end_caret2_y, 1).perform()
        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()),
            'this 3\nuser can select this 4\nuser can select this 5\nuser')

        # Drag first caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret1_x, caret1_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()),
            '4\nuser can select this 5\nuser')

    def test_drag_caret_to_beginning_of_a_line(self):
        '''Bug 1094056
        Test caret visibility when caret is dragged to beginning of a line
        '''
        self.openTestHtml(enabled=True)

        # Select the first word in the second line
        self._long_press_to_select_word(self._sel2, 0)
        sel = SelectionManager(self._body)
        (start_caret_x, start_caret_y), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        # Select target word in the first line
        self._long_press_to_select_word(self._sel1, 2)

        # Drag end caret to the beginning of the second line
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, start_caret_x, start_caret_y).perform()

        # Drag end caret back to the target word
        self.actions.flick(self._body, start_caret_x, start_caret_y, caret2_x, caret2_y).perform()

        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()), 'select')

    def test_caret_position_after_changing_orientation_of_device(self):
        '''Bug 1094072
        If positions of carets are updated correctly, they should be draggable.
        '''
        # Skip running test on non-rotatable device ex.desktop browser
        if not self.marionette.session_capabilities['rotatable']:
            return

        self.openTestHtmlLongText(enabled=True)

        # Select word in portrait mode, then change to landscape mode
        self.marionette.set_orientation('portrait')
        self._long_press_to_select_word(self._longtext, 12)
        sel = SelectionManager(self._body)
        (p_start_caret_x, p_start_caret_y), (p_end_caret_x, p_end_caret_y) = sel.selection_carets_location()
        self.marionette.set_orientation('landscape')
        (l_start_caret_x, l_start_caret_y), (l_end_caret_x, l_end_caret_y) = sel.selection_carets_location()

        # Drag end caret to the start caret to change the selected content
        self.actions.flick(self._body, l_end_caret_x, l_end_caret_y, l_start_caret_x, l_start_caret_y).perform()

        # Change orientation back to portrait mode to prevent affecting
        # other tests
        self.marionette.set_orientation('portrait')

        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()), 'o')

    def test_select_word_inside_an_iframe(self):
        '''Bug 1088552
        The scroll offset in iframe should be taken into consideration properly.
        In this test, we scroll content in the iframe to the bottom to cause a
        huge offset. If we use the right coordinate system, selection should
        work. Otherwise, it would be hard to trigger select word.
        '''
        self.openTestHtmlIframe(enabled=True)

        # switch to inner iframe and scroll to the bottom
        self.marionette.switch_to_frame(self._iframe)
        self.marionette.execute_script(
         'document.getElementById("bd").scrollTop += 999')

        # long press to select bottom text
        self._body = self.marionette.find_element(By.ID, 'bd')
        sel = SelectionManager(self._body)
        self._bottomtext = self.marionette.find_element(By.ID, 'bottomtext')
        long_press_without_contextmenu(self.marionette, self._bottomtext, self._long_press_time)

        self.assertNotEqual(self._to_unix_line_ending(sel.selected_content.strip()), '')
class SelectionCaretsMultipleRangeTest(MarionetteTestCase):
    _long_press_time = 1        # 1 second

    def setUp(self):
        # Code to execute before a tests are run.
        MarionetteTestCase.setUp(self)
        self.actions = Actions(self.marionette)

    def openTestHtml(self, enabled=True):
        # Open html for testing and enable selectioncaret and
        # non-editable support
        self.marionette.execute_script(
            'SpecialPowers.setBoolPref("selectioncaret.enabled", %s);' %
            ('true' if enabled else 'false'))
        self.marionette.execute_script(
            'SpecialPowers.setBoolPref("selectioncaret.noneditable", %s);' %
            ('true' if enabled else 'false'))

        test_html = self.marionette.absolute_url('test_selectioncarets_multiplerange.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._sel1 = self.marionette.find_element(By.ID, 'sel1')
        self._sel2 = self.marionette.find_element(By.ID, 'sel2')
        self._sel3 = self.marionette.find_element(By.ID, 'sel3')
        self._sel4 = self.marionette.find_element(By.ID, 'sel4')
        self._sel6 = self.marionette.find_element(By.ID, 'sel6')
        self._nonsel1 = self.marionette.find_element(By.ID, 'nonsel1')

    def _long_press_to_select_word(self, el, wordOrdinal):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(wordOrdinal < len(words),
            'Expect at least %d words in the content.' % wordOrdinal)

        # Calc offset
        offset = 0
        for i in range(wordOrdinal):
            offset += (len(words[i]) + 1)

        # Move caret inside the word.
        el.tap()
        sel.move_caret_to_front()
        sel.move_caret_by_offset(offset)
        x, y = sel.caret_location()

        # Long press the caret position. Selection carets should appear, and the
        # word will be selected. On Windows, those spaces after the word
        # will also be selected.
        long_press_without_contextmenu(self.marionette, el, self._long_press_time, x, y)

    def _to_unix_line_ending(self, s):
        """Changes all Windows/Mac line endings in s to UNIX line endings."""

        return s.replace('\r\n', '\n').replace('\r', '\n')

    def test_long_press_to_select_non_selectable_word(self):
        '''Testing long press on non selectable field.
        We should not select anything when long press on non selectable fields.'''

        self.openTestHtml(enabled=True)
        halfY = self._nonsel1.size['height'] / 2
        long_press_without_contextmenu(self.marionette, self._nonsel1, self._long_press_time, 0, halfY)
        sel = SelectionManager(self._nonsel1)
        range_count = sel.range_count()
        self.assertEqual(range_count, 0)

    def test_drag_caret_over_non_selectable_field(self):
        '''Testing drag caret over non selectable field.
        So that the selected content should exclude non selectable field and
        end selection caret should appear in last range's position.'''
        self.openTestHtml(enabled=True)

        # Select target element and get target caret location
        self._long_press_to_select_word(self._sel4, 3)
        sel = SelectionManager(self._body)
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        self._long_press_to_select_word(self._sel6, 0)
        (_, _), (end_caret2_x, end_caret2_y) = sel.selection_carets_location()

        # Select start element
        self._long_press_to_select_word(self._sel3, 3)

        # Drag end caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()),
            'this 3\nuser can select this')

        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret2_x, end_caret2_y, 1).perform()
        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()),
            'this 3\nuser can select this 4\nuser can select this 5\nuser')

    def test_drag_caret_to_beginning_of_a_line(self):
        '''Bug 1094056
        Test caret visibility when caret is dragged to beginning of a line
        '''
        self.openTestHtml(enabled=True)

        # Select the first word in the second line
        self._long_press_to_select_word(self._sel2, 0)
        sel = SelectionManager(self._body)
        (start_caret_x, start_caret_y), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        # Select target word in the first line
        self._long_press_to_select_word(self._sel1, 2)

        # Drag end caret to the beginning of the second line
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, start_caret_x, start_caret_y).perform()

        # Drag end caret back to the target word
        self.actions.flick(self._body, start_caret_x, start_caret_y, caret2_x, caret2_y).perform()

        self.assertEqual(self._to_unix_line_ending(sel.selected_content.strip()), 'select')
class AccessibleCaretSelectionModeTestCase(MarionetteTestCase):
    '''Test cases for AccessibleCaret under selection mode, aka selection carets.

    '''

    def setUp(self):
        # Code to execute before a tests are run.
        super(AccessibleCaretSelectionModeTestCase, self).setUp()
        self.carets_tested_pref = 'layout.accessiblecaret.enabled'
        self.prefs = {
            'layout.word_select.eat_space_to_next_word': False,
            'layout.accessiblecaret.use_long_tap_injector': False,
            self.carets_tested_pref: True,
        }
        self.marionette.set_prefs(self.prefs)
        self.actions = Actions(self.marionette)

    def open_test_html(self):
        'Open html for testing and locate elements.'
        test_html = self.marionette.absolute_url('test_selectioncarets.html')
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(By.ID, 'input')
        self._textarea = self.marionette.find_element(By.ID, 'textarea')
        self._textarea_rtl = self.marionette.find_element(By.ID, 'textarea_rtl')
        self._contenteditable = self.marionette.find_element(By.ID, 'contenteditable')
        self._content = self.marionette.find_element(By.ID, 'content')
        self._non_selectable = self.marionette.find_element(By.ID, 'non_selectable')

    def open_test_html2(self):
        'Open html for testing and locate elements.'
        test_html2 = self.marionette.absolute_url('test_selectioncarets_multipleline.html')
        self.marionette.navigate(test_html2)

        self._textarea2 = self.marionette.find_element(By.ID, 'textarea2')
        self._contenteditable2 = self.marionette.find_element(By.ID, 'contenteditable2')
        self._content2 = self.marionette.find_element(By.ID, 'content2')

    def open_test_html_multirange(self):
        'Open html for testing non-editable support.'
        test_html = self.marionette.absolute_url('test_selectioncarets_multiplerange.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._sel1 = self.marionette.find_element(By.ID, 'sel1')
        self._sel2 = self.marionette.find_element(By.ID, 'sel2')
        self._sel3 = self.marionette.find_element(By.ID, 'sel3')
        self._sel4 = self.marionette.find_element(By.ID, 'sel4')
        self._sel6 = self.marionette.find_element(By.ID, 'sel6')
        self._nonsel1 = self.marionette.find_element(By.ID, 'nonsel1')

    def open_test_html_long_text(self):
        'Open html for testing long text.'
        test_html = self.marionette.absolute_url('test_selectioncarets_longtext.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._longtext = self.marionette.find_element(By.ID, 'longtext')

    def open_test_html_iframe(self):
        'Open html for testing iframe.'
        test_html = self.marionette.absolute_url('test_selectioncarets_iframe.html')
        self.marionette.navigate(test_html)

        self._iframe = self.marionette.find_element(By.ID, 'frame')

    def open_test_html_display_none(self):
        'Open html for testing html with display: none.'
        test_html = self.marionette.absolute_url('test_carets_display_none.html')
        self.marionette.navigate(test_html)

        self._html = self.marionette.find_element(By.ID, 'html')
        self._content = self.marionette.find_element(By.ID, 'content')

    def word_offset(self, text, ordinal):
        'Get the character offset of the ordinal-th word in text.'
        tokens = re.split(r'(\S+)', text)         # both words and spaces
        spaces = tokens[0::2]                     # collect spaces at odd indices
        words = tokens[1::2]                      # collect word at even indices

        if ordinal >= len(words):
            raise IndexError('Only %d words in text, but got ordinal %d' %
                             (len(words), ordinal))

        # Cursor position of the targeting word is behind the the first
        # character in the word. For example, offset to 'def' in 'abc def' is
        # between 'd' and 'e'.
        offset = len(spaces[0]) + 1
        offset += sum(len(words[i]) + len(spaces[i + 1]) for i in range(ordinal))
        return offset

    def test_word_offset(self):
        text = ' ' * 3 + 'abc' + ' ' * 3 + 'def'

        self.assertTrue(self.word_offset(text, 0), 4)
        self.assertTrue(self.word_offset(text, 1), 10)
        with self.assertRaises(IndexError):
            self.word_offset(text, 2)

    def word_location(self, el, ordinal):
        '''Get the location (x, y) of the ordinal-th word in el.

        The ordinal starts from 0.

        Note: this function has a side effect which changes focus to the
        target element el.

        '''
        sel = SelectionManager(el)
        offset = self.word_offset(sel.content, ordinal)

        # Move caret to the word.
        el.tap()
        sel.move_caret_to_front()
        sel.move_caret_by_offset(offset)
        x, y = sel.caret_location()

        return x, y

    def rect_relative_to_window(self, el):
        '''Get element's bounding rectangle.

        This function is similar to el.rect, but the coordinate is relative to
        the top left corner of the window instead of the document.

        '''
        return self.marionette.execute_script('''
            let rect = arguments[0].getBoundingClientRect();
            return {x: rect.x, y:rect.y, width: rect.width, height: rect.height};
            ''', script_args=[el])

    def long_press_on_location(self, el, x=None, y=None):
        '''Long press the location (x, y) to select a word.

        If no (x, y) are given, it will be targeted at the center of the
        element. On Windows, those spaces after the word will also be selected.
        This function sends synthesized eMouseLongTap to gecko.

        '''
        rect = self.rect_relative_to_window(el)
        target_x = rect['x'] + (x if x is not None else rect['width'] // 2)
        target_y = rect['y'] + (y if y is not None else rect['height'] // 2)

        self.marionette.execute_script('''
            let Ci = Components.interfaces;
            let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
            utils.sendTouchEventToWindow('touchstart', [0],
                                         [arguments[0]], [arguments[1]],
                                         [1], [1], [0], [1], 1, 0);
            utils.sendMouseEventToWindow('mouselongtap', arguments[0], arguments[1],
                                          0, 1, 0);
            utils.sendTouchEventToWindow('touchend', [0],
                                         [arguments[0]], [arguments[1]],
                                         [1], [1], [0], [1], 1, 0);
            ''', script_args=[target_x, target_y], sandbox='system')

    def long_press_on_word(self, el, wordOrdinal):
        x, y = self.word_location(el, wordOrdinal)
        self.long_press_on_location(el, x, y)

    def to_unix_line_ending(self, s):
        """Changes all Windows/Mac line endings in s to UNIX line endings."""

        return s.replace('\r\n', '\n').replace('\r', '\n')

    def _test_long_press_to_select_a_word(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 2, 'Expect at least two words in the content.')
        target_content = words[0]

        # Goal: Select the first word.
        self.long_press_on_word(el, 0)

        # Ignore extra spaces selected after the word.
        assertFunc(target_content, sel.selected_content)

    def _test_move_selection_carets(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        # Goal: Select all text after the first word.
        target_content = original_content[len(words[0]):]

        # Get the location of the selection carets at the end of the content for
        # later use.
        el.tap()
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        self.long_press_on_word(el, 0)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Move the left caret to the previous position of the right caret.
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        assertFunc(target_content, sel.selected_content)

    def _test_minimum_select_one_character(self, el, assertFunc,
                                           x=None, y=None):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        # Get the location of the selection carets at the end of the content for
        # later use.
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()
        el.tap()

        # Goal: Select the first character.
        target_content = original_content[0]

        if x and y:
            # If we got x and y from the arguments, use it as a hint of the
            # location of the first word
            pass
        else:
            x, y = self.word_location(el, 0)
        self.long_press_on_location(el, x, y)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()

        # Move the right caret to the position of the left caret.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    def _test_focus_obtained_by_long_press(self, el1, el2):
        '''Test the focus could be changed from el1 to el2 by long press.

        If the focus is changed to e2 successfully, SelectionCarets should
        appear and could be dragged.

        '''
        # Goal: Tap to focus el1, and then select the first character on
        # el2.

        # We want to collect the location of the first word in el2 here
        # since self.word_location() has the side effect which would
        # change the focus.
        x, y = self.word_location(el2, 0)
        el1.tap()
        self._test_minimum_select_one_character(el2, self.assertEqual,
                                                x=x, y=y)

    def _test_focus_not_being_changed_by_long_press_on_non_selectable(self, el):
        # Goal: Focus remains on the editable element el after long pressing on
        # the non-selectable element.
        sel = SelectionManager(el)
        self.long_press_on_word(el, 0)
        self.long_press_on_location(self._non_selectable)
        active_sel = SelectionManager(self.marionette.get_active_element())
        self.assertEqual(sel.content, active_sel.content)

    def _test_handle_tilt_when_carets_overlap_to_each_other(self, el, assertFunc):
        '''Test tilt handling when carets overlap to each other.

        Let SelectionCarets overlap to each other. If SelectionCarets are set
        to tilted successfully, tapping the tilted carets should not cause the
        selection to be collapsed and the carets should be draggable.
        '''

        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')

        # Goal: Select the first word.
        self.long_press_on_word(el, 0)
        target_content = sel.selected_content

        # Move the left caret to the position of the right caret to trigger
        # carets overlapping.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()

        # We make two hit tests targeting the left edge of the left tilted caret
        # and the right edge of the right tilted caret. If either of the hits is
        # missed, selection would be collapsed and both carets should not be
        # draggable.
        (caret3_x, caret3_y), (caret4_x, caret4_y) = sel.selection_carets_location()

        # The following values are from ua.css and all.js
        if self.carets_tested_pref == 'selectioncaret.enabled':
            caret_width = 44
            caret_margin_left = -23
            tilt_right_margin_left = 18
            tilt_left_margin_left = -17
        elif self.carets_tested_pref == 'layout.accessiblecaret.enabled':
            caret_width = float(self.marionette.get_pref('layout.accessiblecaret.width'))
            caret_margin_left = float(self.marionette.get_pref('layout.accessiblecaret.margin-left'))
            tilt_right_margin_left = 0.41 * caret_width;
            tilt_left_margin_left = -0.39 * caret_width;

        left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
        el.tap(left_caret_left_edge_x + 2, caret3_y)

        right_caret_right_edge_x = (caret4_x + caret_margin_left +
                                    tilt_right_margin_left + caret_width)
        el.tap(right_caret_right_edge_x - 2, caret4_y)

        # Drag the left caret back to the initial selection, the first word.
        self.actions.flick(el, caret3_x, caret3_y, caret1_x, caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    def test_long_press_to_select_non_selectable_word(self):
        '''Testing long press on non selectable field.
        We should not select anything when long press on non selectable fields.'''

        self.open_test_html_multirange()
        halfY = self._nonsel1.size['height'] / 2
        self.long_press_on_location(self._nonsel1, 0, halfY)
        sel = SelectionManager(self._nonsel1)
        range_count = sel.range_count()
        self.assertEqual(range_count, 0)

    def test_drag_caret_over_non_selectable_field(self):
        '''Testing drag caret over non selectable field.
        So that the selected content should exclude non selectable field and
        end selection caret should appear in last range's position.'''
        self.open_test_html_multirange()

        # Select target element and get target caret location
        self.long_press_on_word(self._sel4, 3)
        sel = SelectionManager(self._body)
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        self.long_press_on_word(self._sel6, 0)
        (_, _), (end_caret2_x, end_caret2_y) = sel.selection_carets_location()

        # Select start element
        self.long_press_on_word(self._sel3, 3)

        # Drag end caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
                         'this 3\nuser can select this')

        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret2_x, end_caret2_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
                         'this 3\nuser can select this 4\nuser can select this 5\nuser')

        # Drag first caret to target location
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret1_x, caret1_y, end_caret_x, end_caret_y, 1).perform()
        self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
                         '4\nuser can select this 5\nuser')

    def test_drag_caret_to_beginning_of_a_line(self):
        '''Bug 1094056
        Test caret visibility when caret is dragged to beginning of a line
        '''
        self.open_test_html_multirange()

        # Select the first word in the second line
        self.long_press_on_word(self._sel2, 0)
        sel = SelectionManager(self._body)
        (start_caret_x, start_caret_y), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        # Select target word in the first line
        self.long_press_on_word(self._sel1, 2)

        # Drag end caret to the beginning of the second line
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, start_caret_x, start_caret_y).perform()

        # Drag end caret back to the target word
        self.actions.flick(self._body, start_caret_x, start_caret_y, caret2_x, caret2_y).perform()

        self.assertEqual(self.to_unix_line_ending(sel.selected_content), 'select')

    @skip_if_not_rotatable
    def test_caret_position_after_changing_orientation_of_device(self):
        '''Bug 1094072
        If positions of carets are updated correctly, they should be draggable.
        '''
        self.open_test_html_long_text()

        # Select word in portrait mode, then change to landscape mode
        self.marionette.set_orientation('portrait')
        self.long_press_on_word(self._longtext, 12)
        sel = SelectionManager(self._body)
        (p_start_caret_x, p_start_caret_y), (p_end_caret_x, p_end_caret_y) = sel.selection_carets_location()
        self.marionette.set_orientation('landscape')
        (l_start_caret_x, l_start_caret_y), (l_end_caret_x, l_end_caret_y) = sel.selection_carets_location()

        # Drag end caret to the start caret to change the selected content
        self.actions.flick(self._body, l_end_caret_x, l_end_caret_y, l_start_caret_x, l_start_caret_y).perform()

        # Change orientation back to portrait mode to prevent affecting
        # other tests
        self.marionette.set_orientation('portrait')

        self.assertEqual(self.to_unix_line_ending(sel.selected_content), 'o')

    def test_select_word_inside_an_iframe(self):
        '''Bug 1088552
        The scroll offset in iframe should be taken into consideration properly.
        In this test, we scroll content in the iframe to the bottom to cause a
        huge offset. If we use the right coordinate system, selection should
        work. Otherwise, it would be hard to trigger select word.
        '''
        self.open_test_html_iframe()

        # switch to inner iframe and scroll to the bottom
        self.marionette.switch_to_frame(self._iframe)
        self.marionette.execute_script(
            'document.getElementById("bd").scrollTop += 999')

        # long press to select bottom text
        self._body = self.marionette.find_element(By.ID, 'bd')
        sel = SelectionManager(self._body)
        self._bottomtext = self.marionette.find_element(By.ID, 'bottomtext')
        self.long_press_on_location(self._bottomtext)

        self.assertNotEqual(self.to_unix_line_ending(sel.selected_content), '')

    def test_carets_initialized_in_display_none(self):
        '''Test AccessibleCaretEventHub is properly initialized on a <html> with
        display: none.

        '''
        self.open_test_html_display_none()

        # Remove 'display: none' from <html>
        self.marionette.execute_script(
            'arguments[0].style.display = "unset";',
            script_args=[self._html]
        )

        # If AccessibleCaretEventHub is initialized successfully, select a word
        # should work.
        self._test_long_press_to_select_a_word(self._content, self.assertEqual)

    ########################################################################
    # <input> test cases with selection carets enabled
    ########################################################################
    def test_input_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._input, self.assertEqual)

    def test_input_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._input, self.assertEqual)

    def test_input_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._input, self.assertEqual)

    def test_input_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._input)

    def test_input_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable, self._input)

    def test_input_focus_obtained_by_long_press_from_content_non_editable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._input)

    def test_input_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._input, self.assertEqual)

    def test_input_focus_not_changed_by_long_press_on_non_selectable(self):
        self.open_test_html()
        self._test_focus_not_being_changed_by_long_press_on_non_selectable(self._input)

    ########################################################################
    # <input> test cases with selection carets disabled
    ########################################################################
    def test_input_long_press_to_select_a_word_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_long_press_to_select_a_word(self._input, self.assertNotEqual)

    def test_input_move_selection_carets_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_move_selection_carets(self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with selection carets enabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._textarea, self.assertEqual)

    def test_textarea_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._textarea, self.assertEqual)

    def test_textarea_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._textarea, self.assertEqual)

    def test_textarea_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable, self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_content_non_editable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._textarea)

    def test_textarea_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._textarea, self.assertEqual)

    def test_textarea_focus_not_changed_by_long_press_on_non_selectable(self):
        self.open_test_html()
        self._test_focus_not_being_changed_by_long_press_on_non_selectable(self._textarea)

    ########################################################################
    # <textarea> test cases with selection carets disabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_long_press_to_select_a_word(self._textarea, self.assertNotEqual)

    def test_textarea_move_selection_carets_disable(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_move_selection_carets(self._textarea, self.assertNotEqual)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets enabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_focus_not_changed_by_long_press_on_non_selectable(self):
        self.open_test_html()
        self._test_focus_not_being_changed_by_long_press_on_non_selectable(self._textarea_rtl)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets disabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_long_press_to_select_a_word(self._textarea_rtl, self.assertNotEqual)

    def test_textarea_rtl_move_selection_carets_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_move_selection_carets(self._textarea_rtl, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with selection carets enabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._contenteditable, self.assertEqual)

    def test_contenteditable_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._contenteditable, self.assertEqual)

    def test_contenteditable_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_content_non_editable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._contenteditable)

    def test_contenteditable_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._contenteditable, self.assertEqual)

    def test_contenteditable_focus_not_changed_by_long_press_on_non_selectable(self):
        self.open_test_html()
        self._test_focus_not_being_changed_by_long_press_on_non_selectable(self._contenteditable)

    ########################################################################
    # <div> contenteditable test cases with selection carets disabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_long_press_to_select_a_word(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_move_selection_carets_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_move_selection_carets(self._contenteditable, self.assertNotEqual)

    ########################################################################
    # <div> non-editable test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._content, self.assertEqual)

    def test_content_non_editable_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._content, self.assertEqual)

    def test_content_non_editable_minimum_select_one_character_by_selection(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._content, self.assertEqual)

    def test_content_non_editable_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable, self._content)

    def test_content_non_editable_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(self._content, self.assertEqual)

    ########################################################################
    # <textarea> (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_textarea2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._textarea2, self.assertEqual)

    ########################################################################
    # <div> contenteditable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_contenteditable2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._contenteditable2, self.assertEqual)

    ########################################################################
    # <div> non-editable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._content2, self.assertEqual)

    def test_long_press_to_select_when_partial_visible_word_is_selected(self):
        self.open_test_html()
        el = self._input
        sel = SelectionManager(el)

        # To successfully select the second word while the first word is being
        # selected, use sufficient spaces between 'a' and 'b' to avoid the
        # second caret covers on the second word.
        original_content = 'aaaaaaaa          bbbbbbbb'
        el.clear()
        el.send_keys(original_content)
        words = original_content.split()

        # We cannot use self.long_press_on_word() directly since it has will
        # change the cursor position which affects this test. We have to store
        # the position of word 0 and word 1 before long-pressing to select the
        # word.
        word0_x, word0_y = self.word_location(el, 0)
        word1_x, word1_y = self.word_location(el, 1)

        self.long_press_on_location(el, word0_x, word0_y)
        self.assertEqual(words[0], sel.selected_content)

        self.long_press_on_location(el, word1_x, word1_y)
        self.assertEqual(words[1], sel.selected_content)

        self.long_press_on_location(el, word0_x, word0_y)
        self.assertEqual(words[0], sel.selected_content)

        # If the second carets is visible, it can be dragged to the position of
        # the first caret. After that, selection will contain only the first
        # character.
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()
        self.assertEqual(words[0][0], sel.selected_content)

    def test_carets_do_not_jump_when_dragging_to_editable_content_boundary(self):
        self.open_test_html()
        el = self._input
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(len(words) >= 3, 'Expect at least three words in the content.')

        # Goal: the selection does not being changed after dragging the caret
        # on the Y-axis only.
        target_content = words[1]

        self.long_press_on_word(el, 1)
        (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.selection_carets_location()

        # Drag the first caret up by 50px.
        self.actions.flick(el, caret1_x, caret1_y, caret1_x, caret1_y - 50).perform()
        self.assertEqual(target_content, sel.selected_content)

        # Drag the first caret down by 50px.
        self.actions.flick(el, caret2_x, caret2_y, caret2_x, caret2_y + 50).perform()
        self.assertEqual(target_content, sel.selected_content)
Esempio n. 30
0
class Calendar(object):
    def __init__(self, parent):
        self.apps = parent.apps
        self.data_layer = parent.data_layer
        self.parent = parent
        self.marionette = parent.marionette
        self.UTILS = parent.UTILS
        self.actions = Actions(self.marionette)

    def launch(self):
        self.app = self.apps.launch(self.__class__.__name__)
        self.UTILS.element.waitForNotElements(
            DOM.GLOBAL.loading_overlay,
            self.__class__.__name__ + " app - loading overlay")
        return self.app

    def moveDayViewBy(self, num):
        """
        Switches to week view, then moves 'p_num' weeks in the future or past (if the p_num is
        positive or negative) relative to today.
        """
        self.UTILS.reporting.logResult(
            "info", "<b>Adjusting day view by {} screens ...</b>".format(num))
        self.setView("day")
        self.setView("today")

        if num == 0:
            return
        """
        Set the y-coordinate offset, depending on which
        direction we need to flick the display.
        """
        numMoves = num
        if numMoves > 0:
            _moveEl = 1
        else:
            _moveEl = 2
            numMoves = numMoves * -1

        # Now move to the desired screen.
        for i in range(numMoves):
            # Flick the screen to move it (tricky to find the element we can flick!).
            _el = self.marionette.find_elements(*DOM.Calendar.dview_events)

            _num = 0
            for i in range(len(_el)):
                if _el[i].is_displayed():
                    _num = i
                    break

            x_pos1 = 0
            x_pos2 = 0
            if _moveEl == 1:
                x_pos1 = _el[_num].size["width"]
            if _moveEl == 2:
                x_pos2 = _el[_num].size["width"]
            self.actions.flick(_el[_num], x_pos1, 0, x_pos2, 0).perform()

        time.sleep(0.3)

        # Check this is the expected day.
        _new_epoch = int(time.time()) + (num * 24 * 60 * 60)
        _new_now = self.UTILS.date_and_time.getDateTimeFromEpochSecs(
            _new_epoch)
        _expected_str = "{} {}, {}".format(_new_now.month_name[:3],
                                           _new_now.mday, _new_now.day_name)

        x = self.UTILS.element.getElement(DOM.Calendar.current_view_header,
                                          "Current view header")
        self.UTILS.test.test(
            x.text == _expected_str,
            "Header is '<b>%s</b>' (it was '%s')." % (_expected_str, x.text))

        x = self.UTILS.debug.screenShotOnErr()
        self.UTILS.reporting.logResult(
            "info", "Day view screen after moving {} pages: ".format(num), x)

    def getEventPreview(self, p_view, p_hour24, p_title, p_location=False):
        """
        Return object for an event in month / week or day view.
        The tag identifiers aren't consistent, so set them here.
        <type>: (<event preview identifier>, <event title identifier>)
        """
        event_view = {
            "month": (DOM.Calendar.view_events_block_m % p_hour24,
                      DOM.Calendar.view_events_title_month),
            "week": (DOM.Calendar.view_events_block_w % p_hour24,
                     DOM.Calendar.view_events_title_week),
            "day": (DOM.Calendar.view_events_block_d % p_hour24,
                    DOM.Calendar.view_events_title_day)
        }

        viewStr = event_view[p_view]
        """
        Switch to the desired view.
        For the life of me I can't get 'wait_for_element' ... to work in day view, so I'm
        just waiting a few seconds then checking with .is_displayed() instead.
        """
        self.setView(p_view)
        time.sleep(2)

        # Start by getting the parent element objects, which could contain event details.
        event_objects = self.UTILS.element.getElements(
            ('xpath', viewStr[0]), "'" + p_view + "' event details list",
            False, 20, False)
        if not event_objects:
            return False

        if len(event_objects) <= 0:
            return False

        for event_object in event_objects:
            if p_title in event_object.text:
                return event_object

        # If we get to here we failed to return the element we're after.
        return False

    def createEvent(self):
        x = self.UTILS.element.getElement(DOM.Calendar.add_event_btn,
                                          "Add event button")
        x.tap()

        title = ("xpath", "//input[@name='title']")
        where = ("xpath", "//input[@name='location']")
        # allday checkbox, True False (use tap())
        allday = ("xpath", "//li[@class='allday']")

        x = self.UTILS.element.getElement(title, "Title", True, 10)
        x.send_keys("hello title")

        x = self.UTILS.element.getElement(where, "Where")
        x.send_keys("hello where")

        x = self.UTILS.element.getElement(allday, "All day")
        x.tap()

        self.marionette.execute_script(
            "document.getElementById('start-date-locale').click()")

    def changeDay(self, numDays, viewType):
        """
        Changes the calendar day to a different day relative to 'today' - <b>uses the
        month view to do this, then switches back to whichever
        view you want (month, week, day)</b>.<br>
        <b>numDays</b> is a number (can be negative to go back, i.e. -5,-2,1,3,5 etc...).<br>
        <b>viewType</b> is the calendar view to return to (today / day / week / month)<br>
        Returns a modified DateTime object from <i>UTILS.getDateTimeFromEpochSecs()</i>.
        """
        self.setView("month")
        self.setView("today")

        now_secs = time.time()
        now_diff = int(now_secs) + (86400 * numDays)
        now_today = self.UTILS.date_and_time.getDateTimeFromEpochSecs(now_secs)
        new_today = self.UTILS.date_and_time.getDateTimeFromEpochSecs(now_diff)

        # Switch to month view and tap this day, then switch back to our view.
        if now_today.mon != new_today.mon:
            x = new_today.mon - now_today.mon
            self.moveMonthViewBy(x)

        el_id_str = "d-%s-%s-%s" % (new_today.year, new_today.mon - 1,
                                    new_today.mday)
        x = self.UTILS.element.getElement(
            ("xpath", "//li[@data-date='{}']".format(el_id_str)),
            "Cell for day {}/{}/{}".format(new_today.mday, new_today.mon,
                                           new_today.year))
        x.tap()
        self.setView(viewType.lower())
        return new_today

    def moveMonthViewBy(self, num):
        """
        Switches to month view, then moves 'num' months in the future or past (if the num is
        positive or negative) relative to today.
        """
        self.UTILS.reporting.logResult(
            "info", "<b>Adjusting month view by {} months ...</b>".format(num))
        self.setView("month")
        self.setView("today")

        if num == 0:
            return
        """
        Set the y-coordinate offset, depending on which
        direction we need to flick the display.
        """
        numMoves = num
        x2 = 0
        if numMoves > 0:
            el_num = -1
            x2 = -500
        if numMoves < 0:
            el_num = 0
            x2 = 500
            numMoves = numMoves * -1

        now = time.localtime(int(time.time()))
        month = now.tm_mon
        year = now.tm_year

        for i in range(numMoves):
            # Flick the display to show the date we're aiming for.
            el = self.marionette.find_elements(
                *DOM.Calendar.mview_first_row_for_flick)[el_num]
            self.actions.flick(el, 0, 0, x2, 0).perform()

            # Increment the month and year so we keep track of what's expected.
            if num < 0:
                month = month - 1
            else:
                month = month + 1

            if month <= 0:
                month = 12
                year = year - 1
            elif month >= 13:
                month = 1
                year = year + 1

        time.sleep(0.3)

        # Work out what the header should now be.
        month_names = [
            "January", "February", "March", "April", "May", "June", "July",
            "August", "September", "October", "November", "December"
        ]

        expect = "{} {}".format(month_names[month - 1], year)
        actual = self.UTILS.element.getElement(
            DOM.Calendar.current_view_header, "Header").text

        self.UTILS.test.test(
            expect.lower() in actual.lower(),
            "Expecting header to contain '{}' (received '{}')".format(
                expect, actual))

    def moveWeekViewBy(self, num):
        """
        Switches to week view, then moves 'num' weeks in the future or past (if the num is
        positive or negative) relative to today.
        """
        self.UTILS.reporting.logResult(
            "info", "<b>Adjusting week view by {} screens ...</b>".format(num))
        self.setView("week")
        self.setView("today")

        if num == 0:
            return
        """
        Set the y-coordinate offset, depending on which
        direction we need to flick the display.
        """
        numMoves = num
        x2 = 0
        if numMoves > 0:
            el = -1
            x2 = -500
        if numMoves < 0:
            el = 0
            x2 = 500
            numMoves = numMoves * -1
        """
        Keep track of how many days we're adjusting the display by (so we can check
        we're looking at the correct display at the end).
        """
        days_offset = 0
        now_epoch = int(time.time())
        now = self.UTILS.date_and_time.getDateTimeFromEpochSecs(now_epoch)
        now_str = "{} {}".format(now.day_name[:3].upper(), now.mday)

        displayed_days = self.UTILS.element.getElements(
            DOM.Calendar.wview_active_days, "Active days")
        startpos = 0
        for i in range(len(displayed_days)):
            x = displayed_days[i].text
            if x:
                if now_str in x:
                    startpos = i - 1
                    break

        if num < 0:
            days_offset = startpos
        else:
            days_offset = len(displayed_days) - startpos - 2

        # Now move to the desired screen.
        for i in range(numMoves):

            # Flick the screen to move it.
            self.actions.flick(displayed_days[el], 0, 0, x2, 0).perform()

            # Get the count of days we're adjusting (so we can check later).
            displayed_days = self.UTILS.element.getElements(
                DOM.Calendar.wview_active_days, "Active days")
            days_offset = days_offset + len(displayed_days)

        time.sleep(0.3)
        """
        Work out what the display should now be:
        1. Today + days_offset should be displayed.
        2. Header should be month + year, now + days_offset should be in active days.
        """
        if num < 0:
            new_epoch = now_epoch - (days_offset * 24 * 60 * 60)
        else:
            new_epoch = now_epoch + (days_offset * 24 * 60 * 60)

        new_now = self.UTILS.date_and_time.getDateTimeFromEpochSecs(new_epoch)

        new_now_str = "{} {}".format(new_now.day_name[:3].upper(),
                                     new_now.mday)

        x = self.UTILS.element.getElements(DOM.Calendar.wview_active_days,
                                           "Active days")
        boolOK = False
        for i in range(len(x)):
            x = displayed_days[i].text
            if x:
                if new_now_str in x:
                    boolOK = True
                    break

        self.UTILS.test.test(
            boolOK,
            "The column for date '<b>{}</b>' displayed.".format(new_now_str))

        x = self.UTILS.element.getElement(DOM.Calendar.current_view_header,
                                          "Current view header")
        self.UTILS.test.test(
            new_now.month_name in x.text,
            "'<b>{}</b>' is in header ('{}').".format(new_now.month_name,
                                                      x.text))
        self.UTILS.test.test(
            str(new_now.year) in x.text,
            "'<b>{}</b>' is in header ('{}').".format(new_now.year, x.text))

        x = self.UTILS.debug.screenShotOnErr()
        self.UTILS.reporting.logResult(
            "info", "Week view screen after moving {} pages: ".format(num), x)

    def setView(self, typ):
        """
        Set to view typ (today / day / week / month).
        """
        x = self.UTILS.element.getElement(
            (DOM.Calendar.view_type[0],
             DOM.Calendar.view_type[1] % typ.lower()),
            "'" + typ + "' view type selector")
        x.tap()

        if typ.lower() != 'today':
            viewTypes = {
                "month": DOM.Calendar.mview_container,
                "week": DOM.Calendar.wview_container,
                "day": DOM.Calendar.dview_container
            }

            self.UTILS.element.waitForElements(
                viewTypes[typ], "Container for '{}' view".format(typ))
        else:
            time.sleep(0.5)
Esempio n. 31
0
class CommonCaretTestCase(object):
    """Common test cases for a collapsed selection with a single caret.

    To run these test cases, a subclass must inherit from both this class and
    MarionetteTestCase.

    """

    def setUp(self):
        # Code to execute before a test is being run.
        super(CommonCaretTestCase, self).setUp()
        self.actions = Actions(self.marionette)

    def timeout_ms(self):
        "Return touch caret expiration time in milliseconds."
        return self.marionette.get_pref(self.caret_timeout_ms_pref)

    def open_test_html(self):
        "Open html for testing and locate elements."
        test_html = self.marionette.absolute_url("test_touchcaret.html")
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(By.ID, "input")
        self._textarea = self.marionette.find_element(By.ID, "textarea")
        self._contenteditable = self.marionette.find_element(By.ID, "contenteditable")

    def _test_move_caret_to_the_right_by_one_character(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = "!"
        target_content = sel.content
        target_content = target_content[:1] + content_to_add + target_content[1:]

        # Get touch caret (x, y) at position 1 and 2.
        el.tap()
        sel.move_caret_to_front()
        caret0_x, caret0_y = sel.caret_location()
        touch_caret0_x, touch_caret0_y = sel.touch_caret_location()
        sel.move_caret_by_offset(1)
        touch_caret1_x, touch_caret1_y = sel.touch_caret_location()

        # Tap the front of the input to make touch caret appear.
        el.tap(caret0_x, caret0_y)

        # Move touch caret
        self.actions.flick(el, touch_caret0_x, touch_caret0_y, touch_caret1_x, touch_caret1_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = "!"
        target_content = sel.content + content_to_add

        # Tap the front of the input to make touch caret appear.
        el.tap()
        sel.move_caret_to_front()
        el.tap(*sel.caret_location())

        # Move touch caret to the bottom-right corner of the element.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = el.size["width"], el.size["height"]
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = "!"
        target_content = content_to_add + sel.content

        # Get touch caret location at the front.
        el.tap()
        sel.move_caret_to_front()
        dest_x, dest_y = sel.touch_caret_location()

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())
        src_x, src_y = sel.touch_caret_location()

        # Move touch caret to the front of the input box.
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(target_content, sel.content)

    def _test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = "!"
        non_target_content = content_to_add + sel.content

        # Get touch caret expiration time in millisecond, and convert it to second.
        timeout = self.timeout_ms() / 1000.0

        # Set a 3x timeout margin to prevent intermittent test failures.
        timeout *= 3

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Wait until touch caret disappears, then pretend to move it to the
        # top-left corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.wait(timeout).flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(non_target_content, sel.content)

    def _test_touch_caret_hides_after_receiving_wheel_event(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = "!"
        non_target_content = content_to_add + sel.content

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Send an arbitrary scroll-down-10px wheel event to the center of the
        # input box to hide touch caret. Then pretend to move it to the top-left
        # corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        el_center_x, el_center_y = el.rect["x"], el.rect["y"]
        self.marionette.execute_script(
            """
            var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                              .getInterface(Components.interfaces.nsIDOMWindowUtils);
            utils.sendWheelEvent(arguments[0], arguments[1],
                                 0, 10, 0, WheelEvent.DOM_DELTA_PIXEL,
                                 0, 0, 0, 0);
            """,
            script_args=[el_center_x, el_center_y],
            sandbox="system",
        )
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(non_target_content, sel.content)

    def _test_caret_not_appear_when_typing_in_scrollable_content(self, el, assertFunc):
        sel = SelectionManager(el)

        content_to_add = "!"
        target_content = sel.content + string.ascii_letters + content_to_add

        el.tap()
        sel.move_caret_to_end()

        # Insert a long string to the end of the <input>, which triggers
        # ScrollPositionChanged event.
        el.send_keys(string.ascii_letters)

        # The caret should not be visible. If it does appear wrongly due to the
        # ScrollPositionChanged event, we can drag it to the front of the
        # <input> to change the cursor position.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        # The content should be inserted at the end of the <input>.
        el.send_keys(content_to_add)

        assertFunc(target_content, sel.content)

    ########################################################################
    # <input> test cases with touch caret enabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(self._input, self.assertEqual)

    def test_input_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._input, self.assertEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._input, self.assertEqual)

    def test_input_caret_not_appear_when_typing_in_scrollable_content(self):
        self.open_test_html()
        self._test_caret_not_appear_when_typing_in_scrollable_content(self._input, self.assertEqual)

    def test_input_touch_caret_timeout(self):
        with self.marionette.using_prefs({self.caret_timeout_ms_pref: 1000}):
            self.open_test_html()
            self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(
                self._input, self.assertNotEqual
            )

    ########################################################################
    # <input> test cases with touch caret disabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_the_right_by_one_character(self._input, self.assertNotEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret enabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self._textarea, self.assertEqual)

    def test_textarea_touch_caret_timeout(self):
        with self.marionette.using_prefs({self.caret_timeout_ms_pref: 1000}):
            self.open_test_html()
            self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(
                self._textarea, self.assertNotEqual
            )

    ########################################################################
    # <textarea> test cases with touch caret disabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertNotEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
                self._textarea, self.assertNotEqual
            )

    ########################################################################
    # <div> contenteditable test cases with touch caret enabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self._contenteditable, self.assertEqual
        )

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
            self._contenteditable, self.assertEqual
        )

    def test_contenteditable_touch_caret_timeout(self):
        with self.marionette.using_prefs({self.caret_timeout_ms_pref: 1000}):
            self.open_test_html()
            self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(
                self._contenteditable, self.assertNotEqual
            )

    ########################################################################
    # <div> contenteditable test cases with touch caret disabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
                self._contenteditable, self.assertNotEqual
            )
Esempio n. 32
0
class TouchCaretTest(MarionetteTestCase):
    _input_selector = (By.ID, 'input')
    _textarea_selector = (By.ID, 'textarea')
    _contenteditable_selector = (By.ID, 'contenteditable')
    _large_expiration_time = 3000 * 20  # 60 seconds

    def setUp(self):
        # Code to execute before a test is being run.
        MarionetteTestCase.setUp(self)
        self.actions = Actions(self.marionette)
        self.original_expiration_time = self.expiration_time

    def tearDown(self):
        # Code to execute after a test is being run.
        self.expiration_time = self.original_expiration_time
        MarionetteTestCase.tearDown(self)

    @property
    def expiration_time(self):
        'Return touch caret expiration time in milliseconds.'
        return self.marionette.execute_script(
            'return SpecialPowers.getIntPref("touchcaret.expiration.time");')

    @expiration_time.setter
    def expiration_time(self, expiration_time):
        'Set touch caret expiration time in milliseconds.'
        self.marionette.execute_script(
            'SpecialPowers.setIntPref("touchcaret.expiration.time", arguments[0]);',
            script_args=[expiration_time])

    def openTestHtml(self, enabled=True, expiration_time=None):
        '''Open html for testing and locate elements, enable/disable touch caret, and
        set touch caret expiration time in milliseconds).

        '''
        self.marionette.execute_async_script(
            'SpecialPowers.pushPrefEnv({"set": [["touchcaret.enabled", %s]]}, marionetteScriptFinished);' %
            ('true' if enabled else 'false'))

        # Set a larger expiration time to avoid intermittent test failures.
        if expiration_time is not None:
            self.expiration_time = expiration_time

        test_html = self.marionette.absolute_url('test_touchcaret.html')
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(*self._input_selector)
        self._textarea = self.marionette.find_element(*self._textarea_selector)
        self._contenteditable = self.marionette.find_element(*self._contenteditable_selector)

    def _test_move_caret_to_the_right_by_one_character(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content
        target_content = target_content[:1] + content_to_add + target_content[1:]

        # Get touch caret (x, y) at position 1 and 2.
        el.tap()
        sel.move_caret_to_front()
        caret0_x, caret0_y = sel.caret_location()
        touch_caret0_x, touch_caret0_y = sel.touch_caret_location()
        sel.move_caret_by_offset(1)
        touch_caret1_x, touch_caret1_y = sel.touch_caret_location()

        # Tap the front of the input to make touch caret appear.
        el.tap(caret0_x, caret0_y)

        # Move touch caret
        self.actions.flick(el, touch_caret0_x, touch_caret0_y,
                           touch_caret1_x, touch_caret1_y).perform()

        el.send_keys(content_to_add)
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content + content_to_add

        # Tap the front of the input to make touch caret appear.
        el.tap()
        sel.move_caret_to_front()
        el.tap(*sel.caret_location())

        # Move touch caret to the bottom-right corner of the element.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = el.size['width'], el.size['height']
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        el.send_keys(content_to_add)
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = content_to_add + sel.content

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Move touch caret to the top-left corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        el.send_keys(content_to_add)
        assertFunc(target_content, sel.content)

    def _test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Get touch caret expiration time in millisecond, and convert it to second.
        timeout = self.expiration_time / 1000.0

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Wait until touch caret disappears, then pretend to move it to the
        # top-left corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.wait(timeout).flick(el, src_x, src_y, dest_x, dest_y).perform()

        el.send_keys(content_to_add)
        assertFunc(non_target_content, sel.content)

    def _test_touch_caret_hides_after_receiving_wheel_event(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Send an arbitrary scroll-down-10px wheel event to the center of the
        # input box to hide touch caret. Then pretend to move it to the top-left
        # corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        el_center_x, el_center_y = el.rect['x'], el.rect['y']
        self.marionette.execute_script(
            '''var utils = SpecialPowers.getDOMWindowUtils(window);
            utils.sendWheelEvent(arguments[0], arguments[1],
                                 0, 10, 0, WheelEvent.DOM_DELTA_PIXEL,
                                 0, 0, 0, 0);''',
            script_args=[el_center_x, el_center_y]
        )
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        el.send_keys(content_to_add)
        assertFunc(non_target_content, sel.content)


    ########################################################################
    # <input> test cases with touch caret enabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_the_right_by_one_character(self._input, self.assertEqual)

    def test_input_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._input, self.assertEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._input, self.assertEqual)

    def test_input_touch_caret_timeout(self):
        self.openTestHtml(enabled=True)
        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._input, self.assertNotEqual)

    def test_input_touch_caret_hides_after_receiving_wheel_event(self):
        self.openTestHtml(enabled=True, expiration_time=0)
        self._test_touch_caret_hides_after_receiving_wheel_event(self._input, self.assertNotEqual)

    ########################################################################
    # <input> test cases with touch caret disabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_caret_to_the_right_by_one_character(self._input, self.assertNotEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret enabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._textarea, self.assertEqual)

    def test_textarea_touch_caret_timeout(self):
        self.openTestHtml(enabled=True)
        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._textarea, self.assertNotEqual)

    def test_textarea_touch_caret_hides_after_receiving_wheel_event(self):
        self.openTestHtml(enabled=True, expiration_time=0)
        self._test_touch_caret_hides_after_receiving_wheel_event(self._textarea, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret disabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertNotEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._textarea, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with touch caret enabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._contenteditable, self.assertEqual)

    def test_contenteditable_touch_caret_timeout(self):
        self.openTestHtml(enabled=True)
        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_touch_caret_hides_after_receiving_wheel_event(self):
        self.openTestHtml(enabled=True, expiration_time=0)
        self._test_touch_caret_hides_after_receiving_wheel_event(self._contenteditable, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with touch caret disabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertNotEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
        self.openTestHtml(enabled=False)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._contenteditable, self.assertNotEqual)
Esempio n. 33
0
class CommonCaretTestCase(object):
    '''Common test cases for a collapsed selection with a single caret.

    To run these test cases, a subclass must inherit from both this class and
    MarionetteTestCase.

    '''
    _input_selector = (By.ID, 'input')
    _textarea_selector = (By.ID, 'textarea')
    _contenteditable_selector = (By.ID, 'contenteditable')

    def setUp(self):
        # Code to execute before a test is being run.
        super(CommonCaretTestCase, self).setUp()
        self.actions = Actions(self.marionette)

        # The caret to be tested.
        self.caret_tested_pref = None

        # The caret to be disabled in this test suite.
        self.caret_disabled_pref = None
        self.caret_timeout_ms_pref = None

    def set_pref(self, pref_name, value):
        '''Set a preference to value.

        For example:
        >>> set_pref('layout.accessiblecaret.enabled', True)

        '''
        pref_name = repr(pref_name)
        if isinstance(value, bool):
            value = 'true' if value else 'false'
            func = 'setBoolPref'
        elif isinstance(value, int):
            value = str(value)
            func = 'setIntPref'
        else:
            value = repr(value)
            func = 'setCharPref'

        with self.marionette.using_context('chrome'):
            script = 'Services.prefs.%s(%s, %s)' % (func, pref_name, value)
            self.marionette.execute_script(script)

    def timeout_ms(self):
        'Return touch caret expiration time in milliseconds.'
        with self.marionette.using_context('chrome'):
            return self.marionette.execute_script(
                'return Services.prefs.getIntPref("%s");' %
                self.caret_timeout_ms_pref)

    def open_test_html(self, enabled=True, timeout_ms=0):
        '''Open html for testing and locate elements, enable/disable touch caret, and
        set touch caret expiration time in milliseconds).

        '''
        self.set_pref(self.caret_tested_pref, enabled)
        self.set_pref(self.caret_disabled_pref, False)
        self.set_pref(self.caret_timeout_ms_pref, timeout_ms)

        test_html = self.marionette.absolute_url('test_touchcaret.html')
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(*self._input_selector)
        self._textarea = self.marionette.find_element(*self._textarea_selector)
        self._contenteditable = self.marionette.find_element(
            *self._contenteditable_selector)

    def _test_move_caret_to_the_right_by_one_character(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content
        target_content = target_content[:1] + content_to_add + target_content[
            1:]

        # Get touch caret (x, y) at position 1 and 2.
        el.tap()
        sel.move_caret_to_front()
        caret0_x, caret0_y = sel.caret_location()
        touch_caret0_x, touch_caret0_y = sel.touch_caret_location()
        sel.move_caret_by_offset(1)
        touch_caret1_x, touch_caret1_y = sel.touch_caret_location()

        # Tap the front of the input to make touch caret appear.
        el.tap(caret0_x, caret0_y)

        # Move touch caret
        self.actions.flick(el, touch_caret0_x, touch_caret0_y, touch_caret1_x,
                           touch_caret1_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content + content_to_add

        # Tap the front of the input to make touch caret appear.
        el.tap()
        sel.move_caret_to_front()
        el.tap(*sel.caret_location())

        # Move touch caret to the bottom-right corner of the element.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = el.size['width'], el.size['height']
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
            self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = content_to_add + sel.content

        # Get touch caret location at the front.
        el.tap()
        sel.move_caret_to_front()
        dest_x, dest_y = sel.touch_caret_location()

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())
        src_x, src_y = sel.touch_caret_location()

        # Move touch caret to the front of the input box.
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(target_content, sel.content)

    def _test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(
            self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Get touch caret expiration time in millisecond, and convert it to second.
        timeout = self.timeout_ms() / 1000.0

        # Set a 3x timeout margin to prevent intermittent test failures.
        timeout *= 3

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Wait until touch caret disappears, then pretend to move it to the
        # top-left corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.wait(timeout).flick(el, src_x, src_y, dest_x,
                                         dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(non_target_content, sel.content)

    def _test_touch_caret_hides_after_receiving_wheel_event(
            self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Send an arbitrary scroll-down-10px wheel event to the center of the
        # input box to hide touch caret. Then pretend to move it to the top-left
        # corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        el_center_x, el_center_y = el.rect['x'], el.rect['y']
        self.marionette.execute_script('''
            var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                              .getInterface(Components.interfaces.nsIDOMWindowUtils);
            utils.sendWheelEvent(arguments[0], arguments[1],
                                 0, 10, 0, WheelEvent.DOM_DELTA_PIXEL,
                                 0, 0, 0, 0);
            ''',
                                       script_args=[el_center_x, el_center_y],
                                       sandbox='system')
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(non_target_content, sel.content)

    def _test_caret_not_appear_when_typing_in_scrollable_content(
            self, el, assertFunc):
        sel = SelectionManager(el)

        content_to_add = '!'
        target_content = sel.content + string.ascii_letters + content_to_add

        el.tap()
        sel.move_caret_to_end()

        # Insert a long string to the end of the <input>, which triggers
        # ScrollPositionChanged event.
        el.send_keys(string.ascii_letters)

        # The caret should not be visible. If it does appear wrongly due to the
        # ScrollPositionChanged event, we can drag it to the front of the
        # <input> to change the cursor position.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        # The content should be inserted at the end of the <input>.
        el.send_keys(content_to_add)

        assertFunc(target_content, sel.content)

    ########################################################################
    # <input> test cases with touch caret enabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(
            self._input, self.assertEqual)

    def test_input_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self._input, self.assertEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(
            self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
            self._input, self.assertEqual)

    def test_input_caret_not_appear_when_typing_in_scrollable_content(self):
        self.open_test_html()
        self._test_caret_not_appear_when_typing_in_scrollable_content(
            self._input, self.assertEqual)

    def test_input_touch_caret_timeout(self):
        self.open_test_html(timeout_ms=1000)
        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(
            self._input, self.assertNotEqual)

    ########################################################################
    # <input> test cases with touch caret disabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_caret_to_the_right_by_one_character(
            self._input, self.assertNotEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(
            self):
        self.open_test_html(enabled=False)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
            self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret enabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(
            self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(
            self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
            self._textarea, self.assertEqual)

    def test_textarea_touch_caret_timeout(self):
        self.open_test_html(timeout_ms=1000)
        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(
            self._textarea, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret disabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character_disabled(self):
        self.open_test_html(enabled=False)
        self._test_move_caret_to_the_right_by_one_character(
            self._textarea, self.assertNotEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(
            self):
        self.open_test_html(enabled=False)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
            self._textarea, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with touch caret enabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(
            self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(
            self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
            self._contenteditable, self.assertEqual)

    def test_contenteditable_touch_caret_timeout(self):
        self.open_test_html(timeout_ms=1000)
        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(
            self._contenteditable, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with touch caret disabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character_disabled(
            self):
        self.open_test_html(enabled=False)
        self._test_move_caret_to_the_right_by_one_character(
            self._contenteditable, self.assertNotEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(
            self):
        self.open_test_html(enabled=False)
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
            self._contenteditable, self.assertNotEqual)
Esempio n. 34
0
class Gallery(object):
    def __init__(self, parent):
        self.apps = parent.apps
        self.data_layer = parent.data_layer
        self.parent = parent
        self.marionette = parent.marionette
        self.UTILS = parent.UTILS
        self.actions = Actions(self.marionette)

    def launch(self):
        self.app = self.apps.launch(self.__class__.__name__)
        self.UTILS.element.waitForNotElements(
            DOM.GLOBAL.loading_overlay,
            self.__class__.__name__ + " app - loading overlay")
        self.UTILS.element.waitForNotElements(DOM.Gallery.loading_bar,
                                              "Loading bar", True, 10)
        return self.app

    def convert_str_to_seconds(self, the_string):
        """
        Converts a str of the form "aa:bb" into seconds
        """
        processed = time.strptime(the_string, '%M:%S')
        return (int(
            datetime.timedelta(minutes=processed.tm_min,
                               seconds=processed.tm_sec).total_seconds()))

    def check_video_length(self, expected_duration, margin=2):
        """
        This method asserts that the video has the desired duration
        @param  int expected_duration   specifies the video duration in seconds
        """

        # Play the video and get total duration
        self.play_current_video()
        video_length = self.UTILS.element.getElement(
            DOM.Gallery.preview_current_video_duration, "Video length")
        real_duration = self.convert_str_to_seconds(video_length.text)

        # Note: we give 1 second margin in case the things went a little bit slower when recording the video
        interval = range(expected_duration - margin,
                         expected_duration + margin + 1, 1)
        self.UTILS.test.test(real_duration in interval, "Duration matches")

    def click_on_thumbnail_at_position(self, position, preview=True):
        """
        Clicks on a thumbnail at a certain position from the gallery.
        @param  boolean     preview     specifies whether we have to check for the preview screen or not
        """
        self.parent.wait_for_element_displayed(*DOM.Gallery.thumbnail_items)
        thumb_list = self.marionette.find_elements(
            *DOM.Gallery.thumbnail_items)
        time.sleep(1)
        thumb = thumb_list[position]
        thumb.tap()

        self.UTILS.element.waitForNotElements(DOM.Gallery.thumbnail_items,
                                              "Thumbnail list", True, 10)
        if preview:
            self.UTILS.element.waitForElements(DOM.Gallery.preview,
                                               "Thumbnail preview", True, 10)

    def _click_on_thumb_external(self, position, frame_to_change):
        """
        Private method which handles image selection and image cropping
        @param  int     position            thumbnail to click
        @param  tuple   frame_to_change     frame to switch once the image has been cropped
        """
        time.sleep(1)
        self.click_on_thumbnail_at_position(position, preview=False)

        time.sleep(2)

        crop = self.UTILS.element.getElement(DOM.Gallery.crop_done,
                                             "Crop Done")
        crop.tap()

        self.UTILS.iframe.switchToFrame(*frame_to_change)

    def click_on_thumbnail_at_position_mms(self, position):
        """
        Clicks a thumbnail from the gallery in order to attach it to a MMS
        """
        self._click_on_thumb_external(position, DOM.Messages.frame_locator)

    def click_on_thumbnail_at_position_email(self, position):
        """
        Clicks a thumbnail from the gallery in order to attach it to an email
        """
        self._click_on_thumb_external(position, DOM.Email.frame_locator)

    def delete_thumbnails(self, num_array):
        """
        Deletes the thumbnails listed in num_array
        (following an index starting at number 0)
        The list must be numeric, i.e "delete_thumbnails([0,1,2])".
        """

        # Get the amount of thumbnails we currently have.
        before_thumbcount = self.get_number_of_thumbnails()
        delete_thumbcount = len(num_array)
        target_thumbcount = before_thumbcount - delete_thumbcount

        select_mode_btn = self.UTILS.element.getElement(
            DOM.Gallery.thumbnail_select_mode, "Select button")
        select_mode_btn.tap()

        # Select the target ones
        thumbs = self.UTILS.element.getElements(DOM.Gallery.thumbnail_items,
                                                "Thumbnails")
        for position in num_array:
            thumbs[position].tap()

        selected = self.UTILS.element.getElement(
            DOM.Gallery.thumbnail_number_selected, "Number selected header")
        self.UTILS.test.test(
            str(delete_thumbcount) in selected.text,
            "Right number of thumbs selected")

        trash_btn = self.UTILS.element.getElement(
            DOM.Gallery.thumbnail_trash_icon, "Trash icon")
        trash_btn.tap()

        confirm = self.UTILS.element.getElement(DOM.GLOBAL.modal_confirm_ok,
                                                "Delete")
        confirm.tap()

        if target_thumbcount < 1:
            self.UTILS.element.waitForElements(
                DOM.Gallery.no_thumbnails_message,
                "Message saying there are no thumbnails", False, 5)
        else:
            # Come out of 'select' mode.
            exit_select_mode_header = self.UTILS.element.getElement(
                DOM.Gallery.exit_select_mode_header, "Exit select mode button")
            exit_select_mode_header.tap(25, 25)

            current_thumbs = self.get_number_of_thumbnails()
            self.UTILS.test.test(current_thumbs == target_thumbcount,
                                 "After deleting [{}] pics, we have the expected number: {}".\
                                 format(delete_thumbcount, target_thumbcount))

    def get_gallery_items(self):
        """
        Returns a list of gallery item objects, with RAW info (date, metadata, size...)
        """
        self.UTILS.element.waitForElements(DOM.Gallery.thumbnail_items,
                                           "Thumbnails", True, 20, False)
        return self.marionette.execute_script(
            "return window.wrappedJSObject.files;")

    def play_current_video(self):
        """
        Plays the video that has previously been loaded (by pressing its thumbnail first), then press a play button.
        """
        play_btn = self.UTILS.element.getElement(
            DOM.Gallery.preview_current_video_play, "Video play button")
        time.sleep(1)
        play_btn.tap()

        self.UTILS.element.waitForElements(
            DOM.Gallery.preview_current_video_pause, "Pause button", True, 20,
            False)

    def get_number_of_thumbnails(self):
        """
        Returns the number of thumbnails.
        """
        try:
            self.parent.wait_for_element_displayed(
                *DOM.Gallery.thumbnail_items)
            return len(
                self.marionette.find_elements(*DOM.Gallery.thumbnail_items))
        except:
            return 0

    def wait_for_thumbnails_number(self, number, timeout=10):
        """
        Waits untils @number thumbnails are present in the thumbnails screen
        """
        msg = "Waiting until we have the expected number of thumbnails"
        self.parent.wait_for_condition(lambda m: len(
            m.find_elements(*DOM.Gallery.thumbnail_items)) == number,
                                       timeout=timeout,
                                       message=msg)

    def swipe_between_gallery_items(self, steps):
        """
        Swipes over the gallery items (the preview screen must be displayed) a certain number of steps
        Important: there is no way of checking that the image is being shown each time we swipe,
        """
        current_frame = self.apps.displayed_app.frame
        x_start = current_frame.size['width']
        x_end = x_start // 4
        y_start = current_frame.size['height'] // 2

        for i in range(steps):
            self.actions.flick(current_frame,
                               x_start,
                               y_start,
                               x_end,
                               y_start,
                               duration=600).perform()
            time.sleep(1)
Esempio n. 35
0
class AccessibleCaretSelectionModeTestCase(MarionetteTestCase):
    '''Test cases for AccessibleCaret under selection mode, aka selection carets.

    '''
    def setUp(self):
        # Code to execute before a tests are run.
        super(AccessibleCaretSelectionModeTestCase, self).setUp()
        self.carets_tested_pref = 'layout.accessiblecaret.enabled'
        self.prefs = {
            'layout.word_select.eat_space_to_next_word': False,
            'layout.accessiblecaret.use_long_tap_injector': False,
            self.carets_tested_pref: True,
        }
        self.marionette.set_prefs(self.prefs)
        self.actions = Actions(self.marionette)

    def open_test_html(self):
        'Open html for testing and locate elements.'
        test_html = self.marionette.absolute_url('test_selectioncarets.html')
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(By.ID, 'input')
        self._textarea = self.marionette.find_element(By.ID, 'textarea')
        self._textarea_rtl = self.marionette.find_element(
            By.ID, 'textarea_rtl')
        self._contenteditable = self.marionette.find_element(
            By.ID, 'contenteditable')
        self._content = self.marionette.find_element(By.ID, 'content')
        self._non_selectable = self.marionette.find_element(
            By.ID, 'non_selectable')

    def open_test_html2(self):
        'Open html for testing and locate elements.'
        test_html2 = self.marionette.absolute_url(
            'test_selectioncarets_multipleline.html')
        self.marionette.navigate(test_html2)

        self._textarea2 = self.marionette.find_element(By.ID, 'textarea2')
        self._contenteditable2 = self.marionette.find_element(
            By.ID, 'contenteditable2')
        self._content2 = self.marionette.find_element(By.ID, 'content2')

    def open_test_html_multirange(self):
        'Open html for testing non-editable support.'
        test_html = self.marionette.absolute_url(
            'test_selectioncarets_multiplerange.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._sel1 = self.marionette.find_element(By.ID, 'sel1')
        self._sel2 = self.marionette.find_element(By.ID, 'sel2')
        self._sel3 = self.marionette.find_element(By.ID, 'sel3')
        self._sel4 = self.marionette.find_element(By.ID, 'sel4')
        self._sel6 = self.marionette.find_element(By.ID, 'sel6')
        self._nonsel1 = self.marionette.find_element(By.ID, 'nonsel1')

    def open_test_html_long_text(self):
        'Open html for testing long text.'
        test_html = self.marionette.absolute_url(
            'test_selectioncarets_longtext.html')
        self.marionette.navigate(test_html)

        self._body = self.marionette.find_element(By.ID, 'bd')
        self._longtext = self.marionette.find_element(By.ID, 'longtext')

    def open_test_html_iframe(self):
        'Open html for testing iframe.'
        test_html = self.marionette.absolute_url(
            'test_selectioncarets_iframe.html')
        self.marionette.navigate(test_html)

        self._iframe = self.marionette.find_element(By.ID, 'frame')

    def open_test_html_display_none(self):
        'Open html for testing html with display: none.'
        test_html = self.marionette.absolute_url(
            'test_carets_display_none.html')
        self.marionette.navigate(test_html)

        self._html = self.marionette.find_element(By.ID, 'html')
        self._content = self.marionette.find_element(By.ID, 'content')

    def word_offset(self, text, ordinal):
        'Get the character offset of the ordinal-th word in text.'
        tokens = re.split(r'(\S+)', text)  # both words and spaces
        spaces = tokens[0::2]  # collect spaces at odd indices
        words = tokens[1::2]  # collect word at even indices

        if ordinal >= len(words):
            raise IndexError('Only %d words in text, but got ordinal %d' %
                             (len(words), ordinal))

        # Cursor position of the targeting word is behind the the first
        # character in the word. For example, offset to 'def' in 'abc def' is
        # between 'd' and 'e'.
        offset = len(spaces[0]) + 1
        offset += sum(
            len(words[i]) + len(spaces[i + 1]) for i in range(ordinal))
        return offset

    def test_word_offset(self):
        text = ' ' * 3 + 'abc' + ' ' * 3 + 'def'

        self.assertTrue(self.word_offset(text, 0), 4)
        self.assertTrue(self.word_offset(text, 1), 10)
        with self.assertRaises(IndexError):
            self.word_offset(text, 2)

    def word_location(self, el, ordinal):
        '''Get the location (x, y) of the ordinal-th word in el.

        The ordinal starts from 0.

        Note: this function has a side effect which changes focus to the
        target element el.

        '''
        sel = SelectionManager(el)
        offset = self.word_offset(sel.content, ordinal)

        # Move caret to the word.
        el.tap()
        sel.move_caret_to_front()
        sel.move_caret_by_offset(offset)
        x, y = sel.caret_location()

        return x, y

    def rect_relative_to_window(self, el):
        '''Get element's bounding rectangle.

        This function is similar to el.rect, but the coordinate is relative to
        the top left corner of the window instead of the document.

        '''
        return self.marionette.execute_script('''
            let rect = arguments[0].getBoundingClientRect();
            return {x: rect.x, y:rect.y, width: rect.width, height: rect.height};
            ''',
                                              script_args=[el])

    def long_press_on_location(self, el, x=None, y=None):
        '''Long press the location (x, y) to select a word.

        If no (x, y) are given, it will be targeted at the center of the
        element. On Windows, those spaces after the word will also be selected.
        This function sends synthesized eMouseLongTap to gecko.

        '''
        rect = self.rect_relative_to_window(el)
        target_x = rect['x'] + (x if x is not None else rect['width'] // 2)
        target_y = rect['y'] + (y if y is not None else rect['height'] // 2)

        self.marionette.execute_script('''
            let Ci = Components.interfaces;
            let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
            utils.sendTouchEventToWindow('touchstart', [0],
                                         [arguments[0]], [arguments[1]],
                                         [1], [1], [0], [1], 1, 0);
            utils.sendMouseEventToWindow('mouselongtap', arguments[0], arguments[1],
                                          0, 1, 0);
            utils.sendTouchEventToWindow('touchend', [0],
                                         [arguments[0]], [arguments[1]],
                                         [1], [1], [0], [1], 1, 0);
            ''',
                                       script_args=[target_x, target_y],
                                       sandbox='system')

    def long_press_on_word(self, el, wordOrdinal):
        x, y = self.word_location(el, wordOrdinal)
        self.long_press_on_location(el, x, y)

    def to_unix_line_ending(self, s):
        """Changes all Windows/Mac line endings in s to UNIX line endings."""

        return s.replace('\r\n', '\n').replace('\r', '\n')

    def _test_long_press_to_select_a_word(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(
            len(words) >= 2, 'Expect at least two words in the content.')
        target_content = words[0]

        # Goal: Select the first word.
        self.long_press_on_word(el, 0)

        # Ignore extra spaces selected after the word.
        assertFunc(target_content, sel.selected_content)

    def _test_move_selection_carets(self, el, assertFunc):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(
            len(words) >= 1, 'Expect at least one word in the content.')

        # Goal: Select all text after the first word.
        target_content = original_content[len(words[0]):]

        # Get the location of the selection carets at the end of the content for
        # later use.
        el.tap()
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        self.long_press_on_word(el, 0)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x,
                           end_caret_y).perform()

        # Move the left caret to the previous position of the right caret.
        self.actions.flick(el, caret1_x, caret1_y, caret2_x,
                           caret2_y).perform()

        assertFunc(target_content, sel.selected_content)

    def _test_minimum_select_one_character(self,
                                           el,
                                           assertFunc,
                                           x=None,
                                           y=None):
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(
            len(words) >= 1, 'Expect at least one word in the content.')

        # Get the location of the selection carets at the end of the content for
        # later use.
        sel.select_all()
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()
        el.tap()

        # Goal: Select the first character.
        target_content = original_content[0]

        if x and y:
            # If we got x and y from the arguments, use it as a hint of the
            # location of the first word
            pass
        else:
            x, y = self.word_location(el, 0)
        self.long_press_on_location(el, x, y)

        # Move the right caret to the end of the content.
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, end_caret_x,
                           end_caret_y).perform()

        # Move the right caret to the position of the left caret.
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x,
                           caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    def _test_focus_obtained_by_long_press(self, el1, el2):
        '''Test the focus could be changed from el1 to el2 by long press.

        If the focus is changed to e2 successfully, SelectionCarets should
        appear and could be dragged.

        '''
        # Goal: Tap to focus el1, and then select the first character on
        # el2.

        # We want to collect the location of the first word in el2 here
        # since self.word_location() has the side effect which would
        # change the focus.
        x, y = self.word_location(el2, 0)
        el1.tap()
        self._test_minimum_select_one_character(el2,
                                                self.assertEqual,
                                                x=x,
                                                y=y)

    def _test_focus_not_being_changed_by_long_press_on_non_selectable(
            self, el):
        # Goal: Focus remains on the editable element el after long pressing on
        # the non-selectable element.
        sel = SelectionManager(el)
        self.long_press_on_word(el, 0)
        self.long_press_on_location(self._non_selectable)
        active_sel = SelectionManager(self.marionette.get_active_element())
        self.assertEqual(sel.content, active_sel.content)

    def _test_handle_tilt_when_carets_overlap_to_each_other(
            self, el, assertFunc):
        '''Test tilt handling when carets overlap to each other.

        Let SelectionCarets overlap to each other. If SelectionCarets are set
        to tilted successfully, tapping the tilted carets should not cause the
        selection to be collapsed and the carets should be draggable.
        '''

        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(
            len(words) >= 1, 'Expect at least one word in the content.')

        # Goal: Select the first word.
        self.long_press_on_word(el, 0)
        target_content = sel.selected_content

        # Move the left caret to the position of the right caret to trigger
        # carets overlapping.
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret1_x, caret1_y, caret2_x,
                           caret2_y).perform()

        # We make two hit tests targeting the left edge of the left tilted caret
        # and the right edge of the right tilted caret. If either of the hits is
        # missed, selection would be collapsed and both carets should not be
        # draggable.
        (caret3_x, caret3_y), (caret4_x,
                               caret4_y) = sel.selection_carets_location()

        # The following values are from ua.css and all.js
        if self.carets_tested_pref == 'selectioncaret.enabled':
            caret_width = 44
            caret_margin_left = -23
            tilt_right_margin_left = 18
            tilt_left_margin_left = -17
        elif self.carets_tested_pref == 'layout.accessiblecaret.enabled':
            caret_width = float(
                self.marionette.get_pref('layout.accessiblecaret.width'))
            caret_margin_left = float(
                self.marionette.get_pref('layout.accessiblecaret.margin-left'))
            tilt_right_margin_left = 0.41 * caret_width
            tilt_left_margin_left = -0.39 * caret_width

        left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
        el.tap(left_caret_left_edge_x + 2, caret3_y)

        right_caret_right_edge_x = (caret4_x + caret_margin_left +
                                    tilt_right_margin_left + caret_width)
        el.tap(right_caret_right_edge_x - 2, caret4_y)

        # Drag the left caret back to the initial selection, the first word.
        self.actions.flick(el, caret3_x, caret3_y, caret1_x,
                           caret1_y).perform()

        assertFunc(target_content, sel.selected_content)

    def test_long_press_to_select_non_selectable_word(self):
        '''Testing long press on non selectable field.
        We should not select anything when long press on non selectable fields.'''

        self.open_test_html_multirange()
        halfY = self._nonsel1.size['height'] / 2
        self.long_press_on_location(self._nonsel1, 0, halfY)
        sel = SelectionManager(self._nonsel1)
        range_count = sel.range_count()
        self.assertEqual(range_count, 0)

    def test_drag_caret_over_non_selectable_field(self):
        '''Testing drag caret over non selectable field.
        So that the selected content should exclude non selectable field and
        end selection caret should appear in last range's position.'''
        self.open_test_html_multirange()

        # Select target element and get target caret location
        self.long_press_on_word(self._sel4, 3)
        sel = SelectionManager(self._body)
        (_, _), (end_caret_x, end_caret_y) = sel.selection_carets_location()

        self.long_press_on_word(self._sel6, 0)
        (_, _), (end_caret2_x, end_caret2_y) = sel.selection_carets_location()

        # Select start element
        self.long_press_on_word(self._sel3, 3)

        # Drag end caret to target location
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret_x,
                           end_caret_y, 1).perform()
        self.assertEqual(
            self.to_unix_line_ending(sel.selected_content.strip()),
            'this 3\nuser can select this')

        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, end_caret2_x,
                           end_caret2_y, 1).perform()
        self.assertEqual(
            self.to_unix_line_ending(sel.selected_content.strip()),
            'this 3\nuser can select this 4\nuser can select this 5\nuser')

        # Drag first caret to target location
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret1_x, caret1_y, end_caret_x,
                           end_caret_y, 1).perform()
        self.assertEqual(
            self.to_unix_line_ending(sel.selected_content.strip()),
            '4\nuser can select this 5\nuser')

    def test_drag_caret_to_beginning_of_a_line(self):
        '''Bug 1094056
        Test caret visibility when caret is dragged to beginning of a line
        '''
        self.open_test_html_multirange()

        # Select the first word in the second line
        self.long_press_on_word(self._sel2, 0)
        sel = SelectionManager(self._body)
        (start_caret_x,
         start_caret_y), (end_caret_x,
                          end_caret_y) = sel.selection_carets_location()

        # Select target word in the first line
        self.long_press_on_word(self._sel1, 2)

        # Drag end caret to the beginning of the second line
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(self._body, caret2_x, caret2_y, start_caret_x,
                           start_caret_y).perform()

        # Drag end caret back to the target word
        self.actions.flick(self._body, start_caret_x, start_caret_y, caret2_x,
                           caret2_y).perform()

        self.assertEqual(self.to_unix_line_ending(sel.selected_content),
                         'select')

    @skip_if_not_rotatable
    def test_caret_position_after_changing_orientation_of_device(self):
        '''Bug 1094072
        If positions of carets are updated correctly, they should be draggable.
        '''
        self.open_test_html_long_text()

        # Select word in portrait mode, then change to landscape mode
        self.marionette.set_orientation('portrait')
        self.long_press_on_word(self._longtext, 12)
        sel = SelectionManager(self._body)
        (p_start_caret_x,
         p_start_caret_y), (p_end_caret_x,
                            p_end_caret_y) = sel.selection_carets_location()
        self.marionette.set_orientation('landscape')
        (l_start_caret_x,
         l_start_caret_y), (l_end_caret_x,
                            l_end_caret_y) = sel.selection_carets_location()

        # Drag end caret to the start caret to change the selected content
        self.actions.flick(self._body, l_end_caret_x, l_end_caret_y,
                           l_start_caret_x, l_start_caret_y).perform()

        # Change orientation back to portrait mode to prevent affecting
        # other tests
        self.marionette.set_orientation('portrait')

        self.assertEqual(self.to_unix_line_ending(sel.selected_content), 'o')

    def test_select_word_inside_an_iframe(self):
        '''Bug 1088552
        The scroll offset in iframe should be taken into consideration properly.
        In this test, we scroll content in the iframe to the bottom to cause a
        huge offset. If we use the right coordinate system, selection should
        work. Otherwise, it would be hard to trigger select word.
        '''
        self.open_test_html_iframe()

        # switch to inner iframe and scroll to the bottom
        self.marionette.switch_to_frame(self._iframe)
        self.marionette.execute_script(
            'document.getElementById("bd").scrollTop += 999')

        # long press to select bottom text
        self._body = self.marionette.find_element(By.ID, 'bd')
        sel = SelectionManager(self._body)
        self._bottomtext = self.marionette.find_element(By.ID, 'bottomtext')
        self.long_press_on_location(self._bottomtext)

        self.assertNotEqual(self.to_unix_line_ending(sel.selected_content), '')

    def test_carets_initialized_in_display_none(self):
        '''Test AccessibleCaretEventHub is properly initialized on a <html> with
        display: none.

        '''
        self.open_test_html_display_none()

        # Remove 'display: none' from <html>
        self.marionette.execute_script('arguments[0].style.display = "unset";',
                                       script_args=[self._html])

        # If AccessibleCaretEventHub is initialized successfully, select a word
        # should work.
        self._test_long_press_to_select_a_word(self._content, self.assertEqual)

    ########################################################################
    # <input> test cases with selection carets enabled
    ########################################################################
    def test_input_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._input, self.assertEqual)

    def test_input_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._input, self.assertEqual)

    def test_input_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._input, self.assertEqual)

    def test_input_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._input)

    def test_input_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable,
                                                self._input)

    def test_input_focus_obtained_by_long_press_from_content_non_editable(
            self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._input)

    def test_input_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(
            self._input, self.assertEqual)

    def test_input_focus_not_changed_by_long_press_on_non_selectable(self):
        self.open_test_html()
        self._test_focus_not_being_changed_by_long_press_on_non_selectable(
            self._input)

    ########################################################################
    # <input> test cases with selection carets disabled
    ########################################################################
    def test_input_long_press_to_select_a_word_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_long_press_to_select_a_word(self._input,
                                                   self.assertNotEqual)

    def test_input_move_selection_carets_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_move_selection_carets(self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with selection carets enabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._textarea,
                                               self.assertEqual)

    def test_textarea_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._textarea, self.assertEqual)

    def test_textarea_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._textarea,
                                                self.assertEqual)

    def test_textarea_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_contenteditable(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable,
                                                self._textarea)

    def test_textarea_focus_obtained_by_long_press_from_content_non_editable(
            self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content, self._textarea)

    def test_textarea_handle_tilt_when_carets_overlap_to_each_other(self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(
            self._textarea, self.assertEqual)

    def test_textarea_focus_not_changed_by_long_press_on_non_selectable(self):
        self.open_test_html()
        self._test_focus_not_being_changed_by_long_press_on_non_selectable(
            self._textarea)

    ########################################################################
    # <textarea> test cases with selection carets disabled
    ########################################################################
    def test_textarea_long_press_to_select_a_word_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_long_press_to_select_a_word(self._textarea,
                                                   self.assertNotEqual)

    def test_textarea_move_selection_carets_disable(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_move_selection_carets(self._textarea,
                                             self.assertNotEqual)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets enabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._textarea_rtl,
                                               self.assertEqual)

    def test_textarea_rtl_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._textarea_rtl, self.assertEqual)

    def test_textarea_rtl_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._textarea_rtl,
                                                self.assertEqual)

    def test_textarea_rtl_focus_not_changed_by_long_press_on_non_selectable(
            self):
        self.open_test_html()
        self._test_focus_not_being_changed_by_long_press_on_non_selectable(
            self._textarea_rtl)

    ########################################################################
    # <textarea> right-to-left test cases with selection carets disabled
    ########################################################################
    def test_textarea_rtl_long_press_to_select_a_word_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_long_press_to_select_a_word(self._textarea_rtl,
                                                   self.assertNotEqual)

    def test_textarea_rtl_move_selection_carets_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_move_selection_carets(self._textarea_rtl,
                                             self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with selection carets enabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._contenteditable,
                                               self.assertEqual)

    def test_contenteditable_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._contenteditable,
                                         self.assertEqual)

    def test_contenteditable_minimum_select_one_character(self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._contenteditable,
                                                self.assertEqual)

    def test_contenteditable_focus_obtained_by_long_press_from_input(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input,
                                                self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_textarea(self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea,
                                                self._contenteditable)

    def test_contenteditable_focus_obtained_by_long_press_from_content_non_editable(
            self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._content,
                                                self._contenteditable)

    def test_contenteditable_handle_tilt_when_carets_overlap_to_each_other(
            self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(
            self._contenteditable, self.assertEqual)

    def test_contenteditable_focus_not_changed_by_long_press_on_non_selectable(
            self):
        self.open_test_html()
        self._test_focus_not_being_changed_by_long_press_on_non_selectable(
            self._contenteditable)

    ########################################################################
    # <div> contenteditable test cases with selection carets disabled
    ########################################################################
    def test_contenteditable_long_press_to_select_a_word_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_long_press_to_select_a_word(self._contenteditable,
                                                   self.assertNotEqual)

    def test_contenteditable_move_selection_carets_disabled(self):
        with self.marionette.using_prefs({self.carets_tested_pref: False}):
            self.open_test_html()
            self._test_move_selection_carets(self._contenteditable,
                                             self.assertNotEqual)

    ########################################################################
    # <div> non-editable test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable_long_press_to_select_a_word(self):
        self.open_test_html()
        self._test_long_press_to_select_a_word(self._content, self.assertEqual)

    def test_content_non_editable_move_selection_carets(self):
        self.open_test_html()
        self._test_move_selection_carets(self._content, self.assertEqual)

    def test_content_non_editable_minimum_select_one_character_by_selection(
            self):
        self.open_test_html()
        self._test_minimum_select_one_character(self._content,
                                                self.assertEqual)

    def test_content_non_editable_focus_obtained_by_long_press_from_input(
            self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._input, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_textarea(
            self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._textarea, self._content)

    def test_content_non_editable_focus_obtained_by_long_press_from_contenteditable(
            self):
        self.open_test_html()
        self._test_focus_obtained_by_long_press(self._contenteditable,
                                                self._content)

    def test_content_non_editable_handle_tilt_when_carets_overlap_to_each_other(
            self):
        self.open_test_html()
        self._test_handle_tilt_when_carets_overlap_to_each_other(
            self._content, self.assertEqual)

    ########################################################################
    # <textarea> (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_textarea2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._textarea2,
                                                self.assertEqual)

    ########################################################################
    # <div> contenteditable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_contenteditable2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._contenteditable2,
                                                self.assertEqual)

    ########################################################################
    # <div> non-editable2 (multi-lines) test cases with selection carets enabled
    ########################################################################
    def test_content_non_editable2_minimum_select_one_character(self):
        self.open_test_html2()
        self._test_minimum_select_one_character(self._content2,
                                                self.assertEqual)

    def test_long_press_to_select_when_partial_visible_word_is_selected(self):
        self.open_test_html()
        el = self._input
        sel = SelectionManager(el)

        # To successfully select the second word while the first word is being
        # selected, use sufficient spaces between 'a' and 'b' to avoid the
        # second caret covers on the second word.
        original_content = 'aaaaaaaa          bbbbbbbb'
        el.clear()
        el.send_keys(original_content)
        words = original_content.split()

        # We cannot use self.long_press_on_word() directly since it has will
        # change the cursor position which affects this test. We have to store
        # the position of word 0 and word 1 before long-pressing to select the
        # word.
        word0_x, word0_y = self.word_location(el, 0)
        word1_x, word1_y = self.word_location(el, 1)

        self.long_press_on_location(el, word0_x, word0_y)
        self.assertEqual(words[0], sel.selected_content)

        self.long_press_on_location(el, word1_x, word1_y)
        self.assertEqual(words[1], sel.selected_content)

        self.long_press_on_location(el, word0_x, word0_y)
        self.assertEqual(words[0], sel.selected_content)

        # If the second carets is visible, it can be dragged to the position of
        # the first caret. After that, selection will contain only the first
        # character.
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()
        self.actions.flick(el, caret2_x, caret2_y, caret1_x,
                           caret1_y).perform()
        self.assertEqual(words[0][0], sel.selected_content)

    def test_carets_do_not_jump_when_dragging_to_editable_content_boundary(
            self):
        self.open_test_html()
        el = self._input
        sel = SelectionManager(el)
        original_content = sel.content
        words = original_content.split()
        self.assertTrue(
            len(words) >= 3, 'Expect at least three words in the content.')

        # Goal: the selection does not being changed after dragging the caret
        # on the Y-axis only.
        target_content = words[1]

        self.long_press_on_word(el, 1)
        (caret1_x, caret1_y), (caret2_x,
                               caret2_y) = sel.selection_carets_location()

        # Drag the first caret up by 50px.
        self.actions.flick(el, caret1_x, caret1_y, caret1_x,
                           caret1_y - 50).perform()
        self.assertEqual(target_content, sel.selected_content)

        # Drag the first caret down by 50px.
        self.actions.flick(el, caret2_x, caret2_y, caret2_x,
                           caret2_y + 50).perform()
        self.assertEqual(target_content, sel.selected_content)
Esempio n. 36
0
class AccessibleCaretCursorModeTestCase(MarionetteTestCase):
    '''Test cases for AccessibleCaret under cursor mode, aka touch caret.

    '''
    def setUp(self):
        # Code to execute before a test is being run.
        super(AccessibleCaretCursorModeTestCase, self).setUp()
        self.caret_tested_pref = 'layout.accessiblecaret.enabled'
        self.caret_timeout_ms_pref = 'layout.accessiblecaret.timeout_ms'
        self.prefs = {
            self.caret_tested_pref: True,
            self.caret_timeout_ms_pref: 0,
        }
        self.marionette.set_prefs(self.prefs)
        self.actions = Actions(self.marionette)

    def timeout_ms(self):
        'Return touch caret expiration time in milliseconds.'
        return self.marionette.get_pref(self.caret_timeout_ms_pref)

    def open_test_html(self):
        'Open html for testing and locate elements.'
        test_html = self.marionette.absolute_url('test_touchcaret.html')
        self.marionette.navigate(test_html)

        self._input = self.marionette.find_element(By.ID, 'input')
        self._textarea = self.marionette.find_element(By.ID, 'textarea')
        self._contenteditable = self.marionette.find_element(
            By.ID, 'contenteditable')

    def _test_move_caret_to_the_right_by_one_character(self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content
        target_content = target_content[:1] + content_to_add + target_content[
            1:]

        # Get touch caret (x, y) at position 1 and 2.
        el.tap()
        sel.move_caret_to_front()
        caret0_x, caret0_y = sel.caret_location()
        touch_caret0_x, touch_caret0_y = sel.touch_caret_location()
        sel.move_caret_by_offset(1)
        touch_caret1_x, touch_caret1_y = sel.touch_caret_location()

        # Tap the front of the input to make touch caret appear.
        el.tap(caret0_x, caret0_y)

        # Move touch caret
        self.actions.flick(el, touch_caret0_x, touch_caret0_y, touch_caret1_x,
                           touch_caret1_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = sel.content + content_to_add

        # Tap the front of the input to make touch caret appear.
        el.tap()
        sel.move_caret_to_front()
        el.tap(*sel.caret_location())

        # Move touch caret to the bottom-right corner of the element.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = el.size['width'], el.size['height']
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(target_content, sel.content)

    def _test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
            self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        target_content = content_to_add + sel.content

        # Get touch caret location at the front.
        el.tap()
        sel.move_caret_to_front()
        dest_x, dest_y = sel.touch_caret_location()

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())
        src_x, src_y = sel.touch_caret_location()

        # Move touch caret to the front of the input box.
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(target_content, sel.content)

    def _test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(
            self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Get touch caret expiration time in millisecond, and convert it to second.
        timeout = self.timeout_ms() / 1000.0

        # Set a 3x timeout margin to prevent intermittent test failures.
        timeout *= 3

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Wait until touch caret disappears, then pretend to move it to the
        # top-left corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.wait(timeout).flick(el, src_x, src_y, dest_x,
                                         dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(non_target_content, sel.content)

    def _test_touch_caret_hides_after_receiving_wheel_event(
            self, el, assertFunc):
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = content_to_add + sel.content

        # Tap to make touch caret appear. Note: it's strange that when the caret
        # is at the end, the rect of the caret in <textarea> cannot be obtained.
        # A bug perhaps.
        el.tap()
        sel.move_caret_to_end()
        sel.move_caret_by_offset(1, backward=True)
        el.tap(*sel.caret_location())

        # Send an arbitrary scroll-down-10px wheel event to the center of the
        # input box to hide touch caret. Then pretend to move it to the top-left
        # corner of the input box.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        el_center_x, el_center_y = el.rect['x'], el.rect['y']
        self.marionette.execute_script('''
            var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                              .getInterface(Components.interfaces.nsIDOMWindowUtils);
            utils.sendWheelEvent(arguments[0], arguments[1],
                                 0, 10, 0, WheelEvent.DOM_DELTA_PIXEL,
                                 0, 0, 0, 0);
            ''',
                                       script_args=[el_center_x, el_center_y],
                                       sandbox='system')
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        assertFunc(non_target_content, sel.content)

    def _test_caret_not_appear_when_typing_in_scrollable_content(
            self, el, assertFunc):
        sel = SelectionManager(el)

        content_to_add = '!'
        target_content = sel.content + string.ascii_letters + content_to_add

        el.tap()
        sel.move_caret_to_end()

        # Insert a long string to the end of the <input>, which triggers
        # ScrollPositionChanged event.
        el.send_keys(string.ascii_letters)

        # The caret should not be visible. If it does appear wrongly due to the
        # ScrollPositionChanged event, we can drag it to the front of the
        # <input> to change the cursor position.
        src_x, src_y = sel.touch_caret_location()
        dest_x, dest_y = 0, 0
        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()

        # The content should be inserted at the end of the <input>.
        el.send_keys(content_to_add)

        assertFunc(target_content, sel.content)

    ########################################################################
    # <input> test cases with touch caret enabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(
            self._input, self.assertEqual)

    def test_input_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self._input, self.assertEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(
            self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
            self._input, self.assertEqual)

    def test_input_caret_not_appear_when_typing_in_scrollable_content(self):
        self.open_test_html()
        self._test_caret_not_appear_when_typing_in_scrollable_content(
            self._input, self.assertEqual)

    def test_input_touch_caret_timeout(self):
        with self.marionette.using_prefs({self.caret_timeout_ms_pref: 1000}):
            self.open_test_html()
            self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(
                self._input, self.assertNotEqual)

    ########################################################################
    # <input> test cases with touch caret disabled
    ########################################################################
    def test_input_move_caret_to_the_right_by_one_character_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_the_right_by_one_character(
                self._input, self.assertNotEqual)

    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(
            self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
                self._input, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret enabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(
            self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self._textarea, self.assertEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(
            self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
            self._textarea, self.assertEqual)

    def test_textarea_touch_caret_timeout(self):
        with self.marionette.using_prefs({self.caret_timeout_ms_pref: 1000}):
            self.open_test_html()
            self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(
                self._textarea, self.assertNotEqual)

    ########################################################################
    # <textarea> test cases with touch caret disabled
    ########################################################################
    def test_textarea_move_caret_to_the_right_by_one_character_disabled(self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_the_right_by_one_character(
                self._textarea, self.assertNotEqual)

    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(
            self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
                self._textarea, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with touch caret enabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character(self):
        self.open_test_html()
        self._test_move_caret_to_the_right_by_one_character(
            self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self):
        self.open_test_html()
        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(
            self._contenteditable, self.assertEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(
            self):
        self.open_test_html()
        self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
            self._contenteditable, self.assertEqual)

    def test_contenteditable_touch_caret_timeout(self):
        with self.marionette.using_prefs({self.caret_timeout_ms_pref: 1000}):
            self.open_test_html()
            self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(
                self._contenteditable, self.assertNotEqual)

    ########################################################################
    # <div> contenteditable test cases with touch caret disabled
    ########################################################################
    def test_contenteditable_move_caret_to_the_right_by_one_character_disabled(
            self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_the_right_by_one_character(
                self._contenteditable, self.assertNotEqual)

    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(
            self):
        with self.marionette.using_prefs({self.caret_tested_pref: False}):
            self.open_test_html()
            self._test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(
                self._contenteditable, self.assertNotEqual)

    def test_caret_does_not_jump_when_dragging_to_editable_content_boundary(
            self):
        self.open_test_html()
        el = self._input
        sel = SelectionManager(el)
        content_to_add = '!'
        non_target_content = sel.content + content_to_add

        # Goal: the cursor position does not being changed after dragging the
        # caret down on the Y-axis.
        el.tap()
        sel.move_caret_to_front()
        el.tap(*sel.caret_location())
        x, y = sel.touch_caret_location()

        # Drag the caret down by 50px, and insert '!'.
        self.actions.flick(el, x, y, x, y + 50).perform()
        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
        self.assertNotEqual(non_target_content, sel.content)