Add type annotations for webelem/webkitelem/webengineelem

per-domain-stylesheets
Florian Bruhin 2018-12-13 11:25:46 +01:00
parent 7c486a76f8
commit 81375b3029
4 changed files with 165 additions and 113 deletions

View File

@ -73,3 +73,15 @@ disallow_incomplete_defs = True
[mypy-qutebrowser.extensions.*]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.browser.webelem]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.browser.webkit.webkitelem]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.browser.webengine.webengineelem]
disallow_untyped_defs = True
disallow_incomplete_defs = True

View File

@ -19,15 +19,23 @@
"""Generic web element related code."""
import typing
import collections.abc
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer, QRect, QPoint
from PyQt5.QtGui import QMouseEvent
from qutebrowser.config import config
from qutebrowser.keyinput import modeman
from qutebrowser.mainwindow import mainwindow
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
MYPY = False
if MYPY:
# pylint: disable=unused-import,useless-suppression
from qutebrowser.browser import browsertab
JsValueType = typing.Union[int, float, str, None]
class Error(Exception):
@ -40,7 +48,7 @@ class OrphanedError(Error):
"""Raised when a webelement's parent has vanished."""
def css_selector(group, url):
def css_selector(group: str, url: QUrl) -> str:
"""Get a CSS selector for the given group/URL."""
selectors = config.instance.get('hints.selectors', url)
if group not in selectors:
@ -60,70 +68,72 @@ class AbstractWebElement(collections.abc.MutableMapping):
tab: The tab associated with this element.
"""
def __init__(self, tab):
def __init__(self, tab: 'browsertab.AbstractTab') -> None:
self._tab = tab
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
raise NotImplementedError
def __str__(self):
def __str__(self) -> str:
raise NotImplementedError
def __getitem__(self, key):
def __getitem__(self, key: str) -> str:
raise NotImplementedError
def __setitem__(self, key, val):
def __setitem__(self, key: str, val: str) -> None:
raise NotImplementedError
def __delitem__(self, key):
def __delitem__(self, key: str) -> None:
raise NotImplementedError
def __iter__(self):
def __iter__(self) -> typing.Iterator[str]:
raise NotImplementedError
def __len__(self):
def __len__(self) -> int:
raise NotImplementedError
def __repr__(self):
def __repr__(self) -> str:
try:
html = utils.compact_text(self.outer_xml(), 500)
except Error:
html = None
return utils.get_repr(self, html=html)
def has_frame(self):
def has_frame(self) -> bool:
"""Check if this element has a valid frame attached."""
raise NotImplementedError
def geometry(self):
def geometry(self) -> QRect:
"""Get the geometry for this element."""
raise NotImplementedError
def classes(self):
def classes(self) -> typing.List[str]:
"""Get a list of classes assigned to this element."""
raise NotImplementedError
def tag_name(self):
def tag_name(self) -> str:
"""Get the tag name of this element.
The returned name will always be lower-case.
"""
raise NotImplementedError
def outer_xml(self):
def outer_xml(self) -> str:
"""Get the full HTML representation of this element."""
raise NotImplementedError
def value(self):
def value(self) -> JsValueType:
"""Get the value attribute for this element, or None."""
raise NotImplementedError
def set_value(self, value):
def set_value(self, value: JsValueType) -> None:
"""Set the element value."""
raise NotImplementedError
def dispatch_event(self, event, bubbles=False,
cancelable=False, composed=False):
def dispatch_event(self, event: str,
bubbles: bool = False,
cancelable: bool = False,
composed: bool = False) -> None:
"""Dispatch an event to the element.
Args:
@ -134,11 +144,12 @@ class AbstractWebElement(collections.abc.MutableMapping):
"""
raise NotImplementedError
def insert_text(self, text):
def insert_text(self, text: str) -> None:
"""Insert the given text into the element."""
raise NotImplementedError
def rect_on_view(self, *, elem_geometry=None, no_js=False):
def rect_on_view(self, *, elem_geometry: QRect = None,
no_js: bool = False) -> QRect:
"""Get the geometry of the element relative to the webview.
Args:
@ -147,11 +158,11 @@ class AbstractWebElement(collections.abc.MutableMapping):
"""
raise NotImplementedError
def is_writable(self):
def is_writable(self) -> bool:
"""Check whether an element is writable."""
return not ('disabled' in self or 'readonly' in self)
def is_content_editable(self):
def is_content_editable(self) -> bool:
"""Check if an element has a contenteditable attribute.
Args:
@ -166,7 +177,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
except KeyError:
return False
def _is_editable_object(self):
def _is_editable_object(self) -> bool:
"""Check if an object-element is editable."""
if 'type' not in self:
log.webelem.debug("<object> without type clicked...")
@ -182,7 +193,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
# Image/Audio/...
return False
def _is_editable_input(self):
def _is_editable_input(self) -> bool:
"""Check if an input-element is editable.
Return:
@ -199,7 +210,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
else:
return False
def _is_editable_classes(self):
def _is_editable_classes(self) -> bool:
"""Check if an element is editable based on its classes.
Return:
@ -218,7 +229,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
return True
return False
def is_editable(self, strict=False):
def is_editable(self, strict: bool = False) -> bool:
"""Check whether we should switch to insert mode for this element.
Args:
@ -249,17 +260,17 @@ class AbstractWebElement(collections.abc.MutableMapping):
return self._is_editable_classes() and not strict
return False
def is_text_input(self):
def is_text_input(self) -> bool:
"""Check if this element is some kind of text box."""
roles = ('combobox', 'textbox')
tag = self.tag_name()
return self.get('role', None) in roles or tag in ['input', 'textarea']
def remove_blank_target(self):
def remove_blank_target(self) -> None:
"""Remove target from link."""
raise NotImplementedError
def resolve_url(self, baseurl):
def resolve_url(self, baseurl: QUrl) -> typing.Optional[QUrl]:
"""Resolve the URL in the element's src/href attribute.
Args:
@ -286,16 +297,16 @@ class AbstractWebElement(collections.abc.MutableMapping):
qtutils.ensure_valid(url)
return url
def is_link(self):
def is_link(self) -> bool:
"""Return True if this AbstractWebElement is a link."""
href_tags = ['a', 'area', 'link']
return self.tag_name() in href_tags and 'href' in self
def _requires_user_interaction(self):
def _requires_user_interaction(self) -> bool:
"""Return True if clicking this element needs user interaction."""
raise NotImplementedError
def _mouse_pos(self):
def _mouse_pos(self) -> QPoint:
"""Get the position to click/hover."""
# Click the center of the largest square fitting into the top/left
# corner of the rectangle, this will help if part of the <a> element
@ -311,35 +322,38 @@ class AbstractWebElement(collections.abc.MutableMapping):
raise Error("Element position is out of view!")
return pos
def _move_text_cursor(self):
def _move_text_cursor(self) -> None:
"""Move cursor to end after clicking."""
raise NotImplementedError
def _click_fake_event(self, click_target):
def _click_fake_event(self, click_target: usertypes.ClickTarget) -> None:
"""Send a fake click event to the element."""
pos = self._mouse_pos()
log.webelem.debug("Sending fake click to {!r} at position {} with "
"target {}".format(self, pos, click_target))
modifiers = {
target_modifiers = {
usertypes.ClickTarget.normal: Qt.NoModifier,
usertypes.ClickTarget.window: Qt.AltModifier | Qt.ShiftModifier,
usertypes.ClickTarget.tab: Qt.ControlModifier,
usertypes.ClickTarget.tab_bg: Qt.ControlModifier,
}
if config.val.tabs.background:
modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier
target_modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier
else:
modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier
target_modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier
modifiers = typing.cast(Qt.KeyboardModifiers,
target_modifiers[click_target])
events = [
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
Qt.NoModifier),
QMouseEvent(QEvent.MouseButtonPress, pos, Qt.LeftButton,
Qt.LeftButton, modifiers[click_target]),
Qt.LeftButton, modifiers),
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
Qt.NoButton, modifiers[click_target]),
Qt.NoButton, modifiers),
]
for evt in events:
@ -347,15 +361,15 @@ class AbstractWebElement(collections.abc.MutableMapping):
QTimer.singleShot(0, self._move_text_cursor)
def _click_editable(self, click_target):
def _click_editable(self, click_target: usertypes.ClickTarget) -> None:
"""Fake a click on an editable input field."""
raise NotImplementedError
def _click_js(self, click_target):
def _click_js(self, click_target: usertypes.ClickTarget) -> None:
"""Fake a click by using the JS .click() method."""
raise NotImplementedError
def _click_href(self, click_target):
def _click_href(self, click_target: usertypes.ClickTarget) -> None:
"""Fake a click on an element with a href by opening the link."""
baseurl = self._tab.url()
url = self.resolve_url(baseurl)
@ -377,7 +391,8 @@ class AbstractWebElement(collections.abc.MutableMapping):
else:
raise ValueError("Unknown ClickTarget {}".format(click_target))
def click(self, click_target, *, force_event=False):
def click(self, click_target: usertypes.ClickTarget, *,
force_event: bool = False) -> None:
"""Simulate a click on the element.
Args:
@ -414,7 +429,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
else:
raise ValueError("Unknown ClickTarget {}".format(click_target))
def hover(self):
def hover(self) -> None:
"""Simulate a mouse hover over the element."""
pos = self._mouse_pos()
event = QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,

View File

@ -22,20 +22,27 @@
"""QtWebEngine specific part of the web element API."""
import typing
from PyQt5.QtCore import QRect, Qt, QPoint, QEventLoop
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineSettings
from qutebrowser.utils import log, javascript, urlutils
from qutebrowser.utils import log, javascript, urlutils, usertypes
from qutebrowser.browser import webelem
MYPY = False
if MYPY:
# pylint: disable=unused-import,useless-suppression
from qutebrowser.browser.webengine import webenginetab
class WebEngineElement(webelem.AbstractWebElement):
"""A web element for QtWebEngine, using JS under the hood."""
def __init__(self, js_dict, tab):
def __init__(self, js_dict: typing.Dict[str, typing.Any],
tab: 'webenginetab.WebEngineTab') -> None:
super().__init__(tab)
# Do some sanity checks on the data we get from JS
js_dict_types = {
@ -48,7 +55,7 @@ class WebEngineElement(webelem.AbstractWebElement):
'rects': list,
'attributes': dict,
'caret_position': (int, type(None)),
}
} # type: typing.Dict[str, typing.Union[type, typing.Tuple[type,...]]]
assert set(js_dict.keys()).issubset(js_dict_types.keys())
for name, typ in js_dict_types.items():
if name in js_dict and not isinstance(js_dict[name], typ):
@ -73,50 +80,51 @@ class WebEngineElement(webelem.AbstractWebElement):
self._id = js_dict['id']
self._js_dict = js_dict
def __str__(self):
def __str__(self) -> str:
return self._js_dict.get('text', '')
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
if not isinstance(other, WebEngineElement):
return NotImplemented
return self._id == other._id # pylint: disable=protected-access
def __getitem__(self, key):
def __getitem__(self, key: str) -> str:
attrs = self._js_dict['attributes']
return attrs[key]
def __setitem__(self, key, val):
def __setitem__(self, key: str, val: str) -> None:
self._js_dict['attributes'][key] = val
self._js_call('set_attribute', key, val)
def __delitem__(self, key):
def __delitem__(self, key: str) -> None:
log.stub()
def __iter__(self):
def __iter__(self) -> typing.Iterator[str]:
return iter(self._js_dict['attributes'])
def __len__(self):
def __len__(self) -> int:
return len(self._js_dict['attributes'])
def _js_call(self, name, *args, callback=None):
def _js_call(self, name: str, *args: webelem.JsValueType,
callback: typing.Callable[[typing.Any], None] = None) -> None:
"""Wrapper to run stuff from webelem.js."""
if self._tab.is_deleted():
raise webelem.OrphanedError("Tab containing element vanished")
js_code = javascript.assemble('webelem', name, self._id, *args)
self._tab.run_js_async(js_code, callback=callback)
def has_frame(self):
def has_frame(self) -> bool:
return True
def geometry(self):
def geometry(self) -> QRect:
log.stub()
return QRect()
def classes(self):
def classes(self) -> typing.List[str]:
"""Get a list of classes assigned to this element."""
return self._js_dict['class_name'].split()
def tag_name(self):
def tag_name(self) -> str:
"""Get the tag name of this element.
The returned name will always be lower-case.
@ -125,34 +133,37 @@ class WebEngineElement(webelem.AbstractWebElement):
assert isinstance(tag, str), tag
return tag.lower()
def outer_xml(self):
def outer_xml(self) -> str:
"""Get the full HTML representation of this element."""
return self._js_dict['outer_xml']
def value(self):
def value(self) -> webelem.JsValueType:
return self._js_dict.get('value', None)
def set_value(self, value):
def set_value(self, value: webelem.JsValueType) -> None:
self._js_call('set_value', value)
def dispatch_event(self, event, bubbles=False,
cancelable=False, composed=False):
def dispatch_event(self, event: str,
bubbles: bool = False,
cancelable: bool = False,
composed: bool = False) -> None:
self._js_call('dispatch_event', event, bubbles, cancelable, composed)
def caret_position(self):
def caret_position(self) -> typing.Optional[int]:
"""Get the text caret position for the current element.
If the element is not a text element, None is returned.
"""
return self._js_dict.get('caret_position', None)
def insert_text(self, text):
def insert_text(self, text: str) -> None:
if not self.is_editable(strict=True):
raise webelem.Error("Element is not editable!")
log.webelem.debug("Inserting text into element {!r}".format(self))
self._js_call('insert_text', text)
def rect_on_view(self, *, elem_geometry=None, no_js=False):
def rect_on_view(self, *, elem_geometry: QRect = None,
no_js: bool = False) -> QRect:
"""Get the geometry of the element relative to the webview.
Skipping of small rectangles is due to <a> elements containing other
@ -193,16 +204,16 @@ class WebEngineElement(webelem.AbstractWebElement):
self, rects))
return QRect()
def remove_blank_target(self):
def remove_blank_target(self) -> None:
if self._js_dict['attributes'].get('target') == '_blank':
self._js_dict['attributes']['target'] = '_top'
self._js_call('remove_blank_target')
def _move_text_cursor(self):
def _move_text_cursor(self) -> None:
if self.is_text_input() and self.is_editable():
self._js_call('move_cursor_to_end')
def _requires_user_interaction(self):
def _requires_user_interaction(self) -> bool:
baseurl = self._tab.url()
url = self.resolve_url(baseurl)
if url is None:
@ -211,7 +222,7 @@ class WebEngineElement(webelem.AbstractWebElement):
return False
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
def _click_editable(self, click_target):
def _click_editable(self, click_target: usertypes.ClickTarget) -> None:
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515
ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),
QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton,
@ -221,10 +232,11 @@ class WebEngineElement(webelem.AbstractWebElement):
self._js_call('focus')
self._move_text_cursor()
def _click_js(self, _click_target):
def _click_js(self, _click_target: usertypes.ClickTarget) -> None:
# FIXME:qtwebengine Have a proper API for this
# pylint: disable=protected-access
view = self._tab._widget
assert view is not None
# pylint: enable=protected-access
attribute = QWebEngineSettings.JavascriptCanOpenWindows
could_open_windows = view.settings().testAttribute(attribute)
@ -238,8 +250,9 @@ class WebEngineElement(webelem.AbstractWebElement):
qapp.processEvents(QEventLoop.ExcludeSocketNotifiers |
QEventLoop.ExcludeUserInputEvents)
def reset_setting(_arg):
def reset_setting(_arg: typing.Any) -> None:
"""Set the JavascriptCanOpenWindows setting to its old value."""
assert view is not None
try:
view.settings().setAttribute(attribute, could_open_windows)
except RuntimeError:

View File

@ -19,12 +19,18 @@
"""QtWebKit specific part of the web element API."""
import typing
from PyQt5.QtCore import QRect
from PyQt5.QtWebKit import QWebElement, QWebSettings
from PyQt5.QtWebKitWidgets import QWebFrame
from qutebrowser.config import config
from qutebrowser.utils import log, utils, javascript
from qutebrowser.utils import log, utils, javascript, usertypes
from qutebrowser.browser import webelem
MYPY = False
if MYPY:
from qutebrowser.browser.webkit import webkittab
class IsNullError(webelem.Error):
@ -36,7 +42,7 @@ class WebKitElement(webelem.AbstractWebElement):
"""A wrapper around a QWebElement."""
def __init__(self, elem, tab):
def __init__(self, elem: QWebElement, tab: webkittab.WebKitTab) -> None:
super().__init__(tab)
if isinstance(elem, self.__class__):
raise TypeError("Trying to wrap a wrapper!")
@ -44,90 +50,94 @@ class WebKitElement(webelem.AbstractWebElement):
raise IsNullError('{} is a null element!'.format(elem))
self._elem = elem
def __str__(self):
def __str__(self) -> str:
self._check_vanished()
return self._elem.toPlainText()
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
if not isinstance(other, WebKitElement):
return NotImplemented
return self._elem == other._elem # pylint: disable=protected-access
def __getitem__(self, key):
def __getitem__(self, key: str) -> str:
self._check_vanished()
if key not in self:
raise KeyError(key)
return self._elem.attribute(key)
def __setitem__(self, key, val):
def __setitem__(self, key: str, val: str) -> None:
self._check_vanished()
self._elem.setAttribute(key, val)
def __delitem__(self, key):
def __delitem__(self, key: str) -> None:
self._check_vanished()
if key not in self:
raise KeyError(key)
self._elem.removeAttribute(key)
def __contains__(self, key):
def __contains__(self, key: object) -> bool:
assert isinstance(key, str)
self._check_vanished()
return self._elem.hasAttribute(key)
def __iter__(self):
def __iter__(self) -> typing.Iterator[str]:
self._check_vanished()
yield from self._elem.attributeNames()
def __len__(self):
def __len__(self) -> int:
self._check_vanished()
return len(self._elem.attributeNames())
def _check_vanished(self):
def _check_vanished(self) -> None:
"""Raise an exception if the element vanished (is null)."""
if self._elem.isNull():
raise IsNullError('Element {} vanished!'.format(self._elem))
def has_frame(self):
def has_frame(self) -> bool:
self._check_vanished()
return self._elem.webFrame() is not None
def geometry(self):
def geometry(self) -> QRect:
self._check_vanished()
return self._elem.geometry()
def classes(self):
def classes(self) -> typing.List[str]:
self._check_vanished()
return self._elem.classes()
def tag_name(self):
def tag_name(self) -> str:
"""Get the tag name for the current element."""
self._check_vanished()
return self._elem.tagName().lower()
def outer_xml(self):
def outer_xml(self) -> str:
"""Get the full HTML representation of this element."""
self._check_vanished()
return self._elem.toOuterXml()
def value(self):
def value(self) -> webelem.JsValueType:
self._check_vanished()
val = self._elem.evaluateJavaScript('this.value')
assert isinstance(val, (int, float, str, type(None))), val
return val
def set_value(self, value):
def set_value(self, value: webelem.JsValueType) -> None:
self._check_vanished()
if self._tab.is_deleted():
raise webelem.OrphanedError("Tab containing element vanished")
if self.is_content_editable():
log.webelem.debug("Filling {!r} via set_text.".format(self))
assert isinstance(value, str)
self._elem.setPlainText(value)
else:
log.webelem.debug("Filling {!r} via javascript.".format(self))
value = javascript.to_js(value)
self._elem.evaluateJavaScript("this.value={}".format(value))
def dispatch_event(self, event, bubbles=False,
cancelable=False, composed=False):
def dispatch_event(self, event: str,
bubbles: bool = False,
cancelable: bool = False,
composed: bool = False) -> None:
self._check_vanished()
log.webelem.debug("Firing event on {!r} via javascript.".format(self))
self._elem.evaluateJavaScript(
@ -138,7 +148,7 @@ class WebKitElement(webelem.AbstractWebElement):
javascript.to_js(cancelable),
javascript.to_js(composed)))
def caret_position(self):
def caret_position(self) -> int:
"""Get the text caret position for the current element."""
self._check_vanished()
pos = self._elem.evaluateJavaScript('this.selectionStart')
@ -146,7 +156,7 @@ class WebKitElement(webelem.AbstractWebElement):
return 0
return int(pos)
def insert_text(self, text):
def insert_text(self, text: str) -> None:
self._check_vanished()
if not self.is_editable(strict=True):
raise webelem.Error("Element is not editable!")
@ -158,7 +168,7 @@ class WebKitElement(webelem.AbstractWebElement):
this.dispatchEvent(event);
""".format(javascript.to_js(text)))
def _parent(self):
def _parent(self) -> typing.Optional['WebKitElement']:
"""Get the parent element of this element."""
self._check_vanished()
elem = self._elem.parent()
@ -166,7 +176,7 @@ class WebKitElement(webelem.AbstractWebElement):
return None
return WebKitElement(elem, tab=self._tab)
def _rect_on_view_js(self):
def _rect_on_view_js(self) -> typing.Optional[QRect]:
"""Javascript implementation for rect_on_view."""
# FIXME:qtwebengine maybe we can reuse this?
rects = self._elem.evaluateJavaScript("this.getClientRects()")
@ -178,8 +188,8 @@ class WebKitElement(webelem.AbstractWebElement):
return None
text = utils.compact_text(self._elem.toOuterXml(), 500)
log.webelem.vdebug("Client rectangles of element '{}': {}".format(
text, rects))
log.webelem.vdebug( # type: ignore
"Client rectangles of element '{}': {}".format(text, rects))
for i in range(int(rects.get("length", 0))):
rect = rects[str(i)]
@ -204,7 +214,8 @@ class WebKitElement(webelem.AbstractWebElement):
return None
def _rect_on_view_python(self, elem_geometry):
def _rect_on_view_python(self,
elem_geometry: typing.Optional[QRect]) -> QRect:
"""Python implementation for rect_on_view."""
if elem_geometry is None:
geometry = self._elem.geometry()
@ -218,7 +229,8 @@ class WebKitElement(webelem.AbstractWebElement):
frame = frame.parentFrame()
return rect
def rect_on_view(self, *, elem_geometry=None, no_js=False):
def rect_on_view(self, *, elem_geometry: QRect = None,
no_js: bool = False) -> QRect:
"""Get the geometry of the element relative to the webview.
Uses the getClientRects() JavaScript method to obtain the collection of
@ -248,7 +260,7 @@ class WebKitElement(webelem.AbstractWebElement):
# No suitable rects found via JS, try via the QWebElement API
return self._rect_on_view_python(elem_geometry)
def _is_visible(self, mainframe):
def _is_visible(self, mainframe: QWebFrame) -> bool:
"""Check if the given element is visible in the given frame.
This is not public API because it can't be implemented easily here with
@ -300,8 +312,8 @@ class WebKitElement(webelem.AbstractWebElement):
visible_in_frame = visible_on_screen
return all([visible_on_screen, visible_in_frame])
def remove_blank_target(self):
elem = self
def remove_blank_target(self) -> None:
elem = self # type: typing.Optional[WebKitElement]
for _ in range(5):
if elem is None:
break
@ -311,14 +323,14 @@ class WebKitElement(webelem.AbstractWebElement):
break
elem = elem._parent() # pylint: disable=protected-access
def _move_text_cursor(self):
def _move_text_cursor(self) -> None:
if self.is_text_input() and self.is_editable():
self._tab.caret.move_to_end_of_document()
def _requires_user_interaction(self):
def _requires_user_interaction(self) -> bool:
return False
def _click_editable(self, click_target):
def _click_editable(self, click_target: usertypes.ClickTarget) -> None:
ok = self._elem.evaluateJavaScript('this.focus(); true;')
if ok:
self._move_text_cursor()
@ -326,7 +338,7 @@ class WebKitElement(webelem.AbstractWebElement):
log.webelem.debug("Failed to focus via JS, falling back to event")
self._click_fake_event(click_target)
def _click_js(self, click_target):
def _click_js(self, click_target: usertypes.ClickTarget) -> None:
settings = QWebSettings.globalSettings()
attribute = QWebSettings.JavascriptCanOpenWindows
could_open_windows = settings.testAttribute(attribute)
@ -337,12 +349,12 @@ class WebKitElement(webelem.AbstractWebElement):
log.webelem.debug("Failed to click via JS, falling back to event")
self._click_fake_event(click_target)
def _click_fake_event(self, click_target):
def _click_fake_event(self, click_target: usertypes.ClickTarget) -> None:
self._tab.data.override_target = click_target
super()._click_fake_event(click_target)
def get_child_frames(startframe):
def get_child_frames(startframe: QWebFrame) -> typing.List[QWebFrame]:
"""Get all children recursively of a given QWebFrame.
Loosely based on http://blog.nextgenetics.net/?e=64
@ -356,7 +368,7 @@ def get_child_frames(startframe):
results = []
frames = [startframe]
while frames:
new_frames = []
new_frames = [] # type: typing.List[QWebFrame]
for frame in frames:
results.append(frame)
new_frames += frame.childFrames()