Initial work on new private browsing

This commit is contained in:
Florian Bruhin 2017-04-24 21:08:00 +02:00
parent 1d4c9d3b3f
commit 1c50377c0a
28 changed files with 173 additions and 239 deletions

View File

@ -195,7 +195,7 @@ def _process_args(args):
session_manager = objreg.get('session-manager') session_manager = objreg.get('session-manager')
if not session_manager.did_load: if not session_manager.did_load:
log.init.debug("Initializing main window...") log.init.debug("Initializing main window...")
window = mainwindow.MainWindow() window = mainwindow.MainWindow(private=None)
if not args.nowindow: if not args.nowindow:
window.show() window.show()
qApp.setActiveWindow(window) qApp.setActiveWindow(window)

View File

@ -35,11 +35,12 @@ from qutebrowser.browser import mouse, hints
tab_id_gen = itertools.count(0) tab_id_gen = itertools.count(0)
def create(win_id, parent=None): def create(win_id, private, parent=None):
"""Get a QtWebKit/QtWebEngine tab object. """Get a QtWebKit/QtWebEngine tab object.
Args: Args:
win_id: The window ID where the tab will be shown. win_id: The window ID where the tab will be shown.
private: Whether the tab is a private/off the record tab.
parent: The Qt parent to set. parent: The Qt parent to set.
""" """
# Importing modules here so we don't depend on QtWebEngine without the # Importing modules here so we don't depend on QtWebEngine without the
@ -51,7 +52,8 @@ def create(win_id, parent=None):
else: else:
from qutebrowser.browser.webkit import webkittab from qutebrowser.browser.webkit import webkittab
tab_class = webkittab.WebKitTab tab_class = webkittab.WebKitTab
return tab_class(win_id=win_id, mode_manager=mode_manager, parent=parent) return tab_class(win_id=win_id, mode_manager=mode_manager, private=private,
parent=parent)
def init(): def init():
@ -542,6 +544,7 @@ class AbstractTab(QWidget):
Attributes: Attributes:
history: The AbstractHistory for the current tab. history: The AbstractHistory for the current tab.
registry: The ObjectRegistry associated with this tab. registry: The ObjectRegistry associated with this tab.
private: Whether private browsing is turned on for this tab.
_load_status: loading status of this page _load_status: loading status of this page
Accessible via load_status() method. Accessible via load_status() method.
@ -581,7 +584,8 @@ class AbstractTab(QWidget):
fullscreen_requested = pyqtSignal(bool) fullscreen_requested = pyqtSignal(bool)
renderer_process_terminated = pyqtSignal(TerminationStatus, int) renderer_process_terminated = pyqtSignal(TerminationStatus, int)
def __init__(self, win_id, mode_manager, parent=None): def __init__(self, *, win_id, mode_manager, private, parent=None):
self.private = private
self.win_id = win_id self.win_id = win_id
self.tab_id = next(tab_id_gen) self.tab_id = next(tab_id_gen)
super().__init__(parent) super().__init__(parent)

View File

@ -67,10 +67,10 @@ class CommandDispatcher:
def __repr__(self): def __repr__(self):
return utils.get_repr(self) return utils.get_repr(self)
def _new_tabbed_browser(self): def _new_tabbed_browser(self, private):
"""Get a tabbed-browser from a new window.""" """Get a tabbed-browser from a new window."""
from qutebrowser.mainwindow import mainwindow from qutebrowser.mainwindow import mainwindow
new_window = mainwindow.MainWindow() new_window = mainwindow.MainWindow(private=private)
new_window.show() new_window.show()
return new_window.tabbed_browser return new_window.tabbed_browser
@ -110,7 +110,7 @@ class CommandDispatcher:
return widget return widget
def _open(self, url, tab=False, background=False, window=False, def _open(self, url, tab=False, background=False, window=False,
explicit=True): explicit=True, private=None):
"""Helper function to open a page. """Helper function to open a page.
Args: Args:
@ -118,12 +118,17 @@ class CommandDispatcher:
tab: Whether to open in a new tab. tab: Whether to open in a new tab.
background: Whether to open in the background. background: Whether to open in the background.
window: Whether to open in a new window window: Whether to open in a new window
private: If opening a new window, open it in private browsing mode.
If not given, inherit the current window's mode.
""" """
urlutils.raise_cmdexc_if_invalid(url) urlutils.raise_cmdexc_if_invalid(url)
tabbed_browser = self._tabbed_browser tabbed_browser = self._tabbed_browser
cmdutils.check_exclusive((tab, background, window), 'tbw') cmdutils.check_exclusive((tab, background, window), 'tbw')
if private is None:
private = self._tabbed_browser.private
if window: if window:
tabbed_browser = self._new_tabbed_browser() tabbed_browser = self._new_tabbed_browser(private)
tabbed_browser.tabopen(url) tabbed_browser.tabopen(url)
elif tab: elif tab:
tabbed_browser.tabopen(url, background=False, explicit=explicit) tabbed_browser.tabopen(url, background=False, explicit=explicit)
@ -228,7 +233,8 @@ class CommandDispatcher:
@cmdutils.argument('url', completion=usertypes.Completion.url) @cmdutils.argument('url', completion=usertypes.Completion.url)
@cmdutils.argument('count', count=True) @cmdutils.argument('count', count=True)
def openurl(self, url=None, implicit=False, def openurl(self, url=None, implicit=False,
bg=False, tab=False, window=False, count=None, secure=False): bg=False, tab=False, window=False, count=None, secure=False,
private=False):
"""Open a URL in the current/[count]th tab. """Open a URL in the current/[count]th tab.
If the URL contains newlines, each line gets opened in its own tab. If the URL contains newlines, each line gets opened in its own tab.
@ -242,12 +248,25 @@ class CommandDispatcher:
clicking on a link). clicking on a link).
count: The tab index to open the URL in, or None. count: The tab index to open the URL in, or None.
secure: Force HTTPS. secure: Force HTTPS.
private: Open a new window in private browsing mode.
""" """
if url is None: if url is None:
urls = [config.get('general', 'default-page')] urls = [config.get('general', 'default-page')]
else: else:
urls = self._parse_url_input(url) urls = self._parse_url_input(url)
if private:
try:
from PyQt5.QtWebKit import qWebKitVersion
except ImportError:
pass
else:
# WORKAROUND for https://github.com/annulen/webkit/issues/54
if qtutils.is_qtwebkit_ng(qWebKitVersion()):
message.warning("Private browsing is not fully "
"implemented by QtWebKit-NG!")
window = True
for i, cur_url in enumerate(urls): for i, cur_url in enumerate(urls):
if secure: if secure:
cur_url.setScheme('https') cur_url.setScheme('https')
@ -255,7 +274,8 @@ class CommandDispatcher:
tab = False tab = False
bg = True bg = True
if tab or bg or window: if tab or bg or window:
self._open(cur_url, tab, bg, window, not implicit) self._open(cur_url, tab, bg, window, explicit=not implicit,
private=private)
else: else:
curtab = self._cntwidget(count) curtab = self._cntwidget(count)
if curtab is None: if curtab is None:
@ -430,7 +450,8 @@ class CommandDispatcher:
# The new tab could be in a new tabbed_browser (e.g. because of # The new tab could be in a new tabbed_browser (e.g. because of
# tabs-are-windows being set) # tabs-are-windows being set)
if window: if window:
new_tabbed_browser = self._new_tabbed_browser() new_tabbed_browser = self._new_tabbed_browser(
private=self._tabbed_browser.private)
else: else:
new_tabbed_browser = self._tabbed_browser new_tabbed_browser = self._tabbed_browser
newtab = new_tabbed_browser.tabopen(background=bg, explicit=True) newtab = new_tabbed_browser.tabopen(background=bg, explicit=True)

View File

@ -274,8 +274,7 @@ class WebHistory(QObject):
(hidden in completion) (hidden in completion)
atime: Override the atime used to add the entry atime: Override the atime used to add the entry
""" """
if config.get('general', 'private-browsing'): assert not config.get('general', 'private-browsing')
return
if not url.isValid(): if not url.isValid():
log.misc.warning("Ignoring invalid URL being added to history") log.misc.warning("Ignoring invalid URL being added to history")
return return

View File

@ -128,17 +128,19 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
return return
qtutils.ensure_valid(url) qtutils.ensure_valid(url)
cur_tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if window: if window:
from qutebrowser.mainwindow import mainwindow from qutebrowser.mainwindow import mainwindow
new_window = mainwindow.MainWindow() new_window = mainwindow.MainWindow(
private=cur_tabbed_browser.private)
new_window.show() new_window.show()
tabbed_browser = objreg.get('tabbed-browser', scope='window', tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=new_window.win_id) window=new_window.win_id)
tabbed_browser.tabopen(url, background=False) tabbed_browser.tabopen(url, background=False)
elif tab: elif tab:
tabbed_browser = objreg.get('tabbed-browser', scope='window', cur_tabbed_browser.tabopen(url, background=background)
window=win_id)
tabbed_browser.tabopen(url, background=background)
else: else:
browsertab.openurl(url) browsertab.openurl(url)

View File

@ -366,7 +366,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
def __init__(self, win_id, parent=None): def __init__(self, win_id, parent=None):
super().__init__(parent) super().__init__(parent)
self._networkmanager = networkmanager.NetworkManager( self._networkmanager = networkmanager.NetworkManager(
win_id, None, self) win_id=win_id, tab_id=None, private=False, parent=self)
@pyqtSlot('QUrl') @pyqtSlot('QUrl')
def get(self, url, *, user_agent=None, **kwargs): def get(self, url, *, user_agent=None, **kwargs):

View File

@ -216,8 +216,10 @@ def get_tab(win_id, target):
win_id = win_id win_id = win_id
bg_tab = True bg_tab = True
elif target == usertypes.ClickTarget.window: elif target == usertypes.ClickTarget.window:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
from qutebrowser.mainwindow import mainwindow from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow() window = mainwindow.MainWindow(private=tabbed_browser.private)
window.show() window.show()
win_id = window.win_id win_id = window.win_id
bg_tab = False bg_tab = False

View File

@ -379,15 +379,16 @@ class AbstractWebElement(collections.abc.MutableMapping):
self._click_fake_event(click_target) self._click_fake_event(click_target)
return return
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._tab.win_id)
if click_target in [usertypes.ClickTarget.tab, if click_target in [usertypes.ClickTarget.tab,
usertypes.ClickTarget.tab_bg]: usertypes.ClickTarget.tab_bg]:
background = click_target == usertypes.ClickTarget.tab_bg background = click_target == usertypes.ClickTarget.tab_bg
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._tab.win_id)
tabbed_browser.tabopen(url, background=background) tabbed_browser.tabopen(url, background=background)
elif click_target == usertypes.ClickTarget.window: elif click_target == usertypes.ClickTarget.window:
from qutebrowser.mainwindow import mainwindow from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow() window = mainwindow.MainWindow(private=tabbed_browser.private)
window.show() window.show()
window.tabbed_browser.tabopen(url) window.tabbed_browser.tabopen(url)
else: else:

View File

@ -521,9 +521,11 @@ class WebEngineTab(browsertab.AbstractTab):
"""A QtWebEngine tab in the browser.""" """A QtWebEngine tab in the browser."""
def __init__(self, win_id, mode_manager, parent=None): def __init__(self, *, win_id, mode_manager, private, parent=None):
# FIXME
assert not private
super().__init__(win_id=win_id, mode_manager=mode_manager, super().__init__(win_id=win_id, mode_manager=mode_manager,
parent=parent) private=private, parent=parent)
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id) widget = webview.WebEngineView(tabdata=self.data, win_id=win_id)
self.history = WebEngineHistory(self) self.history = WebEngineHistory(self)
self.scroller = WebEngineScroller(self, parent=self) self.scroller = WebEngineScroller(self, parent=self)

View File

@ -21,8 +21,7 @@
import os.path import os.path
from PyQt5.QtCore import pyqtSlot from PyQt5.QtNetwork import QNetworkDiskCache
from PyQt5.QtNetwork import QNetworkDiskCache, QNetworkCacheMetaData
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.utils import utils, objreg, qtutils from qutebrowser.utils import utils, objreg, qtutils
@ -30,24 +29,21 @@ from qutebrowser.utils import utils, objreg, qtutils
class DiskCache(QNetworkDiskCache): class DiskCache(QNetworkDiskCache):
"""Disk cache which sets correct cache dir and size. """Disk cache which sets correct cache dir and size."""
Attributes:
_activated: Whether the cache should be used.
_cache_dir: The base directory for cache files (standarddir.cache())
"""
def __init__(self, cache_dir, parent=None): def __init__(self, cache_dir, parent=None):
super().__init__(parent) super().__init__(parent)
self._cache_dir = cache_dir assert not config.get('general', 'private-browsing')
self._maybe_activate() self.setCacheDirectory(os.path.join(cache_dir, 'http'))
objreg.get('config').changed.connect(self.on_config_changed) self._set_cache_size()
objreg.get('config').changed.connect(self._set_cache_size)
def __repr__(self): def __repr__(self):
return utils.get_repr(self, size=self.cacheSize(), return utils.get_repr(self, size=self.cacheSize(),
maxsize=self.maximumCacheSize(), maxsize=self.maximumCacheSize(),
path=self.cacheDirectory()) path=self.cacheDirectory())
@config.change_filter('storage', 'cache-size')
def _set_cache_size(self): def _set_cache_size(self):
"""Set the cache size based on the config.""" """Set the cache size based on the config."""
size = config.get('storage', 'cache-size') size = config.get('storage', 'cache-size')
@ -58,128 +54,3 @@ class DiskCache(QNetworkDiskCache):
not qtutils.version_check('5.9')): # pragma: no cover not qtutils.version_check('5.9')): # pragma: no cover
size = 0 size = 0
self.setMaximumCacheSize(size) self.setMaximumCacheSize(size)
def _maybe_activate(self):
"""Activate/deactivate the cache based on the config."""
if config.get('general', 'private-browsing'):
self._activated = False
else:
self._activated = True
self.setCacheDirectory(os.path.join(self._cache_dir, 'http'))
self._set_cache_size()
@pyqtSlot(str, str)
def on_config_changed(self, section, option):
"""Update cache size/activated if the config was changed."""
if (section, option) == ('storage', 'cache-size'):
self._set_cache_size()
elif (section, option) == ('general', # pragma: no branch
'private-browsing'):
self._maybe_activate()
def cacheSize(self):
"""Return the current size taken up by the cache.
Return:
An int.
"""
if self._activated:
return super().cacheSize()
else:
return 0
def fileMetaData(self, filename):
"""Return the QNetworkCacheMetaData for the cache file filename.
Args:
filename: The file name as a string.
Return:
A QNetworkCacheMetaData object.
"""
if self._activated:
return super().fileMetaData(filename)
else:
return QNetworkCacheMetaData()
def data(self, url):
"""Return the data associated with url.
Args:
url: A QUrl.
return:
A QIODevice or None.
"""
if self._activated:
return super().data(url)
else:
return None
def insert(self, device):
"""Insert the data in device and the prepared meta data into the cache.
Args:
device: A QIODevice.
"""
if self._activated:
super().insert(device)
else:
return None
def metaData(self, url):
"""Return the meta data for the url url.
Args:
url: A QUrl.
Return:
A QNetworkCacheMetaData object.
"""
if self._activated:
return super().metaData(url)
else:
return QNetworkCacheMetaData()
def prepare(self, meta_data):
"""Return the device that should be populated with the data.
Args:
meta_data: A QNetworkCacheMetaData object.
Return:
A QIODevice or None.
"""
if self._activated:
return super().prepare(meta_data)
else:
return None
def remove(self, url):
"""Remove the cache entry for url.
Return:
True on success, False otherwise.
"""
if self._activated:
return super().remove(url)
else:
return False
def updateMetaData(self, meta_data):
"""Update the cache meta date for the meta_data's url to meta_data.
Args:
meta_data: A QNetworkCacheMetaData object.
"""
if self._activated:
super().updateMetaData(meta_data)
else:
return
def clear(self):
"""Remove all items from the cache."""
if self._activated:
super().clear()
else:
return

View File

@ -128,6 +128,7 @@ class NetworkManager(QNetworkAccessManager):
_tab_id: The tab ID this NetworkManager is associated with. _tab_id: The tab ID this NetworkManager is associated with.
_rejected_ssl_errors: A {QUrl: [SslError]} dict of rejected errors. _rejected_ssl_errors: A {QUrl: [SslError]} dict of rejected errors.
_accepted_ssl_errors: A {QUrl: [SslError]} dict of accepted errors. _accepted_ssl_errors: A {QUrl: [SslError]} dict of accepted errors.
_private: Whether we're in private browsing mode.
Signals: Signals:
shutting_down: Emitted when the QNAM is shutting down. shutting_down: Emitted when the QNAM is shutting down.
@ -135,7 +136,7 @@ class NetworkManager(QNetworkAccessManager):
shutting_down = pyqtSignal() shutting_down = pyqtSignal()
def __init__(self, win_id, tab_id, parent=None): def __init__(self, *, win_id, tab_id, private, parent=None):
log.init.debug("Initializing NetworkManager") log.init.debug("Initializing NetworkManager")
with log.disable_qt_msghandler(): with log.disable_qt_msghandler():
# WORKAROUND for a hang when a message is printed - See: # WORKAROUND for a hang when a message is printed - See:
@ -146,11 +147,12 @@ class NetworkManager(QNetworkAccessManager):
self._win_id = win_id self._win_id = win_id
self._tab_id = tab_id self._tab_id = tab_id
self._requests = [] self._requests = []
self._private = private
self._scheme_handlers = { self._scheme_handlers = {
'qute': webkitqutescheme.QuteSchemeHandler(win_id), 'qute': webkitqutescheme.QuteSchemeHandler(win_id),
'file': filescheme.FileSchemeHandler(win_id), 'file': filescheme.FileSchemeHandler(win_id),
} }
self._set_cookiejar(private=config.get('general', 'private-browsing')) self._set_cookiejar()
self._set_cache() self._set_cache()
self.sslErrors.connect(self.on_ssl_errors) self.sslErrors.connect(self.on_ssl_errors)
self._rejected_ssl_errors = collections.defaultdict(list) self._rejected_ssl_errors = collections.defaultdict(list)
@ -158,15 +160,10 @@ class NetworkManager(QNetworkAccessManager):
self.authenticationRequired.connect(self.on_authentication_required) self.authenticationRequired.connect(self.on_authentication_required)
self.proxyAuthenticationRequired.connect( self.proxyAuthenticationRequired.connect(
self.on_proxy_authentication_required) self.on_proxy_authentication_required)
objreg.get('config').changed.connect(self.on_config_changed)
def _set_cookiejar(self, private=False): def _set_cookiejar(self):
"""Set the cookie jar of the NetworkManager correctly. """Set the cookie jar of the NetworkManager correctly."""
if self._private:
Args:
private: Whether we're currently in private browsing mode.
"""
if private:
cookie_jar = objreg.get('ram-cookie-jar') cookie_jar = objreg.get('ram-cookie-jar')
else: else:
cookie_jar = objreg.get('cookie-jar') cookie_jar = objreg.get('cookie-jar')
@ -178,11 +175,9 @@ class NetworkManager(QNetworkAccessManager):
cookie_jar.setParent(app) cookie_jar.setParent(app)
def _set_cache(self): def _set_cache(self):
"""Set the cache of the NetworkManager correctly. """Set the cache of the NetworkManager correctly."""
if self._private:
We can't switch the whole cache in private mode because QNAM would return
delete the old cache.
"""
# We have a shared cache - we restore its parent so we don't take # We have a shared cache - we restore its parent so we don't take
# ownership of it. # ownership of it.
app = QCoreApplication.instance() app = QCoreApplication.instance()
@ -324,17 +319,6 @@ class NetworkManager(QNetworkAccessManager):
authenticator.setPassword(answer.password) authenticator.setPassword(answer.password)
_proxy_auth_cache[proxy_id] = answer _proxy_auth_cache[proxy_id] = answer
@config.change_filter('general', 'private-browsing')
def on_config_changed(self):
"""Set cookie jar when entering/leaving private browsing mode."""
private_browsing = config.get('general', 'private-browsing')
if private_browsing:
# switched from normal mode to private mode
self._set_cookiejar(private=True)
else:
# switched from private mode to normal mode
self._set_cookiejar()
@pyqtSlot() @pyqtSlot()
def on_adopted_download_destroyed(self): def on_adopted_download_destroyed(self):
"""Check if we can clean up if an adopted download was destroyed. """Check if we can clean up if an adopted download was destroyed.

View File

@ -89,20 +89,19 @@ def _set_user_stylesheet():
def _init_private_browsing(): def _init_private_browsing():
if config.get('general', 'private-browsing'): if qtutils.is_qtwebkit_ng():
if qtutils.is_qtwebkit_ng(): # WORKAROUND for https://github.com/annulen/webkit/issues/54
message.warning("Private browsing is not fully implemented by " message.warning("Private browsing is not fully implemented by QtWebKit-NG!")
"QtWebKit-NG!") elif not qtutils.version_check('5.4.2'):
# WORKAROUND for https://codereview.qt-project.org/#/c/108936/
# Won't work when private browsing is not enabled globally, but that's
# the best we can do...
QWebSettings.setIconDatabasePath('') QWebSettings.setIconDatabasePath('')
else:
QWebSettings.setIconDatabasePath(standarddir.cache())
def update_settings(section, option): def update_settings(section, option):
"""Update global settings when qwebsettings changed.""" """Update global settings when qwebsettings changed."""
if (section, option) == ('general', 'private-browsing'): if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
_init_private_browsing()
elif section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
_set_user_stylesheet() _set_user_stylesheet()
websettings.update_mappings(MAPPINGS, section, option) websettings.update_mappings(MAPPINGS, section, option)
@ -113,8 +112,7 @@ def init(_args):
cache_path = standarddir.cache() cache_path = standarddir.cache()
data_path = standarddir.data() data_path = standarddir.data()
_init_private_browsing() QWebSettings.setIconDatabasePath(standarddir.cache())
QWebSettings.setOfflineWebApplicationCachePath( QWebSettings.setOfflineWebApplicationCachePath(
os.path.join(cache_path, 'application-cache')) os.path.join(cache_path, 'application-cache'))
QWebSettings.globalSettings().setLocalStoragePath( QWebSettings.globalSettings().setLocalStoragePath(
@ -122,6 +120,9 @@ def init(_args):
QWebSettings.setOfflineStoragePath( QWebSettings.setOfflineStoragePath(
os.path.join(data_path, 'offline-storage')) os.path.join(data_path, 'offline-storage'))
if config.get('general', 'private-browsing'):
_init_private_browsing()
websettings.init_mappings(MAPPINGS) websettings.init_mappings(MAPPINGS)
_set_user_stylesheet() _set_user_stylesheet()
objreg.get('config').changed.connect(update_settings) objreg.get('config').changed.connect(update_settings)
@ -254,8 +255,6 @@ MAPPINGS = {
setter=QWebSettings.setOfflineWebApplicationCacheQuota), setter=QWebSettings.setOfflineWebApplicationCacheQuota),
}, },
'general': { 'general': {
'private-browsing':
Attribute(QWebSettings.PrivateBrowsingEnabled),
'developer-extras': 'developer-extras':
Attribute(QWebSettings.DeveloperExtrasEnabled), Attribute(QWebSettings.DeveloperExtrasEnabled),
'print-element-backgrounds': 'print-element-backgrounds':

View File

@ -629,10 +629,13 @@ class WebKitTab(browsertab.AbstractTab):
"""A QtWebKit tab in the browser.""" """A QtWebKit tab in the browser."""
def __init__(self, win_id, mode_manager, parent=None): def __init__(self, *, win_id, mode_manager, private, parent=None):
super().__init__(win_id=win_id, mode_manager=mode_manager, super().__init__(win_id=win_id, mode_manager=mode_manager,
parent=parent) private=private, parent=parent)
widget = webview.WebView(win_id, self.tab_id, tab=self) widget = webview.WebView(win_id=win_id, tab_id=self.tab_id,
private=private, tab=self)
if private:
self._make_private(widget)
self.history = WebKitHistory(self) self.history = WebKitHistory(self)
self.scroller = WebKitScroller(self, parent=self) self.scroller = WebKitScroller(self, parent=self)
self.caret = WebKitCaret(win_id=win_id, mode_manager=mode_manager, self.caret = WebKitCaret(win_id=win_id, mode_manager=mode_manager,
@ -649,6 +652,10 @@ class WebKitTab(browsertab.AbstractTab):
def _install_event_filter(self): def _install_event_filter(self):
self._widget.installEventFilter(self._mouse_event_filter) self._widget.installEventFilter(self._mouse_event_filter)
def _make_private(self, widget):
settings = widget.settings()
settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True)
def openurl(self, url): def openurl(self, url):
self._openurl_prepare(url) self._openurl_prepare(url)
self._widget.openurl(url) self._widget.openurl(url)

View File

@ -59,7 +59,7 @@ class BrowserPage(QWebPage):
shutting_down = pyqtSignal() shutting_down = pyqtSignal()
reloading = pyqtSignal(QUrl) reloading = pyqtSignal(QUrl)
def __init__(self, win_id, tab_id, tabdata, parent=None): def __init__(self, win_id, tab_id, tabdata, private, parent=None):
super().__init__(parent) super().__init__(parent)
self._win_id = win_id self._win_id = win_id
self._tabdata = tabdata self._tabdata = tabdata
@ -72,7 +72,7 @@ class BrowserPage(QWebPage):
self.error_occurred = False self.error_occurred = False
self.open_target = usertypes.ClickTarget.normal self.open_target = usertypes.ClickTarget.normal
self._networkmanager = networkmanager.NetworkManager( self._networkmanager = networkmanager.NetworkManager(
win_id, tab_id, self) win_id=win_id, tab_id=tab_id, private=private, parent=self)
self.setNetworkAccessManager(self._networkmanager) self.setNetworkAccessManager(self._networkmanager)
self.setForwardUnsupportedContent(True) self.setForwardUnsupportedContent(True)
self.reloading.connect(self._networkmanager.clear_rejected_ssl_errors) self.reloading.connect(self._networkmanager.clear_rejected_ssl_errors)

View File

@ -55,7 +55,7 @@ class WebView(QWebView):
scroll_pos_changed = pyqtSignal(int, int) scroll_pos_changed = pyqtSignal(int, int)
shutting_down = pyqtSignal() shutting_down = pyqtSignal()
def __init__(self, win_id, tab_id, tab, parent=None): def __init__(self, *, win_id, tab_id, tab, private, parent=None):
super().__init__(parent) super().__init__(parent)
if sys.platform == 'darwin' and qtutils.version_check('5.4'): if sys.platform == 'darwin' and qtutils.version_check('5.4'):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948 # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
@ -71,7 +71,8 @@ class WebView(QWebView):
self._set_bg_color() self._set_bg_color()
self._tab_id = tab_id self._tab_id = tab_id
page = webpage.BrowserPage(self.win_id, self._tab_id, tab.data, page = webpage.BrowserPage(win_id=self.win_id, tab_id=self._tab_id,
tabdata=tab.data, private=private,
parent=self) parent=self)
try: try:

View File

@ -1125,6 +1125,14 @@ def data(readonly=False):
SettingValue(typ.QssColor(), 'black'), SettingValue(typ.QssColor(), 'black'),
"Background color of the statusbar."), "Background color of the statusbar."),
('statusbar.fg.private',
SettingValue(typ.QssColor(), '${statusbar.fg}'),
"Foreground color of the statusbar in private browsing mode."),
('statusbar.bg.private',
SettingValue(typ.QssColor(), 'grey'), # FIXME darker color
"Background color of the statusbar in private browsing mode."),
('statusbar.fg.insert', ('statusbar.fg.insert',
SettingValue(typ.QssColor(), '${statusbar.fg}'), SettingValue(typ.QssColor(), '${statusbar.fg}'),
"Foreground color of the statusbar in insert mode."), "Foreground color of the statusbar in insert mode."),

View File

@ -81,7 +81,7 @@ def get_window(via_ipc, force_window=False, force_tab=False,
# Otherwise, or if no window was found, create a new one # Otherwise, or if no window was found, create a new one
if window is None: if window is None:
window = MainWindow() window = MainWindow(private=None)
window.show() window.show()
raise_window = True raise_window = True
@ -127,13 +127,15 @@ class MainWindow(QWidget):
_vbox: The main QVBoxLayout. _vbox: The main QVBoxLayout.
_commandrunner: The main CommandRunner instance. _commandrunner: The main CommandRunner instance.
_overlays: Widgets shown as overlay for the current webpage. _overlays: Widgets shown as overlay for the current webpage.
_private: Whether the window is in private browsing mode.
""" """
def __init__(self, geometry=None, parent=None): def __init__(self, *, private, geometry=None, parent=None):
"""Create a new main window. """Create a new main window.
Args: Args:
geometry: The geometry to load, as a bytes-object (or None). geometry: The geometry to load, as a bytes-object (or None).
private: Whether the window is in private browsing mode.
parent: The parent the window should get. parent: The parent the window should get.
""" """
super().__init__(parent) super().__init__(parent)
@ -161,7 +163,14 @@ class MainWindow(QWidget):
self._init_downloadmanager() self._init_downloadmanager()
self._downloadview = downloadview.DownloadView(self.win_id) self._downloadview = downloadview.DownloadView(self.win_id)
self.tabbed_browser = tabbedbrowser.TabbedBrowser(self.win_id) if config.get('general', 'private-browsing'):
# This setting always trumps what's passed in.
private = True
else:
private = bool(private)
self._private = private
self.tabbed_browser = tabbedbrowser.TabbedBrowser(win_id=self.win_id,
private=private)
objreg.register('tabbed-browser', self.tabbed_browser, scope='window', objreg.register('tabbed-browser', self.tabbed_browser, scope='window',
window=self.win_id) window=self.win_id)
self._init_command_dispatcher() self._init_command_dispatcher()
@ -169,7 +178,8 @@ class MainWindow(QWidget):
# We need to set an explicit parent for StatusBar because it does some # We need to set an explicit parent for StatusBar because it does some
# show/hide magic immediately which would mean it'd show up as a # show/hide magic immediately which would mean it'd show up as a
# window. # window.
self.status = bar.StatusBar(self.win_id, parent=self) self.status = bar.StatusBar(win_id=self.win_id, private=private,
parent=self)
self._add_widgets() self._add_widgets()
self._downloadview.show() self._downloadview.show()
@ -446,17 +456,15 @@ class MainWindow(QWidget):
message_bridge.s_maybe_reset_text.connect(status.txt.maybe_reset_text) message_bridge.s_maybe_reset_text.connect(status.txt.maybe_reset_text)
# statusbar # statusbar
tabs.current_tab_changed.connect(status.prog.on_tab_changed) tabs.current_tab_changed.connect(status.on_tab_changed)
tabs.cur_progress.connect(status.prog.setValue) tabs.cur_progress.connect(status.prog.setValue)
tabs.cur_load_finished.connect(status.prog.hide) tabs.cur_load_finished.connect(status.prog.hide)
tabs.cur_load_started.connect(status.prog.on_load_started) tabs.cur_load_started.connect(status.prog.on_load_started)
tabs.current_tab_changed.connect(status.percentage.on_tab_changed)
tabs.cur_scroll_perc_changed.connect(status.percentage.set_perc) tabs.cur_scroll_perc_changed.connect(status.percentage.set_perc)
tabs.tab_index_changed.connect(status.tabindex.on_tab_index_changed) tabs.tab_index_changed.connect(status.tabindex.on_tab_index_changed)
tabs.current_tab_changed.connect(status.url.on_tab_changed)
tabs.cur_url_changed.connect(status.url.set_url) tabs.cur_url_changed.connect(status.url.set_url)
tabs.cur_link_hovered.connect(status.url.set_hover_url) tabs.cur_link_hovered.connect(status.url.set_hover_url)
tabs.cur_load_status_changed.connect(status.url.on_load_status_changed) tabs.cur_load_status_changed.connect(status.url.on_load_status_changed)

View File

@ -22,6 +22,7 @@
from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QSize, QTimer from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QSize, QTimer
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy
from qutebrowser.browser import browsertab
from qutebrowser.config import config, style from qutebrowser.config import config, style
from qutebrowser.utils import usertypes, log, objreg, utils from qutebrowser.utils import usertypes, log, objreg, utils
from qutebrowser.mainwindow.statusbar import (command, progress, keystring, from qutebrowser.mainwindow.statusbar import (command, progress, keystring,
@ -70,6 +71,11 @@ class StatusBar(QWidget):
For some reason we need to have this as class attribute For some reason we need to have this as class attribute
so pyqtProperty works correctly. so pyqtProperty works correctly.
_private: Whether we're in private browsing mode.
For some reason we need to have this as class attribute
so pyqtProperty works correctly.
Signals: Signals:
resized: Emitted when the statusbar has resized, so the completion resized: Emitted when the statusbar has resized, so the completion
widget can adjust its size to it. widget can adjust its size to it.
@ -86,6 +92,7 @@ class StatusBar(QWidget):
_insert_active = False _insert_active = False
_command_active = False _command_active = False
_caret_mode = CaretMode.off _caret_mode = CaretMode.off
_private = False
STYLESHEET = """ STYLESHEET = """
@ -97,6 +104,13 @@ class StatusBar(QWidget):
color: {{ color['statusbar.fg'] }}; color: {{ color['statusbar.fg'] }};
} }
QWidget#StatusBar[private="true"],
QWidget#StatusBar[private="true"] QLabel,
QWidget#StatusBar[private="true"] QLineEdit {
color: {{ color['statusbar.fg.private'] }};
background-color: {{ color['statusbar.bg.private'] }};
}
QWidget#StatusBar[caret_mode="on"], QWidget#StatusBar[caret_mode="on"],
QWidget#StatusBar[caret_mode="on"] QLabel, QWidget#StatusBar[caret_mode="on"] QLabel,
QWidget#StatusBar[caret_mode="on"] QLineEdit { QWidget#StatusBar[caret_mode="on"] QLineEdit {
@ -134,7 +148,7 @@ class StatusBar(QWidget):
""" """
def __init__(self, win_id, parent=None): def __init__(self, *, win_id, private, parent=None):
super().__init__(parent) super().__init__(parent)
objreg.register('statusbar', self, scope='window', window=win_id) objreg.register('statusbar', self, scope='window', window=win_id)
self.setObjectName(self.__class__.__name__) self.setObjectName(self.__class__.__name__)
@ -146,6 +160,7 @@ class StatusBar(QWidget):
self._win_id = win_id self._win_id = win_id
self._option = None self._option = None
self._page_fullscreen = False self._page_fullscreen = False
self._private = private
self._hbox = QHBoxLayout(self) self._hbox = QHBoxLayout(self)
self.set_hbox_padding() self.set_hbox_padding()
@ -156,7 +171,7 @@ class StatusBar(QWidget):
self._hbox.addLayout(self._stack) self._hbox.addLayout(self._stack)
self._stack.setContentsMargins(0, 0, 0, 0) self._stack.setContentsMargins(0, 0, 0, 0)
self.cmd = command.Command(win_id) self.cmd = command.Command(private=private, win_id=win_id)
self._stack.addWidget(self.cmd) self._stack.addWidget(self.cmd)
objreg.register('status-command', self.cmd, scope='window', objreg.register('status-command', self.cmd, scope='window',
window=win_id) window=win_id)
@ -226,6 +241,11 @@ class StatusBar(QWidget):
"""Getter for self._caret_mode, so it can be used as Qt property.""" """Getter for self._caret_mode, so it can be used as Qt property."""
return self._caret_mode.name return self._caret_mode.name
@pyqtProperty(bool)
def private(self):
"""Getter for self.private so it can be used as Qt property."""
return self._private
def set_mode_active(self, mode, val): def set_mode_active(self, mode, val):
"""Setter for self.{insert,command,caret}_active. """Setter for self.{insert,command,caret}_active.
@ -314,6 +334,13 @@ class StatusBar(QWidget):
self._page_fullscreen = on self._page_fullscreen = on
self.maybe_hide() self.maybe_hide()
@pyqtSlot(browsertab.AbstractTab)
def on_tab_changed(self, tab):
self.url.on_tab_changed(tab)
self.prog.on_tab_changed(tab)
self.percentage.on_tab_changed(tab)
assert tab.private == self._private
def resizeEvent(self, e): def resizeEvent(self, e):
"""Extend resizeEvent of QWidget to emit a resized signal afterwards. """Extend resizeEvent of QWidget to emit a resized signal afterwards.

View File

@ -54,12 +54,11 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
show_cmd = pyqtSignal() show_cmd = pyqtSignal()
hide_cmd = pyqtSignal() hide_cmd = pyqtSignal()
def __init__(self, win_id, parent=None): def __init__(self, *, win_id, private, parent=None):
misc.CommandLineEdit.__init__(self, parent) misc.CommandLineEdit.__init__(self, private=private, parent=parent)
misc.MinimalLineEditMixin.__init__(self) misc.MinimalLineEditMixin.__init__(self)
self._win_id = win_id self._win_id = win_id
command_history = objreg.get('command-history') command_history = objreg.get('command-history')
self.history.handle_private_mode = True
self.history.history = command_history.data self.history.history = command_history.data
self.history.changed.connect(command_history.changed) self.history.changed.connect(command_history.changed)
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored)

View File

@ -21,7 +21,6 @@
from PyQt5.QtCore import pyqtSlot from PyQt5.QtCore import pyqtSlot
from qutebrowser.browser import browsertab
from qutebrowser.mainwindow.statusbar import textbase from qutebrowser.mainwindow.statusbar import textbase
@ -51,7 +50,6 @@ class Percentage(textbase.TextBase):
else: else:
self.setText('[{:2}%]'.format(y)) self.setText('[{:2}%]'.format(y))
@pyqtSlot(browsertab.AbstractTab)
def on_tab_changed(self, tab): def on_tab_changed(self, tab):
"""Update scroll position when tab changed.""" """Update scroll position when tab changed."""
self.set_perc(*tab.scroller.pos_perc()) self.set_perc(*tab.scroller.pos_perc())

View File

@ -22,7 +22,6 @@
from PyQt5.QtCore import pyqtSlot, QSize from PyQt5.QtCore import pyqtSlot, QSize
from PyQt5.QtWidgets import QProgressBar, QSizePolicy from PyQt5.QtWidgets import QProgressBar, QSizePolicy
from qutebrowser.browser import browsertab
from qutebrowser.config import style from qutebrowser.config import style
from qutebrowser.utils import utils, usertypes from qutebrowser.utils import utils, usertypes
@ -60,7 +59,6 @@ class Progress(QProgressBar):
self.setValue(0) self.setValue(0)
self.show() self.show()
@pyqtSlot(browsertab.AbstractTab)
def on_tab_changed(self, tab): def on_tab_changed(self, tab):
"""Set the correct value when the current tab changed.""" """Set the correct value when the current tab changed."""
if self is None: # pragma: no branch if self is None: # pragma: no branch

View File

@ -21,7 +21,6 @@
from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt, QUrl from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt, QUrl
from qutebrowser.browser import browsertab
from qutebrowser.mainwindow.statusbar import textbase from qutebrowser.mainwindow.statusbar import textbase
from qutebrowser.config import style from qutebrowser.config import style
from qutebrowser.utils import usertypes, urlutils from qutebrowser.utils import usertypes, urlutils
@ -165,7 +164,6 @@ class UrlText(textbase.TextBase):
self._hover_url = None self._hover_url = None
self._update_url() self._update_url()
@pyqtSlot(browsertab.AbstractTab)
def on_tab_changed(self, tab): def on_tab_changed(self, tab):
"""Update URL if the tab changed.""" """Update URL if the tab changed."""
self._hover_url = None self._hover_url = None

View File

@ -68,6 +68,7 @@ class TabbedBrowser(tabwidget.TabWidget):
_local_marks: Jump markers local to each page _local_marks: Jump markers local to each page
_global_marks: Jump markers used across all pages _global_marks: Jump markers used across all pages
default_window_icon: The qutebrowser window icon default_window_icon: The qutebrowser window icon
private: Whether private browsing is on for this window.
Signals: Signals:
cur_progress: Progress of the current tab changed (load_progress). cur_progress: Progress of the current tab changed (load_progress).
@ -100,7 +101,7 @@ class TabbedBrowser(tabwidget.TabWidget):
new_tab = pyqtSignal(browsertab.AbstractTab, int) new_tab = pyqtSignal(browsertab.AbstractTab, int)
page_fullscreen_requested = pyqtSignal(bool) page_fullscreen_requested = pyqtSignal(bool)
def __init__(self, win_id, parent=None): def __init__(self, *, win_id, private, parent=None):
super().__init__(win_id, parent) super().__init__(win_id, parent)
self._win_id = win_id self._win_id = win_id
self._tab_insert_idx_left = 0 self._tab_insert_idx_left = 0
@ -118,6 +119,7 @@ class TabbedBrowser(tabwidget.TabWidget):
self._local_marks = {} self._local_marks = {}
self._global_marks = {} self._global_marks = {}
self.default_window_icon = self.window().windowIcon() self.default_window_icon = self.window().windowIcon()
self.private = private
objreg.get('config').changed.connect(self.update_favicons) objreg.get('config').changed.connect(self.update_favicons)
objreg.get('config').changed.connect(self.update_window_title) objreg.get('config').changed.connect(self.update_window_title)
objreg.get('config').changed.connect(self.update_tab_titles) objreg.get('config').changed.connect(self.update_tab_titles)
@ -205,7 +207,9 @@ class TabbedBrowser(tabwidget.TabWidget):
tab.renderer_process_terminated.connect( tab.renderer_process_terminated.connect(
functools.partial(self._on_renderer_process_terminated, tab)) functools.partial(self._on_renderer_process_terminated, tab))
tab.new_tab_requested.connect(self.tabopen) tab.new_tab_requested.connect(self.tabopen)
tab.add_history_item.connect(objreg.get('web-history').add_from_tab) if not self.private:
web_history = objreg.get('web-history')
tab.add_history_item.connect(web_history.add_from_tab)
tab.fullscreen_requested.connect(self.page_fullscreen_requested) tab.fullscreen_requested.connect(self.page_fullscreen_requested)
tab.fullscreen_requested.connect( tab.fullscreen_requested.connect(
self.tabBar().on_page_fullscreen_requested) self.tabBar().on_page_fullscreen_requested)
@ -399,13 +403,14 @@ class TabbedBrowser(tabwidget.TabWidget):
if (config.get('tabs', 'tabs-are-windows') and self.count() > 0 and if (config.get('tabs', 'tabs-are-windows') and self.count() > 0 and
not ignore_tabs_are_windows): not ignore_tabs_are_windows):
from qutebrowser.mainwindow import mainwindow from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow() window = mainwindow.MainWindow(private=self.private)
window.show() window.show()
tabbed_browser = objreg.get('tabbed-browser', scope='window', tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=window.win_id) window=window.win_id)
return tabbed_browser.tabopen(url, background, explicit) return tabbed_browser.tabopen(url, background, explicit)
tab = browsertab.create(win_id=self._win_id, parent=self) tab = browsertab.create(win_id=self._win_id, private=self.private,
parent=self)
self._connect_tab_signals(tab) self._connect_tab_signals(tab)
if idx is None: if idx is None:

View File

@ -44,9 +44,9 @@ class History(QObject):
"""Command history. """Command history.
Attributes: Attributes:
handle_private_mode: Whether to ignore history in private mode.
history: A list of executed commands, with newer commands at the end. history: A list of executed commands, with newer commands at the end.
_tmphist: Temporary history for history browsing (as NeighborList) _tmphist: Temporary history for history browsing (as NeighborList)
_private: Whether this history is in private browsing mode.
Signals: Signals:
changed: Emitted when an entry was added to the history. changed: Emitted when an entry was added to the history.
@ -54,15 +54,15 @@ class History(QObject):
changed = pyqtSignal() changed = pyqtSignal()
def __init__(self, history=None, parent=None): def __init__(self, *, private=False, history=None, parent=None):
"""Constructor. """Constructor.
Args: Args:
history: The initial history to set. history: The initial history to set.
""" """
super().__init__(parent) super().__init__(parent)
self.handle_private_mode = False
self._tmphist = None self._tmphist = None
self._private = private
if history is None: if history is None:
self.history = [] self.history = []
else: else:
@ -129,8 +129,7 @@ class History(QObject):
Args: Args:
text: The text to append. text: The text to append.
""" """
if (self.handle_private_mode and if self._private:
config.get('general', 'private-browsing')):
return return
if not self.history or text != self.history[-1]: if not self.history or text != self.history[-1]:
self.history.append(text) self.history.append(text)

View File

@ -50,7 +50,7 @@ class ConsoleLineEdit(miscwidgets.CommandLineEdit):
Args: Args:
_namespace: The local namespace of the interpreter. _namespace: The local namespace of the interpreter.
""" """
super().__init__(parent) super().__init__(parent=parent)
self.update_font() self.update_font()
objreg.get('config').changed.connect(self.update_font) objreg.get('config').changed.connect(self.update_font)
self._history = cmdhistory.History(parent=self) self._history = cmdhistory.History(parent=self)

View File

@ -69,9 +69,9 @@ class CommandLineEdit(QLineEdit):
_promptlen: The length of the current prompt. _promptlen: The length of the current prompt.
""" """
def __init__(self, parent=None): def __init__(self, *, private=False, parent=None):
super().__init__(parent) super().__init__(parent)
self.history = cmdhistory.History(parent=self) self.history = cmdhistory.History(private=private, parent=self)
self._validator = _CommandValidator(self) self._validator = _CommandValidator(self)
self.setValidator(self._validator) self.setValidator(self._validator)
self.textEdited.connect(self.on_text_edited) self.textEdited.connect(self.on_text_edited)

View File

@ -379,7 +379,8 @@ class SessionManager(QObject):
raise SessionError(e) raise SessionError(e)
log.sessions.debug("Loading session {} from {}...".format(name, path)) log.sessions.debug("Loading session {} from {}...".format(name, path))
for win in data['windows']: for win in data['windows']:
window = mainwindow.MainWindow(geometry=win['geometry']) window = mainwindow.MainWindow(geometry=win['geometry'],
private=win.get('private', False))
window.show() window.show()
tabbed_browser = objreg.get('tabbed-browser', scope='window', tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=window.win_id) window=window.win_id)

View File

@ -146,7 +146,7 @@ def test_previtem_index_error(hist):
def test_append_private_mode(hist, config_stub): def test_append_private_mode(hist, config_stub):
"""Test append in private mode.""" """Test append in private mode."""
hist.handle_private_mode = True hist._private = True
# We want general.private-browsing set to True # We want general.private-browsing set to True
config_stub.data = CONFIG_PRIVATE config_stub.data = CONFIG_PRIVATE
hist.append('new item') hist.append('new item')