diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index ab8775e6e..059185cbc 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -30,6 +30,7 @@ Changed - New way of clicking hints with which solves various small issues - When yanking a mailto: link via hints, the mailto: prefix is now stripped - Zoom level messages are now not stacked on top of each other anymore. +- qutebrowser now automatically uses QtWebEngine if QtWebKit is unavailable Fixed ----- diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 3c4c605a6..070112ccd 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -446,7 +446,7 @@ def _init_modules(args, crash_handler): else: os.environ.pop('QT_WAYLAND_DISABLE_WINDOWDECORATION', None) # Init backend-specific stuff - browsertab.init(args) + browsertab.init() def _init_late_modules(args): diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 4144928d2..febb6dc4f 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -28,7 +28,7 @@ from PyQt5.QtWidgets import QWidget, QApplication from qutebrowser.keyinput import modeman from qutebrowser.config import config from qutebrowser.utils import utils, objreg, usertypes, log, qtutils -from qutebrowser.misc import miscwidgets +from qutebrowser.misc import miscwidgets, objects from qutebrowser.browser import mouse, hints @@ -45,7 +45,7 @@ def create(win_id, parent=None): # Importing modules here so we don't depend on QtWebEngine without the # argument and to avoid circular imports. mode_manager = modeman.instance(win_id) - if objreg.get('args').backend == 'webengine': + if objects.backend == usertypes.Backend.QtWebEngine: from qutebrowser.browser.webengine import webenginetab tab_class = webenginetab.WebEngineTab else: @@ -54,9 +54,9 @@ def create(win_id, parent=None): return tab_class(win_id=win_id, mode_manager=mode_manager, parent=parent) -def init(args): +def init(): """Initialize backend-specific modules.""" - if args.backend == 'webengine': + if objects.backend == usertypes.Backend.QtWebEngine: from qutebrowser.browser.webengine import webenginetab webenginetab.init() else: diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 130a25c67..e4288f891 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -28,7 +28,7 @@ from qutebrowser.commands import cmdutils from qutebrowser.utils import (utils, objreg, standarddir, log, qtutils, usertypes) from qutebrowser.config import config -from qutebrowser.misc import lineparser +from qutebrowser.misc import lineparser, objects class Entry: @@ -293,7 +293,6 @@ def init(parent=None): parent=parent) objreg.register('web-history', history) - used_backend = usertypes.arg2backend[objreg.get('args').backend] - if used_backend == usertypes.Backend.QtWebKit: + if objects.backend == usertypes.Backend.QtWebKit: from qutebrowser.browser.webkit import webkithistory webkithistory.init(history) diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index e605cfd8a..a18d1ecf2 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -24,8 +24,8 @@ import binascii from PyQt5.QtWidgets import QWidget -from qutebrowser.utils import log, objreg -from qutebrowser.misc import miscwidgets +from qutebrowser.utils import log, objreg, usertypes +from qutebrowser.misc import miscwidgets, objects def create(parent=None): @@ -36,7 +36,7 @@ def create(parent=None): """ # Importing modules here so we don't depend on QtWebEngine without the # argument and to avoid circular imports. - if objreg.get('args').backend == 'webengine': + if objects.backend == usertypes.Backend.QtWebEngine: from qutebrowser.browser.webengine import webengineinspector return webengineinspector.WebEngineInspector(parent) else: diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 3c426c232..4d27d694a 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -28,7 +28,8 @@ import urllib.parse import qutebrowser from qutebrowser.utils import (version, utils, jinja, log, message, docutils, - objreg, usertypes) + objreg) +from qutebrowser.misc import objects pyeval_output = ":pyeval was never called" @@ -89,8 +90,7 @@ class add_handler: # pylint: disable=invalid-name return function def wrapper(self, *args, **kwargs): - used_backend = usertypes.arg2backend[objreg.get('args').backend] - if self._backend is not None and used_backend != self._backend: + if self._backend is not None and objects.backend != self._backend: return self.wrong_backend_handler(*args, **kwargs) else: return self._function(*args, **kwargs) diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index a7404fb3a..d46cc5c77 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -27,6 +27,7 @@ from qutebrowser.commands import cmdexc, argparser from qutebrowser.utils import (log, utils, message, docutils, objreg, usertypes, typing) from qutebrowser.utils import debug as debug_utils +from qutebrowser.misc import objects class ArgInfo: @@ -158,8 +159,7 @@ class Command: window=win_id) self.validate_mode(mode_manager.mode) - used_backend = usertypes.arg2backend[objreg.get('args').backend] - if self.backend is not None and used_backend != self.backend: + if self.backend is not None and objects.backend != self.backend: raise cmdexc.PrerequisitesError( "{}: Only available with {} " "backend.".format(self.name, self.backend.name)) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index aa9ac1ca3..a4be0dc53 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -43,6 +43,7 @@ from qutebrowser.config.parsers import ini from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.utils import (message, objreg, utils, standarddir, log, qtutils, error, usertypes) +from qutebrowser.misc import objects from qutebrowser.utils.usertypes import Completion @@ -882,10 +883,9 @@ class ConfigManager(QObject): # Will be handled later in .setv() pass else: - backend = usertypes.arg2backend[objreg.get('args').backend] if (allowed_backends is not None and - backend not in allowed_backends): - raise configexc.BackendError(backend) + objects.backend not in allowed_backends): + raise configexc.BackendError(objects.backend) else: interpolated = None diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 3942189eb..e4950d353 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -20,7 +20,8 @@ """Bridge from QWeb(Engine)Settings to our own settings.""" from qutebrowser.config import config -from qutebrowser.utils import log, utils, debug, objreg +from qutebrowser.utils import log, utils, debug, objreg, usertypes +from qutebrowser.misc import objects UNSET = object() @@ -261,7 +262,7 @@ def update_mappings(mappings, section, option): def init(args): """Initialize all QWeb(Engine)Settings.""" - if args.backend == 'webengine': + if objects.backend == usertypes.Backend.QtWebEngine: from qutebrowser.browser.webengine import webenginesettings webenginesettings.init(args) else: @@ -271,7 +272,7 @@ def init(args): def shutdown(): """Shut down QWeb(Engine)Settings.""" - if objreg.get('args').backend == 'webengine': + if objects.backend == usertypes.Backend.QtWebEngine: from qutebrowser.browser.webengine import webenginesettings webenginesettings.shutdown() else: diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 741fe87ec..542081719 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -28,6 +28,7 @@ from qutebrowser.keyinput import modeparsers, keyparser from qutebrowser.config import config from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.utils import usertypes, log, objreg, utils +from qutebrowser.misc import objects class KeyEvent: @@ -266,13 +267,12 @@ class ModeManager(QObject): except KeyError: raise cmdexc.CommandError("Mode {} does not exist!".format(mode)) - backend = usertypes.arg2backend[objreg.get('args').backend] if m in [usertypes.KeyMode.hint, usertypes.KeyMode.command, usertypes.KeyMode.yesno, usertypes.KeyMode.prompt]: raise cmdexc.CommandError( "Mode {} can't be entered manually!".format(mode)) elif (m == usertypes.KeyMode.caret and - backend == usertypes.Backend.QtWebEngine): + objects.backend == usertypes.Backend.QtWebEngine): raise cmdexc.CommandError("Caret mode is not supported with " "QtWebEngine yet.") diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index c4c4beaa5..c639c293f 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -29,6 +29,7 @@ from PyQt5.QtGui import QIcon, QPalette, QColor from qutebrowser.utils import qtutils, objreg, utils, usertypes, log from qutebrowser.config import config +from qutebrowser.misc import objects PixelMetrics = usertypes.enum('PixelMetrics', ['icon_padding'], @@ -124,7 +125,7 @@ class TabWidget(QTabWidget): fields['title'] = page_title fields['title_sep'] = ' - ' if page_title else '' fields['perc_raw'] = tab.progress() - fields['backend'] = objreg.get('args').backend + fields['backend'] = objects.backend.name if tab.load_status() == usertypes.LoadStatus.loading: fields['perc'] = '[{}%] '.format(tab.progress()) diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 66bc55ca0..13226f9fa 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -239,7 +239,27 @@ def check_pyqt_core(): sys.exit(1) -def check_qt_version(args): +def get_backend(args): + """Find out what backend to use based on available libraries. + + Note this function returns the backend as a string so we don't have to + import qutebrowser.utils.usertypes yet. + """ + try: + import PyQt5.QtWebKit + webkit_available = True + except ImportError: + webkit_available = False + + if args.backend is not None: + return args.backend + elif webkit_available: + return 'webkit' + else: + return 'webengine' + + +def check_qt_version(backend): """Check if the Qt version is recent enough.""" from PyQt5.QtCore import qVersion from qutebrowser.utils import qtutils @@ -247,8 +267,8 @@ def check_qt_version(args): text = ("Fatal error: Qt and PyQt >= 5.2.0 are required, but {} is " "installed.".format(qVersion())) _die(text) - elif args.backend == 'webengine' and qtutils.version_check('5.6.0', - operator.lt): + elif backend == 'webengine' and qtutils.version_check('5.6.0', + operator.lt): text = ("Fatal error: Qt and PyQt >= 5.6.0 are required for " "QtWebEngine support, but {} is installed.".format(qVersion())) _die(text) @@ -267,7 +287,7 @@ def check_ssl_support(): _die(text) -def check_libraries(args): +def check_libraries(backend): """Check if all needed Python libraries are installed.""" modules = { 'pkg_resources': @@ -293,10 +313,11 @@ def check_libraries(args): "or Install via pip.", pip="PyYAML"), } - if args.backend == 'webengine': + if backend == 'webengine': modules['PyQt5.QtWebEngineWidgets'] = _missing_str("QtWebEngine", webengine=True) else: + assert backend == 'webkit' modules['PyQt5.QtWebKit'] = _missing_str("PyQt5.QtWebKit") from qutebrowser.utils import log @@ -345,6 +366,17 @@ def check_optimize_flag(): "unexpected behavior may occur.") +def set_backend(backend): + """Set the objects.backend global to the given backend (as string).""" + from qutebrowser.misc import objects + from qutebrowser.utils import usertypes + backends = { + 'webkit': usertypes.Backend.QtWebKit, + 'webengine': usertypes.Backend.QtWebEngine, + } + objects.backend = backends[backend] + + def earlyinit(args): """Do all needed early initialization. @@ -367,8 +399,10 @@ def earlyinit(args): fix_harfbuzz(args) # Now we can be sure QtCore is available, so we can print dialogs on # errors, so people only using the GUI notice them as well. - check_qt_version(args) + backend = get_backend(args) + check_qt_version(backend) remove_inputhook() - check_libraries(args) + check_libraries(backend) check_ssl_support() check_optimize_flag() + set_backend(backend) diff --git a/qutebrowser/misc/objects.py b/qutebrowser/misc/objects.py new file mode 100644 index 000000000..9af210498 --- /dev/null +++ b/qutebrowser/misc/objects.py @@ -0,0 +1,26 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2017 Florian Bruhin (The Compiler) +# +# 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 . + +"""Various global objects.""" + +# NOTE: We need to be careful with imports here, as this is imported from +# earlyinit. + +# A usertypes.Backend member +backend = None diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index f0b8e4f06..8321fb04b 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -65,7 +65,7 @@ def get_argparser(): "qutebrowser instance running.") parser.add_argument('--backend', choices=['webkit', 'webengine'], help="Which backend to use (webengine backend is " - "EXPERIMENTAL!).", default='webkit') + "EXPERIMENTAL!).") parser.add_argument('--enable-webengine-inspector', action='store_true', help="Enable the web inspector for QtWebEngine. Note " "that this is a SECURITY RISK and you should not " diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 7ad89b3a2..34148f9af 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -255,10 +255,6 @@ LoadStatus = enum('LoadStatus', ['none', 'success', 'success_https', 'error', # Backend of a tab Backend = enum('Backend', ['QtWebKit', 'QtWebEngine']) -arg2backend = { - 'webkit': Backend.QtWebKit, - 'webengine': Backend.QtWebEngine, -} # JS world for QtWebEngine diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index f494943a7..b1a6966e8 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -155,11 +155,10 @@ def tab_registry(win_registry): @pytest.fixture -def fake_web_tab(stubs, tab_registry, mode_manager, qapp, fake_args): +def fake_web_tab(stubs, tab_registry, mode_manager, qapp): """Fixture providing the FakeWebTab *class*.""" if PYQT_VERSION < 0x050600: pytest.skip('Causes segfaults, see #1638') - fake_args.backend = 'webengine' return stubs.FakeWebTab diff --git a/tests/unit/browser/webkit/network/test_webkitqutescheme.py b/tests/unit/browser/webkit/network/test_webkitqutescheme.py index 224d06b06..88c4368db 100644 --- a/tests/unit/browser/webkit/network/test_webkitqutescheme.py +++ b/tests/unit/browser/webkit/network/test_webkitqutescheme.py @@ -22,6 +22,7 @@ import logging from PyQt5.QtCore import QUrl +from qutebrowser.utils import usertypes from qutebrowser.browser import pdfjs, qutescheme # pylint: disable=unused-import from qutebrowser.browser.webkit.network import webkitqutescheme @@ -42,8 +43,9 @@ class TestPDFJSHandler: get_pdfjs_res) @pytest.fixture(autouse=True) - def patch_args(self, fake_args): - fake_args.backend = 'webkit' + def patch_backend(self, monkeypatch): + monkeypatch.setattr(qutescheme.objects, 'backend', + usertypes.Backend.QtWebKit) def test_existing_resource(self): """Test with a resource that exists.""" diff --git a/tests/unit/browser/webkit/test_history.py b/tests/unit/browser/webkit/test_history.py index 94169e40e..6f5cf6492 100644 --- a/tests/unit/browser/webkit/test_history.py +++ b/tests/unit/browser/webkit/test_history.py @@ -27,7 +27,7 @@ from hypothesis import strategies from PyQt5.QtCore import QUrl from qutebrowser.browser import history -from qutebrowser.utils import objreg, urlutils +from qutebrowser.utils import objreg, urlutils, usertypes class FakeWebHistory: @@ -380,14 +380,14 @@ def test_history_interface(qtbot, webview, hist_interface): webview.load(url) -@pytest.mark.parametrize('backend', ['webengine', 'webkit']) -def test_init(backend, qapp, tmpdir, monkeypatch, fake_save_manager, - fake_args): +@pytest.mark.parametrize('backend', [usertypes.Backend.QtWebEngine, + usertypes.Backend.QtWebKit]) +def test_init(backend, qapp, tmpdir, monkeypatch, fake_save_manager): if backend == 'webkit': pytest.importorskip('PyQt5.QtWebKitWidgets') - fake_args.backend = backend monkeypatch.setattr(history.standarddir, 'data', lambda: str(tmpdir)) + monkeypatch.setattr(history.objects, 'backend', backend) history.init(qapp) hist = objreg.get('web-history') assert hist.parent() is qapp @@ -397,11 +397,11 @@ def test_init(backend, qapp, tmpdir, monkeypatch, fake_save_manager, except ImportError: QWebHistoryInterface = None - if backend == 'webkit': + if backend == usertypes.Backend.QtWebKit: default_interface = QWebHistoryInterface.defaultInterface() assert default_interface._history is hist else: - assert backend == 'webengine' + assert backend == usertypes.Backend.QtWebEngine if QWebHistoryInterface is None: default_interface = None else: diff --git a/tests/unit/commands/test_cmdutils.py b/tests/unit/commands/test_cmdutils.py index 51225e3b7..577a85540 100644 --- a/tests/unit/commands/test_cmdutils.py +++ b/tests/unit/commands/test_cmdutils.py @@ -446,19 +446,20 @@ class TestArgument: class TestRun: @pytest.fixture(autouse=True) - def patching(self, mode_manager, fake_args): - fake_args.backend = 'webkit' + def patch_backend(self, mode_manager, monkeypatch): + monkeypatch.setattr(command.objects, 'backend', + usertypes.Backend.QtWebKit) @pytest.mark.parametrize('backend, used, ok', [ - (usertypes.Backend.QtWebEngine, 'webengine', True), - (usertypes.Backend.QtWebEngine, 'webkit', False), - (usertypes.Backend.QtWebKit, 'webengine', False), - (usertypes.Backend.QtWebKit, 'webkit', True), - (None, 'webengine', True), - (None, 'webkit', True), + (usertypes.Backend.QtWebEngine, usertypes.Backend.QtWebEngine, True), + (usertypes.Backend.QtWebEngine, usertypes.Backend.QtWebKit, False), + (usertypes.Backend.QtWebKit, usertypes.Backend.QtWebEngine, False), + (usertypes.Backend.QtWebKit, usertypes.Backend.QtWebKit, True), + (None, usertypes.Backend.QtWebEngine, True), + (None, usertypes.Backend.QtWebKit, True), ]) - def test_backend(self, fake_args, backend, used, ok): - fake_args.backend = used + def test_backend(self, monkeypatch, backend, used, ok): + monkeypatch.setattr(command.objects, 'backend', used) cmd = _get_cmd(backend=backend) if ok: cmd.run(win_id=0) @@ -471,7 +472,7 @@ class TestRun: cmd = _get_cmd() cmd.run(win_id=0) - def test_instance_unavailable_with_backend(self, fake_args): + def test_instance_unavailable_with_backend(self, monkeypatch): """Test what happens when a backend doesn't have an objreg object. For example, QtWebEngine doesn't have 'hintmanager' registered. We make @@ -484,7 +485,8 @@ class TestRun: """Blah.""" pass - fake_args.backend = 'webkit' + monkeypatch.setattr(command.objects, 'backend', + usertypes.Backend.QtWebKit) cmd = cmdutils.cmd_dict['fun'] with pytest.raises(cmdexc.PrerequisitesError) as excinfo: cmd.run(win_id=0) diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py index b9a95c810..3e26e9f7a 100644 --- a/tests/unit/mainwindow/test_tabwidget.py +++ b/tests/unit/mainwindow/test_tabwidget.py @@ -21,11 +21,14 @@ import pytest -from qutebrowser.mainwindow import tabwidget -from qutebrowser.config import configtypes from PyQt5.QtGui import QIcon, QPixmap, QFont, QColor from PyQt5.QtCore import Qt +from qutebrowser.mainwindow import tabwidget +from qutebrowser.config import configtypes +from qutebrowser.misc import objects +from qutebrowser.utils import usertypes + class TestTabWidget: @@ -56,10 +59,12 @@ class TestTabWidget: } @pytest.fixture - def widget(self, qtbot, config_stub): + def widget(self, qtbot, monkeypatch, config_stub): config_stub.data = self.CONFIG w = tabwidget.TabWidget(0) qtbot.addWidget(w) + monkeypatch.setattr(tabwidget.objects, 'backend', + usertypes.Backend.QtWebKit) return w def test_small_icon_doesnt_crash(self, widget, qtbot, fake_web_tab):