mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1019441 - Part 1: Add SelectionManager in marionette. r=mdas
* Extract those caret manipulating functions in test_touchcaret.py to selection.py, and generalize them for manipulating selection. * Use SelectionManager in test_touchcaret.py. * Remove unneeded </input> from test_touchcaret.html.
This commit is contained in:
parent
c2fbcb462c
commit
b988a6d7d3
@ -6,6 +6,7 @@
|
||||
from by import By
|
||||
from marionette import Actions
|
||||
from marionette_test import MarionetteTestCase
|
||||
from selection import SelectionManager
|
||||
|
||||
|
||||
class TouchCaretTest(MarionetteTestCase):
|
||||
@ -58,117 +59,19 @@ class TouchCaretTest(MarionetteTestCase):
|
||||
self._textarea = self.marionette.find_element(*self._textarea_selector)
|
||||
self._contenteditable = self.marionette.find_element(*self._contenteditable_selector)
|
||||
|
||||
def is_input_or_textarea(self, element):
|
||||
'''Return True if element is either <input> or <textarea>'''
|
||||
return element.tag_name in ('input', 'textarea')
|
||||
|
||||
def get_js_selection_cmd(self, element):
|
||||
'''Return a command snippet to get selection object.
|
||||
|
||||
If the element is <input> or <textarea>, return the selection object
|
||||
associated with it. Otherwise, return the current selection object.
|
||||
|
||||
Note: "element" must be provided as the first argument to
|
||||
execute_script().
|
||||
|
||||
'''
|
||||
if self.is_input_or_textarea(element):
|
||||
# We must unwrap sel so that DOMRect could be returned to Python
|
||||
# side.
|
||||
return '''var sel = SpecialPowers.wrap(arguments[0]).editor.selection;
|
||||
sel = SpecialPowers.unwrap(sel);'''
|
||||
else:
|
||||
return '''var sel = window.getSelection();'''
|
||||
|
||||
def caret_rect(self, element):
|
||||
'''Return the caret's DOMRect object.
|
||||
|
||||
If the element is either <input> or <textarea>, return the caret's
|
||||
DOMRect within the element. Otherwise, return the DOMRect of the
|
||||
current selected caret.
|
||||
|
||||
'''
|
||||
cmd = self.get_js_selection_cmd(element) +\
|
||||
'''return sel.getRangeAt(0).getClientRects()[0];'''
|
||||
return self.marionette.execute_script(cmd, script_args=[element])
|
||||
|
||||
def caret_location(self, element):
|
||||
'''Return caret's center location by the number of characters offset
|
||||
within the given element.
|
||||
|
||||
Return (x, y) coordinates of the caret's center by the number of
|
||||
characters offset relative to the top left-hand corner of the given
|
||||
element.
|
||||
|
||||
'''
|
||||
rect = self.caret_rect(element)
|
||||
x = rect['left'] + rect['width'] / 2.0 - element.location['x']
|
||||
y = rect['top'] + rect['height'] / 2.0 - element.location['y']
|
||||
return x, y
|
||||
|
||||
def touch_caret_location(self, element):
|
||||
'''Return touch caret's location (based on current caret location).
|
||||
|
||||
Return (x, y) coordinates of the touch caret's tip relative to the top
|
||||
left-hand corner of the given element.
|
||||
|
||||
'''
|
||||
rect = self.caret_rect(element)
|
||||
x = rect['left'] - element.location['x']
|
||||
|
||||
# Touch caret's tip is below the bottom of the caret. Add 5px to y
|
||||
# should be sufficient to locate it.
|
||||
y = rect['bottom'] + 5 - element.location['y']
|
||||
|
||||
return x, y
|
||||
|
||||
def move_caret_by_offset(self, element, offset, backward=False):
|
||||
'''Move caret in the element by offset.'''
|
||||
cmd = self.get_js_selection_cmd(element) +\
|
||||
'''sel.modify("move", arguments[1], "character");'''
|
||||
direction = 'backward' if backward else 'forward'
|
||||
|
||||
for i in range(offset):
|
||||
self.marionette.execute_script(
|
||||
cmd, script_args=[element, direction])
|
||||
|
||||
def move_caret_to_front(self, element):
|
||||
if self.is_input_or_textarea(element):
|
||||
cmd = '''arguments[0].setSelectionRange(0, 0);'''
|
||||
else:
|
||||
cmd = '''var sel = window.getSelection();
|
||||
sel.collapse(arguments[0].firstChild, 0);'''
|
||||
|
||||
self.marionette.execute_script(cmd, script_args=[element])
|
||||
|
||||
def move_caret_to_end(self, element):
|
||||
if self.is_input_or_textarea(element):
|
||||
cmd = '''var len = arguments[0].value.length;
|
||||
arguments[0].setSelectionRange(len, len);'''
|
||||
else:
|
||||
cmd = '''var sel = window.getSelection();
|
||||
sel.collapse(arguments[0].lastChild, arguments[0].lastChild.length);'''
|
||||
|
||||
self.marionette.execute_script(cmd, script_args=[element])
|
||||
|
||||
def get_content(self, element):
|
||||
'''Return the content of the element.'''
|
||||
if self.is_input_or_textarea(element):
|
||||
return element.get_attribute('value')
|
||||
else:
|
||||
return element.text
|
||||
|
||||
def _test_move_caret_to_the_right_by_one_character(self, el, assertFunc):
|
||||
sel = SelectionManager(el)
|
||||
content_to_add = '!'
|
||||
target_content = self.get_content(el)
|
||||
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.
|
||||
self.move_caret_to_front(el)
|
||||
caret0_x, caret0_y = self.caret_location(el)
|
||||
touch_caret0_x, touch_caret0_y = self.touch_caret_location(el)
|
||||
self.move_caret_by_offset(el, 1)
|
||||
touch_caret1_x, touch_caret1_y = self.touch_caret_location(el)
|
||||
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)
|
||||
@ -178,46 +81,51 @@ class TouchCaretTest(MarionetteTestCase):
|
||||
touch_caret1_x, touch_caret1_y).perform()
|
||||
|
||||
el.send_keys(content_to_add)
|
||||
assertFunc(target_content, self.get_content(el))
|
||||
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 = self.get_content(el) + content_to_add
|
||||
target_content = sel.content + content_to_add
|
||||
|
||||
# Tap the front of the input to make touch caret appear.
|
||||
self.move_caret_to_front(el)
|
||||
el.tap(*self.caret_location(el))
|
||||
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 = self.touch_caret_location(el)
|
||||
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, self.get_content(el))
|
||||
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 + self.get_content(el)
|
||||
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.
|
||||
self.move_caret_to_end(el)
|
||||
self.move_caret_by_offset(el, 1, backward=True)
|
||||
el.tap(*self.caret_location(el))
|
||||
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 = self.touch_caret_location(el)
|
||||
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, self.get_content(el))
|
||||
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 + self.get_content(el)
|
||||
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
|
||||
@ -225,18 +133,19 @@ class TouchCaretTest(MarionetteTestCase):
|
||||
# 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.
|
||||
self.move_caret_to_end(el)
|
||||
self.move_caret_by_offset(el, 1, backward=True)
|
||||
el.tap(*self.caret_location(el))
|
||||
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 = self.touch_caret_location(el)
|
||||
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, self.get_content(el))
|
||||
assertFunc(non_target_content, sel.content)
|
||||
|
||||
########################################################################
|
||||
# <input> test cases with touch caret enabled
|
||||
|
188
testing/marionette/client/marionette/selection.py
Normal file
188
testing/marionette/client/marionette/selection.py
Normal file
@ -0,0 +1,188 @@
|
||||
# -*- 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/.
|
||||
|
||||
|
||||
class SelectionManager(object):
|
||||
'''Interface for manipulating the selection and carets of the element.
|
||||
|
||||
Simple usage example:
|
||||
|
||||
::
|
||||
|
||||
element = marionette.find_element('id', 'input')
|
||||
sel = SelectionManager(element)
|
||||
sel.move_caret_to_front()
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, element):
|
||||
self.element = element
|
||||
|
||||
def _input_or_textarea(self):
|
||||
'''Return True if element is either <input> or <textarea>.'''
|
||||
return self.element.tag_name in ('input', 'textarea')
|
||||
|
||||
def js_selection_cmd(self):
|
||||
'''Return a command snippet to get selection object.
|
||||
|
||||
If the element is <input> or <textarea>, return the selection object
|
||||
associated with it. Otherwise, return the current selection object.
|
||||
|
||||
Note: "element" must be provided as the first argument to
|
||||
execute_script().
|
||||
|
||||
'''
|
||||
if self._input_or_textarea():
|
||||
# We must unwrap sel so that DOMRect could be returned to Python
|
||||
# side.
|
||||
return '''var sel = SpecialPowers.wrap(arguments[0]).editor.selection;
|
||||
sel = SpecialPowers.unwrap(sel);'''
|
||||
else:
|
||||
return '''var sel = window.getSelection();'''
|
||||
|
||||
def move_caret_by_offset(self, offset, backward=False):
|
||||
'''Move caret in the element by character offset.'''
|
||||
cmd = self.js_selection_cmd() +\
|
||||
'''sel.modify("move", arguments[1], "character");'''
|
||||
direction = 'backward' if backward else 'forward'
|
||||
|
||||
for i in range(offset):
|
||||
self.element.marionette.execute_script(
|
||||
cmd, script_args=[self.element, direction])
|
||||
|
||||
def move_caret_to_front(self):
|
||||
'''Move caret in the element to the front of the content.'''
|
||||
if self._input_or_textarea():
|
||||
cmd = '''arguments[0].setSelectionRange(0, 0);'''
|
||||
else:
|
||||
cmd = '''var sel = window.getSelection();
|
||||
sel.collapse(arguments[0].firstChild, 0);'''
|
||||
|
||||
self.element.marionette.execute_script(cmd, script_args=[self.element])
|
||||
|
||||
def move_caret_to_end(self):
|
||||
'''Move caret in the element to the end of the content.'''
|
||||
if self._input_or_textarea():
|
||||
cmd = '''var len = arguments[0].value.length;
|
||||
arguments[0].setSelectionRange(len, len);'''
|
||||
else:
|
||||
cmd = '''var sel = window.getSelection();
|
||||
sel.collapse(arguments[0].lastChild, arguments[0].lastChild.length);'''
|
||||
|
||||
self.element.marionette.execute_script(cmd, script_args=[self.element])
|
||||
|
||||
def selection_rect_list(self):
|
||||
'''Return the selection's DOMRectList object.
|
||||
|
||||
If the element is either <input> or <textarea>, return the selection's
|
||||
DOMRectList within the element. Otherwise, return the DOMRectList of the
|
||||
current selection.
|
||||
|
||||
'''
|
||||
cmd = self.js_selection_cmd() +\
|
||||
'''return sel.getRangeAt(0).getClientRects();'''
|
||||
return self.element.marionette.execute_script(cmd, script_args=[self.element])
|
||||
|
||||
def _selection_location_helper(self, location_type):
|
||||
'''Return the start and end location of the selection in the element.
|
||||
|
||||
Return a tuple containing two pairs of (x, y) coordinates of the start
|
||||
and end locations in the element. The coordinates are relative to the
|
||||
top left-hand corner of the element. Both ltr and rtl directions are
|
||||
considered.
|
||||
|
||||
'''
|
||||
rect_list = self.selection_rect_list()
|
||||
list_length = rect_list['length']
|
||||
first_rect, last_rect = rect_list['0'], rect_list[str(list_length - 1)]
|
||||
origin_x, origin_y = self.element.location['x'], self.element.location['y']
|
||||
|
||||
if self.element.get_attribute('dir') == 'rtl': # such as Arabic
|
||||
start_pos, end_pos = 'right', 'left'
|
||||
else:
|
||||
start_pos, end_pos = 'left', 'right'
|
||||
|
||||
# Calculate y offset according to different needs.
|
||||
if location_type == 'center':
|
||||
start_y_offset = first_rect['height'] / 2.0
|
||||
end_y_offset = last_rect['height'] / 2.0
|
||||
elif location_type == 'caret':
|
||||
# Selection carets' tip are below the bottom of the two ends of the
|
||||
# selection. Add 5px to y should be sufficient to locate them.
|
||||
caret_tip_y_offset = 5
|
||||
start_y_offset = first_rect['height'] + caret_tip_y_offset
|
||||
end_y_offset = last_rect['height'] + caret_tip_y_offset
|
||||
else:
|
||||
start_y_offset = end_y_offset = 0
|
||||
|
||||
caret1_x = first_rect[start_pos] - origin_x
|
||||
caret1_y = first_rect['top'] + start_y_offset - origin_y
|
||||
caret2_x = last_rect[end_pos] - origin_x
|
||||
caret2_y = last_rect['top'] + end_y_offset - origin_y
|
||||
|
||||
return ((caret1_x, caret1_y), (caret2_x, caret2_y))
|
||||
|
||||
def selection_location(self):
|
||||
'''Return the start and end location of the selection in the element.
|
||||
|
||||
Return a tuple containing two pairs of (x, y) coordinates of the start
|
||||
and end of the selection. The coordinates are relative to the top
|
||||
left-hand corner of the element. Both ltr and rtl direction are
|
||||
considered.
|
||||
|
||||
'''
|
||||
return self._selection_location_helper('center')
|
||||
|
||||
def selection_carets_location(self):
|
||||
'''Return a pair of the two selection carets' location.
|
||||
|
||||
Return a tuple containing two pairs of (x, y) coordinates of the two
|
||||
selection carets' tip. The coordinates are relative to the top left-hand
|
||||
corner of the element. Both ltr and rtl direction are considered.
|
||||
|
||||
'''
|
||||
return self._selection_location_helper('caret')
|
||||
|
||||
def caret_location(self):
|
||||
'''Return caret's center location within the element.
|
||||
|
||||
Return (x, y) coordinates of the caret's center relative to the top
|
||||
left-hand corner of the element.
|
||||
|
||||
'''
|
||||
return self._selection_location_helper('center')[0]
|
||||
|
||||
def touch_caret_location(self):
|
||||
'''Return touch caret's location (based on current caret location).
|
||||
|
||||
Return (x, y) coordinates of the touch caret's tip relative to the top
|
||||
left-hand corner of the element.
|
||||
|
||||
'''
|
||||
return self._selection_location_helper('caret')[0]
|
||||
|
||||
def select_all(self):
|
||||
'''Select all the content in the element.'''
|
||||
if self._input_or_textarea():
|
||||
cmd = '''var len = arguments[0].value.length;
|
||||
arguments[0].focus();
|
||||
arguments[0].setSelectionRange(0, len);'''
|
||||
else:
|
||||
cmd = '''var range = document.createRange();
|
||||
range.setStart(arguments[0].firstChild, 0);
|
||||
range.setEnd(arguments[0].lastChild, arguments[0].lastChild.length);
|
||||
var sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);'''
|
||||
|
||||
self.element.marionette.execute_script(cmd, script_args=[self.element])
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
'''Return all the content of the element.'''
|
||||
if self._input_or_textarea():
|
||||
return self.element.get_attribute('value')
|
||||
else:
|
||||
return self.element.text
|
@ -8,7 +8,7 @@
|
||||
<title>Bug 960897: Marionette tests for touch caret</title>
|
||||
</head>
|
||||
<body>
|
||||
<div><input id="input" value="ABCDEFGHI"></input></div>
|
||||
<div><input id="input" value="ABCDEFGHI"></div>
|
||||
<br />
|
||||
<div><textarea name="textarea" id="textarea" rows="4" cols="6">ABCDEFGHI</textarea></div>
|
||||
<br />
|
||||
|
Loading…
Reference in New Issue
Block a user