summaryrefslogtreecommitdiffstats
path: root/layout/base/tests/marionette
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base/tests/marionette')
-rw-r--r--layout/base/tests/marionette/manifest.ini5
-rw-r--r--layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py298
-rw-r--r--layout/base/tests/marionette/test_accessiblecaret_selection_mode.py632
3 files changed, 935 insertions, 0 deletions
diff --git a/layout/base/tests/marionette/manifest.ini b/layout/base/tests/marionette/manifest.ini
new file mode 100644
index 000000000..98428ccf5
--- /dev/null
+++ b/layout/base/tests/marionette/manifest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+run-if = buildapp == 'browser'
+
+[test_accessiblecaret_cursor_mode.py]
+[test_accessiblecaret_selection_mode.py]
diff --git a/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py b/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
new file mode 100644
index 000000000..e330e4d70
--- /dev/null
+++ b/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
@@ -0,0 +1,298 @@
+# -*- coding: utf-8 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import string
+
+from marionette_driver.by import By
+from marionette_driver.marionette import Actions
+from marionette_driver.selection import SelectionManager
+from marionette_harness.marionette_test import (
+ MarionetteTestCase,
+ parameterized,
+)
+
+
+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 = '!'
+ 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)
diff --git a/layout/base/tests/marionette/test_accessiblecaret_selection_mode.py b/layout/base/tests/marionette/test_accessiblecaret_selection_mode.py
new file mode 100644
index 000000000..2cc93dd74
--- /dev/null
+++ b/layout/base/tests/marionette/test_accessiblecaret_selection_mode.py
@@ -0,0 +1,632 @@
+# -*- coding: utf-8 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import re
+
+from marionette_driver.by import By
+from marionette_driver.marionette import Actions
+from marionette_driver.selection import SelectionManager
+from marionette_harness.marionette_test import (
+ MarionetteTestCase,
+ SkipTest,
+ parameterized
+)
+
+
+def skip_if_not_rotatable(target):
+ def wrapper(self, *args, **kwargs):
+ if not self.marionette.session_capabilities.get('rotatable'):
+ raise SkipTest('skipping due to device not rotatable')
+ return target(self, *args, **kwargs)
+ return wrapper
+
+
+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)