From 04905380b94b82b73b343e23e64d53393386d470 Mon Sep 17 00:00:00 2001
From: Julin S <48789920+ju-sh@users.noreply.github.com>
Date: Sun, 15 Mar 2020 21:17:05 +0530
Subject: [PATCH 001/245] begin using pathlib
---
scripts/asciidoc2html.py | 57 ++++++++++++++++++++++------------------
1 file changed, 31 insertions(+), 26 deletions(-)
diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py
index ceac1ff41..58e5da249 100755
--- a/scripts/asciidoc2html.py
+++ b/scripts/asciidoc2html.py
@@ -19,6 +19,7 @@
# along with qutebrowser. If not, see .
"""Generate the html documentation based on the asciidoc files."""
+from typing import Optional, List, Tuple
import re
import os
@@ -30,11 +31,14 @@ import shutil
import tempfile
import argparse
import io
+import pathlib
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
from scripts import utils
+DOC_DIR = pathlib.Path("qutebrowser/html/doc")
+
class AsciiDoc:
@@ -42,24 +46,23 @@ class AsciiDoc:
FILES = ['faq', 'changelog', 'contributing', 'quickstart', 'userscripts']
- def __init__(self, asciidoc, website):
+ def __init__(self, asciidoc: pathlib.Path, website: pathlib.Path) -> None:
self._cmd = None
- self._asciidoc = asciidoc
- self._website = website
- self._homedir = None
- self._themedir = None
- self._tempdir = None
- self._failed = False
+ self._asciidoc: Optional[pathlib.Path] = asciidoc
+ self._website: list = website
+ self._homedir: Optional[pathlib.Path] = None
+ self._themedir: Optional[pathlib.Path] = None
+ self._tempdir: Optional[pathlib.Path] = None
+ self._failed: bool = False
def prepare(self):
"""Get the asciidoc command and create the homedir to use."""
self._cmd = self._get_asciidoc_cmd()
- self._homedir = tempfile.mkdtemp()
- self._themedir = os.path.join(
- self._homedir, '.asciidoc', 'themes', 'qute')
- self._tempdir = os.path.join(self._homedir, 'tmp')
- os.makedirs(self._tempdir)
- os.makedirs(self._themedir)
+ self._homedir = pathlib.Path(tempfile.mkdtemp())
+ self._themedir = self._homedir / '.asciidoc' / 'themes' / 'qute'
+ self._tempdir = self._homedir / 'tmp'
+ self._tempdir.mkdir(parents=True)
+ self._themedir.mkdir(parents=True)
def cleanup(self):
"""Clean up the temporary home directory for asciidoc."""
@@ -76,12 +79,11 @@ class AsciiDoc:
def _build_docs(self):
"""Render .asciidoc files to .html sites."""
- files = [('doc/{}.asciidoc'.format(f),
- 'qutebrowser/html/doc/{}.html'.format(f))
+ files: List[Tuple[pathlib.Path, pathlib.Path]] = [(pathlib.Path('doc/{}.asciidoc'.format(f)),
+ DOC_DIR / (f + ".html"))
for f in self.FILES]
- for src in glob.glob('doc/help/*.asciidoc'):
- name, _ext = os.path.splitext(os.path.basename(src))
- dst = 'qutebrowser/html/doc/{}.html'.format(name)
+ for src in pathlib.Path('doc/help/').glob('*.asciidoc'):
+ dst = DOC_DIR / (src.stem + ".html")
files.append((src, dst))
# patch image links to use local copy
@@ -94,8 +96,7 @@ class AsciiDoc:
asciidoc_args = ['-a', 'source-highlighter=pygments']
for src, dst in files:
- src_basename = os.path.basename(src)
- modified_src = os.path.join(self._tempdir, src_basename)
+ modified_src = self._tempdir / src.name
with open(modified_src, 'w', encoding='utf-8') as modified_f, \
open(src, 'r', encoding='utf-8') as f:
for line in f:
@@ -189,7 +190,8 @@ class AsciiDoc:
def _build_website(self):
"""Prepare and build the website."""
- theme_file = os.path.abspath(os.path.join('www', 'qute.css'))
+ #theme_file = os.path.abspath(os.path.join('www', 'qute.css'))
+ theme_file = (pathlib.Path('www') / 'qute.css').resolve()
shutil.copy(theme_file, self._themedir)
outdir = self._website[0]
@@ -243,7 +245,7 @@ class AsciiDoc:
raise FileNotFoundError
- def call(self, src, dst, *args):
+ def call(self, src: pathlib.Path, dst: pathlib.Path, *args):
"""Call asciidoc for the given files.
Args:
@@ -251,15 +253,18 @@ class AsciiDoc:
dst: The destination .html file, or None to auto-guess.
*args: Additional arguments passed to asciidoc.
"""
- print("Calling asciidoc for {}...".format(os.path.basename(src)))
+ #print("Calling asciidoc for {}...".format(os.path.basename(src)))
+ src = pathlib.Path(src)
+ dst = pathlib.Path(dst)
+ print("Calling asciidoc for {}...".format(src.name))
cmdline = self._cmd[:]
if dst is not None:
- cmdline += ['--out-file', dst]
+ cmdline += ['--out-file', str(dst)]
cmdline += args
- cmdline.append(src)
+ cmdline.append(str(src))
try:
env = os.environ.copy()
- env['HOME'] = self._homedir
+ env['HOME'] = str(self._homedir)
subprocess.run(cmdline, check=True, env=env)
except (subprocess.CalledProcessError, OSError) as e:
self._failed = True
From c77757125499369a68263e06292e682f4efe86f3 Mon Sep 17 00:00:00 2001
From: Julin S <48789920+ju-sh@users.noreply.github.com>
Date: Sun, 15 Mar 2020 21:38:30 +0530
Subject: [PATCH 002/245] finish _build_website()
---
scripts/asciidoc2html.py | 20 ++++++++------------
1 file changed, 8 insertions(+), 12 deletions(-)
diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py
index 58e5da249..cd91f65c9 100755
--- a/scripts/asciidoc2html.py
+++ b/scripts/asciidoc2html.py
@@ -190,24 +190,20 @@ class AsciiDoc:
def _build_website(self):
"""Prepare and build the website."""
- #theme_file = os.path.abspath(os.path.join('www', 'qute.css'))
theme_file = (pathlib.Path('www') / 'qute.css').resolve()
shutil.copy(theme_file, self._themedir)
- outdir = self._website[0]
+ outdir = pathlib.Path(self._website[0])
- for root, _dirs, files in os.walk(os.getcwd()):
- for filename in files:
- basename, ext = os.path.splitext(filename)
- if (ext != '.asciidoc' or
- basename in ['header', 'OpenSans-License']):
- continue
- self._build_website_file(root, filename)
+ for item_path in pathlib.Path().rglob('*.asciidoc'):
+ if item_path.stem in ['header', 'OpenSans-License']:
+ continue
+ self._build_website_file(item_path.parent, item_path.name)
copy = {'icons': 'icons', 'doc/img': 'doc/img', 'www/media': 'media/'}
for src, dest in copy.items():
- full_dest = os.path.join(outdir, dest)
+ full_dest = outdir / dest
try:
shutil.rmtree(full_dest)
except FileNotFoundError:
@@ -216,9 +212,9 @@ class AsciiDoc:
for dst, link_name in [
('README.html', 'index.html'),
- (os.path.join('doc', 'quickstart.html'), 'quickstart.html')]:
+ ((pathlib.Path('doc') / 'quickstart.html'), 'quickstart.html')]:
try:
- os.symlink(dst, os.path.join(outdir, link_name))
+ (outdir / link_name).symlink_to(dst)
except FileExistsError:
pass
From e298992ec1b18c3fe516471a46f52b3a8c0bb174 Mon Sep 17 00:00:00 2001
From: Julin S <48789920+ju-sh@users.noreply.github.com>
Date: Sun, 15 Mar 2020 22:27:39 +0530
Subject: [PATCH 003/245] finish _build_website_file()
---
scripts/asciidoc2html.py | 47 ++++++++++++++++++++++------------------
1 file changed, 26 insertions(+), 21 deletions(-)
diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py
index cd91f65c9..fac0c4607 100755
--- a/scripts/asciidoc2html.py
+++ b/scripts/asciidoc2html.py
@@ -105,7 +105,7 @@ class AsciiDoc:
modified_f.write(line)
self.call(modified_src, dst, *asciidoc_args)
- def _copy_images(self):
+ def _copy_images(self) -> None:
"""Copy image files to qutebrowser/html/doc."""
print("Copying files...")
dst_path = os.path.join('qutebrowser', 'html', 'doc', 'img')
@@ -118,21 +118,29 @@ class AsciiDoc:
dst = os.path.join(dst_path, filename)
shutil.copy(src, dst)
- def _build_website_file(self, root, filename):
+ def _build_website_file(self, root: pathlib.Path, filename: str) -> None:
"""Build a single website file."""
- src = os.path.join(root, filename)
- src_basename = os.path.basename(src)
- parts = [self._website[0]]
- dirname = os.path.dirname(src)
- if dirname:
- parts.append(os.path.relpath(os.path.dirname(src)))
- parts.append(
- os.extsep.join((os.path.splitext(src_basename)[0],
- 'html')))
- dst = os.path.join(*parts)
- os.makedirs(os.path.dirname(dst), exist_ok=True)
+ #src = os.path.join(root, filename)
+ src = root / filename
+ src_basename = src.name
+ dst = pathlib.Path(self._website[0])
+ #parts = [self._website[0]]
+ dirname = src.parent
+ dst = src.parent.relative_to('.') / (src.stem + ".html")
+ dst.parent.mkdir(exist_ok=True)
+
+ #if len(dirname.parents) > 0:
+ # parts.append(os.path.relpath(dirname))
+ #parts.append(os.extsep.join(src.stem, 'html')) #WHY CAN'T WE MAKE IT A SIMPLE +???
+
+ #parts.append(
+ # os.extsep.join((os.path.splitext(src_basename)[0],
+ # 'html')))
+ #dst = os.path.join(*parts)
+ #os.makedirs(os.path.dirname(dst), exist_ok=True)
- modified_src = os.path.join(self._tempdir, src_basename)
+ #modified_src = os.path.join(self._tempdir, src.name)
+ modified_src = self._tempdir / src.name
shutil.copy('www/header.asciidoc', modified_src)
outfp = io.StringIO()
@@ -188,7 +196,7 @@ class AsciiDoc:
'-a', 'source-highlighter=pygments']
self.call(modified_src, dst, *asciidoc_args)
- def _build_website(self):
+ def _build_website(self) -> None:
"""Prepare and build the website."""
theme_file = (pathlib.Path('www') / 'qute.css').resolve()
shutil.copy(theme_file, self._themedir)
@@ -281,12 +289,9 @@ def parse_args():
return parser.parse_args()
-def run(**kwargs):
+def run(**kwargs) -> None:
"""Regenerate documentation."""
- try:
- os.mkdir('qutebrowser/html/doc')
- except FileExistsError:
- pass
+ DOC_DIR.mkdir(exist_ok=True)
asciidoc = AsciiDoc(**kwargs)
try:
@@ -303,7 +308,7 @@ def run(**kwargs):
asciidoc.cleanup()
-def main(colors=False):
+def main(colors: bool = False) -> None:
"""Generate html files for the online documentation."""
utils.change_cwd()
utils.use_color = colors
From 6fab7845f0ce3f77037d73a928fc2a973ddf12f5 Mon Sep 17 00:00:00 2001
From: Julin S <48789920+ju-sh@users.noreply.github.com>
Date: Sun, 15 Mar 2020 23:09:11 +0530
Subject: [PATCH 004/245] add pathlib to asciidoc2html script
---
scripts/asciidoc2html.py | 61 +++++++++++++++-------------------------
1 file changed, 23 insertions(+), 38 deletions(-)
diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py
index fac0c4607..ce00043fe 100755
--- a/scripts/asciidoc2html.py
+++ b/scripts/asciidoc2html.py
@@ -19,7 +19,7 @@
# along with qutebrowser. If not, see .
"""Generate the html documentation based on the asciidoc files."""
-from typing import Optional, List, Tuple
+from typing import List, Tuple
import re
import os
@@ -46,16 +46,20 @@ class AsciiDoc:
FILES = ['faq', 'changelog', 'contributing', 'quickstart', 'userscripts']
- def __init__(self, asciidoc: pathlib.Path, website: pathlib.Path) -> None:
+ def __init__(self, asciidoc, website) -> None:
+ """
+ asciidoc: Optional[List[str]]
+ website: Optional[List[str]])
+ """
self._cmd = None
- self._asciidoc: Optional[pathlib.Path] = asciidoc
- self._website: list = website
- self._homedir: Optional[pathlib.Path] = None
- self._themedir: Optional[pathlib.Path] = None
- self._tempdir: Optional[pathlib.Path] = None
- self._failed: bool = False
+ self._asciidoc = asciidoc
+ self._website = website
+ self._homedir = None
+ self._themedir = None
+ self._tempdir = None
+ self._failed = False
- def prepare(self):
+ def prepare(self) -> None:
"""Get the asciidoc command and create the homedir to use."""
self._cmd = self._get_asciidoc_cmd()
self._homedir = pathlib.Path(tempfile.mkdtemp())
@@ -64,12 +68,12 @@ class AsciiDoc:
self._tempdir.mkdir(parents=True)
self._themedir.mkdir(parents=True)
- def cleanup(self):
+ def cleanup(self) -> None:
"""Clean up the temporary home directory for asciidoc."""
if self._homedir is not None and not self._failed:
shutil.rmtree(self._homedir)
- def build(self):
+ def build(self) -> None:
"""Build either the website or the docs."""
if self._website:
self._build_website()
@@ -77,7 +81,7 @@ class AsciiDoc:
self._build_docs()
self._copy_images()
- def _build_docs(self):
+ def _build_docs(self) -> None:
"""Render .asciidoc files to .html sites."""
files: List[Tuple[pathlib.Path, pathlib.Path]] = [(pathlib.Path('doc/{}.asciidoc'.format(f)),
DOC_DIR / (f + ".html"))
@@ -108,38 +112,22 @@ class AsciiDoc:
def _copy_images(self) -> None:
"""Copy image files to qutebrowser/html/doc."""
print("Copying files...")
- dst_path = os.path.join('qutebrowser', 'html', 'doc', 'img')
- try:
- os.mkdir(dst_path)
- except FileExistsError:
- pass
+ dst_path = DOC_DIR / 'img'
+ dst_path.mkdir(exist_ok=True)
for filename in ['cheatsheet-big.png', 'cheatsheet-small.png']:
- src = os.path.join('doc', 'img', filename)
- dst = os.path.join(dst_path, filename)
+ src = pathlib.Path('doc') / 'img' / filename
+ dst = dst_path / filename
shutil.copy(src, dst)
def _build_website_file(self, root: pathlib.Path, filename: str) -> None:
"""Build a single website file."""
- #src = os.path.join(root, filename)
src = root / filename
src_basename = src.name
dst = pathlib.Path(self._website[0])
- #parts = [self._website[0]]
dirname = src.parent
dst = src.parent.relative_to('.') / (src.stem + ".html")
dst.parent.mkdir(exist_ok=True)
- #if len(dirname.parents) > 0:
- # parts.append(os.path.relpath(dirname))
- #parts.append(os.extsep.join(src.stem, 'html')) #WHY CAN'T WE MAKE IT A SIMPLE +???
-
- #parts.append(
- # os.extsep.join((os.path.splitext(src_basename)[0],
- # 'html')))
- #dst = os.path.join(*parts)
- #os.makedirs(os.path.dirname(dst), exist_ok=True)
-
- #modified_src = os.path.join(self._tempdir, src.name)
modified_src = self._tempdir / src.name
shutil.copy('www/header.asciidoc', modified_src)
@@ -222,11 +210,11 @@ class AsciiDoc:
('README.html', 'index.html'),
((pathlib.Path('doc') / 'quickstart.html'), 'quickstart.html')]:
try:
- (outdir / link_name).symlink_to(dst)
+ (outdir / link_name).symlink_to(dst) # mypy gives error here. Not sure why
except FileExistsError:
pass
- def _get_asciidoc_cmd(self):
+ def _get_asciidoc_cmd(self): # -> List[str]
"""Try to find out what commandline to use to invoke asciidoc."""
if self._asciidoc is not None:
return self._asciidoc
@@ -257,9 +245,6 @@ class AsciiDoc:
dst: The destination .html file, or None to auto-guess.
*args: Additional arguments passed to asciidoc.
"""
- #print("Calling asciidoc for {}...".format(os.path.basename(src)))
- src = pathlib.Path(src)
- dst = pathlib.Path(dst)
print("Calling asciidoc for {}...".format(src.name))
cmdline = self._cmd[:]
if dst is not None:
@@ -277,7 +262,7 @@ class AsciiDoc:
sys.exit(1)
-def parse_args():
+def parse_args() -> argparse.Namespace:
"""Parse command-line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument('--website', help="Build website into a given "
From 0d94278967b63630f7c50a719f765c636a1e7e44 Mon Sep 17 00:00:00 2001
From: Julin S <48789920+ju-sh@users.noreply.github.com>
Date: Sun, 19 Apr 2020 14:17:16 +0530
Subject: [PATCH 005/245] add type annotations to asciidoc2html script
---
scripts/asciidoc2html.py | 46 ++++++++++++++++++++--------------------
1 file changed, 23 insertions(+), 23 deletions(-)
diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py
index ce00043fe..cf1d2b200 100755
--- a/scripts/asciidoc2html.py
+++ b/scripts/asciidoc2html.py
@@ -19,14 +19,12 @@
# along with qutebrowser. If not, see .
"""Generate the html documentation based on the asciidoc files."""
-from typing import List, Tuple
-
+from typing import List, Tuple, Optional
import re
import os
import os.path
import sys
import subprocess
-import glob
import shutil
import tempfile
import argparse
@@ -46,17 +44,15 @@ class AsciiDoc:
FILES = ['faq', 'changelog', 'contributing', 'quickstart', 'userscripts']
- def __init__(self, asciidoc, website) -> None:
- """
- asciidoc: Optional[List[str]]
- website: Optional[List[str]])
- """
- self._cmd = None
+ def __init__(self,
+ asciidoc: Optional[List[str]],
+ website: Optional[str]) -> None:
+ self._cmd = None # type: Optional[List[str]]
self._asciidoc = asciidoc
self._website = website
- self._homedir = None
- self._themedir = None
- self._tempdir = None
+ self._homedir = None # type: Optional[pathlib.Path]
+ self._themedir = None # type: Optional[pathlib.Path]
+ self._tempdir = None # type: Optional[pathlib.Path]
self._failed = False
def prepare(self) -> None:
@@ -83,9 +79,8 @@ class AsciiDoc:
def _build_docs(self) -> None:
"""Render .asciidoc files to .html sites."""
- files: List[Tuple[pathlib.Path, pathlib.Path]] = [(pathlib.Path('doc/{}.asciidoc'.format(f)),
- DOC_DIR / (f + ".html"))
- for f in self.FILES]
+ files = [(pathlib.Path('doc/{}.asciidoc'.format(f)),
+ DOC_DIR / (f + ".html")) for f in self.FILES]
for src in pathlib.Path('doc/help/').glob('*.asciidoc'):
dst = DOC_DIR / (src.stem + ".html")
files.append((src, dst))
@@ -100,6 +95,7 @@ class AsciiDoc:
asciidoc_args = ['-a', 'source-highlighter=pygments']
for src, dst in files:
+ assert self._tempdir is not None # for mypy
modified_src = self._tempdir / src.name
with open(modified_src, 'w', encoding='utf-8') as modified_f, \
open(src, 'r', encoding='utf-8') as f:
@@ -122,12 +118,12 @@ class AsciiDoc:
def _build_website_file(self, root: pathlib.Path, filename: str) -> None:
"""Build a single website file."""
src = root / filename
- src_basename = src.name
- dst = pathlib.Path(self._website[0])
- dirname = src.parent
+ assert self._website is not None # for mypy
+ dst = pathlib.Path(self._website)
dst = src.parent.relative_to('.') / (src.stem + ".html")
dst.parent.mkdir(exist_ok=True)
-
+
+ assert self._tempdir is not None # for mypy
modified_src = self._tempdir / src.name
shutil.copy('www/header.asciidoc', modified_src)
@@ -187,9 +183,11 @@ class AsciiDoc:
def _build_website(self) -> None:
"""Prepare and build the website."""
theme_file = (pathlib.Path('www') / 'qute.css').resolve()
+ assert self._themedir is not None # for mypy
shutil.copy(theme_file, self._themedir)
- outdir = pathlib.Path(self._website[0])
+ assert self._website is not None # for mypy
+ outdir = pathlib.Path(self._website)
for item_path in pathlib.Path().rglob('*.asciidoc'):
if item_path.stem in ['header', 'OpenSans-License']:
@@ -209,12 +207,13 @@ class AsciiDoc:
for dst, link_name in [
('README.html', 'index.html'),
((pathlib.Path('doc') / 'quickstart.html'), 'quickstart.html')]:
+ assert isinstance(dst, (str, pathlib.Path)) # for mypy
try:
- (outdir / link_name).symlink_to(dst) # mypy gives error here. Not sure why
+ (outdir / link_name).symlink_to(dst)
except FileExistsError:
pass
- def _get_asciidoc_cmd(self): # -> List[str]
+ def _get_asciidoc_cmd(self) -> List[str]:
"""Try to find out what commandline to use to invoke asciidoc."""
if self._asciidoc is not None:
return self._asciidoc
@@ -246,6 +245,7 @@ class AsciiDoc:
*args: Additional arguments passed to asciidoc.
"""
print("Calling asciidoc for {}...".format(src.name))
+ assert self._cmd is not None # for mypy
cmdline = self._cmd[:]
if dst is not None:
cmdline += ['--out-file', str(dst)]
@@ -266,7 +266,7 @@ def parse_args() -> argparse.Namespace:
"""Parse command-line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument('--website', help="Build website into a given "
- "directory.", nargs=1)
+ "directory.")
parser.add_argument('--asciidoc', help="Full path to python and "
"asciidoc.py. If not given, it's searched in PATH.",
nargs=2, required=False,
From 9914e448dc2362d88a40765360963133194563cd Mon Sep 17 00:00:00 2001
From: Julin S <48789920+ju-sh@users.noreply.github.com>
Date: Sun, 19 Apr 2020 14:19:58 +0530
Subject: [PATCH 006/245] add blank line between module docstring and imports
---
scripts/asciidoc2html.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py
index cf1d2b200..e792792d9 100755
--- a/scripts/asciidoc2html.py
+++ b/scripts/asciidoc2html.py
@@ -19,6 +19,7 @@
# along with qutebrowser. If not, see .
"""Generate the html documentation based on the asciidoc files."""
+
from typing import List, Tuple, Optional
import re
import os
From 680d6ebb583bef77b9b28d0fede471a6efa0fdba Mon Sep 17 00:00:00 2001
From: Julin S <48789920+ju-sh@users.noreply.github.com>
Date: Sun, 19 Apr 2020 17:04:36 +0530
Subject: [PATCH 007/245] fix pylint error
---
scripts/asciidoc2html.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py
index e792792d9..fea106979 100755
--- a/scripts/asciidoc2html.py
+++ b/scripts/asciidoc2html.py
@@ -81,7 +81,7 @@ class AsciiDoc:
def _build_docs(self) -> None:
"""Render .asciidoc files to .html sites."""
files = [(pathlib.Path('doc/{}.asciidoc'.format(f)),
- DOC_DIR / (f + ".html")) for f in self.FILES]
+ DOC_DIR / (f + ".html")) for f in self.FILES]
for src in pathlib.Path('doc/help/').glob('*.asciidoc'):
dst = DOC_DIR / (src.stem + ".html")
files.append((src, dst))
From fd9e0f19c6153ab04bc5940212294e96f805fe5c Mon Sep 17 00:00:00 2001
From: Julin S <48789920+ju-sh@users.noreply.github.com>
Date: Mon, 20 Apr 2020 13:30:23 +0530
Subject: [PATCH 008/245] fix another pylint error
---
scripts/asciidoc2html.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py
index fea106979..90b94f014 100755
--- a/scripts/asciidoc2html.py
+++ b/scripts/asciidoc2html.py
@@ -20,7 +20,7 @@
"""Generate the html documentation based on the asciidoc files."""
-from typing import List, Tuple, Optional
+from typing import List, Optional
import re
import os
import os.path
@@ -207,7 +207,8 @@ class AsciiDoc:
for dst, link_name in [
('README.html', 'index.html'),
- ((pathlib.Path('doc') / 'quickstart.html'), 'quickstart.html')]:
+ ((pathlib.Path('doc') / 'quickstart.html'),
+ 'quickstart.html')]:
assert isinstance(dst, (str, pathlib.Path)) # for mypy
try:
(outdir / link_name).symlink_to(dst)
From fb19a2815d2eaec1dd5a58939863f50e2e0aecd8 Mon Sep 17 00:00:00 2001
From: svetelna
Date: Mon, 20 Apr 2020 15:42:04 +0200
Subject: [PATCH 009/245] implementing visual-line-mode on webengine side
---
qutebrowser/browser/browsertab.py | 24 ++++-
qutebrowser/browser/webengine/webenginetab.py | 17 +++-
qutebrowser/components/caretcommands.py | 10 +-
qutebrowser/config/configdata.yml | 1 +
qutebrowser/javascript/caret.js | 91 +++++++++++++++----
qutebrowser/mainwindow/statusbar/bar.py | 12 ++-
qutebrowser/mainwindow/tabbedbrowser.py | 2 +-
7 files changed, 127 insertions(+), 30 deletions(-)
diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py
index 00d46c813..16ddeef8b 100644
--- a/qutebrowser/browser/browsertab.py
+++ b/qutebrowser/browser/browsertab.py
@@ -427,13 +427,22 @@ class AbstractZoom(QObject):
self._set_factor_internal(self._zoom_factor)
+class SelectionState(enum.IntEnum):
+
+ """Possible states of selection in Caret mode."""
+
+ none = 1
+ normal = 2
+ line = 3
+
+
class AbstractCaret(QObject):
"""Attribute ``caret`` of AbstractTab for caret browsing."""
#: Signal emitted when the selection was toggled.
#: (argument - whether the selection is now active)
- selection_toggled = pyqtSignal(bool)
+ selection_toggled = pyqtSignal(SelectionState)
#: Emitted when a ``follow_selection`` action is done.
follow_selected_done = pyqtSignal()
@@ -444,7 +453,7 @@ class AbstractCaret(QObject):
super().__init__(parent)
self._tab = tab
self._widget = typing.cast(QWidget, None)
- self.selection_enabled = False
+ self.selection_state = SelectionState.none
self._mode_manager = mode_manager
mode_manager.entered.connect(self._on_mode_entered)
mode_manager.left.connect(self._on_mode_left)
@@ -500,7 +509,7 @@ class AbstractCaret(QObject):
def move_to_end_of_document(self) -> None:
raise NotImplementedError
- def toggle_selection(self) -> None:
+ def toggle_selection(self, line: bool = False) -> None:
raise NotImplementedError
def drop_selection(self) -> None:
@@ -826,6 +835,15 @@ class AbstractTabPrivate:
def shutdown(self) -> None:
raise NotImplementedError
+ def run_js_sync(self, code: str) -> None:
+ """Run javascript sync.
+
+ Result will be returned when running JS is complete.
+ This is only implemented for QtWebKit.
+ For QtWebEngine, always raises UnsupportedOperationError.
+ """
+ raise NotImplementedError
+
class AbstractTab(QWidget):
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index 647fa60ab..5b0721c18 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -369,7 +369,10 @@ class WebEngineCaret(browsertab.AbstractCaret):
if enabled is None:
log.webview.debug("Ignoring selection status None")
return
- self.selection_toggled.emit(enabled)
+ if enabled:
+ self.selection_toggled.emit(browsertab.SelectionState.normal)
+ else:
+ self.selection_toggled.emit(browsertab.SelectionState.none)
@pyqtSlot(usertypes.KeyMode)
def _on_mode_left(self, mode):
@@ -424,8 +427,9 @@ class WebEngineCaret(browsertab.AbstractCaret):
def move_to_end_of_document(self):
self._js_call('moveToEndOfDocument')
- def toggle_selection(self):
- self._js_call('toggleSelection', callback=self.selection_toggled.emit)
+ def toggle_selection(self, line=False):
+ self._js_call('toggleSelection', line,
+ callback=self._toggle_sel_translate)
def drop_selection(self):
self._js_call('dropSelection')
@@ -500,6 +504,10 @@ class WebEngineCaret(browsertab.AbstractCaret):
code = javascript.assemble('caret', command, *args)
self._tab.run_js_async(code, callback)
+ def _toggle_sel_translate(self, state_int):
+ state = browsertab.SelectionState(state_int)
+ self.selection_toggled.emit(state)
+
class WebEngineScroller(browsertab.AbstractScroller):
@@ -1231,6 +1239,9 @@ class WebEngineTabPrivate(browsertab.AbstractTabPrivate):
self._tab.action.exit_fullscreen()
self._widget.shutdown()
+ def run_js_sync(self, code):
+ raise browsertab.UnsupportedOperationError
+
class WebEngineTab(browsertab.AbstractTab):
diff --git a/qutebrowser/components/caretcommands.py b/qutebrowser/components/caretcommands.py
index 173653bd9..966b193de 100644
--- a/qutebrowser/components/caretcommands.py
+++ b/qutebrowser/components/caretcommands.py
@@ -185,9 +185,13 @@ def move_to_end_of_document(tab: apitypes.Tab) -> None:
@cmdutils.register(modes=[cmdutils.KeyMode.caret])
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
-def toggle_selection(tab: apitypes.Tab) -> None:
- """Toggle caret selection mode."""
- tab.caret.toggle_selection()
+def toggle_selection(tab: apitypes.Tab, line: bool = False) -> None:
+ """Toggle caret selection mode.
+
+ Args:
+ line: Enables line-selection.
+ """
+ tab.caret.toggle_selection(line)
@cmdutils.register(modes=[cmdutils.KeyMode.caret])
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 38db52304..df4efee06 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -3051,6 +3051,7 @@ bindings.default:
: leave-mode
caret:
v: toggle-selection
+ V: toggle-selection --line
: toggle-selection
: drop-selection
c: enter-mode normal
diff --git a/qutebrowser/javascript/caret.js b/qutebrowser/javascript/caret.js
index 55ff6a8b5..6d7a3bb00 100644
--- a/qutebrowser/javascript/caret.js
+++ b/qutebrowser/javascript/caret.js
@@ -705,6 +705,16 @@ window._qutebrowser.caret = (function() {
*/
CaretBrowsing.isCaretVisible = false;
+ /**
+ * selection modes
+ * @type {enum}
+ */
+ CaretBrowsing.SelectionState = {
+ "NONE": 1,
+ "NORMAL": 2,
+ "LINE": 3,
+ };
+
/**
* The actual caret element, an absolute-positioned flashing line.
* @type {Element}
@@ -887,7 +897,11 @@ window._qutebrowser.caret = (function() {
CaretBrowsing.injectCaretStyles();
CaretBrowsing.toggle();
CaretBrowsing.initiated = true;
- CaretBrowsing.selectionEnabled = selectionRange > 0;
+ if (selectionRange > 0) {
+ CaretBrowsing.selectionState = CaretBrowsing.SelectionState.NORMAL;
+ } else {
+ CaretBrowsing.selectionState = CaretBrowsing.SelectionState.NONE;
+ }
};
/**
@@ -1145,16 +1159,52 @@ window._qutebrowser.caret = (function() {
}
};
+ CaretBrowsing.reverseSelection = () => {
+ const sel = window.getSelection();
+ sel.setBaseAndExtent(
+ sel.extentNode, sel.extentOffset, sel.baseNode,
+ sel.baseOffset
+ );
+ };
+
+ CaretBrowsing.selectLine = function() {
+ const sel = window.getSelection();
+ sel.modify("extend", "right", "lineboundary");
+ CaretBrowsing.reverseSelection();
+ sel.modify("extend", "left", "lineboundary");
+ CaretBrowsing.reverseSelection();
+ };
+
+ CaretBrowsing.updateLineSelection = function(direction, granularity) {
+ if (!(granularity === "character") && !(granularity === "word")) {
+ window.
+ getSelection().
+ modify("extend", direction, granularity);
+ CaretBrowsing.selectLine();
+ }
+ };
+
+ CaretBrowsing.selectionEnabled = function() {
+ if (CaretBrowsing.selectionState === CaretBrowsing.SelectionState.NONE) {
+ return false;
+ }
+ return true;
+ };
+
CaretBrowsing.move = function(direction, granularity, count = 1) {
let action = "move";
- if (CaretBrowsing.selectionEnabled) {
+ if (CaretBrowsing.selectionState !== CaretBrowsing.SelectionState.NONE) {
action = "extend";
}
for (let i = 0; i < count; i++) {
- window.
- getSelection().
- modify(action, direction, granularity);
+ if (CaretBrowsing.selectionState === CaretBrowsing.SelectionState.LINE) {
+ CaretBrowsing.updateLineSelection(direction, granularity);
+ } else {
+ window.
+ getSelection().
+ modify(action, direction, granularity);
+ }
}
if (CaretBrowsing.isWindows &&
@@ -1174,7 +1224,7 @@ window._qutebrowser.caret = (function() {
CaretBrowsing.moveToBlock = function(paragraph, boundary, count = 1) {
let action = "move";
- if (CaretBrowsing.selectionEnabled) {
+ if (CaretBrowsing.selectionState !== CaretBrowsing.SelectionState.NONE) {
action = "extend";
}
for (let i = 0; i < count; i++) {
@@ -1185,6 +1235,10 @@ window._qutebrowser.caret = (function() {
window.
getSelection().
modify(action, boundary, "paragraphboundary");
+
+ if (CaretBrowsing.selectionState === CaretBrowsing.SelectionState.LINE) {
+ CaretBrowsing.selectLine();
+ }
}
};
@@ -1294,14 +1348,14 @@ window._qutebrowser.caret = (function() {
funcs.setInitialCursor = () => {
if (!CaretBrowsing.initiated) {
CaretBrowsing.setInitialCursor();
- return CaretBrowsing.selectionEnabled;
+ return CaretBrowsing.selectionEnabled();
}
if (window.getSelection().toString().length === 0) {
positionCaret();
}
CaretBrowsing.toggle();
- return CaretBrowsing.selectionEnabled;
+ return CaretBrowsing.selectionEnabled();
};
funcs.setFlags = (flags) => {
@@ -1399,17 +1453,22 @@ window._qutebrowser.caret = (function() {
funcs.getSelection = () => window.getSelection().toString();
- funcs.toggleSelection = () => {
- CaretBrowsing.selectionEnabled = !CaretBrowsing.selectionEnabled;
- return CaretBrowsing.selectionEnabled;
+ funcs.toggleSelection = (line) => {
+ if (line) {
+ CaretBrowsing.selectionState =
+ CaretBrowsing.SelectionState.LINE;
+ CaretBrowsing.selectLine();
+ CaretBrowsing.finishMove();
+ } else if (CaretBrowsing.selectionState === CaretBrowsing.SelectionState.NONE) {
+ CaretBrowsing.selectionState = CaretBrowsing.SelectionState.NORMAL;
+ } else {
+ CaretBrowsing.selectionState = CaretBrowsing.SelectionState.NONE;
+ }
+ return CaretBrowsing.selectionState;
};
funcs.reverseSelection = () => {
- const sel = window.getSelection();
- sel.setBaseAndExtent(
- sel.extentNode, sel.extentOffset, sel.baseNode,
- sel.baseOffset
- );
+ CaretBrowsing.reverseSelection();
};
return funcs;
diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py
index b1aa4da38..95022803d 100644
--- a/qutebrowser/mainwindow/statusbar/bar.py
+++ b/qutebrowser/mainwindow/statusbar/bar.py
@@ -372,13 +372,17 @@ class StatusBar(QWidget):
self.maybe_hide()
assert tab.is_private == self._color_flags.private
- @pyqtSlot(bool)
- def on_caret_selection_toggled(self, selection):
+ @pyqtSlot(browsertab.SelectionState)
+ def on_caret_selection_toggled(self, selection_state):
"""Update the statusbar when entering/leaving caret selection mode."""
- log.statusbar.debug("Setting caret selection {}".format(selection))
- if selection:
+ log.statusbar.debug("Setting caret selection {}"
+ .format(selection_state))
+ if selection_state is browsertab.SelectionState.normal:
self._set_mode_text("caret selection")
self._color_flags.caret = ColorFlags.CaretMode.selection
+ elif selection_state is browsertab.SelectionState.line:
+ self._set_mode_text("caret line selection")
+ self._color_flags.caret = ColorFlags.CaretMode.selection
else:
self._set_mode_text("caret")
self._color_flags.caret = ColorFlags.CaretMode.on
diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py
index f5dc3277b..e7756d321 100644
--- a/qutebrowser/mainwindow/tabbedbrowser.py
+++ b/qutebrowser/mainwindow/tabbedbrowser.py
@@ -189,7 +189,7 @@ class TabbedBrowser(QWidget):
cur_scroll_perc_changed = pyqtSignal(int, int)
cur_load_status_changed = pyqtSignal(usertypes.LoadStatus)
cur_fullscreen_requested = pyqtSignal(bool)
- cur_caret_selection_toggled = pyqtSignal(bool)
+ cur_caret_selection_toggled = pyqtSignal(browsertab.SelectionState)
close_window = pyqtSignal()
resized = pyqtSignal('QRect')
current_tab_changed = pyqtSignal(browsertab.AbstractTab)
From 90028d21d6ecf6d7c2271167f17bd264cec68eb7 Mon Sep 17 00:00:00 2001
From: svetelna
Date: Sun, 26 Apr 2020 13:55:18 +0200
Subject: [PATCH 010/245] adding webkit part of line selection
---
qutebrowser/browser/webkit/webkittab.py | 199 ++++++++++++++++--------
1 file changed, 137 insertions(+), 62 deletions(-)
diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py
index d1122b78e..0a60e073b 100644
--- a/qutebrowser/browser/webkit/webkittab.py
+++ b/qutebrowser/browser/webkit/webkittab.py
@@ -177,8 +177,11 @@ class WebKitCaret(browsertab.AbstractCaret):
if mode != usertypes.KeyMode.caret:
return
- self.selection_enabled = self._widget.hasSelection()
- self.selection_toggled.emit(self.selection_enabled)
+ if self._widget.hasSelection():
+ self.selection_state = browsertab.SelectionState.normal
+ else:
+ self.selection_state = browsertab.SelectionState.none
+ self.selection_toggled.emit(self.selection_state)
settings = self._widget.settings()
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True)
@@ -193,7 +196,7 @@ class WebKitCaret(browsertab.AbstractCaret):
#
# Note: We can't use hasSelection() here, as that's always
# true in caret mode.
- if not self.selection_enabled:
+ if self.selection_state is browsertab.SelectionState.none:
self._widget.page().currentFrame().evaluateJavaScript(
utils.read_file('javascript/position_caret.js'))
@@ -201,151 +204,190 @@ class WebKitCaret(browsertab.AbstractCaret):
def _on_mode_left(self, _mode):
settings = self._widget.settings()
if settings.testAttribute(QWebSettings.CaretBrowsingEnabled):
- if self.selection_enabled and self._widget.hasSelection():
+ if (self.selection_state is not
+ browsertab.SelectionState.none and
+ self._widget.hasSelection()):
# Remove selection if it exists
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False)
- self.selection_enabled = False
+ self.selection_state = browsertab.SelectionState.none
def move_to_next_line(self, count=1):
- if not self.selection_enabled:
- act = QWebPage.MoveToNextLine
- else:
+ if self.selection_state is not browsertab.SelectionState.none:
act = QWebPage.SelectNextLine
+ else:
+ act = QWebPage.MoveToNextLine
for _ in range(count):
self._widget.triggerPageAction(act)
+ if self.selection_state is browsertab.SelectionState.line:
+ self._select_line_to_end()
def move_to_prev_line(self, count=1):
- if not self.selection_enabled:
- act = QWebPage.MoveToPreviousLine
- else:
+ if self.selection_state is not browsertab.SelectionState.none:
act = QWebPage.SelectPreviousLine
+ else:
+ act = QWebPage.MoveToPreviousLine
for _ in range(count):
self._widget.triggerPageAction(act)
+ if self.selection_state is browsertab.SelectionState.line:
+ self._select_line_to_start()
def move_to_next_char(self, count=1):
- if not self.selection_enabled:
- act = QWebPage.MoveToNextChar
- else:
+ if self.selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectNextChar
+ elif self.selection_state is browsertab.SelectionState.line:
+ return
+ else:
+ act = QWebPage.MoveToNextChar
for _ in range(count):
self._widget.triggerPageAction(act)
def move_to_prev_char(self, count=1):
- if not self.selection_enabled:
- act = QWebPage.MoveToPreviousChar
- else:
+ if self.selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectPreviousChar
+ elif self.selection_state is browsertab.SelectionState.line:
+ return
+ else:
+ act = QWebPage.MoveToPreviousChar
for _ in range(count):
self._widget.triggerPageAction(act)
def move_to_end_of_word(self, count=1):
- if not self.selection_enabled:
- act = [QWebPage.MoveToNextWord]
- if utils.is_windows: # pragma: no cover
- act.append(QWebPage.MoveToPreviousChar)
- else:
+ if self.selection_state is browsertab.SelectionState.normal:
act = [QWebPage.SelectNextWord]
if utils.is_windows: # pragma: no cover
act.append(QWebPage.SelectPreviousChar)
+ elif self.selection_state is browsertab.SelectionState.line:
+ return
+ else:
+ act = [QWebPage.MoveToNextWord]
+ if utils.is_windows: # pragma: no cover
+ act.append(QWebPage.MoveToPreviousChar)
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
def move_to_next_word(self, count=1):
- if not self.selection_enabled:
- act = [QWebPage.MoveToNextWord]
- if not utils.is_windows: # pragma: no branch
- act.append(QWebPage.MoveToNextChar)
- else:
+ if self.selection_state is browsertab.SelectionState.normal:
act = [QWebPage.SelectNextWord]
if not utils.is_windows: # pragma: no branch
act.append(QWebPage.SelectNextChar)
+ elif self.selection_state is browsertab.SelectionState.line:
+ return
+ else:
+ act = [QWebPage.MoveToNextWord]
+ if not utils.is_windows: # pragma: no branch
+ act.append(QWebPage.MoveToNextChar)
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
def move_to_prev_word(self, count=1):
- if not self.selection_enabled:
- act = QWebPage.MoveToPreviousWord
- else:
+ if self.selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectPreviousWord
+ elif self.selection_state is browsertab.SelectionState.line:
+ return
+ else:
+ act = QWebPage.MoveToPreviousWord
for _ in range(count):
self._widget.triggerPageAction(act)
def move_to_start_of_line(self):
- if not self.selection_enabled:
- act = QWebPage.MoveToStartOfLine
- else:
+ if self.selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectStartOfLine
+ elif self.selection_state is browsertab.SelectionState.line:
+ return
+ else:
+ act = QWebPage.MoveToStartOfLine
self._widget.triggerPageAction(act)
def move_to_end_of_line(self):
- if not self.selection_enabled:
- act = QWebPage.MoveToEndOfLine
- else:
+ if self.selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectEndOfLine
+ elif self.selection_state is browsertab.SelectionState.line:
+ return
+ else:
+ act = QWebPage.MoveToEndOfLine
self._widget.triggerPageAction(act)
def move_to_start_of_next_block(self, count=1):
- if not self.selection_enabled:
- act = [QWebPage.MoveToNextLine,
- QWebPage.MoveToStartOfBlock]
- else:
+ if self.selection_state is not browsertab.SelectionState.none:
act = [QWebPage.SelectNextLine,
QWebPage.SelectStartOfBlock]
+ else:
+ act = [QWebPage.MoveToNextLine,
+ QWebPage.MoveToStartOfBlock]
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
+ if self.selection_state is browsertab.SelectionState.line:
+ self._select_line_to_end()
def move_to_start_of_prev_block(self, count=1):
- if not self.selection_enabled:
- act = [QWebPage.MoveToPreviousLine,
- QWebPage.MoveToStartOfBlock]
- else:
+ if self.selection_state is not browsertab.SelectionState.none:
act = [QWebPage.SelectPreviousLine,
QWebPage.SelectStartOfBlock]
+ else:
+ act = [QWebPage.MoveToPreviousLine,
+ QWebPage.MoveToStartOfBlock]
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
+ if self.selection_state is browsertab.SelectionState.line:
+ self._select_line_to_start()
def move_to_end_of_next_block(self, count=1):
- if not self.selection_enabled:
- act = [QWebPage.MoveToNextLine,
- QWebPage.MoveToEndOfBlock]
- else:
+ if self.selection_state is not browsertab.SelectionState.none:
act = [QWebPage.SelectNextLine,
QWebPage.SelectEndOfBlock]
+ else:
+ act = [QWebPage.MoveToNextLine,
+ QWebPage.MoveToEndOfBlock]
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
+ if self.selection_state is browsertab.SelectionState.line:
+ self._select_line_to_end()
def move_to_end_of_prev_block(self, count=1):
- if not self.selection_enabled:
- act = [QWebPage.MoveToPreviousLine, QWebPage.MoveToEndOfBlock]
- else:
+ if self.selection_state is not browsertab.SelectionState.none:
act = [QWebPage.SelectPreviousLine, QWebPage.SelectEndOfBlock]
+ else:
+ act = [QWebPage.MoveToPreviousLine, QWebPage.MoveToEndOfBlock]
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
+ if self.selection_state is browsertab.SelectionState.line:
+ self._select_line_to_start()
def move_to_start_of_document(self):
- if not self.selection_enabled:
- act = QWebPage.MoveToStartOfDocument
- else:
+ if self.selection_state is not browsertab.SelectionState.none:
act = QWebPage.SelectStartOfDocument
+ else:
+ act = QWebPage.MoveToStartOfDocument
self._widget.triggerPageAction(act)
+ if self.selection_state is browsertab.SelectionState.line:
+ self._select_line()
def move_to_end_of_document(self):
- if not self.selection_enabled:
- act = QWebPage.MoveToEndOfDocument
- else:
+ if self.selection_state is not browsertab.SelectionState.none:
act = QWebPage.SelectEndOfDocument
+ else:
+ act = QWebPage.MoveToEndOfDocument
self._widget.triggerPageAction(act)
- def toggle_selection(self):
- self.selection_enabled = not self.selection_enabled
- self.selection_toggled.emit(self.selection_enabled)
+ def toggle_selection(self, line=False):
+ if line:
+ self.selection_state = browsertab.SelectionState.line
+ self._select_line()
+ self.reverse_selection()
+ self._select_line()
+ self.reverse_selection()
+ elif self.selection_state is not browsertab.SelectionState.normal:
+ self.selection_state = browsertab.SelectionState.normal
+ else:
+ self.selection_state = browsertab.SelectionState.none
+ self.selection_toggled.emit(self.selection_state)
def drop_selection(self):
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
@@ -362,6 +404,35 @@ class WebKitCaret(browsertab.AbstractCaret):
);
}""")
+ def _select_line(self):
+ self._widget.triggerPageAction(QWebPage.SelectStartOfLine)
+ self.reverse_selection()
+ self._widget.triggerPageAction(QWebPage.SelectEndOfLine)
+ self.reverse_selection()
+
+ def _select_line_to_end(self):
+ # direction of selection (if anchor is to the left or right
+ # of focus) has to be checked before moving selection
+ # to the end of line
+ direction = self._js_selection_direction()
+ if direction:
+ self._widget.triggerPageAction(QWebPage.SelectEndOfLine)
+
+ def _select_line_to_start(self):
+ direction = self._js_selection_direction()
+ if not direction:
+ self._widget.triggerPageAction(QWebPage.SelectStartOfLine)
+
+ def _js_selection_direction(self):
+ # return true if selection's direction
+ # is left to right else false
+ return self._tab.private_api.run_js_sync("""
+ var sel = window.getSelection();
+ var position = sel.anchorNode.compareDocumentPosition(sel.focusNode);
+ (!position && sel.anchorOffset < sel.focusOffset ||
+ position === Node.DOCUMENT_POSITION_FOLLOWING);
+ """)
+
def _follow_selected(self, *, tab=False):
if QWebSettings.globalSettings().testAttribute(
QWebSettings.JavascriptEnabled):
@@ -693,6 +764,11 @@ class WebKitTabPrivate(browsertab.AbstractTabPrivate):
def shutdown(self):
self._widget.shutdown()
+ def run_js_sync(self, code):
+ document_element = self._widget.page().mainFrame().documentElement()
+ result = document_element.evaluateJavaScript(code)
+ return result
+
class WebKitTab(browsertab.AbstractTab):
@@ -751,8 +827,7 @@ class WebKitTab(browsertab.AbstractTab):
def run_js_async(self, code, callback=None, *, world=None):
if world is not None and world != usertypes.JsWorld.jseval:
log.webview.warning("Ignoring world ID {}".format(world))
- document_element = self._widget.page().mainFrame().documentElement()
- result = document_element.evaluateJavaScript(code)
+ result = self.private_api.run_js_sync(code)
if callback is not None:
callback(result)
From 7db846e6443ef3f8955a554dc92e94cd5f88657c Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 27 Apr 2020 17:45:31 +0200
Subject: [PATCH 011/245] Harden caret tests and always show window
With newer Qt versions, it looks like the caret window always needs to be shown
for the selection to work correctly. However, the test silently passed when no
selection was available.
---
tests/unit/browser/test_caret.py | 26 ++++++--------------------
1 file changed, 6 insertions(+), 20 deletions(-)
diff --git a/tests/unit/browser/test_caret.py b/tests/unit/browser/test_caret.py
index 9b817c4ac..fb940f7c0 100644
--- a/tests/unit/browser/test_caret.py
+++ b/tests/unit/browser/test_caret.py
@@ -29,6 +29,8 @@ from qutebrowser.utils import utils, qtutils, usertypes
@pytest.fixture
def caret(web_tab, qtbot, mode_manager):
+ web_tab.container.expose()
+
with qtbot.wait_signal(web_tab.load_finished):
web_tab.load_url(QUrl('qute://testdata/data/caret.html'))
@@ -61,9 +63,13 @@ class Selection:
selection = selection.strip()
assert selection == expected
return
+ elif not selection and not expected:
+ return
self._qtbot.wait(50)
+ assert False, 'Failed to get selection!'
+
def check_multiline(self, expected, *, strip=False):
self.check(textwrap.dedent(expected).strip(), strip=strip)
@@ -287,17 +293,6 @@ def test_drop_selection(caret, selection):
class TestSearch:
- @pytest.fixture(autouse=True)
- def expose(self, web_tab):
- """Expose the web view if needed.
-
- With QtWebEngine 5.13 on macOS/Windows, searching fails (callback
- called with False) when the view isn't exposed.
- """
- if qtutils.version_check('5.13') and not utils.is_linux:
- web_tab.container.expose()
- web_tab.show()
-
# https://bugreports.qt.io/browse/QTBUG-60673
@pytest.mark.qtbug60673
@@ -340,15 +335,6 @@ class TestFollowSelected:
def toggle_js(self, request, config_stub):
config_stub.val.content.javascript.enabled = request.param
- @pytest.fixture(autouse=True)
- def expose(self, web_tab):
- """Expose the web view if needed.
-
- On QtWebKit, or Qt < 5.11 and > 5.12 on QtWebEngine, we need to
- show the tab for selections to work properly.
- """
- web_tab.container.expose()
-
def test_follow_selected_without_a_selection(self, qtbot, caret, selection, web_tab,
mode_manager):
caret.move_to_next_word() # Move cursor away from the link
From e0a9088f6395fe791568dff8c157fb06d208165f Mon Sep 17 00:00:00 2001
From: svetelna
Date: Tue, 28 Apr 2020 17:45:34 +0200
Subject: [PATCH 012/245] added tests
---
tests/unit/browser/test_caret.py | 93 ++++++++++++++++++++++++++++++--
1 file changed, 90 insertions(+), 3 deletions(-)
diff --git a/tests/unit/browser/test_caret.py b/tests/unit/browser/test_caret.py
index fb940f7c0..e5ab90708 100644
--- a/tests/unit/browser/test_caret.py
+++ b/tests/unit/browser/test_caret.py
@@ -24,7 +24,7 @@ import textwrap
import pytest
from PyQt5.QtCore import QUrl
-from qutebrowser.utils import utils, qtutils, usertypes
+from qutebrowser.utils import usertypes
@pytest.fixture
@@ -73,9 +73,9 @@ class Selection:
def check_multiline(self, expected, *, strip=False):
self.check(textwrap.dedent(expected).strip(), strip=strip)
- def toggle(self):
+ def toggle(self, line=False):
with self._qtbot.wait_signal(self._caret.selection_toggled):
- self._caret.toggle_selection()
+ self._caret.toggle_selection(line=line)
@pytest.fixture
@@ -391,3 +391,90 @@ class TestReverse:
caret.reverse_selection()
caret.move_to_start_of_line()
selection.check("one two three")
+
+
+class TestLineSelection:
+
+ def test_toggle(self, caret, selection):
+ selection.toggle(True)
+ selection.check("one two three")
+
+ def test_toggle_untoggle(self, caret, selection):
+ selection.toggle()
+ selection.check("")
+ selection.toggle(True)
+ selection.check("one two three")
+ selection.toggle()
+ selection.check("one two three")
+
+ def test_from_center(self, caret, selection):
+ caret.move_to_next_char(4)
+ selection.toggle(True)
+ selection.check("one two three")
+
+ def test_more_lines(self, caret, selection):
+ selection.toggle(True)
+ caret.move_to_next_line(2)
+ selection.check_multiline("""
+ one two three
+ eins zwei drei
+
+ four five six
+ """, strip=True)
+
+ def test_not_selecting_char(self, caret, selection):
+ selection.toggle(True)
+ caret.move_to_next_char()
+ selection.check("one two three")
+ caret.move_to_prev_char()
+ selection.check("one two three")
+
+ def test_selecting_prev_next_word(self, caret, selection):
+ selection.toggle(True)
+ caret.move_to_next_word()
+ selection.check("one two three")
+ caret.move_to_prev_word()
+ selection.check("one two three")
+
+ def test_selecting_end_word(self, caret, selection):
+ selection.toggle(True)
+ caret.move_to_end_of_word()
+ selection.check("one two three")
+
+ def test_selecting_prev_next_line(self, caret, selection):
+ selection.toggle(True)
+ caret.move_to_next_line()
+ selection.check_multiline("""
+ one two three
+ eins zwei drei
+ """, strip=True)
+ caret.move_to_prev_line()
+ selection.check("one two three")
+
+ def test_not_selecting_start_end_line(self, caret, selection):
+ selection.toggle(True)
+ caret.move_to_end_of_line()
+ selection.check("one two three")
+ caret.move_to_start_of_line()
+ selection.check("one two three")
+
+ def test_selecting_block(self, caret, selection):
+ selection.toggle(True)
+ caret.move_to_end_of_next_block()
+ selection.check_multiline("""
+ one two three
+ eins zwei drei
+ """, strip=True)
+
+ def test_selecting_start_end_document(self, caret, selection):
+ selection.toggle(True)
+ caret.move_to_end_of_document()
+ selection.check_multiline("""
+ one two three
+ eins zwei drei
+
+ four five six
+ vier fünf sechs
+ """, strip=True)
+ caret.move_to_start_of_document()
+ selection.check("one two three")
From 31af9cc54b835064ddd1eb12ab38293e1eb902a1 Mon Sep 17 00:00:00 2001
From: svetelna
Date: Mon, 11 May 2020 10:48:43 +0200
Subject: [PATCH 013/245] removed selectionEnabled function in caret.js
---
qutebrowser/javascript/caret.js | 11 ++---------
1 file changed, 2 insertions(+), 9 deletions(-)
diff --git a/qutebrowser/javascript/caret.js b/qutebrowser/javascript/caret.js
index 6d7a3bb00..51a1cf7cf 100644
--- a/qutebrowser/javascript/caret.js
+++ b/qutebrowser/javascript/caret.js
@@ -1184,13 +1184,6 @@ window._qutebrowser.caret = (function() {
}
};
- CaretBrowsing.selectionEnabled = function() {
- if (CaretBrowsing.selectionState === CaretBrowsing.SelectionState.NONE) {
- return false;
- }
- return true;
- };
-
CaretBrowsing.move = function(direction, granularity, count = 1) {
let action = "move";
if (CaretBrowsing.selectionState !== CaretBrowsing.SelectionState.NONE) {
@@ -1348,14 +1341,14 @@ window._qutebrowser.caret = (function() {
funcs.setInitialCursor = () => {
if (!CaretBrowsing.initiated) {
CaretBrowsing.setInitialCursor();
- return CaretBrowsing.selectionEnabled();
+ return CaretBrowsing.selectionState !== CaretBrowsing.SelectionState.NONE;
}
if (window.getSelection().toString().length === 0) {
positionCaret();
}
CaretBrowsing.toggle();
- return CaretBrowsing.selectionEnabled();
+ return CaretBrowsing.selectionState !== CaretBrowsing.SelectionState.NONE;
};
funcs.setFlags = (flags) => {
From 5a0497a087f9fda0883930d1beda537a540ba73b Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Sun, 17 May 2020 07:01:06 -0400
Subject: [PATCH 014/245] Add loglevel config settings.
Log levels can now be configured in config.py by setting
loggin.level.console (for stdout/stderr) and logging.level.ram (for
:messages).
This is of interest for users who would like password-manager
integration that uses `fake-key` to send sensitive strings to websites.
The default 'debug' ram logging would store various logs containing the
sensitive data.
Previously the loglevel was only configurable through CLI flags, and
those only affected the console loglevel. We felt a config value would
be nicer than just adding another flag for RAM loglevel, requiring you
to create an alias for qutebrowser and ensure _that_ alias gets used
anywhere qutebrowser might be invoked.
Logging is initialized before the config is loaded, so configuration-set
loglevels have to be loaded in a second, later stage. However, this
stage will only set the console loglevel if it wasn't set on the CLI, as
a CLI flag feels like a more explicit action that should override a
config.
Fixes #5286.
---
qutebrowser/app.py | 2 ++
qutebrowser/config/configdata.yml | 23 +++++++++++++++++++++++
qutebrowser/qutebrowser.py | 2 +-
qutebrowser/utils/log.py | 26 +++++++++++++++++++++++++-
tests/unit/utils/test_log.py | 24 ++++++++++++++++++++++++
5 files changed, 75 insertions(+), 2 deletions(-)
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index 6d01e0ddd..b89fa4b37 100644
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -379,6 +379,8 @@ def _init_modules(*, args):
Args:
args: The argparse namespace.
"""
+ log.init.debug("Initializing logging from config...")
+ log.init_from_config(config.val)
log.init.debug("Initializing save manager...")
save_manager = savemanager.SaveManager(q_app)
objreg.register('save-manager', save_manager)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 393ae8ac0..98ec76fd7 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -3194,3 +3194,26 @@ bindings.commands:
* register: Entered when qutebrowser is waiting for a register name/key for
commands like `:set-mark`.
+
+## logging
+
+logging.level.ram:
+ default: null
+ type:
+ name: String
+ none_ok: true
+ # levels match those in qutebrowser/utils/log.py
+ valid_values: ['VDEBUG', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
+ desc:
+ Level for in-memory logs.
+
+logging.level.console:
+ default: null
+ type:
+ name: String
+ none_ok: true
+ # levels match those in qutebrowser/utils/log.py
+ valid_values: ['VDEBUG', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
+ desc: >-
+ Level for console (stdout/stderr) logs.
+ Ignored if the --loglevel or --debug CLI flags are used.
diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py
index 0ffdb5567..3369c1ebe 100644
--- a/qutebrowser/qutebrowser.py
+++ b/qutebrowser/qutebrowser.py
@@ -96,7 +96,7 @@ def get_argparser():
debug = parser.add_argument_group('debug arguments')
debug.add_argument('-l', '--loglevel', dest='loglevel',
- help="Set loglevel", default='info',
+ help="Override the configured console loglevel",
choices=['critical', 'error', 'warning', 'info',
'debug', 'vdebug'])
debug.add_argument('--logfilter', type=logfilter_error,
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 2718f10ba..6cf204b5d 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -176,7 +176,7 @@ def stub(suffix: str = '') -> None:
def init_log(args: argparse.Namespace) -> None:
"""Init loggers based on the argparse namespace passed."""
- level = args.loglevel.upper()
+ level = (args.loglevel or "info").upper()
try:
numeric_level = getattr(logging, level)
except AttributeError:
@@ -526,6 +526,30 @@ def hide_qt_warning(pattern: str, logger: str = 'qt') -> typing.Iterator[None]:
logger_obj.removeFilter(log_filter)
+def init_from_config(conf: typing.Any) -> None:
+ """Initialize logging settings from the config.
+
+ init_log is called before the config module is initialized, so config-based
+ initialization cannot be performed there.
+
+ Args:
+ conf: The global ConfigContainer.
+ This is passed rather than accessed via the module to avoid a
+ cyclic import.
+ """
+ ramlevel = conf.logging.level.ram
+ consolelevel = conf.logging.level.console
+ if ramlevel and ram_handler:
+ init.info("Configuring RAM loglevel to %s", ramlevel)
+ ram_handler.setLevel(LOG_LEVELS[ramlevel])
+ if consolelevel and console_handler:
+ if _args and _args.loglevel:
+ init.info("--loglevel flag overrides logging.level.console")
+ else:
+ init.info("Configuring console loglevel to %s", consolelevel)
+ console_handler.setLevel(LOG_LEVELS[consolelevel])
+
+
class QtWarningFilter(logging.Filter):
"""Filter to filter Qt warnings.
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index a74d81600..45543a136 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -255,6 +255,30 @@ class TestInitLog:
warnings.warn("test warning", PendingDeprecationWarning)
+@pytest.mark.parametrize(
+ 'console_cli,console_conf,console_expected,ram_conf,ram_expected',
+ [
+ (None, None, logging.INFO, None, logging.NOTSET),
+ (None, None, logging.INFO, 'CRITICAL', logging.CRITICAL),
+ (None, 'WARNING', logging.WARNING, 'INFO', logging.INFO),
+ ('INFO', 'WARNING', logging.INFO, 'VDEBUG', logging.VDEBUG),
+ ('WARNING', 'INFO', logging.WARNING, 'CRITICAL', logging.CRITICAL),
+ ])
+def test_init_from_config(mocker, console_cli, console_conf, console_expected,
+ ram_conf, ram_expected):
+ args = argparse.Namespace(debug=False, loglevel=console_cli, color=True,
+ loglines=10, logfilter="", force_color=False,
+ json_logging=False, debug_flags=set())
+ log.init_log(args)
+
+ conf = mocker.Mock()
+ conf.logging.level.ram = ram_conf
+ conf.logging.level.console = console_conf
+ log.init_from_config(conf)
+ assert log.ram_handler.level == ram_expected
+ assert log.console_handler.level == console_expected
+
+
class TestHideQtWarning:
"""Tests for hide_qt_warning/QtWarningFilter."""
From 9f1f9e5a40769c8bc9928c287574d5f92858c5d7 Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Tue, 19 May 2020 22:45:07 -0400
Subject: [PATCH 015/245] PR feedback for loglevel config.
- Use conditional import to enable typechecking without cyclic import
- Use asserts to prove optionals are non-null
- Use args and config_stub fixtures
---
qutebrowser/utils/log.py | 14 ++++++++----
tests/unit/utils/test_log.py | 42 +++++++++++++++++-------------------
2 files changed, 30 insertions(+), 26 deletions(-)
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 6cf204b5d..20a3dd4fb 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -41,6 +41,9 @@ try:
except ImportError:
colorama = None
+if typing.TYPE_CHECKING:
+ from qutebrowser.config import config as configmodule
+
_log_inited = False
_args = None
@@ -526,7 +529,7 @@ def hide_qt_warning(pattern: str, logger: str = 'qt') -> typing.Iterator[None]:
logger_obj.removeFilter(log_filter)
-def init_from_config(conf: typing.Any) -> None:
+def init_from_config(conf: 'configmodule.ConfigContainer') -> None:
"""Initialize logging settings from the config.
init_log is called before the config module is initialized, so config-based
@@ -537,13 +540,16 @@ def init_from_config(conf: typing.Any) -> None:
This is passed rather than accessed via the module to avoid a
cyclic import.
"""
+ assert ram_handler is not None
+ assert console_handler is not None
+ assert _args is not None
ramlevel = conf.logging.level.ram
consolelevel = conf.logging.level.console
- if ramlevel and ram_handler:
+ if ramlevel:
init.info("Configuring RAM loglevel to %s", ramlevel)
ram_handler.setLevel(LOG_LEVELS[ramlevel])
- if consolelevel and console_handler:
- if _args and _args.loglevel:
+ if consolelevel:
+ if _args.loglevel:
init.info("--loglevel flag overrides logging.level.console")
else:
init.info("Configuring console loglevel to %s", consolelevel)
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index 45543a136..c8b778149 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -254,29 +254,27 @@ class TestInitLog:
with pytest.raises(PendingDeprecationWarning):
warnings.warn("test warning", PendingDeprecationWarning)
+ @pytest.mark.parametrize(
+ 'console_cli, console_conf, console_expected, ram_conf, ram_expected',
+ [
+ (None, None, logging.INFO, None, logging.NOTSET),
+ (None, None, logging.INFO, 'CRITICAL', logging.CRITICAL),
+ (None, 'WARNING', logging.WARNING, 'INFO', logging.INFO),
+ ('INFO', 'WARNING', logging.INFO, 'VDEBUG', logging.VDEBUG),
+ ('WARNING', 'INFO', logging.WARNING, 'CRITICAL', logging.CRITICAL),
+ ])
+ def test_init_from_config(self, mocker, console_cli, console_conf,
+ console_expected, ram_conf, ram_expected, args,
+ config_stub):
+ args.debug = False
+ args.loglevel = console_cli
+ log.init_log(args)
-@pytest.mark.parametrize(
- 'console_cli,console_conf,console_expected,ram_conf,ram_expected',
- [
- (None, None, logging.INFO, None, logging.NOTSET),
- (None, None, logging.INFO, 'CRITICAL', logging.CRITICAL),
- (None, 'WARNING', logging.WARNING, 'INFO', logging.INFO),
- ('INFO', 'WARNING', logging.INFO, 'VDEBUG', logging.VDEBUG),
- ('WARNING', 'INFO', logging.WARNING, 'CRITICAL', logging.CRITICAL),
- ])
-def test_init_from_config(mocker, console_cli, console_conf, console_expected,
- ram_conf, ram_expected):
- args = argparse.Namespace(debug=False, loglevel=console_cli, color=True,
- loglines=10, logfilter="", force_color=False,
- json_logging=False, debug_flags=set())
- log.init_log(args)
-
- conf = mocker.Mock()
- conf.logging.level.ram = ram_conf
- conf.logging.level.console = console_conf
- log.init_from_config(conf)
- assert log.ram_handler.level == ram_expected
- assert log.console_handler.level == console_expected
+ config_stub.val.logging.level.ram = ram_conf
+ config_stub.val.logging.level.console = console_conf
+ log.init_from_config(config_stub.val)
+ assert log.ram_handler.level == ram_expected
+ assert log.console_handler.level == console_expected
class TestHideQtWarning:
From ea9a5395487c0ac6120ec6012de1f6e9eef5d3df Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Tue, 19 May 2020 23:03:03 -0400
Subject: [PATCH 016/245] Add custom configtype for loglevel.
Avoids the need to duplicate the log level strings.
---
qutebrowser/config/configdata.yml | 8 ++------
qutebrowser/config/configtypes.py | 11 ++++++++++-
2 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 98ec76fd7..d7988f571 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -3200,20 +3200,16 @@ bindings.commands:
logging.level.ram:
default: null
type:
- name: String
+ name: LogLevel
none_ok: true
- # levels match those in qutebrowser/utils/log.py
- valid_values: ['VDEBUG', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
desc:
Level for in-memory logs.
logging.level.console:
default: null
type:
- name: String
+ name: LogLevel
none_ok: true
- # levels match those in qutebrowser/utils/log.py
- valid_values: ['VDEBUG', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
desc: >-
Level for console (stdout/stderr) logs.
Ignored if the --loglevel or --debug CLI flags are used.
diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py
index 6eec13293..7927e0425 100644
--- a/qutebrowser/config/configtypes.py
+++ b/qutebrowser/config/configtypes.py
@@ -63,7 +63,7 @@ from PyQt5.QtNetwork import QNetworkProxy
from qutebrowser.misc import objects, debugcachestats
from qutebrowser.config import configexc, configutils
from qutebrowser.utils import (standarddir, utils, qtutils, urlutils, urlmatch,
- usertypes)
+ usertypes, log)
from qutebrowser.keyinput import keyutils
from qutebrowser.browser.network import pac
@@ -2014,6 +2014,15 @@ class NewTabPosition(String):
('last', "At the end."))
+class LogLevel(String):
+
+ """Log level."""
+
+ def __init__(self, none_ok: bool = False) -> None:
+ super().__init__(none_ok=none_ok)
+ self.valid_values = ValidValues(*log.LOG_LEVELS.keys())
+
+
class Key(BaseType):
"""A name of a key."""
From da5d153dcd453263f59a80259ebda69b623a480c Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 16:02:26 +0200
Subject: [PATCH 017/245] Add workaround for missing QWebEngineFindTextResult
See #5412 and
https://www.riverbankcomputing.com/pipermail/pyqt/2020-May/042892.html
---
doc/changelog.asciidoc | 2 ++
qutebrowser/browser/webengine/webenginetab.py | 19 ++++++++++++++++---
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 001089247..776b35e0e 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -59,6 +59,8 @@ Fixed
the main thread), qutebrowser did crash or freeze when trying to show its
exception handler. This is now fixed.
- `:inspector` now works correctly when cookies are disabled globally.
+- Added workaround for a (Gentoo?) PyQt/packaging issue related to the
+ `QWebEngineFindTextResult` handling added in v1.11.0.
v1.11.1 (2020-05-07)
--------------------
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index 2f16b6ae3..b83a8b2ba 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -183,9 +183,22 @@ class _WebEngineSearchWrapHandler:
Args:
page: The QtWebEnginePage to connect to this handler.
"""
- if qtutils.version_check("5.14"):
- page.findTextFinished.connect(self._store_match_data)
- self._nowrap_available = True
+ if not qtutils.version_check("5.14"):
+ return
+
+ try:
+ from PyQt5.QtWebEngineCore import QWebEngineFindTextResult
+ except ImportError:
+ # WORKAROUND for some odd PyQt/packaging bug where the
+ # findTextResult signal is available, but QWebEngineFindTextResult
+ # is not. Seems to happen on e.g. Gentoo.
+ log.webview.warn("Could not import QWebEngineFindTextResult "
+ "despite running on Qt 5.14. You might need to "
+ "rebuild PyQtWebEngine.")
+ return
+
+ page.findTextFinished.connect(self._store_match_data)
+ self._nowrap_available = True
def _store_match_data(self, result):
"""Store information on the last match.
From abe16d5f06407805f7cfda21270cff5b306f9be5 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 16:49:23 +0200
Subject: [PATCH 018/245] Fix lint
---
qutebrowser/browser/webengine/webenginetab.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index b83a8b2ba..84d35211a 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -187,14 +187,15 @@ class _WebEngineSearchWrapHandler:
return
try:
+ # pylint: disable=unused-import
from PyQt5.QtWebEngineCore import QWebEngineFindTextResult
except ImportError:
# WORKAROUND for some odd PyQt/packaging bug where the
# findTextResult signal is available, but QWebEngineFindTextResult
# is not. Seems to happen on e.g. Gentoo.
- log.webview.warn("Could not import QWebEngineFindTextResult "
- "despite running on Qt 5.14. You might need to "
- "rebuild PyQtWebEngine.")
+ log.webview.warning("Could not import QWebEngineFindTextResult "
+ "despite running on Qt 5.14. You might need "
+ "to rebuild PyQtWebEngine.")
return
page.findTextFinished.connect(self._store_match_data)
From bae1431703cf53032f14c8c92a701309fe8a6a19 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 17:12:51 +0200
Subject: [PATCH 019/245] src2asciidoc: Make sure usage lines are generated
with fixed width
Due to a change in Python 3.8, the output depended on the calling terminal's
width. Set a fixed with of 200 (rather than 80) so that we always have the
expanded version for the generated documentation.
See #5393 and
https://github.com/python/cpython/commit/74102c9a5f2327c4fc47feefa072854a53551d1f#diff-837b312b1f3508216ace6adb46492836
---
doc/help/commands.asciidoc | 17 +++++------------
scripts/dev/src2asciidoc.py | 5 +++++
2 files changed, 10 insertions(+), 12 deletions(-)
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index 7c8da37a8..a6a5c3e87 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -286,8 +286,7 @@ Set all settings back to their default.
[[config-cycle]]
=== config-cycle
-Syntax: +:config-cycle [*--pattern* 'pattern'] [*--temp*] [*--print*]
- 'option' ['values' ['values' ...]]+
+Syntax: +:config-cycle [*--pattern* 'pattern'] [*--temp*] [*--print*] 'option' ['values' ['values' ...]]+
Cycle an option between multiple values.
@@ -608,8 +607,7 @@ Show help about a command or setting.
[[hint]]
=== hint
-Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*] [*--first*]
- ['group'] ['target'] ['args' ['args' ...]]+
+Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*] [*--first*] ['group'] ['target'] ['args' ['args' ...]]+
Start hinting.
@@ -866,8 +864,7 @@ Do nothing.
[[open]]
=== open
-Syntax: +:open [*--related*] [*--bg*] [*--tab*] [*--window*] [*--secure*] [*--private*]
- ['url']+
+Syntax: +:open [*--related*] [*--bg*] [*--tab*] [*--window*] [*--secure*] [*--private*] ['url']+
Open a URL in the current/[count]th tab.
@@ -1197,9 +1194,7 @@ Load a session.
[[session-save]]
=== session-save
-Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] [*--only-active-window*]
- [*--with-private*]
- ['name']+
+Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] [*--only-active-window*] [*--with-private*] ['name']+
Save a session.
@@ -1263,9 +1258,7 @@ Set a mark at the current scroll position in the current tab.
[[spawn]]
=== spawn
-Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output*] [*--output-messages*]
- [*--detach*]
- 'cmdline'+
+Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output*] [*--output-messages*] [*--detach*] 'cmdline'+
Spawn an external command.
diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py
index 51f4a3633..70df0ebe0 100755
--- a/scripts/dev/src2asciidoc.py
+++ b/scripts/dev/src2asciidoc.py
@@ -59,6 +59,11 @@ class UsageFormatter(argparse.HelpFormatter):
argparse.HelpFormatter while copying 99% of the code :-/
"""
+ def __init__(self, prog, indent_increment=2, max_help_position=24,
+ width=200):
+ """Override __init__ to set a fixed width as default."""
+ super().__init__(prog, indent_increment, max_help_position, width)
+
def _format_usage(self, usage, actions, groups, _prefix):
"""Override _format_usage to not add the 'usage:' prefix."""
return super()._format_usage(usage, actions, groups, '')
From 12a6590364f69d627cddcbe48cc675593827e4c4 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 17:16:34 +0200
Subject: [PATCH 020/245] Add Ctrl+I to bindings.key_mappings
See #5455
---
doc/changelog.asciidoc | 1 +
doc/help/settings.asciidoc | 1 +
qutebrowser/config/configdata.yml | 1 +
3 files changed, 3 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 776b35e0e..7a97507e5 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -49,6 +49,7 @@ Changed
the exact dependencies listed in
`misc/requirements/requirements-tests.txt{,-raw}` is supported.
- The `:tab-focus` command now has completion for tabs in the current window.
+- The `bindings.key_mappings` setting now maps `` to the tab key by default.
Fixed
~~~~~
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index cf6ee2069..8548d834a 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -739,6 +739,7 @@ Default:
- +pass:[<Ctrl-6>]+: +pass:[<Ctrl-^>]+
- +pass:[<Ctrl-Enter>]+: +pass:[<Ctrl-Return>]+
+- +pass:[<Ctrl-I>]+: +pass:[<Tab>]+
- +pass:[<Ctrl-J>]+: +pass:[<Return>]+
- +pass:[<Ctrl-M>]+: +pass:[<Return>]+
- +pass:[<Ctrl-[>]+: +pass:[<Escape>]+
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index b231fccb4..660bd661c 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -2799,6 +2799,7 @@ bindings.key_mappings:
:
:
:
+ :
:
:
:
From 9b8767b64eea1498e383ae9ef790cb377bfb7508 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 17:46:45 +0200
Subject: [PATCH 021/245] tests: Make sure WidgetContainer.expose() sets focus
on widget
This is needed to convert the searched string into a selection properly.
Fixes #5363
See #5393
---
tests/helpers/fixtures.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py
index 51fd98272..eb4186894 100644
--- a/tests/helpers/fixtures.py
+++ b/tests/helpers/fixtures.py
@@ -64,14 +64,17 @@ class WidgetContainer(QWidget):
self._qtbot = qtbot
self.vbox = QVBoxLayout(self)
qtbot.add_widget(self)
+ self._widget = None
def set_widget(self, widget):
self.vbox.addWidget(widget)
widget.container = self
+ self._widget = widget
def expose(self):
with self._qtbot.waitExposed(self):
self.show()
+ self._widget.setFocus()
@pytest.fixture
From d2f3bc4bad6b519b972472b9fb32dcb0b4f2f85a Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 18:06:54 +0200
Subject: [PATCH 022/245] tests: Increase timeout for caret.html loading
For some odd reason, loading the file sometimes takes >5s on macOS...
See https://github.com/qutebrowser/qutebrowser/issues/5390#issuecomment-629269038
---
tests/unit/browser/test_caret.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/unit/browser/test_caret.py b/tests/unit/browser/test_caret.py
index e5ab90708..13994a654 100644
--- a/tests/unit/browser/test_caret.py
+++ b/tests/unit/browser/test_caret.py
@@ -31,7 +31,7 @@ from qutebrowser.utils import usertypes
def caret(web_tab, qtbot, mode_manager):
web_tab.container.expose()
- with qtbot.wait_signal(web_tab.load_finished):
+ with qtbot.wait_signal(web_tab.load_finished, timeout=10000):
web_tab.load_url(QUrl('qute://testdata/data/caret.html'))
mode_manager.enter(usertypes.KeyMode.caret)
From 2cb1ba1e7c4b5d6b074a5161a46ec2252cf364a4 Mon Sep 17 00:00:00 2001
From: Pedro Lucas Porcellis
Date: Thu, 21 May 2020 22:50:53 -0300
Subject: [PATCH 023/245] Allow to pluck tab into a private window instance
If the tab is actually located into a private window, it will just behave
normally.
---
qutebrowser/browser/commands.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
index 0f42a09d8..9f328c768 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -453,7 +453,7 @@ class CommandDispatcher:
@cmdutils.argument('win_id', completion=miscmodels.window)
@cmdutils.argument('count', value=cmdutils.Value.count)
def tab_give(self, win_id: int = None, keep: bool = False,
- count: int = None) -> None:
+ count: int = None, private: bool = False) -> None:
"""Give the current tab to a new or existing window if win_id given.
If no win_id is given, the tab will get detached into a new window.
@@ -462,6 +462,7 @@ class CommandDispatcher:
win_id: The window ID of the window to give the current tab to.
keep: If given, keep the old tab around.
count: Overrides win_id (index starts at 1 for win_id=0).
+ private: If the tab should be detached into a private instance.
"""
if config.val.tabs.tabs_are_windows:
raise cmdutils.CommandError("Can't give tabs when using "
@@ -479,7 +480,7 @@ class CommandDispatcher:
"only one tab")
tabbed_browser = self._new_tabbed_browser(
- private=self._tabbed_browser.is_private)
+ private=private or self._tabbed_browser.is_private)
else:
if win_id not in objreg.window_registry:
raise cmdutils.CommandError(
@@ -488,6 +489,9 @@ class CommandDispatcher:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
+ if private and not tabbed_browser.is_private:
+ raise cmdutils.CommandError("The window with id {} is not private".format(win_id))
+
tabbed_browser.tabopen(self._current_url())
if not keep:
self._tabbed_browser.close_tab(self._current_widget(),
From 781a68a10e10f559575f23c6ed8faa2fb46ab7d9 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 18:26:58 +0200
Subject: [PATCH 024/245] tests: make sure webengine_tab/webkit_tab take care
of shutdown
This still doesn't help with this inside Xvfb:
XIO: fatal IO error 0 (Success) on X server ":1001"
But at least it prevents an unknown segfault inside QtWebEngine.
---
tests/helpers/fixtures.py | 23 ++++++++++++++++++++---
tests/helpers/stubs.py | 4 ++++
2 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py
index eb4186894..0624ef698 100644
--- a/tests/helpers/fixtures.py
+++ b/tests/helpers/fixtures.py
@@ -45,11 +45,12 @@ import helpers.stubs as stubsmod
from qutebrowser.config import (config, configdata, configtypes, configexc,
configfiles, configcache, stylesheet)
from qutebrowser.api import config as configapi
-from qutebrowser.utils import objreg, standarddir, utils, usertypes
+from qutebrowser.utils import objreg, standarddir, utils, usertypes, qtutils
from qutebrowser.browser import greasemonkey, history, qutescheme
from qutebrowser.browser.webkit import cookies, cache
from qutebrowser.misc import savemanager, sql, objects, sessions
from qutebrowser.keyinput import modeman
+from qutebrowser.qt import sip
_qute_scheme_handler = None
@@ -207,14 +208,17 @@ def web_tab_setup(qtbot, tab_registry, session_manager_stub,
@pytest.fixture
def webkit_tab(web_tab_setup, qtbot, cookiejar_and_cache, mode_manager,
- widget_container, webpage):
+ widget_container, download_stub, webpage):
webkittab = pytest.importorskip('qutebrowser.browser.webkit.webkittab')
tab = webkittab.WebKitTab(win_id=0, mode_manager=mode_manager,
private=False)
widget_container.set_widget(tab)
- return tab
+ yield tab
+
+ # Make sure the tab shuts itself down properly
+ tab.private_api.shutdown()
@pytest.fixture
@@ -230,11 +234,24 @@ def webengine_tab(web_tab_setup, qtbot, redirect_webengine_data,
tab = webenginetab.WebEngineTab(win_id=0, mode_manager=mode_manager,
private=False)
widget_container.set_widget(tab)
+
yield tab
+
# If a page is still loading here, _on_load_finished could get called
# during teardown when session_manager_stub is already deleted.
tab.stop()
+ # Make sure the tab shuts itself down properly
+ tab.private_api.shutdown()
+
+ # If we wait for the GC to clean things up, there's a segfault inside
+ # QtWebEngine sometimes (e.g. if we only run
+ # tests/unit/browser/test_caret.py).
+ # However, with Qt < 5.12, doing this here will lead to an immediate
+ # segfault...
+ if qtutils.version_check('5.12'):
+ sip.delete(tab._widget)
+
@pytest.fixture(params=['webkit', 'webengine'])
def web_tab(request):
diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py
index caa7aac3f..f775092f5 100644
--- a/tests/helpers/stubs.py
+++ b/tests/helpers/stubs.py
@@ -615,6 +615,10 @@ class FakeDownloadManager:
self.downloads.append(download_item)
return download_item
+ def has_downloads_with_nam(self, _nam):
+ """Needed during WebView.shutdown()."""
+ return False
+
class FakeHistoryProgress:
From 0f839b848b9e7738d14bce3311e3cacf8e02019f Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 19:12:49 +0200
Subject: [PATCH 025/245] caret: Style improvements for line mode
---
qutebrowser/browser/browsertab.py | 1 -
qutebrowser/browser/webkit/webkittab.py | 24 ++++++++++--------------
qutebrowser/javascript/caret.js | 2 +-
tests/unit/browser/test_caret.py | 25 +++++++++++++------------
4 files changed, 24 insertions(+), 28 deletions(-)
diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py
index 16ddeef8b..f37d143b6 100644
--- a/qutebrowser/browser/browsertab.py
+++ b/qutebrowser/browser/browsertab.py
@@ -441,7 +441,6 @@ class AbstractCaret(QObject):
"""Attribute ``caret`` of AbstractTab for caret browsing."""
#: Signal emitted when the selection was toggled.
- #: (argument - whether the selection is now active)
selection_toggled = pyqtSignal(SelectionState)
#: Emitted when a ``follow_selection`` action is done.
follow_selected_done = pyqtSignal()
diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py
index 0a60e073b..e73833f67 100644
--- a/qutebrowser/browser/webkit/webkittab.py
+++ b/qutebrowser/browser/webkit/webkittab.py
@@ -204,8 +204,7 @@ class WebKitCaret(browsertab.AbstractCaret):
def _on_mode_left(self, _mode):
settings = self._widget.settings()
if settings.testAttribute(QWebSettings.CaretBrowsingEnabled):
- if (self.selection_state is not
- browsertab.SelectionState.none and
+ if (self.selection_state is not browsertab.SelectionState.none and
self._widget.hasSelection()):
# Remove selection if it exists
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
@@ -414,24 +413,21 @@ class WebKitCaret(browsertab.AbstractCaret):
# direction of selection (if anchor is to the left or right
# of focus) has to be checked before moving selection
# to the end of line
- direction = self._js_selection_direction()
- if direction:
+ if self._js_selection_left_to_right():
self._widget.triggerPageAction(QWebPage.SelectEndOfLine)
def _select_line_to_start(self):
- direction = self._js_selection_direction()
- if not direction:
+ if not self._js_selection_left_to_right():
self._widget.triggerPageAction(QWebPage.SelectStartOfLine)
- def _js_selection_direction(self):
- # return true if selection's direction
- # is left to right else false
+ def _js_selection_left_to_right(self):
+ """Return True iff the selection's direction is left to right."""
return self._tab.private_api.run_js_sync("""
- var sel = window.getSelection();
- var position = sel.anchorNode.compareDocumentPosition(sel.focusNode);
- (!position && sel.anchorOffset < sel.focusOffset ||
- position === Node.DOCUMENT_POSITION_FOLLOWING);
- """)
+ var sel = window.getSelection();
+ var position = sel.anchorNode.compareDocumentPosition(sel.focusNode);
+ (!position && sel.anchorOffset < sel.focusOffset ||
+ position === Node.DOCUMENT_POSITION_FOLLOWING);
+ """)
def _follow_selected(self, *, tab=False):
if QWebSettings.globalSettings().testAttribute(
diff --git a/qutebrowser/javascript/caret.js b/qutebrowser/javascript/caret.js
index 51a1cf7cf..e2063a2d4 100644
--- a/qutebrowser/javascript/caret.js
+++ b/qutebrowser/javascript/caret.js
@@ -1176,7 +1176,7 @@ window._qutebrowser.caret = (function() {
};
CaretBrowsing.updateLineSelection = function(direction, granularity) {
- if (!(granularity === "character") && !(granularity === "word")) {
+ if (granularity !== "character" && granularity !== "word") {
window.
getSelection().
modify("extend", direction, granularity);
diff --git a/tests/unit/browser/test_caret.py b/tests/unit/browser/test_caret.py
index 13994a654..2b65081f0 100644
--- a/tests/unit/browser/test_caret.py
+++ b/tests/unit/browser/test_caret.py
@@ -73,7 +73,7 @@ class Selection:
def check_multiline(self, expected, *, strip=False):
self.check(textwrap.dedent(expected).strip(), strip=strip)
- def toggle(self, line=False):
+ def toggle(self, *, line=False):
with self._qtbot.wait_signal(self._caret.selection_toggled):
self._caret.toggle_selection(line=line)
@@ -396,24 +396,24 @@ class TestReverse:
class TestLineSelection:
def test_toggle(self, caret, selection):
- selection.toggle(True)
+ selection.toggle(line=True)
selection.check("one two three")
def test_toggle_untoggle(self, caret, selection):
selection.toggle()
selection.check("")
- selection.toggle(True)
+ selection.toggle(line=True)
selection.check("one two three")
selection.toggle()
selection.check("one two three")
def test_from_center(self, caret, selection):
caret.move_to_next_char(4)
- selection.toggle(True)
+ selection.toggle(line=True)
selection.check("one two three")
def test_more_lines(self, caret, selection):
- selection.toggle(True)
+ selection.toggle(line=True)
caret.move_to_next_line(2)
selection.check_multiline("""
one two three
@@ -423,26 +423,26 @@ class TestLineSelection:
""", strip=True)
def test_not_selecting_char(self, caret, selection):
- selection.toggle(True)
+ selection.toggle(line=True)
caret.move_to_next_char()
selection.check("one two three")
caret.move_to_prev_char()
selection.check("one two three")
def test_selecting_prev_next_word(self, caret, selection):
- selection.toggle(True)
+ selection.toggle(line=True)
caret.move_to_next_word()
selection.check("one two three")
caret.move_to_prev_word()
selection.check("one two three")
def test_selecting_end_word(self, caret, selection):
- selection.toggle(True)
+ selection.toggle(line=True)
caret.move_to_end_of_word()
selection.check("one two three")
def test_selecting_prev_next_line(self, caret, selection):
- selection.toggle(True)
+ selection.toggle(line=True)
caret.move_to_next_line()
selection.check_multiline("""
one two three
@@ -452,14 +452,14 @@ class TestLineSelection:
selection.check("one two three")
def test_not_selecting_start_end_line(self, caret, selection):
- selection.toggle(True)
+ selection.toggle(line=True)
caret.move_to_end_of_line()
selection.check("one two three")
caret.move_to_start_of_line()
selection.check("one two three")
def test_selecting_block(self, caret, selection):
- selection.toggle(True)
+ selection.toggle(line=True)
caret.move_to_end_of_next_block()
selection.check_multiline("""
one two three
@@ -467,7 +467,7 @@ class TestLineSelection:
""", strip=True)
def test_selecting_start_end_document(self, caret, selection):
- selection.toggle(True)
+ selection.toggle(line=True)
caret.move_to_end_of_document()
selection.check_multiline("""
one two three
@@ -476,5 +476,6 @@ class TestLineSelection:
four five six
vier fünf sechs
""", strip=True)
+
caret.move_to_start_of_document()
selection.check("one two three")
From 118882b861105428fc698d2c5cea6aecbd59b2ee Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 19:13:31 +0200
Subject: [PATCH 026/245] tests: Skip failing caret test on macOS
See #5459
---
tests/unit/browser/test_caret.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tests/unit/browser/test_caret.py b/tests/unit/browser/test_caret.py
index 2b65081f0..3aff8cce1 100644
--- a/tests/unit/browser/test_caret.py
+++ b/tests/unit/browser/test_caret.py
@@ -466,6 +466,8 @@ class TestLineSelection:
eins zwei drei
""", strip=True)
+ @pytest.mark.not_mac(
+ reason='https://github.com/qutebrowser/qutebrowser/issues/5459')
def test_selecting_start_end_document(self, caret, selection):
selection.toggle(line=True)
caret.move_to_end_of_document()
From 374d28de77d926d207b9658b23cd16dd1990a9fc Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 19:50:32 +0200
Subject: [PATCH 027/245] webkittab: Make selection_state private
The previous selection_enabled boolean was public, but didn't need to be since
e50068021d084cf01507cd20693318189193e073.
---
qutebrowser/browser/browsertab.py | 1 -
qutebrowser/browser/webkit/webkittab.py | 89 ++++++++++++++-----------
2 files changed, 49 insertions(+), 41 deletions(-)
diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py
index f37d143b6..d11ec2fa1 100644
--- a/qutebrowser/browser/browsertab.py
+++ b/qutebrowser/browser/browsertab.py
@@ -452,7 +452,6 @@ class AbstractCaret(QObject):
super().__init__(parent)
self._tab = tab
self._widget = typing.cast(QWidget, None)
- self.selection_state = SelectionState.none
self._mode_manager = mode_manager
mode_manager.entered.connect(self._on_mode_entered)
mode_manager.left.connect(self._on_mode_left)
diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py
index e73833f67..ac10539e8 100644
--- a/qutebrowser/browser/webkit/webkittab.py
+++ b/qutebrowser/browser/webkit/webkittab.py
@@ -25,6 +25,7 @@ import xml.etree.ElementTree
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QPoint, QTimer, QSizeF, QSize
from PyQt5.QtGui import QIcon
+from PyQt5.QtWidgets import QWidget
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtPrintSupport import QPrinter
@@ -34,6 +35,7 @@ from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
webkitsettings)
from qutebrowser.utils import qtutils, usertypes, utils, log, debug
from qutebrowser.qt import sip
+from qutebrowser.keyinput import modeman
class WebKitAction(browsertab.AbstractAction):
@@ -172,16 +174,23 @@ class WebKitCaret(browsertab.AbstractCaret):
"""QtWebKit implementations related to moving the cursor/selection."""
+ def __init__(self,
+ tab: browsertab.AbstractTab,
+ mode_manager: modeman.ModeManager,
+ parent: QWidget = None) -> None:
+ super().__init__(tab, mode_manager, parent)
+ self._selection_state = browsertab.SelectionState.none
+
@pyqtSlot(usertypes.KeyMode)
def _on_mode_entered(self, mode):
if mode != usertypes.KeyMode.caret:
return
if self._widget.hasSelection():
- self.selection_state = browsertab.SelectionState.normal
+ self._selection_state = browsertab.SelectionState.normal
else:
- self.selection_state = browsertab.SelectionState.none
- self.selection_toggled.emit(self.selection_state)
+ self._selection_state = browsertab.SelectionState.none
+ self.selection_toggled.emit(self._selection_state)
settings = self._widget.settings()
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True)
@@ -196,7 +205,7 @@ class WebKitCaret(browsertab.AbstractCaret):
#
# Note: We can't use hasSelection() here, as that's always
# true in caret mode.
- if self.selection_state is browsertab.SelectionState.none:
+ if self._selection_state is browsertab.SelectionState.none:
self._widget.page().currentFrame().evaluateJavaScript(
utils.read_file('javascript/position_caret.js'))
@@ -204,37 +213,37 @@ class WebKitCaret(browsertab.AbstractCaret):
def _on_mode_left(self, _mode):
settings = self._widget.settings()
if settings.testAttribute(QWebSettings.CaretBrowsingEnabled):
- if (self.selection_state is not browsertab.SelectionState.none and
+ if (self._selection_state is not browsertab.SelectionState.none and
self._widget.hasSelection()):
# Remove selection if it exists
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False)
- self.selection_state = browsertab.SelectionState.none
+ self._selection_state = browsertab.SelectionState.none
def move_to_next_line(self, count=1):
- if self.selection_state is not browsertab.SelectionState.none:
+ if self._selection_state is not browsertab.SelectionState.none:
act = QWebPage.SelectNextLine
else:
act = QWebPage.MoveToNextLine
for _ in range(count):
self._widget.triggerPageAction(act)
- if self.selection_state is browsertab.SelectionState.line:
+ if self._selection_state is browsertab.SelectionState.line:
self._select_line_to_end()
def move_to_prev_line(self, count=1):
- if self.selection_state is not browsertab.SelectionState.none:
+ if self._selection_state is not browsertab.SelectionState.none:
act = QWebPage.SelectPreviousLine
else:
act = QWebPage.MoveToPreviousLine
for _ in range(count):
self._widget.triggerPageAction(act)
- if self.selection_state is browsertab.SelectionState.line:
+ if self._selection_state is browsertab.SelectionState.line:
self._select_line_to_start()
def move_to_next_char(self, count=1):
- if self.selection_state is browsertab.SelectionState.normal:
+ if self._selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectNextChar
- elif self.selection_state is browsertab.SelectionState.line:
+ elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = QWebPage.MoveToNextChar
@@ -242,9 +251,9 @@ class WebKitCaret(browsertab.AbstractCaret):
self._widget.triggerPageAction(act)
def move_to_prev_char(self, count=1):
- if self.selection_state is browsertab.SelectionState.normal:
+ if self._selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectPreviousChar
- elif self.selection_state is browsertab.SelectionState.line:
+ elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = QWebPage.MoveToPreviousChar
@@ -252,11 +261,11 @@ class WebKitCaret(browsertab.AbstractCaret):
self._widget.triggerPageAction(act)
def move_to_end_of_word(self, count=1):
- if self.selection_state is browsertab.SelectionState.normal:
+ if self._selection_state is browsertab.SelectionState.normal:
act = [QWebPage.SelectNextWord]
if utils.is_windows: # pragma: no cover
act.append(QWebPage.SelectPreviousChar)
- elif self.selection_state is browsertab.SelectionState.line:
+ elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = [QWebPage.MoveToNextWord]
@@ -267,11 +276,11 @@ class WebKitCaret(browsertab.AbstractCaret):
self._widget.triggerPageAction(a)
def move_to_next_word(self, count=1):
- if self.selection_state is browsertab.SelectionState.normal:
+ if self._selection_state is browsertab.SelectionState.normal:
act = [QWebPage.SelectNextWord]
if not utils.is_windows: # pragma: no branch
act.append(QWebPage.SelectNextChar)
- elif self.selection_state is browsertab.SelectionState.line:
+ elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = [QWebPage.MoveToNextWord]
@@ -282,9 +291,9 @@ class WebKitCaret(browsertab.AbstractCaret):
self._widget.triggerPageAction(a)
def move_to_prev_word(self, count=1):
- if self.selection_state is browsertab.SelectionState.normal:
+ if self._selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectPreviousWord
- elif self.selection_state is browsertab.SelectionState.line:
+ elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = QWebPage.MoveToPreviousWord
@@ -292,25 +301,25 @@ class WebKitCaret(browsertab.AbstractCaret):
self._widget.triggerPageAction(act)
def move_to_start_of_line(self):
- if self.selection_state is browsertab.SelectionState.normal:
+ if self._selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectStartOfLine
- elif self.selection_state is browsertab.SelectionState.line:
+ elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = QWebPage.MoveToStartOfLine
self._widget.triggerPageAction(act)
def move_to_end_of_line(self):
- if self.selection_state is browsertab.SelectionState.normal:
+ if self._selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectEndOfLine
- elif self.selection_state is browsertab.SelectionState.line:
+ elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = QWebPage.MoveToEndOfLine
self._widget.triggerPageAction(act)
def move_to_start_of_next_block(self, count=1):
- if self.selection_state is not browsertab.SelectionState.none:
+ if self._selection_state is not browsertab.SelectionState.none:
act = [QWebPage.SelectNextLine,
QWebPage.SelectStartOfBlock]
else:
@@ -319,11 +328,11 @@ class WebKitCaret(browsertab.AbstractCaret):
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
- if self.selection_state is browsertab.SelectionState.line:
+ if self._selection_state is browsertab.SelectionState.line:
self._select_line_to_end()
def move_to_start_of_prev_block(self, count=1):
- if self.selection_state is not browsertab.SelectionState.none:
+ if self._selection_state is not browsertab.SelectionState.none:
act = [QWebPage.SelectPreviousLine,
QWebPage.SelectStartOfBlock]
else:
@@ -332,11 +341,11 @@ class WebKitCaret(browsertab.AbstractCaret):
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
- if self.selection_state is browsertab.SelectionState.line:
+ if self._selection_state is browsertab.SelectionState.line:
self._select_line_to_start()
def move_to_end_of_next_block(self, count=1):
- if self.selection_state is not browsertab.SelectionState.none:
+ if self._selection_state is not browsertab.SelectionState.none:
act = [QWebPage.SelectNextLine,
QWebPage.SelectEndOfBlock]
else:
@@ -345,31 +354,31 @@ class WebKitCaret(browsertab.AbstractCaret):
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
- if self.selection_state is browsertab.SelectionState.line:
+ if self._selection_state is browsertab.SelectionState.line:
self._select_line_to_end()
def move_to_end_of_prev_block(self, count=1):
- if self.selection_state is not browsertab.SelectionState.none:
+ if self._selection_state is not browsertab.SelectionState.none:
act = [QWebPage.SelectPreviousLine, QWebPage.SelectEndOfBlock]
else:
act = [QWebPage.MoveToPreviousLine, QWebPage.MoveToEndOfBlock]
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
- if self.selection_state is browsertab.SelectionState.line:
+ if self._selection_state is browsertab.SelectionState.line:
self._select_line_to_start()
def move_to_start_of_document(self):
- if self.selection_state is not browsertab.SelectionState.none:
+ if self._selection_state is not browsertab.SelectionState.none:
act = QWebPage.SelectStartOfDocument
else:
act = QWebPage.MoveToStartOfDocument
self._widget.triggerPageAction(act)
- if self.selection_state is browsertab.SelectionState.line:
+ if self._selection_state is browsertab.SelectionState.line:
self._select_line()
def move_to_end_of_document(self):
- if self.selection_state is not browsertab.SelectionState.none:
+ if self._selection_state is not browsertab.SelectionState.none:
act = QWebPage.SelectEndOfDocument
else:
act = QWebPage.MoveToEndOfDocument
@@ -377,16 +386,16 @@ class WebKitCaret(browsertab.AbstractCaret):
def toggle_selection(self, line=False):
if line:
- self.selection_state = browsertab.SelectionState.line
+ self._selection_state = browsertab.SelectionState.line
self._select_line()
self.reverse_selection()
self._select_line()
self.reverse_selection()
- elif self.selection_state is not browsertab.SelectionState.normal:
- self.selection_state = browsertab.SelectionState.normal
+ elif self._selection_state is not browsertab.SelectionState.normal:
+ self._selection_state = browsertab.SelectionState.normal
else:
- self.selection_state = browsertab.SelectionState.none
- self.selection_toggled.emit(self.selection_state)
+ self._selection_state = browsertab.SelectionState.none
+ self.selection_toggled.emit(self._selection_state)
def drop_selection(self):
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
From 0770ef11ebeac8072158a639c9297d5c4ef14dfb Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 20:01:08 +0200
Subject: [PATCH 028/245] tests: Make sure entering caret mode is finished
Otherwise, the caret JS code could still be initializing while the tests start
to run.
---
tests/unit/browser/test_caret.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/tests/unit/browser/test_caret.py b/tests/unit/browser/test_caret.py
index 3aff8cce1..830dda0ff 100644
--- a/tests/unit/browser/test_caret.py
+++ b/tests/unit/browser/test_caret.py
@@ -34,7 +34,8 @@ def caret(web_tab, qtbot, mode_manager):
with qtbot.wait_signal(web_tab.load_finished, timeout=10000):
web_tab.load_url(QUrl('qute://testdata/data/caret.html'))
- mode_manager.enter(usertypes.KeyMode.caret)
+ with qtbot.wait_signal(web_tab.caret.selection_toggled):
+ mode_manager.enter(usertypes.KeyMode.caret)
return web_tab.caret
From 7a0cbf54fce491a48a5518c6c7422ab5d998d0a2 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 20:01:51 +0200
Subject: [PATCH 029/245] caret: Fix toggling behavior with QtWebEngine
The behavior when pressing `v` in line selection mode was different between
QtWebKit and QtWebEngine: With QtWebKit, normal selection mode was entered,
while with QtWebEngine, selection mode was left.
Do the former with QtWebEngine as well, as that's also what vim does.
---
qutebrowser/javascript/caret.js | 2 +-
tests/unit/browser/test_caret.py | 17 ++++++++++++++++-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/qutebrowser/javascript/caret.js b/qutebrowser/javascript/caret.js
index e2063a2d4..2d0bced84 100644
--- a/qutebrowser/javascript/caret.js
+++ b/qutebrowser/javascript/caret.js
@@ -1452,7 +1452,7 @@ window._qutebrowser.caret = (function() {
CaretBrowsing.SelectionState.LINE;
CaretBrowsing.selectLine();
CaretBrowsing.finishMove();
- } else if (CaretBrowsing.selectionState === CaretBrowsing.SelectionState.NONE) {
+ } else if (CaretBrowsing.selectionState !== CaretBrowsing.SelectionState.NORMAL) {
CaretBrowsing.selectionState = CaretBrowsing.SelectionState.NORMAL;
} else {
CaretBrowsing.selectionState = CaretBrowsing.SelectionState.NONE;
diff --git a/tests/unit/browser/test_caret.py b/tests/unit/browser/test_caret.py
index 830dda0ff..7d1325612 100644
--- a/tests/unit/browser/test_caret.py
+++ b/tests/unit/browser/test_caret.py
@@ -25,6 +25,7 @@ import pytest
from PyQt5.QtCore import QUrl
from qutebrowser.utils import usertypes
+from qutebrowser.browser import browsertab
@pytest.fixture
@@ -75,8 +76,10 @@ class Selection:
self.check(textwrap.dedent(expected).strip(), strip=strip)
def toggle(self, *, line=False):
- with self._qtbot.wait_signal(self._caret.selection_toggled):
+ """Toggle the selection and return the new selection state."""
+ with self._qtbot.wait_signal(self._caret.selection_toggled) as blocker:
self._caret.toggle_selection(line=line)
+ return blocker.args[0]
@pytest.fixture
@@ -84,6 +87,18 @@ def selection(qtbot, caret):
return Selection(qtbot, caret)
+def test_toggle(caret, selection, qtbot):
+ """Make sure calling toggleSelection produces the correct callback values.
+
+ This also makes sure that the SelectionState enum in JS lines up with the
+ Python browsertab.SelectionState enum.
+ """
+ assert selection.toggle() == browsertab.SelectionState.normal
+ assert selection.toggle(line=True) == browsertab.SelectionState.line
+ assert selection.toggle() == browsertab.SelectionState.normal
+ assert selection.toggle() == browsertab.SelectionState.none
+
+
class TestDocument:
def test_selecting_entire_document(self, caret, selection):
From d57fe79f5f568113f97392c91087702bddf0fec3 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 20:05:38 +0200
Subject: [PATCH 030/245] caret: Use strings instead of ints for enums
---
qutebrowser/browser/browsertab.py | 7 +++++--
qutebrowser/browser/webengine/webenginetab.py | 4 ++--
qutebrowser/javascript/caret.js | 10 ++++++----
3 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py
index d11ec2fa1..ba758abc7 100644
--- a/qutebrowser/browser/browsertab.py
+++ b/qutebrowser/browser/browsertab.py
@@ -427,9 +427,12 @@ class AbstractZoom(QObject):
self._set_factor_internal(self._zoom_factor)
-class SelectionState(enum.IntEnum):
+class SelectionState(enum.Enum):
- """Possible states of selection in Caret mode."""
+ """Possible states of selection in caret mode.
+
+ NOTE: Names need to line up with SelectionState in caret.js!
+ """
none = 1
normal = 2
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index 5b0721c18..33db6e631 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -504,8 +504,8 @@ class WebEngineCaret(browsertab.AbstractCaret):
code = javascript.assemble('caret', command, *args)
self._tab.run_js_async(code, callback)
- def _toggle_sel_translate(self, state_int):
- state = browsertab.SelectionState(state_int)
+ def _toggle_sel_translate(self, state_str):
+ state = browsertab.SelectionState[state_str]
self.selection_toggled.emit(state)
diff --git a/qutebrowser/javascript/caret.js b/qutebrowser/javascript/caret.js
index 2d0bced84..d7ba88fe6 100644
--- a/qutebrowser/javascript/caret.js
+++ b/qutebrowser/javascript/caret.js
@@ -706,13 +706,15 @@ window._qutebrowser.caret = (function() {
CaretBrowsing.isCaretVisible = false;
/**
- * selection modes
+ * Selection modes.
+ * NOTE: Values need to line up with SelectionState in browsertab.py!
+ *
* @type {enum}
*/
CaretBrowsing.SelectionState = {
- "NONE": 1,
- "NORMAL": 2,
- "LINE": 3,
+ "NONE": "none",
+ "NORMAL": "normal",
+ "LINE": "line",
};
/**
From dcab5eb11b8b018bb296475c24e5716a2ac415ca Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 17:12:51 +0200
Subject: [PATCH 031/245] src2asciidoc: Make sure usage lines are generated
with fixed width
Due to a change in Python 3.8, the output depended on the calling terminal's
width. Set a fixed with of 200 (rather than 80) so that we always have the
expanded version for the generated documentation.
See #5393 and
https://github.com/python/cpython/commit/74102c9a5f2327c4fc47feefa072854a53551d1f#diff-837b312b1f3508216ace6adb46492836
---
doc/help/commands.asciidoc | 17 +++++------------
scripts/dev/src2asciidoc.py | 5 +++++
2 files changed, 10 insertions(+), 12 deletions(-)
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index 565b87ac8..3266c7153 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -285,8 +285,7 @@ Set all settings back to their default.
[[config-cycle]]
=== config-cycle
-Syntax: +:config-cycle [*--pattern* 'pattern'] [*--temp*] [*--print*]
- 'option' ['values' ['values' ...]]+
+Syntax: +:config-cycle [*--pattern* 'pattern'] [*--temp*] [*--print*] 'option' ['values' ['values' ...]]+
Cycle an option between multiple values.
@@ -597,8 +596,7 @@ Show help about a command or setting.
[[hint]]
=== hint
-Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*] [*--first*]
- ['group'] ['target'] ['args' ['args' ...]]+
+Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*] [*--first*] ['group'] ['target'] ['args' ['args' ...]]+
Start hinting.
@@ -855,8 +853,7 @@ Do nothing.
[[open]]
=== open
-Syntax: +:open [*--related*] [*--bg*] [*--tab*] [*--window*] [*--secure*] [*--private*]
- ['url']+
+Syntax: +:open [*--related*] [*--bg*] [*--tab*] [*--window*] [*--secure*] [*--private*] ['url']+
Open a URL in the current/[count]th tab.
@@ -1186,9 +1183,7 @@ Load a session.
[[session-save]]
=== session-save
-Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] [*--only-active-window*]
- [*--with-private*]
- ['name']+
+Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] [*--only-active-window*] [*--with-private*] ['name']+
Save a session.
@@ -1252,9 +1247,7 @@ Set a mark at the current scroll position in the current tab.
[[spawn]]
=== spawn
-Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output*] [*--output-messages*]
- [*--detach*]
- 'cmdline'+
+Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output*] [*--output-messages*] [*--detach*] 'cmdline'+
Spawn an external command.
diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py
index b82ede3e1..1d3ad8bf4 100755
--- a/scripts/dev/src2asciidoc.py
+++ b/scripts/dev/src2asciidoc.py
@@ -59,6 +59,11 @@ class UsageFormatter(argparse.HelpFormatter):
argparse.HelpFormatter while copying 99% of the code :-/
"""
+ def __init__(self, prog, indent_increment=2, max_help_position=24,
+ width=200):
+ """Override __init__ to set a fixed width as default."""
+ super().__init__(prog, indent_increment, max_help_position, width)
+
def _format_usage(self, usage, actions, groups, _prefix):
"""Override _format_usage to not add the 'usage:' prefix."""
return super()._format_usage(usage, actions, groups, '')
From 3d950c7611e5f45d6fc5373c85f64899d51a8111 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 21:29:24 +0200
Subject: [PATCH 032/245] Update docs
---
doc/changelog.asciidoc | 1 +
doc/help/commands.asciidoc | 5 +++++
doc/help/settings.asciidoc | 1 +
3 files changed, 7 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 3d39eec85..510ffeef2 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -35,6 +35,7 @@ Added
- New `:debug-keytester` command, which shows a "key tester" widget.
Previously, that was only available as a separate application via `python3 -m
scripts.keytester`.
+- New line selection mode (`:toggle-selection --line`), bound to `Shift-V` in caret mode.
Fixed
~~~~~
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index 3266c7153..608329fe8 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -1885,8 +1885,13 @@ This acts like readline's yank.
[[toggle-selection]]
=== toggle-selection
+Syntax: +:toggle-selection [*--line*]+
+
Toggle caret selection mode.
+==== optional arguments
+* +*-l*+, +*--line*+: Enables line-selection.
+
== Debugging commands
These commands are mainly intended for debugging. They are hidden if qutebrowser was started without the `--debug`-flag.
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 5182968a6..ca1f41fb9 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -444,6 +444,7 @@ Default:
* +pass:[J]+: +pass:[scroll down]+
* +pass:[K]+: +pass:[scroll up]+
* +pass:[L]+: +pass:[scroll right]+
+* +pass:[V]+: +pass:[toggle-selection --line]+
* +pass:[Y]+: +pass:[yank selection -s]+
* +pass:[[]+: +pass:[move-to-start-of-prev-block]+
* +pass:[]]+: +pass:[move-to-start-of-next-block]+
From 57bc2b49c6ff6c67a0a8a6967389f03a12473e9f Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 21:29:33 +0200
Subject: [PATCH 033/245] Fix segfault with test_webenginetab and Qt 5.9
---
tests/helpers/fixtures.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py
index 0624ef698..aed243b4b 100644
--- a/tests/helpers/fixtures.py
+++ b/tests/helpers/fixtures.py
@@ -223,7 +223,8 @@ def webkit_tab(web_tab_setup, qtbot, cookiejar_and_cache, mode_manager,
@pytest.fixture
def webengine_tab(web_tab_setup, qtbot, redirect_webengine_data,
- tabbed_browser_stubs, mode_manager, widget_container):
+ tabbed_browser_stubs, mode_manager, widget_container,
+ monkeypatch):
tabwidget = tabbed_browser_stubs[0].widget
tabwidget.current_index = 0
tabwidget.index_of = 0
@@ -249,6 +250,7 @@ def webengine_tab(web_tab_setup, qtbot, redirect_webengine_data,
# tests/unit/browser/test_caret.py).
# However, with Qt < 5.12, doing this here will lead to an immediate
# segfault...
+ monkeypatch.undo() # version_check could be patched
if qtutils.version_check('5.12'):
sip.delete(tab._widget)
From 399ff092dc3880ebc56b80cd6d8dcdd00ff341a5 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 22:25:40 +0200
Subject: [PATCH 034/245] Fix lint
---
qutebrowser/browser/commands.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
index 9f328c768..f115501d3 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -453,7 +453,7 @@ class CommandDispatcher:
@cmdutils.argument('win_id', completion=miscmodels.window)
@cmdutils.argument('count', value=cmdutils.Value.count)
def tab_give(self, win_id: int = None, keep: bool = False,
- count: int = None, private: bool = False) -> None:
+ count: int = None, private: bool = False) -> None:
"""Give the current tab to a new or existing window if win_id given.
If no win_id is given, the tab will get detached into a new window.
@@ -490,7 +490,8 @@ class CommandDispatcher:
window=win_id)
if private and not tabbed_browser.is_private:
- raise cmdutils.CommandError("The window with id {} is not private".format(win_id))
+ raise cmdutils.CommandError(
+ "The window with id {} is not private".format(win_id))
tabbed_browser.tabopen(self._current_url())
if not keep:
From 1463fc4e35910defe3ddb30ae292c9db37dbe4d4 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 22:26:09 +0200
Subject: [PATCH 035/245] Update docs
---
doc/changelog.asciidoc | 1 +
doc/help/commands.asciidoc | 3 ++-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 46c72e4e2..535a2f16b 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -51,6 +51,7 @@ Changed
`misc/requirements/requirements-tests.txt{,-raw}` is supported.
- The `:tab-focus` command now has completion for tabs in the current window.
- The `bindings.key_mappings` setting now maps `` to the tab key by default.
+- `:tab-give --private` now detaches a tab into a new private window.
Fixed
~~~~~
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index 8cd8a62c8..dc40c76a6 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -1341,7 +1341,7 @@ The tab index to focus, starting with 1.
[[tab-give]]
=== tab-give
-Syntax: +:tab-give [*--keep*] ['win-id']+
+Syntax: +:tab-give [*--keep*] [*--private*] ['win-id']+
Give the current tab to a new or existing window if win_id given.
@@ -1352,6 +1352,7 @@ If no win_id is given, the tab will get detached into a new window.
==== optional arguments
* +*-k*+, +*--keep*+: If given, keep the old tab around.
+* +*-p*+, +*--private*+: If the tab should be detached into a private instance.
==== count
Overrides win_id (index starts at 1 for win_id=0).
From ea9f2fcf49fb16ce90422096cd097cd54b2592f8 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 22:42:53 +0200
Subject: [PATCH 036/245] Handle None in _toggle_sel_translate
This is the equivalent of the previous:
TypeError: AbstractCaret.selection_toggled[bool].emit(): argument 1 has unexpected type 'NoneType'
When e.g. doing:
qutebrowser --temp-basedir ':later 100 enter-mode caret' ':later 110 toggle-selection'
Handle this case and show an error instead of crashing.
---
qutebrowser/browser/webengine/webenginetab.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index cd305d11a..709c5c1b5 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -531,6 +531,9 @@ class WebEngineCaret(browsertab.AbstractCaret):
self._tab.run_js_async(code, callback)
def _toggle_sel_translate(self, state_str):
+ if state_str is None:
+ message.error("Error toggling caret selection")
+ return
state = browsertab.SelectionState[state_str]
self.selection_toggled.emit(state)
From e471b6aa299e01af4d4c341a949017e16467b60b Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 23:04:45 +0200
Subject: [PATCH 037/245] Update changelog
---
doc/changelog.asciidoc | 2 ++
1 file changed, 2 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 535a2f16b..741558ec1 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -64,6 +64,8 @@ Fixed
- `:inspector` now works correctly when cookies are disabled globally.
- Added workaround for a (Gentoo?) PyQt/packaging issue related to the
`QWebEngineFindTextResult` handling added in v1.11.0.
+- When entering caret selection mode (`v, v`) very early before a page is
+ loaded, an error is now shown instead of a crash happening.
v1.11.1 (2020-05-07)
--------------------
From fc4cdb26b20f33a09ddfa3d421a12de4929f589f Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 23:06:11 +0200
Subject: [PATCH 038/245] Fix imports after merge
---
qutebrowser/browser/webkit/webkittab.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py
index db97af153..1e9276265 100644
--- a/qutebrowser/browser/webkit/webkittab.py
+++ b/qutebrowser/browser/webkit/webkittab.py
@@ -28,7 +28,6 @@ from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
from PyQt5.QtWebKit import QWebSettings
-from PyQt5.QtWidgets import QWidget
from PyQt5.QtPrintSupport import QPrinter
from qutebrowser.browser import browsertab, shared
@@ -37,7 +36,6 @@ from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
from qutebrowser.utils import qtutils, usertypes, utils, log, debug
from qutebrowser.keyinput import modeman
from qutebrowser.qt import sip
-from qutebrowser.keyinput import modeman
class WebKitAction(browsertab.AbstractAction):
From 6b1d1bd2e112350671b15af1aa7a2c949d63a9a6 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 22 May 2020 23:07:54 +0200
Subject: [PATCH 039/245] eslint: Turn off no-negated-condition
---
qutebrowser/javascript/.eslintrc.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/qutebrowser/javascript/.eslintrc.yaml b/qutebrowser/javascript/.eslintrc.yaml
index 4fdd43854..23456e801 100644
--- a/qutebrowser/javascript/.eslintrc.yaml
+++ b/qutebrowser/javascript/.eslintrc.yaml
@@ -62,3 +62,4 @@ rules:
max-params: "off"
prefer-named-capture-group: "off"
function-call-argument-newline: "off"
+ no-negated-condition: "off"
From 8803d77985a854733de81ba86c3a1c34e7db8dfc Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Fri, 22 May 2020 18:01:57 -0400
Subject: [PATCH 040/245] Remove unused mocker argument.
Co-authored-by: Florian Bruhin
---
tests/unit/utils/test_log.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index c8b778149..e3fc1e2ee 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -263,7 +263,7 @@ class TestInitLog:
('INFO', 'WARNING', logging.INFO, 'VDEBUG', logging.VDEBUG),
('WARNING', 'INFO', logging.WARNING, 'CRITICAL', logging.CRITICAL),
])
- def test_init_from_config(self, mocker, console_cli, console_conf,
+ def test_init_from_config(self, console_cli, console_conf,
console_expected, ram_conf, ram_expected, args,
config_stub):
args.debug = False
From 92e9de19ebb21702cbcc124a8c2286c3d5d08f84 Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Fri, 22 May 2020 18:07:38 -0400
Subject: [PATCH 041/245] Don't configure loglevels for nil handlers.
ram_handler might be None if --loglines=0 is passed.
console_handler is only None if sys.stderr is None, which probably never
happens, but let's just check anyways.
---
qutebrowser/utils/log.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 20a3dd4fb..73ef1b042 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -540,15 +540,13 @@ def init_from_config(conf: 'configmodule.ConfigContainer') -> None:
This is passed rather than accessed via the module to avoid a
cyclic import.
"""
- assert ram_handler is not None
- assert console_handler is not None
assert _args is not None
ramlevel = conf.logging.level.ram
consolelevel = conf.logging.level.console
- if ramlevel:
+ if ram_handler and ramlevel:
init.info("Configuring RAM loglevel to %s", ramlevel)
ram_handler.setLevel(LOG_LEVELS[ramlevel])
- if consolelevel:
+ if console_handler and consolelevel:
if _args.loglevel:
init.info("--loglevel flag overrides logging.level.console")
else:
From abe2c4b351dd10ac06531b8bbf74b56e149ea34a Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Sat, 23 May 2020 13:50:51 +0200
Subject: [PATCH 042/245] Clarify effect of content.cookies.accept
---
doc/help/settings.asciidoc | 1 +
qutebrowser/config/configdata.yml | 4 ++++
2 files changed, 5 insertions(+)
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index d7b07db34..d0ca82515 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -1762,6 +1762,7 @@ This setting is only available with the QtWebEngine backend.
[[content.cookies.accept]]
=== content.cookies.accept
Which cookies to accept.
+With QtWebEngine, this setting also controls other features with tracking capabilities similar to those of cookies; including IndexedDB, DOM storage, filesystem API, service workers, and AppCache.
Note that with QtWebKit, only `all` and `never` are supported as per-domain values. Setting `no-3rdparty` or `no-unknown-3rdparty` per-domain on QtWebKit will have the same effect as `all`.
This setting supports URL patterns.
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 01e9fe64b..1ea93793b 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -365,6 +365,10 @@ content.cookies.accept:
desc: >-
Which cookies to accept.
+ With QtWebEngine, this setting also controls other features with tracking
+ capabilities similar to those of cookies; including IndexedDB, DOM storage,
+ filesystem API, service workers, and AppCache.
+
Note that with QtWebKit, only `all` and `never` are supported as per-domain
values. Setting `no-3rdparty` or `no-unknown-3rdparty` per-domain on
QtWebKit will have the same effect as `all`.
From 1a97a1b5c74cb33ef2e0160e4a9100a59cd27d15 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 25 May 2020 15:45:34 +0200
Subject: [PATCH 043/245] caret: Add V binding in normal mode
See #5264
---
doc/help/settings.asciidoc | 1 +
qutebrowser/config/configdata.yml | 1 +
2 files changed, 2 insertions(+)
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index d0ca82515..4c20126c3 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -591,6 +591,7 @@ Default:
* +pass:[Sq]+: +pass:[open qute://bookmarks]+
* +pass:[Ss]+: +pass:[open qute://settings]+
* +pass:[T]+: +pass:[tab-focus]+
+* +pass:[V]+: +pass:[enter-mode caret ;; toggle-selection --line]+
* +pass:[ZQ]+: +pass:[quit]+
* +pass:[ZZ]+: +pass:[quit --save]+
* +pass:[[[]+: +pass:[navigate prev]+
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 1ea93793b..3700e5e1d 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -2901,6 +2901,7 @@ bindings.default:
N: search-prev
i: enter-mode insert
v: enter-mode caret
+ V: enter-mode caret ;; toggle-selection --line
"`": enter-mode set_mark
"'": enter-mode jump_mark
yy: yank
From a17bfea32fc0b4b607977403510d1541742c6d2a Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Mon, 25 May 2020 10:27:30 -0400
Subject: [PATCH 044/245] Lowercase loglevel config values
Co-authored-by: Florian Bruhin
---
qutebrowser/config/configtypes.py | 3 ++-
qutebrowser/utils/log.py | 4 ++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py
index 7927e0425..98926ca5e 100644
--- a/qutebrowser/config/configtypes.py
+++ b/qutebrowser/config/configtypes.py
@@ -2020,7 +2020,8 @@ class LogLevel(String):
def __init__(self, none_ok: bool = False) -> None:
super().__init__(none_ok=none_ok)
- self.valid_values = ValidValues(*log.LOG_LEVELS.keys())
+ self.valid_values = ValidValues(*[level.lower()
+ for level in log.LOG_LEVELS])
class Key(BaseType):
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 73ef1b042..4b19a9a84 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -545,13 +545,13 @@ def init_from_config(conf: 'configmodule.ConfigContainer') -> None:
consolelevel = conf.logging.level.console
if ram_handler and ramlevel:
init.info("Configuring RAM loglevel to %s", ramlevel)
- ram_handler.setLevel(LOG_LEVELS[ramlevel])
+ ram_handler.setLevel(LOG_LEVELS[ramlevel].upper())
if console_handler and consolelevel:
if _args.loglevel:
init.info("--loglevel flag overrides logging.level.console")
else:
init.info("Configuring console loglevel to %s", consolelevel)
- console_handler.setLevel(LOG_LEVELS[consolelevel])
+ console_handler.setLevel(LOG_LEVELS[consolelevel].upper())
class QtWarningFilter(logging.Filter):
From 1a8afe05103a443f0675e5683cebe4ca9ee0f072 Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Mon, 25 May 2020 10:27:53 -0400
Subject: [PATCH 045/245] Make loglevel docstring consistent
Co-authored-by: Florian Bruhin
---
qutebrowser/config/configtypes.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py
index 98926ca5e..546d90c38 100644
--- a/qutebrowser/config/configtypes.py
+++ b/qutebrowser/config/configtypes.py
@@ -2016,7 +2016,7 @@ class NewTabPosition(String):
class LogLevel(String):
- """Log level."""
+ """A logging level."""
def __init__(self, none_ok: bool = False) -> None:
super().__init__(none_ok=none_ok)
From e28e707930e23004def77f96bf12104ff4789bf0 Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Mon, 25 May 2020 10:35:23 -0400
Subject: [PATCH 046/245] Use defaults for loglevel configs.
There's no reason for them to be None, use defaults so the behavior is
better documented..
---
qutebrowser/config/configdata.yml | 6 ++----
qutebrowser/utils/log.py | 12 ++++++------
tests/unit/utils/test_log.py | 10 +++++-----
3 files changed, 13 insertions(+), 15 deletions(-)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index d7988f571..6d8078ad2 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -3198,18 +3198,16 @@ bindings.commands:
## logging
logging.level.ram:
- default: null
+ default: debug
type:
name: LogLevel
- none_ok: true
desc:
Level for in-memory logs.
logging.level.console:
- default: null
+ default: info
type:
name: LogLevel
- none_ok: true
desc: >-
Level for console (stdout/stderr) logs.
Ignored if the --loglevel or --debug CLI flags are used.
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 4b19a9a84..635fccabb 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -541,17 +541,17 @@ def init_from_config(conf: 'configmodule.ConfigContainer') -> None:
cyclic import.
"""
assert _args is not None
- ramlevel = conf.logging.level.ram
- consolelevel = conf.logging.level.console
- if ram_handler and ramlevel:
+ if ram_handler:
+ ramlevel = conf.logging.level.ram
init.info("Configuring RAM loglevel to %s", ramlevel)
- ram_handler.setLevel(LOG_LEVELS[ramlevel].upper())
- if console_handler and consolelevel:
+ ram_handler.setLevel(LOG_LEVELS[ramlevel.upper()])
+ if console_handler:
+ consolelevel = conf.logging.level.console
if _args.loglevel:
init.info("--loglevel flag overrides logging.level.console")
else:
init.info("Configuring console loglevel to %s", consolelevel)
- console_handler.setLevel(LOG_LEVELS[consolelevel].upper())
+ console_handler.setLevel(LOG_LEVELS[consolelevel.upper()])
class QtWarningFilter(logging.Filter):
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index e3fc1e2ee..8ad870f73 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -257,11 +257,11 @@ class TestInitLog:
@pytest.mark.parametrize(
'console_cli, console_conf, console_expected, ram_conf, ram_expected',
[
- (None, None, logging.INFO, None, logging.NOTSET),
- (None, None, logging.INFO, 'CRITICAL', logging.CRITICAL),
- (None, 'WARNING', logging.WARNING, 'INFO', logging.INFO),
- ('INFO', 'WARNING', logging.INFO, 'VDEBUG', logging.VDEBUG),
- ('WARNING', 'INFO', logging.WARNING, 'CRITICAL', logging.CRITICAL),
+ (None, 'info', logging.INFO, 'debug', logging.DEBUG),
+ (None, 'info', logging.INFO, 'critical', logging.CRITICAL),
+ (None, 'warning', logging.WARNING, 'info', logging.INFO),
+ ('info', 'warning', logging.INFO, 'vdebug', logging.VDEBUG),
+ ('warning', 'info', logging.WARNING, 'critical', logging.CRITICAL),
])
def test_init_from_config(self, console_cli, console_conf,
console_expected, ram_conf, ram_expected, args,
From dc20806ab2e68d2201f801e5c659b16b9442e063 Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Mon, 25 May 2020 10:40:05 -0400
Subject: [PATCH 047/245] Split up init_from_config test.
The RAM and console configuration are independent, and splitting them
into two separate tests makes the tests easier to read, as there aren't
an overwhelming number of arguments and permutations.
---
tests/unit/utils/test_log.py | 40 ++++++++++++++++++++++--------------
1 file changed, 25 insertions(+), 15 deletions(-)
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index 8ad870f73..eb634d76a 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -254,27 +254,37 @@ class TestInitLog:
with pytest.raises(PendingDeprecationWarning):
warnings.warn("test warning", PendingDeprecationWarning)
- @pytest.mark.parametrize(
- 'console_cli, console_conf, console_expected, ram_conf, ram_expected',
+ @pytest.mark.parametrize('cli, conf, expected',
[
- (None, 'info', logging.INFO, 'debug', logging.DEBUG),
- (None, 'info', logging.INFO, 'critical', logging.CRITICAL),
- (None, 'warning', logging.WARNING, 'info', logging.INFO),
- ('info', 'warning', logging.INFO, 'vdebug', logging.VDEBUG),
- ('warning', 'info', logging.WARNING, 'critical', logging.CRITICAL),
+ (None, 'info', logging.INFO),
+ (None, 'warning', logging.WARNING),
+ ('info', 'warning', logging.INFO),
+ ('warning', 'info', logging.WARNING),
])
- def test_init_from_config(self, console_cli, console_conf,
- console_expected, ram_conf, ram_expected, args,
- config_stub):
+ def test_init_from_config_console(self, cli, conf, expected, args,
+ config_stub):
args.debug = False
- args.loglevel = console_cli
+ args.loglevel = cli
log.init_log(args)
- config_stub.val.logging.level.ram = ram_conf
- config_stub.val.logging.level.console = console_conf
+ config_stub.val.logging.level.console = conf
log.init_from_config(config_stub.val)
- assert log.ram_handler.level == ram_expected
- assert log.console_handler.level == console_expected
+ assert log.console_handler.level == expected
+
+ @pytest.mark.parametrize('conf, expected',
+ [
+ ('vdebug', logging.VDEBUG),
+ ('debug', logging.DEBUG),
+ ('info', logging.INFO),
+ ('critical', logging.CRITICAL),
+ ])
+ def test_init_from_config_ram(self, conf, expected, args, config_stub):
+ args.debug = False
+ log.init_log(args)
+
+ config_stub.val.logging.level.ram = conf
+ log.init_from_config(config_stub.val)
+ assert log.ram_handler.level == expected
class TestHideQtWarning:
From e2475b9f23f3e38cf306fb2773dcab8200af3150 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 25 May 2020 17:56:17 +0200
Subject: [PATCH 048/245] tests: Don't suppress output if on CI
---
tests/end2end/fixtures/testprocess.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/tests/end2end/fixtures/testprocess.py b/tests/end2end/fixtures/testprocess.py
index 3c19b86ef..08f9754db 100644
--- a/tests/end2end/fixtures/testprocess.py
+++ b/tests/end2end/fixtures/testprocess.py
@@ -74,7 +74,10 @@ def _render_log(data, *, verbose, threshold=100):
data = [str(d) for d in data]
is_exception = any('Traceback (most recent call last):' in line or
'Uncaught exception' in line for line in data)
- if len(data) > threshold and not verbose and not is_exception:
+ if (len(data) > threshold and
+ not verbose and
+ not is_exception and
+ not utils.ON_CI):
msg = '[{} lines suppressed, use -v to show]'.format(
len(data) - threshold)
data = [msg] + data[-threshold:]
From 27c737599556110e486bb7dbf2a7b9c7d7b9fa47 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:09 +0200
Subject: [PATCH 049/245] Update codecov from 2.1.0 to 2.1.3
---
misc/requirements/requirements-codecov.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt
index 54d5bdfb4..0a38e12b5 100644
--- a/misc/requirements/requirements-codecov.txt
+++ b/misc/requirements/requirements-codecov.txt
@@ -2,7 +2,7 @@
certifi==2020.4.5.1
chardet==3.0.4
-codecov==2.1.0
+codecov==2.1.3
coverage==5.1
idna==2.9
requests==2.23.0
From 06396a7d2f4bafc9c5f4b0a3e42db76b25833256 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:10 +0200
Subject: [PATCH 050/245] Update lxml from 4.5.0 to 4.5.1
---
misc/requirements/requirements-dev.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt
index 1c790abc4..83ba9a82e 100644
--- a/misc/requirements/requirements-dev.txt
+++ b/misc/requirements/requirements-dev.txt
@@ -11,7 +11,7 @@ github3.py==1.3.0
hunter==3.1.3
idna==2.9
jwcrypto==0.7
-lxml==4.5.0
+lxml==4.5.1
manhole==1.6.0
packaging==20.3
pycparser==2.20
From a728480328cb7ce3a2b7b61891f4ec3c8288761e Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:11 +0200
Subject: [PATCH 051/245] Update packaging from 20.3 to 20.4
---
misc/requirements/requirements-dev.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt
index 83ba9a82e..cf40bd5bb 100644
--- a/misc/requirements/requirements-dev.txt
+++ b/misc/requirements/requirements-dev.txt
@@ -13,7 +13,7 @@ idna==2.9
jwcrypto==0.7
lxml==4.5.1
manhole==1.6.0
-packaging==20.3
+packaging==20.4
pycparser==2.20
Pympler==0.8
pyparsing==2.4.7
From 357faf1bcabb30c38edaea6f49178449d7d6fbaf Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:12 +0200
Subject: [PATCH 052/245] Update packaging from 20.3 to 20.4
---
misc/requirements/requirements-pip.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt
index 92b8cf057..6d2ee85ca 100644
--- a/misc/requirements/requirements-pip.txt
+++ b/misc/requirements/requirements-pip.txt
@@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
appdirs==1.4.4
-packaging==20.3
+packaging==20.4
pyparsing==2.4.7
setuptools==46.4.0
six==1.14.0
From a2150d9c4a35cb597ec8b2856e908c942c2f033f Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:13 +0200
Subject: [PATCH 053/245] Update packaging from 20.3 to 20.4
---
misc/requirements/requirements-sphinx.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt
index 1556e1d93..8fb334a72 100644
--- a/misc/requirements/requirements-sphinx.txt
+++ b/misc/requirements/requirements-sphinx.txt
@@ -9,7 +9,7 @@ idna==2.9
imagesize==1.2.0
Jinja2==2.11.2
MarkupSafe==1.1.1
-packaging==20.3
+packaging==20.4
Pygments==2.6.1
pyparsing==2.4.7
pytz==2020.1
From 7303e52a41ee6b4a1e85e39f99dc6573ad4c220a Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:14 +0200
Subject: [PATCH 054/245] Update packaging from 20.3 to 20.4
---
misc/requirements/requirements-tests.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index 8e94f36d6..87c4b2881 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -18,7 +18,7 @@ Mako==1.1.2
manhole==1.6.0
# MarkupSafe==1.1.1
more-itertools==8.3.0
-packaging==20.3
+packaging==20.4
parse==1.15.0
parse-type==0.5.2
pluggy==0.13.1
From 6e009ba0a79d99d8c426411a1ae9cb4ad680e5bb Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:15 +0200
Subject: [PATCH 055/245] Update packaging from 20.3 to 20.4
---
misc/requirements/requirements-tox.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt
index 33bcd072b..78da631cb 100644
--- a/misc/requirements/requirements-tox.txt
+++ b/misc/requirements/requirements-tox.txt
@@ -3,7 +3,7 @@
appdirs==1.4.4
distlib==0.3.0
filelock==3.0.12
-packaging==20.3
+packaging==20.4
pluggy==0.13.1
py==1.8.1
pyparsing==2.4.7
From e3ba806cfcd74429d20927652889c57a011a2469 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:17 +0200
Subject: [PATCH 056/245] Update six from 1.14.0 to 1.15.0
---
misc/requirements/requirements-dev.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt
index cf40bd5bb..620e5772c 100644
--- a/misc/requirements/requirements-dev.txt
+++ b/misc/requirements/requirements-dev.txt
@@ -21,7 +21,7 @@ PyQt-builder==1.3.2
python-dateutil==2.8.1
requests==2.23.0
sip==5.2.0
-six==1.14.0
+six==1.15.0
toml==0.10.1
uritemplate==3.0.1
urllib3==1.25.9
From 55041090651633d97d4ae419056b0e23829b069a Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:18 +0200
Subject: [PATCH 057/245] Update six from 1.14.0 to 1.15.0
---
misc/requirements/requirements-flake8.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt
index e4b59b368..e636b3abc 100644
--- a/misc/requirements/requirements-flake8.txt
+++ b/misc/requirements/requirements-flake8.txt
@@ -20,5 +20,5 @@ pep8-naming==0.10.0
pycodestyle==2.6.0
pydocstyle==5.0.2
pyflakes==2.2.0
-six==1.14.0
+six==1.15.0
snowballstemmer==2.0.0
From 749af343cc99afd952af938d9a549f8a4dad4b68 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:19 +0200
Subject: [PATCH 058/245] Update six from 1.14.0 to 1.15.0
---
misc/requirements/requirements-pip.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt
index 6d2ee85ca..3a71ad0a8 100644
--- a/misc/requirements/requirements-pip.txt
+++ b/misc/requirements/requirements-pip.txt
@@ -4,5 +4,5 @@ appdirs==1.4.4
packaging==20.4
pyparsing==2.4.7
setuptools==46.4.0
-six==1.14.0
+six==1.15.0
wheel==0.34.2
From 3acadbffc11d25f1bc11af7805be7a070e37bd01 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:20 +0200
Subject: [PATCH 059/245] Update six from 1.14.0 to 1.15.0
---
misc/requirements/requirements-pylint.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt
index 1f5552a97..e80ddbf50 100644
--- a/misc/requirements/requirements-pylint.txt
+++ b/misc/requirements/requirements-pylint.txt
@@ -16,7 +16,7 @@ pylint==2.4.4 # rq.filter: < 2.5
python-dateutil==2.8.1
./scripts/dev/pylint_checkers
requests==2.23.0
-six==1.14.0
+six==1.15.0
typed-ast==1.4.1 ; python_version<"3.8"
uritemplate==3.0.1
urllib3==1.25.9
From 2191a258bd6bd7ff1af893e4fe11ecb503101530 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:21 +0200
Subject: [PATCH 060/245] Update six from 1.14.0 to 1.15.0
---
misc/requirements/requirements-sphinx.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt
index 8fb334a72..e4ca26a6a 100644
--- a/misc/requirements/requirements-sphinx.txt
+++ b/misc/requirements/requirements-sphinx.txt
@@ -14,7 +14,7 @@ Pygments==2.6.1
pyparsing==2.4.7
pytz==2020.1
requests==2.23.0
-six==1.14.0
+six==1.15.0
snowballstemmer==2.0.0
Sphinx==3.0.3
sphinxcontrib-applehelp==1.0.2
From 3ecfc815c8563d0464aaacc459bc25b67ddc7d13 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:22 +0200
Subject: [PATCH 061/245] Update six from 1.14.0 to 1.15.0
---
misc/requirements/requirements-tests.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index 87c4b2881..22663c1a8 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -38,7 +38,7 @@ pytest-rerunfailures==9.0
pytest-travis-fold==1.3.0
pytest-xvfb==1.2.0
PyVirtualDisplay==0.2.5
-six==1.14.0
+six==1.15.0
sortedcontainers==2.1.0
soupsieve==2.0.1
vulture==1.4
From 3f4495651b17430b8bba85098d07861d4eb174b2 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:23 +0200
Subject: [PATCH 062/245] Update six from 1.14.0 to 1.15.0
---
misc/requirements/requirements-tox.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt
index 78da631cb..a19a0b794 100644
--- a/misc/requirements/requirements-tox.txt
+++ b/misc/requirements/requirements-tox.txt
@@ -7,7 +7,7 @@ packaging==20.4
pluggy==0.13.1
py==1.8.1
pyparsing==2.4.7
-six==1.14.0
+six==1.15.0
toml==0.10.1
tox==3.15.0
tox-pip-version==0.0.7
From 4008b53ada5c17f619add27c404ac5acb2e309c2 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:24 +0200
Subject: [PATCH 063/245] Update flake8 from 3.8.1 to 3.8.2
---
misc/requirements/requirements-flake8.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt
index e636b3abc..d0292a4eb 100644
--- a/misc/requirements/requirements-flake8.txt
+++ b/misc/requirements/requirements-flake8.txt
@@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==19.3.0
-flake8==3.8.1
+flake8==3.8.2
flake8-bugbear==20.1.4
flake8-builtins==1.5.3
flake8-comprehensions==3.2.2
From bc8ba23710a7f6daf63dd1c78103187b836c9556 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:25 +0200
Subject: [PATCH 064/245] Update hypothesis from 5.14.0 to 5.15.1
---
misc/requirements/requirements-tests.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index 22663c1a8..b9db85998 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -10,7 +10,7 @@ EasyProcess==0.3
Flask==1.1.2
glob2==0.7
hunter==3.1.3
-hypothesis==5.14.0
+hypothesis==5.15.1
itsdangerous==1.1.0
jaraco.functools==3.0.1 ; python_version>="3.6"
# Jinja2==2.11.2
From fe1559dc39bda31466502d87cfe9b6c5830e0b61 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:26 +0200
Subject: [PATCH 065/245] Update pytest-cov from 2.8.1 to 2.9.0
---
misc/requirements/requirements-tests.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index b9db85998..1c0c9acde 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -29,7 +29,7 @@ pyparsing==2.4.7
pytest==5.4.2
pytest-bdd==3.3.0
pytest-benchmark==3.2.3
-pytest-cov==2.8.1
+pytest-cov==2.9.0
pytest-instafail==0.4.1.post0
pytest-mock==3.1.0
pytest-qt==3.3.0
From fbad4a3a822c55eb8971e21f871db9607855b6a8 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:27 +0200
Subject: [PATCH 066/245] Update tox from 3.15.0 to 3.15.1
---
misc/requirements/requirements-tox.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt
index a19a0b794..4c6795e53 100644
--- a/misc/requirements/requirements-tox.txt
+++ b/misc/requirements/requirements-tox.txt
@@ -9,7 +9,7 @@ py==1.8.1
pyparsing==2.4.7
six==1.15.0
toml==0.10.1
-tox==3.15.0
+tox==3.15.1
tox-pip-version==0.0.7
tox-venv==0.4.0
virtualenv==20.0.20
From a91f68b6ef92a0025a395283204fdac39db7b639 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 25 May 2020 21:11:28 +0200
Subject: [PATCH 067/245] Update virtualenv from 20.0.20 to 20.0.21
---
misc/requirements/requirements-tox.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt
index 4c6795e53..3b4c93bb4 100644
--- a/misc/requirements/requirements-tox.txt
+++ b/misc/requirements/requirements-tox.txt
@@ -12,4 +12,4 @@ toml==0.10.1
tox==3.15.1
tox-pip-version==0.0.7
tox-venv==0.4.0
-virtualenv==20.0.20
+virtualenv==20.0.21
From 0e7b3c91a0470e49decebb10c3a361e379c46f26 Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Tue, 26 May 2020 07:42:34 -0400
Subject: [PATCH 068/245] Ensure log configs consistent with defaults.
Previously the RAM handler defaulted to logging.NOTSET, which will allow
all loglevels to pass through (since the root logger is also NOTSET).
There was a separate check that omitted VDEBUG logs.
Let's explicitly set the RAMHandler to our desired loglevel instead.
---
qutebrowser/utils/log.py | 6 ++----
tests/unit/utils/test_log.py | 14 ++++++++++++++
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 635fccabb..683ee92f4 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -296,7 +296,7 @@ def _init_handlers(
ram_handler = None
else:
ram_handler = RAMHandler(capacity=ram_capacity)
- ram_handler.setLevel(logging.NOTSET)
+ ram_handler.setLevel(logging.DEBUG)
ram_handler.setFormatter(ram_fmt)
ram_handler.html_formatter = html_fmt
@@ -629,9 +629,7 @@ class RAMHandler(logging.Handler):
self._data = collections.deque()
def emit(self, record: logging.LogRecord) -> None:
- if record.levelno >= logging.DEBUG:
- # We don't log VDEBUG to RAM.
- self._data.append(record)
+ self._data.append(record)
def dump_log(self, html: bool = False, level: str = 'vdebug') -> str:
"""Dump the complete formatted log data as string.
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index eb634d76a..2ced94c42 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -30,6 +30,7 @@ import pytest
import _pytest.logging
from PyQt5 import QtCore
+from qutebrowser import qutebrowser
from qutebrowser.utils import log
from qutebrowser.misc import utilcmds
@@ -286,6 +287,19 @@ class TestInitLog:
log.init_from_config(config_stub.val)
assert log.ram_handler.level == expected
+ def test_init_from_config_consistent_default(self, config_stub):
+ """Ensure config defaults are consistent with the builtin defaults"""
+ args = qutebrowser.get_argparser().parse_args([])
+ log.init_log(args)
+
+ assert log.ram_handler.level == logging.DEBUG
+ assert log.console_handler.level == logging.INFO
+
+ log.init_from_config(config_stub.val)
+
+ assert log.ram_handler.level == logging.DEBUG
+ assert log.console_handler.level == logging.INFO
+
class TestHideQtWarning:
From 98965f5c083a8b2859d721b7ea0e8e7170e7c847 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 26 May 2020 14:14:02 +0200
Subject: [PATCH 069/245] tests: Improve IDs in test_configtypes
---
tests/unit/config/test_configtypes.py | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
index e49075500..abd79820c 100644
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -216,13 +216,21 @@ class TestAll:
pass
elif (member is configtypes.List or
member is configtypes.ListOrValue):
- yield functools.partial(member, valtype=configtypes.Int())
- yield functools.partial(member, valtype=configtypes.Url())
+ yield pytest.param(
+ functools.partial(member, valtype=configtypes.Int()),
+ id=member.__name__ + '-Int')
+ yield pytest.param(
+ functools.partial(member, valtype=configtypes.Url()),
+ id=member.__name__ + '-Url')
elif member is configtypes.Dict:
- yield functools.partial(member, keytype=configtypes.String(),
- valtype=configtypes.String())
+ yield pytest.param(
+ functools.partial(member, keytype=configtypes.String(),
+ valtype=configtypes.String()),
+ id=member.__name__)
elif member is configtypes.FormatString:
- yield functools.partial(member, fields=['a', 'b'])
+ yield pytest.param(
+ functools.partial(member, fields=['a', 'b']),
+ id=member.__name__)
elif issubclass(member, configtypes.BaseType):
yield member
From 84fcdd51c0ac9baa93e4481999cc93864beb7e60 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 26 May 2020 14:23:01 +0200
Subject: [PATCH 070/245] tests: Return early in hypothesis test for List
configtype
See https://github.com/qutebrowser/qutebrowser/issues/5390#issuecomment-622881094
---
tests/unit/config/test_configtypes.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
index abd79820c..18019fb4f 100644
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -271,8 +271,10 @@ class TestAll:
]:
return
elif (isinstance(typ, functools.partial) and
- isinstance(typ.func, configtypes.ListOrValue)):
- # "- /" -> "/"
+ isinstance(typ.func, (configtypes.ListOrValue,
+ configtypes.List))):
+ # ListOrValue: "- /" -> "/"
+ # List: "- /" -> ["/"]
return
elif (isinstance(typ, configtypes.ListOrValue) and
isinstance(typ.valtype, configtypes.Int)):
From aa0d0ccb5db7f5278b8dbeb2115a6e9bc4fb9b25 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 26 May 2020 14:29:59 +0200
Subject: [PATCH 071/245] tests: Try increasing file_updated timeout
See https://github.com/qutebrowser/qutebrowser/issues/5390#issuecomment-631066927
---
tests/unit/misc/test_editor.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py
index edd32b5a5..5fb4478a7 100644
--- a/tests/unit/misc/test_editor.py
+++ b/tests/unit/misc/test_editor.py
@@ -167,7 +167,7 @@ class TestFileHandling:
def test_backup(self, qtbot, message_mock):
editor = editormod.ExternalEditor(watch=True)
editor.edit('foo')
- with qtbot.wait_signal(editor.file_updated):
+ with qtbot.wait_signal(editor.file_updated, timeout=5000):
_update_file(editor._filename, 'bar')
editor.backup()
From 286c91c01e94da742b45fb6cffeb0ae5825519fe Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 26 May 2020 15:27:15 +0200
Subject: [PATCH 072/245] Log exception traceback for init errors
Also improve error handling in test_err_windows
---
qutebrowser/utils/error.py | 1 +
tests/unit/utils/test_error.py | 13 ++++++++-----
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/qutebrowser/utils/error.py b/qutebrowser/utils/error.py
index cfc3c3f5a..4cba06a10 100644
--- a/qutebrowser/utils/error.py
+++ b/qutebrowser/utils/error.py
@@ -63,6 +63,7 @@ def handle_fatal_exc(exc: BaseException,
]
log.misc.exception('\n'.join(lines))
else:
+ log.misc.exception("Fatal exception:")
if pre_text:
msg_text = '{}: {}'.format(pre_text, exc)
else:
diff --git a/tests/unit/utils/test_error.py b/tests/unit/utils/test_error.py
index 82ca3ec55..a1a7ea6de 100644
--- a/tests/unit/utils/test_error.py
+++ b/tests/unit/utils/test_error.py
@@ -73,10 +73,11 @@ def test_no_err_windows(caplog, exc, name, exc_text):
('foo', 'bar', 'foo: exception\n\nbar'),
('', 'bar', 'exception\n\nbar'),
], ids=repr)
-def test_err_windows(qtbot, qapp, pre_text, post_text, expected):
+def test_err_windows(qtbot, qapp, pre_text, post_text, expected, caplog):
def err_window_check():
w = qapp.activeModalWidget()
+ assert w is not None
try:
qtbot.add_widget(w)
if not utils.is_mac:
@@ -87,7 +88,9 @@ def test_err_windows(qtbot, qapp, pre_text, post_text, expected):
finally:
w.close()
- QTimer.singleShot(0, err_window_check)
- error.handle_fatal_exc(ValueError("exception"), 'title',
- pre_text=pre_text, post_text=post_text,
- no_err_windows=False)
+ QTimer.singleShot(10, err_window_check)
+
+ with caplog.at_level(logging.ERROR):
+ error.handle_fatal_exc(ValueError("exception"), 'title',
+ pre_text=pre_text, post_text=post_text,
+ no_err_windows=False)
From e5c2c0fb799c4a2837aeedaabd366e0c7c299575 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 26 May 2020 16:31:00 +0200
Subject: [PATCH 073/245] Fix indent
---
tests/unit/utils/test_error.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tests/unit/utils/test_error.py b/tests/unit/utils/test_error.py
index a1a7ea6de..f8847d39a 100644
--- a/tests/unit/utils/test_error.py
+++ b/tests/unit/utils/test_error.py
@@ -92,5 +92,5 @@ def test_err_windows(qtbot, qapp, pre_text, post_text, expected, caplog):
with caplog.at_level(logging.ERROR):
error.handle_fatal_exc(ValueError("exception"), 'title',
- pre_text=pre_text, post_text=post_text,
- no_err_windows=False)
+ pre_text=pre_text, post_text=post_text,
+ no_err_windows=False)
From 093a454bf4e386eda8715bc3ea2b217760d5f070 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 26 May 2020 16:48:00 +0200
Subject: [PATCH 074/245] Update Qt 5.15 session workaround for lazy_restore
See #5359
---
doc/changelog.asciidoc | 3 +++
qutebrowser/browser/webengine/webenginetab.py | 6 +++++-
qutebrowser/html/warning-sessions.html | 1 +
3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 741558ec1..d6e42e2b6 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -66,6 +66,9 @@ Fixed
`QWebEngineFindTextResult` handling added in v1.11.0.
- When entering caret selection mode (`v, v`) very early before a page is
loaded, an error is now shown instead of a crash happening.
+- The workaround for session loading with Qt 5.15 now handles
+ `sessions.lazy_restore` so that the saved page is loaded instead of the
+ "stub" page with no possibility to get to the web page.
v1.11.1 (2020-05-07)
--------------------
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index 709c5c1b5..5fd4a9e11 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -685,7 +685,11 @@ class WebEngineHistoryPrivate(browsertab.AbstractHistoryPrivate):
if qtutils.version_check('5.15', compiled=False):
# WORKAROUND for https://github.com/qutebrowser/qutebrowser/issues/5359
if items:
- self._tab.load_url(items[-1].url)
+ url = items[-1].url
+ if ((url.scheme(), url.host()) == ('qute', 'back') and
+ len(items) >= 2):
+ url = items[-2].url
+ self._tab.load_url(url)
return
if items:
diff --git a/qutebrowser/html/warning-sessions.html b/qutebrowser/html/warning-sessions.html
index f93971c6a..dd0c4127b 100644
--- a/qutebrowser/html/warning-sessions.html
+++ b/qutebrowser/html/warning-sessions.html
@@ -15,6 +15,7 @@ qute://warning/sessions to show it again at a later time.
- Loading a session with this release will only load the most recently opened page for every tab. As a result, the back/forward-history of every tab will be lost as soon as the session is saved again.
+ - Due to that, the session.lazy_restore setting does not have any effect.
- A one-time backup of the session folder has been created at {{ datadir }}{{ sep }}sessions{{ sep }}before-qt-515.
From 0d191a47bb34959102ddc2e1ecc07896dab3eda6 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 26 May 2020 16:55:02 +0200
Subject: [PATCH 075/245] Update completions for content.headers.user_agent
---
qutebrowser/config/configdata.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 3700e5e1d..46b745089 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -562,10 +562,10 @@ content.headers.user_agent:
completions:
# See https://techblog.willshouse.com/2012/01/03/most-common-user-agents/
- - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
- like Gecko) Chrome/80.0.3987.163 Safari/537.36"
+ like Gecko) Chrome/81.0.4044.129 Safari/537.36"
- Chrome 80 Win10
- - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like
- Gecko) Chrome/80.0.3987.149 Safari/537.36 "
+ Gecko) Chrome/81.0.4044.138 Safari/537.36"
- Chrome 80 Linux
supports_pattern: true
desc: |
From e8b0ce7597627a5b5312a214a84ff2aed9bcdfe0 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 26 May 2020 20:20:56 +0200
Subject: [PATCH 076/245] Add filename if QtOSError is used with QFileDevice
If we have a filename available, let's add it to the error message.
This also effectively reverts 00747be9d3790534e8b32464605d1b5b6c2d6627 since
that's not needed anymore (Qt 5.7 is the oldest supported release).
---
qutebrowser/utils/qtutils.py | 19 ++++++++++---------
tests/unit/utils/test_qtutils.py | 6 +++---
2 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py
index 3e8afae3f..109d2dfed 100644
--- a/qutebrowser/utils/qtutils.py
+++ b/qutebrowser/utils/qtutils.py
@@ -35,7 +35,7 @@ import typing
import pkg_resources
from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray,
- QIODevice, QSaveFile, QT_VERSION_STR,
+ QIODevice, QFileDevice, QSaveFile, QT_VERSION_STR,
PYQT_VERSION_STR, QObject, QUrl)
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QApplication
@@ -44,9 +44,6 @@ try:
except ImportError: # pragma: no cover
qWebKitVersion = None # type: ignore[assignment] # noqa: N816
-if typing.TYPE_CHECKING:
- from PyQt5.QtCore import QFileDevice
-
from qutebrowser.misc import objects
from qutebrowser.utils import usertypes
@@ -74,13 +71,17 @@ class QtOSError(OSError):
if msg is None:
msg = dev.errorString()
+ self.qt_errno = None # type: typing.Optional[QFileDevice.FileError]
+ if isinstance(dev, QFileDevice):
+ msg = self._init_filedev(dev, msg)
+
super().__init__(msg)
- self.qt_errno = None # type: typing.Optional[QFileDevice.FileError]
- try:
- self.qt_errno = dev.error()
- except AttributeError:
- pass
+ def _init_filedev(self, dev: QFileDevice, msg: str) -> str:
+ self.qt_errno = dev.error()
+ filename = dev.fileName()
+ msg += ": {!r}".format(filename)
+ return msg
def version_check(version: str,
diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py
index 150a03f6e..81d198946 100644
--- a/tests/unit/utils/test_qtutils.py
+++ b/tests/unit/utils/test_qtutils.py
@@ -467,9 +467,9 @@ class TestSavefileOpen:
with pytest.raises(OSError) as excinfo:
with qtutils.savefile_open(str(filename)):
pass
- errors = ["Filename refers to a directory", # Qt >= 5.4
- "Commit failed!"] # older Qt versions
- assert str(excinfo.value) in errors
+
+ msg = "Filename refers to a directory: {!r}".format(str(filename))
+ assert str(excinfo.value) == msg
assert tmpdir.listdir() == [filename]
def test_failing_flush(self, tmpdir):
From 64ffce27f9348e14836528c0313d3048669a29c7 Mon Sep 17 00:00:00 2001
From: Julin S <48789920+ju-sh@users.noreply.github.com>
Date: Wed, 27 May 2020 11:12:45 +0530
Subject: [PATCH 077/245] fix error in asciidoc2html.py script
---
scripts/asciidoc2html.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py
index 90b94f014..2f64eac52 100755
--- a/scripts/asciidoc2html.py
+++ b/scripts/asciidoc2html.py
@@ -121,7 +121,7 @@ class AsciiDoc:
src = root / filename
assert self._website is not None # for mypy
dst = pathlib.Path(self._website)
- dst = src.parent.relative_to('.') / (src.stem + ".html")
+ dst = dst / src.parent.relative_to('.') / (src.stem + ".html")
dst.parent.mkdir(exist_ok=True)
assert self._tempdir is not None # for mypy
From 8ab4133bfc5bae1656f74c87a56d2d5f45a1c788 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 27 May 2020 12:43:40 +0200
Subject: [PATCH 078/245] configtypes: Fix handling of Unset in _Numeric with
bounds
---
qutebrowser/config/configtypes.py | 18 +++++++++++++-----
tests/unit/config/test_configtypes.py | 4 ++++
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py
index 6eec13293..cfcc5f13a 100644
--- a/qutebrowser/config/configtypes.py
+++ b/qutebrowser/config/configtypes.py
@@ -797,11 +797,16 @@ class _Numeric(BaseType): # pylint: disable=abstract-method
assert isinstance(bound, (int, float)), bound
return bound
- def _validate_bounds(self, value: typing.Union[None, int, float],
- suffix: str = '') -> None:
+ def _validate_bounds(
+ self,
+ value: typing.Union[None, int, float, usertypes.Unset],
+ suffix: str = ''
+ ) -> None:
"""Validate self.minval and self.maxval."""
if value is None:
return
+ elif isinstance(value, usertypes.Unset):
+ return
elif self.minval is not None and value < self.minval:
raise configexc.ValidationError(
value, "must be {}{} or bigger!".format(self.minval, suffix))
@@ -837,7 +842,10 @@ class Int(_Numeric):
self.to_py(intval)
return intval
- def to_py(self, value: typing.Optional[int]) -> typing.Optional[int]:
+ def to_py(
+ self,
+ value: typing.Union[None, int, usertypes.Unset]
+ ) -> typing.Union[None, int, usertypes.Unset]:
self._basic_py_validation(value, int)
self._validate_bounds(value)
return value
@@ -861,8 +869,8 @@ class Float(_Numeric):
def to_py(
self,
- value: typing.Union[None, int, float],
- ) -> typing.Union[None, int, float]:
+ value: typing.Union[None, int, float, usertypes.Unset],
+ ) -> typing.Union[None, int, float, usertypes.Unset]:
self._basic_py_validation(value, (int, float))
self._validate_bounds(value)
return value
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
index 18019fb4f..841892ef2 100644
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -1047,6 +1047,10 @@ class TestInt:
converted = typ.from_str(text)
assert typ.to_str(converted) == text
+ def test_bounds_handling_unset(self, klass):
+ typ = klass(minval=1, maxval=2)
+ assert typ.to_py(usertypes.UNSET) is usertypes.UNSET
+
class TestFloat:
From 70625c3f8718124137e087b52db66983cf4df0ef Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 27 May 2020 12:49:04 +0200
Subject: [PATCH 079/245] configtypes: Add alias for "Union[None,
usertypes.Unset]"
---
qutebrowser/config/configtypes.py | 52 +++++++++++++------------------
1 file changed, 21 insertions(+), 31 deletions(-)
diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py
index cfcc5f13a..61284c34e 100644
--- a/qutebrowser/config/configtypes.py
+++ b/qutebrowser/config/configtypes.py
@@ -82,7 +82,8 @@ BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True,
_Completions = typing.Optional[typing.Iterable[typing.Tuple[str, str]]]
_StrUnset = typing.Union[str, usertypes.Unset]
-_StrUnsetNone = typing.Union[None, str, usertypes.Unset]
+_UnsetNone = typing.Union[None, usertypes.Unset]
+_StrUnsetNone = typing.Union[str, _UnsetNone]
class ValidValues:
@@ -797,11 +798,9 @@ class _Numeric(BaseType): # pylint: disable=abstract-method
assert isinstance(bound, (int, float)), bound
return bound
- def _validate_bounds(
- self,
- value: typing.Union[None, int, float, usertypes.Unset],
- suffix: str = ''
- ) -> None:
+ def _validate_bounds(self,
+ value: typing.Union[int, float, _UnsetNone],
+ suffix: str = '') -> None:
"""Validate self.minval and self.maxval."""
if value is None:
return
@@ -844,8 +843,8 @@ class Int(_Numeric):
def to_py(
self,
- value: typing.Union[None, int, usertypes.Unset]
- ) -> typing.Union[None, int, usertypes.Unset]:
+ value: typing.Union[int, _UnsetNone]
+ ) -> typing.Union[int, _UnsetNone]:
self._basic_py_validation(value, int)
self._validate_bounds(value)
return value
@@ -869,8 +868,8 @@ class Float(_Numeric):
def to_py(
self,
- value: typing.Union[None, int, float, usertypes.Unset],
- ) -> typing.Union[None, int, float, usertypes.Unset]:
+ value: typing.Union[int, float, _UnsetNone],
+ ) -> typing.Union[int, float, _UnsetNone]:
self._basic_py_validation(value, (int, float))
self._validate_bounds(value)
return value
@@ -882,8 +881,8 @@ class Perc(_Numeric):
def to_py(
self,
- value: typing.Union[None, float, int, str, usertypes.Unset]
- ) -> typing.Union[None, float, int, usertypes.Unset]:
+ value: typing.Union[float, int, str, _UnsetNone]
+ ) -> typing.Union[float, int, _UnsetNone]:
self._basic_py_validation(value, (float, int, str))
if isinstance(value, usertypes.Unset):
return value
@@ -1078,8 +1077,7 @@ class QtColor(BaseType):
except ValueError:
raise configexc.ValidationError(val, "must be a valid color value")
- def to_py(self, value: _StrUnset) -> typing.Union[usertypes.Unset,
- None, QColor]:
+ def to_py(self, value: _StrUnset) -> typing.Union[_UnsetNone, QColor]:
self._basic_py_validation(value, str)
if isinstance(value, usertypes.Unset):
return value
@@ -1362,8 +1360,7 @@ class QtFont(FontBase):
else: # pragma: no cover
font.setFamily(families.to_str(quote=False))
- def to_py(self, value: _StrUnset) -> typing.Union[usertypes.Unset,
- None, QFont]:
+ def to_py(self, value: _StrUnset) -> typing.Union[_UnsetNone, QFont]:
self._basic_py_validation(value, str)
if isinstance(value, usertypes.Unset):
return value
@@ -1442,7 +1439,7 @@ class Regex(BaseType):
def to_py(
self,
value: typing.Union[str, typing.Pattern[str], usertypes.Unset]
- ) -> typing.Union[usertypes.Unset, None, typing.Pattern[str]]:
+ ) -> typing.Union[_UnsetNone, typing.Pattern[str]]:
"""Get a compiled regex from either a string or a regex object."""
self._basic_py_validation(value, (str, self._regex_type))
if isinstance(value, usertypes.Unset):
@@ -1533,7 +1530,7 @@ class Dict(BaseType):
def to_py(
self,
- value: typing.Union[typing.Dict, usertypes.Unset, None]
+ value: typing.Union[typing.Dict, _UnsetNone]
) -> typing.Union[typing.Dict, usertypes.Unset]:
self._basic_py_validation(value, dict)
if isinstance(value, usertypes.Unset):
@@ -1732,8 +1729,7 @@ class Proxy(BaseType):
def to_py(
self,
value: _StrUnset
- ) -> typing.Union[usertypes.Unset, None,
- QNetworkProxy, _SystemProxy, pac.PACFetcher]:
+ ) -> typing.Union[_UnsetNone, QNetworkProxy, _SystemProxy, pac.PACFetcher]:
self._basic_py_validation(value, str)
if isinstance(value, usertypes.Unset):
return value
@@ -1803,10 +1799,7 @@ class FuzzyUrl(BaseType):
"""A URL which gets interpreted as search if needed."""
- def to_py(
- self,
- value: _StrUnset
- ) -> typing.Union[None, QUrl, usertypes.Unset]:
+ def to_py(self, value: _StrUnset) -> typing.Union[QUrl, _UnsetNone]:
self._basic_py_validation(value, str)
if isinstance(value, usertypes.Unset):
return value
@@ -1844,7 +1837,7 @@ class Padding(Dict):
def to_py( # type: ignore[override]
self,
- value: typing.Union[usertypes.Unset, typing.Dict, None],
+ value: typing.Union[typing.Dict, _UnsetNone],
) -> typing.Union[usertypes.Unset, PaddingValues]:
d = super().to_py(value)
if isinstance(d, usertypes.Unset):
@@ -1916,10 +1909,7 @@ class Url(BaseType):
"""A URL as a string."""
- def to_py(
- self,
- value: _StrUnset
- ) -> typing.Union[usertypes.Unset, None, QUrl]:
+ def to_py(self, value: _StrUnset) -> typing.Union[_UnsetNone, QUrl]:
self._basic_py_validation(value, str)
if isinstance(value, usertypes.Unset):
return value
@@ -2033,7 +2023,7 @@ class Key(BaseType):
def to_py(
self,
value: _StrUnset
- ) -> typing.Union[usertypes.Unset, None, keyutils.KeySequence]:
+ ) -> typing.Union[_UnsetNone, keyutils.KeySequence]:
self._basic_py_validation(value, str)
if isinstance(value, usertypes.Unset):
return value
@@ -2057,7 +2047,7 @@ class UrlPattern(BaseType):
def to_py(
self,
value: _StrUnset
- ) -> typing.Union[usertypes.Unset, None, urlmatch.UrlPattern]:
+ ) -> typing.Union[_UnsetNone, urlmatch.UrlPattern]:
self._basic_py_validation(value, str)
if isinstance(value, usertypes.Unset):
return value
From 85aee23639bb78c07151618af031317c9ecd3408 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 27 May 2020 15:02:44 +0200
Subject: [PATCH 080/245] Add dark mode settings
Closes #5394
See #2377
---
doc/changelog.asciidoc | 3 +
doc/help/settings.asciidoc | 163 +++++++++++++++++++++++++++
qutebrowser/config/configdata.yml | 163 +++++++++++++++++++++++++++
qutebrowser/config/configinit.py | 89 +++++++++++++++
scripts/dev/misc_checks.py | 2 +-
tests/unit/config/test_configinit.py | 116 ++++++++++++++++++-
6 files changed, 534 insertions(+), 2 deletions(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index d6e42e2b6..a6aead789 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -41,6 +41,9 @@ Added
- New `colors.contextmenu.disabled.{fg,bg}` settings to customize colors for
disabled items in the context menu.
- New line selection mode (`:toggle-selection --line`), bound to `Shift-V` in caret mode.
+- New `colors.webpage.darkmode.*` settings to control Chromium's dark mode.
+ Note that those settings only work with QtWebEngine on Qt >= 5.14 and require
+ a restart of qutebrowser.
Changed
~~~~~~~
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 4c20126c3..3787238a9 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -111,6 +111,15 @@
|<>|Background color of selected odd tabs.
|<>|Foreground color of selected odd tabs.
|<>|Background color for webpages if unset (or empty to use the theme's color).
+|<>|Which algorithm to use for modifying how colors are rendered with darkmode.
+|<>|Contrast for dark mode.
+|<>|Render all web contents using a dark theme.
+|<>|Render all colors as grayscale.
+|<>|Desaturation factor for images in dark mode.
+|<>|Which images to apply dark mode to.
+|<>|Which pages to apply dark mode to.
+|<>|Threshold for inverting background elements with dark mode.
+|<>|Threshold for inverting text with dark mode.
|<>|Force `prefers-color-scheme: dark` colors for websites.
|<>|Number of commands to save in the command history.
|<>|Delay (in milliseconds) before updating completions after typing a character.
@@ -1534,6 +1543,160 @@ Type: <>
Default: +pass:[white]+
+[[colors.webpage.darkmode.algorithm]]
+=== colors.webpage.darkmode.algorithm
+Which algorithm to use for modifying how colors are rendered with darkmode.
+This setting requires a restart.
+
+Type: <>
+
+Valid values:
+
+ * +lightness-cielab+: Modify colors by converting them to CIELAB color space and inverting the L value.
+ * +lightness-hsl+: Modify colors by converting them to the HSL color space and inverting the lightness (i.e. the "L" in HSL).
+ * +brightness-rgb+: Modify colors by subtracting each of r, g, and b from their maximum value.
+
+Default: +pass:[lightness-cielab]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.contrast]]
+=== colors.webpage.darkmode.contrast
+Contrast for dark mode.
+This only has an effect when `colors.webpage.darkmode.algorithm` is set to `lightness-hsl` or `brightness-rgb`.
+This setting requires a restart.
+
+Type: <>
+
+Default: +pass:[0.0]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.enabled]]
+=== colors.webpage.darkmode.enabled
+Render all web contents using a dark theme.
+Example configurations from Chromium's `chrome://flags`:
+
+- "With simple HSL/CIELAB/RGB-based inversion": Set
+ `colors.webpage.darkmode.algorithm` accordingly.
+
+- "With selective image inversion": Set
+ `colors.webpage.darkmode.policy.images` to `smart`.
+
+- "With selective inversion of non-image elements": Set
+ `colors.webpage.darkmode.threshold.text` to 150 and
+ `colors.webpage.darkmode.threshold.background` to 205.
+
+- "With selective inversion of everything": Combines the two variants
+ above.
+This setting requires a restart.
+
+Type: <>
+
+Default: +pass:[false]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.grayscale.all]]
+=== colors.webpage.darkmode.grayscale.all
+Render all colors as grayscale.
+This only has an effect when `colors.webpage.darkmode.algorithm` is set to `lightness-hsl` or `brightness-rgb`.
+This setting requires a restart.
+
+Type: <>
+
+Default: +pass:[false]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.grayscale.images]]
+=== colors.webpage.darkmode.grayscale.images
+Desaturation factor for images in dark mode.
+If set to 0, images are left as-is. If set to 1, images are completely grayscale. Values between 0 and 1 desaturate the colors accordingly.
+This setting requires a restart.
+
+Type: <>
+
+Default: +pass:[0.0]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.policy.images]]
+=== colors.webpage.darkmode.policy.images
+Which images to apply dark mode to.
+This setting requires a restart.
+
+Type: <>
+
+Valid values:
+
+ * +always+: Apply dark mode filter to all images.
+ * +never+: Never apply dark mode filter to any images.
+ * +smart+: Apply dark mode based on image content.
+
+Default: +pass:[never]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.policy.page]]
+=== colors.webpage.darkmode.policy.page
+Which pages to apply dark mode to.
+This setting requires a restart.
+
+Type: <>
+
+Valid values:
+
+ * +always+: Apply dark mode filter to all frames, regardless of content.
+ * +smart+: Apply dark mode filter to frames based on background color.
+
+Default: +pass:[smart]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.threshold.background]]
+=== colors.webpage.darkmode.threshold.background
+Threshold for inverting background elements with dark mode.
+Background elements with brightness above this threshold will be inverted, and below it will be left as in the original, non-dark-mode page. Set to 256 to never invert the color or to 0 to always invert it.
+Note: This behavior is the opposite of `colors.webpage.darkmode.threshold.text`!
+This setting requires a restart.
+
+Type: <>
+
+Default: +pass:[0]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.threshold.text]]
+=== colors.webpage.darkmode.threshold.text
+Threshold for inverting text with dark mode.
+Text colors with brightness below this threshold will be inverted, and above it will be left as in the original, non-dark-mode page. Set to 256 to always invert text color or to 0 to never invert text color.
+This setting requires a restart.
+
+Type: <>
+
+Default: +pass:[256]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
[[colors.webpage.prefers_color_scheme_dark]]
=== colors.webpage.prefers_color_scheme_dark
Force `prefers-color-scheme: dark` colors for websites.
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 46b745089..dc49e4cdd 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -2612,6 +2612,169 @@ colors.webpage.prefers_color_scheme_dark:
QtWebEngine: Qt 5.14
QtWebKit: false
+## dark mode
+
+# darkModeClassifierType is not exposed, as the icon classifier isn't actually
+# implemented in Chromium:
+#
+# https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/graphics/dark_mode_icon_classifier.cc
+
+colors.webpage.darkmode.enabled:
+ default: false
+ type: Bool
+ desc: >-
+ Render all web contents using a dark theme.
+
+ Example configurations from Chromium's `chrome://flags`:
+
+
+ - "With simple HSL/CIELAB/RGB-based inversion": Set
+ `colors.webpage.darkmode.algorithm` accordingly.
+
+ - "With selective image inversion": Set
+ `colors.webpage.darkmode.policy.images` to `smart`.
+
+ - "With selective inversion of non-image elements": Set
+ `colors.webpage.darkmode.threshold.text` to 150 and
+ `colors.webpage.darkmode.threshold.background` to 205.
+
+ - "With selective inversion of everything": Combines the two variants
+ above.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.algorithm:
+ default: lightness-cielab
+ desc: "Which algorithm to use for modifying how colors are rendered with
+ darkmode."
+ type:
+ name: String
+ valid_values:
+ - lightness-cielab: Modify colors by converting them to CIELAB color
+ space and inverting the L value.
+ - lightness-hsl: Modify colors by converting them to the HSL color space
+ and inverting the lightness (i.e. the "L" in HSL).
+ - brightness-rgb: Modify colors by subtracting each of r, g, and b from
+ their maximum value.
+ # kSimpleInvertForTesting is not exposed, as it's equivalent to
+ # kInvertBrightness without gamma correction, and only available for
+ # Chromium's automated tests
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.contrast:
+ default: 0.0
+ type:
+ name: Float
+ minval: -1.0
+ maxval: 1.0
+ desc: >-
+ Contrast for dark mode.
+
+ This only has an effect when `colors.webpage.darkmode.algorithm` is set to
+ `lightness-hsl` or `brightness-rgb`.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.policy.images:
+ default: never
+ type:
+ name: String
+ valid_values:
+ - always: Apply dark mode filter to all images.
+ - never: Never apply dark mode filter to any images.
+ - smart: Apply dark mode based on image content.
+ desc: Which images to apply dark mode to.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.policy.page:
+ default: smart
+ type:
+ name: String
+ valid_values:
+ - always: Apply dark mode filter to all frames, regardless of content.
+ - smart: Apply dark mode filter to frames based on background color.
+ desc: Which pages to apply dark mode to.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.threshold.text:
+ default: 256
+ type:
+ name: Int
+ minval: 0
+ maxval: 256
+ desc: >-
+ Threshold for inverting text with dark mode.
+
+ Text colors with brightness below this threshold will be inverted, and
+ above it will be left as in the original, non-dark-mode page. Set to 256
+ to always invert text color or to 0 to never invert text color.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.threshold.background:
+ default: 0
+ type:
+ name: Int
+ minval: 0
+ maxval: 256
+ desc: >-
+ Threshold for inverting background elements with dark mode.
+
+ Background elements with brightness above this threshold will be inverted,
+ and below it will be left as in the original, non-dark-mode page. Set to
+ 256 to never invert the color or to 0 to always invert it.
+
+ Note: This behavior is the opposite of
+ `colors.webpage.darkmode.threshold.text`!
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.grayscale.all:
+ default: false
+ type: Bool
+ desc: >-
+ Render all colors as grayscale.
+
+ This only has an effect when `colors.webpage.darkmode.algorithm` is set to
+ `lightness-hsl` or `brightness-rgb`.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.grayscale.images:
+ default: 0.0
+ type:
+ name: Float
+ minval: 0.0
+ maxval: 1.0
+ desc: >-
+ Desaturation factor for images in dark mode.
+
+ If set to 0, images are left as-is. If set to 1, images are completely
+ grayscale. Values between 0 and 1 desaturate the colors accordingly.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
# emacs: '
## fonts
diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py
index b15225210..3c80cfe1b 100644
--- a/qutebrowser/config/configinit.py
+++ b/qutebrowser/config/configinit.py
@@ -199,6 +199,90 @@ def qt_args(namespace: argparse.Namespace) -> typing.List[str]:
return argv
+def _darkmode_settings() -> typing.Iterator[typing.Tuple[str, str]]:
+ """Get necessary blink settings to configure dark mode for QtWebEngine."""
+ if not config.val.colors.webpage.darkmode.enabled:
+ return
+
+ # Mapping from a colors.webpage.darkmode.algorithm setting value to
+ # Chromium's DarkModeInversionAlgorithm enum values.
+ algorithms = {
+ # 0: kOff (not exposed)
+ # 1: kSimpleInvertForTesting (not exposed)
+ 'brightness-rgb': 2, # kInvertBrightness
+ 'lightness-hsl': 3, # kInvertLightness
+ 'lightness-cielab': 4, # kInvertLightnessLAB
+ }
+
+ # Mapping from a colors.webpage.darkmode.policy.images setting value to
+ # Chromium's DarkModeImagePolicy enum values.
+ image_policies = {
+ 'always': 0, # kFilterAll
+ 'never': 1, # kFilterNone
+ 'smart': 2, # kFilterSmart
+ }
+
+ # Mapping from a colors.webpage.darkmode.policy.page setting value to
+ # Chromium's DarkModePagePolicy enum values.
+ page_policies = {
+ 'always': 0, # kFilterAll
+ 'smart': 1, # kFilterByBackground
+ }
+
+ bools = {
+ True: 'true',
+ False: 'false',
+ }
+
+ _setting_description_type = typing.Tuple[
+ str, # qutebrowser option name
+ str, # darkmode setting name
+ # Mapping from the config value to a string (or something convertable
+ # to a string) which gets passed to Chromium.
+ typing.Optional[typing.Mapping[typing.Any, typing.Union[str, int]]],
+ ]
+ if qtutils.version_check('5.15', compiled=False):
+ settings = [
+ ('enabled', 'Enabled', bools),
+ ('algorithm', 'InversionAlgorithm', algorithms),
+ ] # type: typing.List[_setting_description_type]
+ mandatory_setting = 'enabled'
+ else:
+ settings = [
+ ('algorithm', '', algorithms),
+ ]
+ mandatory_setting = 'algorithm'
+
+ settings += [
+ ('contrast', 'Contrast', None),
+ ('policy.images', 'ImagePolicy', image_policies),
+ ('policy.page', 'PagePolicy', page_policies),
+ ('threshold.text', 'TextBrightnessThreshold', None),
+ ('threshold.background', 'BackgroundBrightnessThreshold', None),
+ ('grayscale.all', 'Grayscale', bools),
+ ('grayscale.images', 'ImageGrayscale', None),
+ ]
+
+ for setting, key, mapping in settings:
+ # To avoid blowing up the commandline length, we only pass modified
+ # settings to Chromium, as our defaults line up with Chromium's.
+ # However, we always pass enabled/algorithm to make sure dark mode gets
+ # actually turned on.
+ value = config.instance.get(
+ 'colors.webpage.darkmode.' + setting,
+ fallback=setting == mandatory_setting)
+ if isinstance(value, usertypes.Unset):
+ continue
+
+ if mapping is not None:
+ value = mapping[value]
+
+ # FIXME: This is "forceDarkMode" starting with Chromium 83
+ prefix = 'darkMode'
+
+ yield prefix + key, str(value)
+
+
def _qtwebengine_args(namespace: argparse.Namespace) -> typing.Iterator[str]:
"""Get the QtWebEngine arguments to use based on the config."""
if not qtutils.version_check('5.11', compiled=False):
@@ -224,6 +308,11 @@ def _qtwebengine_args(namespace: argparse.Namespace) -> typing.Iterator[str]:
yield '--enable-logging'
yield '--v=1'
+ blink_settings = list(_darkmode_settings())
+ if blink_settings:
+ yield '--blink-settings=' + ','.join('{}={}'.format(k, v)
+ for k, v in blink_settings)
+
settings = {
'qt.force_software_rendering': {
'software-opengl': None,
diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py
index 24c3a1ddc..6bf411bba 100644
--- a/scripts/dev/misc_checks.py
+++ b/scripts/dev/misc_checks.py
@@ -91,7 +91,7 @@ def check_spelling():
'[Mm]ininum', '[Rr]esett?ed', '[Rr]ecieved', '[Rr]egularily',
'[Uu]nderlaying', '[Ii]nexistant', '[Ee]lipsis', 'commiting',
'existant', '[Rr]esetted', '[Ss]imilarily', '[Ii]nformations',
- '[Aa]n [Uu][Rr][Ll]'}
+ '[Aa]n [Uu][Rr][Ll]', '[Tt]reshold'}
# Words which look better when splitted, but might need some fine tuning.
words |= {'[Ww]ebelements', '[Mm]ouseevent', '[Kk]eysequence',
diff --git a/tests/unit/config/test_configinit.py b/tests/unit/config/test_configinit.py
index 731c62a66..25c6ce85e 100644
--- a/tests/unit/config/test_configinit.py
+++ b/tests/unit/config/test_configinit.py
@@ -28,7 +28,7 @@ import pytest
from qutebrowser import qutebrowser
from qutebrowser.config import (config, configexc, configfiles, configinit,
configdata, configtypes)
-from qutebrowser.utils import objreg, usertypes
+from qutebrowser.utils import objreg, usertypes, version
from helpers import utils
@@ -705,6 +705,120 @@ class TestQtArgs:
assert ('--force-dark-mode' in args) == added
+ def test_blink_settings(self, config_stub, monkeypatch, parser):
+ monkeypatch.setattr(configinit.objects, 'backend',
+ usertypes.Backend.QtWebEngine)
+ monkeypatch.setattr(configinit.qtutils, 'version_check',
+ lambda version, exact=False, compiled=True:
+ True)
+
+ config_stub.val.colors.webpage.darkmode.enabled = True
+
+ parsed = parser.parse_args([])
+ args = configinit.qt_args(parsed)
+
+ assert '--blink-settings=darkModeEnabled=true' in args
+
+
+class TestDarkMode:
+
+ @pytest.mark.parametrize('settings, new_qt, expected', [
+ # Disabled
+ ({}, True, []),
+ ({}, False, []),
+
+ # Enabled without customization
+ (
+ {'enabled': True},
+ True,
+ [('darkModeEnabled', 'true')]
+ ),
+ (
+ {'enabled': True},
+ False,
+ [('darkMode', '4')]
+ ),
+
+ # Algorithm
+ (
+ {'enabled': True, 'algorithm': 'brightness-rgb'},
+ True,
+ [('darkModeEnabled', 'true'),
+ ('darkModeInversionAlgorithm', '2')],
+ ),
+ (
+ {'enabled': True, 'algorithm': 'brightness-rgb'},
+ False,
+ [('darkMode', '2')],
+ ),
+
+ ])
+ @utils.qt514
+ def test_basics(self, config_stub, monkeypatch,
+ settings, new_qt, expected):
+ for k, v in settings.items():
+ config_stub.set_obj('colors.webpage.darkmode.' + k, v)
+ monkeypatch.setattr(configinit.qtutils, 'version_check',
+ lambda version, exact=False, compiled=True:
+ new_qt)
+
+ assert list(configinit._darkmode_settings()) == expected
+
+ @pytest.mark.parametrize('setting, value, exp_key, exp_val', [
+ ('contrast', -0.5,
+ 'darkModeContrast', '-0.5'),
+ ('policy.page', 'smart',
+ 'darkModePagePolicy', '1'),
+ ('policy.images', 'smart',
+ 'darkModeImagePolicy', '2'),
+ ('threshold.text', 100,
+ 'darkModeTextBrightnessThreshold', '100'),
+ ('threshold.background', 100,
+ 'darkModeBackgroundBrightnessThreshold', '100'),
+ ('grayscale.all', True,
+ 'darkModeGrayscale', 'true'),
+ ('grayscale.images', 0.5,
+ 'darkModeImageGrayscale', '0.5'),
+ ])
+ def test_customization(self, config_stub, monkeypatch,
+ setting, value, exp_key, exp_val):
+ config_stub.val.colors.webpage.darkmode.enabled = True
+ config_stub.set_obj('colors.webpage.darkmode.' + setting, value)
+ monkeypatch.setattr(configinit.qtutils, 'version_check',
+ lambda version, exact=False, compiled=True:
+ True)
+
+ expected = [('darkModeEnabled', 'true'), (exp_key, exp_val)]
+ assert list(configinit._darkmode_settings()) == expected
+
+ @utils.qt514
+ def test_new_chromium(self):
+ """Fail if we encounter an unknown Chromium version.
+
+ Dark mode in Chromium currently is undergoing various changes (as it's
+ relatively recent), and Qt 5.15 is supposed to update the underlying
+ Chromium at some point.
+
+ Make this test fail deliberately with newer Chromium versions, so that
+ we can test whether dark mode still works manually, and adjust if not.
+ """
+ assert version._chromium_version() in [
+ 'unavailable', # QtWebKit
+ '77.0.3865.129', # Qt 5.14
+ '80.0.3987.163', # Qt 5.15
+ ]
+
+ def test_options(self, configdata_init):
+ """Make sure all darkmode options have the right attributes set."""
+ for name, opt in configdata.DATA.items():
+ if not name.startswith('colors.webpage.darkmode.'):
+ continue
+
+ backends = {'QtWebEngine': 'Qt 5.14', 'QtWebKit': False}
+ assert not opt.supports_pattern, name
+ assert opt.restart, name
+ assert opt.raw_backends == backends, name
+
@pytest.mark.parametrize('arg, confval, used', [
# overridden by commandline arg
From ef3a4b00f0de7a2a2f5f013faa9e3df09611d60e Mon Sep 17 00:00:00 2001
From: Christopher Crockett
Date: Wed, 27 May 2020 09:23:49 -0400
Subject: [PATCH 081/245] Corrected "c.tabs.possition" typo
possition -> position
---
doc/help/configuring.asciidoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/help/configuring.asciidoc b/doc/help/configuring.asciidoc
index 575104fc1..02a77059c 100644
--- a/doc/help/configuring.asciidoc
+++ b/doc/help/configuring.asciidoc
@@ -108,7 +108,7 @@ c.completion.shrink = True
----
Note that qutebrowser does some Python magic so it's able to warn you about
-mistyped config settings. As an example, if you do `c.tabs.possition = "left"`,
+mistyped config settings. As an example, if you do `c.tabs.position = "left"`,
you'll get an error when starting.
See the link:settings{outfilesuffix}[settings help page] for all available settings. The
From 6657d1bfe35c5c2f7626cc4559cfaa2eea4a9e33 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 27 May 2020 16:06:46 +0200
Subject: [PATCH 082/245] tests: Ensure consistency for configdata float values
See #5394
---
tests/unit/config/test_configdata.py | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/tests/unit/config/test_configdata.py b/tests/unit/config/test_configdata.py
index 3dd6a588f..321135994 100644
--- a/tests/unit/config/test_configdata.py
+++ b/tests/unit/config/test_configdata.py
@@ -47,10 +47,17 @@ def test_data(config_stub):
# https://github.com/qutebrowser/qutebrowser/issues/3104
# For lists/dicts, don't use None as default
if isinstance(option.typ, (configtypes.Dict, configtypes.List)):
- assert option.default is not None
+ assert option.default is not None, option
# For ListOrValue, use a list as default
if isinstance(option.typ, configtypes.ListOrValue):
- assert isinstance(option.default, list)
+ assert isinstance(option.default, list), option
+
+ # Make sure floats also have floats for defaults/bounds
+ if isinstance(option.typ, configtypes.Float):
+ for value in [option.default,
+ option.typ.minval,
+ option.typ.maxval]:
+ assert value is None or isinstance(value, float), option
def test_init_benchmark(benchmark):
From 723c061271987d76f9043735e98c5f8fbb604068 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 27 May 2020 16:19:43 +0200
Subject: [PATCH 083/245] configdata: Remove double spaces after periods
See #5394
---
doc/help/settings.asciidoc | 6 +++---
qutebrowser/config/configdata.yml | 6 +++---
tests/unit/config/test_configdata.py | 3 +++
3 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 3787238a9..cc020ea88 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -1671,7 +1671,7 @@ On QtWebKit, this setting is unavailable.
[[colors.webpage.darkmode.threshold.background]]
=== colors.webpage.darkmode.threshold.background
Threshold for inverting background elements with dark mode.
-Background elements with brightness above this threshold will be inverted, and below it will be left as in the original, non-dark-mode page. Set to 256 to never invert the color or to 0 to always invert it.
+Background elements with brightness above this threshold will be inverted, and below it will be left as in the original, non-dark-mode page. Set to 256 to never invert the color or to 0 to always invert it.
Note: This behavior is the opposite of `colors.webpage.darkmode.threshold.text`!
This setting requires a restart.
@@ -1686,7 +1686,7 @@ On QtWebKit, this setting is unavailable.
[[colors.webpage.darkmode.threshold.text]]
=== colors.webpage.darkmode.threshold.text
Threshold for inverting text with dark mode.
-Text colors with brightness below this threshold will be inverted, and above it will be left as in the original, non-dark-mode page. Set to 256 to always invert text color or to 0 to never invert text color.
+Text colors with brightness below this threshold will be inverted, and above it will be left as in the original, non-dark-mode page. Set to 256 to always invert text color or to 0 to never invert text color.
This setting requires a restart.
Type: <>
@@ -4029,7 +4029,7 @@ characters in the search terms are replaced by safe characters (called
The search engine named `DEFAULT` is used when `url.auto_search` is turned
on and something else than a URL was entered to be opened. Other search
engines can be used by prepending the search engine name to the search
-term, e.g. `:open google qutebrowser`.
+term, e.g. `:open google qutebrowser`.
Type: <>
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index dc49e4cdd..664f9facf 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -1975,7 +1975,7 @@ url.searchengines:
The search engine named `DEFAULT` is used when `url.auto_search` is turned
on and something else than a URL was entered to be opened. Other search
engines can be used by prepending the search engine name to the search
- term, e.g. `:open google qutebrowser`.
+ term, e.g. `:open google qutebrowser`.
url.start_pages:
type:
@@ -2719,7 +2719,7 @@ colors.webpage.darkmode.threshold.text:
Threshold for inverting text with dark mode.
Text colors with brightness below this threshold will be inverted, and
- above it will be left as in the original, non-dark-mode page. Set to 256
+ above it will be left as in the original, non-dark-mode page. Set to 256
to always invert text color or to 0 to never invert text color.
restart: true
backend:
@@ -2736,7 +2736,7 @@ colors.webpage.darkmode.threshold.background:
Threshold for inverting background elements with dark mode.
Background elements with brightness above this threshold will be inverted,
- and below it will be left as in the original, non-dark-mode page. Set to
+ and below it will be left as in the original, non-dark-mode page. Set to
256 to never invert the color or to 0 to always invert it.
Note: This behavior is the opposite of
diff --git a/tests/unit/config/test_configdata.py b/tests/unit/config/test_configdata.py
index 321135994..4ea5ffe6d 100644
--- a/tests/unit/config/test_configdata.py
+++ b/tests/unit/config/test_configdata.py
@@ -59,6 +59,9 @@ def test_data(config_stub):
option.typ.maxval]:
assert value is None or isinstance(value, float), option
+ # No double spaces after dots
+ assert '. ' not in option.description, option
+
def test_init_benchmark(benchmark):
benchmark(configdata.init)
From fec1b303bfb92402d0f154360d0f23d659ddb8c6 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 27 May 2020 17:13:30 +0200
Subject: [PATCH 084/245] Make test_configinit work again with all platforms
---
tests/unit/config/test_configinit.py | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/tests/unit/config/test_configinit.py b/tests/unit/config/test_configinit.py
index 25c6ce85e..2e149fa70 100644
--- a/tests/unit/config/test_configinit.py
+++ b/tests/unit/config/test_configinit.py
@@ -705,6 +705,7 @@ class TestQtArgs:
assert ('--force-dark-mode' in args) == added
+ @utils.qt514
def test_blink_settings(self, config_stub, monkeypatch, parser):
monkeypatch.setattr(configinit.objects, 'backend',
usertypes.Backend.QtWebEngine)
@@ -722,6 +723,13 @@ class TestQtArgs:
class TestDarkMode:
+ pytestmark = utils.qt514
+
+ @pytest.fixture(autouse=True)
+ def patch_backend(self, monkeypatch):
+ monkeypatch.setattr(configinit.objects, 'backend',
+ usertypes.Backend.QtWebEngine)
+
@pytest.mark.parametrize('settings, new_qt, expected', [
# Disabled
({}, True, []),
@@ -753,7 +761,6 @@ class TestDarkMode:
),
])
- @utils.qt514
def test_basics(self, config_stub, monkeypatch,
settings, new_qt, expected):
for k, v in settings.items():
@@ -791,7 +798,6 @@ class TestDarkMode:
expected = [('darkModeEnabled', 'true'), (exp_key, exp_val)]
assert list(configinit._darkmode_settings()) == expected
- @utils.qt514
def test_new_chromium(self):
"""Fail if we encounter an unknown Chromium version.
From a31e93c69289223c1f6db3366b5c66db02e67c7b Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 27 May 2020 19:48:42 +0200
Subject: [PATCH 085/245] Revert "Corrected "c.tabs.possition" typo"
This reverts commit ef3a4b00f0de7a2a2f5f013faa9e3df09611d60e.
Reverts #5469.
That misspelling is intentional (see context).
---
doc/help/configuring.asciidoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/help/configuring.asciidoc b/doc/help/configuring.asciidoc
index 02a77059c..575104fc1 100644
--- a/doc/help/configuring.asciidoc
+++ b/doc/help/configuring.asciidoc
@@ -108,7 +108,7 @@ c.completion.shrink = True
----
Note that qutebrowser does some Python magic so it's able to warn you about
-mistyped config settings. As an example, if you do `c.tabs.position = "left"`,
+mistyped config settings. As an example, if you do `c.tabs.possition = "left"`,
you'll get an error when starting.
See the link:settings{outfilesuffix}[settings help page] for all available settings. The
From 5205ba12318a44ec40da4033fd99fb2a5829fefb Mon Sep 17 00:00:00 2001
From: Samir Benmendil
Date: Sat, 23 May 2020 23:31:00 +0100
Subject: [PATCH 086/245] Add bindings to toggle cookies for current url/host
---
qutebrowser/config/configdata.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 664f9facf..7e874f2aa 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -3168,6 +3168,12 @@ bindings.default:
tIH: config-cycle -p -u *://*.{url:host}/* content.images ;; reload
tiu: config-cycle -p -t -u {url} content.images ;; reload
tIu: config-cycle -p -u {url} content.images ;; reload
+ tch: config-cycle -p -t -u *://{url:host}/* content.cookies.accept all no-3rdparty never ;; reload
+ tCh: config-cycle -p -u *://{url:host}/* content.cookies.accept all no-3rdparty never ;; reload
+ tcH: config-cycle -p -t -u *://*.{url:host}/* content.cookies.accept all no-3rdparty never ;; reload
+ tCH: config-cycle -p -u *://*.{url:host}/* content.cookies.accept all no-3rdparty never ;; reload
+ tcu: config-cycle -p -t -u {url} content.cookies.accept all no-3rdparty never ;; reload
+ tCu: config-cycle -p -u {url} content.cookies.accept all no-3rdparty never ;; reload
insert:
: open-editor
: insert-text -- {primary}
From ac7a3ce861b9c564749fa6961ee43b98dbca462b Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 28 May 2020 18:08:53 +0200
Subject: [PATCH 087/245] tests: Don't use ";;" for caret tests
If we chain two commands, the end2end test code will still only wait for
"command called: *" once. This causes all future waits to be "shifted" by one,
which can cause flaky tests on Windows.
All other usages of command chaining in tests actually *need* the second
command to run as soon as possible after the original one. However, for the
caret tests, we only need to run two commands, see
2b0870084b9185b8f8a12639d238c12b202d3284.
Because pytest-bdd doesn't allow us to re-use "Given" steps, and "Background:"
only accepts "Given", let's add a second "Given" step as an ugly but acceptable
hack. See https://github.com/pytest-dev/pytest-bdd/issues/157
See https://github.com/qutebrowser/qutebrowser/issues/5390#issuecomment-622885572
---
tests/end2end/features/caret.feature | 3 ++-
tests/end2end/features/conftest.py | 11 +++++++++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/tests/end2end/features/caret.feature b/tests/end2end/features/caret.feature
index e540bafcb..ec45efaea 100644
--- a/tests/end2end/features/caret.feature
+++ b/tests/end2end/features/caret.feature
@@ -5,7 +5,8 @@ Feature: Caret mode
Background:
Given I open data/caret.html
- And I run :tab-only ;; enter-mode caret
+ And I run :tab-only
+ And I also run :enter-mode caret
# :yank selection
diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py
index 6ac5f281d..c1e7e32ae 100644
--- a/tests/end2end/features/conftest.py
+++ b/tests/end2end/features/conftest.py
@@ -152,6 +152,17 @@ def run_command_given(quteproc, command):
quteproc.send_cmd(command)
+@bdd.given(bdd.parsers.parse("I also run {command}"))
+def run_command_given_2(quteproc, command):
+ """Run a qutebrowser command.
+
+ Separate from the above as a hack to run two commands in a Background
+ without having to use ";;". This is needed because pytest-bdd doesn't allow
+ re-using a Given step...
+ """
+ quteproc.send_cmd(command)
+
+
@bdd.given("I have a fresh instance")
def fresh_instance(quteproc):
"""Restart qutebrowser instance for tests needing a fresh state."""
From 73e08124d858b2da9041d944b256947b77b2cfcd Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 29 May 2020 15:31:18 +0200
Subject: [PATCH 088/245] Add site-specific quirk for Google Drive
Fixes #5472
See #4805, #4810
---
doc/changelog.asciidoc | 3 +++
qutebrowser/browser/webengine/webenginesettings.py | 1 +
2 files changed, 4 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index a6aead789..b982c8763 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -72,6 +72,9 @@ Fixed
- The workaround for session loading with Qt 5.15 now handles
`sessions.lazy_restore` so that the saved page is loaded instead of the
"stub" page with no possibility to get to the web page.
+- A site specific quirk to allow typing accented characters on Google
+ Docs was active for docs.google.com, but not drive.google.com. It is
+ now applied for both subdomains.
v1.11.1 (2020-05-07)
--------------------
diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py
index d5d654dbf..c76a1c90a 100644
--- a/qutebrowser/browser/webengine/webenginesettings.py
+++ b/qutebrowser/browser/webengine/webenginesettings.py
@@ -403,6 +403,7 @@ def _init_site_specific_quirks():
'https://accounts.google.com/*': firefox_ua,
'https://*.slack.com/*': new_chrome_ua,
'https://docs.google.com/*': firefox_ua,
+ 'https://drive.google.com/*': firefox_ua,
}
if not qtutils.version_check('5.9'):
From 6356e80ecc3c94c1f2f4098105d3bdf4089f3c06 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 29 May 2020 16:16:09 +0200
Subject: [PATCH 089/245] Remove unused import in utils.message
Not needed anymore since 16d98a4137762dfb2731d8bc185549de721d3ca6 - for some
odd reason, the pylint failure only came up on CI now, and I don't see it
locally...
---
qutebrowser/utils/message.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py
index a8c7c8d85..2754d87e7 100644
--- a/qutebrowser/utils/message.py
+++ b/qutebrowser/utils/message.py
@@ -25,7 +25,7 @@
import traceback
import typing
-from PyQt5.QtCore import pyqtSignal, QObject, QUrl
+from PyQt5.QtCore import pyqtSignal, QObject
from qutebrowser.utils import usertypes, log, utils
From 6aeb3d81859a53b3ac779b881d6ab0e8035939fa Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 29 May 2020 20:53:21 +0200
Subject: [PATCH 090/245] Refactor how we get OpenGL info
This allows us to get the version string in addition to the vendor. We also
show that version string in the version info output.
See #5313
---
qutebrowser/misc/backendproblem.py | 16 ++----
qutebrowser/utils/utils.py | 15 ++++++
qutebrowser/utils/version.py | 70 ++++++++++++++++++++++---
tests/conftest.py | 6 +--
tests/unit/utils/test_version.py | 82 ++++++++++++++++++++++++++++--
5 files changed, 159 insertions(+), 30 deletions(-)
diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py
index 2ed273547..73044cd99 100644
--- a/qutebrowser/misc/backendproblem.py
+++ b/qutebrowser/misc/backendproblem.py
@@ -23,8 +23,6 @@ import os
import sys
import functools
import html
-import ctypes
-import ctypes.util
import enum
import shutil
import typing
@@ -201,19 +199,10 @@ class _BackendProblemChecker:
def _nvidia_shader_workaround(self) -> None:
"""Work around QOpenGLShaderProgram issues.
- NOTE: This needs to be called before _handle_nouveau_graphics, or some
- setups will segfault in version.opengl_vendor().
-
See https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
"""
self._assert_backend(usertypes.Backend.QtWebEngine)
-
- if os.environ.get('QUTE_SKIP_LIBGL_WORKAROUND'):
- return
-
- libgl = ctypes.util.find_library("GL")
- if libgl is not None:
- ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL)
+ utils.libgl_workaround()
def _handle_nouveau_graphics(self) -> None:
"""Force software rendering when using the Nouveau driver.
@@ -231,7 +220,8 @@ class _BackendProblemChecker:
if qtutils.version_check('5.10', compiled=False):
return
- if version.opengl_vendor() != 'nouveau':
+ opengl_info = version.opengl_info()
+ if opengl_info is None or opengl_info.vendor != 'nouveau':
return
if (os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or
diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py
index 368cb0ab6..39d46add8 100644
--- a/qutebrowser/utils/utils.py
+++ b/qutebrowser/utils/utils.py
@@ -36,6 +36,8 @@ import shlex
import glob
import mimetypes
import typing
+import ctypes
+import ctypes.util
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QColor, QClipboard, QDesktopServices
@@ -776,3 +778,16 @@ def ceil_log(number: int, base: int) -> int:
result += 1
accum *= base
return result
+
+
+def libgl_workaround():
+ """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:
+ ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL)
diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
index 1ad8b22cf..8dc4ec593 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -31,6 +31,7 @@ import enum
import datetime
import getpass
import typing
+import functools
import attr
import pkg_resources
@@ -442,8 +443,8 @@ def version() -> str:
if qapp:
style = qapp.style()
lines.append('Style: {}'.format(style.metaObject().className()))
- platform_name = qapp.platformName()
- lines.append('Platform plugin: {}'.format(platform_name))
+ lines.append('Platform plugin: {}'.format(qapp.platformName()))
+ lines.append('OpenGL: {}'.format(opengl_info()))
importpath = os.path.dirname(os.path.abspath(qutebrowser.__file__))
@@ -487,7 +488,55 @@ def version() -> str:
return '\n'.join(lines)
-def opengl_vendor() -> typing.Optional[str]: # pragma: no cover
+@attr.s
+class OpenGLInfo:
+
+ """Information about the OpenGL setup in use."""
+
+ # If we're using OpenGL ES. If so, no further information is available.
+ gles = attr.ib(False) # type: bool
+
+ # The name of the vendor. Examples:
+ # - nouveau
+ # - "Intel Open Source Technology Center", "Intel", "Intel Inc."
+ vendor = attr.ib(None) # type: typing.Optional[str]
+
+ # The OpenGL version as a string. See tests for examples.
+ version_str = attr.ib(None) # type: typing.Optional[str]
+
+ # The parsed version as a (major, minor) tuple of ints
+ version = attr.ib(None) # type: typing.Optional[typing.Tuple[int, ...]]
+
+ # The vendor specific information following the version number
+ vendor_specific = attr.ib(None) # type: typing.Optional[str]
+
+ def __str__(self) -> str:
+ if self.gles:
+ return 'OpenGL ES'
+ return '{}, {}'.format(self.vendor, self.version_str)
+
+ @classmethod
+ def parse(cls, *, vendor: str, version: str) -> 'OpenGLInfo':
+ if ' ' not in version:
+ log.misc.warning("Failed to parse OpenGL version (missing space): "
+ "{}".format(version))
+ return cls(vendor=vendor, version_str=version)
+
+ num_str, vendor_specific = version.split(' ', maxsplit=1)
+
+ try:
+ parsed_version = tuple(int(i) for i in num_str.split('.'))
+ except ValueError:
+ log.misc.warning("Failed to parse OpenGL version (parsing int): "
+ "{}".format(version))
+ return cls(vendor=vendor, version_str=version)
+
+ return cls(vendor=vendor, version_str=version,
+ version=parsed_version, vendor_specific=vendor_specific)
+
+
+@functools.lru_cache(maxsize=1)
+def opengl_info() -> typing.Optional[OpenGLInfo]: # pragma: no cover
"""Get the OpenGL vendor used.
This returns a string such as 'nouveau' or
@@ -496,10 +545,14 @@ def opengl_vendor() -> typing.Optional[str]: # pragma: no cover
"""
assert QApplication.instance()
- override = os.environ.get('QUTE_FAKE_OPENGL_VENDOR')
+ # 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))
- return override
+ vendor, version = override.split(', ', maxsplit=1)
+ return OpenGLInfo.parse(vendor=vendor, version=version)
old_context = typing.cast(typing.Optional[QOpenGLContext],
QOpenGLContext.currentContext())
@@ -522,7 +575,7 @@ def opengl_vendor() -> typing.Optional[str]: # pragma: no cover
try:
if ctx.isOpenGLES():
# Can't use versionFunctions there
- return None
+ return OpenGLInfo(gles=True)
vp = QOpenGLVersionProfile()
vp.setVersion(2, 0)
@@ -537,7 +590,10 @@ def opengl_vendor() -> typing.Optional[str]: # pragma: no cover
log.init.debug("Getting version functions failed!")
return None
- return vf.glGetString(vf.GL_VENDOR)
+ vendor = vf.glGetString(vf.GL_VENDOR)
+ version = vf.glGetString(vf.GL_VERSION)
+
+ return OpenGLInfo.parse(vendor=vendor, version=version)
finally:
ctx.doneCurrent()
if old_context and old_surface:
diff --git a/tests/conftest.py b/tests/conftest.py
index c6b6c2efc..e698bde74 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -25,8 +25,6 @@ import os
import sys
import warnings
import pathlib
-import ctypes
-import ctypes.util
import pytest
import hypothesis
@@ -258,9 +256,7 @@ def set_backend(monkeypatch, request):
@pytest.fixture(autouse=True, scope='session')
def apply_libgl_workaround():
"""Make sure we load libGL early so QtWebEngine tests run properly."""
- libgl = ctypes.util.find_library("GL")
- if libgl is not None:
- ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL)
+ utils.libgl_workaround()
@pytest.fixture(autouse=True)
diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py
index 0a3c5e4aa..903585b62 100644
--- a/tests/unit/utils/test_version.py
+++ b/tests/unit/utils/test_version.py
@@ -35,6 +35,8 @@ import datetime
import attr
import pkg_resources
import pytest
+import hypothesis
+import hypothesis.strategies
import qutebrowser
from qutebrowser.config import config
@@ -956,11 +958,15 @@ def test_version_output(params, stubs, monkeypatch, config_stub):
'config.instance.yaml_loaded': params.autoconfig_loaded,
}
+ version.opengl_info.cache_clear()
+ monkeypatch.setenv('QUTE_FAKE_OPENGL', 'VENDOR, 1.0 VERSION')
+
substitutions = {
'git_commit': '\nGit commit: GIT COMMIT' if params.git_commit else '',
'style': '\nStyle: STYLE' if params.qapp else '',
'platform_plugin': ('\nPlatform plugin: PLATFORM' if params.qapp
else ''),
+ 'opengl': '\nOpenGL: VENDOR, 1.0 VERSION' if params.qapp else '',
'qt': 'QT VERSION',
'frozen': str(params.frozen),
'import_path': import_path,
@@ -1026,7 +1032,7 @@ def test_version_output(params, stubs, monkeypatch, config_stub):
pdf.js: PDFJS VERSION
sqlite: SQLITE VERSION
QtNetwork SSL: {ssl}
- {style}{platform_plugin}
+ {style}{platform_plugin}{opengl}
Platform: PLATFORM, ARCHITECTURE{linuxdist}
Frozen: {frozen}
Imported from {import_path}
@@ -1045,10 +1051,76 @@ def test_version_output(params, stubs, monkeypatch, config_stub):
assert version.version() == expected
-def test_opengl_vendor(qapp):
- """Simply call version.opengl_vendor() and see if it doesn't crash."""
- pytest.importorskip("PyQt5.QtOpenGL")
- return version.opengl_vendor()
+class TestOpenGLInfo:
+
+ @pytest.fixture(autouse=True)
+ def cache_clear(self):
+ """Clear the lru_cache between tests."""
+ version.opengl_info.cache_clear()
+
+ def test_func(self, qapp):
+ """Simply call version.opengl_info() and see if it doesn't crash."""
+ pytest.importorskip("PyQt5.QtOpenGL")
+ version.opengl_info()
+
+ def test_func_fake(self, qapp, monkeypatch):
+ monkeypatch.setenv('QUTE_FAKE_OPENGL', 'Outtel Inc., 3.0 Messiah 20.0')
+ info = version.opengl_info()
+ assert info.vendor == 'Outtel Inc.'
+ assert info.version_str == '3.0 Messiah 20.0'
+ assert info.version == (3, 0)
+ assert info.vendor_specific == 'Messiah 20.0'
+
+ @pytest.mark.parametrize('version_str, reason', [
+ ('blah', 'missing space'),
+ ('2,x blah', 'parsing int'),
+ ])
+ def test_parse_invalid(self, caplog, version_str, reason):
+ with caplog.at_level(logging.WARNING):
+ info = version.OpenGLInfo.parse(vendor="vendor",
+ version=version_str)
+
+ assert info.version is None
+ assert info.vendor_specific is None
+ assert info.vendor == 'vendor'
+ assert info.version_str == version_str
+
+ msg = "Failed to parse OpenGL version ({}): {}".format(
+ reason, version_str)
+ assert caplog.messages == [msg]
+
+ @hypothesis.given(vendor=hypothesis.strategies.text(),
+ version_str=hypothesis.strategies.text())
+ def test_parse_hypothesis(self, caplog, vendor, version_str):
+ with caplog.at_level(logging.WARNING):
+ info = version.OpenGLInfo.parse(vendor=vendor, version=version_str)
+
+ assert info.vendor == vendor
+ assert info.version_str == version_str
+ assert vendor in str(info)
+ assert version_str in str(info)
+
+ if info.version is not None:
+ reconstructed = ' '.join(['.'.join(str(part)
+ for part in info.version),
+ info.vendor_specific])
+ assert reconstructed == info.version_str
+
+ @pytest.mark.parametrize('version_str, expected', [
+ ("2.1 INTEL-10.36.26", (2, 1)),
+ ("4.6 (Compatibility Profile) Mesa 20.0.7", (4, 6)),
+ ("3.0 Mesa 20.0.7", (3, 0)),
+ ("3.0 Mesa 20.0.6", (3, 0)),
+ # Not from the wild, but can happen according to standards
+ ("3.0.2 Mesa 20.0.6", (3, 0, 2)),
+ ])
+ def test_version(self, version_str, expected):
+ info = version.OpenGLInfo.parse(vendor='vendor', version=version_str)
+ assert info.version == expected
+
+ def test_str_gles(self):
+ info = version.OpenGLInfo(gles=True)
+ assert str(info) == 'OpenGL ES'
@pytest.fixture
From ec017f3aa3b58e2d8839b2fd98ea36428d001bb9 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 29 May 2020 20:58:21 +0200
Subject: [PATCH 091/245] backendproblem: Deduplicate text
---
qutebrowser/misc/backendproblem.py | 26 +++++++++++---------------
1 file changed, 11 insertions(+), 15 deletions(-)
diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py
index 73044cd99..01cfc988a 100644
--- a/qutebrowser/misc/backendproblem.py
+++ b/qutebrowser/misc/backendproblem.py
@@ -165,6 +165,14 @@ class _BackendProblemChecker:
"""Check for various backend-specific issues."""
+ SOFTWARE_RENDERING_TEXT = (
+ "Forcing software rendering
"
+ "This allows you to use the newer QtWebEngine backend (based on "
+ "Chromium) but could have noticeable performance impact (depending on "
+ "your hardware). This sets the qt.force_software_rendering = "
+ "'chromium' option (if you have a config.py file, you'll "
+ "need to set this manually).
")
+
def __init__(self, *,
no_err_windows: bool,
save_manager: savemanager.SaveManager) -> None:
@@ -238,14 +246,8 @@ class _BackendProblemChecker:
self._show_dialog(
backend=usertypes.Backend.QtWebEngine,
because="you're using Nouveau graphics",
- text=("There are two ways to fix this:
"
- "Forcing software rendering
"
- "This allows you to use the newer QtWebEngine backend "
- "(based on Chromium) but could have noticeable performance "
- "impact (depending on your hardware). This sets the "
- "qt.force_software_rendering = 'chromium' option "
- "(if you have a config.py file, you'll need to set "
- "this manually).
"),
+ text=("There are two ways to fix this:
" +
+ self.SOFTWARE_RENDERING_TEXT),
buttons=[button],
)
@@ -290,13 +292,7 @@ class _BackendProblemChecker:
buttons.append(_Button("Force software rendering",
'qt.force_software_rendering',
'chromium'))
- text += ("Forcing software rendering
"
- "This allows you to use the newer QtWebEngine backend "
- "(based on Chromium) but could have noticeable "
- "performance impact (depending on your hardware). This "
- "sets the qt.force_software_rendering = "
- "'chromium' option (if you have a config.py "
- "file, you'll need to set this manually).
")
+ text += self.SOFTWARE_RENDERING_TEXT
self._show_dialog(backend=usertypes.Backend.QtWebEngine,
because="you're using Wayland",
From 081453b29cda86f2db0f2187cd0d3f2e7b16a5fa Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Sat, 30 May 2020 07:25:26 -0400
Subject: [PATCH 092/245] Use code quotes for flags in docs.
Co-authored-by: Florian Bruhin
---
qutebrowser/config/configdata.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 6d8078ad2..b9bdf4594 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -3210,4 +3210,4 @@ logging.level.console:
name: LogLevel
desc: >-
Level for console (stdout/stderr) logs.
- Ignored if the --loglevel or --debug CLI flags are used.
+ Ignored if the `--loglevel` or `--debug` CLI flags are used.
From 682c9d1ba1719765f72a37c5ad26b5813d48621b Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Sat, 30 May 2020 07:55:27 -0400
Subject: [PATCH 093/245] Ignore log configs if --debug is passed.
CLI flags should take priority over configs. If --debug is passed, that
is a deliberate request from the user to increase log verbosity above
the norm.
The e2e tests were breaking because the default configs were overriding
the --debug flag and hiding logs the tests wait for.
---
qutebrowser/utils/log.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 683ee92f4..58311693f 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -541,6 +541,9 @@ def init_from_config(conf: 'configmodule.ConfigContainer') -> None:
cyclic import.
"""
assert _args is not None
+ if _args.debug:
+ init.info("--debug flag overrides log configs")
+ return
if ram_handler:
ramlevel = conf.logging.level.ram
init.info("Configuring RAM loglevel to %s", ramlevel)
From b668034afa9dbbf5e0e847773d6edc1146dc5228 Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Sat, 30 May 2020 08:00:06 -0400
Subject: [PATCH 094/245] Fix flake8 and lint
---
tests/unit/utils/test_log.py | 28 +++++++++++++---------------
1 file changed, 13 insertions(+), 15 deletions(-)
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index 2ced94c42..6b770ed44 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -255,13 +255,12 @@ class TestInitLog:
with pytest.raises(PendingDeprecationWarning):
warnings.warn("test warning", PendingDeprecationWarning)
- @pytest.mark.parametrize('cli, conf, expected',
- [
- (None, 'info', logging.INFO),
- (None, 'warning', logging.WARNING),
- ('info', 'warning', logging.INFO),
- ('warning', 'info', logging.WARNING),
- ])
+ @pytest.mark.parametrize('cli, conf, expected', [
+ (None, 'info', logging.INFO),
+ (None, 'warning', logging.WARNING),
+ ('info', 'warning', logging.INFO),
+ ('warning', 'info', logging.WARNING),
+ ])
def test_init_from_config_console(self, cli, conf, expected, args,
config_stub):
args.debug = False
@@ -272,13 +271,12 @@ class TestInitLog:
log.init_from_config(config_stub.val)
assert log.console_handler.level == expected
- @pytest.mark.parametrize('conf, expected',
- [
- ('vdebug', logging.VDEBUG),
- ('debug', logging.DEBUG),
- ('info', logging.INFO),
- ('critical', logging.CRITICAL),
- ])
+ @pytest.mark.parametrize('conf, expected', [
+ ('vdebug', logging.VDEBUG),
+ ('debug', logging.DEBUG),
+ ('info', logging.INFO),
+ ('critical', logging.CRITICAL),
+ ])
def test_init_from_config_ram(self, conf, expected, args, config_stub):
args.debug = False
log.init_log(args)
@@ -288,7 +286,7 @@ class TestInitLog:
assert log.ram_handler.level == expected
def test_init_from_config_consistent_default(self, config_stub):
- """Ensure config defaults are consistent with the builtin defaults"""
+ """Ensure config defaults are consistent with the builtin defaults."""
args = qutebrowser.get_argparser().parse_args([])
log.init_log(args)
From 2da752b47386a847cf74bc7e706d3db6d39cea8a Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Sat, 30 May 2020 08:04:25 -0400
Subject: [PATCH 095/245] More pylint fixes
---
qutebrowser/config/configtypes.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py
index 546d90c38..c92e404cf 100644
--- a/qutebrowser/config/configtypes.py
+++ b/qutebrowser/config/configtypes.py
@@ -2021,7 +2021,7 @@ class LogLevel(String):
def __init__(self, none_ok: bool = False) -> None:
super().__init__(none_ok=none_ok)
self.valid_values = ValidValues(*[level.lower()
- for level in log.LOG_LEVELS])
+ for level in log.LOG_LEVELS])
class Key(BaseType):
From d8fe8597c0e65154e52627aea7d4d8ecffcafdb6 Mon Sep 17 00:00:00 2001
From: Maxim Baz
Date: Mon, 1 Jun 2020 12:35:45 +0200
Subject: [PATCH 096/245] Decrease log verbosity of adblock component
By default every blocked request is logged, which seems way too verbose for those who aren't debugging this component
---
qutebrowser/components/adblock.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qutebrowser/components/adblock.py b/qutebrowser/components/adblock.py
index 2ceea2cf7..a683e9190 100644
--- a/qutebrowser/components/adblock.py
+++ b/qutebrowser/components/adblock.py
@@ -128,7 +128,7 @@ class HostBlocker:
"""Block the given request if necessary."""
if self._is_blocked(request_url=info.request_url,
first_party_url=info.first_party_url):
- logger.info("Request to {} blocked by host blocker."
+ logger.debug("Request to {} blocked by host blocker."
.format(info.request_url.host()))
info.block()
From deba7273785638de7ddd6921db0a50d88771e21c Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 13:43:59 +0200
Subject: [PATCH 097/245] backendproblem: Suggest to disable WebGL with older
OpenGL versions
Also make it a bit clearer that using QtWebKit isn't the preferred solution to
those problems.
Fixes #5313
---
doc/changelog.asciidoc | 3 +
qutebrowser/misc/backendproblem.py | 95 ++++++++++++++++++++++++------
2 files changed, 79 insertions(+), 19 deletions(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index b982c8763..8d8deaec0 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -75,6 +75,9 @@ Fixed
- A site specific quirk to allow typing accented characters on Google
Docs was active for docs.google.com, but not drive.google.com. It is
now applied for both subdomains.
+- With older graphics hardware (OpenGL < 4.3) with Qt 5.14 on Wayland, WebGL
+ causes segfaults. Now qutebrowser detects that combination and suggests to
+ disable WebGL or use XWayland.
v1.11.1 (2020-05-07)
--------------------
diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py
index 01cfc988a..5c8e1002d 100644
--- a/qutebrowser/misc/backendproblem.py
+++ b/qutebrowser/misc/backendproblem.py
@@ -79,19 +79,21 @@ def _error_text(because: str, text: str, backend: usertypes.Backend) -> str:
if other_backend == usertypes.Backend.QtWebKit:
warning = ("Note that QtWebKit hasn't been updated since "
"July 2017 (including security updates).")
+ suffix = " (not recommended)"
else:
warning = ""
+ suffix = ""
return ("Failed to start with the {backend} backend!"
"qutebrowser tried to start with the {backend} backend but "
"failed because {because}.
{text}"
- "Forcing the {other_backend.name} backend
"
+ "Forcing the {other_backend.name} backend{suffix}
"
"This forces usage of the {other_backend.name} backend by "
"setting the backend = '{other_setting}' option "
"(if you have a config.py file, you'll need to set "
"this manually). {warning}
".format(
backend=backend.name, because=because, text=text,
other_backend=other_backend, other_setting=other_setting,
- warning=warning))
+ warning=warning, suffix=suffix))
class _Dialog(QDialog):
@@ -253,23 +255,8 @@ class _BackendProblemChecker:
raise utils.Unreachable
- def _handle_wayland(self) -> None:
- self._assert_backend(usertypes.Backend.QtWebEngine)
-
- if os.environ.get('QUTE_SKIP_WAYLAND_CHECK'):
- return
-
- platform = QApplication.instance().platformName()
- if platform not in ['wayland', 'wayland-egl']:
- return
-
- has_qt511 = qtutils.version_check('5.11', compiled=False)
- if has_qt511 and config.val.qt.force_software_rendering == 'chromium':
- return
-
- if qtutils.version_check('5.11.2', compiled=False):
- return
-
+ def _xwayland_options(self) -> typing.Tuple[typing.List[_Button], str]:
+ """Get buttons/text for a possible XWayland solution."""
buttons = []
text = "You can work around this in one of the following ways:
"
@@ -288,6 +275,27 @@ class _BackendProblemChecker:
"This allows you to use the newer QtWebEngine backend "
"(based on Chromium). ")
+ return text, buttons
+
+ def _handle_wayland(self) -> None:
+ self._assert_backend(usertypes.Backend.QtWebEngine)
+
+ if os.environ.get('QUTE_SKIP_WAYLAND_CHECK'):
+ return
+
+ platform = QApplication.instance().platformName()
+ if platform not in ['wayland', 'wayland-egl']:
+ return
+
+ has_qt511 = qtutils.version_check('5.11', compiled=False)
+ if has_qt511 and config.val.qt.force_software_rendering == 'chromium':
+ return
+
+ if qtutils.version_check('5.11.2', compiled=False):
+ return
+
+ text, buttons = self._xwayland_options()
+
if has_qt511:
buttons.append(_Button("Force software rendering",
'qt.force_software_rendering',
@@ -299,6 +307,54 @@ class _BackendProblemChecker:
text=text,
buttons=buttons)
+ def _handle_wayland_webgl(self) -> None:
+ """On older graphic hardware, WebGL on Wayland causes segfaults.
+
+ See https://github.com/qutebrowser/qutebrowser/issues/5313
+ """
+ self._assert_backend(usertypes.Backend.QtWebEngine)
+
+ if os.environ.get('QUTE_SKIP_WAYLAND_WEBGL_CHECK'):
+ return
+
+ platform = QApplication.instance().platformName()
+ if platform not in ['wayland', 'wayland-egl']:
+ return
+
+ # Only Qt 5.14 should be affected
+ if not qtutils.version_check('5.14', compiled=False):
+ return
+ if qtutils.version_check('5.15', compiled=False):
+ return
+
+ # Newer graphic hardware isn't affected
+ opengl_info = version.opengl_info()
+ if (opengl_info is None or
+ opengl_info.gles or
+ opengl_info.version is None or
+ opengl_info.version >= (4, 3)):
+ return
+
+ # If WebGL is turned off, we're fine
+ if not config.val.content.webgl:
+ return
+
+ text, buttons = self._xwayland_options()
+
+ buttons.append(_Button("Turn off WebGL (recommended)",
+ 'content.webgl',
+ False))
+ text += ("
Disable WebGL (recommended)
"
+ "This sets the content.webgl = False option "
+ "(if you have a config.py file, you'll need to "
+ "set this manually).
")
+
+ self._show_dialog(backend=usertypes.Backend.QtWebEngine,
+ because=("of frequent crashes with Qt 5.14 on "
+ "Wayland with older graphics hardware"),
+ text=text,
+ buttons=buttons)
+
def _try_import_backends(self) -> _BackendImports:
"""Check whether backends can be imported and return BackendImports."""
# pylint: disable=unused-import
@@ -480,6 +536,7 @@ class _BackendProblemChecker:
self._handle_ssl_support()
self._handle_wayland()
self._nvidia_shader_workaround()
+ self._handle_wayland_webgl()
self._handle_nouveau_graphics()
self._handle_cache_nuking()
self._handle_serviceworker_nuking()
From f971cc041f5b3c096ca23e1291005079b8bf3431 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 13:54:58 +0200
Subject: [PATCH 098/245] ci: Switch to Qt 5.15
---
.appveyor.yml | 4 ++--
.travis.yml | 20 ++++++++++++++-----
misc/requirements/requirements-pyqt-5.15.txt | 5 +++++
.../requirements-pyqt-5.15.txt-raw | 4 ++++
misc/requirements/requirements-pyqt.txt | 6 +++---
tox.ini | 7 ++++---
6 files changed, 33 insertions(+), 13 deletions(-)
create mode 100644 misc/requirements/requirements-pyqt-5.15.txt
create mode 100644 misc/requirements/requirements-pyqt-5.15.txt-raw
diff --git a/.appveyor.yml b/.appveyor.yml
index 0ee670b37..23a96055c 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -10,8 +10,8 @@ image:
environment:
PYTHONUNBUFFERED: 1
- PYTHON: C:\Python37-x64\python.exe
- TESTENV: py37-pyqt514
+ PYTHON: C:\Python38-x64\python.exe
+ TESTENV: py38-pyqt515
install:
- '%PYTHON% --version'
diff --git a/.travis.yml b/.travis.yml
index 4ca28c375..28ad24af9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -45,7 +45,6 @@ matrix:
### PyQt 5.12 (Python 3.8)
- env: TESTENV=py38-pyqt512
- # http://code.qt.io/cgit/qt/qtbase.git/commit/?id=c3a963da1f9e7b1d37e63eedded61da4fbdaaf9a
addons:
apt:
packages:
@@ -53,20 +52,31 @@ matrix:
### PyQt 5.13 (Python 3.8)
- env: TESTENV=py38-pyqt513
- # http://code.qt.io/cgit/qt/qtbase.git/commit/?id=c3a963da1f9e7b1d37e63eedded61da4fbdaaf9a
addons:
apt:
packages:
- libxkbcommon-x11-0
- ### PyQt 5.14 (Python 3.8, with coverage)
- - env: TESTENV=py38-pyqt514-cov
- # http://code.qt.io/cgit/qt/qtbase.git/commit/?id=c3a963da1f9e7b1d37e63eedded61da4fbdaaf9a
+ ### PyQt 5.14 (Python 3.8)
+ - env: TESTENV=py38-pyqt514
addons:
apt:
packages:
- libxkbcommon-x11-0
+ ### PyQt 5.15 (Python 3.8, with coverage)
+ - env: TESTENV=py38-pyqt515-cov
+ addons:
+ apt:
+ packages:
+ - libxkbcommon-x11-0
+ - libxcb-icccm4
+ - libxcb-image0
+ - libxcb-keysyms1
+ - libxcb-randr0
+ - libxcb-render-util0
+ - libxcb-xinerama0
+
### macOS Mojave (10.14)
- os: osx
env: TESTENV=py37-pyqt514 OSX=mojave
diff --git a/misc/requirements/requirements-pyqt-5.15.txt b/misc/requirements/requirements-pyqt-5.15.txt
new file mode 100644
index 000000000..c21b7b742
--- /dev/null
+++ b/misc/requirements/requirements-pyqt-5.15.txt
@@ -0,0 +1,5 @@
+# This file is automatically generated by scripts/dev/recompile_requirements.py
+
+PyQt5==5.15.0 # rq.filter: < 6
+PyQt5-sip==12.8.0
+PyQtWebEngine==5.15.0 # rq.filter: < 6
diff --git a/misc/requirements/requirements-pyqt-5.15.txt-raw b/misc/requirements/requirements-pyqt-5.15.txt-raw
new file mode 100644
index 000000000..c9eeb9fb7
--- /dev/null
+++ b/misc/requirements/requirements-pyqt-5.15.txt-raw
@@ -0,0 +1,4 @@
+#@ filter: PyQt5 < 6
+#@ filter: PyQtWebEngine < 6
+PyQt5 >= 5.15, < 6
+PyQtWebEngine >= 5.15, < 6
diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt
index 90febc2e7..74d86e8d5 100644
--- a/misc/requirements/requirements-pyqt.txt
+++ b/misc/requirements/requirements-pyqt.txt
@@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-PyQt5==5.14.2
-PyQt5-sip==12.7.2
-PyQtWebEngine==5.14.0
+PyQt5==5.15.0
+PyQt5-sip==12.8.0
+PyQtWebEngine==5.15.0
diff --git a/tox.ini b/tox.ini
index 00b14bfb0..4e16742cc 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,15 +4,15 @@
# and then run "tox" from this directory.
[tox]
-envlist = py37-pyqt514-cov,misc,vulture,flake8,pylint,pyroma,check-manifest,eslint
+envlist = py37-pyqt515-cov,misc,vulture,flake8,pylint,pyroma,check-manifest,eslint
distshare = {toxworkdir}
skipsdist = true
[testenv]
setenv =
PYTEST_QT_API=pyqt5
- pyqt{,57,59,510,511,512,513,514}: LINK_PYQT_SKIP=true
- pyqt{,57,59,510,511,512,513,514}: QUTE_BDD_WEBENGINE=true
+ pyqt{,57,59,510,511,512,513,514,515}: LINK_PYQT_SKIP=true
+ pyqt{,57,59,510,511,512,513,514,515}: QUTE_BDD_WEBENGINE=true
cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report=
passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI TRAVIS XDG_* QUTE_* DOCKER QT_QUICK_BACKEND
basepython =
@@ -32,6 +32,7 @@ deps =
pyqt512: -r{toxinidir}/misc/requirements/requirements-pyqt-5.12.txt
pyqt513: -r{toxinidir}/misc/requirements/requirements-pyqt-5.13.txt
pyqt514: -r{toxinidir}/misc/requirements/requirements-pyqt-5.14.txt
+ pyqt515: -r{toxinidir}/misc/requirements/requirements-pyqt-5.15.txt
commands =
{envpython} scripts/link_pyqt.py --tox {envdir}
{envpython} -bb -m pytest {posargs:tests}
From c59165c92ff6f83986662f6e70e5d0ea423eccd2 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 14:09:00 +0200
Subject: [PATCH 099/245] Recompile requirements
---
misc/requirements/requirements-dev.txt | 4 ++--
misc/requirements/requirements-pylint.txt | 2 +-
misc/requirements/requirements-pyqt-5.12.txt | 2 +-
misc/requirements/requirements-pyqt-5.13.txt | 2 +-
misc/requirements/requirements-pyqt-5.14.txt | 2 +-
misc/requirements/requirements-sphinx.txt | 2 +-
misc/requirements/requirements-tests.txt | 8 ++++----
misc/requirements/requirements-tests.txt-raw | 3 +++
misc/requirements/requirements-vulture.txt | 2 +-
9 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt
index 620e5772c..bb834483f 100644
--- a/misc/requirements/requirements-dev.txt
+++ b/misc/requirements/requirements-dev.txt
@@ -17,10 +17,10 @@ packaging==20.4
pycparser==2.20
Pympler==0.8
pyparsing==2.4.7
-PyQt-builder==1.3.2
+PyQt-builder==1.4.0
python-dateutil==2.8.1
requests==2.23.0
-sip==5.2.0
+sip==5.3.0
six==1.15.0
toml==0.10.1
uritemplate==3.0.1
diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt
index e80ddbf50..988b58786 100644
--- a/misc/requirements/requirements-pylint.txt
+++ b/misc/requirements/requirements-pylint.txt
@@ -20,4 +20,4 @@ six==1.15.0
typed-ast==1.4.1 ; python_version<"3.8"
uritemplate==3.0.1
urllib3==1.25.9
-wrapt==1.12.1
+wrapt==1.11.2
diff --git a/misc/requirements/requirements-pyqt-5.12.txt b/misc/requirements/requirements-pyqt-5.12.txt
index b1be83265..9b458cd98 100644
--- a/misc/requirements/requirements-pyqt-5.12.txt
+++ b/misc/requirements/requirements-pyqt-5.12.txt
@@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.12.3 # rq.filter: < 5.13
-PyQt5-sip==12.7.2
+PyQt5-sip==12.8.0
PyQtWebEngine==5.12.1 # rq.filter: < 5.13
diff --git a/misc/requirements/requirements-pyqt-5.13.txt b/misc/requirements/requirements-pyqt-5.13.txt
index dc2f0359a..7c07eac3d 100644
--- a/misc/requirements/requirements-pyqt-5.13.txt
+++ b/misc/requirements/requirements-pyqt-5.13.txt
@@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.13.2 # rq.filter: < 5.14
-PyQt5-sip==12.7.2
+PyQt5-sip==12.8.0
PyQtWebEngine==5.13.2 # rq.filter: < 5.14
diff --git a/misc/requirements/requirements-pyqt-5.14.txt b/misc/requirements/requirements-pyqt-5.14.txt
index 7640a8adb..c82acedb0 100644
--- a/misc/requirements/requirements-pyqt-5.14.txt
+++ b/misc/requirements/requirements-pyqt-5.14.txt
@@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.14.2 # rq.filter: < 5.15
-PyQt5-sip==12.7.2
+PyQt5-sip==12.8.0
PyQtWebEngine==5.14.0 # rq.filter: < 5.15
diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt
index e4ca26a6a..c90606b66 100644
--- a/misc/requirements/requirements-sphinx.txt
+++ b/misc/requirements/requirements-sphinx.txt
@@ -16,7 +16,7 @@ pytz==2020.1
requests==2.23.0
six==1.15.0
snowballstemmer==2.0.0
-Sphinx==3.0.3
+Sphinx==3.0.4
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==1.0.3
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index 1c0c9acde..9a1b261d0 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -10,11 +10,11 @@ EasyProcess==0.3
Flask==1.1.2
glob2==0.7
hunter==3.1.3
-hypothesis==5.15.1
+hypothesis==5.16.0
itsdangerous==1.1.0
jaraco.functools==3.0.1 ; python_version>="3.6"
# Jinja2==2.11.2
-Mako==1.1.2
+Mako==1.1.3
manhole==1.6.0
# MarkupSafe==1.1.1
more-itertools==8.3.0
@@ -37,11 +37,11 @@ pytest-repeat==0.8.0
pytest-rerunfailures==9.0
pytest-travis-fold==1.3.0
pytest-xvfb==1.2.0
-PyVirtualDisplay==0.2.5
+PyVirtualDisplay==0.2.5 # rq.filter: < 1.0
six==1.15.0
sortedcontainers==2.1.0
soupsieve==2.0.1
-vulture==1.4
+vulture==1.5
wcwidth==0.1.9
Werkzeug==1.0.1
jaraco.functools==2.0; python_version<"3.6" # rq.filter: <= 2.0
diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw
index 4d6932c82..d5a20dea3 100644
--- a/misc/requirements/requirements-tests.txt-raw
+++ b/misc/requirements/requirements-tests.txt-raw
@@ -12,6 +12,8 @@ pytest-mock
pytest-qt
pytest-rerunfailures
pytest-xvfb
+# https://github.com/The-Compiler/pytest-xvfb/issues/22
+PyVirtualDisplay < 1.0
## optional:
# To test :debug-trace, gets skipped if hunter is not installed
@@ -28,3 +30,4 @@ pytest-repeat
#@ markers: jaraco.functools python_version>="3.6"
#@ add: jaraco.functools==2.0; python_version<"3.6" # rq.filter: <= 2.0
#@ ignore: Jinja2, MarkupSafe, colorama
+#@ filter: PyVirtualDisplay < 1.0
diff --git a/misc/requirements/requirements-vulture.txt b/misc/requirements/requirements-vulture.txt
index c5c343f9e..32d36560b 100644
--- a/misc/requirements/requirements-vulture.txt
+++ b/misc/requirements/requirements-vulture.txt
@@ -1,3 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-vulture==1.4
+vulture==1.5
From 41a7db811108fa698687bff4dbb085f6af29018f Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 14:10:25 +0200
Subject: [PATCH 100/245] Mark only-active-window test as flaky
---
tests/end2end/features/private.feature | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/end2end/features/private.feature b/tests/end2end/features/private.feature
index 35097f545..6ea9e7b33 100644
--- a/tests/end2end/features/private.feature
+++ b/tests/end2end/features/private.feature
@@ -153,7 +153,7 @@ Feature: Using private browsing
- url: http://localhost:*/data/numbers/1.txt
- url: http://localhost:*/data/numbers/2.txt
-
+ @flaky
Scenario: Saving a private session with only-active-window
When I open data/numbers/1.txt
And I open data/numbers/2.txt in a new tab
From 4787b30f96c8832797fcc3c56457400d3ebb9893 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 14:43:14 +0200
Subject: [PATCH 101/245] Fix lint
---
qutebrowser/misc/backendproblem.py | 4 ++--
qutebrowser/utils/utils.py | 2 +-
qutebrowser/utils/version.py | 10 ++++++++++
3 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py
index 5c8e1002d..6e2bd1866 100644
--- a/qutebrowser/misc/backendproblem.py
+++ b/qutebrowser/misc/backendproblem.py
@@ -57,7 +57,7 @@ class _Button:
text = attr.ib() # type: str
setting = attr.ib() # type: str
- value = attr.ib() # type: str
+ value = attr.ib() # type: typing.Any
default = attr.ib(default=False) # type: bool
@@ -255,7 +255,7 @@ class _BackendProblemChecker:
raise utils.Unreachable
- def _xwayland_options(self) -> typing.Tuple[typing.List[_Button], str]:
+ def _xwayland_options(self) -> typing.Tuple[str, typing.List[_Button]]:
"""Get buttons/text for a possible XWayland solution."""
buttons = []
text = "You can work around this in one of the following ways:
"
diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py
index 39d46add8..ccae5a5d3 100644
--- a/qutebrowser/utils/utils.py
+++ b/qutebrowser/utils/utils.py
@@ -780,7 +780,7 @@ def ceil_log(number: int, base: int) -> int:
return result
-def libgl_workaround():
+def libgl_workaround() -> None:
"""Work around QOpenGLShaderProgram issues, especially for Nvidia.
See https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
index 8dc4ec593..9c8c8f9e8 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -517,6 +517,16 @@ class OpenGLInfo:
@classmethod
def parse(cls, *, vendor: str, version: str) -> 'OpenGLInfo':
+ """Parse OpenGL version info from a string.
+
+ The arguments should be the strings returned by OpenGL for GL_VENDOR
+ and GL_VERSION, respectively.
+
+ According to the OpenGL reference, the version string should have the
+ following format:
+
+ .[.]
+ """
if ' ' not in version:
log.misc.warning("Failed to parse OpenGL version (missing space): "
"{}".format(version))
From fb6594be27be1da68352f64c4d0550254d9e5378 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 14:47:54 +0200
Subject: [PATCH 102/245] Rename version.version() to version_info()
To get a less generic name.
---
qutebrowser/app.py | 2 +-
qutebrowser/browser/qutescheme.py | 2 +-
qutebrowser/misc/crashdialog.py | 4 ++--
qutebrowser/utils/version.py | 4 ++--
tests/unit/utils/test_version.py | 12 ++++++------
5 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index 6d01e0ddd..9621882e9 100644
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -96,7 +96,7 @@ def run(args):
q_app.setApplicationVersion(qutebrowser.__version__)
if args.version:
- print(version.version())
+ print(version.version_info())
sys.exit(usertypes.Exit.ok)
quitter.init(args)
diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py
index 53004e66a..ec6e19082 100644
--- a/qutebrowser/browser/qutescheme.py
+++ b/qutebrowser/browser/qutescheme.py
@@ -302,7 +302,7 @@ def qute_spawn_output(_url: QUrl) -> _HandlerRet:
def qute_version(_url):
"""Handler for qute://version."""
src = jinja.render('version.html', title='Version info',
- version=version.version(),
+ version=version.version_info(),
copyright=qutebrowser.__copyright__)
return 'text/html', src
diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py
index 6bd6ec325..0f6d02712 100644
--- a/qutebrowser/misc/crashdialog.py
+++ b/qutebrowser/misc/crashdialog.py
@@ -246,7 +246,7 @@ class _CrashDialog(QDialog):
except Exception:
self._crash_info.append(("Launch time", traceback.format_exc()))
try:
- self._crash_info.append(("Version info", version.version()))
+ self._crash_info.append(("Version info", version.version_info()))
except Exception:
self._crash_info.append(("Version info", traceback.format_exc()))
try:
@@ -650,7 +650,7 @@ def dump_exception_info(exc, pages, cmdhist, qobjects):
print(''.join(traceback.format_exception(*exc)), file=sys.stderr)
print("\n---- Version info ----", file=sys.stderr)
try:
- print(version.version(), file=sys.stderr)
+ print(version.version_info(), file=sys.stderr)
except Exception:
traceback.print_exc()
print("\n---- Config ----", file=sys.stderr)
diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
index 9c8c8f9e8..c0ff7e1ac 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -412,7 +412,7 @@ def _config_py_loaded() -> str:
return "no config.py was loaded"
-def version() -> str:
+def version_info() -> str:
"""Return a string with various version information."""
lines = ["qutebrowser v{}".format(qutebrowser.__version__)]
gitver = _git_str()
@@ -646,5 +646,5 @@ def pastebin_version(pbclient: pastebin.PastebinClient = None) -> None:
pbclient.paste(getpass.getuser(),
"qute version info {}".format(qutebrowser.__version__),
- version(),
+ version_info(),
private=True)
diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py
index 903585b62..f1e0ee0b6 100644
--- a/tests/unit/utils/test_version.py
+++ b/tests/unit/utils/test_version.py
@@ -927,8 +927,8 @@ class VersionParams:
VersionParams('no-autoconfig-loaded', autoconfig_loaded=False),
VersionParams('no-config-py-loaded', config_py_loaded=False),
], ids=lambda param: param.name)
-def test_version_output(params, stubs, monkeypatch, config_stub):
- """Test version.version()."""
+def test_version_info(params, stubs, monkeypatch, config_stub):
+ """Test version.version_info()."""
config.instance.config_py_loaded = params.config_py_loaded
import_path = os.path.abspath('/IMPORTPATH')
@@ -1048,7 +1048,7 @@ def test_version_output(params, stubs, monkeypatch, config_stub):
""".lstrip('\n'))
expected = template.rstrip('\n').format(**substitutions)
- assert version.version() == expected
+ assert version.version_info() == expected
class TestOpenGLInfo:
@@ -1133,7 +1133,7 @@ def pbclient(stubs):
def test_pastebin_version(pbclient, message_mock, monkeypatch, qtbot):
"""Test version.pastebin_version() sets the url."""
- monkeypatch.setattr('qutebrowser.utils.version.version',
+ monkeypatch.setattr('qutebrowser.utils.version.version_info',
lambda: "dummy")
monkeypatch.setattr('qutebrowser.utils.utils.log_clipboard', True)
@@ -1148,7 +1148,7 @@ def test_pastebin_version(pbclient, message_mock, monkeypatch, qtbot):
def test_pastebin_version_twice(pbclient, monkeypatch):
"""Test whether calling pastebin_version twice sends no data."""
- monkeypatch.setattr('qutebrowser.utils.version.version',
+ monkeypatch.setattr('qutebrowser.utils.version.version_info',
lambda: "dummy")
version.pastebin_version(pbclient)
@@ -1166,7 +1166,7 @@ def test_pastebin_version_twice(pbclient, monkeypatch):
def test_pastebin_version_error(pbclient, caplog, message_mock, monkeypatch):
"""Test version.pastebin_version() with errors."""
- monkeypatch.setattr('qutebrowser.utils.version.version',
+ monkeypatch.setattr('qutebrowser.utils.version.version_info',
lambda: "dummy")
version.pastebin_url = None
From 46efdb736f2796687db3ed4c6f02fcc4f00d54a6 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 14:54:36 +0200
Subject: [PATCH 103/245] Add test for utils.libgl_workaround()
---
qutebrowser/utils/utils.py | 2 +-
tests/unit/utils/test_utils.py | 7 +++++++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py
index ccae5a5d3..92ca34a08 100644
--- a/qutebrowser/utils/utils.py
+++ b/qutebrowser/utils/utils.py
@@ -789,5 +789,5 @@ def libgl_workaround() -> None:
return
libgl = ctypes.util.find_library("GL")
- if libgl is not None:
+ if libgl is not None: # pragma: no branch
ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL)
diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py
index 18abd444e..35f04201e 100644
--- a/tests/unit/utils/test_utils.py
+++ b/tests/unit/utils/test_utils.py
@@ -885,3 +885,10 @@ def test_ceil_log_invalid(number, base):
math.log(number, base)
with pytest.raises(ValueError):
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.
From 56404bc52c80f1ed01a53770e0ba66e1647fc34c Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 14:56:46 +0200
Subject: [PATCH 104/245] AppVeyor: Go back to Qt 5.14
See https://github.com/qutebrowser/qutebrowser/issues/5237#issuecomment-636845641
---
.appveyor.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.appveyor.yml b/.appveyor.yml
index 23a96055c..47ad9964a 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -11,7 +11,7 @@ image:
environment:
PYTHONUNBUFFERED: 1
PYTHON: C:\Python38-x64\python.exe
- TESTENV: py38-pyqt515
+ TESTENV: py38-pyqt514
install:
- '%PYTHON% --version'
From d7db0a4d37df8c68d48ebb879f03b797549b44ec Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 14:59:34 +0200
Subject: [PATCH 105/245] Revert "AppVeyor: Go back to Qt 5.14"
This reverts commit 56404bc52c80f1ed01a53770e0ba66e1647fc34c.
---
.appveyor.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.appveyor.yml b/.appveyor.yml
index 47ad9964a..23a96055c 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -11,7 +11,7 @@ image:
environment:
PYTHONUNBUFFERED: 1
PYTHON: C:\Python38-x64\python.exe
- TESTENV: py38-pyqt514
+ TESTENV: py38-pyqt515
install:
- '%PYTHON% --version'
From fde776de20798904f092f7efec4daafa2885650e Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 15:01:14 +0200
Subject: [PATCH 106/245] tests: Ignore new Qt 5.15 Chromium error on AppVeyor
See https://github.com/qutebrowser/qutebrowser/issues/5237#issuecomment-636845641
---
tests/end2end/fixtures/quteprocess.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py
index 5f8263334..626981f13 100644
--- a/tests/end2end/fixtures/quteprocess.py
+++ b/tests/end2end/fixtures/quteprocess.py
@@ -285,6 +285,11 @@ def is_ignored_chromium_message(line):
# [5306:5324:0417/151739.362362:ERROR:address_tracker_linux.cc(171)]
# Could not bind NETLINK socket: Address already in use (98)
'Could not bind NETLINK socket: Address already in use (98)',
+
+ # Qt 5.15 with AppVeyor
+ # [2968:3108:0601/123442.125:ERROR:mf_helpers.cc(14)] Error in
+ # dxva_video_decode_accelerator_win.cc on line 517
+ 'Error in dxva_video_decode_accelerator_win.cc on line 517',
]
return any(testutils.pattern_match(pattern=pattern, value=message)
for pattern in ignored_messages)
From 0bc5b049b56c03301732dfeee8e3ce3d7dda600b Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 15:02:06 +0200
Subject: [PATCH 107/245] tests: Ignore QHttpNetworkConnection warning
See https://github.com/qutebrowser/qutebrowser/issues/5390#issuecomment-634062762
---
tests/unit/utils/test_urlutils.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py
index 39a43479b..a9f32161d 100644
--- a/tests/unit/utils/test_urlutils.py
+++ b/tests/unit/utils/test_urlutils.py
@@ -716,6 +716,9 @@ class TestProxyFromUrl:
def test_proxy_from_url_valid(self, url, expected):
assert urlutils.proxy_from_url(QUrl(url)) == expected
+ @pytest.mark.qt_log_ignore(
+ r'^QHttpNetworkConnectionPrivate::_q_hostLookupFinished could not '
+ r'de-queue request, failed to report HostNotFoundError')
@pytest.mark.parametrize('scheme', ['pac+http', 'pac+https'])
def test_proxy_from_url_pac(self, scheme, qapp):
fetcher = urlutils.proxy_from_url(QUrl('{}://foo'.format(scheme)))
From 3df4dd58312262915525d4ca322df19e51119b9e Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 14:56:46 +0200
Subject: [PATCH 108/245] AppVeyor: Go back to Qt 5.14
See https://github.com/qutebrowser/qutebrowser/issues/5237#issuecomment-636845641
---
.appveyor.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.appveyor.yml b/.appveyor.yml
index 23a96055c..47ad9964a 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -11,7 +11,7 @@ image:
environment:
PYTHONUNBUFFERED: 1
PYTHON: C:\Python38-x64\python.exe
- TESTENV: py38-pyqt515
+ TESTENV: py38-pyqt514
install:
- '%PYTHON% --version'
From eaff6e82d13fcf30ec4da06513c08afcae794bb4 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 15:57:24 +0200
Subject: [PATCH 109/245] Release v1.12.0
---
.bumpversion.cfg | 2 +-
doc/changelog.asciidoc | 2 +-
misc/org.qutebrowser.qutebrowser.appdata.xml | 1 +
qutebrowser/__init__.py | 2 +-
4 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index c5defd11a..c260a28da 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 1.11.1
+current_version = 1.12.0
commit = True
message = Release v{new_version}
tag = True
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 8d8deaec0..6c4588c53 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -15,7 +15,7 @@ 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.
-v1.12.0 (unreleased)
+v1.12.0 (2020-06-01)
--------------------
Removed
diff --git a/misc/org.qutebrowser.qutebrowser.appdata.xml b/misc/org.qutebrowser.qutebrowser.appdata.xml
index ca58f4bd5..f02fcb00d 100644
--- a/misc/org.qutebrowser.qutebrowser.appdata.xml
+++ b/misc/org.qutebrowser.qutebrowser.appdata.xml
@@ -44,6 +44,7 @@
+
diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py
index 6d684d6e3..147606f42 100644
--- a/qutebrowser/__init__.py
+++ b/qutebrowser/__init__.py
@@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2020 Florian Bruhin (The Compiler)"
__license__ = "GPL"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
-__version__ = "1.11.1"
+__version__ = "1.12.0"
__version_info__ = tuple(int(part) for part in __version__.split('.'))
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
From 804099f5dab951e560d4a187e4360bf3d4b59e36 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 17:35:53 +0200
Subject: [PATCH 110/245] Amend changelog for v1.12.0
---
doc/changelog.asciidoc | 2 ++
1 file changed, 2 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 6c4588c53..4b70c6e0f 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -48,6 +48,8 @@ Added
Changed
~~~~~~~
+- Windows and macOS releases now ship Qt 5.15, which is based on Chromium
+ 80.0.3987.163 with security fixes up to 81.0.4044.138.
- The `content.cookies.accept` setting now accepts URL patterns.
- Tests are now included in release tarballs. Note that only running them with
the exact dependencies listed in
From 83796255edb1bfc3ef70270ac1821900b4526649 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 18:42:50 +0200
Subject: [PATCH 111/245] Don't run test_standarddir.test_fake_mac_config on
Windows
We can't be sure that os.path.expanduser('~') actually uses $HOME there.
Fixes #5477
---
tests/unit/utils/test_standarddir.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py
index c2d2c6cd7..064c51b30 100644
--- a/tests/unit/utils/test_standarddir.py
+++ b/tests/unit/utils/test_standarddir.py
@@ -79,6 +79,7 @@ def test_unset_organization_no_qapp(monkeypatch):
@pytest.mark.fake_os('mac')
+@pytest.mark.posix
def test_fake_mac_config(tmpdir, monkeypatch):
"""Test standardir.config on a fake Mac."""
monkeypatch.setenv('HOME', str(tmpdir))
From 1b6f708814e4bf4953cf69ea7327194eddaeb703 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 19:40:11 +0200
Subject: [PATCH 112/245] README: Update list of alternatives
---
README.asciidoc | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/README.asciidoc b/README.asciidoc
index 41d0e7f1b..3a7778def 100644
--- a/README.asciidoc
+++ b/README.asciidoc
@@ -206,8 +206,8 @@ link:doc/backers.asciidoc[crowdfunding campaigns]!
Similar projects
----------------
-Many projects with a similar goal as qutebrowser exist.
-Most of them were inspirations for qutebrowser in some way, thanks for that!
+Various projects with a similar goal like qutebrowser exist.
+Many of them were inspirations for qutebrowser in some way, thanks for that!
Active
~~~~~~
@@ -215,8 +215,9 @@ Active
* https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2)
* https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2)
* https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
-* https://next.atlas.engineer/[next] (Lisp, Emacs-like but also offers Vim bindings, various backends - note there was a http://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution] which was handled quite badly)
+* https://next.atlas.engineer/[next] (Lisp, Emacs-like but also offers Vim bindings, QtWebKit or GTK+/WebKit2 - note there was a http://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution] which was handled quite badly)
* https://github.com/parkouss/webmacs/[webmacs] (Python, Emacs-like with QtWebEngine)
+* https://vieb.dev/[Vieb] (JavaScript, Electron)
* Chrome/Chromium addons:
https://vimium.github.io/[Vimium],
* Firefox addons (based on WebExtensions):
From bed5647930824e5eab94a51fa7ea8235ec8deae0 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 19:42:33 +0200
Subject: [PATCH 113/245] Mark second --only-active-window test as flaky
---
tests/end2end/features/sessions.feature | 1 +
1 file changed, 1 insertion(+)
diff --git a/tests/end2end/features/sessions.feature b/tests/end2end/features/sessions.feature
index 494feb0ba..69c58f3c3 100644
--- a/tests/end2end/features/sessions.feature
+++ b/tests/end2end/features/sessions.feature
@@ -282,6 +282,7 @@ Feature: Saving and loading sessions
Then "Saved session quiet_session." should be logged with level debug
And the session quiet_session should exist
+ @flaky
Scenario: Saving session with --only-active-window
When I open data/numbers/1.txt
And I open data/numbers/2.txt in a new tab
From 03d1e7712309434d02c02203c13e1b407121bbb7 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 1 Jun 2020 20:40:06 +0200
Subject: [PATCH 114/245] changelog: Fix typo and redundancy
---
doc/changelog.asciidoc | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 4b70c6e0f..5da5ef553 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -36,8 +36,7 @@ Added
Previously, that was only available as a separate application via `python3 -m
scripts.keytester`.
- New `:config-diff` command which opens the `qute://configdiff` page.
-- New `--debug-flag log-cookies` to log cokies to the debug log for
- debugging.
+- New `--debug-flag log-cookies` to log cookies to the debug log.
- New `colors.contextmenu.disabled.{fg,bg}` settings to customize colors for
disabled items in the context menu.
- New line selection mode (`:toggle-selection --line`), bound to `Shift-V` in caret mode.
From e8d88eb407b7198c4fe80247505b95ec0b9aa212 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 1 Jun 2020 21:29:10 +0200
Subject: [PATCH 115/245] Update setuptools from 46.4.0 to 47.1.1
---
misc/requirements/requirements-pip.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt
index 3a71ad0a8..db2eb7a02 100644
--- a/misc/requirements/requirements-pip.txt
+++ b/misc/requirements/requirements-pip.txt
@@ -3,6 +3,6 @@
appdirs==1.4.4
packaging==20.4
pyparsing==2.4.7
-setuptools==46.4.0
+setuptools==47.1.1
six==1.15.0
wheel==0.34.2
From de5a113e90888cc7b5aee9c1f7874087a8e483e2 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 1 Jun 2020 21:29:11 +0200
Subject: [PATCH 116/245] Update wrapt from 1.11.2 to 1.12.1
---
misc/requirements/requirements-pylint.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt
index 988b58786..e80ddbf50 100644
--- a/misc/requirements/requirements-pylint.txt
+++ b/misc/requirements/requirements-pylint.txt
@@ -20,4 +20,4 @@ six==1.15.0
typed-ast==1.4.1 ; python_version<"3.8"
uritemplate==3.0.1
urllib3==1.25.9
-wrapt==1.11.2
+wrapt==1.12.1
From 895b6aa24fdccd0a459763688dabca9faafcae8f Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 1 Jun 2020 21:29:12 +0200
Subject: [PATCH 117/245] Update wcwidth from 0.1.9 to 0.2.2
---
misc/requirements/requirements-tests.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index 9a1b261d0..0c96a2af1 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -42,6 +42,6 @@ six==1.15.0
sortedcontainers==2.1.0
soupsieve==2.0.1
vulture==1.5
-wcwidth==0.1.9
+wcwidth==0.2.2
Werkzeug==1.0.1
jaraco.functools==2.0; python_version<"3.6" # rq.filter: <= 2.0
From b5626c19d624ae372bd8093da3a267807ab0262b Mon Sep 17 00:00:00 2001
From: jcromero
Date: Tue, 2 Jun 2020 17:33:35 +0200
Subject: [PATCH 118/245] Fix typo in directory option (-d) for certutil
command
---
doc/faq.asciidoc | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/doc/faq.asciidoc b/doc/faq.asciidoc
index 3df5b8e40..651df9665 100644
--- a/doc/faq.asciidoc
+++ b/doc/faq.asciidoc
@@ -305,13 +305,13 @@ If you ever need to renew any of these certificates, you can take a look
at the currently imported certificates using:
+
----
-certutil -D "sql:${HOME}/.pki/nssdb" -L
+certutil -d "sql:${HOME}/.pki/nssdb" -L
----
+
Then remove the expired certificates using:
+
----
-certutil -D "sql:${HOME}/.pki/nssdb" -D -n "My Fancy Certificate Nickname"
+certutil -d "sql:${HOME}/.pki/nssdb" -D -n "My Fancy Certificate Nickname"
----
+
And then import the new and valid certificates using the procedure
From 37e59112a5bb218f6202fef2b579631771b0c7ac Mon Sep 17 00:00:00 2001
From: Julin S <48789920+ju-sh@users.noreply.github.com>
Date: Wed, 3 Jun 2020 13:45:08 +0530
Subject: [PATCH 119/245] Use tmpdir and pathlib
---
tests/unit/scripts/test_check_coverage.py | 15 ++++-----
tests/unit/scripts/test_dictcli.py | 37 ++++++++++++-----------
tests/unit/scripts/test_importer.py | 18 ++++-------
tests/unit/scripts/test_run_vulture.py | 24 +++++++++------
4 files changed, 48 insertions(+), 46 deletions(-)
diff --git a/tests/unit/scripts/test_check_coverage.py b/tests/unit/scripts/test_check_coverage.py
index bb23db512..a60c95525 100644
--- a/tests/unit/scripts/test_check_coverage.py
+++ b/tests/unit/scripts/test_check_coverage.py
@@ -18,8 +18,9 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see .
+import os
import sys
-import os.path
+import pathlib
import pytest
@@ -216,15 +217,15 @@ def test_skipped_non_linux(covtest):
def _generate_files():
"""Get filenames from WHITELISTED_/PERFECT_FILES."""
for src_file in check_coverage.WHITELISTED_FILES:
- yield os.path.join('qutebrowser', src_file)
+ yield pathlib.Path('qutebrowser') / src_file
for test_file, src_file in check_coverage.PERFECT_FILES:
if test_file is not None:
- yield test_file
- yield os.path.join('qutebrowser', src_file)
+ yield pathlib.Path(test_file)
+ yield pathlib.Path('qutebrowser') / src_file
@pytest.mark.parametrize('filename', list(_generate_files()))
def test_files_exist(filename):
- basedir = os.path.join(os.path.dirname(check_coverage.__file__),
- os.pardir, os.pardir)
- assert os.path.exists(os.path.join(basedir, filename))
+ basedir = (pathlib.Path(check_coverage.__file__).parent /
+ os.pardir / os.pardir)
+ assert (basedir / filename).exists()
diff --git a/tests/unit/scripts/test_dictcli.py b/tests/unit/scripts/test_dictcli.py
index 8f02974d9..9add389d8 100644
--- a/tests/unit/scripts/test_dictcli.py
+++ b/tests/unit/scripts/test_dictcli.py
@@ -19,7 +19,8 @@
# along with qutebrowser. If not, see .
-import py.path # pylint: disable=no-name-in-module
+import pathlib
+
import pytest
from qutebrowser.browser.webengine import spell
@@ -59,13 +60,13 @@ def configdata_init():
@pytest.fixture(autouse=True)
-def dict_tmpdir(tmpdir, monkeypatch):
- monkeypatch.setattr(spell, 'dictionary_dir', lambda: str(tmpdir))
- return tmpdir
+def dict_tmp_path(tmp_path, monkeypatch):
+ monkeypatch.setattr(spell, 'dictionary_dir', lambda: str(tmp_path))
+ return tmp_path
-def test_language(dict_tmpdir):
- (dict_tmpdir / 'pl-PL-2-0.bdic').ensure()
+def test_language(dict_tmp_path):
+ (dict_tmp_path / 'pl-PL-2-0.bdic').touch()
assert english().local_filename is None
assert polish()
@@ -82,9 +83,9 @@ def test_latest_yet():
assert dictcli.latest_yet(code2file, 'en-US', 'en-US-8-0.bdic')
-def test_available_languages(dict_tmpdir, monkeypatch):
+def test_available_languages(dict_tmp_path, monkeypatch):
for f in ['pl-PL-2-0.bdic', english().remote_filename]:
- (dict_tmpdir / f).ensure()
+ (dict_tmp_path / f).touch()
monkeypatch.setattr(dictcli, 'language_list_from_api', lambda: [
(lang.code, lang.remote_filename) for lang in langs()
])
@@ -118,27 +119,27 @@ def test_filter_languages():
dictcli.filter_languages(langs(), ['pl-PL', 'en-GB'])
-def test_install(dict_tmpdir, monkeypatch):
+def test_install(dict_tmp_path, monkeypatch):
# given
monkeypatch.setattr(
dictcli, 'download_dictionary',
- lambda _url, dest: py.path.local(dest).ensure()) # pylint: disable=no-member
+ lambda _url, dest: pathlib.Path(dest).touch())
# when
dictcli.install(langs())
# then
- installed_files = [f.basename for f in dict_tmpdir.listdir()]
+ installed_files = [f.name for f in dict_tmp_path.glob('*')]
expected_files = [lang.remote_filename for lang in langs()]
assert sorted(installed_files) == sorted(expected_files)
-def test_update(dict_tmpdir, monkeypatch):
+def test_update(dict_tmp_path, monkeypatch):
# given
monkeypatch.setattr(
dictcli, 'download_dictionary',
- lambda _url, dest: py.path.local(dest).ensure()) # pylint: disable=no-member
- (dict_tmpdir / 'pl-PL-2-0.bdic').ensure()
+ lambda _url, dest: pathlib.Path(dest).touch())
+ (dict_tmp_path / 'pl-PL-2-0.bdic').touch()
assert polish().local_version < polish().remote_version
# when
@@ -148,20 +149,20 @@ def test_update(dict_tmpdir, monkeypatch):
assert polish().local_version == polish().remote_version
-def test_remove_old(dict_tmpdir, monkeypatch):
+def test_remove_old(dict_tmp_path, monkeypatch):
# given
monkeypatch.setattr(
dictcli, 'download_dictionary',
- lambda _url, dest: py.path.local(dest).ensure()) # pylint: disable=no-member
+ lambda _url, dest: pathlib.Path(dest).touch())
for f in ['pl-PL-2-0.bdic',
polish().remote_filename,
english().remote_filename]:
- (dict_tmpdir / f).ensure()
+ (dict_tmp_path / f).touch()
# when
dictcli.remove_old(langs())
# then
- installed_files = [f.basename for f in dict_tmpdir.listdir()]
+ installed_files = [f.name for f in dict_tmp_path.glob('*')]
expected_files = [polish().remote_filename, english().remote_filename]
assert sorted(installed_files) == sorted(expected_files)
diff --git a/tests/unit/scripts/test_importer.py b/tests/unit/scripts/test_importer.py
index 950987afc..4a70ae63e 100644
--- a/tests/unit/scripts/test_importer.py
+++ b/tests/unit/scripts/test_importer.py
@@ -18,37 +18,31 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see .
-import os
+import pathlib
import pytest
from scripts import importer
-_samples = 'tests/unit/scripts/importer_sample'
+_samples = pathlib.Path('tests/unit/scripts/importer_sample')
def qm_expected(input_format):
"""Read expected quickmark-formatted output."""
- with open(os.path.join(_samples, input_format, 'quickmarks'),
- 'r', encoding='utf-8') as f:
- return f.read()
+ return (_samples / input_format / 'quickmarks').read_text(encoding='utf-8')
def bm_expected(input_format):
"""Read expected bookmark-formatted output."""
- with open(os.path.join(_samples, input_format, 'bookmarks'),
- 'r', encoding='utf-8') as f:
- return f.read()
+ return (_samples / input_format / 'bookmarks').read_text(encoding='utf-8')
def search_expected(input_format):
"""Read expected search-formatted (config.py) output."""
- with open(os.path.join(_samples, input_format, 'config_py'),
- 'r', encoding='utf-8') as f:
- return f.read()
+ return (_samples / input_format / 'config_py').read_text(encoding='utf-8')
def sample_input(input_format):
"""Get the sample input path."""
- return os.path.join(_samples, input_format, 'input')
+ return str(_samples / input_format / 'input')
def test_opensearch_convert():
diff --git a/tests/unit/scripts/test_run_vulture.py b/tests/unit/scripts/test_run_vulture.py
index 25630b9fc..0008f3147 100644
--- a/tests/unit/scripts/test_run_vulture.py
+++ b/tests/unit/scripts/test_run_vulture.py
@@ -18,6 +18,8 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see .
+import os
+import pathlib
import sys
import textwrap
@@ -41,29 +43,33 @@ class VultureDir:
"""Fixture similar to pytest's testdir fixture for vulture.
Attributes:
- _tmpdir: The pytest tmpdir fixture.
+ _tmp_path: The pytest tmp_path fixture.
"""
- def __init__(self, tmpdir):
- self._tmpdir = tmpdir
+ def __init__(self, tmp_path):
+ self._tmp_path = tmp_path
def run(self):
"""Run vulture over all generated files and return the output."""
- files = self._tmpdir.listdir()
+ #files = self._tmp_path.listdir()
+ files = list(self._tmp_path.glob('*'))
assert files
- with self._tmpdir.as_cwd():
- return run_vulture.run([str(e.basename) for e in files])
+ old_cwd = pathlib.Path.cwd()
+ os.chdir(str(self._tmp_path))
+ return_value = run_vulture.run([e.name for e in files])
+ os.chdir(str(old_cwd))
+ return return_value
def makepyfile(self, **kwargs):
"""Create a python file, similar to TestDir.makepyfile."""
for filename, data in kwargs.items():
text = textwrap.dedent(data)
- (self._tmpdir / filename + '.py').write_text(text, 'utf-8')
+ (self._tmp_path / (filename + '.py')).write_text(text, 'utf-8')
@pytest.fixture
-def vultdir(tmpdir):
- return VultureDir(tmpdir)
+def vultdir(tmp_path):
+ return VultureDir(tmp_path)
def test_used(vultdir):
From 06bad5beb3489c55050a63b41baaeae712e28cea Mon Sep 17 00:00:00 2001
From: Julin S <48789920+ju-sh@users.noreply.github.com>
Date: Wed, 3 Jun 2020 17:52:40 +0530
Subject: [PATCH 120/245] use tmp_path and pathlib for tests/unit/misc
---
tests/unit/misc/test_editor.py | 56 +++++++++++-----------
tests/unit/misc/test_ipc.py | 25 +++++-----
tests/unit/misc/test_lineparser.py | 31 ++++++------
tests/unit/misc/test_sessions.py | 77 +++++++++++++++---------------
4 files changed, 97 insertions(+), 92 deletions(-)
diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py
index 5fb4478a7..96dd558ff 100644
--- a/tests/unit/misc/test_editor.py
+++ b/tests/unit/misc/test_editor.py
@@ -20,8 +20,8 @@
"""Tests for qutebrowser.misc.editor."""
import time
+import pathlib
import os
-import os.path
import logging
from PyQt5.QtCore import QProcess
@@ -76,16 +76,16 @@ class TestFileHandling:
def test_ok(self, editor):
"""Test file handling when closing with an exit status == 0."""
editor.edit("")
- filename = editor._filename
- assert os.path.exists(filename)
- assert os.path.basename(filename).startswith('qutebrowser-editor-')
+ filename = pathlib.Path(editor._filename)
+ assert filename.exists()
+ assert filename.name.startswith('qutebrowser-editor-')
editor._proc.finished.emit(0, QProcess.NormalExit)
- assert not os.path.exists(filename)
+ assert not filename.exists()
- def test_existing_file(self, editor, tmpdir):
+ def test_existing_file(self, editor, tmp_path):
"""Test editing an existing file."""
- path = tmpdir / 'foo.txt'
- path.ensure()
+ path = tmp_path / 'foo.txt'
+ path.touch()
editor.edit_file(str(path))
editor._proc.finished.emit(0, QProcess.NormalExit)
@@ -95,62 +95,62 @@ class TestFileHandling:
def test_error(self, editor):
"""Test file handling when closing with an exit status != 0."""
editor.edit("")
- filename = editor._filename
- assert os.path.exists(filename)
+ filename = pathlib.Path(editor._filename)
+ assert filename.exists()
editor._proc._proc.exitStatus = lambda: QProcess.CrashExit
editor._proc.finished.emit(1, QProcess.NormalExit)
- assert os.path.exists(filename)
+ assert filename.exists()
- os.remove(filename)
+ filename.unlink()
def test_crash(self, editor):
"""Test file handling when closing with a crash."""
editor.edit("")
- filename = editor._filename
- assert os.path.exists(filename)
+ filename = pathlib.Path(editor._filename)
+ assert filename.exists()
editor._proc._proc.exitStatus = lambda: QProcess.CrashExit
editor._proc.error.emit(QProcess.Crashed)
editor._proc.finished.emit(0, QProcess.CrashExit)
- assert os.path.exists(filename)
+ assert filename.exists()
- os.remove(filename)
+ filename.unlink()
def test_unreadable(self, message_mock, editor, caplog, qtbot):
"""Test file handling when closing with an unreadable file."""
editor.edit("")
- filename = editor._filename
- assert os.path.exists(filename)
- os.chmod(filename, 0o277)
- if os.access(filename, os.R_OK):
+ filename = pathlib.Path(editor._filename)
+ assert filename.exists()
+ filename.chmod(0o277)
+ if os.access(str(filename), os.R_OK):
# Docker container or similar
pytest.skip("File was still readable")
with caplog.at_level(logging.ERROR):
editor._proc.finished.emit(0, QProcess.NormalExit)
- assert not os.path.exists(filename)
+ assert not filename.exists()
msg = message_mock.getmsg(usertypes.MessageLevel.error)
assert msg.text.startswith("Failed to read back edited file: ")
@pytest.fixture
- def unwritable_tmpdir(self, tmpdir):
- tmpdir.chmod(0)
- if os.access(str(tmpdir), os.W_OK):
+ def unwritable_tmp_path(self, tmp_path):
+ tmp_path.chmod(0)
+ if os.access(str(tmp_path), os.W_OK):
# Docker container or similar
pytest.skip("File was still writable")
- yield tmpdir
+ yield tmp_path
- tmpdir.chmod(0o755)
+ tmp_path.chmod(0o755)
def test_unwritable(self, monkeypatch, message_mock, editor,
- unwritable_tmpdir, caplog):
+ unwritable_tmp_path, caplog):
"""Test file handling when the initial file is not writable."""
monkeypatch.setattr(editormod.tempfile, 'tempdir',
- str(unwritable_tmpdir))
+ str(unwritable_tmp_path))
with caplog.at_level(logging.ERROR):
editor.edit("")
diff --git a/tests/unit/misc/test_ipc.py b/tests/unit/misc/test_ipc.py
index 4b94162c6..1da88d3bb 100644
--- a/tests/unit/misc/test_ipc.py
+++ b/tests/unit/misc/test_ipc.py
@@ -20,6 +20,7 @@
"""Tests for qutebrowser.misc.ipc."""
import os
+import pathlib
import getpass
import logging
import json
@@ -297,10 +298,10 @@ class TestListen:
def test_permissions_posix(self, ipc_server):
ipc_server.listen()
sockfile = ipc_server._server.fullServerName()
- sockdir = os.path.dirname(sockfile)
+ sockdir = pathlib.Path(sockfile).parent
file_stat = os.stat(sockfile)
- dir_stat = os.stat(sockdir)
+ dir_stat = sockdir.stat()
# pylint: disable=no-member,useless-suppression
file_owner_ok = file_stat.st_uid == os.getuid()
@@ -504,7 +505,7 @@ class TestSendToRunningInstance:
@pytest.mark.parametrize('has_cwd', [True, False])
@pytest.mark.linux(reason="Causes random trouble on Windows and macOS")
- def test_normal(self, qtbot, tmpdir, ipc_server, mocker, has_cwd):
+ def test_normal(self, qtbot, tmp_path, ipc_server, mocker, has_cwd):
ipc_server.listen()
with qtbot.assertNotEmitted(ipc_server.got_invalid_data):
@@ -512,16 +513,18 @@ class TestSendToRunningInstance:
timeout=5000) as blocker:
with qtbot.waitSignal(ipc_server.got_raw,
timeout=5000) as raw_blocker:
- with tmpdir.as_cwd():
- if not has_cwd:
- m = mocker.patch('qutebrowser.misc.ipc.os')
- m.getcwd.side_effect = OSError
- sent = ipc.send_to_running_instance(
- 'qute-test', ['foo'], None)
+ old_cwd = pathlib.Path.cwd()
+ os.chdir(str(tmp_path))
+ if not has_cwd:
+ m = mocker.patch('qutebrowser.misc.ipc.os')
+ m.getcwd.side_effect = OSError
+ sent = ipc.send_to_running_instance(
+ 'qute-test', ['foo'], None)
+ os.chdir(str(old_cwd))
assert sent
- expected_cwd = str(tmpdir) if has_cwd else ''
+ expected_cwd = str(tmp_path) if has_cwd else ''
assert blocker.args == [['foo'], '', expected_cwd]
@@ -529,7 +532,7 @@ class TestSendToRunningInstance:
'version': qutebrowser.__version__,
'protocol_version': ipc.PROTOCOL_VERSION}
if has_cwd:
- raw_expected['cwd'] = str(tmpdir)
+ raw_expected['cwd'] = str(tmp_path)
assert len(raw_blocker.args) == 1
parsed = json.loads(raw_blocker.args[0].decode('utf-8'))
diff --git a/tests/unit/misc/test_lineparser.py b/tests/unit/misc/test_lineparser.py
index 9ddeaa93e..cdb16d04a 100644
--- a/tests/unit/misc/test_lineparser.py
+++ b/tests/unit/misc/test_lineparser.py
@@ -19,7 +19,7 @@
"""Tests for qutebrowser.misc.lineparser."""
-import os
+import pathlib
from unittest import mock
import pytest
@@ -66,7 +66,7 @@ class TestBaseLineParser:
lineparser._write(f, [testdata])
open_mock.assert_called_once_with(
- os.path.join(self.CONFDIR, self.FILENAME), 'rb')
+ str(pathlib.Path(self.CONFDIR) / self.FILENAME), 'rb')
open_mock().write.assert_has_calls([
mock.call(testdata),
@@ -77,30 +77,31 @@ class TestBaseLineParser:
class TestLineParser:
@pytest.fixture
- def lineparser(self, tmpdir):
+ def lineparser(self, tmp_path):
"""Fixture to get a LineParser for tests."""
- lp = lineparsermod.LineParser(str(tmpdir), 'file')
+ lp = lineparsermod.LineParser(str(tmp_path), 'file')
lp.save()
return lp
- def test_init(self, tmpdir):
+ def test_init(self, tmp_path):
"""Test if creating a line parser correctly reads its file."""
- (tmpdir / 'file').write('one\ntwo\n')
- lineparser = lineparsermod.LineParser(str(tmpdir), 'file')
+ (tmp_path / 'file').write_text('one\ntwo\n')
+ lineparser = lineparsermod.LineParser(str(tmp_path), 'file')
assert lineparser.data == ['one', 'two']
- (tmpdir / 'file').write_binary(b'\xfe\n\xff\n')
- lineparser = lineparsermod.LineParser(str(tmpdir), 'file', binary=True)
+ (tmp_path / 'file').write_bytes(b'\xfe\n\xff\n')
+ lineparser = lineparsermod.LineParser(str(tmp_path), 'file',
+ binary=True)
assert lineparser.data == [b'\xfe', b'\xff']
- def test_clear(self, tmpdir, lineparser):
+ def test_clear(self, tmp_path, lineparser):
"""Test if clear() empties its file."""
lineparser.data = ['one', 'two']
lineparser.save()
- assert (tmpdir / 'file').read() == 'one\ntwo\n'
+ assert (tmp_path / 'file').read_text() == 'one\ntwo\n'
lineparser.clear()
assert not lineparser.data
- assert (tmpdir / 'file').read() == ''
+ assert (tmp_path / 'file').read_text() == ''
def test_double_open(self, lineparser):
"""Test if save() bails on an already open file."""
@@ -109,10 +110,10 @@ class TestLineParser:
match="Refusing to double-open LineParser."):
lineparser.save()
- def test_prepare_save(self, tmpdir, lineparser):
+ def test_prepare_save(self, tmp_path, lineparser):
"""Test if save() bails when _prepare_save() returns False."""
- (tmpdir / 'file').write('pristine\n')
+ (tmp_path / 'file').write_text('pristine\n')
lineparser.data = ['changed']
lineparser._prepare_save = lambda: False
lineparser.save()
- assert (tmpdir / 'file').read() == 'pristine\n'
+ assert (tmp_path / 'file').read_text() == 'pristine\n'
diff --git a/tests/unit/misc/test_sessions.py b/tests/unit/misc/test_sessions.py
index 4aec19dc5..e052751b5 100644
--- a/tests/unit/misc/test_sessions.py
+++ b/tests/unit/misc/test_sessions.py
@@ -40,9 +40,9 @@ webengine_refactoring_xfail = pytest.mark.xfail(
@pytest.fixture
-def sess_man(tmpdir):
+def sess_man(tmp_path):
"""Fixture providing a SessionManager."""
- return sessions.SessionManager(base_path=str(tmpdir))
+ return sessions.SessionManager(base_path=str(tmp_path))
class TestInit:
@@ -57,11 +57,12 @@ class TestInit:
pass
@pytest.mark.parametrize('create_dir', [True, False])
- def test_with_standarddir(self, tmpdir, monkeypatch, create_dir):
- monkeypatch.setattr(sessions.standarddir, 'data', lambda: str(tmpdir))
- session_dir = tmpdir / 'sessions'
+ def test_with_standarddir(self, tmp_path, monkeypatch, create_dir):
+ monkeypatch.setattr(sessions.standarddir, 'data',
+ lambda: str(tmp_path))
+ session_dir = tmp_path / 'sessions'
if create_dir:
- session_dir.ensure(dir=True)
+ session_dir.mkdir()
sessions.init()
@@ -76,14 +77,14 @@ def test_did_not_load(sess_man):
class TestExists:
@pytest.mark.parametrize('absolute', [True, False])
- def test_existent(self, tmpdir, absolute):
- session_dir = tmpdir / 'sessions'
- abs_session = tmpdir / 'foo.yml'
+ def test_existent(self, tmp_path, absolute):
+ session_dir = tmp_path / 'sessions'
+ abs_session = tmp_path / 'foo.yml'
rel_session = session_dir / 'foo.yml'
- session_dir.ensure(dir=True)
- abs_session.ensure()
- rel_session.ensure()
+ session_dir.mkdir()
+ abs_session.touch()
+ rel_session.touch()
man = sessions.SessionManager(str(session_dir))
@@ -95,11 +96,11 @@ class TestExists:
assert man.exists(name)
@pytest.mark.parametrize('absolute', [True, False])
- def test_inexistent(self, tmpdir, absolute):
- man = sessions.SessionManager(str(tmpdir))
+ def test_inexistent(self, tmp_path, absolute):
+ man = sessions.SessionManager(str(tmp_path))
if absolute:
- name = str(tmpdir / 'foo')
+ name = str(tmp_path / 'foo')
else:
name = 'foo'
@@ -208,13 +209,13 @@ class TestSave:
objreg.delete('main-window', scope='window', window=0)
objreg.delete('tabbed-browser', scope='window', window=0)
- def test_no_state_config(self, sess_man, tmpdir, state_config):
- session_path = tmpdir / 'foo.yml'
+ def test_no_state_config(self, sess_man, tmp_path, state_config):
+ session_path = tmp_path / 'foo.yml'
sess_man.save(str(session_path))
assert 'session' not in state_config['general']
- def test_last_window_session_none(self, caplog, sess_man, tmpdir):
- session_path = tmpdir / 'foo.yml'
+ def test_last_window_session_none(self, caplog, sess_man, tmp_path):
+ session_path = tmp_path / 'foo.yml'
with caplog.at_level(logging.ERROR):
sess_man.save(str(session_path), last_window=True)
@@ -222,9 +223,9 @@ class TestSave:
assert caplog.messages == [msg]
assert not session_path.exists()
- def test_last_window_session(self, sess_man, tmpdir):
+ def test_last_window_session(self, sess_man, tmp_path):
sess_man.save_last_window_session()
- session_path = tmpdir / 'foo.yml'
+ session_path = tmp_path / 'foo.yml'
sess_man.save(str(session_path), last_window=True)
data = session_path.read_text('utf-8')
assert data == 'windows: []\n'
@@ -232,24 +233,24 @@ class TestSave:
@pytest.mark.parametrize('exception', [
OSError('foo'), UnicodeEncodeError('ascii', '', 0, 2, 'foo'),
yaml.YAMLError('foo')])
- def test_fake_exception(self, mocker, sess_man, tmpdir, exception):
+ def test_fake_exception(self, mocker, sess_man, tmp_path, exception):
mocker.patch('qutebrowser.misc.sessions.yaml.dump',
side_effect=exception)
with pytest.raises(sessions.SessionError, match=str(exception)):
- sess_man.save(str(tmpdir / 'foo.yml'))
+ sess_man.save(str(tmp_path / 'foo.yml'))
- assert not tmpdir.listdir()
+ assert not list(tmp_path.glob('*'))
- def test_load_next_time(self, tmpdir, state_config, sess_man):
- session_path = tmpdir / 'foo.yml'
+ def test_load_next_time(self, tmp_path, state_config, sess_man):
+ session_path = tmp_path / 'foo.yml'
sess_man.save(str(session_path), load_next_time=True)
assert state_config['general']['session'] == str(session_path)
@webengine_refactoring_xfail
- def test_utf_8_invalid(self, tmpdir, sess_man, fake_history):
+ def test_utf_8_invalid(self, tmp_path, sess_man, fake_history):
"""Make sure data containing invalid UTF8 raises SessionError."""
- session_path = tmpdir / 'foo.yml'
+ session_path = tmp_path / 'foo.yml'
fake_history([Item(QUrl('http://www.qutebrowser.org/'), '\ud800',
active=True)])
@@ -356,18 +357,18 @@ class TestLoadTab:
class TestListSessions:
- def test_no_sessions(self, tmpdir):
- sess_man = sessions.SessionManager(str(tmpdir))
+ def test_no_sessions(self, tmp_path):
+ sess_man = sessions.SessionManager(str(tmp_path))
assert not sess_man.list_sessions()
- def test_with_sessions(self, tmpdir):
- (tmpdir / 'foo.yml').ensure()
- (tmpdir / 'bar.yml').ensure()
- sess_man = sessions.SessionManager(str(tmpdir))
+ def test_with_sessions(self, tmp_path):
+ (tmp_path / 'foo.yml').touch()
+ (tmp_path / 'bar.yml').touch()
+ sess_man = sessions.SessionManager(str(tmp_path))
assert sess_man.list_sessions() == ['bar', 'foo']
- def test_with_other_files(self, tmpdir):
- (tmpdir / 'foo.yml').ensure()
- (tmpdir / 'bar.html').ensure()
- sess_man = sessions.SessionManager(str(tmpdir))
+ def test_with_other_files(self, tmp_path):
+ (tmp_path / 'foo.yml').touch()
+ (tmp_path / 'bar.html').touch()
+ sess_man = sessions.SessionManager(str(tmp_path))
assert sess_man.list_sessions() == ['foo']
From 1606246dc106047ceb5cba6d1e9d464402a8a80f Mon Sep 17 00:00:00 2001
From: "J. Nathanael Philipp"
Date: Thu, 4 Jun 2020 09:27:02 +0200
Subject: [PATCH 121/245] Add cmd argument for control port.
---
misc/userscripts/tor_identity | 29 +++++++++++++++++++----------
1 file changed, 19 insertions(+), 10 deletions(-)
diff --git a/misc/userscripts/tor_identity b/misc/userscripts/tor_identity
index 93b6d4136..1631a0b94 100755
--- a/misc/userscripts/tor_identity
+++ b/misc/userscripts/tor_identity
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-# Copyright 2018 jnphilipp
+# Copyright 2018-2020 J. Nathanael Philipp (jnphilipp)
#
# This file is part of qutebrowser.
#
@@ -30,6 +30,8 @@
import os
import sys
+from argparse import ArgumentParser
+
try:
from stem import Signal
from stem.control import Controller
@@ -41,12 +43,19 @@ except ImportError:
print('Failed to import stem.')
-password = sys.argv[1]
-with Controller.from_port(port=9051) as controller:
- controller.authenticate(password)
- controller.signal(Signal.NEWNYM)
- if os.getenv('QUTE_FIFO'):
- with open(os.environ['QUTE_FIFO'], 'w') as f:
- f.write('message-info "Tor identity changed."')
- else:
- print('Tor identity changed.')
+if __name__ == '__main__':
+ parser = ArgumentParser(prog='tor_identity')
+ parser.add_argument('-c', '--control-port', default=9051,
+ help='Tor control port (default 9051).')
+ parser.add_argument('-p', '--password', type=str, default=None,
+ help='Tor control port password.')
+ args = parser.parse_args()
+
+ with Controller.from_port(port=args.control_port) as controller:
+ controller.authenticate(args.password)
+ controller.signal(Signal.NEWNYM)
+ if os.getenv('QUTE_FIFO'):
+ with open(os.environ['QUTE_FIFO'], 'w') as f:
+ f.write('message-info "Tor identity changed."')
+ else:
+ print('Tor identity changed.')
From 7507825a174a9fddae35bb12794ff397940a7881 Mon Sep 17 00:00:00 2001
From: Nicholas Lantz
Date: Sun, 7 Jun 2020 15:02:47 -0600
Subject: [PATCH 122/245] Add setting to disable back and forward buttons on
mouse.
Resolves #5239
---
doc/help/settings.asciidoc | 9 +++++++++
qutebrowser/browser/eventfilter.py | 5 +++++
qutebrowser/config/configdata.yml | 5 +++++
3 files changed, 19 insertions(+)
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index cc020ea88..ea0d6eb5f 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -251,6 +251,7 @@
|<>|Leave insert mode when starting a new page load.
|<>|Switch to insert mode when clicking flash and other plugins.
|<>|Include hyperlinks in the keyboard focus chain when tabbing.
+|<>|Enable back and forward buttons on the mouse.
|<>|Timeout (in milliseconds) for partially typed key bindings.
|<>|Enable Opera-like mouse rocker gestures.
|<>|Enable spatial navigation.
@@ -3194,6 +3195,14 @@ Type: <>
Default: +pass:[true]+
+[[input.mouse_backforward]]
+=== input.mouse_backforward
+Enable back and forward buttons on the mouse.
+
+Type: <>
+
+Default: +pass:[true]+
+
[[input.partial_timeout]]
=== input.partial_timeout
Timeout (in milliseconds) for partially typed key bindings.
diff --git a/qutebrowser/browser/eventfilter.py b/qutebrowser/browser/eventfilter.py
index f6901003e..d78b608a5 100644
--- a/qutebrowser/browser/eventfilter.py
+++ b/qutebrowser/browser/eventfilter.py
@@ -276,6 +276,11 @@ class TabEventFilter(QObject):
Return:
True if the event should be filtered, False otherwise.
"""
+ if (not config.val.input.mouse_backforward and
+ e.button() in [Qt.XButton1, Qt.XButton2]):
+ # Back and forward on mice are disabled
+ return
+
if e.button() in [Qt.XButton1, Qt.LeftButton]:
# Back button on mice which have it, or rocker gesture
if self._tab.history.can_go_back():
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 664f9facf..ca4506621 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -1399,6 +1399,11 @@ input.rocker_gestures:
This disables the context menu.
+input.mouse_backforward:
+ default: true
+ type: Bool
+ desc: Enable back and forward buttons on the mouse.
+
input.spatial_navigation:
default: false
type: Bool
From 9a1f78fc763b79792600408a09806608ab93d079 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Mon, 8 Jun 2020 09:56:18 +0100
Subject: [PATCH 123/245] Add smarthide property to hide status only on normal
mode
---
qutebrowser/config/configdata.yml | 7 +++++++
qutebrowser/mainwindow/statusbar/bar.py | 20 ++++++++++++++++++--
2 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 664f9facf..b8af6a9a5 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -1564,6 +1564,13 @@ statusbar.hide:
default: false
desc: Hide the statusbar unless a message is shown.
+statusbar.smarthide:
+ type: Bool
+ default: false
+ desc: >-
+ If statusbar.hide is true, only hide the statusbar in normal mode, and show
+ it in other modes.
+
statusbar.padding:
type: Padding
default:
diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py
index 91bdb0b6e..ca211a200 100644
--- a/qutebrowser/mainwindow/statusbar/bar.py
+++ b/qutebrowser/mainwindow/statusbar/bar.py
@@ -254,10 +254,22 @@ class StatusBar(QWidget):
@pyqtSlot()
def maybe_hide(self):
"""Hide the statusbar if it's configured to do so."""
- tab = self._current_tab()
hide = config.val.statusbar.hide
- if hide or (tab is not None and tab.data.fullscreen):
+ smarthide = config.val.statusbar.smarthide
+ tab = self._current_tab()
+ # smarthide is done on mode change.
+ if tab is not None and tab.data.fullscreen:
self.hide()
+ elif hide and not smarthide:
+ self.hide()
+ elif hide and smarthide:
+ try:
+ mode_manager = modeman.instance(self._win_id)
+ if mode_manager.mode == usertypes.KeyMode.normal:
+ self.hide()
+ except KeyError:
+ # If modeman hasn't been initialised, hide the bar.
+ self.hide()
else:
self.show()
@@ -336,6 +348,8 @@ class StatusBar(QWidget):
def on_mode_entered(self, mode):
"""Mark certain modes in the commandline."""
mode_manager = modeman.instance(self._win_id)
+ if config.val.statusbar.smarthide and config.val.statusbar.hide:
+ self.show()
if mode_manager.parsers[mode].passthrough:
self._set_mode_text(mode.name)
if mode in [usertypes.KeyMode.insert,
@@ -350,6 +364,8 @@ class StatusBar(QWidget):
def on_mode_left(self, old_mode, new_mode):
"""Clear marked mode."""
mode_manager = modeman.instance(self._win_id)
+ if config.val.statusbar.smarthide and config.val.statusbar.hide:
+ self.hide()
if mode_manager.parsers[old_mode].passthrough:
if mode_manager.parsers[new_mode].passthrough:
self._set_mode_text(new_mode.name)
From c3978ac74927397e2b45e33ead5aa5020315c633 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Mon, 8 Jun 2020 11:07:15 +0100
Subject: [PATCH 124/245] Make Travis happy
---
qutebrowser/mainwindow/statusbar/bar.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py
index ca211a200..cf9e6aa45 100644
--- a/qutebrowser/mainwindow/statusbar/bar.py
+++ b/qutebrowser/mainwindow/statusbar/bar.py
@@ -257,7 +257,6 @@ class StatusBar(QWidget):
hide = config.val.statusbar.hide
smarthide = config.val.statusbar.smarthide
tab = self._current_tab()
- # smarthide is done on mode change.
if tab is not None and tab.data.fullscreen:
self.hide()
elif hide and not smarthide:
@@ -268,7 +267,7 @@ class StatusBar(QWidget):
if mode_manager.mode == usertypes.KeyMode.normal:
self.hide()
except KeyError:
- # If modeman hasn't been initialised, hide the bar.
+ # If modeman hasn't been initialized, hide the bar.
self.hide()
else:
self.show()
From e19c133e1dc790905aafa52e92bca235d2a2ce31 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 8 Jun 2020 15:27:48 +0200
Subject: [PATCH 125/245] Fix -1 focus_stack_size in TabDeque
---
qutebrowser/mainwindow/tabbedbrowser.py | 5 +++-
tests/unit/mainwindow/test_tabbedbrowser.py | 32 +++++++++++++++++++++
2 files changed, 36 insertions(+), 1 deletion(-)
create mode 100644 tests/unit/mainwindow/test_tabbedbrowser.py
diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py
index 25b05a036..f9112c6ab 100644
--- a/qutebrowser/mainwindow/tabbedbrowser.py
+++ b/qutebrowser/mainwindow/tabbedbrowser.py
@@ -64,8 +64,11 @@ class TabDeque:
"""
def __init__(self) -> None:
+ size = config.val.tabs.focus_stack_size
+ if size < 0:
+ size = None
self._stack = collections.deque(
- maxlen=config.val.tabs.focus_stack_size
+ maxlen=size
) # type: typing.Deque[weakref.ReferenceType[QWidget]]
# Items that have been removed from the primary stack.
self._stack_deleted = [
diff --git a/tests/unit/mainwindow/test_tabbedbrowser.py b/tests/unit/mainwindow/test_tabbedbrowser.py
new file mode 100644
index 000000000..a0f772cf9
--- /dev/null
+++ b/tests/unit/mainwindow/test_tabbedbrowser.py
@@ -0,0 +1,32 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2020 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 .
+
+
+import pytest
+
+from qutebrowser.mainwindow import tabbedbrowser
+
+
+class TestTabDeque:
+
+ @pytest.mark.parametrize('size', [-1, 5])
+ def test_size_handling(self, size, config_stub):
+ config_stub.val.tabs.focus_stack_size = size
+ dq = tabbedbrowser.TabDeque()
+ dq.update_size()
From eb8681556ff8a4da06002eb455ab3556df3d8cf9 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 8 Jun 2020 17:12:48 +0200
Subject: [PATCH 126/245] pdfjs: Move _SYSTEM_PATHS to module-level
This makes it possible to patch them for tests.
---
qutebrowser/browser/pdfjs.py | 24 ++++++++++++++----------
1 file changed, 14 insertions(+), 10 deletions(-)
diff --git a/qutebrowser/browser/pdfjs.py b/qutebrowser/browser/pdfjs.py
index cca21abcb..af3b368cd 100644
--- a/qutebrowser/browser/pdfjs.py
+++ b/qutebrowser/browser/pdfjs.py
@@ -30,6 +30,19 @@ from qutebrowser.misc import objects
from qutebrowser.config import config
+_SYSTEM_PATHS = [
+ # Debian pdf.js-common
+ # Arch Linux pdfjs (AUR)
+ '/usr/share/pdf.js/',
+ # Flatpak (Flathub)
+ '/app/share/pdf.js/',
+ # Arch Linux pdf.js (AUR)
+ '/usr/share/javascript/pdf.js/',
+ # Debian libjs-pdf
+ '/usr/share/javascript/pdf/',
+]
+
+
class PDFJSNotFound(Exception):
"""Raised when no pdf.js installation is found.
@@ -130,16 +143,7 @@ def get_pdfjs_res_and_path(path):
content = None
file_path = None
- system_paths = [
- # Debian pdf.js-common
- # Arch Linux pdfjs (AUR)
- '/usr/share/pdf.js/',
- # Flatpak (Flathub)
- '/app/share/pdf.js/',
- # Arch Linux pdf.js (AUR)
- '/usr/share/javascript/pdf.js/',
- # Debian libjs-pdf
- '/usr/share/javascript/pdf/',
+ system_paths = _SYSTEM_PATHS + [
# fallback
os.path.join(standarddir.data(), 'pdfjs'),
# hardcoded fallback for --temp-basedir
From 1303d92c29c19dd42a3747c48cde5f69447cc9fa Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 8 Jun 2020 17:01:06 +0200
Subject: [PATCH 127/245] pdfjs: Also check for web/viewer.html in
is_available()
If a build/pdf.js file exists, but web/viewer.html does not, we would run into
a PDFJSNotFound exception inside of generate_pdfjs_page().
---
qutebrowser/browser/pdfjs.py | 1 +
tests/unit/browser/test_pdfjs.py | 9 +++++++++
2 files changed, 10 insertions(+)
diff --git a/qutebrowser/browser/pdfjs.py b/qutebrowser/browser/pdfjs.py
index af3b368cd..0c6d02501 100644
--- a/qutebrowser/browser/pdfjs.py
+++ b/qutebrowser/browser/pdfjs.py
@@ -228,6 +228,7 @@ def is_available():
"""Return true if a pdfjs installation is available."""
try:
get_pdfjs_res('build/pdf.js')
+ get_pdfjs_res('web/viewer.html')
except PDFJSNotFound:
return False
else:
diff --git a/tests/unit/browser/test_pdfjs.py b/tests/unit/browser/test_pdfjs.py
index e95f665c7..d05ff1fc0 100644
--- a/tests/unit/browser/test_pdfjs.py
+++ b/tests/unit/browser/test_pdfjs.py
@@ -52,6 +52,15 @@ def test_generate_pdfjs_page(available, snippet, monkeypatch):
assert snippet in content
+def test_broken_installation(data_tmpdir, monkeypatch):
+ """Make sure we don't crash with a broken local installation."""
+ monkeypatch.setattr(pdfjs, '_SYSTEM_PATHS', [])
+ (data_tmpdir / 'pdfjs' / 'pdf.js').ensure() # But no viewer.html
+
+ content = pdfjs.generate_pdfjs_page('example.pdf', QUrl())
+ assert 'No pdf.js installation found
' in content
+
+
# Note that we got double protection, once because we use QUrl.FullyEncoded and
# because we use qutebrowser.utils.javascript.to_js. Characters like " are
# already replaced by QUrl.
From e58ba17dd2df200a322b97075af6171f8b17688c Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 8 Jun 2020 15:28:21 +0200
Subject: [PATCH 128/245] Update changelog
---
doc/changelog.asciidoc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 5da5ef553..fd6932f48 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -15,6 +15,15 @@ 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.
+v1.13.0 (unreleased)
+--------------------
+
+Fixed
+~~~~~
+
+- Crash when `tabs.focus_stack_size` is set to -1.
+- Crash when a `pdf.js` file for PDF.js exists, but `viewer.html` does not.
+
v1.12.0 (2020-06-01)
--------------------
From fe6462f307d6226cda81247e72c4560e0d7233f1 Mon Sep 17 00:00:00 2001
From: Nicholas Lantz
Date: Mon, 8 Jun 2020 10:45:35 -0600
Subject: [PATCH 129/245] Changed position of input.mouse_backforward in
configdata.
It was out of alphabetic order before.
---
qutebrowser/config/configdata.yml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index ca4506621..382a8308e 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -1379,6 +1379,11 @@ input.links_included_in_focus_chain:
supports_pattern: true
desc: Include hyperlinks in the keyboard focus chain when tabbing.
+input.mouse_backforward:
+ default: true
+ type: Bool
+ desc: Enable back and forward buttons on the mouse.
+
input.partial_timeout:
default: 5000
type:
@@ -1399,11 +1404,6 @@ input.rocker_gestures:
This disables the context menu.
-input.mouse_backforward:
- default: true
- type: Bool
- desc: Enable back and forward buttons on the mouse.
-
input.spatial_navigation:
default: false
type: Bool
From cfdebbbe232708b1e1e9f4f77746d466a7c7d86e Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 8 Jun 2020 20:07:16 +0200
Subject: [PATCH 130/245] tests: Stabilize test_qute_settings_persistence
The :jseval command triggers an XHR in JS, and we'll need to wait for that to
actually finish (async) before the setting is really set.
See https://github.com/qutebrowser/qutebrowser/issues/5390#issuecomment-628773737
---
tests/end2end/test_invocations.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py
index d61458ef3..a31494634 100644
--- a/tests/end2end/test_invocations.py
+++ b/tests/end2end/test_invocations.py
@@ -381,6 +381,8 @@ def test_qute_settings_persistence(short_tmpdir, request, quteproc_new):
quteproc_new.send_cmd(':jseval --world main '
'cset("search.ignore_case", "always")')
quteproc_new.wait_for(message='No output or error')
+ quteproc_new.wait_for(category='config',
+ message='Config option changed: *')
assert quteproc_new.get_setting('search.ignore_case') == 'always'
From fa85e855524914531cd23abe89e7292859e82591 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 8 Jun 2020 21:01:39 +0200
Subject: [PATCH 131/245] Add quirk for missing globalThis
globalThis was introduced in Chrome 71:
https://caniuse.com/#feat=mdn-javascript_builtins_globalthis
Since those websites are quite common and we probably want to continue
supporting Qt 5.12 for a bit, let's bite the bullet and add a small polyfill,
because it's trivial.
Fixes #5486
---
qutebrowser/browser/webengine/webenginetab.py | 22 ++++++++++++++-----
.../javascript/globalthis_quirk.user.js | 9 ++++++++
2 files changed, 26 insertions(+), 5 deletions(-)
create mode 100644 qutebrowser/javascript/globalthis_quirk.user.js
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index 5fd4a9e11..d1e7e88e6 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -1237,19 +1237,31 @@ class _WebEngineScripts(QObject):
"""Add site-specific quirk scripts.
NOTE: This isn't implemented for Qt 5.7 because of different UserScript
- semantics there. We only have a quirk for WhatsApp Web right now. It
- looks like that quirk isn't needed for Qt < 5.13.
+ semantics there. The WhatsApp Web quirk isn't needed for Qt < 5.13.
+ The globalthis_quirk would be, but let's not keep such old QtWebEngine
+ versions on life support.
"""
if not config.val.content.site_specific_quirks:
return
page_scripts = self._widget.page().scripts()
+ quirks = [
+ (
+ 'whatsapp_web_quirk',
+ QWebEngineScript.DocumentReady,
+ QWebEngineScript.ApplicationWorld,
+ ),
+ ]
+ if not qtutils.version_check('5.13'):
+ quirks.append(('globalthis_quirk',
+ QWebEngineScript.DocumentCreation,
+ QWebEngineScript.MainWorld))
- for filename in ['whatsapp_web_quirk']:
+ for filename, injection_point, world in quirks:
script = QWebEngineScript()
script.setName(filename)
- script.setWorldId(QWebEngineScript.ApplicationWorld)
- script.setInjectionPoint(QWebEngineScript.DocumentReady)
+ script.setWorldId(world)
+ script.setInjectionPoint(injection_point)
src = utils.read_file("javascript/{}.user.js".format(filename))
script.setSourceCode(src)
page_scripts.insert(script)
diff --git a/qutebrowser/javascript/globalthis_quirk.user.js b/qutebrowser/javascript/globalthis_quirk.user.js
new file mode 100644
index 000000000..03e74de3c
--- /dev/null
+++ b/qutebrowser/javascript/globalthis_quirk.user.js
@@ -0,0 +1,9 @@
+// ==UserScript==
+// @include https://www.reddit.com/*
+// @include https://open.spotify.com/*
+// ==/UserScript==
+
+// Polyfill for a failing globalThis with older Qt versions.
+
+"use strict";
+window.globalThis = window;
From d373d12b52ffa1456b06d496affea19d993a4735 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 8 Jun 2020 21:19:09 +0200
Subject: [PATCH 132/245] Update changelog
---
doc/changelog.asciidoc | 1 +
1 file changed, 1 insertion(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index fd6932f48..4ba21a4d3 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -23,6 +23,7 @@ Fixed
- Crash when `tabs.focus_stack_size` is set to -1.
- Crash when a `pdf.js` file for PDF.js exists, but `viewer.html` does not.
+- New site-specific quirk for a missing `globalThis` in Qt <= 5.12 on Reddit and Spotify
v1.12.0 (2020-06-01)
--------------------
From 6047a83b9057ab20e5b559681117188e91400cfa Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:11 +0200
Subject: [PATCH 133/245] Update certifi from 2020.4.5.1 to 2020.4.5.2
---
misc/requirements/requirements-codecov.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt
index 0a38e12b5..986909ef9 100644
--- a/misc/requirements/requirements-codecov.txt
+++ b/misc/requirements/requirements-codecov.txt
@@ -1,6 +1,6 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-certifi==2020.4.5.1
+certifi==2020.4.5.2
chardet==3.0.4
codecov==2.1.3
coverage==5.1
From 992f4cb41e7250367d594913860db8723817d16f Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:12 +0200
Subject: [PATCH 134/245] Update certifi from 2020.4.5.1 to 2020.4.5.2
---
misc/requirements/requirements-dev.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt
index bb834483f..2e7939deb 100644
--- a/misc/requirements/requirements-dev.txt
+++ b/misc/requirements/requirements-dev.txt
@@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
bump2version==1.0.0
-certifi==2020.4.5.1
+certifi==2020.4.5.2
cffi==1.14.0
chardet==3.0.4
colorama==0.4.3
From 63070432a718805837325f3ef9627fb38d46f643 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:13 +0200
Subject: [PATCH 135/245] Update certifi from 2020.4.5.1 to 2020.4.5.2
---
misc/requirements/requirements-pylint.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt
index e80ddbf50..791ced87c 100644
--- a/misc/requirements/requirements-pylint.txt
+++ b/misc/requirements/requirements-pylint.txt
@@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
astroid==2.3.3 # rq.filter: < 2.4
-certifi==2020.4.5.1
+certifi==2020.4.5.2
cffi==1.14.0
chardet==3.0.4
cryptography==2.9.2
From 0285c0a206d6215c43377c6f9fd395296e7b1fd4 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:14 +0200
Subject: [PATCH 136/245] Update certifi from 2020.4.5.1 to 2020.4.5.2
---
misc/requirements/requirements-sphinx.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt
index c90606b66..6340faddc 100644
--- a/misc/requirements/requirements-sphinx.txt
+++ b/misc/requirements/requirements-sphinx.txt
@@ -2,7 +2,7 @@
alabaster==0.7.12
Babel==2.8.0
-certifi==2020.4.5.1
+certifi==2020.4.5.2
chardet==3.0.4
docutils==0.16
idna==2.9
From 3284adf5fe4bf2be092da9aa531fafcfb7023a3f Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:16 +0200
Subject: [PATCH 137/245] Update codecov from 2.1.3 to 2.1.4
---
misc/requirements/requirements-codecov.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt
index 986909ef9..7e869803a 100644
--- a/misc/requirements/requirements-codecov.txt
+++ b/misc/requirements/requirements-codecov.txt
@@ -2,7 +2,7 @@
certifi==2020.4.5.2
chardet==3.0.4
-codecov==2.1.3
+codecov==2.1.4
coverage==5.1
idna==2.9
requests==2.23.0
From daca14aa3b93290e1ea2b8d34342c145c0551d05 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:17 +0200
Subject: [PATCH 138/245] Update flake8-comprehensions from 3.2.2 to 3.2.3
---
misc/requirements/requirements-flake8.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt
index d0292a4eb..0cd0df369 100644
--- a/misc/requirements/requirements-flake8.txt
+++ b/misc/requirements/requirements-flake8.txt
@@ -4,7 +4,7 @@ attrs==19.3.0
flake8==3.8.2
flake8-bugbear==20.1.4
flake8-builtins==1.5.3
-flake8-comprehensions==3.2.2
+flake8-comprehensions==3.2.3
flake8-copyright==0.2.2
flake8-debugger==3.2.1
flake8-deprecated==1.3
From 95b2063506a6dcdcb65384113399e24526ec03db Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:18 +0200
Subject: [PATCH 139/245] Update mypy from 0.770 to 0.780
---
misc/requirements/requirements-mypy.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt
index 3900a8c20..7759f96b8 100644
--- a/misc/requirements/requirements-mypy.txt
+++ b/misc/requirements/requirements-mypy.txt
@@ -1,6 +1,6 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-mypy==0.770
+mypy==0.780
mypy-extensions==0.4.3
-e git+https://github.com/stlehmann/PyQt5-stubs.git@master#egg=PyQt5_stubs
typed-ast==1.4.1
From 593fee8793737d2d2dd7a6f20e8ae7ed3f0bf6b9 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:19 +0200
Subject: [PATCH 140/245] Update lazy-object-proxy from 1.4.3 to 1.5.0
---
misc/requirements/requirements-pylint.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt
index 791ced87c..cb4892f9c 100644
--- a/misc/requirements/requirements-pylint.txt
+++ b/misc/requirements/requirements-pylint.txt
@@ -9,7 +9,7 @@ github3.py==1.3.0
idna==2.9
isort==4.3.21
jwcrypto==0.7
-lazy-object-proxy==1.4.3
+lazy-object-proxy==1.5.0
mccabe==0.6.1
pycparser==2.20
pylint==2.4.4 # rq.filter: < 2.5
From d32063dcf5049da2c3a963b5826a28a94955735f Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:20 +0200
Subject: [PATCH 141/245] Update sphinx from 3.0.4 to 3.1.0
---
misc/requirements/requirements-sphinx.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt
index 6340faddc..5851b8b72 100644
--- a/misc/requirements/requirements-sphinx.txt
+++ b/misc/requirements/requirements-sphinx.txt
@@ -16,7 +16,7 @@ pytz==2020.1
requests==2.23.0
six==1.15.0
snowballstemmer==2.0.0
-Sphinx==3.0.4
+Sphinx==3.1.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==1.0.3
From b550fb348f2b5dd093a33adce701c994ee6bda27 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:21 +0200
Subject: [PATCH 142/245] Update pytest from 5.4.2 to 5.4.3
---
misc/requirements/requirements-tests.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index 0c96a2af1..8a6dab6da 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -26,7 +26,7 @@ py==1.8.1
py-cpuinfo==5.0.0
Pygments==2.6.1
pyparsing==2.4.7
-pytest==5.4.2
+pytest==5.4.3
pytest-bdd==3.3.0
pytest-benchmark==3.2.3
pytest-cov==2.9.0
From 59dbbc63961a0cfd4107b7c47623ec15812f8350 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:22 +0200
Subject: [PATCH 143/245] Update pytest-bdd from 3.3.0 to 3.4.0
---
misc/requirements/requirements-tests.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index 8a6dab6da..65f83a1fb 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -27,7 +27,7 @@ py-cpuinfo==5.0.0
Pygments==2.6.1
pyparsing==2.4.7
pytest==5.4.3
-pytest-bdd==3.3.0
+pytest-bdd==3.4.0
pytest-benchmark==3.2.3
pytest-cov==2.9.0
pytest-instafail==0.4.1.post0
From 4e97186a1151b33f883b9ebff1d72734ab2503a4 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:23 +0200
Subject: [PATCH 144/245] Update pytest-mock from 3.1.0 to 3.1.1
---
misc/requirements/requirements-tests.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index 65f83a1fb..22bfc3c2d 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -31,7 +31,7 @@ pytest-bdd==3.4.0
pytest-benchmark==3.2.3
pytest-cov==2.9.0
pytest-instafail==0.4.1.post0
-pytest-mock==3.1.0
+pytest-mock==3.1.1
pytest-qt==3.3.0
pytest-repeat==0.8.0
pytest-rerunfailures==9.0
From 6f5f80f77f92872f7b71e3a9a461173b23ae8491 Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:25 +0200
Subject: [PATCH 145/245] Update sortedcontainers from 2.1.0 to 2.2.2
---
misc/requirements/requirements-tests.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index 22bfc3c2d..dd9073681 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -39,7 +39,7 @@ pytest-travis-fold==1.3.0
pytest-xvfb==1.2.0
PyVirtualDisplay==0.2.5 # rq.filter: < 1.0
six==1.15.0
-sortedcontainers==2.1.0
+sortedcontainers==2.2.2
soupsieve==2.0.1
vulture==1.5
wcwidth==0.2.2
From 82c77bae8b9d58cad6c2f23cf6bbd86d762e104e Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:26 +0200
Subject: [PATCH 146/245] Update wcwidth from 0.2.2 to 0.2.4
---
misc/requirements/requirements-tests.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index dd9073681..ed4596a82 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -42,6 +42,6 @@ six==1.15.0
sortedcontainers==2.2.2
soupsieve==2.0.1
vulture==1.5
-wcwidth==0.2.2
+wcwidth==0.2.4
Werkzeug==1.0.1
jaraco.functools==2.0; python_version<"3.6" # rq.filter: <= 2.0
From 0f9720adac09002dd883d2ee9137d660e7d4769f Mon Sep 17 00:00:00 2001
From: pyup-bot
Date: Mon, 8 Jun 2020 21:30:27 +0200
Subject: [PATCH 147/245] Update tox from 3.15.1 to 3.15.2
---
misc/requirements/requirements-tox.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt
index 3b4c93bb4..dd288088d 100644
--- a/misc/requirements/requirements-tox.txt
+++ b/misc/requirements/requirements-tox.txt
@@ -9,7 +9,7 @@ py==1.8.1
pyparsing==2.4.7
six==1.15.0
toml==0.10.1
-tox==3.15.1
+tox==3.15.2
tox-pip-version==0.0.7
tox-venv==0.4.0
virtualenv==20.0.21
From ee8b2cb27a40271381fc85ff00ac046c4090fd7a Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 9 Jun 2020 13:18:45 +0200
Subject: [PATCH 148/245] Fix cast for new mypy version
---
qutebrowser/utils/qtutils.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py
index 109d2dfed..04db1f0cb 100644
--- a/qutebrowser/utils/qtutils.py
+++ b/qutebrowser/utils/qtutils.py
@@ -230,7 +230,7 @@ def savefile_open(
if not open_ok:
raise QtOSError(f)
- dev = typing.cast(typing.IO[bytes], PyQIODevice(f))
+ dev = typing.cast(typing.BinaryIO, PyQIODevice(f))
if binary:
new_f = dev # type: typing.IO
From 5b9237318e33009268780f28f0c84b0e0fb148d6 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 9 Jun 2020 13:19:02 +0200
Subject: [PATCH 149/245] Hide mypy.ini
---
mypy.ini => .mypy.ini | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename mypy.ini => .mypy.ini (100%)
diff --git a/mypy.ini b/.mypy.ini
similarity index 100%
rename from mypy.ini
rename to .mypy.ini
From ba25246c310e033342e7374c125ce3df17d1c212 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Tue, 9 Jun 2020 12:40:34 +0100
Subject: [PATCH 150/245] Merge statusbar.hide and statusbar.smarthide options
into statusbar.show
---
doc/help/settings.asciidoc | 24 +++++++++++++++---------
qutebrowser/config/configdata.yml | 22 +++++++++++-----------
qutebrowser/mainwindow/statusbar/bar.py | 15 +++++++--------
3 files changed, 33 insertions(+), 28 deletions(-)
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index cc020ea88..82588c9a0 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -277,9 +277,9 @@
|<>|Name of the session to save by default.
|<>|Load a restored tab as soon as it takes focus.
|<>|Languages to use for spell checking.
-|<>|Hide the statusbar unless a message is shown.
|<>|Padding (in pixels) for the statusbar.
|<>|Position of the status bar.
+|<>|When to show the statusbar.
|<>|List of widgets displayed in the statusbar.
|<>|Open new tabs (middleclick/ctrl+click) in the background.
|<>|Mouse button with which to close tabs.
@@ -3540,14 +3540,6 @@ On QtWebEngine, this setting requires Qt 5.8 or newer.
On QtWebKit, this setting is unavailable.
-[[statusbar.hide]]
-=== statusbar.hide
-Hide the statusbar unless a message is shown.
-
-Type: <>
-
-Default: +pass:[false]+
-
[[statusbar.padding]]
=== statusbar.padding
Padding (in pixels) for the statusbar.
@@ -3574,6 +3566,20 @@ Valid values:
Default: +pass:[bottom]+
+[[statusbar.show]]
+=== statusbar.show
+When to show the statusbar.
+
+Type: <>
+
+Valid values:
+
+ * +always+: Always show the statusbar.
+ * +never+: Always hide the statusbar unless a message is shown.
+ * +in_mode+: Only show the statusbar when in modes other than normal mode or when a message is shown.
+
+Default: +pass:[always]+
+
[[statusbar.widgets]]
=== statusbar.widgets
List of widgets displayed in the statusbar.
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index b8af6a9a5..4ca2231a3 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -1559,17 +1559,17 @@ spellcheck.languages:
## statusbar
-statusbar.hide:
- type: Bool
- default: false
- desc: Hide the statusbar unless a message is shown.
-
-statusbar.smarthide:
- type: Bool
- default: false
- desc: >-
- If statusbar.hide is true, only hide the statusbar in normal mode, and show
- it in other modes.
+statusbar.show:
+ default: always
+ type:
+ name: String
+ valid_values:
+ - always: Always show the statusbar.
+ - never: Always hide the statusbar unless a message is shown.
+ - in_mode: >-
+ Only show the statusbar when in modes other than normal mode or
+ when a message is shown.
+ desc: When to show the statusbar.
statusbar.padding:
type: Padding
diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py
index cf9e6aa45..1485dd86a 100644
--- a/qutebrowser/mainwindow/statusbar/bar.py
+++ b/qutebrowser/mainwindow/statusbar/bar.py
@@ -203,7 +203,7 @@ class StatusBar(QWidget):
@pyqtSlot(str)
def _on_config_changed(self, option):
- if option == 'statusbar.hide':
+ if option == 'statusbar.show':
self.maybe_hide()
elif option == 'statusbar.padding':
self._set_hbox_padding()
@@ -254,14 +254,13 @@ class StatusBar(QWidget):
@pyqtSlot()
def maybe_hide(self):
"""Hide the statusbar if it's configured to do so."""
- hide = config.val.statusbar.hide
- smarthide = config.val.statusbar.smarthide
+ strategy = config.val.statusbar.show
tab = self._current_tab()
if tab is not None and tab.data.fullscreen:
self.hide()
- elif hide and not smarthide:
+ elif strategy == 'never':
self.hide()
- elif hide and smarthide:
+ elif strategy == 'in_mode':
try:
mode_manager = modeman.instance(self._win_id)
if mode_manager.mode == usertypes.KeyMode.normal:
@@ -269,7 +268,7 @@ class StatusBar(QWidget):
except KeyError:
# If modeman hasn't been initialized, hide the bar.
self.hide()
- else:
+ elif strategy == 'always':
self.show()
def _set_hbox_padding(self):
@@ -347,7 +346,7 @@ class StatusBar(QWidget):
def on_mode_entered(self, mode):
"""Mark certain modes in the commandline."""
mode_manager = modeman.instance(self._win_id)
- if config.val.statusbar.smarthide and config.val.statusbar.hide:
+ if config.val.statusbar.show == 'in_mode':
self.show()
if mode_manager.parsers[mode].passthrough:
self._set_mode_text(mode.name)
@@ -363,7 +362,7 @@ class StatusBar(QWidget):
def on_mode_left(self, old_mode, new_mode):
"""Clear marked mode."""
mode_manager = modeman.instance(self._win_id)
- if config.val.statusbar.smarthide and config.val.statusbar.hide:
+ if config.val.statusbar.show == 'in_mode':
self.hide()
if mode_manager.parsers[old_mode].passthrough:
if mode_manager.parsers[new_mode].passthrough:
From 8d7b13f1efab8ab0777dd6c0aebba4b3e6728fc2 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Tue, 9 Jun 2020 12:40:53 +0100
Subject: [PATCH 151/245] Add statusbar.show to YamlMigrations
---
qutebrowser/config/configfiles.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py
index 7d567ebd3..498f2ae5d 100644
--- a/qutebrowser/config/configfiles.py
+++ b/qutebrowser/config/configfiles.py
@@ -332,6 +332,11 @@ class YamlMigrations(QObject):
new_name='tabs.mode_on_change',
true_value='persist',
false_value='normal')
+ self._migrate_renamed_bool(
+ old_name='statusbar.hide',
+ new_name='statusbar.show',
+ true_value='never',
+ false_value='always')
for setting in ['tabs.title.format',
'tabs.title.format_pinned',
From d4a7d8ef6210002e6990a9108e49ba9b5b134e82 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Tue, 9 Jun 2020 12:49:36 +0100
Subject: [PATCH 152/245] Add additional else clause for statusbar.show=in_mode
---
qutebrowser/mainwindow/statusbar/bar.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py
index 1485dd86a..8ccfc2070 100644
--- a/qutebrowser/mainwindow/statusbar/bar.py
+++ b/qutebrowser/mainwindow/statusbar/bar.py
@@ -265,6 +265,8 @@ class StatusBar(QWidget):
mode_manager = modeman.instance(self._win_id)
if mode_manager.mode == usertypes.KeyMode.normal:
self.hide()
+ else:
+ self.show()
except KeyError:
# If modeman hasn't been initialized, hide the bar.
self.hide()
From d38a2094f1a006b5702d1d9702f1da8b3a610ce9 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 9 Jun 2020 13:57:20 +0200
Subject: [PATCH 153/245] tests: Add ignores for Qt 5.15 Chromium debug build
---
tests/end2end/fixtures/quteprocess.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py
index 626981f13..2e47c9e43 100644
--- a/tests/end2end/fixtures/quteprocess.py
+++ b/tests/end2end/fixtures/quteprocess.py
@@ -290,6 +290,18 @@ def is_ignored_chromium_message(line):
# [2968:3108:0601/123442.125:ERROR:mf_helpers.cc(14)] Error in
# dxva_video_decode_accelerator_win.cc on line 517
'Error in dxva_video_decode_accelerator_win.cc on line 517',
+
+ # Qt 5.15 and debug build
+ # [134188:134199:0609/132454.797229:WARNING:
+ # simple_synchronous_entry.cc(1389)]
+ # Could not open platform files for entry.
+ # [134151:134187:0609/132456.754321:ERROR:process_posix.cc(333)]
+ # Unable to terminate process 134188: No such process (3)
+ # [134151:134187:0609/132456.754414:WARNING:internal_linux.cc(64)]
+ # Failed to read /proc/134188/stat
+ 'Could not open platform files for entry.',
+ 'Unable to terminate process *: No such process (3)',
+ 'Failed to read /proc/*/stat',
]
return any(testutils.pattern_match(pattern=pattern, value=message)
for pattern in ignored_messages)
From a87bff269914b5924c9df21bf3763aeb7f20451a Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 9 Jun 2020 13:59:21 +0200
Subject: [PATCH 154/245] Update changelog
---
doc/changelog.asciidoc | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 4ba21a4d3..33b69121e 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -18,6 +18,12 @@ breaking changes (such as renamed commands) can happen in minor releases.
v1.13.0 (unreleased)
--------------------
+Changed
+~~~~~~~
+
+- The `tor_identity` userscript now takes the password via a `-p` flag and has
+ a new `-c` flag to customize the Tor control port.
+
Fixed
~~~~~
From 3204364d9d4bceacc82ac1dcdd71dba8ea4dcb14 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Tue, 9 Jun 2020 13:11:07 +0100
Subject: [PATCH 155/245] Update naming in qutebrowser/config/configdata.yml
Co-authored-by: Florian Bruhin
---
qutebrowser/config/configdata.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 4ca2231a3..256fdc906 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -1566,7 +1566,7 @@ statusbar.show:
valid_values:
- always: Always show the statusbar.
- never: Always hide the statusbar unless a message is shown.
- - in_mode: >-
+ - in-mode: >-
Only show the statusbar when in modes other than normal mode or
when a message is shown.
desc: When to show the statusbar.
From 2bc14ddc681f946535f905d07d8e4f80013590a5 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Tue, 9 Jun 2020 13:29:03 +0100
Subject: [PATCH 156/245] Modify modeman.instance() to return specific error
when unavailable.
This avoids the need to have a generic 'except KeyError' in
statusbar/bar.py when querying the current mode, in the case when the
mode manager has not been initialized yet.
---
qutebrowser/keyinput/modeman.py | 20 ++++++++++++++++++--
qutebrowser/mainwindow/statusbar/bar.py | 6 +++---
2 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py
index 880b1ec93..eb96020f3 100644
--- a/qutebrowser/keyinput/modeman.py
+++ b/qutebrowser/keyinput/modeman.py
@@ -68,6 +68,14 @@ class NotInModeError(Exception):
"""Exception raised when we want to leave a mode we're not in."""
+class UnavailableError(Exception):
+
+ """Exception raised when trying to access modeman before initialization.
+
+ Thrown by instance() if modeman has not been initialized yet.
+ """
+
+
def init(win_id: int, parent: QObject) -> 'ModeManager':
"""Initialize the mode manager and the keyparsers for the given win_id."""
modeman = ModeManager(win_id, parent)
@@ -169,8 +177,16 @@ def init(win_id: int, parent: QObject) -> 'ModeManager':
def instance(win_id: Union[int, str]) -> 'ModeManager':
- """Get a modemanager object."""
- return objreg.get('mode-manager', scope='window', window=win_id)
+ """Get a modemanager object.
+
+ Raises UnavailableError if there is no instance available yet.
+ """
+ mode_manager = objreg.get('mode-manager', scope='window', window=win_id,
+ default=None)
+ if mode_manager is not None:
+ return mode_manager
+ else:
+ raise UnavailableError("ModeManager is not initialized yet.")
def enter(win_id: int,
diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py
index 8ccfc2070..384b73d7a 100644
--- a/qutebrowser/mainwindow/statusbar/bar.py
+++ b/qutebrowser/mainwindow/statusbar/bar.py
@@ -263,13 +263,13 @@ class StatusBar(QWidget):
elif strategy == 'in_mode':
try:
mode_manager = modeman.instance(self._win_id)
+ except modeman.UnavailableError:
+ self.hide()
+ else:
if mode_manager.mode == usertypes.KeyMode.normal:
self.hide()
else:
self.show()
- except KeyError:
- # If modeman hasn't been initialized, hide the bar.
- self.hide()
elif strategy == 'always':
self.show()
From 10211f51557b29341e6283c26e562c2eff6185e3 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Tue, 9 Jun 2020 13:46:30 +0100
Subject: [PATCH 157/245] Add unreachable test in statusbar/bar.py
---
qutebrowser/mainwindow/statusbar/bar.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py
index 384b73d7a..2735ad073 100644
--- a/qutebrowser/mainwindow/statusbar/bar.py
+++ b/qutebrowser/mainwindow/statusbar/bar.py
@@ -272,6 +272,8 @@ class StatusBar(QWidget):
self.show()
elif strategy == 'always':
self.show()
+ else:
+ raise utils.Unreachable
def _set_hbox_padding(self):
padding = config.val.statusbar.padding
From 66b3cdb225e1b605dd391f8cb38510ea82a43044 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 9 Jun 2020 15:11:48 +0200
Subject: [PATCH 158/245] Simplify configdata.yml
---
qutebrowser/config/configdata.yml | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 42a73095e..a17df8fb5 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -3389,15 +3389,13 @@ bindings.commands:
logging.level.ram:
default: debug
- type:
- name: LogLevel
+ type: LogLevel
desc:
Level for in-memory logs.
logging.level.console:
default: info
- type:
- name: LogLevel
+ type: LogLevel
desc: >-
Level for console (stdout/stderr) logs.
Ignored if the `--loglevel` or `--debug` CLI flags are used.
From 26cdc87226f19ca26d44c402450bd4313afdd4b6 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 9 Jun 2020 15:14:08 +0200
Subject: [PATCH 159/245] Update docs
---
doc/changelog.asciidoc | 7 +++++++
doc/help/settings.asciidoc | 37 +++++++++++++++++++++++++++++++++++++
doc/qutebrowser.1.asciidoc | 2 +-
3 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 33b69121e..7bccf5d8e 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -24,6 +24,13 @@ Changed
- The `tor_identity` userscript now takes the password via a `-p` flag and has
a new `-c` flag to customize the Tor control port.
+Added
+~~~~~
+
+- New settings:
+ * `logging.level.ram` and `logging.level.console` to configure the default
+ logging levels via the config.
+
Fixed
~~~~~
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index cc020ea88..72e39bb9c 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -257,6 +257,8 @@
|<>|Keychains that shouldn't be shown in the keyhint dialog.
|<>|Time (in milliseconds) from pressing a key to seeing the keyhint dialog.
|<>|Rounding radius (in pixels) for the edges of the keyhint dialog.
+|<>|Level for console (stdout/stderr) logs. Ignored if the `--loglevel` or `--debug` CLI flags are used.
+|<>|Level for in-memory logs.
|<>|Duration (in milliseconds) to show messages in the statusbar for.
|<>|How to open links in an existing instance if a new one is launched.
|<>|Which window to choose when opening links as new tabs.
@@ -3248,6 +3250,40 @@ Type: <>
Default: +pass:[6]+
+[[logging.level.console]]
+=== logging.level.console
+Level for console (stdout/stderr) logs. Ignored if the `--loglevel` or `--debug` CLI flags are used.
+
+Type: <>
+
+Valid values:
+
+ * +vdebug+
+ * +debug+
+ * +info+
+ * +warning+
+ * +error+
+ * +critical+
+
+Default: +pass:[info]+
+
+[[logging.level.ram]]
+=== logging.level.ram
+Level for in-memory logs.
+
+Type: <>
+
+Valid values:
+
+ * +vdebug+
+ * +debug+
+ * +info+
+ * +warning+
+ * +error+
+ * +critical+
+
+Default: +pass:[debug]+
+
[[messages.timeout]]
=== messages.timeout
Duration (in milliseconds) to show messages in the statusbar for.
@@ -4170,6 +4206,7 @@ Lists with duplicate flags are invalid. Each item is checked against the valid v
When setting from a string, pass a json-like list, e.g. `["one", "two"]`.
|ListOrValue|A list of values, or a single value.
+|LogLevel|A logging level.
|NewTabPosition|How new tabs are positioned.
|Padding|Setting for paddings around elements.
|Perc|A percentage.
diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc
index 52ed64d3e..8dae3eaef 100644
--- a/doc/qutebrowser.1.asciidoc
+++ b/doc/qutebrowser.1.asciidoc
@@ -67,7 +67,7 @@ show it.
=== debug arguments
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
- Set loglevel
+ Override the configured console loglevel
*--logfilter* 'LOGFILTER'::
Comma-separated list of things to be logged to the debug log on stdout.
From ca14a91741f04fbbd1c40263dab256ed15a030d2 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Tue, 9 Jun 2020 14:52:15 +0100
Subject: [PATCH 160/245] Change in_mode to in-mode everywhere
---
doc/help/settings.asciidoc | 2 +-
qutebrowser/mainwindow/statusbar/bar.py | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 82588c9a0..718377c4f 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -3576,7 +3576,7 @@ Valid values:
* +always+: Always show the statusbar.
* +never+: Always hide the statusbar unless a message is shown.
- * +in_mode+: Only show the statusbar when in modes other than normal mode or when a message is shown.
+ * +in-mode+: Only show the statusbar when in modes other than normal mode or when a message is shown.
Default: +pass:[always]+
diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py
index 2735ad073..f83c77db9 100644
--- a/qutebrowser/mainwindow/statusbar/bar.py
+++ b/qutebrowser/mainwindow/statusbar/bar.py
@@ -260,7 +260,7 @@ class StatusBar(QWidget):
self.hide()
elif strategy == 'never':
self.hide()
- elif strategy == 'in_mode':
+ elif strategy == 'in-mode':
try:
mode_manager = modeman.instance(self._win_id)
except modeman.UnavailableError:
@@ -350,7 +350,7 @@ class StatusBar(QWidget):
def on_mode_entered(self, mode):
"""Mark certain modes in the commandline."""
mode_manager = modeman.instance(self._win_id)
- if config.val.statusbar.show == 'in_mode':
+ if config.val.statusbar.show == 'in-mode':
self.show()
if mode_manager.parsers[mode].passthrough:
self._set_mode_text(mode.name)
@@ -366,7 +366,7 @@ class StatusBar(QWidget):
def on_mode_left(self, old_mode, new_mode):
"""Clear marked mode."""
mode_manager = modeman.instance(self._win_id)
- if config.val.statusbar.show == 'in_mode':
+ if config.val.statusbar.show == 'in-mode':
self.hide()
if mode_manager.parsers[old_mode].passthrough:
if mode_manager.parsers[new_mode].passthrough:
From 5e74b62c67620c99150e37c7f61072ba9afb4ae7 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 9 Jun 2020 17:19:06 +0200
Subject: [PATCH 161/245] Fix ~ with :config-write-py
df4a011d48fa9f12fccf59ebca0954ccdf646d20 introduced a bug because the
os.path.isabs check was done before os.path.expanduser - and isabs('~/...')
will always be False.
---
qutebrowser/config/configcommands.py | 2 +-
tests/unit/config/test_configcommands.py | 13 +++++++++++++
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/qutebrowser/config/configcommands.py b/qutebrowser/config/configcommands.py
index 25b49055c..20702be10 100644
--- a/qutebrowser/config/configcommands.py
+++ b/qutebrowser/config/configcommands.py
@@ -460,9 +460,9 @@ class ConfigCommands:
if filename is None:
filename = standarddir.config_py()
else:
+ filename = os.path.expanduser(filename)
if not os.path.isabs(filename):
filename = os.path.join(standarddir.config(), filename)
- filename = os.path.expanduser(filename)
if os.path.exists(filename) and not force:
raise cmdutils.CommandError("{} already exists - use --force to "
diff --git a/tests/unit/config/test_configcommands.py b/tests/unit/config/test_configcommands.py
index b793a49ce..5718f6dc9 100644
--- a/tests/unit/config/test_configcommands.py
+++ b/tests/unit/config/test_configcommands.py
@@ -635,6 +635,19 @@ class TestWritePy:
lines = confpy.read_text('utf-8').splitlines()
assert '# Autogenerated config.py' in lines
+ @pytest.mark.posix
+ def test_expanduser(self, commands, monkeypatch, tmpdir):
+ """Make sure that using a path with ~/... works correctly."""
+ home = tmpdir / 'home'
+ home.ensure(dir=True)
+ monkeypatch.setenv('HOME', str(home))
+
+ commands.config_write_py('~/config.py')
+
+ confpy = home / 'config.py'
+ lines = confpy.read_text('utf-8').splitlines()
+ assert '# Autogenerated config.py' in lines
+
def test_existing_file(self, commands, tmpdir):
confpy = tmpdir / 'config.py'
confpy.ensure()
From 3cd86f30fbf5c8ebeedb7ef3c3a8258ad49de8db Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 9 Jun 2020 17:32:02 +0200
Subject: [PATCH 162/245] Add note to generated config.py
---
qutebrowser/config/configfiles.py | 11 +++++++++++
tests/unit/config/test_configfiles.py | 22 +++++++++++-----------
2 files changed, 22 insertions(+), 11 deletions(-)
diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py
index 7d567ebd3..3e7040b8c 100644
--- a/qutebrowser/config/configfiles.py
+++ b/qutebrowser/config/configfiles.py
@@ -603,6 +603,17 @@ class ConfigPyWriter:
def _gen_header(self) -> typing.Iterator[str]:
"""Generate the initial header of the config."""
yield self._line("# Autogenerated config.py")
+ yield self._line("#")
+
+ note = ("NOTE: config.py is intended for advanced users who are "
+ "comfortable with manually migrating the config file on "
+ "qutebrowser upgrades. If you prefer, you can also configure "
+ "qutebrowser using the :set/:bind/:config-* commands without "
+ "having to write a config.py file.")
+ for line in textwrap.wrap(note):
+ yield self._line("# {}".format(line))
+
+ yield self._line("#")
yield self._line("# Documentation:")
yield self._line("# qute://help/configuring.html")
yield self._line("# qute://help/settings.html")
diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py
index 4c0b6305c..2fce0bfb4 100644
--- a/tests/unit/config/test_configfiles.py
+++ b/tests/unit/config/test_configfiles.py
@@ -1052,6 +1052,13 @@ class TestConfigPyWriter:
assert text == textwrap.dedent("""
# Autogenerated config.py
+ #
+ # NOTE: config.py is intended for advanced users who are comfortable
+ # with manually migrating the config file on qutebrowser upgrades. If
+ # you prefer, you can also configure qutebrowser using the
+ # :set/:bind/:config-* commands without having to write a config.py
+ # file.
+ #
# Documentation:
# qute://help/configuring.html
# qute://help/settings.html
@@ -1156,17 +1163,10 @@ class TestConfigPyWriter:
def test_empty(self):
writer = configfiles.ConfigPyWriter(options=[], bindings={},
commented=False)
- text = '\n'.join(writer._gen_lines())
- expected = textwrap.dedent("""
- # Autogenerated config.py
- # Documentation:
- # qute://help/configuring.html
- # qute://help/settings.html
-
- # Uncomment this to still load settings configured via autoconfig.yml
- # config.load_autoconfig()
- """).lstrip()
- assert text == expected
+ lines = list(writer._gen_lines())
+ assert lines[0] == '# Autogenerated config.py'
+ assert lines[-2] == '# config.load_autoconfig()'
+ assert not lines[-1]
def test_pattern(self):
opt = configdata.Option(
From 5852b5dbc9bcb95615fb26667a8f52607585ebe9 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 9 Jun 2020 17:20:12 +0200
Subject: [PATCH 163/245] Update changelog
---
doc/changelog.asciidoc | 3 +++
1 file changed, 3 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 7bccf5d8e..daed2fda7 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -23,6 +23,8 @@ Changed
- The `tor_identity` userscript now takes the password via a `-p` flag and has
a new `-c` flag to customize the Tor control port.
+- `:config-write-py` now adds a note about `config.py` files being targeted at
+ advanced users.
Added
~~~~~
@@ -36,6 +38,7 @@ Fixed
- Crash when `tabs.focus_stack_size` is set to -1.
- Crash when a `pdf.js` file for PDF.js exists, but `viewer.html` does not.
+- `:config-write-py` now works with paths starting with `~/...` again.
- New site-specific quirk for a missing `globalThis` in Qt <= 5.12 on Reddit and Spotify
v1.12.0 (2020-06-01)
From ea2271c2945063d30c4a5f48893f322e4fd3fef4 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 9 Jun 2020 18:59:58 +0200
Subject: [PATCH 164/245] Add arguments for :report
---
doc/changelog.asciidoc | 2 ++
doc/help/commands.asciidoc | 7 +++++++
qutebrowser/misc/crashdialog.py | 18 ++++++++++++------
qutebrowser/misc/crashsignal.py | 17 ++++++++++++++---
4 files changed, 35 insertions(+), 9 deletions(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index daed2fda7..f1f5e0dea 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -25,6 +25,8 @@ Changed
a new `-c` flag to customize the Tor control port.
- `:config-write-py` now adds a note about `config.py` files being targeted at
advanced users.
+- `:report` now takes two optional arguments for bug/contact information, so
+ that it can be used without the report window popping up.
Added
~~~~~
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index dc40c76a6..9b2cae6c6 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -1015,8 +1015,15 @@ Which count to pass the command.
[[report]]
=== report
+Syntax: +:report ['info'] ['contact']+
+
Report a bug in qutebrowser.
+==== positional arguments
+* +'info'+: Information about the bug report. If given, no report dialog shows up.
+
+* +'contact'+: Contact information for the report.
+
[[restart]]
=== restart
Restart qutebrowser while keeping existing tabs open.
diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py
index 0f6d02712..4387e479a 100644
--- a/qutebrowser/misc/crashdialog.py
+++ b/qutebrowser/misc/crashdialog.py
@@ -125,9 +125,13 @@ class _CrashDialog(QDialog):
self.setWindowTitle("Whoops!")
self.resize(QSize(640, 600))
self._vbox = QVBoxLayout(self)
+
http_client = httpclient.HTTPClient()
self._paste_client = pastebin.PastebinClient(http_client, self)
self._pypi_client = autoupdate.PyPIVersionClient(self)
+ self._paste_client.success.connect(self.on_paste_success)
+ self._paste_client.error.connect(self.show_error)
+
self._init_text()
self._init_contact_input()
@@ -296,13 +300,17 @@ class _CrashDialog(QDialog):
except Exception:
log.misc.exception("Failed to save contact information!")
- def report(self):
- """Paste the crash info into the pastebin."""
+ def report(self, *, info=None, contact=None):
+ """Paste the crash info into the pastebin.
+
+ If info/contact are given as arguments, they override the values
+ entered in the dialog.
+ """
lines = []
lines.append("========== Report ==========")
- lines.append(self._info.toPlainText())
+ lines.append(info or self._info.toPlainText())
lines.append("========== Contact ==========")
- lines.append(self._contact.toPlainText())
+ lines.append(contact or self._contact.toPlainText())
lines.append("========== Debug log ==========")
lines.append(self._debug_log.toPlainText())
self._paste_text = '\n\n'.join(lines)
@@ -326,8 +334,6 @@ class _CrashDialog(QDialog):
self._btn_report.setEnabled(False)
self._btn_cancel.setEnabled(False)
self._btn_report.setText("Reporting...")
- self._paste_client.success.connect(self.on_paste_success)
- self._paste_client.error.connect(self.show_error)
self.report()
@pyqtSlot()
diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py
index 5d8bd0ff5..3f80db769 100644
--- a/qutebrowser/misc/crashsignal.py
+++ b/qutebrowser/misc/crashsignal.py
@@ -162,14 +162,25 @@ class CrashHandler(QObject):
earlyinit.init_faulthandler(self._crash_log_file)
@cmdutils.register(instance='crash-handler')
- def report(self):
- """Report a bug in qutebrowser."""
+ def report(self, info=None, contact=None):
+ """Report a bug in qutebrowser.
+
+ Args:
+ info: Information about the bug report. If given, no report dialog
+ shows up.
+ contact: Contact information for the report.
+ """
pages = self._recover_pages()
cmd_history = objreg.get('command-history')[-5:]
all_objects = debug.get_all_objects()
+
self._crash_dialog = crashdialog.ReportDialog(pages, cmd_history,
all_objects)
- self._crash_dialog.show()
+
+ if info is None:
+ self._crash_dialog.show()
+ else:
+ self._crash_dialog.report(info=info, contact=contact)
@pyqtSlot()
def shutdown(self):
From 2535d6dc7aa06ca0032716f787771c40431a9ad4 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Tue, 9 Jun 2020 18:29:06 +0100
Subject: [PATCH 165/245] Add ability to customise tab font based on selected
status
Also, use Qt Style Sheets to set the tab bar font instead of
setFont(QFont). This makes the fonts.tabs.* settings consistent with all
the other font settings (using Font instead of QFont).
---
doc/help/settings.asciidoc | 19 ++++++++++++++-----
qutebrowser/config/configdata.yml | 11 ++++++++---
qutebrowser/config/configfiles.py | 16 ++++++++++++++++
qutebrowser/mainwindow/tabwidget.py | 28 +++++++++++++++-------------
4 files changed, 53 insertions(+), 21 deletions(-)
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index cc020ea88..648168a60 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -214,7 +214,8 @@
|<>|Font used for warning messages.
|<>|Font used for prompts.
|<>|Font used in the statusbar.
-|<>|Font used in the tab bar.
+|<>|Font used in the tab bar for selected tabs.
+|<>|Font used in the tab bar for unselected tabs.
|<>|Font family for cursive fonts.
|<>|Font family for fantasy fonts.
|<>|Font family for fixed fonts.
@@ -2785,11 +2786,19 @@ Type: <>
Default: +pass:[default_size default_family]+
-[[fonts.tabs]]
-=== fonts.tabs
-Font used in the tab bar.
+[[fonts.tabs.selected]]
+=== fonts.tabs.selected
+Font used in the tab bar for selected tabs.
-Type: <>
+Type: <>
+
+Default: +pass:[default_size default_family]+
+
+[[fonts.tabs.unselected]]
+=== fonts.tabs.unselected
+Font used in the tab bar for unselected tabs.
+
+Type: <>
Default: +pass:[default_size default_family]+
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 664f9facf..68b17fdde 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -2872,10 +2872,15 @@ fonts.statusbar:
type: Font
desc: Font used in the statusbar.
-fonts.tabs:
+fonts.tabs.selected:
default: default_size default_family
- type: QtFont
- desc: Font used in the tab bar.
+ type: Font
+ desc: Font used in the tab bar for selected tabs.
+
+fonts.tabs.unselected:
+ default: default_size default_family
+ type: Font
+ desc: Font used in the tab bar for unselected tabs.
fonts.web.family.standard:
default: ''
diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py
index 7d567ebd3..da203cc84 100644
--- a/qutebrowser/config/configfiles.py
+++ b/qutebrowser/config/configfiles.py
@@ -340,6 +340,10 @@ class YamlMigrations(QObject):
r'(? None:
+ if old_name not in self._settings:
+ return
+
+ for new_name in new_names:
+ self._settings[new_name] = {}
+ for scope, val in self._settings[old_name].items():
+ self._settings[new_name][scope] = val
+
+ del self._settings[old_name]
+ self.changed.emit()
+
def _migrate_string_value(self, name: str,
source: str,
target: str) -> None:
diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py
index 558713fdf..006bd6cca 100644
--- a/qutebrowser/mainwindow/tabwidget.py
+++ b/qutebrowser/mainwindow/tabwidget.py
@@ -385,8 +385,13 @@ class TabBar(QTabBar):
STYLESHEET = """
TabBar {
+ font: {{ conf.fonts.tabs.unselected }};
background-color: {{ conf.colors.tabs.bar.bg }};
}
+
+ TabBar::tab:selected {
+ font: {{ conf.fonts.tabs.selected }};
+ }
"""
new_tab_requested = pyqtSignal()
@@ -395,8 +400,6 @@ class TabBar(QTabBar):
super().__init__(parent)
self._win_id = win_id
self.setStyle(TabBarStyle())
- self._set_font()
- config.instance.changed.connect(self._on_config_changed)
self.vertical = False
self._auto_hide_timer = QTimer()
self._auto_hide_timer.setSingleShot(True)
@@ -405,6 +408,9 @@ class TabBar(QTabBar):
self.setAutoFillBackground(True)
self.drag_in_progress = False
stylesheet.set_register(self)
+ self.ensurePolished()
+ config.instance.changed.connect(self._on_config_changed)
+ self._set_icon_size()
QTimer.singleShot(0, self.maybe_hide)
def __repr__(self):
@@ -416,8 +422,10 @@ class TabBar(QTabBar):
@pyqtSlot(str)
def _on_config_changed(self, option: str) -> None:
- if option == 'fonts.tabs':
- self._set_font()
+ if option.startswith('fonts.tabs.'):
+ self.update()
+ self.ensurePolished()
+ self._set_icon_size()
elif option == 'tabs.favicons.scale':
self._set_icon_size()
elif option == 'tabs.show_switching_delay':
@@ -433,7 +441,9 @@ class TabBar(QTabBar):
"tabs.padding",
"tabs.indicator.width",
"tabs.min_width",
- "tabs.pinned.shrink"]:
+ "tabs.pinned.shrink",
+ "fonts.tabs.selected",
+ "fonts.tabs.unselected"]:
self._minimum_tab_size_hint_helper.cache_clear()
self._minimum_tab_height.cache_clear()
@@ -506,14 +516,6 @@ class TabBar(QTabBar):
# code sets layoutDirty so it actually relayouts the tabs.
self.setIconSize(self.iconSize())
- def _set_font(self):
- """Set the tab bar font."""
- self.setFont(config.val.fonts.tabs)
- self._set_icon_size()
- # clear tab size cache
- self._minimum_tab_size_hint_helper.cache_clear()
- self._minimum_tab_height.cache_clear()
-
def _set_icon_size(self):
"""Set the tab bar favicon size."""
size = self.fontMetrics().height() - 2
From 9c9ab7015efd50ec9aafeb4612cc13b2e63dbe45 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Tue, 9 Jun 2020 19:50:48 +0100
Subject: [PATCH 166/245] Add test for _migrate_multiple
---
tests/unit/config/test_configfiles.py | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py
index 4c0b6305c..953ab60b5 100644
--- a/tests/unit/config/test_configfiles.py
+++ b/tests/unit/config/test_configfiles.py
@@ -588,6 +588,17 @@ class TestYamlMigrations:
def test_font_replacements(self, migration_test, setting, old, new):
migration_test(setting, old, new)
+ def test_migrate_multiple(self, yaml, autoconfig):
+ val = '10pt default_family'
+ autoconfig.write({'fonts.tabs': {'global': val}})
+
+ yaml.load()
+ yaml._save()
+
+ data = autoconfig.read()
+ assert data['fonts.tabs.unselected']['global'] == val
+ assert data['fonts.tabs.selected']['global'] == val
+
class ConfPy:
From 84b6d224ffd68c9f368a910e5b83c16de4596d69 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Tue, 9 Jun 2020 19:51:40 +0100
Subject: [PATCH 167/245] Remove unnecessary call to update()
Also, split a line that pylint complains is too long.
---
qutebrowser/config/configfiles.py | 3 ++-
qutebrowser/mainwindow/tabwidget.py | 1 -
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py
index da203cc84..c07ad04e9 100644
--- a/qutebrowser/config/configfiles.py
+++ b/qutebrowser/config/configfiles.py
@@ -450,7 +450,8 @@ class YamlMigrations(QObject):
self._settings[name][scope] = value
self.changed.emit()
- def _migrate_to_multiple(self, old_name: str, new_names: typing.Iterator[str]) -> None:
+ def _migrate_to_multiple(self, old_name: str,
+ new_names: typing.Iterator[str]) -> None:
if old_name not in self._settings:
return
diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py
index 006bd6cca..60006fa14 100644
--- a/qutebrowser/mainwindow/tabwidget.py
+++ b/qutebrowser/mainwindow/tabwidget.py
@@ -423,7 +423,6 @@ class TabBar(QTabBar):
@pyqtSlot(str)
def _on_config_changed(self, option: str) -> None:
if option.startswith('fonts.tabs.'):
- self.update()
self.ensurePolished()
self._set_icon_size()
elif option == 'tabs.favicons.scale':
From 4f96bb0f9530a78fb3f49614524b4d4064c28ec1 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Wed, 10 Jun 2020 13:36:47 +0100
Subject: [PATCH 168/245] Update qutebrowser/config/configdata.yml
Co-authored-by: Marcel Schilling
---
qutebrowser/config/configdata.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 68b17fdde..12b4cedc1 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -2875,12 +2875,12 @@ fonts.statusbar:
fonts.tabs.selected:
default: default_size default_family
type: Font
- desc: Font used in the tab bar for selected tabs.
+ desc: Font used for selected tabs.
fonts.tabs.unselected:
default: default_size default_family
type: Font
- desc: Font used in the tab bar for unselected tabs.
+ desc: Font used for unselected tabs.
fonts.web.family.standard:
default: ''
From a867cab03554401e772abba64ebb93c7608f1575 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 10 Jun 2020 14:56:56 +0200
Subject: [PATCH 169/245] Update docs
---
doc/changelog.asciidoc | 4 ++++
doc/help/settings.asciidoc | 8 ++++----
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index f1f5e0dea..e0a2bac8f 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -27,6 +27,8 @@ Changed
advanced users.
- `:report` now takes two optional arguments for bug/contact information, so
that it can be used without the report window popping up.
+- The `fonts.tabs` setting has been split into
+ `fonts.tabs.{selected,unselected}` (see below).
Added
~~~~~
@@ -34,6 +36,8 @@ Added
- New settings:
* `logging.level.ram` and `logging.level.console` to configure the default
logging levels via the config.
+ * `fonts.tabs.selected` and `fonts.tabs.unselected` to set the font of the
+ selected tab independently from unselected tabs (e.g. to make it bold).
Fixed
~~~~~
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 6cbaff03c..8f40c1444 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -214,8 +214,8 @@
|<>|Font used for warning messages.
|<>|Font used for prompts.
|<>|Font used in the statusbar.
-|<>|Font used in the tab bar for selected tabs.
-|<>|Font used in the tab bar for unselected tabs.
+|<>|Font used for selected tabs.
+|<>|Font used for unselected tabs.
|<>|Font family for cursive fonts.
|<>|Font family for fantasy fonts.
|<>|Font family for fixed fonts.
@@ -2790,7 +2790,7 @@ Default: +pass:[default_size default_family]+
[[fonts.tabs.selected]]
=== fonts.tabs.selected
-Font used in the tab bar for selected tabs.
+Font used for selected tabs.
Type: <>
@@ -2798,7 +2798,7 @@ Default: +pass:[default_size default_family]+
[[fonts.tabs.unselected]]
=== fonts.tabs.unselected
-Font used in the tab bar for unselected tabs.
+Font used for unselected tabs.
Type: <>
From c5257184c752c9fc3686e45d88fac0f53e11eb4c Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 10 Jun 2020 14:57:17 +0200
Subject: [PATCH 170/245] Rename test_migrate_multiple
---
tests/unit/config/test_configfiles.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py
index 0f087f37e..e877092fd 100644
--- a/tests/unit/config/test_configfiles.py
+++ b/tests/unit/config/test_configfiles.py
@@ -588,7 +588,7 @@ class TestYamlMigrations:
def test_font_replacements(self, migration_test, setting, old, new):
migration_test(setting, old, new)
- def test_migrate_multiple(self, yaml, autoconfig):
+ def test_fonts_tabs(self, yaml, autoconfig):
val = '10pt default_family'
autoconfig.write({'fonts.tabs': {'global': val}})
From 50a04d5412386604b7771fcfdb4b298e10bca078 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 10 Jun 2020 15:20:46 +0200
Subject: [PATCH 171/245] Update docs
---
doc/changelog.asciidoc | 8 ++++++--
doc/help/settings.asciidoc | 4 ++--
qutebrowser/config/configdata.yml | 6 ++----
3 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index e0a2bac8f..3a6a70ea4 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -27,8 +27,12 @@ Changed
advanced users.
- `:report` now takes two optional arguments for bug/contact information, so
that it can be used without the report window popping up.
-- The `fonts.tabs` setting has been split into
- `fonts.tabs.{selected,unselected}` (see below).
+- Changes to settings:
+ * `fonts.tabs` has been split into `fonts.tabs.{selected,unselected}` (see
+ below).
+ * `statusbar.hide` has been renamed to `statusbar.show` with the possible
+ values being `always` (`hide = False`), `never` (`hide = True`) or
+ `in-mode` (new, only show statusbar outside of normal mode.
Added
~~~~~
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 654fe4af0..f15dd6f58 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -3620,8 +3620,8 @@ Type: <>
Valid values:
* +always+: Always show the statusbar.
- * +never+: Always hide the statusbar unless a message is shown.
- * +in-mode+: Only show the statusbar when in modes other than normal mode or when a message is shown.
+ * +never+: Always hide the statusbar.
+ * +in-mode+: Show the statusbar when in modes other than normal mode.
Default: +pass:[always]+
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 963533a1b..62e14e06b 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -1565,10 +1565,8 @@ statusbar.show:
name: String
valid_values:
- always: Always show the statusbar.
- - never: Always hide the statusbar unless a message is shown.
- - in-mode: >-
- Only show the statusbar when in modes other than normal mode or
- when a message is shown.
+ - never: Always hide the statusbar.
+ - in-mode: Show the statusbar when in modes other than normal mode.
desc: When to show the statusbar.
statusbar.padding:
From df49a8f3e433afd47da6bf9b0ce1e132b6881bd2 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 10 Jun 2020 15:56:26 +0200
Subject: [PATCH 172/245] tests: Add testutils.change_cwd()
---
tests/helpers/utils.py | 12 ++++++++++++
tests/unit/misc/test_ipc.py | 16 +++++++---------
2 files changed, 19 insertions(+), 9 deletions(-)
diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py
index 25c3fb9a1..70a10436c 100644
--- a/tests/helpers/utils.py
+++ b/tests/helpers/utils.py
@@ -25,6 +25,7 @@ import gzip
import pprint
import os.path
import contextlib
+import pathlib
import pytest
@@ -186,6 +187,17 @@ def nop_contextmanager():
yield
+@contextlib.contextmanager
+def change_cwd(path):
+ """Use a path as current working directory."""
+ old_cwd = pathlib.Path.cwd()
+ os.chdir(str(path))
+ try:
+ yield
+ finally:
+ os.chdir(str(old_cwd))
+
+
@contextlib.contextmanager
def ignore_bs4_warning():
"""WORKAROUND for https://bugs.launchpad.net/beautifulsoup/+bug/1847592."""
diff --git a/tests/unit/misc/test_ipc.py b/tests/unit/misc/test_ipc.py
index 1da88d3bb..a598043b0 100644
--- a/tests/unit/misc/test_ipc.py
+++ b/tests/unit/misc/test_ipc.py
@@ -36,7 +36,7 @@ from PyQt5.QtTest import QSignalSpy
import qutebrowser
from qutebrowser.misc import ipc
from qutebrowser.utils import standarddir, utils
-from helpers import stubs
+from helpers import stubs, utils as testutils
pytestmark = pytest.mark.usefixtures('qapp')
@@ -513,14 +513,12 @@ class TestSendToRunningInstance:
timeout=5000) as blocker:
with qtbot.waitSignal(ipc_server.got_raw,
timeout=5000) as raw_blocker:
- old_cwd = pathlib.Path.cwd()
- os.chdir(str(tmp_path))
- if not has_cwd:
- m = mocker.patch('qutebrowser.misc.ipc.os')
- m.getcwd.side_effect = OSError
- sent = ipc.send_to_running_instance(
- 'qute-test', ['foo'], None)
- os.chdir(str(old_cwd))
+ with testutils.change_cwd(tmp_path):
+ if not has_cwd:
+ m = mocker.patch('qutebrowser.misc.ipc.os')
+ m.getcwd.side_effect = OSError
+ sent = ipc.send_to_running_instance(
+ 'qute-test', ['foo'], None)
assert sent
From 3900c359ec920fa54f4f12535bdd0678cf514664 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 10 Jun 2020 15:57:34 +0200
Subject: [PATCH 173/245] tests: Fix test_configinit after #5512
---
tests/unit/config/test_configinit.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/tests/unit/config/test_configinit.py b/tests/unit/config/test_configinit.py
index 2e149fa70..d25dd3596 100644
--- a/tests/unit/config/test_configinit.py
+++ b/tests/unit/config/test_configinit.py
@@ -344,12 +344,12 @@ class TestLateInit:
# fonts.default_family and font settings customized
# https://github.com/qutebrowser/qutebrowser/issues/3096
([('fonts.default_family', 'Comic Sans MS'),
- ('fonts.tabs', '12pt default_family'),
+ ('fonts.debug_console', '12pt default_family'),
('fonts.keyhint', '12pt default_family')], 12, 'Comic Sans MS'),
# as above, but with default_size
([('fonts.default_family', 'Comic Sans MS'),
('fonts.default_size', '23pt'),
- ('fonts.tabs', 'default_size default_family'),
+ ('fonts.debug_console', 'default_size default_family'),
('fonts.keyhint', 'default_size default_family')],
23, 'Comic Sans MS'),
])
@@ -382,7 +382,7 @@ class TestLateInit:
expected = '{}pt "{}"'.format(size, family)
assert config.instance.get('fonts.keyhint') == expected
# QtFont
- font = config.instance.get('fonts.tabs')
+ font = config.instance.get('fonts.debug_console')
assert font.pointSize() == size
assert font.family() == family
@@ -405,10 +405,10 @@ class TestLateInit:
assert 'fonts.keyhint' in changed_options # Font
assert config.instance.get('fonts.keyhint') == '23pt "Comic Sans MS"'
- assert 'fonts.tabs' in changed_options # QtFont
- tabs_font = config.instance.get('fonts.tabs')
- assert tabs_font.family() == 'Comic Sans MS'
- assert tabs_font.pointSize() == 23
+ assert 'fonts.debug_console' in changed_options # QtFont
+ debug_console_font = config.instance.get('fonts.debug_console')
+ assert debug_console_font.family() == 'Comic Sans MS'
+ assert debug_console_font.pointSize() == 23
# Font subclass, but doesn't end with "default_family"
assert 'fonts.web.family.standard' not in changed_options
From 1e413e814ce4fdb8318a63de5793e3af42333c49 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 10 Jun 2020 15:59:56 +0200
Subject: [PATCH 174/245] mypy: Fix type for _migrate_to_multiple
---
qutebrowser/config/configfiles.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py
index 635702b01..01f58e5d1 100644
--- a/qutebrowser/config/configfiles.py
+++ b/qutebrowser/config/configfiles.py
@@ -456,7 +456,7 @@ class YamlMigrations(QObject):
self.changed.emit()
def _migrate_to_multiple(self, old_name: str,
- new_names: typing.Iterator[str]) -> None:
+ new_names: typing.Iterable[str]) -> None:
if old_name not in self._settings:
return
From e7cdc03685a35f8e96435c4f76fd233dc98e589d Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 10 Jun 2020 16:13:45 +0200
Subject: [PATCH 175/245] tests: Cleanups after pathlib migration in
tests/unit/scripts/
---
tests/unit/scripts/test_check_coverage.py | 3 +--
tests/unit/scripts/test_run_vulture.py | 14 ++++++--------
2 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/tests/unit/scripts/test_check_coverage.py b/tests/unit/scripts/test_check_coverage.py
index a60c95525..cc16736f3 100644
--- a/tests/unit/scripts/test_check_coverage.py
+++ b/tests/unit/scripts/test_check_coverage.py
@@ -226,6 +226,5 @@ def _generate_files():
@pytest.mark.parametrize('filename', list(_generate_files()))
def test_files_exist(filename):
- basedir = (pathlib.Path(check_coverage.__file__).parent /
- os.pardir / os.pardir)
+ basedir = pathlib.Path(check_coverage.__file__).parents[2]
assert (basedir / filename).exists()
diff --git a/tests/unit/scripts/test_run_vulture.py b/tests/unit/scripts/test_run_vulture.py
index 0008f3147..217233aae 100644
--- a/tests/unit/scripts/test_run_vulture.py
+++ b/tests/unit/scripts/test_run_vulture.py
@@ -25,6 +25,8 @@ import textwrap
import pytest
+from tests.helpers import utils
+
try:
from scripts.dev import run_vulture
except ImportError:
@@ -51,14 +53,10 @@ class VultureDir:
def run(self):
"""Run vulture over all generated files and return the output."""
- #files = self._tmp_path.listdir()
- files = list(self._tmp_path.glob('*'))
- assert files
- old_cwd = pathlib.Path.cwd()
- os.chdir(str(self._tmp_path))
- return_value = run_vulture.run([e.name for e in files])
- os.chdir(str(old_cwd))
- return return_value
+ names = [p.name for p in self._tmp_path.glob('*')]
+ assert names
+ with utils.change_cwd(self._tmp_path):
+ return run_vulture.run(names)
def makepyfile(self, **kwargs):
"""Create a python file, similar to TestDir.makepyfile."""
From 846adef2113680a383b9395cdaf5a92ad984e308 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 10 Jun 2020 16:23:43 +0200
Subject: [PATCH 176/245] tests: Remove unused import
---
tests/unit/scripts/test_check_coverage.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/tests/unit/scripts/test_check_coverage.py b/tests/unit/scripts/test_check_coverage.py
index cc16736f3..d805eb184 100644
--- a/tests/unit/scripts/test_check_coverage.py
+++ b/tests/unit/scripts/test_check_coverage.py
@@ -18,7 +18,6 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see .
-import os
import sys
import pathlib
From abfd975ae98538200fa0d8a9d031383fcb6a3500 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 10 Jun 2020 17:01:59 +0200
Subject: [PATCH 177/245] Use debug logging for log config messages
See #5445
---
qutebrowser/utils/log.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 58311693f..d1f654d8f 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -542,18 +542,18 @@ def init_from_config(conf: 'configmodule.ConfigContainer') -> None:
"""
assert _args is not None
if _args.debug:
- init.info("--debug flag overrides log configs")
+ init.debug("--debug flag overrides log configs")
return
if ram_handler:
ramlevel = conf.logging.level.ram
- init.info("Configuring RAM loglevel to %s", ramlevel)
+ init.debug("Configuring RAM loglevel to %s", ramlevel)
ram_handler.setLevel(LOG_LEVELS[ramlevel.upper()])
if console_handler:
consolelevel = conf.logging.level.console
if _args.loglevel:
- init.info("--loglevel flag overrides logging.level.console")
+ init.debug("--loglevel flag overrides logging.level.console")
else:
- init.info("Configuring console loglevel to %s", consolelevel)
+ init.debug("Configuring console loglevel to %s", consolelevel)
console_handler.setLevel(LOG_LEVELS[consolelevel.upper()])
From 011dee7c7ae92f86f2e882eae2e2f961e447bde5 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Wed, 10 Jun 2020 16:08:05 +0100
Subject: [PATCH 178/245] Remove QtFont as a config type
---
doc/help/settings.asciidoc | 5 +-
qutebrowser/config/configdata.yml | 2 +-
qutebrowser/config/configtypes.py | 92 +--------------------------
qutebrowser/misc/consolewidget.py | 30 ++++-----
tests/unit/config/test_configfiles.py | 2 -
tests/unit/config/test_configinit.py | 10 ---
tests/unit/config/test_configtypes.py | 53 +++------------
7 files changed, 26 insertions(+), 168 deletions(-)
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index f15dd6f58..7642391e7 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -2700,7 +2700,7 @@ Default: empty
=== fonts.debug_console
Font used for the debugging console.
-Type: <>
+Type: <>
Default: +pass:[default_size default_family]+
@@ -4234,9 +4234,6 @@ A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#RRRGGGBBB`/
|QtColor|A color value.
A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#RRRGGGBBB`/`#RRRRGGGGBBBB` * An SVG color name as specified in http://www.w3.org/TR/SVG/types.html#ColorKeywords[the W3C specification]. * transparent (no color) * `rgb(r, g, b)` / `rgba(r, g, b, a)` (values 0-255 or percentages) * `hsv(h, s, v)` / `hsva(h, s, v, a)` (values 0-255, hue 0-359)
-|QtFont|A font family, with optional style/weight/size.
-
-* Style: `normal`/`italic`/`oblique` * Weight: `normal`, `bold`, `100`..`900` * Size: _number_ `px`/`pt`
|Regex|A regular expression.
When setting from `config.py`, both a string or a `re.compile(...)` object are valid.
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 62e14e06b..f709a8e28 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -2834,7 +2834,7 @@ fonts.contextmenu:
fonts.debug_console:
default: default_size default_family
- type: QtFont
+ type: Font
desc: Font used for the debugging console.
fonts.downloads:
diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py
index c8d100a7b..0f489acd5 100644
--- a/qutebrowser/config/configtypes.py
+++ b/qutebrowser/config/configtypes.py
@@ -1157,7 +1157,7 @@ class QssColor(BaseType):
class FontBase(BaseType):
- """Base class for Font/QtFont/FontFamily."""
+ """Base class for Font/FontFamily."""
# Gets set when the config is initialized.
default_family = None # type: str
@@ -1292,96 +1292,6 @@ class FontFamily(FontBase):
return value
-class QtFont(FontBase):
-
- """A Font which gets converted to a QFont."""
-
- __doc__ = Font.__doc__ # for src2asciidoc.py
-
- def _parse_families(self, family_str: str) -> configutils.FontFamilies:
- if family_str == 'default_family' and self.default_family is not None:
- family_str = self.default_family
-
- return configutils.FontFamilies.from_str(family_str)
-
- def _set_style(self, font: QFont, match: typing.Match) -> None:
- style = match.group('style')
- style_map = {
- 'normal': QFont.StyleNormal,
- 'italic': QFont.StyleItalic,
- 'oblique': QFont.StyleOblique,
- }
- if style:
- font.setStyle(style_map[style])
- else:
- font.setStyle(QFont.StyleNormal)
-
- def _set_weight(self, font: QFont, match: typing.Match) -> None:
- weight = match.group('weight')
- namedweight = match.group('namedweight')
- weight_map = {
- 'normal': QFont.Normal,
- 'bold': QFont.Bold,
- }
- if namedweight:
- font.setWeight(weight_map[namedweight])
- elif weight:
- # based on qcssparser.cpp:setFontWeightFromValue
- font.setWeight(min(int(weight) // 8, 99))
- else:
- font.setWeight(QFont.Normal)
-
- def _set_size(self, font: QFont, match: typing.Match) -> None:
- size = match.group('size')
- if size:
- if size == 'default_size':
- size = self.default_size
-
- if size is None:
- # initial validation before default_size is set up.
- pass
- elif size.lower().endswith('pt'):
- font.setPointSizeF(float(size[:-2]))
- elif size.lower().endswith('px'):
- font.setPixelSize(int(size[:-2]))
- else:
- # This should never happen as the regex only lets pt/px
- # through.
- raise ValueError("Unexpected size unit in {!r}!".format(
- size)) # pragma: no cover
-
- def _set_families(self, font: QFont, match: typing.Match) -> None:
- family_str = match.group('family')
- families = self._parse_families(family_str)
- if hasattr(font, 'setFamilies'):
- # Added in Qt 5.13
- font.setFamily(families.family) # type: ignore[arg-type]
- font.setFamilies(list(families))
- else: # pragma: no cover
- font.setFamily(families.to_str(quote=False))
-
- def to_py(self, value: _StrUnset) -> typing.Union[_UnsetNone, QFont]:
- self._basic_py_validation(value, str)
- if isinstance(value, usertypes.Unset):
- return value
- elif not value:
- return None
-
- match = self.font_regex.fullmatch(value)
- if not match: # pragma: no cover
- # This should never happen, as the regex always matches everything
- # as family.
- raise configexc.ValidationError(value, "must be a valid font")
-
- font = QFont()
- self._set_style(font, match)
- self._set_weight(font, match)
- self._set_size(font, match)
- self._set_families(font, match)
-
- return font
-
-
class Regex(BaseType):
"""A regular expression.
diff --git a/qutebrowser/misc/consolewidget.py b/qutebrowser/misc/consolewidget.py
index 7e46da3c8..4132244c9 100644
--- a/qutebrowser/misc/consolewidget.py
+++ b/qutebrowser/misc/consolewidget.py
@@ -27,7 +27,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
from PyQt5.QtWidgets import QTextEdit, QWidget, QVBoxLayout, QApplication
from PyQt5.QtGui import QTextCursor
-from qutebrowser.config import config
+from qutebrowser.config import config, stylesheet
from qutebrowser.misc import cmdhistory, miscwidgets
from qutebrowser.utils import utils, objreg
@@ -48,6 +48,12 @@ class ConsoleLineEdit(miscwidgets.CommandLineEdit):
execute = pyqtSignal(str)
+ STYLESHEET = """
+ ConsoleLineEdit {
+ font: {{ conf.fonts.debug_console }};
+ }
+ """
+
def __init__(self, _namespace, parent):
"""Constructor.
@@ -55,8 +61,7 @@ class ConsoleLineEdit(miscwidgets.CommandLineEdit):
_namespace: The local namespace of the interpreter.
"""
super().__init__(parent=parent)
- self._update_font()
- config.instance.changed.connect(self._update_font)
+ stylesheet.set_register(self)
self._history = cmdhistory.History(parent=self)
self.returnPressed.connect(self.on_return_pressed)
@@ -106,32 +111,27 @@ class ConsoleLineEdit(miscwidgets.CommandLineEdit):
else:
super().keyPressEvent(e)
- @config.change_filter('fonts.debug_console')
- def _update_font(self):
- """Set the correct font."""
- self.setFont(config.val.fonts.debug_console)
-
class ConsoleTextEdit(QTextEdit):
"""Custom QTextEdit for console output."""
+ STYLESHEET = """
+ ConsoleTextEdit {
+ font: {{ conf.fonts.debug_console }};
+ }
+ """
+
def __init__(self, parent=None):
super().__init__(parent)
self.setAcceptRichText(False)
self.setReadOnly(True)
- config.instance.changed.connect(self._update_font)
- self._update_font()
+ stylesheet.set_register(self)
self.setFocusPolicy(Qt.ClickFocus)
def __repr__(self):
return utils.get_repr(self)
- @config.change_filter('fonts.debug_console')
- def _update_font(self):
- """Update font when config changed."""
- self.setFont(config.val.fonts.debug_console)
-
def append_text(self, text):
"""Append new text and scroll output to bottom.
diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py
index e877092fd..3cd0c3339 100644
--- a/tests/unit/config/test_configfiles.py
+++ b/tests/unit/config/test_configfiles.py
@@ -578,8 +578,6 @@ class TestYamlMigrations:
@pytest.mark.parametrize('setting, old, new', [
# Font
('fonts.hints', '10pt monospace', '10pt default_family'),
- # QtFont
- ('fonts.debug_console', '10pt monospace', '10pt default_family'),
# String
('content.headers.accept_language', 'x monospace', 'x monospace'),
# Not at end of string
diff --git a/tests/unit/config/test_configinit.py b/tests/unit/config/test_configinit.py
index d25dd3596..6e95cadbe 100644
--- a/tests/unit/config/test_configinit.py
+++ b/tests/unit/config/test_configinit.py
@@ -344,12 +344,10 @@ class TestLateInit:
# fonts.default_family and font settings customized
# https://github.com/qutebrowser/qutebrowser/issues/3096
([('fonts.default_family', 'Comic Sans MS'),
- ('fonts.debug_console', '12pt default_family'),
('fonts.keyhint', '12pt default_family')], 12, 'Comic Sans MS'),
# as above, but with default_size
([('fonts.default_family', 'Comic Sans MS'),
('fonts.default_size', '23pt'),
- ('fonts.debug_console', 'default_size default_family'),
('fonts.keyhint', 'default_size default_family')],
23, 'Comic Sans MS'),
])
@@ -381,10 +379,6 @@ class TestLateInit:
# Font
expected = '{}pt "{}"'.format(size, family)
assert config.instance.get('fonts.keyhint') == expected
- # QtFont
- font = config.instance.get('fonts.debug_console')
- assert font.pointSize() == size
- assert font.family() == family
@pytest.fixture
def run_configinit(self, init_patch, fake_save_manager, args):
@@ -405,10 +399,6 @@ class TestLateInit:
assert 'fonts.keyhint' in changed_options # Font
assert config.instance.get('fonts.keyhint') == '23pt "Comic Sans MS"'
- assert 'fonts.debug_console' in changed_options # QtFont
- debug_console_font = config.instance.get('fonts.debug_console')
- assert debug_console_font.family() == 'Comic Sans MS'
- assert debug_console_font.pointSize() == 23
# Font subclass, but doesn't end with "default_family"
assert 'fonts.web.family.standard' not in changed_options
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
index 841892ef2..5b5d159a6 100644
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -255,8 +255,8 @@ class TestAll:
# For some types, we don't actually get the internal (YAML-like) value
# back from from_str(), so we can't convert it back.
- if klass in [configtypes.FuzzyUrl, configtypes.QtFont,
- configtypes.ShellCommand, configtypes.Url]:
+ if klass in [configtypes.FuzzyUrl, configtypes.ShellCommand,
+ configtypes.Url]:
return
converted = typ.to_str(val)
@@ -1381,7 +1381,7 @@ class FontDesc:
class TestFont:
- """Test Font/QtFont."""
+ """Test Font."""
TESTS = {
# (style, weight, pointsize, pixelsize, family
@@ -1430,7 +1430,7 @@ class TestFont:
font_xfail = pytest.mark.xfail(reason='FIXME: #103')
- @pytest.fixture(params=[configtypes.Font, configtypes.QtFont])
+ @pytest.fixture(params=[configtypes.Font])
def klass(self, request):
return request.param
@@ -1438,45 +1438,12 @@ class TestFont:
def font_class(self):
return configtypes.Font
- @pytest.fixture
- def qtfont_class(self):
- return configtypes.QtFont
-
@pytest.mark.parametrize('val, desc', sorted(TESTS.items()))
def test_to_py_valid(self, klass, val, desc):
- if klass is configtypes.Font:
- expected = val
- elif klass is configtypes.QtFont:
- expected = Font.fromdesc(desc)
+ assert klass is configtypes.Font
+ expected = val
assert klass().to_py(val) == expected
- def test_qtfont(self, qtfont_class):
- """Test QtFont's to_py."""
- value = Font(qtfont_class().to_py('10pt "Foobar Neue", Fubar'))
-
- if hasattr(value, 'families'):
- # Added in Qt 5.13
- assert value.family() == 'Foobar Neue'
- assert value.families() == ['Foobar Neue', 'Fubar']
- else:
- assert value.family() == 'Foobar Neue, Fubar'
-
- assert value.weight() == QFont.Normal
- assert value.style() == QFont.StyleNormal
-
- assert value.pointSize() == 10
-
- def test_qtfont_float(self, qtfont_class):
- """Test QtFont's to_py with a float as point size.
-
- We can't test the point size for equality as Qt seems to do some
- rounding as appropriate.
- """
- value = Font(qtfont_class().to_py('10.5pt Test'))
- assert value.family() == 'Test'
- assert value.pointSize() >= 10
- assert value.pointSize() <= 11
-
@pytest.mark.parametrize('val', [
pytest.param('green "Foobar Neue"', marks=font_xfail),
pytest.param('italic green "Foobar Neue"', marks=font_xfail),
@@ -1495,12 +1462,8 @@ class TestFont:
def test_defaults_replacement(self, klass, monkeypatch):
configtypes.FontBase.set_defaults(['Terminus'], '23pt')
- if klass is configtypes.Font:
- expected = '23pt Terminus'
- elif klass is configtypes.QtFont:
- desc = FontDesc(QFont.StyleNormal, QFont.Normal, 23, None,
- 'Terminus')
- expected = Font.fromdesc(desc)
+ assert klass is configtypes.Font
+ expected = '23pt Terminus'
assert klass().to_py('23pt default_family') == expected
From 946e52903e482fb26d54b50ca3e74f70e5017cdf Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Wed, 10 Jun 2020 16:29:48 +0100
Subject: [PATCH 179/245] Remove unneeded imports
---
qutebrowser/config/configtypes.py | 2 +-
qutebrowser/misc/consolewidget.py | 2 +-
tests/unit/scripts/test_run_vulture.py | 2 --
3 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py
index 0f489acd5..e798498fc 100644
--- a/qutebrowser/config/configtypes.py
+++ b/qutebrowser/config/configtypes.py
@@ -56,7 +56,7 @@ import typing
import attr
import yaml
from PyQt5.QtCore import QUrl, Qt
-from PyQt5.QtGui import QColor, QFont, QFontDatabase
+from PyQt5.QtGui import QColor, QFontDatabase
from PyQt5.QtWidgets import QTabWidget, QTabBar, QApplication
from PyQt5.QtNetwork import QNetworkProxy
diff --git a/qutebrowser/misc/consolewidget.py b/qutebrowser/misc/consolewidget.py
index 4132244c9..f4369274a 100644
--- a/qutebrowser/misc/consolewidget.py
+++ b/qutebrowser/misc/consolewidget.py
@@ -27,7 +27,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
from PyQt5.QtWidgets import QTextEdit, QWidget, QVBoxLayout, QApplication
from PyQt5.QtGui import QTextCursor
-from qutebrowser.config import config, stylesheet
+from qutebrowser.config import stylesheet
from qutebrowser.misc import cmdhistory, miscwidgets
from qutebrowser.utils import utils, objreg
diff --git a/tests/unit/scripts/test_run_vulture.py b/tests/unit/scripts/test_run_vulture.py
index 217233aae..edf3451cb 100644
--- a/tests/unit/scripts/test_run_vulture.py
+++ b/tests/unit/scripts/test_run_vulture.py
@@ -18,8 +18,6 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see .
-import os
-import pathlib
import sys
import textwrap
From 29887dfc607c94dcaad268f5b64de99e01a76afe Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Wed, 10 Jun 2020 16:29:59 +0100
Subject: [PATCH 180/245] Simplify some tests
---
tests/unit/config/test_configtypes.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
index 5b5d159a6..5e82333c9 100644
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -1430,9 +1430,9 @@ class TestFont:
font_xfail = pytest.mark.xfail(reason='FIXME: #103')
- @pytest.fixture(params=[configtypes.Font])
+ @pytest.fixture
def klass(self, request):
- return request.param
+ return configtypes.Font
@pytest.fixture
def font_class(self):
@@ -1440,7 +1440,6 @@ class TestFont:
@pytest.mark.parametrize('val, desc', sorted(TESTS.items()))
def test_to_py_valid(self, klass, val, desc):
- assert klass is configtypes.Font
expected = val
assert klass().to_py(val) == expected
@@ -1462,7 +1461,6 @@ class TestFont:
def test_defaults_replacement(self, klass, monkeypatch):
configtypes.FontBase.set_defaults(['Terminus'], '23pt')
- assert klass is configtypes.Font
expected = '23pt Terminus'
assert klass().to_py('23pt default_family') == expected
From 32c0bd1478e8f6b752861b869ef1fe405f3a356f Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Wed, 10 Jun 2020 17:33:09 +0100
Subject: [PATCH 181/245] Simplify test_configtypes.py
---
tests/unit/config/test_configtypes.py | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
index 5e82333c9..37a5580d0 100644
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -1381,8 +1381,6 @@ class FontDesc:
class TestFont:
- """Test Font."""
-
TESTS = {
# (style, weight, pointsize, pixelsize, family
'"Foobar Neue"':
@@ -1440,8 +1438,7 @@ class TestFont:
@pytest.mark.parametrize('val, desc', sorted(TESTS.items()))
def test_to_py_valid(self, klass, val, desc):
- expected = val
- assert klass().to_py(val) == expected
+ assert klass().to_py(val) == val
@pytest.mark.parametrize('val', [
pytest.param('green "Foobar Neue"', marks=font_xfail),
@@ -1461,8 +1458,7 @@ class TestFont:
def test_defaults_replacement(self, klass, monkeypatch):
configtypes.FontBase.set_defaults(['Terminus'], '23pt')
- expected = '23pt Terminus'
- assert klass().to_py('23pt default_family') == expected
+ assert klass().to_py('23pt default_family') == '23pt Terminus'
class TestFontFamily:
From 2af00b6bf2d0cbd867867dcadde4a815823d1acc Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Wed, 10 Jun 2020 17:39:57 +0100
Subject: [PATCH 182/245] Move stylesheet to ConsoleWidget to avoid duplication
---
qutebrowser/misc/consolewidget.py | 21 +++++++--------------
1 file changed, 7 insertions(+), 14 deletions(-)
diff --git a/qutebrowser/misc/consolewidget.py b/qutebrowser/misc/consolewidget.py
index f4369274a..aed42237a 100644
--- a/qutebrowser/misc/consolewidget.py
+++ b/qutebrowser/misc/consolewidget.py
@@ -48,12 +48,6 @@ class ConsoleLineEdit(miscwidgets.CommandLineEdit):
execute = pyqtSignal(str)
- STYLESHEET = """
- ConsoleLineEdit {
- font: {{ conf.fonts.debug_console }};
- }
- """
-
def __init__(self, _namespace, parent):
"""Constructor.
@@ -61,7 +55,6 @@ class ConsoleLineEdit(miscwidgets.CommandLineEdit):
_namespace: The local namespace of the interpreter.
"""
super().__init__(parent=parent)
- stylesheet.set_register(self)
self._history = cmdhistory.History(parent=self)
self.returnPressed.connect(self.on_return_pressed)
@@ -116,17 +109,10 @@ class ConsoleTextEdit(QTextEdit):
"""Custom QTextEdit for console output."""
- STYLESHEET = """
- ConsoleTextEdit {
- font: {{ conf.fonts.debug_console }};
- }
- """
-
def __init__(self, parent=None):
super().__init__(parent)
self.setAcceptRichText(False)
self.setReadOnly(True)
- stylesheet.set_register(self)
self.setFocusPolicy(Qt.ClickFocus)
def __repr__(self):
@@ -157,6 +143,12 @@ class ConsoleWidget(QWidget):
_interpreter: The InteractiveInterpreter to execute code with.
"""
+ STYLESHEET = """
+ ConsoleWidget > ConsoleTextEdit, ConsoleWidget > ConsoleLineEdit {
+ font: {{ conf.fonts.debug_console }};
+ }
+ """
+
def __init__(self, parent=None):
super().__init__(parent)
if not hasattr(sys, 'ps1'):
@@ -182,6 +174,7 @@ class ConsoleWidget(QWidget):
self._vbox.setSpacing(0)
self._vbox.addWidget(self._output)
self._vbox.addWidget(self._lineedit)
+ stylesheet.set_register(self)
self.setLayout(self._vbox)
self._lineedit.setFocus()
self._interpreter = code.InteractiveInterpreter(namespace)
From c40cb30855231dd0eb09e0a7c94b3f772ded87c7 Mon Sep 17 00:00:00 2001
From: Constantine Theocharis
Date: Wed, 10 Jun 2020 17:46:05 +0100
Subject: [PATCH 183/245] Remove unnecessary argument in TestFont.klass
---
tests/unit/config/test_configtypes.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
index 37a5580d0..8b0c4b191 100644
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -1429,7 +1429,7 @@ class TestFont:
font_xfail = pytest.mark.xfail(reason='FIXME: #103')
@pytest.fixture
- def klass(self, request):
+ def klass(self):
return configtypes.Font
@pytest.fixture
From 161b866b75544a6d615b97f7cea3614c2a9d3924 Mon Sep 17 00:00:00 2001
From: Pierguido Lambri
Date: Wed, 10 Jun 2020 18:31:55 +0100
Subject: [PATCH 184/245] Added instructions on how to install the Fedora
debuginfo packages
Signed-off-by: Pierguido Lambri
---
doc/stacktrace.asciidoc | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/doc/stacktrace.asciidoc b/doc/stacktrace.asciidoc
index f38c54940..a50a563b6 100644
--- a/doc/stacktrace.asciidoc
+++ b/doc/stacktrace.asciidoc
@@ -34,6 +34,27 @@ is available in the repositories:
# apt-get install python3-pyqt5-dbg python3-pyqt5.qtwebkit-dbg python3-dbg libqt5webkit5-dbg
----
+Fedora
+^^^^^^^^^^^^^^^^^
+
+For Fedora you first need to install the dnf/yum-utils:
+
+----
+# sudo dnf install dnf-utils
+----
+
+Or:
+
+----
+# sudo yum install yum-utils
+----
+
+Then install the needed debuginfo packages:
+
+----
+# sudo dnf install python3 qt5-qtwebkit python3-qt5-webkit python3-qt5-base python-qt5 python3-qt5
+----
+
Archlinux
^^^^^^^^^
From ed7d0c037cf85e900d8b56b646b31a29cc88fa38 Mon Sep 17 00:00:00 2001
From: Pierguido Lambri
Date: Wed, 10 Jun 2020 18:33:16 +0100
Subject: [PATCH 185/245] Fixed a wrong formatting
Signed-off-by: Pierguido Lambri
---
doc/stacktrace.asciidoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/stacktrace.asciidoc b/doc/stacktrace.asciidoc
index a50a563b6..d812cc26b 100644
--- a/doc/stacktrace.asciidoc
+++ b/doc/stacktrace.asciidoc
@@ -35,7 +35,7 @@ is available in the repositories:
----
Fedora
-^^^^^^^^^^^^^^^^^
+^^^^^^
For Fedora you first need to install the dnf/yum-utils:
From 09fab1e38356dc6d207bb4ad50d67fb210c31298 Mon Sep 17 00:00:00 2001
From: Pierguido Lambri
Date: Wed, 10 Jun 2020 18:44:40 +0100
Subject: [PATCH 186/245] And fixed with the right command
Signed-off-by: Pierguido Lambri
---
doc/stacktrace.asciidoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/stacktrace.asciidoc b/doc/stacktrace.asciidoc
index d812cc26b..97660c931 100644
--- a/doc/stacktrace.asciidoc
+++ b/doc/stacktrace.asciidoc
@@ -52,7 +52,7 @@ Or:
Then install the needed debuginfo packages:
----
-# sudo dnf install python3 qt5-qtwebkit python3-qt5-webkit python3-qt5-base python-qt5 python3-qt5
+# sudo debuginfo-install python3 qt5-qtwebkit python3-qt5-webkit python3-qt5-base python-qt5 python3-qt5
----
Archlinux
From 44e16ec2c66ecae36e7589ba4d2f731d21a92a8d Mon Sep 17 00:00:00 2001
From: Pierguido Lambri
Date: Wed, 10 Jun 2020 19:22:27 +0100
Subject: [PATCH 187/245] Removed the sudo command
Signed-off-by: Pierguido Lambri
---
doc/stacktrace.asciidoc | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/doc/stacktrace.asciidoc b/doc/stacktrace.asciidoc
index 97660c931..ef37c80b4 100644
--- a/doc/stacktrace.asciidoc
+++ b/doc/stacktrace.asciidoc
@@ -40,13 +40,13 @@ Fedora
For Fedora you first need to install the dnf/yum-utils:
----
-# sudo dnf install dnf-utils
+# dnf install dnf-utils
----
Or:
----
-# sudo yum install yum-utils
+# yum install yum-utils
----
Then install the needed debuginfo packages:
From 6f545d30481a28b99904068b6aa2773b12bdb9d0 Mon Sep 17 00:00:00 2001
From: Pierguido Lambri
Date: Wed, 10 Jun 2020 19:23:28 +0100
Subject: [PATCH 188/245] Fixed the debuginfo command, removed the sudo command
and installed the right debug symbol packages for qtwebengine
Signed-off-by: Pierguido Lambri
---
doc/stacktrace.asciidoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/stacktrace.asciidoc b/doc/stacktrace.asciidoc
index ef37c80b4..3bcca49bf 100644
--- a/doc/stacktrace.asciidoc
+++ b/doc/stacktrace.asciidoc
@@ -52,7 +52,7 @@ Or:
Then install the needed debuginfo packages:
----
-# sudo debuginfo-install python3 qt5-qtwebkit python3-qt5-webkit python3-qt5-base python-qt5 python3-qt5
+# debuginfo-install python3 qt5-qtwebengine python3-qt5-webengine python3-qt5-base python-qt5 python3-qt5 python3-qt5-webkit-debuginfo
----
Archlinux
From ec7dd49e16d37004fb8ce0fd433984b9e574097d Mon Sep 17 00:00:00 2001
From: Pierguido Lambri
Date: Wed, 10 Jun 2020 19:30:34 +0100
Subject: [PATCH 189/245] Removed the useless debuginfo suffix
Signed-off-by: Pierguido Lambri
---
doc/stacktrace.asciidoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/stacktrace.asciidoc b/doc/stacktrace.asciidoc
index 3bcca49bf..4dc327e0e 100644
--- a/doc/stacktrace.asciidoc
+++ b/doc/stacktrace.asciidoc
@@ -52,7 +52,7 @@ Or:
Then install the needed debuginfo packages:
----
-# debuginfo-install python3 qt5-qtwebengine python3-qt5-webengine python3-qt5-base python-qt5 python3-qt5 python3-qt5-webkit-debuginfo
+# debuginfo-install python3 qt5-qtwebengine python3-qt5-webengine python3-qt5-base python-qt5 python3-qt5 python3-qt5-webkit
----
Archlinux
From 8d80910373ecf2395729a5964f8e606897879e9a Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 10 Jun 2020 20:41:36 +0200
Subject: [PATCH 190/245] Update changelog
---
doc/changelog.asciidoc | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 3a6a70ea4..b7ebcb83f 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -33,6 +33,10 @@ Changed
* `statusbar.hide` has been renamed to `statusbar.show` with the possible
values being `always` (`hide = False`), `never` (`hide = True`) or
`in-mode` (new, only show statusbar outside of normal mode.
+ * The `QtFont` config type formerly used for `fonts.tabs` and
+ `fonts.debug_console` is now removed and entirely replaced by `Font`. The
+ former distinction was mainly an implementation detail, and the accepted
+ values shouldn't have changed.
Added
~~~~~
From 0af3a8a4675f876121db22b07a21f23e09d9048a Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Wed, 10 Jun 2020 21:30:13 +0200
Subject: [PATCH 191/245] Update docs
---
doc/changelog.asciidoc | 2 ++
doc/help/settings.asciidoc | 6 ++++++
2 files changed, 8 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index b7ebcb83f..f52f9b02a 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -27,6 +27,8 @@ Changed
advanced users.
- `:report` now takes two optional arguments for bug/contact information, so
that it can be used without the report window popping up.
+- New `t[Cc][Hh]` default bindings which work similarly to the `t[Ss][Hh]`
+ bindings for JavaScript but toggle cookie permissions.
- Changes to settings:
* `fonts.tabs` has been split into `fonts.tabs.{selected,unselected}` (see
below).
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 7642391e7..466b0ec1b 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -651,6 +651,9 @@ Default:
* +pass:[sk]+: +pass:[set-cmd-text -s :bind]+
* +pass:[sl]+: +pass:[set-cmd-text -s :set -t]+
* +pass:[ss]+: +pass:[set-cmd-text -s :set]+
+* +pass:[tCH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.cookies.accept all no-3rdparty never ;; reload]+
+* +pass:[tCh]+: +pass:[config-cycle -p -u *://{url:host}/* content.cookies.accept all no-3rdparty never ;; reload]+
+* +pass:[tCu]+: +pass:[config-cycle -p -u {url} content.cookies.accept all no-3rdparty never ;; reload]+
* +pass:[tIH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.images ;; reload]+
* +pass:[tIh]+: +pass:[config-cycle -p -u *://{url:host}/* content.images ;; reload]+
* +pass:[tIu]+: +pass:[config-cycle -p -u {url} content.images ;; reload]+
@@ -660,6 +663,9 @@ Default:
* +pass:[tSH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.javascript.enabled ;; reload]+
* +pass:[tSh]+: +pass:[config-cycle -p -u *://{url:host}/* content.javascript.enabled ;; reload]+
* +pass:[tSu]+: +pass:[config-cycle -p -u {url} content.javascript.enabled ;; reload]+
+* +pass:[tcH]+: +pass:[config-cycle -p -t -u *://*.{url:host}/* content.cookies.accept all no-3rdparty never ;; reload]+
+* +pass:[tch]+: +pass:[config-cycle -p -t -u *://{url:host}/* content.cookies.accept all no-3rdparty never ;; reload]+
+* +pass:[tcu]+: +pass:[config-cycle -p -t -u {url} content.cookies.accept all no-3rdparty never ;; reload]+
* +pass:[th]+: +pass:[back -t]+
* +pass:[tiH]+: +pass:[config-cycle -p -t -u *://*.{url:host}/* content.images ;; reload]+
* +pass:[tih]+: +pass:[config-cycle -p -t -u *://{url:host}/* content.images ;; reload]+
From fab5f5137353295906e63a28051f6770439962e7 Mon Sep 17 00:00:00 2001
From: Ryan Roden-Corrent
Date: Wed, 10 Jun 2020 22:37:54 -0400
Subject: [PATCH 192/245] Enable configuring loglevel at runtime.
Now `:set logging.level.{ram,console}` will work after qutebrowser has
started. This allows us to derecate debug-set-loglevel.
Logging is initialized too early for config.change_filter to work, so I
had to hack it.
---
qutebrowser/app.py | 9 +++++++++
qutebrowser/misc/utilcmds.py | 2 +-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index 2c164fb96..abb6dfa76 100644
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -373,6 +373,13 @@ def open_desktopservices_url(url):
tabbed_browser.tabopen(url)
+# This is effectively a @config.change_filter
+# Howerver, logging is initialized too early to use that annotation
+def _on_config_changed(name: str) -> None:
+ if name.startswith('logging.'):
+ log.init_from_config(config.val)
+
+
def _init_modules(*, args):
"""Initialize all 'modules' which need to be initialized.
@@ -381,6 +388,8 @@ def _init_modules(*, args):
"""
log.init.debug("Initializing logging from config...")
log.init_from_config(config.val)
+ config.instance.changed.connect(_on_config_changed)
+
log.init.debug("Initializing save manager...")
save_manager = savemanager.SaveManager(q_app)
objreg.register('save-manager', save_manager)
diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py
index fa8b97628..379bd400b 100644
--- a/qutebrowser/misc/utilcmds.py
+++ b/qutebrowser/misc/utilcmds.py
@@ -224,7 +224,7 @@ def log_capacity(capacity: int) -> None:
log.ram_handler.change_log_capacity(capacity)
-@cmdutils.register(debug=True)
+@cmdutils.register(debug=True, deprecated="Use `:set log.level.console`")
@cmdutils.argument('level', choices=sorted(
(level.lower() for level in log.LOG_LEVELS),
key=lambda e: log.LOG_LEVELS[e.upper()]))
From 23b5ee977714a56f64f2d9059dea050de504863d Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 11:37:51 +0200
Subject: [PATCH 193/245] Remove :debug-log-level
It's a hidden :debug-* command anyways, so no need to keep it around.
---
doc/help/commands.asciidoc | 10 ----------
qutebrowser/misc/utilcmds.py | 18 ------------------
tests/end2end/features/utilcmds.feature | 4 ----
tests/unit/misc/test_utilcmds.py | 11 -----------
4 files changed, 43 deletions(-)
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index 9b2cae6c6..1e870cbaa 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -1929,7 +1929,6 @@ These commands are mainly intended for debugging. They are hidden if qutebrowser
|<>|Show a keytester widget.
|<>|Change the number of log lines to be stored in RAM.
|<>|Change the log filter for console logging.
-|<>|Change the log level for console logging.
|<>|Evaluate a python string and display the results as a web page.
|<>|Put data into the fake clipboard and enable logging, used for tests.
|<>|Trace executed code via hunter.
@@ -2004,15 +2003,6 @@ Change the log filter for console logging.
* +'filters'+: A comma separated list of logger names. Can also be "none" to clear any existing filters.
-[[debug-log-level]]
-=== debug-log-level
-Syntax: +:debug-log-level 'level'+
-
-Change the log level for console logging.
-
-==== positional arguments
-* +'level'+: The log level to set.
-
[[debug-pyeval]]
=== debug-pyeval
Syntax: +:debug-pyeval [*--file*] [*--quiet*] 's'+
diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py
index 379bd400b..2a4b543f8 100644
--- a/qutebrowser/misc/utilcmds.py
+++ b/qutebrowser/misc/utilcmds.py
@@ -224,24 +224,6 @@ def log_capacity(capacity: int) -> None:
log.ram_handler.change_log_capacity(capacity)
-@cmdutils.register(debug=True, deprecated="Use `:set log.level.console`")
-@cmdutils.argument('level', choices=sorted(
- (level.lower() for level in log.LOG_LEVELS),
- key=lambda e: log.LOG_LEVELS[e.upper()]))
-def debug_log_level(level: str) -> None:
- """Change the log level for console logging.
-
- Args:
- level: The log level to set.
- """
- if log.console_handler is None:
- raise cmdutils.CommandError("No log.console_handler. Not attached "
- "to a console?")
-
- log.change_console_formatter(log.LOG_LEVELS[level.upper()])
- log.console_handler.setLevel(log.LOG_LEVELS[level.upper()])
-
-
@cmdutils.register(debug=True)
def debug_log_filter(filters: str) -> None:
"""Change the log filter for console logging.
diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature
index 9b4eb5760..bfe0035dd 100644
--- a/tests/end2end/features/utilcmds.feature
+++ b/tests/end2end/features/utilcmds.feature
@@ -166,10 +166,6 @@ Feature: Miscellaneous utility commands exposed to the user.
# Other :debug-log-{level,filter} features are tested in
# unit/utils/test_log.py as using them would break end2end tests.
- Scenario: Using debug-log-level with invalid level
- When I run :debug-log-level hello
- Then the error "level: Invalid value hello - expected one of: vdebug, debug, info, warning, error, critical" should be shown
-
Scenario: Using debug-log-filter with invalid filter
When I run :debug-log-filter blah
Then the error "filters: Invalid value blah - expected one of: statusbar, *" should be shown
diff --git a/tests/unit/misc/test_utilcmds.py b/tests/unit/misc/test_utilcmds.py
index 3066dcc7a..87e80afc1 100644
--- a/tests/unit/misc/test_utilcmds.py
+++ b/tests/unit/misc/test_utilcmds.py
@@ -42,17 +42,6 @@ def test_repeat_command_initial(mocker, mode_manager):
utilcmds.repeat_command(win_id=0)
-def test_debug_log_level(mocker):
- """Test interactive log level changing."""
- formatter_mock = mocker.patch(
- 'qutebrowser.misc.utilcmds.log.change_console_formatter')
- handler_mock = mocker.patch(
- 'qutebrowser.misc.utilcmds.log.console_handler')
- utilcmds.debug_log_level(level='debug')
- formatter_mock.assert_called_with(logging.DEBUG)
- handler_mock.setLevel.assert_called_with(logging.DEBUG)
-
-
class FakeWindow:
"""Mock class for window_only."""
From a9d10aee8403fdfcfc68d38a40312c7811d0dfe7 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 11:39:05 +0200
Subject: [PATCH 194/245] tests: Separate init_from_config logs in
utils.test_log
---
tests/unit/utils/test_log.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index 6b770ed44..3e3243e48 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -255,14 +255,16 @@ class TestInitLog:
with pytest.raises(PendingDeprecationWarning):
warnings.warn("test warning", PendingDeprecationWarning)
+
+class TestInitFromConfig:
+
@pytest.mark.parametrize('cli, conf, expected', [
(None, 'info', logging.INFO),
(None, 'warning', logging.WARNING),
('info', 'warning', logging.INFO),
('warning', 'info', logging.WARNING),
])
- def test_init_from_config_console(self, cli, conf, expected, args,
- config_stub):
+ def test_console(self, cli, conf, expected, args, config_stub):
args.debug = False
args.loglevel = cli
log.init_log(args)
@@ -277,7 +279,7 @@ class TestInitLog:
('info', logging.INFO),
('critical', logging.CRITICAL),
])
- def test_init_from_config_ram(self, conf, expected, args, config_stub):
+ def test_ram(self, conf, expected, args, config_stub):
args.debug = False
log.init_log(args)
@@ -285,7 +287,7 @@ class TestInitLog:
log.init_from_config(config_stub.val)
assert log.ram_handler.level == expected
- def test_init_from_config_consistent_default(self, config_stub):
+ def test_consistent_default(self, config_stub):
"""Ensure config defaults are consistent with the builtin defaults."""
args = qutebrowser.get_argparser().parse_args([])
log.init_log(args)
From 49b0c73fdd4a5410a49ec97d46ff2686ab65549c Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 11:43:10 +0200
Subject: [PATCH 195/245] Update changelog
---
doc/changelog.asciidoc | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index f52f9b02a..8a77a3fd5 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -18,6 +18,12 @@ breaking changes (such as renamed commands) can happen in minor releases.
v1.13.0 (unreleased)
--------------------
+Removed
+~~~~~~~
+
+- The `:debug-log-level` command was removed as it's replaced by the new
+ `logging.level.console` setting.
+
Changed
~~~~~~~
From fe9bde1b1d737a0aa9cbc9ad4346e9a491e31a42 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 11:56:22 +0200
Subject: [PATCH 196/245] Switch some webview/misc logging messages to network
logger
---
qutebrowser/browser/shared.py | 4 ++--
qutebrowser/browser/webengine/interceptor.py | 8 ++++----
qutebrowser/browser/webengine/webenginequtescheme.py | 12 ++++++------
qutebrowser/browser/webengine/webenginetab.py | 6 +++---
qutebrowser/browser/webkit/network/networkmanager.py | 6 +++---
.../browser/webkit/network/webkitqutescheme.py | 2 +-
qutebrowser/components/adblock.py | 2 +-
7 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py
index 4fbede419..a689e287a 100644
--- a/qutebrowser/browser/shared.py
+++ b/qutebrowser/browser/shared.py
@@ -160,7 +160,7 @@ def ignore_certificate_errors(url, errors, abort_on):
True if the error should be ignored, False otherwise.
"""
ssl_strict = config.instance.get('content.ssl_strict', url=url)
- log.webview.debug("Certificate errors {!r}, strict {}".format(
+ log.network.debug("Certificate errors {!r}, strict {}".format(
errors, ssl_strict))
for error in errors:
@@ -186,7 +186,7 @@ def ignore_certificate_errors(url, errors, abort_on):
ignore = False
return ignore
elif ssl_strict is False:
- log.webview.debug("ssl_strict is False, only warning about errors")
+ log.network.debug("ssl_strict is False, only warning about errors")
for err in errors:
# FIXME we might want to use warn here (non-fatal error)
# https://github.com/qutebrowser/qutebrowser/issues/114
diff --git a/qutebrowser/browser/webengine/interceptor.py b/qutebrowser/browser/webengine/interceptor.py
index 7d455d4c3..d4dcb522f 100644
--- a/qutebrowser/browser/webengine/interceptor.py
+++ b/qutebrowser/browser/webengine/interceptor.py
@@ -154,7 +154,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
info.resourceType())
navigation_type_str = debug.qenum_key(QWebEngineUrlRequestInfo,
info.navigationType())
- log.webview.debug("{} {}, first-party {}, resource {}, "
+ log.network.debug("{} {}, first-party {}, resource {}, "
"navigation {}".format(
bytes(info.requestMethod()).decode('ascii'),
info.requestUrl().toDisplayString(),
@@ -164,7 +164,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
url = info.requestUrl()
first_party = info.firstPartyUrl()
if not url.isValid():
- log.webview.debug("Ignoring invalid intercepted URL: {}".format(
+ log.network.debug("Ignoring invalid intercepted URL: {}".format(
url.errorString()))
return
@@ -173,7 +173,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
try:
resource_type = self._resource_types[info.resourceType()]
except KeyError:
- log.webview.warning(
+ log.network.warning(
"Resource type {} not found in RequestInterceptor dict."
.format(debug.qenum_key(QWebEngineUrlRequestInfo,
info.resourceType())))
@@ -184,7 +184,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
if (first_party != QUrl('qute://settings/') or
info.resourceType() !=
QWebEngineUrlRequestInfo.ResourceTypeXhr):
- log.webview.warning("Blocking malicious request from {} to {}"
+ log.network.warning("Blocking malicious request from {} to {}"
.format(first_party.toDisplayString(),
url.toDisplayString()))
info.block(True)
diff --git a/qutebrowser/browser/webengine/webenginequtescheme.py b/qutebrowser/browser/webengine/webenginequtescheme.py
index 2c69b521f..879f8aeca 100644
--- a/qutebrowser/browser/webengine/webenginequtescheme.py
+++ b/qutebrowser/browser/webengine/webenginequtescheme.py
@@ -86,9 +86,9 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
return True
if initiator.isValid() and initiator.scheme() != 'qute':
- log.misc.warning("Blocking malicious request from {} to {}".format(
- initiator.toDisplayString(),
- request_url.toDisplayString()))
+ log.network.warning("Blocking malicious request from {} to {}"
+ .format(initiator.toDisplayString(),
+ request_url.toDisplayString()))
job.fail(QWebEngineUrlRequestJob.RequestDenied)
return False
@@ -119,7 +119,7 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
assert url.scheme() == 'qute'
- log.misc.debug("Got request for {}".format(url.toDisplayString()))
+ log.network.debug("Got request for {}".format(url.toDisplayString()))
try:
mimetype, data = qutescheme.data_for_url(url)
except qutescheme.Error as e:
@@ -136,14 +136,14 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
QWebEngineUrlRequestJob.RequestFailed,
}
exctype = type(e)
- log.misc.error("{} while handling qute://* URL".format(
+ log.network.error("{} while handling qute://* URL".format(
exctype.__name__))
job.fail(errors[exctype])
except qutescheme.Redirect as e:
qtutils.ensure_valid(e.url)
job.redirect(e.url)
else:
- log.misc.debug("Returning {} data".format(mimetype))
+ log.network.debug("Returning {} data".format(mimetype))
# We can't just use the QBuffer constructor taking a QByteArray,
# because that somehow segfaults...
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index d1e7e88e6..85190e1c7 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -1619,16 +1619,16 @@ class WebEngineTab(browsertab.AbstractTab):
url = error.url()
self._insecure_hosts.add(url.host())
- log.webview.debug("Certificate error: {}".format(error))
+ log.network.debug("Certificate error: {}".format(error))
if error.is_overridable():
error.ignore = shared.ignore_certificate_errors(
url, [error], abort_on=[self.abort_questions])
else:
- log.webview.error("Non-overridable certificate error: "
+ log.network.error("Non-overridable certificate error: "
"{}".format(error))
- log.webview.debug("ignore {}, URL {}, requested {}".format(
+ log.network.debug("ignore {}, URL {}, requested {}".format(
error.ignore, url, self.url(requested=True)))
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-56207
diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py
index 039ed0dba..1662f84fc 100644
--- a/qutebrowser/browser/webkit/network/networkmanager.py
+++ b/qutebrowser/browser/webkit/network/networkmanager.py
@@ -236,7 +236,7 @@ class NetworkManager(QNetworkAccessManager):
errors: A list of errors.
"""
errors = [certificateerror.CertificateErrorWrapper(e) for e in errors]
- log.webview.debug("Certificate errors: {!r}".format(
+ log.network.debug("Certificate errors: {!r}".format(
' / '.join(str(err) for err in errors)))
try:
host_tpl = urlutils.host_tuple(
@@ -252,7 +252,7 @@ class NetworkManager(QNetworkAccessManager):
is_rejected = set(errors).issubset(
self._rejected_ssl_errors[host_tpl])
- log.webview.debug("Already accepted: {} / "
+ log.network.debug("Already accepted: {} / "
"rejected {}".format(is_accepted, is_rejected))
if is_rejected:
@@ -425,7 +425,7 @@ class NetworkManager(QNetworkAccessManager):
if 'log-requests' in objects.debug_flags:
operation = debug.qenum_key(QNetworkAccessManager, op)
operation = operation.replace('Operation', '').upper()
- log.webview.debug("{} {}, first-party {}".format(
+ log.network.debug("{} {}, first-party {}".format(
operation,
req.url().toDisplayString(),
current_url.toDisplayString()))
diff --git a/qutebrowser/browser/webkit/network/webkitqutescheme.py b/qutebrowser/browser/webkit/network/webkitqutescheme.py
index 782bcc94a..0dce98765 100644
--- a/qutebrowser/browser/webkit/network/webkitqutescheme.py
+++ b/qutebrowser/browser/webkit/network/webkitqutescheme.py
@@ -48,7 +48,7 @@ def handler(request, operation, current_url):
if ((url.scheme(), url.host(), url.path()) ==
('qute', 'settings', '/set')):
if current_url != QUrl('qute://settings/'):
- log.webview.warning("Blocking malicious request from {} to {}"
+ log.network.warning("Blocking malicious request from {} to {}"
.format(current_url.toDisplayString(),
url.toDisplayString()))
return networkreply.ErrorNetworkReply(
diff --git a/qutebrowser/components/adblock.py b/qutebrowser/components/adblock.py
index a683e9190..f6bc8bb3e 100644
--- a/qutebrowser/components/adblock.py
+++ b/qutebrowser/components/adblock.py
@@ -33,7 +33,7 @@ from qutebrowser.api import (cmdutils, hook, config, message, downloads,
interceptor, apitypes, qtutils)
-logger = logging.getLogger('misc')
+logger = logging.getLogger('network')
_host_blocker = typing.cast('HostBlocker', None)
From 2b629e0d3d0c4b5ecbe49630718cf9579294020e Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 12:01:49 +0200
Subject: [PATCH 197/245] qutescheme: Unify code between qute_log and
qute_plainlog
---
qutebrowser/browser/qutescheme.py | 35 ++++++++++++-------------------
1 file changed, 13 insertions(+), 22 deletions(-)
diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py
index ec6e19082..83536d3e9 100644
--- a/qutebrowser/browser/qutescheme.py
+++ b/qutebrowser/browser/qutescheme.py
@@ -307,43 +307,34 @@ def qute_version(_url):
return 'text/html', src
-@add_handler('plainlog')
-def qute_plainlog(url: QUrl) -> _HandlerRet:
- """Handler for qute://plainlog.
+def _qute_log(url: QUrl, *, html: bool) -> _HandlerRet:
+ """Shared code between qute://log and qute://plainlog.
An optional query parameter specifies the minimum log level to print.
For example, qute://log?level=warning prints warnings and errors.
Level can be one of: vdebug, debug, info, warning, error, critical.
"""
if log.ram_handler is None:
- text = "Log output was disabled."
+ content = None if html else "Log output was disabled."
else:
level = QUrlQuery(url).queryItemValue('level')
if not level:
level = 'vdebug'
- text = log.ram_handler.dump_log(html=False, level=level)
- src = jinja.render('pre.html', title='log', content=text)
+ content = log.ram_handler.dump_log(html=html, level=level)
+
+ template = 'log.html' if html else 'pre.html'
+ src = jinja.render(template, title='log', content=content)
return 'text/html', src
+@add_handler('plainlog')
+def qute_plainlog(url: QUrl) -> _HandlerRet:
+ return _qute_log(url, html=False)
+
+
@add_handler('log')
def qute_log(url: QUrl) -> _HandlerRet:
- """Handler for qute://log.
-
- An optional query parameter specifies the minimum log level to print.
- For example, qute://log?level=warning prints warnings and errors.
- Level can be one of: vdebug, debug, info, warning, error, critical.
- """
- if log.ram_handler is None:
- html_log = None
- else:
- level = QUrlQuery(url).queryItemValue('level')
- if not level:
- level = 'vdebug'
- html_log = log.ram_handler.dump_log(html=True, level=level)
-
- src = jinja.render('log.html', title='log', content=html_log)
- return 'text/html', src
+ return _qute_log(url, html=True)
@add_handler('gpl')
From 679eaab28f6ee470bfdd7ca3699efdb619b23ebe Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 12:11:08 +0200
Subject: [PATCH 198/245] Merge qute://plainlog into qute://log?plain
---
doc/changelog.asciidoc | 3 +++
qutebrowser/browser/commands.py | 11 ++++++---
qutebrowser/browser/qutescheme.py | 27 +++++++++--------------
tests/end2end/features/qutescheme.feature | 4 ----
4 files changed, 22 insertions(+), 23 deletions(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 8a77a3fd5..f5f039c2e 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -23,6 +23,9 @@ Removed
- The `:debug-log-level` command was removed as it's replaced by the new
`logging.level.console` setting.
+- The `qute://plainlog` special page got replaced by `qute://log?plain` - the
+ names of those pages is considered an implementation detail, and
+ `:messages --plain` should be used instead.
Changed
~~~~~~~
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
index f115501d3..639f641f5 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -1398,10 +1398,15 @@ class CommandDispatcher:
"""
if level.upper() not in log.LOG_LEVELS:
raise cmdutils.CommandError("Invalid log level {}!".format(level))
+
+ query = QUrlQuery()
+ query.addQueryItem('level', level)
if plain:
- url = QUrl('qute://plainlog?level={}'.format(level))
- else:
- url = QUrl('qute://log?level={}'.format(level))
+ query.addQueryItem('plain', None)
+
+ url = QUrl('qute://log')
+ url.setQuery(query)
+
self._open(url, tab, bg, window)
def _open_editor_cb(self, elem):
diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py
index 83536d3e9..fc1f2a123 100644
--- a/qutebrowser/browser/qutescheme.py
+++ b/qutebrowser/browser/qutescheme.py
@@ -307,36 +307,31 @@ def qute_version(_url):
return 'text/html', src
-def _qute_log(url: QUrl, *, html: bool) -> _HandlerRet:
- """Shared code between qute://log and qute://plainlog.
+@add_handler('log')
+def qute_log(url: QUrl) -> _HandlerRet:
+ """Handler for qute://log.
An optional query parameter specifies the minimum log level to print.
For example, qute://log?level=warning prints warnings and errors.
Level can be one of: vdebug, debug, info, warning, error, critical.
"""
+ query = QUrlQuery(url)
+ plain = (query.hasQueryItem('plain') and
+ query.queryItemValue('plain').lower() != 'false')
+
if log.ram_handler is None:
- content = None if html else "Log output was disabled."
+ content = "Log output was disabled." if plain else None
else:
- level = QUrlQuery(url).queryItemValue('level')
+ level = query.queryItemValue('level')
if not level:
level = 'vdebug'
- content = log.ram_handler.dump_log(html=html, level=level)
+ content = log.ram_handler.dump_log(html=not plain, level=level)
- template = 'log.html' if html else 'pre.html'
+ template = 'pre.html' if plain else 'log.html'
src = jinja.render(template, title='log', content=content)
return 'text/html', src
-@add_handler('plainlog')
-def qute_plainlog(url: QUrl) -> _HandlerRet:
- return _qute_log(url, html=False)
-
-
-@add_handler('log')
-def qute_log(url: QUrl) -> _HandlerRet:
- return _qute_log(url, html=True)
-
-
@add_handler('gpl')
def qute_gpl(_url: QUrl) -> _HandlerRet:
"""Handler for qute://gpl. Return HTML content as string."""
diff --git a/tests/end2end/features/qutescheme.feature b/tests/end2end/features/qutescheme.feature
index 35c110dc5..d41975d78 100644
--- a/tests/end2end/features/qutescheme.feature
+++ b/tests/end2end/features/qutescheme.feature
@@ -269,10 +269,6 @@ Feature: Special qute:// pages
And I wait for "Changing title for idx * to 'log'" in the log
Then no crash should happen
- Scenario: Using qute://plainlog directly
- When I open qute://plainlog
- Then no crash should happen
-
# :version
Scenario: Open qute://version
From 33dd70dae67ce051c46fc8a1631e2a5fd94f50b8 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 12:34:50 +0200
Subject: [PATCH 199/245] Revert "tests: Separate init_from_config logs in
utils.test_log"
Whoops... It uses fixtures from that test class!
This reverts commit a9d10aee8403fdfcfc68d38a40312c7811d0dfe7.
---
tests/unit/utils/test_log.py | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index 3e3243e48..6b770ed44 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -255,16 +255,14 @@ class TestInitLog:
with pytest.raises(PendingDeprecationWarning):
warnings.warn("test warning", PendingDeprecationWarning)
-
-class TestInitFromConfig:
-
@pytest.mark.parametrize('cli, conf, expected', [
(None, 'info', logging.INFO),
(None, 'warning', logging.WARNING),
('info', 'warning', logging.INFO),
('warning', 'info', logging.WARNING),
])
- def test_console(self, cli, conf, expected, args, config_stub):
+ def test_init_from_config_console(self, cli, conf, expected, args,
+ config_stub):
args.debug = False
args.loglevel = cli
log.init_log(args)
@@ -279,7 +277,7 @@ class TestInitFromConfig:
('info', logging.INFO),
('critical', logging.CRITICAL),
])
- def test_ram(self, conf, expected, args, config_stub):
+ def test_init_from_config_ram(self, conf, expected, args, config_stub):
args.debug = False
log.init_log(args)
@@ -287,7 +285,7 @@ class TestInitFromConfig:
log.init_from_config(config_stub.val)
assert log.ram_handler.level == expected
- def test_consistent_default(self, config_stub):
+ def test_init_from_config_consistent_default(self, config_stub):
"""Ensure config defaults are consistent with the builtin defaults."""
args = qutebrowser.get_argparser().parse_args([])
log.init_log(args)
From 3edaa173020cac1079437d67f96ccb5309ed5640 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 12:37:57 +0200
Subject: [PATCH 200/245] Fix lint
---
tests/unit/misc/test_utilcmds.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/tests/unit/misc/test_utilcmds.py b/tests/unit/misc/test_utilcmds.py
index 87e80afc1..e8f651dc8 100644
--- a/tests/unit/misc/test_utilcmds.py
+++ b/tests/unit/misc/test_utilcmds.py
@@ -19,8 +19,6 @@
"""Tests for qutebrowser.misc.utilcmds."""
-import logging
-
import pytest
from PyQt5.QtCore import QUrl
From 34822bb530a57aaa82420828ff6e630dfa1b5cd8 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 12:44:31 +0200
Subject: [PATCH 201/245] Change logging format when the loglevel is changed
via config
---
qutebrowser/utils/log.py | 4 +++-
tests/unit/utils/test_log.py | 19 ++++++++++++++++---
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index d1f654d8f..117d5f5d1 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -554,7 +554,9 @@ def init_from_config(conf: 'configmodule.ConfigContainer') -> None:
init.debug("--loglevel flag overrides logging.level.console")
else:
init.debug("Configuring console loglevel to %s", consolelevel)
- console_handler.setLevel(LOG_LEVELS[consolelevel.upper()])
+ level = LOG_LEVELS[consolelevel.upper()]
+ console_handler.setLevel(level)
+ change_console_formatter(level)
class QtWarningFilter(logging.Filter):
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index 6b770ed44..bf2af5147 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -216,6 +216,11 @@ class TestInitLog:
"""Fixture providing an argparse namespace for init_log."""
return self._get_default_args()
+ @pytest.fixture
+ def empty_args(self):
+ """Logging commandline arguments without any customization."""
+ return qutebrowser.get_argparser().parse_args([])
+
def test_stderr_none(self, args):
"""Test init_log with sys.stderr = None."""
old_stderr = sys.stderr
@@ -285,10 +290,9 @@ class TestInitLog:
log.init_from_config(config_stub.val)
assert log.ram_handler.level == expected
- def test_init_from_config_consistent_default(self, config_stub):
+ def test_init_from_config_consistent_default(self, config_stub, empty_args):
"""Ensure config defaults are consistent with the builtin defaults."""
- args = qutebrowser.get_argparser().parse_args([])
- log.init_log(args)
+ log.init_log(empty_args)
assert log.ram_handler.level == logging.DEBUG
assert log.console_handler.level == logging.INFO
@@ -298,6 +302,15 @@ class TestInitLog:
assert log.ram_handler.level == logging.DEBUG
assert log.console_handler.level == logging.INFO
+ def test_init_from_config_format(self, config_stub, empty_args):
+ """If we change to the debug level, make sure the format changes."""
+ log.init_log(empty_args)
+ assert log.console_handler.formatter._fmt == log.SIMPLE_FMT
+
+ config_stub.val.logging.level.console = 'debug'
+ log.init_from_config(config_stub.val)
+ assert log.console_handler.formatter._fmt == log.EXTENDED_FMT
+
class TestHideQtWarning:
From d9ecc35eefb354b2a1cdbca9944a120cd87474d1 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 14:26:19 +0200
Subject: [PATCH 202/245] Fix lint
---
qutebrowser/browser/commands.py | 2 +-
qutebrowser/components/adblock.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
index 639f641f5..76f802891 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -1402,7 +1402,7 @@ class CommandDispatcher:
query = QUrlQuery()
query.addQueryItem('level', level)
if plain:
- query.addQueryItem('plain', None)
+ query.addQueryItem('plain', typing.cast(str, None))
url = QUrl('qute://log')
url.setQuery(query)
diff --git a/qutebrowser/components/adblock.py b/qutebrowser/components/adblock.py
index f6bc8bb3e..b34711fdd 100644
--- a/qutebrowser/components/adblock.py
+++ b/qutebrowser/components/adblock.py
@@ -129,7 +129,7 @@ class HostBlocker:
if self._is_blocked(request_url=info.request_url,
first_party_url=info.first_party_url):
logger.debug("Request to {} blocked by host blocker."
- .format(info.request_url.host()))
+ .format(info.request_url.host()))
info.block()
def _read_hosts_line(self, raw_line: bytes) -> typing.Set[str]:
From ed26d43c16dbcc7b9534cb57d5a8b82f80706f7a Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 13:04:06 +0200
Subject: [PATCH 203/245] Fix typo
(cherry picked from commit 7dceedb26bd5d0df97a97cd727e19fd9937bd850)
---
qutebrowser/utils/log.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 117d5f5d1..6f117f1a6 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -639,7 +639,7 @@ class RAMHandler(logging.Handler):
def dump_log(self, html: bool = False, level: str = 'vdebug') -> str:
"""Dump the complete formatted log data as string.
- FIXME: We should do all the HTML formatter via jinja2.
+ FIXME: We should do all the HTML formatting via jinja2.
(probably obsolete when moving to a widget for logging,
https://github.com/qutebrowser/qutebrowser/issues/34
"""
From 025a70254f621b2c7f2c08f0f028e39b37c24f18 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 15:15:19 +0200
Subject: [PATCH 204/245] commands: Add some additional tests for *args
handling
(cherry picked from commit 3983a6d3ee30f86b1afd809bc786322cbff8fd55)
---
tests/unit/api/test_cmdutils.py | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/tests/unit/api/test_cmdutils.py b/tests/unit/api/test_cmdutils.py
index 1d2cd3f46..9ca6fc2ae 100644
--- a/tests/unit/api/test_cmdutils.py
+++ b/tests/unit/api/test_cmdutils.py
@@ -140,9 +140,31 @@ class TestRegister:
@cmdutils.register()
def fun(*args):
"""Blah."""
+ assert args == ['one', 'two']
+
+ objects.commands['fun'].parser.parse_args(['one', 'two'])
+
+ def test_star_args_empty(self):
+ """Check handling of *args without any value."""
+ @cmdutils.register()
+ def fun(*args):
+ """Blah."""
+ assert not args
+
with pytest.raises(argparser.ArgumentParserError):
objects.commands['fun'].parser.parse_args([])
+ def test_star_args_type(self):
+ """Check handling of *args with a type.
+
+ This isn't implemented, so be sure we catch it.
+ """
+ with pytest.raises(AssertionError):
+ @cmdutils.register()
+ def fun(*args: int):
+ """Blah."""
+ pass
+
def test_star_args_optional(self):
"""Check handling of *args withstar_args_optional."""
@cmdutils.register(star_args_optional=True)
From ac4fc1a1e3aa87cc0a26fb7060141c9a059c9599 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 15:15:41 +0200
Subject: [PATCH 205/245] command: Use consistent name for arg_info
(cherry picked from commit 279d28f982f3afe32c94fc938677bcad816b647e)
---
qutebrowser/commands/command.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py
index 4eefe481c..2672fcd68 100644
--- a/qutebrowser/commands/command.py
+++ b/qutebrowser/commands/command.py
@@ -334,8 +334,8 @@ class Command:
Args:
param: The inspect.Parameter to look at.
"""
- arginfo = self.get_arg_info(param)
- if arginfo.value:
+ arg_info = self.get_arg_info(param)
+ if arg_info.value:
# Filled values are passed 1:1
return None
elif param.kind in [inspect.Parameter.VAR_POSITIONAL,
From 58dc10ec66e0dd8c75c7aee18641752914065b17 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 13:02:20 +0200
Subject: [PATCH 206/245] Refactor log.LogFilter
This essentially does two things:
1) Use a set for LogFilter
This means we don't support filters like "eggs.bacon" anymore (but we *do*
support loggers like that) - however, those were already disallowed by the
--logfilter argument validation anyways!
In return, we get probably slightly better performance (checking set membership
rather than iterating all filters) and more straightforward code.
2) Move parsing from various locations around the code to the LogFilter class.
---
qutebrowser/misc/utilcmds.py | 14 ++--
qutebrowser/qutebrowser.py | 11 ++--
qutebrowser/utils/log.py | 72 ++++++++++++++-------
tests/unit/test_qutebrowser.py | 63 ++++++++++++++++++
tests/unit/utils/test_log.py | 114 +++++++++++++++++++++------------
5 files changed, 193 insertions(+), 81 deletions(-)
create mode 100644 tests/unit/test_qutebrowser.py
diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py
index 2a4b543f8..05fea9501 100644
--- a/qutebrowser/misc/utilcmds.py
+++ b/qutebrowser/misc/utilcmds.py
@@ -236,16 +236,12 @@ def debug_log_filter(filters: str) -> None:
raise cmdutils.CommandError("No log.console_filter. Not attached "
"to a console?")
- if filters.strip().lower() == 'none':
- log.console_filter.names = None
- return
+ try:
+ new_filter = log.LogFilter.parse(filters)
+ except log.InvalidLogFilterError as e:
+ raise cmdutils.CommandError(e)
- if not set(filters.split(',')).issubset(log.LOGGER_NAMES):
- raise cmdutils.CommandError("filters: Invalid value {} - expected one "
- "of: {}".format(
- filters, ', '.join(log.LOGGER_NAMES)))
-
- log.console_filter.names = filters.split(',')
+ log.console_filter.update_from(new_filter)
@cmdutils.register()
diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py
index 3369c1ebe..8765f5217 100644
--- a/qutebrowser/qutebrowser.py
+++ b/qutebrowser/qutebrowser.py
@@ -150,12 +150,11 @@ def logfilter_error(logfilter):
logfilter: A comma separated list of logger names.
"""
from qutebrowser.utils import log
- if set(logfilter.lstrip('!').split(',')).issubset(log.LOGGER_NAMES):
- return logfilter
- else:
- raise argparse.ArgumentTypeError(
- "filters: Invalid value {} - expected a list of: {}".format(
- logfilter, ', '.join(log.LOGGER_NAMES)))
+ try:
+ log.LogFilter.parse(logfilter)
+ except log.InvalidLogFilterError as e:
+ raise argparse.ArgumentTypeError(e)
+ return logfilter
def debug_flag_error(flag):
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 6f117f1a6..e603ea2b2 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -193,16 +193,7 @@ def init_log(args: argparse.Namespace) -> None:
root = logging.getLogger()
global console_filter
if console is not None:
- if not args.logfilter:
- negate = False
- names = None
- elif args.logfilter.startswith('!'):
- negate = True
- names = args.logfilter[1:].split(',')
- else:
- negate = False
- names = args.logfilter.split(',')
- console_filter = LogFilter(names, negate)
+ console_filter = LogFilter.parse(args.logfilter)
console.addFilter(console_filter)
root.addHandler(console)
if ram is not None:
@@ -577,6 +568,17 @@ class QtWarningFilter(logging.Filter):
return do_log
+class InvalidLogFilterError(Exception):
+
+ """Raised when an invalid filter string is passed to LogFilter.parse()."""
+
+ def __init__(self, names: typing.Set[str]):
+ invalid = names - set(LOGGER_NAMES)
+ super().__init__("Invalid log category {} - valid categories: {}"
+ .format(', '.join(sorted(invalid)),
+ ', '.join(LOGGER_NAMES)))
+
+
class LogFilter(logging.Filter):
"""Filter to filter log records based on the commandline argument.
@@ -585,30 +587,52 @@ class LogFilter(logging.Filter):
comma-separated list instead.
Attributes:
- names: A list of record names to filter.
- negated: Whether names is a list of records to log or to suppress.
+ names: A set of logging names to allow.
+ negated: Whether names is a set of names to log or to suppress.
"""
- def __init__(self, names: typing.Optional[typing.Iterable[str]],
- negate: bool = False) -> None:
+ def __init__(self, names: typing.Set[str], negated: bool = False) -> None:
super().__init__()
self.names = names
- self.negated = negate
+ self.negated = negated
+
+ @classmethod
+ def parse(cls, filter_str: typing.Optional[str]) -> 'LogFilter':
+ """Parse a log filter from a string."""
+ if filter_str is None or filter_str == 'none':
+ names = set()
+ negated = False
+ else:
+ filter_str = filter_str.lower()
+
+ if filter_str.startswith('!'):
+ negated = True
+ filter_str = filter_str[1:]
+ else:
+ negated = False
+
+ names = {e.strip() for e in filter_str.split(',')}
+
+ if not names.issubset(LOGGER_NAMES):
+ raise InvalidLogFilterError(names)
+
+ return cls(names=names, negated=negated)
+
+ def update_from(self, other: 'LogFilter') -> None:
+ """Update this filter's properties from another filter."""
+ self.names = other.names
+ self.negated = other.negated
def filter(self, record: logging.LogRecord) -> bool:
"""Determine if the specified record is to be logged."""
- if self.names is None:
+ if not self.names:
+ # No filter
return True
- if record.levelno > logging.DEBUG:
+ elif record.levelno > logging.DEBUG:
# More important than DEBUG, so we won't filter at all
return True
- for name in self.names:
- if record.name == name:
- return not self.negated
- elif not record.name.startswith(name):
- continue
- elif record.name[len(name)] == '.':
- return not self.negated
+ elif record.name.split('.')[0] in self.names:
+ return not self.negated
return self.negated
diff --git a/tests/unit/test_qutebrowser.py b/tests/unit/test_qutebrowser.py
new file mode 100644
index 000000000..6dbc24351
--- /dev/null
+++ b/tests/unit/test_qutebrowser.py
@@ -0,0 +1,63 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2020 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 .
+
+"""Tests for qutebrowser.qutebrowser.
+
+(Mainly commandline flag parsing)
+"""
+
+import argparse
+
+import pytest
+
+from qutebrowser import qutebrowser
+
+
+@pytest.fixture
+def parser():
+ return qutebrowser.get_argparser()
+
+
+class TestDebugFlag:
+
+ def test_valid(self, parser):
+ args = parser.parse_args(['--debug-flag', 'chromium', '--debug-flag', 'stack'])
+ assert args.debug_flags == ['chromium', 'stack']
+
+ def test_invalid(self, parser, capsys):
+ with pytest.raises(SystemExit):
+ parser.parse_args(['--debug-flag', 'invalid'])
+
+ _out, err = capsys.readouterr()
+ assert 'Invalid debug flag - valid flags:' in err
+
+
+class TestLogFilter:
+
+ def test_valid(self, parser):
+ args = parser.parse_args(['--logfilter', 'misc'])
+ assert args.logfilter == 'misc'
+
+ def test_invalid(self, parser, capsys):
+ with pytest.raises(SystemExit):
+ parser.parse_args(['--logfilter', 'invalid'])
+
+ _out, err = capsys.readouterr()
+ print(err)
+ assert 'Invalid log category invalid - valid categories' in err
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index bf2af5147..14fa07aeb 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -33,6 +33,7 @@ from PyQt5 import QtCore
from qutebrowser import qutebrowser
from qutebrowser.utils import log
from qutebrowser.misc import utilcmds
+from qutebrowser.api import cmdutils
@pytest.fixture(autouse=True)
@@ -118,28 +119,23 @@ class TestLogFilter:
@pytest.mark.parametrize('filters, negated, category, logged', [
# Filter letting all messages through
- (None, False, 'eggs.bacon.spam', True),
- (None, False, 'eggs', True),
- (None, True, 'ham', True),
+ (set(), False, 'eggs.bacon.spam', True),
+ (set(), False, 'eggs', True),
+ (set(), True, 'ham', True),
# Matching records
- (['eggs', 'bacon'], False, 'eggs', True),
- (['eggs', 'bacon'], False, 'bacon', True),
- (['eggs.bacon'], False, 'eggs.bacon', True),
+ ({'eggs', 'bacon'}, False, 'eggs', True),
+ ({'eggs', 'bacon'}, False, 'bacon', True),
+ ({'eggs'}, False, 'eggs.fried', True),
# Non-matching records
- (['eggs', 'bacon'], False, 'spam', False),
- (['eggs'], False, 'eggsauce', False),
- (['eggs.bacon'], False, 'eggs.baconstrips', False),
- # Child loggers
- (['eggs.bacon', 'spam.ham'], False, 'eggs.bacon.spam', True),
- (['eggs.bacon', 'spam.ham'], False, 'spam.ham.salami', True),
+ ({'eggs', 'bacon'}, False, 'spam', False),
+ ({'eggs'}, False, 'eggsauce', False),
+ ({'fried'}, False, 'eggs.fried', False),
# Suppressed records
- (['eggs', 'bacon'], True, 'eggs', False),
- (['eggs', 'bacon'], True, 'bacon', False),
- (['eggs.bacon'], True, 'eggs.bacon', False),
+ ({'eggs', 'bacon'}, True, 'eggs', False),
+ ({'eggs', 'bacon'}, True, 'bacon', False),
# Non-suppressed records
- (['eggs', 'bacon'], True, 'spam', True),
- (['eggs'], True, 'eggsauce', True),
- (['eggs.bacon'], True, 'eggs.baconstrips', True),
+ ({'eggs', 'bacon'}, True, 'spam', True),
+ ({'eggs'}, True, 'eggsauce', True),
])
def test_logfilter(self, logger, filters, negated, category, logged):
"""Ensure the multi-record filtering filterer filters multiple records.
@@ -150,19 +146,29 @@ class TestLogFilter:
record = self._make_record(logger, category)
assert logfilter.filter(record) == logged
+ def test_logfilter_benchmark(self, logger, benchmark):
+ record = self._make_record(logger, 'unfiltered')
+ filters = set(log.LOGGER_NAMES) # Extreme case
+ logfilter = log.LogFilter(filters, negated=False)
+ benchmark(lambda: logfilter.filter(record))
+
@pytest.mark.parametrize('category', ['eggs', 'bacon'])
def test_debug(self, logger, category):
"""Test if messages more important than debug are never filtered."""
- logfilter = log.LogFilter(['eggs'])
+ logfilter = log.LogFilter({'eggs'})
record = self._make_record(logger, category, level=logging.INFO)
assert logfilter.filter(record)
- @pytest.mark.parametrize('category, logged_before, logged_after', [
- ('init', True, False), ('url', False, True), ('js', False, True)])
+ @pytest.mark.parametrize('category, filter_str, logged_before, logged_after', [
+ ('init', 'url,js', True, False),
+ ('url', 'url,js', False, True),
+ ('js', 'url,js', False, True),
+ ('js', 'none', False, True),
+ ])
def test_debug_log_filter_cmd(self, monkeypatch, logger, category,
- logged_before, logged_after):
+ filter_str, logged_before, logged_after):
"""Test the :debug-log-filter command handler."""
- logfilter = log.LogFilter(["init"])
+ logfilter = log.LogFilter({"init"})
monkeypatch.setattr(log, 'console_filter', logfilter)
record = self._make_record(logger, category)
@@ -171,6 +177,37 @@ class TestLogFilter:
utilcmds.debug_log_filter('url,js')
assert logfilter.filter(record) == logged_after
+ def test_debug_log_filter_cmd_invalid(self, monkeypatch):
+ logfilter = log.LogFilter(set())
+ monkeypatch.setattr(log, 'console_filter', logfilter)
+ with pytest.raises(cmdutils.CommandError,
+ match='Invalid log category blabla'):
+ utilcmds.debug_log_filter('blabla')
+
+ @pytest.mark.parametrize('filter_str, expected_names, negated', [
+ ('!js,misc', {'js', 'misc'}, True),
+ ('js,misc', {'js', 'misc'}, False),
+ ('js, misc', {'js', 'misc'}, False),
+ ('JS, Misc', {'js', 'misc'}, False),
+ (None, set(), False),
+ ('none', set(), False),
+ ])
+ def test_parsing(self, filter_str, expected_names, negated):
+ logfilter = log.LogFilter.parse(filter_str)
+ assert logfilter.names == expected_names
+ assert logfilter.negated == negated
+
+ @pytest.mark.parametrize('filter_str, invalid', [
+ ('js,!misc', '!misc'),
+ ('blabla,js,blablub', 'blabla, blablub'),
+ ])
+ def test_parsing_invalid(self, filter_str, invalid):
+ with pytest.raises(
+ log.InvalidLogFilterError,
+ match='Invalid log category {} - '
+ 'valid categories: statusbar, .*'.format(invalid)):
+ log.LogFilter.parse(filter_str)
+
@pytest.mark.parametrize('data, expected', [
# Less data
@@ -199,7 +236,7 @@ class TestInitLog:
def _get_default_args(self):
return argparse.Namespace(debug=True, loglevel='debug', color=True,
- loglines=10, logfilter="", force_color=False,
+ loglines=10, logfilter=None, force_color=False,
json_logging=False, debug_flags=set())
@pytest.fixture(autouse=True)
@@ -217,9 +254,13 @@ class TestInitLog:
return self._get_default_args()
@pytest.fixture
- def empty_args(self):
+ def parser(self):
+ return qutebrowser.get_argparser()
+
+ @pytest.fixture
+ def empty_args(self, parser):
"""Logging commandline arguments without any customization."""
- return qutebrowser.get_argparser().parse_args([])
+ return parser.parse_args([])
def test_stderr_none(self, args):
"""Test init_log with sys.stderr = None."""
@@ -228,22 +269,6 @@ class TestInitLog:
log.init_log(args)
sys.stderr = old_stderr
- @pytest.mark.parametrize('logfilter, expected_names, negated', [
- ('!one,two', ['one', 'two'], True),
- ('one,two', ['one', 'two'], False),
- ('one,!two', ['one', '!two'], False),
- (None, None, False),
- ])
- def test_negation_parser(self, args, mocker,
- logfilter, expected_names, negated):
- """Test parsing the --logfilter argument."""
- filter_mock = mocker.patch('qutebrowser.utils.log.LogFilter',
- autospec=True)
- args.logfilter = logfilter
- log.init_log(args)
- assert filter_mock.called
- assert filter_mock.call_args[0] == (expected_names, negated)
-
def test_python_warnings(self, args, caplog):
log.init_log(args)
@@ -311,6 +336,11 @@ class TestInitLog:
log.init_from_config(config_stub.val)
assert log.console_handler.formatter._fmt == log.EXTENDED_FMT
+ def test_logfilter(self, parser):
+ args = parser.parse_args(['--logfilter', 'misc'])
+ log.init_log(args)
+ assert log.console_filter.names == {'misc'}
+
class TestHideQtWarning:
From 48fed500ef546092374d92e42fe60203896f4773 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 13:05:31 +0200
Subject: [PATCH 207/245] Add categories to qute://log
---
doc/help/commands.asciidoc | 5 +++-
qutebrowser/browser/commands.py | 15 ++++++++++--
qutebrowser/browser/qutescheme.py | 21 +++++++++++++++--
qutebrowser/utils/log.py | 28 ++++++++++++++++++-----
tests/end2end/features/qutescheme.feature | 21 +++++++++++++++++
tests/end2end/features/utilcmds.feature | 2 +-
tests/unit/utils/test_log.py | 12 +++++-----
7 files changed, 86 insertions(+), 18 deletions(-)
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index 1e870cbaa..8d70b19c8 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -809,7 +809,7 @@ Show a warning message in the statusbar.
[[messages]]
=== messages
-Syntax: +:messages [*--plain*] [*--tab*] [*--bg*] [*--window*] ['level']+
+Syntax: +:messages [*--plain*] [*--tab*] [*--bg*] [*--window*] [*--logfilter* 'logfilter'] ['level']+
Show a log of past messages.
@@ -822,6 +822,9 @@ Show a log of past messages.
* +*-t*+, +*--tab*+: Open in a new tab.
* +*-b*+, +*--bg*+: Open in a background tab.
* +*-w*+, +*--window*+: Open in a new window.
+* +*-f*+, +*--logfilter*+: A comma-separated filter string of logging categories. If the filter string starts with an exclamation mark, it
+ is negated.
+
[[navigate]]
=== navigate
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
index 76f802891..78ed6c383 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -1384,14 +1384,18 @@ class CommandDispatcher:
self._open(url, tab, bg, window)
@cmdutils.register(instance='command-dispatcher', scope='window')
- def messages(self, level='info', plain=False, tab=False, bg=False,
- window=False):
+ @cmdutils.argument('logfilter', flag='f')
+ def messages(self, level='info', *, plain=False, tab=False, bg=False,
+ window=False, logfilter=None):
"""Show a log of past messages.
Args:
level: Include messages with `level` or higher severity.
Valid values: vdebug, debug, info, warning, error, critical.
plain: Whether to show plaintext (as opposed to html).
+ logfilter: A comma-separated filter string of logging categories.
+ If the filter string starts with an exclamation mark, it
+ is negated.
tab: Open in a new tab.
bg: Open in a background tab.
window: Open in a new window.
@@ -1404,6 +1408,13 @@ class CommandDispatcher:
if plain:
query.addQueryItem('plain', typing.cast(str, None))
+ if logfilter:
+ try:
+ log.LogFilter.parse(logfilter)
+ except log.InvalidLogFilterError as e:
+ raise cmdutils.CommandError(e)
+ query.addQueryItem('logfilter', logfilter)
+
url = QUrl('qute://log')
url.setQuery(query)
diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py
index fc1f2a123..5e8eb36c5 100644
--- a/qutebrowser/browser/qutescheme.py
+++ b/qutebrowser/browser/qutescheme.py
@@ -311,9 +311,16 @@ def qute_version(_url):
def qute_log(url: QUrl) -> _HandlerRet:
"""Handler for qute://log.
- An optional query parameter specifies the minimum log level to print.
+ There are three query parameters:
+
+ - level: The minimum log level to print.
For example, qute://log?level=warning prints warnings and errors.
Level can be one of: vdebug, debug, info, warning, error, critical.
+
+ - plain: If given (and not 'false'), plaintext is shown.
+
+ - logfilter: A filter string like the --logfilter commandline argument
+ accepts.
"""
query = QUrlQuery(url)
plain = (query.hasQueryItem('plain') and
@@ -325,7 +332,17 @@ def qute_log(url: QUrl) -> _HandlerRet:
level = query.queryItemValue('level')
if not level:
level = 'vdebug'
- content = log.ram_handler.dump_log(html=not plain, level=level)
+
+ filter_str = query.queryItemValue('logfilter')
+
+ try:
+ logfilter = (log.LogFilter.parse(filter_str, only_debug=False)
+ if filter_str else None)
+ except log.InvalidLogFilterError as e:
+ raise UrlInvalidError(e)
+
+ content = log.ram_handler.dump_log(html=not plain,
+ level=level, logfilter=logfilter)
template = 'pre.html' if plain else 'log.html'
src = jinja.render(template, title='log', content=content)
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index e603ea2b2..197f594f9 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -589,15 +589,20 @@ class LogFilter(logging.Filter):
Attributes:
names: A set of logging names to allow.
negated: Whether names is a set of names to log or to suppress.
+ only_debug: Only filter debug logs, always show anything more important
+ than debug.
"""
- def __init__(self, names: typing.Set[str], negated: bool = False) -> None:
+ def __init__(self, names: typing.Set[str], *, negated: bool = False,
+ only_debug: bool = True) -> None:
super().__init__()
self.names = names
self.negated = negated
+ self.only_debug = only_debug
@classmethod
- def parse(cls, filter_str: typing.Optional[str]) -> 'LogFilter':
+ def parse(cls, filter_str: typing.Optional[str], *,
+ only_debug: bool = True) -> 'LogFilter':
"""Parse a log filter from a string."""
if filter_str is None or filter_str == 'none':
names = set()
@@ -616,19 +621,20 @@ class LogFilter(logging.Filter):
if not names.issubset(LOGGER_NAMES):
raise InvalidLogFilterError(names)
- return cls(names=names, negated=negated)
+ return cls(names=names, negated=negated, only_debug=only_debug)
def update_from(self, other: 'LogFilter') -> None:
"""Update this filter's properties from another filter."""
self.names = other.names
self.negated = other.negated
+ self.only_debug = other.only_debug
def filter(self, record: logging.LogRecord) -> bool:
"""Determine if the specified record is to be logged."""
if not self.names:
# No filter
return True
- elif record.levelno > logging.DEBUG:
+ elif record.levelno > logging.DEBUG and self.only_debug:
# More important than DEBUG, so we won't filter at all
return True
elif record.name.split('.')[0] in self.names:
@@ -660,15 +666,24 @@ class RAMHandler(logging.Handler):
def emit(self, record: logging.LogRecord) -> None:
self._data.append(record)
- def dump_log(self, html: bool = False, level: str = 'vdebug') -> str:
+ def dump_log(self, html: bool = False, level: str = 'vdebug',
+ logfilter: LogFilter = None) -> str:
"""Dump the complete formatted log data as string.
FIXME: We should do all the HTML formatting via jinja2.
(probably obsolete when moving to a widget for logging,
https://github.com/qutebrowser/qutebrowser/issues/34
+
+ Args:
+ html: Produce HTML rather than plaintext output.
+ level: The minimal loglevel to show.
+ logfilter: A LogFilter instance used to filter log lines.
"""
minlevel = LOG_LEVELS.get(level.upper(), VDEBUG_LEVEL)
+ if logfilter is None:
+ logfilter = LogFilter(set())
+
if html:
assert self.html_formatter is not None
fmt = self.html_formatter.format
@@ -679,7 +694,8 @@ class RAMHandler(logging.Handler):
try:
lines = [fmt(record)
for record in self._data
- if record.levelno >= minlevel]
+ if record.levelno >= minlevel and
+ logfilter.filter(record)]
finally:
self.release()
return '\n'.join(lines)
diff --git a/tests/end2end/features/qutescheme.feature b/tests/end2end/features/qutescheme.feature
index d41975d78..2325912c5 100644
--- a/tests/end2end/features/qutescheme.feature
+++ b/tests/end2end/features/qutescheme.feature
@@ -258,17 +258,38 @@ Feature: Special qute:// pages
And the page should contain the plaintext "the-warning-message"
And the page should contain the plaintext "the-info-message"
+ Scenario: Showing messages of category 'message'
+ When I run :message-info the-info-message
+ And I run :messages -f message
+ Then qute://log/?level=info&logfilter=message should be loaded
+ And the page should contain the plaintext "the-info-message"
+
+ Scenario: Showing messages of category 'misc'
+ When I run :message-info the-info-message
+ And I run :messages -f misc
+ Then qute://log/?level=info&logfilter=misc should be loaded
+ And the page should not contain the plaintext "the-info-message"
+
@qtwebengine_flaky
Scenario: Showing messages of an invalid level
When I run :messages cataclysmic
Then the error "Invalid log level cataclysmic!" should be shown
+ Scenario: Showing messages with an invalid category
+ When I run :messages -f invalid
+ Then the error "Invalid log category invalid - *" should be shown
+
Scenario: Using qute://log directly
When I open qute://log without waiting
# With Qt 5.9, we don't get a loaded message?
And I wait for "Changing title for idx * to 'log'" in the log
Then no crash should happen
+ # FIXME More possible tests:
+ # :message --plain
+ # Using qute://log directly with invalid category
+ # same with invalid level
+
# :version
Scenario: Open qute://version
diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature
index bfe0035dd..94db7c403 100644
--- a/tests/end2end/features/utilcmds.feature
+++ b/tests/end2end/features/utilcmds.feature
@@ -168,7 +168,7 @@ Feature: Miscellaneous utility commands exposed to the user.
Scenario: Using debug-log-filter with invalid filter
When I run :debug-log-filter blah
- Then the error "filters: Invalid value blah - expected one of: statusbar, *" should be shown
+ Then the error "Invalid log category blah - valid categories: statusbar, *" should be shown
Scenario: Using debug-log-filter
When I run :debug-log-filter commands,ipc,webview
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index 14fa07aeb..8c2e636a1 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -142,7 +142,7 @@ class TestLogFilter:
(Blame @toofar for this comment)
"""
- logfilter = log.LogFilter(filters, negated)
+ logfilter = log.LogFilter(filters, negated=negated)
record = self._make_record(logger, category)
assert logfilter.filter(record) == logged
@@ -152,12 +152,12 @@ class TestLogFilter:
logfilter = log.LogFilter(filters, negated=False)
benchmark(lambda: logfilter.filter(record))
- @pytest.mark.parametrize('category', ['eggs', 'bacon'])
- def test_debug(self, logger, category):
+ @pytest.mark.parametrize('only_debug', [True, False])
+ def test_debug(self, logger, only_debug):
"""Test if messages more important than debug are never filtered."""
- logfilter = log.LogFilter({'eggs'})
- record = self._make_record(logger, category, level=logging.INFO)
- assert logfilter.filter(record)
+ logfilter = log.LogFilter({'eggs'}, only_debug=only_debug)
+ record = self._make_record(logger, 'bacon', level=logging.INFO)
+ assert logfilter.filter(record) == only_debug
@pytest.mark.parametrize('category, filter_str, logged_before, logged_after', [
('init', 'url,js', True, False),
From 290f5ec527c93be4789ff772e23493ea1d56eb82 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 16:54:39 +0200
Subject: [PATCH 208/245] Update changelog
---
doc/changelog.asciidoc | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index f5f039c2e..eaf4f5146 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -38,6 +38,9 @@ Changed
that it can be used without the report window popping up.
- New `t[Cc][Hh]` default bindings which work similarly to the `t[Ss][Hh]`
bindings for JavaScript but toggle cookie permissions.
+- The `:message` command now takes a `--logfilter` / `-f` argument, which is a
+ list of logging categories to show.
+- The `:debug-log-filter` command now understands the full logfilter syntax.
- Changes to settings:
* `fonts.tabs` has been split into `fonts.tabs.{selected,unselected}` (see
below).
@@ -48,6 +51,7 @@ Changed
`fonts.debug_console` is now removed and entirely replaced by `Font`. The
former distinction was mainly an implementation detail, and the accepted
values shouldn't have changed.
+- Small performance improvements.
Added
~~~~~
From 049d51d1d79753509e85087939a88011286c5090 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 17:03:53 +0200
Subject: [PATCH 209/245] Fix lint
---
qutebrowser/browser/qutescheme.py | 2 +-
tests/unit/api/test_cmdutils.py | 1 -
tests/unit/test_qutebrowser.py | 5 ++---
tests/unit/utils/test_log.py | 19 +++++++++++--------
4 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py
index 5e8eb36c5..b661f533d 100644
--- a/qutebrowser/browser/qutescheme.py
+++ b/qutebrowser/browser/qutescheme.py
@@ -337,7 +337,7 @@ def qute_log(url: QUrl) -> _HandlerRet:
try:
logfilter = (log.LogFilter.parse(filter_str, only_debug=False)
- if filter_str else None)
+ if filter_str else None)
except log.InvalidLogFilterError as e:
raise UrlInvalidError(e)
diff --git a/tests/unit/api/test_cmdutils.py b/tests/unit/api/test_cmdutils.py
index 9ca6fc2ae..58643640c 100644
--- a/tests/unit/api/test_cmdutils.py
+++ b/tests/unit/api/test_cmdutils.py
@@ -163,7 +163,6 @@ class TestRegister:
@cmdutils.register()
def fun(*args: int):
"""Blah."""
- pass
def test_star_args_optional(self):
"""Check handling of *args withstar_args_optional."""
diff --git a/tests/unit/test_qutebrowser.py b/tests/unit/test_qutebrowser.py
index 6dbc24351..5a792a6d2 100644
--- a/tests/unit/test_qutebrowser.py
+++ b/tests/unit/test_qutebrowser.py
@@ -22,8 +22,6 @@
(Mainly commandline flag parsing)
"""
-import argparse
-
import pytest
from qutebrowser import qutebrowser
@@ -37,7 +35,8 @@ def parser():
class TestDebugFlag:
def test_valid(self, parser):
- args = parser.parse_args(['--debug-flag', 'chromium', '--debug-flag', 'stack'])
+ args = parser.parse_args(['--debug-flag', 'chromium',
+ '--debug-flag', 'stack'])
assert args.debug_flags == ['chromium', 'stack']
def test_invalid(self, parser, capsys):
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index 8c2e636a1..f73b88b2c 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -159,12 +159,14 @@ class TestLogFilter:
record = self._make_record(logger, 'bacon', level=logging.INFO)
assert logfilter.filter(record) == only_debug
- @pytest.mark.parametrize('category, filter_str, logged_before, logged_after', [
- ('init', 'url,js', True, False),
- ('url', 'url,js', False, True),
- ('js', 'url,js', False, True),
- ('js', 'none', False, True),
- ])
+ @pytest.mark.parametrize(
+ 'category, filter_str, logged_before, logged_after', [
+ ('init', 'url,js', True, False),
+ ('url', 'url,js', False, True),
+ ('js', 'url,js', False, True),
+ ('js', 'none', False, True),
+ ]
+ )
def test_debug_log_filter_cmd(self, monkeypatch, logger, category,
filter_str, logged_before, logged_after):
"""Test the :debug-log-filter command handler."""
@@ -236,8 +238,9 @@ class TestInitLog:
def _get_default_args(self):
return argparse.Namespace(debug=True, loglevel='debug', color=True,
- loglines=10, logfilter=None, force_color=False,
- json_logging=False, debug_flags=set())
+ loglines=10, logfilter=None,
+ force_color=False, json_logging=False,
+ debug_flags=set())
@pytest.fixture(autouse=True)
def setup(self, mocker):
From f9bcda014727f97b3b253a922a155d6499202166 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Thu, 11 Jun 2020 22:20:17 +0200
Subject: [PATCH 210/245] Fix unhandled SelectionUnsupportedError in
:completion-item-yank
---
doc/changelog.asciidoc | 2 ++
qutebrowser/completion/completionwidget.py | 4 ++++
2 files changed, 6 insertions(+)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index eaf4f5146..03d97f87b 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -67,6 +67,8 @@ Fixed
- Crash when `tabs.focus_stack_size` is set to -1.
- Crash when a `pdf.js` file for PDF.js exists, but `viewer.html` does not.
+- Crash when `:completion-item-yank --sel` is used on a platform without
+ primary selection support (e.g. Windows/macOS).
- `:config-write-py` now works with paths starting with `~/...` again.
- New site-specific quirk for a missing `globalThis` in Qt <= 5.12 on Reddit and Spotify
diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py
index b9be0bd9d..26fbcdf4f 100644
--- a/qutebrowser/completion/completionwidget.py
+++ b/qutebrowser/completion/completionwidget.py
@@ -424,4 +424,8 @@ class CompletionView(QTreeView):
if not index.isValid():
raise cmdutils.CommandError("No item selected!")
text = self.model().data(index)
+
+ if not utils.supports_selection():
+ sel = False
+
utils.set_clipboard(text, selection=sel)
From 24f7b1f36977033058ceebd73aa2af8bed2c1320 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 12 Jun 2020 12:31:05 +0200
Subject: [PATCH 211/245] requirements: Remove lxml from dev requirements
d805e2d71e8e54428aae9a814a898e4ba7004118 removed lxml in check_coverage.py,
5e64aae70be25e8871846af15ff92cfce7a816d9 removed ua_fetch.py
Thus, no lxml usages are remaining.
---
misc/requirements/requirements-dev.txt | 1 -
misc/requirements/requirements-dev.txt-raw | 1 -
2 files changed, 2 deletions(-)
diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt
index 2e7939deb..cf4d246f4 100644
--- a/misc/requirements/requirements-dev.txt
+++ b/misc/requirements/requirements-dev.txt
@@ -11,7 +11,6 @@ github3.py==1.3.0
hunter==3.1.3
idna==2.9
jwcrypto==0.7
-lxml==4.5.1
manhole==1.6.0
packaging==20.4
pycparser==2.20
diff --git a/misc/requirements/requirements-dev.txt-raw b/misc/requirements/requirements-dev.txt-raw
index f75a837af..71e19f502 100644
--- a/misc/requirements/requirements-dev.txt-raw
+++ b/misc/requirements/requirements-dev.txt-raw
@@ -4,5 +4,4 @@ pympler
github3.py
bump2version
requests
-lxml
pyqt-builder
From f07c7043673970426fe903c4c1927ce0cd46f27f Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 12 Jun 2020 12:40:12 +0200
Subject: [PATCH 212/245] Improve some init logging
---
qutebrowser/app.py | 4 +++-
qutebrowser/browser/webkit/network/networkmanager.py | 9 +++++----
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index abb6dfa76..c90de481e 100644
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -485,7 +485,9 @@ class Application(QApplication):
self._last_focus_object = None
qt_args = configinit.qt_args(args)
- log.init.debug("Qt arguments: {}, based on {}".format(qt_args, args))
+ log.init.debug("Commandline args: {}".format(sys.argv[1:]))
+ log.init.debug("Parsed: {}".format(args))
+ log.init.debug("Qt arguments: {}".format(qt_args[1:]))
super().__init__(qt_args)
objects.args = args
diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py
index 1662f84fc..954c92a89 100644
--- a/qutebrowser/browser/webkit/network/networkmanager.py
+++ b/qutebrowser/browser/webkit/network/networkmanager.py
@@ -105,7 +105,7 @@ def _is_secure_cipher(cipher):
def init():
"""Disable insecure SSL ciphers on old Qt versions."""
default_ciphers = QSslSocket.defaultCiphers()
- log.init.debug("Default Qt ciphers: {}".format(
+ log.init.vdebug("Default Qt ciphers: {}".format(
', '.join(c.name() for c in default_ciphers)))
good_ciphers = []
@@ -116,9 +116,10 @@ def init():
else:
bad_ciphers.append(cipher)
- log.init.debug("Disabling bad ciphers: {}".format(
- ', '.join(c.name() for c in bad_ciphers)))
- QSslSocket.setDefaultCiphers(good_ciphers)
+ if bad_ciphers:
+ log.init.debug("Disabling bad ciphers: {}".format(
+ ', '.join(c.name() for c in bad_ciphers)))
+ QSslSocket.setDefaultCiphers(good_ciphers)
_SavedErrorsType = typing.MutableMapping[urlutils.HostTupleType,
From 2880e8215b1bfda747f8cca40958cd6990d8211b Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 12 Jun 2020 13:53:46 +0200
Subject: [PATCH 213/245] Adjust version number in session warning
---
qutebrowser/html/warning-sessions.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qutebrowser/html/warning-sessions.html b/qutebrowser/html/warning-sessions.html
index dd0c4127b..0c6622df6 100644
--- a/qutebrowser/html/warning-sessions.html
+++ b/qutebrowser/html/warning-sessions.html
@@ -9,7 +9,7 @@ qute://warning/sessions to show it again at a later time.
Since Qt doesn't provide an API to load the history of a tab, qutebrowser relies on a reverse-engineered binary serialization format to load tab history from session files. With Qt 5.15, unfortunately that format changed (due to the underlying Chromium upgrade), in a way which makes it impossible for qutebrowser to load tab history from existing session data.
-At the time of writing (April 2020), a new session format which stores part of the needed binary data in saved sessions is in development and will be released with qutebrowser v1.12.0.
+At the time of writing (April 2020), a new session format which stores part of the needed binary data in saved sessions is in development and is expected to be released with qutebrowser v1.13.0.
As a stop-gap measure:
From 0c6c566996372df13c8d846493d3008d0ec40af2 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Fri, 12 Jun 2020 23:43:45 +0200
Subject: [PATCH 214/245] configdata: Remove comment about
darkModeClassifierType
My analysis was correct and the setting now got removed from Chromium:
https://chromium-review.googlesource.com/c/chromium/src/+/2169489
Also see some other refactorings in the linked bug report:
https://bugs.chromium.org/p/chromium/issues/detail?id=1091095
See #5394
---
qutebrowser/config/configdata.yml | 5 -----
1 file changed, 5 deletions(-)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index cbf18660e..12c0d8e0b 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -2619,11 +2619,6 @@ colors.webpage.prefers_color_scheme_dark:
## dark mode
-# darkModeClassifierType is not exposed, as the icon classifier isn't actually
-# implemented in Chromium:
-#
-# https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/graphics/dark_mode_icon_classifier.cc
-
colors.webpage.darkmode.enabled:
default: false
type: Bool
From 306eff2f94a665b4e41ea4dd5604a604283f8dba Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Sat, 13 Jun 2020 00:19:01 +0200
Subject: [PATCH 215/245] Improve lost focusproxy handling
Disable the workaround if not on Qt 5.11, don't log if nothing was found, and
clean up the code a bit.
---
qutebrowser/browser/webengine/webview.py | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py
index 2b197323b..be507c8b6 100644
--- a/qutebrowser/browser/webengine/webview.py
+++ b/qutebrowser/browser/webengine/webview.py
@@ -66,20 +66,26 @@ class WebEngineView(QWebEngineView):
However, it sometimes isn't, so we use this as a WORKAROUND for
https://bugreports.qt.io/browse/QTBUG-68727
- This got introduced in Qt 5.11.0 and fixed in 5.12.0.
+ The above bug got introduced in Qt 5.11.0 and fixed in 5.12.0.
"""
- if 'lost-focusproxy' not in objects.debug_flags:
- proxy = self.focusProxy()
- if proxy is not None:
- return proxy
+ proxy = self.focusProxy()
+
+ if 'lost-focusproxy' in objects.debug_flags:
+ proxy = None
+
+ if (proxy is not None or
+ not qtutils.version_check('5.11', compiled=False) or
+ qtutils.version_check('5.12', compiled=False)):
+ return proxy
# We don't want e.g. a QMenu.
rwhv_class = 'QtWebEngineCore::RenderWidgetHostViewQtDelegateWidget'
children = [c for c in self.findChildren(QWidget)
if c.isVisible() and c.inherits(rwhv_class)]
- log.webview.debug("Found possibly lost focusProxy: {}"
- .format(children))
+ if children:
+ log.webview.debug("Found possibly lost focusProxy: {}"
+ .format(children))
return children[-1] if children else None
From 6d8553af3a58687811fce1b1f48cd4d0ec73be42 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Sat, 13 Jun 2020 00:40:08 +0200
Subject: [PATCH 216/245] Fix lint
---
qutebrowser/browser/webengine/webview.py | 6 ++++--
qutebrowser/browser/webkit/network/networkmanager.py | 5 +++--
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py
index be507c8b6..9f2984f8d 100644
--- a/qutebrowser/browser/webengine/webview.py
+++ b/qutebrowser/browser/webengine/webview.py
@@ -19,6 +19,8 @@
"""The main browser widget for QtWebEngine."""
+import typing
+
from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QWidget
@@ -68,7 +70,7 @@ class WebEngineView(QWebEngineView):
The above bug got introduced in Qt 5.11.0 and fixed in 5.12.0.
"""
- proxy = self.focusProxy()
+ proxy = self.focusProxy() # type: typing.Optional[QWidget]
if 'lost-focusproxy' in objects.debug_flags:
proxy = None
@@ -85,7 +87,7 @@ class WebEngineView(QWebEngineView):
if children:
log.webview.debug("Found possibly lost focusProxy: {}"
- .format(children))
+ .format(children))
return children[-1] if children else None
diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py
index 954c92a89..0f5063cfb 100644
--- a/qutebrowser/browser/webkit/network/networkmanager.py
+++ b/qutebrowser/browser/webkit/network/networkmanager.py
@@ -105,8 +105,9 @@ def _is_secure_cipher(cipher):
def init():
"""Disable insecure SSL ciphers on old Qt versions."""
default_ciphers = QSslSocket.defaultCiphers()
- log.init.vdebug("Default Qt ciphers: {}".format(
- ', '.join(c.name() for c in default_ciphers)))
+ log.init.vdebug( # type: ignore[attr-defined]
+ "Default Qt ciphers: {}".format(
+ ', '.join(c.name() for c in default_ciphers)))
good_ciphers = []
bad_ciphers = []
From 8add93f49b01b13bca32f036825cff2ed8fa6ad6 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 15 Jun 2020 15:57:38 +0200
Subject: [PATCH 217/245] Add note about colors.webpage.darkmode.policy.images
on Qt 5.15.0
See #5505
---
doc/help/settings.asciidoc | 1 +
qutebrowser/config/configdata.yml | 7 ++++++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 466b0ec1b..725611a69 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -1643,6 +1643,7 @@ On QtWebKit, this setting is unavailable.
[[colors.webpage.darkmode.policy.images]]
=== colors.webpage.darkmode.policy.images
Which images to apply dark mode to.
+WARNING: On Qt 5.15.0, this setting can cause frequent renderer process crashes due to a https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/304211[bug in Qt].
This setting requires a restart.
Type: <>
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 12c0d8e0b..40cf9fa0b 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -2690,7 +2690,12 @@ colors.webpage.darkmode.policy.images:
- always: Apply dark mode filter to all images.
- never: Never apply dark mode filter to any images.
- smart: Apply dark mode based on image content.
- desc: Which images to apply dark mode to.
+ desc: >-
+ Which images to apply dark mode to.
+
+ WARNING: On Qt 5.15.0, this setting can cause frequent renderer process
+ crashes due to a
+ https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/304211[bug in Qt].
restart: true
backend:
QtWebEngine: Qt 5.14
From ed8de2786be659dedaee95d3bef786bd57116435 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 15 Jun 2020 19:03:28 +0200
Subject: [PATCH 218/245] Fix exception with invalid feature permission URLs
---
doc/changelog.asciidoc | 2 ++
qutebrowser/browser/webengine/webenginetab.py | 10 +++++++++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 03d97f87b..1c76fff27 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -69,6 +69,8 @@ Fixed
- Crash when a `pdf.js` file for PDF.js exists, but `viewer.html` does not.
- Crash when `:completion-item-yank --sel` is used on a platform without
primary selection support (e.g. Windows/macOS).
+- Crash when there's a feature permission request from Qt with an invalid URL
+ (which seems to happen with Qt 5.15 sometimes).
- `:config-write-py` now works with paths starting with `~/...` again.
- New site-specific quirk for a missing `globalThis` in Qt <= 5.12 on Reddit and Spotify
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index 85190e1c7..683b2c1fd 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -969,9 +969,17 @@ class _WebEnginePermissions(QObject):
page.setFeaturePermission, url, feature,
QWebEnginePage.PermissionDeniedByUser)
+ permission_str = debug.qenum_key(QWebEnginePage, feature)
+
+ if not url.isValid():
+ log.webview.warning("Ignoring feature permission {} for invalid "
+ "URL {}".format(permission_str, url))
+ deny_permission()
+ return
+
if feature not in self._options:
log.webview.error("Unhandled feature permission {}".format(
- debug.qenum_key(QWebEnginePage, feature)))
+ permission_str))
deny_permission()
return
From e39b2335d6a376633f28cb80d590f44cd4775bcb Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 15 Jun 2020 20:24:13 +0200
Subject: [PATCH 219/245] Create codeql-analysis.yml
---
.github/workflows/codeql-analysis.yml | 51 +++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
create mode 100644 .github/workflows/codeql-analysis.yml
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 000000000..da885d80b
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,51 @@
+name: "Code scanning - action"
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: '0 3 * * 1'
+
+jobs:
+ CodeQL-Build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ with:
+ # We must fetch at least the immediate parents so that if this is
+ # a pull request then we can checkout the head.
+ fetch-depth: 2
+
+ # If this run was triggered by a pull request event, then checkout
+ # the head of the pull request instead of the merge commit.
+ - run: git checkout HEAD^2
+ if: ${{ github.event_name == 'pull_request' }}
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ # Override language selection by uncommenting this and choosing your languages
+ # with:
+ # languages: go, javascript, csharp, python, cpp, java
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
From 7cbc447c55152aeeb06606f03f32e7f9ad161f83 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Mon, 15 Jun 2020 20:27:14 +0200
Subject: [PATCH 220/245] Code scanning: Exclude C++
---
.github/workflows/codeql-analysis.yml | 24 +++---------------------
1 file changed, 3 insertions(+), 21 deletions(-)
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index da885d80b..5de8a8726 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -1,4 +1,4 @@
-name: "Code scanning - action"
+name: "Code scanning"
on:
push:
@@ -24,28 +24,10 @@ jobs:
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
- # Override language selection by uncommenting this and choosing your languages
- # with:
- # languages: go, javascript, csharp, python, cpp, java
-
- # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
- # If this step fails, then you should remove it and run the build manually (see below)
- - name: Autobuild
- uses: github/codeql-action/autobuild@v1
-
- # ℹ️ Command-line programs to run using the OS shell.
- # 📚 https://git.io/JvXDl
-
- # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
- # and modify them (or add more) to build your code if your project
- # uses a compiled language
-
- #- run: |
- # make bootstrap
- # make release
+ with:
+ languages: javascript, python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
From 267fb64fcb2f3324cfedeafc198b31bbfcee1e68 Mon Sep 17 00:00:00 2001
From: Nicholas Lantz
Date: Mon, 15 Jun 2020 16:07:34 -0600
Subject: [PATCH 221/245] Renamed a couple of mouse settings.
Changed:
- input.mouse_backforward to input.mouse.back_forward_buttons
- input.rocker_gestures to input.mouse.rocker_gestures
---
doc/help/settings.asciidoc | 26 +++++++++++++-------------
qutebrowser/browser/eventfilter.py | 6 +++---
qutebrowser/config/configdata.yml | 17 ++++++++++-------
3 files changed, 26 insertions(+), 23 deletions(-)
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 9122571f3..4d68cf3a7 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -252,9 +252,9 @@
|<>|Leave insert mode when starting a new page load.
|<>|Switch to insert mode when clicking flash and other plugins.
|<>|Include hyperlinks in the keyboard focus chain when tabbing.
-|<>|Enable back and forward buttons on the mouse.
+|<>|Enable back and forward buttons on the mouse.
+|<>|Enable Opera-like mouse rocker gestures.
|<>|Timeout (in milliseconds) for partially typed key bindings.
-|<>|Enable Opera-like mouse rocker gestures.
|<>|Enable spatial navigation.
|<>|Keychains that shouldn't be shown in the keyhint dialog.
|<>|Time (in milliseconds) from pressing a key to seeing the keyhint dialog.
@@ -3213,14 +3213,23 @@ Type: <>
Default: +pass:[true]+
-[[input.mouse_backforward]]
-=== input.mouse_backforward
+[[input.mouse.back_forward_buttons]]
+=== input.mouse.back_forward_buttons
Enable back and forward buttons on the mouse.
Type: <>
Default: +pass:[true]+
+[[input.mouse.rocker_gestures]]
+=== input.mouse.rocker_gestures
+Enable Opera-like mouse rocker gestures.
+This disables the context menu.
+
+Type: <>
+
+Default: +pass:[false]+
+
[[input.partial_timeout]]
=== input.partial_timeout
Timeout (in milliseconds) for partially typed key bindings.
@@ -3230,15 +3239,6 @@ Type: <>
Default: +pass:[5000]+
-[[input.rocker_gestures]]
-=== input.rocker_gestures
-Enable Opera-like mouse rocker gestures.
-This disables the context menu.
-
-Type: <>
-
-Default: +pass:[false]+
-
[[input.spatial_navigation]]
=== input.spatial_navigation
Enable spatial navigation.
diff --git a/qutebrowser/browser/eventfilter.py b/qutebrowser/browser/eventfilter.py
index d78b608a5..9e93fd13f 100644
--- a/qutebrowser/browser/eventfilter.py
+++ b/qutebrowser/browser/eventfilter.py
@@ -116,7 +116,7 @@ class TabEventFilter(QObject):
Return:
True if the event should be filtered, False otherwise.
"""
- is_rocker_gesture = (config.val.input.rocker_gestures and
+ is_rocker_gesture = (config.val.input.mouse.rocker_gestures and
e.buttons() == Qt.LeftButton | Qt.RightButton)
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
@@ -204,7 +204,7 @@ class TabEventFilter(QObject):
Return:
True if the event should be filtered, False otherwise.
"""
- return config.val.input.rocker_gestures
+ return config.val.input.mouse.rocker_gestures
def _handle_key_release(self, e):
"""Ignore repeated key release events going to the website.
@@ -276,7 +276,7 @@ class TabEventFilter(QObject):
Return:
True if the event should be filtered, False otherwise.
"""
- if (not config.val.input.mouse_backforward and
+ if (not config.val.input.mouse.back_forward_buttons and
e.button() in [Qt.XButton1, Qt.XButton2]):
# Back and forward on mice are disabled
return
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 15a687283..67d2eef5f 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -1379,11 +1379,19 @@ input.links_included_in_focus_chain:
supports_pattern: true
desc: Include hyperlinks in the keyboard focus chain when tabbing.
-input.mouse_backforward:
+input.mouse.back_forward_buttons:
default: true
type: Bool
desc: Enable back and forward buttons on the mouse.
+input.mouse.rocker_gestures:
+ default: false
+ type: Bool
+ desc: >-
+ Enable Opera-like mouse rocker gestures.
+
+ This disables the context menu.
+
input.partial_timeout:
default: 5000
type:
@@ -1397,12 +1405,7 @@ input.partial_timeout:
cleared after this time.
input.rocker_gestures:
- default: false
- type: Bool
- desc: >-
- Enable Opera-like mouse rocker gestures.
-
- This disables the context menu.
+ renamed: input.mouse.rocker_gestures
input.spatial_navigation:
default: false
From 590b73015bdf15ec181a7c53e82f7641cd545c33 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 16 Jun 2020 16:28:07 +0200
Subject: [PATCH 222/245] Rename urlmarks.py to test_urlmarks.py
---
tests/unit/browser/{urlmarks.py => test_urlmarks.py} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename tests/unit/browser/{urlmarks.py => test_urlmarks.py} (100%)
diff --git a/tests/unit/browser/urlmarks.py b/tests/unit/browser/test_urlmarks.py
similarity index 100%
rename from tests/unit/browser/urlmarks.py
rename to tests/unit/browser/test_urlmarks.py
From 2e12ba21afcba7989e574a4819929bf2bdbe0181 Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 16 Jun 2020 17:19:32 +0200
Subject: [PATCH 223/245] Improve QtWebKit/QtWebEngine import checks
This handles two cases found in crash reports:
- "AssertionError: qWebKitVersion is not None" when QtWebKit itself is
importable but qWebKitVersion is not. Fixes #5484.
- "ValueError: PyCapsule_GetPointer called with incorrect name" when importing
QtWebKit - no idea what's going on here exactly.
Note that there might be other cases in the code where those imports are
attempted and only ImportError is handled (because ValueError really shouldn't
happen there...) - but we can't really track them down until we see more crash
reports with the same issue.
---
doc/changelog.asciidoc | 2 ++
qutebrowser/misc/backendproblem.py | 5 +++--
qutebrowser/utils/version.py | 2 +-
3 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 1c76fff27..3f8a48b27 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -71,6 +71,8 @@ Fixed
primary selection support (e.g. Windows/macOS).
- Crash when there's a feature permission request from Qt with an invalid URL
(which seems to happen with Qt 5.15 sometimes).
+- Crash in rare cases where QtWebKit/QtWebEngine imports fail in unexpected
+ ways.
- `:config-write-py` now works with paths starting with `~/...` again.
- New site-specific quirk for a missing `globalThis` in Qt <= 5.12 on Reddit and Spotify
diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py
index 6e2bd1866..089e3191f 100644
--- a/qutebrowser/misc/backendproblem.py
+++ b/qutebrowser/misc/backendproblem.py
@@ -362,8 +362,9 @@ class _BackendProblemChecker:
try:
from PyQt5 import QtWebKit
+ from PyQt5.QtWebKit import qWebKitVersion
from PyQt5 import QtWebKitWidgets
- except ImportError as e:
+ except (ImportError, ValueError) as e:
results.webkit_available = False
results.webkit_error = str(e)
else:
@@ -375,7 +376,7 @@ class _BackendProblemChecker:
try:
from PyQt5 import QtWebEngineWidgets
- except ImportError as e:
+ except (ImportError, ValueError) as e:
results.webengine_available = False
results.webengine_error = str(e)
else:
diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
index c0ff7e1ac..75c6f6ede 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -234,7 +234,7 @@ def _module_versions() -> typing.Sequence[str]:
for modname, attributes in modules.items():
try:
module = importlib.import_module(modname)
- except ImportError:
+ except (ImportError, ValueError):
text = '{}: no'.format(modname)
else:
for name in attributes:
From 06e23f44f8f1558b21723c9279870ad862465bce Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 16 Jun 2020 17:30:42 +0200
Subject: [PATCH 224/245] tests: Stabilize test_qute_settings_persistence for
real
Before, the "Config option changed: *" triggered on:
Config option changed: content.headers.user_agent = ...
https://github.com/qutebrowser/qutebrowser/issues/5390#issuecomment-628773737
---
tests/end2end/test_invocations.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py
index a31494634..b4a343a37 100644
--- a/tests/end2end/test_invocations.py
+++ b/tests/end2end/test_invocations.py
@@ -382,7 +382,8 @@ def test_qute_settings_persistence(short_tmpdir, request, quteproc_new):
'cset("search.ignore_case", "always")')
quteproc_new.wait_for(message='No output or error')
quteproc_new.wait_for(category='config',
- message='Config option changed: *')
+ message='Config option changed: '
+ 'search.ignore_case = always')
assert quteproc_new.get_setting('search.ignore_case') == 'always'
From ef0482cabbf07952b164ebd6ad252d42d45102dc Mon Sep 17 00:00:00 2001
From: Florian Bruhin
Date: Tue, 16 Jun 2020 17:36:07 +0200
Subject: [PATCH 225/245] Update tests/manual/mouse.html
---
tests/manual/mouse.html | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/tests/manual/mouse.html b/tests/manual/mouse.html
index eb75df44d..d1f0f7dee 100644
--- a/tests/manual/mouse.html
+++ b/tests/manual/mouse.html
@@ -10,7 +10,8 @@
When clicking the link with shift, tabs.background should be reversed accordingly.
Ctrl + Mousewheel should zoom in/out
Back/forward keys on mouse should navigate back/forward
- With input.rocker_gestures set, no context menu should be shown, but pressing left+right/right+left buttons should navigate back/forward
- When setting input.rocker_gestures dynamically, the context menu should be hidden/shown accordingly.
+ If input.mouse.back_forward_buttons is set to false, those buttons should not have any effect
+ With input.mouse.rocker_gestures set, no context menu should be shown, but pressing left+right/right+left buttons should navigate back/forward
+ When setting input.mouse.rocker_gestures dynamically, the context menu should be hidden/shown accordingly.