diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index 795e7a1d0..74c5f834c 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -30,7 +30,8 @@ from PyQt5.QtWebKitWidgets import QWebPage from qutebrowser.config import config from qutebrowser.network import networkmanager -from qutebrowser.utils import message, usertypes, log, http, jinja, qtutils +from qutebrowser.utils import (message, usertypes, log, http, jinja, qtutils, + utils) class BrowserPage(QWebPage): @@ -223,6 +224,9 @@ class BrowserPage(QWebPage): """ return ext in self._extension_handlers + # WORKAROUND for: + # http://www.riverbankcomputing.com/pipermail/pyqt/2014-August/034722.html + @utils.prevent_exceptions(False, PYQT_VERSION < 0x50302) def extension(self, ext, opt, out): """Override QWebPage::extension to provide error pages. @@ -235,28 +239,11 @@ class BrowserPage(QWebPage): Handler return value. """ try: - try: - handler = self._extension_handlers[ext] - except KeyError: - log.webview.warning("Extension {} not supported!".format(ext)) - return super().extension(ext, opt, out) - return handler(opt, out) - except: # pylint: disable=bare-except - if PYQT_VERSION >= 0x50302: - raise - else: - # WORKAROUND: - # - # Due to a bug in PyQt, exceptions inside extension() get - # swallowed: - # http://www.riverbankcomputing.com/pipermail/pyqt/2014-August/034722.html - # - # We used to re-raise the exception with a single-shot QTimer - # here, but that lead to a strange proble with a KeyError with - # some random jinja template stuff as content. For now, we only - # log it, so it doesn't pass 100% silently. - log.webview.exception("Error inside WebPage::extension") - return False + handler = self._extension_handlers[ext] + except KeyError: + log.webview.warning("Extension {} not supported!".format(ext)) + return super().extension(ext, opt, out) + return handler(opt, out) def javaScriptAlert(self, _frame, msg): """Override javaScriptAlert to use the statusbar.""" diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index c790e6410..a866a4b22 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -35,7 +35,7 @@ from PyQt5.QtGui import QKeySequence, QColor import pkg_resources import qutebrowser -from qutebrowser.utils import qtutils +from qutebrowser.utils import qtutils, log def elide(text, length): @@ -494,3 +494,59 @@ def disabled_excepthook(): # unchanged. Otherwise, we reset it. if sys.excepthook is sys.__excepthook__: sys.excepthook = old_excepthook + + +class prevent_exceptions: # pylint: disable=invalid-name + + """Decorator to ignore and log exceptions. + + This needs to be used for some places where PyQt segfaults on exceptions or + silently ignores them. + + We used to re-raise the exception with a single-shot QTimer in a similiar + case, but that lead to a strange proble with a KeyError with some random + jinja template stuff as content. For now, we only log it, so it doesn't + pass 100% silently. + + This could also be a function, but as a class (with a "wrong" name) it's + much cleaner to implement. + + Attributes: + retval: The value to return in case of an exception. + predicate: The condition which needs to be True to prevent exceptions + """ + + def __init__(self, retval, predicate=True): + """Save decorator arguments. + + Gets called on parse-time with the decorator arguments. + + Args: + See class attributes. + """ + self.retval = retval + self.predicate = predicate + + def __call__(self, func): + """Gets called when a function should be decorated. + + Args: + func: The function to be decorated. + + Return: + The decorated function. + """ + if not self.predicate: + return func + + retval = self.retval + + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except BaseException: + log.misc.exception("Error in {}".format(func.__qualname__)) + return retval + + return wrapper