Merge branch 'master' of https://github.com/qutebrowser/qutebrowser into jay/visible-update-titles

This commit is contained in:
Jay Kamat 2018-09-29 12:29:50 -07:00
commit 322b053cbf
No known key found for this signature in database
GPG Key ID: 5D2E399600F4F7B5
17 changed files with 323 additions and 75 deletions

View File

@ -43,6 +43,10 @@ Added
- Running qutebrowser with QtWebKit or Qt < 5.9 now shows a warning (only
once), as support for those is going to be removed in a future release.
- New t[iI][hHu] default bindings (similar to `tsh` etc.) to toggle images.
- New `tabs.max_width` setting which allows to have a more "normal" look for
tabs.
- New `content.mute` setting which allows to mute pages (or all tabs) by
default.
Changed
~~~~~~~
@ -73,6 +77,11 @@ Changed
- Editing text in an external editor now simulates a JS "input" event, which
improves compatibility with websites reacting via JS to input.
- The `qute://settings` page is now properly sorted on Python 3.5.
- `:zoom`, `:zoom-in` and `:zoom-out` now have a `--quiet` switch which causes
them to not display a message.
- The `scrolling.bar` setting now takes three values instead of being a
boolean: `always`, `never`, and `when-searching` (which only displays it
while a search is active).
Fixed
~~~~~

View File

@ -1413,7 +1413,7 @@ Yank something to the clipboard or primary selection.
[[zoom]]
=== zoom
Syntax: +:zoom ['zoom']+
Syntax: +:zoom [*--quiet*] ['zoom']+
Set the zoom level for the current tab.
@ -1422,20 +1422,33 @@ The zoom can be given as argument or as [count]. If neither is given, the zoom i
==== positional arguments
* +'zoom'+: The zoom percentage to set.
==== optional arguments
* +*-q*+, +*--quiet*+: Don't show a zoom level message.
==== count
The zoom percentage to set.
[[zoom-in]]
=== zoom-in
Syntax: +:zoom-in [*--quiet*]+
Increase the zoom level for the current tab.
==== optional arguments
* +*-q*+, +*--quiet*+: Don't show a zoom level message.
==== count
How many steps to zoom in.
[[zoom-out]]
=== zoom-out
Syntax: +:zoom-out [*--quiet*]+
Decrease the zoom level for the current tab.
==== optional arguments
* +*-q*+, +*--quiet*+: Don't show a zoom level message.
==== count
How many steps to zoom out.

View File

@ -145,6 +145,7 @@
|<<content.local_storage,content.local_storage>>|Enable support for HTML 5 local storage and Web SQL.
|<<content.media_capture,content.media_capture>>|Allow websites to record audio/video.
|<<content.mouse_lock,content.mouse_lock>>|Allow websites to lock your mouse pointer.
|<<content.mute,content.mute>>|Automatically mute tabs.
|<<content.netrc_file,content.netrc_file>>|Netrc-file for HTTP authentication.
|<<content.notifications,content.notifications>>|Allow websites to show notifications.
|<<content.pdfjs,content.pdfjs>>|Allow pdf.js to view PDF files in the browser.
@ -231,7 +232,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.
|<<scrolling.bar,scrolling.bar>>|Show a scrollbar.
|<<scrolling.bar,scrolling.bar>>|When to show the scrollbar.
|<<scrolling.smooth,scrolling.smooth>>|Enable smooth scrolling for web pages.
|<<search.ignore_case,search.ignore_case>>|When to find text on a page case-insensitively.
|<<search.incremental,search.incremental>>|Find text on a page incrementally, renewing the search for each typed character.
@ -250,6 +251,7 @@
|<<tabs.indicator.padding,tabs.indicator.padding>>|Padding (in pixels) for tab indicators.
|<<tabs.indicator.width,tabs.indicator.width>>|Width (in pixels) of the progress indicator (0 to disable).
|<<tabs.last_close,tabs.last_close>>|How to behave when the last tab is closed.
|<<tabs.max_width,tabs.max_width>>|Maximum width (in pixels) of tabs (-1 for no maximum).
|<<tabs.min_width,tabs.min_width>>|Minimum width (in pixels) of tabs (-1 for the default minimum size behavior).
|<<tabs.mode_on_change,tabs.mode_on_change>>|When switching tabs, what input mode is applied.
|<<tabs.mousewheel_switching,tabs.mousewheel_switching>>|Switch between tabs using the mouse wheel.
@ -1915,6 +1917,17 @@ On QtWebEngine, this setting requires Qt 5.8 or newer.
On QtWebKit, this setting is unavailable.
[[content.mute]]
=== content.mute
Automatically mute tabs.
Note that if the `:tab-mute` command is used, the mute status for the affected tab is now controlled manually, and this setting doesn't have any effect.
This setting supports URL patterns.
Type: <<types,Bool>>
Default: +pass:[false]+
[[content.netrc_file]]
=== content.netrc_file
Netrc-file for HTTP authentication.
@ -2815,11 +2828,17 @@ This setting is only available with the QtWebEngine backend.
[[scrolling.bar]]
=== scrolling.bar
Show a scrollbar.
When to show the scrollbar.
Type: <<types,Bool>>
Type: <<types,String>>
Default: +pass:[false]+
Valid values:
* +always+: Always show the scrollbar.
* +never+: Never show the scrollbar.
* +when-searching+: Show the scrollbar when searching for text in the webpage. With the QtWebKit backend, this is equal to `never`.
Default: +pass:[when-searching]+
[[scrolling.smooth]]
=== scrolling.smooth
@ -3086,6 +3105,17 @@ Valid values:
Default: +pass:[ignore]+
[[tabs.max_width]]
=== tabs.max_width
Maximum width (in pixels) of tabs (-1 for no maximum).
This setting only applies when tabs are horizontal.
This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is False.
This setting may not apply properly if max_width is smaller than the minimum size of tab contents, or smaller than tabs.min_width.
Type: <<types,Int>>
Default: +pass:[-1]+
[[tabs.min_width]]
=== tabs.min_width
Minimum width (in pixels) of tabs (-1 for the default minimum size behavior).

View File

@ -248,10 +248,19 @@ class AbstractSearch(QObject):
this view.
_flags: The flags of the last search (needs to be set by subclasses).
_widget: The underlying WebView widget.
Signals:
finished: Emitted when a search was finished.
arg: True if the text was found, False otherwise.
cleared: Emitted when an existing search was cleared.
"""
def __init__(self, parent=None):
finished = pyqtSignal(bool)
cleared = pyqtSignal()
def __init__(self, tab, parent=None):
super().__init__(parent)
self._tab = tab
self._widget = None
self.text = None
self.search_displayed = False
@ -668,20 +677,27 @@ class AbstractAudio(QObject):
muted_changed = pyqtSignal(bool)
recently_audible_changed = pyqtSignal(bool)
def __init__(self, parent=None):
def __init__(self, tab, parent=None):
super().__init__(parent)
self._widget = None
self._tab = tab
def set_muted(self, muted: bool):
"""Set this tab as muted or not."""
def set_muted(self, muted: bool, override: bool = False):
"""Set this tab as muted or not.
Arguments:
override: If set to True, muting/unmuting was done manually and
overrides future automatic mute/unmute changes based on
the URL.
"""
raise NotImplementedError
def is_muted(self):
"""Whether this tab is muted."""
raise NotImplementedError
def toggle_muted(self):
self.set_muted(not self.is_muted())
def toggle_muted(self, *, override: bool = False):
self.set_muted(not self.is_muted(), override=override)
def is_recently_audible(self):
"""Whether this tab has had audio playing recently."""

View File

@ -870,37 +870,41 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def zoom_in(self, count=1):
def zoom_in(self, count=1, quiet=False):
"""Increase the zoom level for the current tab.
Args:
count: How many steps to zoom in.
quiet: Don't show a zoom level message.
"""
tab = self._current_widget()
try:
perc = tab.zoom.offset(count)
except ValueError as e:
raise cmdexc.CommandError(e)
message.info("Zoom level: {}%".format(int(perc)), replace=True)
if not quiet:
message.info("Zoom level: {}%".format(int(perc)), replace=True)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def zoom_out(self, count=1):
def zoom_out(self, count=1, quiet=False):
"""Decrease the zoom level for the current tab.
Args:
count: How many steps to zoom out.
quiet: Don't show a zoom level message.
"""
tab = self._current_widget()
try:
perc = tab.zoom.offset(-count)
except ValueError as e:
raise cmdexc.CommandError(e)
message.info("Zoom level: {}%".format(int(perc)), replace=True)
if not quiet:
message.info("Zoom level: {}%".format(int(perc)), replace=True)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def zoom(self, zoom=None, count=None):
def zoom(self, zoom=None, count=None, quiet=False):
"""Set the zoom level for the current tab.
The zoom can be given as argument or as [count]. If neither is
@ -910,6 +914,7 @@ class CommandDispatcher:
Args:
zoom: The zoom percentage to set.
count: The zoom percentage to set.
quiet: Don't show a zoom level message.
"""
if zoom is not None:
try:
@ -927,7 +932,8 @@ class CommandDispatcher:
tab.zoom.set_factor(float(level) / 100)
except ValueError:
raise cmdexc.CommandError("Can't zoom {}%!".format(level))
message.info("Zoom level: {}%".format(int(level)), replace=True)
if not quiet:
message.info("Zoom level: {}%".format(int(level)), replace=True)
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_only(self, prev=False, next_=False, force=False):
@ -2231,6 +2237,6 @@ class CommandDispatcher:
if tab is None:
return
try:
tab.audio.toggle_muted()
tab.audio.toggle_muted(override=True)
except browsertab.WebTabError as e:
raise cmdexc.CommandError(e)

View File

@ -274,7 +274,7 @@ def get_tab(win_id, target):
return tabbed_browser.tabopen(url=None, background=bg_tab)
def get_user_stylesheet():
def get_user_stylesheet(searching=False):
"""Get the combined user-stylesheet."""
css = ''
stylesheets = config.val.content.user_stylesheets
@ -283,7 +283,8 @@ def get_user_stylesheet():
with open(filename, 'r', encoding='utf-8') as f:
css += f.read()
if not config.val.scrolling.bar:
if (config.val.scrolling.bar == 'never' or
config.val.scrolling.bar == 'when-searching' and not searching):
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
return css

View File

@ -162,8 +162,8 @@ class WebEngineSearch(browsertab.AbstractSearch):
back yet.
"""
def __init__(self, parent=None):
super().__init__(parent)
def __init__(self, tab, parent=None):
super().__init__(tab, parent)
self._flags = QWebEnginePage.FindFlags(0)
self._pending_searches = 0
@ -191,8 +191,11 @@ class WebEngineSearch(browsertab.AbstractSearch):
flag_text = ''
log.webview.debug(' '.join([caller, found_text, text, flag_text])
.strip())
if callback is not None:
callback(found)
self.finished.emit(found)
self._widget.findText(text, flags, wrapped_callback)
def search(self, text, *, ignore_case='never', reverse=False,
@ -213,6 +216,8 @@ class WebEngineSearch(browsertab.AbstractSearch):
self._find(text, self._flags, result_cb, 'search')
def clear(self):
if self.search_displayed:
self.cleared.emit()
self.search_displayed = False
self._widget.findText('')
@ -637,14 +642,26 @@ class WebEngineElements(browsertab.AbstractElements):
class WebEngineAudio(browsertab.AbstractAudio):
"""QtWebEngine implemementations related to audio/muting."""
"""QtWebEngine implemementations related to audio/muting.
Attributes:
_overridden: Whether the user toggled muting manually.
If that's the case, we leave it alone.
"""
def __init__(self, tab, parent=None):
super().__init__(tab, parent)
self._overridden = False
def _connect_signals(self):
page = self._widget.page()
page.audioMutedChanged.connect(self.muted_changed)
page.recentlyAudibleChanged.connect(self.recently_audible_changed)
self._tab.url_changed.connect(self._on_url_changed)
config.instance.changed.connect(self._on_config_changed)
def set_muted(self, muted: bool):
def set_muted(self, muted: bool, override: bool = False):
self._overridden = override
page = self._widget.page()
page.setAudioMuted(muted)
@ -656,6 +673,17 @@ class WebEngineAudio(browsertab.AbstractAudio):
page = self._widget.page()
return page.recentlyAudible()
@pyqtSlot(QUrl)
def _on_url_changed(self, url):
if self._overridden:
return
mute = config.instance.get('content.mute', url=url)
self.set_muted(mute)
@config.change_filter('content.mute')
def _on_config_changed(self):
self._on_url_changed(self._tab.url())
class _WebEnginePermissions(QObject):
@ -812,17 +840,23 @@ class _WebEngineScripts(QObject):
self._greasemonkey = objreg.get('greasemonkey')
def connect_signals(self):
"""Connect signals to our private slots."""
config.instance.changed.connect(self._on_config_changed)
self._tab.search.cleared.connect(functools.partial(
self._update_stylesheet, searching=False))
self._tab.search.finished.connect(self._update_stylesheet)
@pyqtSlot(str)
def _on_config_changed(self, option):
if option in ['scrolling.bar', 'content.user_stylesheets']:
self._init_stylesheet()
self._update_stylesheet()
def _update_stylesheet(self):
@pyqtSlot(bool)
def _update_stylesheet(self, searching=False):
"""Update the custom stylesheet in existing tabs."""
css = shared.get_user_stylesheet()
css = shared.get_user_stylesheet(searching=searching)
code = javascript.assemble('stylesheet', 'set_css', css)
self._tab.run_js_async(code)
@ -991,16 +1025,16 @@ class WebEngineTab(browsertab.AbstractTab):
private=private, parent=parent)
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id,
private=private)
self.history = WebEngineHistory(self)
self.scroller = WebEngineScroller(self, parent=self)
self.history = WebEngineHistory(tab=self)
self.scroller = WebEngineScroller(tab=self, parent=self)
self.caret = WebEngineCaret(mode_manager=mode_manager,
tab=self, parent=self)
self.zoom = WebEngineZoom(tab=self, parent=self)
self.search = WebEngineSearch(parent=self)
self.search = WebEngineSearch(tab=self, parent=self)
self.printing = WebEnginePrinting(tab=self)
self.elements = WebEngineElements(tab=self)
self.action = WebEngineAction(tab=self)
self.audio = WebEngineAudio(parent=self)
self.audio = WebEngineAudio(tab=self, parent=self)
self._permissions = _WebEnginePermissions(tab=self, parent=self)
self._scripts = _WebEngineScripts(tab=self, parent=self)
# We're assigning settings in _set_widget

View File

@ -84,8 +84,8 @@ class WebKitSearch(browsertab.AbstractSearch):
"""QtWebKit implementations related to searching on the page."""
def __init__(self, parent=None):
super().__init__(parent)
def __init__(self, tab, parent=None):
super().__init__(tab, parent)
self._flags = QWebPage.FindFlags(0)
def _call_cb(self, callback, found, text, flags, caller):
@ -115,7 +115,11 @@ class WebKitSearch(browsertab.AbstractSearch):
if callback is not None:
QTimer.singleShot(0, functools.partial(callback, found))
self.finished.emit(found)
def clear(self):
if self.search_displayed:
self.cleared.emit()
self.search_displayed = False
# We first clear the marked text, then the highlights
self._widget.findText('')
@ -637,7 +641,7 @@ class WebKitAudio(browsertab.AbstractAudio):
"""Dummy handling of audio status for QtWebKit."""
def set_muted(self, muted: bool):
def set_muted(self, muted: bool, override: bool = False):
raise browsertab.WebTabError('Muting is not supported on QtWebKit!')
def is_muted(self):
@ -658,16 +662,16 @@ class WebKitTab(browsertab.AbstractTab):
private=private, tab=self)
if private:
self._make_private(widget)
self.history = WebKitHistory(self)
self.scroller = WebKitScroller(self, parent=self)
self.history = WebKitHistory(tab=self)
self.scroller = WebKitScroller(tab=self, parent=self)
self.caret = WebKitCaret(mode_manager=mode_manager,
tab=self, parent=self)
self.zoom = WebKitZoom(tab=self, parent=self)
self.search = WebKitSearch(parent=self)
self.search = WebKitSearch(tab=self, parent=self)
self.printing = WebKitPrinting(tab=self)
self.elements = WebKitElements(tab=self)
self.action = WebKitAction(tab=self)
self.audio = WebKitAudio(parent=self)
self.audio = WebKitAudio(tab=self, parent=self)
# We're assigning settings in _set_widget
self.settings = webkitsettings.WebKitSettings(settings=None)
self._set_widget(widget)

View File

@ -312,10 +312,14 @@ class Config(QObject):
name, deleted=deleted, renamed=renamed)
raise exception from None
def get(self, name, url=None):
"""Get the given setting converted for Python code."""
def get(self, name, url=None, *, fallback=True):
"""Get the given setting converted for Python code.
Args:
fallback: Use the global value if there's no URL-specific one.
"""
opt = self.get_opt(name)
obj = self.get_obj(name, url=url)
obj = self.get_obj(name, url=url, fallback=fallback)
return opt.typ.to_py(obj)
def _maybe_copy(self, value):
@ -329,14 +333,14 @@ class Config(QObject):
assert value.__hash__ is not None, value
return value
def get_obj(self, name, *, url=None):
def get_obj(self, name, *, url=None, fallback=True):
"""Get the given setting as object (for YAML/config.py).
Note that the returned values are not watched for mutation.
If a URL is given, return the value which should be used for that URL.
"""
self.get_opt(name) # To make sure it exists
value = self._values[name].get_for_url(url)
value = self._values[name].get_for_url(url, fallback=fallback)
return self._maybe_copy(value)
def get_obj_for_pattern(self, name, *, pattern):

View File

@ -806,6 +806,17 @@ content.xss_auditing:
Suspicious scripts will be blocked and reported in the inspector's
JavaScript console.
content.mute:
default: false
type: Bool
supports_pattern: true
desc: >-
Automatically mute tabs.
Note that if the `:tab-mute` command is used, the mute status for the
affected tab is now controlled manually, and this setting doesn't have any
effect.
# emacs: '
## completion
@ -1271,9 +1282,15 @@ prompt.radius:
## scrolling
scrolling.bar:
type: Bool
default: false
desc: Show a scrollbar.
type:
name: String
valid_values:
- always: Always show the scrollbar.
- never: Never show the scrollbar.
- when-searching: Show the scrollbar when searching for text in the
webpage. With the QtWebKit backend, this is equal to `never`.
default: when-searching
desc: When to show the scrollbar.
scrolling.smooth:
type: Bool
@ -1602,6 +1619,23 @@ tabs.min_width:
This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is False.
tabs.max_width:
default: -1
type:
name: Int
minval: -1
maxval: maxint
desc: >-
Maximum width (in pixels) of tabs (-1 for no maximum).
This setting only applies when tabs are horizontal.
This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is
False.
This setting may not apply properly if max_width is smaller than the
minimum size of tab contents, or smaller than tabs.min_width.
tabs.width.indicator:
renamed: tabs.indicator.width

View File

@ -292,6 +292,8 @@ class YamlConfig(QObject):
self._mark_changed()
self._migrate_bool(settings, 'tabs.favicons.show', 'always', 'never')
self._migrate_bool(settings, 'scrolling.bar',
'when-searching', 'never')
self._migrate_bool(settings, 'qt.force_software_rendering',
'software-opengl', 'none')

View File

@ -60,7 +60,7 @@ from PyQt5.QtGui import QColor, QFont
from PyQt5.QtWidgets import QTabWidget, QTabBar
from qutebrowser.commands import cmdutils
from qutebrowser.config import configexc
from qutebrowser.config import configexc, configutils
from qutebrowser.utils import standarddir, utils, qtutils, urlutils, urlmatch
from qutebrowser.keyinput import keyutils
@ -149,6 +149,9 @@ class BaseType:
value: The value to check.
pytype: A Python type to check the value against.
"""
if value is configutils.UNSET:
return
if (value is None or (pytype == list and value == []) or
(pytype == dict and value == {})):
if not self.none_ok:
@ -309,7 +312,9 @@ class MappingType(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
self._validate_valid_values(value.lower())
return self.MAPPING[value.lower()]
@ -367,7 +372,9 @@ class String(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
self._validate_encoding(value)
@ -399,7 +406,9 @@ class UniqueCharString(String):
def to_py(self, value):
value = super().to_py(value)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
# Check for duplicate values
@ -455,7 +464,9 @@ class List(BaseType):
def to_py(self, value):
self._basic_py_validation(value, list)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return []
for val in value:
@ -534,6 +545,9 @@ class ListOrValue(BaseType):
return value
def to_py(self, value):
if value is configutils.UNSET:
return value
try:
return [self.valtype.to_py(value)]
except configexc.ValidationError:
@ -577,7 +591,8 @@ class FlagList(List):
def to_py(self, value):
vals = super().to_py(value)
self._check_duplicates(vals)
if vals is not configutils.UNSET:
self._check_duplicates(vals)
return vals
def complete(self):
@ -764,7 +779,9 @@ class Perc(_Numeric):
def to_py(self, value):
self._basic_py_validation(value, (float, int, str))
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
if isinstance(value, str):
@ -907,7 +924,9 @@ class QtColor(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
color = QColor(value)
@ -936,7 +955,9 @@ class QssColor(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
functions = ['rgb', 'rgba', 'hsv', 'hsva', 'qlineargradient',
@ -981,7 +1002,9 @@ class Font(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
if not self.font_regex.fullmatch(value): # pragma: no cover
@ -1000,7 +1023,9 @@ class FontFamily(Font):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
match = self.font_regex.fullmatch(value)
@ -1024,7 +1049,9 @@ class QtFont(Font):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
style_map = {
@ -1136,7 +1163,9 @@ class Regex(BaseType):
def to_py(self, value):
"""Get a compiled regex from either a string or a regex object."""
self._basic_py_validation(value, (str, self._regex_type))
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
elif isinstance(value, str):
return self._compile_regex(value)
@ -1214,7 +1243,9 @@ class Dict(BaseType):
def to_py(self, value):
self._basic_py_validation(value, dict)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return self._fill_fixed_keys({})
self._validate_keys(value)
@ -1256,7 +1287,9 @@ class File(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
value = os.path.expanduser(value)
@ -1282,7 +1315,9 @@ class Directory(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
value = os.path.expandvars(value)
value = os.path.expanduser(value)
@ -1309,7 +1344,9 @@ class FormatString(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
try:
@ -1341,8 +1378,10 @@ class ShellCommand(List):
def to_py(self, value):
value = super().to_py(value)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return []
if (self.placeholder and
'{}' not in ' '.join(value) and
@ -1365,7 +1404,9 @@ class Proxy(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
try:
@ -1401,7 +1442,9 @@ class SearchEngineUrl(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
if not ('{}' in value or '{0}' in value):
@ -1429,7 +1472,9 @@ class FuzzyUrl(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
try:
@ -1463,6 +1508,9 @@ class Padding(Dict):
def to_py(self, value):
d = super().to_py(value)
if d is configutils.UNSET:
return d
return PaddingValues(**d)
@ -1472,7 +1520,9 @@ class Encoding(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
try:
codecs.lookup(value)
@ -1529,7 +1579,9 @@ class Url(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
qurl = QUrl.fromUserInput(value)
@ -1545,7 +1597,9 @@ class SessionName(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
if value.startswith('_'):
raise configexc.ValidationError(value, "may not start with '_'!")
@ -1593,8 +1647,10 @@ class ConfirmQuit(FlagList):
def to_py(self, value):
values = super().to_py(value)
if not values:
if values is configutils.UNSET:
return values
elif not values:
return []
# Never can't be set with other options
if 'never' in values and len(values) > 1:
@ -1630,7 +1686,9 @@ class TimestampTemplate(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
try:
@ -1654,7 +1712,9 @@ class Key(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
try:
@ -1673,7 +1733,9 @@ class UrlPattern(BaseType):
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
if value is configutils.UNSET:
return value
elif not value:
return None
try:

View File

@ -642,6 +642,9 @@ class TabBar(QTabBar):
# Qt shrink us down. If for some reason (tests, bugs)
# self.width() gives 0, use a sane min of 10 px
width = max(self.width(), 10)
max_width = config.cache['tabs.max_width']
if max_width > 0:
width = min(max_width, width)
size = QSize(width, height)
qtutils.ensure_valid(size)
return size

View File

@ -257,7 +257,7 @@ class FakeWebTab(browsertab.AbstractTab):
self.history = FakeWebTabHistory(self, can_go_back=can_go_back,
can_go_forward=can_go_forward)
self.scroller = FakeWebTabScroller(self, scroll_pos_perc)
self.audio = FakeWebTabAudio()
self.audio = FakeWebTabAudio(self)
wrapped = QWidget()
self._layout.wrap(self, wrapped)

View File

@ -480,6 +480,17 @@ class TestConfig:
conf.set_obj(name, False, pattern=pattern)
assert conf.get(name, url=QUrl('https://example.com/')) is False
@pytest.mark.parametrize('fallback, expected', [
(True, True),
(False, configutils.UNSET)
])
def test_get_for_url_fallback(self, conf, fallback, expected):
"""Test conf.get() with an URL and fallback."""
value = conf.get('content.javascript.enabled',
url=QUrl('https://example.com/'),
fallback=fallback)
assert value is expected
@pytest.mark.parametrize('value', [{}, {'normal': {'a': 'nop'}}])
def test_get_bindings(self, config_stub, conf, value):
"""Test conf.get() with bindings which have missing keys."""

View File

@ -34,7 +34,7 @@ from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QColor, QFont
from PyQt5.QtNetwork import QNetworkProxy
from qutebrowser.config import configtypes, configexc
from qutebrowser.config import configtypes, configexc, configutils
from qutebrowser.utils import debug, utils, qtutils, urlmatch
from qutebrowser.browser.network import pac
from qutebrowser.keyinput import keyutils
@ -274,6 +274,11 @@ class TestAll:
with pytest.raises(configexc.ValidationError):
meth(value)
@pytest.mark.parametrize('none_ok', [True, False])
def test_unset(self, klass, none_ok):
typ = klass(none_ok=none_ok)
assert typ.to_py(configutils.UNSET) is configutils.UNSET
def test_to_str_none(self, klass):
assert klass().to_str(None) == ''

View File

@ -38,6 +38,7 @@ class TestTabWidget:
qtbot.addWidget(w)
monkeypatch.setattr(tabwidget.objects, 'backend',
usertypes.Backend.QtWebKit)
w.show()
return w
def test_small_icon_doesnt_crash(self, widget, qtbot, fake_web_tab):
@ -120,6 +121,19 @@ class TestTabWidget:
benchmark(widget.update_tab_titles)
def test_tab_min_width(self, widget, fake_web_tab, config_stub, qtbot):
widget.addTab(fake_web_tab(), 'foobar')
widget.addTab(fake_web_tab(), 'foobar1')
min_size = widget.tabBar().tabRect(0).width() + 10
config_stub.val.tabs.min_width = min_size
assert widget.tabBar().tabRect(0).width() == min_size
def test_tab_max_width(self, widget, fake_web_tab, config_stub, qtbot):
widget.addTab(fake_web_tab(), 'foobar')
max_size = widget.tabBar().tabRect(0).width() - 10
config_stub.val.tabs.max_width = max_size
assert widget.tabBar().tabRect(0).width() == max_size
@pytest.mark.parametrize("num_tabs", [4, 100])
@pytest.mark.parametrize("rev", [True, False])
def test_add_remove_tab_benchmark(self, benchmark, widget,