Improve webelement API
This commit is contained in:
parent
becc4490bc
commit
b856bf3a47
@ -51,3 +51,6 @@ defining-attr-methods=__init__,__new__,setUp
|
|||||||
|
|
||||||
[DESIGN]
|
[DESIGN]
|
||||||
max-args=10
|
max-args=10
|
||||||
|
|
||||||
|
[TYPECHECK]
|
||||||
|
ignored-classes=WebElementWrapper
|
||||||
|
2
doc/TODO
2
doc/TODO
@ -127,8 +127,6 @@ style
|
|||||||
- Always use a list as argument for namedtuple?
|
- Always use a list as argument for namedtuple?
|
||||||
- Refactor enum() so it uses a list as second argument (like python
|
- Refactor enum() so it uses a list as second argument (like python
|
||||||
enum/namedtuple).
|
enum/namedtuple).
|
||||||
- utils.webelem could be a wrapper class over QWebElement instead of a
|
|
||||||
collection of functions.
|
|
||||||
|
|
||||||
dwb keybindings to possibly implement
|
dwb keybindings to possibly implement
|
||||||
=====================================
|
=====================================
|
||||||
|
@ -763,13 +763,14 @@ class CommandDispatcher:
|
|||||||
and do everything async.
|
and do everything async.
|
||||||
"""
|
"""
|
||||||
frame = self._current_widget().page().currentFrame()
|
frame = self._current_widget().page().currentFrame()
|
||||||
elem = webelem.focus_elem(frame)
|
try:
|
||||||
if elem.isNull():
|
elem = webelem.focus_elem(frame)
|
||||||
|
except webelem.IsNullError:
|
||||||
raise cmdexc.CommandError("No element focused!")
|
raise cmdexc.CommandError("No element focused!")
|
||||||
if not webelem.is_editable(elem, strict=True):
|
if not elem.is_editable(strict=True):
|
||||||
raise cmdexc.CommandError("Focused element is not editable!")
|
raise cmdexc.CommandError("Focused element is not editable!")
|
||||||
if webelem.is_content_editable(elem):
|
if elem.is_content_editable():
|
||||||
text = elem.toPlainText()
|
text = str(elem)
|
||||||
else:
|
else:
|
||||||
text = elem.evaluateJavaScript('this.value')
|
text = elem.evaluateJavaScript('this.value')
|
||||||
self._editor = editor.ExternalEditor(self._tabs)
|
self._editor = editor.ExternalEditor(self._tabs)
|
||||||
@ -783,17 +784,18 @@ class CommandDispatcher:
|
|||||||
Callback for QProcess when the editor was closed.
|
Callback for QProcess when the editor was closed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
elem: The QWebElement which was modified.
|
elem: The WebElementWrapper which was modified.
|
||||||
text: The new text to insert.
|
text: The new text to insert.
|
||||||
"""
|
"""
|
||||||
if elem.isNull():
|
try:
|
||||||
|
if elem.is_content_editable():
|
||||||
|
log.misc.debug("Filling element {} via setPlainText.".format(
|
||||||
|
elem.debug_text()))
|
||||||
|
elem.setPlainText(text)
|
||||||
|
else:
|
||||||
|
log.misc.debug("Filling element {} via javascript.".format(
|
||||||
|
elem.debug_text()))
|
||||||
|
text = webelem.javascript_escape(text)
|
||||||
|
elem.evaluateJavaScript("this.value='{}'".format(text))
|
||||||
|
except webelem.IsNullError:
|
||||||
raise cmdexc.CommandError("Element vanished while editing!")
|
raise cmdexc.CommandError("Element vanished while editing!")
|
||||||
if webelem.is_content_editable(elem):
|
|
||||||
log.misc.debug("Filling element {} via setPlainText.".format(
|
|
||||||
webelem.debug_text(elem)))
|
|
||||||
elem.setPlainText(text)
|
|
||||||
else:
|
|
||||||
log.misc.debug("Filling element {} via javascript.".format(
|
|
||||||
webelem.debug_text(elem)))
|
|
||||||
text = webelem.javascript_escape(text)
|
|
||||||
elem.evaluateJavaScript("this.value='{}'".format(text))
|
|
||||||
|
@ -155,8 +155,10 @@ class HintManager(QObject):
|
|||||||
def _cleanup(self):
|
def _cleanup(self):
|
||||||
"""Clean up after hinting."""
|
"""Clean up after hinting."""
|
||||||
for elem in self._context.elems.values():
|
for elem in self._context.elems.values():
|
||||||
if not elem.label.isNull():
|
try:
|
||||||
elem.label.removeFromDocument()
|
elem.label.removeFromDocument()
|
||||||
|
except webelem.IsNullError:
|
||||||
|
pass
|
||||||
text = self.HINT_TEXTS[self._context.target]
|
text = self.HINT_TEXTS[self._context.target]
|
||||||
message.instance().maybe_reset_text(text)
|
message.instance().maybe_reset_text(text)
|
||||||
self._context = None
|
self._context = None
|
||||||
@ -263,7 +265,7 @@ class HintManager(QObject):
|
|||||||
Return:
|
Return:
|
||||||
The CSS to set as a string.
|
The CSS to set as a string.
|
||||||
"""
|
"""
|
||||||
if label is None or label.attribute('hidden') != 'true':
|
if label is None or label['hidden'] != 'true':
|
||||||
display = 'inline'
|
display = 'inline'
|
||||||
else:
|
else:
|
||||||
display = 'none'
|
display = 'none'
|
||||||
@ -290,7 +292,9 @@ class HintManager(QObject):
|
|||||||
# See: http://stackoverflow.com/q/7364852/2085149
|
# See: http://stackoverflow.com/q/7364852/2085149
|
||||||
doc.appendInside('<span class="qutehint" style="{}">{}</span>'.format(
|
doc.appendInside('<span class="qutehint" style="{}">{}</span>'.format(
|
||||||
css, string))
|
css, string))
|
||||||
return doc.lastChild()
|
elem = webelem.WebElementWrapper(doc.lastChild())
|
||||||
|
elem['hidden'] = 'false'
|
||||||
|
return elem
|
||||||
|
|
||||||
def _click(self, elem):
|
def _click(self, elem):
|
||||||
"""Click an element.
|
"""Click an element.
|
||||||
@ -306,9 +310,9 @@ class HintManager(QObject):
|
|||||||
# FIXME Instead of clicking the center, we could have nicer heuristics.
|
# FIXME Instead of clicking the center, we could have nicer heuristics.
|
||||||
# e.g. parse (-webkit-)border-radius correctly and click text fields at
|
# e.g. parse (-webkit-)border-radius correctly and click text fields at
|
||||||
# the bottom right, and everything else on the top left or so.
|
# the bottom right, and everything else on the top left or so.
|
||||||
pos = webelem.rect_on_view(elem).center()
|
pos = elem.rect_on_view().center()
|
||||||
log.hints.debug("Clicking on '{}' at {}/{}".format(elem.toPlainText(),
|
log.hints.debug("Clicking on '{}' at {}/{}".format(
|
||||||
pos.x(), pos.y()))
|
elem, pos.x(), pos.y()))
|
||||||
events = (
|
events = (
|
||||||
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
|
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
|
||||||
Qt.NoModifier),
|
Qt.NoModifier),
|
||||||
@ -384,7 +388,7 @@ class HintManager(QObject):
|
|||||||
Return:
|
Return:
|
||||||
A QUrl with the absolute URL, or None.
|
A QUrl with the absolute URL, or None.
|
||||||
"""
|
"""
|
||||||
text = elem.attribute('href')
|
text = elem['href']
|
||||||
if not text:
|
if not text:
|
||||||
return None
|
return None
|
||||||
if baseurl is None:
|
if baseurl is None:
|
||||||
@ -402,12 +406,14 @@ class HintManager(QObject):
|
|||||||
webelem.SELECTORS[webelem.Group.links])
|
webelem.SELECTORS[webelem.Group.links])
|
||||||
rel_values = ('prev', 'previous') if prev else ('next')
|
rel_values = ('prev', 'previous') if prev else ('next')
|
||||||
for e in elems:
|
for e in elems:
|
||||||
if not e.hasAttribute('rel'):
|
e = webelem.WebElementWrapper(e)
|
||||||
|
try:
|
||||||
|
rel_attr = e['rel']
|
||||||
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
rel_attr = e.attribute('rel')
|
|
||||||
if rel_attr in rel_values:
|
if rel_attr in rel_values:
|
||||||
log.hints.debug("Found '{}' with rel={}".format(
|
log.hints.debug("Found '{}' with rel={}".format(
|
||||||
webelem.debug_text(e), rel_attr))
|
e.debug_text(), rel_attr))
|
||||||
return e
|
return e
|
||||||
# Then check for regular links/buttons.
|
# Then check for regular links/buttons.
|
||||||
elems = frame.findAllElements(
|
elems = frame.findAllElements(
|
||||||
@ -418,7 +424,8 @@ class HintManager(QObject):
|
|||||||
for regex in config.get('hints', option):
|
for regex in config.get('hints', option):
|
||||||
log.hints.vdebug("== Checking regex '{}'.".format(regex.pattern))
|
log.hints.vdebug("== Checking regex '{}'.".format(regex.pattern))
|
||||||
for e in elems:
|
for e in elems:
|
||||||
text = e.toPlainText()
|
e = webelem.WebElementWrapper(e)
|
||||||
|
text = str(e)
|
||||||
if not text:
|
if not text:
|
||||||
continue
|
continue
|
||||||
if regex.search(text):
|
if regex.search(text):
|
||||||
@ -498,10 +505,11 @@ class HintManager(QObject):
|
|||||||
ctx = HintContext()
|
ctx = HintContext()
|
||||||
ctx.frames = webelem.get_child_frames(mainframe)
|
ctx.frames = webelem.get_child_frames(mainframe)
|
||||||
for f in ctx.frames:
|
for f in ctx.frames:
|
||||||
elems += f.findAllElements(webelem.SELECTORS[group])
|
for e in f.findAllElements(webelem.SELECTORS[group]):
|
||||||
|
elems.append(webelem.WebElementWrapper(e))
|
||||||
filterfunc = webelem.FILTERS.get(group, lambda e: True)
|
filterfunc = webelem.FILTERS.get(group, lambda e: True)
|
||||||
visible_elems = [e for e in elems if filterfunc(e) and
|
visible_elems = [e for e in elems if filterfunc(e) and
|
||||||
webelem.is_visible(e, mainframe)]
|
e.is_visible(mainframe)]
|
||||||
if not visible_elems:
|
if not visible_elems:
|
||||||
raise cmdexc.CommandError("No elements found.")
|
raise cmdexc.CommandError("No elements found.")
|
||||||
ctx.target = target
|
ctx.target = target
|
||||||
@ -529,34 +537,34 @@ class HintManager(QObject):
|
|||||||
rest = string[len(keystr):]
|
rest = string[len(keystr):]
|
||||||
elems.label.setInnerXml('<font color="{}">{}</font>{}'.format(
|
elems.label.setInnerXml('<font color="{}">{}</font>{}'.format(
|
||||||
config.get('colors', 'hints.fg.match'), matched, rest))
|
config.get('colors', 'hints.fg.match'), matched, rest))
|
||||||
if elems.label.attribute('hidden') == 'true':
|
if elems.label['hidden'] == 'true':
|
||||||
# hidden element which matches again -> unhide it
|
# hidden element which matches again -> unhide it
|
||||||
elems.label.setAttribute('hidden', 'false')
|
elems.label['hidden'] = 'false'
|
||||||
css = self._get_hint_css(elems.elem, elems.label)
|
css = self._get_hint_css(elems.elem, elems.label)
|
||||||
elems.label.setAttribute('style', css)
|
elems.label['style'] = css
|
||||||
else:
|
else:
|
||||||
# element doesn't match anymore -> hide it
|
# element doesn't match anymore -> hide it
|
||||||
elems.label.setAttribute('hidden', 'true')
|
elems.label['hidden'] = 'true'
|
||||||
css = self._get_hint_css(elems.elem, elems.label)
|
css = self._get_hint_css(elems.elem, elems.label)
|
||||||
elems.label.setAttribute('style', css)
|
elems.label['style'] = css
|
||||||
|
|
||||||
def filter_hints(self, filterstr):
|
def filter_hints(self, filterstr):
|
||||||
"""Filter displayed hints according to a text."""
|
"""Filter displayed hints according to a text."""
|
||||||
for elems in self._context.elems.values():
|
for elems in self._context.elems.values():
|
||||||
if elems.elem.toPlainText().lower().startswith(filterstr):
|
if str(elems.elem).lower().startswith(filterstr):
|
||||||
if elems.label.attribute('hidden') == 'true':
|
if elems.label['hidden'] == 'true':
|
||||||
# hidden element which matches again -> unhide it
|
# hidden element which matches again -> unhide it
|
||||||
elems.label.setAttribute('hidden', 'false')
|
elems.label['hidden'] = 'false'
|
||||||
css = self._get_hint_css(elems.elem, elems.label)
|
css = self._get_hint_css(elems.elem, elems.label)
|
||||||
elems.label.setAttribute('style', css)
|
elems.label['style'] = css
|
||||||
else:
|
else:
|
||||||
# element doesn't match anymore -> hide it
|
# element doesn't match anymore -> hide it
|
||||||
elems.label.setAttribute('hidden', 'true')
|
elems.label['hidden'] = 'true'
|
||||||
css = self._get_hint_css(elems.elem, elems.label)
|
css = self._get_hint_css(elems.elem, elems.label)
|
||||||
elems.label.setAttribute('style', css)
|
elems.label['style'] = css
|
||||||
visible = {}
|
visible = {}
|
||||||
for k, e in self._context.elems.items():
|
for k, e in self._context.elems.items():
|
||||||
if e.label.attribute('hidden') != 'true':
|
if e.label['hidden'] != 'true':
|
||||||
visible[k] = e
|
visible[k] = e
|
||||||
if not visible:
|
if not visible:
|
||||||
# Whoops, filtered all hints
|
# Whoops, filtered all hints
|
||||||
@ -625,7 +633,7 @@ class HintManager(QObject):
|
|||||||
log.hints.debug("Contents size changed...!")
|
log.hints.debug("Contents size changed...!")
|
||||||
for elems in self._context.elems.values():
|
for elems in self._context.elems.values():
|
||||||
css = self._get_hint_css(elems.elem, elems.label)
|
css = self._get_hint_css(elems.elem, elems.label)
|
||||||
elems.label.setAttribute('style', css)
|
elems.label['style'] = css
|
||||||
|
|
||||||
@pyqtSlot(usertypes.KeyMode)
|
@pyqtSlot(usertypes.KeyMode)
|
||||||
def on_mode_entered(self, mode):
|
def on_mode_entered(self, mode):
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from PyQt5.QtCore import QPoint, QProcess
|
from PyQt5.QtCore import QPoint, QProcess
|
||||||
from PyQt5.QtWebKit import QWebElement
|
|
||||||
from PyQt5.QtNetwork import QNetworkRequest
|
from PyQt5.QtNetwork import QNetworkRequest
|
||||||
|
|
||||||
|
|
||||||
@ -78,84 +77,6 @@ class FakeKeyEvent:
|
|||||||
self.modifiers = mock.Mock(return_value=modifiers)
|
self.modifiers = mock.Mock(return_value=modifiers)
|
||||||
|
|
||||||
|
|
||||||
class FakeWebElement:
|
|
||||||
|
|
||||||
"""A stub for QWebElement."""
|
|
||||||
|
|
||||||
def __init__(self, geometry=None, frame=None, null=False, visibility='',
|
|
||||||
display='', attributes=None, tagname=None, classes=None):
|
|
||||||
"""Constructor.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
geometry: The geometry of the QWebElement as QRect.
|
|
||||||
frame: The QWebFrame the element is in.
|
|
||||||
null: Whether the element is null or not.
|
|
||||||
visibility: The CSS visibility style property calue.
|
|
||||||
display: The CSS display style property calue.
|
|
||||||
attributes: Boolean HTML attributes to be added.
|
|
||||||
tagname: The tag name.
|
|
||||||
classes: HTML classes to be added.
|
|
||||||
|
|
||||||
Raise:
|
|
||||||
ValueError if element is not null and geometry/frame are not given.
|
|
||||||
"""
|
|
||||||
self.geometry = mock.Mock(return_value=geometry)
|
|
||||||
self.webFrame = mock.Mock(return_value=frame)
|
|
||||||
self.isNull = mock.Mock(return_value=null)
|
|
||||||
self.tagName = mock.Mock(return_value=tagname)
|
|
||||||
self._visibility = visibility
|
|
||||||
self._display = display
|
|
||||||
self._attributes = attributes
|
|
||||||
self._classes = classes
|
|
||||||
|
|
||||||
def toOuterXml(self):
|
|
||||||
"""Imitate toOuterXml."""
|
|
||||||
return '<fakeelem>'
|
|
||||||
|
|
||||||
def styleProperty(self, name, strategy):
|
|
||||||
"""Return the CSS style property named name.
|
|
||||||
|
|
||||||
Only display/visibility and ComputedStyle are simulated.
|
|
||||||
|
|
||||||
Raise:
|
|
||||||
ValueError if strategy is not ComputedStyle or name is not
|
|
||||||
visibility/display.
|
|
||||||
"""
|
|
||||||
if strategy != QWebElement.ComputedStyle:
|
|
||||||
raise ValueError("styleProperty called with strategy != "
|
|
||||||
"ComputedStyle ({})!".format(strategy))
|
|
||||||
if name == 'visibility':
|
|
||||||
return self._visibility
|
|
||||||
elif name == 'display':
|
|
||||||
return self._display
|
|
||||||
else:
|
|
||||||
raise ValueError("styleProperty called with unknown name "
|
|
||||||
"'{}'".format(name))
|
|
||||||
|
|
||||||
def hasAttribute(self, name):
|
|
||||||
"""Check if the element has an attribute named name."""
|
|
||||||
if self._attributes is None:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return name in self._attributes
|
|
||||||
|
|
||||||
def attribute(self, name):
|
|
||||||
"""Get the attribute named name."""
|
|
||||||
if self._attributes is None:
|
|
||||||
return ''
|
|
||||||
try:
|
|
||||||
return self._attributes[name]
|
|
||||||
except KeyError:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def classes(self):
|
|
||||||
"""Get the classes of the object."""
|
|
||||||
if self._classes is not None:
|
|
||||||
return self._classes.split(' ')
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class FakeWebFrame:
|
class FakeWebFrame:
|
||||||
|
|
||||||
"""A stub for QWebFrame."""
|
"""A stub for QWebFrame."""
|
||||||
|
@ -17,16 +17,86 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
|
||||||
"""Tests for the webelement utils."""
|
"""Tests for the webelement utils."""
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
import unittest.mock
|
||||||
|
import collections.abc
|
||||||
|
|
||||||
from PyQt5.QtCore import QRect, QPoint
|
from PyQt5.QtCore import QRect, QPoint
|
||||||
|
from PyQt5.QtWebKit import QWebElement
|
||||||
|
|
||||||
from qutebrowser.utils import webelem
|
from qutebrowser.utils import webelem
|
||||||
from qutebrowser.test import stubs
|
from qutebrowser.test import stubs
|
||||||
|
|
||||||
|
|
||||||
|
def get_webelem(geometry=None, frame=None, null=False, visibility='',
|
||||||
|
display='', attributes=None, tagname=None, classes=None):
|
||||||
|
"""Factory for WebElementWrapper objects based on a mock.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
geometry: The geometry of the QWebElement as QRect.
|
||||||
|
frame: The QWebFrame the element is in.
|
||||||
|
null: Whether the element is null or not.
|
||||||
|
visibility: The CSS visibility style property calue.
|
||||||
|
display: The CSS display style property calue.
|
||||||
|
attributes: Boolean HTML attributes to be added.
|
||||||
|
tagname: The tag name.
|
||||||
|
classes: HTML classes to be added.
|
||||||
|
"""
|
||||||
|
elem = unittest.mock.Mock()
|
||||||
|
elem.isNull.return_value = null
|
||||||
|
elem.geometry.return_value = geometry
|
||||||
|
elem.webFrame.return_value = frame
|
||||||
|
elem.tagName.return_value = tagname
|
||||||
|
elem.toOuterXml.return_value = '<fakeelem/>'
|
||||||
|
if attributes is not None:
|
||||||
|
if not isinstance(attributes, collections.abc.Mapping):
|
||||||
|
attributes = {e: None for e in attributes}
|
||||||
|
elem.hasAttribute.side_effect = lambda k: k in attributes
|
||||||
|
elem.attribute.side_effect = lambda k: attributes.get(k, '')
|
||||||
|
elem.attributeNames.return_value = list(attributes)
|
||||||
|
else:
|
||||||
|
elem.hasAttribute.return_value = False
|
||||||
|
elem.attribute.return_value = ''
|
||||||
|
elem.attributeNames.return_value = []
|
||||||
|
if classes is not None:
|
||||||
|
elem.classes.return_value = classes.split(' ')
|
||||||
|
else:
|
||||||
|
elem.classes.return_value = []
|
||||||
|
|
||||||
|
def _style_property(name, strategy):
|
||||||
|
"""Helper function to act as styleProperty method."""
|
||||||
|
if strategy != QWebElement.ComputedStyle:
|
||||||
|
raise ValueError("styleProperty called with strategy != "
|
||||||
|
"ComputedStyle ({})!".format(strategy))
|
||||||
|
if name == 'visibility':
|
||||||
|
return visibility
|
||||||
|
elif name == 'display':
|
||||||
|
return display
|
||||||
|
else:
|
||||||
|
raise ValueError("styleProperty called with unknown name "
|
||||||
|
"'{}'".format(name))
|
||||||
|
|
||||||
|
elem.styleProperty.side_effect = _style_property
|
||||||
|
wrapped = webelem.WebElementWrapper(elem)
|
||||||
|
if attributes is not None:
|
||||||
|
wrapped.update(attributes)
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
class WebElementWrapperTests(unittest.TestCase):
|
||||||
|
|
||||||
|
"""Test WebElementWrapper."""
|
||||||
|
|
||||||
|
def test_nullelem(self):
|
||||||
|
"""Test __init__ with a null element."""
|
||||||
|
with self.assertRaises(webelem.IsNullError):
|
||||||
|
get_webelem(null=True)
|
||||||
|
|
||||||
|
|
||||||
class IsVisibleInvalidTests(unittest.TestCase):
|
class IsVisibleInvalidTests(unittest.TestCase):
|
||||||
|
|
||||||
"""Tests for is_visible with invalid elements.
|
"""Tests for is_visible with invalid elements.
|
||||||
@ -44,19 +114,17 @@ class IsVisibleInvalidTests(unittest.TestCase):
|
|||||||
geometry() and webFrame() should not be called, and ValueError should
|
geometry() and webFrame() should not be called, and ValueError should
|
||||||
be raised.
|
be raised.
|
||||||
"""
|
"""
|
||||||
elem = stubs.FakeWebElement(null=True)
|
elem = get_webelem()
|
||||||
with self.assertRaises(ValueError):
|
elem._elem.isNull.return_value = True
|
||||||
webelem.is_visible(elem, self.frame)
|
with self.assertRaises(webelem.IsNullError):
|
||||||
elem.isNull.assert_called_once_with()
|
elem.is_visible(self.frame)
|
||||||
self.assertFalse(elem.geometry.called)
|
|
||||||
self.assertFalse(elem.webFrame.called)
|
|
||||||
|
|
||||||
def test_invalid_invisible(self):
|
def test_invalid_invisible(self):
|
||||||
"""Test elements with an invalid geometry which are invisible."""
|
"""Test elements with an invalid geometry which are invisible."""
|
||||||
elem = stubs.FakeWebElement(QRect(0, 0, 0, 0), self.frame)
|
elem = get_webelem(QRect(0, 0, 0, 0), self.frame)
|
||||||
self.assertFalse(elem.geometry().isValid())
|
self.assertFalse(elem.geometry().isValid())
|
||||||
self.assertEqual(elem.geometry().x(), 0)
|
self.assertEqual(elem.geometry().x(), 0)
|
||||||
self.assertFalse(webelem.is_visible(elem, self.frame))
|
self.assertFalse(elem.is_visible(self.frame))
|
||||||
|
|
||||||
def test_invalid_visible(self):
|
def test_invalid_visible(self):
|
||||||
"""Test elements with an invalid geometry which are visible.
|
"""Test elements with an invalid geometry which are visible.
|
||||||
@ -64,9 +132,9 @@ class IsVisibleInvalidTests(unittest.TestCase):
|
|||||||
This seems to happen sometimes in the real world, with real elements
|
This seems to happen sometimes in the real world, with real elements
|
||||||
which *are* visible, but don't have a valid geometry.
|
which *are* visible, but don't have a valid geometry.
|
||||||
"""
|
"""
|
||||||
elem = stubs.FakeWebElement(QRect(10, 10, 0, 0), self.frame)
|
elem = get_webelem(QRect(10, 10, 0, 0), self.frame)
|
||||||
self.assertFalse(elem.geometry().isValid())
|
self.assertFalse(elem.geometry().isValid())
|
||||||
self.assertTrue(webelem.is_visible(elem, self.frame))
|
self.assertTrue(elem.is_visible(self.frame))
|
||||||
|
|
||||||
|
|
||||||
class IsVisibleScrollTests(unittest.TestCase):
|
class IsVisibleScrollTests(unittest.TestCase):
|
||||||
@ -83,13 +151,13 @@ class IsVisibleScrollTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_invisible(self):
|
def test_invisible(self):
|
||||||
"""Test elements which should be invisible due to scrolling."""
|
"""Test elements which should be invisible due to scrolling."""
|
||||||
elem = stubs.FakeWebElement(QRect(5, 5, 4, 4), self.frame)
|
elem = get_webelem(QRect(5, 5, 4, 4), self.frame)
|
||||||
self.assertFalse(webelem.is_visible(elem, self.frame))
|
self.assertFalse(elem.is_visible(self.frame))
|
||||||
|
|
||||||
def test_visible(self):
|
def test_visible(self):
|
||||||
"""Test elements which still should be visible after scrolling."""
|
"""Test elements which still should be visible after scrolling."""
|
||||||
elem = stubs.FakeWebElement(QRect(10, 10, 1, 1), self.frame)
|
elem = get_webelem(QRect(10, 10, 1, 1), self.frame)
|
||||||
self.assertTrue(webelem.is_visible(elem, self.frame))
|
self.assertTrue(elem.is_visible(self.frame))
|
||||||
|
|
||||||
|
|
||||||
class IsVisibleCssTests(unittest.TestCase):
|
class IsVisibleCssTests(unittest.TestCase):
|
||||||
@ -105,27 +173,25 @@ class IsVisibleCssTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_visibility_visible(self):
|
def test_visibility_visible(self):
|
||||||
"""Check that elements with "visibility = visible" are visible."""
|
"""Check that elements with "visibility = visible" are visible."""
|
||||||
elem = stubs.FakeWebElement(QRect(0, 0, 10, 10), self.frame,
|
elem = get_webelem(QRect(0, 0, 10, 10), self.frame,
|
||||||
visibility='visible')
|
visibility='visible')
|
||||||
self.assertTrue(webelem.is_visible(elem, self.frame))
|
self.assertTrue(elem.is_visible(self.frame))
|
||||||
|
|
||||||
def test_visibility_hidden(self):
|
def test_visibility_hidden(self):
|
||||||
"""Check that elements with "visibility = hidden" are not visible."""
|
"""Check that elements with "visibility = hidden" are not visible."""
|
||||||
elem = stubs.FakeWebElement(QRect(0, 0, 10, 10), self.frame,
|
elem = get_webelem(QRect(0, 0, 10, 10), self.frame,
|
||||||
visibility='hidden')
|
visibility='hidden')
|
||||||
self.assertFalse(webelem.is_visible(elem, self.frame))
|
self.assertFalse(elem.is_visible(self.frame))
|
||||||
|
|
||||||
def test_display_inline(self):
|
def test_display_inline(self):
|
||||||
"""Check that elements with "display = inline" are visible."""
|
"""Check that elements with "display = inline" are visible."""
|
||||||
elem = stubs.FakeWebElement(QRect(0, 0, 10, 10), self.frame,
|
elem = get_webelem(QRect(0, 0, 10, 10), self.frame, display='inline')
|
||||||
display='inline')
|
self.assertTrue(elem.is_visible(self.frame))
|
||||||
self.assertTrue(webelem.is_visible(elem, self.frame))
|
|
||||||
|
|
||||||
def test_display_none(self):
|
def test_display_none(self):
|
||||||
"""Check that elements with "display = none" are not visible."""
|
"""Check that elements with "display = none" are not visible."""
|
||||||
elem = stubs.FakeWebElement(QRect(0, 0, 10, 10), self.frame,
|
elem = get_webelem(QRect(0, 0, 10, 10), self.frame, display='none')
|
||||||
display='none')
|
self.assertFalse(elem.is_visible(self.frame))
|
||||||
self.assertFalse(webelem.is_visible(elem, self.frame))
|
|
||||||
|
|
||||||
|
|
||||||
class IsVisibleIframeTests(unittest.TestCase):
|
class IsVisibleIframeTests(unittest.TestCase):
|
||||||
@ -162,26 +228,26 @@ class IsVisibleIframeTests(unittest.TestCase):
|
|||||||
self.frame = stubs.FakeWebFrame(QRect(0, 0, 300, 300))
|
self.frame = stubs.FakeWebFrame(QRect(0, 0, 300, 300))
|
||||||
self.iframe = stubs.FakeWebFrame(QRect(0, 10, 100, 100),
|
self.iframe = stubs.FakeWebFrame(QRect(0, 10, 100, 100),
|
||||||
parent=self.frame)
|
parent=self.frame)
|
||||||
self.elem1 = stubs.FakeWebElement(QRect(0, 0, 10, 10), self.iframe)
|
self.elem1 = get_webelem(QRect(0, 0, 10, 10), self.iframe)
|
||||||
self.elem2 = stubs.FakeWebElement(QRect(20, 90, 10, 10), self.iframe)
|
self.elem2 = get_webelem(QRect(20, 90, 10, 10), self.iframe)
|
||||||
self.elem3 = stubs.FakeWebElement(QRect(20, 150, 10, 10), self.iframe)
|
self.elem3 = get_webelem(QRect(20, 150, 10, 10), self.iframe)
|
||||||
self.elem4 = stubs.FakeWebElement(QRect(30, 180, 10, 10), self.frame)
|
self.elem4 = get_webelem(QRect(30, 180, 10, 10), self.frame)
|
||||||
|
|
||||||
def test_not_scrolled(self):
|
def test_not_scrolled(self):
|
||||||
"""Test base situation."""
|
"""Test base situation."""
|
||||||
self.assertTrue(self.frame.geometry().contains(self.iframe.geometry()))
|
self.assertTrue(self.frame.geometry().contains(self.iframe.geometry()))
|
||||||
self.assertTrue(webelem.is_visible(self.elem1, self.frame))
|
self.assertTrue(self.elem1.is_visible(self.frame))
|
||||||
self.assertTrue(webelem.is_visible(self.elem2, self.frame))
|
self.assertTrue(self.elem2.is_visible(self.frame))
|
||||||
self.assertFalse(webelem.is_visible(self.elem3, self.frame))
|
self.assertFalse(self.elem3.is_visible(self.frame))
|
||||||
self.assertTrue(webelem.is_visible(self.elem4, self.frame))
|
self.assertTrue(self.elem4.is_visible(self.frame))
|
||||||
|
|
||||||
def test_iframe_scrolled(self):
|
def test_iframe_scrolled(self):
|
||||||
"""Scroll iframe down so elem3 gets visible and elem1/elem2 not."""
|
"""Scroll iframe down so elem3 gets visible and elem1/elem2 not."""
|
||||||
self.iframe.scrollPosition.return_value = QPoint(0, 100)
|
self.iframe.scrollPosition.return_value = QPoint(0, 100)
|
||||||
self.assertFalse(webelem.is_visible(self.elem1, self.frame))
|
self.assertFalse(self.elem1.is_visible(self.frame))
|
||||||
self.assertFalse(webelem.is_visible(self.elem2, self.frame))
|
self.assertFalse(self.elem2.is_visible(self.frame))
|
||||||
self.assertTrue(webelem.is_visible(self.elem3, self.frame))
|
self.assertTrue(self.elem3.is_visible(self.frame))
|
||||||
self.assertTrue(webelem.is_visible(self.elem4, self.frame))
|
self.assertTrue(self.elem4.is_visible(self.frame))
|
||||||
|
|
||||||
def test_mainframe_scrolled_iframe_visible(self):
|
def test_mainframe_scrolled_iframe_visible(self):
|
||||||
"""Scroll mainframe down so iframe is partly visible but elem1 not."""
|
"""Scroll mainframe down so iframe is partly visible but elem1 not."""
|
||||||
@ -189,10 +255,10 @@ class IsVisibleIframeTests(unittest.TestCase):
|
|||||||
geom = self.frame.geometry().translated(self.frame.scrollPosition())
|
geom = self.frame.geometry().translated(self.frame.scrollPosition())
|
||||||
self.assertFalse(geom.contains(self.iframe.geometry()))
|
self.assertFalse(geom.contains(self.iframe.geometry()))
|
||||||
self.assertTrue(geom.intersects(self.iframe.geometry()))
|
self.assertTrue(geom.intersects(self.iframe.geometry()))
|
||||||
self.assertFalse(webelem.is_visible(self.elem1, self.frame))
|
self.assertFalse(self.elem1.is_visible(self.frame))
|
||||||
self.assertTrue(webelem.is_visible(self.elem2, self.frame))
|
self.assertTrue(self.elem2.is_visible(self.frame))
|
||||||
self.assertFalse(webelem.is_visible(self.elem3, self.frame))
|
self.assertFalse(self.elem3.is_visible(self.frame))
|
||||||
self.assertTrue(webelem.is_visible(self.elem4, self.frame))
|
self.assertTrue(self.elem4.is_visible(self.frame))
|
||||||
|
|
||||||
def test_mainframe_scrolled_iframe_invisible(self):
|
def test_mainframe_scrolled_iframe_invisible(self):
|
||||||
"""Scroll mainframe down so iframe is invisible."""
|
"""Scroll mainframe down so iframe is invisible."""
|
||||||
@ -200,10 +266,10 @@ class IsVisibleIframeTests(unittest.TestCase):
|
|||||||
geom = self.frame.geometry().translated(self.frame.scrollPosition())
|
geom = self.frame.geometry().translated(self.frame.scrollPosition())
|
||||||
self.assertFalse(geom.contains(self.iframe.geometry()))
|
self.assertFalse(geom.contains(self.iframe.geometry()))
|
||||||
self.assertFalse(geom.intersects(self.iframe.geometry()))
|
self.assertFalse(geom.intersects(self.iframe.geometry()))
|
||||||
self.assertFalse(webelem.is_visible(self.elem1, self.frame))
|
self.assertFalse(self.elem1.is_visible(self.frame))
|
||||||
self.assertFalse(webelem.is_visible(self.elem2, self.frame))
|
self.assertFalse(self.elem2.is_visible(self.frame))
|
||||||
self.assertFalse(webelem.is_visible(self.elem3, self.frame))
|
self.assertFalse(self.elem3.is_visible(self.frame))
|
||||||
self.assertTrue(webelem.is_visible(self.elem4, self.frame))
|
self.assertTrue(self.elem4.is_visible(self.frame))
|
||||||
|
|
||||||
|
|
||||||
class IsWritableTests(unittest.TestCase):
|
class IsWritableTests(unittest.TestCase):
|
||||||
@ -212,18 +278,18 @@ class IsWritableTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_writable(self):
|
def test_writable(self):
|
||||||
"""Test a normal element."""
|
"""Test a normal element."""
|
||||||
elem = stubs.FakeWebElement()
|
elem = get_webelem()
|
||||||
self.assertTrue(webelem.is_writable(elem))
|
self.assertTrue(elem.is_writable())
|
||||||
|
|
||||||
def test_disabled(self):
|
def test_disabled(self):
|
||||||
"""Test a disabled element."""
|
"""Test a disabled element."""
|
||||||
elem = stubs.FakeWebElement(attributes=['disabled'])
|
elem = get_webelem(attributes=['disabled'])
|
||||||
self.assertFalse(webelem.is_writable(elem))
|
self.assertFalse(elem.is_writable())
|
||||||
|
|
||||||
def test_readonly(self):
|
def test_readonly(self):
|
||||||
"""Test a readonly element."""
|
"""Test a readonly element."""
|
||||||
elem = stubs.FakeWebElement(attributes=['readonly'])
|
elem = get_webelem(attributes=['readonly'])
|
||||||
self.assertFalse(webelem.is_writable(elem))
|
self.assertFalse(elem.is_writable())
|
||||||
|
|
||||||
|
|
||||||
class JavascriptEscapeTests(unittest.TestCase):
|
class JavascriptEscapeTests(unittest.TestCase):
|
||||||
@ -309,204 +375,186 @@ class IsEditableTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_input_plain(self):
|
def test_input_plain(self):
|
||||||
"""Test with plain input element."""
|
"""Test with plain input element."""
|
||||||
elem = stubs.FakeWebElement(tagname='input')
|
elem = get_webelem(tagname='input')
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
self.assertTrue(elem.is_editable())
|
||||||
|
|
||||||
def test_input_text(self):
|
def test_input_text(self):
|
||||||
"""Test with text input element."""
|
"""Test with text input element."""
|
||||||
elem = stubs.FakeWebElement(tagname='input',
|
elem = get_webelem(tagname='input', attributes={'type': 'text'})
|
||||||
attributes={'type': 'text'})
|
self.assertTrue(elem.is_editable())
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_input_text_caps(self):
|
def test_input_text_caps(self):
|
||||||
"""Test with text input element with caps attributes."""
|
"""Test with text input element with caps attributes."""
|
||||||
elem = stubs.FakeWebElement(tagname='INPUT',
|
elem = get_webelem(tagname='INPUT', attributes={'TYPE': 'TEXT'})
|
||||||
attributes={'TYPE': 'TEXT'})
|
self.assertTrue(elem.is_editable())
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_input_email(self):
|
def test_input_email(self):
|
||||||
"""Test with email input element."""
|
"""Test with email input element."""
|
||||||
elem = stubs.FakeWebElement(tagname='input',
|
elem = get_webelem(tagname='input', attributes={'type': 'email'})
|
||||||
attributes={'type': 'email'})
|
self.assertTrue(elem.is_editable())
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_input_url(self):
|
def test_input_url(self):
|
||||||
"""Test with url input element."""
|
"""Test with url input element."""
|
||||||
elem = stubs.FakeWebElement(tagname='input',
|
elem = get_webelem(tagname='input', attributes={'type': 'url'})
|
||||||
attributes={'type': 'url'})
|
self.assertTrue(elem.is_editable())
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_input_tel(self):
|
def test_input_tel(self):
|
||||||
"""Test with tel input element."""
|
"""Test with tel input element."""
|
||||||
elem = stubs.FakeWebElement(tagname='input',
|
elem = get_webelem(tagname='input', attributes={'type': 'tel'})
|
||||||
attributes={'type': 'tel'})
|
self.assertTrue(elem.is_editable())
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_input_number(self):
|
def test_input_number(self):
|
||||||
"""Test with number input element."""
|
"""Test with number input element."""
|
||||||
elem = stubs.FakeWebElement(tagname='input',
|
elem = get_webelem(tagname='input', attributes={'type': 'number'})
|
||||||
attributes={'type': 'number'})
|
self.assertTrue(elem.is_editable())
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_input_password(self):
|
def test_input_password(self):
|
||||||
"""Test with password input element."""
|
"""Test with password input element."""
|
||||||
elem = stubs.FakeWebElement(tagname='input',
|
elem = get_webelem(tagname='input', attributes={'type': 'password'})
|
||||||
attributes={'type': 'password'})
|
self.assertTrue(elem.is_editable())
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_input_search(self):
|
def test_input_search(self):
|
||||||
"""Test with search input element."""
|
"""Test with search input element."""
|
||||||
elem = stubs.FakeWebElement(tagname='input',
|
elem = get_webelem(tagname='input', attributes={'type': 'search'})
|
||||||
attributes={'type': 'search'})
|
self.assertTrue(elem.is_editable())
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_input_button(self):
|
def test_input_button(self):
|
||||||
"""Button should not be editable."""
|
"""Button should not be editable."""
|
||||||
elem = stubs.FakeWebElement(tagname='input',
|
elem = get_webelem(tagname='input', attributes={'type': 'button'})
|
||||||
attributes={'type': 'button'})
|
self.assertFalse(elem.is_editable())
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_input_checkbox(self):
|
def test_input_checkbox(self):
|
||||||
"""Checkbox should not be editable."""
|
"""Checkbox should not be editable."""
|
||||||
elem = stubs.FakeWebElement(tagname='input',
|
elem = get_webelem(tagname='input', attributes={'type': 'checkbox'})
|
||||||
attributes={'type': 'checkbox'})
|
self.assertFalse(elem.is_editable())
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_textarea(self):
|
def test_textarea(self):
|
||||||
"""Test textarea element."""
|
"""Test textarea element."""
|
||||||
elem = stubs.FakeWebElement(tagname='textarea')
|
elem = get_webelem(tagname='textarea')
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
self.assertTrue(elem.is_editable())
|
||||||
|
|
||||||
def test_select(self):
|
def test_select(self):
|
||||||
"""Test selectbox."""
|
"""Test selectbox."""
|
||||||
elem = stubs.FakeWebElement(tagname='select')
|
elem = get_webelem(tagname='select')
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
self.assertFalse(elem.is_editable())
|
||||||
|
|
||||||
def test_input_disabled(self):
|
def test_input_disabled(self):
|
||||||
"""Test disabled input element."""
|
"""Test disabled input element."""
|
||||||
elem = stubs.FakeWebElement(tagname='input',
|
elem = get_webelem(tagname='input', attributes={'disabled': None})
|
||||||
attributes={'disabled': None})
|
self.assertFalse(elem.is_editable())
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_input_readonly(self):
|
def test_input_readonly(self):
|
||||||
"""Test readonly input element."""
|
"""Test readonly input element."""
|
||||||
elem = stubs.FakeWebElement(tagname='input',
|
elem = get_webelem(tagname='input', attributes={'readonly': None})
|
||||||
attributes={'readonly': None})
|
self.assertFalse(elem.is_editable())
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_textarea_disabled(self):
|
def test_textarea_disabled(self):
|
||||||
"""Test disabled textarea element."""
|
"""Test disabled textarea element."""
|
||||||
elem = stubs.FakeWebElement(tagname='textarea',
|
elem = get_webelem(tagname='textarea', attributes={'disabled': None})
|
||||||
attributes={'disabled': None})
|
self.assertFalse(elem.is_editable())
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_textarea_readonly(self):
|
def test_textarea_readonly(self):
|
||||||
"""Test readonly textarea element."""
|
"""Test readonly textarea element."""
|
||||||
elem = stubs.FakeWebElement(tagname='textarea',
|
elem = get_webelem(tagname='textarea', attributes={'readonly': None})
|
||||||
attributes={'readonly': None})
|
self.assertFalse(elem.is_editable())
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_embed_true(self):
|
def test_embed_true(self):
|
||||||
"""Test embed-element with insert-mode-on-plugins true."""
|
"""Test embed-element with insert-mode-on-plugins true."""
|
||||||
webelem.config = stubs.ConfigStub({'input':
|
webelem.config = stubs.ConfigStub({'input':
|
||||||
{'insert-mode-on-plugins': True}})
|
{'insert-mode-on-plugins': True}})
|
||||||
elem = stubs.FakeWebElement(tagname='embed')
|
elem = get_webelem(tagname='embed')
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
self.assertTrue(elem.is_editable())
|
||||||
|
|
||||||
def test_applet_true(self):
|
def test_applet_true(self):
|
||||||
"""Test applet-element with insert-mode-on-plugins true."""
|
"""Test applet-element with insert-mode-on-plugins true."""
|
||||||
webelem.config = stubs.ConfigStub({'input':
|
webelem.config = stubs.ConfigStub({'input':
|
||||||
{'insert-mode-on-plugins': True}})
|
{'insert-mode-on-plugins': True}})
|
||||||
elem = stubs.FakeWebElement(tagname='applet')
|
elem = get_webelem(tagname='applet')
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
self.assertTrue(elem.is_editable())
|
||||||
|
|
||||||
def test_embed_false(self):
|
def test_embed_false(self):
|
||||||
"""Test embed-element with insert-mode-on-plugins false."""
|
"""Test embed-element with insert-mode-on-plugins false."""
|
||||||
webelem.config = stubs.ConfigStub({'input':
|
webelem.config = stubs.ConfigStub({'input':
|
||||||
{'insert-mode-on-plugins': False}})
|
{'insert-mode-on-plugins': False}})
|
||||||
elem = stubs.FakeWebElement(tagname='embed')
|
elem = get_webelem(tagname='embed')
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
self.assertFalse(elem.is_editable())
|
||||||
|
|
||||||
def test_applet_false(self):
|
def test_applet_false(self):
|
||||||
"""Test applet-element with insert-mode-on-plugins false."""
|
"""Test applet-element with insert-mode-on-plugins false."""
|
||||||
webelem.config = stubs.ConfigStub({'input':
|
webelem.config = stubs.ConfigStub({'input':
|
||||||
{'insert-mode-on-plugins': False}})
|
{'insert-mode-on-plugins': False}})
|
||||||
elem = stubs.FakeWebElement(tagname='applet')
|
elem = get_webelem(tagname='applet')
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
self.assertFalse(elem.is_editable())
|
||||||
|
|
||||||
def test_object_no_type(self):
|
def test_object_no_type(self):
|
||||||
"""Test object-element without type."""
|
"""Test object-element without type."""
|
||||||
elem = stubs.FakeWebElement(tagname='object')
|
elem = get_webelem(tagname='object')
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
self.assertFalse(elem.is_editable())
|
||||||
|
|
||||||
def test_object_image(self):
|
def test_object_image(self):
|
||||||
"""Test object-element with image type."""
|
"""Test object-element with image type."""
|
||||||
elem = stubs.FakeWebElement(tagname='object',
|
elem = get_webelem(tagname='object', attributes={'type': 'image/gif'})
|
||||||
attributes={'type': 'image/gif'})
|
self.assertFalse(elem.is_editable())
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_object_application(self):
|
def test_object_application(self):
|
||||||
"""Test object-element with application type."""
|
"""Test object-element with application type."""
|
||||||
webelem.config = stubs.ConfigStub({'input':
|
webelem.config = stubs.ConfigStub({'input':
|
||||||
{'insert-mode-on-plugins': True}})
|
{'insert-mode-on-plugins': True}})
|
||||||
elem = stubs.FakeWebElement(tagname='object',
|
elem = get_webelem(tagname='object',
|
||||||
attributes={'type': 'application/foo'})
|
attributes={'type': 'application/foo'})
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
self.assertTrue(elem.is_editable())
|
||||||
|
|
||||||
def test_object_application_false(self):
|
def test_object_application_false(self):
|
||||||
"""Test object-element with application type but not ...-on-plugins."""
|
"""Test object-element with application type but not ...-on-plugins."""
|
||||||
webelem.config = stubs.ConfigStub({'input':
|
webelem.config = stubs.ConfigStub({'input':
|
||||||
{'insert-mode-on-plugins': False}})
|
{'insert-mode-on-plugins': False}})
|
||||||
elem = stubs.FakeWebElement(tagname='object',
|
elem = get_webelem(tagname='object',
|
||||||
attributes={'type': 'application/foo'})
|
attributes={'type': 'application/foo'})
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
self.assertFalse(elem.is_editable())
|
||||||
|
|
||||||
def test_object_classid(self):
|
def test_object_classid(self):
|
||||||
"""Test object-element with classid."""
|
"""Test object-element with classid."""
|
||||||
webelem.config = stubs.ConfigStub({'input':
|
webelem.config = stubs.ConfigStub({'input':
|
||||||
{'insert-mode-on-plugins': True}})
|
{'insert-mode-on-plugins': True}})
|
||||||
elem = stubs.FakeWebElement(tagname='object',
|
elem = get_webelem(tagname='object',
|
||||||
attributes={'type': 'foo',
|
attributes={'type': 'foo', 'classid': 'foo'})
|
||||||
'classid': 'foo'})
|
self.assertTrue(elem.is_editable())
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_object_classid_false(self):
|
def test_object_classid_false(self):
|
||||||
"""Test object-element with classid but not insert-mode-on-plugins."""
|
"""Test object-element with classid but not insert-mode-on-plugins."""
|
||||||
webelem.config = stubs.ConfigStub({'input':
|
webelem.config = stubs.ConfigStub({'input':
|
||||||
{'insert-mode-on-plugins': False}})
|
{'insert-mode-on-plugins': False}})
|
||||||
elem = stubs.FakeWebElement(tagname='object',
|
elem = get_webelem(tagname='object',
|
||||||
attributes={'type': 'foo',
|
attributes={'type': 'foo', 'classid': 'foo'})
|
||||||
'classid': 'foo'})
|
self.assertFalse(elem.is_editable())
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
def test_div_empty(self):
|
def test_div_empty(self):
|
||||||
"""Test div-element without class."""
|
"""Test div-element without class."""
|
||||||
elem = stubs.FakeWebElement(tagname='div')
|
elem = get_webelem(tagname='div')
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
self.assertFalse(elem.is_editable())
|
||||||
|
|
||||||
def test_div_noneditable(self):
|
def test_div_noneditable(self):
|
||||||
"""Test div-element with non-editableclass."""
|
"""Test div-element with non-editableclass."""
|
||||||
elem = stubs.FakeWebElement(tagname='div', classes='foo-kix-bar')
|
elem = get_webelem(tagname='div', classes='foo-kix-bar')
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
self.assertFalse(elem.is_editable())
|
||||||
|
|
||||||
def test_div_xik(self):
|
def test_div_xik(self):
|
||||||
"""Test div-element with xik class."""
|
"""Test div-element with xik class."""
|
||||||
elem = stubs.FakeWebElement(tagname='div', classes='foo kix-foo')
|
elem = get_webelem(tagname='div', classes='foo kix-foo')
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
self.assertTrue(elem.is_editable())
|
||||||
|
|
||||||
def test_div_xik_caps(self):
|
def test_div_xik_caps(self):
|
||||||
"""Test div-element with xik class in caps.
|
"""Test div-element with xik class in caps.
|
||||||
|
|
||||||
This tests if classes are case sensitive as they should.
|
This tests if classes are case sensitive as they should.
|
||||||
"""
|
"""
|
||||||
elem = stubs.FakeWebElement(tagname='div', classes='KIX-FOO')
|
elem = get_webelem(tagname='div', classes='KIX-FOO')
|
||||||
self.assertFalse(webelem.is_editable(elem))
|
self.assertFalse(elem.is_editable())
|
||||||
|
|
||||||
def test_div_codemirror(self):
|
def test_div_codemirror(self):
|
||||||
"""Test div-element with codemirror class."""
|
"""Test div-element with codemirror class."""
|
||||||
elem = stubs.FakeWebElement(tagname='div',
|
elem = get_webelem(tagname='div', classes='foo CodeMirror-foo')
|
||||||
classes='foo CodeMirror-foo')
|
self.assertTrue(elem.is_editable())
|
||||||
self.assertTrue(webelem.is_editable(elem))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -27,6 +27,9 @@ Module attributes:
|
|||||||
without "href".
|
without "href".
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import collections.abc
|
||||||
|
import functools
|
||||||
|
|
||||||
from PyQt5.QtCore import QRect, QUrl
|
from PyQt5.QtCore import QRect, QUrl
|
||||||
from PyQt5.QtWebKit import QWebElement
|
from PyQt5.QtWebKit import QWebElement
|
||||||
|
|
||||||
@ -50,80 +53,278 @@ SELECTORS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FILTERS = {
|
FILTERS = {
|
||||||
Group.links: (lambda e: e.hasAttribute('href') and
|
Group.links: (lambda e: 'href' in e and
|
||||||
QUrl(e.attribute('href')).scheme() != 'javascript'),
|
QUrl(e['href']).scheme() != 'javascript'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def is_visible(elem, mainframe):
|
class IsNullError(Exception):
|
||||||
"""Check whether the element is currently visible on the screen.
|
|
||||||
|
|
||||||
Args:
|
"""Gets raised by WebElementWrapper if an element is null."""
|
||||||
elem: The QWebElement to check.
|
|
||||||
mainframe: The main QWebFrame.
|
|
||||||
|
|
||||||
Return:
|
pass
|
||||||
True if the element is visible, False otherwise.
|
|
||||||
"""
|
|
||||||
# CSS attributes which hide an element
|
class WebElementWrapper(collections.abc.MutableMapping):
|
||||||
hidden_attributes = {
|
|
||||||
'visibility': 'hidden',
|
"""A wrapper around QWebElement to make it more intelligent."""
|
||||||
'display': 'none',
|
|
||||||
}
|
def __init__(self, elem):
|
||||||
if elem.isNull():
|
if elem.isNull():
|
||||||
raise ValueError("Element is a null-element!")
|
raise IsNullError('{} is a null element!'.format(elem))
|
||||||
for k, v in hidden_attributes.items():
|
self._elem = elem
|
||||||
if elem.styleProperty(k, QWebElement.ComputedStyle) == v:
|
for name in ('addClass', 'appendInside', 'appendOutside',
|
||||||
|
'attributeNS', 'classes', 'clone', 'document',
|
||||||
|
'encloseContentsWith', 'encloseWith',
|
||||||
|
'evaluateJavaScript', 'findAll', 'findFirst',
|
||||||
|
'firstChild', 'geometry', 'hasAttributeNS',
|
||||||
|
'hasAttributes', 'hasClass', 'hasFocus', 'lastChild',
|
||||||
|
'localName', 'namespaceUri', 'nextSibling', 'parent',
|
||||||
|
'prefix', 'prependInside', 'prependOutside',
|
||||||
|
'previousSibling', 'removeAllChildren',
|
||||||
|
'removeAttributeNS', 'removeClass', 'removeFromDocument',
|
||||||
|
'render', 'replace', 'setAttributeNS', 'setFocus',
|
||||||
|
'setInnerXml', 'setOuterXml', 'setPlainText',
|
||||||
|
'setStyleProperty', 'styleProperty', 'tagName',
|
||||||
|
'takeFromDocument', 'toInnerXml', 'toOuterXml',
|
||||||
|
'toggleClass', 'webFrame', '__eq__', '__ne__'):
|
||||||
|
# We don't wrap some methods for which we have better alternatives:
|
||||||
|
# - Mapping access for attributeNames/hasAttribute/setAttribute/
|
||||||
|
# attribute/removeAttribute.
|
||||||
|
# - isNull is checked automagically.
|
||||||
|
# - str(...) instead of toPlainText
|
||||||
|
# For the rest, we create a wrapper which checks if the element is
|
||||||
|
# null.
|
||||||
|
|
||||||
|
method = getattr(self._elem, name)
|
||||||
|
|
||||||
|
@functools.wraps(method)
|
||||||
|
def _wrapper(meth, *args, **kwargs):
|
||||||
|
# pylint: disable=missing-docstring
|
||||||
|
self._check_vanished()
|
||||||
|
return meth(*args, **kwargs)
|
||||||
|
|
||||||
|
wrapper = functools.partial(_wrapper, method)
|
||||||
|
functools.update_wrapper(wrapper, method)
|
||||||
|
|
||||||
|
setattr(self, name, wrapper)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
self._check_vanished()
|
||||||
|
return self._elem.toPlainText()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
try:
|
||||||
|
return "<WebElementWrapper '{}'>".format(self.debug_text())
|
||||||
|
except IsNullError:
|
||||||
|
return "<WebElementWrapper null>"
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
self._check_vanished()
|
||||||
|
if key not in self:
|
||||||
|
raise KeyError(key)
|
||||||
|
return self._elem.attribute(key)
|
||||||
|
|
||||||
|
def __setitem__(self, key, val):
|
||||||
|
self._check_vanished()
|
||||||
|
self._elem.setAttribute(key, val)
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
self._check_vanished()
|
||||||
|
if key not in self:
|
||||||
|
raise KeyError(key)
|
||||||
|
self.removeAttribute(key)
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
self._check_vanished()
|
||||||
|
return self._elem.hasAttribute(key)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
self._check_vanished()
|
||||||
|
yield from self._elem.attributeNames()
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
self._check_vanished()
|
||||||
|
return len(self._elem.attributeNames())
|
||||||
|
|
||||||
|
def _check_vanished(self):
|
||||||
|
"""Raise an exception if the element vanished (is null)."""
|
||||||
|
if self._elem.isNull():
|
||||||
|
raise IsNullError('Element {} vanished!'.format(
|
||||||
|
self._elem))
|
||||||
|
|
||||||
|
def is_visible(self, mainframe):
|
||||||
|
"""Check whether the element is currently visible on the screen.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mainframe: The main QWebFrame.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
True if the element is visible, False otherwise.
|
||||||
|
"""
|
||||||
|
self._check_vanished()
|
||||||
|
# CSS attributes which hide an element
|
||||||
|
hidden_attributes = {
|
||||||
|
'visibility': 'hidden',
|
||||||
|
'display': 'none',
|
||||||
|
}
|
||||||
|
for k, v in hidden_attributes.items():
|
||||||
|
if self._elem.styleProperty(k, QWebElement.ComputedStyle) == v:
|
||||||
|
return False
|
||||||
|
geometry = self._elem.geometry()
|
||||||
|
if not geometry.isValid() and geometry.x() == 0:
|
||||||
|
# Most likely an invisible link
|
||||||
return False
|
return False
|
||||||
if (not elem.geometry().isValid()) and elem.geometry().x() == 0:
|
# First check if the element is visible on screen
|
||||||
# Most likely an invisible link
|
elem_rect = self.rect_on_view()
|
||||||
return False
|
|
||||||
# First check if the element is visible on screen
|
|
||||||
elem_rect = rect_on_view(elem)
|
|
||||||
if elem_rect.isValid():
|
|
||||||
visible_on_screen = mainframe.geometry().intersects(elem_rect)
|
|
||||||
else:
|
|
||||||
# We got an invalid rectangle (width/height 0/0 probably), but this can
|
|
||||||
# still be a valid link.
|
|
||||||
visible_on_screen = mainframe.geometry().contains(elem_rect.topLeft())
|
|
||||||
# Then check if it's visible in its frame if it's not in the main frame.
|
|
||||||
elem_frame = elem.webFrame()
|
|
||||||
elem_rect = elem.geometry()
|
|
||||||
framegeom = QRect(elem_frame.geometry())
|
|
||||||
if not framegeom.isValid():
|
|
||||||
visible_in_frame = False
|
|
||||||
elif elem_frame.parentFrame() is not None:
|
|
||||||
framegeom.moveTo(0, 0)
|
|
||||||
framegeom.translate(elem_frame.scrollPosition())
|
|
||||||
if elem_rect.isValid():
|
if elem_rect.isValid():
|
||||||
visible_in_frame = framegeom.intersects(elem_rect)
|
visible_on_screen = mainframe.geometry().intersects(elem_rect)
|
||||||
else:
|
else:
|
||||||
# We got an invalid rectangle (width/height 0/0 probably), but this
|
# We got an invalid rectangle (width/height 0/0 probably), but this
|
||||||
# can still be a valid link.
|
# can still be a valid link.
|
||||||
visible_in_frame = framegeom.contains(elem_rect.topLeft())
|
visible_on_screen = mainframe.geometry().contains(
|
||||||
else:
|
elem_rect.topLeft())
|
||||||
visible_in_frame = visible_on_screen
|
# Then check if it's visible in its frame if it's not in the main
|
||||||
return all([visible_on_screen, visible_in_frame])
|
# frame.
|
||||||
|
elem_frame = self._elem.webFrame()
|
||||||
|
elem_rect = self._elem.geometry()
|
||||||
|
framegeom = QRect(elem_frame.geometry())
|
||||||
|
if not framegeom.isValid():
|
||||||
|
visible_in_frame = False
|
||||||
|
elif elem_frame.parentFrame() is not None:
|
||||||
|
framegeom.moveTo(0, 0)
|
||||||
|
framegeom.translate(elem_frame.scrollPosition())
|
||||||
|
if elem_rect.isValid():
|
||||||
|
visible_in_frame = framegeom.intersects(elem_rect)
|
||||||
|
else:
|
||||||
|
# We got an invalid rectangle (width/height 0/0 probably), but
|
||||||
|
# this can still be a valid link.
|
||||||
|
visible_in_frame = framegeom.contains(elem_rect.topLeft())
|
||||||
|
else:
|
||||||
|
visible_in_frame = visible_on_screen
|
||||||
|
return all([visible_on_screen, visible_in_frame])
|
||||||
|
|
||||||
|
def rect_on_view(self):
|
||||||
|
"""Get the geometry of the element relative to the webview."""
|
||||||
|
self._check_vanished()
|
||||||
|
frame = self._elem.webFrame()
|
||||||
|
rect = QRect(self._elem.geometry())
|
||||||
|
while frame is not None:
|
||||||
|
rect.translate(frame.geometry().topLeft())
|
||||||
|
rect.translate(frame.scrollPosition() * -1)
|
||||||
|
frame = frame.parentFrame()
|
||||||
|
return rect
|
||||||
|
|
||||||
def rect_on_view(elem):
|
def is_writable(self):
|
||||||
"""Get the geometry of the element relative to the webview."""
|
"""Check whether an element is writable."""
|
||||||
frame = elem.webFrame()
|
self._check_vanished()
|
||||||
rect = QRect(elem.geometry())
|
return not ('disabled' in self or 'readonly' in self)
|
||||||
while frame is not None:
|
|
||||||
rect.translate(frame.geometry().topLeft())
|
|
||||||
rect.translate(frame.scrollPosition() * -1)
|
|
||||||
frame = frame.parentFrame()
|
|
||||||
return rect
|
|
||||||
|
|
||||||
|
def is_content_editable(self):
|
||||||
|
"""Check if an element has a contenteditable attribute.
|
||||||
|
|
||||||
def is_writable(elem):
|
FIXME: Add tests.
|
||||||
"""Check wheter an element is writable.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
elem: The QWebElement to check.
|
elem: The QWebElement to check.
|
||||||
"""
|
|
||||||
return not (elem.hasAttribute('disabled') or elem.hasAttribute('readonly'))
|
Return:
|
||||||
|
True if the element has a contenteditable attribute,
|
||||||
|
False otherwise.
|
||||||
|
"""
|
||||||
|
self._check_vanished()
|
||||||
|
try:
|
||||||
|
return self['contenteditable'].lower() not in ('false', 'inherit')
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _is_editable_object(self):
|
||||||
|
"""Check if an object-element is editable."""
|
||||||
|
if 'type' not in self:
|
||||||
|
log.webview.debug("<object> without type clicked...")
|
||||||
|
return False
|
||||||
|
objtype = self['type'].lower()
|
||||||
|
if objtype.startswith('application/') or 'classid' in self:
|
||||||
|
# Let's hope flash/java stuff has an application/* mimetype OR
|
||||||
|
# at least a classid attribute. Oh, and let's hope images/...
|
||||||
|
# DON'T have a classid attribute. HTML sucks.
|
||||||
|
log.webview.debug("<object type='{}'> clicked.".format(objtype))
|
||||||
|
return config.get('input', 'insert-mode-on-plugins')
|
||||||
|
else:
|
||||||
|
# Image/Audio/...
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _is_editable_input(self):
|
||||||
|
"""Check if an input-element is editable.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
True if the element is editable, False otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
objtype = self['type'].lower()
|
||||||
|
except KeyError:
|
||||||
|
return self.is_writable()
|
||||||
|
else:
|
||||||
|
if objtype in ['text', 'email', 'url', 'tel', 'number', 'password',
|
||||||
|
'search']:
|
||||||
|
return self.is_writable()
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _is_editable_div(self):
|
||||||
|
"""Check if a div-element is editable.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
True if the element is editable, False otherwise.
|
||||||
|
"""
|
||||||
|
# Beginnings of div-classes which are actually some kind of editor.
|
||||||
|
div_classes = ('CodeMirror', # Javascript editor over a textarea
|
||||||
|
'kix-', # Google Docs editor
|
||||||
|
'ace_') # http://ace.c9.io/
|
||||||
|
for klass in self._elem.classes():
|
||||||
|
if any([klass.startswith(e) for e in div_classes]):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_editable(self, strict=False):
|
||||||
|
"""Check whether we should switch to insert mode for this element.
|
||||||
|
|
||||||
|
FIXME: add tests
|
||||||
|
|
||||||
|
Args:
|
||||||
|
strict: Whether to do stricter checking so only fields where we can
|
||||||
|
get the value match, for use with the :editor command.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
True if we should switch to insert mode, False otherwise.
|
||||||
|
"""
|
||||||
|
# pylint: disable=too-many-return-statements
|
||||||
|
self._check_vanished()
|
||||||
|
roles = ('combobox', 'textbox')
|
||||||
|
log.misc.debug("Checking if element is editable: {}".format(
|
||||||
|
self.debug_text()))
|
||||||
|
tag = self._elem.tagName().lower()
|
||||||
|
if self.is_content_editable() and self.is_writable():
|
||||||
|
return True
|
||||||
|
elif self.get('role', None) in roles:
|
||||||
|
return True
|
||||||
|
elif tag == 'input':
|
||||||
|
return self._is_editable_input()
|
||||||
|
elif tag == 'textarea':
|
||||||
|
return self.is_writable()
|
||||||
|
elif tag in ('embed', 'applet'):
|
||||||
|
# Flash/Java/...
|
||||||
|
return config.get('input', 'insert-mode-on-plugins') and not strict
|
||||||
|
elif tag == 'object':
|
||||||
|
return self._is_editable_object() and not strict
|
||||||
|
elif tag == 'div':
|
||||||
|
return self._is_editable_div() and not strict
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def debug_text(self):
|
||||||
|
"""Get a text based on an element suitable for debug output."""
|
||||||
|
self._check_vanished()
|
||||||
|
return utils.compact_text(repr(self._elem), 500)
|
||||||
|
|
||||||
|
|
||||||
def javascript_escape(text):
|
def javascript_escape(text):
|
||||||
@ -168,108 +369,6 @@ def get_child_frames(startframe):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
def is_content_editable(elem):
|
|
||||||
"""Check if an element hsa a contenteditable attribute.
|
|
||||||
|
|
||||||
FIXME: Add tests.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
elem: The QWebElement to check.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
True if the element has a contenteditable attribute, False otherwise.
|
|
||||||
"""
|
|
||||||
return (elem.hasAttribute('contenteditable') and
|
|
||||||
elem.attribute('contenteditable') not in ('false', 'inherit'))
|
|
||||||
|
|
||||||
|
|
||||||
def _is_editable_object(elem):
|
|
||||||
"""Check if an object-element is editable."""
|
|
||||||
if not elem.hasAttribute('type'):
|
|
||||||
log.webview.debug("<object> without type clicked...")
|
|
||||||
return False
|
|
||||||
objtype = elem.attribute('type').lower()
|
|
||||||
if objtype.startswith('application/') or elem.hasAttribute('classid'):
|
|
||||||
# Let's hope flash/java stuff has an application/* mimetype OR
|
|
||||||
# at least a classid attribute. Oh, and let's hope images/...
|
|
||||||
# DON'T have a classid attribute. HTML sucks.
|
|
||||||
log.webview.debug("<object type='{}'> clicked.".format(objtype))
|
|
||||||
return config.get('input', 'insert-mode-on-plugins')
|
|
||||||
else:
|
|
||||||
# Image/Audio/...
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _is_editable_input(elem):
|
|
||||||
"""Check if an input-element is editable.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
elem: The QWebElement to check.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
True if the element is editable, False otherwise.
|
|
||||||
"""
|
|
||||||
objtype = elem.attribute('type').lower()
|
|
||||||
if objtype in ['text', 'email', 'url', 'tel', 'number', 'password',
|
|
||||||
'search', '']:
|
|
||||||
return is_writable(elem)
|
|
||||||
|
|
||||||
|
|
||||||
def _is_editable_div(elem):
|
|
||||||
"""Check if a div-element is editable.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
elem: The QWebElement to check.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
True if the element is editable, False otherwise.
|
|
||||||
"""
|
|
||||||
# Beginnings of div-classes which are actually some kind of editor.
|
|
||||||
div_classes = ('CodeMirror', # Javascript editor over a textarea
|
|
||||||
'kix-', # Google Docs editor
|
|
||||||
'ace_') # http://ace.c9.io/
|
|
||||||
for klass in elem.classes():
|
|
||||||
if any([klass.startswith(e) for e in div_classes]):
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def is_editable(elem, strict=False):
|
|
||||||
"""Check whether we should switch to insert mode for this element.
|
|
||||||
|
|
||||||
FIXME: add tests
|
|
||||||
|
|
||||||
Args:
|
|
||||||
elem: The QWebElement to check.
|
|
||||||
strict: Whether to do stricter checking so only fields where we can get
|
|
||||||
the value match, for use with the :editor command.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
True if we should switch to insert mode, False otherwise.
|
|
||||||
"""
|
|
||||||
# pylint: disable=too-many-return-statements
|
|
||||||
roles = ('combobox', 'textbox')
|
|
||||||
log.misc.debug("Checking if element is editable: {}".format(
|
|
||||||
debug_text(elem)))
|
|
||||||
tag = elem.tagName().lower()
|
|
||||||
if is_content_editable(elem) and is_writable(elem):
|
|
||||||
return True
|
|
||||||
elif elem.hasAttribute('role') and elem.attribute('role') in roles:
|
|
||||||
return True
|
|
||||||
elif tag == 'input':
|
|
||||||
return _is_editable_input(elem)
|
|
||||||
elif tag == 'textarea':
|
|
||||||
return is_writable(elem)
|
|
||||||
elif tag in ('embed', 'applet'):
|
|
||||||
# Flash/Java/...
|
|
||||||
return config.get('input', 'insert-mode-on-plugins') and not strict
|
|
||||||
elif tag == 'object':
|
|
||||||
return _is_editable_object(elem) and not strict
|
|
||||||
elif tag == 'div':
|
|
||||||
return _is_editable_div(elem) and not strict
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def focus_elem(frame):
|
def focus_elem(frame):
|
||||||
"""Get the focused element in a webframe.
|
"""Get the focused element in a webframe.
|
||||||
|
|
||||||
@ -278,9 +377,5 @@ def focus_elem(frame):
|
|||||||
Args:
|
Args:
|
||||||
frame: The QWebFrame to search in.
|
frame: The QWebFrame to search in.
|
||||||
"""
|
"""
|
||||||
return frame.findFirstElement(SELECTORS[Group.focus])
|
elem = frame.findFirstElement(SELECTORS[Group.focus])
|
||||||
|
return WebElementWrapper(elem)
|
||||||
|
|
||||||
def debug_text(elem):
|
|
||||||
"""Get a text based on an element suitable for debug output."""
|
|
||||||
return utils.compact_text(elem.toOuterXml(), 500)
|
|
||||||
|
@ -177,8 +177,9 @@ class WebView(QWebView):
|
|||||||
log.mouse.debug("Hitresult is null!")
|
log.mouse.debug("Hitresult is null!")
|
||||||
self._check_insertmode = True
|
self._check_insertmode = True
|
||||||
return
|
return
|
||||||
elem = hitresult.element()
|
try:
|
||||||
if elem.isNull():
|
elem = webelem.WebElementWrapper(hitresult.element())
|
||||||
|
except webelem.IsNullError:
|
||||||
# For some reason, the hitresult element can be a null element
|
# For some reason, the hitresult element can be a null element
|
||||||
# sometimes (e.g. when clicking the timetable fields on
|
# sometimes (e.g. when clicking the timetable fields on
|
||||||
# http://www.sbb.ch/ ). If this is the case, we schedule a check
|
# http://www.sbb.ch/ ). If this is the case, we schedule a check
|
||||||
@ -186,8 +187,8 @@ class WebView(QWebView):
|
|||||||
log.mouse.debug("Hitresult element is null!")
|
log.mouse.debug("Hitresult element is null!")
|
||||||
self._check_insertmode = True
|
self._check_insertmode = True
|
||||||
return
|
return
|
||||||
elif ((hitresult.isContentEditable() and webelem.is_writable(elem)) or
|
if ((hitresult.isContentEditable() and elem.is_writable()) or
|
||||||
webelem.is_editable(elem)):
|
elem.is_editable(elem)):
|
||||||
log.mouse.debug("Clicked editable element!")
|
log.mouse.debug("Clicked editable element!")
|
||||||
modeman.maybe_enter(usertypes.KeyMode.insert, 'click')
|
modeman.maybe_enter(usertypes.KeyMode.insert, 'click')
|
||||||
else:
|
else:
|
||||||
@ -200,8 +201,12 @@ class WebView(QWebView):
|
|||||||
if not self._check_insertmode:
|
if not self._check_insertmode:
|
||||||
return
|
return
|
||||||
self._check_insertmode = False
|
self._check_insertmode = False
|
||||||
elem = webelem.focus_elem(self.page().currentFrame())
|
try:
|
||||||
if webelem.is_editable(elem):
|
elem = webelem.focus_elem(self.page().currentFrame())
|
||||||
|
except webelem.IsNullError:
|
||||||
|
log.mouse.warning("Element vanished!")
|
||||||
|
return
|
||||||
|
if elem.is_editable():
|
||||||
log.mouse.debug("Clicked editable element (delayed)!")
|
log.mouse.debug("Clicked editable element (delayed)!")
|
||||||
modeman.maybe_enter(usertypes.KeyMode.insert, 'click-delayed')
|
modeman.maybe_enter(usertypes.KeyMode.insert, 'click-delayed')
|
||||||
else:
|
else:
|
||||||
@ -353,11 +358,13 @@ class WebView(QWebView):
|
|||||||
if modeman.instance().mode() == usertypes.KeyMode.insert or not ok:
|
if modeman.instance().mode() == usertypes.KeyMode.insert or not ok:
|
||||||
return
|
return
|
||||||
frame = self.page().currentFrame()
|
frame = self.page().currentFrame()
|
||||||
elem = frame.findFirstElement(':focus')
|
try:
|
||||||
log.modes.debug("focus element: {}".format(elem.toOuterXml()))
|
elem = webelem.WebElementWrapper(frame.findFirstElement(':focus'))
|
||||||
if elem.isNull():
|
except webelem.IsNullError:
|
||||||
log.webview.debug("Focused element is null!")
|
log.webview.debug("Focused element is null!")
|
||||||
elif webelem.is_editable(elem):
|
return
|
||||||
|
log.modes.debug("focus element: {}".format(repr(elem)))
|
||||||
|
if elem.is_editable():
|
||||||
modeman.maybe_enter(usertypes.KeyMode.insert, 'load finished')
|
modeman.maybe_enter(usertypes.KeyMode.insert, 'load finished')
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
|
Loading…
Reference in New Issue
Block a user