Merge remote-tracking branch 'upstream/master' into HEAD

This commit is contained in:
Jay Kamat 2019-03-27 19:19:59 -07:00
commit be7d8bbbd5
No known key found for this signature in database
GPG Key ID: 5D2E399600F4F7B5
445 changed files with 6798 additions and 3930 deletions

View File

@ -5,15 +5,15 @@ cache:
build: off
environment:
PYTHONUNBUFFERED: 1
PYTHON: C:\Python36-x64\python.exe
PYTHON: C:\Python37-x64\python.exe
matrix:
- TESTENV: py36-pyqt511
- TESTENV: py37-pyqt512
- TESTENV: pylint
install:
- '%PYTHON% -m pip install -U pip'
- '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt'
- 'set PATH=C:\Python36-x64;%PATH'
- 'set PATH=C:\Python37-x64;%PATH'
test_script:
- '%PYTHON% -m tox -e %TESTENV%'

View File

@ -14,6 +14,7 @@ exclude_lines =
raise NotImplementedError
raise utils\.Unreachable
if __name__ == ["']__main__["']:
if MYPY:
[xml]
output=coverage.xml

10
.flake8
View File

@ -46,11 +46,11 @@ ignore =
min-version = 3.4.0
max-complexity = 12
per-file-ignores =
/tests/**/*.py : D100,D101,D401
/tests/unit/browser/test_history.py : N806
/tests/helpers/fixtures.py : N806
/tests/unit/browser/webkit/http/test_content_disposition.py : D400
/scripts/dev/ci/appveyor_install.py : FI53
qutebrowser/api/hook.py : N801
tests/* : D100,D101
tests/unit/browser/test_history.py : D100,D101,N806
tests/helpers/fixtures.py : D100,D101,N806
tests/unit/browser/webkit/http/test_content_disposition.py : D100,D101,D400
copyright-check = True
copyright-regexp = # Copyright [\d-]+ .*
copyright-min-file-size = 110

View File

@ -1,10 +1,6 @@
IMPORTANT: *Currently, bigger changes are going on in qutebrowser, as
part of a
https://lists.schokokeks.org/pipermail/qutebrowser-announce/2018-September/000051.html[student research project]
about adding a plugin API to qutebrowser and moving a lot of code from the code
into plugins.* Due to that, bandwidth for pull request review is currently
very limited, and contributions might lead to merge conflicts due to
ongoing refactorings.
IMPORTANT: Bandwidth for pull request review is currently quite limited. If you
want to contribute where it's most needed, please consider reviewing or testing
open pull requests.
- Before you start to work on something, please leave a comment on the relevant
issue (or open one). This makes sure there is no duplicate work done.

View File

@ -6,7 +6,7 @@ about: Report errors and problems
**Version info (see `:version`)**:
**Does the bug happen if you start with `--temp-basedir`?** (if applicable):
**Does the bug happen if you start with `--temp-basedir`?**:
**Description**

1
.gitignore vendored
View File

@ -41,3 +41,4 @@ TODO
/scripts/testbrowser/cpp/webengine/.qmake.stash
/scripts/dev/pylint_checkers/qute_pylint.egg-info
/misc/file_version_info.txt
/doc/extapi/_build

View File

@ -1,74 +1,87 @@
dist: xenial
language: python
group: edge
python: 3.6
python: 3.7
os: linux
matrix:
fast_finish: true
include:
- os: linux
env: DOCKER=archlinux
### Archlinux QtWebKit
- env: DOCKER=archlinux
services: docker
- os: linux
env: DOCKER=archlinux-webengine QUTE_BDD_WEBENGINE=true
### Archlinux QtWebEngine
- env: DOCKER=archlinux-webengine QUTE_BDD_WEBENGINE=true
services: docker
- os: linux
env: TESTENV=py36-pyqt571
- os: linux
python: 3.5
### PyQt 5.7.1 (Python 3.5)
- python: 3.5
env: TESTENV=py35-pyqt571
- os: linux
env: TESTENV=py36-pyqt59
- os: linux
env: TESTENV=py36-pyqt510
### PyQt 5.7.1 (Python 3.6)
- python: 3.6
env: TESTENV=py36-pyqt571
### PyQt 5.9
- env: TESTENV=py37-pyqt59
### PyQt 5.10
- env: TESTENV=py37-pyqt510
addons:
apt:
packages:
- xfonts-base
- os: linux
env: TESTENV=py36-pyqt511-cov
- os: linux
python: 3.7
env: TESTENV=py37-pyqt511
### PyQt 5.11
- env: TESTENV=py37-pyqt511
### PyQt 5.12 (Python 3.7, with coverage)
- env: TESTENV=py37-pyqt512-cov
# http://code.qt.io/cgit/qt/qtbase.git/commit/?id=c3a963da1f9e7b1d37e63eedded61da4fbdaaf9a
addons:
apt:
packages:
- libxkbcommon-x11-0
### macOS sierra
- os: osx
env: TESTENV=py37 OSX=sierra
env: TESTENV=py37-pyqt512 OSX=sierra
osx_image: xcode9.2
language: generic
### macOS yosemite
# https://github.com/qutebrowser/qutebrowser/issues/2013
# - os: osx
# env: TESTENV=py35 OSX=yosemite
# osx_image: xcode6.4
- os: linux
env: TESTENV=pylint PYTHON=python3.6
- os: linux
env: TESTENV=flake8
- os: linux
env: TESTENV=docs
### pylint/flake8/mypy
- env: TESTENV=pylint
- env: TESTENV=flake8
- env: TESTENV=mypy
### docs
- env: TESTENV=docs
addons:
apt:
packages:
- asciidoc
- os: linux
env: TESTENV=vulture
- os: linux
env: TESTENV=misc
- os: linux
env: TESTENV=pyroma
- os: linux
env: TESTENV=check-manifest
- os: linux
env: TESTENV=eslint
### vulture/misc/pyroma/check-manifest
- env: TESTENV=vulture
- env: TESTENV=misc
- env: TESTENV=pyroma
- env: TESTENV=check-manifest
### eslint
- env: TESTENV=eslint
language: node_js
python: null
node_js: "lts/*"
- os: linux
language: generic
### shellcheck
- language: generic
env: TESTENV=shellcheck
services: docker
fast_finish: true
allow_failures:
# https://github.com/qutebrowser/qutebrowser/issues/4055
- os: linux
env: TESTENV=py36-pyqt510
cache:
directories:
@ -87,16 +100,3 @@ after_success:
after_failure:
- bash scripts/dev/ci/travis_backtrace.sh
notifications:
webhooks:
- https://buildtimetrend.herokuapp.com/travis
irc:
channels:
- "chat.freenode.net#qutebrowser-dev"
on_success: always
on_failure: always
skip_join: true
template:
- "%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}"
- "%{compare_url} - %{build_url}"

View File

@ -12,8 +12,8 @@ recursive-include scripts *.py *.sh *.js
include qutebrowser/utils/testfile
include qutebrowser/git-commit-id
include LICENSE doc/* README.asciidoc
include misc/qutebrowser.desktop
include misc/qutebrowser.appdata.xml
include misc/org.qutebrowser.qutebrowser.desktop
include misc/org.qutebrowser.qutebrowser.appdata.xml
include misc/Makefile
include requirements.txt
include tox.ini
@ -32,6 +32,7 @@ include doc/changelog.asciidoc
prune tests
prune qutebrowser/3rdparty
exclude pytest.ini
exclude mypy.ini
exclude qutebrowser/javascript/.eslintrc.yaml
exclude qutebrowser/javascript/.eslintignore
exclude doc/help
@ -39,5 +40,6 @@ exclude .*
exclude misc/qutebrowser.spec
exclude misc/qutebrowser.nsi
exclude misc/qutebrowser.rcc
prune doc/extapi
global-exclude __pycache__ *.pyc *.pyo

View File

@ -97,7 +97,7 @@ Requirements
The following software and libraries are required to run qutebrowser:
* https://www.python.org/[Python] 3.5 or newer (3.6 recommended)
* https://www.qt.io/[Qt] 5.7.1 or newer (5.11 recommended, support for < 5.9
* https://www.qt.io/[Qt] 5.7.1 or newer (5.12 recommended, support for < 5.9
will be dropped soon) with the following modules:
- QtCore / qtbase
- QtQuick (part of qtbase in some distributions)
@ -108,7 +108,7 @@ The following software and libraries are required to run qutebrowser:
only the link:https://github.com/annulen/webkit/wiki[updated fork] (5.212)
is supported
* https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
(5.11 recommended, support for < 5.9 will be dropped soon) for Python 3
(5.12 recommended, support for < 5.9 will be dropped soon) for Python 3
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
* https://fdik.org/pyPEG/[pyPEG2]
* http://jinja.pocoo.org/[jinja2]

View File

@ -15,32 +15,91 @@ breaking changes (such as renamed commands) can happen in minor releases.
// `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities.
v1.6.0 (unreleased)
v1.7.0 (unreleased)
-------------------
Added
~~~~~
- New `tabs.new_position.stacking` setting which controls whether new tabs
opened from a page should stack on each other or not.
- New `completion.open_categories` setting which allows to configure which
categories are shown in the `:open` completion, and how they are ordered.
- New settings:
* `colors.tabs.pinned.*` to control colors of pinned tabs.
Changed
~~~~~~~
- The desktop file `qutebrowser.desktop` is now renamed to
`org.qutebrowser.qutebrowser.desktop`.
- Pinned tabs now always show a favicon (even if the site doesn't provide one)
when shrinking.
Fixed
~~~~~
v1.6.1
------
Changed
~~~~~~~
- Windows/macOS releases now ship with Qt 5.12.2, which includes
security fixes up to Chromium 72.0.3626.121 (including CVE-2019-5786
which is known to be exploited in the wild).
Fixed
~~~~~
- Crash when using `:config-{dict,list}-{add,remove}` with an invalid setting.
- Functionality like hinting on pages with an element with ID `_qutebrowser` (such as qutebrowser.org) on Qt 5.12.
- The .desktop file in v1.6.0 was missing the "Actions" key, which is now fixed.
- The SVG icon now has a size of 256x256px set to comply with freedesktop standards.
- Setting `colors.statusbar.*.bg` to a gradient now has the expected effect of
the gradient spanning the entire statusbar.
v1.6.0
------
Added
~~~~~
- New settings:
* `tabs.new_position.stacking` which controls whether new tabs opened from a
page should stack on each other or not.
* `completion.open_categories` which allows to configure which categories are
shown in the `:open` completion, and how they are ordered.
* `tabs.pinned.frozen` to allow/deny navigating in pinned tabs.
* `hints.selectors` which allows to configure what CSS selectors are used for
hints, and also allows adding custom hint groups.
* `input.insert_mode.leave_on_load` to turn off leaving insert mode when a
new page is loaded.
- New config manipulation commands:
* `:config-dict-add` and `:config-list-add` to a new element to a dict/list
setting.
* `:config-dict-remove` and `:config-list-remove` to remove an element from a
dict/list setting.
- New `hints.selectors` setting which allows to configure what CSS selectors
are used for hints, and also allows adding custom hint groups.
- New `:yank markdown` feature which yanks the current URL and title in
markdown format.
- New command `:navigate strip` which removes queries and parameters from the
current URL (bound to `gs`/`gS` by default).
- Support for new QtWebEngine features in Qt 5.12:
* Basic support for client certificates. Selecting the certificate to use
when there are multiple matching certificates isn't implemented yet.
* Support for DNS prefetching (plus new `content.dns_prefetch` setting).
Changed
~~~~~~~
- `:q` now closes current window instead of quitting qutebrowser completely
- Various changes to the Windows and macOS builds:
* Bundling Qt 5.12.1, based on Chromium 69.0.3497.128 with security fixes up
to 71.0.3578.94.
* Windows: A 32-bit build is available again.
* Windows: The builds now bundle the Universal CRT DLLs, causing them to work
on earlier versions of Windows 10.
* macOS: Support for OS X 10.11 El Capitan was dropped, requiring macOS 10.12
Sierra or newer.
* macOS: The IPC socket path used to communicate with existing instances
changed due to changes in Qt 5.12. Please make sure to quit qutebrowser
before upgrading.
- `:q` now closes the current window instead of quitting qutebrowser completely
(`:close`), while `:qa` quits (`:quit`). The behavior of `:wq` remains
unchanged (`:quit --save`), as closing a window while saving the session
doesn't make sense.
@ -53,6 +112,20 @@ Changed
adblocker can be disabled on a given page.
- Elements with a `tabindex` attribute now also get hints by default.
- Various small performance improvements for hints and the completion.
- The Wayland check for QtWebEngine is now disabled on Qt >= 5.11.2, as those
versions should work without any issues.
- The JavaScript `console` object is now available in PAC files.
- PAC proxies currently don't work properly on QtWebEngine (and never did), so
an error is now shown when trying to configure a PAC proxy.
- The metainfo file `qutebrowser.appdata.xml` is now renamed to
`org.qutebrowser.qutebrowser.appdata.xml`.
- The `qute-pass` userscript now understands domains in gpg filenames
in addition to directory names.
- The autocompletion for `content.headers.user_agent` got updated to only
include the default and Chrome, as setting the UA to Firefox has various
bad side-effects.
- Combining Qt 5.12 with an older PyQt can lead to issues, so a warning is
now shown when starting qutebrowser with that combination.
Fixed
~~~~~
@ -67,7 +140,28 @@ Fixed
`content.cookies.accept = no-3rdparty` from working properly on some pages
like GMail. However, the default for `content.cookies.accept` is still `all`
to be in line with what other browsers do.
- `:navigate` not incrementing in anchors or queries or anchors.
- `:navigate` not incrementing in anchors or queries.
- Crash when trying to use a proxy requiring authentication with QtWebKit.
- Slashes in search terms are now percent-escaped.
- When `scrolling.bar = True` was set in versions before v1.5.0, this now
correctly gets migrated to `always` instead of `when-searching`.
- Completion highlighting now works again on Qt 5.11.3 and 5.12.1.
- The non-standard header `X-Do-Not-Track` is no longer sent.
- PAC proxies were never correctly supported with QtWebEngine, but are now
explicitly disallowed.
- macOS: Context menus for download items now show in the correct macOS style.
- Issues with fullscreen handling when exiting a video player.
- Various fixes for Qt 5.12 issues:
* A javascript error on page load was fixed.
* `window.print()` works with Qt 5.12 now.
* Fixed handling of duplicate download filenames.
* Fixed broken `qute://history` page.
* Fixed PDF.js not working properly.
* The download button in PDF.js now works (it's not possible to make
it work with earlier Qt versions).
* Since Greasemonkey scripts modifying the DOM fail when being run at
document-start, some known-broken scripts (Iridium, userstyles.org) are now
forced to run at document-end.
v1.5.2
------
@ -367,11 +461,11 @@ v1.3.3
Security
~~~~~~~~
- An XSS vulnerability on the `qute://history` page allowed websites to inject
HTML into the page via a crafted title tag. This could allow them to steal
your browsing history. If you're currently unable to upgrade, avoid using
`:history`. A CVE request for this issue is pending, see
https://github.com/qutebrowser/qutebrowser/issues/4011[#4011] for updates.
- CVE-2018-1000559: An XSS vulnerability on the `qute://history` page allowed
websites to inject HTML into the page via a crafted title tag. This could
allow them to steal your browsing history. If you're currently unable to
upgrade, avoid using `:history`. See the related GitHub issue for details:
https://github.com/qutebrowser/qutebrowser/issues/4011.
Fixed
~~~~~

View File

@ -5,13 +5,9 @@ The Compiler <mail@qutebrowser.org>
:data-uri:
:toc:
IMPORTANT: *Currently, bigger changes are going on in qutebrowser, as
part of a
https://lists.schokokeks.org/pipermail/qutebrowser-announce/2018-September/000051.html[student research project]
about adding a plugin API to qutebrowser and moving a lot of code from the code
into plugins.* Due to that, bandwidth for pull request review is currently
very limited, and contributions might lead to merge conflicts due to
ongoing refactorings.
IMPORTANT: Bandwidth for pull request review is currently quite limited. If you
want to contribute where it's most needed, please consider reviewing or testing
open pull requests.
I `&lt;3` footnote:[Of course, that says `<3` in HTML.] contributors!
@ -222,7 +218,7 @@ Some resources which might be handy:
* http://doc.qt.io/qt-5/classes.html[The Qt5 reference]
* https://docs.python.org/3/library/index.html[The Python reference]
* http://httpbin.org/[httpbin, a test service for HTTP requests/responses]
* http://requestb.in/[RequestBin, a service to inspect HTTP requests]
* https://requestbin.com/[RequestBin, a service to inspect HTTP requests]
Documentation of used Python libraries:
@ -407,7 +403,7 @@ Creating a new command is straightforward:
[source,python]
----
import qutebrowser.commands.cmdutils
from qutebrowser.api import cmdutils
...
@ -429,7 +425,7 @@ selects which object registry (global, per-tab, etc.) to use. See the
There are also other arguments to customize the way the command is
registered; see the class documentation for `register` in
`qutebrowser.commands.cmdutils` for details.
`qutebrowser.api.cmdutils` for details.
The types of the function arguments are inferred based on their default values,
e.g., an argument `foo=True` will be converted to a flag `-f`/`--foo` in
@ -480,8 +476,10 @@ For `typing.Union` types, the given `choices` are only checked if other types
The following arguments are supported for `@cmdutils.argument`:
- `flag`: Customize the short flag (`-x`) the argument will get.
- `win_id=True`: Mark the argument as special window ID argument.
- `count=True`: Mark the argument as special count argument.
- `value`: Tell qutebrowser to fill the argument with special values:
- `value=cmdutils.Value.count`: The `count` given by the user to the command.
- `value=cmdutils.Value.win_id`: The window ID of the current window.
- `value=cmdutils.Value.cur_tab`: The tab object which is currently focused.
- `completion`: A completion function (see `qutebrowser.completions.models.*`)
to use when completing arguments for the given command.
- `choices`: The allowed string choices for the argument.
@ -606,7 +604,7 @@ Style conventions
-----------------
qutebrowser's coding conventions are based on
http://legacy.python.org/dev/peps/pep-0008/[PEP8] and the https://google-styleguide.googlecode.com/svn/trunk/pyguide.html[Google Python style guidelines] with some additions:
http://legacy.python.org/dev/peps/pep-0008/[PEP8] and the https://google.github.io/styleguide/pyguide.html[Google Python style guidelines] with some additions:
* The _Raise:_ section is not added to the docstring.
* Methods overriding Qt methods (obviously!) don't follow the naming schemes.
@ -708,6 +706,7 @@ qutebrowser release
* Update changelog (remove *(unreleased)*).
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
* Consider updating the completions for `content.headers.user_agent` in `configdata.yml`.
* Commit.
* Create annotated git tag (`git tag -s "v1.$x.$y" -m "Release v1.$x.$y"`).
@ -721,7 +720,7 @@ as closed.
* Windows: Run `git checkout v1.X.Y; py -3 scripts\dev\build_release.py --asciidoc C:\Python27\python %userprofile%\bin\asciidoc-8.6.10\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
* macOS: Run `git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
* On server:
- Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
- Run `bash download_release.sh 1.X.Y` (replace X/Y by hand).
- Run `git pull github master && sudo python3 scripts/asciidoc2html.py --website /srv/http/qutebrowser`
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed.
* Announce to qutebrowser and qutebrowser-announce mailinglist.

View File

View File

48
doc/extapi/api.rst Normal file
View File

@ -0,0 +1,48 @@
API modules
===========
cmdutils module
---------------
.. automodule:: qutebrowser.api.cmdutils
:members:
:imported-members:
apitypes module
---------------
.. automodule:: qutebrowser.api.apitypes
:members:
:imported-members:
config module
-------------
.. automodule:: qutebrowser.api.config
:members:
downloads module
----------------
.. automodule:: qutebrowser.api.downloads
:members:
hook module
-----------
.. automodule:: qutebrowser.api.hook
:members:
interceptor module
------------------
.. automodule:: qutebrowser.api.interceptor
:members:
:imported-members:
message module
--------------
.. automodule:: qutebrowser.api.message
:members:
:imported-members:

179
doc/extapi/conf.py Normal file
View File

@ -0,0 +1,179 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'qutebrowser extensions'
copyright = '2018-2019, Florian Bruhin'
author = 'Florian Bruhin'
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = ''
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
]
autodoc_member_order = 'bysource'
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'qutebrowserextensionsdoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'qutebrowserextensions.tex', 'qutebrowser extensions Documentation',
'Florian Bruhin', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'qutebrowserextensions', 'qutebrowser extensions Documentation',
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'qutebrowserextensions', 'qutebrowser extensions Documentation',
author, 'qutebrowserextensions', 'One line description of project.',
'Miscellaneous'),
]
# -- Options for Epub output -------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# -- Extension configuration -------------------------------------------------

22
doc/extapi/index.rst Normal file
View File

@ -0,0 +1,22 @@
.. qutebrowser extensions documentation master file, created by
sphinx-quickstart on Tue Dec 11 18:59:44 2018.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to qutebrowser extensions's documentation!
==================================================
.. toctree::
:maxdepth: 2
:caption: Contents:
api
tab
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

44
doc/extapi/tab.rst Normal file
View File

@ -0,0 +1,44 @@
Tab API
=======
.. autoclass:: qutebrowser.browser.browsertab.AbstractTab()
:members:
.. autoclass:: qutebrowser.browser.browsertab.AbstractAction()
:members:
.. autoclass:: qutebrowser.browser.browsertab.AbstractPrinting()
:members:
.. autoclass:: qutebrowser.browser.browsertab.AbstractSearch()
:members:
.. autoclass:: qutebrowser.browser.browsertab.AbstractZoom()
:members:
.. autoclass:: qutebrowser.browser.browsertab.AbstractCaret()
:members:
.. autoclass:: qutebrowser.browser.browsertab.AbstractScroller()
:members:
.. autoclass:: qutebrowser.browser.browsertab.AbstractHistory()
:members:
.. autoclass:: qutebrowser.browser.browsertab.AbstractElements()
:members:
.. autoclass:: qutebrowser.browser.browsertab.AbstractAudio()
:members:
Web element API
===============
.. autoclass:: qutebrowser.browser.webelem.AbstractWebElement
:members:
.. autoclass:: qutebrowser.browser.webelem.Error
:members:
.. autoclass:: qutebrowser.browser.webelem.OrphanedError
:members:

View File

@ -211,9 +211,10 @@ Why does J move to the next (right) tab, and K to the previous (left) one?::
What's the difference between insert and passthrough mode?::
They are quite similar, but insert mode has some bindings (like `Ctrl-e` to
open an editor) while passthrough mode only has escape bound. It might also
be useful to rebind escape to something else in passthrough mode only, to be
able to send an escape keypress to the website.
open an editor) while passthrough mode only has shift+escape bound. This is
because shift+escape is unlikely to be a useful binding to be passed to a
webpage. However, any other keys may be assigned to leaving passthrough mode
instead of shift+escape should this be desired.
Why does it take longer to open a URL in qutebrowser than in chromium?::
When opening a URL in an existing instance, the normal qutebrowser
@ -260,6 +261,12 @@ Note that there are some missing features which you may run into:
. Any greasemonkey API function to do with adding UI elements is not currently
supported. That means context menu extentensions and background pages.
How do I change the `WM_CLASS` used by qutebrowser windows?::
Qt only supports setting `WM_CLASS` globally, which you can do by starting
with `--qt-arg name foo`. Note that all windows are part of the same
qutebrowser instance (unless you use `--temp-basedir` or `--basedir`), so
they all will share the same `WM_CLASS`.
== Troubleshooting
Unable to view flash content.::

View File

@ -165,8 +165,7 @@ If no command is given, show the current binding for the given key. Using :bind
* +'command'+: The command to execute, with optional args.
==== optional arguments
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`). See `:help bindings.commands` for the
available modes.
* +*-m*+, +*--mode*+: The mode to bind the key in (default: `normal`). See `:help bindings.commands` for the available modes.
* +*-d*+, +*--default*+: If given, restore a default binding.
@ -1426,7 +1425,7 @@ Unbind a keychain.
==== optional arguments
* +*-m*+, +*--mode*+: A mode to unbind the key in (default: `normal`). See `:help bindings.commands` for the available modes.
* +*-m*+, +*--mode*+: The mode to unbind the key in (default: `normal`). See `:help bindings.commands` for the available modes.
[[undo]]
@ -1485,14 +1484,14 @@ Yank something to the clipboard or primary selection.
[[zoom]]
=== zoom
Syntax: +:zoom [*--quiet*] ['zoom']+
Syntax: +:zoom [*--quiet*] ['level']+
Set the zoom level for the current tab.
The zoom can be given as argument or as [count]. If neither is given, the zoom is set to the default zoom. If both are given, use [count].
==== positional arguments
* +'zoom'+: The zoom percentage to set.
* +'level'+: The zoom percentage to set.
==== optional arguments
* +*-q*+, +*--quiet*+: Don't show a zoom level message.

View File

@ -19,10 +19,10 @@ hand, you can simply use those - see
<<autoconfig,"Configuring qutebrowser via the user interface">> for details.
For more advanced configuration, you can write a `config.py` file - see
<<configpy,"Configuring qutebrowser via config.py">>. As soon as a `config.py`
<<configpy,"Configuring qutebrowser via config.py">>. When a `config.py`
exists, the `autoconfig.yml` file **is not read anymore** by default. You need
to <<configpy-autoconfig,load it by hand>> if you want settings done via
`:set`/`:bind` to still persist.
to <<configpy-autoconfig,load it from `config.py`>> if you want settings changed via
`:set`/`:bind` to persist between restarts.
[[autoconfig]]
Configuring qutebrowser via the user interface
@ -89,7 +89,7 @@ You can run `:config-edit` inside qutebrowser to open the file in your editor,
The file should be located in the "config" location listed on
link:qute://version[], which is typically `~/.config/qutebrowser/config.py` on
Linux, `~/.qutebrowser/config.py` on macOS, and
`%APPDATA%/qutebrowser/config.py` on Windows.
`%APPDATA%/qutebrowser/config/config.py` on Windows.
Two global objects are pre-defined when running `config.py`: `c` and `config`.
@ -229,18 +229,18 @@ Loading `autoconfig.yml`
~~~~~~~~~~~~~~~~~~~~~~~~
All customization done via the UI (`:set`, `:bind` and `:unbind`) is
stored in the `autoconfig.yml` file, which is not loaded automatically as soon
as a `config.py` exists. If you want those settings to be loaded, you'll need to
explicitly load the `autoconfig.yml` file in your `config.py` by doing:
stored in the `autoconfig.yml` file. When a `config.py` file exists, `autoconfig.yml`
is not loaded automatically. To load `autoconfig.yml` automatically, add the
following snippet to `config.py`:
.config.py:
[source,python]
----
config.load_autoconfig()
----
If you do so at the top of your file, your `config.py` settings will take
precedence as they overwrite the settings done in `autoconfig.yml`.
You can configure which file overrides the other by the location of the above code snippet.
Place the snippet at the top to allow `config.py` to override `autoconfig.yml`.
Place the snippet at the bottom for the opposite effect.
Importing other modules
~~~~~~~~~~~~~~~~~~~~~~~
@ -309,7 +309,7 @@ You can use:
import yaml
with (config.configdir / 'config.yml').open() as f:
yaml_data = yaml.load(f)
yaml_data = yaml.safe_load(f)
for k, v in yaml_data.items():
config.set(k, v)
@ -339,7 +339,7 @@ You can use:
import yaml
with (config.configdir / 'colors.yml').open() as f:
yaml_data = yaml.load(f)
yaml_data = yaml.safe_load(f)
def dict_attrs(obj, path=''):
if isinstance(obj, dict):
@ -396,6 +396,7 @@ Pre-built colorschemes
- A collection of https://github.com/chriskempson/base16[base16] color-schemes can be found in https://github.com/theova/base16-qutebrowser[base16-qutebrowser] and used with https://github.com/AuditeMarlow/base16-manager[base16-manager].
- Two implementations of the https://github.com/arcticicestudio/nord[Nord] colorscheme for qutebrowser exist: https://github.com/Linuus/nord-qutebrowser[Linuus], https://github.com/KnownAsDon/QuteBrowser-Nord-Theme[KnownAsDon]
- https://github.com/evannagle/qutebrowser-dracula-theme[Dracula]
- https://github.com/jjzmajic/qutewal[Pywal theme]
Avoiding flake8 errors
^^^^^^^^^^^^^^^^^^^^^^

View File

@ -91,6 +91,14 @@
|<<colors.tabs.indicator.system,colors.tabs.indicator.system>>|Color gradient interpolation system for the tab indicator.
|<<colors.tabs.odd.bg,colors.tabs.odd.bg>>|Background color of unselected odd tabs.
|<<colors.tabs.odd.fg,colors.tabs.odd.fg>>|Foreground color of unselected odd tabs.
|<<colors.tabs.pinned.even.bg,colors.tabs.pinned.even.bg>>|Background color of pinned unselected even tabs.
|<<colors.tabs.pinned.even.fg,colors.tabs.pinned.even.fg>>|Foreground color of pinned unselected even tabs.
|<<colors.tabs.pinned.odd.bg,colors.tabs.pinned.odd.bg>>|Background color of pinned unselected odd tabs.
|<<colors.tabs.pinned.odd.fg,colors.tabs.pinned.odd.fg>>|Foreground color of pinned unselected odd tabs.
|<<colors.tabs.pinned.selected.even.bg,colors.tabs.pinned.selected.even.bg>>|Background color of pinned selected even tabs.
|<<colors.tabs.pinned.selected.even.fg,colors.tabs.pinned.selected.even.fg>>|Foreground color of pinned selected even tabs.
|<<colors.tabs.pinned.selected.odd.bg,colors.tabs.pinned.selected.odd.bg>>|Background color of pinned selected odd tabs.
|<<colors.tabs.pinned.selected.odd.fg,colors.tabs.pinned.selected.odd.fg>>|Foreground color of pinned selected odd tabs.
|<<colors.tabs.selected.even.bg,colors.tabs.selected.even.bg>>|Background color of selected even tabs.
|<<colors.tabs.selected.even.fg,colors.tabs.selected.even.fg>>|Foreground color of selected even tabs.
|<<colors.tabs.selected.odd.bg,colors.tabs.selected.odd.bg>>|Background color of selected odd tabs.
@ -215,6 +223,7 @@
|<<input.insert_mode.auto_enter,input.insert_mode.auto_enter>>|Enter insert mode if an editable element is clicked.
|<<input.insert_mode.auto_leave,input.insert_mode.auto_leave>>|Leave insert mode if a non-editable element is clicked.
|<<input.insert_mode.auto_load,input.insert_mode.auto_load>>|Automatically enter insert mode if an editable element is focused after loading the page.
|<<input.insert_mode.leave_on_load,input.insert_mode.leave_on_load>>|Leave insert mode when starting a new page load.
|<<input.insert_mode.plugins,input.insert_mode.plugins>>|Switch to insert mode when clicking flash and other plugins.
|<<input.links_included_in_focus_chain,input.links_included_in_focus_chain>>|Include hyperlinks in the keyboard focus chain when tabbing.
|<<input.partial_timeout,input.partial_timeout>>|Timeout (in milliseconds) for partially typed key bindings.
@ -261,6 +270,7 @@
|<<tabs.new_position.stacking,tabs.new_position.stacking>>|Stack related tabs on top of each other when opened consecutively.
|<<tabs.new_position.unrelated,tabs.new_position.unrelated>>|Position of new tabs which are not opened from another tab.
|<<tabs.padding,tabs.padding>>|Padding (in pixels) around text for tabs.
|<<tabs.pinned.frozen,tabs.pinned.frozen>>|Force pinned tabs to stay at fixed URL.
|<<tabs.pinned.shrink,tabs.pinned.shrink>>|Shrink pinned tabs down to their contents.
|<<tabs.position,tabs.position>>|Position of the tab bar.
|<<tabs.select_on_remove,tabs.select_on_remove>>|Which tab to select when the focused tab is removed.
@ -1333,6 +1343,70 @@ Type: <<types,QtColor>>
Default: +pass:[white]+
[[colors.tabs.pinned.even.bg]]
=== colors.tabs.pinned.even.bg
Background color of pinned unselected even tabs.
Type: <<types,QtColor>>
Default: +pass:[darkseagreen]+
[[colors.tabs.pinned.even.fg]]
=== colors.tabs.pinned.even.fg
Foreground color of pinned unselected even tabs.
Type: <<types,QtColor>>
Default: +pass:[white]+
[[colors.tabs.pinned.odd.bg]]
=== colors.tabs.pinned.odd.bg
Background color of pinned unselected odd tabs.
Type: <<types,QtColor>>
Default: +pass:[seagreen]+
[[colors.tabs.pinned.odd.fg]]
=== colors.tabs.pinned.odd.fg
Foreground color of pinned unselected odd tabs.
Type: <<types,QtColor>>
Default: +pass:[white]+
[[colors.tabs.pinned.selected.even.bg]]
=== colors.tabs.pinned.selected.even.bg
Background color of pinned selected even tabs.
Type: <<types,QtColor>>
Default: +pass:[black]+
[[colors.tabs.pinned.selected.even.fg]]
=== colors.tabs.pinned.selected.even.fg
Foreground color of pinned selected even tabs.
Type: <<types,QtColor>>
Default: +pass:[white]+
[[colors.tabs.pinned.selected.odd.bg]]
=== colors.tabs.pinned.selected.odd.bg
Background color of pinned selected odd tabs.
Type: <<types,QtColor>>
Default: +pass:[black]+
[[colors.tabs.pinned.selected.odd.fg]]
=== colors.tabs.pinned.selected.odd.fg
Foreground color of pinned selected odd tabs.
Type: <<types,QtColor>>
Default: +pass:[white]+
[[colors.tabs.selected.even.bg]]
=== colors.tabs.selected.even.bg
Background color of selected even tabs.
@ -1648,7 +1722,7 @@ Type: <<types,Bool>>
Default: +pass:[true]+
This setting is only available with the QtWebKit backend.
On QtWebEngine, this setting requires Qt 5.12 or newer.
[[content.frame_flattening]]
=== content.frame_flattening
@ -2712,6 +2786,17 @@ Type: <<types,Bool>>
Default: +pass:[false]+
[[input.insert_mode.leave_on_load]]
=== input.insert_mode.leave_on_load
Leave insert mode when starting a new page load.
Patterns may be unreliable on this setting, and they may match the url you are navigating to, or the URL you are navigating from.
This setting supports URL patterns.
Type: <<types,Bool>>
Default: +pass:[true]+
[[input.insert_mode.plugins]]
=== input.insert_mode.plugins
Switch to insert mode when clicking flash and other plugins.
@ -2962,7 +3047,7 @@ Default: +pass:[false]+
=== search.ignore_case
When to find text on a page case-insensitively.
Type: <<types,String>>
Type: <<types,IgnoreCase>>
Valid values:
@ -3309,6 +3394,14 @@ Default:
- +pass:[right]+: +pass:[5]+
- +pass:[top]+: +pass:[0]+
[[tabs.pinned.frozen]]
=== tabs.pinned.frozen
Force pinned tabs to stay at fixed URL.
Type: <<types,Bool>>
Default: +pass:[true]+
[[tabs.pinned.shrink]]
=== tabs.pinned.shrink
Shrink pinned tabs down to their contents.
@ -3626,6 +3719,7 @@ Lists with duplicate flags are invalid. Each item is checked against the valid v
|FontFamily|A Qt font family.
|FormatString|A string with placeholders.
|FuzzyUrl|A URL which gets interpreted as search if needed.
|IgnoreCase|Whether to search case insensitively.
|Int|Base class for an integer setting.
|Key|A name of a key.
|List|A list of values.

View File

@ -102,18 +102,12 @@ $ python3 scripts/asciidoc2html.py
On Fedora
---------
NOTE: Fedora's packages used to be outdated for a long time, but are
now (November 2017) maintained and up-to-date again.
qutebrowser is available in the official repositories:
-----
# dnf install qutebrowser
-----
However, note that Fedora 25/26 won't be updated to qutebrowser v1.0, so you
might want to <<tox,install qutebrowser via tox>> instead there.
Additional hints
~~~~~~~~~~~~~~~~

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -17,12 +17,12 @@ doc/qutebrowser.1.html:
install: doc/qutebrowser.1.html
$(PYTHON) setup.py install --prefix="$(PREFIX)" --optimize=1 $(SETUPTOOLSOPTS)
install -Dm644 misc/qutebrowser.appdata.xml \
"$(DESTDIR)$(DATADIR)/metainfo/qutebrowser.appdata.xml"
install -Dm644 misc/org.qutebrowser.qutebrowser.appdata.xml \
"$(DESTDIR)$(DATADIR)/metainfo/org.qutebrowser.qutebrowser.appdata.xml"
install -Dm644 doc/qutebrowser.1 \
"$(DESTDIR)$(MANDIR)/man1/qutebrowser.1"
install -Dm644 misc/qutebrowser.desktop \
"$(DESTDIR)$(DATADIR)/applications/qutebrowser.desktop"
install -Dm644 misc/org.qutebrowser.qutebrowser.desktop \
"$(DESTDIR)$(DATADIR)/applications/org.qutebrowser.qutebrowser.desktop"
$(foreach i,$(ICONSIZES),install -Dm644 "icons/qutebrowser-$(i)x$(i).png" \
"$(DESTDIR)$(DATADIR)/icons/hicolor/$(i)x$(i)/apps/qutebrowser.png";)
install -Dm644 icons/qutebrowser.svg \

View File

@ -20,7 +20,7 @@
<provides>
<binary>qutebrowser</binary>
</provides>
<launchable type="desktop-id">qutebrowser.desktop</launchable>
<launchable type="desktop-id">org.qutebrowser.qutebrowser.desktop</launchable>
<screenshots>
<screenshot type="default">
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/main.png</image>

View File

@ -0,0 +1,176 @@
[Desktop Entry]
Name=qutebrowser
GenericName=Web Browser
GenericName[ar]=
GenericName[bg]=Уеб браузър
GenericName[ca]=Navegador web
GenericName[cs]=WWW prohlížeč
GenericName[da]=Browser
GenericName[de]=Web-Browser
GenericName[el]=Περιηγητής ιστού
GenericName[en_GB]=Web Browser
GenericName[es]=Navegador web
GenericName[et]=Veebibrauser
GenericName[fi]=WWW-selain
GenericName[fr]=Navigateur Web
GenericName[gu]=
GenericName[he]=דפדפן אינטרנט
GenericName[hi]=
GenericName[hu]=Webböngésző
GenericName[it]=Browser Web
GenericName[ja]=
GenericName[kn]=
GenericName[ko]=
GenericName[lt]=Žiniatinklio naršyklė
GenericName[lv]=Tīmekļa pārlūks
GenericName[ml]= <200d>
GenericName[mr]=
GenericName[nb]=Nettleser
GenericName[nl]=Webbrowser
GenericName[pl]=Przeglądarka WWW
GenericName[pt]=Navegador Web
GenericName[pt_BR]=Navegador da Internet
GenericName[ro]=Navigator de Internet
GenericName[ru]=Веб-браузер
GenericName[sl]=Spletni brskalnik
GenericName[sv]=Webbläsare
GenericName[ta]= ி
GenericName[th]=
GenericName[tr]=Web Tarayıcı
GenericName[uk]=Навігатор Тенет
Comment=A keyboard-driven, vim-like browser based on PyQt5
Comment[it]= Un browser web vim-like utilizzabile da tastiera basato su PyQt5
Icon=qutebrowser
Type=Application
Categories=Network;WebBrowser;
Exec=qutebrowser %u
Terminal=false
StartupNotify=false
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute;
Keywords=Browser
Actions=new-window;preferences;
[Desktop Action new-window]
Name=New Window
Name[am]=
Name[ar]=
Name[bg]=Нов прозорец
Name[bn]=
Name[ca]=Finestra nova
Name[cs]=Nové okno
Name[da]=Nyt vindue
Name[de]=Neues Fenster
Name[el]=Νέο Παράθυρο
Name[en_GB]=New Window
Name[es]=Nueva ventana
Name[et]=Uus aken
Name[fa]=پ ی
Name[fi]=Uusi ikkuna
Name[fil]=New Window
Name[fr]=Nouvelle fenêtre
Name[gu]= િ
Name[hi]= ि
Name[hr]=Novi prozor
Name[hu]=Új ablak
Name[id]=Jendela Baru
Name[it]=Nuova finestra
Name[iw]=חלון חדש
Name[ja]=
Name[kn]= ಿ
Name[ko]=
Name[lt]=Naujas langas
Name[lv]=Jauns logs
Name[ml]=ി ി<200d>
Name[mr]= ि
Name[nl]=Nieuw venster
Name[no]=Nytt vindu
Name[pl]=Nowe okno
Name[pt]=Nova janela
Name[pt_BR]=Nova janela
Name[ro]=Fereastră nouă
Name[ru]=Новое окно
Name[sk]=Nové okno
Name[sl]=Novo okno
Name[sr]=Нови прозор
Name[sv]=Nytt fönster
Name[sw]=Dirisha Jipya
Name[ta]=ி
Name[te]= ి
Name[th]=
Name[tr]=Yeni Pencere
Name[uk]=Нове вікно
Name[vi]=Ca s Mi
Exec=qutebrowser
[Desktop Action preferences]
Name=Preferences
Name[an]=Preferencias
Name[ar]=
Name[as]=
Name[be]=Настройкі
Name[bg]=Настройки
Name[bn_IN]=
Name[bs]=Postavke
Name[ca]=Preferències
Name[ca@valencia]=Preferències
Name[cs]=Předvolby
Name[da]=Indstillinger
Name[de]=Einstellungen
Name[el]=Προτιμήσεις
Name[en_GB]=Preferences
Name[eo]=Agordoj
Name[es]=Preferencias
Name[et]=Eelistused
Name[eu]=Hobespenak
Name[fa]=ی
Name[fi]=Asetukset
Name[fr]=Préférences
Name[fur]=Preferencis
Name[ga]=Sainroghanna
Name[gd]=Roghainnean
Name[gl]=Preferencias
Name[gu]=
Name[he]=העדפות
Name[hi]=
Name[hr]=Osobitosti
Name[hu]=Beállítások
Name[id]=Preferensi
Name[is]=Kjörstillingar
Name[it]=Preferenze
Name[ja]=
Name[kk]=Баптаулар
Name[km]=
Name[kn]=
Name[ko]=
Name[lt]=Nuostatos
Name[lv]=Iestatījumi
Name[ml]=<200d><200d>
Name[mr]=
Name[nb]=Brukervalg
Name[ne]=ि
Name[nl]=Voorkeuren
Name[oc]=Preferéncias
Name[or]=
Name[pa]=
Name[pl]=Preferencje
Name[pt]=Preferências
Name[pt_BR]=Preferências
Name[ro]=Preferințe
Name[ru]=Параметры
Name[sk]=Nastavenia
Name[sl]=Možnosti
Name[sr]=Поставке
Name[sr@latin]=Postavke
Name[sv]=Inställningar
Name[ta]=ி
Name[te]=
Name[tg]=Хусусиятҳо
Name[th]=
Name[tr]=Tercihler
Name[ug]=
Name[uk]=Параметри
Name[vi]=Tùy thích
Name[zh_CN]=
Name[zh_HK]=
Name[zh_TW]=
Exec=qutebrowser "qute://settings"

View File

@ -1,12 +0,0 @@
[Desktop Entry]
Name=qutebrowser
GenericName=Web Browser
Comment=A keyboard-driven, vim-like browser based on PyQt5
Icon=qutebrowser
Type=Application
Categories=Network;WebBrowser;
Exec=qutebrowser %u
Terminal=false
StartupNotify=false
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute;
Keywords=Browser

View File

@ -6,6 +6,8 @@ import os
sys.path.insert(0, os.getcwd())
from scripts import setupcommon
from qutebrowser.extensions import loader
block_cipher = None
@ -27,13 +29,20 @@ def get_data_files():
return data_files
def get_hidden_imports():
imports = ['PyQt5.QtOpenGL', 'PyQt5._QOpenGLFunctions_2_0']
for info in loader.walk_components():
imports.append(info.name)
return imports
setupcommon.write_git_file()
if os.name == 'nt':
icon = 'icons/qutebrowser.ico'
icon = '../icons/qutebrowser.ico'
elif sys.platform == 'darwin':
icon = 'icons/qutebrowser.icns'
icon = '../icons/qutebrowser.icns'
else:
icon = None
@ -42,7 +51,7 @@ a = Analysis(['../qutebrowser/__main__.py'],
pathex=['misc'],
binaries=None,
datas=get_data_files(),
hiddenimports=['PyQt5.QtOpenGL', 'PyQt5._QOpenGLFunctions_2_0'],
hiddenimports=get_hidden_imports(),
hookspath=[],
runtime_hooks=[],
excludes=['tkinter'],
@ -60,7 +69,7 @@ exe = EXE(pyz,
strip=False,
upx=False,
console=False,
version='misc/file_version_info.txt')
version='../misc/file_version_info.txt')
coll = COLLECT(exe,
a.binaries,
a.zipfiles,

View File

@ -1,9 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
certifi==2018.10.15
certifi==2019.3.9
chardet==3.0.4
codecov==2.0.15
coverage==4.5.2
idna==2.7
requests==2.20.1
coverage==4.5.3
idna==2.8
requests==2.21.0
urllib3==1.24.1

View File

@ -1,27 +1,25 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==18.2.0
flake8==3.6.0
attrs==19.1.0
entrypoints==0.3
flake8==3.7.7
flake8-bugbear==18.8.0
flake8-builtins==1.4.1
flake8-comprehensions==1.4.1
flake8-comprehensions==2.1.0
flake8-copyright==0.2.2
flake8-debugger==3.1.0
flake8-deprecated==1.3
flake8-docstrings==1.3.0
flake8-future-import==0.4.5
flake8-mock==0.3
flake8-per-file-ignores==0.6
flake8-polyfill==1.0.2
flake8-string-format==0.2.3
flake8-tidy-imports==1.1.0
flake8-tuple==0.2.13
flake8-tidy-imports==2.0.0
flake8-tuple==0.2.14
mccabe==0.6.1
pathmatch==0.2.1
pep8-naming==0.7.0
pycodestyle==2.4.0
pep8-naming==0.8.2
pycodestyle==2.5.0
pydocstyle==3.0.0
pyflakes==2.0.0
six==1.11.0
pyflakes==2.1.1
six==1.12.0
snowballstemmer==1.2.1
typing==3.6.6

View File

@ -8,7 +8,6 @@ flake8-deprecated
flake8-docstrings
flake8-future-import
flake8-mock
flake8-per-file-ignores
flake8-string-format
flake8-tidy-imports
flake8-tuple

View File

@ -0,0 +1,8 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
mypy==0.670
mypy-extensions==0.4.1
# PyQt5==5.11.3
# PyQt5-sip==4.19.14
-e git+https://github.com/qutebrowser/PyQt5-stubs.git@wip#egg=PyQt5_stubs
typed-ast==1.3.1

View File

@ -0,0 +1,6 @@
mypy
-e git+https://github.com/qutebrowser/PyQt5-stubs.git@wip#egg=PyQt5-stubs
# remove @commit-id for scm installs
#@ replace: @.*# @wip#
#@ ignore: PyQt5, PyQt5-sip

View File

@ -0,0 +1,6 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
colorama==0.4.1
cssutils==1.0.2
hunter==2.2.1
Pympler==0.6

View File

@ -0,0 +1,3 @@
hunter
cssutils
pympler

View File

@ -1,8 +1,8 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
appdirs==1.4.3
packaging==18.0
pyparsing==2.3.0
setuptools==40.5.0
six==1.11.0
wheel==0.32.2
packaging==19.0
pyparsing==2.3.1
setuptools==40.8.0
six==1.12.0
wheel==0.33.1

View File

@ -1,23 +1,23 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
asn1crypto==0.24.0
astroid==2.0.4
certifi==2018.10.15
cffi==1.11.5
astroid==2.2.5
certifi==2019.3.9
cffi==1.12.2
chardet==3.0.4
cryptography==2.4.1
github3.py==1.2.0
idna==2.7
isort==4.3.4
cryptography==2.6.1
github3.py==1.3.0
idna==2.8
isort==4.3.15
jwcrypto==0.6.0
lazy-object-proxy==1.3.1
mccabe==0.6.1
pycparser==2.19
pylint==2.1.1
python-dateutil==2.7.5
pylint==2.3.1
python-dateutil==2.8.0
./scripts/dev/pylint_checkers
requests==2.20.1
six==1.11.0
requests==2.21.0
six==1.12.0
uritemplate==3.0.0
urllib3==1.24.1
wrapt==1.10.11
wrapt==1.11.1

View File

@ -1,4 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.11.3
PyQt5-sip==4.19.13
PyQt5==5.12.1
PyQt5-sip==4.19.15
PyQtWebEngine==5.12.1

View File

@ -1 +1,2 @@
PyQt5
PyQt5
PyQtWebEngine

View File

@ -0,0 +1,21 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
alabaster==0.7.12
Babel==2.6.0
certifi==2019.3.9
chardet==3.0.4
docutils==0.14
idna==2.8
imagesize==1.1.0
Jinja2==2.10
MarkupSafe==1.1.1
packaging==19.0
Pygments==2.3.1
pyparsing==2.3.1
pytz==2018.9
requests==2.21.0
six==1.12.0
snowballstemmer==1.2.1
Sphinx==1.8.5
sphinxcontrib-websupport==1.1.0
urllib3==1.24.1

View File

@ -0,0 +1 @@
sphinx

View File

@ -1,42 +1,42 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
atomicwrites==1.2.1
attrs==18.2.0
atomicwrites==1.3.0
attrs==19.1.0
backports.functools-lru-cache==1.5
beautifulsoup4==4.6.3
cheroot==6.5.2
beautifulsoup4==4.7.1
cheroot==6.5.4
Click==7.0
# colorama==0.4.0
coverage==4.5.2
EasyProcess==0.2.3
fields==5.0.0
# colorama==0.4.1
coverage==4.5.3
EasyProcess==0.2.5
Flask==1.0.2
glob2==0.6
hunter==2.0.2
hypothesis==3.82.1
hunter==2.2.1
hypothesis==4.12.0
itsdangerous==1.1.0
# Jinja2==2.10
Mako==1.0.7
# MarkupSafe==1.1.0
more-itertools==4.3.0
parse==1.9.0
more-itertools==6.0.0
parse==1.11.1
parse-type==0.4.2
pluggy==0.8.0
py==1.7.0
pluggy==0.9.0
py==1.8.0
py-cpuinfo==4.0.0
pytest==4.0.0
pytest-bdd==3.0.0
pytest-benchmark==3.1.1
pytest-cov==2.6.0
pytest==4.3.1
pytest-bdd==3.1.0
pytest-benchmark==3.2.2
pytest-cov==2.6.1
pytest-faulthandler==1.5.0
pytest-instafail==0.4.0
pytest-mock==1.10.0
pytest-qt==3.2.1
pytest-repeat==0.7.0
pytest-rerunfailures==5.0
pytest-instafail==0.4.1
pytest-mock==1.10.1
pytest-qt==3.2.2
pytest-repeat==0.8.0
pytest-rerunfailures==6.0
pytest-travis-fold==1.3.0
pytest-xvfb==1.1.0
pytest-xvfb==1.2.0
PyVirtualDisplay==0.2.1
six==1.11.0
six==1.12.0
soupsieve==1.8
vulture==1.0
Werkzeug==0.14.1

View File

@ -1,9 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
filelock==3.0.10
pluggy==0.8.0
py==1.7.0
six==1.11.0
pluggy==0.9.0
py==1.8.0
six==1.12.0
toml==0.10.0
tox==3.5.3
virtualenv==16.1.0
tox==3.7.0
virtualenv==16.4.3

View File

@ -53,9 +53,10 @@ The following userscripts can be found on their own repositories.
- [qtb.us](https://github.com/Chinggis6/qtb.us): small pack of userscripts.
- [pinboard.zsh](https://github.com/dmix/pinboard.zsh): Add URL to your
[Pinboard][] bookmark manager.
- [qute-capture](https://github.com/alcah/qute-capture): Capture links with
Emacs's org-mode to a read-later file.
[Zotero]: https://www.zotero.org/
[Pocket]: https://getpocket.com/
[Instapaper]: https://www.instapaper.com/
[Pinboard]: https://pinboard.in/

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Copyright 2015-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
set -euo pipefail
#
# Behavior:

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015 jnphilipp <me@jnphilipp.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -64,6 +64,7 @@ die() {
javascript_escape() {
# print the first argument in an escaped way, such that it can safely
# be used within javascripts double quotes
# shellcheck disable=SC2001
sed "s,[\\\\'\"],\\\\&,g" <<< "$1"
}
@ -111,6 +112,7 @@ simplify_url() {
# are found:
no_entries_found() {
while [ 0 -eq "${#files[@]}" ] && [ -n "$simple_url" ]; do
# shellcheck disable=SC2001
shorter_simple_url=$(sed 's,^[^.]*\.,,' <<< "$simple_url")
if [ "$shorter_simple_url" = "$simple_url" ] ; then
# if no dot, then even remove the top level domain

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
# Copyright 2018 Jay Kamat <jaygkamat@gmail.com>
# Copyright 2018-2019 Jay Kamat <jaygkamat@gmail.com>
#
# This file is part of qutebrowser.
#
@ -148,7 +148,7 @@ def dmenu(items, invocation, encoding):
def get_password():
"""Get a keepass db password from user."""
_app = QApplication(sys.argv)
_app = QApplication(sys.argv) # don't remove this local variable
text, ok = QInputDialog.getText(
None, "KeePass DB Password",
"Please enter your KeePass Master Password",

View File

@ -97,13 +97,19 @@ def qute_command(command):
def find_pass_candidates(domain, password_store_path):
candidates = []
for path, directories, file_names in os.walk(password_store_path, followlinks=True):
if directories or domain not in path.split(os.path.sep):
secrets = fnmatch.filter(file_names, '*.gpg')
if not secrets:
continue
# Strip password store path prefix to get the relative pass path
pass_path = path[len(password_store_path) + 1:]
secrets = fnmatch.filter(file_names, '*.gpg')
candidates.extend(os.path.join(pass_path, os.path.splitext(secret)[0]) for secret in secrets)
split_path = pass_path.split(os.path.sep)
for secret in secrets:
secret_base = os.path.splitext(secret)[0]
if domain not in (split_path + [secret_base]):
continue
candidates.append(os.path.join(pass_path, secret_base))
return candidates

View File

@ -37,7 +37,7 @@ get_selection() {
# https://github.com/halfwit/dotfiles/blob/master/.config/dmenu/font
[[ -s $confdir/dmenu/font ]] && read -r font < "$confdir"/dmenu/font
[[ $font ]] && opts+=(-fn "$font")
[[ -n $font ]] && opts+=(-fn "$font")
# shellcheck source=/dev/null
[[ -s $optsfile ]] && source "$optsfile"
@ -46,7 +46,7 @@ url=$(get_selection)
url=${url/*http/http}
# If no selection is made, exit (escape pressed, e.g.)
[[ ! $url ]] && exit 0
[[ -z $url ]] && exit 0
case $1 in
open) printf '%s' "open $url" >> "$QUTE_FIFO" || qutebrowser "$url" ;;

87
mypy.ini Normal file
View File

@ -0,0 +1,87 @@
[mypy]
# We also need to support 3.5, but if we'd chose that here, we'd need to deal
# with conditional imports (like secrets.py).
python_version = 3.6
# --strict
warn_redundant_casts = True
warn_unused_ignores = True
disallow_subclassing_any = True
disallow_untyped_decorators = True
## https://github.com/python/mypy/issues/5957
# warn_unused_configs = True
# disallow_untyped_calls = True
# disallow_untyped_defs = True
## https://github.com/python/mypy/issues/5954
# disallow_incomplete_defs = True
# check_untyped_defs = True
# no_implicit_optional = True
# warn_return_any = True
[mypy-colorama]
# https://github.com/tartley/colorama/issues/206
ignore_missing_imports = True
[mypy-hunter]
# https://github.com/ionelmc/python-hunter/issues/43
ignore_missing_imports = True
[mypy-pygments.*]
# https://bitbucket.org/birkenfeld/pygments-main/issues/1485/type-hints
ignore_missing_imports = True
[mypy-cssutils]
# Pretty much inactive currently
ignore_missing_imports = True
[mypy-pypeg2]
# Pretty much inactive currently
ignore_missing_imports = True
[mypy-bdb]
# stdlib, missing in typeshed
ignore_missing_imports = True
[mypy-qutebrowser.browser.webkit.rfc6266]
# subclasses dynamic PyPEG2 classes
disallow_subclassing_any = False
[mypy-qutebrowser.browser.browsertab]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.misc.objects]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.commands.cmdutils]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.*]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.api.*]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.components.*]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.extensions.*]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.browser.webelem]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.browser.webkit.webkitelem]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.browser.webengine.webengineelem]
disallow_untyped_defs = True
disallow_incomplete_defs = True

View File

@ -64,10 +64,8 @@ qt_log_ignore =
^QSettings::value: Empty key passed
^Icon theme ".*" not found
^Error receiving trust for a CA certificate
^QBackingStore::endPaint\(\) called with active painter on backingstore paint device
^QPaintDevice: Cannot destroy paint device that is being painted
^DirectWrite: CreateFontFaceFromHDC\(\) failed .*
xfail_strict = true
filterwarnings =
error
# This happens in many qutebrowser dependencies...
ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working:DeprecationWarning
# WORKAROUND for https://github.com/ionelmc/pytest-benchmark/issues/124
ignore:Node\.warn\(code, message\) form has been deprecated, use Node\.warn\(warning_instance\) instead:pytest.PytestDeprecationWarning
filterwarnings = error

View File

@ -2,7 +2,7 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -22,11 +22,11 @@
import os.path
__author__ = "Florian Bruhin"
__copyright__ = "Copyright 2014-2018 Florian Bruhin (The Compiler)"
__copyright__ = "Copyright 2014-2019 Florian Bruhin (The Compiler)"
__license__ = "GPL"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
__version_info__ = (1, 5, 2)
__version_info__ = (1, 6, 1)
__version__ = '.'.join(str(e) for e in __version_info__)
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -0,0 +1,26 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <http://www.gnu.org/licenses/>.
"""API for extensions.
This API currently isn't exposed to third-party extensions yet, but will be in
the future. Thus, care must be taken when adding new APIs here.
Code in qutebrowser.components only uses this API.
"""

View File

@ -0,0 +1,27 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <http://www.gnu.org/licenses/>.
"""A single tab."""
# pylint: disable=unused-import
from qutebrowser.browser.browsertab import WebTabError, AbstractTab as Tab
from qutebrowser.browser.webelem import (Error as WebElemError,
AbstractWebElement as WebElement)
from qutebrowser.utils.usertypes import ClickTarget, JsWorld
from qutebrowser.extensions.loader import InitContext

219
qutebrowser/api/cmdutils.py Normal file
View File

@ -0,0 +1,219 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <http://www.gnu.org/licenses/>.
"""qutebrowser has the concept of functions, exposed to the user as commands.
Creating a new command is straightforward::
from qutebrowser.api import cmdutils
@cmdutils.register(...)
def foo():
...
The commands arguments are automatically deduced by inspecting your function.
The types of the function arguments are inferred based on their default values,
e.g., an argument `foo=True` will be converted to a flag `-f`/`--foo` in
qutebrowser's commandline.
The type can be overridden using Python's function annotations::
@cmdutils.register(...)
def foo(bar: int, baz=True):
...
Possible values:
- A callable (``int``, ``float``, etc.): Gets called to validate/convert the
value.
- A python enum type: All members of the enum are possible values.
- A ``typing.Union`` of multiple types above: Any of these types are valid
values, e.g., ``typing.Union[str, int]``.
"""
import inspect
import typing
from qutebrowser.utils import qtutils
from qutebrowser.commands import command, cmdexc
# pylint: disable=unused-import
from qutebrowser.utils.usertypes import KeyMode, CommandValue as Value
class CommandError(cmdexc.Error):
"""Raised when a command encounters an error while running.
If your command handler encounters an error and cannot continue, raise this
exception with an appropriate error message::
raise cmdexc.CommandError("Message")
The message will then be shown in the qutebrowser status bar.
.. note::
You should only raise this exception while a command handler is run.
Raising it at another point causes qutebrowser to crash due to an
unhandled exception.
"""
def check_overflow(arg: int, ctype: str) -> None:
"""Check if the given argument is in bounds for the given type.
Args:
arg: The argument to check.
ctype: The C++/Qt type to check as a string ('int'/'int64').
"""
try:
qtutils.check_overflow(arg, ctype)
except OverflowError:
raise CommandError("Numeric argument is too large for internal {} "
"representation.".format(ctype))
def check_exclusive(flags: typing.Iterable[bool],
names: typing.Iterable[str]) -> None:
"""Check if only one flag is set with exclusive flags.
Raise a CommandError if not.
Args:
flags: The flag values to check.
names: A list of names (corresponding to the flags argument).
"""
if sum(1 for e in flags if e) > 1:
argstr = '/'.join('-' + e for e in names)
raise CommandError("Only one of {} can be given!".format(argstr))
class register: # noqa: N801,N806 pylint: disable=invalid-name
"""Decorator to register a new command handler."""
def __init__(self, *,
instance: str = None,
name: str = None,
**kwargs: typing.Any) -> None:
"""Save decorator arguments.
Gets called on parse-time with the decorator arguments.
Args:
See class attributes.
"""
# The object from the object registry to be used as "self".
self._instance = instance
# The name (as string) or names (as list) of the command.
self._name = name
# The arguments to pass to Command.
self._kwargs = kwargs
def __call__(self, func: typing.Callable) -> typing.Callable:
"""Register the command before running the function.
Gets called when a function should be decorated.
Doesn't actually decorate anything, but creates a Command object and
registers it in the global commands dict.
Args:
func: The function to be decorated.
Return:
The original function (unmodified).
"""
if self._name is None:
name = func.__name__.lower().replace('_', '-')
else:
assert isinstance(self._name, str), self._name
name = self._name
cmd = command.Command(name=name, instance=self._instance,
handler=func, **self._kwargs)
cmd.register()
return func
class argument: # noqa: N801,N806 pylint: disable=invalid-name
"""Decorator to customize an argument.
You can customize how an argument is handled using the
``@cmdutils.argument`` decorator *after* ``@cmdutils.register``. This can,
for example, be used to customize the flag an argument should get::
@cmdutils.register(...)
@cmdutils.argument('bar', flag='c')
def foo(bar):
...
For a ``str`` argument, you can restrict the allowed strings using
``choices``::
@cmdutils.register(...)
@cmdutils.argument('bar', choices=['val1', 'val2'])
def foo(bar: str):
...
For ``typing.Union`` types, the given ``choices`` are only checked if other
types (like ``int``) don't match.
The following arguments are supported for ``@cmdutils.argument``:
- ``flag``: Customize the short flag (``-x``) the argument will get.
- ``value``: Tell qutebrowser to fill the argument with special values:
* ``value=cmdutils.Value.count``: The ``count`` given by the user to the
command.
* ``value=cmdutils.Value.win_id``: The window ID of the current window.
* ``value=cmdutils.Value.cur_tab``: The tab object which is currently
focused.
- ``completion``: A completion function to use when completing arguments
for the given command.
- ``choices``: The allowed string choices for the argument.
The name of an argument will always be the parameter name, with any
trailing underscores stripped and underscores replaced by dashes.
"""
def __init__(self, argname: str, **kwargs: typing.Any) -> None:
self._argname = argname # The name of the argument to handle.
self._kwargs = kwargs # Valid ArgInfo members.
def __call__(self, func: typing.Callable) -> typing.Callable:
funcname = func.__name__
if self._argname not in inspect.signature(func).parameters:
raise ValueError("{} has no argument {}!".format(funcname,
self._argname))
if not hasattr(func, 'qute_args'):
func.qute_args = {} # type: ignore
elif func.qute_args is None: # type: ignore
raise ValueError("@cmdutils.argument got called above (after) "
"@cmdutils.register for {}!".format(funcname))
arginfo = command.ArgInfo(**self._kwargs)
func.qute_args[self._argname] = arginfo # type: ignore
return func

43
qutebrowser/api/config.py Normal file
View File

@ -0,0 +1,43 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <http://www.gnu.org/licenses/>.
"""Access to the qutebrowser configuration."""
import typing
from PyQt5.QtCore import QUrl
from qutebrowser.config import config
#: Simplified access to config values using attribute acccess.
#: For example, to access the ``content.javascript.enabled`` setting,
#: you can do::
#:
#: if config.val.content.javascript.enabled:
#: ...
#:
#: This also supports setting configuration values::
#:
#: config.val.content.javascript.enabled = False
val = typing.cast('config.ConfigContainer', None)
def get(name: str, url: QUrl = None) -> typing.Any:
"""Get a value from the config based on a string name."""
return config.instance.get(name, url)

View File

@ -0,0 +1,75 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <http://www.gnu.org/licenses/>.
"""APIs related to downloading files."""
import io
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QUrl
from qutebrowser.browser import downloads, qtnetworkdownloads
from qutebrowser.utils import objreg
class TempDownload(QObject):
"""A download of some data into a file object."""
finished = pyqtSignal()
def __init__(self, item: qtnetworkdownloads.DownloadItem) -> None:
super().__init__()
self._item = item
self._item.finished.connect(self._on_download_finished)
self.successful = False
self.fileobj = item.fileobj
@pyqtSlot()
def _on_download_finished(self) -> None:
self.successful = self._item.successful
self.finished.emit()
def download_temp(url: QUrl) -> TempDownload:
"""Download the given URL into a file object.
The download is not saved to disk.
Returns a ``TempDownload`` object, which triggers a ``finished`` signal
when the download has finished::
dl = downloads.download_temp(QUrl("https://www.example.com/"))
dl.finished.connect(functools.partial(on_download_finished, dl))
After the download has finished, its ``successful`` attribute can be
checked to make sure it finished successfully. If so, its contents can be
read by accessing the ``fileobj`` attribute::
def on_download_finished(download: downloads.TempDownload) -> None:
if download.successful:
print(download.fileobj.read())
download.fileobj.close()
"""
fobj = io.BytesIO()
fobj.name = 'temporary: ' + url.host()
target = downloads.FileObjDownloadTarget(fobj)
download_manager = objreg.get('qtnetwork-download-manager')
return download_manager.get(url, target=target, auto_remove=True)

92
qutebrowser/api/hook.py Normal file
View File

@ -0,0 +1,92 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <http://www.gnu.org/licenses/>.
# pylint: disable=invalid-name
"""Hooks for extensions."""
import importlib
import typing
from qutebrowser.extensions import loader
def _add_module_info(func: typing.Callable) -> loader.ModuleInfo:
"""Add module info to the given function."""
module = importlib.import_module(func.__module__)
return loader.add_module_info(module)
class init:
"""Decorator to mark a function to run when initializing.
The decorated function gets called with a
:class:`qutebrowser.api.apitypes.InitContext` as argument.
Example::
@hook.init()
def init(_context):
message.info("Extension initialized.")
"""
def __call__(self, func: typing.Callable) -> typing.Callable:
info = _add_module_info(func)
if info.init_hook is not None:
raise ValueError("init hook is already registered!")
info.init_hook = func
return func
class config_changed:
"""Decorator to get notified about changed configs.
By default, the decorated function is called when any change in the config
occurs::
@hook.config_changed()
def on_config_changed():
...
When an option name is passed, it's only called when the given option was
changed::
@hook.config_changed('content.javascript.enabled')
def on_config_changed():
...
Alternatively, a part of an option name can be specified. In the following
snippet, ``on_config_changed`` gets called when either
``bindings.commands`` or ``bindings.key_mappings`` have changed::
@hook.config_changed('bindings')
def on_config_changed():
...
"""
def __init__(self, option_filter: str = None) -> None:
self._filter = option_filter
def __call__(self, func: typing.Callable) -> typing.Callable:
info = _add_module_info(func)
info.config_changed_hooks.append((self._filter, func))
return func

View File

@ -0,0 +1,46 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <http://www.gnu.org/licenses/>.
"""APIs related to intercepting/blocking requests."""
from qutebrowser.extensions import interceptors
# pylint: disable=unused-import
from qutebrowser.extensions.interceptors import Request
#: Type annotation for an interceptor function.
InterceptorType = interceptors.InterceptorType
#: Possible resource types for requests sent to interceptor.
ResourceType = interceptors.ResourceType
def register(interceptor: InterceptorType) -> None:
"""Register a request interceptor.
Whenever a request happens, the interceptor gets called with a
:class:`Request` object.
Example::
def intercept(request: interceptor.Request) -> None:
if request.request_url.host() == 'badhost.example.com':
request.block()
"""
interceptors.register(interceptor)

View File

@ -0,0 +1,23 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <http://www.gnu.org/licenses/>.
"""Utilities to display messages above the status bar."""
# pylint: disable=unused-import
from qutebrowser.utils.message import error, warning, info

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -60,13 +60,15 @@ except ImportError:
import qutebrowser
import qutebrowser.resources
from qutebrowser.completion.models import miscmodels
from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.commands import runners
from qutebrowser.api import cmdutils
from qutebrowser.config import config, websettings, configfiles, configinit
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
from qutebrowser.browser import (urlmarks, history, browsertab,
qtnetworkdownloads, downloads, greasemonkey)
from qutebrowser.browser.network import proxy
from qutebrowser.browser.webkit import cookies, cache
from qutebrowser.browser.webkit.network import networkmanager
from qutebrowser.extensions import loader
from qutebrowser.keyinput import macros
from qutebrowser.mainwindow import mainwindow, prompt
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
@ -81,7 +83,7 @@ from qutebrowser.misc import utilcmds
# pylint: enable=unused-import
qApp = None
q_app = None
def run(args):
@ -99,25 +101,25 @@ def run(args):
log.init.debug("Initializing config...")
configinit.early_init(args)
global qApp
qApp = Application(args)
qApp.setOrganizationName("qutebrowser")
qApp.setApplicationName("qutebrowser")
qApp.setDesktopFileName("qutebrowser")
qApp.setApplicationVersion(qutebrowser.__version__)
qApp.lastWindowClosed.connect(quitter.on_last_window_closed)
global q_app
q_app = Application(args)
q_app.setOrganizationName("qutebrowser")
q_app.setApplicationName("qutebrowser")
q_app.setDesktopFileName("org.qutebrowser.qutebrowser")
q_app.setApplicationVersion(qutebrowser.__version__)
q_app.lastWindowClosed.connect(quitter.on_last_window_closed)
if args.version:
print(version.version())
sys.exit(usertypes.Exit.ok)
crash_handler = crashsignal.CrashHandler(
app=qApp, quitter=quitter, args=args, parent=qApp)
app=q_app, quitter=quitter, args=args, parent=q_app)
crash_handler.activate()
objreg.register('crash-handler', crash_handler)
signal_handler = crashsignal.SignalHandler(app=qApp, quitter=quitter,
parent=qApp)
signal_handler = crashsignal.SignalHandler(app=q_app, quitter=quitter,
parent=q_app)
signal_handler.activate()
objreg.register('signal-handler', signal_handler)
@ -149,7 +151,7 @@ def qt_mainloop():
WARNING: misc/crashdialog.py checks the stacktrace for this function
name, so if this is changed, it should be changed there as well!
"""
return qApp.exec_()
return q_app.exec_()
def init(args, crash_handler):
@ -160,9 +162,11 @@ def init(args, crash_handler):
crash_handler: The CrashHandler instance.
"""
log.init.debug("Starting init...")
qApp.setQuitOnLastWindowClosed(False)
q_app.setQuitOnLastWindowClosed(False)
_init_icon()
loader.init()
loader.load_components()
try:
_init_modules(args, crash_handler)
except (OSError, UnicodeDecodeError, browsertab.WebTabError) as e:
@ -171,12 +175,12 @@ def init(args, crash_handler):
sys.exit(usertypes.Exit.err_init)
log.init.debug("Initializing eventfilter...")
event_filter = EventFilter(qApp)
qApp.installEventFilter(event_filter)
event_filter = EventFilter(q_app)
q_app.installEventFilter(event_filter)
objreg.register('event-filter', event_filter)
log.init.debug("Connecting signals...")
qApp.focusChanged.connect(on_focus_changed)
q_app.focusChanged.connect(on_focus_changed)
_process_args(args)
@ -193,7 +197,7 @@ def _init_icon():
icon = QIcon()
fallback_icon = QIcon()
for size in [16, 24, 32, 48, 64, 96, 128, 256, 512]:
filename = ':/icons/qutebrowser-{}x{}.png'.format(size, size)
filename = ':/icons/qutebrowser-{size}x{size}.png'.format(size=size)
pixmap = QPixmap(filename)
if pixmap.isNull():
log.init.warning("Failed to load {}".format(filename))
@ -203,7 +207,7 @@ def _init_icon():
if icon.isNull():
log.init.warning("Failed to load icon")
else:
qApp.setWindowIcon(icon)
q_app.setWindowIcon(icon)
def _process_args(args):
@ -216,7 +220,7 @@ def _process_args(args):
window = mainwindow.MainWindow(private=None)
if not args.nowindow:
window.show()
qApp.setActiveWindow(window)
q_app.setActiveWindow(window)
process_pos_args(args.command)
_open_startpage()
@ -421,7 +425,7 @@ def _init_modules(args, crash_handler):
crash_handler: The CrashHandler instance.
"""
log.init.debug("Initializing save manager...")
save_manager = savemanager.SaveManager(qApp)
save_manager = savemanager.SaveManager(q_app)
objreg.register('save-manager', save_manager)
configinit.late_init(save_manager)
@ -446,7 +450,7 @@ def _init_modules(args, crash_handler):
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))
log.init.debug("Initializing web history...")
history.init(qApp)
history.init(q_app)
except sql.SqlEnvironmentError as e:
error.handle_fatal_exc(e, args, 'Error initializing SQL',
pre_text='Error initializing SQL')
@ -460,36 +464,31 @@ def _init_modules(args, crash_handler):
crash_handler.handle_segfault()
log.init.debug("Initializing sessions...")
sessions.init(qApp)
sessions.init(q_app)
log.init.debug("Initializing websettings...")
websettings.init(args)
log.init.debug("Initializing adblock...")
host_blocker = adblock.HostBlocker()
host_blocker.read_hosts()
objreg.register('host-blocker', host_blocker)
log.init.debug("Initializing quickmarks...")
quickmark_manager = urlmarks.QuickmarkManager(qApp)
quickmark_manager = urlmarks.QuickmarkManager(q_app)
objreg.register('quickmark-manager', quickmark_manager)
log.init.debug("Initializing bookmarks...")
bookmark_manager = urlmarks.BookmarkManager(qApp)
bookmark_manager = urlmarks.BookmarkManager(q_app)
objreg.register('bookmark-manager', bookmark_manager)
log.init.debug("Initializing cookies...")
cookie_jar = cookies.CookieJar(qApp)
ram_cookie_jar = cookies.RAMCookieJar(qApp)
cookie_jar = cookies.CookieJar(q_app)
ram_cookie_jar = cookies.RAMCookieJar(q_app)
objreg.register('cookie-jar', cookie_jar)
objreg.register('ram-cookie-jar', ram_cookie_jar)
log.init.debug("Initializing cache...")
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
diskcache = cache.DiskCache(standarddir.cache(), parent=q_app)
objreg.register('cache', diskcache)
log.init.debug("Initializing downloads...")
download_manager = qtnetworkdownloads.DownloadManager(parent=qApp)
download_manager = qtnetworkdownloads.DownloadManager(parent=q_app)
objreg.register('qtnetwork-download-manager', download_manager)
log.init.debug("Initializing Greasemonkey...")
@ -619,10 +618,11 @@ class Quitter:
ok = self.restart(session='_restart')
except sessions.SessionError as e:
log.destroy.exception("Failed to save session!")
raise cmdexc.CommandError("Failed to save session: {}!".format(e))
raise cmdutils.CommandError("Failed to save session: {}!"
.format(e))
except SyntaxError as e:
log.destroy.exception("Got SyntaxError")
raise cmdexc.CommandError("SyntaxError in {}:{}: {}".format(
raise cmdutils.CommandError("SyntaxError in {}:{}: {}".format(
e.filename, e.lineno, e))
if ok:
self.shutdown(restart=True)
@ -684,7 +684,7 @@ class Quitter:
session: The name of the session to save.
"""
if session is not None and not save:
raise cmdexc.CommandError("Session name given without --save!")
raise cmdutils.CommandError("Session name given without --save!")
if save:
if session is None:
session = sessions.default
@ -735,7 +735,7 @@ class Quitter:
def _shutdown(self, status, restart): # noqa
"""Second stage of shutdown."""
log.destroy.debug("Stage 2 of shutting down...")
if qApp is None:
if q_app is None:
# No QApplication exists yet, so quit hard.
sys.exit(status)
# Remove eventfilter
@ -743,7 +743,7 @@ class Quitter:
log.destroy.debug("Removing eventfilter...")
event_filter = objreg.get('event-filter', None)
if event_filter is not None:
qApp.removeEventFilter(event_filter)
q_app.removeEventFilter(event_filter)
except AttributeError:
pass
# Close all windows
@ -792,7 +792,7 @@ class Quitter:
session_manager.delete_autosave()
# We use a singleshot timer to exit here to minimize the likelihood of
# segfaults.
QTimer.singleShot(0, functools.partial(qApp.exit, status))
QTimer.singleShot(0, functools.partial(q_app.exit, status))
class Application(QApplication):
@ -893,7 +893,7 @@ class EventFilter(QObject):
Return:
True if the event should be filtered, False if it's passed through.
"""
if qApp.activeWindow() not in objreg.window_registry.values():
if q_app.activeWindow() not in objreg.window_registry.values():
# Some other window (print dialog, etc.) is focused so we pass the
# event through.
return False

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -33,14 +33,18 @@ from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
QTimer, QAbstractListModel, QUrl)
from qutebrowser.browser import pdfjs
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.api import cmdutils
from qutebrowser.config import config
from qutebrowser.utils import (usertypes, standarddir, utils, message, log,
qtutils, objreg)
from qutebrowser.qt import sip
ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole)
class ModelRole(enum.IntEnum):
"""Custom download model roles."""
item = Qt.UserRole
# Remember the last used directory
@ -60,8 +64,6 @@ class UnsupportedAttribute:
supported with QtWebengine.
"""
pass
class UnsupportedOperationError(Exception):
@ -1007,11 +1009,11 @@ class DownloadModel(QAbstractListModel):
count: The index of the download
"""
if not count:
raise cmdexc.CommandError("There's no download!")
raise cmdexc.CommandError("There's no download {}!".format(count))
raise cmdutils.CommandError("There's no download!")
raise cmdutils.CommandError("There's no download {}!".format(count))
@cmdutils.register(instance='download-model', scope='window')
@cmdutils.argument('count', count=True)
@cmdutils.argument('count', value=cmdutils.Value.count)
def download_cancel(self, all_=False, count=0):
"""Cancel the last/[count]th download.
@ -1032,12 +1034,12 @@ class DownloadModel(QAbstractListModel):
if download.done:
if not count:
count = len(self)
raise cmdexc.CommandError("Download {} is already done!"
.format(count))
raise cmdutils.CommandError("Download {} is already done!"
.format(count))
download.cancel()
@cmdutils.register(instance='download-model', scope='window')
@cmdutils.argument('count', count=True)
@cmdutils.argument('count', value=cmdutils.Value.count)
def download_delete(self, count=0):
"""Delete the last/[count]th download from disk.
@ -1051,14 +1053,15 @@ class DownloadModel(QAbstractListModel):
if not download.successful:
if not count:
count = len(self)
raise cmdexc.CommandError("Download {} is not done!".format(count))
raise cmdutils.CommandError("Download {} is not done!"
.format(count))
download.delete()
download.remove()
log.downloads.debug("deleted download {}".format(download))
@cmdutils.register(instance='download-model', scope='window', maxsplit=0)
@cmdutils.argument('count', count=True)
def download_open(self, cmdline: str = None, count=0):
@cmdutils.argument('count', value=cmdutils.Value.count)
def download_open(self, cmdline: str = None, count: int = 0) -> None:
"""Open the last/[count]th download.
If no specific command is given, this will use the system's default
@ -1078,11 +1081,12 @@ class DownloadModel(QAbstractListModel):
if not download.successful:
if not count:
count = len(self)
raise cmdexc.CommandError("Download {} is not done!".format(count))
raise cmdutils.CommandError("Download {} is not done!"
.format(count))
download.open_file(cmdline)
@cmdutils.register(instance='download-model', scope='window')
@cmdutils.argument('count', count=True)
@cmdutils.argument('count', value=cmdutils.Value.count)
def download_retry(self, count=0):
"""Retry the first failed/[count]th download.
@ -1095,14 +1099,13 @@ class DownloadModel(QAbstractListModel):
except IndexError:
self._raise_no_download(count)
if download.successful or not download.done:
raise cmdexc.CommandError("Download {} did not fail!".format(
count))
raise cmdutils.CommandError("Download {} did not fail!"
.format(count))
else:
to_retry = [d for d in self if d.done and not d.successful]
if not to_retry:
raise cmdexc.CommandError("No failed downloads!")
else:
download = to_retry[0]
raise cmdutils.CommandError("No failed downloads!")
download = to_retry[0]
download.try_retry()
def can_clear(self):
@ -1117,7 +1120,7 @@ class DownloadModel(QAbstractListModel):
download.remove()
@cmdutils.register(instance='download-model', scope='window')
@cmdutils.argument('count', count=True)
@cmdutils.argument('count', value=cmdutils.Value.count)
def download_remove(self, all_=False, count=0):
"""Remove the last/[count]th download from the list.
@ -1135,8 +1138,8 @@ class DownloadModel(QAbstractListModel):
if not download.done:
if not count:
count = len(self)
raise cmdexc.CommandError("Download {} is not done!"
.format(count))
raise cmdutils.CommandError("Download {} is not done!"
.format(count))
download.remove()
def running_downloads(self):

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -75,7 +75,8 @@ class DownloadView(QListView):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self.setStyle(QStyleFactory.create('Fusion'))
if not utils.is_mac:
self.setStyle(QStyleFactory.create('Fusion'))
config.set_register_stylesheet(self)
self.setResizeMode(QListView.Adjust)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2017-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -31,8 +31,9 @@ import attr
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
from qutebrowser.utils import (log, standarddir, jinja, objreg, utils,
javascript, urlmatch, version, usertypes)
from qutebrowser.commands import cmdutils
javascript, urlmatch, version, usertypes,
qtutils)
from qutebrowser.api import cmdutils
from qutebrowser.browser import downloads
from qutebrowser.misc import objects
@ -116,6 +117,40 @@ class GreasemonkeyScript:
script.includes = ['*']
return script
def needs_document_end_workaround(self):
"""Check whether to force @run-at document-end.
This needs to be done on QtWebEngine with Qt 5.12 for known-broken
scripts.
On Qt 5.12, accessing the DOM isn't possible with "@run-at
document-start". It was documented to be impossible before, but seems
to work fine.
However, some scripts do DOM access with "@run-at document-start". Fix
those by forcing them to use document-end instead.
"""
if objects.backend != usertypes.Backend.QtWebEngine:
return False
elif not qtutils.version_check('5.12', compiled=False):
return False
broken_scripts = [
('http://userstyles.org', None),
('https://github.com/ParticleCore', 'Iridium'),
]
return any(self._matches_id(namespace=namespace, name=name)
for namespace, name in broken_scripts)
def _matches_id(self, *, namespace, name):
"""Check if this script matches the given namespace/name.
Both namespace and name can be None in order to match any script.
"""
matches_namespace = namespace is None or self.namespace == namespace
matches_name = name is None or self.name == name
return matches_namespace and matches_name
def code(self):
"""Return the processed JavaScript code of this script.

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -34,7 +34,8 @@ from PyQt5.QtWidgets import QLabel
from qutebrowser.config import config, configexc
from qutebrowser.keyinput import modeman, modeparsers
from qutebrowser.browser import webelem
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
from qutebrowser.commands import userscripts, runners
from qutebrowser.api import cmdutils
from qutebrowser.utils import usertypes, log, qtutils, message, objreg, utils
@ -217,9 +218,7 @@ class HintActions:
if context.target in [Target.normal, Target.current]:
# Set the pre-jump mark ', so we can jump back here after following
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
tabbed_browser.set_mark("'")
context.tab.scroller.before_jump_requested.emit()
try:
if context.target == Target.hover:
@ -304,8 +303,8 @@ class HintActions:
raise HintingError("No suitable link found for this element.")
prompt = False if context.rapid else None
qnam = context.tab.networkaccessmanager()
user_agent = context.tab.user_agent()
qnam = context.tab.private_api.networkaccessmanager()
user_agent = context.tab.private_api.user_agent()
# FIXME:qtwebengine do this with QtWebEngine downloads?
download_manager = objreg.get('qtnetwork-download-manager')
@ -563,12 +562,12 @@ class HintManager(QObject):
if target in [Target.userscript, Target.spawn, Target.run,
Target.fill]:
if not args:
raise cmdexc.CommandError(
raise cmdutils.CommandError(
"'args' is required with target userscript/spawn/run/"
"fill.")
else:
if args:
raise cmdexc.CommandError(
raise cmdutils.CommandError(
"'args' is only allowed with target userscript/spawn.")
def _filter_matches(self, filterstr, elemstr):
@ -596,13 +595,6 @@ class HintManager(QObject):
log.hints.debug("In _start_cb without context!")
return
if elems is None:
message.error("Unknown error while getting hint elements.")
return
elif isinstance(elems, webelem.Error):
message.error(str(elems))
return
if not elems:
message.error("No elements found.")
return
@ -705,7 +697,7 @@ class HintManager(QObject):
window=self._win_id)
tab = tabbed_browser.widget.currentWidget()
if tab is None:
raise cmdexc.CommandError("No WebView available yet!")
raise cmdutils.CommandError("No WebView available yet!")
mode_manager = objreg.get('mode-manager', scope='window',
window=self._win_id)
@ -722,8 +714,8 @@ class HintManager(QObject):
pass
else:
name = target.name.replace('_', '-')
raise cmdexc.CommandError("Rapid hinting makes no sense with "
"target {}!".format(name))
raise cmdutils.CommandError("Rapid hinting makes no sense "
"with target {}!".format(name))
self._check_args(target, *args)
self._context = HintContext()
@ -736,18 +728,21 @@ class HintManager(QObject):
try:
self._context.baseurl = tabbed_browser.current_url()
except qtutils.QtValueError:
raise cmdexc.CommandError("No URL set for this page yet!")
self._context.args = args
raise cmdutils.CommandError("No URL set for this page yet!")
self._context.args = list(args)
self._context.group = group
try:
selector = webelem.css_selector(self._context.group,
self._context.baseurl)
except webelem.Error as e:
raise cmdexc.CommandError(str(e))
raise cmdutils.CommandError(str(e))
self._context.tab.elements.find_css(selector, self._start_cb,
only_visible=True)
self._context.tab.elements.find_css(
selector,
callback=self._start_cb,
error_cb=lambda err: message.error(str(err)),
only_visible=True)
def _get_hint_mode(self, mode):
"""Get the hinting mode to use based on a mode argument."""
@ -758,7 +753,7 @@ class HintManager(QObject):
try:
opt.typ.to_py(mode)
except configexc.ValidationError as e:
raise cmdexc.CommandError("Invalid mode: {}".format(e))
raise cmdutils.CommandError("Invalid mode: {}".format(e))
return mode
def current_mode(self):
@ -960,13 +955,12 @@ class HintManager(QObject):
"""
if keystring is None:
if self._context.to_follow is None:
raise cmdexc.CommandError("No hint to follow")
elif select:
raise cmdexc.CommandError("Can't use --select without hint.")
else:
keystring = self._context.to_follow
raise cmdutils.CommandError("No hint to follow")
if select:
raise cmdutils.CommandError("Can't use --select without hint.")
keystring = self._context.to_follow
elif keystring not in self._context.labels:
raise cmdexc.CommandError("No hint {}!".format(keystring))
raise cmdutils.CommandError("No hint {}!".format(keystring))
if select:
self.handle_partial_key(keystring)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -27,7 +27,7 @@ from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal
from PyQt5.QtWidgets import QProgressDialog, QApplication
from qutebrowser.config import config
from qutebrowser.commands import cmdutils, cmdexc
from qutebrowser.api import cmdutils
from qutebrowser.utils import utils, objreg, log, usertypes, message, qtutils
from qutebrowser.misc import objects, sql
@ -365,7 +365,8 @@ class WebHistory(sql.SqlTable):
f.write('\n'.join(lines))
message.info("Dumped history to {}".format(dest))
except OSError as e:
raise cmdexc.CommandError('Could not write history: {}'.format(e))
raise cmdutils.CommandError('Could not write history: {}'
.format(e))
def init(parent=None):

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -49,8 +49,6 @@ class WebInspectorError(Exception):
"""Raised when the inspector could not be initialized."""
pass
class AbstractWebInspector(QWidget):

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -240,7 +240,7 @@ class MouseEventFilter(QObject):
evtype = event.type()
if evtype not in self._handlers:
return False
if obj is not self._tab.event_target():
if obj is not self._tab.private_api.event_target():
log.mouse.debug("Ignoring {} to {}".format(
event.__class__.__name__, obj))
return False

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -116,13 +116,6 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
window: True to open in a new window, False for the current one.
"""
def _prevnext_cb(elems):
if elems is None:
message.error("Unknown error while getting hint elements")
return
elif isinstance(elems, webelem.Error):
message.error(str(elems))
return
elem = _find_prevnext(prev, elems)
word = 'prev' if prev else 'forward'
@ -140,7 +133,7 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
if window:
new_window = mainwindow.MainWindow(
private=cur_tabbed_browser.private)
private=cur_tabbed_browser.is_private)
new_window.show()
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=new_window.win_id)
@ -148,11 +141,12 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
elif tab:
cur_tabbed_browser.tabopen(url, background=background)
else:
browsertab.openurl(url)
browsertab.load_url(url)
try:
link_selector = webelem.css_selector('links', baseurl)
except webelem.Error as e:
raise Error(str(e))
browsertab.elements.find_css(link_selector, _prevnext_cb)
browsertab.elements.find_css(link_selector, callback=_prevnext_cb,
error_cb=lambda err: message.error(str(err)))

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -35,15 +35,11 @@ class ParseProxyError(Exception):
"""Error while parsing PAC result string."""
pass
class EvalProxyError(Exception):
"""Error while evaluating PAC script."""
pass
def _js_slot(*args):
"""Wrap a methods as a JavaScript function.
@ -146,7 +142,8 @@ class PACResolver:
config = [c.strip() for c in proxy_str.split(' ') if c]
if not config:
raise ParseProxyError("Empty proxy entry")
elif config[0] == "DIRECT":
if config[0] == "DIRECT":
if len(config) != 1:
raise ParseProxyError("Invalid number of parameters for " +
"DIRECT")
@ -184,6 +181,8 @@ class PACResolver:
"""
self._engine = QJSEngine()
self._engine.installExtensions(QJSEngine.ConsoleExtension)
self._ctx = _PACContext(self._engine)
self._engine.globalObject().setProperty(
"PAC", self._engine.newQObject(self._ctx))
@ -210,6 +209,8 @@ class PACResolver:
Return:
A list of QNetworkProxy objects in order of preference.
"""
qtutils.ensure_valid(query.url())
if from_file:
string_flags = QUrl.PrettyDecoded
else:

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -20,10 +20,12 @@
"""Handling of proxies."""
from PyQt5.QtCore import QUrl
from PyQt5.QtNetwork import QNetworkProxy, QNetworkProxyFactory
from qutebrowser.config import config, configtypes
from qutebrowser.utils import objreg
from qutebrowser.utils import objreg, message, usertypes, urlutils
from qutebrowser.misc import objects
from qutebrowser.browser.network import pac
@ -33,6 +35,18 @@ def init():
objreg.register('proxy-factory', proxy_factory)
QNetworkProxyFactory.setApplicationProxyFactory(proxy_factory)
config.instance.changed.connect(_warn_for_pac)
_warn_for_pac()
@config.change_filter('content.proxy', function=True)
def _warn_for_pac():
"""Show a warning if PAC is used with QtWebEngine."""
proxy = config.val.content.proxy
if (isinstance(proxy, pac.PACFetcher) and
objects.backend == usertypes.Backend.QtWebEngine):
message.error("PAC support isn't implemented for QtWebEngine yet!")
def shutdown():
QNetworkProxyFactory.setApplicationProxyFactory(None)
@ -70,7 +84,11 @@ class ProxyFactory(QNetworkProxyFactory):
# ref. http://doc.qt.io/qt-5/qnetworkproxyfactory.html#systemProxyForQuery
proxies = QNetworkProxyFactory.systemProxyForQuery(query)
elif isinstance(proxy, pac.PACFetcher):
proxies = proxy.resolve(query)
if objects.backend == usertypes.Backend.QtWebEngine:
# Looks like query.url() is always invalid on QtWebEngine...
proxies = [urlutils.proxy_from_url(QUrl('direct://'))]
else:
proxies = proxy.resolve(query)
else:
proxies = [proxy]
for p in proxies:

View File

@ -1,7 +1,7 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015 Daniel Schadt
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -37,7 +37,7 @@ try:
import secrets
except ImportError:
# New in Python 3.6
secrets = None
secrets = None # type: ignore
from PyQt5.QtCore import QUrlQuery, QUrl, qVersion
@ -61,36 +61,26 @@ class Error(Exception):
"""Exception for generic errors on a qute:// page."""
pass
class NotFoundError(Error):
"""Raised when the given URL was not found."""
pass
class SchemeOSError(Error):
"""Raised when there was an OSError inside a handler."""
pass
class UrlInvalidError(Error):
"""Raised when an invalid URL was opened."""
pass
class RequestDeniedError(Error):
"""Raised when the request is forbidden."""
pass
class Redirect(Exception):
@ -350,19 +340,11 @@ def qute_gpl(_url):
def _asciidoc_fallback_path(html_path):
"""Fall back to plaintext asciidoc if the HTML is unavailable."""
asciidoc_path = html_path.replace('.html', '.asciidoc')
asciidoc_paths = [asciidoc_path]
if asciidoc_path.startswith('html/doc/'):
asciidoc_paths += [asciidoc_path.replace('html/doc/', '../doc/help/'),
asciidoc_path.replace('html/doc/', '../doc/')]
for path in asciidoc_paths:
try:
return utils.read_file(path)
except OSError:
pass
return None
path = html_path.replace('.html', '.asciidoc')
try:
return utils.read_file(path)
except OSError:
return None
@add_handler('help')

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -42,7 +42,6 @@ def custom_headers(url):
if dnt_config is not None:
dnt = b'1' if dnt_config else b'0'
headers[b'DNT'] = dnt
headers[b'X-Do-Not-Track'] = dnt
conf_headers = config.instance.get('content.headers.custom', url=url)
for header, value in conf_headers.items():
@ -262,7 +261,7 @@ def get_tab(win_id, target):
elif target == usertypes.ClickTarget.window:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
window = mainwindow.MainWindow(private=tabbed_browser.private)
window = mainwindow.MainWindow(private=tabbed_browser.is_private)
window.show()
win_id = window.win_id
bg_tab = False

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2018 Antoni Boucher <bouanto@zoho.com>
#
# This file is part of qutebrowser.
@ -35,7 +35,7 @@ from PyQt5.QtCore import pyqtSignal, QUrl, QObject
from qutebrowser.utils import (message, usertypes, qtutils, urlutils,
standarddir, objreg, log)
from qutebrowser.commands import cmdutils
from qutebrowser.api import cmdutils
from qutebrowser.misc import lineparser
@ -43,29 +43,21 @@ class Error(Exception):
"""Base class for all errors in this module."""
pass
class InvalidUrlError(Error):
"""Exception emitted when a URL is invalid."""
pass
class DoesNotExistError(Error):
"""Exception emitted when a given URL does not exist."""
pass
class AlreadyExistsError(Error):
"""Exception emitted when a given URL does already exist."""
pass
class UrlMarkManager(QObject):
@ -174,7 +166,7 @@ class QuickmarkManager(UrlMarkManager):
url: The url to add as quickmark.
name: The name for the new quickmark.
"""
# We don't raise cmdexc.CommandError here as this can be called async
# We don't raise cmdutils.CommandError here as this can be called async
# via prompt_save.
if not name:
message.error("Can't set mark with empty name!")

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -19,32 +19,36 @@
"""Generic web element related code."""
import typing
import collections.abc
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer, QRect, QPoint
from PyQt5.QtGui import QMouseEvent
from qutebrowser.config import config
from qutebrowser.keyinput import modeman
from qutebrowser.mainwindow import mainwindow
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
MYPY = False
if MYPY:
# pylint: disable=unused-import,useless-suppression
from qutebrowser.browser import browsertab
JsValueType = typing.Union[int, float, str, None]
class Error(Exception):
"""Base class for WebElement errors."""
pass
class OrphanedError(Error):
"""Raised when a webelement's parent has vanished."""
pass
def css_selector(group, url):
def css_selector(group: str, url: QUrl) -> str:
"""Get a CSS selector for the given group/URL."""
selectors = config.instance.get('hints.selectors', url)
if group not in selectors:
@ -58,76 +62,74 @@ def css_selector(group, url):
class AbstractWebElement(collections.abc.MutableMapping):
"""A wrapper around QtWebKit/QtWebEngine web element.
"""A wrapper around QtWebKit/QtWebEngine web element."""
Attributes:
tab: The tab associated with this element.
"""
def __init__(self, tab):
def __init__(self, tab: 'browsertab.AbstractTab') -> None:
self._tab = tab
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
raise NotImplementedError
def __str__(self):
def __str__(self) -> str:
raise NotImplementedError
def __getitem__(self, key):
def __getitem__(self, key: str) -> str:
raise NotImplementedError
def __setitem__(self, key, val):
def __setitem__(self, key: str, val: str) -> None:
raise NotImplementedError
def __delitem__(self, key):
def __delitem__(self, key: str) -> None:
raise NotImplementedError
def __iter__(self):
def __iter__(self) -> typing.Iterator[str]:
raise NotImplementedError
def __len__(self):
def __len__(self) -> int:
raise NotImplementedError
def __repr__(self):
def __repr__(self) -> str:
try:
html = utils.compact_text(self.outer_xml(), 500)
except Error:
html = None
return utils.get_repr(self, html=html)
def has_frame(self):
def has_frame(self) -> bool:
"""Check if this element has a valid frame attached."""
raise NotImplementedError
def geometry(self):
def geometry(self) -> QRect:
"""Get the geometry for this element."""
raise NotImplementedError
def classes(self):
def classes(self) -> typing.List[str]:
"""Get a list of classes assigned to this element."""
raise NotImplementedError
def tag_name(self):
def tag_name(self) -> str:
"""Get the tag name of this element.
The returned name will always be lower-case.
"""
raise NotImplementedError
def outer_xml(self):
def outer_xml(self) -> str:
"""Get the full HTML representation of this element."""
raise NotImplementedError
def value(self):
def value(self) -> JsValueType:
"""Get the value attribute for this element, or None."""
raise NotImplementedError
def set_value(self, value):
def set_value(self, value: JsValueType) -> None:
"""Set the element value."""
raise NotImplementedError
def dispatch_event(self, event, bubbles=False,
cancelable=False, composed=False):
def dispatch_event(self, event: str,
bubbles: bool = False,
cancelable: bool = False,
composed: bool = False) -> None:
"""Dispatch an event to the element.
Args:
@ -138,35 +140,25 @@ class AbstractWebElement(collections.abc.MutableMapping):
"""
raise NotImplementedError
def insert_text(self, text):
def insert_text(self, text: str) -> None:
"""Insert the given text into the element."""
raise NotImplementedError
def rect_on_view(self, *, elem_geometry=None, no_js=False):
def rect_on_view(self, *, elem_geometry: QRect = None,
no_js: bool = False) -> QRect:
"""Get the geometry of the element relative to the webview.
Uses the getClientRects() JavaScript method to obtain the collection of
rectangles containing the element and returns the first rectangle which
is large enough (larger than 1px times 1px). If all rectangles returned
by getClientRects() are too small, falls back to elem.rect_on_view().
Skipping of small rectangles is due to <a> elements containing other
elements with "display:block" style, see
https://github.com/qutebrowser/qutebrowser/issues/1298
Args:
elem_geometry: The geometry of the element, or None.
Calling QWebElement::geometry is rather expensive so
we want to avoid doing it twice.
no_js: Fall back to the Python implementation
no_js: Fall back to the Python implementation.
"""
raise NotImplementedError
def is_writable(self):
def is_writable(self) -> bool:
"""Check whether an element is writable."""
return not ('disabled' in self or 'readonly' in self)
def is_content_editable(self):
def is_content_editable(self) -> bool:
"""Check if an element has a contenteditable attribute.
Args:
@ -181,7 +173,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
except KeyError:
return False
def _is_editable_object(self):
def _is_editable_object(self) -> bool:
"""Check if an object-element is editable."""
if 'type' not in self:
log.webelem.debug("<object> without type clicked...")
@ -197,7 +189,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
# Image/Audio/...
return False
def _is_editable_input(self):
def _is_editable_input(self) -> bool:
"""Check if an input-element is editable.
Return:
@ -214,7 +206,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
else:
return False
def _is_editable_classes(self):
def _is_editable_classes(self) -> bool:
"""Check if an element is editable based on its classes.
Return:
@ -233,7 +225,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
return True
return False
def is_editable(self, strict=False):
def is_editable(self, strict: bool = False) -> bool:
"""Check whether we should switch to insert mode for this element.
Args:
@ -264,17 +256,17 @@ class AbstractWebElement(collections.abc.MutableMapping):
return self._is_editable_classes() and not strict
return False
def is_text_input(self):
def is_text_input(self) -> bool:
"""Check if this element is some kind of text box."""
roles = ('combobox', 'textbox')
tag = self.tag_name()
return self.get('role', None) in roles or tag in ['input', 'textarea']
def remove_blank_target(self):
def remove_blank_target(self) -> None:
"""Remove target from link."""
raise NotImplementedError
def resolve_url(self, baseurl):
def resolve_url(self, baseurl: QUrl) -> typing.Optional[QUrl]:
"""Resolve the URL in the element's src/href attribute.
Args:
@ -301,16 +293,16 @@ class AbstractWebElement(collections.abc.MutableMapping):
qtutils.ensure_valid(url)
return url
def is_link(self):
def is_link(self) -> bool:
"""Return True if this AbstractWebElement is a link."""
href_tags = ['a', 'area', 'link']
return self.tag_name() in href_tags and 'href' in self
def _requires_user_interaction(self):
def _requires_user_interaction(self) -> bool:
"""Return True if clicking this element needs user interaction."""
raise NotImplementedError
def _mouse_pos(self):
def _mouse_pos(self) -> QPoint:
"""Get the position to click/hover."""
# Click the center of the largest square fitting into the top/left
# corner of the rectangle, this will help if part of the <a> element
@ -326,35 +318,38 @@ class AbstractWebElement(collections.abc.MutableMapping):
raise Error("Element position is out of view!")
return pos
def _move_text_cursor(self):
def _move_text_cursor(self) -> None:
"""Move cursor to end after clicking."""
raise NotImplementedError
def _click_fake_event(self, click_target):
def _click_fake_event(self, click_target: usertypes.ClickTarget) -> None:
"""Send a fake click event to the element."""
pos = self._mouse_pos()
log.webelem.debug("Sending fake click to {!r} at position {} with "
"target {}".format(self, pos, click_target))
modifiers = {
target_modifiers = {
usertypes.ClickTarget.normal: Qt.NoModifier,
usertypes.ClickTarget.window: Qt.AltModifier | Qt.ShiftModifier,
usertypes.ClickTarget.tab: Qt.ControlModifier,
usertypes.ClickTarget.tab_bg: Qt.ControlModifier,
}
if config.val.tabs.background:
modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier
target_modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier
else:
modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier
target_modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier
modifiers = typing.cast(Qt.KeyboardModifiers,
target_modifiers[click_target])
events = [
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
Qt.NoModifier),
QMouseEvent(QEvent.MouseButtonPress, pos, Qt.LeftButton,
Qt.LeftButton, modifiers[click_target]),
Qt.LeftButton, modifiers),
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
Qt.NoButton, modifiers[click_target]),
Qt.NoButton, modifiers),
]
for evt in events:
@ -362,15 +357,15 @@ class AbstractWebElement(collections.abc.MutableMapping):
QTimer.singleShot(0, self._move_text_cursor)
def _click_editable(self, click_target):
def _click_editable(self, click_target: usertypes.ClickTarget) -> None:
"""Fake a click on an editable input field."""
raise NotImplementedError
def _click_js(self, click_target):
def _click_js(self, click_target: usertypes.ClickTarget) -> None:
"""Fake a click by using the JS .click() method."""
raise NotImplementedError
def _click_href(self, click_target):
def _click_href(self, click_target: usertypes.ClickTarget) -> None:
"""Fake a click on an element with a href by opening the link."""
baseurl = self._tab.url()
url = self.resolve_url(baseurl)
@ -386,13 +381,14 @@ class AbstractWebElement(collections.abc.MutableMapping):
background = click_target == usertypes.ClickTarget.tab_bg
tabbed_browser.tabopen(url, background=background)
elif click_target == usertypes.ClickTarget.window:
window = mainwindow.MainWindow(private=tabbed_browser.private)
window = mainwindow.MainWindow(private=tabbed_browser.is_private)
window.show()
window.tabbed_browser.tabopen(url)
else:
raise ValueError("Unknown ClickTarget {}".format(click_target))
def click(self, click_target, *, force_event=False):
def click(self, click_target: usertypes.ClickTarget, *,
force_event: bool = False) -> None:
"""Simulate a click on the element.
Args:
@ -429,7 +425,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
else:
raise ValueError("Unknown ClickTarget {}".format(click_target))
def hover(self):
def hover(self) -> None:
"""Simulate a mouse hover over the element."""
pos = self._mouse_pos()
event = QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2018-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -26,15 +26,59 @@ from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
from qutebrowser.config import config
from qutebrowser.browser import shared
from qutebrowser.utils import utils, log, debug
from qutebrowser.extensions import interceptors
class RequestInterceptor(QWebEngineUrlRequestInterceptor):
"""Handle ad blocking and custom headers."""
def __init__(self, host_blocker, args, parent=None):
# This dict should be from QWebEngine Resource Types to qutebrowser
# extension ResourceTypes. If a ResourceType is added to Qt, this table
# should be updated too.
RESOURCE_TYPES = {
QWebEngineUrlRequestInfo.ResourceTypeMainFrame:
interceptors.ResourceType.main_frame,
QWebEngineUrlRequestInfo.ResourceTypeSubFrame:
interceptors.ResourceType.sub_frame,
QWebEngineUrlRequestInfo.ResourceTypeStylesheet:
interceptors.ResourceType.stylesheet,
QWebEngineUrlRequestInfo.ResourceTypeScript:
interceptors.ResourceType.script,
QWebEngineUrlRequestInfo.ResourceTypeImage:
interceptors.ResourceType.image,
QWebEngineUrlRequestInfo.ResourceTypeFontResource:
interceptors.ResourceType.font_resource,
QWebEngineUrlRequestInfo.ResourceTypeSubResource:
interceptors.ResourceType.sub_resource,
QWebEngineUrlRequestInfo.ResourceTypeObject:
interceptors.ResourceType.object,
QWebEngineUrlRequestInfo.ResourceTypeMedia:
interceptors.ResourceType.media,
QWebEngineUrlRequestInfo.ResourceTypeWorker:
interceptors.ResourceType.worker,
QWebEngineUrlRequestInfo.ResourceTypeSharedWorker:
interceptors.ResourceType.shared_worker,
QWebEngineUrlRequestInfo.ResourceTypePrefetch:
interceptors.ResourceType.prefetch,
QWebEngineUrlRequestInfo.ResourceTypeFavicon:
interceptors.ResourceType.favicon,
QWebEngineUrlRequestInfo.ResourceTypeXhr:
interceptors.ResourceType.xhr,
QWebEngineUrlRequestInfo.ResourceTypePing:
interceptors.ResourceType.ping,
QWebEngineUrlRequestInfo.ResourceTypeServiceWorker:
interceptors.ResourceType.service_worker,
QWebEngineUrlRequestInfo.ResourceTypeCspReport:
interceptors.ResourceType.csp_report,
QWebEngineUrlRequestInfo.ResourceTypePluginResource:
interceptors.ResourceType.plugin_resource,
QWebEngineUrlRequestInfo.ResourceTypeUnknown:
interceptors.ResourceType.unknown,
}
def __init__(self, args, parent=None):
super().__init__(parent)
self._host_blocker = host_blocker
self._args = args
def install(self, profile):
@ -71,6 +115,17 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
url = info.requestUrl()
first_party = info.firstPartyUrl()
# Per QWebEngineUrlRequestInfo::ResourceType documentation, if we fail
# our lookup, we should fall back to ResourceTypeUnknown
try:
resource_type = RequestInterceptor.RESOURCE_TYPES[
info.resourceType()]
except KeyError:
log.webview.warning(
"Resource type {} not found in RequestInterceptor dict."
.format(debug.qenum_key(QWebEngineUrlRequestInfo,
info.resourceType())))
resource_type = interceptors.ResourceType.unknown
if ((url.scheme(), url.host(), url.path()) ==
('qute', 'settings', '/set')):
@ -84,9 +139,11 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
return
# FIXME:qtwebengine only block ads for NavigationTypeOther?
if self._host_blocker.is_blocked(url, first_party):
log.webview.info("Request to {} blocked by host blocker.".format(
url.host()))
request = interceptors.Request(first_party_url=first_party,
request_url=url,
resource_type=resource_type)
interceptors.run(request)
if request.is_blocked:
info.block(True)
for header, value in shared.custom_headers(url=url):

View File

@ -1,5 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2017-2018 Michal Siedlaczek <michal.siedlaczek@gmail.com>
# This file is part of qutebrowser.

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -117,8 +117,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
def _get_open_filename(self):
return self._filename
def _set_fileobj(self, fileobj, *,
autoclose=True): # pylint: disable=unused-argument
def _set_fileobj(self, fileobj, *, autoclose=True):
raise downloads.UnsupportedOperationError
def _set_tempfile(self, fileobj):
@ -181,7 +180,21 @@ def _get_suggested_filename(path):
See https://bugreports.qt.io/browse/QTBUG-56978
"""
filename = os.path.basename(path)
filename = re.sub(r'\([0-9]+\)(?=\.|$)', '', filename)
suffix_re = re.compile(r"""
\ ? # Optional space between filename and suffix
(
# Numerical suffix
\([0-9]+\)
|
# ISO-8601 suffix
# https://cs.chromium.org/chromium/src/base/time/time_to_iso8601.cc
\ -\ \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z
)
(?=\.|$) # Begin of extension, or filename without extension
""", re.VERBOSE)
filename = suffix_re.sub('', filename)
if not qtutils.version_check('5.9', compiled=False):
# https://bugreports.qt.io/browse/QTBUG-58155
filename = urllib.parse.unquote(filename)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -22,20 +22,27 @@
"""QtWebEngine specific part of the web element API."""
import typing
from PyQt5.QtCore import QRect, Qt, QPoint, QEventLoop
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineSettings
from qutebrowser.utils import log, javascript, urlutils
from qutebrowser.utils import log, javascript, urlutils, usertypes
from qutebrowser.browser import webelem
MYPY = False
if MYPY:
# pylint: disable=unused-import,useless-suppression
from qutebrowser.browser.webengine import webenginetab
class WebEngineElement(webelem.AbstractWebElement):
"""A web element for QtWebEngine, using JS under the hood."""
def __init__(self, js_dict, tab):
def __init__(self, js_dict: typing.Dict[str, typing.Any],
tab: 'webenginetab.WebEngineTab') -> None:
super().__init__(tab)
# Do some sanity checks on the data we get from JS
js_dict_types = {
@ -48,7 +55,7 @@ class WebEngineElement(webelem.AbstractWebElement):
'rects': list,
'attributes': dict,
'caret_position': (int, type(None)),
}
} # type: typing.Dict[str, typing.Union[type, typing.Tuple[type,...]]]
assert set(js_dict.keys()).issubset(js_dict_types.keys())
for name, typ in js_dict_types.items():
if name in js_dict and not isinstance(js_dict[name], typ):
@ -73,50 +80,51 @@ class WebEngineElement(webelem.AbstractWebElement):
self._id = js_dict['id']
self._js_dict = js_dict
def __str__(self):
def __str__(self) -> str:
return self._js_dict.get('text', '')
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
if not isinstance(other, WebEngineElement):
return NotImplemented
return self._id == other._id # pylint: disable=protected-access
def __getitem__(self, key):
def __getitem__(self, key: str) -> str:
attrs = self._js_dict['attributes']
return attrs[key]
def __setitem__(self, key, val):
def __setitem__(self, key: str, val: str) -> None:
self._js_dict['attributes'][key] = val
self._js_call('set_attribute', key, val)
def __delitem__(self, key):
def __delitem__(self, key: str) -> None:
log.stub()
def __iter__(self):
def __iter__(self) -> typing.Iterator[str]:
return iter(self._js_dict['attributes'])
def __len__(self):
def __len__(self) -> int:
return len(self._js_dict['attributes'])
def _js_call(self, name, *args, callback=None):
def _js_call(self, name: str, *args: webelem.JsValueType,
callback: typing.Callable[[typing.Any], None] = None) -> None:
"""Wrapper to run stuff from webelem.js."""
if self._tab.is_deleted():
raise webelem.OrphanedError("Tab containing element vanished")
js_code = javascript.assemble('webelem', name, self._id, *args)
self._tab.run_js_async(js_code, callback=callback)
def has_frame(self):
def has_frame(self) -> bool:
return True
def geometry(self):
def geometry(self) -> QRect:
log.stub()
return QRect()
def classes(self):
def classes(self) -> typing.List[str]:
"""Get a list of classes assigned to this element."""
return self._js_dict['class_name'].split()
def tag_name(self):
def tag_name(self) -> str:
"""Get the tag name of this element.
The returned name will always be lower-case.
@ -125,34 +133,37 @@ class WebEngineElement(webelem.AbstractWebElement):
assert isinstance(tag, str), tag
return tag.lower()
def outer_xml(self):
def outer_xml(self) -> str:
"""Get the full HTML representation of this element."""
return self._js_dict['outer_xml']
def value(self):
def value(self) -> webelem.JsValueType:
return self._js_dict.get('value', None)
def set_value(self, value):
def set_value(self, value: webelem.JsValueType) -> None:
self._js_call('set_value', value)
def dispatch_event(self, event, bubbles=False,
cancelable=False, composed=False):
def dispatch_event(self, event: str,
bubbles: bool = False,
cancelable: bool = False,
composed: bool = False) -> None:
self._js_call('dispatch_event', event, bubbles, cancelable, composed)
def caret_position(self):
def caret_position(self) -> typing.Optional[int]:
"""Get the text caret position for the current element.
If the element is not a text element, None is returned.
"""
return self._js_dict.get('caret_position', None)
def insert_text(self, text):
def insert_text(self, text: str) -> None:
if not self.is_editable(strict=True):
raise webelem.Error("Element is not editable!")
log.webelem.debug("Inserting text into element {!r}".format(self))
self._js_call('insert_text', text)
def rect_on_view(self, *, elem_geometry=None, no_js=False):
def rect_on_view(self, *, elem_geometry: QRect = None,
no_js: bool = False) -> QRect:
"""Get the geometry of the element relative to the webview.
Skipping of small rectangles is due to <a> elements containing other
@ -193,16 +204,16 @@ class WebEngineElement(webelem.AbstractWebElement):
self, rects))
return QRect()
def remove_blank_target(self):
def remove_blank_target(self) -> None:
if self._js_dict['attributes'].get('target') == '_blank':
self._js_dict['attributes']['target'] = '_top'
self._js_call('remove_blank_target')
def _move_text_cursor(self):
def _move_text_cursor(self) -> None:
if self.is_text_input() and self.is_editable():
self._js_call('move_cursor_to_end')
def _requires_user_interaction(self):
def _requires_user_interaction(self) -> bool:
baseurl = self._tab.url()
url = self.resolve_url(baseurl)
if url is None:
@ -211,7 +222,7 @@ class WebEngineElement(webelem.AbstractWebElement):
return False
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
def _click_editable(self, click_target):
def _click_editable(self, click_target: usertypes.ClickTarget) -> None:
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515
ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),
QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton,
@ -221,10 +232,11 @@ class WebEngineElement(webelem.AbstractWebElement):
self._js_call('focus')
self._move_text_cursor()
def _click_js(self, _click_target):
def _click_js(self, _click_target: usertypes.ClickTarget) -> None:
# FIXME:qtwebengine Have a proper API for this
# pylint: disable=protected-access
view = self._tab._widget
assert view is not None
# pylint: enable=protected-access
attribute = QWebEngineSettings.JavascriptCanOpenWindows
could_open_windows = view.settings().testAttribute(attribute)
@ -238,8 +250,9 @@ class WebEngineElement(webelem.AbstractWebElement):
qapp.processEvents(QEventLoop.ExcludeSocketNotifiers |
QEventLoop.ExcludeUserInputEvents)
def reset_setting(_arg):
def reset_setting(_arg: typing.Any) -> None:
"""Set the JavascriptCanOpenWindows setting to its old value."""
assert view is not None
try:
view.settings().setAttribute(attribute, could_open_windows)
except RuntimeError:

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -22,6 +22,11 @@
from PyQt5.QtCore import QBuffer, QIODevice, QUrl
from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
QWebEngineUrlRequestJob)
try:
from PyQt5.QtWebEngineCore import QWebEngineUrlScheme # type: ignore
except ImportError:
# Added in Qt 5.12
QWebEngineUrlScheme = None
from qutebrowser.browser import qutescheme
from qutebrowser.utils import log, qtutils
@ -33,8 +38,12 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
def install(self, profile):
"""Install the handler for qute:// URLs on the given profile."""
if QWebEngineUrlScheme is not None:
assert QWebEngineUrlScheme.schemeByName(b'qute') is not None
profile.installUrlSchemeHandler(b'qute', self)
if qtutils.version_check('5.11', compiled=False):
if (qtutils.version_check('5.11', compiled=False) and
not qtutils.version_check('5.12', compiled=False)):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
profile.installUrlSchemeHandler(b'chrome-error', self)
profile.installUrlSchemeHandler(b'chrome-extension', self)
@ -53,18 +62,33 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
"""
try:
initiator = job.initiator()
request_url = job.requestUrl()
except AttributeError:
# Added in Qt 5.11
return True
if initiator == QUrl('null') and not qtutils.version_check('5.12'):
# https://codereview.qt-project.org/#/c/234849/
is_opaque = initiator == QUrl('null')
target = request_url.scheme(), request_url.host()
if is_opaque and not qtutils.version_check('5.12'):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70421
# When we don't register the qute:// scheme, all requests are
# flagged as opaque.
return True
if (target == ('qute', 'testdata') and
is_opaque and
qtutils.version_check('5.12')):
# Allow requests to qute://testdata, as this is needed in Qt 5.12
# for all tests to work properly. No qute://testdata handler is
# installed outside of tests.
return True
if initiator.isValid() and initiator.scheme() != 'qute':
log.misc.warning("Blocking malicious request from {} to {}".format(
initiator.toDisplayString(),
job.requestUrl().toDisplayString()))
request_url.toDisplayString()))
job.fail(QWebEngineUrlRequestJob.RequestDenied)
return False
@ -130,3 +154,17 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
buf.seek(0)
buf.close()
job.reply(mimetype.encode('ascii'), buf)
def init():
"""Register the qute:// scheme.
Note this needs to be called early, before constructing any QtWebEngine
classes.
"""
if QWebEngineUrlScheme is not None:
assert not QWebEngineUrlScheme.schemeByName(b'qute').name()
scheme = QWebEngineUrlScheme(b'qute')
scheme.setFlags(QWebEngineUrlScheme.LocalScheme |
QWebEngineUrlScheme.LocalAccessAllowed)
QWebEngineUrlScheme.registerScheme(scheme)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -25,12 +25,13 @@ Module attributes:
"""
import os
import operator
from PyQt5.QtGui import QFont
from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
QWebEnginePage)
from qutebrowser.browser.webengine import spell
from qutebrowser.browser.webengine import spell, webenginequtescheme
from qutebrowser.config import config, websettings
from qutebrowser.config.websettings import AttributeInfo as Attr
from qutebrowser.utils import utils, standarddir, qtutils, message, log
@ -163,9 +164,14 @@ class WebEngineSettings(websettings.AbstractSettings):
# Qt 5.8
'content.print_element_backgrounds':
('PrintElementBackgrounds', None),
# Qt 5.11
'content.autoplay':
('PlaybackRequiresUserGesture', lambda val: not val),
('PlaybackRequiresUserGesture', operator.not_),
# Qt 5.12
'content.dns_prefetch':
('DnsPrefetchEnabled', None),
}
for name, (attribute, converter) in new_attributes.items():
try:
@ -298,6 +304,7 @@ def init(args):
not hasattr(QWebEnginePage, 'setInspectedPage')): # only Qt < 5.11
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
webenginequtescheme.init()
spell.init()
_init_profiles()

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -24,9 +24,9 @@ import functools
import re
import html as html_utils
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF,
QUrl, QTimer, QObject)
from PyQt5.QtGui import QKeyEvent, QIcon
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QUrl,
QTimer, QObject)
from PyQt5.QtGui import QIcon
from PyQt5.QtNetwork import QAuthenticator
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
@ -60,10 +60,8 @@ def init():
_qute_scheme_handler.install(webenginesettings.private_profile)
log.init.debug("Initializing request interceptor...")
host_blocker = objreg.get('host-blocker')
args = objreg.get('args')
req_interceptor = interceptor.RequestInterceptor(
host_blocker, args=args, parent=app)
req_interceptor = interceptor.RequestInterceptor(args=args, parent=app)
req_interceptor.install(webenginesettings.default_profile)
req_interceptor.install(webenginesettings.private_profile)
@ -132,7 +130,7 @@ class WebEnginePrinting(browsertab.AbstractPrinting):
"""QtWebEngine implementations related to printing."""
def check_pdf_support(self):
return True
pass
def check_printer_support(self):
if not hasattr(self._widget.page(), 'print'):
@ -205,8 +203,8 @@ class WebEngineSearch(browsertab.AbstractSearch):
self._widget.findText(text, flags, wrapped_callback)
def search(self, text, *, ignore_case='never', reverse=False,
result_cb=None):
def search(self, text, *, ignore_case=usertypes.IgnoreCase.never,
reverse=False, result_cb=None):
# Don't go to next entry on duplicate search
if self.text == text and self.search_displayed:
log.webview.debug("Ignoring duplicate search request"
@ -423,7 +421,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
def _repeated_key_press(self, key, count=1, modifier=Qt.NoModifier):
"""Send count fake key presses to this scroller's WebEngineTab."""
for _ in range(min(count, 1000)):
self._tab.key_press(key, modifier)
self._tab.fake_key_press(key, modifier)
@pyqtSlot(QPointF)
def _update_pos(self, pos):
@ -478,7 +476,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
def to_anchor(self, name):
url = self._tab.url()
url.setFragment(name)
self._tab.openurl(url)
self._tab.load_url(url)
def delta(self, x=0, y=0):
self._tab.run_js_async(javascript.assemble('window', 'scrollBy', x, y))
@ -500,10 +498,10 @@ class WebEngineScroller(browsertab.AbstractScroller):
self._repeated_key_press(Qt.Key_Right, count)
def top(self):
self._tab.key_press(Qt.Key_Home)
self._tab.fake_key_press(Qt.Key_Home)
def bottom(self):
self._tab.key_press(Qt.Key_End)
self._tab.fake_key_press(Qt.Key_End)
def page_up(self, count=1):
self._repeated_key_press(Qt.Key_PageUp, count)
@ -518,25 +516,9 @@ class WebEngineScroller(browsertab.AbstractScroller):
return self._at_bottom
class WebEngineHistory(browsertab.AbstractHistory):
class WebEngineHistoryPrivate(browsertab.AbstractHistoryPrivate):
"""QtWebEngine implementations related to page history."""
def current_idx(self):
return self._history.currentItemIndex()
def can_go_back(self):
return self._history.canGoBack()
def can_go_forward(self):
return self._history.canGoForward()
def _item_at(self, i):
return self._history.itemAt(i)
def _go_to_item(self, item):
self._tab.predicted_navigation.emit(item.url())
self._history.goToItem(item)
"""History-related methods which are not part of the extension API."""
def serialize(self):
if not qtutils.version_check('5.9', compiled=False):
@ -551,11 +533,11 @@ class WebEngineHistory(browsertab.AbstractHistory):
return qtutils.serialize(self._history)
def deserialize(self, data):
return qtutils.deserialize(data, self._history)
qtutils.deserialize(data, self._history)
def load_items(self, items):
if items:
self._tab.predicted_navigation.emit(items[-1].url)
self._tab.before_load_started.emit(items[-1].url)
stream, _data, cur_data = tabhistory.serialize(items)
qtutils.deserialize_stream(stream, self._history)
@ -573,6 +555,37 @@ class WebEngineHistory(browsertab.AbstractHistory):
self._tab.load_finished.connect(_on_load_finished)
class WebEngineHistory(browsertab.AbstractHistory):
"""QtWebEngine implementations related to page history."""
def __init__(self, tab):
super().__init__(tab)
self.private_api = WebEngineHistoryPrivate(tab)
def __len__(self):
return len(self._history)
def __iter__(self):
return iter(self._history.items())
def current_idx(self):
return self._history.currentItemIndex()
def can_go_back(self):
return self._history.canGoBack()
def can_go_forward(self):
return self._history.canGoForward()
def _item_at(self, i):
return self._history.itemAt(i)
def _go_to_item(self, item):
self._tab.before_load_started.emit(item.url())
self._history.goToItem(item)
class WebEngineZoom(browsertab.AbstractZoom):
"""QtWebEngine implementations related to zooming."""
@ -585,19 +598,20 @@ class WebEngineElements(browsertab.AbstractElements):
"""QtWebEngine implemementations related to elements on the page."""
def _js_cb_multiple(self, callback, js_elems):
def _js_cb_multiple(self, callback, error_cb, js_elems):
"""Handle found elements coming from JS and call the real callback.
Args:
callback: The callback to call with the found elements.
Called with None if there was an error.
error_cb: The callback to call in case of an error.
js_elems: The elements serialized from javascript.
"""
if js_elems is None:
callback(None)
error_cb(webelem.Error("Unknown error while getting "
"elements"))
return
elif not js_elems['success']:
callback(webelem.Error(js_elems['error']))
error_cb(webelem.Error(js_elems['error']))
return
elems = []
@ -624,10 +638,11 @@ class WebEngineElements(browsertab.AbstractElements):
elem = webengineelem.WebEngineElement(js_elem, tab=self._tab)
callback(elem)
def find_css(self, selector, callback, *, only_visible=False):
def find_css(self, selector, callback, error_cb, *,
only_visible=False):
js_code = javascript.assemble('webelem', 'find_css', selector,
only_visible)
js_cb = functools.partial(self._js_cb_multiple, callback)
js_cb = functools.partial(self._js_cb_multiple, callback, error_cb)
self._tab.run_js_async(js_code, js_cb)
def find_id(self, elem_id, callback):
@ -670,8 +685,9 @@ class WebEngineAudio(browsertab.AbstractAudio):
self._tab.url_changed.connect(self._on_url_changed)
config.instance.changed.connect(self._on_config_changed)
def set_muted(self, muted: bool, override: bool = False):
def set_muted(self, muted: bool, override: bool = False) -> None:
self._overridden = override
assert self._widget is not None
page = self._widget.page()
page.setAudioMuted(muted)
@ -699,8 +715,6 @@ class _WebEnginePermissions(QObject):
"""Handling of various permission-related signals."""
_abort_questions = pyqtSignal()
def __init__(self, tab, parent=None):
super().__init__(parent)
self._tab = tab
@ -720,9 +734,6 @@ class _WebEnginePermissions(QObject):
page.registerProtocolHandlerRequested.connect(
self._on_register_protocol_handler_requested)
self._tab.shutting_down.connect(self._abort_questions)
self._tab.load_started.connect(self._abort_questions)
@pyqtSlot('QWebEngineFullScreenRequest')
def _on_fullscreen_requested(self, request):
request.accept()
@ -800,7 +811,7 @@ class _WebEnginePermissions(QObject):
question = shared.feature_permission(
url=url, option=options[feature], msg=messages[feature],
yes_action=yes_action, no_action=no_action,
abort_on=[self._abort_questions])
abort_on=[self._tab.abort_questions])
if question is not None:
page.featurePermissionRequestCanceled.connect(
@ -828,7 +839,7 @@ class _WebEnginePermissions(QObject):
option='content.persistent_storage',
msg='use {} of persistent storage'.format(size),
yes_action=request.accept, no_action=request.reject,
abort_on=[self._abort_questions],
abort_on=[self._tab.abort_questions],
blocking=True)
def _on_register_protocol_handler_requested(self, request):
@ -837,7 +848,7 @@ class _WebEnginePermissions(QObject):
option='content.register_protocol_handler',
msg='open all {} links'.format(request.scheme()),
yes_action=request.accept, no_action=request.reject,
abort_on=[self._abort_questions],
abort_on=[self._tab.abort_questions],
blocking=True)
@ -911,10 +922,14 @@ class _WebEngineScripts(QObject):
utils.read_file('javascript/webelem.js'),
utils.read_file('javascript/caret.js'),
)
self._inject_early_js('js',
utils.read_file('javascript/print.js'),
subframes=True,
world=QWebEngineScript.MainWorld)
if not qtutils.version_check('5.12'):
# WORKAROUND for Qt versions < 5.12 not exposing window.print().
# Qt 5.12 has a printRequested() signal so we don't need this hack
# anymore.
self._inject_early_js('js',
utils.read_file('javascript/print.js'),
subframes=True,
world=QWebEngineScript.MainWorld)
# FIXME:qtwebengine what about subframes=True?
self._inject_early_js('js', js_code, subframes=True)
self._init_stylesheet()
@ -1023,14 +1038,42 @@ class _WebEngineScripts(QObject):
new_script.setSourceCode(script.code())
new_script.setName("GM-{}".format(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))
new_script.setInjectionPoint(QWebEngineScript.DocumentReady)
log.greasemonkey.debug('adding script: {}'
.format(new_script.name()))
page_scripts.insert(new_script)
class WebEngineTabPrivate(browsertab.AbstractTabPrivate):
"""QtWebEngine-related methods which aren't part of the public API."""
def networkaccessmanager(self):
return None
def user_agent(self):
return None
def clear_ssl_errors(self):
raise browsertab.UnsupportedOperationError
def event_target(self):
return self._widget.render_widget()
def shutdown(self):
self._tab.shutting_down.emit()
self._tab.action.exit_fullscreen()
self._widget.shutdown()
class WebEngineTab(browsertab.AbstractTab):
"""A QtWebEngine tab in the browser.
@ -1038,14 +1081,16 @@ class WebEngineTab(browsertab.AbstractTab):
Signals:
_load_finished_fake:
Used in place of unreliable loadFinished
abort_questions: Emitted when a new load started or we're shutting
down.
"""
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
_load_finished_fake = pyqtSignal(bool)
abort_questions = pyqtSignal()
def __init__(self, *, win_id, mode_manager, private, parent=None):
super().__init__(win_id=win_id, mode_manager=mode_manager,
private=private, parent=parent)
super().__init__(win_id=win_id, private=private, parent=parent)
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id,
private=private)
self.history = WebEngineHistory(tab=self)
@ -1058,6 +1103,8 @@ class WebEngineTab(browsertab.AbstractTab):
self.elements = WebEngineElements(tab=self)
self.action = WebEngineAction(tab=self)
self.audio = WebEngineAudio(tab=self, parent=self)
self.private_api = WebEngineTabPrivate(mode_manager=mode_manager,
tab=self)
self._permissions = _WebEnginePermissions(tab=self, parent=self)
self._scripts = _WebEngineScripts(tab=self, parent=self)
# We're assigning settings in _set_widget
@ -1095,21 +1142,23 @@ class WebEngineTab(browsertab.AbstractTab):
self.zoom.set_factor(self._saved_zoom)
self._saved_zoom = None
def openurl(self, url, *, predict=True):
"""Open the given URL in this tab.
def load_url(self, url, *, emit_before_load_started=True):
"""Load the given URL in this tab.
Arguments:
url: The QUrl to open.
predict: If set to False, predicted_navigation is not emitted.
url: The QUrl to load.
emit_before_load_started: If set to False, before_load_started is
not emitted.
"""
if sip.isdeleted(self._widget):
# https://github.com/qutebrowser/qutebrowser/issues/3896
return
self._saved_zoom = self.zoom.factor()
self._openurl_prepare(url, predict=predict)
self._load_url_prepare(
url, emit_before_load_started=emit_before_load_started)
self._widget.load(url)
def url(self, requested=False):
def url(self, *, requested=False):
page = self._widget.page()
if requested:
return page.requestedUrl()
@ -1139,11 +1188,6 @@ class WebEngineTab(browsertab.AbstractTab):
else:
self._widget.page().runJavaScript(code, world_id, callback)
def shutdown(self):
self.shutting_down.emit()
self.action.exit_fullscreen()
self._widget.shutdown()
def reload(self, *, force=False):
if force:
action = QWebEnginePage.ReloadAndBypassCache
@ -1168,22 +1212,6 @@ class WebEngineTab(browsertab.AbstractTab):
# percent encoded content is 2 megabytes minus 30 bytes.
self._widget.setHtml(html, base_url)
def networkaccessmanager(self):
return None
def user_agent(self):
return None
def clear_ssl_errors(self):
raise browsertab.UnsupportedOperationError
def key_press(self, key, modifier=Qt.NoModifier):
press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0)
release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier,
0, 0, 0)
self.send_event(press_evt)
self.send_event(release_evt)
def _show_error_page(self, url, error):
"""Show an error page in the tab."""
log.misc.debug("Showing error page for {}".format(error))
@ -1220,7 +1248,7 @@ class WebEngineTab(browsertab.AbstractTab):
log.misc.debug("Ignoring invalid URL being added to history")
return
self.add_history_item.emit(url, requested_url, title)
self.history_item_triggered.emit(url, requested_url, title)
@pyqtSlot(QUrl, 'QAuthenticator*', 'QString')
def _on_proxy_authentication_required(self, url, authenticator,
@ -1232,15 +1260,13 @@ class WebEngineTab(browsertab.AbstractTab):
answer = message.ask(
title="Proxy authentication required", text=msg,
mode=usertypes.PromptMode.user_pwd,
abort_on=[self.shutting_down, self.load_started], url=urlstr)
abort_on=[self.abort_questions], url=urlstr)
if answer is not None:
authenticator.setUser(answer.user)
authenticator.setPassword(answer.password)
else:
try:
# pylint: disable=no-member, useless-suppression
sip.assign(authenticator, QAuthenticator())
# pylint: enable=no-member, useless-suppression
except AttributeError:
self._show_error_page(url, "Proxy authentication required")
@ -1256,15 +1282,12 @@ class WebEngineTab(browsertab.AbstractTab):
if not netrc_success:
log.network.debug("Asking for credentials")
abort_on = [self.shutting_down, self.load_started]
answer = shared.authentication_required(url, authenticator,
abort_on)
answer = shared.authentication_required(
url, authenticator, abort_on=[self.abort_questions])
if not netrc_success and answer is None:
log.network.debug("Aborting auth")
try:
# pylint: disable=no-member, useless-suppression
sip.assign(authenticator, QAuthenticator())
# pylint: enable=no-member, useless-suppression
except AttributeError:
# WORKAROUND for
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html
@ -1348,9 +1371,9 @@ class WebEngineTab(browsertab.AbstractTab):
log.config.debug(
"Loading {} again because of config change".format(
self._reload_url.toDisplayString()))
QTimer.singleShot(100, functools.partial(self.openurl,
self._reload_url,
predict=False))
QTimer.singleShot(100, functools.partial(
self.load_url, self._reload_url,
emit_before_load_started=False))
self._reload_url = None
if not qtutils.version_check('5.10', compiled=False):
@ -1369,7 +1392,7 @@ class WebEngineTab(browsertab.AbstractTab):
if error.is_overridable():
error.ignore = shared.ignore_certificate_errors(
url, [error], abort_on=[self.shutting_down, self.load_started])
url, [error], abort_on=[self.abort_questions])
else:
log.webview.error("Non-overridable certificate error: "
"{}".format(error))
@ -1389,24 +1412,29 @@ class WebEngineTab(browsertab.AbstractTab):
self._show_error_page(url, str(error))
@pyqtSlot(QUrl)
def _on_predicted_navigation(self, url):
def _on_before_load_started(self, url):
"""If we know we're going to visit a URL soon, change the settings.
This is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
"""
super()._on_predicted_navigation(url)
super()._on_before_load_started(url)
if not qtutils.version_check('5.11.1', compiled=False):
self.settings.update_for_url(url)
@pyqtSlot()
def _on_print_requested(self):
"""Slot for window.print() in JS."""
try:
self.printing.show_dialog()
except browsertab.WebTabError as e:
message.error(str(e))
@pyqtSlot(usertypes.NavigationRequest)
def _on_navigation_request(self, navigation):
super()._on_navigation_request(navigation)
if navigation.url == QUrl('qute://print'):
try:
self.printing.show_dialog()
except browsertab.WebTabError as e:
message.error(str(e))
self._on_print_requested()
navigation.accepted = False
if not navigation.accepted or not navigation.is_main_frame:
@ -1438,6 +1466,37 @@ class WebEngineTab(browsertab.AbstractTab):
if reload_needed:
self._reload_url = navigation.url
def _on_select_client_certificate(self, selection):
"""Handle client certificates.
Currently, we simply pick the first available certificate and show an
additional note if there are multiple matches.
"""
certificate = selection.certificates()[0]
text = ('<b>Subject:</b> {subj}<br/>'
'<b>Issuer:</b> {issuer}<br/>'
'<b>Serial:</b> {serial}'.format(
subj=html_utils.escape(certificate.subjectDisplayName()),
issuer=html_utils.escape(certificate.issuerDisplayName()),
serial=bytes(certificate.serialNumber()).decode('ascii')))
if len(selection.certificates()) > 1:
text += ('<br/><br/><b>Note:</b> Multiple matching certificates '
'were found, but certificate selection is not '
'implemented yet!')
urlstr = selection.host().host()
present = message.ask(
title='Present client certificate to {}?'.format(urlstr),
text=text,
mode=usertypes.PromptMode.yesno,
abort_on=[self.abort_questions],
url=urlstr)
if present:
selection.select(certificate)
else:
selection.selectNone()
def _connect_signals(self):
view = self._widget
page = view.page()
@ -1453,6 +1512,11 @@ class WebEngineTab(browsertab.AbstractTab):
page.contentsSizeChanged.connect(self.contents_size_changed)
page.navigation_request.connect(self._on_navigation_request)
if qtutils.version_check('5.12'):
page.printRequested.connect(self._on_print_requested)
page.selectClientCertificate.connect(
self._on_select_client_certificate)
view.titleChanged.connect(self.title_changed)
view.urlChanged.connect(self._on_url_changed)
view.renderProcessTerminated.connect(
@ -1472,12 +1536,11 @@ class WebEngineTab(browsertab.AbstractTab):
page.loadFinished.connect(self._restore_zoom)
page.loadFinished.connect(self._on_load_finished)
self.predicted_navigation.connect(self._on_predicted_navigation)
self.before_load_started.connect(self._on_before_load_started)
self.shutting_down.connect(self.abort_questions)
self.load_started.connect(self.abort_questions)
# pylint: disable=protected-access
self.audio._connect_signals()
self._permissions.connect_signals()
self._scripts.connect_signals()
def event_target(self):
return self._widget.render_widget()

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -64,6 +64,8 @@ class WebEngineView(QWebEngineView):
Normally, this would always be the focusProxy().
However, it sometimes isn't, so we use this as a WORKAROUND for
https://bugreports.qt.io/browse/QTBUG-68727
This got introduced in Qt 5.11.0 and fixed in 5.12.0.
"""
if 'lost-focusproxy' not in objreg.get('args').debug_flags:
proxy = self.focusProxy()
@ -240,7 +242,7 @@ class WebEnginePage(QWebEnginePage):
def acceptNavigationRequest(self,
url: QUrl,
typ: QWebEnginePage.NavigationType,
is_main_frame: bool):
is_main_frame: bool) -> bool:
"""Override acceptNavigationRequest to forward it to the tab API."""
type_map = {
QWebEnginePage.NavigationTypeLinkClicked:

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

Some files were not shown because too many files have changed in this diff Show More