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

* upstream/master: (327 commits)
  Remove unused import
  tox: Update Werkzeug to 0.11.8
  Regenerate authors
  Use __file__ instead of sys.argv[0]
  Regenerate authors
  Make update_3rdparty.py install correctly when run from any directory
  Open command line urls explicitly.
  tox: Update Werkzeug to 0.11.6
  Move qutebrowser.rcc to misc/
  Regenerate resources
  Fix CHANGELOG/link in README
  New qutebrowser logo!
  www: Add releases link
  Release v0.6.1
  release checklist: Clarify how to build on Windows
  Make sure the cheatsheet PNG is included in sdist
  Fix cheatsheet link URL in quickstart
  Mark segfault on exit in test_smoke as xfail
  Add a xfail test for #797
  Add missing file
  ...

Conflicts:
	tests/integration/features/hints.feature
This commit is contained in:
Jakub Klinkovský 2016-04-15 22:37:47 +02:00
commit 3265601eab
142 changed files with 11373 additions and 6447 deletions

View File

@ -11,7 +11,7 @@ environment:
- TESTENV: pylint
install:
- C:\Python27\python -u scripts\dev\ci_install.py
- C:\Python27\python -u scripts\dev\ci\install.py
test_script:
- C:\Python34\Scripts\tox -e %TESTENV% -- -v --junitxml=junit.xml
- C:\Python34\Scripts\tox -e %TESTENV%

17
.editorconfig Normal file
View File

@ -0,0 +1,17 @@
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
max_line_length = 79
indent_style = space
indent_size = 4
[*.yml]
indent_size = 2
[*.feature]
max_line_length = 9999

3
.gitignore vendored
View File

@ -31,3 +31,6 @@ __pycache__
/.hypothesis
/prof
TODO
/scripts/testbrowser_cpp/Makefile
/scripts/testbrowser_cpp/main.o
/scripts/testbrowser_cpp/testbrowser

View File

@ -1,67 +1,70 @@
# So we get Ubuntu Trusty - using "dist: trusty" breaks OS X.
sudo: required
dist: trusty
os:
- linux
- osx
env:
global:
- PATH=/home/travis/bin:/home/travis/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
matrix:
- TESTENV=py35
- TESTENV=py34-cov
- TESTENV=unittests-nodisp
- TESTENV=misc
- TESTENV=vulture
- TESTENV=flake8
- TESTENV=pyroma
- TESTENV=check-manifest
- TESTENV=pylint
- TESTENV=eslint
# Not really, but this is here so we can do stuff by hand.
language: c
matrix:
include:
- os: linux
env: TESTENV=py34-cov
- os: linux
env: DOCKER=debian-jessie
services: docker
- os: linux
env: DOCKER=archlinux
services: docker
- os: linux
env: DOCKER=ubuntu-wily
services: docker
- os: osx
env: TESTENV=py35
- os: linux
env: TESTENV=pylint
- os: linux
env: TESTENV=flake8
- os: linux
env: TESTENV=docs
- 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
allow_failures:
- os: osx
env: TESTENV=py35
cache:
directories:
- $HOME/.cache/pip
- $HOME/build/The-Compiler/qutebrowser/.cache
before_install:
# We need to do this so we pick up the system-wide python properly
- 'export PATH="/usr/bin:$PATH"'
install:
- python scripts/dev/ci_install.py
- python scripts/dev/ci/install.py
script:
- tox -e $TESTENV -- -v --cov-report term tests
- bash scripts/dev/ci/travis_run.sh
after_success:
- '[[ $TESTENV == *-cov ]] && codecov -e TESTENV -X gcov'
matrix:
exclude:
- os: linux
env: TESTENV=py35
- os: osx
env: TESTENV=py34-cov
- os: osx
env: TESTENV=unittests-nodisp
- os: osx
env: TESTENV=misc
- os: osx
env: TESTENV=vulture
- os: osx
env: TESTENV=flake8
- os: osx
env: TESTENV=pyroma
- os: osx
env: TESTENV=check-manifest
- os: osx
env: TESTENV=pylint
- os: osx
env: TESTENV=eslint
notifications:
webhooks:
- https://buildtimetrend.herokuapp.com/travis
irc:
channels:
- "chat.freenode.net#qutebrowser"
on_success: change
on_failure: always
skip_join: true
template:
- "%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}"
- "%{compare_url} - %{build_url}"

View File

@ -14,12 +14,42 @@ This project adheres to http://semver.org/[Semantic Versioning].
// `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities.
v0.6.0 (unreleased)
v0.7.0 (unreleased)
-------------------
Added
~~~~~
- New `:edit-url` command to edit the URL in an external editor.
Changed
~~~~~~~
- qutebrowser got a new (slightly updated) logo
- `:tab-focus` can now take a negative index to focus the nth tab counted from
the right.
v0.6.1
-----
Fixed
~~~~~~
- Fixed broken cheatsheet image which was missing from package
- Fixed occasional crash when switching/disconnecting monitors
- Fixed crash when downloading non-ascii files with a broken locale (`LC_ALL=C`)
- Added workaround for a Qt/PyQt bug which is too weird to describe here
v0.6.0
------
Added
~~~~~
- New `:buffer` command to easily switch tabs by name. This is not bound to a
key by default for existing users due to a conflict with the `gt`/`gT`
bindings (which are now removed from the default bindings).
You can bind it by hand by running `:bind -f gt set-cmd-text -s :buffer`.
- New `--quiet` argument for the `:debug-pyeval` command to not open a tab with
the results. Note `:debug-pyeval` is still only intended for debugging.
- The completion now matches each entered word separately.
@ -28,6 +58,10 @@ Added
clipboard.
- New mode `word` for `hints -> mode` which uses a dictionary and link-texts
for hints instead of single characters.
- New `--all` argument for `:download-cancel` to cancel all running downloads.
- New `password_fill` userscript to fill passwords using the `pass` executable.
- New `current` hinting mode which forces opening hints in the current tab
(even with `target="_blank"`)
Changed
~~~~~~~
@ -37,6 +71,13 @@ Changed
- `general -> editor` can now also handle `{}` inside another argument (e.g. to open `vim` via `termite`)
- Improved performance when scrolling with many tabs open.
- Shift-Insert now also pastes primary selection for prompts.
- `:download-remove --all` got un-deprecated to provide symmetry with
`:download-cancel --all`. It does the same as `:download-clear`.
- Improved detection of URLs/search terms when pasting multiple lines.
- Don't remove `qutebrowser-editor-*` temporary file if editor subprocess crashed
- Userscripts are also searched in `/usr/share/qutebrowser/userscripts`.
- Blocked hosts are now also read from a `blocked-hosts` file in the config dir
(e.g. `~/.config/qutebrowser/blocked-hosts`).
Fixed
~~~~~
@ -51,6 +92,25 @@ Fixed
- Fixed crashes when opening an empty URL (e.g. via pasting).
- Fixed validation of duplicate values in `hints -> chars`.
- Fixed crash when PDF.js was partially installed.
- Fixed crash when XDG_DOWNLOAD_DIR was not an absolute path.
- Fixed very long filenames when downloading `data://`-URLs.
- Fixed ugly UI fonts on Windows when Liberation Mono is installed
- Fixed crash when unbinding key from a section which doesn't exist in the config
- Fixed report window after a segfault
- Fixed some directory browser issues on Windows
- Fixed crash when closing a window with a finished download and delayed
`remove-finished-downloads` setting.
- Fixed crash when hitting `<Tab>` then `<Ctrl-C>` on pages without keyboard
focus.
- Fixed "Frame load interrupted by policy change" error showing up when
downloading files with Qt 5.6.
Removed
~~~~~~~
- The `gt`/`gT` bindings (luakit-like alternatives to `J`/`K`) were removed
(except for existing configs) to make room for the `gt` binding to show
buffers.
v0.5.1
------

View File

@ -95,7 +95,7 @@ Currently, following tox environments are available:
- `py34`: Run pytest for python-3.4.
- `py35`: Run pytest for python-3.5.
- `py34-cov`: Run pytest for python-3.4 with code coverage report.
- `py35-cov`: Run pytest for python-3.4 with code coverage report.
- `py35-cov`: Run pytest for python-3.5 with code coverage report.
* `flake8`: Run https://pypi.python.org/pypi/flake8[flake8] checks:
https://pypi.python.org/pypi/pyflakes[pyflakes],
https://pypi.python.org/pypi/pep8[pep8],
@ -163,6 +163,9 @@ tox -e py35 -- tests/integration/features
# run everything with undo in the generated name, based on the scenario text
tox -e py35 -- tests/integration/features/test_tabs.py -k undo
# run coverage test for specific file (updates htmlcov/index.html)
tox -e py35-cov -- tests/unit/browser/test_webelem.py
----
Profiling
@ -603,6 +606,11 @@ qutebrowser release
* Make sure there are no unstaged changes and the tests are green.
* Add newest config to `tests/unit/config/old_configs` and update `test_upgrade_version`
- `python -m qutebrowser --basedir conf :quit`
- `sed '/^#/d' conf/config/qutebrowser.conf > tests/unit/config/old_configs/qutebrowser-v0.x.y.conf`
- `rm -r conf`
- commit
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
* Remove *(unreleased)* from changelog.
* Run tests again
@ -610,7 +618,6 @@ qutebrowser release
* Commit
* Create annotated git tag (`git tag -s "v0.X.Y" -m "Release v0.X.Y"`)
* If it's a new minor, create git branch `v0.X.x`
* If committing on minor branch, cherry-pick release commit to master.
* `git push origin`; `git push origin v0.X.Y`
* Create release on github
@ -619,12 +626,15 @@ as closed.
* Run `scripts/dev/build_release.py` on Linux to build an sdist
* Upload to PyPI: `twine upload dist/foo{,.asc}`
* Create Windows packages via `scripts/dev/build_release.py` and upload.
* Create Windows packages via `C:\Python34_x32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py`
* Upload to github
* Upload to qutebrowser.org with checksum/GPG
- On server: `sudo mkdir -p /srv/http/qutebrowser/releases/v0.X.Y/windows`
- `rsync -avPh dist/ tonks:`
- On server: `sudo mv qutebrowser-0.X.Y.tar.gz* /srv/http/qutebrowser/releases/v0.X.Y`
* Update AUR package
- Upload windows release:
- `scp bb-win8:proj/qutebrowser/qutebrowser-0.X.Y-windows.zip .`
- `aunpack qutebrowser-0.X.Y-windows.zip`
- `sudo mv qutebrowser-0.X.Y-windows/* /srv/http/qutebrowser/releases/v0.6.0/windows`
* Announce to qutebrowser mailinglist

View File

@ -61,34 +61,23 @@ repository (rather than a release):
$ python3 scripts/asciidoc2html.py
----
If video or sound don't seem to work, try installing the gstreamer plugins:
----
# apt-get install gstreamer1.0-plugins-{bad,base,good,ugly}
----
Then <<tox,install qutebrowser via tox>>.
On Fedora
---------
qutebrowser should run on Fedora 22.
Unfortunately there is no Fedora package yet, but installing qutebrowser is
still relatively easy! If you want to help packaging it for Fedora, please
mailto:mail@qutebrowser.org[get in touch]!
Install the dependencies via dnf:
qutebrowser is available in the official repositories for Fedora 22 and newer.
----
# dnf update
# dnf install python3-qt5 python-tox python3-sip
# dnf install qutebrowser
----
To generate the documentation for the `:help` command, when using the git
repository (rather than a release):
----
# dnf install asciidoc source-highlight
$ python3 scripts/asciidoc2html.py
----
Then <<tox,install qutebrowser via tox>>.
On Archlinux
------------
@ -113,6 +102,12 @@ $ rm -r qutebrowser-git
or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`.
If video or sound don't seem to work, try installing the gstreamer plugins:
----
# pacman -S gst-plugins-{base,good,bad,ugly} gst-libav
----
On Gentoo
---------
@ -146,6 +141,21 @@ it with:
$ nix-env -i qutebrowser
----
On openSUSE
-----------
There are prebuilt RPMs available for Tumbleweed and Leap 42.1:
http://software.opensuse.org/download.html?project=home%3Aarpraher&package=qutebrowser[One Click Install]
Or add the repo manually:
----
# zypper addrepo http://download.opensuse.org/repositories/home:arpraher/openSUSE_Tumbleweed/home:arpraher.repo
# zypper refresh
# zypper install qutebrowser
----
On Windows
----------
@ -191,16 +201,24 @@ On OS X
-------
To install qutebrowser on OS X, you'll want a package manager, e.g.
http://brew.sh/[Homebrew] or https://www.macports.org/[MacPorts]. Also make
sure, you have https://itunes.apple.com/en/app/xcode/id497799835[XCode]
installed to compile PyQt5 in a later step.
http://brew.sh/[Homebrew] or https://www.macports.org/[MacPorts].
For Homebrew, a few extra steps are necessary since Homebrew dropped QtWebKit
from Qt 5.6.
This installs a Qt 5.5 and symlinks it so PyQt5 will work with it instead of Qt
5.6. This requires that `qt5` is not installed via Homebrew:
----
$ brew install python3 pyqt5
$ brew install python3 d-bus mysql sip xz
$ brew install homebrew/versions/qt55
$ brew install --ignore-dependencies pyqt5
$ ln -s /usr/local/opt/qt55 /usr/local/opt/qt5
$ pip3.5 install qutebrowser
----
if you are using Homebrew. For MacPorts, run:
For MacPorts, run:
----
$ sudo port install python34 py34-jinja2 asciidoc py34-pygments py34-pyqt5
@ -225,7 +243,16 @@ it as part of the packaging process.
Installing qutebrowser with tox
-------------------------------
Run tox inside the qutebrowser repository to set up a
First of all, clone the repository using http://git-scm.org/[git] and switch
into the repository folder:
----
$ git clone https://github.com/The-Compiler/qutebrowser.git
$ cd qutebrowser
----
Then run tox inside the qutebrowser repository to set up a
https://docs.python.org/3/library/venv.html[virtual environment]:
----
@ -243,6 +270,14 @@ your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser "$@"
----
If you are developing on qutebrowser, you may want to redirect it to a local
config:
----
#!/bin/bash
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser -c .qutebrowser-local "$@"
----
Updating
~~~~~~~~

View File

@ -1,8 +1,8 @@
recursive-include qutebrowser *.py
recursive-include qutebrowser/html *.html
recursive-include qutebrowser/img *.svg *.png
recursive-include qutebrowser/test *.py
recursive-include qutebrowser/javascript *.js
graft qutebrowser/html
graft qutebrowser/3rdparty
graft icons
graft doc/img
@ -18,12 +18,14 @@ include qutebrowser.py
prune www
prune scripts/dev
prune scripts/testbrowser_cpp
exclude scripts/asciidoc2html.py
exclude doc/notes
recursive-exclude doc *.asciidoc
include doc/qutebrowser.1.asciidoc
prune tests
prune qutebrowser/3rdparty
exclude .editorconfig
exclude pytest.ini
exclude qutebrowser.rcc
exclude .coveragerc

View File

@ -22,6 +22,12 @@ on Python, PyQt5 and QtWebKit and free software, licensed under the GPL.
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
// QUTE_WEB_HIDE
**qutebrowser is currently running a crowdfunding campaign to add support for
the QtWebEngine backend, which fixes many issues. Please
link:http://igg.me/at/qutebrowser[check it out]!**
// QUTE_WEB_HIDE_END
Screenshots
-----------
@ -150,22 +156,26 @@ Contributors, sorted by the number of commits in descending order:
* Felix Van der Jeugt
* Raphael Pierzina
* Joel Torstensson
* Claude
* Patric Schmitz
* Claude
* meles5
* Tarcisio Fedrizzi
* Artur Shaik
* Nathan Isom
* Austin Anderson
* Thorsten Wißmann
* Philipp Hansch
* Kevin Velghe
* Austin Anderson
* Alexey "Averrin" Nabrodov
* avk
* ZDarian
* Milan Svoboda
* John ShaggyTwoDope Jenkins
* Jimmy
* Peter Vilim
* Tarcisio Fedrizzi
* Clayton Craft
* Oliver Caldwell
* Jonas Schürmann
* Jimmy
* Panagiotis Ktistakis
* Jakub Klinkovský
* skinnay
@ -173,6 +183,7 @@ Contributors, sorted by the number of commits in descending order:
* Zach-Button
* Halfwit
* rikn00
* Ryan Roden-Corrent
* Michael Ilsaas
* Martin Zimmermann
* Brian Jackson
@ -184,17 +195,23 @@ Contributors, sorted by the number of commits in descending order:
* Link
* Larry Hynes
* Johannes Altmanninger
* avk
* Samir Benmendil
* Regina Hug
* Mathias Fussenegger
* Marcelo Santos
* Fritz V155 Reichwald
* Franz Fellner
* Corentin Jule
* zwarag
* xd1le
* issue
* haxwithaxe
* evan
* dylan araps
* Xitian9
* Tomasz Kramkowski
* Tomas Orsava
* Tobias Werth
* Tim Harder
* Thiago Barroso Perrotta
* Stefan Tatschner
@ -202,7 +219,10 @@ Contributors, sorted by the number of commits in descending order:
* Samuel Loury
* Matthias Lisin
* Marcel Schilling
* Johannes Martinsson
* Jean-Christophe Petkovich
* Jay Kamat
* Jan Verbeek
* Helen Sherwood-Taylor
* HalosGhost
* Gregor Pohl
@ -215,7 +235,8 @@ Contributors, sorted by the number of commits in descending order:
The following people have contributed graphics:
* WOFall (icon)
* Jad/http://yelostudio.com[yelo] (new icon)
* WOFall (original icon)
* regines (key binding cheatsheet)
Thanks / Similar projects

View File

@ -11,6 +11,7 @@
|<<bookmark-add,bookmark-add>>|Save the current page as a bookmark.
|<<bookmark-del,bookmark-del>>|Delete a bookmark.
|<<bookmark-load,bookmark-load>>|Load a bookmark.
|<<buffer,buffer>>|Select tab by index or url/title best match.
|<<close,close>>|Close the current window.
|<<download,download>>|Download a given URL, or current page if no URL given.
|<<download-cancel,download-cancel>>|Cancel the last/[count]th download.
@ -19,6 +20,7 @@
|<<download-open,download-open>>|Open the last/[count]th download.
|<<download-remove,download-remove>>|Remove the last/[count]th download from the list.
|<<download-retry,download-retry>>|Retry the first failed/[count]th download.
|<<edit-url,edit-url>>|Navigate to a url formed in an external editor.
|<<fake-key,fake-key>>|Send a fake keypress or key string to the website or qutebrowser.
|<<forward,forward>>|Go forward in the history of the current tab.
|<<fullscreen,fullscreen>>|Toggle fullscreen mode.
@ -72,6 +74,8 @@
=== adblock-update
Update the adblock block lists.
This updates ~/.local/share/qutebrowser/blocked-hosts with downloaded host lists and re-reads ~/.config/qutebrowser/blocked-hosts.
[[back]]
=== back
Syntax: +:back [*--tab*] [*--bg*] [*--window*]+
@ -140,6 +144,18 @@ Load a bookmark.
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
[[buffer]]
=== buffer
Syntax: +:buffer 'index'+
Select tab by index or url/title best match.
Focuses window if necessary.
==== positional arguments
* +'index'+: The [win_id/]index of the tab to focus. Or a substring in which case the closest match will be focused.
[[close]]
=== close
Close the current window.
@ -161,8 +177,13 @@ The form `:download [url] [dest]` is deprecated, use `:download --dest [dest] [u
[[download-cancel]]
=== download-cancel
Syntax: +:download-cancel [*--all*]+
Cancel the last/[count]th download.
==== optional arguments
* +*-a*+, +*--all*+: Cancel all running downloads
==== count
The index of the download to cancel.
@ -175,14 +196,14 @@ Remove all finished downloads from the list.
Delete the last/[count]th download from disk.
==== count
The index of the download to cancel.
The index of the download to delete.
[[download-open]]
=== download-open
Open the last/[count]th download.
==== count
The index of the download to cancel.
The index of the download to open.
[[download-remove]]
=== download-remove
@ -191,17 +212,36 @@ Syntax: +:download-remove [*--all*]+
Remove the last/[count]th download from the list.
==== optional arguments
* +*-a*+, +*--all*+: Deprecated argument for removing all finished downloads.
* +*-a*+, +*--all*+: Remove all finished downloads.
==== count
The index of the download to cancel.
The index of the download to remove.
[[download-retry]]
=== download-retry
Retry the first failed/[count]th download.
==== count
The index of the download to cancel.
The index of the download to retry.
[[edit-url]]
=== edit-url
Syntax: +:edit-url [*--bg*] [*--tab*] [*--window*] ['url']+
Navigate to a url formed in an external editor.
The editor which should be launched can be configured via the `general -> editor` config option.
==== positional arguments
* +'url'+: URL to edit; defaults to the current page url.
==== optional arguments
* +*-b*+, +*--bg*+: Open in a new background tab.
* +*-t*+, +*--tab*+: Open in a new tab.
* +*-w*+, +*--window*+: Open in a new window.
==== count
The tab index to open the URL in.
[[fake-key]]
=== fake-key
@ -270,7 +310,8 @@ Start hinting.
* +'target'+: What to do with the selected element.
- `normal`: Open the link in the current tab.
- `normal`: Open the link.
- `current`: Open the link in the current tab.
- `tab`: Open the link in a new tab (honoring the
background-tabs setting).
- `tab-fg`: Open the link in a new foreground tab.
@ -620,8 +661,11 @@ Note the {url} variable which gets replaced by the current URL might be useful h
* +'cmdline'+: The commandline to execute.
==== optional arguments
* +*-u*+, +*--userscript*+: Run the command as a userscript. Either store the userscript in `~/.local/share/qutebrowser/userscripts`
(or `$XDG_DATA_DIR`), or use an absolute path.
* +*-u*+, +*--userscript*+: Run the command as a userscript. You can use an absolute path, or store the userscript in one of those
locations:
- `~/.local/share/qutebrowser/userscripts`
(or `$XDG_DATA_DIR`)
- `/usr/share/qutebrowser/userscripts`
* +*-v*+, +*--verbose*+: Show notifications when the command started/exited.
* +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser.
@ -675,7 +719,8 @@ Select the tab given as argument/[count].
If neither count nor index are given, it behaves like tab-next.
==== positional arguments
* +'index'+: The tab index to focus, starting with 1. The special value `last` focuses the last focused tab.
* +'index'+: The tab index to focus, starting with 1. The special value `last` focuses the last focused tab. Negative indexes
counts from the end, such that -1 is the last tab.
==== count

View File

@ -180,7 +180,7 @@
|<<hints-scatter,scatter>>|Whether to scatter hint key chains (like Vimium) or not (like dwb).
|<<hints-uppercase,uppercase>>|Make chars in hint strings uppercase.
|<<hints-dictionary,dictionary>>|The dictionary file to be used by the word hints.
|<<hints-auto-follow,auto-follow>>|Whether to auto-follow a hint if there's only one left.
|<<hints-auto-follow,auto-follow>>|Follow a hint immediately when the hint text is completely matched.
|<<hints-next-regexes,next-regexes>>|A comma-separated list of regexes to use for 'next' links.
|<<hints-prev-regexes,prev-regexes>>|A comma-separated list of regexes to use for 'prev' links.
|==============
@ -1585,7 +1585,7 @@ Default: +pass:[/usr/share/dict/words]+
[[hints-auto-follow]]
=== auto-follow
Whether to auto-follow a hint if there's only one left.
Follow a hint immediately when the hint text is completely matched.
Valid values:
@ -2041,7 +2041,7 @@ Fonts used for the UI, with optional style/weight/size.
=== _monospace
Default monospace fonts.
Default: +pass:[Terminus, Monospace, &quot;DejaVu Sans Mono&quot;, Monaco, &quot;Bitstream Vera Sans Mono&quot;, &quot;Andale Mono&quot;, &quot;Liberation Mono&quot;, &quot;Courier New&quot;, Courier, monospace, Fixed, Consolas, Terminal]+
Default: +pass:[Terminus, Monospace, &quot;DejaVu Sans Mono&quot;, Monaco, &quot;Bitstream Vera Sans Mono&quot;, &quot;Andale Mono&quot;, &quot;Courier New&quot;, Courier, &quot;Liberation Mono&quot;, monospace, Fixed, Consolas, Terminal]+
[[fonts-completion]]
=== completion

BIN
doc/img/cheatsheet-big.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -8,7 +8,7 @@ time, use the `:help` command.
What to do now
--------------
* View the http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]
* View the link:http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]
to make yourself familiar with the key bindings: +
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
* Run `:adblock-update` to download adblock lists and activate adblocking.

View File

@ -66,7 +66,7 @@ show it.
How URLs should be opened if there is already a qutebrowser instance running.
=== debug arguments
*-l* 'LOGLEVEL', *--loglevel* 'LOGLEVEL'::
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
Set loglevel
*--logfilter* 'LOGFILTER'::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 872 B

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

2183
icons/qutebrowser-all.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 128 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -13,7 +13,7 @@
height="640"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.48.5 r10040"
inkscape:version="0.91 r13725"
version="1.0"
sodipodi:docname="cheatsheet.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
@ -32,18 +32,18 @@
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.8791156"
inkscape:cx="768.67127"
inkscape:cy="133.80749"
inkscape:zoom="1.7582312"
inkscape:cx="875.18895"
inkscape:cy="136.8726"
inkscape:document-units="px"
inkscape:current-layer="layer1"
width="1024px"
height="640px"
showgrid="false"
inkscape:window-width="636"
inkscape:window-height="536"
inkscape:window-x="2560"
inkscape:window-y="0"
inkscape:window-width="1362"
inkscape:window-height="740"
inkscape:window-x="0"
inkscape:window-y="24"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-maximized="0"
@ -3040,17 +3040,13 @@
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
id="flowSpan3852">(10)</flowSpan> misc. commands:</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3725-0"><flowSpan
style="fill:#0000ff"
id="flowSpan5471">gm</flowSpan> - move tab</flowPara><flowPara
id="flowPara3725-0">gt - switch tabs by name</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3854"><flowSpan
id="flowPara4052"><flowSpan
style="fill:#0000ff"
id="flowSpan5473">gl</flowSpan> - move tab to left</flowPara><flowPara
id="flowSpan4054">gm/gl/lr</flowSpan> - move tab</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3856"><flowSpan
style="fill:#0000ff"
id="flowSpan5475">gr</flowSpan> - move tab to right</flowPara><flowPara
id="flowPara4056"> (to index/left/right)</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3858">gC - clone tab</flowPara><flowPara
style="font-size:10px;fill:#000000"

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

View File

@ -0,0 +1,35 @@
FROM base/archlinux
MAINTAINER Florian Bruhin <me@the-compiler.org>
RUN echo 'Server = http://mirror.de.leaseweb.net/archlinux/$repo/os/$arch' > /etc/pacman.d/mirrorlist
RUN pacman-key --init && pacman-key --populate archlinux && pacman -Sy --noconfirm archlinux-keyring
RUN pacman -Suyy --noconfirm
RUN pacman-db-upgrade
RUN pacman -S --noconfirm \
git \
python-tox \
qt5-base \
qt5-webkit \
python-pyqt5 \
xorg-xinit \
herbstluftwm \
xorg-server-xvfb
RUN echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen && locale-gen
RUN useradd user && mkdir /home/user && chown -R user:users /home/user
USER user
WORKDIR /home/user
ENV DISPLAY=:0
ENV LC_ALL=en_US.UTF-8
ENV LANG=en_US.UTF-8
CMD Xvfb -screen 0 800x600x24 :0 & \
sleep 2 && \
herbstluftwm & \
git clone /outside qutebrowser.git && \
cd qutebrowser.git && \
tox -e py35

View File

@ -0,0 +1,35 @@
FROM debian:jessie
MAINTAINER Florian Bruhin <me@the-compiler.org>
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -y update && \
apt-get -y dist-upgrade && \
apt-get -y install --no-install-recommends \
python3-pyqt5 \
python3-pyqt5.qtwebkit \
python-tox \
python3-sip \
xvfb \
git \
python3-setuptools \
wget \
herbstluftwm \
locales \
libjs-pdf
RUN echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen && locale-gen
RUN useradd user && mkdir /home/user && chown -R user:users /home/user
USER user
WORKDIR /home/user
ENV DISPLAY=:0
ENV LC_ALL=en_US.UTF-8
ENV LANG=en_US.UTF-8
CMD Xvfb -screen 0 800x600x24 :0 & \
sleep 2 && \
herbstluftwm & \
git clone /outside qutebrowser.git && \
cd qutebrowser.git && \
tox -e py34

View File

@ -0,0 +1,34 @@
FROM ubuntu:wily
MAINTAINER Florian Bruhin <me@the-compiler.org>
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -y update && \
apt-get -y dist-upgrade && \
apt-get -y install --no-install-recommends \
python3-pyqt5 \
python3-pyqt5.qtwebkit \
python-tox \
python3-sip \
xvfb \
git \
python3-setuptools \
wget \
herbstluftwm \
language-pack-en \
libjs-pdf
RUN useradd user && mkdir /home/user && chown -R user:users /home/user
USER user
WORKDIR /home/user
ENV DISPLAY=:0
ENV LC_ALL=en_US.UTF-8
ENV LANG=en_US.UTF-8
CMD Xvfb -screen 0 800x600x24 :0 & \
sleep 2 && \
herbstluftwm & \
git clone /outside qutebrowser.git && \
cd qutebrowser.git && \
tox -e py34

View File

@ -30,9 +30,10 @@ import os
import re
from bs4 import BeautifulSoup
from urllib.parse import urljoin
with open(os.environ['QUTE_HTML'], 'r') as f:
soup = BeautifulSoup(f)
with open(os.environ['QUTE_FIFO'], 'w') as f:
for link in soup.find_all('link', rel='alternate', type=re.compile(r'application/((rss|rdf|atom)\+)?xml|text/xml')):
f.write('open -t %s\n' % link.get('href'))
f.write('open -t %s\n' % urljoin(os.environ['QUTE_URL'], link.get('href')))

364
misc/userscripts/password_fill Executable file
View File

@ -0,0 +1,364 @@
#!/bin/bash -e
help() {
blink=$'\e[1;31m' reset=$'\e[0m'
cat <<EOF
This script can only be used as a userscript for qutebrowser
2015, Thorsten Wißmann <edu _at_ thorsten-wissmann _dot_ de>
In case of questions or suggestions, do not hesitate to send me an E-Mail or to
directly ask me via IRC (nickname thorsten\`) in #qutebrowser on freenode.
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
WARNING: the passwords are stored in qutebrowser's
debug log reachable via the url qute:log
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
Usage: run as a userscript form qutebrowser, e.g.:
spawn --userscript ~/.config/qutebrowser/password_fill
Pass backend: (see also passwordstore.org)
This script expects pass to store the credentials of each page in an extra
file, where the filename (or filepath) contains the domain of the respective
page. The first line of the file must contain the password, the login name
must be contained in a later line beginning with "user:", "login:", or
"username:" (configurable by the user_pattern variable).
Behaviour:
It will try to find a username/password entry in the configured backend
(currently only pass) for the current website and will load that pair of
username and password to any form on the current page that has some password
entry field. If multiple entries are found, a zenity menu is offered.
If no entry is found, then it crops subdomains from the url if at least one
entry is found in the backend. (In that case, it always shows a menu)
Configuration:
This script loads the bash script ~/.config/qutebrowser/password_fill_rc (if
it exists), so you can change any configuration variable and overwrite any
function you like.
EOF
}
set -o pipefail
shopt -s nocasematch # make regexp matching in bash case insensitive
if [ -z "$QUTE_FIFO" ] ; then
help
exit
fi
error() {
local msg="$*"
echo "message-error '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
}
msg() {
local msg="$*"
echo "message-info '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
}
die() {
error "$*"
exit 0
}
javascript_escape() {
# print the first argument in a escaped way, such that it can savely
# be used within javascripts double quotes
sed "s,[\\\'\"],\\\&,g" <<< "$1"
}
# ======================================================= #
# CONFIGURATION
# ======================================================= #
# The configuration file is per default located in
# ~/.config/qutebrowser/password_fill_rc and is a bash script that is loaded
# later in the present script. So basically you can replace all of the
# following definitions and make them fit your needs.
# The following simplifies a URL to the domain (e.g. "wiki.qutebrowser.org")
# which is later used to search the correct entries in the password backend. If
# you e.g. don't want the "www." to be removed or if you want to distinguish
# between different paths on the same domain.
simplify_url() {
simple_url="${1##*://}" # remove protocoll specification
simple_url="${simple_url%%\?*}" # remove GET parameters
simple_url="${simple_url%%/*}" # remove directory path
simple_url="${simple_url%:*}" # remove port
simple_url="${simple_url##www.}" # remove www. subdomain
}
# no_entries_found() is called if the first query_entries() call did not find
# any matching entries. Multiple implementations are possible:
# The easiest behaviour is to quit:
#no_entries_found() {
# if [ 0 -eq "${#files[@]}" ] ; then
# die "No entry found for »$simple_url«"
# fi
#}
# But you could also fill the files array with all entries from your pass db
# if the first db query did not find anything
# no_entries_found() {
# if [ 0 -eq "${#files[@]}" ] ; then
# query_entries ""
# if [ 0 -eq "${#files[@]}" ] ; then
# die "No entry found for »$simple_url«"
# fi
# fi
# }
# Another beahviour is to drop another level of subdomains until search hits
# are found:
no_entries_found() {
while [ 0 -eq "${#files[@]}" ] && [ -n "$simple_url" ]; do
shorter_simple_url=$(sed 's,^[^.]*\.,,' <<< "$simple_url")
if [ "$shorter_simple_url" = "$simple_url" ] ; then
# if no dot, then even remove the top level domain
simple_url=""
query_entries "$simple_url"
break
fi
simple_url="$shorter_simple_url"
query_entries "$simple_url"
#die "No entry found for »$simple_url«"
# enforce menu if we do "fuzzy" matching
menu_if_one_entry=1
done
if [ 0 -eq "${#files[@]}" ] ; then
die "No entry found for »$simple_url«"
fi
}
# Backend implementations tell, how the actual password store is accessed.
# Right now, there is only one fully functional password backend, namely for
# the program "pass".
# A password backend consists of three actions:
# - init() initializes backend-specific things and does sanity checks.
# - query_entries() is called with a simplified url and is expected to fill
# the bash array $files with the names of matching password entries. There
# are no requirements how these names should look like.
# - open_entry() is called with some specific entry of the $files array and is
# expected to write the username of that entry to the $username variable and
# the corresponding password to $password
reset_backend() {
init() { true ; }
query_entries() { true ; }
open_entry() { true ; }
}
# choose_entry() is expected to choose one entry from the array $files and
# write it to the variable $file.
choose_entry() {
choose_entry_zenity
}
# The default implementation chooses a random entry from the array. So if there
# are multiple matching entries, multiple calls to this userscript will
# eventually pick the "correct" entry. I.e. if this userscript is bound to
# "zl", the user has to press "zl" until the correct username shows up in the
# login form.
choose_entry_random() {
local nr=${#files[@]}
file="${files[$((RANDOM % nr))]}"
# Warn user, that there might be other matching password entries
if [ "$nr" -gt 1 ] ; then
msg "Picked $file out of $nr entries: ${files[*]}"
fi
}
# another implementation would be to ask the user via some menu (like rofi or
# dmenu or zenity or even qutebrowser completion in future?) which entry to
# pick
MENU_COMMAND=( head -n 1 )
# whether to show the menu if there is only one entrie in it
menu_if_one_entry=0
choose_entry_menu() {
local nr=${#files[@]}
if [ "$nr" -eq 1 ] && ! ((menu_if_one_entry)) ; then
file="${files[0]}"
else
file=$( printf "%s\n" "${files[@]}" | "${MENU_COMMAND[@]}" )
fi
}
choose_entry_rofi() {
MENU_COMMAND=( rofi -p "qutebrowser> " -dmenu
-mesg $'Pick a password entry for <b>'"${QUTE_URL//&/&amp;}"'</b>' )
choose_entry_menu || true
}
choose_entry_zenity() {
MENU_COMMAND=( zenity --list --title "Qutebrowser password fill"
--text "Pick the password entry:"
--column "Name" )
choose_entry_menu || true
}
choose_entry_zenity_radio() {
zenity_helper() {
awk '{ print $0 ; print $0 }' \
| zenity --list --radiolist \
--title "Qutebrowser password fill" \
--text "Pick the password entry:" \
--column " " --column "Name"
}
MENU_COMMAND=( zenity_helper )
choose_entry_menu || true
}
# =======================================================
# backend: PASS
# configuration options:
match_filename=1 # whether allowing entry match by filepath
match_line=0 # whether allowing entry match by URL-Pattern in file
# Note: match_line=1 gets very slow, even for small password stores!
match_line_pattern='^url: .*' # applied using grep -iE
user_pattern='^(user|username|login): '
GPG_OPTS=( "--quiet" "--yes" "--compress-algo=none" "--no-encrypt-to" )
GPG="gpg"
export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}"
which gpg2 &>/dev/null && GPG="gpg2"
[[ -n $GPG_AGENT_INFO || $GPG == "gpg2" ]] && GPG_OPTS+=( "--batch" "--use-agent" )
pass_backend() {
init() {
PREFIX="${PASSWORD_STORE_DIR:-$HOME/.password-store}"
if ! [ -d "$PREFIX" ] ; then
die "Can not open password store dir »$PREFIX«"
fi
}
query_entries() {
local url="$1"
if ((match_line)) ; then
# add entries with matching URL-tag
while read -r -d "" passfile ; do
if $GPG "${GPG_OPTS}" -d "$passfile" \
| grep --max-count=1 -iE "${match_line_pattern}${url}" > /dev/null
then
passfile="${passfile#$PREFIX}"
passfile="${passfile#/}"
files+=( "${passfile%.gpg}" )
fi
done < <(find -L "$PREFIX" -iname '*.gpg' -print0)
fi
if ((match_filename)) ; then
# add entries wth matching filepath
while read -r passfile ; do
passfile="${passfile#$PREFIX}"
passfile="${passfile#/}"
files+=( "${passfile%.gpg}" )
done < <(find -L "$PREFIX" -iname '*.gpg' | grep "$url")
fi
}
open_entry() {
local path="$PREFIX/${1}.gpg"
password=""
local firstline=1
while read -r line ; do
if ((firstline)) ; then
password="$line"
firstline=0
else
if [[ $line =~ $user_pattern ]] ; then
# remove the matching prefix "user: " from the beginning of the line
username=${line#${BASH_REMATCH[0]}}
break
fi
fi
done < <($GPG "${GPG_OPTS}" -d "$path" )
}
}
# =======================================================
# =======================================================
# backend: secret
secret_backend() {
init() {
return
}
query_entries() {
local domain="$1"
while read -r line ; do
if [[ "$line" =~ "attribute.username = " ]] ; then
files+=("$domain ${line#${BASH_REMATCH[0]}}")
fi
done < <( secret-tool search --unlock --all domain "$domain" 2>&1 )
}
open_entry() {
local domain="${1%% *}"
username="${1#* }"
password=$(secret-tool lookup domain "$domain" username "$username")
}
}
# =======================================================
# load some sane default backend
reset_backend
pass_backend
# load configuration
QUTE_CONFIG_DIR=${QUTE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/qutebrowser/}
PWFILL_CONFIG=${PWFILL_CONFIG:-${QUTE_CONFIG_DIR}/password_fill_rc}
if [ -f "$PWFILL_CONFIG" ] ; then
source "$PWFILL_CONFIG"
fi
init
simplify_url "$QUTE_URL"
query_entries "${simple_url}"
no_entries_found
# remove duplicates
mapfile -t files < <(printf "%s\n" "${files[@]}" | sort | uniq )
choose_entry
if [ -z "$file" ] ; then
# choose_entry didn't want any of these entries
exit 0
fi
open_entry "$file"
#username="$(date)"
#password="XYZ"
#msg "$username, ${#password}"
[ -n "$username" ] || die "Username not set in entry $file"
[ -n "$password" ] || die "Password not set in entry $file"
js() {
cat <<EOF
function hasPasswordField(form) {
var inputs = form.getElementsByTagName("input");
for (var j = 0; j < inputs.length; j++) {
var input = inputs[j];
if (input.type == "password") {
return true;
}
}
return false;
};
function loadData2Form (form) {
var inputs = form.getElementsByTagName("input");
for (var j = 0; j < inputs.length; j++) {
var input = inputs[j];
if (input.type == "text" || input.type == "email") {
input.value = "$(javascript_escape "${username}")";
}
if (input.type == "password") {
input.value = "$(javascript_escape "${password}")";
}
}
};
var forms = document.getElementsByTagName("form");
for (i = 0; i < forms.length; i++) {
if (hasPasswordField(forms[i])) {
loadData2Form(forms[i]);
}
}
EOF
}
printjs() {
js | sed 's,//.*$,,' | tr '\n' ' '
}
echo "jseval -q $(printjs)" >> "$QUTE_FIFO"

View File

@ -1,6 +1,6 @@
[pytest]
norecursedirs = .tox .venv
addopts = --strict -rfEsw --faulthandler-timeout=70 --instafail
addopts = --strict -rfEw --faulthandler-timeout=70 --instafail
markers =
gui: Tests using the GUI (e.g. spawning widgets)
posix: Tests which only can run on a POSIX OS.
@ -15,8 +15,8 @@ markers =
skip: Always skipped test.
pyqt531_or_newer: Needs PyQt 5.3.1 or newer.
xfail_norun: xfail the test with out running it
flaky: Tests which are flaky and should be rerun
ci: Tests which should only run on CI.
flaky: Tests which are flaky and should be rerun
qt_log_level_fail = WARNING
qt_log_ignore =
^SpellCheck: .*
@ -38,3 +38,4 @@ qt_log_ignore =
^QObject::connect: Cannot connect \(null\)::stateChanged\(QNetworkSession::State\) to QNetworkReplyHttpImpl::_q_networkSessionStateChanged\(QNetworkSession::State\)
^QXcbClipboard: Cannot transfer data, no data available
qt_wait_signal_raising = true
xfail_strict = true

View File

@ -28,7 +28,7 @@ __copyright__ = "Copyright 2014-2016 Florian Bruhin (The Compiler)"
__license__ = "GPL"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
__version_info__ = (0, 5, 1)
__version_info__ = (0, 6, 1)
__version__ = '.'.join(str(e) for e in __version_info__)
__description__ = "A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit."

View File

@ -35,7 +35,7 @@ from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QCursor, QWindow
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
QObject, Qt, QEvent)
QObject, Qt, QEvent, pyqtSignal)
try:
import hunter
except ImportError:
@ -282,7 +282,8 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
"{}".format(cmd, e))
else:
background = open_target in ('tab-bg', 'tab-bg-silent')
tabbed_browser.tabopen(url, background=background)
tabbed_browser.tabopen(url, background=background,
explicit=True)
def _open_startpage(win_id=None):
@ -742,6 +743,8 @@ class Application(QApplication):
_args: ArgumentParser instance.
"""
new_window = pyqtSignal(mainwindow.MainWindow)
def __init__(self, args):
"""Constructor.

View File

@ -92,9 +92,11 @@ class HostBlocker:
Attributes:
_blocked_hosts: A set of blocked hosts.
_config_blocked_hosts: A set of blocked hosts from ~/.config.
_in_progress: The DownloadItems which are currently downloading.
_done_count: How many files have been read successfully.
_hosts_file: The path to the blocked-hosts file.
_local_hosts_file: The path to the blocked-hosts file.
_config_hosts_file: The path to a blocked-hosts in ~/.config
Class attributes:
WHITELISTED: Hosts which never should be blocked.
@ -105,13 +107,22 @@ class HostBlocker:
def __init__(self):
self._blocked_hosts = set()
self._config_blocked_hosts = set()
self._in_progress = []
self._done_count = 0
data_dir = standarddir.data()
if data_dir is None:
self._hosts_file = None
self._local_hosts_file = None
else:
self._hosts_file = os.path.join(data_dir, 'blocked-hosts')
self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts')
config_dir = standarddir.config()
if config_dir is None:
self._config_hosts_file = None
else:
self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts')
objreg.get('config').changed.connect(self.on_config_changed)
def is_blocked(self, url):
@ -119,21 +130,46 @@ class HostBlocker:
if not config.get('content', 'host-blocking-enabled'):
return False
host = url.host()
return host in self._blocked_hosts and not is_whitelisted_host(host)
return ((host in self._blocked_hosts or
host in self._config_blocked_hosts) and
not is_whitelisted_host(host))
def _read_hosts_file(self, filename, target):
"""Read hosts from the given filename.
Args:
filename: The file to read.
target: The set to store the hosts in.
Return:
True if a read was attempted, False otherwise
"""
if filename is None or not os.path.exists(filename):
return False
try:
with open(filename, 'r', encoding='utf-8') as f:
for line in f:
target.add(line.strip())
except OSError:
log.misc.exception("Failed to read host blocklist!")
return True
def read_hosts(self):
"""Read hosts from the existing blocked-hosts file."""
self._blocked_hosts = set()
if self._hosts_file is None:
if self._local_hosts_file is None:
return
if os.path.exists(self._hosts_file):
try:
with open(self._hosts_file, 'r', encoding='utf-8') as f:
for line in f:
self._blocked_hosts.add(line.strip())
except OSError:
log.misc.exception("Failed to read host blocklist!")
else:
self._read_hosts_file(self._config_hosts_file,
self._config_blocked_hosts)
found = self._read_hosts_file(self._local_hosts_file,
self._blocked_hosts)
if not found:
args = objreg.get('args')
if (config.get('content', 'host-block-lists') is not None and
args.basedir is None):
@ -142,8 +178,14 @@ class HostBlocker:
@cmdutils.register(instance='host-blocker', win_id='win_id')
def adblock_update(self, win_id):
"""Update the adblock block lists."""
if self._hosts_file is None:
"""Update the adblock block lists.
This updates ~/.local/share/qutebrowser/blocked-hosts with downloaded
host lists and re-reads ~/.config/qutebrowser/blocked-hosts.
"""
self._read_hosts_file(self._config_hosts_file,
self._config_blocked_hosts)
if self._local_hosts_file is None:
raise cmdexc.CommandError("No data storage is configured!")
self._blocked_hosts = set()
self._done_count = 0
@ -221,7 +263,7 @@ class HostBlocker:
def on_lists_downloaded(self):
"""Install block lists after files have been downloaded."""
with open(self._hosts_file, 'w', encoding='utf-8') as f:
with open(self._local_hosts_file, 'w', encoding='utf-8') as f:
for host in sorted(self._blocked_hosts):
f.write(host + '\n')
message.info('current', "adblock: Read {} hosts from {} sources."
@ -233,7 +275,7 @@ class HostBlocker:
urls = config.get('content', 'host-block-lists')
if urls is None:
try:
os.remove(self._hosts_file)
os.remove(self._local_hosts_file)
except OSError:
log.misc.exception("Failed to delete hosts file.")

View File

@ -45,6 +45,7 @@ from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
objreg, utils)
from qutebrowser.utils.usertypes import KeyMode
from qutebrowser.misc import editor, guiprocess
from qutebrowser.completion.models import instances, sortfilter
class CommandDispatcher:
@ -64,7 +65,6 @@ class CommandDispatcher:
"""
def __init__(self, win_id, tabbed_browser):
self._editor = None
self._win_id = win_id
self._tabbed_browser = tabbed_browser
@ -248,7 +248,10 @@ class CommandDispatcher:
try:
url = urlutils.fuzzy_url(url)
except urlutils.InvalidUrlError as e:
raise cmdexc.CommandError(e)
# We don't use cmdexc.CommandError here as this can be called
# async from edit_url
message.error(self._win_id, str(e))
return
if tab or bg or window:
self._open(url, tab, bg, window)
else:
@ -818,10 +821,13 @@ class CommandDispatcher:
text = utils.get_clipboard(selection=sel)
if not text.strip():
raise cmdexc.CommandError("{} is empty.".format(target))
log.misc.debug("{} contained: '{}'".format(target,
text.replace('\n', '\\n')))
text_urls = enumerate(u for u in text.split('\n') if u.strip())
for i, text_url in text_urls:
log.misc.debug("{} contained: {!r}".format(target, text))
text_urls = [u for u in text.split('\n') if u.strip()]
if (len(text_urls) > 1 and not urlutils.is_url(text_urls[0]) and
urlutils.get_path_if_valid(
text_urls[0], check_exists=True) is None):
text_urls = [text]
for i, text_url in enumerate(text_urls):
if not window and i > 0:
tab = False
bg = True
@ -831,6 +837,60 @@ class CommandDispatcher:
raise cmdexc.CommandError(e)
self._open(url, tab, bg, window)
@cmdutils.register(instance='command-dispatcher', scope='window',
completion=[usertypes.Completion.tab])
def buffer(self, index):
"""Select tab by index or url/title best match.
Focuses window if necessary.
Args:
index: The [win_id/]index of the tab to focus. Or a substring
in which case the closest match will be focused.
"""
index_parts = index.split('/', 1)
try:
for part in index_parts:
int(part)
except ValueError:
model = instances.get(usertypes.Completion.tab)
sf = sortfilter.CompletionFilterModel(source=model)
sf.set_pattern(index)
if sf.count() > 0:
index = sf.data(sf.first_item())
index_parts = index.split('/', 1)
else:
raise cmdexc.CommandError(
"No matching tab for: {}".format(index))
if len(index_parts) == 2:
win_id = int(index_parts[0])
idx = int(index_parts[1])
elif len(index_parts) == 1:
idx = int(index_parts[0])
active_win = objreg.get('app').activeWindow()
if active_win is None:
# Not sure how you enter a command without an active window...
raise cmdexc.CommandError(
"No window specified and couldn't find active window!")
win_id = active_win.win_id
if win_id not in objreg.window_registry:
raise cmdexc.CommandError(
"There's no window with id {}!".format(win_id))
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if not 0 < idx <= tabbed_browser.count():
raise cmdexc.CommandError(
"There's no tab with index {}!".format(idx))
window = objreg.window_registry[win_id]
window.activateWindow()
window.raise_()
tabbed_browser.setCurrentIndex(idx-1)
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
def tab_focus(self, index: {'type': (int, 'last')}=None, count=None):
@ -840,7 +900,8 @@ class CommandDispatcher:
Args:
index: The tab index to focus, starting with 1. The special value
`last` focuses the last focused tab.
`last` focuses the last focused tab. Negative indexes
counts from the end, such that -1 is the last tab.
count: The tab index to focus, starting with 1.
"""
if index == 'last':
@ -849,6 +910,8 @@ class CommandDispatcher:
if index is None and count is None:
self.tab_next()
return
if index is not None and index < 0:
index = self._count() + index + 1
try:
idx = cmdutils.arg_or_count(index, count, default=1,
countzero=self._count())
@ -915,9 +978,12 @@ class CommandDispatcher:
useful here.
Args:
userscript: Run the command as a userscript. Either store the
userscript in `~/.local/share/qutebrowser/userscripts`
(or `$XDG_DATA_DIR`), or use an absolute path.
userscript: Run the command as a userscript. You can use an
absolute path, or store the userscript in one of those
locations:
- `~/.local/share/qutebrowser/userscripts`
(or `$XDG_DATA_DIR`)
- `/usr/share/qutebrowser/userscripts`
verbose: Show notifications when the command started/exited.
detach: Whether the command should be detached from qutebrowser.
cmdline: The commandline to execute.
@ -1269,11 +1335,10 @@ class CommandDispatcher:
text = str(elem)
else:
text = elem.evaluateJavaScript('this.value')
self._editor = editor.ExternalEditor(
self._win_id, self._tabbed_browser)
self._editor.editing_finished.connect(
functools.partial(self.on_editing_finished, elem))
self._editor.edit(text)
ed = editor.ExternalEditor(self._win_id, self._tabbed_browser)
ed.editing_finished.connect(functools.partial(
self.on_editing_finished, elem))
ed.edit(text)
def on_editing_finished(self, elem, text):
"""Write the editor text into the form field and clean up tempfile.
@ -1786,3 +1851,29 @@ class CommandDispatcher:
"""Clear remembered SSL error answers."""
nam = self._current_widget().page().networkAccessManager()
nam.clear_all_ssl_errors()
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
def edit_url(self, url=None, bg=False, tab=False, window=False,
count=None):
"""Navigate to a url formed in an external editor.
The editor which should be launched can be configured via the
`general -> editor` config option.
Args:
url: URL to edit; defaults to the current page url.
bg: Open in a new background tab.
tab: Open in a new tab.
window: Open in a new window.
count: The tab index to open the URL in, or None.
"""
cmdutils.check_exclusive((tab, bg, window), 'tbw')
ed = editor.ExternalEditor(self._win_id, self._tabbed_browser)
# Passthrough for openurl args (e.g. -t, -b, -w)
ed.editing_finished.connect(functools.partial(
self.openurl, bg=bg, tab=tab, window=window, count=count))
ed.edit(url or self._current_url().toString())

View File

@ -27,6 +27,7 @@ import shutil
import functools
import collections
import sip
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QTimer,
Qt, QVariant, QAbstractListModel, QModelIndex, QUrl)
from PyQt5.QtGui import QDesktopServices
@ -89,7 +90,7 @@ def path_suggestion(filename):
return filename
elif suggestion == 'both':
return os.path.join(download_dir(), filename)
else:
else: # pragma: no cover
raise ValueError("Invalid suggestion value {}!".format(suggestion))
@ -103,6 +104,11 @@ def create_full_filename(basename, filename):
Return:
The full absolute path, or None if filename creation was not possible.
"""
# Remove chars which can't be encoded in the filename encoding.
# See https://github.com/The-Compiler/qutebrowser/issues/427
encoding = sys.getfilesystemencoding()
filename = utils.force_encoding(filename, encoding)
basename = utils.force_encoding(basename, encoding)
if os.path.isabs(filename) and os.path.isdir(filename):
# We got an absolute directory from the user, so we save it under
# the default filename in that directory.
@ -517,15 +523,11 @@ class DownloadItem(QObject):
None: special value to stop the download.
"""
global last_used_directory
if self.fileobj is not None:
if self.fileobj is not None: # pragma: no cover
raise ValueError("fileobj was already set! filename: {}, "
"existing: {}, fileobj {}".format(
filename, self._filename, self.fileobj))
filename = os.path.expanduser(filename)
# Remove chars which can't be encoded in the filename encoding.
# See https://github.com/The-Compiler/qutebrowser/issues/427
encoding = sys.getfilesystemencoding()
filename = utils.force_encoding(filename, encoding)
self._filename = create_full_filename(self.basename, filename)
if self._filename is None:
# We only got a filename (without directory) or a relative path
@ -534,6 +536,22 @@ class DownloadItem(QObject):
self._filename = create_full_filename(
self.basename, os.path.join(download_dir(), filename))
# At this point, we have a misconfigured XDG_DOWNLOAd_DIR, as
# download_dir() + filename is still no absolute path.
# The config value is checked for "absoluteness", but
# ~/.config/user-dirs.dirs may be misconfigured and a non-absolute path
# may be set for XDG_DOWNLOAD_DIR
if self._filename is None:
message.error(
self._win_id,
"XDG_DOWNLOAD_DIR points to a relative path - please check"
" your ~/.config/user-dirs.dirs. The download is saved in"
" your home directory.",
)
# fall back to $HOME as download_dir
self._filename = create_full_filename(
self.basename, os.path.expanduser(os.path.join('~', filename)))
self.basename = os.path.basename(self._filename)
last_used_directory = os.path.dirname(self._filename)
@ -558,7 +576,7 @@ class DownloadItem(QObject):
Args:
fileobj: A file-like object.
"""
if self.fileobj is not None:
if self.fileobj is not None: # pragma: no cover
raise ValueError("fileobj was already set! Old: {}, new: "
"{}".format(self.fileobj, fileobj))
self.fileobj = fileobj
@ -768,14 +786,32 @@ class DownloadManager(QAbstractListModel):
If not, None.
"""
if fileobj is not None and filename is not None:
if fileobj is not None and filename is not None: # pragma: no cover
raise TypeError("Only one of fileobj/filename may be given!")
# WORKAROUND for Qt corrupting data loaded from cache:
# https://bugreports.qt.io/browse/QTBUG-42757
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
QNetworkRequest.AlwaysNetwork)
suggested_fn = urlutils.filename_from_url(request.url())
if request.url().scheme().lower() != 'data':
suggested_fn = urlutils.filename_from_url(request.url())
else:
# We might be downloading a binary blob embedded on a page or even
# generated dynamically via javascript. We try to figure out a more
# sensible name than the base64 content of the data.
origin = request.originatingObject()
try:
origin_url = origin.url()
except AttributeError:
# Raised either if origin is None or some object that doesn't
# have its own url. We're probably fine with a default fallback
# then.
suggested_fn = 'binary blob'
else:
# Use the originating URL as a base for the filename (works
# e.g. for pdf.js).
suggested_fn = urlutils.filename_from_url(origin_url)
if suggested_fn is None:
suggested_fn = 'qutebrowser-download'
@ -834,7 +870,7 @@ class DownloadManager(QAbstractListModel):
Return:
The created DownloadItem.
"""
if fileobj is not None and filename is not None:
if fileobj is not None and filename is not None: # pragma: no cover
raise TypeError("Only one of fileobj/filename may be given!")
if not suggested_filename:
if filename is not None:
@ -914,22 +950,30 @@ class DownloadManager(QAbstractListModel):
@cmdutils.register(instance='download-manager', scope='window',
count='count')
def download_cancel(self, count=0):
def download_cancel(self, all_=False, count=0):
"""Cancel the last/[count]th download.
Args:
all_: Cancel all running downloads
count: The index of the download to cancel.
"""
try:
download = self.downloads[count - 1]
except IndexError:
self.raise_no_download(count)
if download.done:
if not count:
count = len(self.downloads)
raise cmdexc.CommandError("Download {} is already done!"
.format(count))
download.cancel()
if all_:
# We need to make a copy as we're indirectly mutating
# self.downloads here
for download in self.downloads[:]:
if not download.done:
download.cancel()
else:
try:
download = self.downloads[count - 1]
except IndexError:
self.raise_no_download(count)
if download.done:
if not count:
count = len(self.downloads)
raise cmdexc.CommandError("Download {} is already done!"
.format(count))
download.cancel()
@cmdutils.register(instance='download-manager', scope='window',
count='count')
@ -937,7 +981,7 @@ class DownloadManager(QAbstractListModel):
"""Delete the last/[count]th download from disk.
Args:
count: The index of the download to cancel.
count: The index of the download to delete.
"""
try:
download = self.downloads[count - 1]
@ -956,7 +1000,7 @@ class DownloadManager(QAbstractListModel):
"""Open the last/[count]th download.
Args:
count: The index of the download to cancel.
count: The index of the download to open.
"""
try:
download = self.downloads[count - 1]
@ -974,7 +1018,7 @@ class DownloadManager(QAbstractListModel):
"""Retry the first failed/[count]th download.
Args:
count: The index of the download to cancel.
count: The index of the download to retry.
"""
if count:
try:
@ -1060,12 +1104,10 @@ class DownloadManager(QAbstractListModel):
"""Remove the last/[count]th download from the list.
Args:
all_: Deprecated argument for removing all finished downloads.
count: The index of the download to cancel.
all_: Remove all finished downloads.
count: The index of the download to remove.
"""
if all_:
message.warning(self._win_id, ":download-remove --all is "
"deprecated - use :download-clear instead!")
self.download_clear()
else:
try:
@ -1090,6 +1132,9 @@ class DownloadManager(QAbstractListModel):
def remove_item(self, download):
"""Remove a given download."""
if sip.isdeleted(self):
# https://github.com/The-Compiler/qutebrowser/issues/1242
return
try:
idx = self.downloads.index(download)
except ValueError:
@ -1184,7 +1229,8 @@ class DownloadManager(QAbstractListModel):
def flags(self, _index):
"""Override flags so items aren't selectable.
The default would be Qt.ItemIsEnabled | Qt.ItemIsSelectable."""
The default would be Qt.ItemIsEnabled | Qt.ItemIsSelectable.
"""
return Qt.ItemIsEnabled | Qt.ItemNeverHasChildren
def rowCount(self, parent=QModelIndex()):

View File

@ -79,6 +79,7 @@ class DownloadView(QListView):
self.setResizeMode(QListView.Adjust)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
self.setFocusPolicy(Qt.NoFocus)
self.setFlow(QListView.LeftToRight)
self.setSpacing(1)
self._menu = None

View File

@ -42,10 +42,10 @@ from qutebrowser.misc import guiprocess
ElemTuple = collections.namedtuple('ElemTuple', ['elem', 'label'])
Target = usertypes.enum('Target', ['normal', 'tab', 'tab_fg', 'tab_bg',
'window', 'yank', 'yank_primary', 'run',
'fill', 'hover', 'download', 'userscript',
'spawn'])
Target = usertypes.enum('Target', ['normal', 'current', 'tab', 'tab_fg',
'tab_bg', 'window', 'yank', 'yank_primary',
'run', 'fill', 'hover', 'download',
'userscript', 'spawn'])
class WordHintingError(Exception):
@ -71,7 +71,8 @@ class HintContext:
elems: A mapping from key strings to (elem, label) namedtuples.
baseurl: The URL of the current page.
target: What to do with the opened links.
normal/tab/tab_fg/tab_bg/window: Get passed to BrowserTab.
normal/current/tab/tab_fg/tab_bg/window: Get passed to
BrowserTab.
yank/yank_primary: Yank to clipboard/primary selection.
run: Run a command.
fill: Fill commandline with link.
@ -128,6 +129,7 @@ class HintManager(QObject):
HINT_TEXTS = {
Target.normal: "Follow hint",
Target.current: "Follow hint in current tab",
Target.tab: "Follow hint in new tab",
Target.tab_fg: "Follow hint in foreground tab",
Target.tab_bg: "Follow hint in background tab",
@ -469,6 +471,7 @@ class HintManager(QObject):
"""
target_mapping = {
Target.normal: usertypes.ClickTarget.normal,
Target.current: usertypes.ClickTarget.normal,
Target.tab_fg: usertypes.ClickTarget.tab,
Target.tab_bg: usertypes.ClickTarget.tab_bg,
Target.window: usertypes.ClickTarget.window,
@ -507,6 +510,8 @@ class HintManager(QObject):
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
Qt.NoButton, modifiers),
]
if context.target == Target.current:
elem.remove_blank_target()
for evt in events:
self.mouse_event.emit(evt)
if elem.is_text_input() and elem.is_editable():
@ -785,7 +790,8 @@ class HintManager(QObject):
target: What to do with the selected element.
- `normal`: Open the link in the current tab.
- `normal`: Open the link.
- `current`: Open the link in the current tab.
- `tab`: Open the link in a new tab (honoring the
background-tabs setting).
- `tab-fg`: Open the link in a new foreground tab.
@ -935,6 +941,7 @@ class HintManager(QObject):
# Handlers which take a QWebElement
elem_handlers = {
Target.normal: self._click,
Target.current: self._click,
Target.tab: self._click,
Target.tab_fg: self._click,
Target.tab_bg: self._click,

View File

@ -57,9 +57,29 @@ def is_root(directory):
Return:
Whether the directory is a root directory or not.
"""
# If you're curious as why this works:
# dirname('/') = '/'
# dirname('/home') = '/'
# dirname('/home/') = '/home'
# dirname('/home/foo') = '/home'
# basically, for files (no trailing slash) it removes the file part, and
# for directories, it removes the trailing slash, so the only way for this
# to be equal is if the directory is the root directory.
return os.path.dirname(directory) == directory
def parent_dir(directory):
"""Return the parent directory for the given directory.
Args:
directory: The path to the directory.
Return:
The path to the parent directory.
"""
return os.path.normpath(os.path.join(directory, os.pardir))
def dirbrowser_html(path):
"""Get the directory browser web page.
@ -70,30 +90,25 @@ def dirbrowser_html(path):
The HTML of the web page.
"""
title = "Browse directory: {}".format(path)
template = jinja.env.get_template('dirbrowser.html')
# pylint: disable=no-member
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/490/
if is_root(path):
parent = None
else:
parent = os.path.dirname(path)
parent = parent_dir(path)
try:
all_files = os.listdir(path)
except OSError as e:
html = jinja.env.get_template('error.html').render(
title="Error while reading directory",
url='file://{}'.format(path),
error=str(e),
icon='')
html = jinja.render('error.html',
title="Error while reading directory",
url='file:///{}'.format(path), error=str(e),
icon='')
return html.encode('UTF-8', errors='xmlcharrefreplace')
files = get_file_list(path, all_files, os.path.isfile)
directories = get_file_list(path, all_files, os.path.isdir)
html = template.render(title=title, url=path, icon='',
parent=parent, files=files,
directories=directories)
html = jinja.render('dirbrowser.html', title=title, url=path, icon='',
parent=parent, files=files, directories=directories)
return html.encode('UTF-8', errors='xmlcharrefreplace')

View File

@ -23,8 +23,6 @@ import urllib.parse
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
from qutebrowser.misc import httpclient
class PastebinClient(QObject):
@ -47,11 +45,17 @@ class PastebinClient(QObject):
success = pyqtSignal(str)
error = pyqtSignal(str)
def __init__(self, parent=None):
def __init__(self, client, parent=None):
"""Constructor.
Args:
client: The HTTPClient to use. Will be reparented.
"""
super().__init__(parent)
self._client = httpclient.HTTPClient(self)
self._client.error.connect(self.error)
self._client.success.connect(self.on_client_success)
client.setParent(self)
client.error.connect(self.error)
client.success.connect(self.on_client_success)
self._client = client
def paste(self, name, title, text, parent=None):
"""Paste the text into a pastebin and return the URL.

View File

@ -16,12 +16,6 @@
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
#
# pylint complains when using .render() on jinja templates, so we make it shut
# up for this whole module.
# pylint: disable=no-member
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/490/
"""Handler functions for different qute:... pages.
@ -149,17 +143,17 @@ class JSBridge(QObject):
@add_handler('pyeval')
def qute_pyeval(_win_id, _request):
"""Handler for qute:pyeval. Return HTML content as bytes."""
html = jinja.env.get_template('pre.html').render(
title='pyeval', content=pyeval_output)
html = jinja.render('pre.html', title='pyeval', content=pyeval_output)
return html.encode('UTF-8', errors='xmlcharrefreplace')
@add_handler('version')
@add_handler('verizon')
def qute_version(_win_id, _request):
"""Handler for qute:version. Return HTML content as bytes."""
html = jinja.env.get_template('version.html').render(
title='Version info', version=version.version(),
copyright=qutebrowser.__copyright__)
html = jinja.render('version.html', title='Version info',
version=version.version(),
copyright=qutebrowser.__copyright__)
return html.encode('UTF-8', errors='xmlcharrefreplace')
@ -170,7 +164,7 @@ def qute_plainlog(_win_id, _request):
text = "Log output was disabled."
else:
text = log.ram_handler.dump_log()
html = jinja.env.get_template('pre.html').render(title='log', content=text)
html = jinja.render('pre.html', title='log', content=text)
return html.encode('UTF-8', errors='xmlcharrefreplace')
@ -181,8 +175,7 @@ def qute_log(_win_id, _request):
html_log = None
else:
html_log = log.ram_handler.dump_log(html=True)
html = jinja.env.get_template('log.html').render(
title='log', content=html_log)
html = jinja.render('log.html', title='log', content=html_log)
return html.encode('UTF-8', errors='xmlcharrefreplace')
@ -198,7 +191,8 @@ def qute_help(win_id, request):
try:
utils.read_file('html/doc/index.html')
except OSError:
html = jinja.env.get_template('error.html').render(
html = jinja.render(
'error.html',
title="Error while loading documentation",
url=request.url().toDisplayString(),
error="This most likely means the documentation was not generated "
@ -217,16 +211,19 @@ def qute_help(win_id, request):
message.error(win_id, "Your documentation is outdated! Please re-run "
"scripts/asciidoc2html.py.")
path = 'html/doc/{}'.format(urlpath)
return utils.read_file(path).encode('UTF-8', errors='xmlcharrefreplace')
if urlpath.endswith('.png'):
return utils.read_file(path, binary=True)
else:
data = utils.read_file(path)
return data.encode('UTF-8', errors='xmlcharrefreplace')
@add_handler('settings')
def qute_settings(win_id, _request):
"""Handler for qute:settings. View/change qute configuration."""
config_getter = functools.partial(objreg.get('config').get, raw=True)
html = jinja.env.get_template('settings.html').render(
win_id=win_id, title='settings', config=configdata,
confget=config_getter)
html = jinja.render('settings.html', win_id=win_id, title='settings',
config=configdata, confget=config_getter)
return html.encode('UTF-8', errors='xmlcharrefreplace')

View File

@ -285,6 +285,19 @@ class WebElementWrapper(collections.abc.MutableMapping):
tag = self._elem.tagName().lower()
return self.get('role', None) in roles or tag in ('input', 'textarea')
def remove_blank_target(self):
"""Remove target from link."""
elem = self._elem
for _ in range(5):
if elem is None:
break
tag = elem.tagName().lower()
if tag == 'a' or tag == 'area':
if elem.attribute('target') == '_blank':
elem.setAttribute('target', '_top')
break
elem = elem.parent()
def debug_text(self):
"""Get a text based on an element suitable for debug output."""
self._check_vanished()

View File

@ -122,7 +122,10 @@ class BrowserPage(QWebPage):
"""
ignored_errors = [
(QWebPage.QtNetwork, QNetworkReply.OperationCanceledError),
(QWebPage.WebKit, 203), # "Loading is handled by the media engine"
# "Loading is handled by the media engine"
(QWebPage.WebKit, 203),
# "Frame load interrupted by policy change"
(QWebPage.WebKit, 102),
]
errpage.baseUrl = info.url
urlstr = info.url.toDisplayString()
@ -166,10 +169,8 @@ class BrowserPage(QWebPage):
log.webview.debug("Error domain: {}, error code: {}".format(
info.domain, info.error))
title = "Error loading page: {}".format(urlstr)
template = jinja.env.get_template('error.html')
# pylint: disable=no-member
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/490/
html = template.render(
html = jinja.render(
'error.html',
title=title, url=urlstr, error=error_str, icon='')
errpage.content = html.encode('utf-8')
errpage.encoding = 'utf-8'
@ -221,14 +222,12 @@ class BrowserPage(QWebPage):
def _show_pdfjs(self, reply):
"""Show the reply with pdfjs."""
try:
page = pdfjs.generate_pdfjs_page(reply.url()).encode('utf-8')
page = pdfjs.generate_pdfjs_page(reply.url())
except pdfjs.PDFJSNotFound:
# pylint: disable=no-member
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/490/
page = (jinja.env.get_template('no_pdfjs.html')
.render(url=reply.url().toDisplayString())
.encode('utf-8'))
self.mainFrame().setContent(page, 'text/html', reply.url())
page = jinja.render('no_pdfjs.html',
url=reply.url().toDisplayString())
self.mainFrame().setContent(page.encode('utf-8'), 'text/html',
reply.url())
reply.deleteLater()
def shutdown(self):

View File

@ -27,7 +27,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QApplication, QStyleFactory
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
from PyQt5.QtWebKitWidgets import QWebView, QWebPage, QWebFrame
from qutebrowser.config import config
from qutebrowser.keyinput import modeman
@ -352,9 +352,14 @@ class WebView(QWebView):
frame = self.page().mainFrame()
frame.javaScriptWindowObjectCleared.connect(self.add_js_bridge)
@pyqtSlot(QWebFrame)
def add_js_bridge(self):
"""Add the javascript bridge for qute:... pages."""
frame = self.sender()
if not isinstance(frame, QWebFrame):
log.webview.error("Got non-QWebFrame in add_js_bridge")
return
if frame.url().scheme() == 'qute':
bridge = objreg.get('js-bridge')
frame.addToJavaScriptWindowObject('qute', bridge)

View File

@ -261,7 +261,8 @@ class CommandRunner(QObject):
"""Run a command and display exceptions in the statusbar.
Contrary to run_safely, error messages are queued so this is more
suitable to use while initializing."""
suitable to use while initializing.
"""
try:
self.run(text, count)
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:

View File

@ -344,14 +344,18 @@ def run(cmd, *args, win_id, env, verbose=False):
user_agent = config.get('network', 'user-agent')
if user_agent is not None:
env['QUTE_USER_AGENT'] = user_agent
cmd = os.path.expanduser(cmd)
cmd_path = os.path.expanduser(cmd)
# if cmd is not given as an absolute path, look it up
# ~/.local/share/qutebrowser/userscripts (or $XDG_DATA_DIR)
if not os.path.isabs(cmd):
log.misc.debug("{} is no absolute path".format(cmd))
cmd = os.path.join(standarddir.data(), "userscripts", cmd)
if not os.path.isabs(cmd_path):
log.misc.debug("{} is no absolute path".format(cmd_path))
cmd_path = os.path.join(standarddir.data(), "userscripts", cmd)
if not os.path.exists(cmd_path):
cmd_path = os.path.join(standarddir.system_data(),
"userscripts", cmd)
log.misc.debug("Userscript to run: {}".format(cmd_path))
runner.run(cmd, *args, env=env, verbose=verbose)
runner.run(cmd_path, *args, env=env, verbose=verbose)
runner.finished.connect(commandrunner.deleteLater)
runner.finished.connect(runner.deleteLater)

View File

@ -59,6 +59,14 @@ def _init_url_completion():
_instances[usertypes.Completion.url] = model
def _init_tab_completion():
"""Initialize the tab completion model."""
log.completion.debug("Initializing tab completion.")
with debug.log_time(log.completion, 'tab completion init'):
model = miscmodels.TabCompletionModel()
_instances[usertypes.Completion.tab] = model
def _init_setting_completions():
"""Initialize setting completion models."""
log.completion.debug("Initializing setting completion.")
@ -115,6 +123,7 @@ INITIALIZERS = {
usertypes.Completion.command: _init_command_completion,
usertypes.Completion.helptopic: _init_helptopic_completion,
usertypes.Completion.url: _init_url_completion,
usertypes.Completion.tab: _init_tab_completion,
usertypes.Completion.section: _init_setting_completions,
usertypes.Completion.option: _init_setting_completions,
usertypes.Completion.value: _init_setting_completions,

View File

@ -19,6 +19,9 @@
"""Misc. CompletionModels."""
from PyQt5.QtCore import Qt, QTimer, pyqtSlot
from qutebrowser.browser import webview
from qutebrowser.config import config, configdata
from qutebrowser.utils import objreg, log
from qutebrowser.commands import cmdutils
@ -138,3 +141,78 @@ class SessionCompletionModel(base.BaseCompletionModel):
self.new_item(cat, name)
except OSError:
log.completion.exception("Failed to list sessions!")
class TabCompletionModel(base.BaseCompletionModel):
"""A model to complete on open tabs across all windows.
Used for switching the buffer command.
"""
# https://github.com/The-Compiler/qutebrowser/issues/545
# pylint: disable=abstract-method
#IDX_COLUMN = 0
URL_COLUMN = 1
TEXT_COLUMN = 2
COLUMN_WIDTHS = (6, 40, 54)
DUMB_SORT = Qt.DescendingOrder
def __init__(self, parent=None):
super().__init__(parent)
self.columns_to_filter = [self.URL_COLUMN, self.TEXT_COLUMN]
for win_id in objreg.window_registry:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
for i in range(tabbed_browser.count()):
tab = tabbed_browser.widget(i)
tab.url_text_changed.connect(self.rebuild)
tab.shutting_down.connect(self.delayed_rebuild)
tabbed_browser.new_tab.connect(self.on_new_tab)
objreg.get("app").new_window.connect(self.on_new_window)
self.rebuild()
# slot argument should be mainwindow.MainWindow but can't import
# that at module level because of import loops.
@pyqtSlot(object)
def on_new_window(self, window):
"""Add hooks to new windows."""
window.tabbed_browser.new_tab.connect(self.on_new_tab)
@pyqtSlot(webview.WebView)
def on_new_tab(self, tab):
"""Add hooks to new tabs."""
tab.url_text_changed.connect(self.rebuild)
tab.shutting_down.connect(self.delayed_rebuild)
self.rebuild()
@pyqtSlot()
def delayed_rebuild(self):
"""Fire a rebuild indirectly so widgets get a chance to update."""
QTimer.singleShot(0, self.rebuild)
@pyqtSlot()
def rebuild(self):
"""Rebuild completion model from current tabs.
Very lazy method of keeping the model up to date. We could connect to
signals for new tab, tab url/title changed, tab close, tab moved and
make sure we handled background loads too ... but iterating over a
few/few dozen/few hundred tabs doesn't take very long at all.
"""
self.removeRows(0, self.rowCount())
for win_id in objreg.window_registry:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if tabbed_browser.shutting_down:
continue
c = self.new_category("{}".format(win_id))
for i in range(tabbed_browser.count()):
tab = tabbed_browser.widget(i)
self.new_item(c, "{}/{}".format(win_id, i+1),
tab.url().toDisplayString(),
tabbed_browser.page_title(i))

View File

@ -32,7 +32,8 @@ class UrlCompletionModel(base.BaseCompletionModel):
"""A model which combines bookmarks, quickmarks and web history URLs.
Used for the `open` command."""
Used for the `open` command.
"""
# https://github.com/The-Compiler/qutebrowser/issues/545
# pylint: disable=abstract-method

View File

@ -896,7 +896,8 @@ def data(readonly=False):
('auto-follow',
SettingValue(typ.Bool(), 'true'),
"Whether to auto-follow a hint if there's only one left."),
"Follow a hint immediately when the hint text is completely "
"matched."),
('next-regexes',
SettingValue(typ.RegexList(flags=re.IGNORECASE),
@ -1199,7 +1200,7 @@ def data(readonly=False):
SettingValue(typ.Font(), 'Terminus, Monospace, '
'"DejaVu Sans Mono", Monaco, '
'"Bitstream Vera Sans Mono", "Andale Mono", '
'"Liberation Mono", "Courier New", Courier, '
'"Courier New", Courier, "Liberation Mono", '
'monospace, Fixed, Consolas, Terminal'),
"Default monospace fonts."),
@ -1396,8 +1397,8 @@ KEY_DATA = collections.OrderedDict([
('tab-move', ['gm']),
('tab-move -', ['gl']),
('tab-move +', ['gr']),
('tab-focus', ['J', 'gt']),
('tab-prev', ['K', 'gT']),
('tab-focus', ['J']),
('tab-prev', ['K']),
('tab-clone', ['gC']),
('reload', ['r']),
('reload -f', ['R']),
@ -1476,6 +1477,7 @@ KEY_DATA = collections.OrderedDict([
('download-cancel', ['ad']),
('download-clear', ['cd']),
('view-source', ['gf']),
('set-cmd-text -s :buffer', ['gt']),
('tab-focus last', ['<Ctrl-Tab>']),
('enter-mode passthrough', ['<Ctrl-V>']),
('quit', ['<Ctrl-Q>']),

View File

@ -1159,6 +1159,8 @@ class Proxy(BaseType):
out.append((val, self.valid_values.descriptions[val]))
out.append(('http://', 'HTTP proxy URL'))
out.append(('socks://', 'SOCKS proxy URL'))
out.append(('socks://localhost:9050/', 'Tor via SOCKS'))
out.append(('http://localhost:8080/', 'Local HTTP proxy'))
return out
def transform(self, value):
@ -1196,7 +1198,7 @@ class SearchEngineUrl(BaseType):
self._basic_validation(value)
if not value:
return
elif '{}' not in value:
elif not ('{}' in value or '{0}' in value):
raise configexc.ValidationError(value, "must contain \"{}\"")
try:
value.format("")

View File

@ -201,7 +201,7 @@ class KeyConfigParser(QObject):
sect = self.keybindings[mode]
except KeyError:
raise cmdexc.CommandError("Can't find mode section '{}'!".format(
sect))
mode))
try:
del sect[key]
except KeyError:

View File

@ -46,21 +46,21 @@ ul.files > li {
<p id="dirbrowserTitleText">Browse directory: {{url}}</p>
</div>
{% if parent %}
{% if parent is not none %}
<ul class="parent">
<li><a href="{{parent}}">..</a></li>
<li><a href="{{ file_url(parent) }}">..</a></li>
</ul>
{% endif %}
<ul class="folders">
{% for item in directories %}
<li><a href="file://{{item.absname}}">{{item.name}}</a></li>
<li><a href="{{ file_url(item.absname) }}">{{item.name}}</a></li>
{% endfor %}
</ul>
<ul class="files">
{% for item in files %}
<li><a href="file://{{item.absname}}">{{item.name}}</a></li>
<li><a href="{{ file_url(item.absname) }}">{{item.name}}</a></li>
{% endfor %}
</ul>
</div>

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<!--
vim: ft=html fileencoding=utf-8 sts=4 sw=4 et:
-->
<html>
<head>
<meta charset="utf-8">
<title>Error while rendering HTML</title>
</head>
<body>
<h1>Error while rendering internal qutebrowser page</h1>
<p>There was an error while rendering {pagename}.</p>
<p>This most likely happened because you updated qutebrowser but didn't restart yet.</p>
<p>If you believe this isn't the case and this is a bug, please do :report.<p>
<h2>Traceback</h2>
<pre>{traceback}</pre>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -23,7 +23,6 @@ import functools
from PyQt5.QtCore import pyqtSignal, Qt, QObject, QEvent
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebKitWidgets import QWebView
from qutebrowser.keyinput import modeparsers, keyparser
from qutebrowser.config import config
@ -171,13 +170,9 @@ class ModeManager(QObject):
is_non_alnum = (
event.modifiers() not in (Qt.NoModifier, Qt.ShiftModifier) or
not event.text().strip())
focus_widget = QApplication.instance().focusWidget()
is_tab = event.key() in (Qt.Key_Tab, Qt.Key_Backtab)
if handled:
filter_this = True
elif is_tab and not isinstance(focus_widget, QWebView):
filter_this = True
elif (parser.passthrough or
self._forward_unbound_keys == 'all' or
(self._forward_unbound_keys == 'auto' and is_non_alnum)):
@ -189,11 +184,12 @@ class ModeManager(QObject):
self._releaseevents_to_pass.add(KeyEvent(event))
if curmode != usertypes.KeyMode.insert:
focus_widget = QApplication.instance().focusWidget()
log.modes.debug("handled: {}, forward-unbound-keys: {}, "
"passthrough: {}, is_non_alnum: {}, is_tab {} --> "
"passthrough: {}, is_non_alnum: {} --> "
"filter: {} (focused: {!r})".format(
handled, self._forward_unbound_keys,
parser.passthrough, is_non_alnum, is_tab,
parser.passthrough, is_non_alnum,
filter_this, focus_widget))
return filter_this

View File

@ -187,6 +187,8 @@ class MainWindow(QWidget):
#self.tabWidget.setCurrentIndex(0)
#QtCore.QMetaObject.connectSlotsByName(MainWindow)
objreg.get("app").new_window.emit(self)
def __repr__(self):
return utils.get_repr(self)

View File

@ -63,7 +63,7 @@ class TabbedBrowser(tabwidget.TabWidget):
tabbar -> new-tab-position set to 'left'.
_tab_insert_idx_right: Same as above, for 'right'.
_undo_stack: List of UndoEntry namedtuples of closed tabs.
_shutting_down: Whether we're currently shutting down.
shutting_down: Whether we're currently shutting down.
Signals:
cur_progress: Progress of the current tab changed (loadProgress).
@ -82,6 +82,7 @@ class TabbedBrowser(tabwidget.TabWidget):
widget can adjust its size to it.
arg: The new size.
current_tab_changed: The current tab changed to the emitted WebView.
new_tab: Emits the new WebView and its index when a new tab is opened.
"""
cur_progress = pyqtSignal(int)
@ -96,13 +97,14 @@ class TabbedBrowser(tabwidget.TabWidget):
resized = pyqtSignal('QRect')
got_cmd = pyqtSignal(str)
current_tab_changed = pyqtSignal(webview.WebView)
new_tab = pyqtSignal(webview.WebView, int)
def __init__(self, win_id, parent=None):
super().__init__(win_id, parent)
self._win_id = win_id
self._tab_insert_idx_left = 0
self._tab_insert_idx_right = -1
self._shutting_down = False
self.shutting_down = False
self.tabCloseRequested.connect(self.on_tab_close_requested)
self.currentChanged.connect(self.on_current_changed)
self.cur_load_started.connect(self.on_cur_load_started)
@ -234,7 +236,7 @@ class TabbedBrowser(tabwidget.TabWidget):
def shutdown(self):
"""Try to shut down all tabs cleanly."""
self._shutting_down = True
self.shutting_down = True
for tab in self.widgets():
self._remove_tab(tab)
@ -398,6 +400,7 @@ class TabbedBrowser(tabwidget.TabWidget):
if not background:
self.setCurrentWidget(tab)
tab.show()
self.new_tab.emit(tab, idx)
return tab
def _get_new_tab_idx(self, explicit):
@ -546,7 +549,7 @@ class TabbedBrowser(tabwidget.TabWidget):
@pyqtSlot(int)
def on_current_changed(self, idx):
"""Set last-focused-tab and leave hinting mode when focus changed."""
if idx == -1 or self._shutting_down:
if idx == -1 or self.shutting_down:
# closing the last tab (before quitting) or shutting down
return
tab = self.widget(idx)

View File

@ -36,7 +36,7 @@ from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton,
import qutebrowser
from qutebrowser.utils import version, log, utils, objreg, qtutils
from qutebrowser.misc import miscwidgets, autoupdate, msgbox
from qutebrowser.misc import miscwidgets, autoupdate, msgbox, httpclient
from qutebrowser.browser.network import pastebin
from qutebrowser.config import config
@ -96,7 +96,8 @@ def get_fatal_crash_dialog(debug, data):
def _get_environment_vars():
"""Gather environment variables for the crash info."""
masks = ('DESKTOP_SESSION', 'DE', 'QT_*', 'PYTHON*', 'LC_*', 'LANG')
masks = ('DESKTOP_SESSION', 'DE', 'QT_*', 'PYTHON*', 'LC_*', 'LANG',
'XDG_*')
info = []
for key, value in os.environ.items():
for m in masks:
@ -140,7 +141,8 @@ class _CrashDialog(QDialog):
self.setWindowTitle("Whoops!")
self.resize(QSize(640, 600))
self._vbox = QVBoxLayout(self)
self._paste_client = pastebin.PastebinClient(self)
http_client = httpclient.HTTPClient()
self._paste_client = pastebin.PastebinClient(http_client, self)
self._pypi_client = autoupdate.PyPIVersionClient(self)
self._init_text()
@ -196,7 +198,8 @@ class _CrashDialog(QDialog):
def _init_text(self):
"""Initialize the main text to be displayed on an exception.
Should be extended by subclasses to set the actual text."""
Should be extended by subclasses to set the actual text.
"""
self._lbl = QLabel(wordWrap=True, openExternalLinks=True,
textInteractionFlags=Qt.LinksAccessibleByMouse)
self._vbox.addWidget(self._lbl)
@ -507,11 +510,23 @@ class FatalCrashDialog(_CrashDialog):
def _init_text(self):
super()._init_text()
text = ("<b>qutebrowser was restarted after a fatal crash.</b><br/>"
"<br/>Note: Crash reports for fatal crashes sometimes don't "
"QTWEBENGINE_NOTE"
"<br/>Crash reports for fatal crashes sometimes don't "
"contain the information necessary to fix an issue. Please "
"follow the steps in <a href='https://github.com/The-Compiler/"
"qutebrowser/blob/master/doc/stacktrace.asciidoc'>"
"stacktrace.asciidoc</a> to submit a stacktrace.<br/>")
if datetime.datetime.now() < datetime.datetime(2016, 4, 23):
note = ("<br/>Fatal crashes like this are often caused by the "
"current QtWebKit backend.<br/><b>I'm currently running a "
"crowdfunding for the new QtWebEngine backend, based on "
"Chromium:</b> <a href='http://igg.me/at/qutebrowser'>"
"igg.me/at/qutebrowser</a><br/>")
text = text.replace('QTWEBENGINE_NOTE', note)
else:
text = text.replace('QTWEBENGINE_NOTE', '')
self._lbl.setText(text)
def _init_checkboxes(self):

View File

@ -72,7 +72,7 @@ class CrashHandler(QObject):
def handle_segfault(self):
"""Handle a segfault from a previous run."""
data_dir = None
data_dir = standarddir.data()
if data_dir is None:
return
logname = os.path.join(data_dir, 'crash.log')

View File

@ -267,7 +267,8 @@ def remove_inputhook():
"""Remove the PyQt input hook.
Doing this means we can't use the interactive shell anymore (which we don't
anyways), but we can use pdb instead."""
anyways), but we can use pdb instead.
"""
from PyQt5.QtCore import pyqtRemoveInputHook
pyqtRemoveInputHook()

View File

@ -58,7 +58,8 @@ class ExternalEditor(QObject):
return
try:
os.close(self._oshandle)
os.remove(self._filename)
if self._proc.exit_status() != QProcess.CrashExit:
os.remove(self._filename)
except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.

View File

@ -160,3 +160,6 @@ class GUIProcess(QObject):
else:
message.error(self._win_id, "Error while spawning {}: {}.".format(
self._what, self._proc.error()), immediately=True)
def exit_status(self):
return self._proc.exitStatus()

View File

@ -143,10 +143,20 @@ class SessionManager(QObject):
history = tab.page().history()
for idx, item in enumerate(history.items()):
qtutils.ensure_valid(item)
item_data = {
'url': bytes(item.url().toEncoded()).decode('ascii'),
'title': item.title(),
}
if item.title():
item_data['title'] = item.title()
else:
# https://github.com/The-Compiler/qutebrowser/issues/879
if history.currentItemIndex() == idx:
item_data['title'] = tab.page().mainFrame().title()
else:
item_data['title'] = item_data['url']
if item.originalUrl() != item.url():
encoded = item.originalUrl().toEncoded()
item_data['original-url'] = bytes(encoded).decode('ascii')
@ -231,7 +241,9 @@ class SessionManager(QObject):
log.sessions.debug("Saving session {} to {}...".format(name, path))
if last_window:
data = self._last_window_session
assert data is not None
if data is None:
log.sessions.error("last_window_session is None while saving!")
return
else:
data = self._save_all()
log.sessions.vdebug("Saving data: {}".format(data))

View File

@ -74,7 +74,9 @@ def get_argparser():
debug = parser.add_argument_group('debug arguments')
debug.add_argument('-l', '--loglevel', dest='loglevel',
help="Set loglevel", default='info')
help="Set loglevel", default='info',
choices=['critical', 'error', 'warning', 'info',
'debug', 'vdebug'])
debug.add_argument('--logfilter',
help="Comma-separated list of things to be logged "
"to the debug log on stdout.")

File diff suppressed because it is too large Load Diff

View File

@ -21,10 +21,12 @@
import os
import os.path
import traceback
import jinja2
import jinja2.exceptions
from qutebrowser.utils import utils
from qutebrowser.utils import utils, log
from PyQt5.QtCore import QUrl
@ -71,5 +73,28 @@ def resource_url(path):
image = utils.resource_filename(path)
return QUrl.fromLocalFile(image).toString(QUrl.FullyEncoded)
env = jinja2.Environment(loader=Loader('html'), autoescape=_guess_autoescape)
env.globals['resource_url'] = resource_url
def file_url(path):
"""Return a file:// url (as string) to the given local path.
Arguments:
path: The absolute path to the local file
"""
return QUrl.fromLocalFile(path).toString(QUrl.FullyEncoded)
def render(template, **kwargs):
"""Render the given template and pass the given arguments to it."""
try:
return _env.get_template(template).render(**kwargs)
except jinja2.exceptions.UndefinedError:
log.misc.exception("UndefinedError while rendering " + template)
err_path = os.path.join('html', 'undef_error.html')
err_template = utils.read_file(err_path)
tb = traceback.format_exc()
return err_template.format(pagename=template, traceback=tb)
_env = jinja2.Environment(loader=Loader('html'), autoescape=_guess_autoescape)
_env.globals['resource_url'] = resource_url
_env.globals['file_url'] = file_url

View File

@ -25,7 +25,7 @@ import os.path
from PyQt5.QtCore import QCoreApplication, QStandardPaths
from qutebrowser.utils import log, qtutils
from qutebrowser.utils import log, qtutils, debug
# The argparse namespace passed to init()
@ -65,6 +65,17 @@ def data():
return path
def system_data():
"""Get a location for system-wide data. This path may be read-only."""
if sys.platform.startswith('linux'):
path = "/usr/share/qutebrowser"
if not os.path.exists(path):
path = data()
else:
path = data()
return path
def cache():
"""Get a location for the cache."""
typ = QStandardPaths.CacheLocation
@ -113,6 +124,8 @@ def _writable_location(typ):
"""Wrapper around QStandardPaths.writableLocation."""
with qtutils.unset_organization():
path = QStandardPaths.writableLocation(typ)
typ_str = debug.qenum_key(QStandardPaths, typ)
log.misc.debug("writable location for {}: {}".format(typ_str, path))
if not path:
raise ValueError("QStandardPaths returned an empty value!")
# Qt seems to use '/' as path separator even on Windows...

View File

@ -80,7 +80,7 @@ def _parse_search_term(s):
engine = None
term = s
log.url.debug("engine {}, term '{}'".format(engine, term))
log.url.debug("engine {}, term {!r}".format(engine, term))
return (engine, term)
@ -93,7 +93,7 @@ def _get_search_url(txt):
Return:
The search URL as a QUrl.
"""
log.url.debug("Finding search engine for '{}'".format(txt))
log.url.debug("Finding search engine for {!r}".format(txt))
engine, term = _parse_search_term(txt)
assert term
if engine is None:
@ -171,22 +171,10 @@ def fuzzy_url(urlstr, cwd=None, relative=False, do_search=True):
A target QUrl to a search page or the original URL.
"""
urlstr = urlstr.strip()
expanded = os.path.expanduser(urlstr)
path = get_path_if_valid(urlstr, cwd=cwd, relative=relative,
check_exists=True)
if os.path.isabs(expanded):
path = expanded
elif relative and cwd:
path = os.path.join(cwd, expanded)
elif relative:
try:
path = os.path.abspath(expanded)
except OSError:
path = None
else:
path = None
if path is not None and os.path.exists(path):
log.url.debug("URL is a local file")
if path is not None:
url = QUrl.fromLocalFile(path)
elif (not do_search) or is_url(urlstr):
# probably an address
@ -198,7 +186,7 @@ def fuzzy_url(urlstr, cwd=None, relative=False, do_search=True):
url = _get_search_url(urlstr)
except ValueError: # invalid search engine
url = qurl_from_user_input(urlstr)
log.url.debug("Converting fuzzy term {} to URL -> {}".format(
log.url.debug("Converting fuzzy term {!r} to URL -> {}".format(
urlstr, url.toDisplayString()))
if do_search and config.get('general', 'auto-search') and urlstr:
qtutils.ensure_valid(url)
@ -246,7 +234,7 @@ def is_url(urlstr):
"""
autosearch = config.get('general', 'auto-search')
log.url.debug("Checking if '{}' is a URL (autosearch={}).".format(
log.url.debug("Checking if {!r} is a URL (autosearch={}).".format(
urlstr, autosearch))
urlstr = urlstr.strip()
@ -349,6 +337,44 @@ def raise_cmdexc_if_invalid(url):
raise cmdexc.CommandError(get_errstring(url))
def get_path_if_valid(pathstr, cwd=None, relative=False, check_exists=False):
"""Check if path is a valid path.
Args:
pathstr: The path as string.
cwd: The current working directory, or None.
relative: Whether to resolve relative files.
check_exists: Whether to check if the file
actually exists of filesystem.
Return:
The path if it is a valid path, None otherwise.
"""
pathstr = pathstr.strip()
log.url.debug("Checking if {!r} is a path".format(pathstr))
expanded = os.path.expanduser(pathstr)
if os.path.isabs(expanded):
path = expanded
elif relative and cwd:
path = os.path.join(cwd, expanded)
elif relative:
try:
path = os.path.abspath(expanded)
except OSError:
path = None
else:
path = None
if check_exists:
if path is not None and os.path.exists(path):
log.url.debug("URL is a local file")
else:
path = None
return path
def filename_from_url(url):
"""Get a suitable filename from an URL.

View File

@ -237,7 +237,7 @@ KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt',
# Available command completions
Completion = enum('Completion', ['command', 'section', 'option', 'value',
'helptopic', 'quickmark_by_name',
'bookmark_by_url', 'url', 'sessions'])
'bookmark_by_url', 'url', 'tab', 'sessions'])
# Exit statuses for errors. Needs to be an int for sys.exit.

View File

@ -1,8 +1,8 @@
Jinja2==2.8.0
MarkupSafe==0.23
Pygments==2.1.1
Pygments==2.1.3
pyPEG2==2.15.2
PyYAML==3.11
colorama==0.3.6
colorama==0.3.7
colorlog==2.6.1
cssutils==1.0.1

View File

@ -76,6 +76,7 @@ class AsciiDoc:
self._build_website()
else:
self._build_docs()
self._copy_images()
def _build_docs(self):
"""Render .asciidoc files to .html sites."""
@ -84,8 +85,38 @@ class AsciiDoc:
name, _ext = os.path.splitext(os.path.basename(src))
dst = 'qutebrowser/html/doc/{}.html'.format(name)
files.append((src, dst))
# patch image links to use local copy
replacements = [
("http://qutebrowser.org/img/cheatsheet-big.png",
"qute://help/img/cheatsheet-big.png"),
("http://qutebrowser.org/img/cheatsheet-small.png",
"qute://help/img/cheatsheet-small.png")
]
for src, dst in files:
self.call(src, dst)
src_basename = os.path.basename(src)
modified_src = os.path.join(self._tempdir, src_basename)
with open(modified_src, 'w', encoding='utf-8') as modified_f, \
open(src, 'r', encoding='utf-8') as f:
for line in f:
for orig, repl in replacements:
line = line.replace(orig, repl)
modified_f.write(line)
self.call(modified_src, dst)
def _copy_images(self):
"""Copy image files to qutebrowser/html/doc."""
print("Copying files...")
dst_path = os.path.join('qutebrowser', 'html', 'doc', 'img')
try:
os.mkdir(dst_path)
except FileExistsError:
pass
for filename in ['cheatsheet-big.png', 'cheatsheet-small.png']:
src = os.path.join('doc', 'img', filename)
dst = os.path.join(dst_path, filename)
shutil.copy(src, dst)
def _build_website_file(self, root, filename):
"""Build a single website file."""
@ -249,6 +280,8 @@ def main(colors=False):
"asciidoc.py. If not given, it's searched in PATH.",
nargs=2, required=False,
metavar=('PYTHON', 'ASCIIDOC'))
parser.add_argument('--no-authors', help=argparse.SUPPRESS,
action='store_true')
args = parser.parse_args()
try:
os.mkdir('qutebrowser/html/doc')

View File

@ -91,7 +91,7 @@ def smoke_test(executable):
def build_windows():
"""Build windows executables/setups."""
utils.print_title("Updating 3rdparty content")
update_3rdparty.main()
update_3rdparty.update_pdfjs()
utils.print_title("Building Windows binaries")
parts = str(sys.version_info.major), str(sys.version_info.minor)

View File

@ -65,6 +65,8 @@ PERFECT_FILES = [
'qutebrowser/browser/network/filescheme.py'),
('tests/unit/browser/network/test_networkreply.py',
'qutebrowser/browser/network/networkreply.py'),
('tests/unit/browser/network/test_pastebin.py',
'qutebrowser/browser/network/pastebin.py'),
('tests/unit/browser/test_signalfilter.py',
'qutebrowser/browser/signalfilter.py'),
@ -102,6 +104,8 @@ PERFECT_FILES = [
'qutebrowser/mainwindow/statusbar/textbase.py'),
('tests/unit/mainwindow/statusbar/test_prompt.py',
'qutebrowser/mainwindow/statusbar/prompt.py'),
('tests/unit/mainwindow/statusbar/test_url.py',
'qutebrowser/mainwindow/statusbar/url.py'),
('tests/unit/config/test_configtypes.py',
'qutebrowser/config/configtypes.py'),
@ -223,8 +227,16 @@ def main_check():
print(e)
messages = []
for msg in messages:
print(msg.text)
if messages:
print()
print()
utils.print_title("Coverage check failed")
for msg in messages:
print(msg.text)
print()
print("You can run 'tox -e py35-cov' (or py34-cov) locally and check "
"htmlcov/index.html to debug this.")
print()
if 'CI' in os.environ:
print("Keeping coverage.xml on CI.")

View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 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/>.
"""Check if docs changed and output an error if so."""
import sys
import subprocess
code = subprocess.call(['git', '--no-pager', 'diff', '--exit-code', '--stat'])
if code != 0:
print()
print('The autogenerated docs changed, please run this to update them:')
print(' tox -e docs')
print(' git commit -am "Update docs"')
print()
print('(Or you have uncommitted changes, in which case you can ignore '
'this.)')
sys.exit(code)

View File

@ -34,19 +34,20 @@ import sys
import subprocess
import urllib
import contextlib
import time
try:
import _winreg as winreg
except ImportError:
winreg = None
TESTENV = os.environ['TESTENV']
TESTENV = os.environ.get('TESTENV', None)
TRAVIS_OS = os.environ.get('TRAVIS_OS_NAME', None)
INSTALL_PYQT = TESTENV in ('py34', 'py35', 'py34-cov', 'py35-cov',
'unittests-nodisp', 'vulture', 'pylint')
'unittests-nodisp', 'vulture', 'pylint', 'docs')
XVFB = TRAVIS_OS == 'linux' and TESTENV == 'py34'
pip_packages = ['tox']
if TESTENV.endswith('-cov'):
if TESTENV is not None and TESTENV.endswith('-cov'):
pip_packages.append('codecov')
@ -68,14 +69,15 @@ def folded_cmd(argv):
subprocess.check_call(argv)
def fix_sources_list():
"""The mirror used by Travis has trouble a lot, so switch to another."""
subprocess.check_call(['sudo', 'sed', '-i', r's/us-central1\.gce/us/',
'/etc/apt/sources.list'])
def apt_get(args):
folded_cmd(['sudo', 'apt-get', '-y', '-q'] + args)
try:
folded_cmd(['sudo', 'apt-get', '-y', '-q'] + args)
except subprocess.CalledProcessError:
print()
print("apt-get failed... trying a second time in 30s...")
print()
time.sleep(30)
folded_cmd(['sudo', 'apt-get', '-y', '-q'] + args)
def brew(args):
@ -108,6 +110,7 @@ if 'APPVEYOR' in os.environ:
print("Installing PyQt5...")
subprocess.check_call([r'C:\install-PyQt5.exe', '/S'])
folded_cmd([r'C:\Python34\python', '-m', 'pip', 'install', '-U', 'pip'])
folded_cmd([r'C:\Python34\Scripts\pip', 'install', '-U'] + pip_packages)
print("Linking Python...")
@ -115,8 +118,11 @@ if 'APPVEYOR' in os.environ:
f.write(r'@C:\Python34\python %*')
check_setup(r'C:\Python34\python')
elif TRAVIS_OS == 'linux' and 'DOCKER' in os.environ:
pass
elif TRAVIS_OS == 'linux':
folded_cmd(['sudo', 'pip', 'install'] + pip_packages)
folded_cmd(['sudo', '-H', 'pip', 'install', '-U', 'pip'])
folded_cmd(['sudo', '-H', 'pip', 'install', '-U'] + pip_packages)
pkgs = []
@ -126,20 +132,21 @@ elif TRAVIS_OS == 'linux':
pkgs += ['python3-pyqt5', 'python3-pyqt5.qtwebkit']
if TESTENV == 'eslint':
pkgs += ['npm', 'nodejs', 'nodejs-legacy']
if TESTENV == 'docs':
pkgs += ['asciidoc']
if pkgs:
fix_sources_list()
apt_get(['update'])
apt_get(['install'] + pkgs)
apt_get(['install', '--no-install-recommends'] + pkgs)
if TESTENV == 'flake8':
fix_sources_list()
apt_get(['update'])
# We need an up-to-date Python because of:
# https://github.com/google/yapf/issues/46
apt_get(['install', '-t', 'trusty-updates', 'python3.4'])
if TESTENV == 'eslint':
folded_cmd(['sudo', 'npm', 'install', '-g', 'npm'])
folded_cmd(['sudo', 'npm', 'install', '-g', 'eslint'])
else:
check_setup('python3')

View File

@ -0,0 +1,15 @@
#!/bin/bash
if [[ $DOCKER ]]; then
# To build a fresh image:
# docker build -t img misc/docker/$DOCKER
# docker run --privileged -v $PWD:/outside img
docker run --privileged -v $PWD:/outside \
thecompiler/qutebrowser-manual:$DOCKER
else
args=()
[[ $TESTENV == docs ]] && args=('--no-authors')
tox -e $TESTENV -- "${args[@]}"
fi

View File

@ -86,7 +86,9 @@ def get_build_exe_options(skip_html=False):
'include_msvcr': True,
'includes': [],
'excludes': ['tkinter'],
'packages': ['pygments', 'pkg_resources._vendor.packaging'],
'packages': ['pygments', 'pkg_resources._vendor.packaging',
'pkg_resources._vendor.pyparsing',
'pkg_resources._vendor.six'],
}

View File

@ -482,6 +482,19 @@ def regenerate_manpage(filename):
_format_block(filename, 'options', options)
def regenerate_cheatsheet():
"""Generate cheatsheet PNGs based on the SVG."""
files = [
('doc/img/cheatsheet-small.png', 300, 185),
('doc/img/cheatsheet-big.png', 3342, 2060),
]
for filename, x, y in files:
subprocess.check_call(['inkscape', '-e', filename, '-b', 'white',
'-w', str(x), '-h', str(y),
'misc/cheatsheet.svg'])
def main():
"""Regenerate all documentation."""
utils.change_cwd()
@ -491,8 +504,12 @@ def main():
generate_settings('doc/help/settings.asciidoc')
print("Generating command help...")
generate_commands('doc/help/commands.asciidoc')
print("Generating authors in README...")
regenerate_authors('README.asciidoc')
if '--no-authors' not in sys.argv:
print("Generating authors in README...")
regenerate_authors('README.asciidoc')
if '--cheatsheet' in sys.argv:
print("Regenerating cheatsheet .pngs")
regenerate_cheatsheet()
if '--html' in sys.argv:
asciidoc2html.main()

View File

@ -31,7 +31,8 @@ import os
def get_latest_pdfjs_url():
"""Get the URL of the latest pdf.js prebuilt package.
Returns a (version, url)-tuple."""
Returns a (version, url)-tuple.
"""
github_api = 'https://api.github.com'
endpoint = 'repos/mozilla/pdf.js/releases/latest'
request_url = '{}/{}'.format(github_api, endpoint)
@ -64,6 +65,8 @@ def update_pdfjs(target_version=None):
url = ('https://github.com/mozilla/pdf.js/releases/download/'
'v{0}/pdfjs-{0}-dist.zip').format(target_version)
os.chdir(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'..', '..'))
target_path = os.path.join('qutebrowser', '3rdparty', 'pdfjs')
print("=> Downloading pdf.js {}".format(version))
try:

View File

@ -21,7 +21,7 @@
"""Tool to import data from other browsers.
Currently only importing bookmarks from Chromium is supported.
Currently only importing bookmarks from Netscape Bookmark files is supported.
"""
@ -30,34 +30,46 @@ import argparse
def main():
args = get_args()
if args.browser == 'chromium':
import_chromium(args.bookmarks)
if args.browser in ['chromium', 'firefox', 'ie']:
import_netscape_bookmarks(args.bookmarks, args.bookmark_format)
def get_args():
"""Get the argparse parser."""
parser = argparse.ArgumentParser(
epilog="To import bookmarks from Chromium, export them to HTML in "
"Chromium's bookmark manager.")
parser.add_argument('browser', help="Which browser?", choices=['chromium'],
epilog="To import bookmarks from Chromium, Firefox or IE, "
"export them to HTML in your browsers bookmark manager. "
"By default, this script will output in a quickmarks format.")
parser.add_argument('browser', help="Which browser? (chromium, firefox)",
choices=['chromium', 'firefox', 'ie'],
metavar='browser')
parser.add_argument('bookmarks', help="Bookmarks file")
parser.add_argument('-b', help="Output in bookmark format.",
dest='bookmark_format', action='store_true',
default=False, required=False)
parser.add_argument('bookmarks', help="Bookmarks file (html format)")
args = parser.parse_args()
return args
def import_chromium(bookmarks_file):
"""Import bookmarks from a HTML file generated by Chromium."""
def import_netscape_bookmarks(bookmarks_file, is_bookmark_format):
"""Import bookmarks from a NETSCAPE-Bookmark-file v1.
Generated by Chromium, Firefox, IE and possibly more browsers
"""
import bs4
with open(bookmarks_file, encoding='utf-8') as f:
soup = bs4.BeautifulSoup(f, 'html.parser')
html_tags = soup.findAll('a')
if is_bookmark_format:
output_template = '{tag[href]} {tag.string}'
else:
output_template = '{tag.string} {tag[href]}'
bookmarks = []
for tag in html_tags:
if tag['href'] not in bookmarks:
bookmarks.append('{tag.string} {tag[href]}'.format(tag=tag))
bookmarks.append(output_template.format(tag=tag))
for bookmark in bookmarks:
print(bookmark)

View File

@ -0,0 +1,13 @@
#include <QApplication>
#include <QWebView>
#include <QUrl>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWebView view;
view.load(QUrl(argv[1]));
view.show();
return app.exec();
}

View File

@ -0,0 +1,6 @@
QT += core widgets webkit webkitwidgets
TARGET = testbrowser
TEMPLATE = app
SOURCES += main.cpp

View File

@ -22,7 +22,6 @@ exclude = .venv,.hypothesis,.git,__pycache__,resources.py
# P101: format string does contain unindexed parameters
# P102: docstring does contain unindexed parameters
# P103: other string does contain unindexed parameters
# D001: found assert_ replace it with assertTrue
# D102: Missing docstring in public method (will be handled by others)
# D103: Missing docstring in public function (will be handled by others)
# D104: Missing docstring in public package (will be handled by others)
@ -31,27 +30,29 @@ exclude = .venv,.hypothesis,.git,__pycache__,resources.py
# D211: No blank lines allowed before class docstring
# (PEP257 got changed, but let's stick to the old standard)
# D402: First line should not be function's signature (false-positives)
# FI10 - FI15: __future__ import missing
# H201: bare except
# H238: Use new-stule classes
# H301: one import per line
# H306: imports not in alphabetical order
ignore =
E128,E226,E265,E501,E402,E266,
F401,
N802,
L101,L102,L103,L201,L202,L203,L204,L207,L302,
P101,P102,P103,
D001,
D102,D103,D104,D105,D209,D211,D402
D102,D103,D104,D105,D209,D211,D402,
FI10,FI11,FI12,FI13,FI14,FI15,
H201,H238,H301,H306
max-complexity = 12
putty-auto-ignore = True
putty-ignore =
/# pylint: disable=invalid-name/ : +N801,N806
/# pylint: disable=wildcard-import/ : +F403
/# pragma: no mccabe/ : +C901
/# flake8: disable=E131/ : +E131
/# flake8: disable=N803/ : +N803
/# flake8: disable=T002/ : +T002
/# flake8: disable=F841/ : +F841
/# flake8: disable=S001/ : +S001
tests/*/*/test_*.py : +D100,D101,D401
tests/*/test_*.py : +D100,D101,D401
tests/unit/browser/http/test_content_disposition.py : +D400
scripts/dev/ci/install.py : +C901,FI53
copyright-check = True
copyright-regexp = # Copyright [\d-]+ .*
copyright-min-file-size = 110

View File

@ -54,7 +54,6 @@ def _apply_platform_markers(item):
"Can't be run when frozen"),
('frozen', not getattr(sys, 'frozen', False),
"Can only run when frozen"),
('skip', True, "Always skipped."),
('pyqt531_or_newer', PYQT_VERSION < 0x050301,
"Needs PyQt 5.3.1 or newer"),
('ci', 'CI' not in os.environ, "Only runs on CI."),
@ -101,13 +100,6 @@ def pytest_collection_modifyitems(items):
for item in items:
if 'qapp' in getattr(item, 'fixturenames', ()):
item.add_marker('gui')
if sys.platform == 'linux' and not os.environ.get('DISPLAY', ''):
if ('CI' in os.environ and
not os.environ.get('QUTE_NO_DISPLAY', '')):
raise Exception("No display available on CI!")
skip_marker = pytest.mark.skipif(
True, reason="No DISPLAY available")
item.add_marker(skip_marker)
if hasattr(item, 'module'):
module_path = os.path.relpath(
@ -152,12 +144,15 @@ def pytest_addoption(parser):
@pytest.fixture(scope='session', autouse=True)
def prevent_xvfb_on_buildbot(request):
def check_display(request):
if (not request.config.getoption('--no-xvfb') and
'QUTE_BUILDBOT' in os.environ and
request.config.xvfb is not None):
raise Exception("Xvfb is running on buildbot!")
if sys.platform == 'linux' and not os.environ.get('DISPLAY', ''):
raise Exception("No display and no Xvfb available!")
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):

View File

@ -38,6 +38,10 @@ from qutebrowser.utils import objreg
from PyQt5.QtCore import QEvent, QSize, Qt
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout
from PyQt5.QtNetwork import QNetworkCookieJar
try:
from PyQt5 import QtWebEngineWidgets
except ImportError as e:
QtWebEngineWidgets = None
class WinRegistryHelper:
@ -213,6 +217,14 @@ def qnam(qapp):
return nam
@pytest.fixture
def webengineview():
"""Get a QWebEngineView if QtWebEngine is available."""
if QtWebEngineWidgets is None:
pytest.skip("QtWebEngine unavailable")
return QtWebEngineWidgets.QWebEngineView()
@pytest.fixture
def webpage(qnam):
"""Get a new QWebPage object."""

View File

@ -22,6 +22,7 @@
import re
import pprint
import os.path
def print_i(text, indent, error=False):
@ -101,3 +102,13 @@ def pattern_match(*, pattern, value):
"""
re_pattern = '.*'.join(re.escape(part) for part in pattern.split('*'))
return re.fullmatch(re_pattern, value) is not None
def abs_datapath():
"""Get the absolute path to the integration data directory.
Return:
The absolute path to the tests/integration/data directory.
"""
file_abs = os.path.abspath(os.path.dirname(__file__))
return os.path.join(file_abs, '..', 'integration', 'data')

View File

@ -0,0 +1,11 @@
<html>
<head>
<title>quteprocess.click_element test</title>
</head>
<body>
<span onclick='console.log("click_element clicked")'>Test Element</span>
<span onclick='console.log("click_element special chars")'>"Don't", he shouted</span>
<span>Duplicate</span>
<span>Duplicate</span>
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Wrong filename when using data: links</title>
</head>
<body>
<a href="data:;base64,cXV0ZWJyb3dzZXI=">download</a>
</body>
</html>

Binary file not shown.

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>A link to use hints on</title>
</head>
<body>
<a href="/data/hello.txt" target="_blank">Follow me!</a>
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>A link to use hints on</title>
</head>
<body>
<a href="/data/hello.txt" target="_blank"><span style="font-size: large">Follow me!</span></a>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test title</title>
<script type="text/javascript">
window.onload = function () {
console.log("Calling history.replaceState");
history.replaceState({}, '', window.location + '?state=2');
}
</script>
</head>
<body>
This page calls history.replaceState() via JS.
</body>
</html>

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