Add initial WebEngineElement implementation

This allows :navigate prev/next to work correctly via the javascript
bridge.
This commit is contained in:
Florian Bruhin 2016-08-08 15:01:41 +02:00
parent 1c73751fd9
commit b8e2d5f8f6
6 changed files with 259 additions and 16 deletions

View File

@ -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'

View File

@ -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

View 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

View File

@ -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

View File

@ -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:

View 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];
}