Add a separate tab.elements object
This commit is contained in:
parent
da73a7123c
commit
d3084dd690
@ -415,6 +415,46 @@ class AbstractHistory:
|
||||
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):
|
||||
|
||||
"""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.search = AbstractSearch(parent=self)
|
||||
# self.printing = AbstractPrinting()
|
||||
# self.elements = AbstractElements(self)
|
||||
|
||||
self.data = TabData()
|
||||
self._layout = miscwidgets.WrapperLayout(self)
|
||||
@ -499,6 +540,7 @@ class AbstractTab(QWidget):
|
||||
self.zoom._widget = widget
|
||||
self.search._widget = widget
|
||||
self.printing._widget = widget
|
||||
self.elements._widget = widget
|
||||
self._install_event_filter()
|
||||
|
||||
def _install_event_filter(self):
|
||||
@ -676,37 +718,6 @@ class AbstractTab(QWidget):
|
||||
def set_html(self, html, base_url):
|
||||
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):
|
||||
try:
|
||||
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode),
|
||||
|
@ -1474,7 +1474,7 @@ class CommandDispatcher:
|
||||
`general -> editor` config option.
|
||||
"""
|
||||
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):
|
||||
"""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.group = 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)
|
||||
|
||||
def current_mode(self):
|
||||
|
@ -94,7 +94,7 @@ class MouseEventFilter(QObject):
|
||||
|
||||
self._ignore_wheel_event = True
|
||||
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
|
||||
|
||||
@ -175,7 +175,7 @@ class MouseEventFilter(QObject):
|
||||
usertypes.KeyMode.insert,
|
||||
'click-delayed')
|
||||
|
||||
self._tab.find_focus_element(mouserelease_insertmode_cb)
|
||||
self._tab.elements.find_focused(mouserelease_insertmode_cb)
|
||||
|
||||
def _mousepress_backforward(self, e):
|
||||
"""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],
|
||||
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()
|
||||
|
||||
|
||||
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):
|
||||
|
||||
"""A QtWebEngine tab in the browser."""
|
||||
@ -327,6 +379,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
self.zoom = WebEngineZoom(win_id=win_id, parent=self)
|
||||
self.search = WebEngineSearch(parent=self)
|
||||
self.printing = WebEnginePrinting()
|
||||
self.elements = WebEngineElements(self)
|
||||
self._set_widget(widget)
|
||||
self._connect_signals()
|
||||
self.backend = usertypes.Backend.QtWebEngine
|
||||
@ -446,53 +499,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
def clear_ssl_errors(self):
|
||||
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):
|
||||
view = self._widget
|
||||
page = view.page()
|
||||
|
@ -492,6 +492,78 @@ class WebKitHistory(browsertab.AbstractHistory):
|
||||
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):
|
||||
|
||||
"""A QtWebKit tab in the browser."""
|
||||
@ -506,6 +578,7 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
self.zoom = WebKitZoom(win_id=win_id, parent=self)
|
||||
self.search = WebKitSearch(parent=self)
|
||||
self.printing = WebKitPrinting()
|
||||
self.elements = WebKitElements(self)
|
||||
self._set_widget(widget)
|
||||
self._connect_signals()
|
||||
self.zoom.set_default()
|
||||
@ -566,72 +639,6 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
def set_html(self, 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()
|
||||
def _on_frame_load_finished(self):
|
||||
"""Make sure we emit an appropriate status when loading finished.
|
||||
|
Loading…
Reference in New Issue
Block a user