diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 95caf24f1..0fb99f260 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -1083,7 +1083,7 @@ Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] [*--only-active-win Save a session. ==== positional arguments -* +'name'+: The name of the session. If not given, the session configured in session_default_name is saved. +* +'name'+: The name of the session. If not given, the session configured in session.default_name is saved. ==== optional arguments diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 3f6a8a016..2da7a5008 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -222,7 +222,7 @@ |<>|Turn on Qt HighDPI scaling. |<>|Show a scrollbar. |<>|Enable smooth scrolling for web pages. -|<>|Name of the session to save by default. +|<>|Name of the session to save by default. |<>|Languages to use for spell checking. |<>|Hide the statusbar unless a message is shown. |<>|Padding (in pixels) for the statusbar. @@ -2556,8 +2556,8 @@ Type: <> Default: +pass:[false]+ -[[session_default_name]] -=== session_default_name +[[session.default_name]] +=== session.default_name Name of the session to save by default. If this is set to null, the session which was last loaded is saved. diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 3fb6459a5..e6262a007 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -29,6 +29,7 @@ import os import time import textwrap import mimetypes +import urllib import pkg_resources from PyQt5.QtCore import QUrlQuery, QUrl @@ -425,6 +426,18 @@ def qute_settings(url): return 'text/html', html +@add_handler('back') +def qute_back(url): + """Handler for qute://back. + + Simple page to free ram / lazy load a site, goes back on focusing the tab. + """ + html = jinja.render( + 'back.html', + title='Suspended: ' + urllib.parse.unquote(url.fragment())) + return 'text/html', html + + @add_handler('configdiff') def qute_configdiff(url): """Handler for qute://configdiff.""" diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 08c854ed3..2cae54993 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -79,6 +79,9 @@ new_instance_open_target_window: When `new_instance_open_target` is not set to `window`, this is ignored. session_default_name: + renamed: session.default_name + +session.default_name: type: name: SessionName none_ok: true @@ -88,6 +91,11 @@ session_default_name: If this is set to null, the session which was last loaded is saved. +session.lazy_restore: + type: Bool + default: false + desc: Load a restored tab as soon as it takes focus. + backend: type: name: String diff --git a/qutebrowser/html/back.html b/qutebrowser/html/back.html new file mode 100644 index 000000000..46945ab65 --- /dev/null +++ b/qutebrowser/html/back.html @@ -0,0 +1,60 @@ +{% extends "base.html" %} + +{% block script %} +const STATE_BACK = "back"; +const STATE_FORWARD = "forward"; + +function switch_state(new_state) { + history.replaceState( + new_state, + document.title, + location.pathname + location.hash); +} + +function go_back() { + switch_state(STATE_FORWARD); + history.back(); +} + +function go_forward() { + switch_state(STATE_BACK); + history.forward(); +} + +function prepare_restore() { + if (!document.hidden) { + go_back(); + return; + } + + document.addEventListener("visibilitychange", go_back); +} + +// there are three states +// default: register focus listener, +// on focus: go back and switch to the state forward +// back: user came from a later history entry +// -> switch to the state forward, +// forward him to the previous history entry +// forward: user came from a previous history entry +// -> switch to the state back, +// forward him to the next history entry +switch (history.state) { + case STATE_BACK: + go_back(); + break; + case STATE_FORWARD: + go_forward(); + break; + default: + setTimeout(prepare_restore, 1000); + break; +} +{% endblock %} + +{% block content %} + +

Loading suspended page...
+
+If nothing happens, something went wrong or you disabled JavaScript.

+{% endblock %} diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index 064d8c9e9..237e46189 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -21,6 +21,8 @@ import os import os.path +import itertools +import urllib import sip from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer @@ -205,7 +207,13 @@ class SessionManager(QObject): for idx, item in enumerate(tab.history): qtutils.ensure_valid(item) item_data = self._save_tab_item(tab, idx, item) - data['history'].append(item_data) + if item.url().scheme() == 'qute' and item.url().host() == 'back': + # don't add qute://back to the session file + if item_data.get('active', False) and data['history']: + # mark entry before qute://back as active + data['history'][-1]['active'] = True + else: + data['history'].append(item_data) return data def _save_all(self, *, only_window=None, with_private=False): @@ -251,7 +259,7 @@ class SessionManager(QObject): object. """ if name is default: - name = config.val.session_default_name + name = config.val.session.default_name if name is None: if self._current is not None: name = self._current @@ -323,7 +331,18 @@ class SessionManager(QObject): def _load_tab(self, new_tab, data): """Load yaml data into a newly opened tab.""" entries = [] - for histentry in data['history']: + lazy_load = [] + # use len(data['history']) + # -> dropwhile empty if not session.lazy_session + lazy_index = len(data['history']) + gen = itertools.chain( + itertools.takewhile(lambda _: not lazy_load, + enumerate(data['history'])), + enumerate(lazy_load), + itertools.dropwhile(lambda i: i[0] < lazy_index, + enumerate(data['history']))) + + for i, histentry in gen: user_data = {} if 'zoom' in data: @@ -347,6 +366,20 @@ class SessionManager(QObject): if 'pinned' in histentry: new_tab.data.pinned = histentry['pinned'] + if (config.val.session.lazy_restore and + histentry.get('active', False) and + not histentry['url'].startswith('qute://back')): + # remove "active" mark and insert back page marked as active + lazy_index = i + 1 + lazy_load.append({ + 'title': histentry['title'], + 'url': + 'qute://back#' + + urllib.parse.quote(histentry['title']), + 'active': True + }) + histentry['active'] = False + active = histentry.get('active', False) url = QUrl.fromEncoded(histentry['url'].encode('ascii')) if 'original-url' in histentry: @@ -360,6 +393,7 @@ class SessionManager(QObject): entries.append(entry) if active: new_tab.title_changed.emit(histentry['title']) + try: new_tab.history.load_items(entries) except ValueError as e: @@ -460,7 +494,7 @@ class SessionManager(QObject): Args: name: The name of the session. If not given, the session configured - in session_default_name is saved. + in session.default_name is saved. current: Save the current session instead of the default. quiet: Don't show confirmation message. force: Force saving internal sessions (starting with an underline). diff --git a/tests/unit/misc/test_sessions.py b/tests/unit/misc/test_sessions.py index 771430d5b..b2cb8a3dd 100644 --- a/tests/unit/misc/test_sessions.py +++ b/tests/unit/misc/test_sessions.py @@ -170,7 +170,7 @@ class TestSaveAll: ]) def test_get_session_name(config_stub, sess_man, arg, config, current, expected): - config_stub.val.session_default_name = config + config_stub.val.session.default_name = config sess_man._current = current assert sess_man._get_session_name(arg) == expected