Add a separate tab.elements object
This commit is contained in:
parent
da73a7123c
commit
d3084dd690
@ -415,6 +415,46 @@ class AbstractHistory:
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractElements:
|
||||||
|
|
||||||
|
"""Finding and handling of elements on the page."""
|
||||||
|
|
||||||
|
def __init__(self, tab):
|
||||||
|
self._widget = None
|
||||||
|
self._tab = tab
|
||||||
|
|
||||||
|
def find_css(self, selector, callback, *, only_visible=False):
|
||||||
|
"""Find all HTML elements matching a given selector async.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
callback: The callback to be called when the search finished.
|
||||||
|
selector: The CSS selector to search for.
|
||||||
|
only_visible: Only show elements which are visible on screen.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def find_focused(self, callback):
|
||||||
|
"""Find the focused element on the page async.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
callback: The callback to be called when the search finished.
|
||||||
|
Called with a WebEngineElement or None.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def find_at_pos(self, pos, callback):
|
||||||
|
"""Find the element at the given position async.
|
||||||
|
|
||||||
|
This is also called "hit test" elsewhere.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pos: The QPoint to get the element for.
|
||||||
|
callback: The callback to be called when the search finished.
|
||||||
|
Called with a WebEngineElement or None.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class AbstractTab(QWidget):
|
class AbstractTab(QWidget):
|
||||||
|
|
||||||
"""A wrapper over the given widget to hide its API and expose another one.
|
"""A wrapper over the given widget to hide its API and expose another one.
|
||||||
@ -472,6 +512,7 @@ class AbstractTab(QWidget):
|
|||||||
# self.zoom = AbstractZoom(win_id=win_id)
|
# self.zoom = AbstractZoom(win_id=win_id)
|
||||||
# self.search = AbstractSearch(parent=self)
|
# self.search = AbstractSearch(parent=self)
|
||||||
# self.printing = AbstractPrinting()
|
# self.printing = AbstractPrinting()
|
||||||
|
# self.elements = AbstractElements(self)
|
||||||
|
|
||||||
self.data = TabData()
|
self.data = TabData()
|
||||||
self._layout = miscwidgets.WrapperLayout(self)
|
self._layout = miscwidgets.WrapperLayout(self)
|
||||||
@ -499,6 +540,7 @@ class AbstractTab(QWidget):
|
|||||||
self.zoom._widget = widget
|
self.zoom._widget = widget
|
||||||
self.search._widget = widget
|
self.search._widget = widget
|
||||||
self.printing._widget = widget
|
self.printing._widget = widget
|
||||||
|
self.elements._widget = widget
|
||||||
self._install_event_filter()
|
self._install_event_filter()
|
||||||
|
|
||||||
def _install_event_filter(self):
|
def _install_event_filter(self):
|
||||||
@ -676,37 +718,6 @@ class AbstractTab(QWidget):
|
|||||||
def set_html(self, html, base_url):
|
def set_html(self, html, base_url):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def find_all_elements(self, selector, callback, *, only_visible=False):
|
|
||||||
"""Find all HTML elements matching a given selector async.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
callback: The callback to be called when the search finished.
|
|
||||||
selector: The CSS selector to search for.
|
|
||||||
only_visible: Only show elements which are visible on screen.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def find_focus_element(self, callback):
|
|
||||||
"""Find the focused element on the page async.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
callback: The callback to be called when the search finished.
|
|
||||||
Called with a WebEngineElement or None.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def find_element_at_pos(self, pos, callback):
|
|
||||||
"""Find the element at the given position async.
|
|
||||||
|
|
||||||
This is also called "hit test" elsewhere.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
pos: The QPoint to get the element for.
|
|
||||||
callback: The callback to be called when the search finished.
|
|
||||||
Called with a WebEngineElement or None.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
try:
|
try:
|
||||||
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode),
|
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode),
|
||||||
|
@ -1474,7 +1474,7 @@ class CommandDispatcher:
|
|||||||
`general -> editor` config option.
|
`general -> editor` config option.
|
||||||
"""
|
"""
|
||||||
tab = self._current_widget()
|
tab = self._current_widget()
|
||||||
tab.find_focus_element(self._open_editor_cb)
|
tab.elements.find_focused(self._open_editor_cb)
|
||||||
|
|
||||||
def on_editing_finished(self, elem, text):
|
def on_editing_finished(self, elem, text):
|
||||||
"""Write the editor text into the form field and clean up tempfile.
|
"""Write the editor text into the form field and clean up tempfile.
|
||||||
|
@ -739,7 +739,7 @@ class HintManager(QObject):
|
|||||||
self._context.args = args
|
self._context.args = args
|
||||||
self._context.group = group
|
self._context.group = group
|
||||||
selector = webelem.SELECTORS[self._context.group]
|
selector = webelem.SELECTORS[self._context.group]
|
||||||
self._context.tab.find_all_elements(selector, self._start_cb,
|
self._context.tab.elements.find_css(selector, self._start_cb,
|
||||||
only_visible=True)
|
only_visible=True)
|
||||||
|
|
||||||
def current_mode(self):
|
def current_mode(self):
|
||||||
|
@ -94,7 +94,7 @@ class MouseEventFilter(QObject):
|
|||||||
|
|
||||||
self._ignore_wheel_event = True
|
self._ignore_wheel_event = True
|
||||||
self._mousepress_opentarget(e)
|
self._mousepress_opentarget(e)
|
||||||
self._tab.find_element_at_pos(e.pos(), self._mousepress_insertmode_cb)
|
self._tab.elements.find_at_pos(e.pos(), self._mousepress_insertmode_cb)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -175,7 +175,7 @@ class MouseEventFilter(QObject):
|
|||||||
usertypes.KeyMode.insert,
|
usertypes.KeyMode.insert,
|
||||||
'click-delayed')
|
'click-delayed')
|
||||||
|
|
||||||
self._tab.find_focus_element(mouserelease_insertmode_cb)
|
self._tab.elements.find_focused(mouserelease_insertmode_cb)
|
||||||
|
|
||||||
def _mousepress_backforward(self, e):
|
def _mousepress_backforward(self, e):
|
||||||
"""Handle back/forward mouse button presses.
|
"""Handle back/forward mouse button presses.
|
||||||
|
@ -141,4 +141,4 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
|
|||||||
|
|
||||||
selector = ', '.join([webelem.SELECTORS[webelem.Group.links],
|
selector = ', '.join([webelem.SELECTORS[webelem.Group.links],
|
||||||
webelem.SELECTORS[webelem.Group.prevnext]])
|
webelem.SELECTORS[webelem.Group.prevnext]])
|
||||||
browsertab.find_all_elements(selector, _prevnext_cb)
|
browsertab.elements.find_css(selector, _prevnext_cb)
|
||||||
|
@ -313,6 +313,58 @@ class WebEngineZoom(browsertab.AbstractZoom):
|
|||||||
return self._widget.zoomFactor()
|
return self._widget.zoomFactor()
|
||||||
|
|
||||||
|
|
||||||
|
class WebEngineElements(browsertab.AbstractElements):
|
||||||
|
|
||||||
|
"""QtWebEngine implemementations related to elements on the page."""
|
||||||
|
|
||||||
|
def _js_cb_multiple(self, callback, js_elems):
|
||||||
|
"""Handle found elements coming from JS and call the real callback.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
callback: The callback to call with the found elements.
|
||||||
|
js_elems: The elements serialized from javascript.
|
||||||
|
"""
|
||||||
|
elems = []
|
||||||
|
for js_elem in js_elems:
|
||||||
|
elem = webengineelem.WebEngineElement(js_elem, tab=self)
|
||||||
|
elems.append(elem)
|
||||||
|
callback(elems)
|
||||||
|
|
||||||
|
def _js_cb_single(self, callback, js_elem):
|
||||||
|
"""Handle a found focus elem coming from JS and call the real callback.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
callback: The callback to call with the found element.
|
||||||
|
Called with a WebEngineElement or None.
|
||||||
|
js_elem: The element serialized from javascript.
|
||||||
|
"""
|
||||||
|
log.webview.debug("Got element from JS: {!r}".format(js_elem))
|
||||||
|
if js_elem is None:
|
||||||
|
callback(None)
|
||||||
|
else:
|
||||||
|
elem = webengineelem.WebEngineElement(js_elem, tab=self)
|
||||||
|
callback(elem)
|
||||||
|
|
||||||
|
def find_css(self, selector, callback, *, only_visible=False):
|
||||||
|
js_code = javascript.assemble('webelem', 'find_all', selector,
|
||||||
|
only_visible)
|
||||||
|
js_cb = functools.partial(self._js_cb_multiple, callback)
|
||||||
|
self._tab.run_js_async(js_code, js_cb)
|
||||||
|
|
||||||
|
def find_focused(self, callback):
|
||||||
|
js_code = javascript.assemble('webelem', 'focus_element')
|
||||||
|
js_cb = functools.partial(self._js_cb_single, callback)
|
||||||
|
self._tab.run_js_async(js_code, js_cb)
|
||||||
|
|
||||||
|
def find_at_pos(self, pos, callback):
|
||||||
|
assert pos.x() >= 0
|
||||||
|
assert pos.y() >= 0
|
||||||
|
js_code = javascript.assemble('webelem', 'element_at_pos',
|
||||||
|
pos.x(), pos.y())
|
||||||
|
js_cb = functools.partial(self._js_cb_single, callback)
|
||||||
|
self._tab.run_js_async(js_code, js_cb)
|
||||||
|
|
||||||
|
|
||||||
class WebEngineTab(browsertab.AbstractTab):
|
class WebEngineTab(browsertab.AbstractTab):
|
||||||
|
|
||||||
"""A QtWebEngine tab in the browser."""
|
"""A QtWebEngine tab in the browser."""
|
||||||
@ -327,6 +379,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
self.zoom = WebEngineZoom(win_id=win_id, parent=self)
|
self.zoom = WebEngineZoom(win_id=win_id, parent=self)
|
||||||
self.search = WebEngineSearch(parent=self)
|
self.search = WebEngineSearch(parent=self)
|
||||||
self.printing = WebEnginePrinting()
|
self.printing = WebEnginePrinting()
|
||||||
|
self.elements = WebEngineElements(self)
|
||||||
self._set_widget(widget)
|
self._set_widget(widget)
|
||||||
self._connect_signals()
|
self._connect_signals()
|
||||||
self.backend = usertypes.Backend.QtWebEngine
|
self.backend = usertypes.Backend.QtWebEngine
|
||||||
@ -446,53 +499,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
def clear_ssl_errors(self):
|
def clear_ssl_errors(self):
|
||||||
log.stub()
|
log.stub()
|
||||||
|
|
||||||
def _js_element_cb_multiple(self, callback, js_elems):
|
|
||||||
"""Handle found elements coming from JS and call the real callback.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
callback: The callback to call with the found elements.
|
|
||||||
js_elems: The elements serialized from javascript.
|
|
||||||
"""
|
|
||||||
elems = []
|
|
||||||
for js_elem in js_elems:
|
|
||||||
elem = webengineelem.WebEngineElement(js_elem, tab=self)
|
|
||||||
elems.append(elem)
|
|
||||||
callback(elems)
|
|
||||||
|
|
||||||
def _js_element_cb_single(self, callback, js_elem):
|
|
||||||
"""Handle a found focus elem coming from JS and call the real callback.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
callback: The callback to call with the found element.
|
|
||||||
Called with a WebEngineElement or None.
|
|
||||||
js_elem: The element serialized from javascript.
|
|
||||||
"""
|
|
||||||
log.webview.debug("Got element from JS: {!r}".format(js_elem))
|
|
||||||
if js_elem is None:
|
|
||||||
callback(None)
|
|
||||||
else:
|
|
||||||
elem = webengineelem.WebEngineElement(js_elem, tab=self)
|
|
||||||
callback(elem)
|
|
||||||
|
|
||||||
def find_all_elements(self, selector, callback, *, only_visible=False):
|
|
||||||
js_code = javascript.assemble('webelem', 'find_all', selector,
|
|
||||||
only_visible)
|
|
||||||
js_cb = functools.partial(self._js_element_cb_multiple, callback)
|
|
||||||
self.run_js_async(js_code, js_cb)
|
|
||||||
|
|
||||||
def find_focus_element(self, callback):
|
|
||||||
js_code = javascript.assemble('webelem', 'focus_element')
|
|
||||||
js_cb = functools.partial(self._js_element_cb_single, callback)
|
|
||||||
self.run_js_async(js_code, js_cb)
|
|
||||||
|
|
||||||
def find_element_at_pos(self, pos, callback):
|
|
||||||
assert pos.x() >= 0
|
|
||||||
assert pos.y() >= 0
|
|
||||||
js_code = javascript.assemble('webelem', 'element_at_pos',
|
|
||||||
pos.x(), pos.y())
|
|
||||||
js_cb = functools.partial(self._js_element_cb_single, callback)
|
|
||||||
self.run_js_async(js_code, js_cb)
|
|
||||||
|
|
||||||
def _connect_signals(self):
|
def _connect_signals(self):
|
||||||
view = self._widget
|
view = self._widget
|
||||||
page = view.page()
|
page = view.page()
|
||||||
|
@ -492,6 +492,78 @@ class WebKitHistory(browsertab.AbstractHistory):
|
|||||||
self._tab.scroller.to_point, cur_data['scroll-pos']))
|
self._tab.scroller.to_point, cur_data['scroll-pos']))
|
||||||
|
|
||||||
|
|
||||||
|
class WebKitElements(browsertab.AbstractElements):
|
||||||
|
|
||||||
|
"""QtWebKit implemementations related to elements on the page."""
|
||||||
|
|
||||||
|
def find_css(self, selector, callback, *, only_visible=False):
|
||||||
|
mainframe = self._widget.page().mainFrame()
|
||||||
|
if mainframe is None:
|
||||||
|
raise browsertab.WebTabError("No frame focused!")
|
||||||
|
|
||||||
|
elems = []
|
||||||
|
frames = webkitelem.get_child_frames(mainframe)
|
||||||
|
for f in frames:
|
||||||
|
for elem in f.findAllElements(selector):
|
||||||
|
elems.append(webkitelem.WebKitElement(elem))
|
||||||
|
|
||||||
|
if only_visible:
|
||||||
|
elems = [e for e in elems if e.is_visible(mainframe)]
|
||||||
|
|
||||||
|
callback(elems)
|
||||||
|
|
||||||
|
def find_focused(self, callback):
|
||||||
|
frame = self._widget.page().currentFrame()
|
||||||
|
if frame is None:
|
||||||
|
callback(None)
|
||||||
|
return
|
||||||
|
|
||||||
|
elem = frame.findFirstElement('*:focus')
|
||||||
|
if elem.isNull():
|
||||||
|
callback(None)
|
||||||
|
else:
|
||||||
|
callback(webkitelem.WebKitElement(elem))
|
||||||
|
|
||||||
|
def find_at_pos(self, pos, callback):
|
||||||
|
assert pos.x() >= 0
|
||||||
|
assert pos.y() >= 0
|
||||||
|
frame = self._widget.page().frameAt(pos)
|
||||||
|
if frame is None:
|
||||||
|
# This happens when we click inside the webview, but not actually
|
||||||
|
# on the QWebPage - for example when clicking the scrollbar
|
||||||
|
# sometimes.
|
||||||
|
log.webview.debug("Hit test at {} but frame is None!".format(pos))
|
||||||
|
callback(None)
|
||||||
|
return
|
||||||
|
|
||||||
|
# You'd think we have to subtract frame.geometry().topLeft() from the
|
||||||
|
# position, but it seems QWebFrame::hitTestContent wants a position
|
||||||
|
# relative to the QWebView, not to the frame. This makes no sense to
|
||||||
|
# me, but it works this way.
|
||||||
|
hitresult = frame.hitTestContent(pos)
|
||||||
|
if hitresult.isNull():
|
||||||
|
# For some reason, the whole hit result can be null sometimes (e.g.
|
||||||
|
# on doodle menu links). If this is the case, we schedule a check
|
||||||
|
# later (in mouseReleaseEvent) which uses webkitelem.focus_elem.
|
||||||
|
log.webview.debug("Hit test result is null!")
|
||||||
|
callback(None)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
elem = webkitelem.WebKitElement(hitresult.element())
|
||||||
|
except webkitelem.IsNullError:
|
||||||
|
# For some reason, the hit result element can be a null element
|
||||||
|
# sometimes (e.g. when clicking the timetable fields on
|
||||||
|
# http://www.sbb.ch/ ). If this is the case, we schedule a check
|
||||||
|
# later (in mouseReleaseEvent) which uses webelem.focus_elem.
|
||||||
|
log.webview.debug("Hit test result element is null!")
|
||||||
|
callback(None)
|
||||||
|
return
|
||||||
|
|
||||||
|
callback(elem)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WebKitTab(browsertab.AbstractTab):
|
class WebKitTab(browsertab.AbstractTab):
|
||||||
|
|
||||||
"""A QtWebKit tab in the browser."""
|
"""A QtWebKit tab in the browser."""
|
||||||
@ -506,6 +578,7 @@ class WebKitTab(browsertab.AbstractTab):
|
|||||||
self.zoom = WebKitZoom(win_id=win_id, parent=self)
|
self.zoom = WebKitZoom(win_id=win_id, parent=self)
|
||||||
self.search = WebKitSearch(parent=self)
|
self.search = WebKitSearch(parent=self)
|
||||||
self.printing = WebKitPrinting()
|
self.printing = WebKitPrinting()
|
||||||
|
self.elements = WebKitElements(self)
|
||||||
self._set_widget(widget)
|
self._set_widget(widget)
|
||||||
self._connect_signals()
|
self._connect_signals()
|
||||||
self.zoom.set_default()
|
self.zoom.set_default()
|
||||||
@ -566,72 +639,6 @@ class WebKitTab(browsertab.AbstractTab):
|
|||||||
def set_html(self, html, base_url):
|
def set_html(self, html, base_url):
|
||||||
self._widget.setHtml(html, base_url)
|
self._widget.setHtml(html, base_url)
|
||||||
|
|
||||||
def find_all_elements(self, selector, callback, *, only_visible=False):
|
|
||||||
mainframe = self._widget.page().mainFrame()
|
|
||||||
if mainframe is None:
|
|
||||||
raise browsertab.WebTabError("No frame focused!")
|
|
||||||
|
|
||||||
elems = []
|
|
||||||
frames = webkitelem.get_child_frames(mainframe)
|
|
||||||
for f in frames:
|
|
||||||
for elem in f.findAllElements(selector):
|
|
||||||
elems.append(webkitelem.WebKitElement(elem))
|
|
||||||
|
|
||||||
if only_visible:
|
|
||||||
elems = [e for e in elems if e.is_visible(mainframe)]
|
|
||||||
|
|
||||||
callback(elems)
|
|
||||||
|
|
||||||
def find_focus_element(self, callback):
|
|
||||||
frame = self._widget.page().currentFrame()
|
|
||||||
if frame is None:
|
|
||||||
callback(None)
|
|
||||||
return
|
|
||||||
|
|
||||||
elem = frame.findFirstElement('*:focus')
|
|
||||||
if elem.isNull():
|
|
||||||
callback(None)
|
|
||||||
else:
|
|
||||||
callback(webkitelem.WebKitElement(elem))
|
|
||||||
|
|
||||||
def find_element_at_pos(self, pos, callback):
|
|
||||||
assert pos.x() >= 0
|
|
||||||
assert pos.y() >= 0
|
|
||||||
frame = self._widget.page().frameAt(pos)
|
|
||||||
if frame is None:
|
|
||||||
# This happens when we click inside the webview, but not actually
|
|
||||||
# on the QWebPage - for example when clicking the scrollbar
|
|
||||||
# sometimes.
|
|
||||||
log.webview.debug("Hit test at {} but frame is None!".format(pos))
|
|
||||||
callback(None)
|
|
||||||
return
|
|
||||||
|
|
||||||
# You'd think we have to subtract frame.geometry().topLeft() from the
|
|
||||||
# position, but it seems QWebFrame::hitTestContent wants a position
|
|
||||||
# relative to the QWebView, not to the frame. This makes no sense to
|
|
||||||
# me, but it works this way.
|
|
||||||
hitresult = frame.hitTestContent(pos)
|
|
||||||
if hitresult.isNull():
|
|
||||||
# For some reason, the whole hit result can be null sometimes (e.g.
|
|
||||||
# on doodle menu links). If this is the case, we schedule a check
|
|
||||||
# later (in mouseReleaseEvent) which uses webkitelem.focus_elem.
|
|
||||||
log.webview.debug("Hit test result is null!")
|
|
||||||
callback(None)
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
elem = webkitelem.WebKitElement(hitresult.element())
|
|
||||||
except webkitelem.IsNullError:
|
|
||||||
# For some reason, the hit result element can be a null element
|
|
||||||
# sometimes (e.g. when clicking the timetable fields on
|
|
||||||
# http://www.sbb.ch/ ). If this is the case, we schedule a check
|
|
||||||
# later (in mouseReleaseEvent) which uses webelem.focus_elem.
|
|
||||||
log.webview.debug("Hit test result element is null!")
|
|
||||||
callback(None)
|
|
||||||
return
|
|
||||||
|
|
||||||
callback(elem)
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _on_frame_load_finished(self):
|
def _on_frame_load_finished(self):
|
||||||
"""Make sure we emit an appropriate status when loading finished.
|
"""Make sure we emit an appropriate status when loading finished.
|
||||||
|
Loading…
Reference in New Issue
Block a user