Permalink
Browse files

Add initial WebEngineElement implementation

This allows :navigate prev/next to work correctly via the javascript
bridge.
  • Loading branch information...
The-Compiler committed Aug 8, 2016
1 parent 1c73751 commit b8e2d5f8f6a3547fede59e1dbc8e65f5e3c7358f
@@ -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 @@ def __eq__(self, other):
raise NotImplementedError
def __str__(self):
- raise NotImplementedError
+ return self.text()
def __getitem__(self, key):
raise NotImplementedError
@@ -90,9 +90,6 @@ def __setitem__(self, key, val):
def __delitem__(self, key):
raise NotImplementedError
- def __contains__(self, key):
- raise NotImplementedError
-
def __iter__(self):
raise NotImplementedError
@@ -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 @@
# 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 @@ def set_html(self, html, base_url):
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 @@ def __eq__(self, other):
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:
@@ -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];
+}

0 comments on commit b8e2d5f

Please sign in to comment.