diff --git a/qutebrowser/app.py b/qutebrowser/app.py index cca9e1a76..225985e66 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -195,7 +195,7 @@ def _process_args(args): session_manager = objreg.get('session-manager') if not session_manager.did_load: log.init.debug("Initializing main window...") - window = mainwindow.MainWindow() + window = mainwindow.MainWindow(private=None) if not args.nowindow: window.show() qApp.setActiveWindow(window) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 62fb67453..d8a5a5132 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -35,11 +35,12 @@ from qutebrowser.browser import mouse, hints 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. Args: 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. """ # Importing modules here so we don't depend on QtWebEngine without the @@ -51,7 +52,8 @@ def create(win_id, parent=None): else: from qutebrowser.browser.webkit import 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(): @@ -542,6 +544,7 @@ class AbstractTab(QWidget): Attributes: history: The AbstractHistory for the current 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 Accessible via load_status() method. @@ -581,7 +584,8 @@ class AbstractTab(QWidget): fullscreen_requested = pyqtSignal(bool) 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.tab_id = next(tab_id_gen) super().__init__(parent) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 6b34e4d30..57ac0eba7 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -67,10 +67,10 @@ class CommandDispatcher: def __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.""" from qutebrowser.mainwindow import mainwindow - new_window = mainwindow.MainWindow() + new_window = mainwindow.MainWindow(private=private) new_window.show() return new_window.tabbed_browser @@ -110,7 +110,7 @@ class CommandDispatcher: return widget def _open(self, url, tab=False, background=False, window=False, - explicit=True): + explicit=True, private=None): """Helper function to open a page. Args: @@ -118,12 +118,17 @@ class CommandDispatcher: tab: Whether to open in a new tab. background: Whether to open in the background. 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) tabbed_browser = self._tabbed_browser cmdutils.check_exclusive((tab, background, window), 'tbw') + if private is None: + private = self._tabbed_browser.private + if window: - tabbed_browser = self._new_tabbed_browser() + tabbed_browser = self._new_tabbed_browser(private) tabbed_browser.tabopen(url) elif tab: tabbed_browser.tabopen(url, background=False, explicit=explicit) @@ -228,7 +233,8 @@ class CommandDispatcher: @cmdutils.argument('url', completion=usertypes.Completion.url) @cmdutils.argument('count', count=True) 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. If the URL contains newlines, each line gets opened in its own tab. @@ -242,12 +248,25 @@ class CommandDispatcher: clicking on a link). count: The tab index to open the URL in, or None. secure: Force HTTPS. + private: Open a new window in private browsing mode. """ if url is None: urls = [config.get('general', 'default-page')] else: 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): if secure: cur_url.setScheme('https') @@ -255,7 +274,8 @@ class CommandDispatcher: tab = False bg = True 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: curtab = self._cntwidget(count) if curtab is None: @@ -430,7 +450,8 @@ class CommandDispatcher: # The new tab could be in a new tabbed_browser (e.g. because of # tabs-are-windows being set) if window: - new_tabbed_browser = self._new_tabbed_browser() + new_tabbed_browser = self._new_tabbed_browser( + private=self._tabbed_browser.private) else: new_tabbed_browser = self._tabbed_browser newtab = new_tabbed_browser.tabopen(background=bg, explicit=True) diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 294425549..db67d686b 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -274,8 +274,7 @@ class WebHistory(QObject): (hidden in completion) atime: Override the atime used to add the entry """ - if config.get('general', 'private-browsing'): - return + assert not config.get('general', 'private-browsing') if not url.isValid(): log.misc.warning("Ignoring invalid URL being added to history") return diff --git a/qutebrowser/browser/navigate.py b/qutebrowser/browser/navigate.py index b1ab6f9b1..113941edb 100644 --- a/qutebrowser/browser/navigate.py +++ b/qutebrowser/browser/navigate.py @@ -128,17 +128,19 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False, return qtutils.ensure_valid(url) + cur_tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=win_id) + if window: from qutebrowser.mainwindow import mainwindow - new_window = mainwindow.MainWindow() + new_window = mainwindow.MainWindow( + private=cur_tabbed_browser.private) new_window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=new_window.win_id) tabbed_browser.tabopen(url, background=False) elif tab: - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window=win_id) - tabbed_browser.tabopen(url, background=background) + cur_tabbed_browser.tabopen(url, background=background) else: browsertab.openurl(url) diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index f3bc4dfe2..5fb0ed163 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -366,7 +366,7 @@ class DownloadManager(downloads.AbstractDownloadManager): def __init__(self, win_id, parent=None): super().__init__(parent) self._networkmanager = networkmanager.NetworkManager( - win_id, None, self) + win_id=win_id, tab_id=None, private=False, parent=self) @pyqtSlot('QUrl') def get(self, url, *, user_agent=None, **kwargs): diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 005d652a3..d400387a9 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -216,8 +216,10 @@ def get_tab(win_id, target): win_id = win_id bg_tab = True elif target == usertypes.ClickTarget.window: + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=win_id) from qutebrowser.mainwindow import mainwindow - window = mainwindow.MainWindow() + window = mainwindow.MainWindow(private=tabbed_browser.private) window.show() win_id = window.win_id bg_tab = False diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index dce808871..7b7f1e3fe 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -379,15 +379,16 @@ class AbstractWebElement(collections.abc.MutableMapping): self._click_fake_event(click_target) return + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=self._tab.win_id) + if click_target in [usertypes.ClickTarget.tab, 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) elif click_target == usertypes.ClickTarget.window: from qutebrowser.mainwindow import mainwindow - window = mainwindow.MainWindow() + window = mainwindow.MainWindow(private=tabbed_browser.private) window.show() window.tabbed_browser.tabopen(url) else: diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index af93786d6..57bd49f6b 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -521,9 +521,11 @@ class WebEngineTab(browsertab.AbstractTab): """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, - parent=parent) + private=private, parent=parent) widget = webview.WebEngineView(tabdata=self.data, win_id=win_id) self.history = WebEngineHistory(self) self.scroller = WebEngineScroller(self, parent=self) diff --git a/qutebrowser/browser/webkit/cache.py b/qutebrowser/browser/webkit/cache.py index 8ae9aa0f2..d04a5cfac 100644 --- a/qutebrowser/browser/webkit/cache.py +++ b/qutebrowser/browser/webkit/cache.py @@ -21,8 +21,7 @@ import os.path -from PyQt5.QtCore import pyqtSlot -from PyQt5.QtNetwork import QNetworkDiskCache, QNetworkCacheMetaData +from PyQt5.QtNetwork import QNetworkDiskCache from qutebrowser.config import config from qutebrowser.utils import utils, objreg, qtutils @@ -30,24 +29,21 @@ from qutebrowser.utils import utils, objreg, qtutils class DiskCache(QNetworkDiskCache): - """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()) - """ + """Disk cache which sets correct cache dir and size.""" def __init__(self, cache_dir, parent=None): super().__init__(parent) - self._cache_dir = cache_dir - self._maybe_activate() - objreg.get('config').changed.connect(self.on_config_changed) + assert not config.get('general', 'private-browsing') + self.setCacheDirectory(os.path.join(cache_dir, 'http')) + self._set_cache_size() + objreg.get('config').changed.connect(self._set_cache_size) def __repr__(self): return utils.get_repr(self, size=self.cacheSize(), maxsize=self.maximumCacheSize(), path=self.cacheDirectory()) + @config.change_filter('storage', 'cache-size') def _set_cache_size(self): """Set the cache size based on the config.""" size = config.get('storage', 'cache-size') @@ -58,128 +54,3 @@ class DiskCache(QNetworkDiskCache): not qtutils.version_check('5.9')): # pragma: no cover size = 0 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 diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 30adf3170..be14e2062 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -128,6 +128,7 @@ class NetworkManager(QNetworkAccessManager): _tab_id: The tab ID this NetworkManager is associated with. _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. Signals: shutting_down: Emitted when the QNAM is shutting down. @@ -135,7 +136,7 @@ class NetworkManager(QNetworkAccessManager): 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") with log.disable_qt_msghandler(): # WORKAROUND for a hang when a message is printed - See: @@ -146,11 +147,12 @@ class NetworkManager(QNetworkAccessManager): self._win_id = win_id self._tab_id = tab_id self._requests = [] + self._private = private self._scheme_handlers = { 'qute': webkitqutescheme.QuteSchemeHandler(win_id), 'file': filescheme.FileSchemeHandler(win_id), } - self._set_cookiejar(private=config.get('general', 'private-browsing')) + self._set_cookiejar() self._set_cache() self.sslErrors.connect(self.on_ssl_errors) self._rejected_ssl_errors = collections.defaultdict(list) @@ -158,15 +160,10 @@ class NetworkManager(QNetworkAccessManager): self.authenticationRequired.connect(self.on_authentication_required) self.proxyAuthenticationRequired.connect( self.on_proxy_authentication_required) - objreg.get('config').changed.connect(self.on_config_changed) - def _set_cookiejar(self, private=False): - """Set the cookie jar of the NetworkManager correctly. - - Args: - private: Whether we're currently in private browsing mode. - """ - if private: + def _set_cookiejar(self): + """Set the cookie jar of the NetworkManager correctly.""" + if self._private: cookie_jar = objreg.get('ram-cookie-jar') else: cookie_jar = objreg.get('cookie-jar') @@ -178,11 +175,9 @@ class NetworkManager(QNetworkAccessManager): cookie_jar.setParent(app) def _set_cache(self): - """Set the cache of the NetworkManager correctly. - - We can't switch the whole cache in private mode because QNAM would - delete the old cache. - """ + """Set the cache of the NetworkManager correctly.""" + if self._private: + return # We have a shared cache - we restore its parent so we don't take # ownership of it. app = QCoreApplication.instance() @@ -324,17 +319,6 @@ class NetworkManager(QNetworkAccessManager): authenticator.setPassword(answer.password) _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() def on_adopted_download_destroyed(self): """Check if we can clean up if an adopted download was destroyed. diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index 0d4564e7e..d7cc91a4d 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -89,20 +89,19 @@ def _set_user_stylesheet(): def _init_private_browsing(): - if config.get('general', 'private-browsing'): - if qtutils.is_qtwebkit_ng(): - message.warning("Private browsing is not fully implemented by " - "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 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('') - else: - QWebSettings.setIconDatabasePath(standarddir.cache()) def update_settings(section, option): """Update global settings when qwebsettings changed.""" - if (section, option) == ('general', 'private-browsing'): - _init_private_browsing() - elif section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']: + if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']: _set_user_stylesheet() websettings.update_mappings(MAPPINGS, section, option) @@ -113,8 +112,7 @@ def init(_args): cache_path = standarddir.cache() data_path = standarddir.data() - _init_private_browsing() - + QWebSettings.setIconDatabasePath(standarddir.cache()) QWebSettings.setOfflineWebApplicationCachePath( os.path.join(cache_path, 'application-cache')) QWebSettings.globalSettings().setLocalStoragePath( @@ -122,6 +120,9 @@ def init(_args): QWebSettings.setOfflineStoragePath( os.path.join(data_path, 'offline-storage')) + if config.get('general', 'private-browsing'): + _init_private_browsing() + websettings.init_mappings(MAPPINGS) _set_user_stylesheet() objreg.get('config').changed.connect(update_settings) @@ -254,8 +255,6 @@ MAPPINGS = { setter=QWebSettings.setOfflineWebApplicationCacheQuota), }, 'general': { - 'private-browsing': - Attribute(QWebSettings.PrivateBrowsingEnabled), 'developer-extras': Attribute(QWebSettings.DeveloperExtrasEnabled), 'print-element-backgrounds': diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index daf46a503..22442817e 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -629,10 +629,13 @@ class WebKitTab(browsertab.AbstractTab): """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, - parent=parent) - widget = webview.WebView(win_id, self.tab_id, tab=self) + private=private, parent=parent) + 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.scroller = WebKitScroller(self, parent=self) self.caret = WebKitCaret(win_id=win_id, mode_manager=mode_manager, @@ -649,6 +652,10 @@ class WebKitTab(browsertab.AbstractTab): def _install_event_filter(self): self._widget.installEventFilter(self._mouse_event_filter) + def _make_private(self, widget): + settings = widget.settings() + settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True) + def openurl(self, url): self._openurl_prepare(url) self._widget.openurl(url) diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index a687dd5e2..acebf5cd3 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -59,7 +59,7 @@ class BrowserPage(QWebPage): shutting_down = pyqtSignal() 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) self._win_id = win_id self._tabdata = tabdata @@ -72,7 +72,7 @@ class BrowserPage(QWebPage): self.error_occurred = False self.open_target = usertypes.ClickTarget.normal 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.setForwardUnsupportedContent(True) self.reloading.connect(self._networkmanager.clear_rejected_ssl_errors) diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index 01abca639..99980b4b1 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -55,7 +55,7 @@ class WebView(QWebView): scroll_pos_changed = pyqtSignal(int, int) 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) if sys.platform == 'darwin' and qtutils.version_check('5.4'): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948 @@ -71,7 +71,8 @@ class WebView(QWebView): self._set_bg_color() 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) try: diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 6c3c61187..11f46094f 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1125,6 +1125,14 @@ def data(readonly=False): SettingValue(typ.QssColor(), 'black'), "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', SettingValue(typ.QssColor(), '${statusbar.fg}'), "Foreground color of the statusbar in insert mode."), diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 96489fbac..1f573c6d1 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -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 if window is None: - window = MainWindow() + window = MainWindow(private=None) window.show() raise_window = True @@ -127,13 +127,15 @@ class MainWindow(QWidget): _vbox: The main QVBoxLayout. _commandrunner: The main CommandRunner instance. _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. Args: 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. """ super().__init__(parent) @@ -161,7 +163,14 @@ class MainWindow(QWidget): self._init_downloadmanager() 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', window=self.win_id) self._init_command_dispatcher() @@ -169,7 +178,8 @@ class MainWindow(QWidget): # 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 # 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._downloadview.show() @@ -446,17 +456,15 @@ class MainWindow(QWidget): message_bridge.s_maybe_reset_text.connect(status.txt.maybe_reset_text) # 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_load_finished.connect(status.prog.hide) 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.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_link_hovered.connect(status.url.set_hover_url) tabs.cur_load_status_changed.connect(status.url.on_load_status_changed) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index cddf4ec35..7a302f276 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -22,6 +22,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QSize, QTimer from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy +from qutebrowser.browser import browsertab from qutebrowser.config import config, style from qutebrowser.utils import usertypes, log, objreg, utils 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 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: resized: Emitted when the statusbar has resized, so the completion widget can adjust its size to it. @@ -86,6 +92,7 @@ class StatusBar(QWidget): _insert_active = False _command_active = False _caret_mode = CaretMode.off + _private = False STYLESHEET = """ @@ -97,6 +104,13 @@ class StatusBar(QWidget): 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"] QLabel, 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) objreg.register('statusbar', self, scope='window', window=win_id) self.setObjectName(self.__class__.__name__) @@ -146,6 +160,7 @@ class StatusBar(QWidget): self._win_id = win_id self._option = None self._page_fullscreen = False + self._private = private self._hbox = QHBoxLayout(self) self.set_hbox_padding() @@ -156,7 +171,7 @@ class StatusBar(QWidget): self._hbox.addLayout(self._stack) 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) objreg.register('status-command', self.cmd, scope='window', window=win_id) @@ -226,6 +241,11 @@ class StatusBar(QWidget): """Getter for self._caret_mode, so it can be used as Qt property.""" 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): """Setter for self.{insert,command,caret}_active. @@ -314,6 +334,13 @@ class StatusBar(QWidget): self._page_fullscreen = on 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): """Extend resizeEvent of QWidget to emit a resized signal afterwards. diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py index d59c87dbc..410e578c9 100644 --- a/qutebrowser/mainwindow/statusbar/command.py +++ b/qutebrowser/mainwindow/statusbar/command.py @@ -54,12 +54,11 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): show_cmd = pyqtSignal() hide_cmd = pyqtSignal() - def __init__(self, win_id, parent=None): - misc.CommandLineEdit.__init__(self, parent) + def __init__(self, *, win_id, private, parent=None): + misc.CommandLineEdit.__init__(self, private=private, parent=parent) misc.MinimalLineEditMixin.__init__(self) self._win_id = win_id command_history = objreg.get('command-history') - self.history.handle_private_mode = True self.history.history = command_history.data self.history.changed.connect(command_history.changed) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) diff --git a/qutebrowser/mainwindow/statusbar/percentage.py b/qutebrowser/mainwindow/statusbar/percentage.py index 050b0747a..0d75e8163 100644 --- a/qutebrowser/mainwindow/statusbar/percentage.py +++ b/qutebrowser/mainwindow/statusbar/percentage.py @@ -21,7 +21,6 @@ from PyQt5.QtCore import pyqtSlot -from qutebrowser.browser import browsertab from qutebrowser.mainwindow.statusbar import textbase @@ -51,7 +50,6 @@ class Percentage(textbase.TextBase): else: self.setText('[{:2}%]'.format(y)) - @pyqtSlot(browsertab.AbstractTab) def on_tab_changed(self, tab): """Update scroll position when tab changed.""" self.set_perc(*tab.scroller.pos_perc()) diff --git a/qutebrowser/mainwindow/statusbar/progress.py b/qutebrowser/mainwindow/statusbar/progress.py index e78d5307c..a2f192732 100644 --- a/qutebrowser/mainwindow/statusbar/progress.py +++ b/qutebrowser/mainwindow/statusbar/progress.py @@ -22,7 +22,6 @@ from PyQt5.QtCore import pyqtSlot, QSize from PyQt5.QtWidgets import QProgressBar, QSizePolicy -from qutebrowser.browser import browsertab from qutebrowser.config import style from qutebrowser.utils import utils, usertypes @@ -60,7 +59,6 @@ class Progress(QProgressBar): self.setValue(0) self.show() - @pyqtSlot(browsertab.AbstractTab) def on_tab_changed(self, tab): """Set the correct value when the current tab changed.""" if self is None: # pragma: no branch diff --git a/qutebrowser/mainwindow/statusbar/url.py b/qutebrowser/mainwindow/statusbar/url.py index b54e020fa..f83fdef1f 100644 --- a/qutebrowser/mainwindow/statusbar/url.py +++ b/qutebrowser/mainwindow/statusbar/url.py @@ -21,7 +21,6 @@ from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt, QUrl -from qutebrowser.browser import browsertab from qutebrowser.mainwindow.statusbar import textbase from qutebrowser.config import style from qutebrowser.utils import usertypes, urlutils @@ -165,7 +164,6 @@ class UrlText(textbase.TextBase): self._hover_url = None self._update_url() - @pyqtSlot(browsertab.AbstractTab) def on_tab_changed(self, tab): """Update URL if the tab changed.""" self._hover_url = None diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 896ea5c9d..8e3ee5e39 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -68,6 +68,7 @@ class TabbedBrowser(tabwidget.TabWidget): _local_marks: Jump markers local to each page _global_marks: Jump markers used across all pages default_window_icon: The qutebrowser window icon + private: Whether private browsing is on for this window. Signals: cur_progress: Progress of the current tab changed (load_progress). @@ -100,7 +101,7 @@ class TabbedBrowser(tabwidget.TabWidget): new_tab = pyqtSignal(browsertab.AbstractTab, int) 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) self._win_id = win_id self._tab_insert_idx_left = 0 @@ -118,6 +119,7 @@ class TabbedBrowser(tabwidget.TabWidget): self._local_marks = {} self._global_marks = {} 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_window_title) objreg.get('config').changed.connect(self.update_tab_titles) @@ -205,7 +207,9 @@ class TabbedBrowser(tabwidget.TabWidget): tab.renderer_process_terminated.connect( functools.partial(self._on_renderer_process_terminated, tab)) 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.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 not ignore_tabs_are_windows): from qutebrowser.mainwindow import mainwindow - window = mainwindow.MainWindow() + window = mainwindow.MainWindow(private=self.private) window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=window.win_id) 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) if idx is None: diff --git a/qutebrowser/misc/cmdhistory.py b/qutebrowser/misc/cmdhistory.py index b0990a67e..9193f7bcc 100644 --- a/qutebrowser/misc/cmdhistory.py +++ b/qutebrowser/misc/cmdhistory.py @@ -44,9 +44,9 @@ class History(QObject): """Command history. Attributes: - handle_private_mode: Whether to ignore history in private mode. history: A list of executed commands, with newer commands at the end. _tmphist: Temporary history for history browsing (as NeighborList) + _private: Whether this history is in private browsing mode. Signals: changed: Emitted when an entry was added to the history. @@ -54,15 +54,15 @@ class History(QObject): changed = pyqtSignal() - def __init__(self, history=None, parent=None): + def __init__(self, *, private=False, history=None, parent=None): """Constructor. Args: history: The initial history to set. """ super().__init__(parent) - self.handle_private_mode = False self._tmphist = None + self._private = private if history is None: self.history = [] else: @@ -129,8 +129,7 @@ class History(QObject): Args: text: The text to append. """ - if (self.handle_private_mode and - config.get('general', 'private-browsing')): + if self._private: return if not self.history or text != self.history[-1]: self.history.append(text) diff --git a/qutebrowser/misc/consolewidget.py b/qutebrowser/misc/consolewidget.py index 537621b4d..963076c21 100644 --- a/qutebrowser/misc/consolewidget.py +++ b/qutebrowser/misc/consolewidget.py @@ -50,7 +50,7 @@ class ConsoleLineEdit(miscwidgets.CommandLineEdit): Args: _namespace: The local namespace of the interpreter. """ - super().__init__(parent) + super().__init__(parent=parent) self.update_font() objreg.get('config').changed.connect(self.update_font) self._history = cmdhistory.History(parent=self) diff --git a/qutebrowser/misc/miscwidgets.py b/qutebrowser/misc/miscwidgets.py index 48f775b85..75f941fd8 100644 --- a/qutebrowser/misc/miscwidgets.py +++ b/qutebrowser/misc/miscwidgets.py @@ -69,9 +69,9 @@ class CommandLineEdit(QLineEdit): _promptlen: The length of the current prompt. """ - def __init__(self, parent=None): + def __init__(self, *, private=False, parent=None): super().__init__(parent) - self.history = cmdhistory.History(parent=self) + self.history = cmdhistory.History(private=private, parent=self) self._validator = _CommandValidator(self) self.setValidator(self._validator) self.textEdited.connect(self.on_text_edited) diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index cba3629ef..6a096c06a 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -379,7 +379,8 @@ class SessionManager(QObject): raise SessionError(e) log.sessions.debug("Loading session {} from {}...".format(name, path)) for win in data['windows']: - window = mainwindow.MainWindow(geometry=win['geometry']) + window = mainwindow.MainWindow(geometry=win['geometry'], + private=win.get('private', False)) window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=window.win_id) diff --git a/tests/unit/misc/test_cmdhistory.py b/tests/unit/misc/test_cmdhistory.py index 7da721c2b..717fd4a96 100644 --- a/tests/unit/misc/test_cmdhistory.py +++ b/tests/unit/misc/test_cmdhistory.py @@ -146,7 +146,7 @@ def test_previtem_index_error(hist): def test_append_private_mode(hist, config_stub): """Test append in private mode.""" - hist.handle_private_mode = True + hist._private = True # We want general.private-browsing set to True config_stub.data = CONFIG_PRIVATE hist.append('new item')