Merge branch 'imransobir-master'
This commit is contained in:
commit
760f016285
@ -31,6 +31,7 @@ Added
|
||||
- Support for the HTML5 fullscreen API (e.g. youtube videos) with QtWebEngine
|
||||
- Support for the `general -> print-element-backgrounds` option with QtWebEngine on Qt >= 5.8
|
||||
- Support for `:download --mhtml` with QtWebEngine
|
||||
- New `qute:history` URL and `:history` command to show the browsing history.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
@ -169,6 +169,7 @@ Contributors, sorted by the number of commits in descending order:
|
||||
* Artur Shaik
|
||||
* Nathan Isom
|
||||
* Thorsten Wißmann
|
||||
* Imran Sobir
|
||||
* Austin Anderson
|
||||
* Fritz Reichwald
|
||||
* Jimmy
|
||||
|
@ -44,6 +44,7 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
||||
|<<fullscreen,fullscreen>>|Toggle fullscreen mode.
|
||||
|<<help,help>>|Show help about a command or setting.
|
||||
|<<hint,hint>>|Start hinting.
|
||||
|<<history,history>>|Show browsing history
|
||||
|<<history-clear,history-clear>>|Clear all browsing history.
|
||||
|<<home,home>>|Open main startpage in current tab.
|
||||
|<<insert-text,insert-text>>|Insert text at cursor position.
|
||||
@ -418,6 +419,17 @@ Start hinting.
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
|
||||
[[history]]
|
||||
=== history
|
||||
Syntax: +:history [*--tab*] [*--bg*] [*--window*]+
|
||||
|
||||
Show browsing history
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||
* +*-b*+, +*--bg*+: Open in a background tab.
|
||||
* +*-w*+, +*--window*+: Open in a new window.
|
||||
|
||||
[[history-clear]]
|
||||
=== history-clear
|
||||
Syntax: +:history-clear [*--force*]+
|
||||
|
@ -19,6 +19,7 @@ parse-type==0.3.4
|
||||
py==1.4.32
|
||||
pytest==3.0.6
|
||||
pytest-bdd==2.18.1
|
||||
pytest-benchmark==3.0.0
|
||||
pytest-catchlog==1.2.2
|
||||
pytest-cov==2.4.0
|
||||
pytest-faulthandler==1.3.1
|
||||
|
@ -6,6 +6,7 @@ httpbin
|
||||
hypothesis
|
||||
pytest
|
||||
pytest-bdd
|
||||
pytest-benchmark
|
||||
pytest-catchlog
|
||||
pytest-cov
|
||||
pytest-faulthandler
|
||||
|
@ -1408,6 +1408,18 @@ class CommandDispatcher:
|
||||
|
||||
tab.dump_async(callback, plain=plain)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def history(self, tab=True, bg=False, window=False):
|
||||
"""Show browsing history.
|
||||
|
||||
Args:
|
||||
tab: Open in a new tab.
|
||||
bg: Open in a background tab.
|
||||
window: Open in a new window.
|
||||
"""
|
||||
url = QUrl('qute://history/')
|
||||
self._open(url, tab, bg, window)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='help',
|
||||
scope='window')
|
||||
@cmdutils.argument('topic', completion=usertypes.Completion.helptopic)
|
||||
|
@ -24,8 +24,13 @@ Module attributes:
|
||||
_HANDLERS: The handlers registered via decorators.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
import urllib.parse
|
||||
|
||||
from PyQt5.QtCore import QUrlQuery
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||
objreg)
|
||||
@ -158,6 +163,87 @@ def qute_bookmarks(_url):
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('history') # noqa
|
||||
def qute_history(url):
|
||||
"""Handler for qute:history. Display history."""
|
||||
# Get current date from query parameter, if not given choose today.
|
||||
curr_date = datetime.date.today()
|
||||
try:
|
||||
query_date = QUrlQuery(url).queryItemValue("date")
|
||||
if query_date:
|
||||
curr_date = datetime.datetime.strptime(query_date, "%Y-%m-%d")
|
||||
curr_date = curr_date.date()
|
||||
except ValueError:
|
||||
log.misc.debug("Invalid date passed to qute:history: " + query_date)
|
||||
|
||||
one_day = datetime.timedelta(days=1)
|
||||
next_date = curr_date + one_day
|
||||
prev_date = curr_date - one_day
|
||||
|
||||
def history_iter(reverse):
|
||||
"""Iterate through the history and get the items we're interested in."""
|
||||
curr_timestamp = time.mktime(curr_date.timetuple())
|
||||
history = objreg.get('web-history').history_dict.values()
|
||||
if reverse:
|
||||
history = reversed(history)
|
||||
|
||||
for item in history:
|
||||
# If we can't apply the reverse performance trick below,
|
||||
# at least continue as early as possible with old items.
|
||||
# This gets us down from 550ms to 123ms with 500k old items on my
|
||||
# machine.
|
||||
if item.atime < curr_timestamp and not reverse:
|
||||
continue
|
||||
|
||||
# Convert timestamp
|
||||
try:
|
||||
item_atime = datetime.datetime.fromtimestamp(item.atime)
|
||||
except (ValueError, OSError, OverflowError):
|
||||
log.misc.debug("Invalid timestamp {}.".format(item.atime))
|
||||
continue
|
||||
|
||||
if reverse and item_atime.date() < curr_date:
|
||||
# If we could reverse the history in-place, and this entry is
|
||||
# older than today, only older entries will follow, so we can
|
||||
# abort here.
|
||||
return
|
||||
|
||||
# Skip items not on curr_date
|
||||
# Skip redirects
|
||||
# Skip qute:// links
|
||||
is_internal = item.url.scheme() == 'qute'
|
||||
is_not_today = item_atime.date() != curr_date
|
||||
if item.redirect or is_internal or is_not_today:
|
||||
continue
|
||||
|
||||
# Use item's url as title if there's no title.
|
||||
item_url = item.url.toDisplayString()
|
||||
item_title = item.title if item.title else item_url
|
||||
display_atime = item_atime.strftime("%X")
|
||||
|
||||
yield (item_url, item_title, display_atime)
|
||||
|
||||
if sys.hexversion >= 0x03050000:
|
||||
# On Python >= 3.5 we can reverse the ordereddict in-place and thus
|
||||
# apply an additional performance improvement in history_iter.
|
||||
# On my machine, this gets us down from 550ms to 72us with 500k old
|
||||
# items.
|
||||
history = list(history_iter(reverse=True))
|
||||
else:
|
||||
# On Python 3.4, we can't do that, so we'd need to copy the entire
|
||||
# history to a list. There, filter first and then reverse it here.
|
||||
history = reversed(list(history_iter(reverse=False)))
|
||||
|
||||
html = jinja.render('history.html',
|
||||
title='History',
|
||||
history=history,
|
||||
curr_date=curr_date,
|
||||
next_date=next_date,
|
||||
prev_date=prev_date,
|
||||
today=datetime.date.today())
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('pyeval')
|
||||
def qute_pyeval(_url):
|
||||
"""Handler for qute:pyeval."""
|
||||
|
92
qutebrowser/html/history.html
Normal file
92
qutebrowser/html/history.html
Normal file
@ -0,0 +1,92 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block style %}
|
||||
body {
|
||||
background: #fefefe;
|
||||
font-family: sans-serif;
|
||||
margin: 0 auto;
|
||||
max-width: 1440px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #444;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td {
|
||||
max-width: 50%;
|
||||
padding: 2px 5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td.title {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
td.time {
|
||||
color: #555;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #2562dc
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.date {
|
||||
color: #888;
|
||||
font-size: 14pt;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.pagination-link {
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.pagination-link > a {
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Browsing history <span class="date">{{curr_date.strftime("%a, %d %B %Y")}}</span></h1>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
{% for url, title, time in history %}
|
||||
<tr>
|
||||
<td class="title"><a href="{{url}}">{{title}}</a></td>
|
||||
<td class="time">{{time}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<span class="pagination-link"><a href="qute://history/?date={{prev_date.strftime("%Y-%m-%d")}}" rel="prev">Previous</a></span>
|
||||
{% if today >= next_date %}
|
||||
<span class="pagination-link"><a href="qute://history/?date={{next_date.strftime("%Y-%m-%d")}}" rel="next">Next</a></span>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -74,6 +74,13 @@ Feature: Page history
|
||||
http://localhost:(port)/data/hints/html/simple.html Simple link
|
||||
http://localhost:(port)/data/hello.txt
|
||||
|
||||
Scenario: Listing history
|
||||
When I open data/numbers/3.txt
|
||||
And I open data/numbers/4.txt
|
||||
And I open qute:history
|
||||
Then the page should contain the plaintext "3.txt"
|
||||
Then the page should contain the plaintext "4.txt"
|
||||
|
||||
## Bugs
|
||||
|
||||
@qtwebengine_skip @qtwebkit_ng_skip
|
||||
|
@ -290,6 +290,24 @@ Feature: Various utility commands.
|
||||
- about:blank
|
||||
- qute://help/index.html (active)
|
||||
|
||||
# :history
|
||||
|
||||
Scenario: :history without arguments
|
||||
When I run :tab-only
|
||||
And I run :history
|
||||
And I wait until qute://history/ is loaded
|
||||
Then the following tabs should be open:
|
||||
- qute://history/ (active)
|
||||
|
||||
Scenario: :history with -t
|
||||
When I open about:blank
|
||||
And I run :tab-only
|
||||
And I run :history -t
|
||||
And I wait until qute://history/ is loaded
|
||||
Then the following tabs should be open:
|
||||
- about:blank
|
||||
- qute://history/ (active)
|
||||
|
||||
# :home
|
||||
|
||||
Scenario: :home with single page
|
||||
|
139
tests/unit/browser/test_qutescheme.py
Normal file
139
tests/unit/browser/test_qutescheme.py
Normal file
@ -0,0 +1,139 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2017 Imran Sobir
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
import collections
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
import pytest
|
||||
|
||||
from qutebrowser.browser import history, qutescheme
|
||||
from qutebrowser.utils import objreg
|
||||
|
||||
|
||||
Dates = collections.namedtuple('Dates', ['yesterday', 'today', 'tomorrow'])
|
||||
|
||||
|
||||
class TestHistoryHandler:
|
||||
|
||||
"""Test the qute://history endpoint."""
|
||||
|
||||
@pytest.fixture
|
||||
def dates(self):
|
||||
one_day = datetime.timedelta(days=1)
|
||||
today = datetime.datetime.today()
|
||||
tomorrow = today + one_day
|
||||
yesterday = today - one_day
|
||||
return Dates(yesterday, today, tomorrow)
|
||||
|
||||
@pytest.fixture
|
||||
def entries(self, dates):
|
||||
today = history.Entry(atime=str(dates.today.timestamp()),
|
||||
url=QUrl('www.today.com'), title='today')
|
||||
tomorrow = history.Entry(atime=str(dates.tomorrow.timestamp()),
|
||||
url=QUrl('www.tomorrow.com'), title='tomorrow')
|
||||
yesterday = history.Entry(atime=str(dates.yesterday.timestamp()),
|
||||
url=QUrl('www.yesterday.com'), title='yesterday')
|
||||
return Dates(yesterday, today, tomorrow)
|
||||
|
||||
@pytest.fixture
|
||||
def fake_web_history(self, fake_save_manager, tmpdir):
|
||||
"""Create a fake web-history and register it into objreg."""
|
||||
web_history = history.WebHistory(tmpdir.dirname, 'fake-history')
|
||||
objreg.register('web-history', web_history)
|
||||
yield web_history
|
||||
objreg.delete('web-history')
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def fake_history(self, fake_web_history, entries):
|
||||
"""Create fake history for three different days."""
|
||||
fake_web_history._add_entry(entries.yesterday)
|
||||
fake_web_history._add_entry(entries.today)
|
||||
fake_web_history._add_entry(entries.tomorrow)
|
||||
fake_web_history.save()
|
||||
|
||||
def test_history_without_query(self):
|
||||
"""Ensure qute://history shows today's history without any query."""
|
||||
_mimetype, data = qutescheme.qute_history(QUrl("qute://history"))
|
||||
key = "<span class=\"date\">{}</span>".format(
|
||||
datetime.date.today().strftime("%a, %d %B %Y"))
|
||||
assert key in data
|
||||
|
||||
def test_history_with_bad_query(self):
|
||||
"""Ensure qute://history shows today's history with bad query."""
|
||||
url = QUrl("qute://history?date=204-blaah")
|
||||
_mimetype, data = qutescheme.qute_history(url)
|
||||
key = "<span class=\"date\">{}</span>".format(
|
||||
datetime.date.today().strftime("%a, %d %B %Y"))
|
||||
assert key in data
|
||||
|
||||
def test_history_today(self):
|
||||
"""Ensure qute://history shows history for today."""
|
||||
url = QUrl("qute://history")
|
||||
_mimetype, data = qutescheme.qute_history(url)
|
||||
assert "today" in data
|
||||
assert "tomorrow" not in data
|
||||
assert "yesterday" not in data
|
||||
|
||||
def test_history_yesterday(self, dates):
|
||||
"""Ensure qute://history shows history for yesterday."""
|
||||
url = QUrl("qute://history?date=" +
|
||||
dates.yesterday.strftime("%Y-%m-%d"))
|
||||
_mimetype, data = qutescheme.qute_history(url)
|
||||
assert "today" not in data
|
||||
assert "tomorrow" not in data
|
||||
assert "yesterday" in data
|
||||
|
||||
def test_history_tomorrow(self, dates):
|
||||
"""Ensure qute://history shows history for tomorrow."""
|
||||
url = QUrl("qute://history?date=" +
|
||||
dates.tomorrow.strftime("%Y-%m-%d"))
|
||||
_mimetype, data = qutescheme.qute_history(url)
|
||||
assert "today" not in data
|
||||
assert "tomorrow" in data
|
||||
assert "yesterday" not in data
|
||||
|
||||
def test_no_next_link_to_future(self, dates):
|
||||
"""Ensure there's no next link pointing to the future."""
|
||||
url = QUrl("qute://history")
|
||||
_mimetype, data = qutescheme.qute_history(url)
|
||||
assert "Next" not in data
|
||||
|
||||
url = QUrl("qute://history?date=" +
|
||||
dates.tomorrow.strftime("%Y-%m-%d"))
|
||||
_mimetype, data = qutescheme.qute_history(url)
|
||||
assert "Next" not in data
|
||||
|
||||
def test_qute_history_benchmark(self, dates, entries, fake_web_history,
|
||||
benchmark):
|
||||
for i in range(100000):
|
||||
entry = history.Entry(
|
||||
atime=str(dates.yesterday.timestamp()),
|
||||
url=QUrl('www.yesterday.com/{}'.format(i)),
|
||||
title='yesterday')
|
||||
fake_web_history._add_entry(entry)
|
||||
fake_web_history._add_entry(entries.today)
|
||||
fake_web_history._add_entry(entries.tomorrow)
|
||||
|
||||
url = QUrl("qute://history")
|
||||
_mimetype, data = benchmark(qutescheme.qute_history, url)
|
||||
|
||||
assert "today" in data
|
||||
assert "tomorrow" not in data
|
||||
assert "yesterday" not in data
|
Loading…
Reference in New Issue
Block a user