From 9063229f4dd2d3cce2ff34ae1909fd653c6cd6aa Mon Sep 17 00:00:00 2001 From: Markus Blöchl Date: Sat, 23 Jan 2021 17:28:13 +0100 Subject: Add userscript qute-keepassxc --- misc/userscripts/README.md | 2 + misc/userscripts/qute-keepassxc | 365 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 367 insertions(+) create mode 100755 misc/userscripts/qute-keepassxc diff --git a/misc/userscripts/README.md b/misc/userscripts/README.md index 938dd776d..395797805 100644 --- a/misc/userscripts/README.md +++ b/misc/userscripts/README.md @@ -17,6 +17,8 @@ The following userscripts are included in the current directory. current website. - [qute-keepass](./qute-keepass): Insertion of usernames and passwords from keepass databases using pykeepass. +- [qute-keepassxc](./qute-keepassxc): Insert credentials from open KeepassXC database + using keepassxc-browser protocol. - [qute-pass](./qute-pass): Insert login information using pass and a dmenu-compatible application (e.g. dmenu, rofi -dmenu, ...). - [qute-lastpass](./qute-lastpass): Similar to qute-pass, for Lastpass. diff --git a/misc/userscripts/qute-keepassxc b/misc/userscripts/qute-keepassxc new file mode 100755 index 000000000..b5da1fb2a --- /dev/null +++ b/misc/userscripts/qute-keepassxc @@ -0,0 +1,365 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2018-2021 Markus Blöchl +# +# 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 . + +""" +Introduction +============ + +This is a [qutebrowser][2] [userscript][5] to fill website credentials from a [KeepassXC][1] password database. + + +Installation +============ + +First, you need to enable [KeepassXC-Browser][6] extensions in your KeepassXC config. + + +Second, you must make sure to have a working private-public-key-pair in your [GPG keyring][3]. + + +Third, install the python module `pynacl`. + + +Finally, adapt your qutebrowser config. +You can e.g. add the following lines to your `~/.config/qutebrowser/config.py` +Remember to replace `ABC1234` with your actual GPG key. + +```python +config.bind('', 'spawn --userscript qute-keepassxc --key ABC1234', mode='insert') +config.bind('pw', 'spawn --userscript qute-keepassxc --key ABC1234', mode='normal') +``` + + +Usage +===== + +If you are on a webpage with a login form, simply activate one of the configured key-bindings. + +The first time you run this script, KeepassXC will ask you for authentication like with any other browser extension. +Just provide a name of your choice and accept the request if nothing looks fishy. + + +How it works +============ + +This script will talk to KeepassXC using the native [KeepassXC-Browser protocol][4]. + + +This script needs to store the key used to associate with your KeepassXC instance somewhere. +Unlike most browser extensions which only use plain local storage, this one attempts to do so in a safe way +by storing the key in encrypted form using GPG. +Therefore you need to have a public-key-pair readily set up. + +GPG might then ask for your private-key passwort whenever you query the database for login credentials. + + +[1]: https://keepassxc.org/ +[2]: https://qutebrowser.org/ +[3]: https://gnupg.org/ +[4]: https://github.com/keepassxreboot/keepassxc-browser/blob/develop/keepassxc-protocol.md +[5]: https://github.com/qutebrowser/qutebrowser/blob/master/doc/userscripts.asciidoc +[6]: https://keepassxc.org/docs/keepassxc-browser-migration/ +""" + +import sys +import os +import socket +import json +import base64 +import subprocess +import argparse + +import nacl.utils +import nacl.public + + +def parse_args(): + parser = argparse.ArgumentParser(description="Full passwords from KeepassXC") + parser.add_argument('url', nargs='?', default=os.environ.get('QUTE_URL')) + parser.add_argument('--socket', '-s', default='/run/user/{}/org.keepassxc.KeePassXC.BrowserServer'.format(os.getuid()), + help='Path to KeepassXC browser socket') + parser.add_argument('--key', '-k', default='alice@example.com', + help='GPG key to encrypt KeepassXC auth key with') + parser.add_argument('--insecure', action='store_true', + help="Do not encrypt auth key") + return parser.parse_args() + + +class KeepassError(Exception): + def __init__(self, code, desc): + self.code = code + self.description = desc + + def __str__(self): + return f"KeepassXC Error [{self.code}]: {self.description}" + + +class KeepassXC: + """ Wrapper around the KeepassXC socket API """ + def __init__(self, id=None, *, key, socket_path): + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.id = id + self.socket_path = socket_path + self.client_key = nacl.public.PrivateKey.generate() + self.id_key = nacl.public.PrivateKey.from_seed(key) + self.cryptobox = None + + def connect(self): + if not os.path.exists(self.socket_path): + raise KeepassError(-1, "KeepassXC Browser socket does not exists") + self.client_id = base64.b64encode(nacl.utils.random(nacl.public.Box.NONCE_SIZE)).decode('utf-8') + self.sock.connect(self.socket_path) + + self.send_raw_msg(dict( + action = 'change-public-keys', + publicKey = base64.b64encode(self.client_key.public_key.encode()).decode('utf-8'), + nonce = base64.b64encode(nacl.utils.random(nacl.public.Box.NONCE_SIZE)).decode('utf-8'), + clientID = self.client_id + )) + + resp = self.recv_raw_msg() + assert resp['action'] == 'change-public-keys' + assert resp['success'] == 'true' + assert resp['nonce'] + self.cryptobox = nacl.public.Box( + self.client_key, + nacl.public.PublicKey(base64.b64decode(resp['publicKey'])) + ) + + def get_databasehash(self): + self.send_msg(dict(action='get-databasehash')) + return self.recv_msg()['hash'] + + def lock_database(self): + self.send_msg(dict(action='lock-database')) + try: + self.recv_msg() + except KeepassError as e: + if e.code == 1: + return True + raise + return False + + + def test_associate(self): + if not self.id: + return False + self.send_msg(dict( + action = 'test-associate', + id = self.id, + key = base64.b64encode(self.id_key.public_key.encode()).decode('utf-8') + )) + return self.recv_msg()['success'] == 'true' + + def associate(self): + self.send_msg(dict( + action = 'associate', + key = base64.b64encode(self.client_key.public_key.encode()).decode('utf-8'), + idKey = base64.b64encode(self.id_key.public_key.encode()).decode('utf-8') + )) + resp = self.recv_msg() + self.id = resp['id'] + + def get_logins(self, url): + self.send_msg(dict( + action = 'get-logins', + url = url, + keys = [{ 'id': self.id, 'key': base64.b64encode(self.id_key.public_key.encode()).decode('utf-8') }] + )) + return self.recv_msg()['entries'] + + def send_raw_msg(self, msg): + self.sock.send( json.dumps(msg).encode('utf-8') ) + + def recv_raw_msg(self): + return json.loads( self.sock.recv(4096).decode('utf-8') ) + + def send_msg(self, msg, **extra): + nonce = nacl.utils.random(nacl.public.Box.NONCE_SIZE) + self.send_raw_msg(dict( + action = msg['action'], + message = base64.b64encode(self.cryptobox.encrypt(json.dumps(msg).encode('utf-8'), nonce).ciphertext).decode('utf-8'), + nonce = base64.b64encode(nonce).decode('utf-8'), + clientID = self.client_id, + **extra + )) + + def recv_msg(self): + resp = self.recv_raw_msg() + if 'error' in resp: + raise KeepassError(resp['errorCode'], resp['error']) + assert resp['action'] + return json.loads(self.cryptobox.decrypt(base64.b64decode(resp['message']), base64.b64decode(resp['nonce'])).decode('utf-8')) + + + +class SecretKeyStore: + def __init__(self, gpgkey): + self.gpgkey = gpgkey + if gpgkey is None: + self.path = os.path.join(os.environ['QUTE_DATA_DIR'], 'keepassxc.key') + else: + self.path = os.path.join(os.environ['QUTE_DATA_DIR'], 'keepassxc.key.gpg') + + def load(self): + "Load existing association key from file" + if self.gpgkey is None: + jsondata = open(self.path, 'r').read() + else: + jsondata = subprocess.check_output(['gpg', '--decrypt', self.path]).decode('utf-8') + data = json.loads(jsondata) + self.id = data['id'] + self.key = base64.b64decode(data['key']) + + def create(self): + "Create new association key" + self.key = nacl.utils.random(32) + self.id = None + + def store(self, id): + "Store newly created association key in file" + self.id = id + jsondata = json.dumps({'id':self.id, 'key':base64.b64encode(self.key).decode('utf-8')}) + if self.gpgkey is None: + open(self.path, "w").write(jsondata) + else: + subprocess.run(['gpg', '--encrypt', '-o', self.path, '-r', self.gpgkey], input=jsondata.encode('utf-8'), check=True) + + +def qute(cmd): + with open(os.environ['QUTE_FIFO'], 'w') as fifo: + fifo.write(cmd) + fifo.write('\n') + fifo.flush() + +def error(msg): + print(msg, file=sys.stderr) + qute('message-error "{}"'.format(msg)) + + +def connect_to_keepassxc(args): + assert args.key or args.insecure, "Missing GPG key to use for auth key encryption" + keystore = SecretKeyStore(args.key) + if os.path.isfile(keystore.path): + keystore.load() + kp = KeepassXC(keystore.id, key=keystore.key, socket_path=args.socket) + kp.connect() + if not kp.test_associate(): + error('No KeepassXC association') + return None + else: + keystore.create() + kp = KeepassXC(key=keystore.key, socket_path=args.socket) + kp.connect() + kp.associate() + if not kp.test_associate(): + error('No KeepassXC association') + return None + keystore.store(kp.id) + return kp + + +def make_js_code(username, password): + return ' '.join(""" + function isVisible(elem) { + var style = elem.ownerDocument.defaultView.getComputedStyle(elem, null); + + if (style.getPropertyValue("visibility") !== "visible" || + style.getPropertyValue("display") === "none" || + style.getPropertyValue("opacity") === "0") { + return false; + } + + return elem.offsetWidth > 0 && elem.offsetHeight > 0; + }; + + function hasPasswordField(form) { + var inputs = form.getElementsByTagName("input"); + for (var j = 0; j < inputs.length; j++) { + var input = inputs[j]; + if (input.type === "password") { + return true; + } + } + return false; + }; + + function loadData2Form (form) { + var inputs = form.getElementsByTagName("input"); + for (var j = 0; j < inputs.length; j++) { + var input = inputs[j]; + if (isVisible(input) && (input.type === "text" || input.type === "email")) { + input.focus(); + input.value = %s; + input.dispatchEvent(new Event('input', { 'bubbles': true })); + input.dispatchEvent(new Event('change', { 'bubbles': true })); + input.blur(); + } + if (input.type === "password") { + input.focus(); + input.value = %s; + input.dispatchEvent(new Event('input', { 'bubbles': true })); + input.dispatchEvent(new Event('change', { 'bubbles': true })); + input.blur(); + } + } + }; + + function fillFirstForm() { + var forms = document.getElementsByTagName("form"); + for (i = 0; i < forms.length; i++) { + if (hasPasswordField(forms[i])) { + loadData2Form(forms[i]); + return; + } + } + alert("No Credentials Form found"); + }; + + fillFirstForm() + """.splitlines()) % (json.dumps(username), json.dumps(password)) + + +def main(): + if 'QUTE_FIFO' not in os.environ: + print(f"No QUTE_FIFO found - {sys.argv[0]} must be run as a qutebrowser userscript") + sys.exit(-1) + + try: + args = parse_args() + assert args.url, "Missing URL" + kp = connect_to_keepassxc(args) + if not kp: + error('Could not connect to KeepassXC') + return + creds = kp.get_logins(args.url) + if not creds: + error('No credentials found') + return + # TODO: handle multiple matches + name, pw = creds[0]['login'], creds[0]['password'] + if name and pw: + qute('jseval -q ' + make_js_code(name, pw)) + except Exception as e: + error(str(e)) + + +if __name__ == '__main__': + main() + -- cgit v1.2.3-54-g00ecf From 62afe97f99e47aa24e1f6760a89aa5f21f78c719 Mon Sep 17 00:00:00 2001 From: Markus Blöchl Date: Mon, 25 Jan 2021 22:48:45 +0100 Subject: Change inline markdown formatting in qute-keepassxc --- misc/userscripts/qute-keepassxc | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/misc/userscripts/qute-keepassxc b/misc/userscripts/qute-keepassxc index b5da1fb2a..6e608455e 100755 --- a/misc/userscripts/qute-keepassxc +++ b/misc/userscripts/qute-keepassxc @@ -18,14 +18,12 @@ # along with qutebrowser. If not, see . """ -Introduction -============ +# Introduction This is a [qutebrowser][2] [userscript][5] to fill website credentials from a [KeepassXC][1] password database. -Installation -============ +# Installation First, you need to enable [KeepassXC-Browser][6] extensions in your KeepassXC config. @@ -46,8 +44,7 @@ config.bind('pw', 'spawn --userscript qute-keepassxc --key ABC1234', mode='norma ``` -Usage -===== +# Usage If you are on a webpage with a login form, simply activate one of the configured key-bindings. @@ -55,8 +52,7 @@ The first time you run this script, KeepassXC will ask you for authentication li Just provide a name of your choice and accept the request if nothing looks fishy. -How it works -============ +# How it works This script will talk to KeepassXC using the native [KeepassXC-Browser protocol][4]. -- cgit v1.2.3-54-g00ecf From 4b61a125db1face5f5d048c7c85ea93d6d986501 Mon Sep 17 00:00:00 2001 From: Axel Dahlberg Date: Mon, 1 Feb 2021 14:31:21 +0100 Subject: Implemented option for file picker to write to stdout instead of temporary file if {} is absent from command --- qutebrowser/browser/shared.py | 61 +++++++++++++++++++++++-------- qutebrowser/config/configdata.yml | 4 +- qutebrowser/misc/guiprocess.py | 11 ++++++ tests/end2end/features/editor.feature | 49 +++++++++++++++---------- tests/end2end/features/test_editor_bdd.py | 31 ++++++++++------ 5 files changed, 107 insertions(+), 49 deletions(-) diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 4d1714cbe..e5f7666b2 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -23,7 +23,7 @@ import os import sys import html import netrc -from typing import Callable, Mapping, List +from typing import Callable, Mapping, List, Optional import tempfile from PyQt5.QtCore import QUrl @@ -355,23 +355,54 @@ def choose_file(multiple: bool) -> List[str]: A list of selected file paths, or empty list if no file is selected. If multiple is False, the return value will have at most 1 item. """ - handle = tempfile.NamedTemporaryFile(prefix='qutebrowser-fileselect-', delete=False) - handle.close() - tmpfilename = handle.name - with utils.cleanup_file(tmpfilename): - if multiple: - command = config.val.fileselect.multiple_files.command - else: - command = config.val.fileselect.single_file.command + if multiple: + command = config.val.fileselect.multiple_files.command + else: + command = config.val.fileselect.single_file.command + use_tmp_file = '{}' in command + if use_tmp_file: + handle = tempfile.NamedTemporaryFile(prefix='qutebrowser-fileselect-', delete=False) + handle.close() + tmpfilename = handle.name + command = command[:1] + [arg.replace('{}', tmpfilename) for arg in command[1:]] + with utils.cleanup_file(tmpfilename): + return execute_fileselect_command( + command=command, + multiple=multiple, + tmpfilename=tmpfilename, + ) + else: + return execute_fileselect_command( + command=command, + multiple=multiple, + ) - proc = guiprocess.GUIProcess(what='choose-file') - proc.start(command[0], - [arg.replace('{}', tmpfilename) for arg in command[1:]]) - loop = qtutils.EventLoop() - proc.finished.connect(lambda _code, _status: loop.exit()) - loop.exec() +def execute_fileselect_command( + command, + multiple: bool, + tmpfilename: Optional[str]=None +) -> List[str]: + """Execute external command to choose file + Args: + multiple: Should selecting multiple files be allowed. + tmpfilename: Path to the temporary file if used, otherwise None. + + Return: + A list of selected file paths, or empty list if no file is selected. + If multiple is False, the return value will have at most 1 item. + """ + proc = guiprocess.GUIProcess(what='choose-file') + proc.start(command[0], command[1:]) + + loop = qtutils.EventLoop() + proc.finished.connect(lambda _code, _status: loop.exit()) + loop.exec() + + if tmpfilename is None: + selected_files = proc.final_stdout().splitlines() + else: with open(tmpfilename, mode='r', encoding=sys.getfilesystemencoding()) as f: selected_files = f.read().splitlines() diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 60705d9d2..e492758ea 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1259,7 +1259,7 @@ fileselect.handler: fileselect.single_file.command: type: name: ShellCommand - placeholder: true + placeholder: false completions: - ['["xterm", "-e", "ranger", "--choosefile={}"]', "Ranger in xterm"] - ['["xterm", "-e", "vifm", "--choose-file", "{}"]', "vifm in xterm"] @@ -1276,7 +1276,7 @@ fileselect.single_file.command: fileselect.multiple_files.command: type: name: ShellCommand - placeholder: true + placeholder: false completions: - ['["xterm", "-e", "ranger", "--choosefiles={}"]', "Ranger in xterm"] - ['["xterm", "-e", "vifm", "--choose-files", "{}"]', "vifm in xterm"] diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 1273b227e..e7b3ab930 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -61,6 +61,9 @@ class GUIProcess(QObject): self.cmd = None self.args = None + self._final_stdout = None + self._final_stderr = None + self._proc = QProcess(self) self._proc.errorOccurred.connect(self._on_error) self._proc.errorOccurred.connect(self.error) @@ -125,6 +128,8 @@ class GUIProcess(QObject): log.procs.error("Process stderr:\n" + stderr.strip()) qutescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr) + self._final_stdout = stdout + self._final_stderr = stderr def _spawn_format(self, exitinfo, stdout, stderr): """Produce a formatted string for spawn output.""" @@ -179,3 +184,9 @@ class GUIProcess(QObject): def exit_status(self): return self._proc.exitStatus() + + def final_stdout(self): + return self._final_stdout + + def final_stderr(self): + return self._final_stderr diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature index ad017ddb8..78ac6e03d 100644 --- a/tests/end2end/features/editor.feature +++ b/tests/end2end/features/editor.feature @@ -6,14 +6,14 @@ Feature: Opening external editors Scenario: Editing a URL When I open data/numbers/1.txt - And I set up a fake editor replacing "1.txt" by "2.txt" + And I setup a fake editor replacing "1.txt" by "2.txt" And I run :edit-url Then data/numbers/2.txt should be loaded Scenario: Editing a URL with -t When I run :tab-only And I open data/numbers/1.txt - And I set up a fake editor replacing "1.txt" by "2.txt" + And I setup a fake editor replacing "1.txt" by "2.txt" And I run :edit-url -t Then data/numbers/2.txt should be loaded And the following tabs should be open: @@ -24,7 +24,7 @@ Feature: Opening external editors When I set tabs.new_position.related to prev And I open data/numbers/1.txt And I run :tab-only - And I set up a fake editor replacing "1.txt" by "2.txt" + And I setup a fake editor replacing "1.txt" by "2.txt" And I run :edit-url -rt Then data/numbers/2.txt should be loaded And the following tabs should be open: @@ -34,7 +34,7 @@ Feature: Opening external editors Scenario: Editing a URL with -b When I run :tab-only And I open data/numbers/1.txt - And I set up a fake editor replacing "1.txt" by "2.txt" + And I setup a fake editor replacing "1.txt" by "2.txt" And I run :edit-url -b Then data/numbers/2.txt should be loaded And the following tabs should be open: @@ -45,7 +45,7 @@ Feature: Opening external editors When I run :window-only And I open data/numbers/1.txt in a new tab And I run :tab-only - And I set up a fake editor replacing "1.txt" by "2.txt" + And I setup a fake editor replacing "1.txt" by "2.txt" And I run :edit-url -w Then data/numbers/2.txt should be loaded And the session should look like: @@ -65,7 +65,7 @@ Feature: Opening external editors When I open data/numbers/1.txt in a new tab And I run :tab-only And I run :window-only - And I set up a fake editor replacing "1.txt" by "2.txt" + And I setup a fake editor replacing "1.txt" by "2.txt" And I run :edit-url -p Then data/numbers/2.txt should be loaded And the session should look like: @@ -90,13 +90,13 @@ Feature: Opening external editors Scenario: Editing a URL with invalid URL When I set url.auto_search to never And I open data/hello.txt - And I set up a fake editor replacing "http://localhost:(port)/data/hello.txt" by "foo!" + And I setup a fake editor replacing "http://localhost:(port)/data/hello.txt" by "foo!" And I run :edit-url Then the error "Invalid URL" should be shown Scenario: Spawning an editor successfully Given I have a fresh instance - When I set up a fake editor returning "foobar" + When I setup a fake editor returning "foobar" And I open data/editor.html And I run :click-element id qute-textarea And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log @@ -105,7 +105,7 @@ Feature: Opening external editors Then the javascript message "text: foobar" should be logged Scenario: Spawning an editor in normal mode - When I set up a fake editor returning "foobar" + When I setup a fake editor returning "foobar" And I open data/editor.html And I run :click-element id qute-textarea And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log @@ -119,7 +119,7 @@ Feature: Opening external editors # There's no guarantee that the tab gets deleted... @posix Scenario: Spawning an editor and closing the tab - When I set up a fake editor that writes "foobar" on save + When I setup a fake editor that writes "foobar" on save And I open data/editor.html And I run :click-element id qute-textarea And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log @@ -134,7 +134,7 @@ Feature: Opening external editors # Could not get signals working on Windows @posix Scenario: Spawning an editor and saving - When I set up a fake editor that writes "foobar" on save + When I setup a fake editor that writes "foobar" on save And I open data/editor.html And I run :click-element id qute-textarea And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log @@ -145,7 +145,7 @@ Feature: Opening external editors Then the javascript message "text: foobar" should be logged Scenario: Spawning an editor in caret mode - When I set up a fake editor returning "foobar" + When I setup a fake editor returning "foobar" And I open data/editor.html And I run :click-element id qute-textarea And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log @@ -159,7 +159,7 @@ Feature: Opening external editors Then the javascript message "text: foobar" should be logged Scenario: Spawning an editor with existing text - When I set up a fake editor replacing "foo" by "bar" + When I setup a fake editor replacing "foo" by "bar" And I open data/editor.html And I run :click-element id qute-textarea And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log @@ -173,20 +173,20 @@ Feature: Opening external editors Scenario: Edit a command and run it When I run :set-cmd-text :message-info foo - And I set up a fake editor replacing "foo" by "bar" + And I setup a fake editor replacing "foo" by "bar" And I run :edit-command --run Then the message "bar" should be shown And "Leaving mode KeyMode.command (reason: cmd accept)" should be logged Scenario: Edit a command and omit the start char - When I set up a fake editor returning "message-info foo" + When I setup a fake editor returning "message-info foo" And I run :edit-command Then the error "command must start with one of :/?" should be shown And "Leaving mode KeyMode.command *" should not be logged Scenario: Edit a command to be empty When I run :set-cmd-text : - When I set up a fake editor returning empty text + When I setup a fake editor returning empty text And I run :edit-command Then the error "command must start with one of :/?" should be shown And "Leaving mode KeyMode.command *" should not be logged @@ -194,13 +194,20 @@ Feature: Opening external editors ## select single file Scenario: Select one file with single command - When I set up a fake "single_file" fileselector selecting "tests/end2end/data/numbers/1.txt" + When I setup a fake "single_file" fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to "a temporary file" + And I open data/fileselect.html + And I run :click-element id single_file + Then the javascript message "Files: 1.txt" should be logged + + Scenario: Select one file with single command that writes to stdout + When I setup a fake "single_file" fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to "stdout" And I open data/fileselect.html And I run :click-element id single_file Then the javascript message "Files: 1.txt" should be logged Scenario: Select two files with single command - When I set up a fake "single_file" fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" + When I setup a fake "single_file" fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" and writes to "a temporary file" + And I open data/fileselect.html And I run :click-element id single_file Then the javascript message "Files: 1.txt" should be logged @@ -209,13 +216,15 @@ Feature: Opening external editors ## select multiple files Scenario: Select one file with multiple command - When I set up a fake "multiple_files" fileselector selecting "tests/end2end/data/numbers/1.txt" + When I setup a fake "multiple_files" fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to "a temporary file" + And I open data/fileselect.html And I run :click-element id multiple_files Then the javascript message "Files: 1.txt" should be logged Scenario: Select two files with multiple command - When I set up a fake "multiple_files" fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" + When I setup a fake "multiple_files" fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" and writes to "a temporary file" + And I open data/fileselect.html And I run :click-element id multiple_files Then the javascript message "Files: 1.txt, 2.txt" should be logged diff --git a/tests/end2end/features/test_editor_bdd.py b/tests/end2end/features/test_editor_bdd.py index 445691bee..803b8d8f1 100644 --- a/tests/end2end/features/test_editor_bdd.py +++ b/tests/end2end/features/test_editor_bdd.py @@ -32,7 +32,7 @@ bdd.scenarios('editor.feature') from qutebrowser.utils import utils -@bdd.when(bdd.parsers.parse('I set up a fake editor replacing "{text}" by ' +@bdd.when(bdd.parsers.parse('I setup a fake editor replacing "{text}" by ' '"{replacement}"')) def set_up_editor_replacement(quteproc, server, tmpdir, text, replacement): """Set up editor.command to a small python script doing a replacement.""" @@ -53,7 +53,7 @@ def set_up_editor_replacement(quteproc, server, tmpdir, text, replacement): quteproc.set_setting('editor.command', editor) -@bdd.when(bdd.parsers.parse('I set up a fake editor returning "{text}"')) +@bdd.when(bdd.parsers.parse('I setup a fake editor returning "{text}"')) def set_up_editor(quteproc, tmpdir, text): """Set up editor.command to a small python script inserting a text.""" script = tmpdir / 'script.py' @@ -67,7 +67,7 @@ def set_up_editor(quteproc, tmpdir, text): quteproc.set_setting('editor.command', editor) -@bdd.when(bdd.parsers.parse('I set up a fake editor returning empty text')) +@bdd.when(bdd.parsers.parse('I setup a fake editor returning empty text')) def set_up_editor_empty(quteproc, tmpdir): """Set up editor.command to a small python script inserting empty text.""" set_up_editor(quteproc, tmpdir, "") @@ -107,7 +107,7 @@ def editor_pid_watcher(tmpdir): return EditorPidWatcher(tmpdir) -@bdd.when(bdd.parsers.parse('I set up a fake editor that writes "{text}" on ' +@bdd.when(bdd.parsers.parse('I setup a fake editor that writes "{text}" on ' 'save')) def set_up_editor_wait(quteproc, tmpdir, text, editor_pid_watcher): """Set up editor.command to a small python script inserting a text.""" @@ -180,18 +180,25 @@ def save_editor_wait(tmpdir): os.kill(pid, signal.SIGUSR2) -@bdd.when(bdd.parsers.parse('I set up a fake "{kind}" fileselector ' - 'selecting "{files}"')) -def set_up_fileselector(quteproc, py_proc, kind, files): +@bdd.when(bdd.parsers.parse('I setup a fake "{kind}" fileselector ' + 'selecting "{files}" and writes to "{output_type}"')) +def set_up_fileselector(quteproc, py_proc, kind, files, output_type): """Set up fileselect.xxx.command to select the file(s).""" cmd, args = py_proc(r""" import os import sys - tmp_file = sys.argv[1] - with open(tmp_file, 'w') as f: - for selected_file in sys.argv[2:]: - f.write(os.path.abspath(selected_file) + "\n") + if '--' in sys.argv: + tmp_file = sys.argv[-1] + with open(tmp_file, 'w') as f: + for selected_file in sys.argv[1:-2]: + f.write(os.path.abspath(selected_file) + '\n') + else: + for selected_file in sys.argv[1:]: + print(os.path.abspath(selected_file)) """) - fileselect_cmd = json.dumps([cmd, *args, '{}', *files.split(' ')]) + args += files.split(' ') + if output_type == "a temporary file": + args += ['--', '{}'] + fileselect_cmd = json.dumps([cmd, *args]) quteproc.set_setting('fileselect.handler', 'external') quteproc.set_setting(f'fileselect.{kind}.command', fileselect_cmd) -- cgit v1.2.3-54-g00ecf From c192ed882b5194a6b6455c7023c6e49fef4b78f8 Mon Sep 17 00:00:00 2001 From: Axel Dahlberg Date: Mon, 1 Feb 2021 14:50:27 +0100 Subject: Fix formatting issues --- qutebrowser/browser/shared.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index e5f7666b2..dc662d03c 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -361,10 +361,16 @@ def choose_file(multiple: bool) -> List[str]: command = config.val.fileselect.single_file.command use_tmp_file = '{}' in command if use_tmp_file: - handle = tempfile.NamedTemporaryFile(prefix='qutebrowser-fileselect-', delete=False) + handle = tempfile.NamedTemporaryFile( + prefix='qutebrowser-fileselect-', + delete=False, + ) handle.close() tmpfilename = handle.name - command = command[:1] + [arg.replace('{}', tmpfilename) for arg in command[1:]] + command = ( + command[:1] + + [arg.replace('{}', tmpfilename) for arg in command[1:]] + ) with utils.cleanup_file(tmpfilename): return execute_fileselect_command( command=command, @@ -379,11 +385,11 @@ def choose_file(multiple: bool) -> List[str]: def execute_fileselect_command( - command, + command: List[str], multiple: bool, - tmpfilename: Optional[str]=None + tmpfilename: Optional[str] = None ) -> List[str]: - """Execute external command to choose file + """Execute external command to choose file. Args: multiple: Should selecting multiple files be allowed. -- cgit v1.2.3-54-g00ecf From 85a950f9be212e5f4a2639041881bb8acc76ab41 Mon Sep 17 00:00:00 2001 From: Axel Dahlberg Date: Mon, 1 Feb 2021 16:34:49 +0100 Subject: Test coverage of guiprocess --- tests/unit/misc/test_guiprocess.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index c224118a8..e9db8ff27 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -64,6 +64,8 @@ def test_start(proc, qtbot, message_mock, py_proc): assert not message_mock.messages assert qutescheme.spawn_output == expected assert proc.exit_status() == QProcess.NormalExit + assert proc.final_stdout().strip() == "test", proc.final_stdout() + assert proc.final_stderr().strip() == "", proc.final_stderr() def test_start_verbose(proc, qtbot, message_mock, py_proc): -- cgit v1.2.3-54-g00ecf From 0f142b45e714ca759623206e7c66f73f0af67d28 Mon Sep 17 00:00:00 2001 From: Axel Dahlberg Date: Mon, 1 Feb 2021 20:49:39 +0100 Subject: Changes based on feedback --- qutebrowser/browser/shared.py | 16 ++++++++-------- qutebrowser/misc/guiprocess.py | 14 ++++---------- tests/end2end/features/editor.feature | 10 +++++----- tests/end2end/features/test_editor_bdd.py | 4 ++-- tests/unit/misc/test_guiprocess.py | 4 ++-- 5 files changed, 21 insertions(+), 27 deletions(-) diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index dc662d03c..643273f20 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -367,24 +367,24 @@ def choose_file(multiple: bool) -> List[str]: ) handle.close() tmpfilename = handle.name - command = ( - command[:1] + - [arg.replace('{}', tmpfilename) for arg in command[1:]] - ) with utils.cleanup_file(tmpfilename): - return execute_fileselect_command( + command = ( + command[:1] + + [arg.replace('{}', tmpfilename) for arg in command[1:]] + ) + return _execute_fileselect_command( command=command, multiple=multiple, tmpfilename=tmpfilename, ) else: - return execute_fileselect_command( + return _execute_fileselect_command( command=command, multiple=multiple, ) -def execute_fileselect_command( +def _execute_fileselect_command( command: List[str], multiple: bool, tmpfilename: Optional[str] = None @@ -407,7 +407,7 @@ def execute_fileselect_command( loop.exec() if tmpfilename is None: - selected_files = proc.final_stdout().splitlines() + selected_files = proc.final_stdout.splitlines() else: with open(tmpfilename, mode='r', encoding=sys.getfilesystemencoding()) as f: selected_files = f.read().splitlines() diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index e7b3ab930..55696ed29 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -61,8 +61,8 @@ class GUIProcess(QObject): self.cmd = None self.args = None - self._final_stdout = None - self._final_stderr = None + self.final_stdout = None + self.final_stderr = None self._proc = QProcess(self) self._proc.errorOccurred.connect(self._on_error) @@ -128,8 +128,8 @@ class GUIProcess(QObject): log.procs.error("Process stderr:\n" + stderr.strip()) qutescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr) - self._final_stdout = stdout - self._final_stderr = stderr + self.final_stdout = stdout + self.final_stderr = stderr def _spawn_format(self, exitinfo, stdout, stderr): """Produce a formatted string for spawn output.""" @@ -184,9 +184,3 @@ class GUIProcess(QObject): def exit_status(self): return self._proc.exitStatus() - - def final_stdout(self): - return self._final_stdout - - def final_stderr(self): - return self._final_stderr diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature index 78ac6e03d..cb12360bc 100644 --- a/tests/end2end/features/editor.feature +++ b/tests/end2end/features/editor.feature @@ -194,19 +194,19 @@ Feature: Opening external editors ## select single file Scenario: Select one file with single command - When I setup a fake "single_file" fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to "a temporary file" + When I setup a fake single_file fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to a temporary file And I open data/fileselect.html And I run :click-element id single_file Then the javascript message "Files: 1.txt" should be logged Scenario: Select one file with single command that writes to stdout - When I setup a fake "single_file" fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to "stdout" + When I setup a fake single_file fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to stdout And I open data/fileselect.html And I run :click-element id single_file Then the javascript message "Files: 1.txt" should be logged Scenario: Select two files with single command - When I setup a fake "single_file" fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" and writes to "a temporary file" + When I setup a fake single_file fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" and writes to a temporary file And I open data/fileselect.html And I run :click-element id single_file @@ -216,14 +216,14 @@ Feature: Opening external editors ## select multiple files Scenario: Select one file with multiple command - When I setup a fake "multiple_files" fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to "a temporary file" + When I setup a fake multiple_files fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to a temporary file And I open data/fileselect.html And I run :click-element id multiple_files Then the javascript message "Files: 1.txt" should be logged Scenario: Select two files with multiple command - When I setup a fake "multiple_files" fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" and writes to "a temporary file" + When I setup a fake multiple_files fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" and writes to a temporary file And I open data/fileselect.html And I run :click-element id multiple_files diff --git a/tests/end2end/features/test_editor_bdd.py b/tests/end2end/features/test_editor_bdd.py index 803b8d8f1..8ec8cf98e 100644 --- a/tests/end2end/features/test_editor_bdd.py +++ b/tests/end2end/features/test_editor_bdd.py @@ -180,8 +180,8 @@ def save_editor_wait(tmpdir): os.kill(pid, signal.SIGUSR2) -@bdd.when(bdd.parsers.parse('I setup a fake "{kind}" fileselector ' - 'selecting "{files}" and writes to "{output_type}"')) +@bdd.when(bdd.parsers.parse('I setup a fake {kind} fileselector ' + 'selecting "{files}" and writes to {output_type}')) def set_up_fileselector(quteproc, py_proc, kind, files, output_type): """Set up fileselect.xxx.command to select the file(s).""" cmd, args = py_proc(r""" diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index e9db8ff27..7f6a11ddd 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -64,8 +64,6 @@ def test_start(proc, qtbot, message_mock, py_proc): assert not message_mock.messages assert qutescheme.spawn_output == expected assert proc.exit_status() == QProcess.NormalExit - assert proc.final_stdout().strip() == "test", proc.final_stdout() - assert proc.final_stderr().strip() == "", proc.final_stderr() def test_start_verbose(proc, qtbot, message_mock, py_proc): @@ -129,9 +127,11 @@ def test_start_output_message(proc, qtbot, caplog, message_mock, py_proc, if stdout_msg is not None: assert stdout_msg.level == usertypes.MessageLevel.info assert stdout_msg.text == 'stdout text' + assert proc.final_stdout.strip() == "stdout text", proc.final_stdout if stderr_msg is not None: assert stderr_msg.level == usertypes.MessageLevel.error assert stderr_msg.text == 'stderr text' + assert proc.final_stderr.strip() == "stderr text", proc.final_stderr def test_start_env(monkeypatch, qtbot, py_proc): -- cgit v1.2.3-54-g00ecf From 18340ada86cc752cc8f90b1c44ff199993876885 Mon Sep 17 00:00:00 2001 From: Axel Dahlberg Date: Mon, 1 Feb 2021 21:33:38 +0100 Subject: fix mypy issues --- qutebrowser/misc/guiprocess.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 55696ed29..79c84c346 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -61,8 +61,8 @@ class GUIProcess(QObject): self.cmd = None self.args = None - self.final_stdout = None - self.final_stderr = None + self.final_stdout: str = "" + self.final_stderr: str = "" self._proc = QProcess(self) self._proc.errorOccurred.connect(self._on_error) -- cgit v1.2.3-54-g00ecf From 13622f4a105fb30fb2f72c0fae380e25f1a986c9 Mon Sep 17 00:00:00 2001 From: Justin Benge Date: Sun, 21 Feb 2021 11:55:26 -0700 Subject: Adding abstract method to browsertab.py --- qutebrowser/browser/browsertab.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 4c26da69d..0320785bc 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -33,6 +33,8 @@ from PyQt5.QtWidgets import QWidget, QApplication, QDialog from PyQt5.QtPrintSupport import QPrintDialog, QPrinter from PyQt5.QtNetwork import QNetworkAccessManager +from abc import abstractmethod + if TYPE_CHECKING: from PyQt5.QtWebKit import QWebHistory from PyQt5.QtWebKitWidgets import QWebPage @@ -806,6 +808,10 @@ class AbstractTabPrivate: self._tab = tab self._mode_manager = mode_manager + @abstractmethod + def __init_inspector(self): + pass + def event_target(self) -> QWidget: """Return the widget events should be sent to.""" raise NotImplementedError -- cgit v1.2.3-54-g00ecf From 913ff4b5ac003fc5658fead060fb0f9d1336cc44 Mon Sep 17 00:00:00 2001 From: Justin Benge Date: Tue, 23 Feb 2021 12:42:14 -0700 Subject: Overriding _init_inspector method in web(engine)(kit) --- qutebrowser/browser/browsertab.py | 21 ++++++++++++++------- qutebrowser/browser/webengine/webenginetab.py | 12 +++++++++++- qutebrowser/browser/webkit/webkittab.py | 12 +++++++++++- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 0320785bc..e51a047e0 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -33,8 +33,6 @@ from PyQt5.QtWidgets import QWidget, QApplication, QDialog from PyQt5.QtPrintSupport import QPrintDialog, QPrinter from PyQt5.QtNetwork import QNetworkAccessManager -from abc import abstractmethod - if TYPE_CHECKING: from PyQt5.QtWebKit import QWebHistory from PyQt5.QtWebKitWidgets import QWebPage @@ -808,10 +806,6 @@ class AbstractTabPrivate: self._tab = tab self._mode_manager = mode_manager - @abstractmethod - def __init_inspector(self): - pass - def event_target(self) -> QWidget: """Return the widget events should be sent to.""" raise NotImplementedError @@ -876,7 +870,7 @@ class AbstractTabPrivate: tabdata = self._tab.data if tabdata.inspector is None: assert tabdata.splitter is not None - tabdata.inspector = inspector.create( + tabdata.inspector = self._init_inspector( splitter=tabdata.splitter, win_id=self._tab.win_id) self._tab.shutting_down.connect(tabdata.inspector.shutdown) @@ -884,6 +878,19 @@ class AbstractTabPrivate: tabdata.inspector.inspect(self._widget.page()) tabdata.inspector.set_position(position) + def _init_inspector(*, splitter: 'miscwidgets.InspectorSplitter', + win_id: int, + parent: QWidget = None) -> 'AbstractWebInspector': + """Get a WebKitInspector/WebEngineInspector. + + Args: + splitter: InspectorSplitter where the inspector can be placed. + win_id: The window ID this inspector is associated with. + parent: The Qt parent to set. + """ + raise NotImplementedError + + class AbstractTab(QWidget): diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 9f129b609..84cac1f07 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -1206,7 +1206,17 @@ class WebEngineTabPrivate(browsertab.AbstractTabPrivate): def run_js_sync(self, code): raise browsertab.UnsupportedOperationError - + + def _init_inspector(*, splitter, win_id, parent=None): + # Importing modules here so we don't depend on QtWebEngine without the + # argument and to avoid circular imports. + if objects.backend == usertypes.Backend.QtWebEngine: + from qutebrowser.browser.webengine import webengineinspector + return webengineinspector.WebEngineInspector(splitter, win_id, parent) + elif objects.backend == usertypes.Backend.QtWebKit: + from qutebrowser.browser.webkit import webkitinspector + return webkitinspector.WebKitInspector(splitter, win_id, parent) + raise utils.Unreachable(objects.backend) class WebEngineTab(browsertab.AbstractTab): diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 066cce348..1e77dc66e 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -37,7 +37,7 @@ 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.misc import miscwidgets, objects class WebKitAction(browsertab.AbstractAction): @@ -808,6 +808,16 @@ class WebKitTabPrivate(browsertab.AbstractTabPrivate): result = document_element.evaluateJavaScript(code) return result + def _init_inspector(*, splitter, win_id, parent=None): + # Importing modules here so we don't depend on QtWebEngine without the + # argument and to avoid circular imports. + if objects.backend == usertypes.Backend.QtWebEngine: + from qutebrowser.browser.webengine import webengineinspector + return webengineinspector.WebEngineInspector(splitter, win_id, parent) + elif objects.backend == usertypes.Backend.QtWebKit: + from qutebrowser.browser.webkit import webkitinspector + return webkitinspector.WebKitInspector(splitter, win_id, parent) + raise utils.Unreachable(objects.backend) class WebKitTab(browsertab.AbstractTab): -- cgit v1.2.3-54-g00ecf From bbfb573e6ae0b25d49136102217187cf38e20a44 Mon Sep 17 00:00:00 2001 From: Justin Benge Date: Tue, 23 Feb 2021 17:14:18 -0700 Subject: Fixed indentation error --- qutebrowser/browser/browsertab.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index e51a047e0..cdf5f7616 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -881,14 +881,14 @@ class AbstractTabPrivate: def _init_inspector(*, splitter: 'miscwidgets.InspectorSplitter', win_id: int, parent: QWidget = None) -> 'AbstractWebInspector': - """Get a WebKitInspector/WebEngineInspector. + """Get a WebKitInspector/WebEngineInspector. - Args: - splitter: InspectorSplitter where the inspector can be placed. - win_id: The window ID this inspector is associated with. - parent: The Qt parent to set. - """ - raise NotImplementedError + Args: + splitter: InspectorSplitter where the inspector can be placed. + win_id: The window ID this inspector is associated with. + parent: The Qt parent to set. + """ + raise NotImplementedError -- cgit v1.2.3-54-g00ecf From 69bf8fca8886c2ea1c1bf95006192d0fdef0fd28 Mon Sep 17 00:00:00 2001 From: Justin Benge Date: Mon, 1 Mar 2021 03:33:46 -0700 Subject: Removing inspector.create, superflous imports, and adding imports to file level --- qutebrowser/browser/inspector.py | 21 --------------------- qutebrowser/browser/webengine/webenginetab.py | 17 +++++------------ qutebrowser/browser/webkit/webkittab.py | 16 ++++------------ 3 files changed, 9 insertions(+), 45 deletions(-) diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index 9ed5b52ea..a22e3c9ff 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -35,27 +35,6 @@ from qutebrowser.keyinput import modeman from qutebrowser.misc import miscwidgets, objects -def create(*, splitter: 'miscwidgets.InspectorSplitter', - win_id: int, - parent: QWidget = None) -> 'AbstractWebInspector': - """Get a WebKitInspector/WebEngineInspector. - - Args: - splitter: InspectorSplitter where the inspector can be placed. - win_id: The window ID this inspector is associated with. - parent: The Qt parent to set. - """ - # Importing modules here so we don't depend on QtWebEngine without the - # argument and to avoid circular imports. - if objects.backend == usertypes.Backend.QtWebEngine: - from qutebrowser.browser.webengine import webengineinspector - return webengineinspector.WebEngineInspector(splitter, win_id, parent) - elif objects.backend == usertypes.Backend.QtWebKit: - from qutebrowser.browser.webkit import webkitinspector - return webkitinspector.WebKitInspector(splitter, win_id, parent) - raise utils.Unreachable(objects.backend) - - class Position(enum.Enum): """Where the inspector is shown.""" diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 84cac1f07..a9fd67838 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -34,8 +34,8 @@ from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngin from qutebrowser.config import config from qutebrowser.browser import browsertab, eventfilter, shared, webelem, greasemonkey from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, - webenginesettings, certificateerror) -from qutebrowser.misc import miscwidgets, objects + webenginesettings, certificateerror,webengineinspector) + from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils, message, jinja, debug, version) from qutebrowser.qt import sip @@ -1207,16 +1207,9 @@ class WebEngineTabPrivate(browsertab.AbstractTabPrivate): def run_js_sync(self, code): raise browsertab.UnsupportedOperationError - def _init_inspector(*, splitter, win_id, parent=None): - # Importing modules here so we don't depend on QtWebEngine without the - # argument and to avoid circular imports. - if objects.backend == usertypes.Backend.QtWebEngine: - from qutebrowser.browser.webengine import webengineinspector - return webengineinspector.WebEngineInspector(splitter, win_id, parent) - elif objects.backend == usertypes.Backend.QtWebKit: - from qutebrowser.browser.webkit import webkitinspector - return webkitinspector.WebKitInspector(splitter, win_id, parent) - raise utils.Unreachable(objects.backend) + def _init_inspector(self, splitter, win_id, parent=None): + return webengineinspector.WebEngineInspector(splitter, win_id, parent) + class WebEngineTab(browsertab.AbstractTab): diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 1e77dc66e..e39eaaa91 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -33,11 +33,11 @@ from PyQt5.QtPrintSupport import QPrinter from qutebrowser.browser import browsertab, shared from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem, - webkitsettings) + webkitsettings,webkitinspector) + from qutebrowser.utils import qtutils, usertypes, utils, log, debug from qutebrowser.keyinput import modeman from qutebrowser.qt import sip -from qutebrowser.misc import miscwidgets, objects class WebKitAction(browsertab.AbstractAction): @@ -808,16 +808,8 @@ class WebKitTabPrivate(browsertab.AbstractTabPrivate): result = document_element.evaluateJavaScript(code) return result - def _init_inspector(*, splitter, win_id, parent=None): - # Importing modules here so we don't depend on QtWebEngine without the - # argument and to avoid circular imports. - if objects.backend == usertypes.Backend.QtWebEngine: - from qutebrowser.browser.webengine import webengineinspector - return webengineinspector.WebEngineInspector(splitter, win_id, parent) - elif objects.backend == usertypes.Backend.QtWebKit: - from qutebrowser.browser.webkit import webkitinspector - return webkitinspector.WebKitInspector(splitter, win_id, parent) - raise utils.Unreachable(objects.backend) + def _init_inspector(self, splitter, win_id, parent=None): + return webkitinspector.WebKitInspector(splitter, win_id, parent) class WebKitTab(browsertab.AbstractTab): -- cgit v1.2.3-54-g00ecf From b9cb26d3e728d1b00962be187b23cc0f1b51f888 Mon Sep 17 00:00:00 2001 From: pspiagicw Date: Mon, 1 Mar 2021 20:45:35 +0530 Subject: Made neccessary changes --- qutebrowser/browser/commands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index dc0664238..4e8ad8d57 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -329,7 +329,9 @@ class CommandDispatcher: # Explicit count with a tab that doesn't exist. return elif curtab.navigation_blocked(): - message.info("Tab is pinned!") + message.info("Tab is pinned! Opening in new tab") + self._tabbed_browser.tabopen(cur_url) + else: curtab.load_url(cur_url) -- cgit v1.2.3-54-g00ecf From 1391994798a74cbb1f10c5e778a9d2f961c83737 Mon Sep 17 00:00:00 2001 From: pspiagicw Date: Mon, 1 Mar 2021 22:34:45 +0530 Subject: Added tests for required chane --- tests/end2end/features/tabs.feature | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index ca0efefc4..c377b3193 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -1535,9 +1535,10 @@ Feature: Tab management When I open data/numbers/1.txt And I run :tab-pin And I open data/numbers/2.txt without waiting - Then the message "Tab is pinned!" should be shown + Then the message "Tab is pinned!Opening in new tab" should be shown And the following tabs should be open: - data/numbers/1.txt (active) (pinned) + - data/numbers/2.txt Scenario: :tab-pin open url with tabs.pinned.frozen = false When I set tabs.pinned.frozen to false -- cgit v1.2.3-54-g00ecf From 00004efa02d556ed1080a130661d186baf932841 Mon Sep 17 00:00:00 2001 From: pspiagicw Date: Mon, 1 Mar 2021 22:51:23 +0530 Subject: Fixed the typo --- tests/end2end/features/tabs.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index c377b3193..2a641beba 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -1535,7 +1535,7 @@ Feature: Tab management When I open data/numbers/1.txt And I run :tab-pin And I open data/numbers/2.txt without waiting - Then the message "Tab is pinned!Opening in new tab" should be shown + Then the message "Tab is pinned! Opening in new tab" should be shown And the following tabs should be open: - data/numbers/1.txt (active) (pinned) - data/numbers/2.txt -- cgit v1.2.3-54-g00ecf From aaa4c6fec288bd9f6bc19c4ca8b19972aa8821a4 Mon Sep 17 00:00:00 2001 From: pspiagicw Date: Mon, 1 Mar 2021 23:20:46 +0530 Subject: Added the session test as well --- tests/end2end/features/sessions.feature | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/sessions.feature b/tests/end2end/features/sessions.feature index 0f0c015e0..cdf214396 100644 --- a/tests/end2end/features/sessions.feature +++ b/tests/end2end/features/sessions.feature @@ -395,9 +395,10 @@ Feature: Saving and loading sessions And I run :session-load -c pin_session And I wait until data/numbers/3.txt is loaded And I run :tab-focus 2 - And I run :open hello world - Then the message "Tab is pinned!" should be shown + And I run :open data/numbers/4.txt + Then the message "Tab is pinned! Opening in new tab" should be shown And the following tabs should be open: - data/numbers/1.txt - data/numbers/2.txt (active) (pinned) + - data/numbers/4.txt - data/numbers/3.txt -- cgit v1.2.3-54-g00ecf From b1a83ca9cc49516f3482cedc68faac94e518dce6 Mon Sep 17 00:00:00 2001 From: Justin Benge Date: Mon, 1 Mar 2021 14:34:06 -0700 Subject: Adding back in necessary imports --- qutebrowser/browser/browsertab.py | 3 +-- qutebrowser/browser/inspector.py | 2 +- qutebrowser/browser/webengine/webenginetab.py | 4 +++- qutebrowser/browser/webkit/webkittab.py | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index cdf5f7616..581d33507 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -878,7 +878,7 @@ class AbstractTabPrivate: tabdata.inspector.inspect(self._widget.page()) tabdata.inspector.set_position(position) - def _init_inspector(*, splitter: 'miscwidgets.InspectorSplitter', + def _init_inspector(self, splitter: 'miscwidgets.InspectorSplitter', win_id: int, parent: QWidget = None) -> 'AbstractWebInspector': """Get a WebKitInspector/WebEngineInspector. @@ -889,7 +889,6 @@ class AbstractTabPrivate: parent: The Qt parent to set. """ raise NotImplementedError - class AbstractTab(QWidget): diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index a22e3c9ff..b65afdf30 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -32,7 +32,7 @@ from qutebrowser.browser import eventfilter from qutebrowser.config import configfiles from qutebrowser.utils import log, usertypes, utils from qutebrowser.keyinput import modeman -from qutebrowser.misc import miscwidgets, objects +from qutebrowser.misc import miscwidgets class Position(enum.Enum): diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index a9fd67838..ab4316cbe 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -34,11 +34,13 @@ from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngin from qutebrowser.config import config from qutebrowser.browser import browsertab, eventfilter, shared, webelem, greasemonkey from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, - webenginesettings, certificateerror,webengineinspector) + webenginesettings, certificateerror, + webengineinspector) from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils, message, jinja, debug, version) from qutebrowser.qt import sip +from qutebrowser.misc import objects, miscwidgets # Mapping worlds from usertypes.JsWorld to QWebEngineScript world IDs. diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index e39eaaa91..baea19e24 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -33,12 +33,13 @@ from PyQt5.QtPrintSupport import QPrinter from qutebrowser.browser import browsertab, shared from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem, - webkitsettings,webkitinspector) + webkitsettings, webkitinspector) from qutebrowser.utils import qtutils, usertypes, utils, log, debug from qutebrowser.keyinput import modeman from qutebrowser.qt import sip + class WebKitAction(browsertab.AbstractAction): """QtWebKit implementations related to web actions.""" -- cgit v1.2.3-54-g00ecf From cdb1de638487a2e292ae511a1d2535c0dde12c62 Mon Sep 17 00:00:00 2001 From: pspiagicw Date: Tue, 2 Mar 2021 08:07:14 +0530 Subject: Changed so that test should pass --- tests/end2end/features/tabs.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 2a641beba..16d2c4f14 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -1534,7 +1534,7 @@ Feature: Tab management Scenario: :tab-pin open url When I open data/numbers/1.txt And I run :tab-pin - And I open data/numbers/2.txt without waiting + And I open data/numbers/2.txt Then the message "Tab is pinned! Opening in new tab" should be shown And the following tabs should be open: - data/numbers/1.txt (active) (pinned) -- cgit v1.2.3-54-g00ecf From 0fa0848d038c471072908b82470768a37649c7b8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 2 Mar 2021 10:04:40 +0100 Subject: Fix session test --- tests/end2end/features/sessions.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/features/sessions.feature b/tests/end2end/features/sessions.feature index cdf214396..233dc4385 100644 --- a/tests/end2end/features/sessions.feature +++ b/tests/end2end/features/sessions.feature @@ -395,7 +395,7 @@ Feature: Saving and loading sessions And I run :session-load -c pin_session And I wait until data/numbers/3.txt is loaded And I run :tab-focus 2 - And I run :open data/numbers/4.txt + And I open data/numbers/4.txt Then the message "Tab is pinned! Opening in new tab" should be shown And the following tabs should be open: - data/numbers/1.txt -- cgit v1.2.3-54-g00ecf From 05d009714137a05678f37c798772dd2653c462f7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 2 Mar 2021 10:05:34 +0100 Subject: Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index c5636204f..c8590ba80 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -54,6 +54,8 @@ Changed long line. - If a command stats with space (e.g. `: open ...`, it's now not saved to command history anymore (similar to how some shells work). +- When a tab is pinned, running `:open` will now open a new tab instead of + displaying an error. Fixed ~~~~~ -- cgit v1.2.3-54-g00ecf From da389f6e3c9c242180fe9c7b86a633b3e23af7de Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 2 Mar 2021 10:06:09 +0100 Subject: Add period --- qutebrowser/browser/commands.py | 2 +- tests/end2end/features/sessions.feature | 2 +- tests/end2end/features/tabs.feature | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 4e8ad8d57..f2dd282df 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -329,7 +329,7 @@ class CommandDispatcher: # Explicit count with a tab that doesn't exist. return elif curtab.navigation_blocked(): - message.info("Tab is pinned! Opening in new tab") + message.info("Tab is pinned! Opening in new tab.") self._tabbed_browser.tabopen(cur_url) else: diff --git a/tests/end2end/features/sessions.feature b/tests/end2end/features/sessions.feature index 233dc4385..e48947cbd 100644 --- a/tests/end2end/features/sessions.feature +++ b/tests/end2end/features/sessions.feature @@ -396,7 +396,7 @@ Feature: Saving and loading sessions And I wait until data/numbers/3.txt is loaded And I run :tab-focus 2 And I open data/numbers/4.txt - Then the message "Tab is pinned! Opening in new tab" should be shown + Then the message "Tab is pinned! Opening in new tab." should be shown And the following tabs should be open: - data/numbers/1.txt - data/numbers/2.txt (active) (pinned) diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 16d2c4f14..7db054573 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -1535,7 +1535,7 @@ Feature: Tab management When I open data/numbers/1.txt And I run :tab-pin And I open data/numbers/2.txt - Then the message "Tab is pinned! Opening in new tab" should be shown + Then the message "Tab is pinned! Opening in new tab." should be shown And the following tabs should be open: - data/numbers/1.txt (active) (pinned) - data/numbers/2.txt -- cgit v1.2.3-54-g00ecf From df4ebec6bce100c0d0163295c799c67e22950039 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 2 Mar 2021 16:47:46 +0100 Subject: Revert "requirements: Pin coverage to 5.4 on Python 3.10" This reverts commit 43ff51fe7b47a7f534b9d88545364d2c84e6e6d6. Now fixed upstream. --- misc/requirements/requirements-tests.txt | 3 +-- misc/requirements/requirements-tests.txt-raw | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index c93b5896d..bf214be0d 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -8,7 +8,7 @@ chardet==4.0.0 cheroot==8.5.2 click==7.1.2 # colorama==0.4.4 -coverage==5.5 ; python_version!="3.10" +coverage==5.5 EasyProcess==0.3 execnet==1.8.0 filelock==3.0.12 @@ -59,4 +59,3 @@ toml==0.10.2 urllib3==1.26.3 vulture==2.3 Werkzeug==1.0.1 -coverage==5.4; python_version=="3.10" diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 196a80753..ab580ac4b 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -34,7 +34,3 @@ pytest-icdiff tldextract #@ ignore: Jinja2, MarkupSafe, colorama - -# WORKAROUND for https://github.com/nedbat/coveragepy/issues/1129 -#@ markers: coverage python_version!="3.10" -#@ add: coverage==5.4; python_version=="3.10" -- cgit v1.2.3-54-g00ecf From 2bcdd04e825e4e27c87495315722b8e71d96c1f9 Mon Sep 17 00:00:00 2001 From: Justin Benge Date: Tue, 2 Mar 2021 08:58:55 -0700 Subject: Fixing linter issues --- qutebrowser/browser/inspector.py | 2 +- qutebrowser/browser/webengine/webenginetab.py | 2 +- qutebrowser/browser/webkit/webkittab.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index b65afdf30..2b40e97e4 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -30,7 +30,7 @@ from PyQt5.QtGui import QCloseEvent from qutebrowser.browser import eventfilter from qutebrowser.config import configfiles -from qutebrowser.utils import log, usertypes, utils +from qutebrowser.utils import log, usertypes from qutebrowser.keyinput import modeman from qutebrowser.misc import miscwidgets diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index ab4316cbe..4092fbe40 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -1208,7 +1208,7 @@ class WebEngineTabPrivate(browsertab.AbstractTabPrivate): def run_js_sync(self, code): raise browsertab.UnsupportedOperationError - + def _init_inspector(self, splitter, win_id, parent=None): return webengineinspector.WebEngineInspector(splitter, win_id, parent) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index baea19e24..f910cf676 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -812,6 +812,7 @@ class WebKitTabPrivate(browsertab.AbstractTabPrivate): def _init_inspector(self, splitter, win_id, parent=None): return webkitinspector.WebKitInspector(splitter, win_id, parent) + class WebKitTab(browsertab.AbstractTab): """A QtWebKit tab in the browser.""" -- cgit v1.2.3-54-g00ecf From 2cbbbe54c64c23fc9bde6016a23ca38082849dc5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 2 Mar 2021 17:43:00 +0100 Subject: tests: Disallow NUL for filesystem tests From what I can see, there's no way for an user to enter a NUL byte. Closes #6223 --- tests/unit/completion/test_models.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index 8a6b24557..3b8aba4fd 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -28,7 +28,7 @@ from datetime import datetime from unittest import mock import hypothesis -import hypothesis.strategies +import hypothesis.strategies as hst import pytest from PyQt5.QtCore import QUrl, QDateTime try: @@ -459,9 +459,9 @@ def test_filesystem_completion_model_interface(info, local_files_path): @hypothesis.given( - as_uri=hypothesis.strategies.booleans(), - add_sep=hypothesis.strategies.booleans(), - text=hypothesis.strategies.text(), + as_uri=hst.booleans(), + add_sep=hst.booleans(), + text=hst.text(alphabet=hst.characters(blacklist_categories=['Cc'], blacklist_characters='\x00')), ) def test_filesystem_completion_hypothesis(info, as_uri, add_sep, text): if as_uri: @@ -1445,7 +1445,7 @@ def undo_completion_retains_sort_order(tabbed_browser_stubs, info): _check_completions(model, {"Closed tabs": expected}) -@hypothesis.given(text=hypothesis.strategies.text()) +@hypothesis.given(text=hst.text()) def test_listcategory_hypothesis(text): """Make sure we can't produce invalid patterns.""" cat = listcategory.ListCategory("test", []) -- cgit v1.2.3-54-g00ecf From 9909bf0b113b1357bb19c678046a14762b2b6901 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 2 Mar 2021 18:23:50 +0100 Subject: Fix lint --- tests/unit/completion/test_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index 3b8aba4fd..22e9c6490 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -461,7 +461,8 @@ def test_filesystem_completion_model_interface(info, local_files_path): @hypothesis.given( as_uri=hst.booleans(), add_sep=hst.booleans(), - text=hst.text(alphabet=hst.characters(blacklist_categories=['Cc'], blacklist_characters='\x00')), + text=hst.text(alphabet=hst.characters( + blacklist_categories=['Cc'], blacklist_characters='\x00')), ) def test_filesystem_completion_hypothesis(info, as_uri, add_sep, text): if as_uri: -- cgit v1.2.3-54-g00ecf From ef945c42f75e7a13fd0191f265fbcdebcb0a9fd7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 Mar 2021 11:47:44 +0100 Subject: readme: Mark Pygments as optional --- README.asciidoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 704058bd7..43e6a19e4 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -92,7 +92,6 @@ websites and using it for transmission of sensitive data._ * https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.12.0 or newer for Python 3 * https://palletsprojects.com/p/jinja/[jinja2] -* https://pygments.org/[pygments] * https://github.com/yaml/pyyaml[PyYAML] On older Python versions (3.6/3.7/3.8), the following backports are also required: @@ -103,6 +102,9 @@ On older Python versions (3.6/3.7/3.8), the following backports are also require The following libraries are optional: * https://pypi.org/project/adblock/[adblock] (for improved adblocking using ABP syntax) +* https://pygments.org/[pygments] for syntax highlighting with `:view-source` + on QtWebKit, or when using `:view-source --pygments` with the (default) + QtWebEngine backend. * On Windows, https://pypi.python.org/pypi/colorama/[colorama] for colored log output. * https://importlib-metadata.readthedocs.io/[importlib_resources] on Python 3.7 -- cgit v1.2.3-54-g00ecf From 82ba01647b9000b5422f6f77e874acdf4f5a511d Mon Sep 17 00:00:00 2001 From: $MYNAME <$MYGITEMAIL> Date: Wed, 3 Mar 2021 14:03:03 +0100 Subject: Better handling of filepicker commands, updated test --- qutebrowser/browser/shared.py | 2 +- scripts/dev/run_vulture.py | 1 + tests/end2end/features/test_editor_bdd.py | 20 +++++++++++++------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 643273f20..19048f88e 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -359,7 +359,7 @@ def choose_file(multiple: bool) -> List[str]: command = config.val.fileselect.multiple_files.command else: command = config.val.fileselect.single_file.command - use_tmp_file = '{}' in command + use_tmp_file = any('{}' in arg for arg in command[1:]) if use_tmp_file: handle = tempfile.NamedTemporaryFile( prefix='qutebrowser-fileselect-', diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py index fab46b115..3728e9ecb 100755 --- a/scripts/dev/run_vulture.py +++ b/scripts/dev/run_vulture.py @@ -80,6 +80,7 @@ def whitelist_generator(): # noqa: C901 yield 'qutebrowser.utils.log.QtWarningFilter.filter' yield 'qutebrowser.browser.pdfjs.is_available' yield 'qutebrowser.misc.guiprocess.spawn_output' + yield 'qutebrowser.misc.guiprocess.GUIProcess.final_stderr' yield 'qutebrowser.utils.usertypes.ExitStatus.reserved' yield 'QEvent.posted' yield 'log_stack' # from message.py diff --git a/tests/end2end/features/test_editor_bdd.py b/tests/end2end/features/test_editor_bdd.py index 8ec8cf98e..40f77a0f7 100644 --- a/tests/end2end/features/test_editor_bdd.py +++ b/tests/end2end/features/test_editor_bdd.py @@ -187,18 +187,24 @@ def set_up_fileselector(quteproc, py_proc, kind, files, output_type): cmd, args = py_proc(r""" import os import sys - if '--' in sys.argv: - tmp_file = sys.argv[-1] + tmp_file = None + for i, arg in enumerate(sys.argv): + if arg.startswith('--file='): + tmp_file = arg[len('--file='):] + sys.argv.pop(i) + break + selected_files = sys.argv[1:] + if tmp_file is None: + for selected_file in selected_files: + print(os.path.abspath(selected_file)) + else: with open(tmp_file, 'w') as f: - for selected_file in sys.argv[1:-2]: + for selected_file in selected_files: f.write(os.path.abspath(selected_file) + '\n') - else: - for selected_file in sys.argv[1:]: - print(os.path.abspath(selected_file)) """) args += files.split(' ') if output_type == "a temporary file": - args += ['--', '{}'] + args += ['--file={}'] fileselect_cmd = json.dumps([cmd, *args]) quteproc.set_setting('fileselect.handler', 'external') quteproc.set_setting(f'fileselect.{kind}.command', fileselect_cmd) -- cgit v1.2.3-54-g00ecf From 7d5acd7239170c478ea9cf696a0f59b91f7fd53d Mon Sep 17 00:00:00 2001 From: Axel Dahlberg Date: Wed, 3 Mar 2021 14:36:03 +0100 Subject: whitelist vulture at correct place --- scripts/dev/run_vulture.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py index e229b2782..612b88637 100755 --- a/scripts/dev/run_vulture.py +++ b/scripts/dev/run_vulture.py @@ -61,6 +61,7 @@ def whitelist_generator(): # noqa: C901 yield 'scripts.utils.bg_colors' yield 'qutebrowser.misc.sql.SqliteErrorCode.CONSTRAINT' yield 'qutebrowser.misc.throttle.Throttle.set_delay' + yield 'qutebrowser.misc.guiprocess.GUIProcess.final_stderr' # Qt attributes yield 'PyQt5.QtWebKit.QWebPage.ErrorPageExtensionReturn().baseUrl' @@ -80,7 +81,6 @@ def whitelist_generator(): # noqa: C901 yield 'qutebrowser.utils.log.QtWarningFilter.filter' yield 'qutebrowser.browser.pdfjs.is_available' yield 'qutebrowser.misc.guiprocess.spawn_output' - yield 'qutebrowser.misc.guiprocess.GUIProcess.final_stderr' yield 'qutebrowser.utils.usertypes.ExitStatus.reserved' yield 'QEvent.posted' yield 'log_stack' # from message.py -- cgit v1.2.3-54-g00ecf From 224f530d7bd965947dc0ab45dad083b89cd43769 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 Mar 2021 16:24:01 +0100 Subject: Update docs --- doc/changelog.asciidoc | 3 +++ doc/help/settings.asciidoc | 14 ++++++++------ qutebrowser/config/configdata.yml | 10 ++++++---- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index c8590ba80..a67c7d088 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -56,6 +56,9 @@ Changed command history anymore (similar to how some shells work). - When a tab is pinned, running `:open` will now open a new tab instead of displaying an error. +- The `fileselect.*.command` settings now support file selectors writing the + selected paths to stdout, which is used if no `{}` placeholder is contained in + the configured command. Fixed ~~~~~ diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 7d0b3469c..392f60c49 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -207,8 +207,8 @@ |<>|Editor (and arguments) to use for the `edit-*` commands. |<>|Encoding to use for the editor. |<>|Handler for selecting file(s) in forms. If `external`, then the commands specified by `fileselect.single_file.command` and `fileselect.multiple_files.command` are used to select one or multiple files respectively. -|<>|Command (and arguments) to use for selecting multiple files in forms. The command should write the selected file paths to the specified file, separated by newlines. -|<>|Command (and arguments) to use for selecting a single file in forms. The command should write the selected file path to the specified file. +|<>|Command (and arguments) to use for selecting multiple files in forms. The command should write the selected file paths to the specified file or to stdout, separated by newlines. +|<>|Command (and arguments) to use for selecting a single file in forms. The command should write the selected file path to the specified file or stdout. |<>|Font used in the completion categories. |<>|Font used in the completion widget. |<>|Font used for the context menu. @@ -2825,9 +2825,10 @@ Default: +pass:[default]+ [[fileselect.multiple_files.command]] === fileselect.multiple_files.command -Command (and arguments) to use for selecting multiple files in forms. The command should write the selected file paths to the specified file, separated by newlines. +Command (and arguments) to use for selecting multiple files in forms. The command should write the selected file paths to the specified file or to stdout, separated by newlines. The following placeholders are defined: -* `{}`: Filename of the file to be written to. +* `{}`: Filename of the file to be written to. If not contained in any argument, the + standard output of the command is read instead. Type: <> @@ -2840,9 +2841,10 @@ Default: [[fileselect.single_file.command]] === fileselect.single_file.command -Command (and arguments) to use for selecting a single file in forms. The command should write the selected file path to the specified file. +Command (and arguments) to use for selecting a single file in forms. The command should write the selected file path to the specified file or stdout. The following placeholders are defined: -* `{}`: Filename of the file to be written to. +* `{}`: Filename of the file to be written to. If not contained in any argument, the + standard output of the command is read instead. Type: <> diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index d93aa1e4b..34d8bec96 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1267,11 +1267,12 @@ fileselect.single_file.command: default: ['xterm', '-e', 'ranger', '--choosefile={}'] desc: >- Command (and arguments) to use for selecting a single file in forms. - The command should write the selected file path to the specified file. + The command should write the selected file path to the specified file or stdout. The following placeholders are defined: - * `{}`: Filename of the file to be written to. + * `{}`: Filename of the file to be written to. If not contained in any argument, the + standard output of the command is read instead. fileselect.multiple_files.command: type: @@ -1284,12 +1285,13 @@ fileselect.multiple_files.command: default: ['xterm', '-e', 'ranger', '--choosefiles={}'] desc: >- Command (and arguments) to use for selecting multiple files in forms. - The command should write the selected file paths to the specified file, + The command should write the selected file paths to the specified file or to stdout, separated by newlines. The following placeholders are defined: - * `{}`: Filename of the file to be written to. + * `{}`: Filename of the file to be written to. If not contained in any argument, the + standard output of the command is read instead. ## hints -- cgit v1.2.3-54-g00ecf From c31614cdbde3c473eeba3a36d803f0dda74684c5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 Mar 2021 17:52:06 +0100 Subject: Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index a67c7d088..3840f369d 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -27,6 +27,7 @@ Added - New optional dependency on the `importlib_metadata` project on Python 3.7 and below. This is only relevant when PyQtWebEngine is installed via pip - thus, this dependency usually isn't relevant for packagers. +- New `qute-keepassxc` userscript integrating with the KeePassXC browser API. Changed ~~~~~~~ -- cgit v1.2.3-54-g00ecf From f088a991380441254e78dba2bdef4309c8b7815e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 Mar 2021 18:13:24 +0100 Subject: Fix license URL --- misc/userscripts/qute-keepassxc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/userscripts/qute-keepassxc b/misc/userscripts/qute-keepassxc index 6e608455e..f0127590b 100755 --- a/misc/userscripts/qute-keepassxc +++ b/misc/userscripts/qute-keepassxc @@ -15,7 +15,7 @@ # 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 . +# along with qutebrowser. If not, see . """ # Introduction -- cgit v1.2.3-54-g00ecf From 423e7e25bdd7de78c7a75d70bfc92171fcf8ccaf Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Mar 2021 19:20:27 +0100 Subject: Remove blank line in cheatsheet --- doc/img/cheatsheet-big.png | Bin 779344 -> 781120 bytes doc/img/cheatsheet-small.png | Bin 30208 -> 30252 bytes misc/cheatsheet.svg | 14 ++++++-------- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/doc/img/cheatsheet-big.png b/doc/img/cheatsheet-big.png index ecd52c14e..75e2abb89 100644 Binary files a/doc/img/cheatsheet-big.png and b/doc/img/cheatsheet-big.png differ diff --git a/doc/img/cheatsheet-small.png b/doc/img/cheatsheet-small.png index 0dc01e8b4..e97d63367 100644 Binary files a/doc/img/cheatsheet-small.png and b/doc/img/cheatsheet-small.png differ diff --git a/misc/cheatsheet.svg b/misc/cheatsheet.svg index 7e8a7b381..e908f9496 100644 --- a/misc/cheatsheet.svg +++ b/misc/cheatsheet.svg @@ -11,7 +11,7 @@ height="682.66669" id="svg2" sodipodi:version="0.32" - inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07, custom)" + inkscape:version="1.0.2 (e86c870879, 2021-01-15)" version="1.0" sodipodi:docname="cheatsheet.svg" inkscape:output_extension="org.inkscape.output.svg.inkscape" @@ -30,16 +30,16 @@ objecttolerance="10" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="1.7536248" - inkscape:cx="466.08451" - inkscape:cy="268.64059" + inkscape:zoom="2.48" + inkscape:cx="834.18001" + inkscape:cy="692.30401" inkscape:document-units="px" inkscape:current-layer="layer1" width="1024px" height="640px" showgrid="false" - inkscape:window-width="3822" - inkscape:window-height="2128" + inkscape:window-width="1914" + inkscape:window-height="1048" inkscape:window-x="0" inkscape:window-y="16" showguides="true" @@ -3113,8 +3113,6 @@ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06667" id="flowPara3925">ss - set setting (sl: temp)sk - bind keySs - show settings Date: Mon, 8 Mar 2021 04:40:33 +0000 Subject: Update dependencies --- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-mypy.txt | 12 ++++++------ misc/requirements/requirements-pyinstaller.txt | 2 +- misc/requirements/requirements-pyroma.txt | 4 ++-- misc/requirements/requirements-sphinx.txt | 4 ++-- misc/requirements/requirements-tests.txt | 6 +++--- misc/requirements/requirements-tox.txt | 4 ++-- requirements.txt | 8 ++++---- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 493fa3cac..c83b57860 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -2,7 +2,7 @@ attrs==20.3.0 flake8==3.8.4 -flake8-bugbear==20.11.1 +flake8-bugbear==21.3.1 flake8-builtins==1.5.3 flake8-comprehensions==3.3.1 flake8-copyright==0.2.2 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 070339ed6..dfa80656b 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -1,10 +1,10 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py chardet==4.0.0 -diff-cover==4.2.1 -importlib-metadata==3.7.0 -importlib-resources==5.1.1 -inflect==5.2.0 +diff-cover==4.2.3 +importlib-metadata==3.7.2 +importlib-resources==5.1.2 +inflect==3.0.2 Jinja2==2.11.3 jinja2-pluralize==0.3.0 lxml==4.6.2 @@ -12,8 +12,8 @@ MarkupSafe==1.1.1 mypy==0.812 mypy-extensions==0.4.3 pluggy==0.13.1 -Pygments==2.8.0 +Pygments==2.8.1 PyQt5-stubs==5.15.2.0 typed-ast==1.4.2 typing-extensions==3.7.4.3 -zipp==3.4.0 +zipp==3.4.1 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index 05a59200f..5b7c0137a 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -2,4 +2,4 @@ altgraph==0.17 pyinstaller==4.2 -pyinstaller-hooks-contrib==2020.11 +pyinstaller-hooks-contrib==2021.1 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 22a195e66..b64b99e24 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py docutils==0.16 -Pygments==2.5.2 -pyroma==2.6.1 +Pygments==2.8.1 +pyroma==3.1 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 495b8dcf5..352be342a 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -10,12 +10,12 @@ imagesize==1.2.0 Jinja2==2.11.3 MarkupSafe==1.1.1 packaging==20.9 -Pygments==2.8.0 +Pygments==2.8.1 pyparsing==2.4.7 pytz==2021.1 requests==2.25.1 snowballstemmer==2.1.0 -Sphinx==3.5.1 +Sphinx==3.5.2 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 bf214be0d..2bfaf91e0 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -15,7 +15,7 @@ filelock==3.0.12 Flask==1.1.2 glob2==0.7 hunter==3.3.1 -hypothesis==6.3.4 +hypothesis==6.6.0 icdiff==1.9.1 idna==2.10 iniconfig==1.1.1 @@ -33,7 +33,7 @@ pluggy==0.13.1 pprintpp==0.4.0 py==1.10.0 py-cpuinfo==7.0.0 -Pygments==2.8.0 +Pygments==2.8.1 pyparsing==2.4.7 pytest==6.2.2 pytest-bdd==4.0.2 @@ -48,7 +48,7 @@ pytest-repeat==0.9.1 pytest-rerunfailures==9.1.1 pytest-xdist==2.2.1 pytest-xvfb==2.0.0 -PyVirtualDisplay==2.0 +PyVirtualDisplay==2.1 requests==2.25.1 requests-file==1.5.1 six==1.15.0 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 1e6382e1e..d44522118 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -8,9 +8,9 @@ pip==21.0.1 pluggy==0.13.1 py==1.10.0 pyparsing==2.4.7 -setuptools==54.0.0 +setuptools==54.1.1 six==1.15.0 toml==0.10.2 -tox==3.22.0 +tox==3.23.0 virtualenv==20.4.2 wheel==0.36.2 diff --git a/requirements.txt b/requirements.txt index c6eb86d6f..5572e206c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,11 +3,11 @@ adblock==0.4.2 ; python_version!="3.10" colorama==0.4.4 dataclasses==0.6 ; python_version<"3.7" -importlib-metadata==3.7.0 ; python_version<"3.8" -importlib-resources==5.1.1 ; python_version<"3.9" +importlib-metadata==3.7.2 ; python_version<"3.8" +importlib-resources==5.1.2 ; python_version<"3.9" Jinja2==2.11.3 MarkupSafe==1.1.1 -Pygments==2.8.0 +Pygments==2.8.1 PyYAML==5.4.1 typing-extensions==3.7.4.3 -zipp==3.4.0 +zipp==3.4.1 -- cgit v1.2.3-54-g00ecf From 45387d18a514103c686be559087420199cb49995 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 8 Mar 2021 11:26:39 +0100 Subject: Fix some tests with non-english locales See #6234 --- tests/end2end/features/misc.feature | 2 +- tests/end2end/features/qutescheme.feature | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 351135fab..5d81a890a 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -140,7 +140,7 @@ Feature: Various utility commands. Scenario: :jseval --file using a file that doesn't exist as js-code When I run :jseval --file /nonexistentfile - Then the error "[Errno 2] No such file or directory: '/nonexistentfile'" should be shown + Then the error "[Errno 2] *: '/nonexistentfile'" should be shown And "No output or error" should not be logged # :debug-webaction diff --git a/tests/end2end/features/qutescheme.feature b/tests/end2end/features/qutescheme.feature index 286f8f80a..1424bbf09 100644 --- a/tests/end2end/features/qutescheme.feature +++ b/tests/end2end/features/qutescheme.feature @@ -215,7 +215,7 @@ Feature: Special qute:// pages Scenario: Running :pyeval --file using a non existing file When I run :debug-pyeval --file nonexistentfile - Then the error "[Errno 2] No such file or directory: 'nonexistentfile'" should be shown + Then the error "[Errno 2] *: 'nonexistentfile'" should be shown Scenario: Running :pyeval with --quiet When I run :debug-pyeval --quiet 1+1 -- cgit v1.2.3-54-g00ecf From 76b03db20eac1d9b52156191656d4c2adb290911 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 8 Mar 2021 14:04:56 +0100 Subject: Revert "ci: Add workaround for Archlinux/Docker issue" This reverts commit 478e4de7bd1f26bebdcdc166d5369b2b5142c3e2. Fixed according to https://github.com/actions/virtual-environments/issues/2658 --- scripts/dev/ci/docker/Dockerfile.j2 | 7 ------- 1 file changed, 7 deletions(-) diff --git a/scripts/dev/ci/docker/Dockerfile.j2 b/scripts/dev/ci/docker/Dockerfile.j2 index 03e5684ad..d3fc82793 100644 --- a/scripts/dev/ci/docker/Dockerfile.j2 +++ b/scripts/dev/ci/docker/Dockerfile.j2 @@ -1,12 +1,5 @@ FROM archlinux:latest -# WORKAROUND for glibc 2.33 and old Docker -# See https://github.com/actions/virtual-environments/issues/2658 -# Thanks to https://github.com/lxqt/lxqt-panel/pull/1562 -RUN patched_glibc=glibc-linux4-2.33-4-x86_64.pkg.tar.zst && \ - curl -LO "https://repo.archlinuxcn.org/x86_64/$patched_glibc" && \ - bsdtar -C / -xvf "$patched_glibc" - {% if unstable %} RUN sed -i '/^# after the header/a[kde-unstable]\nInclude = /etc/pacman.d/mirrorlist\n\n[testing]\nInclude = /etc/pacman.d/mirrorlist' /etc/pacman.conf {% endif %} -- cgit v1.2.3-54-g00ecf From 55fdae8eed0f93152b5676cca77b6243fd95d9b0 Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Mon, 8 Mar 2021 17:40:57 +0100 Subject: userscripts/readability: add unique body class Until per-domains stylesheets are implemented, there is no way to apply style to the readability page only. With this patch, you can just use the global setting `content.user_stylesheets` by writing a more specific CSS selector. For example: body.qute-readability { font-family: Libertinus; font-size: 1.2em; text-align: justify; } will change the font and text alignment of the readability page, without altering the style of other websites. --- misc/userscripts/readability | 3 +++ misc/userscripts/readability-js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/misc/userscripts/readability b/misc/userscripts/readability index f9cbbf829..a6a6f2d52 100755 --- a/misc/userscripts/readability +++ b/misc/userscripts/readability @@ -57,6 +57,9 @@ with codecs.open(os.environ['QUTE_HTML'], 'r', 'utf-8') as source: title = doc.title() content = doc.summary().replace('', HEADER % title) + # add a class to make styling the page easier + content = content.replace('', '') + with codecs.open(tmpfile, 'w', 'utf-8') as target: target.write(content.lstrip()) diff --git a/misc/userscripts/readability-js b/misc/userscripts/readability-js index 2f24e065d..532df51c6 100755 --- a/misc/userscripts/readability-js +++ b/misc/userscripts/readability-js @@ -131,6 +131,9 @@ getDOM(target, domOpts).then(dom => { let article = reader.parse(); let content = util.format(HEADER, article.title) + article.content; + // add a class to make styling the page easier + content = content.replace('', '') + fs.writeFile(tmpFile, content, (err) => { if (err) { qute.messageError([`"${err}"`]) -- cgit v1.2.3-54-g00ecf From 163517800365ebc48c4947b3ad4ddd538ca8f31f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 8 Mar 2021 18:06:20 +0100 Subject: Update changelog --- doc/changelog.asciidoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 3840f369d..c78fe556a 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -60,6 +60,9 @@ Changed - The `fileselect.*.command` settings now support file selectors writing the selected paths to stdout, which is used if no `{}` placeholder is contained in the configured command. +- The `readability` and `readability-js` userscripts now add a + `qute-readability` CSS class to the page, so that it can be styled easily via + a user stylesheet. Fixed ~~~~~ -- cgit v1.2.3-54-g00ecf From 0fb5352cdf0f220d2cce34f84bf96cfbbf541a30 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 8 Mar 2021 20:51:32 +0100 Subject: Simplify _inject_greasemonkey_scripts The arguments aren't needed anymore since 05111e84236621d3923ed4efd36a7c6578407c20 and we can use f-strings nowadays. --- qutebrowser/browser/webengine/webenginetab.py | 47 ++++++++------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 4092fbe40..439d99570 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -1080,18 +1080,11 @@ class _WebEngineScripts(QObject): removed = page_scripts.remove(script) assert removed, script.name() - def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None, - remove_first=True): + def _inject_greasemonkey_scripts(self, scripts): """Register user JavaScript files with the current tab. Args: - scripts: A list of GreasemonkeyScripts, or None to add all - known by the Greasemonkey subsystem. - injection_point: The QWebEngineScript::InjectionPoint stage - to inject the script into, None to use - auto-detection. - remove_first: Whether to remove all previously injected - scripts before adding these ones. + scripts: A list of GreasemonkeyScripts. """ if sip.isdeleted(self._widget): return @@ -1102,11 +1095,7 @@ class _WebEngineScripts(QObject): # While, taking care not to remove any other scripts that might # have been added elsewhere, like the one for stylesheets. page_scripts = self._widget.page().scripts() - if remove_first: - self._remove_all_greasemonkey_scripts() - - if not scripts: - return + self._remove_all_greasemonkey_scripts() for script in scripts: new_script = QWebEngineScript() @@ -1114,37 +1103,29 @@ class _WebEngineScripts(QObject): world = int(script.jsworld) if not 0 <= world <= qtutils.MAX_WORLD_ID: log.greasemonkey.error( - "script {} has invalid value for '@qute-js-world'" - ": {}, should be between 0 and {}" - .format( - script.name, - script.jsworld, - qtutils.MAX_WORLD_ID)) + f"script {script.name} has invalid value for '@qute-js-world'" + f": {script.jsworld}, should be between 0 and " + f"{qtutils.MAX_WORLD_ID}") continue except ValueError: try: - world = _JS_WORLD_MAP[usertypes.JsWorld[ - script.jsworld.lower()]] + world = _JS_WORLD_MAP[usertypes.JsWorld[script.jsworld.lower()]] except KeyError: log.greasemonkey.error( - "script {} has invalid value for '@qute-js-world'" - ": {}".format(script.name, script.jsworld)) + f"script {script.name} has invalid value for '@qute-js-world'" + f": {script.jsworld}") continue new_script.setWorldId(world) new_script.setSourceCode(script.code()) - new_script.setName("GM-{}".format(script.name)) + new_script.setName(f"GM-{script.name}") new_script.setRunsOnSubFrames(script.runs_on_sub_frames) - # Override the @run-at value parsed by QWebEngineScript if desired. - if injection_point: - new_script.setInjectionPoint(injection_point) - elif script.needs_document_end_workaround(): - log.greasemonkey.debug("Forcing @run-at document-end for {}" - .format(script.name)) + if script.needs_document_end_workaround(): + log.greasemonkey.debug( + f"Forcing @run-at document-end for {script.name}") new_script.setInjectionPoint(QWebEngineScript.DocumentReady) - log.greasemonkey.debug('adding script: {}' - .format(new_script.name())) + log.greasemonkey.debug(f'adding script: {new_script.name()}') page_scripts.insert(new_script) def _inject_site_specific_quirks(self): -- cgit v1.2.3-54-g00ecf From 321a350b8fd9206a8677902b30327790698e151e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 Mar 2021 17:28:43 +0100 Subject: Add renderer process status to all error messages Might help debug issues such as #6235 --- qutebrowser/mainwindow/tabbedbrowser.py | 14 +++++--------- tests/end2end/features/misc.feature | 10 +++++----- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 52241d777..8d2801d31 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -929,16 +929,12 @@ class TabbedBrowser(QWidget): return messages = { - browsertab.TerminationStatus.abnormal: - "Renderer process exited with status {}".format(code), - browsertab.TerminationStatus.crashed: - "Renderer process crashed", - browsertab.TerminationStatus.killed: - "Renderer process was killed", - browsertab.TerminationStatus.unknown: - "Renderer process did not start", + browsertab.TerminationStatus.abnormal: "Renderer process exited", + browsertab.TerminationStatus.crashed: "Renderer process crashed", + browsertab.TerminationStatus.killed: "Renderer process was killed", + browsertab.TerminationStatus.unknown: "Renderer process did not start", } - msg = messages[status] + msg = messages[status] + f" (status {code})" def show_error_page(html): tab.set_html(html) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 5d81a890a..e6a02e038 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -528,13 +528,13 @@ Feature: Various utility commands. @qtwebkit_skip @no_invalid_lines @posix Scenario: Renderer crash When I run :open -t chrome://crash - Then "Renderer process crashed" should be logged + Then "Renderer process crashed (status *)" should be logged And "* 'Error loading chrome://crash/'" should be logged @qtwebkit_skip @no_invalid_lines @flaky Scenario: Renderer kill When I run :open -t chrome://kill - Then "Renderer process was killed" should be logged + Then "Renderer process was killed (status *)" should be logged And "* 'Error loading chrome://kill/'" should be logged # https://github.com/qutebrowser/qutebrowser/issues/2290 @@ -544,7 +544,7 @@ Feature: Various utility commands. And I open data/numbers/1.txt And I open data/numbers/2.txt in a new tab And I run :open chrome://kill - And I wait for "Renderer process was killed" in the log + And I wait for "Renderer process was killed (status *)" in the log And I open data/numbers/3.txt Then no crash should happen @@ -554,11 +554,11 @@ Feature: Various utility commands. When I open data/crashers/webrtc.html in a new tab And I run :reload And I wait until data/crashers/webrtc.html is loaded - Then "Renderer process crashed" should not be logged + Then "Renderer process crashed (status *)" should not be logged Scenario: InstalledApps crash When I open data/crashers/installedapp.html in a new tab - Then "Renderer process was killed" should not be logged + Then "Renderer process was killed (status *)" should not be logged ## Other -- cgit v1.2.3-54-g00ecf From c4144a6e4b524631cad3113617ab64a909fd0503 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 Mar 2021 17:38:48 +0100 Subject: Add log-sensitive-keys debug flag --- doc/changelog.asciidoc | 3 +++ qutebrowser/keyinput/modeman.py | 11 ++++++----- qutebrowser/qutebrowser.py | 3 ++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index c78fe556a..93a85866b 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -60,6 +60,9 @@ Changed - The `fileselect.*.command` settings now support file selectors writing the selected paths to stdout, which is used if no `{}` placeholder is contained in the configured command. +- The `--debug-flag` argument now understands a new `log-sensitive-keys` value + which logs all keypresses (including those in insert/passthrough/prompt/... + mode) for debugging. - The `readability` and `readability-js` userscripts now add a `qute-readability` CSS class to the page, so that it can be styled easily via a user stylesheet. diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 08c5a151b..c00120596 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -86,9 +86,10 @@ def init(win_id: int, parent: QObject) -> 'ModeManager': hintmanager = hints.HintManager(win_id, parent=parent) objreg.register('hintmanager', hintmanager, scope='window', window=win_id, command_only=True) - modeman.hintmanager = hintmanager + log_sensitive_keys = 'log-sensitive-keys' in objects.debug_flags + keyparsers: ParserDictType = { usertypes.KeyMode.normal: modeparsers.NormalKeyParser( @@ -110,7 +111,7 @@ def init(win_id: int, parent: QObject) -> 'ModeManager': commandrunner=commandrunner, parent=modeman, passthrough=True, - do_log=False, + do_log=log_sensitive_keys, supports_count=False), usertypes.KeyMode.passthrough: @@ -120,7 +121,7 @@ def init(win_id: int, parent: QObject) -> 'ModeManager': commandrunner=commandrunner, parent=modeman, passthrough=True, - do_log=False, + do_log=log_sensitive_keys, supports_count=False), usertypes.KeyMode.command: @@ -130,7 +131,7 @@ def init(win_id: int, parent: QObject) -> 'ModeManager': commandrunner=commandrunner, parent=modeman, passthrough=True, - do_log=False, + do_log=log_sensitive_keys, supports_count=False), usertypes.KeyMode.prompt: @@ -140,7 +141,7 @@ def init(win_id: int, parent: QObject) -> 'ModeManager': commandrunner=commandrunner, parent=modeman, passthrough=True, - do_log=False, + do_log=log_sensitive_keys, supports_count=False), usertypes.KeyMode.yesno: diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 64c175293..9e1fb91cd 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -173,6 +173,7 @@ def debug_flag_error(flag): log-requests: Log all network requests. log-cookies: Log cookies in cookie filter. log-scroll-pos: Log all scrolling changes. + log-sensitive-keys: Log keypresses in passthrough modes. stack: Enable Chromium stack logging. chromium: Enable Chromium logging. wait-renderer-process: Wait for debugger in renderer process. @@ -181,7 +182,7 @@ def debug_flag_error(flag): """ valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history', 'no-scroll-filtering', 'log-requests', 'log-cookies', - 'log-scroll-pos', 'stack', 'chromium', + 'log-scroll-pos', 'log-sensitive-keys', 'stack', 'chromium', 'wait-renderer-process', 'avoid-chromium-init', 'werror'] if flag in valid_flags: -- cgit v1.2.3-54-g00ecf From 7eae8f069406bb72fa4a663c22a4c04245d750f2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 Mar 2021 18:10:51 +0100 Subject: Fix @run-at default for GreaseMonkey --- doc/changelog.asciidoc | 4 +++ qutebrowser/browser/webengine/webenginetab.py | 12 ++++++++ tests/unit/browser/webengine/test_webenginetab.py | 34 +++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 93a85866b..d57698df7 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -93,6 +93,10 @@ Fixed properly. - The "try again" button on error pages now works correctly with JavaScript disabled. +- If a GreaseMonkey script doesn't have a "@run-at" comment, qutebrowser + accidentally treated that as "@run-at document-idle". However, other + GreaseMonkey implementations default to "@run-at document-end" instead, which + is what qutebrowser now does, too. [[v2.0.2]] v2.0.2 (2021-02-04) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 439d99570..450d68751 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -1099,6 +1099,7 @@ class _WebEngineScripts(QObject): for script in scripts: new_script = QWebEngineScript() + try: world = int(script.jsworld) if not 0 <= world <= qtutils.MAX_WORLD_ID: @@ -1116,6 +1117,17 @@ class _WebEngineScripts(QObject): f": {script.jsworld}") continue new_script.setWorldId(world) + + # Corresponds to "@run-at document-end" which is the default according to + # https://wiki.greasespot.net/Metadata_Block#.40run-at - however, + # QtWebEngine uses QWebEngineScript.Deferred (@run-at document-idle) as + # default. + # + # NOTE that this needs to be done before setSourceCode, so that + # QtWebEngine's parsing of GreaseMonkey tags will override it if there is a + # @run-at comment. + new_script.setInjectionPoint(QWebEngineScript.DocumentReady) + new_script.setSourceCode(script.code()) new_script.setName(f"GM-{script.name}") new_script.setRunsOnSubFrames(script.runs_on_sub_frames) diff --git a/tests/unit/browser/webengine/test_webenginetab.py b/tests/unit/browser/webengine/test_webenginetab.py index 7827c379b..2c792f80e 100644 --- a/tests/unit/browser/webengine/test_webenginetab.py +++ b/tests/unit/browser/webengine/test_webenginetab.py @@ -20,6 +20,7 @@ """Test webenginetab.""" import logging +import textwrap import pytest QtWebEngineWidgets = pytest.importorskip("PyQt5.QtWebEngineWidgets") @@ -116,6 +117,39 @@ class TestWebengineScripts: script = collection.toList()[-1] assert script.injectionPoint() == QWebEngineScript.DocumentReady + @pytest.mark.parametrize('run_at, expected', [ + # UserScript::DocumentElementCreation + ('document-start', QWebEngineScript.DocumentCreation), + # UserScript::DocumentLoadFinished + ('document-end', QWebEngineScript.DocumentReady), + # UserScript::AfterLoad + ('document-idle', QWebEngineScript.Deferred), + # default according to https://wiki.greasespot.net/Metadata_Block#.40run-at + (None, QWebEngineScript.DocumentReady), + ]) + def test_run_at_values(self, webengine_scripts, run_at, expected): + if run_at is None: + script = """ + // ==UserScript== + // @name qutebrowser test userscript + // ==/UserScript== + """ + else: + script = f""" + // ==UserScript== + // @name qutebrowser test userscript + // @run-at {run_at} + // ==/UserScript== + """ + + script = textwrap.dedent(script.lstrip('\n')) + scripts = [greasemonkey.GreasemonkeyScript.parse(script)] + webengine_scripts._inject_greasemonkey_scripts(scripts) + + collection = webengine_scripts._widget.page().scripts() + script = collection.toList()[-1] + assert script.injectionPoint() == expected + def test_notification_permission_workaround(): """Make sure the value for QWebEnginePage::Notifications is correct.""" -- cgit v1.2.3-54-g00ecf From fb4a56318c8159551d54f7098324ea908275854f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 Mar 2021 18:23:10 +0100 Subject: Simplify TestWebengineScripts --- tests/unit/browser/webengine/test_webenginetab.py | 70 ++++++++++++++--------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/tests/unit/browser/webengine/test_webenginetab.py b/tests/unit/browser/webengine/test_webenginetab.py index 2c792f80e..156f7d26f 100644 --- a/tests/unit/browser/webengine/test_webenginetab.py +++ b/tests/unit/browser/webengine/test_webenginetab.py @@ -36,15 +36,38 @@ webenginetab = pytest.importorskip( pytestmark = pytest.mark.usefixtures('greasemonkey_manager') +class ScriptsHelper: + + """Helper to get the processed (usually Greasemonkey) scripts.""" + + def __init__(self, tab): + self._tab = tab + + def get_scripts(self, prefix='GM-'): + return [ + s for s in self._tab._widget.page().scripts().toList() + if s.name().startswith(prefix) + ] + + def get_script(self): + scripts = self.get_scripts() + assert len(scripts) == 1 + return scripts[0] + + def inject(self, scripts): + self._tab._scripts._inject_greasemonkey_scripts(scripts) + return self.get_scripts() + + class TestWebengineScripts: """Test the _WebEngineScripts utility class.""" @pytest.fixture - def webengine_scripts(self, webengine_tab): - return webengine_tab._scripts + def scripts_helper(self, webengine_tab): + return ScriptsHelper(webengine_tab) - def test_greasemonkey_undefined_world(self, webengine_scripts, caplog): + def test_greasemonkey_undefined_world(self, scripts_helper, caplog): """Make sure scripts with non-existent worlds are rejected.""" scripts = [ greasemonkey.GreasemonkeyScript( @@ -52,18 +75,16 @@ class TestWebengineScripts: ] with caplog.at_level(logging.ERROR, 'greasemonkey'): - webengine_scripts._inject_greasemonkey_scripts(scripts) + injected = scripts_helper.inject(scripts) assert len(caplog.records) == 1 msg = caplog.messages[0] assert "has invalid value for '@qute-js-world': Mars" in msg - collection = webengine_scripts._widget.page().scripts().toList() - assert not any(script.name().startswith('GM-') - for script in collection) + + assert not injected @pytest.mark.parametrize("worldid", [-1, 257]) - def test_greasemonkey_out_of_range_world(self, worldid, webengine_scripts, - caplog): + def test_greasemonkey_out_of_range_world(self, worldid, scripts_helper, caplog): """Make sure scripts with out-of-range worlds are rejected.""" scripts = [ greasemonkey.GreasemonkeyScript( @@ -71,19 +92,18 @@ class TestWebengineScripts: ] with caplog.at_level(logging.ERROR, 'greasemonkey'): - webengine_scripts._inject_greasemonkey_scripts(scripts) + injected = scripts_helper.inject(scripts) assert len(caplog.records) == 1 msg = caplog.messages[0] assert "has invalid value for '@qute-js-world': " in msg assert "should be between 0 and" in msg - collection = webengine_scripts._widget.page().scripts().toList() - assert not any(script.name().startswith('GM-') - for script in collection) + + assert not injected @pytest.mark.parametrize("worldid", [0, 10]) def test_greasemonkey_good_worlds_are_passed(self, worldid, - webengine_scripts, caplog): + scripts_helper, caplog): """Make sure scripts with valid worlds have it set.""" scripts = [ greasemonkey.GreasemonkeyScript( @@ -92,13 +112,11 @@ class TestWebengineScripts: ] with caplog.at_level(logging.ERROR, 'greasemonkey'): - webengine_scripts._inject_greasemonkey_scripts(scripts) + scripts_helper.inject(scripts) - collection = webengine_scripts._widget.page().scripts() - assert collection.toList()[-1].worldId() == worldid + assert scripts_helper.get_script().worldId() == worldid - def test_greasemonkey_document_end_workaround(self, monkeypatch, - webengine_scripts): + def test_greasemonkey_document_end_workaround(self, monkeypatch, scripts_helper): """Make sure document-end is forced when needed.""" monkeypatch.setattr(greasemonkey.objects, 'backend', usertypes.Backend.QtWebEngine) @@ -110,11 +128,9 @@ class TestWebengineScripts: ('run-at', 'document-start'), ], None) ] + scripts_helper.inject(scripts) - webengine_scripts._inject_greasemonkey_scripts(scripts) - - collection = webengine_scripts._widget.page().scripts() - script = collection.toList()[-1] + script = scripts_helper.get_script() assert script.injectionPoint() == QWebEngineScript.DocumentReady @pytest.mark.parametrize('run_at, expected', [ @@ -127,7 +143,7 @@ class TestWebengineScripts: # default according to https://wiki.greasespot.net/Metadata_Block#.40run-at (None, QWebEngineScript.DocumentReady), ]) - def test_run_at_values(self, webengine_scripts, run_at, expected): + def test_greasemonkey_run_at_values(self, scripts_helper, run_at, expected): if run_at is None: script = """ // ==UserScript== @@ -144,11 +160,9 @@ class TestWebengineScripts: script = textwrap.dedent(script.lstrip('\n')) scripts = [greasemonkey.GreasemonkeyScript.parse(script)] - webengine_scripts._inject_greasemonkey_scripts(scripts) + scripts_helper.inject(scripts) - collection = webengine_scripts._widget.page().scripts() - script = collection.toList()[-1] - assert script.injectionPoint() == expected + assert scripts_helper.get_script().injectionPoint() == expected def test_notification_permission_workaround(): -- cgit v1.2.3-54-g00ecf From 0a38fff4c675b384289728a4d45f9c1fe1f9d4fc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 Mar 2021 18:42:19 +0100 Subject: Simplify test_greasemonkey via js_tester fixture --- pytest.ini | 1 - tests/conftest.py | 6 - tests/unit/javascript/conftest.py | 7 +- tests/unit/javascript/test_greasemonkey.py | 189 ++++++++++++----------------- 4 files changed, 80 insertions(+), 123 deletions(-) diff --git a/pytest.ini b/pytest.ini index d0f41948b..7f4a58de3 100644 --- a/pytest.ini +++ b/pytest.ini @@ -34,7 +34,6 @@ markers = no_invalid_lines: Don't fail on unparsable lines in end2end tests fake_os: Fake utils.is_* to a fake operating system unicode_locale: Tests which need a unicode locale to work - qtwebkit6021_xfail: Tests which would fail on WebKit version 602.1 js_headers: Sets JS headers dynamically on QtWebEngine (unsupported on some versions) qtwebkit_pdf_imageformat_skip: Broken on QtWebKit with PDF image format plugin installed windows_skip: Tests which should be skipped on Windows diff --git a/tests/conftest.py b/tests/conftest.py index ea7381a2f..ee945ac4c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -109,12 +109,6 @@ def _apply_platform_markers(config, item): pytest.mark.skipif, sys.getfilesystemencoding() == 'ascii', "Skipped because of ASCII locale"), - - ('qtwebkit6021_xfail', - pytest.mark.xfail, - version.qWebKitVersion and # type: ignore[unreachable] - version.qWebKitVersion() == '602.1', - "Broken on WebKit 602.1") ] for searched_marker, new_marker_kind, condition, default_reason in markers: diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index 47884687d..85d5ebe0a 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -28,6 +28,7 @@ import jinja2 from PyQt5.QtCore import QUrl import qutebrowser +from qutebrowser.utils import usertypes class JSTester: @@ -113,7 +114,7 @@ class JSTester: source = f.read() self.run(source, expected) - def run(self, source: str, expected, world=None) -> None: + def run(self, source: str, expected=usertypes.UNSET, world=None) -> None: """Run the given javascript source. Args: @@ -123,7 +124,9 @@ class JSTester: """ with self.qtbot.wait_callback() as callback: self.tab.run_js_async(source, callback, world=world) - callback.assert_called_with(expected) + + if expected is not usertypes.UNSET: + callback.assert_called_with(expected) @pytest.fixture diff --git a/tests/unit/javascript/test_greasemonkey.py b/tests/unit/javascript/test_greasemonkey.py index c28b9c8f7..3a3ea0294 100644 --- a/tests/unit/javascript/test_greasemonkey.py +++ b/tests/unit/javascript/test_greasemonkey.py @@ -25,7 +25,7 @@ import pytest import py.path # pylint: disable=no-name-in-module from PyQt5.QtCore import QUrl -from qutebrowser.utils import usertypes +from qutebrowser.utils import usertypes, version from qutebrowser.browser import greasemonkey from qutebrowser.misc import objects @@ -77,8 +77,7 @@ def test_get_scripts_by_url(url, expected_matches): gm_manager = greasemonkey.GreasemonkeyManager() scripts = gm_manager.scripts_for(QUrl(url)) - assert (len(scripts.start + scripts.end + scripts.idle) == - expected_matches) + assert len(scripts.start + scripts.end + scripts.idle) == expected_matches @pytest.mark.parametrize("url, expected_matches", [ @@ -102,8 +101,7 @@ def test_regex_includes_scripts_for(url, expected_matches): gm_manager = greasemonkey.GreasemonkeyManager() scripts = gm_manager.scripts_for(QUrl(url)) - assert (len(scripts.start + scripts.end + scripts.idle) == - expected_matches) + assert len(scripts.start + scripts.end + scripts.idle) == expected_matches def test_no_metadata(caplog): @@ -229,124 +227,87 @@ def test_required_scripts_are_included(download_stub, tmpdir): assert scripts[0].excludes -class TestWindowIsolation: +def test_window_isolation(js_tester, request): """Check that greasemonkey scripts get a shadowed global scope.""" + # Change something in the global scope + setup_script = "window.$ = 'global'" - @pytest.fixture - def setup(self): - # pylint: disable=attribute-defined-outside-init - class SetupData: - pass - ret = SetupData() - - # Change something in the global scope - ret.setup_script = "window.$ = 'global'" - - # Greasemonkey script to report back on its scope. - test_script = greasemonkey.GreasemonkeyScript.parse( - textwrap.dedent(""" - // ==UserScript== - // @name scopetest - // ==/UserScript== - // Check the thing the page set is set to the expected type - result.push(window.$); - result.push($); - // Now overwrite it - window.$ = 'shadowed'; - // And check everything is how the script would expect it to be - // after just writing to the "global" scope - result.push(window.$); - result.push($); - """) - ) - - # The compiled source of that scripts with some additional setup - # bookending it. - ret.test_script = "\n".join([ - """ - const result = []; - """, - test_script.code(), - """ - // Now check that the actual global scope has - // not been overwritten + # Greasemonkey script to report back on its scope. + test_gm_script = greasemonkey.GreasemonkeyScript.parse( + textwrap.dedent(""" + // ==UserScript== + // @name scopetest + // ==/UserScript== + // Check the thing the page set is set to the expected type + result.push(window.$); + result.push($); + // Now overwrite it + window.$ = 'shadowed'; + // And check everything is how the script would expect it to be + // after just writing to the "global" scope result.push(window.$); result.push($); - // And return our findings - result; - """ - ]) + """) + ) + + # The compiled source of that scripts with some additional setup + # bookending it. + test_script = "\n".join([ + """ + const result = []; + """, + test_gm_script.code(), + """ + // Now check that the actual global scope has + // not been overwritten + result.push(window.$); + result.push($); + // And return our findings + result; + """ + ]) - # What we expect the script to report back. - ret.expected = ["global", "global", - "shadowed", "shadowed", - "global", "global"] - return ret + # What we expect the script to report back. + expected = ["global", "global", "shadowed", "shadowed", "global", "global"] - def test_webengine(self, qtbot, webengineview, setup): - page = webengineview.page() - page.runJavaScript(setup.setup_script) + # The JSCore in 602.1 doesn't fully support Proxy. + xfail = False + if (js_tester.tab.backend == usertypes.Backend.QtWebKit and + version.qWebKitVersion() == '602.1'): + expected[-1] = 'shadowed' + expected[-2] = 'shadowed' + xfail = True - with qtbot.wait_callback() as callback: - page.runJavaScript(setup.test_script, callback) - callback.assert_called_with(setup.expected) + js_tester.run(setup_script) + js_tester.run(test_script, expected=expected) - # The JSCore in 602.1 doesn't fully support Proxy. - @pytest.mark.qtwebkit6021_xfail - def test_webkit(self, webview, setup): - elem = webview.page().mainFrame().documentElement() - elem.evaluateJavaScript(setup.setup_script) - result = elem.evaluateJavaScript(setup.test_script) - assert result == setup.expected + if xfail: + pytest.xfail("Broken on WebKit 602.1") -class TestSharedWindowProxy: +def test_shared_window_proxy(js_tester): """Check that all scripts have access to the same window proxy.""" + # Greasemonkey script to add a property to the window proxy. + test_script_a = greasemonkey.GreasemonkeyScript.parse( + textwrap.dedent(""" + // ==UserScript== + // @name a + // ==/UserScript== + // Set a value from script a + window.$ = 'test'; + """) + ).code() + + # Greasemonkey script to retrieve a property from the window proxy. + test_script_b = greasemonkey.GreasemonkeyScript.parse( + textwrap.dedent(""" + // ==UserScript== + // @name b + // ==/UserScript== + // Check that the value is accessible from script b + return [window.$, $]; + """) + ).code() - @pytest.fixture - def setup(self): - # pylint: disable=attribute-defined-outside-init - class SetupData: - pass - ret = SetupData() - - # Greasemonkey script to add a property to the window proxy. - ret.test_script_a = greasemonkey.GreasemonkeyScript.parse( - textwrap.dedent(""" - // ==UserScript== - // @name a - // ==/UserScript== - // Set a value from script a - window.$ = 'test'; - """) - ).code() - - # Greasemonkey script to retrieve a property from the window proxy. - ret.test_script_b = greasemonkey.GreasemonkeyScript.parse( - textwrap.dedent(""" - // ==UserScript== - // @name b - // ==/UserScript== - // Check that the value is accessible from script b - return [window.$, $]; - """) - ).code() - - # What we expect the script to report back. - ret.expected = ["test", "test"] - return ret - - def test_webengine(self, qtbot, webengineview, setup): - page = webengineview.page() - - with qtbot.wait_callback() as callback: - page.runJavaScript(setup.test_script_a, callback) - with qtbot.wait_callback() as callback: - page.runJavaScript(setup.test_script_b, callback) - callback.assert_called_with(setup.expected) - - def test_webkit(self, webview, setup): - elem = webview.page().mainFrame().documentElement() - elem.evaluateJavaScript(setup.test_script_a) - result = elem.evaluateJavaScript(setup.test_script_b) - assert result == setup.expected + js_tester.run(test_script_a) + js_tester.run(test_script_b, expected=["test", "test"]) -- cgit v1.2.3-54-g00ecf