Compare commits

..

274 Commits
v1.2.0 ... osx

Author SHA1 Message Date
Florian Bruhin
11f930db0d Add more macOS versions 2018-04-24 10:55:37 +02:00
Florian Bruhin
b4f877d991 Update changelog 2018-04-24 09:44:54 +02:00
Florian Bruhin
fa41af63b6 Always set FocusOnNavigationEnabled
This fixes some focus issues after Qt 5.11 changes. There might be better ways
to solve them, but for now, this will work.

See https://codereview.qt-project.org/#/c/221408/10 and #3661:
https://github.com/qutebrowser/qutebrowser/issues/3661#issuecomment-375969315

Might also negatively affect #3834 as it essentially reintroduces QTBUG-52999 on
any Qt version: https://bugreports.qt.io/browse/QTBUG-52999

Might be reverted at a later date, but for now, I want an easy way to make tests
work on Qt 5.11 to spot further issues.
2018-04-23 16:57:10 +02:00
Florian Bruhin
e789296b7f Handle new focus object for Qt 5.11
See https://codereview.qt-project.org/#/c/221408/10 and #3661:
https://github.com/qutebrowser/qutebrowser/issues/3661#issuecomment-375969315
2018-04-23 16:54:47 +02:00
Florian Bruhin
bc9a8dd63f Handle focusProxy being None
This fixes running with Qt 5.11

See https://codereview.qt-project.org/#/c/221408/10 and #3661:
https://github.com/qutebrowser/qutebrowser/issues/3661#issuecomment-375969315
2018-04-23 16:52:53 +02:00
Florian Bruhin
6640768860 Enable libGL workaround on any system where it's available
Fixes #3772
2018-04-23 11:20:56 +02:00
Florian Bruhin
178eeaed0d Update changelog 2018-04-17 07:49:10 +02:00
Florian Bruhin
f1967718b7 Merge remote-tracking branch 'origin/pr/3791' 2018-04-17 07:48:46 +02:00
Florian Bruhin
1021c3a330 Merge remote-tracking branch 'origin/pr/3826' 2018-04-16 17:26:59 +02:00
Florian Bruhin
ec57e58530 Update changelog 2018-04-16 17:21:42 +02:00
Florian Bruhin
06a8a68fcb Merge remote-tracking branch 'origin/pr/3844' 2018-04-16 17:21:13 +02:00
Florian Bruhin
4a78519b63 Mark opening/closing window via JS test as flaky 2018-04-16 17:14:47 +02:00
Florian Bruhin
d2207f66f1 Skip test_set_error entirely
See #3771
2018-04-16 17:14:14 +02:00
Florian Bruhin
23d4d72f3b Update changelog 2018-04-16 17:05:10 +02:00
Florian Bruhin
4a93389356 Merge remote-tracking branch 'origin/pr/3813' 2018-04-16 17:04:53 +02:00
Sebastian Heinlein
3704e3ddd5 Fix DESTDIR and PREFIX in makefile 2018-04-16 13:44:22 +02:00
Florian Bruhin
643bf4bc20 Remove 32-bit check in build_release.py
This is probably not needed anymore, see #3842
2018-04-16 08:33:01 +02:00
Florian Bruhin
b89b38fd3c Use latest release of PyInstaller
This is as a stop-gap solution for #3842.
2018-04-16 08:28:24 +02:00
Jay Kamat
0829511221 Merge pull request #3803 from toofar/fix/greasemonkey_includes_fallback
Greasemonkey: fix default include value
2018-04-13 18:26:46 -04:00
Jay Kamat
48b865073c Update changelog 2018-04-13 12:21:12 -04:00
Jay Kamat
3f9099613b Merge pull request #3807 from slackhead/tabs.mute_messages
Add option to mute the Last Tab/First Tab messages when tabs.wrap is false
2018-04-13 12:11:45 -04:00
Jay Kamat
77fa0730c8 Merge pull request #3802 from jgkamat/jay/tab-take-completion
Fix win_id 0 always being included in :tab-take completion
2018-04-10 16:24:48 -04:00
Jay Kamat
ed76d689b0 Add test for completing other buffer excluding id0 2018-04-10 02:45:12 -04:00
pyup-bot
ca311f8588 Update tox from 2.9.1 to 3.0.0 2018-04-09 18:13:31 +02:00
pyup-bot
849e427231 Update pytest-mock from 1.7.1 to 1.8.0 2018-04-09 18:13:29 +02:00
pyup-bot
9e628901e9 Update pytest-bdd from 2.20.0 to 2.21.0 2018-04-09 18:13:27 +02:00
pyup-bot
28126055da Update py-cpuinfo from 3.3.0 to 4.0.0 2018-04-09 18:13:26 +02:00
pyup-bot
3d75d86123 Update hypothesis from 3.52.0 to 3.55.1 2018-04-09 18:13:24 +02:00
pyup-bot
03ea07e99f Update cheroot from 6.0.0 to 6.1.2 2018-04-09 18:13:23 +02:00
pyup-bot
780ced8a52 Update pylint from 1.8.3 to 1.8.4 2018-04-09 18:13:21 +02:00
pyup-bot
fc33b065c2 Update astroid from 1.6.2 to 1.6.3 2018-04-09 18:13:20 +02:00
pyup-bot
03b7459b00 Update github3.py from 1.0.1 to 1.0.2 2018-04-09 18:13:18 +02:00
pyup-bot
f964bf1b67 Update github3.py from 1.0.1 to 1.0.2 2018-04-09 18:13:17 +02:00
pyup-bot
ef2a2702f5 Update wheel from 0.30.0 to 0.31.0 2018-04-09 18:13:15 +02:00
pyup-bot
6374b6dd4c Update flake8-builtins from 1.1.1 to 1.2.2 2018-04-09 18:13:14 +02:00
toofar
69d642cab8 Merge pull request #3792 from toofar/fix/gm_addstyle_earlyload
Greasemonkey: fix early GM_addStyle with fast sites.
Seems to be a working fix, despite noone having clarity on what is the ideal approach.
2018-04-09 19:11:10 +12:00
Slackhead
62aa9bdbb3 Added debug() logging for next/prev-tab and test scenarios 2018-04-09 02:03:02 +01:00
Florian Bruhin
e35c91043e pyinstaller: Use '.' as path for git-commit-id
See #384
2018-04-08 20:46:43 +02:00
Slackhead
fac546e9b4 Remove test scenarios for last/first tab when wrap is off 2018-04-08 18:56:16 +01:00
Jay Kamat
b74ddc3493 Merge pull request #3820 from AlvaroLuken/master
Added surrounding gray box to launch command for Ubuntu + Tox
2018-04-08 13:39:16 -04:00
Slackhead
b7964d9baf Remove first/last tab messages 2018-04-08 10:31:12 +01:00
AlvaroLuken
feb2f99ea9 Added surrounding gray box to launch command for Ubuntu + Tox 2018-04-05 11:22:38 -04:00
Jay Kamat
d0d5ad2eda Stop read timer when download is cancelled 2018-04-04 01:17:37 -04:00
Slackhead
eb18f0a2ac Adjust the help docs entry 2018-04-03 17:49:51 +01:00
Slackhead
39c08cb582 Add option to mute the Last Tab/First Tab messages when tabs.wrap is false 2018-04-03 16:27:04 +01:00
Jimmy
164ea98a5b Greasemonkey: fix default include value
Greasemonkey scripts are supposed to default to running on all pages.
@jgkamat and @nemanjan00 repurted some script not running on all pages
unless they either removed (or broke) the metadata block or added an
include directive. Indeed I had a logic error when it only defaulted to
being included on all pages when no metadata block at all was included.
Whoops.
2018-04-03 20:11:15 +12:00
Jay Kamat
3b2c0823af Fix win_id 0 always being included in :tab-take completion 2018-04-02 20:34:34 -04:00
Florian Bruhin
79823a9a0b Regenerate docs 2018-04-01 13:22:01 +02:00
Jimmy
1c0616f3ec Greasemonkey: fix early addstyle with fast sites.
nemanjan00 reported this script not having any effect da850e49cc/duckduckgo-deepdark.user.js

It turns out that the previous implementation of GM_addStyle was relying
on `document.onreadystatechange` when the script ran early enough that
there was no `head` element. That event wasn't getting fired for the
main frame of duckduckgo.com for whatever reason. Maybe using
`DOMContentLoaded` or something would have worked but I just copied the
fallback in the above linked script which seems to work just fine.
2018-04-01 12:38:48 +12:00
pylipp
599a3d75a4 Add userscript to download bibtex for DOI scraped from current tab 2018-03-31 21:43:20 +02:00
Florian Bruhin
6151d077e5 Update changelog 2018-03-30 11:49:39 +02:00
Florian Bruhin
d438aa15fa Simplify setting _qute_script_id 2018-03-30 11:48:06 +02:00
Jay Kamat
7f5a79cdfd Escape strings with string_escape rather than tojson 2018-03-30 01:40:49 -04:00
Florian Bruhin
5bc794f85a Update changelog 2018-03-28 21:11:20 +02:00
Florian Bruhin
d7455bcdba Merge remote-tracking branch 'origin/pr/3765' into adblock 2018-03-28 20:32:47 +02:00
Florian Bruhin
0b667e4701 Merge remote-tracking branch 'origin/pr/3763' into adblock 2018-03-28 20:32:39 +02:00
George Edward Bulmer
2789bec1e7 Modify assert_url to treat localhost differently 2018-03-28 14:27:17 +01:00
George Edward Bulmer
1ccb464d1c Return removed comment about hosts format 2018-03-28 14:17:13 +01:00
Florian Bruhin
758ea8b171 Update changelog 2018-03-28 09:36:32 +02:00
Florian Bruhin
32568a6da4 Simplify tests 2018-03-28 09:33:27 +02:00
Florian Bruhin
14792472db Merge remote-tracking branch 'origin/pr/3680' 2018-03-28 09:29:54 +02:00
Florian Bruhin
005fa8b675 Fix newline 2018-03-28 09:14:26 +02:00
Florian Bruhin
c7e5033eaa Set MainWindow as parent of TabbedBrowser
If we close the MainWindow (and it gets deleted), we need to make sure to delete
the TabbedBrowser as well.

Fixes #3781
2018-03-28 08:58:07 +02:00
Florian Bruhin
a828851640 Update recommended Qt version in readme 2018-03-28 08:48:18 +02:00
Jussi Timperi
046a3dc159 Add option to only show favicons for pinned tabs
Closes #3440
2018-03-28 00:45:57 +03:00
Florian Bruhin
68b707c749 Update changelog 2018-03-27 12:01:42 +02:00
Florian Bruhin
9cff0e7367 Merge remote-tracking branch 'origin/pr/3742' 2018-03-27 12:01:18 +02:00
Florian Bruhin
7e66c3cf46 Update changelog 2018-03-27 11:11:34 +02:00
Florian Bruhin
bf4aab79ac Merge remote-tracking branch 'origin/pr/3751' 2018-03-27 11:11:12 +02:00
Florian Bruhin
a6f6fdf19b Update docs 2018-03-27 11:09:18 +02:00
Florian Bruhin
e5ffcbd49f Merge remote-tracking branch 'origin/pr/3750' 2018-03-27 11:07:29 +02:00
Florian Bruhin
730347e449 Merge branch 'pyup-scheduled-update-2018-03-26' 2018-03-27 10:27:59 +02:00
Florian Bruhin
55112b52e0 Merge remote-tracking branch 'origin/pr/3776' 2018-03-27 10:25:41 +02:00
Florian Bruhin
2249a88e3a Make test_simple_js_webengine work correctly 2018-03-27 07:29:43 +02:00
Florian Bruhin
6dbd6d1ddf Move test_qt_javascript.py 2018-03-27 07:14:05 +02:00
Florian Bruhin
6ecea8ef17 Revert accidental changes to test_qt_javascript.py 2018-03-27 07:13:17 +02:00
Florian Bruhin
021bb25622 Add regenerating website to contributing.asciidoc
[ci skip]

(cherry picked from commit 9f95736bbe4a00c9cc4a8b222ab3dc55d6bdf96c)
2018-03-26 22:53:46 +02:00
Florian Bruhin
22e887045b Remove flake8-builtins filter 2018-03-26 18:27:15 +02:00
pyup-bot
4896765fcc Update virtualenv from 15.1.0 to 15.2.0 2018-03-26 18:17:25 +02:00
pyup-bot
d98590d712 Update pytest-faulthandler from 1.4.1 to 1.5.0 2018-03-26 18:17:23 +02:00
pyup-bot
496e6fc624 Update pytest from 3.4.2 to 3.5.0 2018-03-26 18:17:22 +02:00
pyup-bot
55a818b156 Update py from 1.5.2 to 1.5.3 2018-03-26 18:17:20 +02:00
pyup-bot
85be0f2801 Update py from 1.5.2 to 1.5.3 2018-03-26 18:17:19 +02:00
pyup-bot
b7b4cc7f31 Update hypothesis from 3.50.0 to 3.52.0 2018-03-26 18:17:17 +02:00
pyup-bot
056a901da0 Update python-dateutil from 2.7.0 to 2.7.2 2018-03-26 18:17:16 +02:00
pyup-bot
ba9b166962 Update python-dateutil from 2.7.0 to 2.7.2 2018-03-26 18:17:15 +02:00
pyup-bot
edf2652431 Update flake8-builtins from 1.0.post0 to 1.1.1 2018-03-26 18:17:13 +02:00
Florian Bruhin
6755e17630 Update changelog 2018-03-26 10:54:15 +02:00
Florian Bruhin
d4899240de Break long lines 2018-03-26 10:51:04 +02:00
Florian Bruhin
ddbb6b5198 Merge remote-tracking branch 'origin/pr/3769' 2018-03-26 10:45:31 +02:00
Florian Bruhin
1087ce075d Merge remote-tracking branch 'origin/pr/3767' 2018-03-26 10:45:23 +02:00
Philip Lewis
cecb79cf05 Fix keyhints for special characters
`prefix` is a string and `seq` is a key sequence, so removing `len(prefix)`
items from `seq` will remove too many if `prefix` contains a special character
(ex "<Ctrl+x>").  Remove the number of characters from `str(seq)` instead.
2018-03-25 15:18:02 -04:00
Florian Bruhin
d4ea1df232 Improve window_open.html tests 2018-03-25 19:56:48 +02:00
Florian Bruhin
91ca7d0911 tests: Rename close function in window_open.html
Naming it close() conflicts with the global JS close()
2018-03-25 19:39:34 +02:00
bitraid
68e3ad6cba Pyinstaller: don't use upx 2018-03-25 19:43:59 +03:00
Florian Bruhin
12a405965a Make QtWebEngine inspector work with JS disabled 2018-03-25 14:55:03 +02:00
George Edward Bulmer
a85ac1725f Missing fullstop in a docstring 2018-03-24 22:56:47 +00:00
George Edward Bulmer
eb5684e5f7 Pylint fix 2018-03-24 21:52:26 +00:00
George Edward Bulmer
b9bcad9c14 Grammar change 2018-03-24 21:13:22 +00:00
George Edward Bulmer
64b01cc076 Remove extraneous part 2018-03-24 21:10:23 +00:00
George Edward Bulmer
1380fef600 Add test for parsing multiple lines 2018-03-24 21:08:55 +00:00
George Edward Bulmer
8809ef02a1 Add support for more than 1 host on a given line 2018-03-24 20:20:16 +00:00
George Edward Bulmer
3f37fcf8fa Modify tests, localhost should never be blocked 2018-03-24 20:15:34 +00:00
George Edward Bulmer
c8db9e1c76 Remove WHITELISTED, making file parsing satisfy:
1) 'dotless' hosts, e.g. localhost, cannot be blocked by a file
2) hosts ending in '.localdomain' cannot be blocked by a file
2018-03-24 19:42:34 +00:00
George Edward Bulmer
01d8314dd8 Change default blocklist to StevenBlack combined 2018-03-24 18:35:03 +00:00
rien333
fa21d280fa Remove unnecessary hide operation 2018-03-24 05:09:03 +01:00
rien333
e211801e16 Handle wayland decoration option rename through configdata.yml 2018-03-23 15:24:18 +01:00
rien333
6db1ab0a58 Cosmetic changes 2018-03-23 15:21:02 +01:00
rien333
aa70395925 Merge branch 'master' of https://github.com/rien333/qutebrowser 2018-03-23 15:19:58 +01:00
rien333
880b33fff5 Restore correct window visibility after decoration config change 2018-03-23 15:19:37 +01:00
Florian Bruhin
f1789effdc Stabilize navigate.feature on Qt 5.11
Looks like we get qute://help as URL from the previous test otherwise?
See #3661
2018-03-23 10:29:25 +01:00
Florian Bruhin
e095f64eb6 Merge remote-tracking branch 'origin/pr/3752' 2018-03-23 08:25:59 +01:00
Florian Bruhin
06df88075e Merge remote-tracking branch 'origin/pr/3749' 2018-03-23 08:25:49 +01:00
Florian Bruhin
00bdb60627 Ignore "Dropping message on closed channel." message
This seems to happen with this test in tabs.feature with Qt 5.11:
Scenario: :buffer with wrong argument (-1)

It only happens ~1/50 times though, and seems like some Qt bug.

See #3661
2018-03-23 07:59:46 +01:00
rien333
1fc0abb064 Delete .#configfiles.py 2018-03-23 02:50:36 +01:00
rien333
e2250d65e9 Merge branch 'master' of git://github.com/qutebrowser/qutebrowser 2018-03-23 01:43:23 +01:00
rien333
bb0c79b5a2 Fix wayland test 2018-03-23 01:38:45 +01:00
rien333
ff299c87a8 Reinsert wayland specific code for toggling decoration visibility 2018-03-22 23:32:37 +01:00
rien333
2d2bdad2ca Do not require restart after decoration option change 2018-03-22 23:26:45 +01:00
Jay Kamat
07d043fe81 Add basic tests for tab width sizing 2018-03-22 14:27:33 -04:00
Jay Kamat
477da6002a Fix minimum size for vertical tabs 2018-03-22 12:59:35 -04:00
Jay Kamat
d2c01d7ee6 Always display plain titles in tab tooltips
Closes #3741
2018-03-22 12:03:15 -04:00
Florian Bruhin
b67a031151 Rephrase autoconfig.yml docs
[ci skip]
2018-03-22 08:37:12 +01:00
rien333
764e79e505 Small refactor 2018-03-22 03:44:24 +01:00
rien333
7b7faa9f66 Fix silly redefinition 2018-03-22 03:42:57 +01:00
rien333
a6b92dbbd3 General window decoration hiding option 2018-03-22 02:23:21 +01:00
bitraid
4fb940241c Add 32bit color for Windows icon 2018-03-21 23:13:26 +02:00
George Edward Bulmer
51f9464eb2 Add test for hints with numberpad numbers 2018-03-21 17:06:13 +00:00
George Edward Bulmer
0645865d22 Add test case for is_special in keyutils 2018-03-21 16:38:58 +00:00
George Edward Bulmer
991ba54499 Change the formatting of the numpad keys
This makes it consistent with as before
2018-03-21 15:41:08 +00:00
George Edward Bulmer
1cf3d66a22 Revert test and modify returned key 2018-03-21 15:34:32 +00:00
Florian Bruhin
300d873b18 Update Debian install instructions
Supersedes #3707
Fixes #3737

[ci skip]
2018-03-21 10:55:54 +01:00
Florian Bruhin
a8bbd5fa4d Update docs for TimestampTemplate 2018-03-21 10:14:48 +01:00
Florian Bruhin
2d655a7230 Update changelog 2018-03-21 08:27:09 +01:00
George Edward Bulmer
4d7f8e4878 Pylint fix 2018-03-21 00:28:52 +00:00
George Edward Bulmer
bc885cc9ee Modify the keypad test
The new behaviour is for the keypad to yield its usual key,
such that instead of a special <Num+2> keypress, it yields <2>
2018-03-20 23:19:36 +00:00
George Edward Bulmer
a5dc8a3025 Fix crash in string representation of key 2018-03-20 23:13:56 +00:00
George Edward Bulmer
d6463d5ade Remove Qt.KeypadModifier as a special key 2018-03-20 22:33:11 +00:00
bitraid
7601e58c81 Merge remote-tracking branch 'upstream/master' 2018-03-20 23:54:18 +02:00
Ryan Roden-Corrent
16bda94e2b Remove unused import and TODO from urlmarks test. 2018-03-20 21:41:19 +00:00
Ryan Roden-Corrent
5a20052bce Add unit tests for urlmarks. 2018-03-20 21:41:19 +00:00
bitraid
e316f1768e More compatible icon for Windows
Fixes #3601
2018-03-20 23:28:42 +02:00
Florian Bruhin
11696f0073 Fix test_configinit 2018-03-20 22:16:16 +01:00
Florian Bruhin
f9d976880e Disable shared web workers on Qt < 5.11 2018-03-20 21:14:04 +01:00
Florian Bruhin
a5f1022330 Log document.body in JS tests 2018-03-20 11:56:46 +01:00
Florian Bruhin
85d3d4baba Mark test_set_error as flaky 2018-03-20 10:25:03 +01:00
Florian Bruhin
561295238d Another try at stabilizing test_set_error 2018-03-20 08:58:48 +01:00
Florian Bruhin
0e670a597e Update docs 2018-03-20 08:53:59 +01:00
Florian Bruhin
0fd3674d9e Update changelog 2018-03-20 07:07:05 +01:00
Florian Bruhin
0ee9d73fe2 Merge remote-tracking branch 'origin/pr/3692' 2018-03-20 07:05:43 +01:00
Florian Bruhin
81827a3150 Try to stabilize stylesheet tests 2018-03-20 07:05:03 +01:00
Florian Bruhin
32145d579b Merge branch 'pyup-scheduled-update-2018-03-19' 2018-03-20 07:00:37 +01:00
Florian Bruhin
f230fd3abb Rebuild requirement files 2018-03-20 06:59:57 +01:00
Florian Bruhin
51318d66c8 Fix ignore for new flake8-per-file-ignores release 2018-03-20 06:55:05 +01:00
Florian Bruhin
59602ec5b5 requirements: Blacklist flake8-builtins 1.1.0
See https://github.com/gforcada/flake8-builtins/issues/19
2018-03-20 06:55:05 +01:00
Florian Bruhin
a374698693 Fix lint 2018-03-20 06:38:11 +01:00
Florian Bruhin
b56988f0a4 Update changelog 2018-03-20 06:27:48 +01:00
Florian Bruhin
32df91fbae Merge remote-tracking branch 'origin/pr/3604' 2018-03-20 06:24:57 +01:00
Jay Kamat
f6c00babbe Prevent minimumTabsizeHint from being called when booting on mac
Move workaround higher up to the start of tabSizeHint
2018-03-19 18:29:51 -04:00
Florian Bruhin
7eaad59be3 caret: Ignore None value from setInitialCursor
See #3583
2018-03-19 22:32:26 +01:00
Florian Bruhin
ea1e52ea7c Load page before loading stylesheets
If we don't do this, when doing:

   self.config_stub.val.content.user_stylesheets = css_path

then _update_stylesheet gets called before the stylesheet QWebEngineScript did
run (as there was no load yet), so we get:

  [:2] Uncaught TypeError: Cannot read property 'stylesheet' of undefined!

Instead, load the page first and then update the stylesheet.
This tests that live updating works properly, and also makes sure we don't run
into the problem described above.
2018-03-19 21:44:47 +01:00
Florian Bruhin
b588f54a53 Fail javascript tests on logging messages 2018-03-19 20:43:55 +01:00
Florian Bruhin
9031b3e535 Remove @pyqtSlot for on_download_requested
For some reason, this breaks when test_pac is run...
2018-03-19 20:17:15 +01:00
Florian Bruhin
1162e640c5 Remove unused imports 2018-03-19 19:42:56 +01:00
Florian Bruhin
0ea7a1457d Make test_position_caret work again
The tests only work properly with QtWebKit (and aren't needed on QtWebEngine).
Also, for some reason the scrolled_down tests only work without Xvfb.
2018-03-19 19:38:21 +01:00
Florian Bruhin
f5d7605ae0 Add a :scroll-to-anchor command
Fixes #2784
2018-03-19 19:18:33 +01:00
Florian Bruhin
e50068021d Use signals to update statusbar in caret mode
This means we don't use objreg anymore to get the status bar, and also makes the
bar more accurately reflect reality.

See #3583
2018-03-19 18:44:06 +01:00
Florian Bruhin
460bd86579 Initial attempt at using the tab API for tests/unit/javascript 2018-03-19 18:18:21 +01:00
Florian Bruhin
e43f0a61b9 Move all QWebEngineScript related code out of webenginesettings
It looks like there's some issue with QWebEngineScript in a profile, at least
with older Qt versions...

See #3497, #3377
2018-03-19 17:33:02 +01:00
pyup-bot
8eb4d15805 Update hypothesis from 3.49.0 to 3.50.0 2018-03-19 17:13:26 +01:00
pyup-bot
3b7e1b3fe2 Update pylint from 1.8.2 to 1.8.3 2018-03-19 17:13:25 +01:00
pyup-bot
650aa532cd Update astroid from 1.6.1 to 1.6.2 2018-03-19 17:13:23 +01:00
pyup-bot
1f3fc756db Update github3.py from 0.9.6 to 1.0.1 2018-03-19 17:13:22 +01:00
pyup-bot
2d5d485daf Update github3.py from 0.9.6 to 1.0.1 2018-03-19 17:13:20 +01:00
pyup-bot
b77e43d74f Update setuptools from 38.5.2 to 39.0.1 2018-03-19 17:13:18 +01:00
pyup-bot
5a26858e07 Update flake8-per-file-ignores from 0.5 to 0.6 2018-03-19 17:13:17 +01:00
pyup-bot
99ea4b98e8 Update flake8-builtins from 1.0.post0 to 1.1.0 2018-03-19 17:13:15 +01:00
Florian Bruhin
da8b6fb50a Decrease maximum repetitions for QtWebEngine scrolling
At least for Qt debug builds, 5000 seems to be much too much.
See #3661
2018-03-19 14:11:01 +01:00
Florian Bruhin
33066af51d Break long comment 2018-03-19 13:59:30 +01:00
Florian Bruhin
6a971e2846 Ignore OnDidStopLoading error message
See #3661, https://bugreports.qt.io/browse/QTBUG-66661
2018-03-19 12:13:10 +01:00
Florian Bruhin
f28a39571c Fix caret.js indent 2018-03-19 11:49:24 +01:00
Florian Bruhin
bee04a1eec Wait until runner is finished in test_custom_env
This seems to at least lead to less warnings when running the test.
2018-03-19 11:43:08 +01:00
Florian Bruhin
39d25c1127 Update _chromium_version comment
[ci skip]
2018-03-19 11:15:19 +01:00
Florian Bruhin
07e831cee5 Update changelog 2018-03-19 10:28:25 +01:00
Florian Bruhin
b3342d8f70 Merge remote-tracking branch 'origin/pr/3728' 2018-03-19 10:28:04 +01:00
Florian Bruhin
6465d64738 Fix lint 2018-03-19 10:22:21 +01:00
Florian Bruhin
232fd19422 Fix unit tests after refactoring 2018-03-19 09:53:35 +01:00
Florian Bruhin
a4530797ea Add a ProfileSetter class to webenginesettings
Easier than passing a profile around everywhere.
2018-03-19 09:40:57 +01:00
Florian Bruhin
1b84bbd61d Refactor initialization of internal JavaScript
- Initialize JavaScript in webenginesettings.py instead of webenginetab.py
- Move JavaScript snippet into a .js file
- Make sure scripts can be re-run and do nothing if already run.
- Run scripts on DocumentCreation *and* DocumentReady. Closes #3717.
- Give each script an unique name for debugging.
- Also make custom stylesheets work on chrome:// pages
2018-03-19 09:14:55 +01:00
Florian Bruhin
f2864c6253 Break greasemonkey_wrapper lines differently 2018-03-19 09:13:50 +01:00
Florian Bruhin
8ae3047f2a Update changelog 2018-03-18 19:00:07 +01:00
Florian Bruhin
b154846bdc Merge remote-tracking branch 'origin/pr/3733' 2018-03-18 18:59:41 +01:00
Florian Bruhin
8a3d9c0c01 Adjust ignored log messages for Qt 5.11 2018-03-18 18:58:29 +01:00
AlternateData
62d30fe589 use 0 and maxint as bounds 2018-03-18 10:06:41 +01:00
AlternateData
a52d18b700 Add correct maximum and minimum value for tabs.switching_delay 2018-03-17 17:59:31 +01:00
gammelon
f57e47c742 Separate tests for _get_search_url 2018-03-16 11:42:51 +01:00
Bryan Bugyi
18146e2fbc Fix: prevent unmatched quote (#3726) 2018-03-16 06:16:16 -04:00
Florian Bruhin
f7074b80d0 Fix lint 2018-03-16 09:07:25 +01:00
Bryan Bugyi
fa282d574d Fix: preserve exit status of task command (#3726) 2018-03-16 03:44:22 -04:00
Bryan Bugyi
3b0b4ffe66 Fix: restrict output of task to one line (closes #3726) 2018-03-16 03:28:44 -04:00
Florian Bruhin
a6ce188e0d Update changelog 2018-03-16 08:21:11 +01:00
Florian Bruhin
01845faac5 Set window title/icon on correct object
This was a regression introduced in #3613.
Fixes #3727
2018-03-16 08:20:27 +01:00
Florian Bruhin
69a013bc82 Update changelog 2018-03-15 14:53:20 +01:00
Florian Bruhin
6f8eb419ae Emit predicted_navigation when loading sessions
This avoids reloads (because of changed settings) after a session has been
loaded.

Related to #3718
2018-03-15 14:51:36 +01:00
Florian Bruhin
1e4b80d1ac Don't emit predicted_navigation when reloading because of it
When we reload because of a config change in _on_load_finished, we can't use
self.reload() as no URL is set yet. Instead, we call self.openurl with the
current URL.

However, we need to make sure we don't emit predicted_navigation again at that
point.

This should (finally) fix #3718
2018-03-15 14:44:44 +01:00
Florian Bruhin
5dbda3016b Clean up predicted_navigation handling
This also adds some more logging for #3718
2018-03-15 14:16:10 +01:00
Florian Bruhin
1d25b212d5 Add missing qapp fixtures to tests
See #3723
2018-03-15 09:06:40 +01:00
Florian Bruhin
f538fc8b74 Update release checklist
[ci skip]
2018-03-14 21:50:22 +01:00
Florian Bruhin
c2b995edde Update build_release for github3.py 1.0 2018-03-14 21:11:03 +01:00
Florian Bruhin
1d562d919e Include requirements files in built release
This is needed to use "tox -e mkvenv"
2018-03-14 21:10:35 +01:00
Florian Bruhin
a60bae30b7 Release v1.2.1
(cherry picked from commit 6145786e46)
2018-03-14 20:20:20 +01:00
Florian Bruhin
523502785a Update changelog for v1.2.1 2018-03-14 20:17:34 +01:00
Florian Bruhin
84c7c37e8e Swap Control/Meta back on macOS
Fixes #3697

(cherry picked from commit fd9e7bed7fd9842eac22ed304a094a92cc953577)
2018-03-14 19:40:56 +01:00
Florian Bruhin
d232b3ea57 Disable test_software_rendering on macOS
For some reason, macOS doesn't care about us disabling software rendering
2018-03-14 19:31:36 +01:00
Florian Bruhin
7a861b7119 Update changelog 2018-03-14 18:19:11 +01:00
Florian Bruhin
a22f973c99 Don't emit predicted_navigation for reloads at all
When we reload a page because of a config change, we won't get another
titleChanged signal (at least sometimes).

Also, the predicted_navigation signal is worthless when reloading anyways, as
we're going to load the same URL and not something different.

Fixes #3718
2018-03-14 18:19:11 +01:00
Florian Bruhin
724e531087 Merge pull request #3715 from toofar/docs/greasy-faq
Greasemonkey: add FAQ entry.
2018-03-14 11:13:21 +01:00
Florian Bruhin
5c73910a33 Revert "Insert qutebrowser scripts on DocumentCreation and DocumentReady"
This reverts commit fac0f66e52.
2018-03-14 10:19:23 +01:00
Florian Bruhin
84bae210ab Fix wrong number in cheatsheet 2018-03-14 09:06:04 +01:00
Jimmy
e5edc0f940 fixup! Greasemonkey: add FAQ entry.
Change the wording and the `--debug` suggestion.

Add comma after "this is currently not supported", I was tempted to add
a semicolon before it to but I resisted.

Added a "then" after QtWebEngine version list, not a comma.
2018-03-14 20:25:59 +13:00
Florian Bruhin
0d21265005 Update changelog 2018-03-14 08:07:38 +01:00
Florian Bruhin
c0fdf19756 Merge remote-tracking branch 'origin/pr/3704' 2018-03-14 08:06:24 +01:00
Florian Bruhin
102b2be361 Update changelog 2018-03-14 07:56:00 +01:00
Florian Bruhin
fac0f66e52 Insert qutebrowser scripts on DocumentCreation and DocumentReady
In #3521, the injection point was changed to DocumentReady as a fix for
https://bugreports.qt.io/browse/QTBUG-66011 / #3490.

However, that prevents e.g. using hints before a page is fully loaded, which can
be annoying on a mobile connection.

Instead, just run the scripts twice, which won't hurt and makes sure they're
available.
2018-03-14 07:50:41 +01:00
Jimmy
2563ecf6d8 Greasemonkey: add FAQ entry.
The most common questsions regarding greasemonkey support on IRC are
"how do I use it" and "why doesn't my script work", hopefully this can
be a starting point for most people experiencing issues.
2018-03-14 19:48:46 +13:00
Florian Bruhin
64530375ab Update changelog 2018-03-14 07:44:51 +01:00
Jay Kamat
7278b7c2e5 Improve wording of documentation 2018-03-13 22:25:26 -04:00
Jay Kamat
35beff98a9 Add test for #3711 2018-03-13 19:18:42 -04:00
Jay Kamat
a6e94cf30c Fix hinting in frames on qt5.9 with input ranges 2018-03-13 18:54:08 -04:00
Florian Bruhin
8e01353a94 Update changelog 2018-03-13 14:41:40 +01:00
Florian Bruhin
8b9c6ccee2 Split up BaseKeyParser.handle into functions 2018-03-13 14:40:54 +01:00
Florian Bruhin
b88ac51d25 Fall back to non-keypad keys without any keypad bindings
Fixes #3701
2018-03-13 14:40:54 +01:00
Ryan Roden-Corrent
73517f0a51 Fix test_backup_error.
- Need caplog at level error
- Rename test to be unique
2018-03-13 08:50:34 -04:00
Ryan Roden-Corrent
27966c94a6 Fix up editor backup patch.
- Use qutebrowser-editor-backup as the backup file prefix
- Consistently use message.error instead of cmdexc
- Improve test coverage for the backup function
- Fix lint errors in the unit test code
2018-03-13 07:34:18 -04:00
Florian Bruhin
a7b6d179d4 Update changelog 2018-03-13 09:51:03 +01:00
Florian Bruhin
1c9598d2c0 Don't emit predicted_navigation with invalid URLs
Fixes #3706
2018-03-13 09:46:09 +01:00
Florian Bruhin
dcd6bcd2f4 Apply changes from PR review 2018-03-13 08:47:41 +01:00
Florian Bruhin
c590648077 Merge remote-tracking branch 'origin/pr/3613' 2018-03-13 08:39:36 +01:00
Florian Bruhin
80843c0b53 Update changelog 2018-03-13 07:39:04 +01:00
Florian Bruhin
14d6e737fa Merge remote-tracking branch 'origin/pr/3606' 2018-03-13 07:37:57 +01:00
Florian Bruhin
9c613fb700 Merge pull request #3705 from qutebrowser/pyup-scheduled-update-2018-03-12
Scheduled weekly dependency update for week 10
2018-03-12 18:57:36 +01:00
pyup-bot
01aa1f755d Update pytest from 3.4.1 to 3.4.2 2018-03-12 17:10:19 +01:00
pyup-bot
3855d49821 Update hypothesis from 3.48.0 to 3.49.0 2018-03-12 17:10:18 +01:00
pyup-bot
55c24cad9a Update setuptools from 38.5.1 to 38.5.2 2018-03-12 17:10:16 +01:00
Ryan Roden-Corrent
38bb3673db Preserve a backup if editor callback fails.
Currently the editor deletes its temp file whenever editing is finished.
With this patch, the file will not be deleted if the editor callback
encounters an exception.

One example is if the tab containing the edited element is closed. The
editor errors with "Edited element vanished", but with this patch it
will also print "Backup at ..." so the user does not lose their work.

Resolves #1596.

Supersedes #3641, using the cleaner approach started in #1677.
2018-03-12 08:34:50 -04:00
gammelon
455f6b8a70 Fix blank lines 2018-03-12 12:37:52 +01:00
Florian Bruhin
8c5b7bcd03 Fix lint 2018-03-12 08:51:36 +01:00
Florian Bruhin
a6885a0d41 Update changelog 2018-03-12 08:03:20 +01:00
Florian Bruhin
9941812127 Normalize keys read from the config
This makes sure the internal bindings.commands object only contains normalized
key sequences.

Fixes #3699
2018-03-12 08:00:56 +01:00
Florian Bruhin
990c0707f4 Make from_obj() work for List/Dict configtypes
We can't easily make it work for ListOrValue as we don't know which of both we
get at this point.
2018-03-12 08:00:18 +01:00
Florian Bruhin
c03ef10d54 tests: Add a yaml_config_stub fixture 2018-03-12 07:39:20 +01:00
Florian Bruhin
d72691ee49 Simplify ListOrValue configtype 2018-03-12 07:38:56 +01:00
Florian Bruhin
27c2650245 build_release: Wait before detaching volume
This hopefully helps with detaching it properly.
2018-03-11 21:06:31 +01:00
Florian Bruhin
b0bf02e23a Update changelog 2018-03-11 21:05:10 +01:00
Florian Bruhin
30ab1d0218 Force PyQt 5.10.0 with "tox -e mkvenv-pypi"
Fixes #3662
2018-03-11 20:47:01 +01:00
Florian Bruhin
7f68affa30 Merge remote-tracking branch 'origin/pr/3695' 2018-03-11 14:36:06 +01:00
Florian Bruhin
b6e29d8eae Be explicit about expected output in test 2018-03-11 14:35:15 +01:00
Florian Bruhin
591883656e Merge remote-tracking branch 'origin/pr/3700' 2018-03-11 14:34:06 +01:00
Florian Bruhin
f0a649e101 Mark another GreaseMonkey test as flaky
See #3238
2018-03-11 14:29:54 +01:00
Roman Bogorodskiy
d0342bffc4 Show version for POSIX OSes
For POSIX OSes other than Linux and macOS set OS Version to
platform.uname() instead of showing 'OS Version: ?'.
2018-03-11 13:28:53 +04:00
Florian Bruhin
75ab8f077d Fix keybinding cheatsheet URLs in quickstart.asciidoc
The URLs and the patching were changed in
96e8151cce but not in quickstart.asciidoc.
2018-03-11 08:30:41 +01:00
Florian Bruhin
d9f7d401c6 Handle ImportError in version.opengl_vendor
Fixes #3698
2018-03-11 08:15:22 +01:00
Jay Kamat
996561b50e Apply tabs.min_width to all tabs when tabs are unshrunk 2018-03-09 14:36:01 -05:00
Sebastian Noack
4cf0311d7f Updated flake8-per-file-ignores to version 0.5 2018-03-09 14:09:49 -05:00
Johannes Wegener
cf4e472461 add basic completion to file dialog 2018-03-09 16:21:57 +01:00
gammelon
0ce94dae1c forgot one bit 2018-03-09 15:55:40 +01:00
gammelon
7e3c966afe rewrite tests 2018-03-09 15:52:03 +01:00
Jay Kamat
1672995639 Clean up style issues 2018-03-09 02:19:49 -05:00
Jay Kamat
4a78b0519d Add tabs.min_width setting
Controls min width in pixels of non pinned tabs

Closes #3690
2018-03-09 02:05:49 -05:00
Jay Kamat
46533c3367 Fix pinned tabs being too small in extreme situations 2018-03-09 02:02:31 -05:00
gammelon
a730290d40 Use QUrl for parsing, add tests 2018-03-05 16:32:41 +01:00
gammelon
16218a9900 Remove unnecessary try, rephrase to imperative mood 2018-02-20 18:11:50 +01:00
bttner
e169e2165d Refactor TabbedBrowser from inheritance to composition 2018-02-19 14:29:05 +01:00
gammelon
42ac3dcda0 Add Option url.open_base_url
when set to true, invoking a searchengine shortcut without argument
opens the baseurl of that searchengine instead of DEFAULT searchengine
2018-02-17 11:21:22 +01:00
117 changed files with 1786 additions and 965 deletions

10
.flake8
View File

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

View File

@@ -30,10 +30,18 @@ matrix:
- sourceline: "deb http://us.archive.ubuntu.com/ubuntu/ xenial main universe"
packages:
- xvfb
- os: osx
env: TESTENV=py36 OSX=highsierra
osx_image: xcode9.3
language: generic
- os: osx
env: TESTENV=py36 OSX=sierra
osx_image: xcode9.2
language: generic
- os: osx
env: TESTENV=py36 OSX=elcapitan
osx_image: xcode8
language: generic
# https://github.com/qutebrowser/qutebrowser/issues/2013
# - os: osx
# env: TESTENV=py35 OSX=yosemite

View File

@@ -8,6 +8,7 @@ graft icons
graft doc/img
graft misc/apparmor
graft misc/userscripts
graft misc/requirements
recursive-include scripts *.py *.sh *.js
include qutebrowser/utils/testfile
include qutebrowser/git-commit-id
@@ -32,8 +33,6 @@ include doc/qutebrowser.1.asciidoc
include doc/changelog.asciidoc
prune tests
prune qutebrowser/3rdparty
prune misc/requirements
prune misc/docker
exclude pytest.ini
exclude qutebrowser.rcc
exclude qutebrowser/javascript/.eslintrc.yaml

View File

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

View File

@@ -15,6 +15,97 @@ breaking changes (such as renamed commands) can happen in minor releases.
// `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities.
v1.3.0 (unreleased)
-------------------
Added
~~~~~
- New `:scroll-to-anchor` command to scroll to an anchor in the document.
- New `url.open_base_url` option to open the base URL of a searchengine when no
search term is given.
- New `tabs.min_width` setting to configure the minimal width for tabs.
- New `getbib` userscript to download bibtex information for DOIs on a page.
Changed
~~~~~~~
- QtWebEngine: Support for JavaScript Shared Web Workers have been disabled on
Qt versions older than 5.11 because of security issues in in Chromium.
You can get the same effect in earlier versions via
`:set qt.args ['disable-shared-workers']`. An equivalent workaround is also
contained in Qt 5.9.5 and 5.10.1.
- The file dialog for downloads now has basic tab completion based on the
entered text.
- `:version` now shows OS information for POSIX OS other than Linux/macOS.
- When there's an error inserting the text from an external editor, a backup
file is now saved.
- The `window.hide_wayland_decoration` setting got renamed to
`window.hide_decoration` and now also works outside of wayland.
- The `tabs.favicons.show` setting now can take three values: `'always'` (was
`True`), `'never'` (was `False`) and `'pinned'` (to only show favicons for
pinned tabs).
- Hover tooltips on tabs now always show the webpage's title.
- The default value for `content.host_blocking.lists` was changed to only
include https://github.com/StevenBlack/hosts[Steven Black's hosts-list] which
combines various sources.
- Error messages when trying to wrap when `tabs.wrap` is `False` are now logged
to debug instead of messages.
Fixed
~~~~~
- Using hints before a page is fully loaded is now possible again.
- Selecting hints with the number keypad now works again.
- Tab titles for tabs loaded from sessions should now really be correct instead
of showing the URL.
- Loading URLs with customized settings from a session now avoids an additional
reload.
- The window icon and title now get set correctly again.
- The `tabs.switching_delay` setting now has a correct maximum value limit set.
- The `taskadd` script now works properly when there's multi-line output.
- QtWebEngine: Worked around issues with GreaseMonkey/stylesheets not being
loaded correctly in some situations.
- The statusbar now more closely reflects the caret mode state.
- The icon on Windows should now be displayed in a higher resolution.
- The QtWebEngine development tools (inspector) now also work when JavaScript is
disabled globally.
- Building `.exe` files now works when `upx` is installed on the system.
- The keyhint widget now shows the correct text for chained modifiers.
- Loading GreaseMonkey scripts now also works with Jinja2 2.8 (e.g. on Debian
Stable).
- Adding styles with GreaseMonkey on fast sites now works properly.
- Window ID 0 is now excluded properly from `:tab-take` completion.
- A rare crash when cancelling a download has been fixed.
- The Makefile (intended for packagers) now supports `PREFIX` properly.
- The workaround for a black window with Nvidia graphics is now enabled on
non-Linux systems (like FreeBSD) as well.
- Initial support for Qt 5.11
v1.2.1
------
Fixed
~~~~~
- qutebrowser now starts properly when the PyQt5 QOpenGLFunctions package wasn't
found.
- The keybinding cheatsheet on the quickstart page is now loaded from a local
`qute://` URL again.
- With "tox -e mkvenv-pypi", PyQt 5.10.0 is used again instead of Qt 5.10.1,
because of an issue with Qt 5.10.1 which causes qutebrowser to fail to start
("Could not find QtWebEngineProcess").
- Unbinding keys which were bound in older qutebrowser versions now doesn't
crash anymore.
- Fixed a crash when reloading a page which wasn't fully loaded with v1.2.0
- Keys on the numeric keypad now fall back to the same bindings without `Num+`
if no `Num+` binding was found.
- Fixed hinting on some pages with Qt < 5.10.
- Titles are now displayed correctly again for tabs which are cloned or loaded
from sessions.
- Shortcuts now correctly use `Ctrl` instead of `Command` on macOS again.
v1.2.0
------

View File

@@ -670,10 +670,11 @@ qutebrowser release
~~~~~~~~~~~~~~~~~~~
* Make sure there are no unstaged changes and the tests are green.
* Make sure all issues with the related milestone are closed.
* Run `x=... y=...` to set the respective shell variables.
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
* Update changelog (remove *(unreleased)*).
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
* Commit.
* Create annotated git tag (`git tag -s "v1.$x.$y" -m "Release v1.$x.$y"`).
@@ -683,9 +684,11 @@ qutebrowser release
* Mark the milestone at https://github.com/qutebrowser/qutebrowser/milestones
as closed.
* Linux: Run `git checkout v1.$x.$y && python3 scripts/dev/build_release.py --upload v1.$x.$y`.
* Linux: Run `git checkout v1.$x.$y && ./.venv/bin/python3 scripts/dev/build_release.py --upload v1.$x.$y`.
* Windows: Run `git checkout v1.X.Y; C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
* macOS: Run `git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
* On server: Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
* On server:
- Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
- Run `git pull github master && sudo python3 scripts/asciidoc2html.py --website /srv/http/qutebrowser`
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed.
* Announce to qutebrowser and qutebrowser-announce mailinglist.

View File

@@ -212,6 +212,37 @@ Why takes it longer to open an URL in qutebrowser than in chromium?::
qutebrowser if it is not running already. Also check if you want
to use webengine as backend in line 17 and change it to your
needs.
How do I make qutebrowser use greasemonkey scripts?::
There is currently no UI elements to handle managing greasemonkey scripts.
All management of what scripts are installed or disabled is done in the
filesystem by you. qutebrowser reads all files that have an extension of
`.js` from the `<data>/greasemonkey/` folder and attempts to load them.
Where `<data>` is the qutebrowser data directory shown in the `Paths`
section of the page displayed by `:version`. If you want to disable a
script just rename it, for example, to have `.disabled` on the end, after
the `.js` extension. To reload scripts from that directory run the command
`:greasemonkey-reload`.
+
Troubleshooting: to check that your script is being loaded when
`:greasemonkey-reload` runs you can start qutebrowser with the arguments
`--debug --logfilter greasemonkey,js` and check the messages on the
program's standard output for errors parsing or loading your script.
You may also see javascript errors if your script is expecting an environment
that we fail to provide.
+
Note that there are some missing features which you may run into:
. Some scripts expect `GM_xmlhttpRequest` to ignore Cross Origin Resource
Sharing restrictions, this is currently not supported, so scripts making
requests to third party sites will often fail to function correctly.
. If your backend is a QtWebEngine version 5.8, 5.9 or 5.10 then regular
expressions are not supported in `@include` or `@exclude` rules. If your
script uses them you can re-write them to use glob expressions or convert
them to `@match` rules.
See https://wiki.greasespot.net/Metadata_Block[the wiki] for more info.
. Any greasemonkey API function to do with adding UI elements is not currently
supported. That means context menu extentensions and background pages.
== Troubleshooting

View File

@@ -93,6 +93,7 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|<<scroll,scroll>>|Scroll the current tab in the given direction.
|<<scroll-page,scroll-page>>|Scroll the frame page-wise.
|<<scroll-px,scroll-px>>|Scroll the current tab by 'count * dx/dy' pixels.
|<<scroll-to-anchor,scroll-to-anchor>>|Scroll to the given anchor in the document.
|<<scroll-to-perc,scroll-to-perc>>|Scroll to a specific percentage of the page.
|<<search,search>>|Search for a text on the current page. With no text, clear results.
|<<search-next,search-next>>|Continue the search to the ([count]th) next term.
@@ -1024,6 +1025,15 @@ Scroll the current tab by 'count * dx/dy' pixels.
==== count
multiplier
[[scroll-to-anchor]]
=== scroll-to-anchor
Syntax: +:scroll-to-anchor 'name'+
Scroll to the given anchor in the document.
==== positional arguments
* +'name'+: The anchor to scroll to.
[[scroll-to-perc]]
=== scroll-to-perc
Syntax: +:scroll-to-perc [*--horizontal*] ['perc']+

View File

@@ -242,10 +242,10 @@ To suppress loading of any default keybindings, you can set
Loading `autoconfig.yml`
~~~~~~~~~~~~~~~~~~~~~~~~
By default, all customization done via `:set`, `:bind` and `:unbind` is
temporary as soon as a `config.py` exists. The settings done that way are always
saved in the `autoconfig.yml` file, but you'll need to explicitly load it in
your `config.py` by doing:
All customization done via the UI (`:set`, `:bind` and `:unbind`) is
stored in the `autoconfig.yml` file, which is not loaded automatically as soon
as a `config.py` exists. If you want those settings to be loaded, you'll need to
explicitly load the `autoconfig.yml` file in your `config.py` by doing:
.config.py:
[source,python]

View File

@@ -236,10 +236,11 @@
|<<tabs.close_mouse_button,tabs.close_mouse_button>>|Mouse button with which to close tabs.
|<<tabs.close_mouse_button_on_bar,tabs.close_mouse_button_on_bar>>|How to behave when the close mouse button is pressed on the tab bar.
|<<tabs.favicons.scale,tabs.favicons.scale>>|Scaling factor for favicons in the tab bar.
|<<tabs.favicons.show,tabs.favicons.show>>|Show favicons in the tab bar.
|<<tabs.favicons.show,tabs.favicons.show>>|When to show favicons in the tab bar.
|<<tabs.indicator.padding,tabs.indicator.padding>>|Padding (in pixels) for tab indicators.
|<<tabs.indicator.width,tabs.indicator.width>>|Width (in pixels) of the progress indicator (0 to disable).
|<<tabs.last_close,tabs.last_close>>|How to behave when the last tab is closed.
|<<tabs.min_width,tabs.min_width>>|Minimum width (in pixels) of tabs (-1 for the default minimum size behavior).
|<<tabs.mode_on_change,tabs.mode_on_change>>|When switching tabs, what input mode is applied.
|<<tabs.mousewheel_switching,tabs.mousewheel_switching>>|Switch between tabs using the mouse wheel.
|<<tabs.new_position.related,tabs.new_position.related>>|Position of new tabs opened from another tab.
@@ -259,10 +260,11 @@
|<<url.auto_search,url.auto_search>>|What search to start when something else than a URL is entered.
|<<url.default_page,url.default_page>>|Page to open if :open -t/-b/-w is used without URL.
|<<url.incdec_segments,url.incdec_segments>>|URL segments where `:navigate increment/decrement` will search for a number.
|<<url.open_base_url,url.open_base_url>>|Open base URL of the searchengine if a searchengine shortcut is invoked without parameters.
|<<url.searchengines,url.searchengines>>|Search engines which can be used via the address bar.
|<<url.start_pages,url.start_pages>>|Page(s) to open at the start.
|<<url.yank_ignored_parameters,url.yank_ignored_parameters>>|URL parameters to strip with `:yank url`.
|<<window.hide_wayland_decoration,window.hide_wayland_decoration>>|Hide the window decoration when using wayland.
|<<window.hide_decoration,window.hide_decoration>>|Hide the window decoration.
|<<window.title_format,window.title_format>>|Format to use for the window title. The same placeholders like for
|<<zoom.default,zoom.default>>|Default zoom level.
|<<zoom.levels,zoom.levels>>|Available zoom levels.
@@ -1653,11 +1655,7 @@ Type: <<types,List of Url>>
Default:
- +pass:[https://www.malwaredomainlist.com/hostslist/hosts.txt]+
- +pass:[http://someonewhocares.org/hosts/hosts]+
- +pass:[http://winhelp2002.mvps.org/hosts.zip]+
- +pass:[http://malwaredomains.lehigh.edu/files/justdomains.zip]+
- +pass:[https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&amp;mimetype=plaintext]+
- +pass:[https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts]+
[[content.host_blocking.whitelist]]
=== content.host_blocking.whitelist
@@ -2823,11 +2821,17 @@ Default: +pass:[1.0]+
[[tabs.favicons.show]]
=== tabs.favicons.show
Show favicons in the tab bar.
When to show favicons in the tab bar.
Type: <<types,Bool>>
Type: <<types,String>>
Default: +pass:[true]+
Valid values:
* +always+: Always show favicons.
* +never+: Always hide favicons.
* +pinned+: Show favicons only on pinned tabs.
Default: +pass:[always]+
[[tabs.indicator.padding]]
=== tabs.indicator.padding
@@ -2866,6 +2870,16 @@ Valid values:
Default: +pass:[ignore]+
[[tabs.min_width]]
=== tabs.min_width
Minimum width (in pixels) of tabs (-1 for the default minimum size behavior).
This setting only applies when tabs are horizontal.
This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is False.
Type: <<types,Int>>
Default: +pass:[-1]+
[[tabs.mode_on_change]]
=== tabs.mode_on_change
When switching tabs, what input mode is applied.
@@ -3102,6 +3116,14 @@ Default:
- +pass:[path]+
- +pass:[query]+
[[url.open_base_url]]
=== url.open_base_url
Open base URL of the searchengine if a searchengine shortcut is invoked without parameters.
Type: <<types,Bool>>
Default: +pass:[false]+
[[url.searchengines]]
=== url.searchengines
Search engines which can be used via the address bar.
@@ -3137,10 +3159,12 @@ Default:
- +pass:[utm_term]+
- +pass:[utm_content]+
[[window.hide_wayland_decoration]]
=== window.hide_wayland_decoration
Hide the window decoration when using wayland.
This setting requires a restart.
[[window.hide_decoration]]
=== window.hide_decoration
Hide the window decoration.
This setting requires a restart on Wayland.
Type: <<types,Bool>>
@@ -3273,7 +3297,7 @@ See the setting's valid values for more information on allowed values.
|TextAlignment|Alignment of text.
|TimestampTemplate|An strftime-like template for timestamps.
See https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior for reference.
See https://sqlite.org/lang_datefunc.html for reference.
|UniqueCharString|A string which may not contain duplicate chars.
|Url|A URL as a string.
|VerticalPosition|The position of the download bar.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -47,17 +47,26 @@ Debian Stretch / Ubuntu 17.04 and 17.10
Those versions come with QtWebEngine in the repositories. This makes it possible
to install qutebrowser via the Debian package.
Download the https://packages.debian.org/sid/all/qutebrowser/download[qutebrowser] and
https://packages.debian.org/sid/all/python3-pypeg2/download[PyPEG2]
package from the Debian repositories.
You'll need to download three packages:
Install the packages:
- https://packages.debian.org/sid/all/python3-pypeg2/download[PyPEG2] (a library
used by qutebrowser which is not in the earlier repositories)
- https://packages.debian.org/sid/all/qutebrowser/download[qutebrowser] itself
- Either https://packages.debian.org/sid/all/qutebrowser-qtwebengine/download[qutebrowser-qtwebengine]
or https://packages.debian.org/sid/all/qutebrowser-qtwebkit/download[qutebrowser-qtwebkit]
(or both) depending on the backend you want to use. QtWebEngine is the
default/recommended choice.
After downloading, install the packages:
----
# apt install ./python3-pypeg2_*_all.deb
# apt install ./qutebrowser_*_all.deb
# apt install ./qutebrowser*.deb
----
For an update after the initial install, you only need to download/install the
qutebrowser package.
Debian Testing / Ubuntu 18.04
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -417,7 +426,11 @@ Creating a wrapper script
~~~~~~~~~~~~~~~~~~~~~~~~~
Running `tox` does not install a system-wide `qutebrowser` script. You can
launch qutebrowser by doing `.venv/bin/python3 -m qutebrowser`.
launch qutebrowser by doing:
----
.venv/bin/python3 -m qutebrowser
----
You can create a simple wrapper script to start qutebrowser somewhere in your
`$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):

View File

@@ -22,9 +22,9 @@ Basic keybindings to get you started
What to do now
--------------
* View the link:https://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]
* View the link:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png[key binding cheatsheet]
to make yourself familiar with the key bindings: +
image:https://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://qutebrowser.org/img/cheatsheet-big.png"]
image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png"]
* There's also a https://www.shortcutfoo.com/app/dojos/qutebrowser[free training
course] on shortcutfoo for the keybindings - note that you need to be in
insert mode (i) for it to work.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -1,25 +1,31 @@
PYTHON = python3
DESTDIR = /
PREFIX = /usr/local
DESTDIR =
ICONSIZES = 16 24 32 48 64 128 256 512
SETUPTOOLSOPTIONS =
ifdef DESTDIR
SETUPTOOLSOPTS = --root="$(DESTDIR)"
endif
.PHONY: install
doc/qutebrowser.1.html:
a2x -f manpage doc/qutebrowser.1.asciidoc
install: doc/qutebrowser.1.html
$(PYTHON) setup.py install --root="$(DESTDIR)" --optimize=1
$(PYTHON) setup.py install --prefix="$(PREFIX)" --optimize=1 $(SETUPTOOLSOPTS)
install -Dm644 doc/qutebrowser.1 \
"$(DESTDIR)/usr/share/man/man1/qutebrowser.1"
"$(DESTDIR)$(PREFIX)/share/man/man1/qutebrowser.1"
install -Dm644 misc/qutebrowser.desktop \
"$(DESTDIR)/usr/share/applications/qutebrowser.desktop"
"$(DESTDIR)$(PREFIX)/share/applications/qutebrowser.desktop"
$(foreach i,$(ICONSIZES),install -Dm644 "icons/qutebrowser-$(i)x$(i).png" \
"$(DESTDIR)/usr/share/icons/hicolor/$(i)x$(i)/apps/qutebrowser.png";)
"$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(i)x$(i)/apps/qutebrowser.png";)
install -Dm644 icons/qutebrowser.svg \
"$(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qutebrowser.svg"
install -Dm755 -t "$(DESTDIR)/usr/share/qutebrowser/userscripts/" \
"$(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/qutebrowser.svg"
install -Dm755 -t "$(DESTDIR)$(PREFIX)/share/qutebrowser/userscripts/" \
$(wildcard misc/userscripts/*)
install -Dm755 -t "$(DESTDIR)/usr/share/qutebrowser/scripts/" \
install -Dm755 -t "$(DESTDIR)$(PREFIX)/share/qutebrowser/scripts/" \
$(filter-out scripts/__init__.py scripts/__pycache__ scripts/dev \
scripts/testbrowser scripts/asciidoc2html.py scripts/setupcommon.py \
scripts/link_pyqt.py,$(wildcard scripts/*))

View File

@@ -33,7 +33,7 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.7536248"
inkscape:cx="376.55567"
inkscape:cx="430.72917"
inkscape:cy="268.64059"
inkscape:document-units="px"
inkscape:current-layer="layer1"
@@ -3710,7 +3710,7 @@
style="font-weight:bold;font-size:10.66666698px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'Sans Bold';fill:#000000;stroke-width:1.06666672"
id="flowPara5701-9-2"><flowSpan
style="font-weight:bold;font-family:sans-serif;-inkscape-font-specification:'Sans Bold';fill:#ff0000;stroke-width:1.06666672"
id="flowSpan5705-5-1">(10)</flowSpan> toggling settings:</flowPara><flowPara
id="flowSpan5705-5-1">(12)</flowSpan> toggling settings:</flowPara><flowPara
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
id="flowPara6196">tsh - toggle scripts for the current host (temporarily)</flowPara><flowPara
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 181 KiB

View File

@@ -15,7 +15,7 @@ def get_data_files():
('../qutebrowser/img', 'img'),
('../qutebrowser/javascript', 'javascript'),
('../qutebrowser/html/doc', 'html/doc'),
('../qutebrowser/git-commit-id', ''),
('../qutebrowser/git-commit-id', '.'),
('../qutebrowser/config/configdata.yml', 'config'),
]
@@ -58,14 +58,14 @@ exe = EXE(pyz,
icon=icon,
debug=False,
strip=False,
upx=True,
upx=False,
console=False )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx=False,
name='qutebrowser')
app = BUNDLE(coll,

View File

@@ -3,7 +3,7 @@
attrs==17.4.0
flake8==3.5.0
flake8-bugbear==18.2.0
flake8-builtins==1.0.post0
flake8-builtins==1.2.2
flake8-comprehensions==1.4.1
flake8-copyright==0.2.0
flake8-debugger==3.1.0
@@ -11,15 +11,17 @@ flake8-deprecated==1.3
flake8-docstrings==1.3.0
flake8-future-import==0.4.4
flake8-mock==0.3
flake8-per-file-ignores==0.4
flake8-per-file-ignores==0.6
flake8-polyfill==1.0.2
flake8-string-format==0.2.3
flake8-tidy-imports==1.1.0
flake8-tuple==0.2.13
mccabe==0.6.1
pathmatch==0.2.1
pep8-naming==0.5.0
pycodestyle==2.3.1
pydocstyle==2.1.1
pyflakes==1.6.0
six==1.11.0
snowballstemmer==1.2.1
typing==3.6.4

View File

@@ -3,6 +3,6 @@
appdirs==1.4.3
packaging==17.1
pyparsing==2.2.0
setuptools==38.5.1
setuptools==39.0.1
six==1.11.0
wheel==0.30.0
wheel==0.31.0

View File

@@ -4,4 +4,4 @@ altgraph==0.15
future==0.16.0
macholib==1.9
pefile==2017.11.5
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
PyInstaller==3.3.1

View File

@@ -1,4 +1 @@
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
# remove @commit-id for scm installs
#@ replace: @.*# @develop#
PyInstaller

View File

@@ -3,16 +3,16 @@
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
certifi==2018.1.18
chardet==3.0.4
github3.py==0.9.6
github3.py==1.0.2
idna==2.6
isort==4.3.4
lazy-object-proxy==1.3.1
mccabe==0.6.1
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
python-dateutil==2.7.2
./scripts/dev/pylint_checkers
requests==2.18.4
six==1.11.0
uritemplate==3.0.0
uritemplate.py==3.0.2
urllib3==1.22
wrapt==1.10.11

View File

@@ -1,18 +1,18 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
astroid==1.6.1
astroid==1.6.3
certifi==2018.1.18
chardet==3.0.4
github3.py==0.9.6
github3.py==1.0.2
idna==2.6
isort==4.3.4
lazy-object-proxy==1.3.1
mccabe==0.6.1
pylint==1.8.2
pylint==1.8.4
python-dateutil==2.7.2
./scripts/dev/pylint_checkers
requests==2.18.4
six==1.11.0
uritemplate==3.0.0
uritemplate.py==3.0.2
urllib3==1.22
wrapt==1.10.11

View File

@@ -0,0 +1,4 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.10 # rq.filter: != 5.10.1
sip==4.19.8

View File

@@ -0,0 +1,2 @@
PyQt5==5.10.0
#@ filter: PyQt5 != 5.10.1

View File

@@ -2,7 +2,7 @@
attrs==17.4.0
beautifulsoup4==4.6.0
cheroot==6.0.0
cheroot==6.1.2
click==6.7
# colorama==0.3.9
coverage==4.5.1
@@ -11,7 +11,7 @@ fields==5.0.0
Flask==0.12.2
glob2==0.6
hunter==2.0.2
hypothesis==3.48.0
hypothesis==3.55.1
itsdangerous==0.24
# Jinja2==2.10
Mako==1.0.7
@@ -20,15 +20,15 @@ more-itertools==4.1.0
parse==1.8.2
parse-type==0.4.2
pluggy==0.6.0
py==1.5.2
py-cpuinfo==3.3.0
pytest==3.4.1
pytest-bdd==2.20.0
py==1.5.3
py-cpuinfo==4.0.0
pytest==3.5.0
pytest-bdd==2.21.0
pytest-benchmark==3.1.1
pytest-cov==2.5.1
pytest-faulthandler==1.4.1
pytest-faulthandler==1.5.0
pytest-instafail==0.3.0
pytest-mock==1.7.1
pytest-mock==1.8.0
pytest-qt==2.3.1
pytest-repeat==0.4.1
pytest-rerunfailures==4.0

View File

@@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
pluggy==0.6.0
py==1.5.2
py==1.5.3
six==1.11.0
tox==2.9.1
virtualenv==15.1.0
tox==3.0.0
virtualenv==15.2.0

View File

@@ -1,4 +1 @@
tox
# The latest tox release still depends on pluggy < 0.4...
pluggy==0.4.0

69
misc/userscripts/getbib Executable file
View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Qutebrowser userscript scraping the current web page for DOIs and downloading
corresponding bibtex information.
Set the environment variable 'QUTE_BIB_FILEPATH' to indicate the path to
download to. Otherwise, bibtex information is downloaded to '/tmp' and hence
deleted at reboot.
Installation: see qute://help/userscripts.html
Inspired by
https://ocefpaf.github.io/python4oceanographers/blog/2014/05/19/doi2bibtex/
"""
import os
import sys
import shutil
import re
from collections import Counter
from urllib import parse as url_parse
from urllib import request as url_request
FIFO_PATH = os.getenv("QUTE_FIFO")
def message_fifo(message, level="warning"):
"""Send message to qutebrowser FIFO. The level must be one of 'info',
'warning' (default) or 'error'."""
with open(FIFO_PATH, "w") as fifo:
fifo.write("message-{} '{}'".format(level, message))
source = os.getenv("QUTE_TEXT")
with open(source) as f:
text = f.read()
# find DOIs on page using regex
dval = re.compile(r'(10\.(\d)+/([^(\s\>\"\<)])+)')
# https://stackoverflow.com/a/10324802/3865876, too strict
# dval = re.compile(r'\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?!["&\'<>])\S)+)\b')
dois = dval.findall(text)
dois = Counter(e[0] for e in dois)
try:
doi = dois.most_common(1)[0][0]
except IndexError:
message_fifo("No DOIs found on page")
sys.exit()
message_fifo("Found {} DOIs on page, selecting {}".format(len(dois), doi),
level="info")
# get bibtex data corresponding to DOI
url = "http://dx.doi.org/" + url_parse.quote(doi)
headers = dict(Accept='text/bibliography; style=bibtex')
request = url_request.Request(url, headers=headers)
response = url_request.urlopen(request)
status_code = response.getcode()
if status_code >= 400:
message_fifo("Request returned {}".format(status_code))
sys.exit()
# obtain content and format it
bibtex = response.read().decode("utf-8").strip()
bibtex = bibtex.replace(" ", "\n ", 1).\
replace("}, ", "},\n ").replace("}}", "}\n}")
# append to file
bib_filepath = os.getenv("QUTE_BIB_FILEPATH", "/tmp/qute.bib")
with open(bib_filepath, "a") as f:
f.write(bibtex + "\n\n")

View File

@@ -28,7 +28,7 @@
if msg="$(task add "$title" "$*" 2>&1)"; then
# annotate the new task with the url, send the output back to the browser
task +LATEST annotate "$QUTE_URL"
echo "message-info '$msg'" >> "$QUTE_FIFO"
echo "message-info '$(echo "$msg" | head -n 1)'" >> "$QUTE_FIFO"
else
echo "message-error '$msg'" >> "$QUTE_FIFO"
echo "message-error '$(echo "$msg" | head -n 1)'" >> "$QUTE_FIFO"
fi

View File

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

View File

@@ -340,7 +340,7 @@ def _open_startpage(win_id=None):
for cur_win_id in list(window_ids): # Copying as the dict could change
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=cur_win_id)
if tabbed_browser.count() == 0:
if tabbed_browser.widget.count() == 0:
log.init.debug("Opening start pages")
for url in config.val.url.start_pages:
tabbed_browser.tabopen(url)

View File

@@ -94,14 +94,8 @@ class HostBlocker:
_done_count: How many files have been read successfully.
_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.
"""
WHITELISTED = ('localhost', 'localhost.localdomain', 'broadcasthost',
'local')
def __init__(self):
self._blocked_hosts = set()
self._config_blocked_hosts = set()
@@ -234,16 +228,14 @@ class HostBlocker:
parts = line.split()
if len(parts) == 1:
# "one host per line" format
host = parts[0]
elif len(parts) == 2:
# /etc/hosts format
host = parts[1]
hosts = [parts[0]]
else:
log.misc.error("Failed to parse: {!r}".format(line))
return False
# /etc/hosts format
hosts = parts[1:]
if host not in self.WHITELISTED:
self._blocked_hosts.add(host)
for host in hosts:
if '.' in host and not host.endswith('.localdomain'):
self._blocked_hosts.add(host)
return True

View File

@@ -114,6 +114,10 @@ class TabData:
netrc_used = attr.ib(False)
input_mode = attr.ib(usertypes.KeyMode.normal)
def should_show_icon(self):
return (config.val.tabs.favicons.show == 'always' or
config.val.tabs.favicons.show == 'pinned' and self.pinned)
class AbstractAction:
@@ -333,7 +337,14 @@ class AbstractZoom(QObject):
class AbstractCaret(QObject):
"""Attribute of AbstractTab for caret browsing."""
"""Attribute of AbstractTab for caret browsing.
Signals:
selection_toggled: Emitted when the selection was toggled.
arg: Whether the selection is now active.
"""
selection_toggled = pyqtSignal(bool)
def __init__(self, tab, mode_manager, parent=None):
super().__init__(parent)
@@ -439,6 +450,9 @@ class AbstractScroller(QObject):
def to_point(self, point):
raise NotImplementedError
def to_anchor(self, name):
raise NotImplementedError
def delta(self, x=0, y=0):
raise NotImplementedError
@@ -665,8 +679,7 @@ class AbstractTab(QWidget):
objreg.register('hintmanager', hintmanager, scope='tab',
window=self.win_id, tab=self.tab_id)
self.predicted_navigation.connect(
lambda url: self.title_changed.emit(url.toDisplayString()))
self.predicted_navigation.connect(self._on_predicted_navigation)
def _set_widget(self, widget):
# pylint: disable=protected-access
@@ -715,6 +728,14 @@ class AbstractTab(QWidget):
evt.posted = True
QApplication.postEvent(recipient, evt)
@pyqtSlot(QUrl)
def _on_predicted_navigation(self, url):
"""Adjust the title if we are going to visit an URL soon."""
qtutils.ensure_valid(url)
url_string = url.toDisplayString()
log.webview.debug("Predicted navigation: {}".format(url_string))
self.title_changed.emit(url_string)
@pyqtSlot(QUrl)
def _on_url_changed(self, url):
"""Update title when URL has changed and no title is available."""
@@ -815,11 +836,12 @@ class AbstractTab(QWidget):
def load_status(self):
return self._load_status
def _openurl_prepare(self, url):
def _openurl_prepare(self, url, *, predict=True):
qtutils.ensure_valid(url)
self.predicted_navigation.emit(url)
if predict:
self.predicted_navigation.emit(url)
def openurl(self, url):
def openurl(self, url, *, predict=True):
raise NotImplementedError
def reload(self, *, force=False):

View File

@@ -53,7 +53,6 @@ class CommandDispatcher:
cmdutils.register() decorators are run, currentWidget() will return None.
Attributes:
_editor: The ExternalEditor object.
_win_id: The window ID the CommandDispatcher is associated with.
_tabbed_browser: The TabbedBrowser used.
"""
@@ -73,16 +72,16 @@ class CommandDispatcher:
def _count(self):
"""Convenience method to get the widget count."""
return self._tabbed_browser.count()
return self._tabbed_browser.widget.count()
def _set_current_index(self, idx):
"""Convenience method to set the current widget index."""
cmdutils.check_overflow(idx, 'int')
self._tabbed_browser.setCurrentIndex(idx)
self._tabbed_browser.widget.setCurrentIndex(idx)
def _current_index(self):
"""Convenience method to get the current widget index."""
return self._tabbed_browser.currentIndex()
return self._tabbed_browser.widget.currentIndex()
def _current_url(self):
"""Convenience method to get the current url."""
@@ -101,7 +100,7 @@ class CommandDispatcher:
def _current_widget(self):
"""Get the currently active widget from a command."""
widget = self._tabbed_browser.currentWidget()
widget = self._tabbed_browser.widget.currentWidget()
if widget is None:
raise cmdexc.CommandError("No WebView available yet!")
return widget
@@ -147,10 +146,10 @@ class CommandDispatcher:
None if no widget was found.
"""
if count is None:
return self._tabbed_browser.currentWidget()
return self._tabbed_browser.widget.currentWidget()
elif 1 <= count <= self._count():
cmdutils.check_overflow(count + 1, 'int')
return self._tabbed_browser.widget(count - 1)
return self._tabbed_browser.widget.widget(count - 1)
else:
return None
@@ -163,7 +162,7 @@ class CommandDispatcher:
if not show_error:
return
raise cmdexc.CommandError("No last focused tab!")
idx = self._tabbed_browser.indexOf(tab)
idx = self._tabbed_browser.widget.indexOf(tab)
if idx == -1:
raise cmdexc.CommandError("Last focused tab vanished!")
self._set_current_index(idx)
@@ -212,7 +211,7 @@ class CommandDispatcher:
what's configured in 'tabs.select_on_remove'.
count: The tab index to close, or None
"""
tabbar = self._tabbed_browser.tabBar()
tabbar = self._tabbed_browser.widget.tabBar()
selection_override = self._get_selection_override(prev, next_,
opposite)
@@ -264,7 +263,7 @@ class CommandDispatcher:
return
to_pin = not tab.data.pinned
self._tabbed_browser.set_tab_pinned(tab, to_pin)
self._tabbed_browser.widget.set_tab_pinned(tab, to_pin)
@cmdutils.register(instance='command-dispatcher', name='open',
maxsplit=0, scope='window')
@@ -483,7 +482,8 @@ class CommandDispatcher:
"""
cmdutils.check_exclusive((bg, window), 'bw')
curtab = self._current_widget()
cur_title = self._tabbed_browser.page_title(self._current_index())
cur_title = self._tabbed_browser.widget.page_title(
self._current_index())
try:
history = curtab.history.serialize()
except browsertab.WebTabError as e:
@@ -499,18 +499,18 @@ class CommandDispatcher:
newtab = new_tabbed_browser.tabopen(background=bg)
new_tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=newtab.win_id)
idx = new_tabbed_browser.indexOf(newtab)
idx = new_tabbed_browser.widget.indexOf(newtab)
new_tabbed_browser.set_page_title(idx, cur_title)
if config.val.tabs.favicons.show:
new_tabbed_browser.setTabIcon(idx, curtab.icon())
new_tabbed_browser.widget.set_page_title(idx, cur_title)
if curtab.data.should_show_icon():
new_tabbed_browser.widget.setTabIcon(idx, curtab.icon())
if config.val.tabs.tabs_are_windows:
new_tabbed_browser.window().setWindowIcon(curtab.icon())
new_tabbed_browser.widget.window().setWindowIcon(curtab.icon())
newtab.data.keep_icon = True
newtab.history.deserialize(history)
newtab.zoom.set_factor(curtab.zoom.factor())
new_tabbed_browser.set_tab_pinned(newtab, curtab.data.pinned)
new_tabbed_browser.widget.set_tab_pinned(newtab, curtab.data.pinned)
return newtab
@cmdutils.register(instance='command-dispatcher', scope='window')
@@ -768,6 +768,15 @@ class CommandDispatcher:
self._current_widget().scroller.to_perc(x, y)
@cmdutils.register(instance='command-dispatcher', scope='window')
def scroll_to_anchor(self, name):
"""Scroll to the given anchor in the document.
Args:
name: The anchor to scroll to.
"""
self._current_widget().scroller.to_anchor(name)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
@cmdutils.argument('top_navigate', metavar='ACTION',
@@ -846,7 +855,7 @@ class CommandDispatcher:
keep: Stay in visual mode after yanking the selection.
"""
if what == 'title':
s = self._tabbed_browser.page_title(self._current_index())
s = self._tabbed_browser.widget.page_title(self._current_index())
elif what == 'domain':
port = self._current_url().port()
s = '{}://{}{}'.format(self._current_url().scheme(),
@@ -958,7 +967,7 @@ class CommandDispatcher:
force: Avoid confirmation for pinned tabs.
"""
cmdutils.check_exclusive((prev, next_), 'pn')
cur_idx = self._tabbed_browser.currentIndex()
cur_idx = self._tabbed_browser.widget.currentIndex()
assert cur_idx != -1
def _to_close(i):
@@ -1013,7 +1022,7 @@ class CommandDispatcher:
elif config.val.tabs.wrap:
self._set_current_index(newidx % self._count())
else:
raise cmdexc.CommandError("First tab")
log.webview.debug("First tab")
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
@@ -1033,7 +1042,7 @@ class CommandDispatcher:
elif config.val.tabs.wrap:
self._set_current_index(newidx % self._count())
else:
raise cmdexc.CommandError("Last tab")
log.webview.debug("Last tab")
def _resolve_buffer_index(self, index):
"""Resolve a buffer index to the tabbedbrowser and tab.
@@ -1075,11 +1084,11 @@ class CommandDispatcher:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if not 0 < idx <= tabbed_browser.count():
if not 0 < idx <= tabbed_browser.widget.count():
raise cmdexc.CommandError(
"There's no tab with index {}!".format(idx))
return (tabbed_browser, tabbed_browser.widget(idx-1))
return (tabbed_browser, tabbed_browser.widget.widget(idx-1))
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0)
@@ -1107,10 +1116,10 @@ class CommandDispatcher:
tabbed_browser, tab = self._resolve_buffer_index(index)
window = tabbed_browser.window()
window = tabbed_browser.widget.window()
window.activateWindow()
window.raise_()
tabbed_browser.setCurrentWidget(tab)
tabbed_browser.widget.setCurrentWidget(tab)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('index', choices=['last'])
@@ -1194,7 +1203,7 @@ class CommandDispatcher:
cur_idx = self._current_index()
cmdutils.check_overflow(cur_idx, 'int')
cmdutils.check_overflow(new_idx, 'int')
self._tabbed_browser.tabBar().moveTab(cur_idx, new_idx)
self._tabbed_browser.widget.tabBar().moveTab(cur_idx, new_idx)
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0, no_replace_variables=True)
@@ -1278,10 +1287,10 @@ class CommandDispatcher:
idx = self._current_index()
if idx != -1:
env['QUTE_TITLE'] = self._tabbed_browser.page_title(idx)
env['QUTE_TITLE'] = self._tabbed_browser.widget.page_title(idx)
# FIXME:qtwebengine: If tab is None, run_async will fail!
tab = self._tabbed_browser.currentWidget()
tab = self._tabbed_browser.widget.currentWidget()
try:
url = self._tabbed_browser.current_url()
@@ -1639,7 +1648,7 @@ class CommandDispatcher:
ed = editor.ExternalEditor(watch=True, parent=self._tabbed_browser)
ed.file_updated.connect(functools.partial(
self.on_file_updated, elem))
self.on_file_updated, ed, elem))
ed.editing_finished.connect(lambda: mainwindow.raise_window(
objreg.last_focused_window(), alert=False))
ed.edit(text, caret_position)
@@ -1654,7 +1663,7 @@ class CommandDispatcher:
tab = self._current_widget()
tab.elements.find_focused(self._open_editor_cb)
def on_file_updated(self, elem, text):
def on_file_updated(self, ed, elem, text):
"""Write the editor text into the form field and clean up tempfile.
Callback for GUIProcess when the edited text was updated.
@@ -1667,8 +1676,10 @@ class CommandDispatcher:
elem.set_value(text)
except webelem.OrphanedError as e:
message.error('Edited element vanished')
ed.backup()
except webelem.Error as e:
raise cmdexc.CommandError(str(e))
message.error(str(e))
ed.backup()
@cmdutils.register(instance='command-dispatcher', maxsplit=0,
scope='window')
@@ -2217,5 +2228,5 @@ class CommandDispatcher:
pass
return
window = self._tabbed_browser.window()
window = self._tabbed_browser.widget.window()
window.setWindowState(window.windowState() ^ Qt.WindowFullScreen)

View File

@@ -30,7 +30,8 @@ import textwrap
import attr
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
from qutebrowser.utils import log, standarddir, jinja, objreg, utils
from qutebrowser.utils import (log, standarddir, jinja, objreg, utils,
javascript)
from qutebrowser.commands import cmdutils
from qutebrowser.browser import downloads
@@ -91,7 +92,7 @@ class GreasemonkeyScript:
props = ""
script = cls(re.findall(cls.PROPS_REGEX, props), source)
script.script_meta = props
if not props:
if not script.includes:
script.includes = ['*']
return script
@@ -104,12 +105,13 @@ class GreasemonkeyScript:
browser's debugger/inspector will not match up to the line
numbers in the source script directly.
"""
return jinja.js_environment.get_template(
'greasemonkey_wrapper.js').render(
scriptName="/".join([self.namespace or '', self.name]),
scriptInfo=self._meta_json(),
scriptMeta=self.script_meta,
scriptSource=self._code)
template = jinja.js_environment.get_template('greasemonkey_wrapper.js')
return template.render(
scriptName=javascript.string_escape(
"/".join([self.namespace or '', self.name])),
scriptInfo=self._meta_json(),
scriptMeta=javascript.string_escape(self.script_meta),
scriptSource=self._code)
def _meta_json(self):
return json.dumps({

View File

@@ -682,7 +682,7 @@ class HintManager(QObject):
"""
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
tab = tabbed_browser.currentWidget()
tab = tabbed_browser.widget.currentWidget()
if tab is None:
raise cmdexc.CommandError("No WebView available yet!")

View File

@@ -162,6 +162,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
QTimer.singleShot(0, lambda: self._die(reply.errorString()))
def _do_cancel(self):
self._read_timer.stop()
if self._reply is not None:
self._reply.finished.disconnect(self._on_reply_finished)
self._reply.abort()

View File

@@ -76,11 +76,11 @@ class SignalFilter(QObject):
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
try:
tabidx = tabbed_browser.indexOf(tab)
tabidx = tabbed_browser.widget.indexOf(tab)
except RuntimeError:
# The tab has been deleted already
return
if tabidx == tabbed_browser.currentIndex():
if tabidx == tabbed_browser.widget.currentIndex():
if log_signal:
log.signals.debug("emitting: {} (tab {})".format(
debug.dbg_signal(signal, args), tabidx))

View File

@@ -22,7 +22,7 @@
import os
from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
from qutebrowser.browser import inspector
@@ -35,6 +35,8 @@ class WebEngineInspector(inspector.AbstractWebInspector):
super().__init__(parent)
self.port = None
view = QWebEngineView()
settings = view.settings()
settings.setAttribute(QWebEngineSettings.JavascriptEnabled, True)
self._set_widget(view)
def inspect(self, _page):

View File

@@ -26,16 +26,12 @@ Module attributes:
import os
import sip
from PyQt5.QtGui import QFont
from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
QWebEngineScript)
from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile
from qutebrowser.browser import shared
from qutebrowser.browser.webengine import spell
from qutebrowser.config import config, websettings
from qutebrowser.utils import (utils, standarddir, javascript, qtutils,
message, log, objreg)
from qutebrowser.utils import utils, standarddir, qtutils, message, log
# The default QWebEngineProfile
default_profile = None
@@ -169,133 +165,104 @@ class WebEngineSettings(websettings.AbstractSettings):
self._ATTRIBUTES[name] = [value]
def _init_stylesheet(profile):
"""Initialize custom stylesheets.
class ProfileSetter:
Partially inspired by QupZilla:
https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101
"""
old_script = profile.scripts().findScript('_qute_stylesheet')
if not old_script.isNull():
profile.scripts().remove(old_script)
"""Helper to set various settings on a profile."""
css = shared.get_user_stylesheet()
source = '\n'.join([
'"use strict";',
'window._qutebrowser = window._qutebrowser || {};',
utils.read_file('javascript/stylesheet.js'),
javascript.assemble('stylesheet', 'set_css', css),
])
def __init__(self, profile):
self._profile = profile
script = QWebEngineScript()
script.setName('_qute_stylesheet')
script.setInjectionPoint(QWebEngineScript.DocumentCreation)
script.setWorldId(QWebEngineScript.ApplicationWorld)
script.setRunsOnSubFrames(True)
script.setSourceCode(source)
profile.scripts().insert(script)
def init_profile(self):
"""Initialize settings on the given profile."""
self.set_http_headers()
self.set_http_cache_size()
self._init_attributes()
if qtutils.version_check('5.8'):
self._profile.setSpellCheckEnabled(True)
self.set_dictionary_language()
def _update_stylesheet():
"""Update the custom stylesheet in existing tabs."""
css = shared.get_user_stylesheet()
code = javascript.assemble('stylesheet', 'set_css', css)
for win_id, window in objreg.window_registry.items():
# We could be in the middle of destroying a window here
if sip.isdeleted(window):
continue
tab_registry = objreg.get('tab-registry', scope='window',
window=win_id)
for tab in tab_registry.values():
tab.run_js_async(code)
def _init_attributes(self):
"""Initialize hard-coded attributes."""
values = {
'FullScreenSupportEnabled': True,
'FocusOnNavigationEnabled': True,
}
settings = self._profile.settings()
for name, value in values.items():
attr = getattr(QWebEngineSettings, name, None)
if attr is not None:
settings.setAttribute(attr, value)
def set_http_headers(self):
"""Set the user agent and accept-language for the given profile.
def _set_http_headers(profile):
"""Set the user agent and accept-language for the given profile.
We override those per request in the URL interceptor (to allow for
per-domain values), but this one still gets used for things like
window.navigator.userAgent/.languages in JS.
"""
self._profile.setHttpUserAgent(config.val.content.headers.user_agent)
accept_language = config.val.content.headers.accept_language
if accept_language is not None:
self._profile.setHttpAcceptLanguage(accept_language)
We override those per request in the URL interceptor (to allow for
per-domain values), but this one still gets used for things like
window.navigator.userAgent/.languages in JS.
"""
profile.setHttpUserAgent(config.val.content.headers.user_agent)
accept_language = config.val.content.headers.accept_language
if accept_language is not None:
profile.setHttpAcceptLanguage(accept_language)
def set_http_cache_size(self):
"""Initialize the HTTP cache size for the given profile."""
size = config.val.content.cache.size
if size is None:
size = 0
else:
size = qtutils.check_overflow(size, 'int', fatal=False)
# 0: automatically managed by QtWebEngine
self._profile.setHttpCacheMaximumSize(size)
def _set_http_cache_size(profile):
"""Initialize the HTTP cache size for the given profile."""
size = config.val.content.cache.size
if size is None:
size = 0
else:
size = qtutils.check_overflow(size, 'int', fatal=False)
def set_persistent_cookie_policy(self):
"""Set the HTTP Cookie size for the given profile."""
assert not self._profile.isOffTheRecord()
if config.val.content.cookies.store:
value = QWebEngineProfile.AllowPersistentCookies
else:
value = QWebEngineProfile.NoPersistentCookies
self._profile.setPersistentCookiesPolicy(value)
# 0: automatically managed by QtWebEngine
profile.setHttpCacheMaximumSize(size)
def set_dictionary_language(self, warn=True):
"""Load the given dictionaries."""
filenames = []
for code in config.val.spellcheck.languages or []:
local_filename = spell.local_filename(code)
if not local_filename:
if warn:
message.warning("Language {} is not installed - see "
"scripts/dictcli.py in qutebrowser's "
"sources".format(code))
continue
filenames.append(local_filename)
def _set_persistent_cookie_policy(profile):
"""Set the HTTP Cookie size for the given profile."""
if config.val.content.cookies.store:
value = QWebEngineProfile.AllowPersistentCookies
else:
value = QWebEngineProfile.NoPersistentCookies
profile.setPersistentCookiesPolicy(value)
def _set_dictionary_language(profile, warn=True):
filenames = []
for code in config.val.spellcheck.languages or []:
local_filename = spell.local_filename(code)
if not local_filename:
if warn:
message.warning(
"Language {} is not installed - see scripts/dictcli.py "
"in qutebrowser's sources".format(code))
continue
filenames.append(local_filename)
log.config.debug("Found dicts: {}".format(filenames))
profile.setSpellCheckLanguages(filenames)
log.config.debug("Found dicts: {}".format(filenames))
self._profile.setSpellCheckLanguages(filenames)
def _update_settings(option):
"""Update global settings when qwebsettings changed."""
global_settings.update_setting(option)
if option in ['scrolling.bar', 'content.user_stylesheets']:
_init_stylesheet(default_profile)
_init_stylesheet(private_profile)
_update_stylesheet()
elif option in ['content.headers.user_agent',
'content.headers.accept_language']:
_set_http_headers(default_profile)
_set_http_headers(private_profile)
if option in ['content.headers.user_agent',
'content.headers.accept_language']:
default_profile.setter.set_http_headers()
private_profile.setter.set_http_headers()
elif option == 'content.cache.size':
_set_http_cache_size(default_profile)
_set_http_cache_size(private_profile)
default_profile.setter.set_http_cache_size()
private_profile.setter.set_http_cache_size()
elif (option == 'content.cookies.store' and
# https://bugreports.qt.io/browse/QTBUG-58650
qtutils.version_check('5.9', compiled=False)):
_set_persistent_cookie_policy(default_profile)
default_profile.setter.set_persistent_cookie_policy()
# We're not touching the private profile's cookie policy.
elif option == 'spellcheck.languages':
_set_dictionary_language(default_profile)
_set_dictionary_language(private_profile, warn=False)
def _init_profile(profile):
"""Init the given profile."""
_init_stylesheet(profile)
_set_http_headers(profile)
_set_http_cache_size(profile)
profile.settings().setAttribute(
QWebEngineSettings.FullScreenSupportEnabled, True)
if qtutils.version_check('5.8'):
profile.setSpellCheckEnabled(True)
_set_dictionary_language(profile)
default_profile.setter.set_dictionary_language()
private_profile.setter.set_dictionary_language(warn=False)
def _init_profiles():
@@ -303,53 +270,18 @@ def _init_profiles():
global default_profile, private_profile
default_profile = QWebEngineProfile.defaultProfile()
default_profile.setter = ProfileSetter(default_profile)
default_profile.setCachePath(
os.path.join(standarddir.cache(), 'webengine'))
default_profile.setPersistentStoragePath(
os.path.join(standarddir.data(), 'webengine'))
_init_profile(default_profile)
_set_persistent_cookie_policy(default_profile)
default_profile.setter.init_profile()
default_profile.setter.set_persistent_cookie_policy()
private_profile = QWebEngineProfile()
private_profile.setter = ProfileSetter(private_profile)
assert private_profile.isOffTheRecord()
_init_profile(private_profile)
def inject_userscripts():
"""Register user JavaScript files with the global profiles."""
# The Greasemonkey metadata block support in QtWebEngine only starts at
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in response
# to urlChanged.
if not qtutils.version_check('5.8'):
return
# Since we are inserting scripts into profile.scripts they won't
# just get replaced by new gm scripts like if we were injecting them
# ourselves so we need to remove all gm scripts, while not removing
# any other stuff that might have been added. Like the one for
# stylesheets.
greasemonkey = objreg.get('greasemonkey')
for profile in [default_profile, private_profile]:
scripts = profile.scripts()
for script in scripts.toList():
if script.name().startswith("GM-"):
log.greasemonkey.debug('Removing script: {}'
.format(script.name()))
removed = scripts.remove(script)
assert removed, script.name()
# Then add the new scripts.
for script in greasemonkey.all_scripts():
# @run-at (and @include/@exclude/@match) is parsed by
# QWebEngineScript.
new_script = QWebEngineScript()
new_script.setWorldId(QWebEngineScript.MainWorld)
new_script.setSourceCode(script.code())
new_script.setName("GM-{}".format(script.name))
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
log.greasemonkey.debug('adding script: {}'
.format(new_script.name()))
scripts.insert(new_script)
private_profile.setter.init_profile()
def init(args):

View File

@@ -33,7 +33,7 @@ from PyQt5.QtNetwork import QAuthenticator
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
from qutebrowser.config import configdata
from qutebrowser.config import configdata, config
from qutebrowser.browser import browsertab, mouse, shared
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
interceptor, webenginequtescheme,
@@ -73,10 +73,6 @@ def init():
download_manager.install(webenginesettings.private_profile)
objreg.register('webengine-download-manager', download_manager)
greasemonkey = objreg.get('greasemonkey')
greasemonkey.scripts_reloaded.connect(webenginesettings.inject_userscripts)
webenginesettings.inject_userscripts()
# Mapping worlds from usertypes.JsWorld to QWebEngineScript world IDs.
_JS_WORLD_MAP = {
@@ -234,7 +230,14 @@ class WebEngineCaret(browsertab.AbstractCaret):
self._tab.run_js_async(
javascript.assemble('caret', 'setPlatform', sys.platform))
self._js_call('setInitialCursor')
self._js_call('setInitialCursor', self._selection_cb)
def _selection_cb(self, enabled):
"""Emit selection_toggled based on setInitialCursor."""
if enabled is None:
log.webview.debug("Ignoring selection status None")
return
self.selection_toggled.emit(enabled)
@pyqtSlot(usertypes.KeyMode)
def _on_mode_left(self, mode):
@@ -301,7 +304,7 @@ class WebEngineCaret(browsertab.AbstractCaret):
self._js_call('moveToEndOfDocument')
def toggle_selection(self):
self._js_call('toggleSelection')
self._js_call('toggleSelection', self.selection_toggled.emit)
def drop_selection(self):
self._js_call('dropSelection')
@@ -356,9 +359,8 @@ class WebEngineCaret(browsertab.AbstractCaret):
self._tab.run_js_async(js_code, lambda jsret:
self._follow_selected_cb(jsret, tab))
def _js_call(self, command):
self._tab.run_js_async(
javascript.assemble('caret', command))
def _js_call(self, command, callback=None):
self._tab.run_js_async(javascript.assemble('caret', command), callback)
class WebEngineScroller(browsertab.AbstractScroller):
@@ -379,7 +381,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
def _repeated_key_press(self, key, count=1, modifier=Qt.NoModifier):
"""Send count fake key presses to this scroller's WebEngineTab."""
for _ in range(min(count, 5000)):
for _ in range(min(count, 1000)):
self._tab.key_press(key, modifier)
@pyqtSlot(QPointF)
@@ -432,6 +434,11 @@ class WebEngineScroller(browsertab.AbstractScroller):
js_code = javascript.assemble('window', 'scroll', point.x(), point.y())
self._tab.run_js_async(js_code)
def to_anchor(self, name):
url = self._tab.url()
url.setFragment(name)
self._tab.openurl(url)
def delta(self, x=0, y=0):
self._tab.run_js_async(javascript.assemble('window', 'scrollBy', x, y))
@@ -506,6 +513,9 @@ class WebEngineHistory(browsertab.AbstractHistory):
return qtutils.deserialize(data, self._history)
def load_items(self, items):
if items:
self._tab.predicted_navigation.emit(items[-1].url)
stream, _data, cur_data = tabhistory.serialize(items)
qtutils.deserialize_stream(stream, self._history)
@@ -627,33 +637,127 @@ class WebEngineTab(browsertab.AbstractTab):
self._set_widget(widget)
self._connect_signals()
self.backend = usertypes.Backend.QtWebEngine
self._init_js()
self._child_event_filter = None
self._saved_zoom = None
self._reload_url = None
config.instance.changed.connect(self._on_config_changed)
self._init_js()
@pyqtSlot(str)
def _on_config_changed(self, option):
if option in ['scrolling.bar', 'content.user_stylesheets']:
self._init_stylesheet()
self._update_stylesheet()
def _update_stylesheet(self):
"""Update the custom stylesheet in existing tabs."""
css = shared.get_user_stylesheet()
code = javascript.assemble('stylesheet', 'set_css', css)
self.run_js_async(code)
def _inject_early_js(self, name, js_code, *,
world=QWebEngineScript.ApplicationWorld,
subframes=False):
"""Inject the given script to run early on a page load.
This runs the script both on DocumentCreation and DocumentReady as on
some internal pages, DocumentCreation will not work.
That is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66011
"""
scripts = self._widget.page().scripts()
for injection in ['creation', 'ready']:
injection_points = {
'creation': QWebEngineScript.DocumentCreation,
'ready': QWebEngineScript.DocumentReady,
}
script = QWebEngineScript()
script.setInjectionPoint(injection_points[injection])
script.setSourceCode(js_code)
script.setWorldId(world)
script.setRunsOnSubFrames(subframes)
script.setName('_qute_{}_{}'.format(name, injection))
scripts.insert(script)
def _remove_early_js(self, name):
"""Remove an early QWebEngineScript."""
scripts = self._widget.page().scripts()
for injection in ['creation', 'ready']:
full_name = '_qute_{}_{}'.format(name, injection)
script = scripts.findScript(full_name)
if not script.isNull():
scripts.remove(script)
def _init_js(self):
js_code = '\n'.join([
'"use strict";',
'window._qutebrowser = window._qutebrowser || {};',
"""Initialize global qutebrowser JavaScript."""
js_code = javascript.wrap_global(
'scripts',
utils.read_file('javascript/scroll.js'),
utils.read_file('javascript/webelem.js'),
utils.read_file('javascript/caret.js'),
])
script = QWebEngineScript()
# We can't use DocumentCreation here as WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-66011
script.setInjectionPoint(QWebEngineScript.DocumentReady)
script.setSourceCode(js_code)
)
# FIXME:qtwebengine what about subframes=True?
self._inject_early_js('js', js_code, subframes=True)
self._init_stylesheet()
page = self._widget.page()
script.setWorldId(QWebEngineScript.ApplicationWorld)
greasemonkey = objreg.get('greasemonkey')
greasemonkey.scripts_reloaded.connect(self._inject_userscripts)
self._inject_userscripts()
# FIXME:qtwebengine what about runsOnSubFrames?
page.scripts().insert(script)
def _init_stylesheet(self):
"""Initialize custom stylesheets.
Partially inspired by QupZilla:
https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101
"""
self._remove_early_js('stylesheet')
css = shared.get_user_stylesheet()
js_code = javascript.wrap_global(
'stylesheet',
utils.read_file('javascript/stylesheet.js'),
javascript.assemble('stylesheet', 'set_css', css),
)
self._inject_early_js('stylesheet', js_code, subframes=True)
def _inject_userscripts(self):
"""Register user JavaScript files with the global profiles."""
# The Greasemonkey metadata block support in QtWebEngine only starts at
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in
# response to urlChanged.
if not qtutils.version_check('5.8'):
return
# Since we are inserting scripts into profile.scripts they won't
# just get replaced by new gm scripts like if we were injecting them
# ourselves so we need to remove all gm scripts, while not removing
# any other stuff that might have been added. Like the one for
# stylesheets.
greasemonkey = objreg.get('greasemonkey')
scripts = self._widget.page().scripts()
for script in scripts.toList():
if script.name().startswith("GM-"):
log.greasemonkey.debug('Removing script: {}'
.format(script.name()))
removed = scripts.remove(script)
assert removed, script.name()
# Then add the new scripts.
for script in greasemonkey.all_scripts():
# @run-at (and @include/@exclude/@match) is parsed by
# QWebEngineScript.
new_script = QWebEngineScript()
new_script.setWorldId(QWebEngineScript.MainWorld)
new_script.setSourceCode(script.code())
new_script.setName("GM-{}".format(script.name))
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
log.greasemonkey.debug('adding script: {}'
.format(new_script.name()))
scripts.insert(new_script)
def _install_event_filter(self):
self._widget.focusProxy().installEventFilter(self._mouse_event_filter)
fp = self._widget.focusProxy()
if fp is not None:
fp.installEventFilter(self._mouse_event_filter)
self._child_event_filter = mouse.ChildEventFilter(
eventfilter=self._mouse_event_filter, widget=self._widget,
parent=self)
@@ -669,9 +773,15 @@ class WebEngineTab(browsertab.AbstractTab):
self.zoom.set_factor(self._saved_zoom)
self._saved_zoom = None
def openurl(self, url):
def openurl(self, url, *, predict=True):
"""Open the given URL in this tab.
Arguments:
url: The QUrl to open.
predict: If set to False, predicted_navigation is not emitted.
"""
self._saved_zoom = self.zoom.factor()
self._openurl_prepare(url)
self._openurl_prepare(url, predict=predict)
self._widget.load(url)
def url(self, requested=False):
@@ -706,7 +816,6 @@ class WebEngineTab(browsertab.AbstractTab):
self._widget.shutdown()
def reload(self, *, force=False):
self.predicted_navigation.emit(self.url())
if force:
action = QWebEnginePage.ReloadAndBypassCache
else:
@@ -915,10 +1024,10 @@ class WebEngineTab(browsertab.AbstractTab):
if ok and self._reload_url is not None:
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
log.config.debug(
"Reloading {} because of config change".format(
"Loading {} again because of config change".format(
self._reload_url.toDisplayString()))
QTimer.singleShot(100, lambda url=self._reload_url:
self.openurl(url))
self.openurl(url, predict=False))
self._reload_url = None
if not qtutils.version_check('5.10', compiled=False):
@@ -931,6 +1040,7 @@ class WebEngineTab(browsertab.AbstractTab):
@pyqtSlot(QUrl)
def _on_predicted_navigation(self, url):
"""If we know we're going to visit an URL soon, change the settings."""
super()._on_predicted_navigation(url)
self.settings.update_for_url(url)
@pyqtSlot(usertypes.NavigationRequest)
@@ -994,4 +1104,6 @@ class WebEngineTab(browsertab.AbstractTab):
self.predicted_navigation.connect(self._on_predicted_navigation)
def event_target(self):
return self._widget.focusProxy()
fp = self._widget.focusProxy()
assert fp is not None
return fp

View File

@@ -196,9 +196,10 @@ class WebKitCaret(browsertab.AbstractCaret):
if mode != usertypes.KeyMode.caret:
return
self.selection_enabled = self._widget.hasSelection()
self.selection_toggled.emit(self.selection_enabled)
settings = self._widget.settings()
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True)
self.selection_enabled = self._widget.hasSelection()
if self._widget.isVisible():
# Sometimes the caret isn't immediately visible, but unfocusing
@@ -363,9 +364,7 @@ class WebKitCaret(browsertab.AbstractCaret):
def toggle_selection(self):
self.selection_enabled = not self.selection_enabled
mainwindow = objreg.get('main-window', scope='window',
window=self._tab.win_id)
mainwindow.status.set_mode_active(usertypes.KeyMode.caret, True)
self.selection_toggled.emit(self.selection_enabled)
def drop_selection(self):
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
@@ -427,6 +426,9 @@ class WebKitScroller(browsertab.AbstractScroller):
def to_point(self, point):
self._widget.page().mainFrame().setScrollPosition(point)
def to_anchor(self, name):
self._widget.page().mainFrame().scrollToAnchor(name)
def delta(self, x=0, y=0):
qtutils.check_overflow(x, 'int')
qtutils.check_overflow(y, 'int')
@@ -537,6 +539,9 @@ class WebKitHistory(browsertab.AbstractHistory):
return qtutils.deserialize(data, self._history)
def load_items(self, items):
if items:
self._tab.predicted_navigation.emit(items[-1].url)
stream, _data, user_data = tabhistory.serialize(items)
qtutils.deserialize_stream(stream, self._history)
for i, data in enumerate(user_data):
@@ -668,8 +673,8 @@ class WebKitTab(browsertab.AbstractTab):
settings = widget.settings()
settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True)
def openurl(self, url):
self._openurl_prepare(url)
def openurl(self, url, *, predict=True):
self._openurl_prepare(url, predict=predict)
self._widget.openurl(url)
def url(self, requested=False):
@@ -701,7 +706,6 @@ class WebKitTab(browsertab.AbstractTab):
self._widget.shutdown()
def reload(self, *, force=False):
self.predicted_navigation.emit(self.url())
if force:
action = QWebPage.ReloadAndBypassCache
else:

View File

@@ -239,7 +239,6 @@ class BrowserPage(QWebPage):
printdiag.setAttribute(Qt.WA_DeleteOnClose)
printdiag.open(lambda: frame.print(printdiag.printer()))
@pyqtSlot('QNetworkRequest')
def on_download_requested(self, request):
"""Called when the user wants to download a link.

View File

@@ -110,18 +110,18 @@ def _buffer(skip_win_id=None):
model = completionmodel.CompletionModel(column_widths=(6, 40, 54))
for win_id in objreg.window_registry:
if skip_win_id and win_id == skip_win_id:
if skip_win_id is not None and win_id == skip_win_id:
continue
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if tabbed_browser.shutting_down:
continue
tabs = []
for idx in range(tabbed_browser.count()):
tab = tabbed_browser.widget(idx)
for idx in range(tabbed_browser.widget.count()):
tab = tabbed_browser.widget.widget(idx)
tabs.append(("{}/{}".format(win_id, idx + 1),
tab.url().toDisplayString(),
tabbed_browser.page_title(idx)))
tabbed_browser.widget.page_title(idx)))
cat = listcategory.ListCategory("{}".format(win_id), tabs,
delete_func=delete_buffer)
model.add_category(cat)

View File

@@ -425,11 +425,7 @@ content.host_blocking.enabled:
content.host_blocking.lists:
default:
- "https://www.malwaredomainlist.com/hostslist/hosts.txt"
- "http://someonewhocares.org/hosts/hosts"
- "http://winhelp2002.mvps.org/hosts.zip"
- "http://malwaredomains.lehigh.edu/files/justdomains.zip"
- "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext"
- "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
type:
name: List
valtype: Url
@@ -1252,9 +1248,14 @@ tabs.favicons.scale:
`tabs.padding`.
tabs.favicons.show:
default: true
type: Bool
desc: Show favicons in the tab bar.
default: always
type:
name: String
valid_values:
- always: Always show favicons.
- never: Always hide favicons.
- pinned: Show favicons only on pinned tabs.
desc: When to show favicons in the tab bar.
tabs.last_close:
default: ignore
@@ -1325,7 +1326,10 @@ tabs.show:
tabs.show_switching_delay:
default: 800
type: Int
type:
name: Int
minval: 0
maxval: maxint
desc: "Duration (in milliseconds) to show the tab bar before hiding it when
tabs.show is set to 'switching'."
@@ -1406,6 +1410,19 @@ tabs.width:
desc: "Width (in pixels or as percentage of the window) of the tab bar if
it's vertical."
tabs.min_width:
default: -1
type:
name: Int
minval: -1
maxval: maxint
desc: >-
Minimum width (in pixels) of tabs (-1 for the default minimum size behavior).
This setting only applies when tabs are horizontal.
This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is False.
tabs.width.indicator:
renamed: tabs.indicator.width
@@ -1469,6 +1486,11 @@ url.incdec_segments:
desc: URL segments where `:navigate increment/decrement` will search for
a number.
url.open_base_url:
type: Bool
default: false
desc: Open base URL of the searchengine if a searchengine shortcut is invoked without parameters.
url.searchengines:
default:
DEFAULT: https://duckduckgo.com/?q={}
@@ -1513,10 +1535,15 @@ url.yank_ignored_parameters:
## window
window.hide_wayland_decoration:
renamed: window.hide_decoration
window.hide_decoration:
type: Bool
default: false
restart: true
desc: Hide the window decoration when using wayland.
desc: |
Hide the window decoration.
This setting requires a restart on Wayland.
window.title_format:
type:

View File

@@ -268,6 +268,15 @@ class YamlConfig(QObject):
del settings['bindings.default']
self._mark_changed()
# Option to show favicons only for pinned tabs changed the type of
# tabs.favicons.show from Bool to String
name = 'tabs.favicons.show'
if name in settings:
for scope, val in settings[name].items():
if isinstance(val, bool):
settings[name][scope] = 'always' if val else 'never'
self._mark_changed()
return settings
def _validate(self, settings):

View File

@@ -26,7 +26,8 @@ from PyQt5.QtWidgets import QMessageBox
from qutebrowser.config import (config, configdata, configfiles, configtypes,
configexc, configcommands)
from qutebrowser.utils import objreg, usertypes, log, standarddir, message
from qutebrowser.utils import (objreg, usertypes, log, standarddir, message,
qtutils)
from qutebrowser.misc import msgbox, objects
@@ -89,7 +90,7 @@ def _init_envvars():
if config.val.qt.force_platform is not None:
os.environ['QT_QPA_PLATFORM'] = config.val.qt.force_platform
if config.val.window.hide_wayland_decoration:
if config.val.window.hide_decoration:
os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1'
if config.val.qt.highdpi:
@@ -161,4 +162,12 @@ def qt_args(namespace):
argv += ['--' + name, value]
argv += ['--' + arg for arg in config.val.qt.args]
if (objects.backend == usertypes.Backend.QtWebEngine and
not qtutils.version_check('5.11', compiled=False)):
# WORKAROUND equivalent to
# https://codereview.qt-project.org/#/c/217932/
# Needed for Qt < 5.9.5 and < 5.10.1
argv.append('--disable-shared-workers')
return argv

View File

@@ -451,7 +451,7 @@ class List(BaseType):
def from_obj(self, value):
if value is None:
return []
return value
return [self.valtype.from_obj(v) for v in value]
def to_py(self, value):
self._basic_py_validation(value, list)
@@ -506,6 +506,16 @@ class ListOrValue(BaseType):
self.listtype = List(valtype, none_ok=none_ok, *args, **kwargs)
self.valtype = valtype
def _val_and_type(self, value):
"""Get the value and type to use for to_str/to_doc/from_str."""
if isinstance(value, list):
if len(value) == 1:
return value[0], self.valtype
else:
return value, self.listtype
else:
return value, self.valtype
def get_name(self):
return self.listtype.get_name() + ', or ' + self.valtype.get_name()
@@ -533,25 +543,15 @@ class ListOrValue(BaseType):
if value is None:
return ''
if isinstance(value, list):
if len(value) == 1:
return self.valtype.to_str(value[0])
else:
return self.listtype.to_str(value)
else:
return self.valtype.to_str(value)
val, typ = self._val_and_type(value)
return typ.to_str(val)
def to_doc(self, value, indent=0):
if value is None:
return 'empty'
if isinstance(value, list):
if len(value) == 1:
return self.valtype.to_doc(value[0], indent)
else:
return self.listtype.to_doc(value, indent)
else:
return self.valtype.to_doc(value, indent)
val, typ = self._val_and_type(value)
return typ.to_doc(val)
class FlagList(List):
@@ -1199,7 +1199,9 @@ class Dict(BaseType):
def from_obj(self, value):
if value is None:
return {}
return value
return {self.keytype.from_obj(key): self.valtype.from_obj(val)
for key, val in value.items()}
def _fill_fixed_keys(self, value):
"""Fill missing fixed keys with a None-value."""
@@ -1623,9 +1625,7 @@ class TimestampTemplate(BaseType):
"""An strftime-like template for timestamps.
See
https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior
for reference.
See https://sqlite.org/lang_datefunc.html for reference.
"""
def to_py(self, value):
@@ -1648,6 +1648,10 @@ class Key(BaseType):
"""A name of a key."""
def from_obj(self, value):
"""Make sure key sequences are always normalized."""
return str(keyutils.KeySequence.parse(value))
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:

View File

@@ -22,7 +22,7 @@
from PyQt5.QtGui import QFont
from qutebrowser.config import config, configutils
from qutebrowser.utils import log, usertypes, urlmatch
from qutebrowser.utils import log, usertypes, urlmatch, qtutils
from qutebrowser.misc import objects
UNSET = object()
@@ -141,6 +141,7 @@ class AbstractSettings:
Return:
A set of settings which actually changed.
"""
qtutils.ensure_valid(url)
changed_settings = set()
for values in config.instance:
if not values.opt.supports_pattern:

View File

@@ -2,3 +2,4 @@
pac_utils.js
# Actually a jinja template so eslint chokes on the {{}} syntax.
greasemonkey_wrapper.js
global_wrapper.js

View File

@@ -324,9 +324,8 @@ window._qutebrowser.caret = (function() {
const color = axs.color.parseColor(style.backgroundColor);
if (color &&
(style.opacity < 1 &&
(color.alpha *= style.opacity),
color.alpha !== 0 &&
(el.push(color), color.alpha === 1))) {
(color.alpha *= style.opacity), color.alpha !== 0 &&
(el.push(color), color.alpha === 1))) {
iter = !0;
break;
}
@@ -1270,13 +1269,14 @@ window._qutebrowser.caret = (function() {
funcs.setInitialCursor = () => {
if (!CaretBrowsing.initiated) {
CaretBrowsing.setInitialCursor();
return;
return CaretBrowsing.selectionEnabled;
}
if (window.getSelection().toString().length === 0) {
positionCaret();
}
CaretBrowsing.toggle();
return CaretBrowsing.selectionEnabled;
};
funcs.setPlatform = (platform) => {
@@ -1362,6 +1362,7 @@ window._qutebrowser.caret = (function() {
funcs.toggleSelection = () => {
CaretBrowsing.selectionEnabled = !CaretBrowsing.selectionEnabled;
return CaretBrowsing.selectionEnabled;
};
return funcs;

View File

@@ -0,0 +1,12 @@
(function() {
"use strict";
if (!("_qutebrowser" in window)) {
window._qutebrowser = {"initialized": {}};
}
if (window._qutebrowser.initialized["{{name}}"]) {
return;
}
{{code}}
window._qutebrowser.initialized["{{name}}"] = true;
})();

View File

@@ -1,5 +1,5 @@
(function() {
const _qute_script_id = "__gm_" + {{ scriptName | tojson }};
const _qute_script_id = "__gm_{{ scriptName }}";
function GM_log(text) {
console.log(text);
@@ -7,7 +7,7 @@
const GM_info = {
'script': {{ scriptInfo }},
'scriptMetaStr': {{ scriptMeta | tojson }},
'scriptMetaStr': "{{ scriptMeta }}",
'scriptWillUpdate': false,
'version': "0.0.1",
// so scripts don't expect exportFunction
@@ -100,11 +100,8 @@
const head = document.getElementsByTagName("head")[0];
if (head === undefined) {
document.onreadystatechange = function() {
if (document.readyState === "interactive") {
document.getElementsByTagName("head")[0].appendChild(oStyle);
}
};
// no head yet, stick it whereever
document.documentElement.appendChild(oStyle);
} else {
head.appendChild(oStyle);
}

View File

@@ -74,9 +74,8 @@ window._qutebrowser.webelem = (function() {
try {
return elem.selectionStart;
} catch (err) {
if (err instanceof (frame
? frame.DOMException
: DOMException) &&
if ((err instanceof DOMException ||
(frame && err instanceof frame.DOMException)) &&
err.name === "InvalidStateError") {
// nothing to do, caret_position is already null
} else {

View File

@@ -108,11 +108,43 @@ class BaseKeyParser(QObject):
assert not isinstance(seq, str), seq
match = sequence.matches(seq)
if match == QKeySequence.ExactMatch:
return (match, cmd)
return match, cmd
elif match == QKeySequence.PartialMatch:
result = QKeySequence.PartialMatch
return (result, None)
return result, None
def _match_without_modifiers(self, sequence):
"""Try to match a key with optional modifiers stripped."""
self._debug_log("Trying match without modifiers")
sequence = sequence.strip_modifiers()
match, binding = self._match_key(sequence)
return match, binding, sequence
def _match_key_mapping(self, sequence):
"""Try to match a key in bindings.key_mappings."""
self._debug_log("Trying match with key_mappings")
mapped = sequence.with_mappings(config.val.bindings.key_mappings)
if sequence != mapped:
self._debug_log("Mapped {} -> {}".format(
sequence, mapped))
match, binding = self._match_key(mapped)
sequence = mapped
return match, binding, sequence
return QKeySequence.NoMatch, None, sequence
def _match_count(self, sequence, dry_run):
"""Try to match a key as count."""
txt = str(sequence[-1]) # To account for sequences changed above.
if (txt.isdigit() and self._supports_count and
not (not self._count and txt == '0')):
self._debug_log("Trying match as count")
assert len(txt) == 1, txt
if not dry_run:
self._count += txt
self.keystring_updated.emit(self._count + str(self._sequence))
return True
return False
def handle(self, e, *, dry_run=False):
"""Handle a new keypress.
@@ -146,28 +178,15 @@ class BaseKeyParser(QObject):
self.clear_keystring()
return QKeySequence.NoMatch
# First, try a straightforward match
match, binding = self._match_key(sequence)
# If that doesn't match, try a key_mapping
if match == QKeySequence.NoMatch:
mapped = sequence.with_mappings(config.val.bindings.key_mappings)
if sequence != mapped:
self._debug_log("Mapped {} -> {}".format(
sequence, mapped))
match, binding = self._match_key(mapped)
sequence = mapped
# If that doesn't match either, try treating it as count.
if (match == QKeySequence.NoMatch and
txt.isdigit() and
self._supports_count and
not (not self._count and txt == '0')):
assert len(txt) == 1, txt
if not dry_run:
self._count += txt
self.keystring_updated.emit(self._count + str(self._sequence))
return QKeySequence.ExactMatch
match, binding, sequence = self._match_without_modifiers(sequence)
if match == QKeySequence.NoMatch:
match, binding, sequence = self._match_key_mapping(sequence)
if match == QKeySequence.NoMatch:
was_count = self._match_count(sequence, dry_run)
if was_count:
return QKeySequence.ExactMatch
if dry_run:
return match

View File

@@ -58,7 +58,8 @@ def is_special(key, modifiers):
_assert_plain_key(key)
_assert_plain_modifier(modifiers)
return not (_is_printable(key) and
modifiers in [Qt.ShiftModifier, Qt.NoModifier])
modifiers in [Qt.ShiftModifier, Qt.NoModifier,
Qt.KeypadModifier])
def is_modifier_key(key):
@@ -303,7 +304,8 @@ class KeyInfo:
key_string = key_string.lower()
# "special" binding
assert is_special(self.key, self.modifiers)
assert (is_special(self.key, self.modifiers) or
self.modifiers == Qt.KeypadModifier)
modifier_string = _modifiers_to_string(modifiers)
return '<{}{}>'.format(modifier_string, key_string)
@@ -505,11 +507,29 @@ class KeySequence:
not ev.text().isupper()):
modifiers = Qt.KeyboardModifiers()
# On macOS, swap Ctrl and Meta back
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-51293
if utils.is_mac:
if modifiers & Qt.ControlModifier and modifiers & Qt.MetaModifier:
pass
elif modifiers & Qt.ControlModifier:
modifiers &= ~Qt.ControlModifier
modifiers |= Qt.MetaModifier
elif modifiers & Qt.MetaModifier:
modifiers &= ~Qt.MetaModifier
modifiers |= Qt.ControlModifier
keys = list(self._iter_keys())
keys.append(key | int(modifiers))
return self.__class__(*keys)
def strip_modifiers(self):
"""Strip optional modifiers from keys."""
modifiers = Qt.KeypadModifier
keys = [key & ~modifiers for key in self._iter_keys()]
return self.__class__(*keys)
def with_mappings(self, mappings):
"""Get a new KeySequence with the given mappings applied."""
keys = []

View File

@@ -184,7 +184,8 @@ class MainWindow(QWidget):
private = bool(private)
self._private = private
self.tabbed_browser = tabbedbrowser.TabbedBrowser(win_id=self.win_id,
private=private)
private=private,
parent=self)
objreg.register('tabbed-browser', self.tabbed_browser, scope='window',
window=self.win_id)
self._init_command_dispatcher()
@@ -230,6 +231,7 @@ class MainWindow(QWidget):
config.instance.changed.connect(self._on_config_changed)
objreg.get("app").new_window.emit(self)
self._set_decoration(config.val.window.hide_decoration)
def _init_geometry(self, geometry):
"""Initialize the window geometry or load it from disk."""
@@ -327,7 +329,7 @@ class MainWindow(QWidget):
self.tabbed_browser)
objreg.register('command-dispatcher', dispatcher, scope='window',
window=self.win_id)
self.tabbed_browser.destroyed.connect(
self.tabbed_browser.widget.destroyed.connect(
functools.partial(objreg.delete, 'command-dispatcher',
scope='window', window=self.win_id))
@@ -344,13 +346,15 @@ class MainWindow(QWidget):
elif option == 'statusbar.position':
self._add_widgets()
self._update_overlay_geometries()
elif option == 'window.hide_decoration':
self._set_decoration(config.val.window.hide_decoration)
def _add_widgets(self):
"""Add or readd all widgets to the VBox."""
self._vbox.removeWidget(self.tabbed_browser)
self._vbox.removeWidget(self.tabbed_browser.widget)
self._vbox.removeWidget(self._downloadview)
self._vbox.removeWidget(self.status)
widgets = [self.tabbed_browser]
widgets = [self.tabbed_browser.widget]
downloads_position = config.val.downloads.position
if downloads_position == 'top':
@@ -469,7 +473,7 @@ class MainWindow(QWidget):
self.tabbed_browser.cur_scroll_perc_changed.connect(
status.percentage.set_perc)
self.tabbed_browser.tab_index_changed.connect(
self.tabbed_browser.widget.tab_index_changed.connect(
status.tabindex.on_tab_index_changed)
self.tabbed_browser.cur_url_changed.connect(status.url.set_url)
@@ -479,6 +483,10 @@ class MainWindow(QWidget):
self.tabbed_browser.cur_link_hovered.connect(status.url.set_hover_url)
self.tabbed_browser.cur_load_status_changed.connect(
status.url.on_load_status_changed)
self.tabbed_browser.cur_caret_selection_toggled.connect(
status.on_caret_selection_toggled)
self.tabbed_browser.cur_fullscreen_requested.connect(
self._on_fullscreen_requested)
self.tabbed_browser.cur_fullscreen_requested.connect(status.maybe_hide)
@@ -489,6 +497,16 @@ class MainWindow(QWidget):
completion_obj.on_clear_completion_selection)
cmd.hide_completion.connect(completion_obj.hide)
def _set_decoration(self, hidden):
"""Set the visibility of the window decoration via Qt."""
window_flags = Qt.Window
refresh_window = self.isVisible()
if hidden:
window_flags |= Qt.CustomizeWindowHint | Qt.NoDropShadowWindowHint
self.setWindowFlags(window_flags)
if refresh_window:
self.show()
@pyqtSlot(bool)
def _on_fullscreen_requested(self, on):
if not config.val.content.windowed_fullscreen:
@@ -517,7 +535,7 @@ class MainWindow(QWidget):
super().resizeEvent(e)
self._update_overlay_geometries()
self._downloadview.updateGeometry()
self.tabbed_browser.tabBar().refresh()
self.tabbed_browser.widget.tabBar().refresh()
def showEvent(self, e):
"""Extend showEvent to register us as the last-visible-main-window.
@@ -546,7 +564,7 @@ class MainWindow(QWidget):
if crashsignal.is_crashing:
e.accept()
return
tab_count = self.tabbed_browser.count()
tab_count = self.tabbed_browser.widget.count()
download_model = objreg.get('download-model', scope='window',
window=self.win_id)
download_count = download_model.running_downloads()

View File

@@ -596,6 +596,8 @@ class FilenamePrompt(_BasePrompt):
if config.val.prompt.filebrowser:
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
self._to_complete = ''
@pyqtSlot(str)
def _set_fileview_root(self, path, *, tabbed=False):
"""Set the root path for the file display."""
@@ -604,6 +606,9 @@ class FilenamePrompt(_BasePrompt):
separators += os.altsep
dirname = os.path.dirname(path)
basename = os.path.basename(path)
if not tabbed:
self._to_complete = ''
try:
if not path:
@@ -617,6 +622,7 @@ class FilenamePrompt(_BasePrompt):
elif os.path.isdir(dirname) and not tabbed:
# Input like /foo/ba -> show /foo contents
path = dirname
self._to_complete = basename
else:
return
except OSError:
@@ -634,7 +640,11 @@ class FilenamePrompt(_BasePrompt):
index: The QModelIndex of the selected element.
clicked: Whether the element was clicked.
"""
path = os.path.normpath(self._file_model.filePath(index))
if index == QModelIndex():
path = os.path.join(self._file_model.rootPath(), self._to_complete)
else:
path = os.path.normpath(self._file_model.filePath(index))
if clicked:
path += os.sep
else:
@@ -696,6 +706,7 @@ class FilenamePrompt(_BasePrompt):
assert last_index.isValid()
idx = selmodel.currentIndex()
if not idx.isValid():
# No item selected yet
idx = last_index if which == 'prev' else first_index
@@ -709,10 +720,24 @@ class FilenamePrompt(_BasePrompt):
if not idx.isValid():
idx = last_index if which == 'prev' else first_index
idx = self._do_completion(idx, which)
selmodel.setCurrentIndex(
idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
self._insert_path(idx, clicked=False)
def _do_completion(self, idx, which):
filename = self._file_model.fileName(idx)
while not filename.startswith(self._to_complete) and idx.isValid():
if which == 'prev':
idx = self._file_view.indexAbove(idx)
else:
assert which == 'next', which
idx = self._file_view.indexBelow(idx)
filename = self._file_model.fileName(idx)
return idx
def _allowed_commands(self):
return [('prompt-accept', 'Accept'), ('leave-mode', 'Abort')]

View File

@@ -32,7 +32,7 @@ class Backforward(textbase.TextBase):
def on_tab_cur_url_changed(self, tabs):
"""Called on URL changes."""
tab = tabs.currentWidget()
tab = tabs.widget.currentWidget()
if tab is None: # pragma: no cover
self.setText('')
self.hide()

View File

@@ -268,7 +268,7 @@ class StatusBar(QWidget):
"""Get the currently displayed tab."""
window = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
return window.currentWidget()
return window.widget.currentWidget()
def set_mode_active(self, mode, val):
"""Setter for self.{insert,command,caret}_active.
@@ -289,17 +289,9 @@ class StatusBar(QWidget):
log.statusbar.debug("Setting prompt flag to {}".format(val))
self._color_flags.prompt = val
elif mode == usertypes.KeyMode.caret:
tab = self._current_tab()
log.statusbar.debug("Setting caret flag - val {}, selection "
"{}".format(val, tab.caret.selection_enabled))
if val:
if tab.caret.selection_enabled:
self._set_mode_text("{} selection".format(mode.name))
self._color_flags.caret = ColorFlags.CaretMode.selection
else:
self._set_mode_text(mode.name)
self._color_flags.caret = ColorFlags.CaretMode.on
else:
if not val:
# Turning on is handled in on_current_caret_selection_toggled
log.statusbar.debug("Setting caret mode off")
self._color_flags.caret = ColorFlags.CaretMode.off
config.set_register_stylesheet(self, update=False)
@@ -377,6 +369,18 @@ class StatusBar(QWidget):
self.maybe_hide()
assert tab.private == self._color_flags.private
@pyqtSlot(bool)
def on_caret_selection_toggled(self, selection):
"""Update the statusbar when entering/leaving caret selection mode."""
log.statusbar.debug("Setting caret selection {}".format(selection))
if selection:
self._set_mode_text("caret selection")
self._color_flags.caret = ColorFlags.CaretMode.selection
else:
self._set_mode_text("caret")
self._color_flags.caret = ColorFlags.CaretMode.on
config.set_register_stylesheet(self, update=False)
def resizeEvent(self, e):
"""Extend resizeEvent of QWidget to emit a resized signal afterwards.

View File

@@ -22,7 +22,7 @@
import functools
import attr
from PyQt5.QtWidgets import QSizePolicy
from PyQt5.QtWidgets import QSizePolicy, QWidget
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl
from PyQt5.QtGui import QIcon
@@ -50,7 +50,7 @@ class TabDeletedError(Exception):
"""Exception raised when _tab_index is called for a deleted tab."""
class TabbedBrowser(tabwidget.TabWidget):
class TabbedBrowser(QWidget):
"""A TabWidget with QWebViews inside.
@@ -104,23 +104,25 @@ class TabbedBrowser(tabwidget.TabWidget):
cur_scroll_perc_changed = pyqtSignal(int, int)
cur_load_status_changed = pyqtSignal(str)
cur_fullscreen_requested = pyqtSignal(bool)
cur_caret_selection_toggled = pyqtSignal(bool)
close_window = pyqtSignal()
resized = pyqtSignal('QRect')
current_tab_changed = pyqtSignal(browsertab.AbstractTab)
new_tab = pyqtSignal(browsertab.AbstractTab, int)
def __init__(self, *, win_id, private, parent=None):
super().__init__(win_id, parent)
super().__init__(parent)
self.widget = tabwidget.TabWidget(win_id, parent=self)
self._win_id = win_id
self._tab_insert_idx_left = 0
self._tab_insert_idx_right = -1
self.shutting_down = False
self.tabCloseRequested.connect(self.on_tab_close_requested)
self.new_tab_requested.connect(self.tabopen)
self.currentChanged.connect(self.on_current_changed)
self.widget.tabCloseRequested.connect(self.on_tab_close_requested)
self.widget.new_tab_requested.connect(self.tabopen)
self.widget.currentChanged.connect(self.on_current_changed)
self.cur_load_started.connect(self.on_cur_load_started)
self.cur_fullscreen_requested.connect(self.tabBar().maybe_hide)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.cur_fullscreen_requested.connect(self.widget.tabBar().maybe_hide)
self.widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self._undo_stack = []
self._filter = signalfilter.SignalFilter(win_id, self)
self._now_focused = None
@@ -128,12 +130,12 @@ class TabbedBrowser(tabwidget.TabWidget):
self.search_options = {}
self._local_marks = {}
self._global_marks = {}
self.default_window_icon = self.window().windowIcon()
self.default_window_icon = self.widget.window().windowIcon()
self.private = private
config.instance.changed.connect(self._on_config_changed)
def __repr__(self):
return utils.get_repr(self, count=self.count())
return utils.get_repr(self, count=self.widget.count())
@pyqtSlot(str)
def _on_config_changed(self, option):
@@ -142,7 +144,7 @@ class TabbedBrowser(tabwidget.TabWidget):
elif option == 'window.title_format':
self._update_window_title()
elif option in ['tabs.title.format', 'tabs.title.format_pinned']:
self._update_tab_titles()
self.widget.update_tab_titles()
def _tab_index(self, tab):
"""Get the index of a given tab.
@@ -150,7 +152,7 @@ class TabbedBrowser(tabwidget.TabWidget):
Raises TabDeletedError if the tab doesn't exist anymore.
"""
try:
idx = self.indexOf(tab)
idx = self.widget.indexOf(tab)
except RuntimeError as e:
log.webview.debug("Got invalid tab ({})!".format(e))
raise TabDeletedError(e)
@@ -166,8 +168,8 @@ class TabbedBrowser(tabwidget.TabWidget):
iterating over the list.
"""
widgets = []
for i in range(self.count()):
widget = self.widget(i)
for i in range(self.widget.count()):
widget = self.widget.widget(i)
if widget is None:
log.webview.debug("Got None-widget in tabbedbrowser!")
else:
@@ -186,16 +188,16 @@ class TabbedBrowser(tabwidget.TabWidget):
if field is not None and ('{' + field + '}') not in title_format:
return
idx = self.currentIndex()
idx = self.widget.currentIndex()
if idx == -1:
# (e.g. last tab removed)
log.webview.debug("Not updating window title because index is -1")
return
fields = self.get_tab_fields(idx)
fields = self.widget.get_tab_fields(idx)
fields['id'] = self._win_id
title = title_format.format(**fields)
self.window().setWindowTitle(title)
self.widget.window().setWindowTitle(title)
def _connect_tab_signals(self, tab):
"""Set up the needed signals for tab."""
@@ -216,6 +218,8 @@ class TabbedBrowser(tabwidget.TabWidget):
self._filter.create(self.cur_load_status_changed, tab))
tab.fullscreen_requested.connect(
self._filter.create(self.cur_fullscreen_requested, tab))
tab.caret.selection_toggled.connect(
self._filter.create(self.cur_caret_selection_toggled, tab))
# misc
tab.scroller.perc_changed.connect(self.on_scroll_pos_changed)
tab.url_changed.connect(
@@ -247,8 +251,8 @@ class TabbedBrowser(tabwidget.TabWidget):
Return:
The current URL as QUrl.
"""
idx = self.currentIndex()
return super().tab_url(idx)
idx = self.widget.currentIndex()
return self.widget.tab_url(idx)
def shutdown(self):
"""Try to shut down all tabs cleanly."""
@@ -284,7 +288,7 @@ class TabbedBrowser(tabwidget.TabWidget):
new_undo: Whether the undo entry should be a new item in the stack.
"""
last_close = config.val.tabs.last_close
count = self.count()
count = self.widget.count()
if last_close == 'ignore' and count == 1:
return
@@ -311,7 +315,7 @@ class TabbedBrowser(tabwidget.TabWidget):
new_undo: Whether the undo entry should be a new item in the stack.
crashed: Whether we're closing a tab with crashed renderer process.
"""
idx = self.indexOf(tab)
idx = self.widget.indexOf(tab)
if idx == -1:
if crashed:
return
@@ -349,7 +353,7 @@ class TabbedBrowser(tabwidget.TabWidget):
self._undo_stack[-1].append(entry)
tab.shutdown()
self.removeTab(idx)
self.widget.removeTab(idx)
if not crashed:
# WORKAROUND for a segfault when we delete the crashed tab.
# see https://bugreports.qt.io/browse/QTBUG-58698
@@ -362,14 +366,14 @@ class TabbedBrowser(tabwidget.TabWidget):
last_close = config.val.tabs.last_close
use_current_tab = False
if last_close in ['blank', 'startpage', 'default-page']:
only_one_tab_open = self.count() == 1
no_history = len(self.widget(0).history) == 1
only_one_tab_open = self.widget.count() == 1
no_history = len(self.widget.widget(0).history) == 1
urls = {
'blank': QUrl('about:blank'),
'startpage': config.val.url.start_pages[0],
'default-page': config.val.url.default_page,
}
first_tab_url = self.widget(0).url()
first_tab_url = self.widget.widget(0).url()
last_close_urlstr = urls[last_close].toString().rstrip('/')
first_tab_urlstr = first_tab_url.toString().rstrip('/')
last_close_url_used = first_tab_urlstr == last_close_urlstr
@@ -378,13 +382,13 @@ class TabbedBrowser(tabwidget.TabWidget):
for entry in reversed(self._undo_stack.pop()):
if use_current_tab:
newtab = self.widget(0)
newtab = self.widget.widget(0)
use_current_tab = False
else:
newtab = self.tabopen(background=False, idx=entry.index)
newtab.history.deserialize(entry.history)
self.set_tab_pinned(newtab, entry.pinned)
self.widget.set_tab_pinned(newtab, entry.pinned)
@pyqtSlot('QUrl', bool)
def openurl(self, url, newtab):
@@ -395,15 +399,15 @@ class TabbedBrowser(tabwidget.TabWidget):
newtab: True to open URL in a new tab, False otherwise.
"""
qtutils.ensure_valid(url)
if newtab or self.currentWidget() is None:
if newtab or self.widget.currentWidget() is None:
self.tabopen(url, background=False)
else:
self.currentWidget().openurl(url)
self.widget.currentWidget().openurl(url)
@pyqtSlot(int)
def on_tab_close_requested(self, idx):
"""Close a tab via an index."""
tab = self.widget(idx)
tab = self.widget.widget(idx)
if tab is None:
log.webview.debug("Got invalid tab {} for index {}!".format(
tab, idx))
@@ -454,7 +458,7 @@ class TabbedBrowser(tabwidget.TabWidget):
"related {}, idx {}".format(
url, background, related, idx))
if (config.val.tabs.tabs_are_windows and self.count() > 0 and
if (config.val.tabs.tabs_are_windows and self.widget.count() > 0 and
not ignore_tabs_are_windows):
window = mainwindow.MainWindow(private=self.private)
window.show()
@@ -464,12 +468,12 @@ class TabbedBrowser(tabwidget.TabWidget):
related=related)
tab = browsertab.create(win_id=self._win_id, private=self.private,
parent=self)
parent=self.widget)
self._connect_tab_signals(tab)
if idx is None:
idx = self._get_new_tab_idx(related)
self.insertTab(idx, tab, "")
self.widget.insertTab(idx, tab, "")
if url is not None:
tab.openurl(url)
@@ -480,10 +484,11 @@ class TabbedBrowser(tabwidget.TabWidget):
# Make sure the background tab has the correct initial size.
# With a foreground tab, it's going to be resized correctly by the
# layout anyways.
tab.resize(self.currentWidget().size())
self.tab_index_changed.emit(self.currentIndex(), self.count())
tab.resize(self.widget.currentWidget().size())
self.widget.tab_index_changed.emit(self.widget.currentIndex(),
self.widget.count())
else:
self.setCurrentWidget(tab)
self.widget.setCurrentWidget(tab)
tab.show()
self.new_tab.emit(tab, idx)
@@ -526,15 +531,8 @@ class TabbedBrowser(tabwidget.TabWidget):
def _update_favicons(self):
"""Update favicons when config was changed."""
for i, tab in enumerate(self.widgets()):
if config.val.tabs.favicons.show:
self.setTabIcon(i, tab.icon())
if config.val.tabs.tabs_are_windows:
self.window().setWindowIcon(tab.icon())
else:
self.setTabIcon(i, QIcon())
if config.val.tabs.tabs_are_windows:
self.window().setWindowIcon(self.default_window_icon)
for tab in self.widgets():
self.widget.update_tab_favicon(tab)
@pyqtSlot()
def on_load_started(self, tab):
@@ -548,14 +546,14 @@ class TabbedBrowser(tabwidget.TabWidget):
except TabDeletedError:
# We can get signals for tabs we already deleted...
return
self._update_tab_title(idx)
self.widget.update_tab_title(idx)
if tab.data.keep_icon:
tab.data.keep_icon = False
else:
if (config.val.tabs.tabs_are_windows and
config.val.tabs.favicons.show):
self.window().setWindowIcon(self.default_window_icon)
if idx == self.currentIndex():
tab.data.should_show_icon()):
self.widget.window().setWindowIcon(self.default_window_icon)
if idx == self.widget.currentIndex():
self._update_window_title()
@pyqtSlot()
@@ -586,8 +584,8 @@ class TabbedBrowser(tabwidget.TabWidget):
return
log.webview.debug("Changing title for idx {} to '{}'".format(
idx, text))
self.set_page_title(idx, text)
if idx == self.currentIndex():
self.widget.set_page_title(idx, text)
if idx == self.widget.currentIndex():
self._update_window_title()
@pyqtSlot(browsertab.AbstractTab, QUrl)
@@ -604,8 +602,8 @@ class TabbedBrowser(tabwidget.TabWidget):
# We can get signals for tabs we already deleted...
return
if not self.page_title(idx):
self.set_page_title(idx, url.toDisplayString())
if not self.widget.page_title(idx):
self.widget.set_page_title(idx, url.toDisplayString())
@pyqtSlot(browsertab.AbstractTab, QIcon)
def on_icon_changed(self, tab, icon):
@@ -617,23 +615,23 @@ class TabbedBrowser(tabwidget.TabWidget):
tab: The WebView where the title was changed.
icon: The new icon
"""
if not config.val.tabs.favicons.show:
if not tab.data.should_show_icon():
return
try:
idx = self._tab_index(tab)
except TabDeletedError:
# We can get signals for tabs we already deleted...
return
self.setTabIcon(idx, icon)
self.widget.setTabIcon(idx, icon)
if config.val.tabs.tabs_are_windows:
self.window().setWindowIcon(icon)
self.widget.window().setWindowIcon(icon)
@pyqtSlot(usertypes.KeyMode)
def on_mode_left(self, mode):
"""Give focus to current tab if command mode was left."""
if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt,
usertypes.KeyMode.yesno]:
widget = self.currentWidget()
widget = self.widget.currentWidget()
log.modes.debug("Left status-input mode, focusing {!r}".format(
widget))
if widget is None:
@@ -649,7 +647,7 @@ class TabbedBrowser(tabwidget.TabWidget):
if idx == -1 or self.shutting_down:
# closing the last tab (before quitting) or shutting down
return
tab = self.widget(idx)
tab = self.widget.widget(idx)
if tab is None:
log.webview.debug("on_current_changed got called with invalid "
"index {}".format(idx))
@@ -677,8 +675,8 @@ class TabbedBrowser(tabwidget.TabWidget):
self._now_focused = tab
self.current_tab_changed.emit(tab)
QTimer.singleShot(0, self._update_window_title)
self._tab_insert_idx_left = self.currentIndex()
self._tab_insert_idx_right = self.currentIndex() + 1
self._tab_insert_idx_left = self.widget.currentIndex()
self._tab_insert_idx_right = self.widget.currentIndex() + 1
@pyqtSlot()
def on_cmd_return_pressed(self):
@@ -696,9 +694,9 @@ class TabbedBrowser(tabwidget.TabWidget):
stop = config.val.colors.tabs.indicator.stop
system = config.val.colors.tabs.indicator.system
color = utils.interpolate_color(start, stop, perc, system)
self.set_tab_indicator_color(idx, color)
self._update_tab_title(idx)
if idx == self.currentIndex():
self.widget.set_tab_indicator_color(idx, color)
self.widget.update_tab_title(idx)
if idx == self.widget.currentIndex():
self._update_window_title()
def on_load_finished(self, tab, ok):
@@ -715,23 +713,23 @@ class TabbedBrowser(tabwidget.TabWidget):
color = utils.interpolate_color(start, stop, 100, system)
else:
color = config.val.colors.tabs.indicator.error
self.set_tab_indicator_color(idx, color)
self._update_tab_title(idx)
if idx == self.currentIndex():
self.widget.set_tab_indicator_color(idx, color)
self.widget.update_tab_title(idx)
if idx == self.widget.currentIndex():
self._update_window_title()
tab.handle_auto_insert_mode(ok)
@pyqtSlot()
def on_scroll_pos_changed(self):
"""Update tab and window title when scroll position changed."""
idx = self.currentIndex()
idx = self.widget.currentIndex()
if idx == -1:
# (e.g. last tab removed)
log.webview.debug("Not updating scroll position because index is "
"-1")
return
self._update_window_title('scroll_pos')
self._update_tab_title(idx, 'scroll_pos')
self.widget.update_tab_title(idx, 'scroll_pos')
def _on_renderer_process_terminated(self, tab, status, code):
"""Show an error when a renderer process terminated."""
@@ -764,7 +762,7 @@ class TabbedBrowser(tabwidget.TabWidget):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58698
message.error(msg)
self._remove_tab(tab, crashed=True)
if self.count() == 0:
if self.widget.count() == 0:
self.tabopen(QUrl('about:blank'))
def resizeEvent(self, e):
@@ -801,7 +799,7 @@ class TabbedBrowser(tabwidget.TabWidget):
if key != "'":
message.error("Failed to set mark: url invalid")
return
point = self.currentWidget().scroller.pos_px()
point = self.widget.currentWidget().scroller.pos_px()
if key.isupper():
self._global_marks[key] = point, url
@@ -822,7 +820,7 @@ class TabbedBrowser(tabwidget.TabWidget):
except qtutils.QtValueError:
urlkey = None
tab = self.currentWidget()
tab = self.widget.currentWidget()
if key.isupper():
if key in self._global_marks:

View File

@@ -60,7 +60,7 @@ class TabWidget(QTabWidget):
self.setTabBar(bar)
bar.tabCloseRequested.connect(self.tabCloseRequested)
bar.tabMoved.connect(functools.partial(
QTimer.singleShot, 0, self._update_tab_titles))
QTimer.singleShot, 0, self.update_tab_titles))
bar.currentChanged.connect(self._on_current_changed)
bar.new_tab_requested.connect(self._on_new_tab_requested)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
@@ -108,7 +108,8 @@ class TabWidget(QTabWidget):
bar.set_tab_data(idx, 'pinned', pinned)
tab.data.pinned = pinned
self._update_tab_title(idx)
self.update_tab_favicon(tab)
self.update_tab_title(idx)
def tab_indicator_color(self, idx):
"""Get the tab indicator color for the given index."""
@@ -117,13 +118,13 @@ class TabWidget(QTabWidget):
def set_page_title(self, idx, title):
"""Set the tab title user data."""
self.tabBar().set_tab_data(idx, 'page-title', title)
self._update_tab_title(idx)
self.update_tab_title(idx)
def page_title(self, idx):
"""Get the tab title user data."""
return self.tabBar().page_title(idx)
def _update_tab_title(self, idx, field=None):
def update_tab_title(self, idx, field=None):
"""Update the tab text for the given tab.
Args:
@@ -148,9 +149,13 @@ class TabWidget(QTabWidget):
title = '' if fmt is None else fmt.format(**fields)
tabbar = self.tabBar()
# Only change the tab title if it changes, setting the tab title causes
# a size recalculation which is slow.
if tabbar.tabText(idx) != title:
tabbar.setTabText(idx, title)
tabbar.setTabToolTip(idx, title)
# always show only plain title in tooltips
tabbar.setTabToolTip(idx, fields['title'])
def get_tab_fields(self, idx):
"""Get the tab field data."""
@@ -197,20 +202,20 @@ class TabWidget(QTabWidget):
fields['scroll_pos'] = scroll_pos
return fields
def _update_tab_titles(self):
def update_tab_titles(self):
"""Update all texts."""
for idx in range(self.count()):
self._update_tab_title(idx)
self.update_tab_title(idx)
def tabInserted(self, idx):
"""Update titles when a tab was inserted."""
super().tabInserted(idx)
self._update_tab_titles()
self.update_tab_titles()
def tabRemoved(self, idx):
"""Update titles when a tab was removed."""
super().tabRemoved(idx)
self._update_tab_titles()
self.update_tab_titles()
def addTab(self, page, icon_or_text, text_or_empty=None):
"""Override addTab to use our own text setting logic.
@@ -296,6 +301,19 @@ class TabWidget(QTabWidget):
qtutils.ensure_valid(url)
return url
def update_tab_favicon(self, tab: QWidget):
"""Update favicon of the given tab."""
idx = self.indexOf(tab)
if tab.data.should_show_icon():
self.setTabIcon(idx, tab.icon())
if config.val.tabs.tabs_are_windows:
self.window().setWindowIcon(tab.icon())
else:
self.setTabIcon(idx, QIcon())
if config.val.tabs.tabs_are_windows:
self.window().setWindowIcon(self.window().windowIcon())
class TabBar(QTabBar):
@@ -358,7 +376,9 @@ class TabBar(QTabBar):
# Clear _minimum_tab_size_hint_helper cache when appropriate
if option in ["tabs.indicator.padding",
"tabs.padding",
"tabs.indicator.width"]:
"tabs.indicator.width",
"tabs.min_width",
"tabs.pinned.shrink"]:
self._minimum_tab_size_hint_helper.cache_clear()
def _on_show_switching_delay_changed(self):
@@ -477,7 +497,8 @@ class TabBar(QTabBar):
Args:
index: The index of the tab to get a size hint for.
ellipsis: Whether to use ellipsis to calculate width
instead of the tab's text.
instead of the tab's text.
Forced to False for pinned tabs.
Return:
A QSize of the smallest tab size we can make.
"""
@@ -489,14 +510,19 @@ class TabBar(QTabBar):
else:
icon_width = min(icon.actualSize(self.iconSize()).width(),
self.iconSize().width()) + icon_padding
pinned = self._tab_pinned(index)
if not self.vertical and pinned and config.val.tabs.pinned.shrink:
# Never consider ellipsis an option for horizontal pinned tabs
ellipsis = False
return self._minimum_tab_size_hint_helper(self.tabText(index),
icon_width,
ellipsis)
icon_width, ellipsis,
pinned)
@functools.lru_cache(maxsize=2**9)
def _minimum_tab_size_hint_helper(self, tab_text: str,
icon_width: int,
ellipsis: bool) -> QSize:
ellipsis: bool, pinned: bool) -> QSize:
"""Helper function to cache tab results.
Config values accessed in here should be added to _on_config_changed to
@@ -521,6 +547,10 @@ class TabBar(QTabBar):
height = self.fontMetrics().height() + padding_v
width = (text_width + icon_width +
padding_h + indicator_width)
min_width = config.val.tabs.min_width
if (not self.vertical and min_width > 0 and
not pinned or not config.val.tabs.pinned.shrink):
width = max(min_width, width)
return QSize(width, height)
def _pinned_statistics(self) -> (int, int):
@@ -550,6 +580,12 @@ class TabBar(QTabBar):
Return:
A QSize.
"""
if self.count() == 0:
# This happens on startup on macOS.
# We return it directly rather than setting `size' because we don't
# want to ensure it's valid in this special case.
return QSize()
minimum_size = self.minimumTabSizeHint(index)
height = minimum_size.height()
if self.vertical:
@@ -562,11 +598,6 @@ class TabBar(QTabBar):
else:
width = int(confwidth)
size = QSize(max(minimum_size.width(), width), height)
elif self.count() == 0:
# This happens on startup on macOS.
# We return it directly rather than setting `size' because we don't
# want to ensure it's valid in this special case.
return QSize()
else:
if config.val.tabs.pinned.shrink:
pinned = self._tab_pinned(index)
@@ -889,7 +920,7 @@ class TabBarStyle(QCommonStyle):
# reserve space for favicon when tab bar is vertical (issue #1968)
position = config.val.tabs.position
if (position in [QTabWidget.East, QTabWidget.West] and
config.val.tabs.favicons.show):
config.val.tabs.favicons.show != 'never'):
tab_icon_size = icon_size
else:
actual_size = opt.icon.actualSize(icon_size, icon_mode, icon_state)

View File

@@ -166,8 +166,9 @@ def _nvidia_shader_workaround():
See https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
"""
assert objects.backend == usertypes.Backend.QtWebEngine, objects.backend
if utils.is_linux:
ctypes.CDLL(ctypes.util.find_library("GL"), mode=ctypes.RTLD_GLOBAL)
libgl = ctypes.util.find_library("GL")
if libgl is not None:
ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL)
def _handle_nouveau_graphics():

View File

@@ -42,6 +42,7 @@ class ExternalEditor(QObject):
_proc: The GUIProcess of the editor.
_watcher: A QFileSystemWatcher to watch the edited file for changes.
Only set if watch=True.
_content: The last-saved text of the editor.
Signals:
file_updated: The text in the edited file was updated.
@@ -112,19 +113,7 @@ class ExternalEditor(QObject):
if self._filename is not None:
raise ValueError("Already editing a file!")
try:
# Close while the external process is running, as otherwise systems
# with exclusive write access (e.g. Windows) may fail to update
# the file from the external editor, see
# https://github.com/qutebrowser/qutebrowser/issues/1767
with tempfile.NamedTemporaryFile(
# pylint: disable=bad-continuation
mode='w', prefix='qutebrowser-editor-',
encoding=config.val.editor.encoding,
delete=False) as fobj:
# pylint: enable=bad-continuation
if text:
fobj.write(text)
self._filename = fobj.name
self._filename = self._create_tempfile(text, 'qutebrowser-editor-')
except OSError as e:
message.error("Failed to create initial file: {}".format(e))
return
@@ -134,6 +123,32 @@ class ExternalEditor(QObject):
line, column = self._calc_line_and_column(text, caret_position)
self._start_editor(line=line, column=column)
def backup(self):
"""Create a backup if the content has changed from the original."""
if not self._content:
return
try:
fname = self._create_tempfile(self._content,
'qutebrowser-editor-backup-')
message.info('Editor backup at {}'.format(fname))
except OSError as e:
message.error('Failed to create editor backup: {}'.format(e))
def _create_tempfile(self, text, prefix):
# Close while the external process is running, as otherwise systems
# with exclusive write access (e.g. Windows) may fail to update
# the file from the external editor, see
# https://github.com/qutebrowser/qutebrowser/issues/1767
with tempfile.NamedTemporaryFile(
# pylint: disable=bad-continuation
mode='w', prefix=prefix,
encoding=config.val.editor.encoding,
delete=False) as fobj:
# pylint: enable=bad-continuation
if text:
fobj.write(text)
return fobj.name
@pyqtSlot(str)
def _on_file_changed(self, path):
try:

View File

@@ -130,7 +130,7 @@ class KeyHintView(QLabel):
).format(
html.escape(prefix),
suffix_color,
html.escape(str(seq[len(prefix):])),
html.escape(str(seq)[len(prefix):]),
html.escape(cmd)
)
text = '<table>{}</table>'.format(text)

View File

@@ -246,7 +246,7 @@ class SessionManager(QObject):
if tabbed_browser.private:
win_data['private'] = True
for i, tab in enumerate(tabbed_browser.widgets()):
active = i == tabbed_browser.currentIndex()
active = i == tabbed_browser.widget.currentIndex()
win_data['tabs'].append(self._save_tab(tab, active))
data['windows'].append(win_data)
return data
@@ -427,11 +427,12 @@ class SessionManager(QObject):
if tab.get('active', False):
tab_to_focus = i
if new_tab.data.pinned:
tabbed_browser.set_tab_pinned(new_tab, new_tab.data.pinned)
tabbed_browser.widget.set_tab_pinned(new_tab,
new_tab.data.pinned)
if tab_to_focus is not None:
tabbed_browser.setCurrentIndex(tab_to_focus)
tabbed_browser.widget.setCurrentIndex(tab_to_focus)
if win.get('active', False):
QTimer.singleShot(0, tabbed_browser.activateWindow)
QTimer.singleShot(0, tabbed_browser.widget.activateWindow)
if data['windows']:
self.did_load = True

View File

@@ -185,7 +185,7 @@ def debug_cache_stats():
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused')
# pylint: disable=protected-access
tab_bar = tabbed_browser.tabBar()
tab_bar = tabbed_browser.widget.tabBar()
tabbed_browser_info = tab_bar._minimum_tab_size_hint_helper.cache_info()
# pylint: enable=protected-access

View File

@@ -87,10 +87,11 @@ def log_signals(obj):
return ret
obj.__init__ = new_init
return obj
else:
connect_log_slot(obj)
return obj
def qenum_key(base, value, add_base=False, klass=None):
"""Convert a Qt Enum value to its key as a string.

View File

@@ -20,6 +20,9 @@
"""Utilities related to javascript interaction."""
from qutebrowser.utils import jinja
def string_escape(text):
"""Escape values special to javascript in strings.
@@ -70,3 +73,9 @@ def assemble(module, function, *args):
parts = ['window', '_qutebrowser', module, function]
code = '"use strict";\n{}({});'.format('.'.join(parts), js_args)
return code
def wrap_global(name, *sources):
"""Wrap a script using window._qutebrowser."""
template = jinja.js_environment.get_template('global_wrapper.js')
return template.render(code='\n'.join(sources), name=name)

View File

@@ -171,7 +171,7 @@ def _get_tab_registry(win_id, tab_id):
if tab_id == 'current':
tabbed_browser = get('tabbed-browser', scope='window', window=win_id)
tab = tabbed_browser.currentWidget()
tab = tabbed_browser.widget.currentWidget()
if tab is None:
raise RegistryUnavailableError('window')
tab_id = tab.tab_id

View File

@@ -102,6 +102,12 @@ def _get_search_url(txt):
engine = 'DEFAULT'
template = config.val.url.searchengines[engine]
url = qurl_from_user_input(template.format(urllib.parse.quote(term)))
if config.val.url.open_base_url and term in config.val.url.searchengines:
url = qurl_from_user_input(config.val.url.searchengines[term])
url.setPath(None)
url.setFragment(None)
url.setQuery(None)
qtutils.ensure_valid(url)
return url

View File

@@ -269,6 +269,8 @@ def _os_info():
else:
versioninfo = '.'.join(versioninfo)
osver = ', '.join([e for e in [release, versioninfo, machine] if e])
elif utils.is_posix:
osver = ' '.join(platform.uname())
else:
osver = '?'
lines.append('OS Version: {}'.format(osver))
@@ -315,8 +317,10 @@ def _chromium_version():
Qt 5.8: Chromium 53
Qt 5.9: Chromium 56
Qt 5.10: Chromium 61
Qt 5.11: Chromium 63
Qt 5.12: Chromium 65 (?)
Qt 5.11: Chromium 65
Qt 5.12: Chromium 69 (?)
Also see https://www.chromium.org/developers/calendar
"""
if QWebEngineProfile is None:
# This should never happen
@@ -453,7 +457,13 @@ def opengl_vendor(): # pragma: no cover
vp = QOpenGLVersionProfile()
vp.setVersion(2, 0)
vf = ctx.versionFunctions(vp)
try:
vf = ctx.versionFunctions(vp)
except ImportError as e:
log.init.debug("opengl_vendor: Importing version functions "
"failed: {}".format(e))
return None
if vf is None:
log.init.debug("opengl_vendor: Getting version functions failed!")
return None

View File

@@ -24,6 +24,7 @@
import os
import os.path
import sys
import time
import glob
import shutil
import plistlib
@@ -195,6 +196,7 @@ def build_mac():
'MacOS', 'qutebrowser')
smoke_test(binary)
finally:
time.sleep(5)
subprocess.run(['hdiutil', 'detach', tmpdir])
except PermissionError as e:
print("Failed to remove tempdir: {}".format(e))
@@ -359,7 +361,7 @@ def github_upload(artifacts, tag):
repo = gh.repository('qutebrowser', 'qutebrowser')
release = None # to satisfy pylint
for release in repo.iter_releases():
for release in repo.releases():
if release.tag_name == tag:
break
else:
@@ -399,14 +401,6 @@ def main():
run_asciidoc2html(args)
if os.name == 'nt':
if sys.maxsize > 2**32:
# WORKAROUND
print("Due to a python/Windows bug, this script needs to be run ")
print("with a 32bit Python.")
print()
print("See http://bugs.python.org/issue24493 and ")
print("https://github.com/pypa/virtualenv/issues/774")
sys.exit(1)
artifacts = build_windows()
elif sys.platform == 'darwin':
artifacts = build_mac()

View File

@@ -246,6 +246,8 @@ def apply_fake_os(monkeypatch, request):
elif name == 'linux':
linux = True
posix = True
elif name == 'posix':
posix = True
else:
raise ValueError("Invalid fake_os {}".format(name))

View File

@@ -0,0 +1,13 @@
<html>
<!-- https://github.com/qutebrowser/qutebrowser/issues/3711 -->
<head>
<title>Issue 3711</title>
</head>
<body>
<!--
Verify no hint error occurs when hinting input range elements in iframes on qt5.9
Possibly an issue in chrome.
-->
<input min="0" max="1" step="0.001" type="range">
</body>
</html>

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Issue 3771 Parent Frame</title>
</head>
<body>
<iframe src="./issue3711.html"></iframe>
</body>
</html>

View File

@@ -3,10 +3,10 @@
<head>
<script type="text/javascript">
var my_window;
let my_window;
function open_modal() {
window.open('about:blank', 'window', 'modal');
my_window = window.open('about:blank', 'window', 'modal');
}
function open_normal() {
@@ -17,13 +17,15 @@
window.open('', 'my_window');
}
function close() {
function close_normal() {
my_window.close();
console.log("window closed");
}
function close_twice() {
my_window.close();
my_window.close();
console.log("window closed");
}
</script>
</head>
@@ -33,7 +35,7 @@
<button onclick="open_normal()" id="open-normal">normal</button>
<button onclick="open_modal()" id="open-modal">modal</button>
<button onclick="open_invalid()" id="open-invalid">invalid/no URL</button>
<button onclick="close()" id="close-normal">close</button>
<button onclick="close_normal()" id="close-normal">close</button>
<button onclick="close_twice()" id="close-twice">close twice (issue 906)</button>
</body>

View File

@@ -128,6 +128,7 @@ Feature: Opening external editors
And I run :tab-close
And I kill the waiting editor
Then the error "Edited element vanished" should be shown
And the message "Editor backup at *" should be shown
# Could not get signals working on Windows
@posix

View File

@@ -249,6 +249,11 @@ Feature: Using hints
And I hint with args "all current" and follow a
Then no crash should happen
Scenario: No error when hinting ranged input in frames
When I open data/hints/issue3711_frame.html
And I hint with args "all current" and follow a
Then no crash should happen
### hints.auto_follow.timeout
@not_mac @flaky

View File

@@ -8,6 +8,7 @@ Feature: Javascript stuff
When I open data/javascript/consolelog.html
Then the javascript message "console.log works!" should be logged
@flaky
Scenario: Opening/Closing a window via JS
When I open data/javascript/window_open.html
And I run :tab-only
@@ -15,7 +16,10 @@ Feature: Javascript stuff
And I wait for "Changing title for idx 1 to 'about:blank'" in the log
And I run :tab-focus 1
And I run :click-element id close-normal
And I wait for "[*] window closed" in the log
Then "Focus object changed: *" should be logged
And the following tabs should be open:
- data/javascript/window_open.html (active)
@qtwebkit_skip
Scenario: Opening/closing a modal window via JS
@@ -25,8 +29,11 @@ Feature: Javascript stuff
And I wait for "Changing title for idx 1 to 'about:blank'" in the log
And I run :tab-focus 1
And I run :click-element id close-normal
And I wait for "[*] window closed" in the log
Then "Focus object changed: *" should be logged
And "Web*Dialog requested, but we don't support that!" should be logged
And the following tabs should be open:
- data/javascript/window_open.html (active)
# https://github.com/qutebrowser/qutebrowser/issues/906
@@ -39,6 +46,7 @@ Feature: Javascript stuff
And I wait for "Changing title for idx 2 to 'about:blank'" in the log
And I run :tab-focus 2
And I run :click-element id close-twice
And I wait for "[*] window closed" in the log
Then "Requested to close * which does not exist!" should be logged
@qtwebkit_skip @flaky
@@ -51,6 +59,7 @@ Feature: Javascript stuff
And I run :buffer window_open.html
And I run :click-element id close-twice
And I wait for "Focus object changed: *" in the log
And I wait for "[*] window closed" in the log
Then no crash should happen
@flaky
@@ -130,6 +139,7 @@ Feature: Javascript stuff
And I run :tab-next
Then the window sizes should be the same
@flaky
Scenario: Have a GreaseMonkey script run at page start
When I have a GreaseMonkey file saved for document-start with noframes unset
And I run :greasemonkey-reload
@@ -173,3 +183,15 @@ Feature: Javascript stuff
When I set content.javascript.enabled to false
And I open 500 without waiting
Then "Showing error page for* 500" should be logged
Scenario: Using JS after window.open
When I open data/hello.txt
And I set content.javascript.can_open_tabs_automatically to true
And I run :jseval window.open('about:blank')
And I open data/hello.txt
And I run :tab-only
And I open data/hints/html/simple.html
And I run :hint all
And I wait for "hints: a" in the log
And I run :leave-mode
Then "There was an error while getting hint elements" should not be logged

View File

@@ -26,7 +26,7 @@ Feature: Using :navigate
# prev/next
Scenario: Navigating to previous page
When I open data/navigate
When I open data/navigate in a new tab
And I run :navigate prev
Then data/navigate/prev.html should be loaded

View File

@@ -336,13 +336,13 @@ Feature: Tab management
When I set tabs.wrap to false
And I open data/numbers/1.txt
And I run :tab-prev
Then the error "First tab" should be shown
Then "First tab" should be logged
Scenario: :tab-next with last tab without wrap
When I set tabs.wrap to false
And I open data/numbers/1.txt
And I run :tab-next
Then the error "Last tab" should be shown
Then "Last tab" should be logged
Scenario: :tab-prev on first tab with wrap
When I set tabs.wrap to true

View File

@@ -101,6 +101,9 @@ def is_ignored_lowlevel_message(message):
' Error: No such file or directory',
# Qt 5.7.1
'qt.network.ssl: QSslSocket: cannot call unresolved function *',
# Qt 5.11
# DevTools listening on ws://127.0.0.1:37945/devtools/browser/...
'DevTools listening on *',
]
return any(testutils.pattern_match(pattern=pattern, value=message)
for pattern in ignored_messages)
@@ -169,7 +172,7 @@ def is_ignored_chromium_message(line):
# /tmp/pytest-of-florian/pytest-32/test_webengine_download_suffix0/
# downloads/download.bin: Operation not supported
('Could not set extended attribute user.xdg.* on file *: '
'Operation not supported'),
'Operation not supported*'),
# [5947:5947:0605/192837.856931:ERROR:render_process_impl.cc(112)]
# WebFrame LEAKED 1 TIMES
'WebFrame LEAKED 1 TIMES',
@@ -192,6 +195,15 @@ def is_ignored_chromium_message(line):
# [2734:2746:1107/131154.072032:ERROR:nss_ocsp.cc(591)] No
# URLRequestContext for NSS HTTP handler. host: ocsp.digicert.com
'No URLRequestContext for NSS HTTP handler. host: *',
# https://bugreports.qt.io/browse/QTBUG-66661
# [23359:23359:0319/115812.168578:WARNING:
# render_frame_host_impl.cc(2744)] OnDidStopLoading was called twice.
'OnDidStopLoading was called twice.',
# [30412:30412:0323/074933.387250:ERROR:node_channel.cc(899)] Dropping
# message on closed channel.
'Dropping message on closed channel.',
]
return any(testutils.pattern_match(pattern=pattern, value=message)
for pattern in ignored_messages)
@@ -345,6 +357,10 @@ class QuteProc(testprocess.Process):
# when calling QApplication::sync
"Focus object changed: "
"<PyQt5.QtWidgets.QWidget object at *>",
# Qt >= 5.11
"Focus object changed: "
"<qutebrowser.browser.webengine.webview.WebEngineView object "
"at *>",
]
if (log_line.category == 'ipc' and

View File

@@ -379,6 +379,7 @@ def test_qute_settings_persistence(short_tmpdir, request, quteproc_new):
@pytest.mark.no_xvfb
@pytest.mark.no_ci
@pytest.mark.not_mac
def test_force_software_rendering(request, quteproc_new):
"""Make sure we can force software rendering with -s."""
if not request.config.webengine:

View File

@@ -43,7 +43,8 @@ import helpers.stubs as stubsmod
import helpers.utils
from qutebrowser.config import (config, configdata, configtypes, configexc,
configfiles)
from qutebrowser.utils import objreg, standarddir
from qutebrowser.utils import objreg, standarddir, utils
from qutebrowser.browser import greasemonkey
from qutebrowser.browser.webkit import cookies
from qutebrowser.misc import savemanager, sql
from qutebrowser.keyinput import modeman
@@ -143,6 +144,47 @@ def fake_web_tab(stubs, tab_registry, mode_manager, qapp):
return stubs.FakeWebTab
@pytest.fixture
def greasemonkey_manager(data_tmpdir):
gm_manager = greasemonkey.GreasemonkeyManager()
objreg.register('greasemonkey', gm_manager)
yield
objreg.delete('greasemonkey')
@pytest.fixture
def webkit_tab(qtbot, tab_registry, cookiejar_and_cache, mode_manager,
session_manager_stub, greasemonkey_manager):
webkittab = pytest.importorskip('qutebrowser.browser.webkit.webkittab')
tab = webkittab.WebKitTab(win_id=0, mode_manager=mode_manager,
private=False)
qtbot.add_widget(tab)
return tab
@pytest.fixture
def webengine_tab(qtbot, tab_registry, fake_args, mode_manager,
session_manager_stub, greasemonkey_manager,
redirect_webengine_data):
webenginetab = pytest.importorskip(
'qutebrowser.browser.webengine.webenginetab')
tab = webenginetab.WebEngineTab(win_id=0, mode_manager=mode_manager,
private=False)
qtbot.add_widget(tab)
return tab
@pytest.fixture(params=['webkit', 'webengine'])
def web_tab(request):
"""A WebKitTab/WebEngineTab."""
if request.param == 'webkit':
return request.getfixturevalue('webkit_tab')
elif request.param == 'webengine':
return request.getfixturevalue('webengine_tab')
else:
raise utils.Unreachable
def _generate_cmdline_tests():
"""Generate testcases for test_split_binding."""
@attr.s
@@ -193,11 +235,15 @@ def configdata_init():
@pytest.fixture
def config_stub(stubs, monkeypatch, configdata_init, config_tmpdir):
"""Fixture which provides a fake config object."""
yaml_config = configfiles.YamlConfig()
def yaml_config_stub(config_tmpdir):
"""Fixture which provides a YamlConfig object."""
return configfiles.YamlConfig()
conf = config.Config(yaml_config=yaml_config)
@pytest.fixture
def config_stub(stubs, monkeypatch, configdata_init, yaml_config_stub):
"""Fixture which provides a fake config object."""
conf = config.Config(yaml_config=yaml_config_stub)
monkeypatch.setattr(config, 'instance', conf)
container = config.ConfigContainer(conf)

View File

@@ -27,6 +27,7 @@ import shutil
import attr
from PyQt5.QtCore import pyqtSignal, QPoint, QProcess, QObject, QUrl
from PyQt5.QtGui import QIcon
from PyQt5.QtNetwork import (QNetworkRequest, QAbstractNetworkCache,
QNetworkCacheMetaData)
from PyQt5.QtWidgets import QCommonStyle, QLineEdit, QWidget, QTabBar
@@ -266,6 +267,9 @@ class FakeWebTab(browsertab.AbstractTab):
def shutdown(self):
pass
def icon(self):
return QIcon()
class FakeSignal:
@@ -472,37 +476,55 @@ class SessionManagerStub:
def list_sessions(self):
return self.sessions
def save_autosave(self):
pass
class TabbedBrowserStub(QObject):
"""Stub for the tabbed-browser object."""
def __init__(self, parent=None):
super().__init__(parent)
self.widget = TabWidgetStub()
self.shutting_down = False
self.opened_url = None
def on_tab_close_requested(self, idx):
del self.widget.tabs[idx]
def widgets(self):
return self.widget.tabs
def tabopen(self, url):
self.opened_url = url
def openurl(self, url, *, newtab):
self.opened_url = url
class TabWidgetStub(QObject):
"""Stub for the tab-widget object."""
new_tab = pyqtSignal(browsertab.AbstractTab, int)
def __init__(self, parent=None):
super().__init__(parent)
self.tabs = []
self.shutting_down = False
self._qtabbar = QTabBar()
self.index_of = None
self.current_index = None
self.opened_url = None
def count(self):
return len(self.tabs)
def widgets(self):
return self.tabs
def widget(self, i):
return self.tabs[i]
def page_title(self, i):
return self.tabs[i].title()
def on_tab_close_requested(self, idx):
del self.tabs[idx]
def tabBar(self):
return self._qtabbar
@@ -526,12 +548,6 @@ class TabbedBrowserStub(QObject):
return None
return self.tabs[idx - 1]
def tabopen(self, url):
self.opened_url = url
def openurl(self, url, *, newtab):
self.opened_url = url
class ApplicationStub(QObject):

View File

@@ -120,8 +120,10 @@ def assert_urls(host_blocker, blocked=BLOCKLIST_HOSTS,
Ensure URLs in 'blocked' and not in 'whitelisted' are blocked.
All other URLs must not be blocked.
localhost is an example of a special case that shouldn't be blocked.
"""
whitelisted = list(whitelisted) + list(host_blocker.WHITELISTED)
whitelisted = list(whitelisted) + ['localhost']
for str_url in urls_to_check:
url = QUrl(str_url)
host = url.host()
@@ -247,6 +249,16 @@ def test_successful_update(config_stub, basedir, download_stub,
assert_urls(host_blocker, whitelisted=[])
def test_parsing_multiple_hosts_on_line(config_stub, basedir, download_stub,
data_tmpdir, tmpdir, win_registry,
caplog):
"""Ensure multiple hosts on a line get parsed correctly."""
host_blocker = adblock.HostBlocker()
bytes_host_line = ' '.join(BLOCKLIST_HOSTS).encode('utf-8')
host_blocker._parse_line(bytes_host_line)
assert_urls(host_blocker, whitelisted=[])
def test_failed_dl_update(config_stub, basedir, download_stub,
data_tmpdir, tmpdir, win_registry, caplog):
"""One blocklist fails to download.
@@ -341,7 +353,7 @@ def test_blocking_with_whitelist(config_stub, basedir, download_stub,
"""Ensure hosts in content.host_blocking.whitelist are never blocked."""
# Simulate adblock_update has already been run
# by creating a file named blocked-hosts,
# Exclude localhost from it, since localhost is in HostBlocker.WHITELISTED
# Exclude localhost from it as localhost is never blocked via list
filtered_blocked_hosts = BLOCKLIST_HOSTS[1:]
blocklist = create_blocklist(data_tmpdir,
blocked_hosts=filtered_blocked_hosts,

View File

@@ -68,8 +68,8 @@ def objects():
@pytest.mark.parametrize('index_of, emitted', [(0, True), (1, False)])
def test_filtering(objects, tabbed_browser_stubs, index_of, emitted):
browser = tabbed_browser_stubs[0]
browser.current_index = 0
browser.index_of = index_of
browser.widget.current_index = 0
browser.widget.index_of = index_of
objects.signaller.signal.emit('foo')
if emitted:
assert objects.signaller.filtered_signal_arg == 'foo'
@@ -80,8 +80,8 @@ def test_filtering(objects, tabbed_browser_stubs, index_of, emitted):
@pytest.mark.parametrize('index_of, verb', [(0, 'emitting'), (1, 'ignoring')])
def test_logging(caplog, objects, tabbed_browser_stubs, index_of, verb):
browser = tabbed_browser_stubs[0]
browser.current_index = 0
browser.index_of = index_of
browser.widget.current_index = 0
browser.widget.index_of = index_of
with caplog.at_level(logging.DEBUG, logger='signals'):
objects.signaller.signal.emit('foo')
@@ -94,8 +94,8 @@ def test_logging(caplog, objects, tabbed_browser_stubs, index_of, verb):
@pytest.mark.parametrize('index_of', [0, 1])
def test_no_logging(caplog, objects, tabbed_browser_stubs, index_of):
browser = tabbed_browser_stubs[0]
browser.current_index = 0
browser.index_of = index_of
browser.widget.current_index = 0
browser.widget.index_of = index_of
with caplog.at_level(logging.DEBUG, logger='signals'):
objects.signaller.link_hovered.emit('foo')
@@ -106,7 +106,7 @@ def test_no_logging(caplog, objects, tabbed_browser_stubs, index_of):
def test_runtime_error(objects, tabbed_browser_stubs):
"""Test that there's no crash if indexOf() raises RuntimeError."""
browser = tabbed_browser_stubs[0]
browser.current_index = 0
browser.index_of = RuntimeError
browser.widget.current_index = 0
browser.widget.index_of = RuntimeError
objects.signaller.signal.emit('foo')
assert objects.signaller.filtered_signal_arg is None

View File

@@ -1,110 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2018 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/>.
import pytest
from qutebrowser.browser import browsertab
from qutebrowser.utils import utils
pytestmark = pytest.mark.usefixtures('redirect_webengine_data')
try:
from PyQt5.QtWebKitWidgets import QWebView
except ImportError:
QWebView = None
try:
from PyQt5.QtWebEngineWidgets import QWebEngineView
except ImportError:
QWebEngineView = None
@pytest.fixture(params=[QWebView, QWebEngineView])
def view(qtbot, config_stub, request):
if request.param is None:
pytest.skip("View not available")
v = request.param()
qtbot.add_widget(v)
return v
@pytest.fixture(params=['webkit', 'webengine'])
def tab(request, qtbot, tab_registry, cookiejar_and_cache, mode_manager):
if request.param == 'webkit':
webkittab = pytest.importorskip('qutebrowser.browser.webkit.webkittab')
tab_class = webkittab.WebKitTab
elif request.param == 'webengine':
webenginetab = pytest.importorskip(
'qutebrowser.browser.webengine.webenginetab')
tab_class = webenginetab.WebEngineTab
else:
raise utils.Unreachable
t = tab_class(win_id=0, mode_manager=mode_manager)
qtbot.add_widget(t)
yield t
class Zoom(browsertab.AbstractZoom):
def _set_factor_internal(self, _factor):
pass
def factor(self):
raise utils.Unreachable
class Tab(browsertab.AbstractTab):
# pylint: disable=abstract-method
def __init__(self, win_id, mode_manager, parent=None):
super().__init__(win_id=win_id, mode_manager=mode_manager,
parent=parent)
self.history = browsertab.AbstractHistory(self)
self.scroller = browsertab.AbstractScroller(self, parent=self)
self.caret = browsertab.AbstractCaret(mode_manager=mode_manager,
tab=self, parent=self)
self.zoom = Zoom(tab=self)
self.search = browsertab.AbstractSearch(parent=self)
self.printing = browsertab.AbstractPrinting()
self.elements = browsertab.AbstractElements(tab=self)
self.action = browsertab.AbstractAction(tab=self)
def _install_event_filter(self):
pass
@pytest.mark.xfail(run=False, reason='Causes segfaults, see #1638')
def test_tab(qtbot, view, config_stub, tab_registry, mode_manager):
tab_w = Tab(win_id=0, mode_manager=mode_manager)
qtbot.add_widget(tab_w)
assert tab_w.win_id == 0
assert tab_w._widget is None
tab_w._set_widget(view)
assert tab_w._widget is view
assert tab_w.history._tab is tab_w
assert tab_w.history._history is view.history()
assert view.parent() is tab_w
with qtbot.waitExposed(tab_w):
tab_w.show()

View File

@@ -40,9 +40,7 @@ def test_big_cache_size(config_stub):
"""Make sure a too big cache size is handled correctly."""
config_stub.val.content.cache.size = 2 ** 63 - 1
profile = webenginesettings.default_profile
webenginesettings._set_http_cache_size(profile)
profile.setter.set_http_cache_size()
assert profile.httpCacheMaximumSize() == 2 ** 31 - 1

View File

@@ -97,10 +97,11 @@ def test_custom_env(qtbot, monkeypatch, py_proc, runner):
f.write('\n')
""")
with qtbot.waitSignal(runner.got_cmd, timeout=10000) as blocker:
runner.prepare_run(cmd, *args, env=env)
runner.store_html('')
runner.store_text('')
with qtbot.waitSignal(runner.finished, timeout=10000):
with qtbot.waitSignal(runner.got_cmd, timeout=10000) as blocker:
runner.prepare_run(cmd, *args, env=env)
runner.store_html('')
runner.store_text('')
data = blocker.args[0]
ret_env = json.loads(data)

View File

@@ -539,12 +539,12 @@ def test_session_completion(qtmodeltester, session_manager_stub):
def test_tab_completion(qtmodeltester, fake_web_tab, app_stub, win_registry,
tabbed_browser_stubs):
tabbed_browser_stubs[0].tabs = [
tabbed_browser_stubs[0].widget.tabs = [
fake_web_tab(QUrl('https://github.com'), 'GitHub', 0),
fake_web_tab(QUrl('https://wikipedia.org'), 'Wikipedia', 1),
fake_web_tab(QUrl('https://duckduckgo.com'), 'DuckDuckGo', 2),
]
tabbed_browser_stubs[1].tabs = [
tabbed_browser_stubs[1].widget.tabs = [
fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0),
]
model = miscmodels.buffer()
@@ -567,12 +567,12 @@ def test_tab_completion(qtmodeltester, fake_web_tab, app_stub, win_registry,
def test_tab_completion_delete(qtmodeltester, fake_web_tab, app_stub,
win_registry, tabbed_browser_stubs):
"""Verify closing a tab by deleting it from the completion widget."""
tabbed_browser_stubs[0].tabs = [
tabbed_browser_stubs[0].widget.tabs = [
fake_web_tab(QUrl('https://github.com'), 'GitHub', 0),
fake_web_tab(QUrl('https://wikipedia.org'), 'Wikipedia', 1),
fake_web_tab(QUrl('https://duckduckgo.com'), 'DuckDuckGo', 2)
]
tabbed_browser_stubs[1].tabs = [
tabbed_browser_stubs[1].widget.tabs = [
fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0),
]
model = miscmodels.buffer()
@@ -588,19 +588,19 @@ def test_tab_completion_delete(qtmodeltester, fake_web_tab, app_stub,
assert model.data(idx) == '0/2'
model.delete_cur_item(idx)
actual = [tab.url() for tab in tabbed_browser_stubs[0].tabs]
actual = [tab.url() for tab in tabbed_browser_stubs[0].widget.tabs]
assert actual == [QUrl('https://github.com'),
QUrl('https://duckduckgo.com')]
def test_other_buffer_completion(qtmodeltester, fake_web_tab, app_stub,
win_registry, tabbed_browser_stubs, info):
tabbed_browser_stubs[0].tabs = [
tabbed_browser_stubs[0].widget.tabs = [
fake_web_tab(QUrl('https://github.com'), 'GitHub', 0),
fake_web_tab(QUrl('https://wikipedia.org'), 'Wikipedia', 1),
fake_web_tab(QUrl('https://duckduckgo.com'), 'DuckDuckGo', 2),
]
tabbed_browser_stubs[1].tabs = [
tabbed_browser_stubs[1].widget.tabs = [
fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0),
]
info.win_id = 1
@@ -618,14 +618,37 @@ def test_other_buffer_completion(qtmodeltester, fake_web_tab, app_stub,
})
def test_other_buffer_completion_id0(qtmodeltester, fake_web_tab, app_stub,
win_registry, tabbed_browser_stubs, info):
tabbed_browser_stubs[0].widget.tabs = [
fake_web_tab(QUrl('https://github.com'), 'GitHub', 0),
fake_web_tab(QUrl('https://wikipedia.org'), 'Wikipedia', 1),
fake_web_tab(QUrl('https://duckduckgo.com'), 'DuckDuckGo', 2),
]
tabbed_browser_stubs[1].widget.tabs = [
fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0),
]
info.win_id = 0
model = miscmodels.other_buffer(info=info)
model.set_pattern('')
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
_check_completions(model, {
'1': [
('1/1', 'https://wiki.archlinux.org', 'ArchWiki'),
],
})
def test_window_completion(qtmodeltester, fake_web_tab, tabbed_browser_stubs,
info):
tabbed_browser_stubs[0].tabs = [
tabbed_browser_stubs[0].widget.tabs = [
fake_web_tab(QUrl('https://github.com'), 'GitHub', 0),
fake_web_tab(QUrl('https://wikipedia.org'), 'Wikipedia', 1),
fake_web_tab(QUrl('https://duckduckgo.com'), 'DuckDuckGo', 2)
]
tabbed_browser_stubs[1].tabs = [
tabbed_browser_stubs[1].widget.tabs = [
fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0)
]

View File

@@ -339,6 +339,24 @@ class TestKeyConfig:
key_config_stub.unbind(seq)
assert key_config_stub.get_command(seq, mode='normal') is None
def test_unbind_old_syntax(self, yaml_config_stub, key_config_stub,
config_stub):
"""Test unbinding bindings added before the keybinding refactoring.
We used to normalize keys differently, so we can have <ctrl+q> in the
config.
See https://github.com/qutebrowser/qutebrowser/issues/3699
"""
bindings = {'normal': {'<ctrl+q>': 'nop'}}
yaml_config_stub.set_obj('bindings.commands', bindings)
config_stub.read_yaml()
key_config_stub.unbind(keyutils.KeySequence.parse('<ctrl+q>'),
save_yaml=True)
assert config.instance.get_obj('bindings.commands') == {'normal': {}}
def test_empty_command(self, key_config_stub):
"""Try binding a key to an empty command."""
message = "Can't add binding 'x' with empty command in normal mode"

View File

@@ -371,7 +371,8 @@ class TestEdit:
"""Tests for :config-edit."""
pytestmark = pytest.mark.usefixtures('config_tmpdir', 'data_tmpdir',
'config_stub', 'key_config_stub')
'config_stub', 'key_config_stub',
'qapp')
def test_no_source(self, commands, mocker):
mock = mocker.patch('qutebrowser.config.configcommands.editor.'

View File

@@ -211,8 +211,11 @@ class TestYaml:
data = autoconfig.read()
assert data == {'tabs.show': {'global': 'value'}}
@pytest.mark.parametrize('persist', [True, False])
def test_merge_persist(self, yaml, autoconfig, persist):
@pytest.mark.parametrize('persist, expected', [
(True, 'persist'),
(False, 'normal'),
])
def test_merge_persist(self, yaml, autoconfig, persist, expected):
"""Tests for migration of tabs.persist_mode_on_change."""
autoconfig.write({'tabs.persist_mode_on_change': {'global': persist}})
yaml.load()
@@ -220,8 +223,7 @@ class TestYaml:
data = autoconfig.read()
assert 'tabs.persist_mode_on_change' not in data
mode = 'persist' if persist else 'normal'
assert data['tabs.mode_on_change']['global'] == mode
assert data['tabs.mode_on_change']['global'] == expected
def test_bindings_default(self, yaml, autoconfig):
"""Make sure bindings.default gets removed from autoconfig.yml."""
@@ -233,6 +235,23 @@ class TestYaml:
data = autoconfig.read()
assert 'bindings.default' not in data
@pytest.mark.parametrize('show, expected', [
(True, 'always'),
(False, 'never'),
('always', 'always'),
('never', 'never'),
('pinned', 'pinned'),
])
def test_tabs_favicons_show(self, yaml, autoconfig, show, expected):
"""Tests for migration of tabs.favicons.show."""
autoconfig.write({'tabs.favicons.show': {'global': show}})
yaml.load()
yaml._save()
data = autoconfig.read()
assert data['tabs.favicons.show']['global'] == expected
def test_renamed_key_unknown_target(self, monkeypatch, yaml,
autoconfig):
"""A key marked as renamed with invalid name should raise an error."""

View File

@@ -292,7 +292,7 @@ class TestEarlyInit:
'QT_XCB_FORCE_SOFTWARE_OPENGL', '1'),
('qt.force_platform', 'toaster', 'QT_QPA_PLATFORM', 'toaster'),
('qt.highdpi', True, 'QT_AUTO_SCREEN_SCALE_FACTOR', '1'),
('window.hide_wayland_decoration', True,
('window.hide_decoration', True,
'QT_WAYLAND_DISABLE_WINDOWDECORATION', '1')
])
def test_env_vars(self, monkeypatch, config_stub,
@@ -347,6 +347,12 @@ class TestQtArgs:
mocker.patch.object(parser, 'exit', side_effect=Exception)
return parser
@pytest.fixture(autouse=True)
def patch_version_check(self, monkeypatch):
"""Make sure no --disable-shared-workers argument gets added."""
monkeypatch.setattr(configinit.qtutils, 'version_check',
lambda version, compiled: True)
@pytest.mark.parametrize('args, expected', [
# No Qt arguments
(['--debug'], [sys.argv[0]]),
@@ -382,6 +388,15 @@ class TestQtArgs:
config_stub.val.qt.args = ['bar']
assert configinit.qt_args(parsed) == [sys.argv[0], '--foo', '--bar']
def test_shared_workers(self, config_stub, monkeypatch, parser):
monkeypatch.setattr(configinit.qtutils, 'version_check',
lambda version, compiled: False)
monkeypatch.setattr(configinit.objects, 'backend',
usertypes.Backend.QtWebEngine)
parsed = parser.parse_args([])
expected = [sys.argv[0], '--disable-shared-workers']
assert configinit.qt_args(parsed) == expected
@pytest.mark.parametrize('arg, confval, used', [
# overridden by commandline arg

View File

@@ -533,6 +533,17 @@ class FlagListSubclass(configtypes.FlagList):
'foo', 'bar', 'baz')
class FromObjType(configtypes.BaseType):
"""Config type to test from_obj for List/Dict."""
def from_obj(self, value):
return int(value)
def to_py(self, value):
return value
class TestList:
"""Test List and FlagList."""
@@ -647,6 +658,12 @@ class TestList:
with pytest.raises(AssertionError):
typ.to_doc([['foo']])
def test_from_obj_sub(self):
"""Make sure the list calls from_obj() on sub-types."""
typ = configtypes.List(valtype=FromObjType())
value = typ.from_obj(['1', '2'])
assert value == [1, 2]
class TestFlagList:
@@ -1665,6 +1682,13 @@ class TestDict:
print(doc)
assert doc == expected
def test_from_obj_sub(self):
"""Make sure the dict calls from_obj() on sub-types."""
typ = configtypes.Dict(keytype=configtypes.String(),
valtype=FromObjType())
value = typ.from_obj({'1': '2'})
assert value == {'1': 2}
def unrequired_class(**kwargs):
return configtypes.File(required=False, **kwargs)

View File

@@ -17,118 +17,42 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""pylint conftest file for javascript test."""
"""pytest conftest file for javascript tests."""
import os
import os.path
import logging
import pytest
import jinja2
from PyQt5.QtCore import QUrl
try:
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWebKitWidgets import QWebPage
except ImportError:
# FIXME:qtwebengine Make these tests use the tab API
QWebSettings = None
QWebPage = None
try:
from PyQt5.QtWebEngineWidgets import (QWebEnginePage,
QWebEngineSettings,
QWebEngineScript)
except ImportError:
QWebEnginePage = None
QWebEngineSettings = None
QWebEngineScript = None
import helpers.utils
import qutebrowser.utils.debug
from qutebrowser.utils import utils
if QWebPage is None:
TestWebPage = None
else:
class TestWebPage(QWebPage):
"""QWebPage subclass which overrides some test methods.
Attributes:
_logger: The logger used for alerts.
"""
def __init__(self, parent=None):
super().__init__(parent)
self._logger = logging.getLogger('js-tests')
def javaScriptAlert(self, _frame, msg):
"""Log javascript alerts."""
self._logger.info("js alert: {}".format(msg))
def javaScriptConfirm(self, _frame, msg):
"""Fail tests on js confirm() as that should never happen."""
pytest.fail("js confirm: {}".format(msg))
def javaScriptPrompt(self, _frame, msg, _default):
"""Fail tests on js prompt() as that should never happen."""
pytest.fail("js prompt: {}".format(msg))
def javaScriptConsoleMessage(self, msg, line, source):
"""Fail tests on js console messages as they're used for errors."""
pytest.fail("js console ({}:{}): {}".format(source, line, msg))
if QWebEnginePage is None:
TestWebEnginePage = None
else:
class TestWebEnginePage(QWebEnginePage):
"""QWebEnginePage which overrides javascript logging methods.
Attributes:
_logger: The logger used for alerts.
"""
def __init__(self, parent=None):
super().__init__(parent)
self._logger = logging.getLogger('js-tests')
def javaScriptAlert(self, _frame, msg):
"""Log javascript alerts."""
self._logger.info("js alert: {}".format(msg))
def javaScriptConfirm(self, _frame, msg):
"""Fail tests on js confirm() as that should never happen."""
pytest.fail("js confirm: {}".format(msg))
def javaScriptPrompt(self, _frame, msg, _default):
"""Fail tests on js prompt() as that should never happen."""
pytest.fail("js prompt: {}".format(msg))
def javaScriptConsoleMessage(self, level, msg, line, source):
"""Fail tests on js console messages as they're used for errors."""
pytest.fail("[{}] js console ({}:{}): {}".format(
qutebrowser.utils.debug.qenum_key(
QWebEnginePage, level), source, line, msg))
class JSTester:
"""Common subclass providing basic functionality for all JS testers.
Attributes:
webview: The webview which is used.
_qtbot: The QtBot fixture from pytest-qt.
tab: The tab object which is used.
qtbot: The QtBot fixture from pytest-qt.
_jinja_env: The jinja2 environment used to get templates.
"""
def __init__(self, webview, qtbot):
self.webview = webview
self._qtbot = qtbot
def __init__(self, tab, qtbot, config_stub):
self.tab = tab
self.qtbot = qtbot
loader = jinja2.FileSystemLoader(os.path.dirname(__file__))
self._jinja_env = jinja2.Environment(loader=loader, autoescape=True)
# Make sure error logging via JS fails tests
config_stub.val.content.javascript.log = {
'info': 'info',
'error': 'error',
'unknown': 'error',
'warning': 'error'
}
def load(self, path, **kwargs):
"""Load and display the given jinja test data.
@@ -139,9 +63,9 @@ class JSTester:
**kwargs: Passed to jinja's template.render().
"""
template = self._jinja_env.get_template(path)
with self._qtbot.waitSignal(self.webview.loadFinished,
timeout=2000) as blocker:
self.webview.setHtml(template.render(**kwargs))
with self.qtbot.waitSignal(self.tab.load_finished,
timeout=2000) as blocker:
self.tab.set_html(template.render(**kwargs))
assert blocker.args == [True]
def load_file(self, path: str, force: bool = False):
@@ -161,77 +85,13 @@ class JSTester:
url: The QUrl to load.
force: Whether to force loading even if the file is invalid.
"""
with self._qtbot.waitSignal(self.webview.loadFinished,
timeout=2000) as blocker:
self.webview.load(url)
with self.qtbot.waitSignal(self.tab.load_finished,
timeout=2000) as blocker:
self.tab.openurl(url)
if not force:
assert blocker.args == [True]
class JSWebKitTester(JSTester):
"""Object returned by js_tester which provides test data and a webview.
Attributes:
webview: The webview which is used.
_qtbot: The QtBot fixture from pytest-qt.
_jinja_env: The jinja2 environment used to get templates.
"""
def __init__(self, webview, qtbot):
super().__init__(webview, qtbot)
self.webview.setPage(TestWebPage(self.webview))
def scroll_anchor(self, name):
"""Scroll the main frame to the given anchor."""
page = self.webview.page()
old_pos = page.mainFrame().scrollPosition()
page.mainFrame().scrollToAnchor(name)
new_pos = page.mainFrame().scrollPosition()
assert old_pos != new_pos
def run_file(self, filename):
"""Run a javascript file.
Args:
filename: The javascript filename, relative to
qutebrowser/javascript.
Return:
The javascript return value.
"""
source = utils.read_file(os.path.join('javascript', filename))
return self.run(source)
def run(self, source):
"""Run the given javascript source.
Args:
source: The source to run as a string.
Return:
The javascript return value.
"""
assert self.webview.settings().testAttribute(
QWebSettings.JavascriptEnabled)
return self.webview.page().mainFrame().evaluateJavaScript(source)
class JSWebEngineTester(JSTester):
"""Object returned by js_tester_webengine which provides a webview.
Attributes:
webview: The webview which is used.
_qtbot: The QtBot fixture from pytest-qt.
_jinja_env: The jinja2 environment used to get templates.
"""
def __init__(self, webview, qtbot):
super().__init__(webview, qtbot)
self.webview.setPage(TestWebEnginePage(self.webview))
def run_file(self, filename: str, expected) -> None:
def run_file(self, filename: str, expected=None) -> None:
"""Run a javascript file.
Args:
@@ -250,24 +110,24 @@ class JSWebEngineTester(JSTester):
expected: The value expected return from the javascript execution
world: The scope the javascript will run in
"""
if world is None:
world = QWebEngineScript.ApplicationWorld
callback_checker = helpers.utils.CallbackChecker(self._qtbot)
assert self.webview.settings().testAttribute(
QWebEngineSettings.JavascriptEnabled)
self.webview.page().runJavaScript(source, world,
callback_checker.callback)
callback_checker = helpers.utils.CallbackChecker(self.qtbot)
self.tab.run_js_async(source, callback_checker.callback, world=world)
callback_checker.check(expected)
@pytest.fixture
def js_tester_webkit(webview, qtbot):
def js_tester_webkit(webkit_tab, qtbot, config_stub):
"""Fixture to test javascript snippets in webkit."""
return JSWebKitTester(webview, qtbot)
return JSTester(webkit_tab, qtbot, config_stub)
@pytest.fixture
def js_tester_webengine(callback_checker, webengineview, qtbot):
def js_tester_webengine(webengine_tab, qtbot, config_stub):
"""Fixture to test javascript snippets in webengine."""
return JSWebEngineTester(webengineview, qtbot)
return JSTester(webengine_tab, qtbot, config_stub)
@pytest.fixture
def js_tester(web_tab, qtbot, config_stub):
"""Fixture to test javascript snippets with both backends."""
return JSTester(web_tab, qtbot, config_stub)

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