Add initial WebEngineElement implementation
This allows :navigate prev/next to work correctly via the javascript bridge.
This commit is contained in:
parent
1c73751fd9
commit
b8e2d5f8f6
@ -109,11 +109,6 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
|
||||
background: True to open in a background tab.
|
||||
window: True to open in a new window, False for the current one.
|
||||
"""
|
||||
# FIXME:qtwebengine have a proper API for this
|
||||
if browsertab.backend == usertypes.Backend.QtWebEngine:
|
||||
raise Error(":navigate prev/next is not supported yet with "
|
||||
"QtWebEngine")
|
||||
|
||||
def _prevnext_cb(elems):
|
||||
elem = _find_prevnext(prev, elems)
|
||||
word = 'prev' if prev else 'forward'
|
||||
|
@ -79,7 +79,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self):
|
||||
raise NotImplementedError
|
||||
return self.text()
|
||||
|
||||
def __getitem__(self, key):
|
||||
raise NotImplementedError
|
||||
@ -90,9 +90,6 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
def __delitem__(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
def __contains__(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
def __iter__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
176
qutebrowser/browser/webengine/webengineelem.py
Normal file
176
qutebrowser/browser/webengine/webengineelem.py
Normal file
@ -0,0 +1,176 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# FIXME:qtwebengine remove this once the stubs are gone
|
||||
# pylint: disable=unused-variable
|
||||
|
||||
"""QtWebEngine specific part of the web element API."""
|
||||
|
||||
from PyQt5.QtCore import QRect
|
||||
|
||||
from qutebrowser.utils import log
|
||||
from qutebrowser.browser import webelem
|
||||
|
||||
|
||||
class WebEngineElement(webelem.AbstractWebElement):
|
||||
|
||||
"""A web element for QtWebEngine, using JS under the hood."""
|
||||
|
||||
def __init__(self, js_dict):
|
||||
self._id = js_dict['id']
|
||||
self._js_dict = js_dict
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, WebEngineElement):
|
||||
return NotImplemented
|
||||
return self._id == other._id # pylint: disable=protected-access
|
||||
|
||||
def __getitem__(self, key):
|
||||
attrs = self._js_dict['attributes']
|
||||
return attrs[key]
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
log.stub()
|
||||
|
||||
def __delitem__(self, key):
|
||||
log.stub()
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._js_dict['attributes'])
|
||||
|
||||
def __len__(self):
|
||||
return len(self._js_dict['attributes'])
|
||||
|
||||
def frame(self):
|
||||
log.stub()
|
||||
return None
|
||||
|
||||
def geometry(self):
|
||||
log.stub()
|
||||
return QRect()
|
||||
|
||||
def document_element(self):
|
||||
log.stub()
|
||||
return None
|
||||
|
||||
def create_inside(self, tagname):
|
||||
log.stub()
|
||||
return None
|
||||
|
||||
def find_first(self, selector):
|
||||
log.stub()
|
||||
return None
|
||||
|
||||
def style_property(self, name, *, strategy):
|
||||
log.stub()
|
||||
return ''
|
||||
|
||||
def classes(self):
|
||||
"""Get a list of classes assigned to this element."""
|
||||
log.stub()
|
||||
return []
|
||||
|
||||
def tag_name(self):
|
||||
"""Get the tag name of this element.
|
||||
|
||||
The returned name will always be lower-case.
|
||||
"""
|
||||
return self._js_dict['tag_name']
|
||||
|
||||
def outer_xml(self):
|
||||
"""Get the full HTML representation of this element."""
|
||||
return self._js_dict['outer_xml']
|
||||
|
||||
def text(self, *, use_js=False):
|
||||
"""Get the plain text content for this element.
|
||||
|
||||
Args:
|
||||
use_js: Whether to use javascript if the element isn't
|
||||
content-editable.
|
||||
"""
|
||||
if use_js:
|
||||
# FIXME:qtwebengine what to do about use_js with WebEngine?
|
||||
log.stub('with use_js=True')
|
||||
return self._js_dict.get('text', '')
|
||||
|
||||
def set_text(self, text, *, use_js=False):
|
||||
"""Set the given plain text.
|
||||
|
||||
Args:
|
||||
use_js: Whether to use javascript if the element isn't
|
||||
content-editable.
|
||||
"""
|
||||
# FIXME:qtwebengine what to do about use_js with WebEngine?
|
||||
log.stub()
|
||||
|
||||
def set_inner_xml(self, xml):
|
||||
"""Set the given inner XML."""
|
||||
# FIXME:qtwebengine get rid of this?
|
||||
log.stub()
|
||||
|
||||
def remove_from_document(self):
|
||||
"""Remove the node from the document."""
|
||||
# FIXME:qtwebengine get rid of this?
|
||||
log.stub()
|
||||
|
||||
def set_style_property(self, name, value):
|
||||
"""Set the element style."""
|
||||
# FIXME:qtwebengine get rid of this?
|
||||
log.stub()
|
||||
|
||||
def run_js_async(self, code, callback=None):
|
||||
"""Run the given JS snippet async on the element."""
|
||||
# FIXME:qtwebengine get rid of this?
|
||||
log.stub()
|
||||
|
||||
def parent(self):
|
||||
"""Get the parent element of this element."""
|
||||
# FIXME:qtwebengine get rid of this?
|
||||
log.stub()
|
||||
return None
|
||||
|
||||
def rect_on_view(self, *, elem_geometry=None, adjust_zoom=True,
|
||||
no_js=False):
|
||||
"""Get the geometry of the element relative to the webview.
|
||||
|
||||
Uses the getClientRects() JavaScript method to obtain the collection of
|
||||
rectangles containing the element and returns the first rectangle which
|
||||
is large enough (larger than 1px times 1px). If all rectangles returned
|
||||
by getClientRects() are too small, falls back to elem.rect_on_view().
|
||||
|
||||
Skipping of small rectangles is due to <a> elements containing other
|
||||
elements with "display:block" style, see
|
||||
https://github.com/The-Compiler/qutebrowser/issues/1298
|
||||
|
||||
Args:
|
||||
elem_geometry: The geometry of the element, or None.
|
||||
Calling QWebElement::geometry is rather expensive so
|
||||
we want to avoid doing it twice.
|
||||
adjust_zoom: Whether to adjust the element position based on the
|
||||
current zoom level.
|
||||
no_js: Fall back to the Python implementation
|
||||
"""
|
||||
log.stub()
|
||||
return QRect()
|
||||
|
||||
def is_visible(self, mainframe):
|
||||
"""Check if the given element is visible in the given frame."""
|
||||
# FIXME:qtwebengine get rid of this?
|
||||
log.stub()
|
||||
return True
|
@ -22,6 +22,8 @@
|
||||
|
||||
"""Wrapper over a QWebEngineView."""
|
||||
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint
|
||||
from PyQt5.QtGui import QKeyEvent, QIcon
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
@ -30,7 +32,7 @@ from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
|
||||
# pylint: enable=no-name-in-module,import-error,useless-suppression
|
||||
|
||||
from qutebrowser.browser import browsertab
|
||||
from qutebrowser.browser.webengine import webview
|
||||
from qutebrowser.browser.webengine import webview, webengineelem
|
||||
from qutebrowser.utils import usertypes, qtutils, log, javascript
|
||||
|
||||
|
||||
@ -411,9 +413,23 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
def clear_ssl_errors(self):
|
||||
log.stub()
|
||||
|
||||
def _find_all_elements_js_cb(self, callback, js_elems):
|
||||
"""Handle found elements coming from JS and call the real callback.
|
||||
|
||||
Args:
|
||||
callback: The callback originally passed to find_all_elements.
|
||||
js_elems: The elements serialized from javascript.
|
||||
"""
|
||||
elems = []
|
||||
for js_elem in js_elems:
|
||||
elem = webengineelem.WebEngineElement(js_elem)
|
||||
elems.append(elem)
|
||||
callback(elems)
|
||||
|
||||
def find_all_elements(self, selector, callback, *, only_visible=False):
|
||||
log.stub()
|
||||
callback([])
|
||||
js_code = javascript.assemble('webelem', 'find_all_elements', selector)
|
||||
js_cb = functools.partial(self._find_all_elements_js_cb, callback)
|
||||
self.run_js_async(js_code, js_cb)
|
||||
|
||||
def _connect_signals(self):
|
||||
view = self._widget
|
||||
|
@ -50,10 +50,6 @@ class WebKitElement(webelem.AbstractWebElement):
|
||||
return NotImplemented
|
||||
return self._elem == other._elem # pylint: disable=protected-access
|
||||
|
||||
def __str__(self):
|
||||
self._check_vanished()
|
||||
return self._elem.toPlainText()
|
||||
|
||||
def __getitem__(self, key):
|
||||
self._check_vanished()
|
||||
if key not in self:
|
||||
|
63
qutebrowser/javascript/webelem.js
Normal file
63
qutebrowser/javascript/webelem.js
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
*
|
||||
* This file is part of qutebrowser.
|
||||
*
|
||||
* qutebrowser is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* qutebrowser is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
document._qutebrowser_elements = [];
|
||||
|
||||
|
||||
function _qutebrowser_serialize_elem(elem, id) {
|
||||
var out = {};
|
||||
|
||||
var attributes = {};
|
||||
for (var i = 0; i < elem.attributes.length; ++i) {
|
||||
attr = elem.attributes[i];
|
||||
attributes[attr.name] = attr.value;
|
||||
}
|
||||
out["attributes"] = attributes;
|
||||
|
||||
out["text"] = elem.text;
|
||||
out["tag_name"] = elem.tagName;
|
||||
out["outer_xml"] = elem.outerHTML;
|
||||
out["id"] = id;
|
||||
|
||||
// console.log(JSON.stringify(out));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
function _qutebrowser_find_all_elements(selector) {
|
||||
var elems = document.querySelectorAll(selector);
|
||||
var out = [];
|
||||
var id = document._qutebrowser_elements.length;
|
||||
|
||||
for (var i = 0; i < elems.length; ++i) {
|
||||
var elem = elems[i];
|
||||
out.push(_qutebrowser_serialize_elem(elem, id));
|
||||
document._qutebrowser_elements[id] = elem;
|
||||
id++;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
function _qutebrowser_get_element(id) {
|
||||
return document._qutebrowser_elements[id];
|
||||
}
|
Loading…
Reference in New Issue
Block a user