Merge branch 'master' into feature/6218-pick-directory
|
|
@ -1,5 +1,5 @@
|
|||
[bumpversion]
|
||||
current_version = 2.0.2
|
||||
current_version = 2.1.0
|
||||
commit = True
|
||||
message = Release v{new_version}
|
||||
tag = True
|
||||
|
|
|
|||
|
|
@ -15,10 +15,102 @@ 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.
|
||||
|
||||
[[v2.1.0]]
|
||||
v2.1.0 (unreleased)
|
||||
[[v2.2.0]]
|
||||
v2.2.0 (unreleased)
|
||||
-------------------
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- Running qutebrowser with Qt 5.12.0 is now unsupported and logs a warning. It
|
||||
should still work, however, a workaround for issues with the Nvidia graphic
|
||||
driver was dropped. Newer Qt 5.12.x versions are still fully supported.
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- New `input.media_keys` setting which can be used to disable Chromium's
|
||||
handling of media keys.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- The `content.ssl_strict` setting got renamed to
|
||||
`content.tls.certificate_errors`, with new values:
|
||||
* `ask`: Prompt on overridable certificate errors (`ssl_strict = 'ask'`)
|
||||
* `ask-block-thirdparty`: See below
|
||||
* `block`: Block the page load (`ssl_strict = True`)
|
||||
* `load-insecurely`: Load the page despite the error (`ssl_strict = False`)
|
||||
- The new `content.tls.certificate_errors` setting now also understands the
|
||||
value `ask-block-thirdparty`, which asks for page loads but automatically blocks
|
||||
resource loads on TLS errors. This behavior is consistent with what other
|
||||
browsers do.
|
||||
- The prompt text shown on certificate errors has been improved to make it
|
||||
clearer what kind of error occurred exactly.
|
||||
- The completion now also shows bindings starting with `set-cmd-text` in its
|
||||
third column, such as `o` for `:open`.
|
||||
|
||||
[[v2.1.1]]
|
||||
v2.1.1 (unreleased)
|
||||
-------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Site-specific quirk for krunker.io, which shows a "Socket Error" with
|
||||
qutebrowser's default Accept-Language header. The workaround is equivalent to
|
||||
doing `:set -u matchmaker.krunker.io content.headers.accept_language ""`.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Clicking the 'x' in the devtools window to hide it now also leaves insert
|
||||
mode.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- The workaround for black on (almost) black formula images in dark mode now
|
||||
also works with Qt 5.12 and 5.13.
|
||||
- When running in Flatpak, the QtWebEngine version is now detected properly.
|
||||
Before, a wrong version was assumed, breaking dark mode and certain workarounds
|
||||
(resulting in crashes on websites like LinkedIn or TradingView).
|
||||
- When running in Flatpak, communicating with an existing instance now works
|
||||
properly. Before, a new instance was always opened.
|
||||
- When the metainfo in the completion database doesn't have the expected
|
||||
structure, qutebrowser now tries to gracefully recover from the situation
|
||||
instead of crashing.
|
||||
- When qutebrowser displays an error during initialization, opening a second
|
||||
instance would lead to a crash. Instead, qutebrowser now ignores the attempt
|
||||
to open a new page as long as it's not fully initialized yet.
|
||||
- When the Brave adblock cache folder was unreadable, qutebrowser crashed. It
|
||||
now displays an error instead.
|
||||
- The `qute-pass` userscript now works correctly when generating OTP tokens
|
||||
with `gopass`.
|
||||
- When using `bindings.key_mappings` to map a key to multiple other keys,
|
||||
qutebrowser would crash. This is now handled correctly - however, note that
|
||||
it's usually better to map keys to commands instead.
|
||||
|
||||
[[v2.1.0]]
|
||||
v2.1.0 (2021-03-12)
|
||||
-------------------
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- The following command aliases were deprecated in v2.0.0 and are now removed:
|
||||
* `run-macro` -> `macro-run`
|
||||
* `record-macro` -> `macro-record`
|
||||
* `buffer` -> `tab-select`
|
||||
* `open-editor` -> `edit-text`
|
||||
* `toggle-selection` -> `selection-toggle`
|
||||
* `drop-selection` -> `selection-drop`
|
||||
* `reverse-selection` -> `selection-reverse`
|
||||
* `follow-selected` -> `selection-follow`
|
||||
* `follow-hint` -> `hint-follow`
|
||||
* `enter-mode` -> `mode-enter`
|
||||
* `leave-mode` -> `mode-leave`
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
|
|
@ -32,7 +124,7 @@ Added
|
|||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Initial support for Qt 5.15.3 and PyQt 5.15.3
|
||||
- Initial support for QtWebEngine 5.15.3 and PyQt 5.15.3/.4
|
||||
- The `colors.webpage.prefers_color_scheme_dark` setting got renamed to
|
||||
`colors.webpage.preferred_color_scheme` and now takes the values `auto`, `light`
|
||||
and `dark` (instead of being `True` for dark and `False` for auto).
|
||||
|
|
@ -60,13 +152,25 @@ Changed
|
|||
- The `fileselect.*.command` settings now support file selectors writing the
|
||||
selected paths to stdout, which is used if no `{}` placeholder is contained in
|
||||
the configured command.
|
||||
- The `--debug-flag` argument now understands a new `log-sensitive-keys` value
|
||||
which logs all keypresses (including those in insert/passthrough/prompt/...
|
||||
mode) for debugging.
|
||||
- The `readability` and `readability-js` userscripts now add a
|
||||
`qute-readability` CSS class to the page, so that it can be styled easily via
|
||||
a user stylesheet.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- With QtWebEngine 5.15.3 and some locales, Chromium can't start its
|
||||
subprocesses. As a result, qutebrowser only shows a blank page and logs
|
||||
"Network service crashed, restarting service.". This release adds a
|
||||
`qt.workarounds.locale` setting working around the issue. It is disabled by
|
||||
default since distributions shipping 5.15.3 will probably have a proper patch
|
||||
for it backported very soon.
|
||||
- The `colors.webpage.preferred_color_scheme` and `colors.webpage.darkmode.*`
|
||||
settings now work correctly with the upcoming QtWebEngine 5.15.3 (and Gentoo,
|
||||
which at the time of writing packages 5.15.3 disguised as 5.15.2).
|
||||
settings now work correctly with QtWebEngine 5.15.3 (and Gentoo, which at the
|
||||
time of writing packages 5.15.3 disguised as 5.15.2).
|
||||
- When dark mode settings were set, existing `blink-features` arguments in
|
||||
`qt.args` (or `--qt-flag`) were overridden. They are now combined properly.
|
||||
- On QtWebEngine 5.15.2, auto detection for the `prefers-color-scheme` media
|
||||
|
|
@ -87,6 +191,23 @@ Fixed
|
|||
properly.
|
||||
- The "try again" button on error pages now works correctly with JavaScript
|
||||
disabled.
|
||||
- If a GreaseMonkey script doesn't have a "@run-at" comment, qutebrowser
|
||||
accidentally treated that as "@run-at document-idle". However, other
|
||||
GreaseMonkey implementations default to "@run-at document-end" instead, which
|
||||
is what qutebrowser now does, too.
|
||||
- The `hist_importer.py` script didn't work correctly after qutebrowser v2.0.0
|
||||
and resulted in a history database qutebrowser couldn't read properly. It now
|
||||
works properly again.
|
||||
- With certain QtWebEngine versions (5.15.0 based on Chromium 80 and 5.15.3
|
||||
based on Chromium 87), Chromium's dark mode doesn't invert certain SVG images,
|
||||
even with `colors.wegpage.darkmode.policy.images` set to `smart`.
|
||||
Most notably, this causes formulae on Wikipedia to display black on (almost)
|
||||
black. If `content.site_specific_quirks` is enabled, qutebrowser now injects
|
||||
some CSS as a workaround, which inverts all math formula images on Wikipedia
|
||||
(and potentially other sites, if they use the same CSS class).
|
||||
- When a hint label text started with an apostrophe, it would show an escaped
|
||||
text until the hints first character has been pressed. It now shows up
|
||||
correctly.
|
||||
|
||||
[[v2.0.2]]
|
||||
v2.0.2 (2021-02-04)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|<<backend,backend>>|Backend to use to display websites.
|
||||
|<<bindings.commands,bindings.commands>>|Keybindings mapping keys to commands in different modes.
|
||||
|<<bindings.default,bindings.default>>|Default keybindings. If you want to add bindings, modify `bindings.commands` instead.
|
||||
|<<bindings.key_mappings,bindings.key_mappings>>|This setting can be used to map keys to other keys.
|
||||
|<<bindings.key_mappings,bindings.key_mappings>>|Map keys to other keys, so that they are equivalent in all modes.
|
||||
|<<changelog_after_upgrade,changelog_after_upgrade>>|When to show a changelog after qutebrowser was upgraded.
|
||||
|<<colors.completion.category.bg,colors.completion.category.bg>>|Background color of the completion widget category headers.
|
||||
|<<colors.completion.category.border.bottom,colors.completion.category.border.bottom>>|Bottom border color of the completion widget category headers.
|
||||
|
|
@ -191,7 +191,7 @@
|
|||
|<<content.proxy_dns_requests,content.proxy_dns_requests>>|Send DNS requests over the configured proxy.
|
||||
|<<content.register_protocol_handler,content.register_protocol_handler>>|Allow websites to register protocol handlers via `navigator.registerProtocolHandler`.
|
||||
|<<content.site_specific_quirks,content.site_specific_quirks>>|Enable quirks (such as faked user agent headers) needed to get specific sites to work properly.
|
||||
|<<content.ssl_strict,content.ssl_strict>>|Validate SSL handshakes.
|
||||
|<<content.tls.certificate_errors,content.tls.certificate_errors>>|How to proceed on TLS certificate errors.
|
||||
|<<content.unknown_url_scheme_policy,content.unknown_url_scheme_policy>>|How navigation requests to URLs with unknown schemes are handled.
|
||||
|<<content.user_stylesheets,content.user_stylesheets>>|List of user stylesheet filenames to use.
|
||||
|<<content.webgl,content.webgl>>|Enable WebGL.
|
||||
|
|
@ -261,6 +261,7 @@
|
|||
|<<input.insert_mode.leave_on_load,input.insert_mode.leave_on_load>>|Leave insert mode when starting a new page load.
|
||||
|<<input.insert_mode.plugins,input.insert_mode.plugins>>|Switch to insert mode when clicking flash and other plugins.
|
||||
|<<input.links_included_in_focus_chain,input.links_included_in_focus_chain>>|Include hyperlinks in the keyboard focus chain when tabbing.
|
||||
|<<input.media_keys,input.media_keys>>|Whether the underlying Chromium should handle media keys.
|
||||
|<<input.mouse.back_forward_buttons,input.mouse.back_forward_buttons>>|Enable back and forward buttons on the mouse.
|
||||
|<<input.mouse.rocker_gestures,input.mouse.rocker_gestures>>|Enable Opera-like mouse rocker gestures.
|
||||
|<<input.partial_timeout,input.partial_timeout>>|Timeout (in milliseconds) for partially typed key bindings.
|
||||
|
|
@ -283,6 +284,7 @@
|
|||
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|
||||
|<<qt.low_end_device_mode,qt.low_end_device_mode>>|When to use Chromium's low-end device mode.
|
||||
|<<qt.process_model,qt.process_model>>|Which Chromium process model to use.
|
||||
|<<qt.workarounds.locale,qt.workarounds.locale>>|Work around locale parsing issues in QtWebEngine 5.15.3.
|
||||
|<<qt.workarounds.remove_service_workers,qt.workarounds.remove_service_workers>>|Delete the QtWebEngine Service Worker directory on every start.
|
||||
|<<scrolling.bar,scrolling.bar>>|When/how to show the scrollbar.
|
||||
|<<scrolling.smooth,scrolling.smooth>>|Enable smooth scrolling for web pages.
|
||||
|
|
@ -773,9 +775,10 @@ Default:
|
|||
|
||||
[[bindings.key_mappings]]
|
||||
=== bindings.key_mappings
|
||||
This setting can be used to map keys to other keys.
|
||||
Map keys to other keys, so that they are equivalent in all modes.
|
||||
When the key used as dictionary-key is pressed, the binding for the key used as dictionary-value is invoked instead.
|
||||
This is useful for global remappings of keys, for example to map Ctrl-[ to Escape.
|
||||
This is useful for global remappings of keys, for example to map <Ctrl-[> to <Escape>.
|
||||
NOTE: This should only be used if two keys should always be equivalent, i.e. for things like <Enter> (keypad) and <Return> (non-keypad). For normal command bindings, qutebrowser works differently to vim: You always bind keys to commands, usually via `:bind` or `config.bind()`. Instead of using this setting, consider finding the command a key is bound to (e.g. via `:bind gg`) and then binding the same command to the desired key.
|
||||
Note that when a key is bound (via `bindings.default` or `bindings.commands`), the mapping is ignored.
|
||||
|
||||
Type: <<types,Dict>>
|
||||
|
|
@ -1599,6 +1602,8 @@ The `lightness-cielab` value was added with QtWebEngine 5.14 and is treated like
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -1609,8 +1614,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[lightness-cielab]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[colors.webpage.darkmode.contrast]]
|
||||
=== colors.webpage.darkmode.contrast
|
||||
Contrast for dark mode.
|
||||
|
|
@ -1618,12 +1621,12 @@ This only has an effect when `colors.webpage.darkmode.algorithm` is set to `ligh
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Float>>
|
||||
|
||||
Default: +pass:[0.0]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[colors.webpage.darkmode.enabled]]
|
||||
=== colors.webpage.darkmode.enabled
|
||||
Render all web contents using a dark theme.
|
||||
|
|
@ -1644,12 +1647,12 @@ Example configurations from Chromium's `chrome://flags`:
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[colors.webpage.darkmode.grayscale.all]]
|
||||
=== colors.webpage.darkmode.grayscale.all
|
||||
Render all colors as grayscale.
|
||||
|
|
@ -1657,12 +1660,12 @@ This only has an effect when `colors.webpage.darkmode.algorithm` is set to `ligh
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[colors.webpage.darkmode.grayscale.images]]
|
||||
=== colors.webpage.darkmode.grayscale.images
|
||||
Desaturation factor for images in dark mode.
|
||||
|
|
@ -1670,14 +1673,14 @@ If set to 0, images are left as-is. If set to 1, images are completely grayscale
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,Float>>
|
||||
|
||||
Default: +pass:[0.0]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
Type: <<types,Float>>
|
||||
|
||||
Default: +pass:[0.0]+
|
||||
|
||||
[[colors.webpage.darkmode.policy.images]]
|
||||
=== colors.webpage.darkmode.policy.images
|
||||
Which images to apply dark mode to.
|
||||
|
|
@ -1685,6 +1688,8 @@ With QtWebEngine 5.15.0, this setting can cause frequent renderer process crashe
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -1695,8 +1700,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[smart]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[colors.webpage.darkmode.policy.page]]
|
||||
=== colors.webpage.darkmode.policy.page
|
||||
Which pages to apply dark mode to.
|
||||
|
|
@ -1704,6 +1707,10 @@ The underlying Chromium setting has been removed in QtWebEngine 5.15.3, thus thi
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -1713,10 +1720,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[smart]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[colors.webpage.darkmode.threshold.background]]
|
||||
=== colors.webpage.darkmode.threshold.background
|
||||
Threshold for inverting background elements with dark mode.
|
||||
|
|
@ -1725,14 +1728,14 @@ Note: This behavior is the opposite of `colors.webpage.darkmode.threshold.text`!
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[0]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[0]+
|
||||
|
||||
[[colors.webpage.darkmode.threshold.text]]
|
||||
=== colors.webpage.darkmode.threshold.text
|
||||
Threshold for inverting text with dark mode.
|
||||
|
|
@ -1740,14 +1743,14 @@ Text colors with brightness below this threshold will be inverted, and above it
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[256]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[256]+
|
||||
|
||||
[[colors.webpage.preferred_color_scheme]]
|
||||
=== colors.webpage.preferred_color_scheme
|
||||
Value to use for `prefers-color-scheme:` for websites.
|
||||
|
|
@ -1756,6 +1759,10 @@ The "auto" value is broken on QtWebEngine 5.15.2 due to a Qt bug. There, it will
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -1766,10 +1773,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[auto]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[completion.cmd_history_max_items]]
|
||||
=== completion.cmd_history_max_items
|
||||
Number of commands to save in the command history.
|
||||
|
|
@ -1941,12 +1944,12 @@ Automatically start playing `<video>` elements.
|
|||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.blocking.adblock.lists]]
|
||||
=== content.blocking.adblock.lists
|
||||
List of URLs to ABP-style adblocking rulesets.
|
||||
|
|
@ -2039,24 +2042,24 @@ An application cache acts like an HTTP cache in some sense. For documents that u
|
|||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
[[content.cache.maximum_pages]]
|
||||
=== content.cache.maximum_pages
|
||||
Maximum number of pages to hold in the global memory page cache.
|
||||
The page cache allows for a nicer user experience when navigating forth or back to pages in the forward/back history, by pausing and resuming up to _n_ pages.
|
||||
For more information about the feature, please refer to: https://webkit.org/blog/427/webkit-page-cache-i-the-basics/
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[0]+
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
[[content.cache.size]]
|
||||
=== content.cache.size
|
||||
Size (in bytes) of the HTTP network cache. Null to use the default value.
|
||||
|
|
@ -2073,12 +2076,12 @@ Note this is needed for some websites to work properly.
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.cookies.accept]]
|
||||
=== content.cookies.accept
|
||||
Which cookies to accept.
|
||||
|
|
@ -2138,12 +2141,12 @@ Try to pre-fetch DNS entries to speed up browsing.
|
|||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.frame_flattening]]
|
||||
=== content.frame_flattening
|
||||
Expand each subframe to its contents.
|
||||
|
|
@ -2151,12 +2154,12 @@ This will flatten all the frames to become one scrollable page.
|
|||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
[[content.fullscreen.overlay_timeout]]
|
||||
=== content.fullscreen.overlay_timeout
|
||||
Set fullscreen notification overlay timeout in milliseconds.
|
||||
|
|
@ -2315,12 +2318,12 @@ Allow JavaScript to close tabs.
|
|||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
[[content.javascript.can_open_tabs_automatically]]
|
||||
=== content.javascript.can_open_tabs_automatically
|
||||
Allow JavaScript to open new tabs without user interaction.
|
||||
|
|
@ -2409,6 +2412,8 @@ Allow websites to record audio.
|
|||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -2419,14 +2424,14 @@ Valid values:
|
|||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.media.audio_video_capture]]
|
||||
=== content.media.audio_video_capture
|
||||
Allow websites to record audio and video.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -2437,14 +2442,14 @@ Valid values:
|
|||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.media.video_capture]]
|
||||
=== content.media.video_capture
|
||||
Allow websites to record video.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -2455,14 +2460,14 @@ Valid values:
|
|||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.mouse_lock]]
|
||||
=== content.mouse_lock
|
||||
Allow websites to lock your mouse pointer.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -2473,8 +2478,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.mute]]
|
||||
=== content.mute
|
||||
Automatically mute tabs.
|
||||
|
|
@ -2501,6 +2504,8 @@ Allow websites to show notifications.
|
|||
|
||||
This setting supports URL patterns.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.13 or newer.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -2511,8 +2516,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.13 or newer.
|
||||
|
||||
[[content.pdfjs]]
|
||||
=== content.pdfjs
|
||||
Allow pdf.js to view PDF files in the browser.
|
||||
|
|
@ -2530,6 +2533,8 @@ Allow websites to request persistent storage quota via `navigator.webkitPersiste
|
|||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -2540,8 +2545,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.plugins]]
|
||||
=== content.plugins
|
||||
Enable plugins in Web pages.
|
||||
|
|
@ -2558,12 +2561,12 @@ Draw the background color and images also when the page is printed.
|
|||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.private_browsing]]
|
||||
=== content.private_browsing
|
||||
Open new windows in private browsing mode which does not record visited pages.
|
||||
|
|
@ -2591,18 +2594,20 @@ Default: +pass:[system]+
|
|||
=== content.proxy_dns_requests
|
||||
Send DNS requests over the configured proxy.
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
[[content.register_protocol_handler]]
|
||||
=== content.register_protocol_handler
|
||||
Allow websites to register protocol handlers via `navigator.registerProtocolHandler`.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -2613,8 +2618,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.site_specific_quirks]]
|
||||
=== content.site_specific_quirks
|
||||
Enable quirks (such as faked user agent headers) needed to get specific sites to work properly.
|
||||
|
|
@ -2625,19 +2628,20 @@ Type: <<types,Bool>>
|
|||
|
||||
Default: +pass:[true]+
|
||||
|
||||
[[content.ssl_strict]]
|
||||
=== content.ssl_strict
|
||||
Validate SSL handshakes.
|
||||
[[content.tls.certificate_errors]]
|
||||
=== content.tls.certificate_errors
|
||||
How to proceed on TLS certificate errors.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
* +ask+
|
||||
* +ask+: Ask how to proceed for every certificate error (unless non-overridable due to HSTS).
|
||||
* +ask-block-thirdparty+: Ask how to proceed for normal page loads, but silently block resource loads.
|
||||
* +block+: Automatically block loading on certificate errors.
|
||||
* +load-insecurely+: Force loading pages despite certificate errors. This is *insecure* and should be avoided. Instead of using this, consider fixing the underlying issue or importing a self-signed certificate via `certutil` (or Chromium) instead.
|
||||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
|
|
@ -2647,6 +2651,8 @@ How navigation requests to URLs with unknown schemes are handled.
|
|||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -2657,8 +2663,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[allow-from-user-interaction]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.user_stylesheets]]
|
||||
=== content.user_stylesheets
|
||||
List of user stylesheet filenames to use.
|
||||
|
|
@ -2683,6 +2687,8 @@ Which interfaces to expose via WebRTC.
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -2694,8 +2700,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[all-interfaces]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.xss_auditing]]
|
||||
=== content.xss_auditing
|
||||
Monitor load requests for cross-site scripting attempts.
|
||||
|
|
@ -3119,6 +3123,8 @@ Default: +pass:[/usr/share/dict/words]+
|
|||
=== hints.find_implementation
|
||||
Which implementation to use to find elements to hint.
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -3128,8 +3134,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[python]+
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
[[hints.hide_unmatched_rapid_hints]]
|
||||
=== hints.hide_unmatched_rapid_hints
|
||||
Hide unmatched hints in rapid mode.
|
||||
|
|
@ -3391,6 +3395,21 @@ Type: <<types,Bool>>
|
|||
|
||||
Default: +pass:[true]+
|
||||
|
||||
[[input.media_keys]]
|
||||
=== input.media_keys
|
||||
Whether the underlying Chromium should handle media keys.
|
||||
On Linux, disabling this also disables Chromium's MPRIS integration.
|
||||
|
||||
This setting requires a restart.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
[[input.mouse.back_forward_buttons]]
|
||||
=== input.mouse.back_forward_buttons
|
||||
Enable back and forward buttons on the mouse.
|
||||
|
|
@ -3599,6 +3618,8 @@ This is needed for QtWebEngine to work with Nouveau drivers and can be useful in
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -3610,8 +3631,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[none]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[qt.highdpi]]
|
||||
=== qt.highdpi
|
||||
Turn on Qt HighDPI scaling.
|
||||
|
|
@ -3631,6 +3650,8 @@ This improves the RAM usage of renderer processes, at the expense of performance
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -3641,8 +3662,6 @@ Valid values:
|
|||
|
||||
Default: +pass:[auto]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[qt.process_model]]
|
||||
=== qt.process_model
|
||||
Which Chromium process model to use.
|
||||
|
|
@ -3654,6 +3673,8 @@ See the following pages for more details:
|
|||
|
||||
This setting requires a restart.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -3664,8 +3685,18 @@ Valid values:
|
|||
|
||||
Default: +pass:[process-per-site-instance]+
|
||||
|
||||
[[qt.workarounds.locale]]
|
||||
=== qt.workarounds.locale
|
||||
Work around locale parsing issues in QtWebEngine 5.15.3.
|
||||
With some locales, QtWebEngine 5.15.3 is unusable without this workaround. In affected scenarios, QtWebEngine will log "Network service crashed, restarting service." and only display a blank page.
|
||||
However, It is expected that distributions shipping QtWebEngine 5.15.3 follow up with a proper fix soon, so it is disabled by default.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[qt.workarounds.remove_service_workers]]
|
||||
=== qt.workarounds.remove_service_workers
|
||||
Delete the QtWebEngine Service Worker directory on every start.
|
||||
|
|
@ -3728,12 +3759,12 @@ Default: +pass:[true]+
|
|||
=== search.wrap
|
||||
Wrap around at the top and bottom of the page when advancing through text matches using `:search-next` and `:search-prev`.
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.14 or newer.
|
||||
|
||||
[[session.default_name]]
|
||||
=== session.default_name
|
||||
Name of the session to save by default.
|
||||
|
|
@ -3756,6 +3787,8 @@ Default: +pass:[false]+
|
|||
Languages to use for spell checking.
|
||||
You can check for available languages and install dictionaries using scripts/dictcli.py. Run the script with -h/--help for instructions.
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
Type: <<types,List of String>>
|
||||
|
||||
Valid values:
|
||||
|
|
@ -3806,8 +3839,6 @@ Valid values:
|
|||
|
||||
Default: empty
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[statusbar.padding]]
|
||||
=== statusbar.padding
|
||||
Padding (in pixels) for the statusbar.
|
||||
|
|
@ -4425,12 +4456,12 @@ Apply the zoom factor on a frame only to the text or to all content.
|
|||
|
||||
This setting supports URL patterns.
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
== Setting types
|
||||
[[types]]
|
||||
[options="header",width="75%",cols="25%,75%"]
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 761 KiB After Width: | Height: | Size: 763 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
|
@ -239,11 +239,12 @@ qutebrowser is available
|
|||
https://flathub.org/apps/details/org.qutebrowser.qutebrowser[on Flathub]
|
||||
as `org.qutebrowser.qutebrowser`.
|
||||
|
||||
WARNING: As of October 2020, the Flatpak package is severely outdated (qutebrowser
|
||||
v1.7.0 from July 2019) and, among other issues, misses fixes for a
|
||||
(low-severity) https://github.com/qutebrowser/qutebrowser/security/advisories/GHSA-4rcq-jv2f-898j[security issue].
|
||||
It's recommended to <<tox,install qutebrowser in a virtualenv>> instead, which
|
||||
is one of the officially maintained options and will always be up-to-date.
|
||||
NOTE: The Flatpak package is
|
||||
https://github.com/flathub/org.qutebrowser.qutebrowser/issues/8#issuecomment-799579975[looking for (co-)maintainers].
|
||||
The package recently was updated after being out of date for multiple years. It
|
||||
currently (March 2021) is up to date again. If that situation changes, consider
|
||||
to <<tox,install qutebrowser in a virtualenv>> instead, which is one of the
|
||||
officially maintained options and will always be up-to-date.
|
||||
|
||||
On FreeBSD
|
||||
----------
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
height="682.66669"
|
||||
id="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07, custom)"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
|
||||
version="1.0"
|
||||
sodipodi:docname="cheatsheet.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
|
|
@ -30,16 +30,16 @@
|
|||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.7536248"
|
||||
inkscape:cx="466.08451"
|
||||
inkscape:cy="268.64059"
|
||||
inkscape:zoom="2.48"
|
||||
inkscape:cx="834.18001"
|
||||
inkscape:cy="692.30401"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
width="1024px"
|
||||
height="640px"
|
||||
showgrid="false"
|
||||
inkscape:window-width="3822"
|
||||
inkscape:window-height="2128"
|
||||
inkscape:window-width="1914"
|
||||
inkscape:window-height="1048"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="16"
|
||||
showguides="true"
|
||||
|
|
@ -3113,8 +3113,6 @@
|
|||
style="font-size:10.6667px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06667"
|
||||
id="flowPara3925">ss - set setting (sl: temp)</flowPara><flowPara
|
||||
style="font-size:10.6667px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06667"
|
||||
id="flowPara3927" /><flowPara
|
||||
style="font-size:10.6667px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06667"
|
||||
id="flowPara3929">sk - bind key</flowPara><flowPara
|
||||
style="font-size:10.6667px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06667"
|
||||
id="flowPara3931">Ss - show settings</flowPara><flowPara
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 175 KiB |
|
|
@ -44,6 +44,7 @@
|
|||
</content_rating>
|
||||
<releases>
|
||||
<!-- Add new releases here -->
|
||||
<release version="2.1.0" date="2021-03-12"/>
|
||||
<release version="2.0.2" date="2021-02-04"/>
|
||||
<release version="2.0.1" date="2021-01-28"/>
|
||||
<release version="2.0.0" date="2021-01-28"/>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
build==0.3.0
|
||||
build==0.3.1.post1
|
||||
check-manifest==0.46
|
||||
packaging==20.9
|
||||
pep517==0.9.1
|
||||
pep517==0.10.0
|
||||
pyparsing==2.4.7
|
||||
toml==0.10.2
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ packaging==20.9
|
|||
pycparser==2.20
|
||||
Pympler==0.9
|
||||
pyparsing==2.4.7
|
||||
PyQt-builder==1.9.0
|
||||
PyQt-builder==1.9.1
|
||||
python-dateutil==2.8.1
|
||||
requests==2.25.1
|
||||
sip==6.0.2
|
||||
sip==6.0.3
|
||||
six==1.15.0
|
||||
toml==0.10.2
|
||||
uritemplate==3.0.1
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==20.3.0
|
||||
flake8==3.8.4
|
||||
flake8-bugbear==20.11.1
|
||||
flake8==3.9.0
|
||||
flake8-bugbear==21.3.2
|
||||
flake8-builtins==1.5.3
|
||||
flake8-comprehensions==3.3.1
|
||||
flake8-copyright==0.2.2
|
||||
|
|
@ -17,8 +17,8 @@ flake8-tidy-imports==4.2.1
|
|||
flake8-tuple==0.4.1
|
||||
mccabe==0.6.1
|
||||
pep8-naming==0.11.1
|
||||
pycodestyle==2.6.0
|
||||
pycodestyle==2.7.0
|
||||
pydocstyle==5.1.1
|
||||
pyflakes==2.2.0
|
||||
pyflakes==2.3.0
|
||||
six==1.15.0
|
||||
snowballstemmer==2.1.0
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
chardet==4.0.0
|
||||
diff-cover==4.2.1
|
||||
importlib-metadata==3.7.0
|
||||
importlib-resources==5.1.1
|
||||
inflect==5.2.0
|
||||
diff-cover==5.0.1
|
||||
importlib-metadata==3.7.3
|
||||
importlib-resources==5.1.2
|
||||
inflect==5.3.0
|
||||
Jinja2==2.11.3
|
||||
jinja2-pluralize==0.3.0
|
||||
lxml==4.6.2
|
||||
|
|
@ -12,8 +12,8 @@ MarkupSafe==1.1.1
|
|||
mypy==0.812
|
||||
mypy-extensions==0.4.3
|
||||
pluggy==0.13.1
|
||||
Pygments==2.8.0
|
||||
Pygments==2.8.1
|
||||
PyQt5-stubs==5.15.2.0
|
||||
typed-ast==1.4.2
|
||||
typing-extensions==3.7.4.3
|
||||
zipp==3.4.0
|
||||
zipp==3.4.1
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
altgraph==0.17
|
||||
pyinstaller==4.2
|
||||
pyinstaller-hooks-contrib==2020.11
|
||||
pyinstaller-hooks-contrib==2021.1
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.3 # rq.filter: < 5.16
|
||||
PyQt5-Qt==5.15.2
|
||||
PyQt5==5.15.4 # rq.filter: < 5.16
|
||||
PyQt5-Qt5==5.15.2
|
||||
PyQt5-sip==12.8.1
|
||||
PyQtWebEngine==5.15.3 # rq.filter: < 5.16
|
||||
PyQtWebEngine-Qt==5.15.2
|
||||
PyQtWebEngine==5.15.4 # rq.filter: < 5.16
|
||||
PyQtWebEngine-Qt5==5.15.2
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.3
|
||||
PyQt5-Qt==5.15.2
|
||||
PyQt5-sip==12.8.1
|
||||
PyQtWebEngine==5.15.3
|
||||
PyQtWebEngine-Qt==5.15.2
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
PyQt5==5.15.3
|
||||
PyQtWebEngine==5.15.3
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.15.3
|
||||
PyQt5-Qt==5.15.2
|
||||
PyQt5==5.15.4
|
||||
PyQt5-Qt5==5.15.2
|
||||
PyQt5-sip==12.8.1
|
||||
PyQtWebEngine==5.15.3
|
||||
PyQtWebEngine-Qt==5.15.2
|
||||
PyQtWebEngine==5.15.4
|
||||
PyQtWebEngine-Qt5==5.15.2
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
docutils==0.16
|
||||
Pygments==2.5.2
|
||||
pyroma==2.6.1
|
||||
Pygments==2.8.1
|
||||
pyroma==3.1
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ imagesize==1.2.0
|
|||
Jinja2==2.11.3
|
||||
MarkupSafe==1.1.1
|
||||
packaging==20.9
|
||||
Pygments==2.8.0
|
||||
Pygments==2.8.1
|
||||
pyparsing==2.4.7
|
||||
pytz==2021.1
|
||||
requests==2.25.1
|
||||
snowballstemmer==2.1.0
|
||||
Sphinx==3.5.1
|
||||
Sphinx==3.5.2
|
||||
sphinxcontrib-applehelp==1.0.2
|
||||
sphinxcontrib-devhelp==1.0.2
|
||||
sphinxcontrib-htmlhelp==1.0.3
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
# Problematic: needs bzr
|
||||
# bzr+lp:beautifulsoup
|
||||
beautifulsoup4
|
||||
git+https://github.com/cherrypy/cheroot.git
|
||||
git+https://github.com/nedbat/coveragepy.git
|
||||
git+https://github.com/pallets/flask.git
|
||||
git+https://github.com/pallets/werkzeug.git # transitive dep, but needed to work
|
||||
git+https://github.com/HypothesisWorks/hypothesis.git#subdirectory=hypothesis-python
|
||||
git+https://github.com/pytest-dev/pytest.git
|
||||
git+https://github.com/pytest-dev/pytest-bdd.git
|
||||
git+https://github.com/ionelmc/pytest-benchmark.git
|
||||
git+https://github.com/pytest-dev/pytest-instafail.git
|
||||
git+https://github.com/pytest-dev/pytest-mock.git
|
||||
git+https://github.com/pytest-dev/pytest-qt.git
|
||||
git+https://github.com/pytest-dev/pytest-rerunfailures.git
|
||||
|
||||
git+https://github.com/ionelmc/python-hunter.git
|
||||
git+https://github.com/jendrikseipp/vulture.git
|
||||
git+https://github.com/pygments/pygments.git
|
||||
git+https://github.com/pytest-dev/pytest-repeat.git
|
||||
git+https://github.com/pytest-dev/pytest-cov.git
|
||||
git+https://github.com/The-Compiler/pytest-xvfb.git
|
||||
git+https://github.com/pytest-dev/pytest-xdist.git
|
||||
git+https://github.com/hjwp/pytest-icdiff.git
|
||||
git+https://github.com/john-kurkowski/tldextract
|
||||
# Problematic: needs rust (and some time to build)
|
||||
# git+https://github.com/ArniDagur/python-adblock.git
|
||||
adblock ; python_version!="3.10"
|
||||
|
||||
## qutebrowser dependencies
|
||||
|
||||
git+https://github.com/pallets/jinja.git
|
||||
git+https://github.com/yaml/pyyaml.git
|
||||
git+https://github.com/tartley/colorama.git
|
||||
|
||||
# https://github.com/pyparsing/pyparsing/issues/271
|
||||
pyparsing!=3.0.0b2,!=3.0.0b1
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
bzr+lp:beautifulsoup
|
||||
git+https://github.com/cherrypy/cheroot.git
|
||||
hg+https://bitbucket.org/ned/coveragepy
|
||||
git+https://github.com/micheles/decorator.git
|
||||
git+https://github.com/pallets/flask.git
|
||||
git+https://github.com/miracle2k/python-glob2.git
|
||||
git+https://github.com/HypothesisWorks/hypothesis-python.git
|
||||
git+https://github.com/pallets/itsdangerous.git
|
||||
git+https://bitbucket.org/zzzeek/mako.git
|
||||
git+https://github.com/r1chardj0n3s/parse.git
|
||||
git+https://github.com/jenisys/parse_type.git
|
||||
hg+https://bitbucket.org/pytest-dev/py
|
||||
git+https://github.com/pytest-dev/pytest.git@features
|
||||
git+https://github.com/pytest-dev/pytest-bdd.git
|
||||
git+https://github.com/pytest-dev/pytest-cov.git
|
||||
git+https://github.com/pytest-dev/pytest-instafail.git
|
||||
git+https://github.com/pytest-dev/pytest-mock.git
|
||||
git+https://github.com/pytest-dev/pytest-qt.git
|
||||
git+https://github.com/pytest-dev/pytest-repeat.git
|
||||
git+https://github.com/pytest-dev/pytest-rerunfailures.git
|
||||
git+https://github.com/The-Compiler/pytest-xvfb.git
|
||||
hg+https://bitbucket.org/gutworth/six
|
||||
hg+https://bitbucket.org/jendrikseipp/vulture
|
||||
git+https://github.com/pallets/werkzeug.git
|
||||
|
||||
|
||||
## qutebrowser dependencies
|
||||
|
||||
git+https://github.com/tartley/colorama.git
|
||||
git+https://github.com/pallets/jinja.git
|
||||
git+https://github.com/pallets/markupsafe.git
|
||||
git+https://github.com/pygments/pygments.git
|
||||
git+https://github.com/python-attrs/attrs.git
|
||||
git+https://github.com/yaml/pyyaml.git
|
||||
|
|
@ -15,7 +15,7 @@ filelock==3.0.12
|
|||
Flask==1.1.2
|
||||
glob2==0.7
|
||||
hunter==3.3.1
|
||||
hypothesis==6.3.4
|
||||
hypothesis==6.8.1
|
||||
icdiff==1.9.1
|
||||
idna==2.10
|
||||
iniconfig==1.1.1
|
||||
|
|
@ -33,7 +33,7 @@ pluggy==0.13.1
|
|||
pprintpp==0.4.0
|
||||
py==1.10.0
|
||||
py-cpuinfo==7.0.0
|
||||
Pygments==2.8.0
|
||||
Pygments==2.8.1
|
||||
pyparsing==2.4.7
|
||||
pytest==6.2.2
|
||||
pytest-bdd==4.0.2
|
||||
|
|
@ -48,7 +48,7 @@ pytest-repeat==0.9.1
|
|||
pytest-rerunfailures==9.1.1
|
||||
pytest-xdist==2.2.1
|
||||
pytest-xvfb==2.0.0
|
||||
PyVirtualDisplay==2.0
|
||||
PyVirtualDisplay==2.1
|
||||
requests==2.25.1
|
||||
requests-file==1.5.1
|
||||
six==1.15.0
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ pip==21.0.1
|
|||
pluggy==0.13.1
|
||||
py==1.10.0
|
||||
pyparsing==2.4.7
|
||||
setuptools==54.0.0
|
||||
setuptools==54.1.2
|
||||
six==1.15.0
|
||||
toml==0.10.2
|
||||
tox==3.22.0
|
||||
tox==3.23.0
|
||||
virtualenv==20.4.2
|
||||
wheel==0.36.2
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ GPG might then ask for your private-key passwort whenever you query the database
|
|||
[3]: https://gnupg.org/
|
||||
[4]: https://github.com/keepassxreboot/keepassxc-browser/blob/develop/keepassxc-protocol.md
|
||||
[5]: https://github.com/qutebrowser/qutebrowser/blob/master/doc/userscripts.asciidoc
|
||||
[6]: https://keepassxc.org/docs/keepassxc-browser-migration/
|
||||
[6]: https://keepassxc.org/docs/KeePassXC_GettingStarted.html#_setup_browser_integration
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ argument_parser.add_argument('--io-encoding', '-i', default='UTF-8',
|
|||
help='Encoding used to communicate with subprocesses')
|
||||
argument_parser.add_argument('--merge-candidates', '-m', action='store_true',
|
||||
help='Merge pass candidates for fully-qualified and registered domain name')
|
||||
argument_parser.add_argument('--no-tld-download', action='store_true',
|
||||
help="Don't download TLD list")
|
||||
group = argument_parser.add_mutually_exclusive_group()
|
||||
group.add_argument('--username-only', '-e',
|
||||
action='store_true', help='Only insert username')
|
||||
|
|
@ -117,7 +119,12 @@ def main(arguments):
|
|||
argument_parser.print_help()
|
||||
return ExitCodes.FAILURE
|
||||
|
||||
extract_result = tldextract.extract(arguments.url)
|
||||
if arguments.no_tld_download:
|
||||
extract = tldextract.TLDExtract(suffix_list_urls=None)
|
||||
else:
|
||||
extract = tldextract.extract
|
||||
|
||||
extract_result = extract(arguments.url)
|
||||
|
||||
# Try to find candidates using targets in the following order: fully-qualified domain name (includes subdomains),
|
||||
# the registered domain name and finally: the IPv4 address if that's what
|
||||
|
|
|
|||
|
|
@ -158,6 +158,8 @@ def pass_(path):
|
|||
|
||||
|
||||
def pass_otp(path):
|
||||
if arguments.mode == "gopass":
|
||||
return _run_pass(['otp', '-o', path])
|
||||
return _run_pass(['otp', path])
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,9 @@ with codecs.open(os.environ['QUTE_HTML'], 'r', 'utf-8') as source:
|
|||
title = doc.title()
|
||||
content = doc.summary().replace('<html>', HEADER % title)
|
||||
|
||||
# add a class to make styling the page easier
|
||||
content = content.replace('<body>', '<body class="qute-readability">')
|
||||
|
||||
with codecs.open(tmpfile, 'w', 'utf-8') as target:
|
||||
target.write(content.lstrip())
|
||||
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ const HEADER = `
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, text/html, charset=UTF-8" http-equiv="Content-Type">
|
||||
</meta>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="UTF-8">
|
||||
<title>%s</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
|
|
@ -106,7 +106,12 @@ const HEADER = `
|
|||
SAwLTIgMC44OTUtMiAyczAuODk1IDIgMiAyaDIwYzEuMTEgMCAyLTAuODk1IDItMnMtMC44OTUtMi0yLTJ6bTAgOGgtMjBjLTEuMTEgMC0yIDAuODk1LTIg
|
||||
MnMwLjg5NSAyIDIgMmgyMGMxLjExIDAgMi0wLjg5NSAyLTJzLTAuODk1LTItMi0yem0tMTIgOGgtOGMtMS4xMSAwLTIgMC44OTUtMiAyczAuODk1IDIgMiA
|
||||
yaDhjMS4xMSAwIDItMC44OTUgMi0ycy0wLjg5NS0yLTItMnoiIGZpbGw9IiNmZmYiLz4KPC9nPgo8L3N2Zz4K"/>
|
||||
</head>`;
|
||||
</head>
|
||||
<body class="qute-readability">
|
||||
%s
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
const scriptsDir = path.join(process.env.QUTE_DATA_DIR, 'userscripts');
|
||||
const tmpFile = path.join(scriptsDir, '/readability.html');
|
||||
|
||||
|
|
@ -129,7 +134,7 @@ else {
|
|||
getDOM(target, domOpts).then(dom => {
|
||||
let reader = new Readability(dom.window.document);
|
||||
let article = reader.parse();
|
||||
let content = util.format(HEADER, article.title) + article.content;
|
||||
let content = util.format(HEADER, article.title, article.content);
|
||||
|
||||
fs.writeFile(tmpFile, content, (err) => {
|
||||
if (err) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ markers =
|
|||
no_invalid_lines: Don't fail on unparsable lines in end2end tests
|
||||
fake_os: Fake utils.is_* to a fake operating system
|
||||
unicode_locale: Tests which need a unicode locale to work
|
||||
qtwebkit6021_xfail: Tests which would fail on WebKit version 602.1
|
||||
js_headers: Sets JS headers dynamically on QtWebEngine (unsupported on some versions)
|
||||
qtwebkit_pdf_imageformat_skip: Broken on QtWebKit with PDF image format plugin installed
|
||||
windows_skip: Tests which should be skipped on Windows
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2021 Florian Bruhin (The Compiler)"
|
|||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version__ = "2.0.2"
|
||||
__version__ = "2.1.0"
|
||||
__version_info__ = tuple(int(part) for part in __version__.split('.'))
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@ from qutebrowser.misc import (ipc, savemanager, sessions, crashsignal,
|
|||
earlyinit, sql, cmdhistory, backendproblem,
|
||||
objects, quitter)
|
||||
from qutebrowser.utils import (log, version, message, utils, urlutils, objreg,
|
||||
usertypes, standarddir, error, qtutils, debug)
|
||||
resources, usertypes, standarddir,
|
||||
error, qtutils, debug)
|
||||
# pylint: disable=unused-import
|
||||
# We import those to run the cmdutils.register decorators.
|
||||
from qutebrowser.mainwindow.statusbar import command
|
||||
|
|
@ -86,7 +87,7 @@ def run(args):
|
|||
|
||||
log.init.debug("Initializing directories...")
|
||||
standarddir.init(args)
|
||||
utils.preload_resources()
|
||||
resources.preload()
|
||||
|
||||
log.init.debug("Initializing config...")
|
||||
configinit.early_init(args)
|
||||
|
|
@ -119,13 +120,14 @@ def run(args):
|
|||
log.init.warning(
|
||||
"Backend from the running instance will be used")
|
||||
sys.exit(usertypes.Exit.ok)
|
||||
else:
|
||||
quitter.instance.shutting_down.connect(server.shutdown)
|
||||
server.got_args.connect(lambda args, target_arg, cwd:
|
||||
process_pos_args(args, cwd=cwd, via_ipc=True,
|
||||
target_arg=target_arg))
|
||||
|
||||
init(args=args)
|
||||
|
||||
quitter.instance.shutting_down.connect(server.shutdown)
|
||||
server.got_args.connect(
|
||||
lambda args, target_arg, cwd:
|
||||
process_pos_args(args, cwd=cwd, via_ipc=True, target_arg=target_arg))
|
||||
|
||||
ret = qt_mainloop()
|
||||
return ret
|
||||
|
||||
|
|
@ -395,7 +397,7 @@ def _open_special_pages(args):
|
|||
return
|
||||
|
||||
try:
|
||||
changelog = utils.read_file('html/doc/changelog.html')
|
||||
changelog = resources.read_file('html/doc/changelog.html')
|
||||
except OSError as e:
|
||||
log.init.warning(f"Not showing changelog due to {e}")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -917,7 +917,7 @@ class CommandDispatcher:
|
|||
return (tabbed_browser, tabbed_browser.widget.widget(idx-1))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0, deprecated_name='buffer')
|
||||
maxsplit=0)
|
||||
@cmdutils.argument('index', completion=miscmodels.tabs)
|
||||
@cmdutils.argument('count', value=cmdutils.Value.count)
|
||||
def tab_select(self, index=None, count=None):
|
||||
|
|
@ -1488,8 +1488,7 @@ class CommandDispatcher:
|
|||
objreg.last_focused_window(), alert=False))
|
||||
ed.edit(text, caret_position)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
deprecated_name='open-editor')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def edit_text(self):
|
||||
"""Open an external editor with the currently selected form field.
|
||||
|
||||
|
|
|
|||
|
|
@ -92,6 +92,8 @@ class HintLabel(QLabel):
|
|||
self._context = context
|
||||
self.elem = elem
|
||||
|
||||
self.setTextFormat(Qt.RichText)
|
||||
|
||||
# Make sure we can style the background via a style sheet, and we don't
|
||||
# get any extra text indent from Qt.
|
||||
# The real stylesheet lives in mainwindow.py for performance reasons..
|
||||
|
|
@ -1000,7 +1002,7 @@ class HintManager(QObject):
|
|||
self._context.first_run = False
|
||||
|
||||
@cmdutils.register(instance='hintmanager', scope='window',
|
||||
modes=[usertypes.KeyMode.hint], deprecated_name='follow-hint')
|
||||
modes=[usertypes.KeyMode.hint])
|
||||
def hint_follow(self, select: bool = False, keystring: str = None) -> None:
|
||||
"""Follow a hint.
|
||||
|
||||
|
|
|
|||
|
|
@ -92,8 +92,11 @@ class CompletionMetaInfo(sql.SqlTable):
|
|||
}
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__("CompletionMetaInfo", ['key', 'value'],
|
||||
constraints={'key': 'PRIMARY KEY'})
|
||||
self._fields = ['key', 'value']
|
||||
self._constraints = {'key': 'PRIMARY KEY'}
|
||||
super().__init__(
|
||||
"CompletionMetaInfo", self._fields, constraints=self._constraints)
|
||||
|
||||
if sql.user_version_changed():
|
||||
self._init_default_values()
|
||||
|
||||
|
|
@ -101,6 +104,15 @@ class CompletionMetaInfo(sql.SqlTable):
|
|||
if key not in self.KEYS:
|
||||
raise KeyError(key)
|
||||
|
||||
def try_recover(self):
|
||||
"""Try recovering the table structure.
|
||||
|
||||
This should be called if getting a value via __getattr__ failed. In theory, this
|
||||
should never happen, in practice, it does.
|
||||
"""
|
||||
self._create_table(self._fields, constraints=self._constraints, force=True)
|
||||
self._init_default_values()
|
||||
|
||||
def _init_default_values(self):
|
||||
for key, default in self.KEYS.items():
|
||||
if key not in self:
|
||||
|
|
@ -164,7 +176,13 @@ class WebHistory(sql.SqlTable):
|
|||
self.completion = CompletionHistory(parent=self)
|
||||
self.metainfo = CompletionMetaInfo(parent=self)
|
||||
|
||||
rebuild_completion = self.metainfo['force_rebuild']
|
||||
try:
|
||||
rebuild_completion = self.metainfo['force_rebuild']
|
||||
except sql.BugError: # pragma: no cover
|
||||
log.sql.warning("Failed to access meta info, trying to recover...",
|
||||
exc_info=True)
|
||||
self.metainfo.try_recover()
|
||||
rebuild_completion = self.metainfo['force_rebuild']
|
||||
|
||||
if sql.user_version_changed():
|
||||
# If the DB user version changed, run a full cleanup and rebuild the
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ from PyQt5.QtNetwork import (QNetworkProxy, QNetworkRequest, QHostInfo,
|
|||
QHostAddress)
|
||||
from PyQt5.QtQml import QJSEngine, QJSValue
|
||||
|
||||
from qutebrowser.utils import log, utils, qtutils
|
||||
from qutebrowser.utils import log, utils, qtutils, resources
|
||||
|
||||
|
||||
class ParseProxyError(Exception):
|
||||
|
|
@ -190,7 +190,7 @@ class PACResolver:
|
|||
self._engine.globalObject().setProperty(
|
||||
"PAC", self._engine.newQObject(self._ctx))
|
||||
self._evaluate(_PACContext.JS_DEFINITIONS, "pac_js_definitions")
|
||||
self._evaluate(utils.read_file("javascript/pac_utils.js"), "pac_utils")
|
||||
self._evaluate(resources.read_file("javascript/pac_utils.js"), "pac_utils")
|
||||
proxy_config = self._engine.newObject()
|
||||
proxy_config.setProperty("bindings", self._engine.newObject())
|
||||
self._engine.globalObject().setProperty("ProxyConfig", proxy_config)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import os
|
|||
|
||||
from PyQt5.QtCore import QUrl, QUrlQuery
|
||||
|
||||
from qutebrowser.utils import utils, javascript, jinja, standarddir, log
|
||||
from qutebrowser.utils import resources, javascript, jinja, standarddir, log
|
||||
from qutebrowser.config import config
|
||||
|
||||
|
||||
|
|
@ -149,7 +149,7 @@ def get_pdfjs_res_and_path(path):
|
|||
if content is None:
|
||||
res_path = '3rdparty/pdfjs/{}'.format(path)
|
||||
try:
|
||||
content = utils.read_file_binary(res_path)
|
||||
content = resources.read_file_binary(res_path)
|
||||
except FileNotFoundError:
|
||||
raise PDFJSNotFound(path) from None
|
||||
except OSError as e:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import qutebrowser
|
|||
from qutebrowser.browser import pdfjs, downloads, history
|
||||
from qutebrowser.config import config, configdata, configexc
|
||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||
objreg, standarddir)
|
||||
resources, objreg, standarddir)
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
|
|
@ -271,7 +271,7 @@ def qute_javascript(url: QUrl) -> _HandlerRet:
|
|||
path = url.path()
|
||||
if path:
|
||||
path = "javascript" + os.sep.join(path.split('/'))
|
||||
return 'text/html', utils.read_file(path)
|
||||
return 'text/html', resources.read_file(path)
|
||||
else:
|
||||
raise UrlInvalidError("No file specified")
|
||||
|
||||
|
|
@ -345,14 +345,14 @@ def qute_log(url: QUrl) -> _HandlerRet:
|
|||
@add_handler('gpl')
|
||||
def qute_gpl(_url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://gpl. Return HTML content as string."""
|
||||
return 'text/html', utils.read_file('html/license.html')
|
||||
return 'text/html', resources.read_file('html/license.html')
|
||||
|
||||
|
||||
def _asciidoc_fallback_path(html_path: str) -> Optional[str]:
|
||||
"""Fall back to plaintext asciidoc if the HTML is unavailable."""
|
||||
path = html_path.replace('.html', '.asciidoc')
|
||||
try:
|
||||
return utils.read_file(path)
|
||||
return resources.read_file(path)
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
|
@ -372,14 +372,14 @@ def qute_help(url: QUrl) -> _HandlerRet:
|
|||
path = 'html/doc/{}'.format(urlpath)
|
||||
if not urlpath.endswith('.html'):
|
||||
try:
|
||||
bdata = utils.read_file_binary(path)
|
||||
bdata = resources.read_file_binary(path)
|
||||
except OSError as e:
|
||||
raise SchemeOSError(e)
|
||||
mimetype = utils.guess_mimetype(urlpath)
|
||||
return mimetype, bdata
|
||||
|
||||
try:
|
||||
data = utils.read_file(path)
|
||||
data = resources.read_file(path)
|
||||
except OSError:
|
||||
asciidoc = _asciidoc_fallback_path(path)
|
||||
|
||||
|
|
@ -575,7 +575,7 @@ def qute_resource(url: QUrl) -> _HandlerRet:
|
|||
path = url.path().lstrip('/')
|
||||
mimetype = utils.guess_mimetype(path, fallback=True)
|
||||
try:
|
||||
data = utils.read_file_binary(path)
|
||||
data = resources.read_file_binary(path)
|
||||
except FileNotFoundError as e:
|
||||
raise NotFoundError(str(e))
|
||||
return mimetype, data
|
||||
|
|
|
|||
|
|
@ -25,15 +25,15 @@ import html
|
|||
import enum
|
||||
import netrc
|
||||
import tempfile
|
||||
from typing import Callable, Mapping, List, Optional
|
||||
from typing import Callable, Mapping, List, Optional, Iterable
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtCore import QUrl, pyqtBoundSignal
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (usertypes, message, log, objreg, jinja, utils,
|
||||
qtutils)
|
||||
qtutils, version)
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
from qutebrowser.misc import guiprocess
|
||||
from qutebrowser.misc import guiprocess, objects
|
||||
|
||||
|
||||
class CallSuper(Exception):
|
||||
|
|
@ -156,54 +156,86 @@ def javascript_log_message(level, source, line, msg):
|
|||
logger(logstring)
|
||||
|
||||
|
||||
def ignore_certificate_errors(url, errors, abort_on):
|
||||
def ignore_certificate_error(
|
||||
*,
|
||||
request_url: QUrl,
|
||||
first_party_url: QUrl,
|
||||
error: usertypes.AbstractCertificateErrorWrapper,
|
||||
abort_on: Iterable[pyqtBoundSignal],
|
||||
) -> bool:
|
||||
"""Display a certificate error question.
|
||||
|
||||
Args:
|
||||
url: The URL the errors happened in
|
||||
errors: A list of QSslErrors or QWebEngineCertificateErrors
|
||||
request_url: The URL of the request where the errors happened.
|
||||
first_party_url: The URL of the page we're visiting. Might be an invalid QUrl.
|
||||
error: A single error.
|
||||
abort_on: Signals aborting a question.
|
||||
|
||||
Return:
|
||||
True if the error should be ignored, False otherwise.
|
||||
"""
|
||||
ssl_strict = config.instance.get('content.ssl_strict', url=url)
|
||||
log.network.debug("Certificate errors {!r}, strict {}".format(
|
||||
errors, ssl_strict))
|
||||
conf = config.instance.get('content.tls.certificate_errors', url=request_url)
|
||||
log.network.debug(f"Certificate error {error!r}, config {conf}")
|
||||
|
||||
for error in errors:
|
||||
assert error.is_overridable(), repr(error)
|
||||
assert error.is_overridable(), repr(error)
|
||||
|
||||
if ssl_strict == 'ask':
|
||||
# We get the first party URL with a heuristic - with HTTP -> HTTPS redirects, the
|
||||
# scheme might not match.
|
||||
is_resource = (
|
||||
first_party_url.isValid() and
|
||||
not request_url.matches(
|
||||
first_party_url,
|
||||
QUrl.RemoveScheme)) # type: ignore[arg-type]
|
||||
|
||||
if conf == 'ask' or conf == 'ask-block-thirdparty' and not is_resource:
|
||||
err_template = jinja.environment.from_string("""
|
||||
Errors while loading <b>{{url.toDisplayString()}}</b>:<br/>
|
||||
<ul>
|
||||
{% for err in errors %}
|
||||
<li>{{err}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
""".strip())
|
||||
msg = err_template.render(url=url, errors=errors)
|
||||
{% if is_resource %}
|
||||
<p>
|
||||
Error while loading resource <b>{{request_url.toDisplayString()}}</b><br/>
|
||||
on page <b>{{first_party_url.toDisplayString()}}</b>:
|
||||
</p>
|
||||
{% else %}
|
||||
<p>Error while loading page <b>{{request_url.toDisplayString()}}</b>:</p>
|
||||
{% endif %}
|
||||
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
ignore = message.ask(title="Certificate errors - continue?", text=msg,
|
||||
{{error.html()|safe}}
|
||||
|
||||
{% if is_resource %}
|
||||
<p><i>Consider reporting this to the website operator, or set
|
||||
<tt>content.tls.certificate_errors</tt> to <tt>ask-block-thirdparty</tt> to
|
||||
always block invalid resource loads.</i></p>
|
||||
{% endif %}
|
||||
|
||||
Do you want to ignore these errors and continue loading the page <b>insecurely</b>?
|
||||
""".strip())
|
||||
msg = err_template.render(
|
||||
request_url=request_url,
|
||||
first_party_url=first_party_url,
|
||||
is_resource=is_resource,
|
||||
error=error,
|
||||
)
|
||||
|
||||
urlstr = request_url.toString(
|
||||
QUrl.RemovePassword | QUrl.FullyEncoded) # type: ignore[arg-type]
|
||||
ignore = message.ask(title="Certificate error", text=msg,
|
||||
mode=usertypes.PromptMode.yesno, default=False,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
if ignore is None:
|
||||
# prompt aborted
|
||||
ignore = False
|
||||
return ignore
|
||||
elif ssl_strict is False:
|
||||
log.network.debug("ssl_strict is False, only warning about errors")
|
||||
for err in errors:
|
||||
# FIXME we might want to use warn here (non-fatal error)
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/114
|
||||
message.error('Certificate error: {}'.format(err))
|
||||
elif conf == 'load-insecurely':
|
||||
message.error(f'Certificate error: {error}')
|
||||
return True
|
||||
elif ssl_strict is True:
|
||||
elif conf == 'block':
|
||||
return False
|
||||
else:
|
||||
raise ValueError("Invalid ssl_strict value {!r}".format(ssl_strict))
|
||||
raise utils.Unreachable
|
||||
elif conf == 'ask-block-thirdparty' and is_resource:
|
||||
log.network.error(
|
||||
f"Certificate error in resource load: {error}\n"
|
||||
f" request URL: {request_url.toDisplayString()}\n"
|
||||
f" first party URL: {first_party_url.toDisplayString()}")
|
||||
return False
|
||||
raise utils.Unreachable(conf, is_resource)
|
||||
|
||||
|
||||
def feature_permission(url, option, msg, yes_action, no_action, abort_on,
|
||||
|
|
@ -299,6 +331,15 @@ def get_user_stylesheet(searching=False):
|
|||
if setting == 'never' or setting == 'when-searching' and not searching:
|
||||
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
|
||||
|
||||
if (objects.backend == usertypes.Backend.QtWebEngine and
|
||||
version.qtwebengine_versions().chromium_major in [69, 73, 80, 87] and
|
||||
config.val.colors.webpage.darkmode.enabled and
|
||||
config.val.colors.webpage.darkmode.policy.images == 'smart' and
|
||||
config.val.content.site_specific_quirks):
|
||||
# WORKAROUND for MathML-output on Wikipedia being black on black.
|
||||
# See https://bugs.chromium.org/p/chromium/issues/detail?id=1126606
|
||||
css += '\nimg.mwe-math-fallback-image-inline { filter: invert(100%); }'
|
||||
|
||||
return css
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
"""Wrapper over a QWebEngineCertificateError."""
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineCertificateError
|
||||
|
||||
from qutebrowser.utils import usertypes, utils, debug
|
||||
|
|
@ -28,21 +29,21 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
|||
|
||||
"""A wrapper over a QWebEngineCertificateError."""
|
||||
|
||||
def __init__(self, error):
|
||||
super().__init__(error)
|
||||
def __init__(self, error: QWebEngineCertificateError) -> None:
|
||||
self._error = error
|
||||
self.ignore = False
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return self._error.errorDescription()
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return utils.get_repr(
|
||||
self, error=debug.qenum_key(QWebEngineCertificateError,
|
||||
self._error.error()),
|
||||
self,
|
||||
error=debug.qenum_key(QWebEngineCertificateError, self._error.error()),
|
||||
string=str(self))
|
||||
|
||||
def url(self):
|
||||
def url(self) -> QUrl:
|
||||
return self._error.url()
|
||||
|
||||
def is_overridable(self):
|
||||
def is_overridable(self) -> bool:
|
||||
return self._error.isOverridable()
|
||||
|
|
|
|||
|
|
@ -342,8 +342,7 @@ def _variant(versions: version.WebEngineVersions) -> Variant:
|
|||
log.init.warning(f"Ignoring invalid QUTE_DARKMODE_VARIANT={env_var}")
|
||||
|
||||
if (versions.webengine == utils.VersionNumber(5, 15, 2) and
|
||||
versions.chromium is not None and
|
||||
versions.chromium.startswith('87.')):
|
||||
versions.chromium_major == 87):
|
||||
# WORKAROUND for Gentoo packaging something newer as 5.15.2...
|
||||
return Variant.qt_515_3
|
||||
elif versions.webengine >= utils.VersionNumber(5, 15, 3):
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ from PyQt5.QtWidgets import QWidget
|
|||
from qutebrowser.browser import inspector
|
||||
from qutebrowser.browser.webengine import webenginesettings
|
||||
from qutebrowser.misc import miscwidgets
|
||||
from qutebrowser.utils import version
|
||||
from qutebrowser.utils import version, usertypes
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
||||
|
||||
class WebEngineInspectorView(QWebEngineView):
|
||||
|
|
@ -60,9 +61,23 @@ class WebEngineInspector(inspector.AbstractWebInspector):
|
|||
parent: QWidget = None) -> None:
|
||||
super().__init__(splitter, win_id, parent)
|
||||
self._check_devtools_resources()
|
||||
|
||||
view = WebEngineInspectorView()
|
||||
self._settings = webenginesettings.WebEngineSettings(view.settings())
|
||||
self._set_widget(view)
|
||||
page = view.page()
|
||||
page.windowCloseRequested.connect( # type: ignore[attr-defined]
|
||||
self._on_window_close_requested)
|
||||
|
||||
def _on_window_close_requested(self) -> None:
|
||||
"""Called when the 'x' was clicked in the devtools."""
|
||||
modeman.leave(
|
||||
self._win_id,
|
||||
usertypes.KeyMode.insert,
|
||||
'devtools close requested',
|
||||
maybe=True,
|
||||
)
|
||||
self.hide()
|
||||
|
||||
def _check_devtools_resources(self) -> None:
|
||||
"""Make sure that the devtools resources are available on Fedora.
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ from qutebrowser.browser.webengine import (spell, webenginequtescheme, cookies,
|
|||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.config.websettings import AttributeInfo as Attr
|
||||
from qutebrowser.utils import (standarddir, qtutils, message, log,
|
||||
urlmatch, usertypes, objreg)
|
||||
urlmatch, usertypes, objreg, version)
|
||||
if TYPE_CHECKING:
|
||||
from qutebrowser.browser.webengine import interceptor
|
||||
|
||||
|
|
@ -374,7 +374,17 @@ def _init_default_profile():
|
|||
|
||||
default_profile = QWebEngineProfile.defaultProfile()
|
||||
|
||||
assert parsed_user_agent is None # avoid earlier profile initialization
|
||||
non_ua_version = version.qtwebengine_versions(avoid_init=True)
|
||||
|
||||
init_user_agent()
|
||||
ua_version = version.qtwebengine_versions()
|
||||
if ua_version.webengine != non_ua_version.webengine:
|
||||
log.init.warning(
|
||||
"QtWebEngine version mismatch - unexpected behavior might occur, "
|
||||
"please open a bug about this.\n"
|
||||
f" Early version: {non_ua_version}\n"
|
||||
f" Real version: {ua_version}")
|
||||
|
||||
default_profile.setCachePath(
|
||||
os.path.join(standarddir.cache(), 'webengine'))
|
||||
|
|
@ -448,6 +458,13 @@ def _init_site_specific_quirks():
|
|||
pattern=urlmatch.UrlPattern(pattern),
|
||||
hide_userconfig=True)
|
||||
|
||||
config.instance.set_obj(
|
||||
'content.headers.accept_language',
|
||||
'',
|
||||
pattern=urlmatch.UrlPattern('https://matchmaker.krunker.io/*'),
|
||||
hide_userconfig=True,
|
||||
)
|
||||
|
||||
|
||||
def _init_devtools_settings():
|
||||
"""Make sure the devtools always get images/JS permissions."""
|
||||
|
|
@ -489,13 +506,16 @@ def init():
|
|||
from qutebrowser.misc import quitter
|
||||
quitter.instance.shutting_down.connect(_download_manager.shutdown)
|
||||
|
||||
log.init.debug("Initializing global settings...")
|
||||
global _global_settings
|
||||
_global_settings = WebEngineSettings(_SettingsWrapper())
|
||||
|
||||
log.init.debug("Initializing profiles...")
|
||||
_init_default_profile()
|
||||
init_private_profile()
|
||||
config.instance.changed.connect(_update_settings)
|
||||
|
||||
log.init.debug("Initializing site specific quirks...")
|
||||
_init_site_specific_quirks()
|
||||
_init_devtools_settings()
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
|||
webengineinspector)
|
||||
|
||||
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
|
||||
message, jinja, debug, version)
|
||||
resources, message, jinja, debug, version)
|
||||
from qutebrowser.qt import sip
|
||||
from qutebrowser.misc import objects, miscwidgets
|
||||
|
||||
|
|
@ -1038,9 +1038,9 @@ class _WebEngineScripts(QObject):
|
|||
"""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'),
|
||||
resources.read_file('javascript/scroll.js'),
|
||||
resources.read_file('javascript/webelem.js'),
|
||||
resources.read_file('javascript/caret.js'),
|
||||
)
|
||||
# FIXME:qtwebengine what about subframes=True?
|
||||
self._inject_js('js', js_code, subframes=True)
|
||||
|
|
@ -1061,7 +1061,7 @@ class _WebEngineScripts(QObject):
|
|||
css = shared.get_user_stylesheet()
|
||||
js_code = javascript.wrap_global(
|
||||
'stylesheet',
|
||||
utils.read_file('javascript/stylesheet.js'),
|
||||
resources.read_file('javascript/stylesheet.js'),
|
||||
javascript.assemble('stylesheet', 'set_css', css),
|
||||
)
|
||||
self._inject_js('stylesheet', js_code, subframes=True)
|
||||
|
|
@ -1080,18 +1080,11 @@ class _WebEngineScripts(QObject):
|
|||
removed = page_scripts.remove(script)
|
||||
assert removed, script.name()
|
||||
|
||||
def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None,
|
||||
remove_first=True):
|
||||
def _inject_greasemonkey_scripts(self, scripts):
|
||||
"""Register user JavaScript files with the current tab.
|
||||
|
||||
Args:
|
||||
scripts: A list of GreasemonkeyScripts, or None to add all
|
||||
known by the Greasemonkey subsystem.
|
||||
injection_point: The QWebEngineScript::InjectionPoint stage
|
||||
to inject the script into, None to use
|
||||
auto-detection.
|
||||
remove_first: Whether to remove all previously injected
|
||||
scripts before adding these ones.
|
||||
scripts: A list of GreasemonkeyScripts.
|
||||
"""
|
||||
if sip.isdeleted(self._widget):
|
||||
return
|
||||
|
|
@ -1102,49 +1095,49 @@ class _WebEngineScripts(QObject):
|
|||
# While, taking care not to remove any other scripts that might
|
||||
# have been added elsewhere, like the one for stylesheets.
|
||||
page_scripts = self._widget.page().scripts()
|
||||
if remove_first:
|
||||
self._remove_all_greasemonkey_scripts()
|
||||
|
||||
if not scripts:
|
||||
return
|
||||
self._remove_all_greasemonkey_scripts()
|
||||
|
||||
for script in scripts:
|
||||
new_script = QWebEngineScript()
|
||||
|
||||
try:
|
||||
world = int(script.jsworld)
|
||||
if not 0 <= world <= qtutils.MAX_WORLD_ID:
|
||||
log.greasemonkey.error(
|
||||
"script {} has invalid value for '@qute-js-world'"
|
||||
": {}, should be between 0 and {}"
|
||||
.format(
|
||||
script.name,
|
||||
script.jsworld,
|
||||
qtutils.MAX_WORLD_ID))
|
||||
f"script {script.name} has invalid value for '@qute-js-world'"
|
||||
f": {script.jsworld}, should be between 0 and "
|
||||
f"{qtutils.MAX_WORLD_ID}")
|
||||
continue
|
||||
except ValueError:
|
||||
try:
|
||||
world = _JS_WORLD_MAP[usertypes.JsWorld[
|
||||
script.jsworld.lower()]]
|
||||
world = _JS_WORLD_MAP[usertypes.JsWorld[script.jsworld.lower()]]
|
||||
except KeyError:
|
||||
log.greasemonkey.error(
|
||||
"script {} has invalid value for '@qute-js-world'"
|
||||
": {}".format(script.name, script.jsworld))
|
||||
f"script {script.name} has invalid value for '@qute-js-world'"
|
||||
f": {script.jsworld}")
|
||||
continue
|
||||
new_script.setWorldId(world)
|
||||
|
||||
# Corresponds to "@run-at document-end" which is the default according to
|
||||
# https://wiki.greasespot.net/Metadata_Block#.40run-at - however,
|
||||
# QtWebEngine uses QWebEngineScript.Deferred (@run-at document-idle) as
|
||||
# default.
|
||||
#
|
||||
# NOTE that this needs to be done before setSourceCode, so that
|
||||
# QtWebEngine's parsing of GreaseMonkey tags will override it if there is a
|
||||
# @run-at comment.
|
||||
new_script.setInjectionPoint(QWebEngineScript.DocumentReady)
|
||||
|
||||
new_script.setSourceCode(script.code())
|
||||
new_script.setName("GM-{}".format(script.name))
|
||||
new_script.setName(f"GM-{script.name}")
|
||||
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
|
||||
|
||||
# Override the @run-at value parsed by QWebEngineScript if desired.
|
||||
if injection_point:
|
||||
new_script.setInjectionPoint(injection_point)
|
||||
elif script.needs_document_end_workaround():
|
||||
log.greasemonkey.debug("Forcing @run-at document-end for {}"
|
||||
.format(script.name))
|
||||
if script.needs_document_end_workaround():
|
||||
log.greasemonkey.debug(
|
||||
f"Forcing @run-at document-end for {script.name}")
|
||||
new_script.setInjectionPoint(QWebEngineScript.DocumentReady)
|
||||
|
||||
log.greasemonkey.debug('adding script: {}'
|
||||
.format(new_script.name()))
|
||||
log.greasemonkey.debug(f'adding script: {new_script.name()}')
|
||||
page_scripts.insert(new_script)
|
||||
|
||||
def _inject_site_specific_quirks(self):
|
||||
|
|
@ -1176,7 +1169,7 @@ class _WebEngineScripts(QObject):
|
|||
for quirk in quirks:
|
||||
if not quirk.predicate:
|
||||
continue
|
||||
src = utils.read_file(f'javascript/quirks/{quirk.filename}.user.js')
|
||||
src = resources.read_file(f'javascript/quirks/{quirk.filename}.user.js')
|
||||
self._inject_js(
|
||||
f'quirk_{quirk.filename}',
|
||||
src,
|
||||
|
|
@ -1523,11 +1516,22 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
url = error.url()
|
||||
self._insecure_hosts.add(url.host())
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-92009
|
||||
# self.url() is not available yet and the requested URL might not match the URL
|
||||
# we get from the error - so we just apply a heuristic here.
|
||||
assert self.data.last_navigation is not None
|
||||
first_party_url = self.data.last_navigation.url
|
||||
|
||||
log.network.debug("Certificate error: {}".format(error))
|
||||
log.network.debug("First party URL: {}".format(first_party_url))
|
||||
|
||||
if error.is_overridable():
|
||||
error.ignore = shared.ignore_certificate_errors(
|
||||
url, [error], abort_on=[self.abort_questions])
|
||||
error.ignore = shared.ignore_certificate_error(
|
||||
request_url=url,
|
||||
first_party_url=first_party_url,
|
||||
error=error,
|
||||
abort_on=[self.abort_questions],
|
||||
)
|
||||
else:
|
||||
log.network.error("Non-overridable certificate error: "
|
||||
"{}".format(error))
|
||||
|
|
@ -1551,12 +1555,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
|
||||
# We can't really know when to show an error page, as the error might
|
||||
# have happened when loading some resource.
|
||||
# However, self.url() is not available yet and the requested URL
|
||||
# might not match the URL we get from the error - so we just apply a
|
||||
# heuristic here.
|
||||
assert self.data.last_navigation is not None
|
||||
if (show_non_overr_cert_error and
|
||||
url.matches(self.data.last_navigation.url, QUrl.RemoveScheme)):
|
||||
is_resource = (
|
||||
first_party_url.isValid() and
|
||||
url.matches(first_party_url, QUrl.RemoveScheme))
|
||||
if show_non_overr_cert_error and is_resource:
|
||||
self._show_error_page(url, str(error))
|
||||
|
||||
@pyqtSlot()
|
||||
|
|
|
|||
|
|
@ -17,31 +17,51 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""Wrapper over a QSslError."""
|
||||
"""A wrapper over a list of QSslErrors."""
|
||||
|
||||
from typing import Sequence
|
||||
|
||||
from PyQt5.QtNetwork import QSslError
|
||||
|
||||
from qutebrowser.utils import usertypes, utils, debug
|
||||
from qutebrowser.utils import usertypes, utils, debug, jinja
|
||||
|
||||
|
||||
class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
||||
|
||||
"""A wrapper over a QSslError."""
|
||||
"""A wrapper over a list of QSslErrors."""
|
||||
|
||||
def __str__(self):
|
||||
return self._error.errorString()
|
||||
def __init__(self, errors: Sequence[QSslError]) -> None:
|
||||
self._errors = tuple(errors) # needs to be hashable
|
||||
|
||||
def __repr__(self):
|
||||
def __str__(self) -> str:
|
||||
return '\n'.join(err.errorString() for err in self._errors)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return utils.get_repr(
|
||||
self, error=debug.qenum_key(QSslError, self._error.error()),
|
||||
self,
|
||||
errors=[debug.qenum_key(QSslError, err.error()) for err in self._errors],
|
||||
string=str(self))
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._error)
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._errors)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._error == other._error
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, CertificateErrorWrapper):
|
||||
return NotImplemented
|
||||
return self._errors == other._errors
|
||||
|
||||
def is_overridable(self):
|
||||
def is_overridable(self) -> bool:
|
||||
return True
|
||||
|
||||
def html(self):
|
||||
if len(self._errors) == 1:
|
||||
return super().html()
|
||||
|
||||
template = jinja.environment.from_string("""
|
||||
<ul>
|
||||
{% for err in errors %}
|
||||
<li>{{err.errorString()}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
""".strip())
|
||||
return template.render(errors=self._errors)
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@
|
|||
import collections
|
||||
import html
|
||||
import dataclasses
|
||||
from typing import TYPE_CHECKING, Dict, MutableMapping, Optional, Sequence
|
||||
from typing import TYPE_CHECKING, Dict, MutableMapping, Optional, Set
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QUrl, QByteArray
|
||||
from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslSocket,
|
||||
QSslError, QNetworkProxy)
|
||||
QNetworkProxy)
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (message, log, usertypes, utils, objreg,
|
||||
|
|
@ -122,7 +122,10 @@ def init():
|
|||
QSslSocket.setDefaultCiphers(good_ciphers)
|
||||
|
||||
|
||||
_SavedErrorsType = MutableMapping[urlutils.HostTupleType, Sequence[QSslError]]
|
||||
_SavedErrorsType = MutableMapping[
|
||||
urlutils.HostTupleType,
|
||||
Set[certificateerror.CertificateErrorWrapper],
|
||||
]
|
||||
|
||||
|
||||
class NetworkManager(QNetworkAccessManager):
|
||||
|
|
@ -141,8 +144,8 @@ class NetworkManager(QNetworkAccessManager):
|
|||
(or None for generic network managers)
|
||||
_tab_id: The tab ID this NetworkManager is associated with.
|
||||
(or None for generic network managers)
|
||||
_rejected_ssl_errors: A {QUrl: [SslError]} dict of rejected errors.
|
||||
_accepted_ssl_errors: A {QUrl: [SslError]} dict of accepted errors.
|
||||
_rejected_ssl_errors: A {QUrl: {SslError}} dict of rejected errors.
|
||||
_accepted_ssl_errors: A {QUrl: {SslError}} dict of accepted errors.
|
||||
_private: Whether we're in private browsing mode.
|
||||
netrc_used: Whether netrc authentication was performed.
|
||||
|
||||
|
|
@ -172,8 +175,8 @@ class NetworkManager(QNetworkAccessManager):
|
|||
self._set_cookiejar()
|
||||
self._set_cache()
|
||||
self.sslErrors.connect(self.on_ssl_errors)
|
||||
self._rejected_ssl_errors: _SavedErrorsType = collections.defaultdict(list)
|
||||
self._accepted_ssl_errors: _SavedErrorsType = collections.defaultdict(list)
|
||||
self._rejected_ssl_errors: _SavedErrorsType = collections.defaultdict(set)
|
||||
self._accepted_ssl_errors: _SavedErrorsType = collections.defaultdict(set)
|
||||
self.authenticationRequired.connect(self.on_authentication_required)
|
||||
self.proxyAuthenticationRequired.connect(self.on_proxy_authentication_required)
|
||||
self.netrc_used = False
|
||||
|
|
@ -214,6 +217,25 @@ class NetworkManager(QNetworkAccessManager):
|
|||
abort_on.append(tab.load_started)
|
||||
return abort_on
|
||||
|
||||
def _get_tab(self):
|
||||
"""Get the tab this NAM is associated with.
|
||||
|
||||
Return:
|
||||
The tab if available, None otherwise.
|
||||
"""
|
||||
# There are some scenarios where we can't figure out current_url:
|
||||
# - There's a generic NetworkManager, e.g. for downloads
|
||||
# - The download was in a tab which is now closed.
|
||||
if self._tab_id is None:
|
||||
return None
|
||||
|
||||
assert self._win_id is not None
|
||||
try:
|
||||
return objreg.get('tab', scope='tab', window=self._win_id, tab=self._tab_id)
|
||||
except KeyError:
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/889
|
||||
return None
|
||||
|
||||
def shutdown(self):
|
||||
"""Abort all running requests."""
|
||||
self.setNetworkAccessible(QNetworkAccessManager.NotAccessible)
|
||||
|
|
@ -221,18 +243,17 @@ class NetworkManager(QNetworkAccessManager):
|
|||
|
||||
# No @pyqtSlot here, see
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2213
|
||||
def on_ssl_errors(self, reply, errors): # noqa: C901 pragma: no mccabe
|
||||
def on_ssl_errors(self, reply, qt_errors): # noqa: C901 pragma: no mccabe
|
||||
"""Decide if SSL errors should be ignored or not.
|
||||
|
||||
This slot is called on SSL/TLS errors by the self.sslErrors signal.
|
||||
|
||||
Args:
|
||||
reply: The QNetworkReply that is encountering the errors.
|
||||
errors: A list of errors.
|
||||
qt_errors: A list of errors.
|
||||
"""
|
||||
errors = [certificateerror.CertificateErrorWrapper(e) for e in errors]
|
||||
log.network.debug("Certificate errors: {!r}".format(
|
||||
' / '.join(str(err) for err in errors)))
|
||||
errors = certificateerror.CertificateErrorWrapper(qt_errors)
|
||||
log.network.debug("Certificate errors: {!r}".format(errors))
|
||||
try:
|
||||
host_tpl: Optional[urlutils.HostTupleType] = urlutils.host_tuple(
|
||||
reply.url())
|
||||
|
|
@ -242,10 +263,8 @@ class NetworkManager(QNetworkAccessManager):
|
|||
is_rejected = False
|
||||
else:
|
||||
assert host_tpl is not None
|
||||
is_accepted = set(errors).issubset(
|
||||
self._accepted_ssl_errors[host_tpl])
|
||||
is_rejected = set(errors).issubset(
|
||||
self._rejected_ssl_errors[host_tpl])
|
||||
is_accepted = errors in self._accepted_ssl_errors[host_tpl]
|
||||
is_rejected = errors in self._rejected_ssl_errors[host_tpl]
|
||||
|
||||
log.network.debug("Already accepted: {} / "
|
||||
"rejected {}".format(is_accepted, is_rejected))
|
||||
|
|
@ -257,15 +276,22 @@ class NetworkManager(QNetworkAccessManager):
|
|||
return
|
||||
|
||||
abort_on = self._get_abort_signals(reply)
|
||||
ignore = shared.ignore_certificate_errors(reply.url(), errors,
|
||||
abort_on=abort_on)
|
||||
|
||||
tab = self._get_tab()
|
||||
first_party_url = QUrl() if tab is None else tab.data.last_navigation.url
|
||||
|
||||
ignore = shared.ignore_certificate_error(
|
||||
request_url=reply.url(),
|
||||
first_party_url=first_party_url,
|
||||
error=errors,
|
||||
abort_on=abort_on,
|
||||
)
|
||||
if ignore:
|
||||
reply.ignoreSslErrors()
|
||||
err_dict = self._accepted_ssl_errors
|
||||
else:
|
||||
err_dict = self._rejected_ssl_errors
|
||||
if host_tpl is not None:
|
||||
err_dict[host_tpl] += errors
|
||||
if host_tpl is not None:
|
||||
self._accepted_ssl_errors[host_tpl].add(errors)
|
||||
elif host_tpl is not None:
|
||||
self._rejected_ssl_errors[host_tpl].add(errors)
|
||||
|
||||
def clear_all_ssl_errors(self):
|
||||
"""Clear all remembered SSL errors."""
|
||||
|
|
@ -392,22 +418,14 @@ class NetworkManager(QNetworkAccessManager):
|
|||
for header, value in shared.custom_headers(url=req.url()):
|
||||
req.setRawHeader(header, value)
|
||||
|
||||
# There are some scenarios where we can't figure out current_url:
|
||||
# - There's a generic NetworkManager, e.g. for downloads
|
||||
# - The download was in a tab which is now closed.
|
||||
tab = self._get_tab()
|
||||
current_url = QUrl()
|
||||
|
||||
if self._tab_id is not None:
|
||||
assert self._win_id is not None
|
||||
if tab is not None:
|
||||
try:
|
||||
tab = objreg.get('tab', scope='tab', window=self._win_id,
|
||||
tab=self._tab_id)
|
||||
current_url = tab.url()
|
||||
except (KeyError, RuntimeError):
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/889
|
||||
# Catching RuntimeError because we could be in the middle of
|
||||
# the webpage shutdown here.
|
||||
current_url = QUrl()
|
||||
except RuntimeError:
|
||||
# We could be in the middle of the webpage shutdown here.
|
||||
pass
|
||||
|
||||
request = interceptors.Request(first_party_url=current_url,
|
||||
request_url=req.url())
|
||||
|
|
|
|||
|
|
@ -34,8 +34,7 @@ from PyQt5.QtPrintSupport import QPrinter
|
|||
from qutebrowser.browser import browsertab, shared
|
||||
from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
|
||||
webkitsettings, webkitinspector)
|
||||
|
||||
from qutebrowser.utils import qtutils, usertypes, utils, log, debug
|
||||
from qutebrowser.utils import qtutils, usertypes, utils, log, debug, resources
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
|
@ -228,7 +227,7 @@ class WebKitCaret(browsertab.AbstractCaret):
|
|||
# true in caret mode.
|
||||
if self._selection_state is browsertab.SelectionState.none:
|
||||
self._widget.page().currentFrame().evaluateJavaScript(
|
||||
utils.read_file('javascript/position_caret.js'))
|
||||
resources.read_file('javascript/position_caret.js'))
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def _on_mode_left(self, _mode):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2021 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
|
||||
#
|
||||
# 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""Module for parsing commands entered into the browser."""
|
||||
|
||||
import dataclasses
|
||||
from typing import List, Iterator
|
||||
|
||||
from qutebrowser.commands import cmdexc, command
|
||||
from qutebrowser.misc import split, objects
|
||||
from qutebrowser.config import config
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ParseResult:
|
||||
|
||||
"""The result of parsing a commandline."""
|
||||
|
||||
cmd: command.Command
|
||||
args: List[str]
|
||||
cmdline: List[str]
|
||||
|
||||
|
||||
class CommandParser:
|
||||
|
||||
"""Parse qutebrowser commandline commands.
|
||||
|
||||
Attributes:
|
||||
_partial_match: Whether to allow partial command matches.
|
||||
"""
|
||||
|
||||
def __init__(self, partial_match: bool = False) -> None:
|
||||
self._partial_match = partial_match
|
||||
|
||||
def _get_alias(self, text: str, *, default: str) -> str:
|
||||
"""Get an alias from the config.
|
||||
|
||||
Args:
|
||||
text: The text to parse.
|
||||
aliases: A map of aliases to commands.
|
||||
default : Default value to return when alias was not found.
|
||||
|
||||
Return:
|
||||
The new command string if an alias was found. Default value
|
||||
otherwise.
|
||||
"""
|
||||
parts = text.strip().split(maxsplit=1)
|
||||
aliases = config.cache['aliases']
|
||||
if parts[0] not in aliases:
|
||||
return default
|
||||
alias = aliases[parts[0]]
|
||||
|
||||
try:
|
||||
new_cmd = '{} {}'.format(alias, parts[1])
|
||||
except IndexError:
|
||||
new_cmd = alias
|
||||
if text.endswith(' '):
|
||||
new_cmd += ' '
|
||||
return new_cmd
|
||||
|
||||
def _parse_all_gen(
|
||||
self,
|
||||
text: str,
|
||||
aliases: bool = True,
|
||||
**kwargs: bool,
|
||||
) -> Iterator[ParseResult]:
|
||||
"""Split a command on ;; and parse all parts.
|
||||
|
||||
If the first command in the commandline is a non-split one, it only
|
||||
returns that.
|
||||
|
||||
Args:
|
||||
text: Text to parse.
|
||||
aliases: Whether to handle aliases.
|
||||
**kwargs: Passed to parse().
|
||||
|
||||
Yields:
|
||||
ParseResult tuples.
|
||||
"""
|
||||
text = text.strip().lstrip(':').strip()
|
||||
if not text:
|
||||
raise cmdexc.NoSuchCommandError("No command given")
|
||||
|
||||
if aliases:
|
||||
text = self._get_alias(text, default=text)
|
||||
|
||||
if ';;' in text:
|
||||
# Get the first command and check if it doesn't want to have ;;
|
||||
# split.
|
||||
first = text.split(';;')[0]
|
||||
result = self.parse(first, **kwargs)
|
||||
if result.cmd.no_cmd_split:
|
||||
sub_texts = [text]
|
||||
else:
|
||||
sub_texts = [e.strip() for e in text.split(';;')]
|
||||
else:
|
||||
sub_texts = [text]
|
||||
for sub in sub_texts:
|
||||
yield self.parse(sub, **kwargs)
|
||||
|
||||
def parse_all(self, text: str, **kwargs: bool) -> List[ParseResult]:
|
||||
"""Wrapper over _parse_all_gen."""
|
||||
return list(self._parse_all_gen(text, **kwargs))
|
||||
|
||||
def parse(self, text: str, *, keep: bool = False) -> ParseResult:
|
||||
"""Split the commandline text into command and arguments.
|
||||
|
||||
Args:
|
||||
text: Text to parse.
|
||||
keep: Whether to keep special chars and whitespace.
|
||||
"""
|
||||
cmdstr, sep, argstr = text.partition(' ')
|
||||
|
||||
if not cmdstr:
|
||||
raise cmdexc.NoSuchCommandError("No command given")
|
||||
|
||||
if self._partial_match:
|
||||
cmdstr = self._completion_match(cmdstr)
|
||||
|
||||
try:
|
||||
cmd = objects.commands[cmdstr]
|
||||
except KeyError:
|
||||
raise cmdexc.NoSuchCommandError(f'{cmdstr}: no such command')
|
||||
|
||||
args = self._split_args(cmd, argstr, keep)
|
||||
if keep and args:
|
||||
cmdline = [cmdstr, sep + args[0]] + args[1:]
|
||||
elif keep:
|
||||
cmdline = [cmdstr, sep]
|
||||
else:
|
||||
cmdline = [cmdstr] + args[:]
|
||||
|
||||
return ParseResult(cmd=cmd, args=args, cmdline=cmdline)
|
||||
|
||||
def _completion_match(self, cmdstr: str) -> str:
|
||||
"""Replace cmdstr with a matching completion if there's only one match.
|
||||
|
||||
Args:
|
||||
cmdstr: The string representing the entered command so far.
|
||||
|
||||
Return:
|
||||
cmdstr modified to the matching completion or unmodified
|
||||
"""
|
||||
matches = [cmd for cmd in sorted(objects.commands, key=len)
|
||||
if cmdstr in cmd]
|
||||
if len(matches) == 1:
|
||||
cmdstr = matches[0]
|
||||
elif len(matches) > 1 and config.val.completion.use_best_match:
|
||||
cmdstr = matches[0]
|
||||
return cmdstr
|
||||
|
||||
def _split_args(self, cmd: command.Command, argstr: str, keep: bool) -> List[str]:
|
||||
"""Split the arguments from an arg string.
|
||||
|
||||
Args:
|
||||
cmd: The command we're currently handling.
|
||||
argstr: An argument string.
|
||||
keep: Whether to keep special chars and whitespace
|
||||
|
||||
Return:
|
||||
A list containing the split strings.
|
||||
"""
|
||||
if not argstr:
|
||||
return []
|
||||
elif cmd.maxsplit is None:
|
||||
return split.split(argstr, keep=keep)
|
||||
else:
|
||||
# If split=False, we still want to split the flags, but not
|
||||
# everything after that.
|
||||
# We first split the arg string and check the index of the first
|
||||
# non-flag args, then we re-split again properly.
|
||||
# example:
|
||||
#
|
||||
# input: "--foo -v bar baz"
|
||||
# first split: ['--foo', '-v', 'bar', 'baz']
|
||||
# 0 1 2 3
|
||||
# second split: ['--foo', '-v', 'bar baz']
|
||||
# (maxsplit=2)
|
||||
split_args = split.simple_split(argstr, keep=keep)
|
||||
flag_arg_count = 0
|
||||
for i, arg in enumerate(split_args):
|
||||
arg = arg.strip()
|
||||
if arg.startswith('-'):
|
||||
if arg in cmd.flags_with_args:
|
||||
flag_arg_count += 1
|
||||
else:
|
||||
maxsplit = i + cmd.maxsplit + flag_arg_count
|
||||
return split.simple_split(argstr, keep=keep,
|
||||
maxsplit=maxsplit)
|
||||
|
||||
# If there are only flags, we got it right on the first try
|
||||
# already.
|
||||
return split_args
|
||||
|
|
@ -22,17 +22,13 @@
|
|||
import traceback
|
||||
import re
|
||||
import contextlib
|
||||
import dataclasses
|
||||
from typing import (TYPE_CHECKING, Callable, Dict, Iterator, Mapping, MutableMapping,
|
||||
List, Optional)
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Iterator, Mapping, MutableMapping
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QObject
|
||||
|
||||
from qutebrowser.api import cmdutils
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.commands import cmdexc, command
|
||||
from qutebrowser.commands import cmdexc, parser
|
||||
from qutebrowser.utils import message, objreg, qtutils, usertypes, utils
|
||||
from qutebrowser.misc import split, objects
|
||||
from qutebrowser.keyinput import macros, modeman
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -43,16 +39,6 @@ _ReplacementFunction = Callable[['tabbedbrowser.TabbedBrowser'], str]
|
|||
last_command = {}
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ParseResult:
|
||||
|
||||
"""The result of parsing a commandline."""
|
||||
|
||||
cmd: Optional[command.Command]
|
||||
args: Optional[List[str]]
|
||||
cmdline: List[str]
|
||||
|
||||
|
||||
def _url(tabbed_browser):
|
||||
"""Convenience method to get the current url."""
|
||||
try:
|
||||
|
|
@ -130,181 +116,6 @@ def replace_variables(win_id, arglist):
|
|||
return args
|
||||
|
||||
|
||||
class CommandParser:
|
||||
|
||||
"""Parse qutebrowser commandline commands.
|
||||
|
||||
Attributes:
|
||||
_partial_match: Whether to allow partial command matches.
|
||||
"""
|
||||
|
||||
def __init__(self, partial_match=False):
|
||||
self._partial_match = partial_match
|
||||
|
||||
def _get_alias(self, text, default=None):
|
||||
"""Get an alias from the config.
|
||||
|
||||
Args:
|
||||
text: The text to parse.
|
||||
default : Default value to return when alias was not found.
|
||||
|
||||
Return:
|
||||
The new command string if an alias was found. Default value
|
||||
otherwise.
|
||||
"""
|
||||
parts = text.strip().split(maxsplit=1)
|
||||
aliases = config.cache['aliases']
|
||||
if parts[0] not in aliases:
|
||||
return default
|
||||
alias = aliases[parts[0]]
|
||||
|
||||
try:
|
||||
new_cmd = '{} {}'.format(alias, parts[1])
|
||||
except IndexError:
|
||||
new_cmd = alias
|
||||
if text.endswith(' '):
|
||||
new_cmd += ' '
|
||||
return new_cmd
|
||||
|
||||
def _parse_all_gen(self, text, *args, aliases=True, **kwargs):
|
||||
"""Split a command on ;; and parse all parts.
|
||||
|
||||
If the first command in the commandline is a non-split one, it only
|
||||
returns that.
|
||||
|
||||
Args:
|
||||
text: Text to parse.
|
||||
aliases: Whether to handle aliases.
|
||||
*args/**kwargs: Passed to parse().
|
||||
|
||||
Yields:
|
||||
ParseResult tuples.
|
||||
"""
|
||||
text = text.strip().lstrip(':').strip()
|
||||
if not text:
|
||||
raise cmdexc.NoSuchCommandError("No command given")
|
||||
|
||||
if aliases:
|
||||
text = self._get_alias(text, text)
|
||||
|
||||
if ';;' in text:
|
||||
# Get the first command and check if it doesn't want to have ;;
|
||||
# split.
|
||||
first = text.split(';;')[0]
|
||||
result = self.parse(first, *args, **kwargs)
|
||||
if result.cmd.no_cmd_split:
|
||||
sub_texts = [text]
|
||||
else:
|
||||
sub_texts = [e.strip() for e in text.split(';;')]
|
||||
else:
|
||||
sub_texts = [text]
|
||||
for sub in sub_texts:
|
||||
yield self.parse(sub, *args, **kwargs)
|
||||
|
||||
def parse_all(self, *args, **kwargs):
|
||||
"""Wrapper over _parse_all_gen."""
|
||||
return list(self._parse_all_gen(*args, **kwargs))
|
||||
|
||||
def parse(self, text, *, fallback=False, keep=False):
|
||||
"""Split the commandline text into command and arguments.
|
||||
|
||||
Args:
|
||||
text: Text to parse.
|
||||
fallback: Whether to do a fallback splitting when the command was
|
||||
unknown.
|
||||
keep: Whether to keep special chars and whitespace
|
||||
|
||||
Return:
|
||||
A ParseResult tuple.
|
||||
"""
|
||||
cmdstr, sep, argstr = text.partition(' ')
|
||||
|
||||
if not cmdstr and not fallback:
|
||||
raise cmdexc.NoSuchCommandError("No command given")
|
||||
|
||||
if self._partial_match:
|
||||
cmdstr = self._completion_match(cmdstr)
|
||||
|
||||
try:
|
||||
cmd = objects.commands[cmdstr]
|
||||
except KeyError:
|
||||
if not fallback:
|
||||
raise cmdexc.NoSuchCommandError(
|
||||
'{}: no such command'.format(cmdstr))
|
||||
cmdline = split.split(text, keep=keep)
|
||||
return ParseResult(cmd=None, args=None, cmdline=cmdline)
|
||||
|
||||
args = self._split_args(cmd, argstr, keep)
|
||||
if keep and args:
|
||||
cmdline = [cmdstr, sep + args[0]] + args[1:]
|
||||
elif keep:
|
||||
cmdline = [cmdstr, sep]
|
||||
else:
|
||||
cmdline = [cmdstr] + args[:]
|
||||
|
||||
return ParseResult(cmd=cmd, args=args, cmdline=cmdline)
|
||||
|
||||
def _completion_match(self, cmdstr):
|
||||
"""Replace cmdstr with a matching completion if there's only one match.
|
||||
|
||||
Args:
|
||||
cmdstr: The string representing the entered command so far
|
||||
|
||||
Return:
|
||||
cmdstr modified to the matching completion or unmodified
|
||||
"""
|
||||
matches = [cmd for cmd in sorted(objects.commands, key=len)
|
||||
if cmdstr in cmd]
|
||||
if len(matches) == 1:
|
||||
cmdstr = matches[0]
|
||||
elif len(matches) > 1 and config.val.completion.use_best_match:
|
||||
cmdstr = matches[0]
|
||||
return cmdstr
|
||||
|
||||
def _split_args(self, cmd, argstr, keep):
|
||||
"""Split the arguments from an arg string.
|
||||
|
||||
Args:
|
||||
cmd: The command we're currently handling.
|
||||
argstr: An argument string.
|
||||
keep: Whether to keep special chars and whitespace
|
||||
|
||||
Return:
|
||||
A list containing the split strings.
|
||||
"""
|
||||
if not argstr:
|
||||
return []
|
||||
elif cmd.maxsplit is None:
|
||||
return split.split(argstr, keep=keep)
|
||||
else:
|
||||
# If split=False, we still want to split the flags, but not
|
||||
# everything after that.
|
||||
# We first split the arg string and check the index of the first
|
||||
# non-flag args, then we re-split again properly.
|
||||
# example:
|
||||
#
|
||||
# input: "--foo -v bar baz"
|
||||
# first split: ['--foo', '-v', 'bar', 'baz']
|
||||
# 0 1 2 3
|
||||
# second split: ['--foo', '-v', 'bar baz']
|
||||
# (maxsplit=2)
|
||||
split_args = split.simple_split(argstr, keep=keep)
|
||||
flag_arg_count = 0
|
||||
for i, arg in enumerate(split_args):
|
||||
arg = arg.strip()
|
||||
if arg.startswith('-'):
|
||||
if arg in cmd.flags_with_args:
|
||||
flag_arg_count += 1
|
||||
else:
|
||||
maxsplit = i + cmd.maxsplit + flag_arg_count
|
||||
return split.simple_split(argstr, keep=keep,
|
||||
maxsplit=maxsplit)
|
||||
|
||||
# If there are only flags, we got it right on the first try
|
||||
# already.
|
||||
return split_args
|
||||
|
||||
|
||||
class AbstractCommandRunner(QObject):
|
||||
|
||||
"""Abstract base class for CommandRunner."""
|
||||
|
|
@ -329,7 +140,7 @@ class CommandRunner(AbstractCommandRunner):
|
|||
|
||||
def __init__(self, win_id, partial_match=False, parent=None):
|
||||
super().__init__(parent)
|
||||
self._parser = CommandParser(partial_match=partial_match)
|
||||
self._parser = parser.CommandParser(partial_match=partial_match)
|
||||
self._win_id = win_id
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
@ -362,7 +173,7 @@ class CommandRunner(AbstractCommandRunner):
|
|||
parsed = self._parser.parse_all(text)
|
||||
|
||||
if parsed is None:
|
||||
return
|
||||
return # type: ignore[unreachable]
|
||||
|
||||
for result in parsed:
|
||||
with self._handle_error(safely):
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ from typing import TYPE_CHECKING
|
|||
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.commands import runners
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.commands import parser, cmdexc
|
||||
from qutebrowser.misc import objects, split
|
||||
from qutebrowser.utils import log, utils, debug, objreg
|
||||
from qutebrowser.completion.models import miscmodels
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -139,13 +139,18 @@ class Completer(QObject):
|
|||
if not text or not text.strip():
|
||||
# Only ":", empty part under the cursor with nothing before/after
|
||||
return [], '', []
|
||||
parser = runners.CommandParser()
|
||||
result = parser.parse(text, fallback=True, keep=True)
|
||||
parts = [x for x in result.cmdline if x]
|
||||
|
||||
try:
|
||||
parse_result = parser.CommandParser().parse(text, keep=True)
|
||||
except cmdexc.NoSuchCommandError:
|
||||
cmdline = split.split(text, keep=True)
|
||||
else:
|
||||
cmdline = parse_result.cmdline
|
||||
|
||||
parts = [x for x in cmdline if x]
|
||||
pos = self._cmd.cursorPosition() - len(self._cmd.prefix())
|
||||
pos = min(pos, len(text)) # Qt treats 2-byte UTF-16 chars as 2 chars
|
||||
log.completion.debug('partitioning {} around position {}'.format(parts,
|
||||
pos))
|
||||
log.completion.debug(f'partitioning {parts} around position {pos}')
|
||||
for i, part in enumerate(parts):
|
||||
pos -= len(part)
|
||||
if pos <= 0:
|
||||
|
|
@ -156,11 +161,10 @@ class Completer(QObject):
|
|||
center = parts[i].strip()
|
||||
# strip trailing whitespace included as a separate token
|
||||
postfix = [x.strip() for x in parts[i+1:] if not x.isspace()]
|
||||
log.completion.debug(
|
||||
"partitioned: {} '{}' {}".format(prefix, center, postfix))
|
||||
log.completion.debug(f"partitioned: {prefix} '{center}' {postfix}")
|
||||
return prefix, center, postfix
|
||||
|
||||
raise utils.Unreachable("Not all parts consumed: {}".format(parts))
|
||||
raise utils.Unreachable(f"Not all parts consumed: {parts}")
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_selection_changed(self, text):
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
from qutebrowser.config import configdata, configexc
|
||||
from qutebrowser.completion.models import completionmodel, listcategory, util
|
||||
from qutebrowser.commands import runners, cmdexc
|
||||
from qutebrowser.commands import parser, cmdexc
|
||||
from qutebrowser.keyinput import keyutils
|
||||
|
||||
|
||||
|
|
@ -117,9 +117,8 @@ def _bind_current_default(key, info):
|
|||
|
||||
cmd_text = info.keyconf.get_command(seq, 'normal')
|
||||
if cmd_text:
|
||||
parser = runners.CommandParser()
|
||||
try:
|
||||
cmd = parser.parse(cmd_text).cmd
|
||||
cmd = parser.CommandParser().parse(cmd_text).cmd
|
||||
except cmdexc.NoSuchCommandError:
|
||||
data.append((cmd_text, '(Current) Invalid command!', key))
|
||||
else:
|
||||
|
|
@ -127,8 +126,7 @@ def _bind_current_default(key, info):
|
|||
|
||||
cmd_text = info.keyconf.get_command(seq, 'normal', default=True)
|
||||
if cmd_text:
|
||||
parser = runners.CommandParser()
|
||||
cmd = parser.parse(cmd_text).cmd
|
||||
cmd = parser.CommandParser().parse(cmd_text).cmd
|
||||
data.append((cmd_text, '(Default) {}'.format(cmd.desc), key))
|
||||
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -203,7 +203,13 @@ class BraveAdBlocker:
|
|||
|
||||
def read_cache(self) -> None:
|
||||
"""Initialize the adblocking engine from cache file."""
|
||||
if self._cache_path.is_file():
|
||||
try:
|
||||
cache_exists = self._cache_path.is_file()
|
||||
except OSError:
|
||||
logger.error("Failed to read adblock cache", exc_info=True)
|
||||
return
|
||||
|
||||
if cache_exists:
|
||||
logger.debug("Loading cached adblock data: %s", self._cache_path)
|
||||
self._engine.deserialize_from_file(str(self._cache_path))
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ def move_to_end_of_document(tab: apitypes.Tab) -> None:
|
|||
tab.caret.move_to_end_of_document()
|
||||
|
||||
|
||||
@cmdutils.register(modes=[cmdutils.KeyMode.caret], deprecated_name='toggle-selection')
|
||||
@cmdutils.register(modes=[cmdutils.KeyMode.caret])
|
||||
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
|
||||
def selection_toggle(tab: apitypes.Tab, line: bool = False) -> None:
|
||||
"""Toggle caret selection mode.
|
||||
|
|
@ -194,14 +194,14 @@ def selection_toggle(tab: apitypes.Tab, line: bool = False) -> None:
|
|||
tab.caret.toggle_selection(line)
|
||||
|
||||
|
||||
@cmdutils.register(modes=[cmdutils.KeyMode.caret], deprecated_name='drop-selection')
|
||||
@cmdutils.register(modes=[cmdutils.KeyMode.caret])
|
||||
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
|
||||
def selection_drop(tab: apitypes.Tab) -> None:
|
||||
"""Drop selection and keep selection mode enabled."""
|
||||
tab.caret.drop_selection()
|
||||
|
||||
|
||||
@cmdutils.register(deprecated_name='follow-selected')
|
||||
@cmdutils.register()
|
||||
@cmdutils.argument('tab_obj', value=cmdutils.Value.cur_tab)
|
||||
def selection_follow(tab_obj: apitypes.Tab, *, tab: bool = False) -> None:
|
||||
"""Follow the selected text.
|
||||
|
|
@ -215,7 +215,7 @@ def selection_follow(tab_obj: apitypes.Tab, *, tab: bool = False) -> None:
|
|||
raise cmdutils.CommandError(str(e))
|
||||
|
||||
|
||||
@cmdutils.register(modes=[cmdutils.KeyMode.caret], deprecated_name='reverse-selection')
|
||||
@cmdutils.register(modes=[cmdutils.KeyMode.caret])
|
||||
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
|
||||
def selection_reverse(tab: apitypes.Tab) -> None:
|
||||
"""Swap the stationary and moving end of the current selection."""
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Mapping,
|
|||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||
|
||||
from qutebrowser.commands import cmdexc, parser
|
||||
from qutebrowser.config import configdata, configexc, configutils
|
||||
from qutebrowser.utils import utils, log, urlmatch
|
||||
from qutebrowser.misc import objects
|
||||
|
|
@ -162,13 +163,38 @@ class KeyConfig:
|
|||
bindings[key] = binding
|
||||
return bindings
|
||||
|
||||
def _implied_cmd(self, cmdline: str) -> Optional[str]:
|
||||
"""Return cmdline, or the implied cmd if cmdline is a set-cmd-text."""
|
||||
try:
|
||||
results = parser.CommandParser().parse_all(cmdline)
|
||||
except cmdexc.NoSuchCommandError:
|
||||
return None
|
||||
|
||||
result = results[0]
|
||||
if result.cmd.name != "set-cmd-text":
|
||||
return cmdline
|
||||
*flags, cmd = result.args
|
||||
if "-a" in flags or "--append" in flags or not cmd.startswith(":"):
|
||||
return None # doesn't look like this sets a command
|
||||
return cmd.lstrip(":")
|
||||
|
||||
def get_reverse_bindings_for(self, mode: str) -> '_ReverseBindings':
|
||||
"""Get a dict of commands to a list of bindings for the mode."""
|
||||
"""Get a dict of commands to a list of bindings for the mode.
|
||||
|
||||
This is intented for user-facing display of keybindings.
|
||||
As such, bindings for 'set-cmd-text [flags] :<cmd> ...' are translated
|
||||
to '<cmd> ...', as from the user's perspective these keys behave like
|
||||
bindings for '<cmd>' (that allow for further input before running).
|
||||
|
||||
See #5942.
|
||||
"""
|
||||
cmd_to_keys: KeyConfig._ReverseBindings = {}
|
||||
bindings = self.get_bindings_for(mode)
|
||||
for seq, full_cmd in sorted(bindings.items()):
|
||||
for cmd in full_cmd.split(';;'):
|
||||
cmd = cmd.strip()
|
||||
for cmdtext in full_cmd.split(';;'):
|
||||
cmd = self._implied_cmd(cmdtext.strip())
|
||||
if not cmd:
|
||||
continue
|
||||
cmd_to_keys.setdefault(cmd, [])
|
||||
# Put bindings involving modifiers last
|
||||
if any(info.modifiers for info in seq):
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import functools
|
|||
import dataclasses
|
||||
|
||||
from qutebrowser.config import configtypes
|
||||
from qutebrowser.utils import usertypes, qtutils, utils
|
||||
from qutebrowser.utils import usertypes, qtutils, utils, resources
|
||||
from qutebrowser.misc import debugcachestats
|
||||
|
||||
DATA = cast(Mapping[str, 'Option'], None)
|
||||
|
|
@ -272,4 +272,4 @@ def is_valid_prefix(prefix: str) -> bool:
|
|||
def init() -> None:
|
||||
"""Initialize configdata from the YAML file."""
|
||||
global DATA, MIGRATIONS
|
||||
DATA, MIGRATIONS = _read_yaml(utils.read_file('config/configdata.yml'))
|
||||
DATA, MIGRATIONS = _read_yaml(resources.read_file('config/configdata.yml'))
|
||||
|
|
|
|||
|
|
@ -311,6 +311,20 @@ qt.workarounds.remove_service_workers:
|
|||
Note however that enabling this option *can lead to data loss* on some pages (as
|
||||
Service Worker data isn't persisted) and will negatively impact start-up time.
|
||||
|
||||
qt.workarounds.locale:
|
||||
type: Bool
|
||||
default: false
|
||||
backend: QtWebEngine
|
||||
desc: >-
|
||||
Work around locale parsing issues in QtWebEngine 5.15.3.
|
||||
|
||||
With some locales, QtWebEngine 5.15.3 is unusable without this workaround.
|
||||
In affected scenarios, QtWebEngine will log "Network service crashed,
|
||||
restarting service." and only display a blank page.
|
||||
|
||||
However, It is expected that distributions shipping QtWebEngine 5.15.3
|
||||
follow up with a proper fix soon, so it is disabled by default.
|
||||
|
||||
## auto_save
|
||||
|
||||
auto_save.interval:
|
||||
|
|
@ -607,14 +621,14 @@ content.headers.user_agent:
|
|||
# Vim-protip: Place your cursor below this comment and run
|
||||
# :r!python scripts/dev/ua_fetch.py
|
||||
- - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like
|
||||
Gecko) Chrome/87.0.4280.66 Safari/537.36"
|
||||
- Chrome 87 Linux
|
||||
Gecko) Chrome/88.0.4324.96 Safari/537.36"
|
||||
- Chrome 88 Linux
|
||||
- - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
|
||||
like Gecko) Chrome/87.0.4280.66 Safari/537.36"
|
||||
- Chrome 87 Win10
|
||||
like Gecko) Chrome/88.0.4324.104 Safari/537.36"
|
||||
- Chrome 88 Win10
|
||||
- - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
|
||||
(KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36"
|
||||
- Chrome 87 macOS
|
||||
(KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36"
|
||||
- Chrome 88 macOS
|
||||
supports_pattern: true
|
||||
desc: |
|
||||
User agent to send.
|
||||
|
|
@ -939,11 +953,22 @@ content.register_protocol_handler:
|
|||
desc: Allow websites to register protocol handlers via
|
||||
`navigator.registerProtocolHandler`.
|
||||
|
||||
content.ssl_strict:
|
||||
content.tls.certificate_errors:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
- ask: Ask how to proceed for every certificate error (unless non-overridable due
|
||||
to HSTS).
|
||||
- ask-block-thirdparty: Ask how to proceed for normal page loads, but silently
|
||||
block resource loads.
|
||||
- block: Automatically block loading on certificate errors.
|
||||
- load-insecurely: Force loading pages despite certificate errors. This is
|
||||
*insecure* and should be avoided. Instead of using this, consider fixing the
|
||||
underlying issue or importing a self-signed certificate via `certutil` (or
|
||||
Chromium) instead.
|
||||
supports_pattern: true
|
||||
desc: Validate SSL handshakes.
|
||||
desc: How to proceed on TLS certificate errors.
|
||||
|
||||
content.user_stylesheets:
|
||||
type:
|
||||
|
|
@ -1617,6 +1642,18 @@ input.spatial_navigation:
|
|||
Right key, heuristics determine whether there is an element he might be
|
||||
trying to reach towards the right and which element he probably wants.
|
||||
|
||||
input.media_keys:
|
||||
default: true
|
||||
type: Bool
|
||||
backend:
|
||||
QtWebEngine: Qt 5.14
|
||||
QtWebKit: false
|
||||
restart: true
|
||||
desc: >-
|
||||
Whether the underlying Chromium should handle media keys.
|
||||
|
||||
On Linux, disabling this also disables Chromium's MPRIS integration.
|
||||
|
||||
## keyhint
|
||||
|
||||
keyhint.blacklist:
|
||||
|
|
@ -3236,13 +3273,20 @@ bindings.key_mappings:
|
|||
keytype: Key
|
||||
valtype: Key
|
||||
desc: >-
|
||||
This setting can be used to map keys to other keys.
|
||||
Map keys to other keys, so that they are equivalent in all modes.
|
||||
|
||||
When the key used as dictionary-key is pressed, the binding for the key
|
||||
used as dictionary-value is invoked instead.
|
||||
|
||||
This is useful for global remappings of keys, for example to map Ctrl-[ to
|
||||
Escape.
|
||||
This is useful for global remappings of keys, for example to map <Ctrl-[> to
|
||||
<Escape>.
|
||||
|
||||
NOTE: This should only be used if two keys should always be equivalent, i.e. for
|
||||
things like <Enter> (keypad) and <Return> (non-keypad). For normal command bindings,
|
||||
qutebrowser works differently to vim: You always bind keys to commands, usually via
|
||||
`:bind` or `config.bind()`. Instead of using this setting, consider finding the
|
||||
command a key is bound to (e.g. via `:bind gg`) and then binding the same command to
|
||||
the desired key.
|
||||
|
||||
Note that when a key is bound (via `bindings.default` or
|
||||
`bindings.commands`), the mapping is ignored.
|
||||
|
|
|
|||
|
|
@ -128,22 +128,21 @@ class StateConfig(configparser.ConfigParser):
|
|||
# https://github.com/python/typeshed/issues/2093
|
||||
return # type: ignore[unreachable]
|
||||
|
||||
old_version = utils.parse_version(old_qutebrowser_version)
|
||||
new_version = utils.parse_version(qutebrowser.__version__)
|
||||
|
||||
if old_version.isNull():
|
||||
try:
|
||||
old_version = utils.VersionNumber.parse(old_qutebrowser_version)
|
||||
except ValueError:
|
||||
log.init.warning(f"Unable to parse old version {old_qutebrowser_version}")
|
||||
return
|
||||
|
||||
assert not new_version.isNull(), qutebrowser.__version__
|
||||
new_version = utils.VersionNumber.parse(qutebrowser.__version__)
|
||||
|
||||
if old_version == new_version:
|
||||
self.qutebrowser_version_changed = VersionChange.equal
|
||||
elif new_version < old_version:
|
||||
self.qutebrowser_version_changed = VersionChange.downgrade
|
||||
elif old_version.segments()[:2] == new_version.segments()[:2]:
|
||||
elif old_version.segments[:2] == new_version.segments[:2]:
|
||||
self.qutebrowser_version_changed = VersionChange.patch
|
||||
elif old_version.majorVersion() == new_version.majorVersion():
|
||||
elif old_version.major == new_version.major:
|
||||
self.qutebrowser_version_changed = VersionChange.minor
|
||||
else:
|
||||
self.qutebrowser_version_changed = VersionChange.major
|
||||
|
|
@ -400,6 +399,13 @@ class YamlMigrations(QObject):
|
|||
new_name='statusbar.show',
|
||||
true_value='never',
|
||||
false_value='always')
|
||||
self._migrate_renamed_bool(
|
||||
old_name='content.ssl_strict',
|
||||
new_name='content.tls.certificate_errors',
|
||||
true_value='block',
|
||||
false_value='load-insecurely',
|
||||
ask_value='ask',
|
||||
)
|
||||
|
||||
for setting in ['colors.webpage.force_dark_color_scheme',
|
||||
'colors.webpage.prefers_color_scheme_dark']:
|
||||
|
|
@ -519,14 +525,21 @@ class YamlMigrations(QObject):
|
|||
def _migrate_renamed_bool(self, old_name: str,
|
||||
new_name: str,
|
||||
true_value: str,
|
||||
false_value: str) -> None:
|
||||
false_value: str,
|
||||
ask_value: str = None) -> None:
|
||||
if old_name not in self._settings:
|
||||
return
|
||||
|
||||
self._settings[new_name] = {}
|
||||
|
||||
for scope, val in self._settings[old_name].items():
|
||||
new_value = true_value if val else false_value
|
||||
if val == 'ask':
|
||||
assert ask_value is not None
|
||||
new_value = ask_value
|
||||
elif val:
|
||||
new_value = true_value
|
||||
else:
|
||||
new_value = false_value
|
||||
self._settings[new_name][scope] = new_value
|
||||
|
||||
del self._settings[old_name]
|
||||
|
|
|
|||
|
|
@ -22,8 +22,11 @@
|
|||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import pathlib
|
||||
from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple
|
||||
|
||||
from PyQt5.QtCore import QLibraryInfo, QLocale
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.utils import usertypes, qtutils, utils, log, version
|
||||
|
|
@ -154,9 +157,77 @@ def _qtwebengine_features(
|
|||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-89740
|
||||
disabled_features.append('InstalledApp')
|
||||
|
||||
if not config.val.input.media_keys:
|
||||
disabled_features.append('HardwareMediaKeyHandling')
|
||||
|
||||
return (enabled_features, disabled_features)
|
||||
|
||||
|
||||
def _get_locale_pak_path(locales_path: pathlib.Path, locale_name: str) -> pathlib.Path:
|
||||
"""Get the path for a locale .pak file."""
|
||||
return locales_path / (locale_name + '.pak')
|
||||
|
||||
|
||||
def _get_pak_name(locale_name: str) -> str:
|
||||
"""Get the Chromium .pak name for a locale name.
|
||||
|
||||
Based on Chromium's behavior in l10n_util::CheckAndResolveLocale:
|
||||
https://source.chromium.org/chromium/chromium/src/+/master:ui/base/l10n/l10n_util.cc;l=344-428;drc=43d5378f7f363dab9271ca37774c71176c9e7b69
|
||||
"""
|
||||
if locale_name in {'en', 'en-PH', 'en-LR'}:
|
||||
return 'en-US'
|
||||
elif locale_name.startswith('en-'):
|
||||
return 'en-GB'
|
||||
elif locale_name.startswith('es-'):
|
||||
return 'es-419'
|
||||
elif locale_name == 'pt':
|
||||
return 'pt-BR'
|
||||
elif locale_name.startswith('pt-'): # pragma: no cover
|
||||
return 'pt-PT'
|
||||
elif locale_name in {'zh-HK', 'zh-MO'}:
|
||||
return 'zh-TW'
|
||||
elif locale_name == 'zh' or locale_name.startswith('zh-'):
|
||||
return 'zh-CN'
|
||||
|
||||
return locale_name.split('-')[0]
|
||||
|
||||
|
||||
def _get_lang_override(
|
||||
webengine_version: utils.VersionNumber,
|
||||
locale_name: str
|
||||
) -> Optional[str]:
|
||||
"""Get a --lang switch to override Qt's locale handling.
|
||||
|
||||
This is needed as a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-91715
|
||||
There is no fix yet, but we assume it'll be fixed with QtWebEngine 5.15.4.
|
||||
"""
|
||||
if not config.val.qt.workarounds.locale:
|
||||
return None
|
||||
|
||||
if webengine_version != utils.VersionNumber(5, 15, 3) or not utils.is_linux:
|
||||
return None
|
||||
|
||||
locales_path = pathlib.Path(
|
||||
QLibraryInfo.location(QLibraryInfo.TranslationsPath)) / 'qtwebengine_locales'
|
||||
if not locales_path.exists():
|
||||
log.init.debug(f"{locales_path} not found, skipping workaround!")
|
||||
return None
|
||||
|
||||
pak_path = _get_locale_pak_path(locales_path, locale_name)
|
||||
if pak_path.exists():
|
||||
log.init.debug(f"Found {pak_path}, skipping workaround")
|
||||
return None
|
||||
|
||||
pak_name = _get_pak_name(locale_name)
|
||||
pak_path = _get_locale_pak_path(locales_path, pak_name)
|
||||
if pak_path.exists():
|
||||
log.init.debug(f"Found {pak_path}, applying workaround")
|
||||
return pak_name
|
||||
|
||||
log.init.debug(f"Can't find pak in {locales_path} for {locale_name} or {pak_name}")
|
||||
return 'en-US'
|
||||
|
||||
|
||||
def _qtwebengine_args(
|
||||
namespace: argparse.Namespace,
|
||||
special_flags: Sequence[str],
|
||||
|
|
@ -183,6 +254,13 @@ def _qtwebengine_args(
|
|||
if 'stack' not in namespace.debug_flags:
|
||||
yield '--disable-in-process-stack-traces'
|
||||
|
||||
lang_override = _get_lang_override(
|
||||
webengine_version=versions.webengine,
|
||||
locale_name=QLocale().bcp47Name(),
|
||||
)
|
||||
if lang_override is not None:
|
||||
yield f'--lang={lang_override}'
|
||||
|
||||
if 'chromium' in namespace.debug_flags:
|
||||
yield '--enable-logging'
|
||||
yield '--v=1'
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ qute://warning/sessions</span> to show it again at a later time.</span>
|
|||
|
||||
<p>Since Qt doesn't provide an API to load the history of a tab, qutebrowser relies on a reverse-engineered binary serialization format to load tab history from session files. With Qt 5.15, unfortunately that format changed (due to the underlying Chromium upgrade), in a way which makes it impossible for qutebrowser to load tab history from existing session data.</p>
|
||||
|
||||
<p>At the time of writing (January 2021), a new session format which stores part of the needed binary data in saved sessions is <a href="https://github.com/qutebrowser/qutebrowser/issues/5359">in development</a>. However, it unfortunately wasn't ready in time for qutebrowser v2.0.0, as it's a rather big refactoring. It's currently expected to be released as part of qutebrowser v2.1.0.</p>
|
||||
<p>At the time of writing (January 2021), a new session format which stores part of the needed binary data in saved sessions is <a href="https://github.com/qutebrowser/qutebrowser/issues/5359">in development</a>. However, it unfortunately wasn't ready in time for qutebrowser v2.0.0, as it's a rather big refactoring. It's currently expected to be released in a future v2.x.0 release.</p>
|
||||
|
||||
<p>As a stop-gap measure:</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -648,10 +648,9 @@ class KeySequence:
|
|||
for key in self._iter_keys():
|
||||
key_seq = KeySequence(key)
|
||||
if key_seq in mappings:
|
||||
new_seq = mappings[key_seq]
|
||||
assert len(new_seq) == 1
|
||||
key = new_seq[0].to_int()
|
||||
keys.append(key)
|
||||
keys += [info.to_int() for info in mappings[key_seq]]
|
||||
else:
|
||||
keys.append(key)
|
||||
return self.__class__(*keys)
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class MacroRecorder:
|
|||
self._macro_count: Dict[int, int] = {}
|
||||
self._last_register: Optional[str] = None
|
||||
|
||||
@cmdutils.register(instance='macro-recorder', deprecated_name='record-macro')
|
||||
@cmdutils.register(instance='macro-recorder')
|
||||
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
|
||||
def macro_record(self, win_id: int, register: str = None) -> None:
|
||||
"""Start or stop recording a macro.
|
||||
|
|
@ -77,7 +77,7 @@ class MacroRecorder:
|
|||
self._macros[register] = []
|
||||
self._recording_macro = register
|
||||
|
||||
@cmdutils.register(instance='macro-recorder', deprecated_name='run-macro')
|
||||
@cmdutils.register(instance='macro-recorder')
|
||||
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
|
||||
@cmdutils.argument('count', value=cmdutils.Value.count)
|
||||
def macro_run(self, win_id: int, count: int = 1, register: str = None) -> None:
|
||||
|
|
|
|||
|
|
@ -86,9 +86,10 @@ def init(win_id: int, parent: QObject) -> 'ModeManager':
|
|||
hintmanager = hints.HintManager(win_id, parent=parent)
|
||||
objreg.register('hintmanager', hintmanager, scope='window',
|
||||
window=win_id, command_only=True)
|
||||
|
||||
modeman.hintmanager = hintmanager
|
||||
|
||||
log_sensitive_keys = 'log-sensitive-keys' in objects.debug_flags
|
||||
|
||||
keyparsers: ParserDictType = {
|
||||
usertypes.KeyMode.normal:
|
||||
modeparsers.NormalKeyParser(
|
||||
|
|
@ -110,7 +111,7 @@ def init(win_id: int, parent: QObject) -> 'ModeManager':
|
|||
commandrunner=commandrunner,
|
||||
parent=modeman,
|
||||
passthrough=True,
|
||||
do_log=False,
|
||||
do_log=log_sensitive_keys,
|
||||
supports_count=False),
|
||||
|
||||
usertypes.KeyMode.passthrough:
|
||||
|
|
@ -120,7 +121,7 @@ def init(win_id: int, parent: QObject) -> 'ModeManager':
|
|||
commandrunner=commandrunner,
|
||||
parent=modeman,
|
||||
passthrough=True,
|
||||
do_log=False,
|
||||
do_log=log_sensitive_keys,
|
||||
supports_count=False),
|
||||
|
||||
usertypes.KeyMode.command:
|
||||
|
|
@ -130,7 +131,7 @@ def init(win_id: int, parent: QObject) -> 'ModeManager':
|
|||
commandrunner=commandrunner,
|
||||
parent=modeman,
|
||||
passthrough=True,
|
||||
do_log=False,
|
||||
do_log=log_sensitive_keys,
|
||||
supports_count=False),
|
||||
|
||||
usertypes.KeyMode.prompt:
|
||||
|
|
@ -140,7 +141,7 @@ def init(win_id: int, parent: QObject) -> 'ModeManager':
|
|||
commandrunner=commandrunner,
|
||||
parent=modeman,
|
||||
passthrough=True,
|
||||
do_log=False,
|
||||
do_log=log_sensitive_keys,
|
||||
supports_count=False),
|
||||
|
||||
usertypes.KeyMode.yesno:
|
||||
|
|
@ -385,11 +386,7 @@ class ModeManager(QObject):
|
|||
self.mode = mode
|
||||
self.entered.emit(mode, self._win_id)
|
||||
|
||||
@cmdutils.register(
|
||||
instance='mode-manager',
|
||||
scope='window',
|
||||
deprecated_name='enter-mode',
|
||||
)
|
||||
@cmdutils.register(instance='mode-manager', scope='window')
|
||||
def mode_enter(self, mode: str) -> None:
|
||||
"""Enter a key mode.
|
||||
|
||||
|
|
@ -442,12 +439,8 @@ class ModeManager(QObject):
|
|||
self.enter(self._prev_mode,
|
||||
reason='restore mode before {}'.format(mode.name))
|
||||
|
||||
@cmdutils.register(
|
||||
instance='mode-manager',
|
||||
not_modes=[usertypes.KeyMode.normal],
|
||||
scope='window',
|
||||
deprecated_name='leave-mode',
|
||||
)
|
||||
@cmdutils.register(instance='mode-manager',
|
||||
not_modes=[usertypes.KeyMode.normal], scope='window')
|
||||
def mode_leave(self) -> None:
|
||||
"""Leave the mode we're currently in."""
|
||||
if self.mode == usertypes.KeyMode.normal:
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ class NormalKeyParser(CommandKeyParser):
|
|||
_partial_timer: Timer to clear partial keypresses.
|
||||
"""
|
||||
|
||||
_sequence: keyutils.KeySequence
|
||||
|
||||
def __init__(self, *, win_id: int,
|
||||
commandrunner: 'runners.CommandRunner',
|
||||
parent: QObject = None) -> None:
|
||||
|
|
@ -154,6 +156,8 @@ class HintKeyParser(basekeyparser.BaseKeyParser):
|
|||
_last_press: The nature of the last keypress, a LastPress member.
|
||||
"""
|
||||
|
||||
_sequence: keyutils.KeySequence
|
||||
|
||||
def __init__(self, *, win_id: int,
|
||||
commandrunner: 'runners.CommandRunner',
|
||||
hintmanager: hints.HintManager,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ from qutebrowser.keyinput import modeman
|
|||
from qutebrowser.mainwindow import tabwidget, mainwindow
|
||||
from qutebrowser.browser import signalfilter, browsertab, history
|
||||
from qutebrowser.utils import (log, usertypes, utils, qtutils, objreg,
|
||||
urlutils, message, jinja)
|
||||
urlutils, message, jinja, version)
|
||||
from qutebrowser.misc import quitter
|
||||
|
||||
|
||||
|
|
@ -929,26 +929,44 @@ class TabbedBrowser(QWidget):
|
|||
return
|
||||
|
||||
messages = {
|
||||
browsertab.TerminationStatus.abnormal:
|
||||
"Renderer process exited with status {}".format(code),
|
||||
browsertab.TerminationStatus.crashed:
|
||||
"Renderer process crashed",
|
||||
browsertab.TerminationStatus.killed:
|
||||
"Renderer process was killed",
|
||||
browsertab.TerminationStatus.unknown:
|
||||
"Renderer process did not start",
|
||||
browsertab.TerminationStatus.abnormal: "Renderer process exited",
|
||||
browsertab.TerminationStatus.crashed: "Renderer process crashed",
|
||||
browsertab.TerminationStatus.killed: "Renderer process was killed",
|
||||
browsertab.TerminationStatus.unknown: "Renderer process did not start",
|
||||
}
|
||||
msg = messages[status]
|
||||
msg = messages[status] + f" (status {code})"
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-91715
|
||||
versions = version.qtwebengine_versions()
|
||||
is_qtbug_91715 = (
|
||||
status == browsertab.TerminationStatus.unknown and
|
||||
code == 1002 and
|
||||
versions.webengine == utils.VersionNumber(5, 15, 3))
|
||||
|
||||
def show_error_page(html):
|
||||
tab.set_html(html)
|
||||
log.webview.error(msg)
|
||||
|
||||
url_string = tab.url(requested=True).toDisplayString()
|
||||
error_page = jinja.render(
|
||||
'error.html', title="Error loading {}".format(url_string),
|
||||
url=url_string, error=msg)
|
||||
QTimer.singleShot(100, lambda: show_error_page(error_page))
|
||||
if is_qtbug_91715:
|
||||
log.webview.error(msg)
|
||||
log.webview.error('')
|
||||
log.webview.error(
|
||||
'NOTE: If you see this and "Network service crashed, restarting '
|
||||
'service.", please see:')
|
||||
log.webview.error('https://github.com/qutebrowser/qutebrowser/issues/6235')
|
||||
log.webview.error(
|
||||
'You can set the "qt.workarounds.locale" setting in qutebrowser to '
|
||||
'work around the issue.')
|
||||
log.webview.error(
|
||||
'A proper fix is likely available in QtWebEngine soon (which is why '
|
||||
'the workaround is disabled by default).')
|
||||
log.webview.error('')
|
||||
else:
|
||||
url_string = tab.url(requested=True).toDisplayString()
|
||||
error_page = jinja.render(
|
||||
'error.html', title="Error loading {}".format(url_string),
|
||||
url=url_string, error=msg)
|
||||
QTimer.singleShot(100, lambda: show_error_page(error_page))
|
||||
|
||||
def resizeEvent(self, e):
|
||||
"""Extend resizeEvent of QWidget to emit a resized signal afterwards.
|
||||
|
|
|
|||
|
|
@ -194,14 +194,6 @@ class _BackendProblemChecker:
|
|||
|
||||
sys.exit(usertypes.Exit.err_init)
|
||||
|
||||
def _nvidia_shader_workaround(self) -> None:
|
||||
"""Work around QOpenGLShaderProgram issues.
|
||||
|
||||
See https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
|
||||
"""
|
||||
self._assert_backend(usertypes.Backend.QtWebEngine)
|
||||
utils.libgl_workaround()
|
||||
|
||||
def _xwayland_options(self) -> Tuple[str, List[_Button]]:
|
||||
"""Get buttons/text for a possible XWayland solution."""
|
||||
buttons = []
|
||||
|
|
@ -435,7 +427,6 @@ class _BackendProblemChecker:
|
|||
self._check_backend_modules()
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
self._handle_ssl_support()
|
||||
self._nvidia_shader_workaround()
|
||||
self._handle_wayland_webgl()
|
||||
self._handle_cache_nuking()
|
||||
self._handle_serviceworker_nuking()
|
||||
|
|
|
|||
|
|
@ -359,8 +359,8 @@ class _CrashDialog(QDialog):
|
|||
Args:
|
||||
newest: The newest version as a string.
|
||||
"""
|
||||
new_version = utils.parse_version(newest)
|
||||
cur_version = utils.parse_version(qutebrowser.__version__)
|
||||
new_version = utils.VersionNumber.parse(newest)
|
||||
cur_version = utils.VersionNumber.parse(qutebrowser.__version__)
|
||||
lines = ['The report has been sent successfully. Thanks!']
|
||||
if new_version > cur_version:
|
||||
lines.append("<b>Note:</b> The newest available version is v{}, "
|
||||
|
|
|
|||
|
|
@ -185,6 +185,11 @@ def check_qt_version():
|
|||
PYQT_VERSION_STR))
|
||||
_die(text)
|
||||
|
||||
if qt_ver == QVersionNumber(5, 12, 0):
|
||||
from qutebrowser.utils import log
|
||||
log.init.warning("Running on Qt 5.12.0. Doing so is unsupported "
|
||||
"(newer 5.12.x versions are fine).")
|
||||
|
||||
|
||||
def check_ssl_support():
|
||||
"""Check if SSL support is available."""
|
||||
|
|
@ -274,6 +279,21 @@ def check_optimize_flag():
|
|||
"unexpected behavior may occur.")
|
||||
|
||||
|
||||
def webengine_early_import():
|
||||
"""If QtWebEngine is available, import it early.
|
||||
|
||||
We need to ensure that QtWebEngine is imported before a QApplication is created for
|
||||
everything to work properly.
|
||||
|
||||
This needs to be done even when using the QtWebKit backend, to ensure that e.g.
|
||||
error messages in backendproblem.py are accurate.
|
||||
"""
|
||||
try:
|
||||
from PyQt5 import QtWebEngineWidgets # pylint: disable=unused-import
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def early_init(args):
|
||||
"""Do all needed early initialization.
|
||||
|
||||
|
|
@ -298,3 +318,4 @@ def early_init(args):
|
|||
configure_pyqt()
|
||||
check_ssl_support()
|
||||
check_optimize_flag()
|
||||
webengine_early_import()
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ from typing import IO, ClassVar, Dict, Optional, Tuple, cast
|
|||
|
||||
from PyQt5.QtCore import QLibraryInfo
|
||||
|
||||
from qutebrowser.utils import log
|
||||
from qutebrowser.utils import log, version
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
|
|
@ -141,7 +141,7 @@ class Ident:
|
|||
@classmethod
|
||||
def parse(cls, fobj: IO[bytes]) -> 'Ident':
|
||||
"""Parse an ELF ident header from a file."""
|
||||
magic, klass, data, version, osabi, abiversion = _unpack(cls._FORMAT, fobj)
|
||||
magic, klass, data, elfversion, osabi, abiversion = _unpack(cls._FORMAT, fobj)
|
||||
|
||||
try:
|
||||
bitness = Bitness(klass)
|
||||
|
|
@ -153,7 +153,7 @@ class Ident:
|
|||
except ValueError:
|
||||
raise ParseError(f"Invalid endianness {data}")
|
||||
|
||||
return cls(magic, bitness, endianness, version, osabi, abiversion)
|
||||
return cls(magic, bitness, endianness, elfversion, osabi, abiversion)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
|
|
@ -310,7 +310,11 @@ def _parse_from_file(f: IO[bytes]) -> Versions:
|
|||
|
||||
def parse_webenginecore() -> Optional[Versions]:
|
||||
"""Parse the QtWebEngineCore library file."""
|
||||
library_path = pathlib.Path(QLibraryInfo.location(QLibraryInfo.LibrariesPath))
|
||||
if version.is_flatpak():
|
||||
# Flatpak has Qt in /usr/lib/x86_64-linux-gnu, but QtWebEngine in /app/lib.
|
||||
library_path = pathlib.Path("/app/lib")
|
||||
else:
|
||||
library_path = pathlib.Path(QLibraryInfo.location(QLibraryInfo.LibrariesPath))
|
||||
|
||||
# PyQt bundles those files with a .5 suffix
|
||||
lib_file = library_path / 'libQt5WebEngineCore.so.5'
|
||||
|
|
|
|||
|
|
@ -84,8 +84,27 @@ class GUIProcess(QObject):
|
|||
if error == QProcess.Crashed and not utils.is_windows:
|
||||
# Already handled via ExitStatus in _on_finished
|
||||
return
|
||||
msg = self._proc.errorString()
|
||||
message.error("Error while spawning {}: {}".format(self._what, msg))
|
||||
|
||||
what = f"{self._what} {self.cmd!r}"
|
||||
error_descriptions = {
|
||||
QProcess.FailedToStart: f"{what.capitalize()} failed to start",
|
||||
QProcess.Crashed: f"{what.capitalize()} crashed",
|
||||
QProcess.Timedout: f"{what.capitalize()} timed out",
|
||||
QProcess.WriteError: f"Write error for {what}",
|
||||
QProcess.WriteError: f"Read error for {what}",
|
||||
}
|
||||
error_string = self._proc.errorString()
|
||||
msg = ': '.join([error_descriptions[error], error_string])
|
||||
|
||||
# We can't get some kind of error code from Qt...
|
||||
# https://bugreports.qt.io/browse/QTBUG-44769
|
||||
# However, it looks like those strings aren't actually translated?
|
||||
known_errors = ['No such file or directory', 'Permission denied']
|
||||
if (': ' in error_string and # pragma: no branch
|
||||
error_string.split(': ', maxsplit=1)[1] in known_errors):
|
||||
msg += f'\n(Hint: Make sure {self.cmd!r} exists and is executable)'
|
||||
|
||||
message.error(msg)
|
||||
|
||||
@pyqtSlot(int, QProcess.ExitStatus)
|
||||
def _on_finished(self, code, status):
|
||||
|
|
|
|||
|
|
@ -351,13 +351,13 @@ class SqlTable(QObject):
|
|||
self._name = name
|
||||
self._create_table(fields, constraints)
|
||||
|
||||
def _create_table(self, fields, constraints):
|
||||
def _create_table(self, fields, constraints, *, force=False):
|
||||
"""Create the table if the database is uninitialized.
|
||||
|
||||
If the table already exists, this does nothing, so it can e.g. be called on
|
||||
every user_version change.
|
||||
If the table already exists, this does nothing (except with force=True), so it
|
||||
can e.g. be called on every user_version change.
|
||||
"""
|
||||
if not user_version_changed():
|
||||
if not user_version_changed() and not force:
|
||||
return
|
||||
|
||||
constraints = constraints or {}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ def debug_flag_error(flag):
|
|||
log-requests: Log all network requests.
|
||||
log-cookies: Log cookies in cookie filter.
|
||||
log-scroll-pos: Log all scrolling changes.
|
||||
log-sensitive-keys: Log keypresses in passthrough modes.
|
||||
stack: Enable Chromium stack logging.
|
||||
chromium: Enable Chromium logging.
|
||||
wait-renderer-process: Wait for debugger in renderer process.
|
||||
|
|
@ -181,7 +182,7 @@ def debug_flag_error(flag):
|
|||
"""
|
||||
valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history',
|
||||
'no-scroll-filtering', 'log-requests', 'log-cookies',
|
||||
'log-scroll-pos', 'stack', 'chromium',
|
||||
'log-scroll-pos', 'log-sensitive-keys', 'stack', 'chromium',
|
||||
'wait-renderer-process', 'avoid-chromium-init', 'werror']
|
||||
|
||||
if flag in valid_flags:
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import jinja2
|
|||
import jinja2.nodes
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.utils import utils, urlutils, log, qtutils
|
||||
from qutebrowser.utils import utils, urlutils, log, qtutils, resources
|
||||
from qutebrowser.misc import debugcachestats
|
||||
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ html_fallback = """
|
|||
|
||||
class Loader(jinja2.BaseLoader):
|
||||
|
||||
"""Jinja loader which uses utils.read_file to load templates.
|
||||
"""Jinja loader which uses resources.read_file to load templates.
|
||||
|
||||
Attributes:
|
||||
_subdir: The subdirectory to find templates in.
|
||||
|
|
@ -72,7 +72,7 @@ class Loader(jinja2.BaseLoader):
|
|||
) -> Tuple[str, str, Callable[[], bool]]:
|
||||
path = os.path.join(self._subdir, template)
|
||||
try:
|
||||
source = utils.read_file(path)
|
||||
source = resources.read_file(path)
|
||||
except OSError as e:
|
||||
source = html_fallback.replace("%ERROR%", html.escape(str(e)))
|
||||
source = source.replace("%FILE%", html.escape(template))
|
||||
|
|
@ -119,7 +119,7 @@ class Environment(jinja2.Environment):
|
|||
|
||||
def _data_url(self, path: str) -> str:
|
||||
"""Get a data: url for the broken qutebrowser logo."""
|
||||
data = utils.read_file_binary(path)
|
||||
data = resources.read_file_binary(path)
|
||||
mimetype = utils.guess_mimetype(path)
|
||||
return urlutils.data_url(mimetype, data).toString()
|
||||
|
||||
|
|
|
|||
|
|
@ -98,15 +98,15 @@ def version_check(version: str,
|
|||
if compiled and exact:
|
||||
raise ValueError("Can't use compiled=True with exact=True!")
|
||||
|
||||
parsed = utils.parse_version(version)
|
||||
parsed = utils.VersionNumber.parse(version)
|
||||
op = operator.eq if exact else operator.ge
|
||||
result = op(utils.parse_version(qVersion()), parsed)
|
||||
result = op(utils.VersionNumber.parse(qVersion()), parsed)
|
||||
if compiled and result:
|
||||
# qVersion() ==/>= parsed, now check if QT_VERSION_STR ==/>= parsed.
|
||||
result = op(utils.parse_version(QT_VERSION_STR), parsed)
|
||||
result = op(utils.VersionNumber.parse(QT_VERSION_STR), parsed)
|
||||
if compiled and result:
|
||||
# Finally, check PYQT_VERSION_STR as well.
|
||||
result = op(utils.parse_version(PYQT_VERSION_STR), parsed)
|
||||
result = op(utils.VersionNumber.parse(PYQT_VERSION_STR), parsed)
|
||||
return result
|
||||
|
||||
|
||||
|
|
@ -116,8 +116,8 @@ MAX_WORLD_ID = 256
|
|||
def is_new_qtwebkit() -> bool:
|
||||
"""Check if the given version is a new QtWebKit."""
|
||||
assert qWebKitVersion is not None
|
||||
return (utils.parse_version(qWebKitVersion()) >
|
||||
utils.parse_version('538.1'))
|
||||
return (utils.VersionNumber.parse(qWebKitVersion()) >
|
||||
utils.VersionNumber.parse('538.1'))
|
||||
|
||||
|
||||
def is_single_process() -> bool:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2021 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""Resources related utilities."""
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
import contextlib
|
||||
import posixpath
|
||||
import pathlib
|
||||
from typing import Iterator, Iterable
|
||||
|
||||
|
||||
# We cannot use the stdlib version on 3.7-3.8 because we need the files() API.
|
||||
if sys.version_info >= (3, 9):
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
import qutebrowser
|
||||
_cache = {}
|
||||
|
||||
def _path(filename: str) -> pathlib.Path:
|
||||
"""Get a pathlib.Path object for a resource."""
|
||||
assert not posixpath.isabs(filename), filename
|
||||
assert os.path.pardir not in filename.split(posixpath.sep), filename
|
||||
|
||||
if hasattr(sys, 'frozen'):
|
||||
# For PyInstaller, where we can't store resource files in a qutebrowser/ folder
|
||||
# because the executable is already named "qutebrowser" (at least on macOS).
|
||||
return pathlib.Path(sys.executable).parent / filename
|
||||
|
||||
return importlib_resources.files(qutebrowser) / filename
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _keyerror_workaround() -> Iterator[None]:
|
||||
"""Re-raise KeyErrors as FileNotFoundErrors.
|
||||
|
||||
WORKAROUND for zipfile.Path resources raising KeyError when a file was notfound:
|
||||
https://bugs.python.org/issue43063
|
||||
|
||||
Only needed for Python 3.8 and 3.9.
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except KeyError as e:
|
||||
raise FileNotFoundError(str(e))
|
||||
|
||||
|
||||
def _glob(
|
||||
resource_path: pathlib.Path,
|
||||
subdir: str,
|
||||
ext: str,
|
||||
) -> Iterable[str]:
|
||||
"""Find resources with the given extension.
|
||||
|
||||
Yields a resource name like "html/log.html" (as string).
|
||||
"""
|
||||
assert '*' not in ext, ext
|
||||
assert ext.startswith('.'), ext
|
||||
glob_path = resource_path / subdir
|
||||
|
||||
if isinstance(resource_path, pathlib.Path):
|
||||
for full_path in glob_path.glob(f'*{ext}'): # . is contained in ext
|
||||
yield full_path.relative_to(resource_path).as_posix()
|
||||
else: # zipfile.Path or importlib_resources compat object
|
||||
# Unfortunately, we can't tell mypy about resource_path being of type
|
||||
# Union[pathlib.Path, zipfile.Path] because we set "python_version = 3.6" in
|
||||
# .mypy.ini, but the zipfiel stubs (correctly) only declare zipfile.Path with
|
||||
# Python 3.8...
|
||||
assert glob_path.is_dir(), glob_path # type: ignore[unreachable]
|
||||
for subpath in glob_path.iterdir():
|
||||
if subpath.name.endswith(ext):
|
||||
yield posixpath.join(subdir, subpath.name)
|
||||
|
||||
|
||||
def preload() -> None:
|
||||
"""Load resource files into the cache."""
|
||||
resource_path = _path('')
|
||||
for subdir, ext in [
|
||||
('html', '.html'),
|
||||
('javascript', '.js'),
|
||||
('javascript/quirks', '.js'),
|
||||
]:
|
||||
for name in _glob(resource_path, subdir, ext):
|
||||
_cache[name] = read_file(name)
|
||||
|
||||
|
||||
def read_file(filename: str) -> str:
|
||||
"""Get the contents of a file contained with qutebrowser.
|
||||
|
||||
Args:
|
||||
filename: The filename to open as string.
|
||||
|
||||
Return:
|
||||
The file contents as string.
|
||||
"""
|
||||
if filename in _cache:
|
||||
return _cache[filename]
|
||||
|
||||
path = _path(filename)
|
||||
with _keyerror_workaround():
|
||||
return path.read_text(encoding='utf-8')
|
||||
|
||||
|
||||
def read_file_binary(filename: str) -> bytes:
|
||||
"""Get the contents of a binary file contained with qutebrowser.
|
||||
|
||||
Args:
|
||||
filename: The filename to open as string.
|
||||
|
||||
Return:
|
||||
The file contents as a bytes object.
|
||||
"""
|
||||
path = _path(filename)
|
||||
with _keyerror_workaround():
|
||||
return path.read_bytes()
|
||||
|
|
@ -30,7 +30,7 @@ from typing import Iterator, Optional
|
|||
from PyQt5.QtCore import QStandardPaths
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from qutebrowser.utils import log, debug, utils
|
||||
from qutebrowser.utils import log, debug, utils, version
|
||||
|
||||
# The cached locations
|
||||
_locations = {}
|
||||
|
|
@ -232,7 +232,16 @@ def _init_runtime(args: Optional[argparse.Namespace]) -> None:
|
|||
# Unfortunately this path could get too long for sockets (which have a
|
||||
# maximum length of 104 chars), so we don't add the username here...
|
||||
|
||||
_create(path)
|
||||
if version.is_flatpak():
|
||||
# We need a path like /run/user/1000/app/org.qutebrowser.qutebrowser rather than
|
||||
# /run/user/1000/qutebrowser on Flatpak, since that's bind-mounted in a way that
|
||||
# it is accessible by any other qutebrowser instances.
|
||||
*parts, app_name = os.path.split(path)
|
||||
assert app_name == APPNAME, app_name
|
||||
path = os.path.join(*parts, 'app', os.environ['FLATPAK_ID'])
|
||||
else:
|
||||
_create(path)
|
||||
|
||||
_locations[_Location.runtime] = path
|
||||
|
||||
|
||||
|
|
@ -314,6 +323,9 @@ def _create(path: str) -> None:
|
|||
should not be changed.
|
||||
"""
|
||||
if APPNAME == 'qute_test' and path.startswith('/home'): # pragma: no cover
|
||||
for k, v in os.environ.items():
|
||||
if k == 'HOME' or k.startswith('XDG_'):
|
||||
log.init.debug(f"{k} = {v}")
|
||||
raise Exception("Trying to create directory inside /home during "
|
||||
"tests, this should not happen.")
|
||||
os.makedirs(path, 0o700, exist_ok=True)
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@
|
|||
|
||||
"""Custom useful data types."""
|
||||
|
||||
import html
|
||||
import operator
|
||||
import enum
|
||||
import dataclasses
|
||||
from typing import Any, Optional, Sequence, TypeVar, Union
|
||||
from typing import Optional, Sequence, TypeVar, Union
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QTimer
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
|
@ -483,9 +484,6 @@ class AbstractCertificateErrorWrapper:
|
|||
|
||||
"""A wrapper over an SSL/certificate error."""
|
||||
|
||||
def __init__(self, error: Any) -> None:
|
||||
self._error = error
|
||||
|
||||
def __str__(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
|
|
@ -495,6 +493,9 @@ class AbstractCertificateErrorWrapper:
|
|||
def is_overridable(self) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
def html(self) -> str:
|
||||
return f'<p>{html.escape(str(self))}</p>'
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class NavigationRequest:
|
||||
|
|
|
|||
|
|
@ -30,14 +30,11 @@ import datetime
|
|||
import traceback
|
||||
import functools
|
||||
import contextlib
|
||||
import posixpath
|
||||
import shlex
|
||||
import mimetypes
|
||||
import pathlib
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
from typing import (Any, Callable, IO, Iterator, Optional, Sequence, Tuple, Type, Union,
|
||||
Iterable, TypeVar, TYPE_CHECKING)
|
||||
from typing import (Any, Callable, IO, Iterator,
|
||||
Optional, Sequence, Tuple, Type, Union,
|
||||
TypeVar, TYPE_CHECKING)
|
||||
try:
|
||||
# Protocol was added in Python 3.8
|
||||
from typing import Protocol
|
||||
|
|
@ -50,11 +47,7 @@ except ImportError: # pragma: no cover
|
|||
from PyQt5.QtCore import QUrl, QVersionNumber, QRect
|
||||
from PyQt5.QtGui import QClipboard, QDesktopServices
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
# We cannot use the stdlib version on 3.7-3.8 because we need the files() API.
|
||||
if sys.version_info >= (3, 9):
|
||||
import importlib.resources as importlib_resources
|
||||
else: # pragma: no cover
|
||||
import importlib_resources
|
||||
|
||||
import yaml
|
||||
try:
|
||||
from yaml import (CSafeLoader as YamlLoader,
|
||||
|
|
@ -65,13 +58,10 @@ except ImportError: # pragma: no cover
|
|||
SafeDumper as YamlDumper)
|
||||
YAML_C_EXT = False
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.utils import log
|
||||
|
||||
|
||||
fake_clipboard = None
|
||||
log_clipboard = False
|
||||
_resource_cache = {}
|
||||
|
||||
is_mac = sys.platform.startswith('darwin')
|
||||
is_linux = sys.platform.startswith('linux')
|
||||
|
|
@ -92,26 +82,74 @@ class Comparable(Protocol):
|
|||
...
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
class VersionNumber(Comparable, QVersionNumber):
|
||||
class VersionNumber:
|
||||
|
||||
"""WORKAROUND for incorrect PyQt stubs."""
|
||||
else:
|
||||
class VersionNumber(QVersionNumber):
|
||||
"""A representation of a version number."""
|
||||
|
||||
"""We can't inherit from Protocol and QVersionNumber at runtime."""
|
||||
def __init__(self, *args: int) -> None:
|
||||
self._ver = QVersionNumber(*args)
|
||||
if self._ver.isNull():
|
||||
raise ValueError("Can't construct a null version")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
normalized = self.normalized()
|
||||
if normalized != self:
|
||||
raise ValueError(
|
||||
f"Refusing to construct non-normalized version from {args} "
|
||||
f"(normalized: {tuple(normalized.segments())}).")
|
||||
normalized = self._ver.normalized()
|
||||
if normalized != self._ver:
|
||||
raise ValueError(
|
||||
f"Refusing to construct non-normalized version from {args} "
|
||||
f"(normalized: {tuple(normalized.segments())}).")
|
||||
|
||||
def __repr__(self):
|
||||
args = ", ".join(str(s) for s in self.segments())
|
||||
return f'VersionNumber({args})'
|
||||
self.major = self._ver.majorVersion()
|
||||
self.minor = self._ver.minorVersion()
|
||||
self.patch = self._ver.microVersion()
|
||||
self.segments = self._ver.segments()
|
||||
|
||||
assert len(self.segments) <= 3, self.segments
|
||||
|
||||
def __str__(self) -> str:
|
||||
return ".".join(str(s) for s in self.segments)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
args = ", ".join(str(s) for s in self.segments)
|
||||
return f'VersionNumber({args})'
|
||||
|
||||
def strip_patch(self) -> 'VersionNumber':
|
||||
"""Get a new VersionNumber with the patch version removed."""
|
||||
return VersionNumber(*self.segments[:2])
|
||||
|
||||
@classmethod
|
||||
def parse(cls, s: str) -> 'VersionNumber':
|
||||
"""Parse a version number from a string."""
|
||||
ver, _suffix = QVersionNumber.fromString(s)
|
||||
# FIXME: Should we support a suffix?
|
||||
|
||||
if ver.isNull():
|
||||
raise ValueError(f"Failed to parse {s}")
|
||||
|
||||
return cls(*ver.normalized().segments())
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._ver)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, VersionNumber):
|
||||
return NotImplemented
|
||||
return self._ver == other._ver
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
if not isinstance(other, VersionNumber):
|
||||
return NotImplemented
|
||||
return self._ver != other._ver
|
||||
|
||||
def __ge__(self, other: 'VersionNumber') -> bool:
|
||||
return self._ver >= other._ver # type: ignore[operator]
|
||||
|
||||
def __gt__(self, other: 'VersionNumber') -> bool:
|
||||
return self._ver > other._ver # type: ignore[operator]
|
||||
|
||||
def __le__(self, other: 'VersionNumber') -> bool:
|
||||
return self._ver <= other._ver # type: ignore[operator]
|
||||
|
||||
def __lt__(self, other: 'VersionNumber') -> bool:
|
||||
return self._ver < other._ver # type: ignore[operator]
|
||||
|
||||
|
||||
class Unreachable(Exception):
|
||||
|
|
@ -196,110 +234,6 @@ def compact_text(text: str, elidelength: int = None) -> str:
|
|||
return out
|
||||
|
||||
|
||||
def _resource_path(filename: str) -> pathlib.Path:
|
||||
"""Get a pathlib.Path object for a resource."""
|
||||
assert not posixpath.isabs(filename), filename
|
||||
assert os.path.pardir not in filename.split(posixpath.sep), filename
|
||||
|
||||
if hasattr(sys, 'frozen'):
|
||||
# For PyInstaller, where we can't store resource files in a qutebrowser/ folder
|
||||
# because the executable is already named "qutebrowser" (at least on macOS).
|
||||
return pathlib.Path(sys.executable).parent / filename
|
||||
|
||||
return importlib_resources.files(qutebrowser) / filename
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _resource_keyerror_workaround() -> Iterator[None]:
|
||||
"""Re-raise KeyErrors as FileNotFoundErrors.
|
||||
|
||||
WORKAROUND for zipfile.Path resources raising KeyError when a file was notfound:
|
||||
https://bugs.python.org/issue43063
|
||||
|
||||
Only needed for Python 3.8 and 3.9.
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except KeyError as e:
|
||||
raise FileNotFoundError(str(e))
|
||||
|
||||
|
||||
def _glob_resources(
|
||||
resource_path: pathlib.Path,
|
||||
subdir: str,
|
||||
ext: str,
|
||||
) -> Iterable[str]:
|
||||
"""Find resources with the given extension.
|
||||
|
||||
Yields a resource name like "html/log.html" (as string).
|
||||
"""
|
||||
assert '*' not in ext, ext
|
||||
assert ext.startswith('.'), ext
|
||||
path = resource_path / subdir
|
||||
|
||||
if isinstance(resource_path, pathlib.Path):
|
||||
for full_path in path.glob(f'*{ext}'): # . is contained in ext
|
||||
yield full_path.relative_to(resource_path).as_posix()
|
||||
else: # zipfile.Path or importlib_resources compat object
|
||||
# Unfortunately, we can't tell mypy about resource_path being of type
|
||||
# Union[pathlib.Path, zipfile.Path] because we set "python_version = 3.6" in
|
||||
# .mypy.ini, but the zipfiel stubs (correctly) only declare zipfile.Path with
|
||||
# Python 3.8...
|
||||
assert path.is_dir(), path # type: ignore[unreachable]
|
||||
for subpath in path.iterdir():
|
||||
if subpath.name.endswith(ext):
|
||||
yield posixpath.join(subdir, subpath.name)
|
||||
|
||||
|
||||
def preload_resources() -> None:
|
||||
"""Load resource files into the cache."""
|
||||
resource_path = _resource_path('')
|
||||
for subdir, ext in [
|
||||
('html', '.html'),
|
||||
('javascript', '.js'),
|
||||
('javascript/quirks', '.js'),
|
||||
]:
|
||||
for name in _glob_resources(resource_path, subdir, ext):
|
||||
_resource_cache[name] = read_file(name)
|
||||
|
||||
|
||||
def read_file(filename: str) -> str:
|
||||
"""Get the contents of a file contained with qutebrowser.
|
||||
|
||||
Args:
|
||||
filename: The filename to open as string.
|
||||
|
||||
Return:
|
||||
The file contents as string.
|
||||
"""
|
||||
if filename in _resource_cache:
|
||||
return _resource_cache[filename]
|
||||
|
||||
path = _resource_path(filename)
|
||||
with _resource_keyerror_workaround():
|
||||
return path.read_text(encoding='utf-8')
|
||||
|
||||
|
||||
def read_file_binary(filename: str) -> bytes:
|
||||
"""Get the contents of a binary file contained with qutebrowser.
|
||||
|
||||
Args:
|
||||
filename: The filename to open as string.
|
||||
|
||||
Return:
|
||||
The file contents as a bytes object.
|
||||
"""
|
||||
path = _resource_path(filename)
|
||||
with _resource_keyerror_workaround():
|
||||
return path.read_bytes()
|
||||
|
||||
|
||||
def parse_version(version: str) -> VersionNumber:
|
||||
"""Parse a version string."""
|
||||
ver, _suffix = QVersionNumber.fromString(version)
|
||||
return VersionNumber(ver.normalized())
|
||||
|
||||
|
||||
def format_seconds(total_seconds: int) -> str:
|
||||
"""Format a count of seconds to get a [H:]M:SS string."""
|
||||
prefix = '-' if total_seconds < 0 else ''
|
||||
|
|
@ -671,7 +605,7 @@ def open_file(filename: str, cmdline: str = None) -> None:
|
|||
# if we want to use the default
|
||||
override = config.val.downloads.open_dispatcher
|
||||
|
||||
if version.is_sandboxed():
|
||||
if version.is_flatpak():
|
||||
if cmdline:
|
||||
message.error("Cannot spawn download dispatcher from sandbox")
|
||||
return
|
||||
|
|
@ -817,19 +751,6 @@ def ceil_log(number: int, base: int) -> int:
|
|||
return result
|
||||
|
||||
|
||||
def libgl_workaround() -> None:
|
||||
"""Work around QOpenGLShaderProgram issues, especially for Nvidia.
|
||||
|
||||
See https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
|
||||
"""
|
||||
if os.environ.get('QUTE_SKIP_LIBGL_WORKAROUND'):
|
||||
return
|
||||
|
||||
libgl = ctypes.util.find_library("GL")
|
||||
if libgl is not None: # pragma: no branch
|
||||
ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL)
|
||||
|
||||
|
||||
def parse_duration(duration: str) -> int:
|
||||
"""Parse duration in format XhYmZs into milliseconds duration."""
|
||||
if duration.isdigit():
|
||||
|
|
|
|||
|
|
@ -53,17 +53,11 @@ except ImportError: # pragma: no cover
|
|||
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.utils import log, utils, standarddir, usertypes, message
|
||||
from qutebrowser.utils import log, utils, standarddir, usertypes, message, resources
|
||||
from qutebrowser.misc import objects, earlyinit, sql, httpclient, pastebin, elf
|
||||
from qutebrowser.browser import pdfjs
|
||||
from qutebrowser.config import config, websettings
|
||||
|
||||
try:
|
||||
from qutebrowser.browser.webengine import webenginesettings
|
||||
except ImportError: # pragma: no cover
|
||||
webenginesettings = None # type: ignore[assignment]
|
||||
|
||||
|
||||
_LOGO = r'''
|
||||
______ ,,
|
||||
,.-"` | ,-` |
|
||||
|
|
@ -160,7 +154,7 @@ def distribution() -> Optional[DistributionInfo]:
|
|||
dist_version: Optional[utils.VersionNumber] = None
|
||||
for version_key in ['VERSION', 'VERSION_ID']:
|
||||
if version_key in info:
|
||||
dist_version = utils.parse_version(info[version_key])
|
||||
dist_version = utils.VersionNumber.parse(info[version_key])
|
||||
break
|
||||
|
||||
dist_id = info.get('ID', None)
|
||||
|
|
@ -189,8 +183,12 @@ def distribution() -> Optional[DistributionInfo]:
|
|||
parsed=parsed, version=dist_version, pretty=pretty, id=dist_id)
|
||||
|
||||
|
||||
def is_sandboxed() -> bool:
|
||||
"""Whether the environment has restricted access to the host system."""
|
||||
def is_flatpak() -> bool:
|
||||
"""Whether qutebrowser is running via Flatpak.
|
||||
|
||||
If packaged via Flatpak, the environment is has restricted access to the host
|
||||
system.
|
||||
"""
|
||||
current_distro = distribution()
|
||||
if current_distro is None:
|
||||
return False
|
||||
|
|
@ -218,7 +216,7 @@ def _git_str() -> Optional[str]:
|
|||
return commit
|
||||
# If that fails, check the git-commit-id file.
|
||||
try:
|
||||
return utils.read_file('git-commit-id')
|
||||
return resources.read_file('git-commit-id')
|
||||
except (OSError, ImportError):
|
||||
return None
|
||||
|
||||
|
|
@ -492,6 +490,9 @@ def _get_pyqt_webengine_qt_version() -> Optional[str]:
|
|||
https://www.riverbankcomputing.com/pipermail/pyqt/2021-February/043591.html
|
||||
https://www.riverbankcomputing.com/pipermail/pyqt/2021-February/043638.html
|
||||
|
||||
PyQtWebEngine 5.15.4 renamed it to PyQtWebEngine-Qt5...:
|
||||
https://www.riverbankcomputing.com/pipermail/pyqt/2021-March/043699.html
|
||||
|
||||
Here, we try to use importlib.metadata or its backport (optional dependency) to
|
||||
figure out that version number. If PyQtWebEngine is installed via pip, this will
|
||||
give us an accurate answer.
|
||||
|
|
@ -505,11 +506,13 @@ def _get_pyqt_webengine_qt_version() -> Optional[str]:
|
|||
log.misc.debug("Neither importlib.metadata nor backport available")
|
||||
return None
|
||||
|
||||
try:
|
||||
return importlib_metadata.version('PyQtWebEngine-Qt')
|
||||
except importlib_metadata.PackageNotFoundError:
|
||||
log.misc.debug("PyQtWebEngine-Qt not found")
|
||||
return None
|
||||
for suffix in ['Qt5', 'Qt']:
|
||||
try:
|
||||
return importlib_metadata.version(f'PyQtWebEngine-{suffix}')
|
||||
except importlib_metadata.PackageNotFoundError:
|
||||
log.misc.debug(f"PyQtWebEngine-{suffix} not found")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
|
|
@ -520,8 +523,9 @@ class WebEngineVersions:
|
|||
webengine: utils.VersionNumber
|
||||
chromium: Optional[str]
|
||||
source: str
|
||||
chromium_major: Optional[int] = dataclasses.field(init=False)
|
||||
|
||||
_CHROMIUM_VERSIONS: ClassVar[Dict[str, str]] = {
|
||||
_CHROMIUM_VERSIONS: ClassVar[Dict[utils.VersionNumber, str]] = {
|
||||
# Qt 5.12: Chromium 69
|
||||
# (LTS) 69.0.3497.128 (~2018-09-11)
|
||||
# 5.12.0: Security fixes up to 70.0.3538.102 (~2018-10-24)
|
||||
|
|
@ -535,21 +539,21 @@ class WebEngineVersions:
|
|||
# 5.12.8: Security fixes up to 80.0.3987.149 (2020-03-18)
|
||||
# 5.12.9: Security fixes up to 83.0.4103.97 (2020-06-03)
|
||||
# 5.12.10: Security fixes up to 86.0.4240.75 (2020-10-06)
|
||||
'5.12': '69.0.3497.128',
|
||||
utils.VersionNumber(5, 12): '69.0.3497.128',
|
||||
|
||||
# Qt 5.13: Chromium 73
|
||||
# 73.0.3683.105 (~2019-02-28)
|
||||
# 5.13.0: Security fixes up to 74.0.3729.157 (2019-05-14)
|
||||
# 5.13.1: Security fixes up to 76.0.3809.87 (2019-07-30)
|
||||
# 5.13.2: Security fixes up to 77.0.3865.120 (2019-10-10)
|
||||
'5.13': '73.0.3683.105',
|
||||
utils.VersionNumber(5, 13): '73.0.3683.105',
|
||||
|
||||
# Qt 5.14: Chromium 77
|
||||
# 77.0.3865.129 (~2019-10-10)
|
||||
# 5.14.0: Security fixes up to 77.0.3865.129 (~2019-09-10)
|
||||
# 5.14.1: Security fixes up to 79.0.3945.117 (2020-01-07)
|
||||
# 5.14.2: Security fixes up to 80.0.3987.132 (2020-03-03)
|
||||
'5.14': '77.0.3865.129',
|
||||
utils.VersionNumber(5, 14): '77.0.3865.129',
|
||||
|
||||
# Qt 5.15: Chromium 80
|
||||
# 80.0.3987.163 (2020-04-02)
|
||||
|
|
@ -557,13 +561,22 @@ class WebEngineVersions:
|
|||
# 5.15.1: Security fixes up to 85.0.4183.83 (2020-08-25)
|
||||
# 5.15.2: Updated to 83.0.4103.122 (~2020-06-24)
|
||||
# Security fixes up to 86.0.4240.183 (2020-11-02)
|
||||
'5.15': '80.0.3987.163',
|
||||
'5.15.2': '83.0.4103.122',
|
||||
'5.15.3': '87.0.4280.144',
|
||||
# 5.15.3: Updated to 87.0.4280.144 (~2020-12-02)
|
||||
# Security fixes up to 88.0.4324.150 (2021-02-04)
|
||||
utils.VersionNumber(5, 15): '80.0.3987.163',
|
||||
utils.VersionNumber(5, 15, 2): '83.0.4103.122',
|
||||
utils.VersionNumber(5, 15, 3): '87.0.4280.144',
|
||||
}
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Set the major Chromium version."""
|
||||
if self.chromium is None:
|
||||
self.chromium_major = None
|
||||
else:
|
||||
self.chromium_major = int(self.chromium.split('.')[0])
|
||||
|
||||
def __str__(self) -> str:
|
||||
s = f'QtWebEngine {self.webengine.toString()}'
|
||||
s = f'QtWebEngine {self.webengine}'
|
||||
if self.chromium is not None:
|
||||
s += f', Chromium {self.chromium}'
|
||||
if self.source != 'UA':
|
||||
|
|
@ -580,7 +593,7 @@ class WebEngineVersions:
|
|||
"""
|
||||
assert ua.qt_version is not None, ua
|
||||
return cls(
|
||||
webengine=utils.parse_version(ua.qt_version),
|
||||
webengine=utils.VersionNumber.parse(ua.qt_version),
|
||||
chromium=ua.upstream_browser_version,
|
||||
source='UA',
|
||||
)
|
||||
|
|
@ -597,19 +610,30 @@ class WebEngineVersions:
|
|||
(though hackish) way to get a more accurate result.
|
||||
"""
|
||||
return cls(
|
||||
webengine=utils.parse_version(versions.webengine),
|
||||
webengine=utils.VersionNumber.parse(versions.webengine),
|
||||
chromium=versions.chromium,
|
||||
source='ELF',
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _infer_chromium_version(cls, pyqt_webengine_version: str) -> Optional[str]:
|
||||
def _infer_chromium_version(
|
||||
cls,
|
||||
pyqt_webengine_version: utils.VersionNumber,
|
||||
) -> Optional[str]:
|
||||
"""Infer the Chromium version based on the PyQtWebEngine version."""
|
||||
chromium_version = cls._CHROMIUM_VERSIONS.get(pyqt_webengine_version)
|
||||
if chromium_version is not None:
|
||||
return chromium_version
|
||||
# 5.14.2 -> 5.14
|
||||
minor_version = pyqt_webengine_version.rsplit('.', maxsplit=1)[0]
|
||||
|
||||
# 5.15 patch versions change their QtWebEngine version, but no changes are
|
||||
# expected after 5.15.3.
|
||||
v5_15_3 = utils.VersionNumber(5, 15, 3)
|
||||
if v5_15_3 <= pyqt_webengine_version < utils.VersionNumber(6):
|
||||
minor_version = v5_15_3
|
||||
else:
|
||||
# e.g. 5.14.2 -> 5.14
|
||||
minor_version = pyqt_webengine_version.strip_patch()
|
||||
|
||||
return cls._CHROMIUM_VERSIONS.get(minor_version)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -631,9 +655,10 @@ class WebEngineVersions:
|
|||
Note that we only can get the PyQtWebEngine version with PyQt 5.13 or newer.
|
||||
With Qt 5.12, we instead rely on qVersion().
|
||||
"""
|
||||
parsed = utils.VersionNumber.parse(pyqt_webengine_version)
|
||||
return cls(
|
||||
webengine=utils.parse_version(pyqt_webengine_version),
|
||||
chromium=cls._infer_chromium_version(pyqt_webengine_version),
|
||||
webengine=parsed,
|
||||
chromium=cls._infer_chromium_version(parsed),
|
||||
source=source,
|
||||
)
|
||||
|
||||
|
|
@ -657,7 +682,7 @@ def qtwebengine_versions(avoid_init: bool = False) -> WebEngineVersions:
|
|||
- https://www.chromium.org/developers/calendar
|
||||
- https://chromereleases.googleblog.com/
|
||||
"""
|
||||
assert webenginesettings is not None
|
||||
from qutebrowser.browser.webengine import webenginesettings
|
||||
|
||||
if webenginesettings.parsed_user_agent is None and not avoid_init:
|
||||
webenginesettings.init_user_agent()
|
||||
|
|
@ -855,9 +880,6 @@ def opengl_info() -> Optional[OpenGLInfo]: # pragma: no cover
|
|||
"""
|
||||
assert QApplication.instance()
|
||||
|
||||
# Some setups can segfault in here if we don't do this.
|
||||
utils.libgl_workaround()
|
||||
|
||||
override = os.environ.get('QUTE_FAKE_OPENGL')
|
||||
if override is not None:
|
||||
log.init.debug("Using override {}".format(override))
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
adblock==0.4.2 ; python_version!="3.10"
|
||||
colorama==0.4.4
|
||||
dataclasses==0.6 ; python_version<"3.7"
|
||||
importlib-metadata==3.7.0 ; python_version<"3.8"
|
||||
importlib-resources==5.1.1 ; python_version<"3.9"
|
||||
importlib-metadata==3.7.3 ; python_version<"3.8"
|
||||
importlib-resources==5.1.2 ; python_version<"3.9"
|
||||
Jinja2==2.11.3
|
||||
MarkupSafe==1.1.1
|
||||
Pygments==2.8.0
|
||||
Pygments==2.8.1
|
||||
PyYAML==5.4.1
|
||||
typing-extensions==3.7.4.3
|
||||
zipp==3.4.0
|
||||
zipp==3.4.1
|
||||
|
|
|
|||
|
|
@ -187,6 +187,8 @@ PERFECT_FILES = [
|
|||
'qutebrowser/utils/usertypes.py'),
|
||||
('tests/unit/utils/test_utils.py',
|
||||
'qutebrowser/utils/utils.py'),
|
||||
('tests/unit/utils/test_resources.py',
|
||||
'qutebrowser/utils/resources.py'),
|
||||
('tests/unit/utils/test_version.py',
|
||||
'qutebrowser/utils/version.py'),
|
||||
('tests/unit/utils/test_debug.py',
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
FROM archlinux:latest
|
||||
|
||||
# WORKAROUND for glibc 2.33 and old Docker
|
||||
# See https://github.com/actions/virtual-environments/issues/2658
|
||||
# Thanks to https://github.com/lxqt/lxqt-panel/pull/1562
|
||||
RUN patched_glibc=glibc-linux4-2.33-4-x86_64.pkg.tar.zst && \
|
||||
curl -LO "https://repo.archlinuxcn.org/x86_64/$patched_glibc" && \
|
||||
bsdtar -C / -xvf "$patched_glibc"
|
||||
|
||||
{% if unstable %}
|
||||
RUN sed -i '/^# after the header/a[kde-unstable]\nInclude = /etc/pacman.d/mirrorlist\n\n[testing]\nInclude = /etc/pacman.d/mirrorlist' /etc/pacman.conf
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ def check_spelling(args: argparse.Namespace) -> Optional[bool]:
|
|||
"""Check commonly misspelled words."""
|
||||
# Words which I often misspell
|
||||
words = {'behaviour', 'quitted', 'likelyhood', 'sucessfully',
|
||||
'occur[^rs .!]', 'seperator', 'explicitely', 'auxillary',
|
||||
'occur[^rs .!,]', 'seperator', 'explicitely', 'auxillary',
|
||||
'accidentaly', 'ambigious', 'loosly', 'initialis', 'convienence',
|
||||
'similiar', 'uncommited', 'reproducable', 'an user',
|
||||
'convienience', 'wether', 'programatically', 'splitted',
|
||||
|
|
@ -247,7 +247,23 @@ def check_spelling(args: argparse.Namespace) -> Optional[bool]:
|
|||
(
|
||||
re.compile(fr'qtbot\.(?!{qtbot_excludes})[a-z]+[A-Z].*'),
|
||||
"use snake-case instead",
|
||||
)
|
||||
),
|
||||
(
|
||||
re.compile(r'\.joinpath\((?!\*)'),
|
||||
"use the / operator for joining paths",
|
||||
),
|
||||
(
|
||||
re.compile(r"""pathlib\.Path\(["']~["']\)\.expanduser\(\)"""),
|
||||
"use pathlib.Path.home() instead",
|
||||
),
|
||||
(
|
||||
re.compile(r'pathlib\.Path\(tmp_path\)'),
|
||||
"tmp_path already is a pathlib.Path",
|
||||
),
|
||||
(
|
||||
re.compile(r'pathlib\.Path\(tmpdir\)'),
|
||||
"use tmp_path instead",
|
||||
),
|
||||
]
|
||||
|
||||
# Files which should be ignored, e.g. because they come from another
|
||||
|
|
|
|||
|
|
@ -138,8 +138,10 @@ CHANGELOG_URLS = {
|
|||
'toml': 'https://github.com/uiri/toml/releases',
|
||||
'PyQt5': 'https://www.riverbankcomputing.com/news',
|
||||
'PyQt5-Qt': 'https://www.riverbankcomputing.com/news',
|
||||
'PyQt5-Qt5': 'https://www.riverbankcomputing.com/news',
|
||||
'PyQtWebEngine': 'https://www.riverbankcomputing.com/news',
|
||||
'PyQtWebEngine-Qt': 'https://www.riverbankcomputing.com/news',
|
||||
'PyQtWebEngine-Qt5': 'https://www.riverbankcomputing.com/news',
|
||||
'PyQt-builder': 'https://www.riverbankcomputing.com/news',
|
||||
'PyQt5-sip': 'https://www.riverbankcomputing.com/news',
|
||||
'PyQt5-stubs': 'https://github.com/stlehmann/PyQt5-stubs/blob/master/CHANGELOG.md',
|
||||
|
|
@ -279,7 +281,7 @@ def run_pip(venv_dir, *args, quiet=False, **kwargs):
|
|||
arg_str = ' '.join(str(arg) for arg in args)
|
||||
utils.print_col('venv$ pip {}'.format(arg_str), 'blue')
|
||||
|
||||
venv_python = os.path.join(venv_dir, 'bin', 'python')
|
||||
venv_python = get_venv_python(venv_dir)
|
||||
return subprocess.run([venv_python, '-m', 'pip'] + args, check=True, **kwargs)
|
||||
|
||||
|
||||
|
|
@ -399,7 +401,13 @@ def _get_changes(diff):
|
|||
for line in diff:
|
||||
if not line.startswith('-') and not line.startswith('+'):
|
||||
continue
|
||||
if line.startswith('+++ ') or line.startswith('--- '):
|
||||
elif line.startswith('+++ ') or line.startswith('--- '):
|
||||
continue
|
||||
elif not line.strip():
|
||||
# Could be newline changes on Windows
|
||||
continue
|
||||
elif line[1:].startswith('# This file is automatically'):
|
||||
# Could be newline changes on Windows
|
||||
continue
|
||||
|
||||
name, version = parse_versioned_line(line[1:])
|
||||
|
|
@ -458,6 +466,12 @@ def get_host_python(name):
|
|||
return sys.executable
|
||||
|
||||
|
||||
def get_venv_python(venv_dir):
|
||||
"""Get the path to Python inside a virtualenv."""
|
||||
subdir = 'Scripts' if os.name == 'nt' else 'bin'
|
||||
return os.path.join(venv_dir, subdir, 'python')
|
||||
|
||||
|
||||
def get_outfile(name):
|
||||
"""Get the path to the output requirements.txt file."""
|
||||
if name == 'qutebrowser':
|
||||
|
|
@ -510,7 +524,7 @@ def test_tox():
|
|||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
venv_dir = os.path.join(tmpdir, 'venv')
|
||||
tox_workdir = os.path.join(tmpdir, 'tox-workdir')
|
||||
venv_python = os.path.join(venv_dir, 'bin', 'python')
|
||||
venv_python = get_venv_python(venv_dir)
|
||||
init_venv(host_python, venv_dir, req_path)
|
||||
list_proc = subprocess.run([venv_python, '-m', 'tox', '--listenvs'],
|
||||
check=True,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ def main():
|
|||
'protected-access',
|
||||
'len-as-condition',
|
||||
'compare-to-empty-string',
|
||||
'pointless-statement',
|
||||
# directories without __init__.py...
|
||||
'import-error',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -447,6 +447,7 @@ def _generate_setting_option(f, opt):
|
|||
f.write("\nThis setting supports URL patterns.\n")
|
||||
if opt.no_autoconfig:
|
||||
f.write("\nThis setting can only be set in config.py.\n")
|
||||
_generate_setting_backend_info(f, opt)
|
||||
f.write("\n")
|
||||
typ = opt.typ.get_name().replace(',', ',')
|
||||
f.write('Type: <<types,{typ}>>\n'.format(typ=typ))
|
||||
|
|
@ -465,7 +466,6 @@ def _generate_setting_option(f, opt):
|
|||
f.write("\n")
|
||||
|
||||
f.write("Default: {}\n".format(opt.typ.to_doc(opt.default)))
|
||||
_generate_setting_backend_info(f, opt)
|
||||
|
||||
|
||||
def generate_settings(filename):
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ for ua_string in reversed(response.json()):
|
|||
continue
|
||||
if any(part.startswith("OPR/") or part.startswith("Edg/") for part in parts):
|
||||
continue
|
||||
if 'Chrome/99.0.7113.93' in parts:
|
||||
# Fake or false-positive entry
|
||||
continue
|
||||
|
||||
user_agent = qutebrowser.config.websettings.UserAgent.parse(ua_string)
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ if __name__ == "__main__":
|
|||
.format(v=version))
|
||||
print("* Windows: git fetch; git checkout v{v}; "
|
||||
"py -3.9 -m tox -e build-release -- --asciidoc "
|
||||
"$env:userprofile\\bin\\asciidoc-9.0.5\\asciidoc.py --upload"
|
||||
"$env:userprofile\\bin\\asciidoc-9.1.0\\asciidoc.py --upload"
|
||||
.format(v=version))
|
||||
print("* macOS: git fetch && git checkout v{v} && "
|
||||
"tox -e build-release -- --upload"
|
||||
|
|
|
|||
|
|
@ -135,7 +135,8 @@ def insert_qb(history, dest):
|
|||
'INSERT INTO History (url,title,atime,redirect) VALUES (?,?,?,?)',
|
||||
history
|
||||
)
|
||||
cursor.execute('DROP TABLE CompletionHistory')
|
||||
cursor.execute('UPDATE CompletionMetaInfo SET value = 1 '
|
||||
'WHERE key = "force_rebuild"')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
|
|
|||
|
|
@ -99,6 +99,18 @@ def parse_args(argv: List[str] = None) -> argparse.Namespace:
|
|||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def _version_key(v):
|
||||
"""Sort PyQt requirement file prefixes.
|
||||
|
||||
If we have a filename like requirements-pyqt-pyinstaller.txt, that should
|
||||
always be sorted after all others (hence we return a "999" key).
|
||||
"""
|
||||
try:
|
||||
return tuple(int(v) for c in v.split('.'))
|
||||
except ValueError:
|
||||
return 999
|
||||
|
||||
|
||||
def pyqt_versions() -> List[str]:
|
||||
"""Get a list of all available PyQt versions.
|
||||
|
||||
|
|
@ -110,8 +122,7 @@ def pyqt_versions() -> List[str]:
|
|||
for req in requirements_dir.glob('requirements-pyqt-*.txt'):
|
||||
version_set.add(req.stem.split('-')[-1])
|
||||
|
||||
versions = sorted(version_set,
|
||||
key=lambda v: [int(c) for c in v.split('.')])
|
||||
versions = sorted(version_set, key=_version_key)
|
||||
return versions + ['auto']
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@
|
|||
"""The qutebrowser test suite conftest file."""
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import warnings
|
||||
import pathlib
|
||||
|
||||
import pytest
|
||||
import hypothesis
|
||||
|
|
@ -109,12 +109,6 @@ def _apply_platform_markers(config, item):
|
|||
pytest.mark.skipif,
|
||||
sys.getfilesystemencoding() == 'ascii',
|
||||
"Skipped because of ASCII locale"),
|
||||
|
||||
('qtwebkit6021_xfail',
|
||||
pytest.mark.xfail,
|
||||
version.qWebKitVersion and # type: ignore[unreachable]
|
||||
version.qWebKitVersion() == '602.1',
|
||||
"Broken on WebKit 602.1")
|
||||
]
|
||||
|
||||
for searched_marker, new_marker_kind, condition, default_reason in markers:
|
||||
|
|
@ -189,9 +183,10 @@ def pytest_collection_modifyitems(config, items):
|
|||
|
||||
def pytest_ignore_collect(path):
|
||||
"""Ignore BDD tests if we're unable to run them."""
|
||||
fspath = pathlib.Path(path)
|
||||
skip_bdd = hasattr(sys, 'frozen')
|
||||
rel_path = path.relto(os.path.dirname(__file__))
|
||||
return rel_path == os.path.join('end2end', 'features') and skip_bdd
|
||||
rel_path = fspath.relative_to(pathlib.Path(__file__).parent)
|
||||
return rel_path == pathlib.Path('end2end') / 'features' and skip_bdd
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
|
|
@ -245,12 +240,6 @@ def set_backend(monkeypatch, request):
|
|||
monkeypatch.setattr(objects, 'backend', backend)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope='session')
|
||||
def apply_libgl_workaround():
|
||||
"""Make sure we load libGL early so QtWebEngine tests run properly."""
|
||||
utils.libgl_workaround()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def apply_fake_os(monkeypatch, request):
|
||||
fake_os = request.node.get_closest_marker('fake_os')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>MathML-like SVG</title>
|
||||
</head>
|
||||
<body style="margin: 0; background-color: #ffff99">
|
||||
<!--
|
||||
Image based on: https://en.wikipedia.org/wiki/Pythagorean_theorem
|
||||
with a black square added for testing.
|
||||
|
||||
onload based on:
|
||||
https://stackoverflow.com/questions/53423742/waiting-for-an-image-to-finish-rendering
|
||||
-->
|
||||
<img
|
||||
class="mwe-math-fallback-image-inline"
|
||||
src="mathml.svg"
|
||||
alt="Pythagorean theorem"
|
||||
onload="requestAnimationFrame(() => requestAnimationFrame(() => console.log('Image loaded')));"
|
||||
>
|
||||
<!-- -->
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="12.983ex"
|
||||
height="2.843ex"
|
||||
style="vertical-align: -0.505ex;"
|
||||
viewBox="0 -1006.6 5589.7 1223.9"
|
||||
role="img"
|
||||
focusable="false"
|
||||
aria-labelledby="MathJax-SVG-1-Title"
|
||||
version="1.1"
|
||||
id="svg36"
|
||||
sodipodi:docname="mathml.svg"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
|
||||
<metadata
|
||||
id="metadata40">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>{\displaystyle a^{2}+b^{2}=c^{2}.}</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="944"
|
||||
inkscape:window-height="1036"
|
||||
id="namedview38"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.27282322"
|
||||
inkscape:cx="1686.0735"
|
||||
inkscape:cy="602.78657"
|
||||
inkscape:window-x="964"
|
||||
inkscape:window-y="22"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg36" />
|
||||
<title
|
||||
id="MathJax-SVG-1-Title">{\displaystyle a^{2}+b^{2}=c^{2}.}</title>
|
||||
<defs
|
||||
aria-hidden="true"
|
||||
id="defs10">
|
||||
<path
|
||||
stroke-width="1"
|
||||
id="E1-MJMATHI-61"
|
||||
d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z" />
|
||||
<path
|
||||
stroke-width="1"
|
||||
id="E1-MJMAIN-32"
|
||||
d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z" />
|
||||
<path
|
||||
stroke-width="1"
|
||||
id="E1-MJMAIN-2B"
|
||||
d="M56 237T56 250T70 270H369V420L370 570Q380 583 389 583Q402 583 409 568V270H707Q722 262 722 250T707 230H409V-68Q401 -82 391 -82H389H387Q375 -82 369 -68V230H70Q56 237 56 250Z" />
|
||||
<path
|
||||
stroke-width="1"
|
||||
id="E1-MJMATHI-62"
|
||||
d="M73 647Q73 657 77 670T89 683Q90 683 161 688T234 694Q246 694 246 685T212 542Q204 508 195 472T180 418L176 399Q176 396 182 402Q231 442 283 442Q345 442 383 396T422 280Q422 169 343 79T173 -11Q123 -11 82 27T40 150V159Q40 180 48 217T97 414Q147 611 147 623T109 637Q104 637 101 637H96Q86 637 83 637T76 640T73 647ZM336 325V331Q336 405 275 405Q258 405 240 397T207 376T181 352T163 330L157 322L136 236Q114 150 114 114Q114 66 138 42Q154 26 178 26Q211 26 245 58Q270 81 285 114T318 219Q336 291 336 325Z" />
|
||||
<path
|
||||
stroke-width="1"
|
||||
id="E1-MJMAIN-3D"
|
||||
d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z" />
|
||||
<path
|
||||
stroke-width="1"
|
||||
id="E1-MJMATHI-63"
|
||||
d="M34 159Q34 268 120 355T306 442Q362 442 394 418T427 355Q427 326 408 306T360 285Q341 285 330 295T319 325T330 359T352 380T366 386H367Q367 388 361 392T340 400T306 404Q276 404 249 390Q228 381 206 359Q162 315 142 235T121 119Q121 73 147 50Q169 26 205 26H209Q321 26 394 111Q403 121 406 121Q410 121 419 112T429 98T420 83T391 55T346 25T282 0T202 -11Q127 -11 81 37T34 159Z" />
|
||||
<path
|
||||
stroke-width="1"
|
||||
id="E1-MJMAIN-2E"
|
||||
d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z" />
|
||||
</defs>
|
||||
<g
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
stroke-width="0"
|
||||
transform="matrix(1 0 0 -1 0 0)"
|
||||
aria-hidden="true"
|
||||
id="g34">
|
||||
<use
|
||||
xlink:href="#E1-MJMATHI-61"
|
||||
x="0"
|
||||
y="0"
|
||||
id="use12" />
|
||||
<use
|
||||
transform="scale(0.707)"
|
||||
xlink:href="#E1-MJMAIN-32"
|
||||
x="748"
|
||||
y="583"
|
||||
id="use14" />
|
||||
<use
|
||||
xlink:href="#E1-MJMAIN-2B"
|
||||
x="1205"
|
||||
y="0"
|
||||
id="use16" />
|
||||
<g
|
||||
transform="translate(2206,0)"
|
||||
id="g22">
|
||||
<use
|
||||
xlink:href="#E1-MJMATHI-62"
|
||||
x="0"
|
||||
y="0"
|
||||
id="use18" />
|
||||
<use
|
||||
transform="scale(0.707)"
|
||||
xlink:href="#E1-MJMAIN-32"
|
||||
x="607"
|
||||
y="583"
|
||||
id="use20" />
|
||||
</g>
|
||||
<use
|
||||
xlink:href="#E1-MJMAIN-3D"
|
||||
x="3367"
|
||||
y="0"
|
||||
id="use24" />
|
||||
<g
|
||||
transform="translate(4423,0)"
|
||||
id="g30">
|
||||
<use
|
||||
xlink:href="#E1-MJMATHI-63"
|
||||
x="0"
|
||||
y="0"
|
||||
id="use26" />
|
||||
<use
|
||||
transform="scale(0.707)"
|
||||
xlink:href="#E1-MJMAIN-32"
|
||||
x="613"
|
||||
y="583"
|
||||
id="use28" />
|
||||
</g>
|
||||
<use
|
||||
xlink:href="#E1-MJMAIN-2E"
|
||||
x="5311"
|
||||
y="0"
|
||||
id="use32" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#000000"
|
||||
id="rect865"
|
||||
width="338.88928"
|
||||
height="316.48901"
|
||||
x="2.5373409"
|
||||
y="-1004.8583" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
|
|
@ -0,0 +1,5 @@
|
|||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
const elem = document.getElementById('text');
|
||||
elem.textContent = 'Script loaded';
|
||||
console.log('Script loaded');
|
||||
})
|
||||
|
|
@ -140,7 +140,7 @@ Feature: Various utility commands.
|
|||
|
||||
Scenario: :jseval --file using a file that doesn't exist as js-code
|
||||
When I run :jseval --file /nonexistentfile
|
||||
Then the error "[Errno 2] No such file or directory: '/nonexistentfile'" should be shown
|
||||
Then the error "[Errno 2] *: '/nonexistentfile'" should be shown
|
||||
And "No output or error" should not be logged
|
||||
|
||||
# :debug-webaction
|
||||
|
|
@ -528,13 +528,13 @@ Feature: Various utility commands.
|
|||
@qtwebkit_skip @no_invalid_lines @posix
|
||||
Scenario: Renderer crash
|
||||
When I run :open -t chrome://crash
|
||||
Then "Renderer process crashed" should be logged
|
||||
Then "Renderer process crashed (status *)" should be logged
|
||||
And "* 'Error loading chrome://crash/'" should be logged
|
||||
|
||||
@qtwebkit_skip @no_invalid_lines @flaky
|
||||
Scenario: Renderer kill
|
||||
When I run :open -t chrome://kill
|
||||
Then "Renderer process was killed" should be logged
|
||||
Then "Renderer process was killed (status *)" should be logged
|
||||
And "* 'Error loading chrome://kill/'" should be logged
|
||||
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2290
|
||||
|
|
@ -544,7 +544,7 @@ Feature: Various utility commands.
|
|||
And I open data/numbers/1.txt
|
||||
And I open data/numbers/2.txt in a new tab
|
||||
And I run :open chrome://kill
|
||||
And I wait for "Renderer process was killed" in the log
|
||||
And I wait for "Renderer process was killed (status *)" in the log
|
||||
And I open data/numbers/3.txt
|
||||
Then no crash should happen
|
||||
|
||||
|
|
@ -554,11 +554,11 @@ Feature: Various utility commands.
|
|||
When I open data/crashers/webrtc.html in a new tab
|
||||
And I run :reload
|
||||
And I wait until data/crashers/webrtc.html is loaded
|
||||
Then "Renderer process crashed" should not be logged
|
||||
Then "Renderer process crashed (status *)" should not be logged
|
||||
|
||||
Scenario: InstalledApps crash
|
||||
When I open data/crashers/installedapp.html in a new tab
|
||||
Then "Renderer process was killed" should not be logged
|
||||
Then "Renderer process was killed (status *)" should not be logged
|
||||
|
||||
## Other
|
||||
|
||||
|
|
|
|||
|
|
@ -163,45 +163,83 @@ Feature: Prompts
|
|||
|
||||
# SSL
|
||||
|
||||
Scenario: SSL error with content.ssl_strict = false
|
||||
Scenario: SSL error with content.tls.certificate_errors = load-insecurely
|
||||
When I clear SSL errors
|
||||
And I set content.ssl_strict to false
|
||||
And I set content.tls.certificate_errors to load-insecurely
|
||||
And I load an SSL page
|
||||
And I wait until the SSL page finished loading
|
||||
Then the error "Certificate error: *" should be shown
|
||||
And the page should contain the plaintext "Hello World via SSL!"
|
||||
|
||||
Scenario: SSL error with content.ssl_strict = true
|
||||
Scenario: SSL error with content.tls.certificate_errors = block
|
||||
When I clear SSL errors
|
||||
And I set content.ssl_strict to true
|
||||
And I set content.tls.certificate_errors to block
|
||||
And I load an SSL page
|
||||
Then a SSL error page should be shown
|
||||
|
||||
Scenario: SSL error with content.ssl_strict = ask -> yes
|
||||
Scenario: SSL error with content.tls.certificate_errors = ask -> yes
|
||||
When I clear SSL errors
|
||||
And I set content.ssl_strict to ask
|
||||
And I set content.tls.certificate_errors to ask
|
||||
And I load an SSL page
|
||||
And I wait for a prompt
|
||||
And I run :prompt-accept yes
|
||||
And I wait until the SSL page finished loading
|
||||
Then the page should contain the plaintext "Hello World via SSL!"
|
||||
|
||||
Scenario: SSL error with content.ssl_strict = ask -> no
|
||||
Scenario: SSL error with content.tls.certificate_errors = ask -> no
|
||||
When I clear SSL errors
|
||||
And I set content.ssl_strict to ask
|
||||
And I set content.tls.certificate_errors to ask
|
||||
And I load an SSL page
|
||||
And I wait for a prompt
|
||||
And I run :prompt-accept no
|
||||
Then a SSL error page should be shown
|
||||
|
||||
Scenario: SSL error with content.ssl_strict = ask -> abort
|
||||
Scenario: SSL error with content.tls.certificate_errors = ask -> abort
|
||||
When I clear SSL errors
|
||||
And I set content.ssl_strict to ask
|
||||
And I set content.tls.certificate_errors to ask
|
||||
And I load an SSL page
|
||||
And I wait for a prompt
|
||||
And I run :mode-leave
|
||||
Then a SSL error page should be shown
|
||||
|
||||
Scenario: SSL error with content.tls.certificate_errors = ask-block-thirdparty -> yes
|
||||
When I clear SSL errors
|
||||
And I set content.tls.certificate_errors to ask-block-thirdparty
|
||||
And I load an SSL page
|
||||
And I wait for a prompt
|
||||
And I run :prompt-accept yes
|
||||
And I wait until the SSL page finished loading
|
||||
Then the page should contain the plaintext "Hello World via SSL!"
|
||||
|
||||
Scenario: SSL resource error with content.tls.certificate_errors = ask -> yes
|
||||
When I clear SSL errors
|
||||
And I set content.tls.certificate_errors to ask
|
||||
And I load an SSL resource page
|
||||
And I wait for a prompt
|
||||
And I run :prompt-accept yes
|
||||
And I wait until the SSL resource page finished loading
|
||||
Then the javascript message "Script loaded" should be logged
|
||||
And the page should contain the plaintext "Script loaded"
|
||||
|
||||
Scenario: SSL resource error with content.tls.certificate_errors = ask -> no
|
||||
When I clear SSL errors
|
||||
And I set content.tls.certificate_errors to ask
|
||||
And I load an SSL resource page
|
||||
And I wait for a prompt
|
||||
And I run :prompt-accept no
|
||||
And I wait until the SSL resource page finished loading
|
||||
Then the javascript message "Script loaded" should not be logged
|
||||
And the page should contain the plaintext "Script not loaded"
|
||||
|
||||
Scenario: SSL resource error with content.tls.certificate_errors = ask-block-thirdparty
|
||||
When I clear SSL errors
|
||||
And I set content.tls.certificate_errors to ask-block-thirdparty
|
||||
And I load an SSL resource page
|
||||
And I wait until the SSL resource page finished loading
|
||||
Then "Certificate error in resource load: *" should be logged
|
||||
And the javascript message "Script loaded" should not be logged
|
||||
And the page should contain the plaintext "Script not loaded"
|
||||
|
||||
# Geolocation
|
||||
|
||||
Scenario: Always rejecting geolocation
|
||||
|
|
@ -484,7 +522,7 @@ Feature: Prompts
|
|||
Scenario: Interrupting SSL prompt during a notification prompt
|
||||
Given I have a fresh instance
|
||||
When I set content.notifications to ask
|
||||
And I set content.ssl_strict to ask
|
||||
And I set content.tls.certificate_errors to ask
|
||||
And I open data/prompt/notifications.html in a new tab
|
||||
And I run :click-element id button
|
||||
And I wait for a prompt
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ Feature: Special qute:// pages
|
|||
|
||||
Scenario: Running :pyeval --file using a non existing file
|
||||
When I run :debug-pyeval --file nonexistentfile
|
||||
Then the error "[Errno 2] No such file or directory: 'nonexistentfile'" should be shown
|
||||
Then the error "[Errno 2] *: 'nonexistentfile'" should be shown
|
||||
|
||||
Scenario: Running :pyeval with --quiet
|
||||
When I run :debug-pyeval --quiet 1+1
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Feature: :spawn
|
|||
|
||||
Scenario: Running :spawn with command that does not exist
|
||||
When I run :spawn command_does_not_exist127623
|
||||
Then the error "Error while spawning command: *" should be shown
|
||||
Then the error "Command 'command_does_not_exist127623' failed to start: *" should be shown
|
||||
|
||||
Scenario: Starting a userscript which doesn't exist
|
||||
When I run :spawn -u this_does_not_exist
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ bdd.scenarios('open.feature')
|
|||
@pytest.mark.parametrize('scheme', ['http://', ''])
|
||||
def test_open_s(request, quteproc, ssl_server, scheme):
|
||||
"""Test :open with -s."""
|
||||
quteproc.set_setting('content.ssl_strict', 'false')
|
||||
quteproc.set_setting('content.tls.certificate_errors', 'load-insecurely')
|
||||
quteproc.send_cmd(':open -s {}localhost:{}/'
|
||||
.format(scheme, ssl_server.port))
|
||||
if scheme == 'http://' or not request.config.webengine:
|
||||
|
|
|
|||
|
|
@ -36,6 +36,17 @@ def wait_ssl_page_finished_loading(quteproc, ssl_server):
|
|||
load_status='warn')
|
||||
|
||||
|
||||
@bdd.when("I load an SSL resource page")
|
||||
def load_ssl_resource_page(quteproc, server, ssl_server):
|
||||
# We don't wait here as we can get an SSL question.
|
||||
quteproc.open_path(f'https-script/{ssl_server.port}', port=server.port, wait=False)
|
||||
|
||||
|
||||
@bdd.when("I wait until the SSL resource page finished loading")
|
||||
def wait_ssl_resource_page_finished_loading(quteproc, server, ssl_server):
|
||||
quteproc.wait_for_load_finished(f'https-script/{ssl_server.port}', port=server.port)
|
||||
|
||||
|
||||
@bdd.when("I wait for a prompt")
|
||||
def wait_for_prompt(quteproc):
|
||||
quteproc.wait_for(message='Asking question *')
|
||||
|
|
@ -66,7 +77,7 @@ def ssl_error_page(request, quteproc):
|
|||
|
||||
def test_certificate_error_load_status(request, quteproc, ssl_server):
|
||||
"""If we load the same page twice, we should get a 'warn' status twice."""
|
||||
quteproc.set_setting('content.ssl_strict', 'false')
|
||||
quteproc.set_setting('content.tls.certificate_errors', 'load-insecurely')
|
||||
|
||||
for i in range(2):
|
||||
quteproc.open_path('/', port=ssl_server.port, https=True, wait=False,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import logging
|
|||
import tempfile
|
||||
import contextlib
|
||||
import itertools
|
||||
import collections
|
||||
import json
|
||||
|
||||
import yaml
|
||||
|
|
@ -453,6 +454,7 @@ class QuteProc(testprocess.Process):
|
|||
self.basedir = None
|
||||
self._instance_id = next(instance_counter)
|
||||
self._run_counter = itertools.count()
|
||||
self._screenshot_counters = collections.defaultdict(itertools.count)
|
||||
|
||||
def _process_line(self, log_line):
|
||||
"""Check if the line matches any initial lines we're interested in."""
|
||||
|
|
@ -645,8 +647,6 @@ class QuteProc(testprocess.Process):
|
|||
('auto_save.interval', '0'),
|
||||
('new_instance_open_target_window', 'last-opened')
|
||||
]
|
||||
if not self.request.config.webengine:
|
||||
settings.append(('content.ssl_strict', 'false'))
|
||||
|
||||
for opt, value in settings:
|
||||
self.set_setting(opt, value)
|
||||
|
|
@ -902,9 +902,14 @@ class QuteProc(testprocess.Process):
|
|||
"""
|
||||
for _ in range(5):
|
||||
tmp_path = self.request.getfixturevalue('tmp_path')
|
||||
path = tmp_path / 'screenshot.png'
|
||||
self.send_cmd(f':screenshot --force {path}')
|
||||
self.wait_for(message=f'Screenshot saved to {path}')
|
||||
counter = self._screenshot_counters[self.request.node.nodeid]
|
||||
|
||||
path = tmp_path / f'screenshot-{next(counter)}.png'
|
||||
self.send_cmd(f':screenshot {path}')
|
||||
|
||||
screenshot_msg = f'Screenshot saved to {path}'
|
||||
self.wait_for(message=screenshot_msg)
|
||||
print(screenshot_msg)
|
||||
|
||||
img = QImage(str(path))
|
||||
assert not img.isNull()
|
||||
|
|
@ -919,8 +924,9 @@ class QuteProc(testprocess.Process):
|
|||
# Rendering might not be completed yet...
|
||||
time.sleep(0.5)
|
||||
|
||||
raise ValueError(
|
||||
f"Pixel probing for {probe_color} failed (got {probed_color} on last try)")
|
||||
# Using assert again for pytest introspection
|
||||
assert probed_color == probe_color, "Color probing failed, values on last try:"
|
||||
raise utils.Unreachable()
|
||||
|
||||
def press_keys(self, keys):
|
||||
"""Press the given keys using :fake-key."""
|
||||
|
|
|
|||
|
|
@ -62,7 +62,11 @@ class Request(testprocess.Line):
|
|||
def _check_status(self):
|
||||
"""Check if the http status is what we expected."""
|
||||
path_to_statuses = {
|
||||
'/favicon.ico': [HTTPStatus.OK, HTTPStatus.PARTIAL_CONTENT],
|
||||
'/favicon.ico': [
|
||||
HTTPStatus.OK,
|
||||
HTTPStatus.PARTIAL_CONTENT,
|
||||
HTTPStatus.NOT_MODIFIED,
|
||||
],
|
||||
|
||||
'/does-not-exist': [HTTPStatus.NOT_FOUND],
|
||||
'/does-not-exist-2': [HTTPStatus.NOT_FOUND],
|
||||
|
|
|
|||