Add type annotations for webelem/webkitelem/webengineelem
This commit is contained in:
parent
7c486a76f8
commit
81375b3029
12
mypy.ini
12
mypy.ini
@ -73,3 +73,15 @@ disallow_incomplete_defs = True
|
|||||||
[mypy-qutebrowser.extensions.*]
|
[mypy-qutebrowser.extensions.*]
|
||||||
disallow_untyped_defs = True
|
disallow_untyped_defs = True
|
||||||
disallow_incomplete_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
|
||||||
|
@ -19,15 +19,23 @@
|
|||||||
|
|
||||||
"""Generic web element related code."""
|
"""Generic web element related code."""
|
||||||
|
|
||||||
|
import typing
|
||||||
import collections.abc
|
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 PyQt5.QtGui import QMouseEvent
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.keyinput import modeman
|
from qutebrowser.keyinput import modeman
|
||||||
from qutebrowser.mainwindow import mainwindow
|
from qutebrowser.mainwindow import mainwindow
|
||||||
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
|
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):
|
class Error(Exception):
|
||||||
@ -40,7 +48,7 @@ class OrphanedError(Error):
|
|||||||
"""Raised when a webelement's parent has vanished."""
|
"""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."""
|
"""Get a CSS selector for the given group/URL."""
|
||||||
selectors = config.instance.get('hints.selectors', url)
|
selectors = config.instance.get('hints.selectors', url)
|
||||||
if group not in selectors:
|
if group not in selectors:
|
||||||
@ -60,70 +68,72 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
tab: The tab associated with this element.
|
tab: The tab associated with this element.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, tab):
|
def __init__(self, tab: 'browsertab.AbstractTab') -> None:
|
||||||
self._tab = tab
|
self._tab = tab
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: object) -> bool:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key: str) -> str:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __setitem__(self, key, val):
|
def __setitem__(self, key: str, val: str) -> None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key: str) -> None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self) -> typing.Iterator[str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self) -> int:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
try:
|
try:
|
||||||
html = utils.compact_text(self.outer_xml(), 500)
|
html = utils.compact_text(self.outer_xml(), 500)
|
||||||
except Error:
|
except Error:
|
||||||
html = None
|
html = None
|
||||||
return utils.get_repr(self, html=html)
|
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."""
|
"""Check if this element has a valid frame attached."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def geometry(self):
|
def geometry(self) -> QRect:
|
||||||
"""Get the geometry for this element."""
|
"""Get the geometry for this element."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def classes(self):
|
def classes(self) -> typing.List[str]:
|
||||||
"""Get a list of classes assigned to this element."""
|
"""Get a list of classes assigned to this element."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def tag_name(self):
|
def tag_name(self) -> str:
|
||||||
"""Get the tag name of this element.
|
"""Get the tag name of this element.
|
||||||
|
|
||||||
The returned name will always be lower-case.
|
The returned name will always be lower-case.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def outer_xml(self):
|
def outer_xml(self) -> str:
|
||||||
"""Get the full HTML representation of this element."""
|
"""Get the full HTML representation of this element."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def value(self):
|
def value(self) -> JsValueType:
|
||||||
"""Get the value attribute for this element, or None."""
|
"""Get the value attribute for this element, or None."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value: JsValueType) -> None:
|
||||||
"""Set the element value."""
|
"""Set the element value."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def dispatch_event(self, event, bubbles=False,
|
def dispatch_event(self, event: str,
|
||||||
cancelable=False, composed=False):
|
bubbles: bool = False,
|
||||||
|
cancelable: bool = False,
|
||||||
|
composed: bool = False) -> None:
|
||||||
"""Dispatch an event to the element.
|
"""Dispatch an event to the element.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -134,11 +144,12 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def insert_text(self, text):
|
def insert_text(self, text: str) -> None:
|
||||||
"""Insert the given text into the element."""
|
"""Insert the given text into the element."""
|
||||||
raise NotImplementedError
|
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.
|
"""Get the geometry of the element relative to the webview.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -147,11 +158,11 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def is_writable(self):
|
def is_writable(self) -> bool:
|
||||||
"""Check whether an element is writable."""
|
"""Check whether an element is writable."""
|
||||||
return not ('disabled' in self or 'readonly' in self)
|
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.
|
"""Check if an element has a contenteditable attribute.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -166,7 +177,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _is_editable_object(self):
|
def _is_editable_object(self) -> bool:
|
||||||
"""Check if an object-element is editable."""
|
"""Check if an object-element is editable."""
|
||||||
if 'type' not in self:
|
if 'type' not in self:
|
||||||
log.webelem.debug("<object> without type clicked...")
|
log.webelem.debug("<object> without type clicked...")
|
||||||
@ -182,7 +193,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
# Image/Audio/...
|
# Image/Audio/...
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _is_editable_input(self):
|
def _is_editable_input(self) -> bool:
|
||||||
"""Check if an input-element is editable.
|
"""Check if an input-element is editable.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
@ -199,7 +210,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _is_editable_classes(self):
|
def _is_editable_classes(self) -> bool:
|
||||||
"""Check if an element is editable based on its classes.
|
"""Check if an element is editable based on its classes.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
@ -218,7 +229,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
return True
|
return True
|
||||||
return False
|
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.
|
"""Check whether we should switch to insert mode for this element.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -249,17 +260,17 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
return self._is_editable_classes() and not strict
|
return self._is_editable_classes() and not strict
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_text_input(self):
|
def is_text_input(self) -> bool:
|
||||||
"""Check if this element is some kind of text box."""
|
"""Check if this element is some kind of text box."""
|
||||||
roles = ('combobox', 'textbox')
|
roles = ('combobox', 'textbox')
|
||||||
tag = self.tag_name()
|
tag = self.tag_name()
|
||||||
return self.get('role', None) in roles or tag in ['input', 'textarea']
|
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."""
|
"""Remove target from link."""
|
||||||
raise NotImplementedError
|
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.
|
"""Resolve the URL in the element's src/href attribute.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -286,16 +297,16 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
qtutils.ensure_valid(url)
|
qtutils.ensure_valid(url)
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def is_link(self):
|
def is_link(self) -> bool:
|
||||||
"""Return True if this AbstractWebElement is a link."""
|
"""Return True if this AbstractWebElement is a link."""
|
||||||
href_tags = ['a', 'area', 'link']
|
href_tags = ['a', 'area', 'link']
|
||||||
return self.tag_name() in href_tags and 'href' in self
|
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."""
|
"""Return True if clicking this element needs user interaction."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def _mouse_pos(self):
|
def _mouse_pos(self) -> QPoint:
|
||||||
"""Get the position to click/hover."""
|
"""Get the position to click/hover."""
|
||||||
# Click the center of the largest square fitting into the top/left
|
# 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
|
# 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!")
|
raise Error("Element position is out of view!")
|
||||||
return pos
|
return pos
|
||||||
|
|
||||||
def _move_text_cursor(self):
|
def _move_text_cursor(self) -> None:
|
||||||
"""Move cursor to end after clicking."""
|
"""Move cursor to end after clicking."""
|
||||||
raise NotImplementedError
|
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."""
|
"""Send a fake click event to the element."""
|
||||||
pos = self._mouse_pos()
|
pos = self._mouse_pos()
|
||||||
|
|
||||||
log.webelem.debug("Sending fake click to {!r} at position {} with "
|
log.webelem.debug("Sending fake click to {!r} at position {} with "
|
||||||
"target {}".format(self, pos, click_target))
|
"target {}".format(self, pos, click_target))
|
||||||
|
|
||||||
modifiers = {
|
target_modifiers = {
|
||||||
usertypes.ClickTarget.normal: Qt.NoModifier,
|
usertypes.ClickTarget.normal: Qt.NoModifier,
|
||||||
usertypes.ClickTarget.window: Qt.AltModifier | Qt.ShiftModifier,
|
usertypes.ClickTarget.window: Qt.AltModifier | Qt.ShiftModifier,
|
||||||
usertypes.ClickTarget.tab: Qt.ControlModifier,
|
usertypes.ClickTarget.tab: Qt.ControlModifier,
|
||||||
usertypes.ClickTarget.tab_bg: Qt.ControlModifier,
|
usertypes.ClickTarget.tab_bg: Qt.ControlModifier,
|
||||||
}
|
}
|
||||||
if config.val.tabs.background:
|
if config.val.tabs.background:
|
||||||
modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier
|
target_modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier
|
||||||
else:
|
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 = [
|
events = [
|
||||||
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
|
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
|
||||||
Qt.NoModifier),
|
Qt.NoModifier),
|
||||||
QMouseEvent(QEvent.MouseButtonPress, pos, Qt.LeftButton,
|
QMouseEvent(QEvent.MouseButtonPress, pos, Qt.LeftButton,
|
||||||
Qt.LeftButton, modifiers[click_target]),
|
Qt.LeftButton, modifiers),
|
||||||
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
|
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
|
||||||
Qt.NoButton, modifiers[click_target]),
|
Qt.NoButton, modifiers),
|
||||||
]
|
]
|
||||||
|
|
||||||
for evt in events:
|
for evt in events:
|
||||||
@ -347,15 +361,15 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
|
|
||||||
QTimer.singleShot(0, self._move_text_cursor)
|
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."""
|
"""Fake a click on an editable input field."""
|
||||||
raise NotImplementedError
|
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."""
|
"""Fake a click by using the JS .click() method."""
|
||||||
raise NotImplementedError
|
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."""
|
"""Fake a click on an element with a href by opening the link."""
|
||||||
baseurl = self._tab.url()
|
baseurl = self._tab.url()
|
||||||
url = self.resolve_url(baseurl)
|
url = self.resolve_url(baseurl)
|
||||||
@ -377,7 +391,8 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Unknown ClickTarget {}".format(click_target))
|
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.
|
"""Simulate a click on the element.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -414,7 +429,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Unknown ClickTarget {}".format(click_target))
|
raise ValueError("Unknown ClickTarget {}".format(click_target))
|
||||||
|
|
||||||
def hover(self):
|
def hover(self) -> None:
|
||||||
"""Simulate a mouse hover over the element."""
|
"""Simulate a mouse hover over the element."""
|
||||||
pos = self._mouse_pos()
|
pos = self._mouse_pos()
|
||||||
event = QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
|
event = QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
|
||||||
|
@ -22,20 +22,27 @@
|
|||||||
|
|
||||||
"""QtWebEngine specific part of the web element API."""
|
"""QtWebEngine specific part of the web element API."""
|
||||||
|
|
||||||
|
import typing
|
||||||
|
|
||||||
from PyQt5.QtCore import QRect, Qt, QPoint, QEventLoop
|
from PyQt5.QtCore import QRect, Qt, QPoint, QEventLoop
|
||||||
from PyQt5.QtGui import QMouseEvent
|
from PyQt5.QtGui import QMouseEvent
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
from PyQt5.QtWebEngineWidgets import QWebEngineSettings
|
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
|
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):
|
class WebEngineElement(webelem.AbstractWebElement):
|
||||||
|
|
||||||
"""A web element for QtWebEngine, using JS under the hood."""
|
"""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)
|
super().__init__(tab)
|
||||||
# Do some sanity checks on the data we get from JS
|
# Do some sanity checks on the data we get from JS
|
||||||
js_dict_types = {
|
js_dict_types = {
|
||||||
@ -48,7 +55,7 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||||||
'rects': list,
|
'rects': list,
|
||||||
'attributes': dict,
|
'attributes': dict,
|
||||||
'caret_position': (int, type(None)),
|
'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())
|
assert set(js_dict.keys()).issubset(js_dict_types.keys())
|
||||||
for name, typ in js_dict_types.items():
|
for name, typ in js_dict_types.items():
|
||||||
if name in js_dict and not isinstance(js_dict[name], typ):
|
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._id = js_dict['id']
|
||||||
self._js_dict = js_dict
|
self._js_dict = js_dict
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return self._js_dict.get('text', '')
|
return self._js_dict.get('text', '')
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: object) -> bool:
|
||||||
if not isinstance(other, WebEngineElement):
|
if not isinstance(other, WebEngineElement):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return self._id == other._id # pylint: disable=protected-access
|
return self._id == other._id # pylint: disable=protected-access
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key: str) -> str:
|
||||||
attrs = self._js_dict['attributes']
|
attrs = self._js_dict['attributes']
|
||||||
return attrs[key]
|
return attrs[key]
|
||||||
|
|
||||||
def __setitem__(self, key, val):
|
def __setitem__(self, key: str, val: str) -> None:
|
||||||
self._js_dict['attributes'][key] = val
|
self._js_dict['attributes'][key] = val
|
||||||
self._js_call('set_attribute', key, val)
|
self._js_call('set_attribute', key, val)
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key: str) -> None:
|
||||||
log.stub()
|
log.stub()
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self) -> typing.Iterator[str]:
|
||||||
return iter(self._js_dict['attributes'])
|
return iter(self._js_dict['attributes'])
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self) -> int:
|
||||||
return len(self._js_dict['attributes'])
|
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."""
|
"""Wrapper to run stuff from webelem.js."""
|
||||||
if self._tab.is_deleted():
|
if self._tab.is_deleted():
|
||||||
raise webelem.OrphanedError("Tab containing element vanished")
|
raise webelem.OrphanedError("Tab containing element vanished")
|
||||||
js_code = javascript.assemble('webelem', name, self._id, *args)
|
js_code = javascript.assemble('webelem', name, self._id, *args)
|
||||||
self._tab.run_js_async(js_code, callback=callback)
|
self._tab.run_js_async(js_code, callback=callback)
|
||||||
|
|
||||||
def has_frame(self):
|
def has_frame(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def geometry(self):
|
def geometry(self) -> QRect:
|
||||||
log.stub()
|
log.stub()
|
||||||
return QRect()
|
return QRect()
|
||||||
|
|
||||||
def classes(self):
|
def classes(self) -> typing.List[str]:
|
||||||
"""Get a list of classes assigned to this element."""
|
"""Get a list of classes assigned to this element."""
|
||||||
return self._js_dict['class_name'].split()
|
return self._js_dict['class_name'].split()
|
||||||
|
|
||||||
def tag_name(self):
|
def tag_name(self) -> str:
|
||||||
"""Get the tag name of this element.
|
"""Get the tag name of this element.
|
||||||
|
|
||||||
The returned name will always be lower-case.
|
The returned name will always be lower-case.
|
||||||
@ -125,34 +133,37 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||||||
assert isinstance(tag, str), tag
|
assert isinstance(tag, str), tag
|
||||||
return tag.lower()
|
return tag.lower()
|
||||||
|
|
||||||
def outer_xml(self):
|
def outer_xml(self) -> str:
|
||||||
"""Get the full HTML representation of this element."""
|
"""Get the full HTML representation of this element."""
|
||||||
return self._js_dict['outer_xml']
|
return self._js_dict['outer_xml']
|
||||||
|
|
||||||
def value(self):
|
def value(self) -> webelem.JsValueType:
|
||||||
return self._js_dict.get('value', None)
|
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)
|
self._js_call('set_value', value)
|
||||||
|
|
||||||
def dispatch_event(self, event, bubbles=False,
|
def dispatch_event(self, event: str,
|
||||||
cancelable=False, composed=False):
|
bubbles: bool = False,
|
||||||
|
cancelable: bool = False,
|
||||||
|
composed: bool = False) -> None:
|
||||||
self._js_call('dispatch_event', event, bubbles, cancelable, composed)
|
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.
|
"""Get the text caret position for the current element.
|
||||||
|
|
||||||
If the element is not a text element, None is returned.
|
If the element is not a text element, None is returned.
|
||||||
"""
|
"""
|
||||||
return self._js_dict.get('caret_position', None)
|
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):
|
if not self.is_editable(strict=True):
|
||||||
raise webelem.Error("Element is not editable!")
|
raise webelem.Error("Element is not editable!")
|
||||||
log.webelem.debug("Inserting text into element {!r}".format(self))
|
log.webelem.debug("Inserting text into element {!r}".format(self))
|
||||||
self._js_call('insert_text', text)
|
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.
|
"""Get the geometry of the element relative to the webview.
|
||||||
|
|
||||||
Skipping of small rectangles is due to <a> elements containing other
|
Skipping of small rectangles is due to <a> elements containing other
|
||||||
@ -193,16 +204,16 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||||||
self, rects))
|
self, rects))
|
||||||
return QRect()
|
return QRect()
|
||||||
|
|
||||||
def remove_blank_target(self):
|
def remove_blank_target(self) -> None:
|
||||||
if self._js_dict['attributes'].get('target') == '_blank':
|
if self._js_dict['attributes'].get('target') == '_blank':
|
||||||
self._js_dict['attributes']['target'] = '_top'
|
self._js_dict['attributes']['target'] = '_top'
|
||||||
self._js_call('remove_blank_target')
|
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():
|
if self.is_text_input() and self.is_editable():
|
||||||
self._js_call('move_cursor_to_end')
|
self._js_call('move_cursor_to_end')
|
||||||
|
|
||||||
def _requires_user_interaction(self):
|
def _requires_user_interaction(self) -> bool:
|
||||||
baseurl = self._tab.url()
|
baseurl = self._tab.url()
|
||||||
url = self.resolve_url(baseurl)
|
url = self.resolve_url(baseurl)
|
||||||
if url is None:
|
if url is None:
|
||||||
@ -211,7 +222,7 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||||||
return False
|
return False
|
||||||
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
|
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
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515
|
||||||
ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),
|
ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),
|
||||||
QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton,
|
QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton,
|
||||||
@ -221,10 +232,11 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||||||
self._js_call('focus')
|
self._js_call('focus')
|
||||||
self._move_text_cursor()
|
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
|
# FIXME:qtwebengine Have a proper API for this
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
view = self._tab._widget
|
view = self._tab._widget
|
||||||
|
assert view is not None
|
||||||
# pylint: enable=protected-access
|
# pylint: enable=protected-access
|
||||||
attribute = QWebEngineSettings.JavascriptCanOpenWindows
|
attribute = QWebEngineSettings.JavascriptCanOpenWindows
|
||||||
could_open_windows = view.settings().testAttribute(attribute)
|
could_open_windows = view.settings().testAttribute(attribute)
|
||||||
@ -238,8 +250,9 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||||||
qapp.processEvents(QEventLoop.ExcludeSocketNotifiers |
|
qapp.processEvents(QEventLoop.ExcludeSocketNotifiers |
|
||||||
QEventLoop.ExcludeUserInputEvents)
|
QEventLoop.ExcludeUserInputEvents)
|
||||||
|
|
||||||
def reset_setting(_arg):
|
def reset_setting(_arg: typing.Any) -> None:
|
||||||
"""Set the JavascriptCanOpenWindows setting to its old value."""
|
"""Set the JavascriptCanOpenWindows setting to its old value."""
|
||||||
|
assert view is not None
|
||||||
try:
|
try:
|
||||||
view.settings().setAttribute(attribute, could_open_windows)
|
view.settings().setAttribute(attribute, could_open_windows)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
|
@ -19,12 +19,18 @@
|
|||||||
|
|
||||||
"""QtWebKit specific part of the web element API."""
|
"""QtWebKit specific part of the web element API."""
|
||||||
|
|
||||||
|
import typing
|
||||||
|
|
||||||
from PyQt5.QtCore import QRect
|
from PyQt5.QtCore import QRect
|
||||||
from PyQt5.QtWebKit import QWebElement, QWebSettings
|
from PyQt5.QtWebKit import QWebElement, QWebSettings
|
||||||
|
from PyQt5.QtWebKitWidgets import QWebFrame
|
||||||
|
|
||||||
from qutebrowser.config import config
|
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
|
from qutebrowser.browser import webelem
|
||||||
|
MYPY = False
|
||||||
|
if MYPY:
|
||||||
|
from qutebrowser.browser.webkit import webkittab
|
||||||
|
|
||||||
|
|
||||||
class IsNullError(webelem.Error):
|
class IsNullError(webelem.Error):
|
||||||
@ -36,7 +42,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
|
|
||||||
"""A wrapper around a QWebElement."""
|
"""A wrapper around a QWebElement."""
|
||||||
|
|
||||||
def __init__(self, elem, tab):
|
def __init__(self, elem: QWebElement, tab: webkittab.WebKitTab) -> None:
|
||||||
super().__init__(tab)
|
super().__init__(tab)
|
||||||
if isinstance(elem, self.__class__):
|
if isinstance(elem, self.__class__):
|
||||||
raise TypeError("Trying to wrap a wrapper!")
|
raise TypeError("Trying to wrap a wrapper!")
|
||||||
@ -44,90 +50,94 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
raise IsNullError('{} is a null element!'.format(elem))
|
raise IsNullError('{} is a null element!'.format(elem))
|
||||||
self._elem = elem
|
self._elem = elem
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
return self._elem.toPlainText()
|
return self._elem.toPlainText()
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: object) -> bool:
|
||||||
if not isinstance(other, WebKitElement):
|
if not isinstance(other, WebKitElement):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return self._elem == other._elem # pylint: disable=protected-access
|
return self._elem == other._elem # pylint: disable=protected-access
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key: str) -> str:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
if key not in self:
|
if key not in self:
|
||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
return self._elem.attribute(key)
|
return self._elem.attribute(key)
|
||||||
|
|
||||||
def __setitem__(self, key, val):
|
def __setitem__(self, key: str, val: str) -> None:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
self._elem.setAttribute(key, val)
|
self._elem.setAttribute(key, val)
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key: str) -> None:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
if key not in self:
|
if key not in self:
|
||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
self._elem.removeAttribute(key)
|
self._elem.removeAttribute(key)
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key: object) -> bool:
|
||||||
|
assert isinstance(key, str)
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
return self._elem.hasAttribute(key)
|
return self._elem.hasAttribute(key)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self) -> typing.Iterator[str]:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
yield from self._elem.attributeNames()
|
yield from self._elem.attributeNames()
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self) -> int:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
return len(self._elem.attributeNames())
|
return len(self._elem.attributeNames())
|
||||||
|
|
||||||
def _check_vanished(self):
|
def _check_vanished(self) -> None:
|
||||||
"""Raise an exception if the element vanished (is null)."""
|
"""Raise an exception if the element vanished (is null)."""
|
||||||
if self._elem.isNull():
|
if self._elem.isNull():
|
||||||
raise IsNullError('Element {} vanished!'.format(self._elem))
|
raise IsNullError('Element {} vanished!'.format(self._elem))
|
||||||
|
|
||||||
def has_frame(self):
|
def has_frame(self) -> bool:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
return self._elem.webFrame() is not None
|
return self._elem.webFrame() is not None
|
||||||
|
|
||||||
def geometry(self):
|
def geometry(self) -> QRect:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
return self._elem.geometry()
|
return self._elem.geometry()
|
||||||
|
|
||||||
def classes(self):
|
def classes(self) -> typing.List[str]:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
return self._elem.classes()
|
return self._elem.classes()
|
||||||
|
|
||||||
def tag_name(self):
|
def tag_name(self) -> str:
|
||||||
"""Get the tag name for the current element."""
|
"""Get the tag name for the current element."""
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
return self._elem.tagName().lower()
|
return self._elem.tagName().lower()
|
||||||
|
|
||||||
def outer_xml(self):
|
def outer_xml(self) -> str:
|
||||||
"""Get the full HTML representation of this element."""
|
"""Get the full HTML representation of this element."""
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
return self._elem.toOuterXml()
|
return self._elem.toOuterXml()
|
||||||
|
|
||||||
def value(self):
|
def value(self) -> webelem.JsValueType:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
val = self._elem.evaluateJavaScript('this.value')
|
val = self._elem.evaluateJavaScript('this.value')
|
||||||
assert isinstance(val, (int, float, str, type(None))), val
|
assert isinstance(val, (int, float, str, type(None))), val
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value: webelem.JsValueType) -> None:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
if self._tab.is_deleted():
|
if self._tab.is_deleted():
|
||||||
raise webelem.OrphanedError("Tab containing element vanished")
|
raise webelem.OrphanedError("Tab containing element vanished")
|
||||||
if self.is_content_editable():
|
if self.is_content_editable():
|
||||||
log.webelem.debug("Filling {!r} via set_text.".format(self))
|
log.webelem.debug("Filling {!r} via set_text.".format(self))
|
||||||
|
assert isinstance(value, str)
|
||||||
self._elem.setPlainText(value)
|
self._elem.setPlainText(value)
|
||||||
else:
|
else:
|
||||||
log.webelem.debug("Filling {!r} via javascript.".format(self))
|
log.webelem.debug("Filling {!r} via javascript.".format(self))
|
||||||
value = javascript.to_js(value)
|
value = javascript.to_js(value)
|
||||||
self._elem.evaluateJavaScript("this.value={}".format(value))
|
self._elem.evaluateJavaScript("this.value={}".format(value))
|
||||||
|
|
||||||
def dispatch_event(self, event, bubbles=False,
|
def dispatch_event(self, event: str,
|
||||||
cancelable=False, composed=False):
|
bubbles: bool = False,
|
||||||
|
cancelable: bool = False,
|
||||||
|
composed: bool = False) -> None:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
log.webelem.debug("Firing event on {!r} via javascript.".format(self))
|
log.webelem.debug("Firing event on {!r} via javascript.".format(self))
|
||||||
self._elem.evaluateJavaScript(
|
self._elem.evaluateJavaScript(
|
||||||
@ -138,7 +148,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
javascript.to_js(cancelable),
|
javascript.to_js(cancelable),
|
||||||
javascript.to_js(composed)))
|
javascript.to_js(composed)))
|
||||||
|
|
||||||
def caret_position(self):
|
def caret_position(self) -> int:
|
||||||
"""Get the text caret position for the current element."""
|
"""Get the text caret position for the current element."""
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
pos = self._elem.evaluateJavaScript('this.selectionStart')
|
pos = self._elem.evaluateJavaScript('this.selectionStart')
|
||||||
@ -146,7 +156,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
return 0
|
return 0
|
||||||
return int(pos)
|
return int(pos)
|
||||||
|
|
||||||
def insert_text(self, text):
|
def insert_text(self, text: str) -> None:
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
if not self.is_editable(strict=True):
|
if not self.is_editable(strict=True):
|
||||||
raise webelem.Error("Element is not editable!")
|
raise webelem.Error("Element is not editable!")
|
||||||
@ -158,7 +168,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
this.dispatchEvent(event);
|
this.dispatchEvent(event);
|
||||||
""".format(javascript.to_js(text)))
|
""".format(javascript.to_js(text)))
|
||||||
|
|
||||||
def _parent(self):
|
def _parent(self) -> typing.Optional['WebKitElement']:
|
||||||
"""Get the parent element of this element."""
|
"""Get the parent element of this element."""
|
||||||
self._check_vanished()
|
self._check_vanished()
|
||||||
elem = self._elem.parent()
|
elem = self._elem.parent()
|
||||||
@ -166,7 +176,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
return None
|
return None
|
||||||
return WebKitElement(elem, tab=self._tab)
|
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."""
|
"""Javascript implementation for rect_on_view."""
|
||||||
# FIXME:qtwebengine maybe we can reuse this?
|
# FIXME:qtwebengine maybe we can reuse this?
|
||||||
rects = self._elem.evaluateJavaScript("this.getClientRects()")
|
rects = self._elem.evaluateJavaScript("this.getClientRects()")
|
||||||
@ -178,8 +188,8 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
text = utils.compact_text(self._elem.toOuterXml(), 500)
|
text = utils.compact_text(self._elem.toOuterXml(), 500)
|
||||||
log.webelem.vdebug("Client rectangles of element '{}': {}".format(
|
log.webelem.vdebug( # type: ignore
|
||||||
text, rects))
|
"Client rectangles of element '{}': {}".format(text, rects))
|
||||||
|
|
||||||
for i in range(int(rects.get("length", 0))):
|
for i in range(int(rects.get("length", 0))):
|
||||||
rect = rects[str(i)]
|
rect = rects[str(i)]
|
||||||
@ -204,7 +214,8 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
|
|
||||||
return None
|
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."""
|
"""Python implementation for rect_on_view."""
|
||||||
if elem_geometry is None:
|
if elem_geometry is None:
|
||||||
geometry = self._elem.geometry()
|
geometry = self._elem.geometry()
|
||||||
@ -218,7 +229,8 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
frame = frame.parentFrame()
|
frame = frame.parentFrame()
|
||||||
return rect
|
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.
|
"""Get the geometry of the element relative to the webview.
|
||||||
|
|
||||||
Uses the getClientRects() JavaScript method to obtain the collection of
|
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
|
# No suitable rects found via JS, try via the QWebElement API
|
||||||
return self._rect_on_view_python(elem_geometry)
|
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.
|
"""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
|
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
|
visible_in_frame = visible_on_screen
|
||||||
return all([visible_on_screen, visible_in_frame])
|
return all([visible_on_screen, visible_in_frame])
|
||||||
|
|
||||||
def remove_blank_target(self):
|
def remove_blank_target(self) -> None:
|
||||||
elem = self
|
elem = self # type: typing.Optional[WebKitElement]
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
if elem is None:
|
if elem is None:
|
||||||
break
|
break
|
||||||
@ -311,14 +323,14 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
break
|
break
|
||||||
elem = elem._parent() # pylint: disable=protected-access
|
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():
|
if self.is_text_input() and self.is_editable():
|
||||||
self._tab.caret.move_to_end_of_document()
|
self._tab.caret.move_to_end_of_document()
|
||||||
|
|
||||||
def _requires_user_interaction(self):
|
def _requires_user_interaction(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _click_editable(self, click_target):
|
def _click_editable(self, click_target: usertypes.ClickTarget) -> None:
|
||||||
ok = self._elem.evaluateJavaScript('this.focus(); true;')
|
ok = self._elem.evaluateJavaScript('this.focus(); true;')
|
||||||
if ok:
|
if ok:
|
||||||
self._move_text_cursor()
|
self._move_text_cursor()
|
||||||
@ -326,7 +338,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
log.webelem.debug("Failed to focus via JS, falling back to event")
|
log.webelem.debug("Failed to focus via JS, falling back to event")
|
||||||
self._click_fake_event(click_target)
|
self._click_fake_event(click_target)
|
||||||
|
|
||||||
def _click_js(self, click_target):
|
def _click_js(self, click_target: usertypes.ClickTarget) -> None:
|
||||||
settings = QWebSettings.globalSettings()
|
settings = QWebSettings.globalSettings()
|
||||||
attribute = QWebSettings.JavascriptCanOpenWindows
|
attribute = QWebSettings.JavascriptCanOpenWindows
|
||||||
could_open_windows = settings.testAttribute(attribute)
|
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")
|
log.webelem.debug("Failed to click via JS, falling back to event")
|
||||||
self._click_fake_event(click_target)
|
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
|
self._tab.data.override_target = click_target
|
||||||
super()._click_fake_event(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.
|
"""Get all children recursively of a given QWebFrame.
|
||||||
|
|
||||||
Loosely based on http://blog.nextgenetics.net/?e=64
|
Loosely based on http://blog.nextgenetics.net/?e=64
|
||||||
@ -356,7 +368,7 @@ def get_child_frames(startframe):
|
|||||||
results = []
|
results = []
|
||||||
frames = [startframe]
|
frames = [startframe]
|
||||||
while frames:
|
while frames:
|
||||||
new_frames = []
|
new_frames = [] # type: typing.List[QWebFrame]
|
||||||
for frame in frames:
|
for frame in frames:
|
||||||
results.append(frame)
|
results.append(frame)
|
||||||
new_frames += frame.childFrames()
|
new_frames += frame.childFrames()
|
||||||
|
Loading…
Reference in New Issue
Block a user