summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLembrun <amadeusk7@free.fr>2021-03-19 13:56:17 +0100
committerLembrun <amadeusk7@free.fr>2021-03-19 13:56:17 +0100
commit6d3edf743e99d3daf5ea9e2280c680053f7af94d (patch)
treed61b66f60dd37d9b05d2b4153c0ee6b09a08a634
parentf0c13a7abef0f712dd135c9217df39d09f2803d8 (diff)
parent7207c88a56db4240bb2408dcb46a9f641f08408e (diff)
downloadqutebrowser-6d3edf743e99d3daf5ea9e2280c680053f7af94d.tar.gz
qutebrowser-6d3edf743e99d3daf5ea9e2280c680053f7af94d.zip
Merge branch 'master' into pathlib-/tests/conftest.py
-rw-r--r--doc/changelog.asciidoc38
-rw-r--r--doc/help/settings.asciidoc16
-rw-r--r--misc/requirements/requirements-tests-bleeding.txt37
-rw-r--r--misc/requirements/requirements-tests-git.txt34
-rw-r--r--qutebrowser/browser/history.py24
-rw-r--r--qutebrowser/browser/webengine/webenginesettings.py19
-rw-r--r--qutebrowser/commands/parser.py209
-rw-r--r--qutebrowser/commands/runners.py197
-rw-r--r--qutebrowser/completion/completer.py24
-rw-r--r--qutebrowser/completion/models/configmodel.py8
-rw-r--r--qutebrowser/config/config.py32
-rw-r--r--qutebrowser/config/configdata.yml12
-rw-r--r--qutebrowser/config/qtargs.py3
-rw-r--r--qutebrowser/keyinput/modeparsers.py4
-rw-r--r--qutebrowser/misc/backendproblem.py9
-rw-r--r--qutebrowser/misc/earlyinit.py21
-rw-r--r--qutebrowser/misc/elf.py12
-rw-r--r--qutebrowser/misc/sql.py8
-rw-r--r--qutebrowser/utils/standarddir.py13
-rw-r--r--qutebrowser/utils/utils.py17
-rw-r--r--qutebrowser/utils/version.py19
-rw-r--r--scripts/dev/misc_checks.py2
-rw-r--r--scripts/dev/run_pylint_on_tests.py1
-rw-r--r--tests/conftest.py6
-rw-r--r--tests/end2end/fixtures/webserver.py6
-rw-r--r--tests/unit/browser/test_history.py25
-rw-r--r--tests/unit/browser/webkit/test_webkitelem.py2
-rw-r--r--tests/unit/commands/test_parser.py (renamed from tests/unit/commands/test_runners.py)56
-rw-r--r--tests/unit/config/test_config.py44
-rw-r--r--tests/unit/config/test_configcache.py2
-rw-r--r--tests/unit/config/test_qtargs.py78
-rw-r--r--tests/unit/javascript/conftest.py15
-rw-r--r--tests/unit/javascript/stylesheet/test_stylesheet_js.py6
-rw-r--r--tests/unit/javascript/test_greasemonkey.py4
-rw-r--r--tests/unit/utils/test_standarddir.py16
-rw-r--r--tests/unit/utils/test_utils.py7
-rw-r--r--tests/unit/utils/test_version.py23
-rw-r--r--tox.ini10
38 files changed, 642 insertions, 417 deletions
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 2d399ad6d..9ada74f7e 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -15,10 +15,40 @@ breaking changes (such as renamed commands) can happen in minor releases.
// `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities.
+[[v2.2.0]]
+v2.2.0 (unreleased)
+-------------------
+
+Deprecated
+~~~~~~~~~~
+
+- Running qutebrowser with Qt 5.12.0 is now unsupported and logs a warning. It
+ should still work, however, a workaround for issues with the Nvidia graphic
+ driver was dropped. Newer Qt 5.12.x versions are still fully supported.
+
+Added
+~~~~~
+
+- New `input.media_keys` setting which can be used to disable Chromium's
+ handling of media keys.
+
+Changed
+~~~~~~~
+
+- The completion now also shows bindings starting with `set-cmd-text` in its
+ third column, such as `o` for `:open`.
+
[[v2.1.1]]
v2.1.1 (unreleased)
-------------------
+Added
+~~~~~
+
+- Site-specific quirk for krunker.io, which shows a "Socket Error" with
+ qutebrowser's default Accept-Language header. The workaround is equivalent to
+ doing `:set -u matchmaker.krunker.io content.headers.accept_language ""`.
+
Changed
~~~~~~~
@@ -30,6 +60,14 @@ Fixed
- The workaround for black on (almost) black formula images in dark mode now
also works with Qt 5.12 and 5.13.
+- When running in Flatpak, the QtWebEngine version is now detected properly.
+ Before, a wrong version was assumed, breaking dark mode and certain workarounds
+ (resulting in crashes on websites like LinkedIn or TradingView).
+- When running in Flatpak, communicating with an existing instance now works
+ properly. Before, a new instance was always opened.
+- When the metainfo in the completion database doesn't have the expected
+ structure, qutebrowser now tries to gracefully recover from the situation
+ instead of crashing.
[[v2.1.0]]
v2.1.0 (2021-03-12)
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 0ecd7d753..8b2964f4f 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -261,6 +261,7 @@
|<<input.insert_mode.leave_on_load,input.insert_mode.leave_on_load>>|Leave insert mode when starting a new page load.
|<<input.insert_mode.plugins,input.insert_mode.plugins>>|Switch to insert mode when clicking flash and other plugins.
|<<input.links_included_in_focus_chain,input.links_included_in_focus_chain>>|Include hyperlinks in the keyboard focus chain when tabbing.
+|<<input.media_keys,input.media_keys>>|Whether the underlying Chromium should handle media keys.
|<<input.mouse.back_forward_buttons,input.mouse.back_forward_buttons>>|Enable back and forward buttons on the mouse.
|<<input.mouse.rocker_gestures,input.mouse.rocker_gestures>>|Enable Opera-like mouse rocker gestures.
|<<input.partial_timeout,input.partial_timeout>>|Timeout (in milliseconds) for partially typed key bindings.
@@ -3392,6 +3393,21 @@ Type: <<types,Bool>>
Default: +pass:[true]+
+[[input.media_keys]]
+=== input.media_keys
+Whether the underlying Chromium should handle media keys.
+On Linux, disabling this also disables Chromium's MPRIS integration.
+
+This setting requires a restart.
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+Type: <<types,Bool>>
+
+Default: +pass:[true]+
+
[[input.mouse.back_forward_buttons]]
=== input.mouse.back_forward_buttons
Enable back and forward buttons on the mouse.
diff --git a/misc/requirements/requirements-tests-bleeding.txt b/misc/requirements/requirements-tests-bleeding.txt
new file mode 100644
index 000000000..cbefb99f3
--- /dev/null
+++ b/misc/requirements/requirements-tests-bleeding.txt
@@ -0,0 +1,37 @@
+# Problematic: needs bzr
+# bzr+lp:beautifulsoup
+beautifulsoup4
+git+https://github.com/cherrypy/cheroot.git
+git+https://github.com/nedbat/coveragepy.git
+git+https://github.com/pallets/flask.git
+git+https://github.com/pallets/werkzeug.git # transitive dep, but needed to work
+git+https://github.com/HypothesisWorks/hypothesis.git#subdirectory=hypothesis-python
+git+https://github.com/pytest-dev/pytest.git
+git+https://github.com/pytest-dev/pytest-bdd.git
+git+https://github.com/ionelmc/pytest-benchmark.git
+git+https://github.com/pytest-dev/pytest-instafail.git
+git+https://github.com/pytest-dev/pytest-mock.git
+git+https://github.com/pytest-dev/pytest-qt.git
+git+https://github.com/pytest-dev/pytest-rerunfailures.git
+
+git+https://github.com/ionelmc/python-hunter.git
+git+https://github.com/jendrikseipp/vulture.git
+git+https://github.com/pygments/pygments.git
+git+https://github.com/pytest-dev/pytest-repeat.git
+git+https://github.com/pytest-dev/pytest-cov.git
+git+https://github.com/The-Compiler/pytest-xvfb.git
+git+https://github.com/pytest-dev/pytest-xdist.git
+git+https://github.com/hjwp/pytest-icdiff.git
+git+https://github.com/john-kurkowski/tldextract
+# Problematic: needs rust (and some time to build)
+# git+https://github.com/ArniDagur/python-adblock.git
+adblock ; python_version!="3.10"
+
+## qutebrowser dependencies
+
+git+https://github.com/pallets/jinja.git
+git+https://github.com/yaml/pyyaml.git
+git+https://github.com/tartley/colorama.git
+
+# https://github.com/pyparsing/pyparsing/issues/271
+pyparsing!=3.0.0b2,!=3.0.0b1
diff --git a/misc/requirements/requirements-tests-git.txt b/misc/requirements/requirements-tests-git.txt
deleted file mode 100644
index 6fc4bb460..000000000
--- a/misc/requirements/requirements-tests-git.txt
+++ /dev/null
@@ -1,34 +0,0 @@
-bzr+lp:beautifulsoup
-git+https://github.com/cherrypy/cheroot.git
-hg+https://bitbucket.org/ned/coveragepy
-git+https://github.com/micheles/decorator.git
-git+https://github.com/pallets/flask.git
-git+https://github.com/miracle2k/python-glob2.git
-git+https://github.com/HypothesisWorks/hypothesis-python.git
-git+https://github.com/pallets/itsdangerous.git
-git+https://bitbucket.org/zzzeek/mako.git
-git+https://github.com/r1chardj0n3s/parse.git
-git+https://github.com/jenisys/parse_type.git
-hg+https://bitbucket.org/pytest-dev/py
-git+https://github.com/pytest-dev/pytest.git@features
-git+https://github.com/pytest-dev/pytest-bdd.git
-git+https://github.com/pytest-dev/pytest-cov.git
-git+https://github.com/pytest-dev/pytest-instafail.git
-git+https://github.com/pytest-dev/pytest-mock.git
-git+https://github.com/pytest-dev/pytest-qt.git
-git+https://github.com/pytest-dev/pytest-repeat.git
-git+https://github.com/pytest-dev/pytest-rerunfailures.git
-git+https://github.com/The-Compiler/pytest-xvfb.git
-hg+https://bitbucket.org/gutworth/six
-hg+https://bitbucket.org/jendrikseipp/vulture
-git+https://github.com/pallets/werkzeug.git
-
-
-## qutebrowser dependencies
-
-git+https://github.com/tartley/colorama.git
-git+https://github.com/pallets/jinja.git
-git+https://github.com/pallets/markupsafe.git
-git+https://github.com/pygments/pygments.git
-git+https://github.com/python-attrs/attrs.git
-git+https://github.com/yaml/pyyaml.git
diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py
index ef4650a35..773c6cc51 100644
--- a/qutebrowser/browser/history.py
+++ b/qutebrowser/browser/history.py
@@ -92,8 +92,11 @@ class CompletionMetaInfo(sql.SqlTable):
}
def __init__(self, parent=None):
- super().__init__("CompletionMetaInfo", ['key', 'value'],
- constraints={'key': 'PRIMARY KEY'})
+ self._fields = ['key', 'value']
+ self._constraints = {'key': 'PRIMARY KEY'}
+ super().__init__(
+ "CompletionMetaInfo", self._fields, constraints=self._constraints)
+
if sql.user_version_changed():
self._init_default_values()
@@ -101,6 +104,15 @@ class CompletionMetaInfo(sql.SqlTable):
if key not in self.KEYS:
raise KeyError(key)
+ def try_recover(self):
+ """Try recovering the table structure.
+
+ This should be called if getting a value via __getattr__ failed. In theory, this
+ should never happen, in practice, it does.
+ """
+ self._create_table(self._fields, constraints=self._constraints, force=True)
+ self._init_default_values()
+
def _init_default_values(self):
for key, default in self.KEYS.items():
if key not in self:
@@ -164,7 +176,13 @@ class WebHistory(sql.SqlTable):
self.completion = CompletionHistory(parent=self)
self.metainfo = CompletionMetaInfo(parent=self)
- rebuild_completion = self.metainfo['force_rebuild']
+ try:
+ rebuild_completion = self.metainfo['force_rebuild']
+ except sql.BugError: # pragma: no cover
+ log.sql.warning("Failed to access meta info, trying to recover...",
+ exc_info=True)
+ self.metainfo.try_recover()
+ rebuild_completion = self.metainfo['force_rebuild']
if sql.user_version_changed():
# If the DB user version changed, run a full cleanup and rebuild the
diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py
index 830c818fc..23d8812c3 100644
--- a/qutebrowser/browser/webengine/webenginesettings.py
+++ b/qutebrowser/browser/webengine/webenginesettings.py
@@ -38,7 +38,7 @@ from qutebrowser.browser.webengine import (spell, webenginequtescheme, cookies,
from qutebrowser.config import config, websettings
from qutebrowser.config.websettings import AttributeInfo as Attr
from qutebrowser.utils import (standarddir, qtutils, message, log,
- urlmatch, usertypes, objreg)
+ urlmatch, usertypes, objreg, version)
if TYPE_CHECKING:
from qutebrowser.browser.webengine import interceptor
@@ -374,7 +374,17 @@ def _init_default_profile():
default_profile = QWebEngineProfile.defaultProfile()
+ assert parsed_user_agent is None # avoid earlier profile initialization
+ non_ua_version = version.qtwebengine_versions(avoid_init=True)
+
init_user_agent()
+ ua_version = version.qtwebengine_versions()
+ if ua_version.webengine != non_ua_version.webengine:
+ log.init.warning(
+ "QtWebEngine version mismatch - unexpected behavior might occur, "
+ "please open a bug about this.\n"
+ f" Early version: {non_ua_version}\n"
+ f" Real version: {ua_version}")
default_profile.setCachePath(
os.path.join(standarddir.cache(), 'webengine'))
@@ -448,6 +458,13 @@ def _init_site_specific_quirks():
pattern=urlmatch.UrlPattern(pattern),
hide_userconfig=True)
+ config.instance.set_obj(
+ 'content.headers.accept_language',
+ '',
+ pattern=urlmatch.UrlPattern('https://matchmaker.krunker.io/*'),
+ hide_userconfig=True,
+ )
+
def _init_devtools_settings():
"""Make sure the devtools always get images/JS permissions."""
diff --git a/qutebrowser/commands/parser.py b/qutebrowser/commands/parser.py
new file mode 100644
index 000000000..06a20cdf6
--- /dev/null
+++ b/qutebrowser/commands/parser.py
@@ -0,0 +1,209 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2021 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
+#
+# 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 <https://www.gnu.org/licenses/>.
+
+"""Module for parsing commands entered into the browser."""
+
+import dataclasses
+from typing import List, Iterator
+
+from qutebrowser.commands import cmdexc, command
+from qutebrowser.misc import split, objects
+from qutebrowser.config import config
+
+
+@dataclasses.dataclass
+class ParseResult:
+
+ """The result of parsing a commandline."""
+
+ cmd: command.Command
+ args: List[str]
+ cmdline: List[str]
+
+
+class CommandParser:
+
+ """Parse qutebrowser commandline commands.
+
+ Attributes:
+ _partial_match: Whether to allow partial command matches.
+ """
+
+ def __init__(self, partial_match: bool = False) -> None:
+ self._partial_match = partial_match
+
+ def _get_alias(self, text: str, *, default: str) -> str:
+ """Get an alias from the config.
+
+ Args:
+ text: The text to parse.
+ aliases: A map of aliases to commands.
+ default : Default value to return when alias was not found.
+
+ Return:
+ The new command string if an alias was found. Default value
+ otherwise.
+ """
+ parts = text.strip().split(maxsplit=1)
+ aliases = config.cache['aliases']
+ if parts[0] not in aliases:
+ return default
+ alias = aliases[parts[0]]
+
+ try:
+ new_cmd = '{} {}'.format(alias, parts[1])
+ except IndexError:
+ new_cmd = alias
+ if text.endswith(' '):
+ new_cmd += ' '
+ return new_cmd
+
+ def _parse_all_gen(
+ self,
+ text: str,
+ aliases: bool = True,
+ **kwargs: bool,
+ ) -> Iterator[ParseResult]:
+ """Split a command on ;; and parse all parts.
+
+ If the first command in the commandline is a non-split one, it only
+ returns that.
+
+ Args:
+ text: Text to parse.
+ aliases: Whether to handle aliases.
+ **kwargs: Passed to parse().
+
+ Yields:
+ ParseResult tuples.
+ """
+ text = text.strip().lstrip(':').strip()
+ if not text:
+ raise cmdexc.NoSuchCommandError("No command given")
+
+ if aliases:
+ text = self._get_alias(text, default=text)
+
+ if ';;' in text:
+ # Get the first command and check if it doesn't want to have ;;
+ # split.
+ first = text.split(';;')[0]
+ result = self.parse(first, **kwargs)
+ if result.cmd.no_cmd_split:
+ sub_texts = [text]
+ else:
+ sub_texts = [e.strip() for e in text.split(';;')]
+ else:
+ sub_texts = [text]
+ for sub in sub_texts:
+ yield self.parse(sub, **kwargs)
+
+ def parse_all(self, text: str, **kwargs: bool) -> List[ParseResult]:
+ """Wrapper over _parse_all_gen."""
+ return list(self._parse_all_gen(text, **kwargs))
+
+ def parse(self, text: str, *, keep: bool = False) -> ParseResult:
+ """Split the commandline text into command and arguments.
+
+ Args:
+ text: Text to parse.
+ keep: Whether to keep special chars and whitespace.
+ """
+ cmdstr, sep, argstr = text.partition(' ')
+
+ if not cmdstr:
+ raise cmdexc.NoSuchCommandError("No command given")
+
+ if self._partial_match:
+ cmdstr = self._completion_match(cmdstr)
+
+ try:
+ cmd = objects.commands[cmdstr]
+ except KeyError:
+ raise cmdexc.NoSuchCommandError(f'{cmdstr}: no such command')
+
+ args = self._split_args(cmd, argstr, keep)
+ if keep and args:
+ cmdline = [cmdstr, sep + args[0]] + args[1:]
+ elif keep:
+ cmdline = [cmdstr, sep]
+ else:
+ cmdline = [cmdstr] + args[:]
+
+ return ParseResult(cmd=cmd, args=args, cmdline=cmdline)
+
+ def _completion_match(self, cmdstr: str) -> str:
+ """Replace cmdstr with a matching completion if there's only one match.
+
+ Args:
+ cmdstr: The string representing the entered command so far.
+
+ Return:
+ cmdstr modified to the matching completion or unmodified
+ """
+ matches = [cmd for cmd in sorted(objects.commands, key=len)
+ if cmdstr in cmd]
+ if len(matches) == 1:
+ cmdstr = matches[0]
+ elif len(matches) > 1 and config.val.completion.use_best_match:
+ cmdstr = matches[0]
+ return cmdstr
+
+ def _split_args(self, cmd: command.Command, argstr: str, keep: bool) -> List[str]:
+ """Split the arguments from an arg string.
+
+ Args:
+ cmd: The command we're currently handling.
+ argstr: An argument string.
+ keep: Whether to keep special chars and whitespace
+
+ Return:
+ A list containing the split strings.
+ """
+ if not argstr:
+ return []
+ elif cmd.maxsplit is None:
+ return split.split(argstr, keep=keep)
+ else:
+ # If split=False, we still want to split the flags, but not
+ # everything after that.
+ # We first split the arg string and check the index of the first
+ # non-flag args, then we re-split again properly.
+ # example:
+ #
+ # input: "--foo -v bar baz"
+ # first split: ['--foo', '-v', 'bar', 'baz']
+ # 0 1 2 3
+ # second split: ['--foo', '-v', 'bar baz']
+ # (maxsplit=2)
+ split_args = split.simple_split(argstr, keep=keep)
+ flag_arg_count = 0
+ for i, arg in enumerate(split_args):
+ arg = arg.strip()
+ if arg.startswith('-'):
+ if arg in cmd.flags_with_args:
+ flag_arg_count += 1
+ else:
+ maxsplit = i + cmd.maxsplit + flag_arg_count
+ return split.simple_split(argstr, keep=keep,
+ maxsplit=maxsplit)
+
+ # If there are only flags, we got it right on the first try
+ # already.
+ return split_args
diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py
index 4d53295dd..5fb054455 100644
--- a/qutebrowser/commands/runners.py
+++ b/qutebrowser/commands/runners.py
@@ -22,17 +22,13 @@
import traceback
import re
import contextlib
-import dataclasses
-from typing import (TYPE_CHECKING, Callable, Dict, Iterator, Mapping, MutableMapping,
- List, Optional)
+from typing import TYPE_CHECKING, Callable, Dict, Iterator, Mapping, MutableMapping
from PyQt5.QtCore import pyqtSlot, QUrl, QObject
from qutebrowser.api import cmdutils
-from qutebrowser.config import config
-from qutebrowser.commands import cmdexc, command
+from qutebrowser.commands import cmdexc, parser
from qutebrowser.utils import message, objreg, qtutils, usertypes, utils
-from qutebrowser.misc import split, objects
from qutebrowser.keyinput import macros, modeman
if TYPE_CHECKING:
@@ -43,16 +39,6 @@ _ReplacementFunction = Callable[['tabbedbrowser.TabbedBrowser'], str]
last_command = {}
-@dataclasses.dataclass
-class ParseResult:
-
- """The result of parsing a commandline."""
-
- cmd: Optional[command.Command]
- args: Optional[List[str]]
- cmdline: List[str]
-
-
def _url(tabbed_browser):
"""Convenience method to get the current url."""
try:
@@ -130,181 +116,6 @@ def replace_variables(win_id, arglist):
return args
-class CommandParser:
-
- """Parse qutebrowser commandline commands.
-
- Attributes:
- _partial_match: Whether to allow partial command matches.
- """
-
- def __init__(self, partial_match=False):
- self._partial_match = partial_match
-
- def _get_alias(self, text, default=None):
- """Get an alias from the config.
-
- Args:
- text: The text to parse.
- default : Default value to return when alias was not found.
-
- Return:
- The new command string if an alias was found. Default value
- otherwise.
- """
- parts = text.strip().split(maxsplit=1)
- aliases = config.cache['aliases']
- if parts[0] not in aliases:
- return default
- alias = aliases[parts[0]]
-
- try:
- new_cmd = '{} {}'.format(alias, parts[1])
- except IndexError:
- new_cmd = alias
- if text.endswith(' '):
- new_cmd += ' '
- return new_cmd
-
- def _parse_all_gen(self, text, *args, aliases=True, **kwargs):
- """Split a command on ;; and parse all parts.
-
- If the first command in the commandline is a non-split one, it only
- returns that.
-
- Args:
- text: Text to parse.
- aliases: Whether to handle aliases.
- *args/**kwargs: Passed to parse().
-
- Yields:
- ParseResult tuples.
- """
- text = text.strip().lstrip(':').strip()
- if not text:
- raise cmdexc.NoSuchCommandError("No command given")
-
- if aliases:
- text = self._get_alias(text, text)
-
- if ';;' in text:
- # Get the first command and check if it doesn't want to have ;;
- # split.
- first = text.split(';;')[0]
- result = self.parse(first, *args, **kwargs)
- if result.cmd.no_cmd_split:
- sub_texts = [text]
- else:
- sub_texts = [e.strip() for e in text.split(';;')]
- else:
- sub_texts = [text]
- for sub in sub_texts:
- yield self.parse(sub, *args, **kwargs)
-
- def parse_all(self, *args, **kwargs):
- """Wrapper over _parse_all_gen."""
- return list(self._parse_all_gen(*args, **kwargs))
-
- def parse(self, text, *, fallback=False, keep=False):
- """Split the commandline text into command and arguments.
-
- Args:
- text: Text to parse.
- fallback: Whether to do a fallback splitting when the command was
- unknown.
- keep: Whether to keep special chars and whitespace
-
- Return:
- A ParseResult tuple.
- """
- cmdstr, sep, argstr = text.partition(' ')
-
- if not cmdstr and not fallback:
- raise cmdexc.NoSuchCommandError("No command given")
-
- if self._partial_match:
- cmdstr = self._completion_match(cmdstr)
-
- try:
- cmd = objects.commands[cmdstr]
- except KeyError:
- if not fallback:
- raise cmdexc.NoSuchCommandError(
- '{}: no such command'.format(cmdstr))
- cmdline = split.split(text, keep=keep)
- return ParseResult(cmd=None, args=None, cmdline=cmdline)
-
- args = self._split_args(cmd, argstr, keep)
- if keep and args:
- cmdline = [cmdstr, sep + args[0]] + args[1:]
- elif keep:
- cmdline = [cmdstr, sep]
- else:
- cmdline = [cmdstr] + args[:]
-
- return ParseResult(cmd=cmd, args=args, cmdline=cmdline)
-
- def _completion_match(self, cmdstr):
- """Replace cmdstr with a matching completion if there's only one match.
-
- Args:
- cmdstr: The string representing the entered command so far
-
- Return:
- cmdstr modified to the matching completion or unmodified
- """
- matches = [cmd for cmd in sorted(objects.commands, key=len)
- if cmdstr in cmd]
- if len(matches) == 1:
- cmdstr = matches[0]
- elif len(matches) > 1 and config.val.completion.use_best_match:
- cmdstr = matches[0]
- return cmdstr
-
- def _split_args(self, cmd, argstr, keep):
- """Split the arguments from an arg string.
-
- Args:
- cmd: The command we're currently handling.
- argstr: An argument string.
- keep: Whether to keep special chars and whitespace
-
- Return:
- A list containing the split strings.
- """
- if not argstr:
- return []
- elif cmd.maxsplit is None:
- return split.split(argstr, keep=keep)
- else:
- # If split=False, we still want to split the flags, but not
- # everything after that.
- # We first split the arg string and check the index of the first
- # non-flag args, then we re-split again properly.
- # example:
- #
- # input: "--foo -v bar baz"
- # first split: ['--foo', '-v', 'bar', 'baz']
- # 0 1 2 3
- # second split: ['--foo', '-v', 'bar baz']
- # (maxsplit=2)
- split_args = split.simple_split(argstr, keep=keep)
- flag_arg_count = 0
- for i, arg in enumerate(split_args):
- arg = arg.strip()
- if arg.startswith('-'):
- if arg in cmd.flags_with_args:
- flag_arg_count += 1
- else:
- maxsplit = i + cmd.maxsplit + flag_arg_count
- return split.simple_split(argstr, keep=keep,
- maxsplit=maxsplit)
-
- # If there are only flags, we got it right on the first try
- # already.
- return split_args
-
-
class AbstractCommandRunner(QObject):
"""Abstract base class for CommandRunner."""
@@ -329,7 +140,7 @@ class CommandRunner(AbstractCommandRunner):
def __init__(self, win_id, partial_match=False, parent=None):
super().__init__(parent)
- self._parser = CommandParser(partial_match=partial_match)
+ self._parser = parser.CommandParser(partial_match=partial_match)
self._win_id = win_id
@contextlib.contextmanager
@@ -362,7 +173,7 @@ class CommandRunner(AbstractCommandRunner):
parsed = self._parser.parse_all(text)
if parsed is None:
- return
+ return # type: ignore[unreachable]
for result in parsed:
with self._handle_error(safely):
diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py
index 52d4dc5f2..778333854 100644
--- a/qutebrowser/completion/completer.py
+++ b/qutebrowser/completion/completer.py
@@ -25,8 +25,8 @@ from typing import TYPE_CHECKING
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
from qutebrowser.config import config
-from qutebrowser.commands import runners
-from qutebrowser.misc import objects
+from qutebrowser.commands import parser, cmdexc
+from qutebrowser.misc import objects, split
from qutebrowser.utils import log, utils, debug, objreg
from qutebrowser.completion.models import miscmodels
if TYPE_CHECKING:
@@ -139,13 +139,18 @@ class Completer(QObject):
if not text or not text.strip():
# Only ":", empty part under the cursor with nothing before/after
return [], '', []
- parser = runners.CommandParser()
- result = parser.parse(text, fallback=True, keep=True)
- parts = [x for x in result.cmdline if x]
+
+ try:
+ parse_result = parser.CommandParser().parse(text, keep=True)
+ except cmdexc.NoSuchCommandError:
+ cmdline = split.split(text, keep=True)
+ else:
+ cmdline = parse_result.cmdline
+
+ parts = [x for x in cmdline if x]
pos = self._cmd.cursorPosition() - len(self._cmd.prefix())
pos = min(pos, len(text)) # Qt treats 2-byte UTF-16 chars as 2 chars
- log.completion.debug('partitioning {} around position {}'.format(parts,
- pos))
+ log.completion.debug(f'partitioning {parts} around position {pos}')
for i, part in enumerate(parts):
pos -= len(part)
if pos <= 0:
@@ -156,11 +161,10 @@ class Completer(QObject):
center = parts[i].strip()
# strip trailing whitespace included as a separate token
postfix = [x.strip() for x in parts[i+1:] if not x.isspace()]
- log.completion.debug(
- "partitioned: {} '{}' {}".format(prefix, center, postfix))
+ log.completion.debug(f"partitioned: {prefix} '{center}' {postfix}")
return prefix, center, postfix
- raise utils.Unreachable("Not all parts consumed: {}".format(parts))
+ raise utils.Unreachable(f"Not all parts consumed: {parts}")
@pyqtSlot(str)
def on_selection_changed(self, text):
diff --git a/qutebrowser/completion/models/configmodel.py b/qutebrowser/completion/models/configmodel.py
index a942b868a..736d09644 100644
--- a/qutebrowser/completion/models/configmodel.py
+++ b/qutebrowser/completion/models/configmodel.py
@@ -21,7 +21,7 @@
from qutebrowser.config import configdata, configexc
from qutebrowser.completion.models import completionmodel, listcategory, util
-from qutebrowser.commands import runners, cmdexc
+from qutebrowser.commands import parser, cmdexc
from qutebrowser.keyinput import keyutils
@@ -117,9 +117,8 @@ def _bind_current_default(key, info):
cmd_text = info.keyconf.get_command(seq, 'normal')
if cmd_text:
- parser = runners.CommandParser()
try:
- cmd = parser.parse(cmd_text).cmd
+ cmd = parser.CommandParser().parse(cmd_text).cmd
except cmdexc.NoSuchCommandError:
data.append((cmd_text, '(Current) Invalid command!', key))
else:
@@ -127,8 +126,7 @@ def _bind_current_default(key, info):
cmd_text = info.keyconf.get_command(seq, 'normal', default=True)
if cmd_text:
- parser = runners.CommandParser()
- cmd = parser.parse(cmd_text).cmd
+ cmd = parser.CommandParser().parse(cmd_text).cmd
data.append((cmd_text, '(Default) {}'.format(cmd.desc), key))
return data
diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py
index c644725b5..07d16ea92 100644
--- a/qutebrowser/config/config.py
+++ b/qutebrowser/config/config.py
@@ -27,6 +27,7 @@ from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Mapping,
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
+from qutebrowser.commands import cmdexc, parser
from qutebrowser.config import configdata, configexc, configutils
from qutebrowser.utils import utils, log, urlmatch
from qutebrowser.misc import objects
@@ -162,13 +163,38 @@ class KeyConfig:
bindings[key] = binding
return bindings
+ def _implied_cmd(self, cmdline: str) -> Optional[str]:
+ """Return cmdline, or the implied cmd if cmdline is a set-cmd-text."""
+ try:
+ results = parser.CommandParser().parse_all(cmdline)
+ except cmdexc.NoSuchCommandError:
+ return None
+
+ result = results[0]
+ if result.cmd.name != "set-cmd-text":
+ return cmdline
+ *flags, cmd = result.args
+ if "-a" in flags or "--append" in flags or not cmd.startswith(":"):
+ return None # doesn't look like this sets a command
+ return cmd.lstrip(":")
+
def get_reverse_bindings_for(self, mode: str) -> '_ReverseBindings':
- """Get a dict of commands to a list of bindings for the mode."""
+ """Get a dict of commands to a list of bindings for the mode.
+
+ This is intented for user-facing display of keybindings.
+ As such, bindings for 'set-cmd-text [flags] :<cmd> ...' are translated
+ to '<cmd> ...', as from the user's perspective these keys behave like
+ bindings for '<cmd>' (that allow for further input before running).
+
+ See #5942.
+ """
cmd_to_keys: KeyConfig._ReverseBindings = {}
bindings = self.get_bindings_for(mode)
for seq, full_cmd in sorted(bindings.items()):
- for cmd in full_cmd.split(';;'):
- cmd = cmd.strip()
+ for cmdtext in full_cmd.split(';;'):
+ cmd = self._implied_cmd(cmdtext.strip())
+ if not cmd:
+ continue
cmd_to_keys.setdefault(cmd, [])
# Put bindings involving modifiers last
if any(info.modifiers for info in seq):
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 22a4b2151..45d8d1a7c 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -1613,6 +1613,18 @@ input.spatial_navigation:
Right key, heuristics determine whether there is an element he might be
trying to reach towards the right and which element he probably wants.
+input.media_keys:
+ default: true
+ type: Bool
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+ restart: true
+ desc: >-
+ Whether the underlying Chromium should handle media keys.
+
+ On Linux, disabling this also disables Chromium's MPRIS integration.
+
## keyhint
keyhint.blacklist:
diff --git a/qutebrowser/config/qtargs.py b/qutebrowser/config/qtargs.py
index 407ccb37e..d9564556a 100644
--- a/qutebrowser/config/qtargs.py
+++ b/qutebrowser/config/qtargs.py
@@ -157,6 +157,9 @@ def _qtwebengine_features(
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-89740
disabled_features.append('InstalledApp')
+ if not config.val.input.media_keys:
+ disabled_features.append('HardwareMediaKeyHandling')
+
return (enabled_features, disabled_features)
diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py
index 1a8b171c2..bd5d4e801 100644
--- a/qutebrowser/keyinput/modeparsers.py
+++ b/qutebrowser/keyinput/modeparsers.py
@@ -86,6 +86,8 @@ class NormalKeyParser(CommandKeyParser):
_partial_timer: Timer to clear partial keypresses.
"""
+ _sequence: keyutils.KeySequence
+
def __init__(self, *, win_id: int,
commandrunner: 'runners.CommandRunner',
parent: QObject = None) -> None:
@@ -154,6 +156,8 @@ class HintKeyParser(basekeyparser.BaseKeyParser):
_last_press: The nature of the last keypress, a LastPress member.
"""
+ _sequence: keyutils.KeySequence
+
def __init__(self, *, win_id: int,
commandrunner: 'runners.CommandRunner',
hintmanager: hints.HintManager,
diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py
index 4751e1cea..001aa3047 100644
--- a/qutebrowser/misc/backendproblem.py
+++ b/qutebrowser/misc/backendproblem.py
@@ -194,14 +194,6 @@ class _BackendProblemChecker:
sys.exit(usertypes.Exit.err_init)
- def _nvidia_shader_workaround(self) -> None:
- """Work around QOpenGLShaderProgram issues.
-
- See https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
- """
- self._assert_backend(usertypes.Backend.QtWebEngine)
- utils.libgl_workaround()
-
def _xwayland_options(self) -> Tuple[str, List[_Button]]:
"""Get buttons/text for a possible XWayland solution."""
buttons = []
@@ -435,7 +427,6 @@ class _BackendProblemChecker:
self._check_backend_modules()
if objects.backend == usertypes.Backend.QtWebEngine:
self._handle_ssl_support()
- self._nvidia_shader_workaround()
self._handle_wayland_webgl()
self._handle_cache_nuking()
self._handle_serviceworker_nuking()
diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py
index 420f90f9a..ca8f9e8fe 100644
--- a/qutebrowser/misc/earlyinit.py
+++ b/qutebrowser/misc/earlyinit.py
@@ -185,6 +185,11 @@ def check_qt_version():
PYQT_VERSION_STR))
_die(text)
+ if qt_ver == QVersionNumber(5, 12, 0):
+ from qutebrowser.utils import log
+ log.init.warning("Running on Qt 5.12.0. Doing so is unsupported "
+ "(newer 5.12.x versions are fine).")
+
def check_ssl_support():
"""Check if SSL support is available."""
@@ -274,6 +279,21 @@ def check_optimize_flag():
"unexpected behavior may occur.")
+def webengine_early_import():
+ """If QtWebEngine is available, import it early.
+
+ We need to ensure that QtWebEngine is imported before a QApplication is created for
+ everything to work properly.
+
+ This needs to be done even when using the QtWebKit backend, to ensure that e.g.
+ error messages in backendproblem.py are accurate.
+ """
+ try:
+ from PyQt5 import QtWebEngineWidgets # pylint: disable=unused-import
+ except ImportError:
+ pass
+
+
def early_init(args):
"""Do all needed early initialization.
@@ -298,3 +318,4 @@ def early_init(args):
configure_pyqt()
check_ssl_support()
check_optimize_flag()
+ webengine_early_import()
diff --git a/qutebrowser/misc/elf.py b/qutebrowser/misc/elf.py
index 1da4709af..18cb9634c 100644
--- a/qutebrowser/misc/elf.py
+++ b/qutebrowser/misc/elf.py
@@ -69,7 +69,7 @@ from typing import IO, ClassVar, Dict, Optional, Tuple, cast
from PyQt5.QtCore import QLibraryInfo
-from qutebrowser.utils import log
+from qutebrowser.utils import log, version
class ParseError(Exception):
@@ -141,7 +141,7 @@ class Ident:
@classmethod
def parse(cls, fobj: IO[bytes]) -> 'Ident':
"""Parse an ELF ident header from a file."""
- magic, klass, data, version, osabi, abiversion = _unpack(cls._FORMAT, fobj)
+ magic, klass, data, elfversion, osabi, abiversion = _unpack(cls._FORMAT, fobj)
try:
bitness = Bitness(klass)
@@ -153,7 +153,7 @@ class Ident:
except ValueError:
raise ParseError(f"Invalid endianness {data}")
- return cls(magic, bitness, endianness, version, osabi, abiversion)
+ return cls(magic, bitness, endianness, elfversion, osabi, abiversion)
@dataclasses.dataclass
@@ -310,7 +310,11 @@ def _parse_from_file(f: IO[bytes]) -> Versions:
def parse_webenginecore() -> Optional[Versions]:
"""Parse the QtWebEngineCore library file."""
- library_path = pathlib.Path(QLibraryInfo.location(QLibraryInfo.LibrariesPath))
+ if version.is_flatpak():
+ # Flatpak has Qt in /usr/lib/x86_64-linux-gnu, but QtWebEngine in /app/lib.
+ library_path = pathlib.Path("/app/lib")
+ else:
+ library_path = pathlib.Path(QLibraryInfo.location(QLibraryInfo.LibrariesPath))
# PyQt bundles those files with a .5 suffix
lib_file = library_path / 'libQt5WebEngineCore.so.5'
diff --git a/qutebrowser/misc/sql.py b/qutebrowser/misc/sql.py
index 7a3626f6e..68c0fd538 100644
--- a/qutebrowser/misc/sql.py
+++ b/qutebrowser/misc/sql.py
@@ -351,13 +351,13 @@ class SqlTable(QObject):
self._name = name
self._create_table(fields, constraints)
- def _create_table(self, fields, constraints):
+ def _create_table(self, fields, constraints, *, force=False):
"""Create the table if the database is uninitialized.
- If the table already exists, this does nothing, so it can e.g. be called on
- every user_version change.
+ If the table already exists, this does nothing (except with force=True), so it
+ can e.g. be called on every user_version change.
"""
- if not user_version_changed():
+ if not user_version_changed() and not force:
return
constraints = constraints or {}
diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py
index f94d46061..7bb632b57 100644
--- a/qutebrowser/utils/standarddir.py
+++ b/qutebrowser/utils/standarddir.py
@@ -30,7 +30,7 @@ from typing import Iterator, Optional
from PyQt5.QtCore import QStandardPaths
from PyQt5.QtWidgets import QApplication
-from qutebrowser.utils import log, debug, utils
+from qutebrowser.utils import log, debug, utils, version
# The cached locations
_locations = {}
@@ -232,7 +232,16 @@ def _init_runtime(args: Optional[argparse.Namespace]) -> None:
# Unfortunately this path could get too long for sockets (which have a
# maximum length of 104 chars), so we don't add the username here...
- _create(path)
+ if version.is_flatpak():
+ # We need a path like /run/user/1000/app/org.qutebrowser.qutebrowser rather than
+ # /run/user/1000/qutebrowser on Flatpak, since that's bind-mounted in a way that
+ # it is accessible by any other qutebrowser instances.
+ *parts, app_name = os.path.split(path)
+ assert app_name == APPNAME, app_name
+ path = os.path.join(*parts, 'app', os.environ['FLATPAK_ID'])
+ else:
+ _create(path)
+
_locations[_Location.runtime] = path
diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py
index 03a3c7842..2a47d60aa 100644
--- a/qutebrowser/utils/utils.py
+++ b/qutebrowser/utils/utils.py
@@ -32,8 +32,6 @@ import functools
import contextlib
import shlex
import mimetypes
-import ctypes
-import ctypes.util
from typing import (Any, Callable, IO, Iterator,
Optional, Sequence, Tuple, Type, Union,
TypeVar, TYPE_CHECKING)
@@ -607,7 +605,7 @@ def open_file(filename: str, cmdline: str = None) -> None:
# if we want to use the default
override = config.val.downloads.open_dispatcher
- if version.is_sandboxed():
+ if version.is_flatpak():
if cmdline:
message.error("Cannot spawn download dispatcher from sandbox")
return
@@ -753,19 +751,6 @@ def ceil_log(number: int, base: int) -> int:
return result
-def libgl_workaround() -> None:
- """Work around QOpenGLShaderProgram issues, especially for Nvidia.
-
- See https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
- """
- if os.environ.get('QUTE_SKIP_LIBGL_WORKAROUND'):
- return
-
- libgl = ctypes.util.find_library("GL")
- if libgl is not None: # pragma: no branch
- ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL)
-
-
def parse_duration(duration: str) -> int:
"""Parse duration in format XhYmZs into milliseconds duration."""
if duration.isdigit():
diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
index a1b8e6c72..89da353fc 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -58,12 +58,6 @@ from qutebrowser.misc import objects, earlyinit, sql, httpclient, pastebin, elf
from qutebrowser.browser import pdfjs
from qutebrowser.config import config, websettings
-try:
- from qutebrowser.browser.webengine import webenginesettings
-except ImportError: # pragma: no cover
- webenginesettings = None # type: ignore[assignment]
-
-
_LOGO = r'''
______ ,,
,.-"` | ,-` |
@@ -189,8 +183,12 @@ def distribution() -> Optional[DistributionInfo]:
parsed=parsed, version=dist_version, pretty=pretty, id=dist_id)
-def is_sandboxed() -> bool:
- """Whether the environment has restricted access to the host system."""
+def is_flatpak() -> bool:
+ """Whether qutebrowser is running via Flatpak.
+
+ If packaged via Flatpak, the environment is has restricted access to the host
+ system.
+ """
current_distro = distribution()
if current_distro is None:
return False
@@ -684,7 +682,7 @@ def qtwebengine_versions(avoid_init: bool = False) -> WebEngineVersions:
- https://www.chromium.org/developers/calendar
- https://chromereleases.googleblog.com/
"""
- assert webenginesettings is not None
+ from qutebrowser.browser.webengine import webenginesettings
if webenginesettings.parsed_user_agent is None and not avoid_init:
webenginesettings.init_user_agent()
@@ -882,9 +880,6 @@ def opengl_info() -> Optional[OpenGLInfo]: # pragma: no cover
"""
assert QApplication.instance()
- # Some setups can segfault in here if we don't do this.
- utils.libgl_workaround()
-
override = os.environ.get('QUTE_FAKE_OPENGL')
if override is not None:
log.init.debug("Using override {}".format(override))
diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py
index 4c913cd3d..bae51e372 100644
--- a/scripts/dev/misc_checks.py
+++ b/scripts/dev/misc_checks.py
@@ -155,7 +155,7 @@ def check_spelling(args: argparse.Namespace) -> Optional[bool]:
"""Check commonly misspelled words."""
# Words which I often misspell
words = {'behaviour', 'quitted', 'likelyhood', 'sucessfully',
- 'occur[^rs .!]', 'seperator', 'explicitely', 'auxillary',
+ 'occur[^rs .!,]', 'seperator', 'explicitely', 'auxillary',
'accidentaly', 'ambigious', 'loosly', 'initialis', 'convienence',
'similiar', 'uncommited', 'reproducable', 'an user',
'convienience', 'wether', 'programatically', 'splitted',
diff --git a/scripts/dev/run_pylint_on_tests.py b/scripts/dev/run_pylint_on_tests.py
index 16a281d57..d0385bd17 100644
--- a/scripts/dev/run_pylint_on_tests.py
+++ b/scripts/dev/run_pylint_on_tests.py
@@ -58,6 +58,7 @@ def main():
'protected-access',
'len-as-condition',
'compare-to-empty-string',
+ 'pointless-statement',
# directories without __init__.py...
'import-error',
]
diff --git a/tests/conftest.py b/tests/conftest.py
index 084acddc5..8e35e1c24 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -240,12 +240,6 @@ def set_backend(monkeypatch, request):
monkeypatch.setattr(objects, 'backend', backend)
-@pytest.fixture(autouse=True, scope='session')
-def apply_libgl_workaround():
- """Make sure we load libGL early so QtWebEngine tests run properly."""
- utils.libgl_workaround()
-
-
@pytest.fixture(autouse=True)
def apply_fake_os(monkeypatch, request):
fake_os = request.node.get_closest_marker('fake_os')
diff --git a/tests/end2end/fixtures/webserver.py b/tests/end2end/fixtures/webserver.py
index 81a864c8e..658ff0e56 100644
--- a/tests/end2end/fixtures/webserver.py
+++ b/tests/end2end/fixtures/webserver.py
@@ -62,7 +62,11 @@ class Request(testprocess.Line):
def _check_status(self):
"""Check if the http status is what we expected."""
path_to_statuses = {
- '/favicon.ico': [HTTPStatus.OK, HTTPStatus.PARTIAL_CONTENT],
+ '/favicon.ico': [
+ HTTPStatus.OK,
+ HTTPStatus.PARTIAL_CONTENT,
+ HTTPStatus.NOT_MODIFIED,
+ ],
'/does-not-exist': [HTTPStatus.NOT_FOUND],
'/does-not-exist-2': [HTTPStatus.NOT_FOUND],
diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py
index 9b08de30d..1a46c5be0 100644
--- a/tests/unit/browser/test_history.py
+++ b/tests/unit/browser/test_history.py
@@ -488,12 +488,11 @@ class TestCompletionMetaInfo:
def test_contains_keyerror(self, metainfo):
with pytest.raises(KeyError):
- # pylint: disable=pointless-statement
'does_not_exist' in metainfo # noqa: B015
def test_getitem_keyerror(self, metainfo):
with pytest.raises(KeyError):
- metainfo['does_not_exist'] # pylint: disable=pointless-statement
+ metainfo['does_not_exist']
def test_setitem_keyerror(self, metainfo):
with pytest.raises(KeyError):
@@ -508,6 +507,28 @@ class TestCompletionMetaInfo:
metainfo['excluded_patterns'] = value
assert metainfo['excluded_patterns'] == value
+ # FIXME: It'd be good to test those two things via WebHistory (and not just
+ # CompletionMetaInfo in isolation), but we can't do that right now - see the
+ # docstring of TestRebuild for details.
+
+ def test_recovery_no_key(self, metainfo):
+ metainfo.delete('key', 'force_rebuild')
+
+ with pytest.raises(sql.BugError, match='No result for single-result query'):
+ metainfo['force_rebuild']
+
+ metainfo.try_recover()
+ assert not metainfo['force_rebuild']
+
+ def test_recovery_no_table(self, metainfo):
+ sql.Query("DROP TABLE CompletionMetaInfo").run()
+
+ with pytest.raises(sql.BugError, match='no such table: CompletionMetaInfo'):
+ metainfo['force_rebuild']
+
+ metainfo.try_recover()
+ assert not metainfo['force_rebuild']
+
class TestHistoryProgress:
diff --git a/tests/unit/browser/webkit/test_webkitelem.py b/tests/unit/browser/webkit/test_webkitelem.py
index 33af45b6c..593896e96 100644
--- a/tests/unit/browser/webkit/test_webkitelem.py
+++ b/tests/unit/browser/webkit/test_webkitelem.py
@@ -303,7 +303,7 @@ class TestWebKitElement:
def test_getitem_keyerror(self, elem):
with pytest.raises(KeyError):
- elem['foo'] # pylint: disable=pointless-statement
+ elem['foo']
def test_setitem(self, elem):
elem['foo'] = 'bar'
diff --git a/tests/unit/commands/test_runners.py b/tests/unit/commands/test_parser.py
index ac9fee485..b851ad3b0 100644
--- a/tests/unit/commands/test_runners.py
+++ b/tests/unit/commands/test_parser.py
@@ -17,12 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
-"""Tests for qutebrowser.commands.runners."""
+"""Tests for qutebrowser.commands.parser."""
import pytest
from qutebrowser.misc import objects
-from qutebrowser.commands import runners, cmdexc
+from qutebrowser.commands import parser, cmdexc
class TestCommandParser:
@@ -35,12 +35,12 @@ class TestCommandParser:
Args:
cmdline_test: A pytest fixture which provides testcases.
"""
- parser = runners.CommandParser()
+ p = parser.CommandParser()
if cmdline_test.valid:
- parser.parse_all(cmdline_test.cmd, aliases=False)
+ p.parse_all(cmdline_test.cmd, aliases=False)
else:
with pytest.raises(cmdexc.NoSuchCommandError):
- parser.parse_all(cmdline_test.cmd, aliases=False)
+ p.parse_all(cmdline_test.cmd, aliases=False)
def test_parse_all_with_alias(self, cmdline_test, monkeypatch,
config_stub):
@@ -49,12 +49,12 @@ class TestCommandParser:
config_stub.val.aliases = {'alias_name': cmdline_test.cmd}
- parser = runners.CommandParser()
+ p = parser.CommandParser()
if cmdline_test.valid:
- assert len(parser.parse_all("alias_name")) > 0
+ assert len(p.parse_all("alias_name")) > 0
else:
with pytest.raises(cmdexc.NoSuchCommandError):
- parser.parse_all("alias_name")
+ p.parse_all("alias_name")
@pytest.mark.parametrize('command', ['', ' '])
def test_parse_empty_with_alias(self, command):
@@ -63,9 +63,33 @@ class TestCommandParser:
See https://github.com/qutebrowser/qutebrowser/issues/1690
and https://github.com/qutebrowser/qutebrowser/issues/1773
"""
- parser = runners.CommandParser()
+ p = parser.CommandParser()
with pytest.raises(cmdexc.NoSuchCommandError):
- parser.parse_all(command)
+ p.parse_all(command)
+
+ @pytest.mark.parametrize('command, name, args', [
+ ("set-cmd-text -s :open", "set-cmd-text", ["-s", ":open"]),
+ ("set-cmd-text :open {url:pretty}", "set-cmd-text",
+ [":open {url:pretty}"]),
+ ("set-cmd-text -s :open -t", "set-cmd-text", ["-s", ":open -t"]),
+ ("set-cmd-text :open -t -r {url:pretty}", "set-cmd-text",
+ [":open -t -r {url:pretty}"]),
+ ("set-cmd-text -s :open -b", "set-cmd-text", ["-s", ":open -b"]),
+ ("set-cmd-text :open -b -r {url:pretty}", "set-cmd-text",
+ [":open -b -r {url:pretty}"]),
+ ("set-cmd-text -s :open -w", "set-cmd-text",
+ ["-s", ":open -w"]),
+ ("set-cmd-text :open -w {url:pretty}", "set-cmd-text",
+ [":open -w {url:pretty}"]),
+ ("set-cmd-text /", "set-cmd-text", ["/"]),
+ ("set-cmd-text ?", "set-cmd-text", ["?"]),
+ ("set-cmd-text :", "set-cmd-text", [":"]),
+ ])
+ def test_parse_result(self, config_stub, command, name, args):
+ p = parser.CommandParser()
+ result = p.parse_all(command)[0]
+ assert result.cmd.name == name
+ assert result.args == args
class TestCompletions:
@@ -86,8 +110,8 @@ class TestCompletions:
The same with it being disabled is tested by test_parse_all.
"""
- parser = runners.CommandParser(partial_match=True)
- result = parser.parse('on')
+ p = parser.CommandParser(partial_match=True)
+ result = p.parse('on')
assert result.cmd.name == 'one'
def test_dont_use_best_match(self, config_stub):
@@ -96,10 +120,10 @@ class TestCompletions:
Should raise NoSuchCommandError
"""
config_stub.val.completion.use_best_match = False
- parser = runners.CommandParser(partial_match=True)
+ p = parser.CommandParser(partial_match=True)
with pytest.raises(cmdexc.NoSuchCommandError):
- parser.parse('tw')
+ p.parse('tw')
def test_use_best_match(self, config_stub):
"""Test multiple completion options with use_best_match set to true.
@@ -107,7 +131,7 @@ class TestCompletions:
The resulting command should be the best match
"""
config_stub.val.completion.use_best_match = True
- parser = runners.CommandParser(partial_match=True)
+ p = parser.CommandParser(partial_match=True)
- result = parser.parse('tw')
+ result = p.parse('tw')
assert result.cmd.name == 'two'
diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py
index 8a9d8154d..dd6ef54fa 100644
--- a/tests/unit/config/test_config.py
+++ b/tests/unit/config/test_config.py
@@ -187,17 +187,39 @@ class TestKeyConfig:
@pytest.mark.parametrize('bindings, expected', [
# Simple
- ({'a': 'message-info foo', 'b': 'message-info bar'},
- {'message-info foo': ['a'], 'message-info bar': ['b']}),
+ ({'a': 'open foo', 'b': 'open bar'},
+ {'open foo': ['a'], 'open bar': ['b']}),
# Multiple bindings
- ({'a': 'message-info foo', 'b': 'message-info foo'},
- {'message-info foo': ['b', 'a']}),
+ ({'a': 'open foo', 'b': 'open foo'},
+ {'open foo': ['b', 'a']}),
# With modifier keys (should be listed last and normalized)
- ({'a': 'message-info foo', '<ctrl-a>': 'message-info foo'},
- {'message-info foo': ['a', '<Ctrl+a>']}),
+ ({'a': 'open foo', '<ctrl-a>': 'open foo'},
+ {'open foo': ['a', '<Ctrl+a>']}),
# Chained command
- ({'a': 'message-info foo ;; message-info bar'},
- {'message-info foo': ['a'], 'message-info bar': ['a']}),
+ ({'a': 'open foo ;; open bar'},
+ {'open foo': ['a'], 'open bar': ['a']}),
+ # Command using set-cmd-text (#5942)
+ (
+ {
+ "o": "set-cmd-text -s :open",
+ "O": "set-cmd-text -s :open -t",
+ "go": "set-cmd-text :open {url:pretty}",
+ # all of these should be ignored
+ "/": "set-cmd-text /",
+ "?": "set-cmd-text ?",
+ ":": "set-cmd-text :",
+ "a": "set-cmd-text no_leading_colon",
+ "b": "set-cmd-text -s -a :skip_cuz_append",
+ "c": "set-cmd-text --append :skip_cuz_append",
+ },
+ {
+ "open": ["o"],
+ "open -t": ["O"],
+ "open {url:pretty}": ["go"],
+ }
+ ),
+ # Empty/unknown commands
+ ({"a": "", "b": "notreal"}, {}),
])
def test_get_reverse_bindings_for(self, key_config_stub, config_stub,
no_bindings, bindings, expected):
@@ -725,7 +747,7 @@ class TestContainer:
def test_getattr_invalid_private(self, container):
"""Make sure an invalid _attribute doesn't try getting a container."""
with pytest.raises(AttributeError):
- container._foo # pylint: disable=pointless-statement
+ container._foo
def test_getattr_prefix(self, container):
new_container = container.tabs
@@ -744,7 +766,7 @@ class TestContainer:
def test_getattr_invalid(self, container):
with pytest.raises(configexc.NoOptionError) as excinfo:
- container.tabs.foobar # pylint: disable=pointless-statement
+ container.tabs.foobar
assert excinfo.value.option == 'tabs.foobar'
def test_setattr_option(self, config_stub, container):
@@ -754,7 +776,7 @@ class TestContainer:
def test_confapi_errors(self, container):
configapi = types.SimpleNamespace(errors=[])
container._configapi = configapi
- container.tabs.foobar # pylint: disable=pointless-statement
+ container.tabs.foobar
assert len(configapi.errors) == 1
error = configapi.errors[0]
diff --git a/tests/unit/config/test_configcache.py b/tests/unit/config/test_configcache.py
index 6bd841a65..87514bada 100644
--- a/tests/unit/config/test_configcache.py
+++ b/tests/unit/config/test_configcache.py
@@ -55,12 +55,10 @@ def test_configcache_get_after_set(config_stub):
def test_configcache_naive_benchmark(config_stub, benchmark):
def _run_bench():
for _i in range(10000):
- # pylint: disable=pointless-statement
config.cache['tabs.padding']
config.cache['tabs.indicator.width']
config.cache['tabs.indicator.padding']
config.cache['tabs.min_width']
config.cache['tabs.max_width']
config.cache['tabs.pinned.shrink']
- # pylint: enable=pointless-statement
benchmark(_run_bench)
diff --git a/tests/unit/config/test_qtargs.py b/tests/unit/config/test_qtargs.py
index 695649213..4c31c5b07 100644
--- a/tests/unit/config/test_qtargs.py
+++ b/tests/unit/config/test_qtargs.py
@@ -52,10 +52,14 @@ def version_patcher(monkeypatch):
@pytest.fixture
-def reduce_args(config_stub, version_patcher):
+def reduce_args(config_stub, version_patcher, monkeypatch):
"""Make sure no --disable-shared-workers/referer argument get added."""
- version_patcher('5.15.0')
+ version_patcher('5.15.3')
config_stub.val.content.headers.referer = 'always'
+ config_stub.val.scrolling.bar = 'never'
+ monkeypatch.setattr(qtargs.utils, 'is_mac', False)
+ # Avoid WebRTC pipewire feature
+ monkeypatch.setattr(qtargs.utils, 'is_linux', False)
@pytest.mark.usefixtures('reduce_args')
@@ -78,11 +82,6 @@ class TestQtArgs:
])
def test_qt_args(self, monkeypatch, config_stub, args, expected, parser):
"""Test commandline with no Qt arguments given."""
- # Avoid scrollbar overlay argument
- config_stub.val.scrolling.bar = 'never'
- # Avoid WebRTC pipewire feature
- monkeypatch.setattr(qtargs.utils, 'is_linux', False)
-
parsed = parser.parse_args(args)
assert qtargs.qt_args(parsed) == expected
@@ -112,7 +111,6 @@ def test_no_webengine_available(monkeypatch, config_stub, parser, stubs):
here.
"""
monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
- monkeypatch.setattr(qtargs.version, 'webenginesettings', None)
fake = stubs.ImportFake({'qutebrowser.browser.webengine': False}, monkeypatch)
fake.patch()
@@ -126,9 +124,10 @@ def test_no_webengine_available(monkeypatch, config_stub, parser, stubs):
class TestWebEngineArgs:
@pytest.fixture(autouse=True)
- def ensure_webengine(self):
+ def ensure_webengine(self, monkeypatch):
"""Skip all tests if QtWebEngine is unavailable."""
pytest.importorskip("PyQt5.QtWebEngine")
+ monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
@pytest.mark.parametrize('backend, qt_version, expected', [
(usertypes.Backend.QtWebEngine, '5.13.0', False),
@@ -184,7 +183,6 @@ class TestWebEngineArgs:
(['--debug-flag', 'wait-renderer-process'], ['--renderer-startup-dialog']),
])
def test_chromium_flags(self, monkeypatch, parser, flags, args):
- monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
parsed = parser.parse_args(flags)
args = qtargs.qt_args(parsed)
@@ -203,7 +201,6 @@ class TestWebEngineArgs:
('chromium', True),
])
def test_disable_gpu(self, config, added, config_stub, monkeypatch, parser):
- monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
config_stub.val.qt.force_software_rendering = config
parsed = parser.parse_args([])
args = qtargs.qt_args(parsed)
@@ -225,7 +222,6 @@ class TestWebEngineArgs:
'disable_non_proxied_udp'),
])
def test_webrtc(self, config_stub, monkeypatch, parser, policy, arg):
- monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
config_stub.val.content.webrtc_ip_handling_policy = policy
parsed = parser.parse_args([])
@@ -241,10 +237,7 @@ class TestWebEngineArgs:
(True, False), # canvas reading enabled
(False, True),
])
- def test_canvas_reading(self, config_stub, monkeypatch, parser,
- canvas_reading, added):
- monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
-
+ def test_canvas_reading(self, config_stub, parser, canvas_reading, added):
config_stub.val.content.canvas_reading = canvas_reading
parsed = parser.parse_args([])
args = qtargs.qt_args(parsed)
@@ -255,10 +248,7 @@ class TestWebEngineArgs:
('process-per-site', True),
('single-process', True),
])
- def test_process_model(self, config_stub, monkeypatch, parser,
- process_model, added):
- monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
-
+ def test_process_model(self, config_stub, parser, process_model, added):
config_stub.val.qt.process_model = process_model
parsed = parser.parse_args([])
args = qtargs.qt_args(parsed)
@@ -276,10 +266,7 @@ class TestWebEngineArgs:
('always', '--enable-low-end-device-mode'),
('never', '--disable-low-end-device-mode'),
])
- def test_low_end_device_mode(self, config_stub, monkeypatch, parser,
- low_end_device_mode, arg):
- monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
-
+ def test_low_end_device_mode(self, config_stub, parser, low_end_device_mode, arg):
config_stub.val.qt.low_end_device_mode = low_end_device_mode
parsed = parser.parse_args([])
args = qtargs.qt_args(parsed)
@@ -307,16 +294,10 @@ class TestWebEngineArgs:
('5.14.0', 'same-domain', '--enable-features=ReducedReferrerGranularity'),
('5.15.0', 'same-domain', '--enable-features=ReducedReferrerGranularity'),
])
- def test_referer(self, config_stub, monkeypatch, version_patcher, parser,
+ def test_referer(self, config_stub, version_patcher, parser,
qt_version, referer, arg):
- monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
version_patcher(qt_version)
- # Avoid WebRTC pipewire feature
- monkeypatch.setattr(qtargs.utils, 'is_linux', False)
- # Avoid overlay scrollbar feature
- config_stub.val.scrolling.bar = 'never'
-
config_stub.val.content.headers.referer = referer
parsed = parser.parse_args([])
args = qtargs.qt_args(parsed)
@@ -380,10 +361,7 @@ class TestWebEngineArgs:
])
def test_overlay_scrollbar(self, config_stub, monkeypatch, parser,
bar, is_mac, added):
- monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
monkeypatch.setattr(qtargs.utils, 'is_mac', is_mac)
- # Avoid WebRTC pipewire feature
- monkeypatch.setattr(qtargs.utils, 'is_linux', False)
config_stub.val.scrolling.bar = bar
@@ -392,15 +370,6 @@ class TestWebEngineArgs:
assert ('--enable-features=OverlayScrollbar' in args) == added
- @pytest.fixture
- def feature_flag_patch(self, monkeypatch, config_stub, version_patcher):
- """Patch away things affecting feature flags."""
- config_stub.val.scrolling.bar = 'never'
- version_patcher('5.15.3')
- monkeypatch.setattr(qtargs.utils, 'is_mac', False)
- # Avoid WebRTC pipewire feature
- monkeypatch.setattr(qtargs.utils, 'is_linux', False)
-
@pytest.mark.parametrize('via_commandline', [True, False])
@pytest.mark.parametrize('overlay, passed_features, expected_features', [
(True,
@@ -413,7 +382,7 @@ class TestWebEngineArgs:
'CustomFeature',
'CustomFeature'),
])
- def test_overlay_features_flag(self, config_stub, parser, feature_flag_patch,
+ def test_overlay_features_flag(self, config_stub, parser,
via_commandline, overlay, passed_features,
expected_features):
"""If enable-features is already specified, we should combine both."""
@@ -442,7 +411,7 @@ class TestWebEngineArgs:
['CustomFeature'],
['CustomFeature1', 'CustomFeature2'],
])
- def test_disable_features_passthrough(self, config_stub, parser, feature_flag_patch,
+ def test_disable_features_passthrough(self, config_stub, parser,
via_commandline, passed_features):
flag = qtargs._DISABLE_FEATURES + ','.join(passed_features)
@@ -458,7 +427,7 @@ class TestWebEngineArgs:
]
assert disable_features_args == [flag]
- def test_blink_settings_passthrough(self, parser, config_stub, feature_flag_patch):
+ def test_blink_settings_passthrough(self, parser, config_stub):
config_stub.val.colors.webpage.darkmode.enabled = True
flag = qtargs._BLINK_SETTINGS + 'foo=bar'
@@ -492,6 +461,16 @@ class TestWebEngineArgs:
expected = ['--disable-features=InstalledApp'] if has_workaround else []
assert disable_features_args == expected
+ @pytest.mark.parametrize('enabled', [True, False])
+ @testutils.qt514
+ def test_media_keys(self, config_stub, parser, enabled):
+ config_stub.val.input.media_keys = enabled
+
+ parsed = parser.parse_args([])
+ args = qtargs.qt_args(parsed)
+
+ assert ('--disable-features=HardwareMediaKeyHandling' in args) != enabled
+
@pytest.mark.parametrize('variant, expected', [
(
'qt_515_1',
@@ -518,7 +497,6 @@ class TestWebEngineArgs:
def test_dark_mode_settings(self, config_stub, monkeypatch, parser,
variant, expected):
from qutebrowser.browser.webengine import darkmode
- monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
monkeypatch.setattr(
darkmode, '_variant', lambda _versions: darkmode.Variant[variant])
@@ -531,16 +509,16 @@ class TestWebEngineArgs:
assert arg in args
@pytest.mark.linux
- def test_locale_workaround(self, config_stub, monkeypatch, version_patcher,
- parser):
+ def test_locale_workaround(self, config_stub, monkeypatch, version_patcher, parser):
class FakeLocale:
def bcp47Name(self):
return 'de-CH'
- monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
+ monkeypatch.setattr(qtargs.utils, 'is_linux', True) # patched in reduce_args
monkeypatch.setattr(qtargs, 'QLocale', FakeLocale)
version_patcher('5.15.3')
+
config_stub.val.qt.workarounds.locale = True
parsed = parser.parse_args([])
args = qtargs.qt_args(parsed)
diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py
index 85d5ebe0a..4a7f09204 100644
--- a/tests/unit/javascript/conftest.py
+++ b/tests/unit/javascript/conftest.py
@@ -19,9 +19,7 @@
"""pytest conftest file for javascript tests."""
-import os
-import os.path
-
+import pathlib
import pytest
import jinja2
@@ -30,6 +28,8 @@ from PyQt5.QtCore import QUrl
import qutebrowser
from qutebrowser.utils import usertypes
+JS_DIR = pathlib.Path(__file__).parent
+
class JSTester:
@@ -44,7 +44,7 @@ class JSTester:
def __init__(self, tab, qtbot, config_stub):
self.tab = tab
self.qtbot = qtbot
- loader = jinja2.FileSystemLoader(os.path.dirname(__file__))
+ loader = jinja2.FileSystemLoader(JS_DIR)
self._jinja_env = jinja2.Environment(loader=loader, autoescape=True)
# Make sure error logging via JS fails tests
config_stub.val.content.javascript.log = {
@@ -87,7 +87,7 @@ class JSTester:
force: Whether to force loading even if the file is invalid.
"""
self.load_url(QUrl.fromLocalFile(
- os.path.join(os.path.dirname(__file__), path)), force)
+ str(JS_DIR / path)), force)
def load_url(self, url: QUrl, force: bool = False):
"""Load a given QUrl.
@@ -109,9 +109,8 @@ class JSTester:
path: The path to the JS file, relative to the qutebrowser package.
expected: The value expected return from the javascript execution
"""
- base_path = os.path.dirname(os.path.abspath(qutebrowser.__file__))
- with open(os.path.join(base_path, path), 'r', encoding='utf-8') as f:
- source = f.read()
+ base_path = pathlib.Path(qutebrowser.__file__).resolve().parent
+ source = (base_path / path).read_text(encoding='utf-8')
self.run(source, expected)
def run(self, source: str, expected=usertypes.UNSET, world=None) -> None:
diff --git a/tests/unit/javascript/stylesheet/test_stylesheet_js.py b/tests/unit/javascript/stylesheet/test_stylesheet_js.py
index 13ec85cd5..1eebe3b7f 100644
--- a/tests/unit/javascript/stylesheet/test_stylesheet_js.py
+++ b/tests/unit/javascript/stylesheet/test_stylesheet_js.py
@@ -19,7 +19,7 @@
"""Tests for stylesheet.js."""
-import os
+import pathlib
import pytest
QtWebEngineWidgets = pytest.importorskip("PyQt5.QtWebEngineWidgets")
@@ -49,8 +49,8 @@ class StylesheetTester:
def init_stylesheet(self, css_file="green.css"):
"""Initialize the stylesheet with a provided css file."""
- css_path = os.path.join(os.path.dirname(__file__), css_file)
- self.config_stub.val.content.user_stylesheets = css_path
+ css_path = pathlib.Path(__file__).parent / css_file
+ self.config_stub.val.content.user_stylesheets = str(css_path)
def set_css(self, css):
"""Set document style to `css` via stylesheet.js."""
diff --git a/tests/unit/javascript/test_greasemonkey.py b/tests/unit/javascript/test_greasemonkey.py
index 3a3ea0294..2bfb9ca83 100644
--- a/tests/unit/javascript/test_greasemonkey.py
+++ b/tests/unit/javascript/test_greasemonkey.py
@@ -198,7 +198,7 @@ class TestForceDocumentEnd:
assert script.needs_document_end_workaround() == force
-def test_required_scripts_are_included(download_stub, tmpdir):
+def test_required_scripts_are_included(download_stub, tmp_path):
test_require_script = textwrap.dedent("""
// ==UserScript==
// @name qutebrowser test userscript
@@ -212,7 +212,7 @@ def test_required_scripts_are_included(download_stub, tmpdir):
console.log("Script is running.");
""")
_save_script(test_require_script, 'requiring.user.js')
- (tmpdir / 'test.js').write_text('REQUIRED SCRIPT', encoding='UTF-8')
+ (tmp_path / 'test.js').write_text('REQUIRED SCRIPT', encoding='UTF-8')
gm_manager = greasemonkey.GreasemonkeyManager()
assert len(gm_manager._in_progress_dls) == 1
diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py
index 5b24ed962..1a9107995 100644
--- a/tests/unit/utils/test_standarddir.py
+++ b/tests/unit/utils/test_standarddir.py
@@ -202,6 +202,22 @@ class TestStandardDir:
standarddir._init_runtime(args=None)
assert standarddir.runtime() == str(tmpdir_env / APPNAME)
+ @pytest.mark.linux
+ def test_flatpak_runtimedir(self, monkeypatch, tmp_path):
+ runtime_path = tmp_path / 'runtime'
+ runtime_path.mkdir()
+ runtime_path.chmod(0o0700)
+
+ app_id = 'org.qutebrowser.qutebrowser'
+ expected = runtime_path / 'app' / app_id
+
+ monkeypatch.setattr(standarddir.version, 'is_flatpak', lambda: True)
+ monkeypatch.setenv('XDG_RUNTIME_DIR', str(runtime_path))
+ monkeypatch.setenv('FLATPAK_ID', app_id)
+
+ standarddir._init_runtime(args=None)
+ assert standarddir.runtime() == str(expected)
+
@pytest.mark.fake_os('windows')
def test_runtimedir_empty_tempdir(self, monkeypatch, tmpdir):
"""With an empty tempdir on non-Linux, we should raise."""
diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py
index b43638cb3..2c726ddb6 100644
--- a/tests/unit/utils/test_utils.py
+++ b/tests/unit/utils/test_utils.py
@@ -892,13 +892,6 @@ def test_ceil_log_invalid(number, base):
utils.ceil_log(number, base)
-@pytest.mark.parametrize('skip', [True, False])
-def test_libgl_workaround(monkeypatch, skip):
- if skip:
- monkeypatch.setenv('QUTE_SKIP_LIBGL_WORKAROUND', '1')
- utils.libgl_workaround() # Just make sure it doesn't crash.
-
-
@pytest.mark.parametrize('duration, out', [
("0", 0),
("0s", 0),
diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py
index c91017e84..a53b4bdce 100644
--- a/tests/unit/utils/test_version.py
+++ b/tests/unit/utils/test_version.py
@@ -40,6 +40,11 @@ from qutebrowser.utils import version, usertypes, utils, standarddir
from qutebrowser.misc import pastebin, objects, elf
from qutebrowser.browser import pdfjs
+try:
+ from qutebrowser.browser.webengine import webenginesettings
+except ImportError:
+ webenginesettings = None
+
@pytest.mark.parametrize('os_release, expected', [
# No file
@@ -314,9 +319,9 @@ def test_distribution(tmpdir, monkeypatch, os_release, expected):
id='arch', parsed=version.Distribution.arch, version=None,
pretty='Arch Linux'), False)
])
-def test_is_sandboxed(monkeypatch, distribution, expected):
+def test_is_flatpak(monkeypatch, distribution, expected):
monkeypatch.setattr(version, "distribution", lambda: distribution)
- assert version.is_sandboxed() == expected
+ assert version.is_flatpak() == expected
class GitStrSubprocessFake:
@@ -1004,7 +1009,6 @@ class TestWebEngineVersions:
versions = version.WebEngineVersions.from_pyqt(pyqt_webengine_version)
- from qutebrowser.browser.webengine import webenginesettings
webenginesettings.init_user_agent()
expected = webenginesettings.parsed_user_agent.upstream_browser_version
@@ -1045,26 +1049,24 @@ class TestChromiumVersion:
@pytest.fixture(autouse=True)
def clear_parsed_ua(self, monkeypatch):
pytest.importorskip('PyQt5.QtWebEngineWidgets')
- if version.webenginesettings is not None:
+ if webenginesettings is not None:
# Not available with QtWebKit
- monkeypatch.setattr(version.webenginesettings, 'parsed_user_agent', None)
+ monkeypatch.setattr(webenginesettings, 'parsed_user_agent', None)
def test_fake_ua(self, monkeypatch, caplog):
ver = '77.0.3865.98'
- version.webenginesettings._init_user_agent_str(
- _QTWE_USER_AGENT.format(ver))
+ webenginesettings._init_user_agent_str(_QTWE_USER_AGENT.format(ver))
assert version.qtwebengine_versions().chromium == ver
def test_prefers_saved_user_agent(self, monkeypatch):
- version.webenginesettings._init_user_agent_str(_QTWE_USER_AGENT.format('87'))
+ webenginesettings._init_user_agent_str(_QTWE_USER_AGENT.format('87'))
class FakeProfile:
def defaultProfile(self):
raise AssertionError("Should not be called")
- monkeypatch.setattr(version.webenginesettings, 'QWebEngineProfile',
- FakeProfile())
+ monkeypatch.setattr(webenginesettings, 'QWebEngineProfile', FakeProfile())
version.qtwebengine_versions()
@@ -1250,7 +1252,6 @@ def test_version_info(params, stubs, monkeypatch, config_stub):
if params.with_webkit:
patches['qWebKitVersion'] = lambda: 'WEBKIT VERSION'
patches['objects.backend'] = usertypes.Backend.QtWebKit
- patches['webenginesettings'] = None
substitutions['backend'] = 'new QtWebKit (WebKit WEBKIT VERSION)'
else:
monkeypatch.delattr(version, 'qtutils.qWebKitVersion', raising=False)
diff --git a/tox.ini b/tox.ini
index 70329f9e6..e305e5c4d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -38,6 +38,16 @@ commands =
{envpython} -bb -m pytest {posargs:tests}
cov: {envpython} scripts/dev/check_coverage.py {posargs}
+[testenv:bleeding]
+basepython = {env:PYTHON:python3}
+setenv =
+ PYTEST_QT_API=pyqt5
+ QUTE_BDD_WEBENGINE=true
+pip_pre = true
+deps = -r{toxinidir}/misc/requirements/requirements-tests-bleeding.txt
+commands_pre = pip install --index-url https://www.riverbankcomputing.com/pypi/simple/ --pre --upgrade PyQt5 PyQtWebEngine
+commands = {envpython} -bb -m pytest {posargs:tests}
+
# other envs
[testenv:misc]