From 845f21b275bf438eccd7854f7f5401233ec6719a Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Sun, 26 Feb 2017 17:07:30 +0500 Subject: [PATCH 001/299] New qute:history page. --- qutebrowser/browser/qutescheme.py | 116 ++++++++------- qutebrowser/html/history.html | 186 ++++++++++++++++++++----- tests/end2end/features/history.feature | 2 +- tests/unit/browser/test_qutescheme.py | 126 ++++++----------- 4 files changed, 251 insertions(+), 179 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 018582abf..e5b581fca 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -24,12 +24,12 @@ Module attributes: _HANDLERS: The handlers registered via decorators. """ +import json import sys import time -import datetime import urllib.parse -from PyQt5.QtCore import QUrlQuery +from PyQt5.QtCore import QUrl, QUrlQuery import qutebrowser from qutebrowser.utils import (version, utils, jinja, log, message, docutils, @@ -165,83 +165,77 @@ def qute_bookmarks(_url): @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) + """Handler for qute:history. Display and serve history.""" + def history_iter(start_time, reverse=False): + """Iterate through the history and get items we're interested. - 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 items we're interested in.""" - curr_timestamp = time.mktime(curr_date.timetuple()) + Keyword arguments: + reverse -- whether to reverse the history_dict before iterating. + start_time -- select history starting from this timestamp. + """ history = objreg.get('web-history').history_dict.values() if reverse: history = reversed(history) + end_time = start_time - 86400.0 # end is 24hrs earlier than start + 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 + # Abort/continue as early as possible + item_newer = item.atime > start_time + item_older = item.atime < end_time + if reverse: + # history_dict is reversed, we are going back in history. + # so: + # abort if item is older than start_time+24hr + # skip if item is newer than start + if item_older: + return + if item_newer: + continue + else: + # history_dict is not reversed, we are going forward in history. + # so: + # abort if item is newer than start_time + # skip if item is older than start_time+24hrs + if item_older: + continue + if item_newer: + return - # 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 items not within start_time and end_time # Skip redirects # Skip qute:// links + is_in_window = item.atime > end_time and item.atime <= start_time is_internal = item.url.scheme() == 'qute' - is_not_today = item_atime.date() != curr_date - if item.redirect or is_internal or is_not_today: + if item.redirect or is_internal or not is_in_window: 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") + item_time = int(item.atime) - yield (item_url, item_title, display_atime) + yield {"url": item_url, "title": item_title, "time": item_time} - 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)) + if QUrl(url).path() == '/data': + # Use start_time in query or current time. + start_time = QUrlQuery(url).queryItemValue("start_time") + start_time = float(start_time) if start_time else time.time() + + 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(start_time, 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(start_time, reverse=False))) + + return 'text/html', json.dumps(history) 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 + return 'text/html', jinja.render('history.html', title='History') @add_handler('pyeval') diff --git a/qutebrowser/html/history.html b/qutebrowser/html/history.html index 94f82182f..ba64316e4 100644 --- a/qutebrowser/html/history.html +++ b/qutebrowser/html/history.html @@ -16,43 +16,165 @@ td.time { white-space: nowrap; } +table { + margin-bottom: 30px; +} + .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; + color: #555; + font-size: 12pt; + padding-bottom: 15px; font-weight: bold; + text-align: left; +} + +.session-separator { + color: #aaa; + height: 40px; + text-align: center; +} + +{% endblock %} + +{% block script %} +/** + * Container for global stuff + */ +var global = { + // The last history item that was seen. + lastItem: null, + // The cutoff interval for session-separator (30 minutes) + SESSION_CUTOFF: 30*60 +}; + +/** + * Finds or creates the session table>tbody to which item with given date + * should be added. + * + * @param {Date} date - the date of the item being added. + */ +var getSessionNode = function(date) { + var histContainer = document.getElementById('hist-container'); + + // Find/create table + var tableId = "hist-" + date.getDate() + date.getMonth() + date.getYear(); + var table = document.getElementById(tableId); + if (table === null) { + table = document.createElement("table"); + table.id = tableId; + + caption = document.createElement("caption"); + caption.className = "date"; + var options = {weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'}; + caption.innerHTML = date.toLocaleDateString('en-US', options); + table.appendChild(caption); + + // Add table to page + histContainer.appendChild(table); + } + + // Find/create tbody + var tbody = table.lastChild; + if (tbody.tagName !== "TBODY") { // this is the caption + tbody = document.createElement("tbody"); + table.appendChild(tbody); + } + + // Create session-separator and new tbody if necessary + if (tbody.lastChild !== null && global.lastItem !== null) { + lastItemDate = new Date(parseInt(global.lastItem.time)*1000); + var interval = (lastItemDate.getTime() - date.getTime())/1000.00; + if (interval > global.SESSION_CUTOFF) { + // Add session-separator + var sessionSeparator = document.createElement('td'); + sessionSeparator.className = "session-separator"; + sessionSeparator.colSpan = 2; + sessionSeparator.innerHTML = "§" + table.appendChild(document.createElement('tr')); + table.lastChild.appendChild(sessionSeparator); + + // Create new tbody + tbody = document.createElement("tbody"); + table.appendChild(tbody); + } + } + + return tbody; +} + +/** + * Given a history item, create and return for it. + * param {string} itemUrl - The url for this item + * param {string} itemTitle - The title for this item + * param {string} itemTime - The formatted time for this item + */ +var makeHistoryRow = function(itemUrl, itemTitle, itemTime) { + var row = document.createElement('tr'); + + var title = document.createElement('td'); + title.className = "title"; + var link = document.createElement('a'); + link.href = itemUrl; + link.innerHTML = itemTitle; + title.appendChild(link); + + var time = document.createElement('td'); + time.className = "time"; + time.innerHTML = itemTime; + + row.appendChild(title); + row.appendChild(time); + + return row; +} + +var getJSON = function(url, callback) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'json'; + xhr.onload = function() { + var status = xhr.status; + callback(status, xhr.response); + }; + xhr.send(); +}; + +/** + * Load new history. + */ +var loadHistory = function() { + url = "qute://history/data"; + if (global.lastItem !== null) { + startTime = parseInt(global.lastItem.time) - 1; + url = "qute://history/data?start_time=" + startTime.toString(); + } + + getJSON(url, function(status, history) { + if (history !== undefined) { + for (item of history) { + atime = new Date(parseInt(item.time)*1000); + var session = getSessionNode(atime); + var row = makeHistoryRow(item.url, item.title, atime.toLocaleTimeString()); + session.appendChild(row) + global.lastItem = item; + } + } + }); } {% endblock %} {% block content %} +

Browsing history

+
+ {% endblock %} diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index bc7f537e6..2df4ada21 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -77,7 +77,7 @@ Feature: Page history Scenario: Listing history When I open data/numbers/3.txt And I open data/numbers/4.txt - And I open qute:history + And I open qute://history/data Then the page should contain the plaintext "3.txt" Then the page should contain the plaintext "4.txt" diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index 5ceecb7f8..6cdeb13f2 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -17,8 +17,8 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -import datetime -import collections +import json +import time from PyQt5.QtCore import QUrl import pytest @@ -27,30 +27,26 @@ 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) + def entries(self): + """Create fake history entries.""" + # create 12 history items spaced 6 hours apart, starting from now + entry_count = 12 + interval = 6 * 60 * 60 + self.now = time.time() - @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) + items = [] + for i in range(entry_count): + entry_atime = int(self.now - i * interval) + entry = history.Entry(atime=str(entry_atime), + url=QUrl("www.x.com/" + str(i)), title="Page " + str(i)) + items.insert(0, entry) + + return items @pytest.fixture def fake_web_history(self, fake_save_manager, tmpdir): @@ -62,78 +58,38 @@ class TestHistoryHandler: @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) + """Create fake history.""" + for item in entries: + fake_web_history._add_entry(item) 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 = "{}".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") + @pytest.mark.parametrize("start_time_offset, expected_item_count", [ + (0, 4), + (24*60*60, 4), + (48*60*60, 4), + (72*60*60, 0) + ]) + def test_qutehistory_data(self, start_time_offset, expected_item_count): + """Ensure qute://history/data returns correct items.""" + start_time = int(self.now) - start_time_offset + url = QUrl("qute://history/data?start_time=" + str(start_time)) _mimetype, data = qutescheme.qute_history(url) - key = "{}".format( - datetime.date.today().strftime("%a, %d %B %Y")) - assert key in data + items = json.loads(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 + assert len(items) == expected_item_count - 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 + end_time = start_time - 24*60*60 + for item in items: + assert item['time'] <= start_time + assert item['time'] > end_time - 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): + def test_qute_history_benchmark(self, fake_web_history, benchmark): + for t in range(100000): # one history per second entry = history.Entry( - atime=str(dates.yesterday.timestamp()), - url=QUrl('www.yesterday.com/{}'.format(i)), - title='yesterday') + atime=str(self.now - t), + url=QUrl('www.x.com/{}'.format(t)), + title='x at {}'.format(t)) fake_web_history._add_entry(entry) - fake_web_history._add_entry(entries.today) - fake_web_history._add_entry(entries.tomorrow) - url = QUrl("qute://history") + url = QUrl("qute://history/data?start_time={}".format(self.now)) _mimetype, data = benchmark(qutescheme.qute_history, url) - - assert "today" in data - assert "tomorrow" not in data - assert "yesterday" not in data From 76bf8c0049cd5385c8c662d746e842a80f2a3d2f Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Sun, 26 Feb 2017 19:58:14 +0500 Subject: [PATCH 002/299] Convert history to list before converting to JSON. --- qutebrowser/browser/qutescheme.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index e5b581fca..fe22738f9 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -227,13 +227,13 @@ def qute_history(url): # 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(start_time, reverse=True)) + history = history_iter(start_time, 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(start_time, reverse=False))) - return 'text/html', json.dumps(history) + return 'text/html', json.dumps(list(history)) else: return 'text/html', jinja.render('history.html', title='History') From c223f6c69daf419257ec11dad306194a4901bb94 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Sun, 26 Feb 2017 20:13:05 +0500 Subject: [PATCH 003/299] Style/misc fixes. --- qutebrowser/browser/qutescheme.py | 17 +++++++---------- tests/unit/browser/test_qutescheme.py | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index fe22738f9..6308e20fe 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -169,20 +169,20 @@ def qute_history(url): def history_iter(start_time, reverse=False): """Iterate through the history and get items we're interested. - Keyword arguments: - reverse -- whether to reverse the history_dict before iterating. - start_time -- select history starting from this timestamp. + Arguments: + reverse -- whether to reverse the history_dict before iterating. + start_time -- select history starting from this timestamp. """ history = objreg.get('web-history').history_dict.values() if reverse: history = reversed(history) - end_time = start_time - 86400.0 # end is 24hrs earlier than start + end_time = start_time - 24*60*60 # end is 24hrs earlier than start for item in history: # Abort/continue as early as possible item_newer = item.atime > start_time - item_older = item.atime < end_time + item_older = item.atime <= end_time if reverse: # history_dict is reversed, we are going back in history. # so: @@ -202,12 +202,9 @@ def qute_history(url): if item_newer: return - # Skip items not within start_time and end_time # Skip redirects # Skip qute:// links - is_in_window = item.atime > end_time and item.atime <= start_time - is_internal = item.url.scheme() == 'qute' - if item.redirect or is_internal or not is_in_window: + if item.redirect or item.url.scheme() == 'qute': continue # Use item's url as title if there's no title. @@ -217,7 +214,7 @@ def qute_history(url): yield {"url": item_url, "title": item_title, "time": item_time} - if QUrl(url).path() == '/data': + if url.path() == '/data': # Use start_time in query or current time. start_time = QUrlQuery(url).queryItemValue("start_time") start_time = float(start_time) if start_time else time.time() diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index 6cdeb13f2..d46626077 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -92,4 +92,4 @@ class TestHistoryHandler: fake_web_history._add_entry(entry) url = QUrl("qute://history/data?start_time={}".format(self.now)) - _mimetype, data = benchmark(qutescheme.qute_history, url) + _mimetype, _data = benchmark(qutescheme.qute_history, url) From c4416c8ac080d75298057a0140e19a7e700f74a0 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Sun, 26 Feb 2017 21:56:24 +0500 Subject: [PATCH 004/299] Prevent crash with invalid start_time param. --- qutebrowser/browser/qutescheme.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 6308e20fe..b6b95de53 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -216,8 +216,11 @@ def qute_history(url): if url.path() == '/data': # Use start_time in query or current time. - start_time = QUrlQuery(url).queryItemValue("start_time") - start_time = float(start_time) if start_time else time.time() + try: + start_time = QUrlQuery(url).queryItemValue("start_time") + start_time = float(start_time) if start_time else time.time() + except ValueError as e: + raise QuteSchemeError("Query parameter start_time is invalid", e) if sys.hexversion >= 0x03050000: # On Python >= 3.5 we can reverse the ordereddict in-place and thus From 783769d302235c8af758499aebdf320b6cf883d9 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Mon, 27 Feb 2017 21:20:54 +0500 Subject: [PATCH 005/299] Load new history items from next item's time. --- qutebrowser/browser/qutescheme.py | 22 +++++++++++++----- qutebrowser/html/history.html | 33 ++++++++++++++++++++++++--- tests/unit/browser/test_qutescheme.py | 29 +++++++++++++++++++---- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index b6b95de53..06417518c 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -179,8 +179,17 @@ def qute_history(url): end_time = start_time - 24*60*60 # end is 24hrs earlier than start + # when history_dict is not reversed, we need to keep track of last item + # so that we can yield its atime + last_item = None + for item in history: - # Abort/continue as early as possible + # Skip redirects + # Skip qute:// links + if item.redirect or item.url.scheme() == 'qute': + continue + + # Skip items out of time window item_newer = item.atime > start_time item_older = item.atime <= end_time if reverse: @@ -189,6 +198,7 @@ def qute_history(url): # abort if item is older than start_time+24hr # skip if item is newer than start if item_older: + yield {"next": int(item.atime)} return if item_newer: continue @@ -198,15 +208,12 @@ def qute_history(url): # abort if item is newer than start_time # skip if item is older than start_time+24hrs if item_older: + last_item = item continue if item_newer: + yield {"next": int(last_item.atime)} return - # Skip redirects - # Skip qute:// links - if item.redirect or item.url.scheme() == 'qute': - 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 @@ -214,6 +221,9 @@ def qute_history(url): yield {"url": item_url, "title": item_title, "time": item_time} + # if we reached here, we had reached the end of history + yield {"next": -1} + if url.path() == '/data': # Use start_time in query or current time. try: diff --git a/qutebrowser/html/history.html b/qutebrowser/html/history.html index ba64316e4..9523b10de 100644 --- a/qutebrowser/html/history.html +++ b/qutebrowser/html/history.html @@ -28,6 +28,19 @@ table { text-align: left; } +#load { + color: #555; + font-weight: bold; + text-decoration: none; +} + +#eof { + color: #aaa; + margin-bottom: 30px; + text-align: center; + width: 100%; +} + .session-separator { color: #aaa; height: 40px; @@ -43,6 +56,8 @@ table { var global = { // The last history item that was seen. lastItem: null, + // The next time to load + nextTime: null, // The cutoff interval for session-separator (30 minutes) SESSION_CUTOFF: 30*60 }; @@ -144,14 +159,25 @@ var getJSON = function(url, callback) { */ var loadHistory = function() { url = "qute://history/data"; - if (global.lastItem !== null) { - startTime = parseInt(global.lastItem.time) - 1; + if (global.nextTime !== null) { + startTime = global.nextTime; url = "qute://history/data?start_time=" + startTime.toString(); } getJSON(url, function(status, history) { if (history !== undefined) { for (item of history) { + if (item.next === -1) { + // Reached end of history + window.onscroll = null; + document.getElementById('eof').style.display = "block" + document.getElementById('load').style.display = "none" + continue; + } else if (item.next !== undefined) { + global.nextTime = parseInt(item.next); + continue; + } + atime = new Date(parseInt(item.time)*1000); var session = getSessionNode(atime); var row = makeHistoryRow(item.url, item.title, atime.toLocaleTimeString()); @@ -166,10 +192,11 @@ var loadHistory = function() { {% block content %}

Browsing history

+ +Show more + {% endblock %} diff --git a/qutebrowser/javascript/history.js b/qutebrowser/javascript/history.js new file mode 100644 index 000000000..b4e584bb0 --- /dev/null +++ b/qutebrowser/javascript/history.js @@ -0,0 +1,138 @@ +/** + * Container for global stuff + */ +var global = { + // The last history item that was seen. + lastItem: null, + // The next time to load + nextTime: null, + // The cutoff interval for session-separator (30 minutes) + SESSION_CUTOFF: 30*60 +}; + +/** + * Finds or creates the session table>tbody to which item with given date + * should be added. + * + * @param {Date} date - the date of the item being added. + */ +var getSessionNode = function(date) { + var histContainer = document.getElementById('hist-container'); + + // Find/create table + var tableId = "hist-" + date.getDate() + date.getMonth() + date.getYear(); + var table = document.getElementById(tableId); + if (table === null) { + table = document.createElement("table"); + table.id = tableId; + + caption = document.createElement("caption"); + caption.className = "date"; + var options = {weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'}; + caption.innerHTML = date.toLocaleDateString('en-US', options); + table.appendChild(caption); + + // Add table to page + histContainer.appendChild(table); + } + + // Find/create tbody + var tbody = table.lastChild; + if (tbody.tagName !== "TBODY") { // this is the caption + tbody = document.createElement("tbody"); + table.appendChild(tbody); + } + + // Create session-separator and new tbody if necessary + if (tbody.lastChild !== null && global.lastItem !== null) { + lastItemDate = new Date(parseInt(global.lastItem.time)*1000); + var interval = (lastItemDate.getTime() - date.getTime())/1000.00; + if (interval > global.SESSION_CUTOFF) { + // Add session-separator + var sessionSeparator = document.createElement('td'); + sessionSeparator.className = "session-separator"; + sessionSeparator.colSpan = 2; + sessionSeparator.innerHTML = "§"; + table.appendChild(document.createElement('tr')); + table.lastChild.appendChild(sessionSeparator); + + // Create new tbody + tbody = document.createElement("tbody"); + table.appendChild(tbody); + } + } + + return tbody; +} + +/** + * Given a history item, create and return for it. + * param {string} itemUrl - The url for this item + * param {string} itemTitle - The title for this item + * param {string} itemTime - The formatted time for this item + */ +var makeHistoryRow = function(itemUrl, itemTitle, itemTime) { + var row = document.createElement('tr'); + + var title = document.createElement('td'); + title.className = "title"; + var link = document.createElement('a'); + link.href = itemUrl; + link.innerHTML = itemTitle; + title.appendChild(link); + + var time = document.createElement('td'); + time.className = "time"; + time.innerHTML = itemTime; + + row.appendChild(title); + row.appendChild(time); + + return row; +} + +var getJSON = function(url, callback) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'json'; + xhr.onload = function() { + var status = xhr.status; + callback(status, xhr.response); + }; + xhr.send(); +}; + +/** + * Load new history. + */ +var loadHistory = function() { + url = "qute://history/data"; + if (global.nextTime !== null) { + startTime = global.nextTime; + url = "qute://history/data?start_time=" + startTime.toString(); + } + + getJSON(url, function(status, history) { + if (history !== undefined) { + for (item of history) { + if (item.next === -1) { + // Reached end of history + window.onscroll = null; + document.getElementById('eof').style.display = "block"; + document.getElementById('load').style.display = "none"; + continue; + } else if (item.next !== undefined) { + global.nextTime = parseInt(item.next); + continue; + } + + atime = new Date(parseInt(item.time)*1000); + var session = getSessionNode(atime); + var row = makeHistoryRow(item.url, item.title, atime.toLocaleTimeString()); + session.appendChild(row); + global.lastItem = item; + } + } + }); +} + From 3e45f739fc73c8b3d74ea6c22d7c2484af4d0adc Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Tue, 28 Feb 2017 17:21:42 +0500 Subject: [PATCH 008/299] Show message when Javascript is turned off. --- qutebrowser/html/history.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qutebrowser/html/history.html b/qutebrowser/html/history.html index a7c0eafc5..75925f546 100644 --- a/qutebrowser/html/history.html +++ b/qutebrowser/html/history.html @@ -47,9 +47,22 @@ table { text-align: center; } +.error { + background-color: #ffbbbb; + border-radius: 5px; + font-weight: bold; + padding: 10px; + text-align: center; + width: 100%; + border: 1px solid #ff7777; +} + {% endblock %} {% block content %}

Browsing history

+
Show more From 9e6b0240f67d56e643bd6d11adc9b7e00149ec06 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Tue, 28 Feb 2017 19:23:31 +0500 Subject: [PATCH 009/299] Put javascript in module, fix lint errors --- qutebrowser/html/history.html | 12 +- qutebrowser/javascript/history.js | 298 ++++++++++++++++++------------ 2 files changed, 186 insertions(+), 124 deletions(-) diff --git a/qutebrowser/html/history.html b/qutebrowser/html/history.html index 75925f546..33244896c 100644 --- a/qutebrowser/html/history.html +++ b/qutebrowser/html/history.html @@ -65,16 +65,22 @@ table {
-Show more +Show more {% endblock %} diff --git a/qutebrowser/javascript/history.js b/qutebrowser/javascript/history.js index b4e584bb0..3ff9f09db 100644 --- a/qutebrowser/javascript/history.js +++ b/qutebrowser/javascript/history.js @@ -1,138 +1,194 @@ /** - * Container for global stuff - */ -var global = { - // The last history item that was seen. - lastItem: null, - // The next time to load - nextTime: null, - // The cutoff interval for session-separator (30 minutes) - SESSION_CUTOFF: 30*60 -}; - -/** - * Finds or creates the session table>tbody to which item with given date - * should be added. + * Copyright 2017 Imran Sobir * - * @param {Date} date - the date of the item being added. + * 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 . */ -var getSessionNode = function(date) { - var histContainer = document.getElementById('hist-container'); - // Find/create table - var tableId = "hist-" + date.getDate() + date.getMonth() + date.getYear(); - var table = document.getElementById(tableId); - if (table === null) { - table = document.createElement("table"); - table.id = tableId; +"use strict"; - caption = document.createElement("caption"); - caption.className = "date"; - var options = {weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'}; - caption.innerHTML = date.toLocaleDateString('en-US', options); - table.appendChild(caption); +window.loadHistory = (function() { + // The time of last history item. + var lastTime = null; - // Add table to page - histContainer.appendChild(table); - } + // The time to load next. + var nextTime = null; - // Find/create tbody - var tbody = table.lastChild; - if (tbody.tagName !== "TBODY") { // this is the caption - tbody = document.createElement("tbody"); - table.appendChild(tbody); - } + // The cutoff interval for session-separator (30 minutes) + var SESSION_CUTOFF = 30 * 60; - // Create session-separator and new tbody if necessary - if (tbody.lastChild !== null && global.lastItem !== null) { - lastItemDate = new Date(parseInt(global.lastItem.time)*1000); - var interval = (lastItemDate.getTime() - date.getTime())/1000.00; - if (interval > global.SESSION_CUTOFF) { - // Add session-separator - var sessionSeparator = document.createElement('td'); - sessionSeparator.className = "session-separator"; - sessionSeparator.colSpan = 2; - sessionSeparator.innerHTML = "§"; - table.appendChild(document.createElement('tr')); - table.lastChild.appendChild(sessionSeparator); + // The URL to fetch data from. + var DATA_URL = "qute://history/data"; - // Create new tbody + // Various fixed elements + var EOF_MESSAGE = document.getElementById("eof"); + var LOAD_LINK = document.getElementById("load"); + var HIST_CONTAINER = document.getElementById("hist-container"); + + /** + * Finds or creates the session table>tbody to which item with given date + * should be added. + * + * @param {Date} date - the date of the item being added. + * @returns {Element} the element to which new rows should be added. + */ + function getSessionNode(date) { + // Find/create table + var tableId = "hist-".concat(date.getDate(), date.getMonth(), + date.getYear()); + var table = document.getElementById(tableId); + if (table === null) { + table = document.createElement("table"); + table.id = tableId; + + // Caption contains human-readable date + var caption = document.createElement("caption"); + caption.className = "date"; + var options = { + "weekday": "long", + "year": "numeric", + "month": "long", + "day": "numeric", + }; + caption.innerHTML = date.toLocaleDateString("en-US", options); + table.appendChild(caption); + + // Add table to page + HIST_CONTAINER.appendChild(table); + } + + // Find/create tbody + var tbody = table.lastChild; + if (tbody.tagName !== "TBODY") { tbody = document.createElement("tbody"); table.appendChild(tbody); } - } - return tbody; -} + // Create session-separator and new tbody if necessary + if (tbody.lastChild !== null && lastTime !== null) { + var lastItemDate = new Date(lastTime * 1000); + var interval = (lastItemDate.getTime() - date.getTime()) / 1000.00; + if (interval > SESSION_CUTOFF) { + // Add session-separator + var sessionSeparator = document.createElement("td"); + sessionSeparator.className = "session-separator"; + sessionSeparator.colSpan = 2; + sessionSeparator.innerHTML = "§"; + table.appendChild(document.createElement("tr")); + table.lastChild.appendChild(sessionSeparator); -/** - * Given a history item, create and return for it. - * param {string} itemUrl - The url for this item - * param {string} itemTitle - The title for this item - * param {string} itemTime - The formatted time for this item - */ -var makeHistoryRow = function(itemUrl, itemTitle, itemTime) { - var row = document.createElement('tr'); - - var title = document.createElement('td'); - title.className = "title"; - var link = document.createElement('a'); - link.href = itemUrl; - link.innerHTML = itemTitle; - title.appendChild(link); - - var time = document.createElement('td'); - time.className = "time"; - time.innerHTML = itemTime; - - row.appendChild(title); - row.appendChild(time); - - return row; -} - -var getJSON = function(url, callback) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.responseType = 'json'; - xhr.onload = function() { - var status = xhr.status; - callback(status, xhr.response); - }; - xhr.send(); -}; - -/** - * Load new history. - */ -var loadHistory = function() { - url = "qute://history/data"; - if (global.nextTime !== null) { - startTime = global.nextTime; - url = "qute://history/data?start_time=" + startTime.toString(); - } - - getJSON(url, function(status, history) { - if (history !== undefined) { - for (item of history) { - if (item.next === -1) { - // Reached end of history - window.onscroll = null; - document.getElementById('eof').style.display = "block"; - document.getElementById('load').style.display = "none"; - continue; - } else if (item.next !== undefined) { - global.nextTime = parseInt(item.next); - continue; - } - - atime = new Date(parseInt(item.time)*1000); - var session = getSessionNode(atime); - var row = makeHistoryRow(item.url, item.title, atime.toLocaleTimeString()); - session.appendChild(row); - global.lastItem = item; + // Create new tbody + tbody = document.createElement("tbody"); + table.appendChild(tbody); } } - }); -} + return tbody; + } + + /** + * Given a history item, create and return for it. + * + * @param {string} itemUrl - The url for this item. + * @param {string} itemTitle - The title for this item. + * @param {string} itemTime - The formatted time for this item. + * @returns {Element} the completed tr. + */ + function makeHistoryRow(itemUrl, itemTitle, itemTime) { + var row = document.createElement("tr"); + + var title = document.createElement("td"); + title.className = "title"; + var link = document.createElement("a"); + link.href = itemUrl; + link.innerHTML = itemTitle; + title.appendChild(link); + + var time = document.createElement("td"); + time.className = "time"; + time.innerHTML = itemTime; + + row.appendChild(title); + row.appendChild(time); + + return row; + } + + /** + * Get JSON from given URL. + * + * @param {string} url - the url to fetch data from. + * @param {function} callback - the function to callback with data. + * @returns {void} + */ + function getJSON(url, callback) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.responseType = "json"; + xhr.onload = function() { + var status = xhr.status; + callback(status, xhr.response); + }; + xhr.send(); + } + + /** + * Receive history data from qute://history/data. + * + * @param {Number} status - The status of the query. + * @param {Array} history - History data. + * @returns {void} + */ + function receiveHistory(status, history) { + if (history === undefined) { + return; + } + + for (var i = 0, len = history.length - 1; i < len; i++) { + var item = history[i]; + var atime = new Date(item.time * 1000); + var session = getSessionNode(atime); + var row = makeHistoryRow(item.url, item.title, + atime.toLocaleTimeString()); + session.appendChild(row); + lastTime = item.time; + } + + var next = history[history.length - 1].next; + if (next === -1) { + // Reached end of history + window.onscroll = null; + EOF_MESSAGE.style.display = "block"; + LOAD_LINK.style.display = "none"; + } else { + nextTime = next; + } + } + + /** + * Load new history. + * @return {void} + */ + function loadHistory() { + if (nextTime === null) { + getJSON(DATA_URL, receiveHistory); + } else { + var url = DATA_URL.concat("?start_time=", nextTime.toString()); + getJSON(url, receiveHistory); + } + } + + return loadHistory; +})(); From cb6c6b814e066c389d4d6bfb3d2da6a919c52222 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Tue, 28 Feb 2017 19:49:00 +0500 Subject: [PATCH 010/299] Fix pylint errors. --- qutebrowser/browser/qutescheme.py | 4 ++-- tests/unit/browser/test_qutescheme.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 77bb5fadc..0a09f4c9d 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -29,7 +29,7 @@ import sys import time import urllib.parse -from PyQt5.QtCore import QUrl, QUrlQuery +from PyQt5.QtCore import QUrlQuery import qutebrowser from qutebrowser.utils import (version, utils, jinja, log, message, docutils, @@ -203,7 +203,7 @@ def qute_history(url): if item_newer: continue else: - # history_dict is not reversed, we are going forward in history. + # history_dict isn't reversed, we are going forward in history. # so: # abort if item is newer than start_time # skip if item is older than start_time+24hrs diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index 736e739aa..f5f517040 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -42,7 +42,7 @@ class TestJavascriptHandler: def patch_read_file(self, monkeypatch): """Patch utils.read_file to return few fake JS files.""" def _read_file(path, binary=False): - """Faked utils.read_file""" + """Faked utils.read_file.""" assert not binary for filename, content in self.js_files: if path == os.path.join('javascript', filename): @@ -75,13 +75,15 @@ class TestHistoryHandler: """Test the qute://history endpoint.""" + # Current time + now = time.time() + @pytest.fixture def entries(self): """Create fake history entries.""" # create 12 history items spaced 6 hours apart, starting from now entry_count = 12 interval = 6 * 60 * 60 - self.now = time.time() items = [] for i in range(entry_count): From 895620b536c50e6a47f43a784fa37ee994e6975c Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Thu, 2 Mar 2017 22:40:24 +0500 Subject: [PATCH 011/299] Don't assume 'next' appears last. --- qutebrowser/browser/qutescheme.py | 2 +- tests/unit/browser/test_qutescheme.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 0a09f4c9d..0ec3989d5 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -211,7 +211,7 @@ def qute_history(url): last_item = item continue if item_newer: - yield {"next": int(last_item.atime)} + yield {"next": int(last_item.atime if last_item else -1)} return # Use item's url as title if there's no title. diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index f5f517040..2fe5a5fc4 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -110,10 +110,10 @@ class TestHistoryHandler: fake_web_history.save() @pytest.mark.parametrize("start_time_offset, expected_item_count", [ - (0, 5), - (24*60*60, 5), - (48*60*60, 5), - (72*60*60, 1) + (0, 4), + (24*60*60, 4), + (48*60*60, 4), + (72*60*60, 0) ]) def test_qutehistory_data(self, start_time_offset, expected_item_count): """Ensure qute://history/data returns correct items.""" @@ -121,12 +121,13 @@ class TestHistoryHandler: url = QUrl("qute://history/data?start_time=" + str(start_time)) _mimetype, data = qutescheme.qute_history(url) items = json.loads(data) + items = [item for item in items if 'time' in item] # skip 'next' item assert len(items) == expected_item_count # test times end_time = start_time - 24*60*60 - for item in items[:expected_item_count-1]: + for item in items: assert item['time'] <= start_time assert item['time'] > end_time @@ -142,6 +143,7 @@ class TestHistoryHandler: url = QUrl("qute://history/data?start_time=" + str(start_time)) _mimetype, data = qutescheme.qute_history(url) items = json.loads(data) + items = [item for item in items if 'next' in item] # 'next' items if next_time == -1: assert items[-1]["next"] == -1 From 96e81f595fb021b5299124ce03e013a1df08ad40 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Thu, 2 Mar 2017 23:14:00 +0500 Subject: [PATCH 012/299] Fix a case where 'next' is not correctly returned. --- qutebrowser/browser/qutescheme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 0ec3989d5..0ad71da36 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -222,7 +222,7 @@ def qute_history(url): yield {"url": item_url, "title": item_title, "time": item_time} # if we reached here, we had reached the end of history - yield {"next": -1} + yield {"next": int(last_item.atime if last_item else -1)} if url.path() == '/data': # Use start_time in query or current time. From 907d94591d2a0f90d9de60b92f5b67c00ac1cfa6 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Thu, 2 Mar 2017 23:14:36 +0500 Subject: [PATCH 013/299] Make a now fixture to hold time of test. --- tests/unit/browser/test_qutescheme.py | 29 +++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index 2fe5a5fc4..98dd4424e 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -75,11 +75,12 @@ class TestHistoryHandler: """Test the qute://history endpoint.""" - # Current time - now = time.time() + @pytest.fixture(scope="module") + def now(self): + return int(time.time()) @pytest.fixture - def entries(self): + def entries(self, now): """Create fake history entries.""" # create 12 history items spaced 6 hours apart, starting from now entry_count = 12 @@ -87,7 +88,7 @@ class TestHistoryHandler: items = [] for i in range(entry_count): - entry_atime = int(self.now - i * interval) + entry_atime = now - i * interval entry = history.Entry(atime=str(entry_atime), url=QUrl("www.x.com/" + str(i)), title="Page " + str(i)) items.insert(0, entry) @@ -115,9 +116,10 @@ class TestHistoryHandler: (48*60*60, 4), (72*60*60, 0) ]) - def test_qutehistory_data(self, start_time_offset, expected_item_count): + def test_qutehistory_data(self, start_time_offset, expected_item_count, + now): """Ensure qute://history/data returns correct items.""" - start_time = int(self.now) - start_time_offset + start_time = now - start_time_offset url = QUrl("qute://history/data?start_time=" + str(start_time)) _mimetype, data = qutescheme.qute_history(url) items = json.loads(data) @@ -137,26 +139,27 @@ class TestHistoryHandler: (48*60*60, -1), (72*60*60, -1) ]) - def test_qutehistory_next(self, start_time_offset, next_time): + def test_qutehistory_next(self, start_time_offset, next_time, now): """Ensure qute://history/data returns correct items.""" - start_time = int(self.now) - start_time_offset + start_time = now - start_time_offset url = QUrl("qute://history/data?start_time=" + str(start_time)) _mimetype, data = qutescheme.qute_history(url) items = json.loads(data) items = [item for item in items if 'next' in item] # 'next' items + assert len(items) == 1 if next_time == -1: - assert items[-1]["next"] == -1 + assert items[0]["next"] == -1 else: - assert items[-1]["next"] == int(self.now) - next_time + assert items[0]["next"] == now - next_time - def test_qute_history_benchmark(self, fake_web_history, benchmark): + def test_qute_history_benchmark(self, fake_web_history, benchmark, now): for t in range(100000): # one history per second entry = history.Entry( - atime=str(self.now - t), + atime=str(now - t), url=QUrl('www.x.com/{}'.format(t)), title='x at {}'.format(t)) fake_web_history._add_entry(entry) - url = QUrl("qute://history/data?start_time={}".format(self.now)) + url = QUrl("qute://history/data?start_time={}".format(now)) _mimetype, _data = benchmark(qutescheme.qute_history, url) From 0092b18c442b82fa571f07a5830d0d04a10e9d49 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Sat, 4 Mar 2017 19:37:48 +0500 Subject: [PATCH 014/299] Fix qute:javascript on Windows. --- qutebrowser/browser/qutescheme.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 0ad71da36..697c6761b 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -25,6 +25,7 @@ Module attributes: """ import json +import os import sys import time import urllib.parse @@ -256,7 +257,8 @@ def qute_javascript(url): """ path = url.path() if path: - return 'text/html', utils.read_file("javascript" + path, binary=False) + path = "javascript" + os.sep.join(path.split('/')) + return 'text/html', utils.read_file(path, binary=False) else: raise QuteSchemeError("No file specified", ValueError()) From 3fbeecbec2da29a5d8fbd2570e6142ef42561476 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Thu, 9 Mar 2017 21:35:22 +0500 Subject: [PATCH 015/299] Hide 'Show more' when Javascript is disabled. --- qutebrowser/html/history.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qutebrowser/html/history.html b/qutebrowser/html/history.html index 33244896c..d52a3c071 100644 --- a/qutebrowser/html/history.html +++ b/qutebrowser/html/history.html @@ -65,11 +65,13 @@ table {
-Show more + diff --git a/tests/end2end/features/sessions.feature b/tests/end2end/features/sessions.feature index 34a545841..0d7a77468 100644 --- a/tests/end2end/features/sessions.feature +++ b/tests/end2end/features/sessions.feature @@ -198,7 +198,7 @@ Feature: Saving and loading sessions Scenario: Saving a session with a page using history.replaceState() When I open data/sessions/history_replace_state.html without waiting - Then the javascript message "Calling history.replaceState" should be logged + Then the javascript message "Called history.replaceState" should be logged And the session should look like: windows: - tabs: @@ -212,7 +212,7 @@ Feature: Saving and loading sessions Scenario: Saving a session with a page using history.replaceState() and navigating away (qtwebkit) When I open data/sessions/history_replace_state.html And I open data/hello.txt - Then the javascript message "Calling history.replaceState" should be logged + Then the javascript message "Called history.replaceState" should be logged And the session should look like: windows: - tabs: @@ -229,7 +229,7 @@ Feature: Saving and loading sessions @qtwebkit_skip Scenario: Saving a session with a page using history.replaceState() and navigating away When I open data/sessions/history_replace_state.html without waiting - And I wait for "* Calling history.replaceState" in the log + And I wait for "* Called history.replaceState" in the log And I open data/hello.txt Then the session should look like: windows: From 004eb742f64e7786d810522fa8d3c71eaa386b59 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 29 Mar 2017 18:48:31 +0200 Subject: [PATCH 104/299] Stabilize test_guiprocess --- tests/unit/misc/test_guiprocess.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index 865e91a84..2d804dbe5 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -41,6 +41,7 @@ def proc(qtbot, caplog): p._proc.terminate() if not blocker.signal_triggered: p._proc.kill() + p._proc.waitForFinished() @pytest.fixture() From 6c3abadb3278b88a035b13e57c6c91874179e0c9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 29 Mar 2017 19:34:04 +0200 Subject: [PATCH 105/299] Stabilize :repeat-command test --- tests/end2end/features/utilcmds.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index 5804dd8cd..a54d68cbc 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -129,6 +129,7 @@ Feature: Miscellaneous utility commands exposed to the user. And I hint with args "all tab-fg" And I run :leave-mode And I run :repeat-command + And I wait for "hints: *" in the log And I run :follow-hint a And I wait until data/hello.txt is loaded Then the following tabs should be open: From a6041834f86049a5414675bf1287874481d3da84 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 29 Mar 2017 17:47:08 +0200 Subject: [PATCH 106/299] Try adding a PyPI testenv on AppVeyor --- .appveyor.yml | 2 ++ tox.ini | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 0f970d98b..7d5c650d5 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,6 +7,8 @@ environment: PYTHONUNBUFFERED: 1 matrix: - TESTENV: py34 + - TESTENV: py36-pyqt58 + PYTHON: C:\Python36\python.exe - TESTENV: unittests-frozen - TESTENV: pylint diff --git a/tox.ini b/tox.ini index 963b99697..29e38f8fd 100644 --- a/tox.ini +++ b/tox.ini @@ -101,7 +101,7 @@ deps = commands = {envpython} -bb -m pytest {posargs:tests} [testenv:py36-pyqt58] -basepython = python3.6 +basepython = {env:PYTHON:python3.6} setenv = {[testenv]setenv} QUTE_BDD_WEBENGINE=true From c28c42805131492a7add8fd4c108d19c86040c9e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 30 Mar 2017 21:53:13 +0200 Subject: [PATCH 107/299] appveyor: Add Python36 to PATH Otherwise the PyQt in the virtualenv won't be able to find python3.dll. --- .appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.appveyor.yml b/.appveyor.yml index 7d5c650d5..d3a790aaa 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -14,6 +14,7 @@ environment: install: - C:\Python27\python -u scripts\dev\ci\appveyor_install.py + - set PATH=%PATH%;C:\Python36 test_script: - C:\Python34\Scripts\tox -e %TESTENV% From fd276dabc732a944b14d3bb6c7ff4cf91c15a345 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 30 Mar 2017 21:58:33 +0200 Subject: [PATCH 108/299] appveyor_install: Don't install old PyQt if unneeded --- scripts/dev/ci/appveyor_install.py | 39 ++++++++++++++++-------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/scripts/dev/ci/appveyor_install.py b/scripts/dev/ci/appveyor_install.py index 2c04304d5..131906248 100644 --- a/scripts/dev/ci/appveyor_install.py +++ b/scripts/dev/ci/appveyor_install.py @@ -28,6 +28,7 @@ CI machines. from __future__ import print_function +import os import time import subprocess import urllib @@ -44,23 +45,6 @@ def pip_install(pkg): pkg]) -print("Getting PyQt5...") -qt_version = '5.5.1' -pyqt_version = '5.5.1' -pyqt_url = ('https://www.qutebrowser.org/pyqt/' - 'PyQt5-{}-gpl-Py3.4-Qt{}-x32.exe'.format( - pyqt_version, qt_version)) - -try: - urllib.urlretrieve(pyqt_url, r'C:\install-PyQt5.exe') -except (OSError, IOError): - print("Downloading PyQt failed, trying again in 10 seconds...") - time.sleep(10) - urllib.urlretrieve(pyqt_url, r'C:\install-PyQt5.exe') - -print("Installing PyQt5...") -subprocess.check_call([r'C:\install-PyQt5.exe', '/S']) - print("Installing tox") pip_install('pip') pip_install(r'-rmisc\requirements\requirements-tox.txt') @@ -69,4 +53,23 @@ print("Linking Python...") with open(r'C:\Windows\system32\python3.bat', 'w') as f: f.write(r'@C:\Python34\python %*') -check_setup(r'C:\Python34\python') + +if '-pyqt' not in os.environ['TESTENV']: + print("Getting PyQt5...") + qt_version = '5.5.1' + pyqt_version = '5.5.1' + pyqt_url = ('https://www.qutebrowser.org/pyqt/' + 'PyQt5-{}-gpl-Py3.4-Qt{}-x32.exe'.format( + pyqt_version, qt_version)) + + try: + urllib.urlretrieve(pyqt_url, r'C:\install-PyQt5.exe') + except (OSError, IOError): + print("Downloading PyQt failed, trying again in 10 seconds...") + time.sleep(10) + urllib.urlretrieve(pyqt_url, r'C:\install-PyQt5.exe') + + print("Installing PyQt5...") + subprocess.check_call([r'C:\install-PyQt5.exe', '/S']) + + check_setup(r'C:\Python34\python') From 189c1721afd51b6d6c05058d2ad6f243c65d91ec Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 31 Mar 2017 14:46:29 +0200 Subject: [PATCH 109/299] Don't wait for download.bin in windows downloads --- tests/end2end/features/downloads.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index c78416abe..51cb6a090 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -130,7 +130,7 @@ Feature: Downloading things from a website. @windows Scenario: Downloading a file to a reserved path When I set storage -> prompt-download-directory to true - And I open data/downloads/download.bin + And I open data/downloads/download.bin without waiting And I wait for "Asking question text='Please enter a location for http://localhost:*/data/downloads/download.bin' title='Save file to:'>, *" in the log And I run :prompt-accept COM1 And I run :leave-mode @@ -139,7 +139,7 @@ Feature: Downloading things from a website. @windows Scenario: Downloading a file to a drive-relative working directory When I set storage -> prompt-download-directory to true - And I open data/downloads/download.bin + And I open data/downloads/download.bin without waiting And I wait for "Asking question text='Please enter a location for http://localhost:*/data/downloads/download.bin' title='Save file to:'>, *" in the log And I run :prompt-accept C:foobar And I run :leave-mode From 8af5cfb4ac9e6d928cfeb0522fa729ba616df70a Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 31 Mar 2017 16:16:31 +0100 Subject: [PATCH 110/299] Add a modeline to all the *.feature files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This really tripped me up yesterday, My "Vim default" is to use tabs. This (where `!···` is a tab) does not work as you'll hope it works: Scenario: Retrying a failed download when the directory didn't exist (issue 2445) When I download http://localhost:(port)/data/downloads/download.bin to And I wait for the error "Download error: No such file or directory: *" And I make the directory And I run :download-retry !···!···And I wait until the download is finished Then the downloaded file should exist Examples: | path | mkdir | expected | | asd/zxc/ | asd/zxc | asd/zxc/download.bin | Unfortunately, pytest-bdd uses the "Python 2 behaviour" of "expand all tabs to 8 spaces", and doesn't give any errors on strange/inconsistent whitespace. It can cause very confusing errors. --- tests/end2end/features/adblock.feature | 2 ++ tests/end2end/features/backforward.feature | 2 ++ tests/end2end/features/caret.feature | 2 ++ tests/end2end/features/completion.feature | 2 ++ tests/end2end/features/downloads.feature | 2 ++ tests/end2end/features/editor.feature | 2 ++ tests/end2end/features/hints.feature | 2 ++ tests/end2end/features/history.feature | 2 ++ tests/end2end/features/invoke.feature | 2 ++ tests/end2end/features/javascript.feature | 2 ++ tests/end2end/features/keyinput.feature | 2 ++ tests/end2end/features/marks.feature | 2 ++ tests/end2end/features/misc.feature | 2 ++ tests/end2end/features/navigate.feature | 2 ++ tests/end2end/features/open.feature | 2 ++ tests/end2end/features/prompts.feature | 2 ++ tests/end2end/features/scroll.feature | 2 ++ tests/end2end/features/search.feature | 2 ++ tests/end2end/features/sessions.feature | 2 ++ tests/end2end/features/set.feature | 2 ++ tests/end2end/features/spawn.feature | 2 ++ tests/end2end/features/tabs.feature | 2 ++ tests/end2end/features/urlmarks.feature | 2 ++ tests/end2end/features/utilcmds.feature | 2 ++ tests/end2end/features/yankpaste.feature | 2 ++ tests/end2end/features/zoom.feature | 2 ++ 26 files changed, 52 insertions(+) diff --git a/tests/end2end/features/adblock.feature b/tests/end2end/features/adblock.feature index c33df30fe..c400df25f 100644 --- a/tests/end2end/features/adblock.feature +++ b/tests/end2end/features/adblock.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Ad blocking Scenario: Simple adblock update diff --git a/tests/end2end/features/backforward.feature b/tests/end2end/features/backforward.feature index 8f970837b..413ee9d95 100644 --- a/tests/end2end/features/backforward.feature +++ b/tests/end2end/features/backforward.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Going back and forward. Testing the :back/:forward commands. diff --git a/tests/end2end/features/caret.feature b/tests/end2end/features/caret.feature index 47b00622d..79a7b9d8f 100644 --- a/tests/end2end/features/caret.feature +++ b/tests/end2end/features/caret.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Caret mode In caret mode, the user can select and yank text using the keyboard. diff --git a/tests/end2end/features/completion.feature b/tests/end2end/features/completion.feature index 87db94e8d..b6c62336c 100644 --- a/tests/end2end/features/completion.feature +++ b/tests/end2end/features/completion.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Using completion Scenario: No warnings when completing with one entry (#1600) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index fe312944f..dec28339f 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Downloading things from a website. Background: diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature index 947dba0b0..e94e8b63c 100644 --- a/tests/end2end/features/editor.feature +++ b/tests/end2end/features/editor.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Opening external editors ## :edit-url diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index 5fac84402..7dc7c42ce 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Using hints # https://bugreports.qt.io/browse/QTBUG-58381 diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index 2df4ada21..a28f2c89e 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Page history Make sure the global page history is saved correctly. diff --git a/tests/end2end/features/invoke.feature b/tests/end2end/features/invoke.feature index 4b50f2af7..95ff3ab5e 100644 --- a/tests/end2end/features/invoke.feature +++ b/tests/end2end/features/invoke.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Invoking a new process Simulate what happens when running qutebrowser with an existing instance diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature index 13ab0d96a..ab96866be 100644 --- a/tests/end2end/features/javascript.feature +++ b/tests/end2end/features/javascript.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Javascript stuff Integration with javascript. diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index 6777056e8..452d66757 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Keyboard input Tests for :bind and :unbind, :clear-keychain and other keyboard input diff --git a/tests/end2end/features/marks.feature b/tests/end2end/features/marks.feature index e2738e23f..28de753c9 100644 --- a/tests/end2end/features/marks.feature +++ b/tests/end2end/features/marks.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Setting positional marks Background: diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 5e13e1feb..9a4f33844 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Various utility commands. ## :set-cmd-text diff --git a/tests/end2end/features/navigate.feature b/tests/end2end/features/navigate.feature index 5153400a4..cb57a4a15 100644 --- a/tests/end2end/features/navigate.feature +++ b/tests/end2end/features/navigate.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Using :navigate Scenario: :navigate with invalid argument diff --git a/tests/end2end/features/open.feature b/tests/end2end/features/open.feature index 89d6c9aa2..efc132466 100644 --- a/tests/end2end/features/open.feature +++ b/tests/end2end/features/open.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Opening pages Scenario: :open with URL diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 8b687abcc..73c8bf602 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Prompts Various prompts (javascript, SSL errors, authentification, etc.) diff --git a/tests/end2end/features/scroll.feature b/tests/end2end/features/scroll.feature index 05f9c22e8..44f60aa66 100644 --- a/tests/end2end/features/scroll.feature +++ b/tests/end2end/features/scroll.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Scrolling Tests the various scroll commands. diff --git a/tests/end2end/features/search.feature b/tests/end2end/features/search.feature index a2e2bd6c9..4d8b8e17f 100644 --- a/tests/end2end/features/search.feature +++ b/tests/end2end/features/search.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Searching on a page Searching text on the page (like /foo) with different options. diff --git a/tests/end2end/features/sessions.feature b/tests/end2end/features/sessions.feature index 34a545841..6508459a5 100644 --- a/tests/end2end/features/sessions.feature +++ b/tests/end2end/features/sessions.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Saving and loading sessions Background: diff --git a/tests/end2end/features/set.feature b/tests/end2end/features/set.feature index 769605c3e..b2b165542 100644 --- a/tests/end2end/features/set.feature +++ b/tests/end2end/features/set.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Setting settings. Background: diff --git a/tests/end2end/features/spawn.feature b/tests/end2end/features/spawn.feature index 445920924..8e3f88bd1 100644 --- a/tests/end2end/features/spawn.feature +++ b/tests/end2end/features/spawn.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: :spawn Scenario: Running :spawn diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index fc456f5f0..5e571d42d 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Tab management Tests for various :tab-* commands. diff --git a/tests/end2end/features/urlmarks.feature b/tests/end2end/features/urlmarks.feature index 800b6f794..45cf1454a 100644 --- a/tests/end2end/features/urlmarks.feature +++ b/tests/end2end/features/urlmarks.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: quickmarks and bookmarks ## bookmarks diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index 5804dd8cd..0ddd1eb6b 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Miscellaneous utility commands exposed to the user. Background: diff --git a/tests/end2end/features/yankpaste.feature b/tests/end2end/features/yankpaste.feature index 52c11ed22..189f74212 100644 --- a/tests/end2end/features/yankpaste.feature +++ b/tests/end2end/features/yankpaste.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Yanking and pasting. :yank, {clipboard} and {primary} can be used to copy/paste the URL or title from/to the clipboard and primary selection. diff --git a/tests/end2end/features/zoom.feature b/tests/end2end/features/zoom.feature index 3aa39df8b..015b85b17 100644 --- a/tests/end2end/features/zoom.feature +++ b/tests/end2end/features/zoom.feature @@ -1,3 +1,5 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + Feature: Zooming in and out Background: From 7f13c9a3c31aa719144ca3afcad7af305dd2f6ed Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Thu, 30 Mar 2017 18:37:15 +0100 Subject: [PATCH 111/299] Relax commandline parsing a bit Problem 1: Entering a command of `:::save` gives an error. Problem 2: Entering a command of `:save\n` gives an error. Both scenarios may seem a bit silly at first, but I encountered both by copy/pasting a command: 1. Enter `:` in qutebrowser. 2. Copy a full line from a terminal starting with `:`. 3. You will now have both of the above problems. Solution: Trim all whitespace and `:` of a command. This is also what Vim does, by the way. --- qutebrowser/commands/runners.py | 3 ++- tests/end2end/features/misc.feature | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index f6a8e07e2..0a2d6085f 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -133,7 +133,8 @@ class CommandRunner(QObject): Yields: ParseResult tuples. """ - if not text.strip(): + text = text.strip().lstrip(':').strip() + if not text: raise cmdexc.NoSuchCommandError("No command given") if aliases: diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index cff7d7854..0aeb65f92 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -538,6 +538,16 @@ Feature: Various utility commands. When I run :message-i "Hello World" (invalid command) Then the error "message-i: no such command" should be shown + Scenario: Multiple leading : in command + When I run :::::set-cmd-text ::::message-i "Hello World" + And I run :command-accept + Then the message "Hello World" should be shown + + Scenario: Whitespace in command + When I run : : set-cmd-text : : message-i "Hello World" + And I run :command-accept + Then the message "Hello World" should be shown + # We can't run :message-i as startup command, so we use # :set-cmd-text @@ -632,7 +642,7 @@ Feature: Various utility commands. And I run :command-history-prev And I run :command-accept Then the message "blah" should be shown - + Scenario: Browsing through commands When I run :set-cmd-text :message-info blarg And I run :command-accept @@ -644,7 +654,7 @@ Feature: Various utility commands. And I run :command-history-next And I run :command-accept Then the message "blarg" should be shown - + Scenario: Calling previous command when history is empty Given I have a fresh instance When I run :set-cmd-text : From 522e105aaf4c326d27e087222c0ba76680d1bed3 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 31 Mar 2017 19:49:32 +0200 Subject: [PATCH 112/299] Update flask from 0.12 to 0.12.1 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index e1123a1d7..0187026ce 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -6,7 +6,7 @@ click==6.7 coverage==4.3.4 decorator==4.0.11 EasyProcess==0.2.3 -Flask==0.12 +Flask==0.12.1 glob2==0.5 httpbin==0.5.0 hypothesis==3.7.0 From 1a337f6a77cf9e284abcf14813b6c70241e0045c Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 31 Mar 2017 18:51:04 +0100 Subject: [PATCH 113/299] Document how to do spell checking in the FAQ --- FAQ.asciidoc | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/FAQ.asciidoc b/FAQ.asciidoc index 71d5f2834..c934d7e64 100644 --- a/FAQ.asciidoc +++ b/FAQ.asciidoc @@ -124,6 +124,34 @@ When using quickmark, you can give them all names, like `:open foodrecipes`, you will see a list of all the food recipe sites, without having to remember the exact website title or address. +How do I use spell checking?:: + Qutebrowser's support for spell checking is somewhat limited at the moment + (see https://github.com/qutebrowser/qutebrowser/issues/700[#700]), but it + can be done. ++ +For QtWebKit: + +. Install https://github.com/QupZilla/qtwebkit-plugins[qtwebkit-plugins]. + . Note: with QtWebKit reloaded you may experience some issues. See + https://github.com/QupZilla/qtwebkit-plugins/issues/10[#10]. +. The dictionary to use is taken from the `DICTIONARY` environment variable. + The default is `en_US`. For example to use Dutch spell check set `DICTIONARY` + to `nl_NL`; you can't use multiple dictionaries or change them at runtime at + the moment. + (also see the README file for `qtwebkit-plugins`). +. Remember to install the hunspell dictionaries if you don't have them already + (most distros should have packages for this). + ++ +For QtWebEngine: + +. Not yet supported unfortunately :-( + + Adding it shouldn't be too hard though, since QtWebEngine 5.8 added an API for + this (see + https://github.com/qutebrowser/qutebrowser/issues/700#issuecomment-290780706[this + comment for a basic example]), so what are you waiting for and why aren't you + hacking qutebrowser yet? + == Troubleshooting Configuration not saved after modifying config.:: From 79a22f1f4751741cabd5f5a19ee4bcdb6b6dfce8 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Sat, 1 Apr 2017 21:14:35 +0100 Subject: [PATCH 114/299] Allow pressing ^C when there's an unknown setting All of it is just converting `objreg.get('xxx')` to `objreg.get('xxx', None)` and adding a `if xxx is not None` check. Fixes #1170 --- qutebrowser/app.py | 29 +++++++++++++++++------------ qutebrowser/misc/crashsignal.py | 8 ++++---- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 15a60ec4e..202eb9721 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -340,8 +340,9 @@ def _open_quickstart(args): def _save_version(): """Save the current version to the state config.""" - state_config = objreg.get('state-config') - state_config['general']['version'] = qutebrowser.__version__ + state_config = objreg.get('state-config', None) + if state_config is not None: + state_config['general']['version'] = qutebrowser.__version__ def on_focus_changed(_old, new): @@ -647,14 +648,14 @@ class Quitter: self._shutting_down = True log.destroy.debug("Shutting down with status {}, session {}...".format( status, session)) - - session_manager = objreg.get('session-manager') - if session is not None: - session_manager.save(session, last_window=last_window, - load_next_time=True) - elif config.get('general', 'save-session'): - session_manager.save(sessions.default, last_window=last_window, - load_next_time=True) + session_manager = objreg.get('session-manager', None) + if session_manager is not None: + if session is not None: + session_manager.save(session, last_window=last_window, + load_next_time=True) + elif config.get('general', 'save-session'): + session_manager.save(sessions.default, last_window=last_window, + load_next_time=True) if prompt.prompt_queue.shutdown(): # If shutdown was called while we were asking a question, we're in @@ -680,7 +681,9 @@ class Quitter: # Remove eventfilter try: log.destroy.debug("Removing eventfilter...") - qApp.removeEventFilter(objreg.get('event-filter')) + event_filter = objreg.get('event-filter', None) + if event_filter is not None: + qApp.removeEventFilter(event_filter) except AttributeError: pass # Close all windows @@ -722,7 +725,9 @@ class Quitter: # Now we can hopefully quit without segfaults log.destroy.debug("Deferring QApplication::exit...") objreg.get('signal-handler').deactivate() - objreg.get('session-manager').delete_autosave() + session_manager = objreg.get('session-manager', None) + if session_manager is not None: + session_manager.delete_autosave() # We use a singleshot timer to exit here to minimize the likelihood of # segfaults. QTimer.singleShot(0, functools.partial(qApp.exit, status)) diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index 35c39bf2e..aa7438732 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -171,14 +171,14 @@ class CrashHandler(QObject): """ try: pages = self._recover_pages(forgiving=True) - except Exception: - log.destroy.exception("Error while recovering pages") + except Exception as e: + log.destroy.exception("Error while recovering pages: {}".format(e)) pages = [] try: cmd_history = objreg.get('command-history')[-5:] - except Exception: - log.destroy.exception("Error while getting history: {}") + except Exception as e: + log.destroy.exception("Error while getting history: {}".format(e)) cmd_history = [] try: From 1f2e04c466c830745caac0aca736cfc33c4d758e Mon Sep 17 00:00:00 2001 From: Penaz Date: Sat, 1 Apr 2017 23:32:42 +0200 Subject: [PATCH 115/299] Update INSTALL.asciidoc Updated Install.asciidoc to include Live Install on Gentoo --- INSTALL.asciidoc | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/INSTALL.asciidoc b/INSTALL.asciidoc index 21bf4176b..7d8cca1d0 100644 --- a/INSTALL.asciidoc +++ b/INSTALL.asciidoc @@ -135,12 +135,34 @@ If video or sound don't seem to work, try installing the gstreamer plugins: On Gentoo --------- -qutebrowser is available in the main repository and can be installed with: +A version of qutebrowser is available in the main repository and can be installed with: ---- # emerge -av qutebrowser ---- +However it is suggested to install the Live version (-9999) to take advantage +of the newest features introduced. + +First of all you need to edit your package.accept_keywords file to accept the live +version: + +---- +# nano /etc/portage/package.accept_keywords +---- + +And add the following line to it: + + =www-client/qutebrowser-9999 ** + +Save the file and then install qutebrowser via + +---- +# emerge -av qutebrowser +---- + +Or rebuild your system if you already installed it. + Make sure you have `python3_4` in your `PYTHON_TARGETS` (`/etc/portage/make.conf`) and rebuild your system (`emerge -uDNav @world`) if necessary. From cdb3605b03911e0cffce7cc7a07bb5b120d195ce Mon Sep 17 00:00:00 2001 From: Penaz Date: Sun, 2 Apr 2017 10:36:11 +0200 Subject: [PATCH 116/299] Update INSTALL.asciidoc --- INSTALL.asciidoc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/INSTALL.asciidoc b/INSTALL.asciidoc index 7d8cca1d0..933859346 100644 --- a/INSTALL.asciidoc +++ b/INSTALL.asciidoc @@ -163,6 +163,14 @@ Save the file and then install qutebrowser via Or rebuild your system if you already installed it. +To update to the last Live version, remember to do + +---- +# emerge -uDNav @live-rebuild @world +---- + +To include qutebrowser among the updates. + Make sure you have `python3_4` in your `PYTHON_TARGETS` (`/etc/portage/make.conf`) and rebuild your system (`emerge -uDNav @world`) if necessary. From 2238a888dc70293adb4e253c81b9f1c600f93f18 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 2 Apr 2017 15:20:44 +0200 Subject: [PATCH 117/299] Fix changelog --- CHANGELOG.asciidoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 1e8afb3a2..e43780735 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,7 +32,8 @@ Changed version info. - Improved `qute:history` page with lazy loading - Messages are now hidden when clicked -- Paths like `C:Downloads` are now treated as absolute paths on Windows. +- Paths like `C:` are now treated as absolute paths on Windows for downloads, + and invalid paths are handled properly. - PAC on QtWebKit now supports SOCK5 as type. Fixed From f2ddf608a89334c40e11f89d689cfe69aa425870 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 2 Apr 2017 18:33:55 +0200 Subject: [PATCH 118/299] Update authors --- README.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/README.asciidoc b/README.asciidoc index 99062bd99..066c68078 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -285,6 +285,7 @@ Contributors, sorted by the number of commits in descending order: * Arseniy Seroka * Andy Balaam * Andreas Fischer +* Amos Bird * Akselmo // QUTE_AUTHORS_END From f595c2a7fb36280c5d0591f7ca711cf1d8c6db5d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 2 Apr 2017 18:34:39 +0200 Subject: [PATCH 119/299] Update changelog --- CHANGELOG.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index e43780735..40b90308d 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -45,6 +45,7 @@ Fixed - Crash when cloning page without history - Continuing a search after clearing it - Crash when downloading a download resulting in a HTTP error +- Crash when pressing ctrl-c while a config error is shown - Various rare crashes v0.10.1 From 338307ac24d3b30a11ef443fdbe88d43839cddc1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 2 Apr 2017 18:35:10 +0200 Subject: [PATCH 120/299] Add #noqa for Quitter._shutdown --- qutebrowser/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 202eb9721..3fe15ff46 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -672,7 +672,7 @@ class Quitter: # event loop, so we can shut down immediately. self._shutdown(status, restart=restart) - def _shutdown(self, status, restart): + def _shutdown(self, status, restart): # noqa """Second stage of shutdown.""" log.destroy.debug("Stage 2 of shutting down...") if qApp is None: From 48094fb33d4f12704c66ea8dee1c278d40bc8263 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 2 Apr 2017 18:47:16 +0200 Subject: [PATCH 121/299] Update authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 066c68078..65d9ed5ba 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -155,8 +155,8 @@ Contributors, sorted by the number of commits in descending order: * Alexander Cogneau * Felix Van der Jeugt * Daniel Karbach -* Imran Sobir * Martin Tournoij +* Imran Sobir * Kevin Velghe * Raphael Pierzina * Joel Torstensson From ad6ed837820cf85217b17879b14a07359f82ec4f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 2 Apr 2017 19:17:13 +0200 Subject: [PATCH 122/299] Update changelog --- CHANGELOG.asciidoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 40b90308d..b74535176 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -34,7 +34,9 @@ Changed - Messages are now hidden when clicked - Paths like `C:` are now treated as absolute paths on Windows for downloads, and invalid paths are handled properly. -- PAC on QtWebKit now supports SOCK5 as type. +- PAC on QtWebKit now supports SOCKS5 as type. +- Comments in the config file are now before the individual + options instead of being before sections. Fixed ~~~~~ From e3fc23fa308fc36d7198e7ac8cbad121d8956fa5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 2 Apr 2017 19:24:06 +0200 Subject: [PATCH 123/299] Update authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 65d9ed5ba..ad4c5232d 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -154,8 +154,8 @@ Contributors, sorted by the number of commits in descending order: * Bruno Oliveira * Alexander Cogneau * Felix Van der Jeugt -* Daniel Karbach * Martin Tournoij +* Daniel Karbach * Imran Sobir * Kevin Velghe * Raphael Pierzina From 30655e29fc292ec99cea41a03edea751fd5022b9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 2 Apr 2017 22:58:22 +0200 Subject: [PATCH 124/299] Regenerate authors --- README.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/README.asciidoc b/README.asciidoc index ad4c5232d..3228d8c7b 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -239,6 +239,7 @@ Contributors, sorted by the number of commits in descending order: * adam * Samir Benmendil * Regina Hug +* Penaz * Mathias Fussenegger * Marcelo Santos * Joel Bradshaw From 4004d5adf09e6d22dae5d781a02c5fc2bbd26724 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Sat, 1 Apr 2017 21:46:36 +0100 Subject: [PATCH 125/299] Don't crash when trying to write an unwritable keyconf. Also change the logic in _load_default a wee bit so that it won't try to write the keys.conf on startup. Fixes #1235 --- qutebrowser/config/parsers/keyconf.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/qutebrowser/config/parsers/keyconf.py b/qutebrowser/config/parsers/keyconf.py index 6ca5f72b7..d8aaa960e 100644 --- a/qutebrowser/config/parsers/keyconf.py +++ b/qutebrowser/config/parsers/keyconf.py @@ -142,9 +142,14 @@ class KeyConfigParser(QObject): def save(self): """Save the key config file.""" log.destroy.debug("Saving key config to {}".format(self._configfile)) - with qtutils.savefile_open(self._configfile, encoding='utf-8') as f: - data = str(self) - f.write(data) + + try: + with qtutils.savefile_open(self._configfile, + encoding='utf-8') as f: + data = str(self) + f.write(data) + except OSError as e: + message.error("Could not save key config: {}".format(e)) @cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True, no_replace_variables=True) @@ -252,6 +257,7 @@ class KeyConfigParser(QObject): """ # {'sectname': {'keychain1': 'command', 'keychain2': 'command'}, ...} bindings_to_add = collections.OrderedDict() + mark_dirty = False for sectname, sect in configdata.KEY_DATA.items(): sectname = self._normalize_sectname(sectname) @@ -261,6 +267,7 @@ class KeyConfigParser(QObject): if not only_new or self._is_new(sectname, command, e): assert e not in bindings_to_add[sectname] bindings_to_add[sectname][e] = command + mark_dirty = True for sectname, sect in bindings_to_add.items(): if not sect: @@ -271,7 +278,7 @@ class KeyConfigParser(QObject): self._add_binding(sectname, keychain, command) self.changed.emit(sectname) - if bindings_to_add: + if mark_dirty: self._mark_config_dirty() def _is_new(self, sectname, command, keychain): From 9dc5e978ac92c514c92ea2e345046f8db84de7b2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 3 Apr 2017 06:55:18 +0200 Subject: [PATCH 126/299] Update docs --- CHANGELOG.asciidoc | 1 + README.asciidoc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index b74535176..ba5b7cfed 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -48,6 +48,7 @@ Fixed - Continuing a search after clearing it - Crash when downloading a download resulting in a HTTP error - Crash when pressing ctrl-c while a config error is shown +- Crash when the key config isn't writable - Various rare crashes v0.10.1 diff --git a/README.asciidoc b/README.asciidoc index 3228d8c7b..90c6abde9 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -153,8 +153,8 @@ Contributors, sorted by the number of commits in descending order: * Marshall Lochbaum * Bruno Oliveira * Alexander Cogneau -* Felix Van der Jeugt * Martin Tournoij +* Felix Van der Jeugt * Daniel Karbach * Imran Sobir * Kevin Velghe From a11356bb992bbe3070594c33af082f967c1c90c5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 3 Apr 2017 08:32:39 +0200 Subject: [PATCH 127/299] Don't require working icon to start --- qutebrowser/app.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 3fe15ff46..6bea00f31 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -170,12 +170,15 @@ def _init_icon(): for size in [16, 24, 32, 48, 64, 96, 128, 256, 512]: filename = ':/icons/qutebrowser-{}x{}.png'.format(size, size) pixmap = QPixmap(filename) - qtutils.ensure_not_null(pixmap) - fallback_icon.addPixmap(pixmap) - qtutils.ensure_not_null(fallback_icon) + if pixmap.isNull(): + log.init.warn("Failed to load {}".format(filename)) + else: + fallback_icon.addPixmap(pixmap) icon = QIcon.fromTheme('qutebrowser', fallback_icon) - qtutils.ensure_not_null(icon) - qApp.setWindowIcon(icon) + if icon.isNull(): + log.init.warn("Failed to load icon") + else: + qApp.setWindowIcon(icon) def _process_args(args): From 3b1b325711ea5a2e5a3d8d59bee2f27b3b06b4fb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 3 Apr 2017 09:04:28 +0200 Subject: [PATCH 128/299] Fix logging --- qutebrowser/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 6bea00f31..81079104a 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -171,12 +171,12 @@ def _init_icon(): filename = ':/icons/qutebrowser-{}x{}.png'.format(size, size) pixmap = QPixmap(filename) if pixmap.isNull(): - log.init.warn("Failed to load {}".format(filename)) + log.init.warning("Failed to load {}".format(filename)) else: fallback_icon.addPixmap(pixmap) icon = QIcon.fromTheme('qutebrowser', fallback_icon) if icon.isNull(): - log.init.warn("Failed to load icon") + log.init.warning("Failed to load icon") else: qApp.setWindowIcon(icon) From 2c3fcda18e14c36cd3d39288cd622ad993fa1408 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 3 Apr 2017 09:32:13 +0200 Subject: [PATCH 129/299] Remove qtutils.ensure_not_null It's not used anymore. --- qutebrowser/utils/qtutils.py | 6 ----- tests/unit/utils/test_qtutils.py | 42 ++++++++++---------------------- 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index e36fd0ffb..5c98a24da 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -174,12 +174,6 @@ def ensure_valid(obj): raise QtValueError(obj) -def ensure_not_null(obj): - """Ensure a Qt object with an .isNull() method is not null.""" - if obj.isNull(): - raise QtValueError(obj, null=True) - - def check_qdatastream(stream): """Check the status of a QDataStream and raise OSError if it's not ok.""" status_to_str = { diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py index b2de7b58d..74da2ac4f 100644 --- a/tests/unit/utils/test_qtutils.py +++ b/tests/unit/utils/test_qtutils.py @@ -209,48 +209,32 @@ class QtObject: return self._null -@pytest.mark.parametrize('func_name, obj, raising, exc_reason, exc_str', [ - # ensure_valid, good examples - ('ensure_valid', QtObject(valid=True, null=True), False, None, None), - ('ensure_valid', QtObject(valid=True, null=False), False, None, None), - # ensure_valid, bad examples - ('ensure_valid', QtObject(valid=False, null=True), True, None, - ' is not valid'), - ('ensure_valid', QtObject(valid=False, null=False), True, None, - ' is not valid'), - ('ensure_valid', QtObject(valid=False, null=True, error='Test'), True, - 'Test', ' is not valid: Test'), - # ensure_not_null, good examples - ('ensure_not_null', QtObject(valid=True, null=False), False, None, None), - ('ensure_not_null', QtObject(valid=False, null=False), False, None, None), - # ensure_not_null, bad examples - ('ensure_not_null', QtObject(valid=True, null=True), True, None, - ' is null'), - ('ensure_not_null', QtObject(valid=False, null=True), True, None, - ' is null'), - ('ensure_not_null', QtObject(valid=False, null=True, error='Test'), True, - 'Test', ' is null: Test'), +@pytest.mark.parametrize('obj, raising, exc_reason, exc_str', [ + # good examples + (QtObject(valid=True, null=True), False, None, None), + (QtObject(valid=True, null=False), False, None, None), + # bad examples + (QtObject(valid=False, null=True), True, None, ' is not valid'), + (QtObject(valid=False, null=False), True, None, ' is not valid'), + (QtObject(valid=False, null=True, error='Test'), True, 'Test', + ' is not valid: Test'), ]) -def test_ensure(func_name, obj, raising, exc_reason, exc_str): - """Test ensure_valid and ensure_not_null. - - The function is parametrized as they do nearly the same. +def test_ensure_valid(obj, raising, exc_reason, exc_str): + """Test ensure_valid. Args: - func_name: The name of the function to call. obj: The object to test with. raising: Whether QtValueError is expected to be raised. exc_reason: The expected .reason attribute of the exception. exc_str: The expected string of the exception. """ - func = getattr(qtutils, func_name) if raising: with pytest.raises(qtutils.QtValueError) as excinfo: - func(obj) + qtutils.ensure_valid(obj) assert excinfo.value.reason == exc_reason assert str(excinfo.value) == exc_str else: - func(obj) + qtutils.ensure_valid(obj) @pytest.mark.parametrize('status, raising, message', [ From cb4c64eec99694af89f5256c176f186032c2395f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 3 Apr 2017 10:18:56 +0200 Subject: [PATCH 130/299] Remove null argument for QtValueError --- qutebrowser/utils/qtutils.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index 5c98a24da..adf0b2737 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -406,15 +406,12 @@ class QtValueError(ValueError): """Exception which gets raised by ensure_valid.""" - def __init__(self, obj, null=False): + def __init__(self, obj): try: self.reason = obj.errorString() except AttributeError: self.reason = None - if null: - err = "{} is null".format(obj) - else: - err = "{} is not valid".format(obj) + err = "{} is not valid".format(obj) if self.reason: err += ": {}".format(self.reason) super().__init__(err) From 8f821137483b7e16c1ff53a03e8774deea2cfc1a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 3 Apr 2017 16:32:48 +0200 Subject: [PATCH 131/299] Update jinja2 from 2.9.5 to 2.9.6 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a62285a08..5b4053a33 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ colorama==0.3.7 cssutils==1.0.2 -Jinja2==2.9.5 +Jinja2==2.9.6 MarkupSafe==1.0 Pygments==2.2.0 pyPEG2==2.15.2 From 1b0ea19ca47e65d353ccb7f19fd5158f013b0cf4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 3 Apr 2017 17:48:12 +0200 Subject: [PATCH 132/299] Disable QtNetwork cache on Qt 5.8 See #2427 --- CHANGELOG.asciidoc | 2 ++ qutebrowser/browser/webkit/cache.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index ba5b7cfed..5ac5f9460 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -37,6 +37,8 @@ Changed - PAC on QtWebKit now supports SOCKS5 as type. - Comments in the config file are now before the individual options instead of being before sections. +- The HTTP cache is disabled with QtWebKit on Qt 5.8 now as it leads to frequent + crashes due to a Qt bug. Fixed ~~~~~ diff --git a/qutebrowser/browser/webkit/cache.py b/qutebrowser/browser/webkit/cache.py index 860a532b0..78f459e3f 100644 --- a/qutebrowser/browser/webkit/cache.py +++ b/qutebrowser/browser/webkit/cache.py @@ -25,7 +25,7 @@ from PyQt5.QtCore import pyqtSlot from PyQt5.QtNetwork import QNetworkDiskCache, QNetworkCacheMetaData from qutebrowser.config import config -from qutebrowser.utils import utils, objreg +from qutebrowser.utils import utils, objreg, qtutils class DiskCache(QNetworkDiskCache): @@ -53,6 +53,9 @@ class DiskCache(QNetworkDiskCache): size = config.get('storage', 'cache-size') if size is None: size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate + # WORKAROUND for https://github.com/qutebrowser/qutebrowser/issues/2427 + if qtutils.version_check('5.8', exact=True): + size = 0 self.setMaximumCacheSize(size) def _maybe_activate(self): From b6642e66fa1594a0a6e598110a1f53a299999a25 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 3 Apr 2017 19:41:37 +0200 Subject: [PATCH 133/299] Fix cache tests on Qt 5.8 --- qutebrowser/browser/webkit/cache.py | 2 +- tests/unit/browser/webkit/test_cache.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/webkit/cache.py b/qutebrowser/browser/webkit/cache.py index 78f459e3f..349f9a61e 100644 --- a/qutebrowser/browser/webkit/cache.py +++ b/qutebrowser/browser/webkit/cache.py @@ -54,7 +54,7 @@ class DiskCache(QNetworkDiskCache): if size is None: size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate # WORKAROUND for https://github.com/qutebrowser/qutebrowser/issues/2427 - if qtutils.version_check('5.8', exact=True): + if qtutils.version_check('5.8', exact=True): # pragma: no cover size = 0 self.setMaximumCacheSize(size) diff --git a/tests/unit/browser/webkit/test_cache.py b/tests/unit/browser/webkit/test_cache.py index c716b9b82..7e2164b9c 100644 --- a/tests/unit/browser/webkit/test_cache.py +++ b/tests/unit/browser/webkit/test_cache.py @@ -17,10 +17,16 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . +import pytest from PyQt5.QtCore import QUrl, QDateTime from PyQt5.QtNetwork import QNetworkDiskCache, QNetworkCacheMetaData from qutebrowser.browser.webkit import cache +from qutebrowser.utils import qtutils + + +pytestmark = pytest.mark.skipif(qtutils.version_check('5.8', exact=True), + reason="QNetworkDiskCache is broken on Qt 5.8") def preload_cache(cache, url='http://www.example.com/', content=b'foobar'): From 2eb365b1468c5a18df267f739dfbc10faa64216f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 3 Apr 2017 20:22:54 +0200 Subject: [PATCH 134/299] Also disable cache on Qt 5.7 --- qutebrowser/browser/webkit/cache.py | 4 ++-- tests/unit/browser/webkit/test_cache.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/webkit/cache.py b/qutebrowser/browser/webkit/cache.py index 349f9a61e..4e09844cc 100644 --- a/qutebrowser/browser/webkit/cache.py +++ b/qutebrowser/browser/webkit/cache.py @@ -53,8 +53,8 @@ class DiskCache(QNetworkDiskCache): size = config.get('storage', 'cache-size') if size is None: size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate - # WORKAROUND for https://github.com/qutebrowser/qutebrowser/issues/2427 - if qtutils.version_check('5.8', exact=True): # pragma: no cover + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909 + if qtutils.version_check('5.7'): # pragma: no cover size = 0 self.setMaximumCacheSize(size) diff --git a/tests/unit/browser/webkit/test_cache.py b/tests/unit/browser/webkit/test_cache.py index 7e2164b9c..8d3f2a99b 100644 --- a/tests/unit/browser/webkit/test_cache.py +++ b/tests/unit/browser/webkit/test_cache.py @@ -25,8 +25,9 @@ from qutebrowser.browser.webkit import cache from qutebrowser.utils import qtutils -pytestmark = pytest.mark.skipif(qtutils.version_check('5.8', exact=True), - reason="QNetworkDiskCache is broken on Qt 5.8") +pytestmark = pytest.mark.skipif(qtutils.version_check('5.7'), + reason="QNetworkDiskCache is broken on Qt >= " + "5.7") def preload_cache(cache, url='http://www.example.com/', content=b'foobar'): From 0de3b5460e5e3f46384309bcff45a096b9fef740 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 4 Apr 2017 08:24:50 +0200 Subject: [PATCH 135/299] Only disable the cache on Qt 5.7.1 I ended up bisecting it, and https://codereview.qt-project.org/#/c/153977/ causes this, which is not in 5.7.0. --- qutebrowser/browser/webkit/cache.py | 2 +- tests/unit/browser/webkit/test_cache.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/webkit/cache.py b/qutebrowser/browser/webkit/cache.py index 4e09844cc..8bbb8f812 100644 --- a/qutebrowser/browser/webkit/cache.py +++ b/qutebrowser/browser/webkit/cache.py @@ -54,7 +54,7 @@ class DiskCache(QNetworkDiskCache): if size is None: size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909 - if qtutils.version_check('5.7'): # pragma: no cover + if qtutils.version_check('5.7.1'): # pragma: no cover size = 0 self.setMaximumCacheSize(size) diff --git a/tests/unit/browser/webkit/test_cache.py b/tests/unit/browser/webkit/test_cache.py index 8d3f2a99b..ea2473949 100644 --- a/tests/unit/browser/webkit/test_cache.py +++ b/tests/unit/browser/webkit/test_cache.py @@ -25,9 +25,9 @@ from qutebrowser.browser.webkit import cache from qutebrowser.utils import qtutils -pytestmark = pytest.mark.skipif(qtutils.version_check('5.7'), +pytestmark = pytest.mark.skipif(qtutils.version_check('5.7.1'), reason="QNetworkDiskCache is broken on Qt >= " - "5.7") + "5.7.1") def preload_cache(cache, url='http://www.example.com/', content=b'foobar'): From c5427a01272230b1b35afaa9764f77368fb7bfb8 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Tue, 4 Apr 2017 09:50:12 +0100 Subject: [PATCH 136/299] Fix display of errors while reading the key config file Also catch `cmdexc.CommandError` on startup to show these errors in the alert dialog on startup. Fixes #1340 --- qutebrowser/config/config.py | 3 ++- qutebrowser/config/parsers/keyconf.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 83e0e9c8f..1e0cec92b 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -186,7 +186,8 @@ def _init_key_config(parent): key_config = keyconf.KeyConfigParser(standarddir.config(), 'keys.conf', args.relaxed_config, parent=parent) - except (keyconf.KeyConfigError, UnicodeDecodeError) as e: + except (keyconf.KeyConfigError, cmdexc.CommandError, + UnicodeDecodeError) as e: log.init.exception(e) errstr = "Error while reading key config:\n" if e.lineno is not None: diff --git a/qutebrowser/config/parsers/keyconf.py b/qutebrowser/config/parsers/keyconf.py index d8aaa960e..2f0d8df69 100644 --- a/qutebrowser/config/parsers/keyconf.py +++ b/qutebrowser/config/parsers/keyconf.py @@ -322,7 +322,7 @@ class KeyConfigParser(QObject): else: line = line.strip() self._read_command(line) - except KeyConfigError as e: + except (KeyConfigError, cmdexc.CommandError) as e: if relaxed: continue else: From be254be13a61171d4109224450db9e67d1076080 Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Tue, 4 Apr 2017 19:20:55 +0500 Subject: [PATCH 137/299] Use new history page on webkit-ng. --- qutebrowser/browser/qutescheme.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 80a808773..9fddb2c38 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -36,7 +36,7 @@ from PyQt5.QtCore import QUrlQuery import qutebrowser from qutebrowser.config import config from qutebrowser.utils import (version, utils, jinja, log, message, docutils, - objreg, usertypes) + objreg, usertypes, qtutils) from qutebrowser.misc import objects @@ -257,9 +257,15 @@ def qute_history(url): return 'text/html', json.dumps(history_data(start_time)) else: + try: + from PyQt5.QtWebKit import qWebKitVersion + is_webkit_ng = qtutils.is_qtwebkit_ng(qWebKitVersion()) + except ImportError: # pragma: no cover + is_webkit_ng = False + if ( config.get('content', 'allow-javascript') and - objects.backend == usertypes.Backend.QtWebEngine + (objects.backend == usertypes.Backend.QtWebEngine or is_webkit_ng) ): return 'text/html', jinja.render( 'history.html', From 6c8ca3076693d41f2dcd843f0e9548430e5b3581 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 4 Apr 2017 18:26:04 +0200 Subject: [PATCH 138/299] Update docs --- CHANGELOG.asciidoc | 1 + README.asciidoc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 5ac5f9460..5f51fe822 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -51,6 +51,7 @@ Fixed - Crash when downloading a download resulting in a HTTP error - Crash when pressing ctrl-c while a config error is shown - Crash when the key config isn't writable +- Crash when unbinding an unbound key in the key config - Various rare crashes v0.10.1 diff --git a/README.asciidoc b/README.asciidoc index 90c6abde9..1496c1787 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -152,8 +152,8 @@ Contributors, sorted by the number of commits in descending order: * Lamar Pavel * Marshall Lochbaum * Bruno Oliveira -* Alexander Cogneau * Martin Tournoij +* Alexander Cogneau * Felix Van der Jeugt * Daniel Karbach * Imran Sobir From 200e439a3051326d0c6a1191f89628aded094f40 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Mon, 3 Apr 2017 02:29:38 +0100 Subject: [PATCH 139/299] Fix crash of :debug-log-filter when --filter wasn't given There was no `LogFilter`. The fix is to always initialize a `LogFilter()` with `None`. as the "filter". Fixes #2303. --- CHANGELOG.asciidoc | 1 + qutebrowser/misc/utilcmds.py | 9 ++++++--- qutebrowser/utils/log.py | 5 +++-- tests/end2end/features/utilcmds.feature | 4 ++++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 5f51fe822..74bf9e661 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -52,6 +52,7 @@ Fixed - Crash when pressing ctrl-c while a config error is shown - Crash when the key config isn't writable - Crash when unbinding an unbound key in the key config +- Crash when using `:debug-log-filter` when `--filter` wasn't given on startup. - Various rare crashes v0.10.1 diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index e617c1af2..1998e8341 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -295,13 +295,16 @@ def debug_log_filter(filters: str): Args: filters: A comma separated list of logger names. """ - if set(filters.split(',')).issubset(log.LOGGER_NAMES): - log.console_filter.names = filters.split(',') - else: + if not set(filters.split(',')).issubset(log.LOGGER_NAMES): raise cmdexc.CommandError("filters: Invalid value {} - expected one " "of: {}".format(filters, ', '.join(log.LOGGER_NAMES))) + if log.console_filter is None: + raise cmdexc.CommandError("No log.console_filter. Not attached " + "to a console?") + log.console_filter.names = filters.split(',') + @cmdutils.register() @cmdutils.argument('current_win_id', win_id=True) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 7c96d4072..9c660f035 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -182,9 +182,10 @@ def init_log(args): root = logging.getLogger() global console_filter if console is not None: + console_filter = LogFilter(None) if args.logfilter is not None: - console_filter = LogFilter(args.logfilter.split(',')) - console.addFilter(console_filter) + console_filter.names = args.logfilter.split(',') + console.addFilter(console_filter) root.addHandler(console) if ram is not None: root.addHandler(ram) diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index f9c43be98..aee57e053 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -164,3 +164,7 @@ Feature: Miscellaneous utility commands exposed to the user. Scenario: Using debug-log-filter with invalid filter When I run :debug-log-filter blah Then the error "filters: Invalid value blah - expected one of: statusbar, *" should be shown + + Scenario: Using debug-log-filter + When I run :debug-log-filter webview + Then no crash should happen From 857565c384b3cbba647c8581e8e11b348db29cd9 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Tue, 4 Apr 2017 19:58:51 +0100 Subject: [PATCH 140/299] Mention that qutebrowser@ also gets the mails from qutebrowser-announce@ --- README.asciidoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 1496c1787..b4f9718ce 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -71,7 +71,8 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. There's also a https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist] -at mailto:qutebrowser-announce@lists.qutebrowser.org[]. +at mailto:qutebrowser-announce@lists.qutebrowser.org[] (the announcements also +get sent to the general qutebrowser@ list). Contributions / Bugs -------------------- From e7755f5d9f8a5e995b83a239c05016cf1d58abba Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Tue, 4 Apr 2017 17:13:08 +0100 Subject: [PATCH 141/299] Add :debug-log-filter none This allows us to clear any filters. Useful for users, and needed for the tests. --- CHANGELOG.asciidoc | 2 ++ qutebrowser/misc/utilcmds.py | 14 ++++++++++---- tests/end2end/features/utilcmds.feature | 7 +++++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 74bf9e661..81ad97343 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -24,6 +24,8 @@ Added - New `ui -> keyhint-delay` setting to configure the delay until the keyhint overlay pops up. - New `-s` option for `:open` to force a HTTPS scheme. +- `:debug-log-filter` now accepts `none` as an argument to clear any log + filters. Changed ~~~~~~~ diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 1998e8341..fc777c2e9 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -293,16 +293,22 @@ def debug_log_filter(filters: str): """Change the log filter for console logging. Args: - filters: A comma separated list of logger names. + filters: A comma separated list of logger names. Can also be "none" to + clear any existing filters. """ + if log.console_filter is None: + raise cmdexc.CommandError("No log.console_filter. Not attached " + "to a console?") + + if filters.strip().lower() == 'none': + log.console_filter.names = None + return + if not set(filters.split(',')).issubset(log.LOGGER_NAMES): raise cmdexc.CommandError("filters: Invalid value {} - expected one " "of: {}".format(filters, ', '.join(log.LOGGER_NAMES))) - if log.console_filter is None: - raise cmdexc.CommandError("No log.console_filter. Not attached " - "to a console?") log.console_filter.names = filters.split(',') diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index aee57e053..5696de75d 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -166,5 +166,8 @@ Feature: Miscellaneous utility commands exposed to the user. Then the error "filters: Invalid value blah - expected one of: statusbar, *" should be shown Scenario: Using debug-log-filter - When I run :debug-log-filter webview - Then no crash should happen + When I run :debug-log-filter commands,ipc,webview + And I run :enter-mode insert + And I run :debug-log-filter none + And I run :leave-mode + Then "Entering mode KeyMode.insert *" should not be logged From 6f952c83af995adbfe5021df71520bc869e05009 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Apr 2017 07:16:18 +0200 Subject: [PATCH 142/299] Update docs --- doc/help/commands.asciidoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 3e10b9d81..1a76e7fc8 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -1599,7 +1599,8 @@ Syntax: +:debug-log-filter 'filters'+ Change the log filter for console logging. ==== positional arguments -* +'filters'+: A comma separated list of logger names. +* +'filters'+: A comma separated list of logger names. Can also be "none" to clear any existing filters. + [[debug-log-level]] === debug-log-level From 6d4948f9d098171403615e08c4e85ce8c85c4cea Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Apr 2017 20:35:32 +0200 Subject: [PATCH 143/299] Update authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index b4f9718ce..96b795463 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -155,9 +155,9 @@ Contributors, sorted by the number of commits in descending order: * Bruno Oliveira * Martin Tournoij * Alexander Cogneau +* Imran Sobir * Felix Van der Jeugt * Daniel Karbach -* Imran Sobir * Kevin Velghe * Raphael Pierzina * Joel Torstensson From 3cc9f9f0731f0b19a912bb03355727a177a59b2e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Apr 2017 20:36:54 +0200 Subject: [PATCH 144/299] Don't use from-import --- qutebrowser/browser/qutescheme.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 9fddb2c38..f7f074c54 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -24,12 +24,12 @@ Module attributes: _HANDLERS: The handlers registered via decorators. """ -from datetime import date, datetime, timedelta import json import os import sys import time import urllib.parse +import datetime from PyQt5.QtCore import QUrlQuery @@ -278,20 +278,21 @@ def qute_history(url): try: query_date = QUrlQuery(url).queryItemValue("date") if query_date: - curr_date = datetime.strptime(query_date, + curr_date = datetime.datetime.strptime(query_date, "%Y-%m-%d").date() except ValueError: log.misc.debug("Invalid date passed to qute:history: " + query_date) - one_day = timedelta(days=1) + one_day = datetime.timedelta(days=1) next_date = curr_date + one_day prev_date = curr_date - one_day # start_time is the last second of curr_date start_time = time.mktime(next_date.timetuple()) - 1 history = [ - (i["url"], i["title"], datetime.fromtimestamp(i["time"]/1000)) + (i["url"], i["title"], + datetime.datetime.fromtimestamp(i["time"]/1000)) for i in history_data(start_time) if "next" not in i ] From 4ec5700cbf9ced71c24d57de3ddcae102c9116c0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Apr 2017 20:38:15 +0200 Subject: [PATCH 145/299] Redirect qute:foo to qute://foo Before, we just returned the same data for both, but then we'll run into same-origin restrictions as qute:history and qute:history/data are not the same host. --- misc/userscripts/password_fill | 2 +- qutebrowser/browser/qtnetworkdownloads.py | 2 +- qutebrowser/browser/qutescheme.py | 57 ++++++++++++------- .../browser/webengine/webenginequtescheme.py | 15 +++-- qutebrowser/browser/webengine/webenginetab.py | 2 +- .../browser/webkit/network/networkreply.py | 24 ++++++-- .../webkit/network/webkitqutescheme.py | 17 +++--- qutebrowser/browser/webkit/webview.py | 2 +- qutebrowser/config/config.py | 2 +- qutebrowser/config/configdata.py | 2 +- qutebrowser/misc/utilcmds.py | 2 +- scripts/dev/run_vulture.py | 2 +- tests/end2end/features/history.feature | 8 +++ tests/end2end/features/misc.feature | 12 ++-- tests/end2end/features/set.feature | 12 ++-- tests/end2end/features/urlmarks.feature | 4 +- tests/end2end/features/utilcmds.feature | 2 +- tests/end2end/test_invocations.py | 4 +- .../webkit/network/test_networkreply.py | 7 +++ tests/unit/utils/test_urlutils.py | 3 + 20 files changed, 118 insertions(+), 63 deletions(-) diff --git a/misc/userscripts/password_fill b/misc/userscripts/password_fill index 5d709512c..327a55690 100755 --- a/misc/userscripts/password_fill +++ b/misc/userscripts/password_fill @@ -9,7 +9,7 @@ directly ask me via IRC (nickname thorsten\`) in #qutebrowser on freenode. $blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset WARNING: the passwords are stored in qutebrowser's - debug log reachable via the url qute:log + debug log reachable via the url qute://log $blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset Usage: run as a userscript form qutebrowser, e.g.: diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 920673d4b..56149a8cc 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -273,7 +273,7 @@ class DownloadItem(downloads.AbstractDownloadItem): if self.fileobj is None or self._reply is None: # No filename has been set yet (so we don't empty the buffer) or we # got a readyRead after the reply was finished (which happens on - # qute:log for example). + # qute://log for example). return if not self._reply.isOpen(): raise OSError("Reply is closed!") diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index f7f074c54..5f25c24d1 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Backend-independent qute:* code. +"""Backend-independent qute://* code. Module attributes: pyeval_output: The output of the last :pyeval command. @@ -31,7 +31,7 @@ import time import urllib.parse import datetime -from PyQt5.QtCore import QUrlQuery +from PyQt5.QtCore import QUrlQuery, QUrl import qutebrowser from qutebrowser.config import config @@ -78,12 +78,25 @@ class QuteSchemeError(Exception): super().__init__(errorstring) -class add_handler: # pylint: disable=invalid-name +class Redirect(Exception): - """Decorator to register a qute:* URL handler. + """Exception to signal a redirect should happen. Attributes: - _name: The 'foo' part of qute:foo + url: The URL to redirect to, as a QUrl. + """ + + def __init__(self, url): + super().__init__(url.toDisplayString()) + self.url = url + + +class add_handler: # pylint: disable=invalid-name + + """Decorator to register a qute://* URL handler. + + Attributes: + _name: The 'foo' part of qute://foo backend: Limit which backends the handler can run with. """ @@ -106,7 +119,7 @@ class add_handler: # pylint: disable=invalid-name def wrong_backend_handler(self, url): """Show an error page about using the invalid backend.""" html = jinja.render('error.html', - title="Error while opening qute:url", + title="Error while opening qute://url", url=url.toDisplayString(), error='{} is not available with this ' 'backend'.format(url.toDisplayString()), @@ -128,13 +141,17 @@ def data_for_url(url): # A url like "qute:foo" is split as "scheme:path", not "scheme:host". log.misc.debug("url: {}, path: {}, host {}".format( url.toDisplayString(), path, host)) + if path and not host: + new_url = QUrl() + new_url.setScheme('qute') + new_url.setHost(path) + raise Redirect(new_url) + try: - handler = _HANDLERS[path] + handler = _HANDLERS[host] except KeyError: - try: - handler = _HANDLERS[host] - except KeyError: - raise NoHandlerFound(url) + raise NoHandlerFound(url) + try: mimetype, data = handler(url) except OSError as e: @@ -153,7 +170,7 @@ def data_for_url(url): @add_handler('bookmarks') def qute_bookmarks(_url): - """Handler for qute:bookmarks. Display all quickmarks / bookmarks.""" + """Handler for qute://bookmarks. Display all quickmarks / bookmarks.""" bookmarks = sorted(objreg.get('bookmark-manager').marks.items(), key=lambda x: x[1]) # Sort by title quickmarks = sorted(objreg.get('quickmark-manager').marks.items(), @@ -246,7 +263,7 @@ def history_data(start_time): # noqa @add_handler('history') def qute_history(url): - """Handler for qute:history. Display and serve history.""" + """Handler for qute://history. Display and serve history.""" if url.path() == '/data': # Use start_time in query or current time. try: @@ -309,7 +326,7 @@ def qute_history(url): @add_handler('javascript') def qute_javascript(url): - """Handler for qute:javascript. + """Handler for qute://javascript. Return content of file given as query parameter. """ @@ -323,7 +340,7 @@ def qute_javascript(url): @add_handler('pyeval') def qute_pyeval(_url): - """Handler for qute:pyeval.""" + """Handler for qute://pyeval.""" html = jinja.render('pre.html', title='pyeval', content=pyeval_output) return 'text/html', html @@ -331,7 +348,7 @@ def qute_pyeval(_url): @add_handler('version') @add_handler('verizon') def qute_version(_url): - """Handler for qute:version.""" + """Handler for qute://version.""" html = jinja.render('version.html', title='Version info', version=version.version(), copyright=qutebrowser.__copyright__) @@ -340,7 +357,7 @@ def qute_version(_url): @add_handler('plainlog') def qute_plainlog(url): - """Handler for qute:plainlog. + """Handler for qute://plainlog. An optional query parameter specifies the minimum log level to print. For example, qute://log?level=warning prints warnings and errors. @@ -360,7 +377,7 @@ def qute_plainlog(url): @add_handler('log') def qute_log(url): - """Handler for qute:log. + """Handler for qute://log. An optional query parameter specifies the minimum log level to print. For example, qute://log?level=warning prints warnings and errors. @@ -381,13 +398,13 @@ def qute_log(url): @add_handler('gpl') def qute_gpl(_url): - """Handler for qute:gpl. Return HTML content as string.""" + """Handler for qute://gpl. Return HTML content as string.""" return 'text/html', utils.read_file('html/COPYING.html') @add_handler('help') def qute_help(url): - """Handler for qute:help.""" + """Handler for qute://help.""" try: utils.read_file('html/doc/index.html') except OSError: diff --git a/qutebrowser/browser/webengine/webenginequtescheme.py b/qutebrowser/browser/webengine/webenginequtescheme.py index 6bc31f9be..cebf31356 100644 --- a/qutebrowser/browser/webengine/webenginequtescheme.py +++ b/qutebrowser/browser/webengine/webenginequtescheme.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""QtWebEngine specific qute:* handlers and glue code.""" +"""QtWebEngine specific qute://* handlers and glue code.""" from PyQt5.QtCore import QBuffer, QIODevice # pylint: disable=no-name-in-module,import-error,useless-suppression @@ -26,15 +26,15 @@ from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler, # pylint: enable=no-name-in-module,import-error,useless-suppression from qutebrowser.browser import qutescheme -from qutebrowser.utils import log +from qutebrowser.utils import log, qtutils class QuteSchemeHandler(QWebEngineUrlSchemeHandler): - """Handle qute:* requests on QtWebEngine.""" + """Handle qute://* requests on QtWebEngine.""" def install(self, profile): - """Install the handler for qute: URLs on the given profile.""" + """Install the handler for qute:// URLs on the given profile.""" profile.installUrlSchemeHandler(b'qute', self) def requestStarted(self, job): @@ -58,12 +58,15 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler): job.fail(QWebEngineUrlRequestJob.UrlNotFound) except qutescheme.QuteSchemeOSError: # FIXME:qtwebengine how do we show a better error here? - log.misc.exception("OSError while handling qute:* URL") + log.misc.exception("OSError while handling qute://* URL") job.fail(QWebEngineUrlRequestJob.UrlNotFound) except qutescheme.QuteSchemeError: # FIXME:qtwebengine how do we show a better error here? - log.misc.exception("Error while handling qute:* URL") + log.misc.exception("Error while handling qute://* URL") job.fail(QWebEngineUrlRequestJob.RequestFailed) + except qutescheme.Redirect as e: + qtutils.ensure_valid(e.url) + job.redirect(e.url) else: log.misc.debug("Returning {} data".format(mimetype)) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 76a526670..7eef0b03a 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -55,7 +55,7 @@ def init(): app = QApplication.instance() profile = QWebEngineProfile.defaultProfile() - log.init.debug("Initializing qute:* handler...") + log.init.debug("Initializing qute://* handler...") _qute_scheme_handler = webenginequtescheme.QuteSchemeHandler(parent=app) _qute_scheme_handler.install(profile) diff --git a/qutebrowser/browser/webkit/network/networkreply.py b/qutebrowser/browser/webkit/network/networkreply.py index 2cc5727be..9638daf4a 100644 --- a/qutebrowser/browser/webkit/network/networkreply.py +++ b/qutebrowser/browser/webkit/network/networkreply.py @@ -19,11 +19,15 @@ # # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . +# +# For some reason, a segfault will be triggered if the unnecessary lambdas in +# this file aren't there. +# pylint: disable=unnecessary-lambda """Special network replies..""" -from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest -from PyQt5.QtCore import pyqtSlot, QIODevice, QByteArray, QTimer +from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest, QNetworkAccessManager +from PyQt5.QtCore import pyqtSlot, QIODevice, QByteArray, QTimer, QUrl class FixedDataNetworkReply(QNetworkReply): @@ -114,9 +118,6 @@ class ErrorNetworkReply(QNetworkReply): # the device to avoid getting a warning. self.setOpenMode(QIODevice.ReadOnly) self.setError(error, errorstring) - # For some reason, a segfault will be triggered if these lambdas aren't - # there. - # pylint: disable=unnecessary-lambda QTimer.singleShot(0, lambda: self.error.emit(error)) QTimer.singleShot(0, lambda: self.finished.emit()) @@ -137,3 +138,16 @@ class ErrorNetworkReply(QNetworkReply): def isRunning(self): return False + + +class RedirectNetworkReply(QNetworkReply): + + """A reply which redirects to the given URL.""" + + def __init__(self, new_url, parent=None): + super().__init__(parent) + self.setAttribute(QNetworkRequest.RedirectionTargetAttribute, new_url) + QTimer.singleShot(0, lambda: self.finished.emit()) + + def readData(self, _maxlen): + return bytes() diff --git a/qutebrowser/browser/webkit/network/webkitqutescheme.py b/qutebrowser/browser/webkit/network/webkitqutescheme.py index 61ef760bc..34db29ee9 100644 --- a/qutebrowser/browser/webkit/network/webkitqutescheme.py +++ b/qutebrowser/browser/webkit/network/webkitqutescheme.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""QtWebKit specific qute:* handlers and glue code.""" +"""QtWebKit specific qute://* handlers and glue code.""" import mimetypes import functools @@ -28,13 +28,13 @@ from PyQt5.QtNetwork import QNetworkReply from qutebrowser.browser import pdfjs, qutescheme from qutebrowser.browser.webkit.network import schemehandler, networkreply -from qutebrowser.utils import jinja, log, message, objreg, usertypes +from qutebrowser.utils import jinja, log, message, objreg, usertypes, qtutils from qutebrowser.config import configexc, configdata class QuteSchemeHandler(schemehandler.SchemeHandler): - """Scheme handler for qute: URLs.""" + """Scheme handler for qute:// URLs.""" def createRequest(self, _op, request, _outgoing_data): """Create a new request. @@ -62,6 +62,9 @@ class QuteSchemeHandler(schemehandler.SchemeHandler): except qutescheme.QuteSchemeError as e: return networkreply.ErrorNetworkReply(request, e.errorstring, e.error, self.parent()) + except qutescheme.Redirect as e: + qtutils.ensure_valid(e.url) + return networkreply.RedirectNetworkReply(e.url, self.parent()) return networkreply.FixedDataNetworkReply(request, data, mimetype, self.parent()) @@ -69,15 +72,15 @@ class QuteSchemeHandler(schemehandler.SchemeHandler): class JSBridge(QObject): - """Javascript-bridge for special qute:... pages.""" + """Javascript-bridge for special qute://... pages.""" @pyqtSlot(str, str, str) def set(self, sectname, optname, value): - """Slot to set a setting from qute:settings.""" + """Slot to set a setting from qute://settings.""" # https://github.com/qutebrowser/qutebrowser/issues/727 if ((sectname, optname) == ('content', 'allow-javascript') and value == 'false'): - message.error("Refusing to disable javascript via qute:settings " + message.error("Refusing to disable javascript via qute://settings " "as it needs javascript support.") return try: @@ -88,7 +91,7 @@ class JSBridge(QObject): @qutescheme.add_handler('settings', backend=usertypes.Backend.QtWebKit) def qute_settings(_url): - """Handler for qute:settings. View/change qute configuration.""" + """Handler for qute://settings. View/change qute configuration.""" config_getter = functools.partial(objreg.get('config').get, raw=True) html = jinja.render('settings.html', title='settings', config=configdata, confget=config_getter) diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index 12670be4f..0c3aa179e 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -140,7 +140,7 @@ class WebView(QWebView): @pyqtSlot() def add_js_bridge(self): - """Add the javascript bridge for qute:... pages.""" + """Add the javascript bridge for qute://... pages.""" frame = self.sender() if not isinstance(frame, QWebFrame): log.webview.error("Got non-QWebFrame {!r} in " diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 1e0cec92b..82c6aca00 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -805,7 +805,7 @@ class ConfigManager(QObject): if section_ is None and option is None: tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) - tabbed_browser.openurl(QUrl('qute:settings'), newtab=False) + tabbed_browser.openurl(QUrl('qute://settings'), newtab=False) return if option.endswith('?') and option != '?': diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 1fbc22937..2c5b27808 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1689,7 +1689,7 @@ KEY_DATA = collections.OrderedDict([ ('home', ['']), ('stop', ['']), ('print', ['']), - ('open qute:settings', ['Ss']), + ('open qute://settings', ['Ss']), ('follow-selected', RETURN_KEYS), ('follow-selected -t', ['', '']), ('repeat-command', ['.']), diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index fc777c2e9..de43c71f4 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -228,7 +228,7 @@ def debug_pyeval(s, quiet=False): else: tabbed_browser = objreg.get('tabbed-browser', scope='window', window='last-focused') - tabbed_browser.openurl(QUrl('qute:pyeval'), newtab=True) + tabbed_browser.openurl(QUrl('qute://pyeval'), newtab=True) @cmdutils.register(debug=True) diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py index 55f4d9c86..f1eed1803 100755 --- a/scripts/dev/run_vulture.py +++ b/scripts/dev/run_vulture.py @@ -74,7 +74,7 @@ def whitelist_generator(): yield 'PyQt5.QtWebKit.QWebPage.ErrorPageExtensionReturn().fileNames' yield 'PyQt5.QtWidgets.QStyleOptionViewItem.backgroundColor' - ## qute:... handlers + ## qute://... handlers for name in qutescheme._HANDLERS: # pylint: disable=protected-access yield 'qutebrowser.browser.qutescheme.qute_' + name diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index 27454c522..613b660af 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -83,6 +83,14 @@ Feature: Page history Then the page should contain the plaintext "3.txt" Then the page should contain the plaintext "4.txt" + Scenario: Listing history with qute:history redirect + When I open data/numbers/3.txt + And I open data/numbers/4.txt + And I open qute:history without waiting + And I wait until qute://history is loaded + Then the page should contain the plaintext "3.txt" + Then the page should contain the plaintext "4.txt" + ## Bugs @qtwebengine_skip @qtwebkit_ng_skip diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 0aeb65f92..e3bb13b6f 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -405,12 +405,12 @@ Feature: Various utility commands. # :pyeval Scenario: Running :pyeval When I run :debug-pyeval 1+1 - And I wait until qute:pyeval is loaded + And I wait until qute://pyeval is loaded Then the page should contain the plaintext "2" Scenario: Causing exception in :pyeval When I run :debug-pyeval 1/0 - And I wait until qute:pyeval is loaded + And I wait until qute://pyeval is loaded Then the page should contain the plaintext "ZeroDivisionError" Scenario: Running :pyeval with --quiet @@ -512,12 +512,12 @@ Feature: Various utility commands. When I run :messages cataclysmic Then the error "Invalid log level cataclysmic!" should be shown - Scenario: Using qute:log directly - When I open qute:log + Scenario: Using qute://log directly + When I open qute://log Then no crash should happen - Scenario: Using qute:plainlog directly - When I open qute:plainlog + Scenario: Using qute://plainlog directly + When I open qute://plainlog Then no crash should happen Scenario: Using :messages without messages diff --git a/tests/end2end/features/set.feature b/tests/end2end/features/set.feature index b2b165542..bc144eb6e 100644 --- a/tests/end2end/features/set.feature +++ b/tests/end2end/features/set.feature @@ -78,15 +78,15 @@ Feature: Setting settings. When I run :set -t colors statusbar.bg green Then colors -> statusbar.bg should be green - # qute:settings isn't actually implemented on QtWebEngine, but this works + # qute://settings isn't actually implemented on QtWebEngine, but this works # (and displays a page saying it's not available) - Scenario: Opening qute:settings + Scenario: Opening qute://settings When I run :set - And I wait until qute:settings is loaded + And I wait until qute://settings is loaded Then the following tabs should be open: - - qute:settings (active) + - qute://settings (active) - @qtwebengine_todo: qute:settings is not implemented yet + @qtwebengine_todo: qute://settings is not implemented yet Scenario: Focusing input fields in qute://settings and entering valid value When I set general -> ignore-case to false And I open qute://settings @@ -101,7 +101,7 @@ Feature: Setting settings. And I press the key "" Then general -> ignore-case should be true - @qtwebengine_todo: qute:settings is not implemented yet + @qtwebengine_todo: qute://settings is not implemented yet Scenario: Focusing input fields in qute://settings and entering invalid value When I open qute://settings # scroll to the right - the table does not fit in the default screen diff --git a/tests/end2end/features/urlmarks.feature b/tests/end2end/features/urlmarks.feature index 873d83563..f233215b8 100644 --- a/tests/end2end/features/urlmarks.feature +++ b/tests/end2end/features/urlmarks.feature @@ -225,12 +225,12 @@ Feature: quickmarks and bookmarks Scenario: Listing quickmarks When I run :quickmark-add http://localhost:(port)/data/numbers/20.txt twenty And I run :quickmark-add http://localhost:(port)/data/numbers/21.txt twentyone - And I open qute:bookmarks + And I open qute://bookmarks Then the page should contain the plaintext "twenty" And the page should contain the plaintext "twentyone" Scenario: Listing bookmarks When I open data/title.html in a new tab And I run :bookmark-add - And I open qute:bookmarks + And I open qute://bookmarks Then the page should contain the plaintext "Test title" diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index 5696de75d..7fd5952a8 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -145,7 +145,7 @@ Feature: Miscellaneous utility commands exposed to the user. And I run :message-info oldstuff And I run :repeat 20 message-info otherstuff And I run :message-info newstuff - And I open qute:log + And I open qute://log Then the page should contain the plaintext "newstuff" And the page should not contain the plaintext "oldstuff" diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index d6697ec29..ed0222014 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -138,10 +138,10 @@ def test_misconfigured_user_dirs(request, httpbin, temp_basedir_env, def test_no_loglines(request, quteproc_new): - """Test qute:log with --loglines=0.""" + """Test qute://log with --loglines=0.""" quteproc_new.start(args=['--temp-basedir', '--loglines=0'] + _base_args(request.config)) - quteproc_new.open_path('qute:log') + quteproc_new.open_path('qute://log') assert quteproc_new.get_content() == 'Log output was disabled.' diff --git a/tests/unit/browser/webkit/network/test_networkreply.py b/tests/unit/browser/webkit/network/test_networkreply.py index 7a9c89393..7505dbc8c 100644 --- a/tests/unit/browser/webkit/network/test_networkreply.py +++ b/tests/unit/browser/webkit/network/test_networkreply.py @@ -91,3 +91,10 @@ def test_error_network_reply(qtbot, req): assert reply.readData(1) == b'' assert reply.error() == QNetworkReply.UnknownNetworkError assert reply.errorString() == "This is an error" + + +def test_redirect_network_reply(): + url = QUrl('https://www.example.com/') + reply = networkreply.RedirectNetworkReply(url) + assert reply.readData(1) == b'' + assert reply.attribute(QNetworkRequest.RedirectionTargetAttribute) == url diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index f944f95b2..4729bd661 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -265,6 +265,7 @@ class TestFuzzyUrl: ('file:///tmp/foo', True), ('about:blank', True), ('qute:version', True), + ('qute://version', True), ('http://www.qutebrowser.org/', False), ('www.qutebrowser.org', False), ]) @@ -317,9 +318,11 @@ def test_get_search_url_invalid(urlutils_config_stub, url): (True, True, False, 'file:///tmp/foo'), (True, True, False, 'about:blank'), (True, True, False, 'qute:version'), + (True, True, False, 'qute://version'), (True, True, False, 'localhost'), # _has_explicit_scheme False, special_url True (True, True, False, 'qute::foo'), + (True, True, False, 'qute:://foo'), # Invalid URLs (False, False, False, ''), (False, True, False, 'onlyscheme:'), From 8878f867a7c8565801bc0187796e6638bfe02c85 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 6 Apr 2017 21:19:49 +0200 Subject: [PATCH 146/299] Update tox from 2.6.0 to 2.7.0 --- misc/requirements/requirements-tox.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index df38c7fca..59f0806bf 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -2,5 +2,5 @@ pluggy==0.4.0 py==1.4.33 -tox==2.6.0 +tox==2.7.0 virtualenv==15.1.0 From 871504d91bc91351862f8cecddc3549eb25a024e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Apr 2017 21:37:23 +0200 Subject: [PATCH 147/299] Fix undefined names --- qutebrowser/browser/qutescheme.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 5f25c24d1..b35fb45f1 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -291,7 +291,7 @@ def qute_history(url): ) else: # Get current date from query parameter, if not given choose today. - curr_date = date.today() + curr_date = datetime.date.today() try: query_date = QUrlQuery(url).queryItemValue("date") if query_date: @@ -320,7 +320,7 @@ def qute_history(url): curr_date=curr_date, next_date=next_date, prev_date=prev_date, - today=date.today(), + today=datetime.date.today(), ) From fd9b86a34017eed5edde19a231f99c8b0b330a65 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Apr 2017 21:40:26 +0200 Subject: [PATCH 148/299] Remove unused imports --- qutebrowser/browser/webkit/network/networkreply.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/webkit/network/networkreply.py b/qutebrowser/browser/webkit/network/networkreply.py index 9638daf4a..ec37e1e84 100644 --- a/qutebrowser/browser/webkit/network/networkreply.py +++ b/qutebrowser/browser/webkit/network/networkreply.py @@ -26,8 +26,8 @@ """Special network replies..""" -from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest, QNetworkAccessManager -from PyQt5.QtCore import pyqtSlot, QIODevice, QByteArray, QTimer, QUrl +from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest +from PyQt5.QtCore import pyqtSlot, QIODevice, QByteArray, QTimer class FixedDataNetworkReply(QNetworkReply): From 3b87e7c2972f24f4391b3c5f4c3ff9212f3bbade Mon Sep 17 00:00:00 2001 From: Jacob Sword Date: Fri, 7 Apr 2017 21:12:42 -0400 Subject: [PATCH 149/299] Add --debug-exit argument and validity check --- qutebrowser/qutebrowser.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 8321fb04b..aaf86850f 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -118,6 +118,9 @@ def get_argparser(): action='append') debug.add_argument('--qt-flag', help="Pass an argument to Qt as flag.", nargs=1, action='append') + debug.add_argument('--debug-flag', type=debug_flag_error, + help="Pass name of debugging feature to be turned on.", + nargs=1, action='append') parser.add_argument('command', nargs='*', help="Commands to execute on " "startup.", metavar=':command') # URLs will actually be in command @@ -144,6 +147,21 @@ def logfilter_error(logfilter: str): "filters: Invalid value {} - expected a list of: {}".format( logfilter, ', '.join(log.LOGGER_NAMES))) +def debug_flag_error(flag): + """Validate flags passed to --debug-flag. + + Available flags: + debug-exit + pdb-postmortem + """ + valid_flags = ['debug-exit','pdb-postmortem'] + + if flag in valid_flags: + return flag + else: + raise argparse.ArgumentTypeError("Invalid flag - valid flags include: " + + str(valid_flags)) + def main(): parser = get_argparser() From 043039d673e9435d80034a80dcfe389f26d2dd06 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 8 Apr 2017 05:19:34 +0200 Subject: [PATCH 150/299] Update setuptools from 34.3.3 to 34.4.0 --- misc/requirements/requirements-pip.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt index 54c6747af..90c042c8e 100644 --- a/misc/requirements/requirements-pip.txt +++ b/misc/requirements/requirements-pip.txt @@ -3,6 +3,6 @@ appdirs==1.4.3 packaging==16.8 pyparsing==2.2.0 -setuptools==34.3.3 +setuptools==34.4.0 six==1.10.0 wheel==0.29.0 From c23e4b1c5f7f8979002ae66c82cd76e05d656ef5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 8 Apr 2017 19:20:53 +0200 Subject: [PATCH 151/299] tests: Allow @qt<... marker for BDD tests --- tests/end2end/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 5dcff3fb5..7d1c0e90d 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -68,7 +68,7 @@ def _get_version_tag(tag): """ version_re = re.compile(r""" (?Pqt|pyqt) - (?P==|>=|!=) + (?P==|>=|!=|<) (?P\d+\.\d+(\.\d+)?) """, re.VERBOSE) @@ -84,6 +84,7 @@ def _get_version_tag(tag): do_skip = { '==': not qtutils.version_check(version, exact=True), '>=': not qtutils.version_check(version), + '<': qtutils.version_check(version), '!=': qtutils.version_check(version, exact=True), } return pytest.mark.skipif(do_skip[op], reason='Needs ' + tag) From a081d4184d45a643be0774ce25e618822d320c7c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 8 Apr 2017 19:22:00 +0200 Subject: [PATCH 152/299] tests: Adjust percent-encoding tests for Qt 5.9 changes See #2514 --- tests/end2end/features/downloads.feature | 12 ++++++++++-- .../browser/webengine/test_webenginedownloads.py | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 768521183..b1dd5155b 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -121,14 +121,22 @@ Feature: Downloading things from a website. And I wait until the download is finished Then the downloaded file download with spaces.bin should exist - @qtwebkit_skip - Scenario: Downloading a file with evil content-disposition header + @qtwebkit_skip @qt<5.9 + Scenario: Downloading a file with evil content-disposition header (Qt 5.8 or older) # Content-Disposition: download; filename=..%2Ffoo When I open response-headers?Content-Disposition=download;%20filename%3D..%252Ffoo without waiting And I wait until the download is finished Then the downloaded file ../foo should not exist And the downloaded file foo should exist + @qtwebkit_skip @qt>=5.9 + Scenario: Downloading a file with evil content-disposition header (Qt 5.9 or newer) + # Content-Disposition: download; filename=..%2Ffoo + When I open response-headers?Content-Disposition=download;%20filename%3D..%252Ffoo without waiting + And I wait until the download is finished + Then the downloaded file ../foo should not exist + And the downloaded file ..%2Ffoo should exist + @windows Scenario: Downloading a file to a reserved path When I set storage -> prompt-download-directory to true diff --git a/tests/unit/browser/webengine/test_webenginedownloads.py b/tests/unit/browser/webengine/test_webenginedownloads.py index ee5b8e22a..735852cd3 100644 --- a/tests/unit/browser/webengine/test_webenginedownloads.py +++ b/tests/unit/browser/webengine/test_webenginedownloads.py @@ -24,6 +24,12 @@ import pytest pytest.importorskip('PyQt5.QtWebEngineWidgets') from qutebrowser.browser.webengine import webenginedownloads +from qutebrowser.utils import qtutils + +qt58 = pytest.mark.skipif( + qtutils.version_check('5.9'), reason="Needs Qt 5.8 or earlier") +qt59 = pytest.mark.skipif( + not qtutils.version_check('5.9'), reason="Needs Qt 5.9 or newer") @pytest.mark.parametrize('path, expected', [ @@ -31,8 +37,10 @@ from qutebrowser.browser.webengine import webenginedownloads ('foo(1)', 'foo'), ('foo(a)', 'foo(a)'), ('foo1', 'foo1'), - ('foo%20bar', 'foo bar'), - ('foo%2Fbar', 'bar'), + qt58(('foo%20bar', 'foo bar')), + qt58(('foo%2Fbar', 'bar')), + qt59(('foo%20bar', 'foo%20bar')), + qt59(('foo%2Fbar', 'foo%2Fbar')), ]) def test_get_suggested_filename(path, expected): assert webenginedownloads._get_suggested_filename(path) == expected From e23318fe914ff31c7f0810736905a9318d2a5e50 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 8 Apr 2017 19:29:46 +0200 Subject: [PATCH 153/299] Mark failing test as flaky on QtWebEngine It consistently fails on Qt 5.9 now while waiting the page to be scrolled to 0/20, but I can't figure out why that is happening. See #2514, #2410 --- tests/end2end/features/marks.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/end2end/features/marks.feature b/tests/end2end/features/marks.feature index 28de753c9..31ddd034d 100644 --- a/tests/end2end/features/marks.feature +++ b/tests/end2end/features/marks.feature @@ -27,6 +27,7 @@ Feature: Setting positional marks And I wait until the scroll position changed to 10/20 Then the page should be scrolled to 10 20 + @qtwebengine_flaky Scenario: Setting the same local mark on another page When I run :scroll-px 5 10 And I wait until the scroll position changed to 5/10 From 6051f93c025cf8c49cb32fec63d5594687d344a2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 8 Apr 2017 19:52:46 +0200 Subject: [PATCH 154/299] Avoid checking for scroll position in macro tests This makes them simpler and more stable. --- tests/end2end/features/keyinput.feature | 51 ++++++++++--------------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index 452d66757..93fbb1b93 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -202,34 +202,30 @@ Feature: Keyboard input # Macros Scenario: Recording a simple macro - Given I open data/scroll/simple.html - And I run :tab-only - When I run :scroll down with count 6 - And I wait until the scroll position changed - And I run :record-macro + When I run :record-macro And I press the key "a" - And I run :scroll up - And I run :scroll up - And I wait until the scroll position changed + And I run :message-info "foo 1" + And I run :message-info "bar 1" And I run :record-macro And I run :run-macro with count 2 And I press the key "a" - And I wait until the scroll position changed to 0/0 - Then the page should not be scrolled + Then the message "foo 1" should be shown + And the message "bar 1" should be shown + And the message "foo 1" should be shown + And the message "bar 1" should be shown + And the message "foo 1" should be shown + And the message "bar 1" should be shown Scenario: Recording a named macro - Given I open data/scroll/simple.html - And I run :tab-only - When I run :scroll down with count 6 - And I wait until the scroll position changed + When I run :record-macro foo + And I run :message-info "foo 2" + And I run :message-info "bar 2" And I run :record-macro foo - And I run :scroll up - And I run :scroll up - And I wait until the scroll position changed - And I run :record-macro foo - And I run :run-macro foo with count 2 - And I wait until the scroll position changed to 0/0 - Then the page should not be scrolled + And I run :run-macro foo + Then the message "foo 2" should be shown + And the message "bar 2" should be shown + And the message "foo 2" should be shown + And the message "bar 2" should be shown Scenario: Running an invalid macro Given I open data/scroll/simple.html @@ -264,17 +260,12 @@ Feature: Keyboard input Then "Leaving mode KeyMode.record_macro (reason: leave current)" should be logged Scenario: Ignoring non-register keys - Given I open data/scroll/simple.html - And I run :tab-only - When I run :scroll down with count 2 - And I wait until the scroll position changed - And I run :record-macro + When I run :record-macro And I press the key "" And I press the key "c" - And I run :scroll up - And I wait until the scroll position changed + And I run :message-info "foo 3" And I run :record-macro And I run :run-macro And I press the key "c" - And I wait until the scroll position changed to 0/0 - Then the page should not be scrolled + Then the message "foo 3" should be shown + And the message "foo 3" should be shown From 28e6158a044804d691a4448175d0460983e9e16a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 8 Apr 2017 20:38:23 +0200 Subject: [PATCH 155/299] Stabilize some tests with Qt 5.9 QtWebEngine --- tests/end2end/features/misc.feature | 8 ++++++-- tests/end2end/features/scroll.feature | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index e3bb13b6f..73950fc41 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -513,11 +513,15 @@ Feature: Various utility commands. Then the error "Invalid log level cataclysmic!" should be shown Scenario: Using qute://log directly - When I open qute://log + When I open qute://log without waiting + # With Qt 5.9, we don't get a loaded message? + And I wait for "Changing title for idx * to 'log'" in the log Then no crash should happen Scenario: Using qute://plainlog directly - When I open qute://plainlog + When I open qute://plainlog without waiting + # With Qt 5.9, we don't get a loaded message? + And I wait for "Changing title for idx * to 'log'" in the log Then no crash should happen Scenario: Using :messages without messages diff --git a/tests/end2end/features/scroll.feature b/tests/end2end/features/scroll.feature index 44f60aa66..4e2643b6d 100644 --- a/tests/end2end/features/scroll.feature +++ b/tests/end2end/features/scroll.feature @@ -39,6 +39,7 @@ Feature: Scrolling And I wait until the scroll position changed to 0/0 Then the page should not be scrolled + @qtwebengine_flaky Scenario: Scrolling left and right with count When I run :scroll-px 10 0 with count 2 And I wait until the scroll position changed to 20/0 @@ -146,7 +147,6 @@ Feature: Scrolling Scenario: Scrolling down with a very big count When I run :scroll down with count 99999999999 - And I wait until the scroll position changed # Make sure it doesn't hang And I run :message-info "Still alive!" Then the message "Still alive!" should be shown From 45dff6c0c81effecb05e6d2a88096d357a0bc736 Mon Sep 17 00:00:00 2001 From: Jacob Sword Date: Sat, 8 Apr 2017 16:54:08 -0400 Subject: [PATCH 156/299] update usage of debug-flag arguments --- qutebrowser/app.py | 2 +- qutebrowser/misc/crashsignal.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 81079104a..086c67528 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -792,7 +792,7 @@ class Application(QApplication): def exit(self, status): """Extend QApplication::exit to log the event.""" log.destroy.debug("Now calling QApplication::exit.") - if self._args.debug_exit: + if 'debug_exit' in self._args.debug_flags: if hunter is None: print("Not logging late shutdown because hunter could not be " "imported!", file=sys.stderr) diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index aa7438732..d8d8bb385 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -207,10 +207,10 @@ class CrashHandler(QObject): is_ignored_exception = (exctype is bdb.BdbQuit or not issubclass(exctype, Exception)) - if self._args.pdb_postmortem: + if 'pdb-postmortem' in self._args.debug_flags: pdb.post_mortem(tb) - if is_ignored_exception or self._args.pdb_postmortem: + if is_ignored_exception or 'pdb-postmortem' in self._args.debug_flags: # pdb exit, KeyboardInterrupt, ... status = 0 if is_ignored_exception else 2 try: From 778832a8131f80d1dc4e1aebdb7e39faf31968ad Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 8 Apr 2017 23:04:10 +0200 Subject: [PATCH 157/299] Set path when redirecting qute:* URLs Fixes #2513 --- qutebrowser/browser/qutescheme.py | 1 + tests/end2end/features/misc.feature | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index b35fb45f1..ccd976d0a 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -145,6 +145,7 @@ def data_for_url(url): new_url = QUrl() new_url.setScheme('qute') new_url.setHost(path) + new_url.setPath('/') raise Redirect(new_url) try: diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 73950fc41..52c56b5c1 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -308,6 +308,14 @@ Feature: Various utility commands. - about:blank - qute://help/index.html (active) + # https://github.com/qutebrowser/qutebrowser/issues/2513 + Scenario: Opening link with qute:help + When I run :tab-only + And I open qute:help without waiting + And I wait for "Changing title for idx 0 to 'qutebrowser help'" in the log + And I hint with args "links normal" and follow a + Then qute://help/quickstart.html should be loaded + # :history Scenario: :history without arguments From 6ccb42023050287f7b16404556e2dc07386cebf5 Mon Sep 17 00:00:00 2001 From: Jacob Sword Date: Sat, 8 Apr 2017 18:42:26 -0400 Subject: [PATCH 158/299] Fix syntax error in debug-exit --- qutebrowser/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 086c67528..8ca322078 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -792,7 +792,7 @@ class Application(QApplication): def exit(self, status): """Extend QApplication::exit to log the event.""" log.destroy.debug("Now calling QApplication::exit.") - if 'debug_exit' in self._args.debug_flags: + if 'debug-exit' in self._args.debug_flags: if hunter is None: print("Not logging late shutdown because hunter could not be " "imported!", file=sys.stderr) From 7588cdb1856d7cada79f4dbbe99f6495838f1a8d Mon Sep 17 00:00:00 2001 From: Jacob Sword Date: Sat, 8 Apr 2017 19:04:25 -0400 Subject: [PATCH 159/299] fixed formatting issues --- qutebrowser/qutebrowser.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index aaf86850f..4019b9650 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -118,9 +118,9 @@ def get_argparser(): action='append') debug.add_argument('--qt-flag', help="Pass an argument to Qt as flag.", nargs=1, action='append') - debug.add_argument('--debug-flag', type=debug_flag_error, - help="Pass name of debugging feature to be turned on.", - nargs=1, action='append') + debug.add_argument('--debug-flag', type=debug_flag_error, + help="Pass name of debugging feature to be turned on.", + nargs=1, action='append') parser.add_argument('command', nargs='*', help="Commands to execute on " "startup.", metavar=':command') # URLs will actually be in command @@ -147,21 +147,22 @@ def logfilter_error(logfilter: str): "filters: Invalid value {} - expected a list of: {}".format( logfilter, ', '.join(log.LOGGER_NAMES))) + def debug_flag_error(flag): """Validate flags passed to --debug-flag. - Available flags: - debug-exit - pdb-postmortem - """ - valid_flags = ['debug-exit','pdb-postmortem'] + Available flags: + debug-exit + pdb-postmortem + """ + valid_flags = ['debug-exit', 'pdb-postmortem'] - if flag in valid_flags: - return flag + if flag in valid_flags: + return flag else: - raise argparse.ArgumentTypeError("Invalid flag - valid flags include: " + - str(valid_flags)) - + raise argparse.ArgumentTypeError("Invalid flag - valid flags include: " + + str(valid_flags)) + def main(): parser = get_argparser() From fc37223d1b3b0430c38f09c88e0a279212a762ee Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 9 Apr 2017 11:36:13 +0200 Subject: [PATCH 160/299] Regenerate docs properly for qute:help test --- tests/end2end/features/misc.feature | 3 ++- tests/end2end/features/test_misc_bdd.py | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 52c56b5c1..83de1c822 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -310,7 +310,8 @@ Feature: Various utility commands. # https://github.com/qutebrowser/qutebrowser/issues/2513 Scenario: Opening link with qute:help - When I run :tab-only + When the documentation is up to date + And I run :tab-only And I open qute:help without waiting And I wait for "Changing title for idx 0 to 'qutebrowser help'" in the log And I hint with args "links normal" and follow a diff --git a/tests/end2end/features/test_misc_bdd.py b/tests/end2end/features/test_misc_bdd.py index 177f7f383..9f7ce63e6 100644 --- a/tests/end2end/features/test_misc_bdd.py +++ b/tests/end2end/features/test_misc_bdd.py @@ -38,11 +38,13 @@ def update_documentation(): doc_path = os.path.join(base_path, 'html', 'doc') script_path = os.path.join(base_path, '..', 'scripts') - if not os.path.exists(doc_path): - # On CI, we can test this without actually building the docs - return + try: + os.mkdir(doc_path) + except FileExistsError: + pass - if all(docutils.docs_up_to_date(p) for p in os.listdir(doc_path)): + files = os.listdir(doc_path) + if files and all(docutils.docs_up_to_date(p) for p in files): return try: From c0ac1bd79a82d6abeaa8b0fa38035f89f9993768 Mon Sep 17 00:00:00 2001 From: Jacob Sword Date: Sun, 9 Apr 2017 10:34:51 -0500 Subject: [PATCH 161/299] Add 'dest' for '--debug-flag' --- qutebrowser/qutebrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 4019b9650..e318cc2e4 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -120,7 +120,7 @@ def get_argparser(): nargs=1, action='append') debug.add_argument('--debug-flag', type=debug_flag_error, help="Pass name of debugging feature to be turned on.", - nargs=1, action='append') + nargs=1, action='append', dest='debug_flags') parser.add_argument('command', nargs='*', help="Commands to execute on " "startup.", metavar=':command') # URLs will actually be in command From dcf8f29a6765fea0f94f580dfa37067431a0e1a5 Mon Sep 17 00:00:00 2001 From: Jacob Sword Date: Sun, 9 Apr 2017 10:43:40 -0500 Subject: [PATCH 162/299] Remove old --pdb-postmortem and --debug-exit flags --- qutebrowser/qutebrowser.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index e318cc2e4..98bb681ef 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -103,10 +103,6 @@ def get_argparser(): help="Silently remove unknown config options.") debug.add_argument('--nowindow', action='store_true', help="Don't show " "the main window.") - debug.add_argument('--debug-exit', help="Turn on debugging of late exit.", - action='store_true') - debug.add_argument('--pdb-postmortem', action='store_true', - help="Drop into pdb on exceptions.") debug.add_argument('--temp-basedir', action='store_true', help="Use a " "temporary basedir.") debug.add_argument('--no-err-windows', action='store_true', help="Don't " @@ -152,8 +148,8 @@ def debug_flag_error(flag): """Validate flags passed to --debug-flag. Available flags: - debug-exit - pdb-postmortem + debug-exit: Turn on debugging of late exit. + pdb-postmortem: Drop into pdb on exceptions. """ valid_flags = ['debug-exit', 'pdb-postmortem'] From f31aead992e829cb15c4fbedbf816a23d2a916a7 Mon Sep 17 00:00:00 2001 From: Jacob Sword Date: Sun, 9 Apr 2017 23:34:33 -0400 Subject: [PATCH 163/299] Add default to --debug-flag --- qutebrowser/qutebrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 98bb681ef..3ce11b07b 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -114,7 +114,7 @@ def get_argparser(): action='append') debug.add_argument('--qt-flag', help="Pass an argument to Qt as flag.", nargs=1, action='append') - debug.add_argument('--debug-flag', type=debug_flag_error, + debug.add_argument('--debug-flag', type=debug_flag_error, default=[], help="Pass name of debugging feature to be turned on.", nargs=1, action='append', dest='debug_flags') parser.add_argument('command', nargs='*', help="Commands to execute on " From b1e3add02e952387bb6e79a83504bdaa0784671a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 10 Apr 2017 08:47:43 +0200 Subject: [PATCH 164/299] Update screenshots --- doc/img/completion.png | Bin 14282 -> 48272 bytes doc/img/downloads.png | Bin 26987 -> 46490 bytes doc/img/hints.png | Bin 32318 -> 59935 bytes doc/img/main.png | Bin 27002 -> 46597 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/img/completion.png b/doc/img/completion.png index 9ba30877e74c6cb1e47475081bbbdee409dd95f0..84a49828ff3e89116c07933e937d5605c2dc1b31 100644 GIT binary patch literal 48272 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 LG^9-Eo6r9N-Ud6C literal 14282 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 W_V|+|>7C>BpS3kks6SUd>-!%;5xk`U literal 26987 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 uCvo zUTwrXgA@p8!YF_!sAPDpKYOLSXr#v7gYMk;8#* zFV{k)LPKm(U00sOlI@G-$xHSU65avy=4NJv z#Y8cjS>gtFRPBl?1lC3)y04+<(iGTXmE+hNE2Towbo}~4Q~k;ISs$p8S}^r-ECDb@ zM+OIl%1h})ts91hJdd{A$N%+b^8SA3T>rTwXASg5g<+$<-yPzrfcotP&1|%A8G>D^ zM5o$S%lFC^{HNRCIFjLciu67f0xsP$ImO9odtfXoE^cjUX(S81n*$evw)|+%2RW7CZg5GyAuuuAwdm{-hAH(gpDtTLb8T^cE}6t^3;-Ms2IZgFV3@jC`& zl=Z3pXWidxW-%dhj*lq0G)iYjikKOy!r%ci8_VCXsd5>^ItU)0v8~KndeO^aByj^* zsw!o@u^c$te?fEn9wW{+3W>PlIQ_GlbFt9wkn@c@tb3s1G!;rbDcTrl`aG8BGBQ5l ziksuep|boDa#O&Se1Tq(m^cuY?Xqoa7DS1b1>dEeaTUUa#XbTwIVqbSW}o|t=%t^`#`m_hc3|Dj~6IgG>+@y{q)6C!}V<7e#B z6cN(7c3Kn(WuA%?|3S}%`$f2+UnIPQILh$jC7!eH55cOWsyZbDpZ0)@1iqUAzL`LR zT$jp=>q9(T!Y{g64Z9o+#;N@RoUi7Pq_Rc4Xgqqt=v3TT7yPah;VbiKp9 zX3|)ewM(PefY!rt^GMnCfcYUMloap_=BCC*fnxnr_PX?)zJ!DqXhb02aBHClw13pZ z(Mu}9Xd^5L_GH3&-F;4tj`~f61p_(iD*!ah#y(J$m;Y;Wk{4cw-1XCwL2PD;agQNn z?t*yWr&;3`T*P~dztY7X9Cs-7+E$Z1&CG*FvtHC8)LTek4Pq=gQ) zqc|Z`>U^h;WrU$al=hQNQ?mI(U)A!UF4D{GidTR^eXwJUefT!fnANk3l7)~7|MNxm zJHNt|jC%1UKTpY@tG&W4UZvF>ndpf=a z1-<>uP|lVmxX4M%)AzkLC$JT=D}!SFden4BG#p@FydawE{Uuzy9-W#qPMcG~bWk7u zQeA(k3*9TTi5mRwey5v}KS0-mp8u#c(yA&vHJ09F@!gStEA8{%v3rG-Hnw<<{Zx@# zckMqME6~5vU-OTA+3+JS&`pM_=VC4AoFCr=+vM&u9owCg^UV}c;m791AK!g8pXSsM zYZUHuhyL1wrlmiaGMe>F5xfU&;c2cPI_};X(;(YwDsFsmLBE@HZi($acYD{FNvBvb z6H)1uP`!gqy%(m+yPn})s-1!xh2LuC)zi1)rQ$|AEL>wNeRCQ_47 diff --git a/doc/img/hints.png b/doc/img/hints.png index 812af3581ca59f33a92f877cec6fbf0f76cc7384..3407ddf47058588a26606e289d56d8147eb4b265 100644 GIT binary patch literal 59935 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
+qutebrowser is currently running a crowdfunding campaign for its new configuration system, allowing for per-domain settings and much more. +See the Kickstarter campaign for more information! +
+++ diff --git a/www/qute.css b/www/qute.css index 2de633b18..9a47b8122 100644 --- a/www/qute.css +++ b/www/qute.css @@ -53,6 +53,12 @@ p { color: #666666; } +#crowdfunding { + padding: 10px 10px; + background-color: #a6dfff; + margin-bottom: 10px; +} + #menu { padding: 0px 20px; background-color: #555555; From b82aada50b11e438a2a4bc9259352a38a1c3ce93 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Apr 2017 16:38:36 +0200 Subject: [PATCH 197/299] Fix highlights in crowdfunding note --- README.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index d9dbd8c0c..fdb82b4d2 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -26,9 +26,9 @@ It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl. // QUTE_WEB_HIDE **qutebrowser is currently running a crowdfunding campaign for its new -configuration system, allowing for per-domain settings and much more. +configuration system, allowing for per-domain settings and much more.** -See the link:https://www.kickstarter.com/projects/the-compiler/qutebrowser-v10-with-per-domain-settings?ref=6zw7qz[Kickstarter campaign] for more information!** +See the link:https://www.kickstarter.com/projects/the-compiler/qutebrowser-v10-with-per-domain-settings?ref=6zw7qz[Kickstarter campaign] for more information! // QUTE_WEB_HIDE_END Screenshots From 0c96a32366c568fe984ed57721b1bdefed4a20e9 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 19 Apr 2017 01:52:32 +0200 Subject: [PATCH 198/299] Update setuptools from 35.0.0 to 35.0.1 --- misc/requirements/requirements-pip.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt index 9979e87f2..b015ca518 100644 --- a/misc/requirements/requirements-pip.txt +++ b/misc/requirements/requirements-pip.txt @@ -3,6 +3,6 @@ appdirs==1.4.3 packaging==16.8 pyparsing==2.2.0 -setuptools==35.0.0 +setuptools==35.0.1 six==1.10.0 wheel==0.29.0 From 0b118f4fd83195e24b3e658c6f919db8718dd820 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 20 Apr 2017 04:35:10 +0200 Subject: [PATCH 199/299] Blacklist pydocstyle >= 2.0.0 Closes #2539 --- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-flake8.txt-raw | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 9c0aad110..d30cb2dd9 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -16,5 +16,5 @@ flake8-tuple==0.2.12 mccabe==0.6.1 pep8-naming==0.4.1 pycodestyle==2.3.1 -pydocstyle==1.1.1 +pydocstyle==1.1.1 # rq.filter: < 2.0.0 pyflakes==1.5.0 diff --git a/misc/requirements/requirements-flake8.txt-raw b/misc/requirements/requirements-flake8.txt-raw index cf660a8ce..e235df395 100644 --- a/misc/requirements/requirements-flake8.txt-raw +++ b/misc/requirements/requirements-flake8.txt-raw @@ -11,7 +11,7 @@ flake8-string-format flake8-tidy-imports flake8-tuple pep8-naming -pydocstyle +pydocstyle<2.0.0 pyflakes # Pinned to 2.0.0 otherwise @@ -21,6 +21,7 @@ mccabe==0.6.1 # Waiting until flake8-putty updated #@ filter: flake8 < 3.0.0 +#@ filter: pydocstyle < 2.0.0 # https://github.com/JBKahn/flake8-debugger/issues/5 #@ filter: flake8-debugger != 2.0.0 From 1ebe0f2ce88927264f5f56dc66f2422df59d069d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 20 Apr 2017 04:40:03 +0200 Subject: [PATCH 200/299] Regenerate docs --- README.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/README.asciidoc b/README.asciidoc index fdb82b4d2..7969d794b 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -292,6 +292,7 @@ Contributors, sorted by the number of commits in descending order: * Dietrich Daroch * Derek Sivers * Daniel Lu +* Daniel Jakots * Arseniy Seroka * Andy Balaam * Andreas Fischer From 616a764b6dc048053bb46919c8ea055e19e67809 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 21 Apr 2017 20:00:01 +0200 Subject: [PATCH 201/299] Update hypothesis from 3.7.0 to 3.7.3 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 0187026ce..9805a1931 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -9,7 +9,7 @@ EasyProcess==0.2.3 Flask==0.12.1 glob2==0.5 httpbin==0.5.0 -hypothesis==3.7.0 +hypothesis==3.7.3 itsdangerous==0.24 # Jinja2==2.9.5 Mako==1.0.6 From 02cccb3673e7880fb1c507ca0f3e12e3ed954d6e Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 23 Apr 2017 11:48:10 +0200 Subject: [PATCH 202/299] Update colorama from 0.3.7 to 0.3.8 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5b4053a33..57ed78734 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -colorama==0.3.7 +colorama==0.3.8 cssutils==1.0.2 Jinja2==2.9.6 MarkupSafe==1.0 From 4220cfc34e85c10fc810a779a37eb1f3871e2ff4 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 23 Apr 2017 16:45:10 +0200 Subject: [PATCH 203/299] Update hypothesis from 3.7.3 to 3.8.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 9805a1931..d5b0a0335 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -9,7 +9,7 @@ EasyProcess==0.2.3 Flask==0.12.1 glob2==0.5 httpbin==0.5.0 -hypothesis==3.7.3 +hypothesis==3.8.0 itsdangerous==0.24 # Jinja2==2.9.5 Mako==1.0.6 From a1de313aa3c11aff44041110a3efbb3237eeb799 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 23 Apr 2017 23:10:29 +0200 Subject: [PATCH 204/299] Add qapp to test_proxy_from_url_pac --- tests/unit/utils/test_urlutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 4729bd661..6649873e9 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -765,7 +765,7 @@ class TestProxyFromUrl: assert urlutils.proxy_from_url(QUrl(url)) == expected @pytest.mark.parametrize('scheme', ['pac+http', 'pac+https']) - def test_proxy_from_url_pac(self, scheme): + def test_proxy_from_url_pac(self, scheme, qapp): fetcher = urlutils.proxy_from_url(QUrl('{}://foo'.format(scheme))) assert isinstance(fetcher, pac.PACFetcher) From beb661cdc713da873dd4a42d7b93b40bd9ab80b1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 23 Apr 2017 23:11:12 +0200 Subject: [PATCH 205/299] Add xos4 Terminus to default monospace fonts --- doc/help/settings.asciidoc | 2 +- qutebrowser/config/configdata.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 9092ea052..c6a80754c 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -2267,7 +2267,7 @@ Fonts used for the UI, with optional style/weight/size. === _monospace Default monospace fonts. -Default: +pass:[Terminus, Monospace, "DejaVu Sans Mono", Monaco, "Bitstream Vera Sans Mono", "Andale Mono", "Courier New", Courier, "Liberation Mono", monospace, Fixed, Consolas, Terminal]+ +Default: +pass:[xos4 Terminus, Terminus, Monospace, "DejaVu Sans Mono", Monaco, "Bitstream Vera Sans Mono", "Andale Mono", "Courier New", Courier, "Liberation Mono", monospace, Fixed, Consolas, Terminal]+ [[fonts-completion]] === completion diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index abf704801..2bfcabce7 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1359,7 +1359,7 @@ def data(readonly=False): ('fonts', sect.KeyValue( ('_monospace', - SettingValue(typ.Font(), 'Terminus, Monospace, ' + SettingValue(typ.Font(), 'xos4 Terminus, Terminus, Monospace, ' '"DejaVu Sans Mono", Monaco, ' '"Bitstream Vera Sans Mono", "Andale Mono", ' '"Courier New", Courier, "Liberation Mono", ' From 195d0ea2074cd559c6f4b29cccff9022dbbe7658 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 24 Apr 2017 06:58:41 +0200 Subject: [PATCH 206/299] Show Punycode URL for IDN pages in addition to decoded one This helps when Unicode homographs are used for phishing purposes. Fixes #2547 --- CHANGELOG.asciidoc | 2 + qutebrowser/mainwindow/statusbar/url.py | 13 +++-- qutebrowser/utils/urlutils.py | 24 ++++++++ tests/unit/mainwindow/statusbar/test_url.py | 65 +++++++++------------ tests/unit/utils/test_urlutils.py | 25 ++++++++ 5 files changed, 88 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 983f8a6d3..6e4c8c372 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,8 @@ Added Changed ~~~~~~~ +- To prevent elaborate phishing attacks, the Punycode version is now shown in + addition to the decoded version for IDN domain names. - When using QtWebEngine, the underlying Chromium version is now shown in the version info. - Improved `qute:history` page with lazy loading diff --git a/qutebrowser/mainwindow/statusbar/url.py b/qutebrowser/mainwindow/statusbar/url.py index bca1f0458..817953d9d 100644 --- a/qutebrowser/mainwindow/statusbar/url.py +++ b/qutebrowser/mainwindow/statusbar/url.py @@ -24,7 +24,7 @@ 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 +from qutebrowser.utils import usertypes, urlutils # Note this has entries for success/error/warn from widgets.webview:LoadStatus @@ -139,7 +139,7 @@ class UrlText(textbase.TextBase): if url is None: self._normal_url = None else: - self._normal_url = url.toDisplayString() + self._normal_url = urlutils.safe_display_url(url) self._normal_url_type = UrlType.normal self._update_url() @@ -156,9 +156,9 @@ class UrlText(textbase.TextBase): if link: qurl = QUrl(link) if qurl.isValid(): - self._hover_url = qurl.toDisplayString() + self._hover_url = urlutils.safe_display_url(qurl) else: - self._hover_url = link + self._hover_url = '(invalid URL!) {}'.format(link) else: self._hover_url = None self._update_url() @@ -167,6 +167,9 @@ class UrlText(textbase.TextBase): def on_tab_changed(self, tab): """Update URL if the tab changed.""" self._hover_url = None - self._normal_url = tab.url().toDisplayString() + if tab.url().isValid(): + self._normal_url = urlutils.safe_display_url(tab.url()) + else: + self._normal_url = '' self.on_load_status_changed(tab.load_status().name) self._update_url() diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 1beebbe92..376d46a42 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -592,6 +592,30 @@ def data_url(mimetype, data): return url +def safe_display_url(qurl): + """Get a IDN-homograph phising safe form of the given QUrl. + + If we're dealing with a Punycode-encoded URL, this prepends the hostname in + its encoded form, to make sure those URLs are distinguishable. + + See https://github.com/qutebrowser/qutebrowser/issues/2547 + and https://bugreports.qt.io/browse/QTBUG-60365 + """ + if not qurl.isValid(): + raise InvalidUrlError(qurl) + + host = qurl.host(QUrl.FullyEncoded) + if '..' in host: + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-60364 + return '(unparseable URL!) {}'.format(qurl.toDisplayString()) + + for part in host.split('.'): + if part.startswith('xn--') and host != qurl.host(QUrl.FullyDecoded): + return '({}) {}'.format(host, qurl.toDisplayString()) + + return qurl.toDisplayString() + + class InvalidProxyTypeError(Exception): """Error raised when proxy_from_url gets an unknown proxy type.""" diff --git a/tests/unit/mainwindow/statusbar/test_url.py b/tests/unit/mainwindow/statusbar/test_url.py index 1fe201bff..97f09840d 100644 --- a/tests/unit/mainwindow/statusbar/test_url.py +++ b/tests/unit/mainwindow/statusbar/test_url.py @@ -22,7 +22,7 @@ import pytest -from qutebrowser.utils import usertypes +from qutebrowser.utils import usertypes, urlutils from qutebrowser.mainwindow.statusbar import url from PyQt5.QtCore import QUrl @@ -51,49 +51,41 @@ def url_widget(qtbot, monkeypatch, config_stub): return widget -@pytest.mark.parametrize('qurl', [ - QUrl('http://abc123.com/this/awesome/url.html'), - QUrl('https://supersecret.gov/nsa/files.txt'), - None -]) -def test_set_url(url_widget, qurl): - """Test text displayed by the widget.""" - url_widget.set_url(qurl) - if qurl is not None: - assert url_widget.text() == qurl.toDisplayString() - else: - assert url_widget.text() == "" - - -@pytest.mark.parametrize('url_text', [ - 'http://abc123.com/this/awesome/url.html', - 'https://supersecret.gov/nsa/files.txt', - None, -]) -def test_set_hover_url(url_widget, url_text): - """Test text when hovering over a link.""" - url_widget.set_hover_url(url_text) - if url_text is not None: - assert url_widget.text() == url_text - assert url_widget._urltype == url.UrlType.hover - else: - assert url_widget.text() == '' - assert url_widget._urltype == url.UrlType.normal - - @pytest.mark.parametrize('url_text, expected', [ + ('http://example.com/foo/bar.html', 'http://example.com/foo/bar.html'), ('http://test.gr/%CE%B1%CE%B2%CE%B3%CE%B4.txt', 'http://test.gr/αβγδ.txt'), ('http://test.ru/%D0%B0%D0%B1%D0%B2%D0%B3.txt', 'http://test.ru/абвг.txt'), ('http://test.com/s%20p%20a%20c%20e.txt', 'http://test.com/s p a c e.txt'), ('http://test.com/%22quotes%22.html', 'http://test.com/%22quotes%22.html'), ('http://username:secret%20password@test.com', 'http://username@test.com'), - ('http://example.com%5b/', 'http://example.com%5b/'), # invalid url + ('http://example.com%5b/', '(invalid URL!) http://example.com%5b/'), + # https://bugreports.qt.io/browse/QTBUG-60364 + ('http://www.xn--80ak6aa92e.com', + '(unparseable URL!) http://www.аррӏе.com'), + # IDN URL + ('http://www.ä.com', '(www.xn--4ca.com) http://www.ä.com'), + (None, ''), ]) -def test_set_hover_url_encoded(url_widget, url_text, expected): +@pytest.mark.parametrize('which', ['normal', 'hover']) +def test_set_url(url_widget, url_text, expected, which): """Test text when hovering over a percent encoded link.""" - url_widget.set_hover_url(url_text) + qurl = QUrl(url_text) + if which == 'normal': + if not qurl.isValid(): + with pytest.raises(urlutils.InvalidUrlError): + url_widget.set_url(qurl) + return + else: + url_widget.set_url(qurl) + else: + url_widget.set_hover_url(url_text) + assert url_widget.text() == expected - assert url_widget._urltype == url.UrlType.hover + + if which == 'hover' and expected: + assert url_widget._urltype == url.UrlType.hover + else: + assert url_widget._urltype == url.UrlType.normal @pytest.mark.parametrize('status, expected', [ @@ -114,6 +106,7 @@ def test_on_load_status_changed(url_widget, status, expected): @pytest.mark.parametrize('load_status, qurl', [ (url.UrlType.success, QUrl('http://abc123.com/this/awesome/url.html')), (url.UrlType.success, QUrl('http://reddit.com/r/linux')), + (url.UrlType.success, QUrl('http://ä.com/')), (url.UrlType.success_https, QUrl('www.google.com')), (url.UrlType.success_https, QUrl('https://supersecret.gov/nsa/files.txt')), (url.UrlType.warn, QUrl('www.shadysite.org/some/file/with/issues.htm')), @@ -123,7 +116,7 @@ def test_on_tab_changed(url_widget, fake_web_tab, load_status, qurl): tab_widget = fake_web_tab(load_status=load_status, url=qurl) url_widget.on_tab_changed(tab_widget) assert url_widget._urltype == load_status - assert url_widget.text() == qurl.toDisplayString() + assert url_widget.text() == urlutils.safe_display_url(qurl) @pytest.mark.parametrize('qurl, load_status, expected_status', [ diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 6649873e9..8b0fb4e3b 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -742,6 +742,31 @@ def test_data_url(): assert url == QUrl('data:text/plain;base64,Zm9v') +@pytest.mark.parametrize('url, expected', [ + # No IDN + (QUrl('http://www.example.com'), 'http://www.example.com'), + # IDN in domain + (QUrl('http://www.ä.com'), '(www.xn--4ca.com) http://www.ä.com'), + # IDN with non-whitelisted TLD + (QUrl('http://www.ä.foo'), 'http://www.xn--4ca.foo'), + # Unicode only in path + (QUrl('http://www.example.com/ä'), 'http://www.example.com/ä'), + # Unicode only in TLD (looks like Qt shows Punycode with рф...) + (QUrl('http://www.example.xn--p1ai'), + '(www.example.xn--p1ai) http://www.example.рф'), + # https://bugreports.qt.io/browse/QTBUG-60364 + (QUrl('http://www.xn--80ak6aa92e.com'), + '(unparseable URL!) http://www.аррӏе.com'), +]) +def test_safe_display_url(url, expected): + assert urlutils.safe_display_url(url) == expected + + +def test_safe_display_url_invalid(): + with pytest.raises(urlutils.InvalidUrlError): + urlutils.safe_display_url(QUrl()) + + class TestProxyFromUrl: @pytest.mark.parametrize('url, expected', [ From b632fe3285728d3bce965e5ee40c0b045c741dba Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 24 Apr 2017 07:47:58 +0200 Subject: [PATCH 207/299] Fix invalid URL handling in statusbar --- qutebrowser/mainwindow/statusbar/url.py | 2 ++ tests/unit/mainwindow/statusbar/test_url.py | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/qutebrowser/mainwindow/statusbar/url.py b/qutebrowser/mainwindow/statusbar/url.py index 817953d9d..b9a9d86aa 100644 --- a/qutebrowser/mainwindow/statusbar/url.py +++ b/qutebrowser/mainwindow/statusbar/url.py @@ -138,6 +138,8 @@ class UrlText(textbase.TextBase): """ if url is None: self._normal_url = None + elif not url.isValid(): + self._normal_url = "Invalid URL!" else: self._normal_url = urlutils.safe_display_url(url) self._normal_url_type = UrlType.normal diff --git a/tests/unit/mainwindow/statusbar/test_url.py b/tests/unit/mainwindow/statusbar/test_url.py index 97f09840d..696b2be9f 100644 --- a/tests/unit/mainwindow/statusbar/test_url.py +++ b/tests/unit/mainwindow/statusbar/test_url.py @@ -71,12 +71,12 @@ def test_set_url(url_widget, url_text, expected, which): """Test text when hovering over a percent encoded link.""" qurl = QUrl(url_text) if which == 'normal': - if not qurl.isValid(): - with pytest.raises(urlutils.InvalidUrlError): - url_widget.set_url(qurl) + if url_text is None: return - else: - url_widget.set_url(qurl) + if not qurl.isValid(): + # Special case for the invalid URL above + expected = "Invalid URL!" + url_widget.set_url(qurl) else: url_widget.set_hover_url(url_text) From 52f31ed15c084b361eabe046eab35b521924d49f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 24 Apr 2017 07:49:12 +0200 Subject: [PATCH 208/299] Rename urlutils.safe_display_url to safe_display_string --- qutebrowser/mainwindow/statusbar/url.py | 6 +++--- qutebrowser/utils/urlutils.py | 2 +- tests/unit/mainwindow/statusbar/test_url.py | 2 +- tests/unit/utils/test_urlutils.py | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/qutebrowser/mainwindow/statusbar/url.py b/qutebrowser/mainwindow/statusbar/url.py index b9a9d86aa..c7bee2ae9 100644 --- a/qutebrowser/mainwindow/statusbar/url.py +++ b/qutebrowser/mainwindow/statusbar/url.py @@ -141,7 +141,7 @@ class UrlText(textbase.TextBase): elif not url.isValid(): self._normal_url = "Invalid URL!" else: - self._normal_url = urlutils.safe_display_url(url) + self._normal_url = urlutils.safe_display_string(url) self._normal_url_type = UrlType.normal self._update_url() @@ -158,7 +158,7 @@ class UrlText(textbase.TextBase): if link: qurl = QUrl(link) if qurl.isValid(): - self._hover_url = urlutils.safe_display_url(qurl) + self._hover_url = urlutils.safe_display_string(qurl) else: self._hover_url = '(invalid URL!) {}'.format(link) else: @@ -170,7 +170,7 @@ class UrlText(textbase.TextBase): """Update URL if the tab changed.""" self._hover_url = None if tab.url().isValid(): - self._normal_url = urlutils.safe_display_url(tab.url()) + self._normal_url = urlutils.safe_display_string(tab.url()) else: self._normal_url = '' self.on_load_status_changed(tab.load_status().name) diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 376d46a42..ac11c1fb5 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -592,7 +592,7 @@ def data_url(mimetype, data): return url -def safe_display_url(qurl): +def safe_display_string(qurl): """Get a IDN-homograph phising safe form of the given QUrl. If we're dealing with a Punycode-encoded URL, this prepends the hostname in diff --git a/tests/unit/mainwindow/statusbar/test_url.py b/tests/unit/mainwindow/statusbar/test_url.py index 696b2be9f..c104cb233 100644 --- a/tests/unit/mainwindow/statusbar/test_url.py +++ b/tests/unit/mainwindow/statusbar/test_url.py @@ -116,7 +116,7 @@ def test_on_tab_changed(url_widget, fake_web_tab, load_status, qurl): tab_widget = fake_web_tab(load_status=load_status, url=qurl) url_widget.on_tab_changed(tab_widget) assert url_widget._urltype == load_status - assert url_widget.text() == urlutils.safe_display_url(qurl) + assert url_widget.text() == urlutils.safe_display_string(qurl) @pytest.mark.parametrize('qurl, load_status, expected_status', [ diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 8b0fb4e3b..9b3c5d1c3 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -758,13 +758,13 @@ def test_data_url(): (QUrl('http://www.xn--80ak6aa92e.com'), '(unparseable URL!) http://www.аррӏе.com'), ]) -def test_safe_display_url(url, expected): - assert urlutils.safe_display_url(url) == expected +def test_safe_display_string(url, expected): + assert urlutils.safe_display_string(url) == expected -def test_safe_display_url_invalid(): +def test_safe_display_string_invalid(): with pytest.raises(urlutils.InvalidUrlError): - urlutils.safe_display_url(QUrl()) + urlutils.safe_display_string(QUrl()) class TestProxyFromUrl: From 930b0f08181f1ba6b96669af29b11b2c1691d7c5 Mon Sep 17 00:00:00 2001 From: Marcel Schilling Date: Mon, 24 Apr 2017 07:56:44 +0200 Subject: [PATCH 209/299] typo fix (in comment) --- qutebrowser/utils/urlutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index ac11c1fb5..23bf97f9f 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -593,7 +593,7 @@ def data_url(mimetype, data): def safe_display_string(qurl): - """Get a IDN-homograph phising safe form of the given QUrl. + """Get a IDN-homograph phishing safe form of the given QUrl. If we're dealing with a Punycode-encoded URL, this prepends the hostname in its encoded form, to make sure those URLs are distinguishable. From 18082526f48afcef3fc574e1536ef282aa0cf5bd Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Mon, 24 Apr 2017 10:59:11 +0500 Subject: [PATCH 210/299] Show hostname in history page. --- qutebrowser/html/history.html | 7 +++++++ qutebrowser/javascript/history.js | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/qutebrowser/html/history.html b/qutebrowser/html/history.html index 35ac419be..955aefb19 100644 --- a/qutebrowser/html/history.html +++ b/qutebrowser/html/history.html @@ -47,6 +47,13 @@ table { text-align: center; } +.hostname { + color: #858585; + font-size: 0.9em; + margin-left: 10px; + text-decoration: none; +} + {% endblock %} {% block content %}

Browsing history

diff --git a/qutebrowser/javascript/history.js b/qutebrowser/javascript/history.js index a2448a14f..f46ceb49d 100644 --- a/qutebrowser/javascript/history.js +++ b/qutebrowser/javascript/history.js @@ -111,7 +111,11 @@ window.loadHistory = (function() { var link = document.createElement("a"); link.href = itemUrl; link.innerHTML = itemTitle; + var host = document.createElement("span"); + host.className = "hostname"; + host.innerHTML = link.hostname; title.appendChild(link); + title.appendChild(host); var time = document.createElement("td"); time.className = "time"; From 21204299604e1ecaddfd834aab44e12fd9bccf07 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 24 Apr 2017 08:01:44 +0200 Subject: [PATCH 211/299] Regenerate authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 7969d794b..8f1af3bf0 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -251,6 +251,7 @@ Contributors, sorted by the number of commits in descending order: * Penaz * Mathias Fussenegger * Marcelo Santos +* Marcel Schilling * Joel Bradshaw * Jean-Louis Fuchs * Franz Fellner @@ -278,7 +279,6 @@ Contributors, sorted by the number of commits in descending order: * Noah Huesser * Moez Bouhlel * Matthias Lisin -* Marcel Schilling * Lazlow Carmichael * Kevin Wang * Ján Kobezda From 1539301d64634dec59d67ad5bd91689b1505d7ca Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 24 Apr 2017 08:41:29 +0200 Subject: [PATCH 212/299] Fix test coverage for statusbar.url --- tests/unit/mainwindow/statusbar/test_url.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/unit/mainwindow/statusbar/test_url.py b/tests/unit/mainwindow/statusbar/test_url.py index c104cb233..78f7ab646 100644 --- a/tests/unit/mainwindow/statusbar/test_url.py +++ b/tests/unit/mainwindow/statusbar/test_url.py @@ -69,13 +69,14 @@ def url_widget(qtbot, monkeypatch, config_stub): @pytest.mark.parametrize('which', ['normal', 'hover']) def test_set_url(url_widget, url_text, expected, which): """Test text when hovering over a percent encoded link.""" - qurl = QUrl(url_text) if which == 'normal': if url_text is None: - return - if not qurl.isValid(): - # Special case for the invalid URL above - expected = "Invalid URL!" + qurl = None + else: + qurl = QUrl(url_text) + if not qurl.isValid(): + # Special case for the invalid URL above + expected = "Invalid URL!" url_widget.set_url(qurl) else: url_widget.set_hover_url(url_text) @@ -111,12 +112,18 @@ def test_on_load_status_changed(url_widget, status, expected): (url.UrlType.success_https, QUrl('https://supersecret.gov/nsa/files.txt')), (url.UrlType.warn, QUrl('www.shadysite.org/some/file/with/issues.htm')), (url.UrlType.error, QUrl('invalid::/url')), + (url.UrlType.error, QUrl()), ]) def test_on_tab_changed(url_widget, fake_web_tab, load_status, qurl): tab_widget = fake_web_tab(load_status=load_status, url=qurl) url_widget.on_tab_changed(tab_widget) + assert url_widget._urltype == load_status - assert url_widget.text() == urlutils.safe_display_string(qurl) + if not qurl.isValid(): + expected = '' + else: + expected = urlutils.safe_display_string(qurl) + assert url_widget.text() == expected @pytest.mark.parametrize('qurl, load_status, expected_status', [ From 11c026bf4cbea51791cfaae2d12be4b280939e70 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 24 Apr 2017 12:22:19 +0200 Subject: [PATCH 213/299] Reenable QtWebKit cache with Qt 5.9. This was fixed here: https://codereview.qt-project.org/#/c/190818/ See #2427 --- CHANGELOG.asciidoc | 4 ++-- qutebrowser/browser/webkit/cache.py | 3 ++- tests/unit/browser/webkit/test_cache.py | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 6e4c8c372..a86dc2669 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -43,8 +43,8 @@ Changed - PAC on QtWebKit now supports SOCKS5 as type. - Comments in the config file are now before the individual options instead of being before sections. -- The HTTP cache is disabled with QtWebKit on Qt 5.8 now as it leads to frequent - crashes due to a Qt bug. +- The HTTP cache is disabled with QtWebKit on Qt 5.7.1 and 5.8 now as it leads + to frequent crashes due to a Qt bug. - stdin is now closed immediately for processes spawned from qutebrowser - When ui -> message-timeout is set to 0, messages are now never cleared. diff --git a/qutebrowser/browser/webkit/cache.py b/qutebrowser/browser/webkit/cache.py index 8bbb8f812..4f96b6528 100644 --- a/qutebrowser/browser/webkit/cache.py +++ b/qutebrowser/browser/webkit/cache.py @@ -54,7 +54,8 @@ class DiskCache(QNetworkDiskCache): if size is None: size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909 - if qtutils.version_check('5.7.1'): # pragma: no cover + if (qtutils.version_check('5.7.1') and # pragma: no cover + not qtutils.version_check('5.9')): size = 0 self.setMaximumCacheSize(size) diff --git a/tests/unit/browser/webkit/test_cache.py b/tests/unit/browser/webkit/test_cache.py index ea2473949..bc8efb675 100644 --- a/tests/unit/browser/webkit/test_cache.py +++ b/tests/unit/browser/webkit/test_cache.py @@ -25,9 +25,9 @@ from qutebrowser.browser.webkit import cache from qutebrowser.utils import qtutils -pytestmark = pytest.mark.skipif(qtutils.version_check('5.7.1'), - reason="QNetworkDiskCache is broken on Qt >= " - "5.7.1") +pytestmark = pytest.mark.skipif( + qtutils.version_check('5.7.1') and not qtutils.version_check('5.9'), + reason="QNetworkDiskCache is broken on Qt 5.7.1 and 5.8") def preload_cache(cache, url='http://www.example.com/', content=b'foobar'): From fe7d21dfbe0b9dabcb66eaa61c20a5d16c9e175c Mon Sep 17 00:00:00 2001 From: Imran Sobir Date: Mon, 24 Apr 2017 15:30:01 +0500 Subject: [PATCH 214/299] Show hostname in non-javascript history page. --- qutebrowser/browser/qutescheme.py | 3 ++- qutebrowser/html/history.html | 8 -------- qutebrowser/html/history_nojs.html | 7 +++++-- qutebrowser/html/styled.html | 7 +++++++ 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index d71b6c135..bca93e3c8 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -311,7 +311,8 @@ def qute_history(url): start_time = time.mktime(next_date.timetuple()) - 1 history = [ (i["url"], i["title"], - datetime.datetime.fromtimestamp(i["time"]/1000)) + datetime.datetime.fromtimestamp(i["time"]/1000), + QUrl(i["url"]).host()) for i in history_data(start_time) if "next" not in i ] diff --git a/qutebrowser/html/history.html b/qutebrowser/html/history.html index 955aefb19..2c6c0a2f1 100644 --- a/qutebrowser/html/history.html +++ b/qutebrowser/html/history.html @@ -46,14 +46,6 @@ table { height: 40px; text-align: center; } - -.hostname { - color: #858585; - font-size: 0.9em; - margin-left: 10px; - text-decoration: none; -} - {% endblock %} {% block content %}

Browsing history

diff --git a/qutebrowser/html/history_nojs.html b/qutebrowser/html/history_nojs.html index 3fed67797..bcc5663c1 100644 --- a/qutebrowser/html/history_nojs.html +++ b/qutebrowser/html/history_nojs.html @@ -42,9 +42,12 @@ table { - {% for url, title, time in history %} + {% for url, title, time, host in history %} - + {% endfor %} diff --git a/qutebrowser/html/styled.html b/qutebrowser/html/styled.html index e2a608538..f4d256422 100644 --- a/qutebrowser/html/styled.html +++ b/qutebrowser/html/styled.html @@ -38,4 +38,11 @@ td { padding: 2px 5px; text-align: left; } + +.hostname { + color: #858585; + font-size: 0.9em; + margin-left: 10px; + text-decoration: none; +} {% endblock %} From 111944fb655bc34776e3ae8ad1a556fe10b6d498 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 24 Apr 2017 23:15:34 +0200 Subject: [PATCH 215/299] Revert "Raise exception when a stylesheet is unparsable." This reverts commit 0400945ac4c3b31dd476e2f8727f75d895a09378. See #2571 --- CHANGELOG.asciidoc | 1 + qutebrowser/utils/log.py | 17 +---------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index a86dc2669..dd27c434b 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -61,6 +61,7 @@ Fixed - Crash when the key config isn't writable - Crash when unbinding an unbound key in the key config - Crash when using `:debug-log-filter` when `--filter` wasn't given on startup. +- Crash with some invalid setting values - Various rare crashes v0.10.1 diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 9c660f035..19b4f506f 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -161,11 +161,6 @@ def stub(suffix=''): misc.warning(text) -class CriticalQtWarning(Exception): - - """Exception raised when there's a critical Qt warning.""" - - def init_log(args): """Init loggers based on the argparse namespace passed.""" level = args.loglevel.upper() @@ -424,17 +419,7 @@ def qt_message_handler(msg_type, context, msg): 'with: -9805', # flake8: disable=E131 ] - # Messages which will trigger an exception immediately - critical_msgs = [ - 'Could not parse stylesheet of object', - ] - - if any(msg.strip().startswith(pattern) for pattern in critical_msgs): - # For some reason, the stack gets lost when raising here... - logger = logging.getLogger('misc') - logger.error("Got critical Qt warning!", stack_info=True) - raise CriticalQtWarning(msg) - elif any(msg.strip().startswith(pattern) for pattern in suppressed_msgs): + if any(msg.strip().startswith(pattern) for pattern in suppressed_msgs): level = logging.DEBUG else: level = qt_to_logging[msg_type] From ab61fc57a98abcbfe6ac50622e9cbff25ad0325c Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 25 Apr 2017 00:34:15 +0200 Subject: [PATCH 216/299] Update codecov from 2.0.5 to 2.0.7 --- misc/requirements/requirements-codecov.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 4f020b5cd..2dcb22731 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -codecov==2.0.5 +codecov==2.0.7 coverage==4.3.4 requests==2.13.0 From 3125b69d194309464ef0abcaa4f915ff4cf4316f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Apr 2017 06:43:31 +0200 Subject: [PATCH 217/299] Fix no-cover pragma --- qutebrowser/browser/webkit/cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/webkit/cache.py b/qutebrowser/browser/webkit/cache.py index 4f96b6528..be7dba486 100644 --- a/qutebrowser/browser/webkit/cache.py +++ b/qutebrowser/browser/webkit/cache.py @@ -54,8 +54,8 @@ class DiskCache(QNetworkDiskCache): if size is None: size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909 - if (qtutils.version_check('5.7.1') and # pragma: no cover - not qtutils.version_check('5.9')): + if (qtutils.version_check('5.7.1') and + not qtutils.version_check('5.9')): # pragma: no cover size = 0 self.setMaximumCacheSize(size) From c3e62222960da5584c8512c74f8791b6acaa51c1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Apr 2017 06:59:51 +0200 Subject: [PATCH 218/299] Close the current tab when the tabbar itself is clicked --- CHANGELOG.asciidoc | 2 ++ qutebrowser/mainwindow/tabwidget.py | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index dd27c434b..b75d20c4a 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -47,6 +47,8 @@ Changed to frequent crashes due to a Qt bug. - stdin is now closed immediately for processes spawned from qutebrowser - When ui -> message-timeout is set to 0, messages are now never cleared. +- Middle/right-clicking the blank parts of the tab bar (when vertical) now + closes the current tab. Fixed ~~~~~ diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index ce96607fa..f6ed0a8d1 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -395,11 +395,12 @@ class TabBar(QTabBar): button = config.get('tabs', 'close-mouse-button') if (e.button() == Qt.RightButton and button == 'right' or e.button() == Qt.MiddleButton and button == 'middle'): + e.accept() idx = self.tabAt(e.pos()) - if idx != -1: - e.accept() - self.tabCloseRequested.emit(idx) - return + if idx == -1: + idx = self.currentIndex() + self.tabCloseRequested.emit(idx) + return super().mousePressEvent(e) def minimumTabSizeHint(self, index): From 1015badb8bb753ed562bbecc64942cf2dc26416e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Apr 2017 09:18:31 +0200 Subject: [PATCH 219/299] Disable animation for completion view --- qutebrowser/completion/completionwidget.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index bd6b8c5be..923a67280 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -124,6 +124,7 @@ class CompletionView(QTreeView): self.setIndentation(0) self.setItemsExpandable(False) self.setExpandsOnDoubleClick(False) + self.setAnimated(False) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # WORKAROUND # This is a workaround for weird race conditions with invalid From ca0e04fd0df85f56116fda9ca8db9c6366ac07d3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Apr 2017 18:41:00 +0200 Subject: [PATCH 220/299] Mention :open in issue template See #2574 --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 9ea69b642..b9bf8d399 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,2 +1,2 @@ +`:open qute:version` or `qutebrowser --version` --> From 66eb330a0a481cdd77820492c4745b6ead5f05eb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Apr 2017 21:40:28 +0200 Subject: [PATCH 221/299] Always base tabbar on Fusion style. Fixes crashes with qt5ct. Fixes #2477. Fixes #1554. --- CHANGELOG.asciidoc | 1 + qutebrowser/mainwindow/tabwidget.py | 17 ++++++----------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index b75d20c4a..0fa268d02 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -65,6 +65,7 @@ Fixed - Crash when using `:debug-log-filter` when `--filter` wasn't given on startup. - Crash with some invalid setting values - Various rare crashes +- Various styling issues with the tabbar and a crash with qt5ct v0.10.1 ------- diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index f6ed0a8d1..6acaf235a 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -24,7 +24,8 @@ import functools from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QSize, QRect, QTimer, QUrl from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle, - QStyle, QStylePainter, QStyleOptionTab) + QStyle, QStylePainter, QStyleOptionTab, + QStyleFactory) from PyQt5.QtGui import QIcon, QPalette, QColor from qutebrowser.utils import qtutils, objreg, utils, usertypes, log @@ -51,7 +52,7 @@ class TabWidget(QTabWidget): def __init__(self, win_id, parent=None): super().__init__(parent) bar = TabBar(win_id) - self.setStyle(TabBarStyle(self.style())) + self.setStyle(TabBarStyle()) self.setTabBar(bar) bar.tabCloseRequested.connect(self.tabCloseRequested) bar.tabMoved.connect(functools.partial( @@ -269,7 +270,7 @@ class TabBar(QTabBar): def __init__(self, win_id, parent=None): super().__init__(parent) self._win_id = win_id - self.setStyle(TabBarStyle(self.style())) + self.setStyle(TabBarStyle()) self.set_font() config_obj = objreg.get('config') config_obj.changed.connect(self.set_font) @@ -554,20 +555,14 @@ class TabBarStyle(QCommonStyle): http://stackoverflow.com/a/17294081 https://code.google.com/p/makehuman/source/browse/trunk/makehuman/lib/qtgui.py - - Attributes: - _style: The base/"parent" style. """ - def __init__(self, style): + def __init__(self): """Initialize all functions we're not overriding. This simply calls the corresponding function in self._style. - - Args: - style: The base/"parent" style. """ - self._style = style + self._style = QStyleFactory.create('Fusion') for method in ['drawComplexControl', 'drawItemPixmap', 'generatedIconPixmap', 'hitTestComplexControl', 'itemPixmapRect', 'itemTextRect', 'polish', 'styleHint', From 70d7a56b1189d2d85da1303a2fbacea57171e324 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Apr 2017 22:13:59 +0200 Subject: [PATCH 222/299] Also set Fusion style for downloads and completion This makes those UI elements look the same on different platforms/OS styles, with the small drawback of overriding the context menu style. This most likely fixes #80 (though I couldn't reproduce that on Windows 10). --- qutebrowser/browser/downloadview.py | 3 ++- qutebrowser/completion/completionwidget.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 633fd8700..715fe2a54 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -23,7 +23,7 @@ import functools import sip from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer -from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu +from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory from qutebrowser.browser import downloads from qutebrowser.config import style @@ -75,6 +75,7 @@ class DownloadView(QListView): def __init__(self, win_id, parent=None): super().__init__(parent) + self.setStyle(QStyleFactory.create('Fusion')) style.set_register_stylesheet(self) self.setResizeMode(QListView.Adjust) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index 923a67280..e9fad39fd 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -23,7 +23,7 @@ Defines a CompletionView which uses CompletionFiterModel and CompletionModel subclasses to provide completions. """ -from PyQt5.QtWidgets import QStyle, QTreeView, QSizePolicy +from PyQt5.QtWidgets import QStyle, QTreeView, QSizePolicy, QStyleFactory from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize from qutebrowser.config import config, style @@ -117,6 +117,7 @@ class CompletionView(QTreeView): self._delegate = completiondelegate.CompletionItemDelegate(self) self.setItemDelegate(self._delegate) + self.setStyle(QStyleFactory.create('Fusion')) style.set_register_stylesheet(self) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.setHeaderHidden(True) From 6a35797a2c3df93fcbcbab988b2ce7f95251fd85 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Apr 2017 22:57:39 +0200 Subject: [PATCH 223/299] Mention C++/JS in CONTRIBUTING --- CONTRIBUTING.asciidoc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CONTRIBUTING.asciidoc b/CONTRIBUTING.asciidoc index 12d52fee7..25a7a8a13 100644 --- a/CONTRIBUTING.asciidoc +++ b/CONTRIBUTING.asciidoc @@ -42,6 +42,12 @@ be easy to solve] * https://github.com/qutebrowser/qutebrowser/labels/not%20code[Issues which require little/no coding] +If you prefer C++ or Javascript to Python, see the relevant issues which involve +work in those languages: + +* https://github.com/qutebrowser/qutebrowser/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3Ac%2B%2B[C++] (mostly work on Qt, the library behind qutebrowser) +* https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3Ajavascript[JavaScript] + There are also some things to do if you don't want to write code: * Help the community, e.g., on the mailinglist and the IRC channel. From 27057622ba77b762d8d063e8a313ac97b133f040 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 26 Apr 2017 08:56:41 +0200 Subject: [PATCH 224/299] Update authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 8f1af3bf0..7aa1afa66 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -161,8 +161,8 @@ Contributors, sorted by the number of commits in descending order: * Marshall Lochbaum * Bruno Oliveira * Martin Tournoij -* Alexander Cogneau * Imran Sobir +* Alexander Cogneau * Felix Van der Jeugt * Daniel Karbach * Kevin Velghe From b1d88b47c13bf3cf6dbb74ad1b0bb77bff40cb73 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 26 Apr 2017 10:32:42 +0200 Subject: [PATCH 225/299] Update vulture from 0.13 to 0.14 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index d5b0a0335..fed0d3dca 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -32,5 +32,5 @@ pytest-travis-fold==1.2.0 pytest-warnings==0.2.0 pytest-xvfb==1.0.0 PyVirtualDisplay==0.2.1 -vulture==0.13 +vulture==0.14 Werkzeug==0.12.1 From 9ef17d434ddc85c6cda4d08f00fef15b7904add1 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 26 Apr 2017 10:32:43 +0200 Subject: [PATCH 226/299] Update vulture from 0.13 to 0.14 --- misc/requirements/requirements-vulture.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-vulture.txt b/misc/requirements/requirements-vulture.txt index 26ee55a6e..56e20c603 100644 --- a/misc/requirements/requirements-vulture.txt +++ b/misc/requirements/requirements-vulture.txt @@ -1,3 +1,3 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -vulture==0.13 +vulture==0.14 From e252862f51db7df5ff34e2dc0221a7f56e022f9f Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 26 Apr 2017 15:15:11 +0200 Subject: [PATCH 227/299] Update hypothesis from 3.8.0 to 3.8.1 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index d5b0a0335..edcab990f 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -9,7 +9,7 @@ EasyProcess==0.2.3 Flask==0.12.1 glob2==0.5 httpbin==0.5.0 -hypothesis==3.8.0 +hypothesis==3.8.1 itsdangerous==0.24 # Jinja2==2.9.5 Mako==1.0.6 From 38099c45bd4b6283fcdf04411263c4378e81cf95 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 26 Apr 2017 21:48:21 +0200 Subject: [PATCH 228/299] Update hypothesis from 3.8.1 to 3.8.2 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index c659218d2..511300e96 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -9,7 +9,7 @@ EasyProcess==0.2.3 Flask==0.12.1 glob2==0.5 httpbin==0.5.0 -hypothesis==3.8.1 +hypothesis==3.8.2 itsdangerous==0.24 # Jinja2==2.9.5 Mako==1.0.6 From 046401c48904caae02c0248ef48410dbbe462228 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 27 Apr 2017 08:20:55 +0200 Subject: [PATCH 229/299] Clean up search.feature --- tests/end2end/features/search.feature | 57 ++++++++--------------- tests/end2end/features/test_search_bdd.py | 14 +++++- 2 files changed, 32 insertions(+), 39 deletions(-) diff --git a/tests/end2end/features/search.feature b/tests/end2end/features/search.feature index 4d8b8e17f..aeea2e4e8 100644 --- a/tests/end2end/features/search.feature +++ b/tests/end2end/features/search.feature @@ -11,20 +11,17 @@ Feature: Searching on a page Scenario: Searching text When I run :search foo - And I run :yank selection - Then the clipboard should contain "foo" + Then "foo" should be found Scenario: Searching twice When I run :search foo And I run :search bar - And I run :yank selection - Then the clipboard should contain "Bar" + Then "Bar" should be found Scenario: Searching with --reverse When I set general -> ignore-case to true And I run :search -r foo - And I run :yank selection - Then the clipboard should contain "Foo" + Then "Foo" should be found Scenario: Searching without matches When I run :search doesnotmatch @@ -34,14 +31,12 @@ Feature: Searching on a page Scenario: Searching with / and spaces at the end (issue 874) When I run :set-cmd-text -s /space And I run :command-accept - And I run :yank selection - Then the clipboard should contain "space " + Then "space " should be found Scenario: Searching with / and slash in search term (issue 507) When I run :set-cmd-text -s //slash And I run :command-accept - And I run :yank selection - Then the clipboard should contain "/slash" + Then "/slash" should be found # This doesn't work because this is QtWebKit behavior. @xfail_norun @@ -54,26 +49,22 @@ Feature: Searching on a page Scenario: Searching text with ignore-case = true When I set general -> ignore-case to true And I run :search bar - And I run :yank selection - Then the clipboard should contain "Bar" + Then "Bar" should be found Scenario: Searching text with ignore-case = false When I set general -> ignore-case to false And I run :search bar - And I run :yank selection - Then the clipboard should contain "bar" + Then "bar" should be found Scenario: Searching text with ignore-case = smart (lower-case) When I set general -> ignore-case to smart And I run :search bar - And I run :yank selection - Then the clipboard should contain "Bar" + Then "Bar" should be found Scenario: Searching text with ignore-case = smart (upper-case) When I set general -> ignore-case to smart And I run :search Foo - And I run :yank selection - Then the clipboard should contain "Foo" # even though foo was first + Then "Foo" should be found # even though foo was first ## :search-next @@ -81,22 +72,19 @@ Feature: Searching on a page When I set general -> ignore-case to true And I run :search foo And I run :search-next - And I run :yank selection - Then the clipboard should contain "Foo" + Then "Foo" should be found Scenario: Jumping to next match with count When I set general -> ignore-case to true And I run :search baz And I run :search-next with count 2 - And I run :yank selection - Then the clipboard should contain "BAZ" + Then "BAZ" should be found Scenario: Jumping to next match with --reverse When I set general -> ignore-case to true And I run :search --reverse foo And I run :search-next - And I run :yank selection - Then the clipboard should contain "foo" + Then "foo" should be found Scenario: Jumping to next match without search # Make sure there was no search in the same window before @@ -109,8 +97,7 @@ Feature: Searching on a page And I run :search foo And I run :tab-prev And I run :search-next - And I run :yank selection - Then the clipboard should contain "foo" + Then "foo" should be found # https://github.com/qutebrowser/qutebrowser/issues/2438 Scenario: Jumping to next match after clearing @@ -118,8 +105,7 @@ Feature: Searching on a page And I run :search foo And I run :search And I run :search-next - And I run :yank selection - Then the clipboard should contain "foo" + Then "foo" should be found ## :search-prev @@ -128,8 +114,7 @@ Feature: Searching on a page And I run :search foo And I run :search-next And I run :search-prev - And I run :yank selection - Then the clipboard should contain "foo" + Then "foo" should be found Scenario: Jumping to previous match with count When I set general -> ignore-case to true @@ -137,16 +122,14 @@ Feature: Searching on a page And I run :search-next And I run :search-next And I run :search-prev with count 2 - And I run :yank selection - Then the clipboard should contain "baz" + Then "baz" should be found Scenario: Jumping to previous match with --reverse When I set general -> ignore-case to true And I run :search --reverse foo And I run :search-next And I run :search-prev - And I run :yank selection - Then the clipboard should contain "Foo" + Then "Foo" should be found Scenario: Jumping to previous match without search # Make sure there was no search in the same window before @@ -160,15 +143,13 @@ Feature: Searching on a page When I run :search foo And I run :search-next And I run :search-next - And I run :yank selection - Then the clipboard should contain "foo" + Then "foo" should be found Scenario: Wrapping around page with --reverse When I run :search --reverse foo And I run :search-next And I run :search-next - And I run :yank selection - Then the clipboard should contain "Foo" + Then "Foo" should be found # TODO: wrapping message with scrolling # TODO: wrapping message without scrolling diff --git a/tests/end2end/features/test_search_bdd.py b/tests/end2end/features/test_search_bdd.py index 12e5d9480..d9866fe03 100644 --- a/tests/end2end/features/test_search_bdd.py +++ b/tests/end2end/features/test_search_bdd.py @@ -17,6 +17,8 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . +import json + import pytest import pytest_bdd as bdd @@ -24,6 +26,16 @@ import pytest_bdd as bdd from end2end.features.test_yankpaste_bdd import init_fake_clipboard -bdd.scenarios('search.feature') +@bdd.then(bdd.parsers.parse('"{text}" should be found')) +def check_found_text(request, quteproc, text): + quteproc.send_cmd(':yank selection') + quteproc.wait_for(message='Setting fake clipboard: {}'.format( + json.dumps(text))) + +# After cancelling the search, the text (sometimes?) shows up as selected. +# However, we can't get it via ':yank selection' it seems? pytestmark = pytest.mark.qtwebengine_skip("Searched text is not selected...") + + +bdd.scenarios('search.feature') From 6549fd84ce461d3098c13818219df4e4bfd6b444 Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Wed, 26 Apr 2017 02:49:57 +0200 Subject: [PATCH 230/299] Add tabs->favicon-scale setting This allows users to change the size of the favicon independently from the size of the font/tab, in order to adjust the balance between favicons and text. The drawing code is also adjusted to place the icon relative to the text center, rather than the text top. Works as expected even for values of 0.0 (which is equivalent to hiding the favicon completely). Closes #2549. --- doc/help/settings.asciidoc | 7 +++++++ qutebrowser/config/configdata.py | 5 +++++ qutebrowser/mainwindow/tabwidget.py | 14 +++++++++++--- tests/unit/mainwindow/test_tabwidget.py | 1 + 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index c6a80754c..68ca0a118 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -126,6 +126,7 @@ |<>|On which mouse button to close tabs. |<>|The position of the tab bar. |<>|Whether to show favicons in the tab bar. +|<>|Scale for favicons in the tab bar. The tab size is unchanged, so big favicons also require extra `tabs->padding`. |<>|The width of the tab bar if it's vertical, in px or as percentage of the window. |<>|Width of the progress indicator (0 to disable). |<>|Whether to open windows instead of tabs. @@ -1206,6 +1207,12 @@ Valid values: Default: +pass:[true]+ +[[tabs-favicon-scale]] +=== favicon-scale +Scale for favicons in the tab bar. The tab size is unchanged, so big favicons also require extra `tabs->padding`. + +Default: +pass:[1.0]+ + [[tabs-width]] === width The width of the tab bar if it's vertical, in px or as percentage of the window. diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 2bfcabce7..2de0c3eb2 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -680,6 +680,11 @@ def data(readonly=False): SettingValue(typ.Bool(), 'true'), "Whether to show favicons in the tab bar."), + ('favicon-scale', + SettingValue(typ.Float(minval=0.0), '1.0'), + "Scale for favicons in the tab bar. The tab size is unchanged, " + "so big favicons also require extra `tabs->padding`."), + ('width', SettingValue(typ.PercOrInt(minperc=0, maxperc=100, minint=1), '20%'), diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index ce96607fa..f5a0b3c55 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -22,7 +22,8 @@ import collections import functools -from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QSize, QRect, QTimer, QUrl +from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QSize, QRect, QPoint, + QTimer, QUrl) from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle, QStyle, QStylePainter, QStyleOptionTab) from PyQt5.QtGui import QIcon, QPalette, QColor @@ -273,6 +274,7 @@ class TabBar(QTabBar): self.set_font() config_obj = objreg.get('config') config_obj.changed.connect(self.set_font) + config_obj.changed.connect(self.set_icon_size) self.vertical = False self._page_fullscreen = False self._auto_hide_timer = QTimer() @@ -374,7 +376,13 @@ class TabBar(QTabBar): def set_font(self): """Set the tab bar font.""" self.setFont(config.get('fonts', 'tabbar')) + self.set_icon_size() + + @config.change_filter('tabs', 'favicon-scale') + def set_icon_size(self): + """Set the tab bar favicon size.""" size = self.fontMetrics().height() - 2 + size *= config.get('tabs', 'favicon-scale') self.setIconSize(QSize(size, size)) @config.change_filter('colors', 'tabs.bg.bar') @@ -775,7 +783,7 @@ class TabBarStyle(QCommonStyle): tab_icon_size = QSize( min(actual_size.width(), icon_size.width()), min(actual_size.height(), icon_size.height())) - icon_rect = QRect(text_rect.left(), text_rect.top() + 1, - tab_icon_size.width(), tab_icon_size.height()) + icon_top = text_rect.center().y() + 1 - tab_icon_size.height() / 2 + icon_rect = QRect(QPoint(text_rect.left(), icon_top), tab_icon_size) icon_rect = self._style.visualRect(opt.direction, opt.rect, icon_rect) return icon_rect diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py index 2d3e9bda2..7a3fd68af 100644 --- a/tests/unit/mainwindow/test_tabwidget.py +++ b/tests/unit/mainwindow/test_tabwidget.py @@ -44,6 +44,7 @@ class TestTabWidget: 'select-on-remove': 1, 'show': 'always', 'show-favicons': True, + 'favicon-scale': 1.0, 'padding': configtypes.PaddingValues(0, 0, 5, 5), 'indicator-width': 3, 'indicator-padding': configtypes.PaddingValues(2, 2, 0, 4), From c12347189fca26261af7dcdce121e51c170b521d Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 27 Apr 2017 09:42:00 +0200 Subject: [PATCH 231/299] Update colorama from 0.3.8 to 0.3.9 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 57ed78734..b2cc93c1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -colorama==0.3.8 +colorama==0.3.9 cssutils==1.0.2 Jinja2==2.9.6 MarkupSafe==1.0 From 0e14117fdad0442520c4e1fe64231b447306cbc7 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 27 Apr 2017 18:27:03 +0200 Subject: [PATCH 232/299] Update codecov from 2.0.7 to 2.0.8 --- misc/requirements/requirements-codecov.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 2dcb22731..8337d667d 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -codecov==2.0.7 +codecov==2.0.8 coverage==4.3.4 requests==2.13.0 From 4cd977cab67ee95b0f1b399e20c4fe863e92039b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 27 Apr 2017 20:14:51 +0200 Subject: [PATCH 233/299] Fix zero handling in qflags_key --- qutebrowser/utils/debug.py | 4 ++++ tests/unit/utils/test_debug.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index 89ae62faf..a516c43f3 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -156,6 +156,10 @@ def qflags_key(base, value, add_base=False, klass=None): klass = value.__class__ if klass == int: raise TypeError("Can't guess enum class of an int!") + + if not value: + return qenum_key(base, value, add_base, klass) + bits = [] names = [] mask = 0x01 diff --git a/tests/unit/utils/test_debug.py b/tests/unit/utils/test_debug.py index 04a2fa436..7bcad59ec 100644 --- a/tests/unit/utils/test_debug.py +++ b/tests/unit/utils/test_debug.py @@ -138,6 +138,7 @@ class TestQEnumKey: (QFrame, QFrame.Sunken, None, 'Sunken'), (QFrame, 0x0030, QFrame.Shadow, 'Sunken'), (QFrame, 0x1337, QFrame.Shadow, '0x1337'), + (Qt, Qt.AnchorLeft, None, 'AnchorLeft'), ]) def test_qenum_key(self, base, value, klass, expected): key = debug.qenum_key(base, value, klass=klass) @@ -168,6 +169,8 @@ class TestQFlagsKey: (Qt, Qt.AlignCenter, None, 'AlignHCenter|AlignVCenter'), fixme((Qt, 0x0021, Qt.Alignment, 'AlignLeft|AlignTop')), (Qt, 0x1100, Qt.Alignment, '0x0100|0x1000'), + (Qt, Qt.DockWidgetAreas(0), Qt.DockWidgetArea, 'NoDockWidgetArea'), + (Qt, Qt.DockWidgetAreas(0), None, '0x0000'), ]) def test_qflags_key(self, base, value, klass, expected): flags = debug.qflags_key(base, value, klass=klass) From d62ebdb926f9dc1fe9d23a29992f7a323ab2b328 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 27 Apr 2017 21:01:10 +0200 Subject: [PATCH 234/299] Make most of search BDD tests work with QtWebEngine --- qutebrowser/browser/webengine/webenginetab.py | 29 +++++++++----- qutebrowser/browser/webkit/webkittab.py | 25 +++++++++--- tests/end2end/features/search.feature | 38 +++++++++++++++++++ tests/end2end/features/test_search_bdd.py | 13 ++++--- 4 files changed, 84 insertions(+), 21 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 821b73a1a..cedd59b1e 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -40,7 +40,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, webenginedownloads) from qutebrowser.misc import miscwidgets from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils, - objreg, jinja) + objreg, jinja, debug) _qute_scheme_handler = None @@ -128,12 +128,21 @@ class WebEngineSearch(browsertab.AbstractSearch): super().__init__(parent) self._flags = QWebEnginePage.FindFlags(0) - def _find(self, text, flags, cb=None): - """Call findText on the widget with optional callback.""" - if cb is None: - self._widget.findText(text, flags) - else: - self._widget.findText(text, flags, cb) + def _find(self, text, flags, callback, caller): + """Call findText on the widget.""" + def wrapped_callback(found): + """Wrap the callback to do debug logging.""" + found_text = 'found' if found else "didn't find" + if flags: + flag_text = 'with flags {}'.format(debug.qflags_key( + QWebEnginePage, flags, klass=QWebEnginePage.FindFlag)) + else: + flag_text = '' + log.webview.debug(' '.join([caller, found_text, text, flag_text]) + .strip()) + if callback is not None: + callback(found) + self._widget.findText(text, flags, wrapped_callback) def search(self, text, *, ignore_case=False, reverse=False, result_cb=None): @@ -148,7 +157,7 @@ class WebEngineSearch(browsertab.AbstractSearch): self.text = text self._flags = flags - self._find(text, flags, result_cb) + self._find(text, flags, result_cb, 'search') def clear(self): self._widget.findText('') @@ -160,10 +169,10 @@ class WebEngineSearch(browsertab.AbstractSearch): flags &= ~QWebEnginePage.FindBackward else: flags |= QWebEnginePage.FindBackward - self._find(self.text, flags, result_cb) + self._find(self.text, flags, result_cb, 'prev_result') def next_result(self, *, result_cb=None): - self._find(self.text, self._flags, result_cb) + self._find(self.text, self._flags, result_cb, 'next_result') class WebEngineCaret(browsertab.AbstractCaret): diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index ef427c3be..0a844ddb0 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -36,7 +36,7 @@ from qutebrowser.browser import browsertab from qutebrowser.browser.network import proxy from qutebrowser.browser.webkit import webview, tabhistory, webkitelem from qutebrowser.browser.webkit.network import webkitqutescheme -from qutebrowser.utils import qtutils, objreg, usertypes, utils, log +from qutebrowser.utils import qtutils, objreg, usertypes, utils, log, debug def init(): @@ -104,7 +104,7 @@ class WebKitSearch(browsertab.AbstractSearch): super().__init__(parent) self._flags = QWebPage.FindFlags(0) - def _call_cb(self, callback, found): + def _call_cb(self, callback, found, text, flags, caller): """Call the given callback if it's non-None. Delays the call via a QTimer so the website is re-rendered in between. @@ -112,7 +112,22 @@ class WebKitSearch(browsertab.AbstractSearch): Args: callback: What to call found: If the text was found + text: The text searched for + flags: The flags searched with + caller: Name of the caller. """ + found_text = 'found' if found else "didn't find" + # Removing FindWrapsAroundDocument to get the same logging as with + # QtWebEngine + debug_flags = debug.qflags_key( + QWebPage, flags & ~QWebPage.FindWrapsAroundDocument, + klass=QWebPage.FindFlag) + if debug_flags != '0x0000': + flag_text = 'with flags {}'.format(debug_flags) + else: + flag_text = '' + log.webview.debug(' '.join([caller, found_text, text, flag_text]) + .strip()) if callback is not None: QTimer.singleShot(0, functools.partial(callback, found)) @@ -137,11 +152,11 @@ class WebKitSearch(browsertab.AbstractSearch): self._widget.findText(text, flags | QWebPage.HighlightAllOccurrences) self.text = text self._flags = flags - self._call_cb(result_cb, found) + self._call_cb(result_cb, found, text, flags, 'search') def next_result(self, *, result_cb=None): found = self._widget.findText(self.text, self._flags) - self._call_cb(result_cb, found) + self._call_cb(result_cb, found, self.text, self._flags, 'next_result') def prev_result(self, *, result_cb=None): # The int() here makes sure we get a copy of the flags. @@ -151,7 +166,7 @@ class WebKitSearch(browsertab.AbstractSearch): else: flags |= QWebPage.FindBackward found = self._widget.findText(self.text, flags) - self._call_cb(result_cb, found) + self._call_cb(result_cb, found, self.text, flags, 'prev_result') class WebKitCaret(browsertab.AbstractCaret): diff --git a/tests/end2end/features/search.feature b/tests/end2end/features/search.feature index aeea2e4e8..285b3cbf8 100644 --- a/tests/end2end/features/search.feature +++ b/tests/end2end/features/search.feature @@ -11,37 +11,45 @@ Feature: Searching on a page Scenario: Searching text When I run :search foo + And I wait for "search found foo" in the log Then "foo" should be found Scenario: Searching twice When I run :search foo + And I wait for "search found foo" in the log And I run :search bar + And I wait for "search found bar" in the log Then "Bar" should be found Scenario: Searching with --reverse When I set general -> ignore-case to true And I run :search -r foo + And I wait for "search found foo with flags FindBackward" in the log Then "Foo" should be found Scenario: Searching without matches When I run :search doesnotmatch + And I wait for "search didn't find doesnotmatch" in the log Then the warning "Text 'doesnotmatch' not found on page!" should be shown @xfail_norun Scenario: Searching with / and spaces at the end (issue 874) When I run :set-cmd-text -s /space And I run :command-accept + And I wait for "search found space " in the log Then "space " should be found Scenario: Searching with / and slash in search term (issue 507) When I run :set-cmd-text -s //slash And I run :command-accept + And I wait for "search found /slash" in the log Then "/slash" should be found # This doesn't work because this is QtWebKit behavior. @xfail_norun Scenario: Searching text with umlauts When I run :search blub + And I wait for "search didn't find blub" in the log Then the warning "Text 'blub' not found on page!" should be shown ## ignore-case @@ -49,21 +57,25 @@ Feature: Searching on a page Scenario: Searching text with ignore-case = true When I set general -> ignore-case to true And I run :search bar + And I wait for "search found bar" in the log Then "Bar" should be found Scenario: Searching text with ignore-case = false When I set general -> ignore-case to false And I run :search bar + And I wait for "search found bar with flags FindCaseSensitively" in the log Then "bar" should be found Scenario: Searching text with ignore-case = smart (lower-case) When I set general -> ignore-case to smart And I run :search bar + And I wait for "search found bar" in the log Then "Bar" should be found Scenario: Searching text with ignore-case = smart (upper-case) When I set general -> ignore-case to smart And I run :search Foo + And I wait for "search found Foo with flags FindCaseSensitively" in the log Then "Foo" should be found # even though foo was first ## :search-next @@ -71,19 +83,25 @@ Feature: Searching on a page Scenario: Jumping to next match When I set general -> ignore-case to true And I run :search foo + And I wait for "search found foo" in the log And I run :search-next + And I wait for "next_result found foo" in the log Then "Foo" should be found Scenario: Jumping to next match with count When I set general -> ignore-case to true And I run :search baz + And I wait for "search found baz" in the log And I run :search-next with count 2 + And I wait for "next_result found baz" in the log Then "BAZ" should be found Scenario: Jumping to next match with --reverse When I set general -> ignore-case to true And I run :search --reverse foo + And I wait for "search found foo with flags FindBackward" in the log And I run :search-next + And I wait for "next_result found foo with flags FindBackward" in the log Then "foo" should be found Scenario: Jumping to next match without search @@ -95,16 +113,20 @@ Feature: Searching on a page Scenario: Repeating search in a second tab (issue #940) When I open data/search.html in a new tab And I run :search foo + And I wait for "search found foo" in the log And I run :tab-prev And I run :search-next + And I wait for "search found foo" in the log Then "foo" should be found # https://github.com/qutebrowser/qutebrowser/issues/2438 Scenario: Jumping to next match after clearing When I set general -> ignore-case to true And I run :search foo + And I wait for "search found foo" in the log And I run :search And I run :search-next + And I wait for "next_result found foo" in the log Then "foo" should be found ## :search-prev @@ -112,23 +134,33 @@ Feature: Searching on a page Scenario: Jumping to previous match When I set general -> ignore-case to true And I run :search foo + And I wait for "search found foo" in the log And I run :search-next + And I wait for "next_result found foo" in the log And I run :search-prev + And I wait for "prev_result found foo with flags FindBackward" in the log Then "foo" should be found Scenario: Jumping to previous match with count When I set general -> ignore-case to true And I run :search baz + And I wait for "search found baz" in the log And I run :search-next + And I wait for "next_result found baz" in the log And I run :search-next + And I wait for "next_result found baz" in the log And I run :search-prev with count 2 + And I wait for "prev_result found baz with flags FindBackward" in the log Then "baz" should be found Scenario: Jumping to previous match with --reverse When I set general -> ignore-case to true And I run :search --reverse foo + And I wait for "search found foo with flags FindBackward" in the log And I run :search-next + And I wait for "next_result found foo with flags FindBackward" in the log And I run :search-prev + And I wait for "prev_result found foo" in the log Then "Foo" should be found Scenario: Jumping to previous match without search @@ -141,14 +173,20 @@ Feature: Searching on a page Scenario: Wrapping around page When I run :search foo + And I wait for "search found foo" in the log And I run :search-next + And I wait for "next_result found foo" in the log And I run :search-next + And I wait for "next_result found foo" in the log Then "foo" should be found Scenario: Wrapping around page with --reverse When I run :search --reverse foo + And I wait for "search found foo with flags FindBackward" in the log And I run :search-next + And I wait for "next_result found foo with flags FindBackward" in the log And I run :search-next + And I wait for "next_result found foo with flags FindBackward" in the log Then "Foo" should be found # TODO: wrapping message with scrolling diff --git a/tests/end2end/features/test_search_bdd.py b/tests/end2end/features/test_search_bdd.py index d9866fe03..caf592ce6 100644 --- a/tests/end2end/features/test_search_bdd.py +++ b/tests/end2end/features/test_search_bdd.py @@ -19,7 +19,6 @@ import json -import pytest import pytest_bdd as bdd # pylint: disable=unused-import @@ -28,14 +27,16 @@ from end2end.features.test_yankpaste_bdd import init_fake_clipboard @bdd.then(bdd.parsers.parse('"{text}" should be found')) def check_found_text(request, quteproc, text): + if request.config.webengine: + # WORKAROUND + # This probably should work with Qt 5.9: + # https://codereview.qt-project.org/#/c/192920/ + # https://codereview.qt-project.org/#/c/192921/ + # https://bugreports.qt.io/browse/QTBUG-53134 + return quteproc.send_cmd(':yank selection') quteproc.wait_for(message='Setting fake clipboard: {}'.format( json.dumps(text))) -# After cancelling the search, the text (sometimes?) shows up as selected. -# However, we can't get it via ':yank selection' it seems? -pytestmark = pytest.mark.qtwebengine_skip("Searched text is not selected...") - - bdd.scenarios('search.feature') From f9055dc1e43390cba2c5d915335ecd80ba5cbd84 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 27 Apr 2017 21:14:28 +0200 Subject: [PATCH 235/299] Update changelog --- CHANGELOG.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 0fa268d02..5ec1d26be 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -28,6 +28,7 @@ Added filters. - New `--debug-flag` argument which replaces `--debug-exit` and `--pdb-postmortem`. +- New `tabs -> favicon-scale` option to scale up/down favicons. Changed ~~~~~~~ From 26b4d13c6faf6217bb116e15c01eec37c10dd976 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 27 Apr 2017 22:07:03 +0200 Subject: [PATCH 236/299] Update setuptools from 35.0.1 to 35.0.2 --- misc/requirements/requirements-pip.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt index b015ca518..1eb264be8 100644 --- a/misc/requirements/requirements-pip.txt +++ b/misc/requirements/requirements-pip.txt @@ -3,6 +3,6 @@ appdirs==1.4.3 packaging==16.8 pyparsing==2.2.0 -setuptools==35.0.1 +setuptools==35.0.2 six==1.10.0 wheel==0.29.0 From 4f92fe689578279b148876ad67f25bc2cc78849e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 10:33:57 +0200 Subject: [PATCH 237/299] Add an assert for tag_name we get from JS This should help tracking down #2569 once we get another report about it. --- qutebrowser/browser/webengine/webengineelem.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 1147b8475..8d364c620 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -88,7 +88,9 @@ class WebEngineElement(webelem.AbstractWebElement): The returned name will always be lower-case. """ - return self._js_dict['tag_name'].lower() + tag = self._js_dict['tag_name'] + assert isinstance(tag, str), tag + return tag.lower() def outer_xml(self): """Get the full HTML representation of this element.""" From 06e317ac53efcecfb772f1c192970740a3f3c839 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 13:58:29 +0200 Subject: [PATCH 238/299] Do type checks on values we get from JS --- .../browser/webengine/webengineelem.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 8d364c620..9c15884f1 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -39,6 +39,37 @@ class WebEngineElement(webelem.AbstractWebElement): def __init__(self, js_dict, tab): super().__init__(tab) + # Do some sanity checks on the data we get from JS + js_dict_types = { + 'id': int, + 'text': str, + 'value': str, + 'tag_name': str, + 'outer_xml': str, + 'class_name': str, + 'rects': list, + 'attributes': dict, + } + assert set(js_dict.keys()).issubset(js_dict_types.keys()) + for name, typ in js_dict_types.items(): + if name in js_dict and not isinstance(js_dict[name], typ): + raise TypeError("Got {} for {} from JS but expected {}: {}".format( + type(js_dict[name]), name, typ, js_dict)) + for name, value in js_dict['attributes'].items(): + if not isinstance(name, str): + raise TypeError("Got {} ({}) for attribute name from JS: " + "{}".format(name, type(name), js_dict)) + if not isinstance(value, str): + raise TypeError("Got {} ({}) for attribute {} from JS: " + "{}".format(value, type(value), name, js_dict)) + for rect in js_dict['rects']: + assert set(rect.keys()) == {'top', 'right', 'bottom', 'left', + 'height', 'width'}, rect.keys() + for value in rect.values(): + if not isinstance(value, (int, float)): + raise TypeError("Got {} ({}) for rect from JS: " + "{}".format(value, type(value), js_dict)) + self._id = js_dict['id'] self._js_dict = js_dict From 513f83d44600479b24090e2ab50ef6b0ec459354 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 14:28:28 +0200 Subject: [PATCH 239/299] Try harder to get tag name from element This could happen for any of the attributes, but for tagName this actually happens in the wild... Since elem.tagName is equal to elem.nodeName we just try to use this. Fixes #2569 --- qutebrowser/javascript/webelem.js | 10 +++++++++- tests/end2end/features/misc.feature | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 206cdf129..749dfcc72 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -52,12 +52,20 @@ window._qutebrowser.webelem = (function() { "id": id, "text": elem.text, "value": elem.value, - "tag_name": elem.tagName, "outer_xml": elem.outerHTML, "class_name": elem.className, "rects": [], // Gets filled up later }; + // https://github.com/qutebrowser/qutebrowser/issues/2569 + if (typeof elem.tagName === "string") { + out.tag_name = elem.tagName; + } else if (typeof elem.nodeName === "string") { + out.tag_name = elem.nodeName; + } else { + out.tag_name = ""; + } + var attributes = {}; for (var i = 0; i < elem.attributes.length; ++i) { var attr = elem.attributes[i]; diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 83de1c822..171fdf476 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -726,3 +726,12 @@ Feature: Various utility commands. Then no crash should happen And the following tabs should be open: - data/numbers/3.txt (active) + + ## Bugs + + # https://github.com/qutebrowser/qutebrowser/issues/2569 + Scenario: Clicking on form element with tagName child + When I open data/issue2569.html + And I run :click-element id theform + And I wait for "Clicked editable element!" in the log + Then no crash should happen From 0c653c4703848085f79b64b2b4c8b7360c78ab24 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 14:48:08 +0200 Subject: [PATCH 240/299] Handle elem.className in webelem.js --- qutebrowser/javascript/webelem.js | 8 +++++++- tests/end2end/features/misc.feature | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 749dfcc72..3b0e324dd 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -53,7 +53,6 @@ window._qutebrowser.webelem = (function() { "text": elem.text, "value": elem.value, "outer_xml": elem.outerHTML, - "class_name": elem.className, "rects": [], // Gets filled up later }; @@ -66,6 +65,13 @@ window._qutebrowser.webelem = (function() { out.tag_name = ""; } + if (typeof elem.className === "string") { + out.class_name = elem.className; + } else { + // e.g. SVG elements + out.class_name = ""; + } + var attributes = {}; for (var i = 0; i < elem.attributes.length; ++i) { var attr = elem.attributes[i]; diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 171fdf476..b65939ec1 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -735,3 +735,9 @@ Feature: Various utility commands. And I run :click-element id theform And I wait for "Clicked editable element!" in the log Then no crash should happen + + Scenario: Clicking on svg element + When I open data/issue2569.html + And I run :click-element id icon + And I wait for "Clicked non-editable element!" in the log + Then no crash should happen From 571f0c448695dd35576a44e4056dd0c844c43ee6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 14:57:14 +0200 Subject: [PATCH 241/299] Loosen JS value type check --- qutebrowser/browser/webengine/webengineelem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 9c15884f1..b033fc90c 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -43,7 +43,7 @@ class WebEngineElement(webelem.AbstractWebElement): js_dict_types = { 'id': int, 'text': str, - 'value': str, + 'value': (str, int, float), 'tag_name': str, 'outer_xml': str, 'class_name': str, From 6458c692cbb97cc28f8d88e8098b54074b47e009 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 15:15:32 +0200 Subject: [PATCH 242/299] Improve JS value type checks --- qutebrowser/browser/commands.py | 1 + qutebrowser/browser/webkit/webkitelem.py | 4 +++- tests/end2end/features/misc.feature | 6 ++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 8d49ddef8..d2fd1bca0 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1505,6 +1505,7 @@ class CommandDispatcher: if text is None: message.error("Could not get text from the focused element.") return + assert isinstance(text, str), text ed = editor.ExternalEditor(self._tabbed_browser) ed.editing_finished.connect(functools.partial( diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 34209c290..ba15e4a66 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -112,7 +112,9 @@ class WebKitElement(webelem.AbstractWebElement): def value(self): self._check_vanished() - return self._elem.evaluateJavaScript('this.value') + val = self._elem.evaluateJavaScript('this.value') + assert isinstance(val, (int, float, str)), val + return val def set_value(self, value): self._check_vanished() diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index b65939ec1..2260298d7 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -741,3 +741,9 @@ Feature: Various utility commands. And I run :click-element id icon And I wait for "Clicked non-editable element!" in the log Then no crash should happen + + Scenario: Clicking on li element + When I open data/issue2569.html + And I run :click-element id listitem + And I wait for "Clicked non-editable element!" in the log + Then no crash should happen From a2c8e093f4c7765d2619f82fe0662e15be633645 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 15:16:36 +0200 Subject: [PATCH 243/299] Move webelement checks to javascript.feature --- tests/end2end/features/javascript.feature | 21 +++++++++++++++++++++ tests/end2end/features/misc.feature | 21 --------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature index ab96866be..7c601f4e1 100644 --- a/tests/end2end/features/javascript.feature +++ b/tests/end2end/features/javascript.feature @@ -73,3 +73,24 @@ Feature: Javascript stuff When I set content -> allow-javascript to false And I run :jseval console.log('jseval executed') Then the javascript message "jseval executed" should be logged + + ## webelement issues (mostly with QtWebEngine) + + # https://github.com/qutebrowser/qutebrowser/issues/2569 + Scenario: Clicking on form element with tagName child + When I open data/issue2569.html + And I run :click-element id theform + And I wait for "Clicked editable element!" in the log + Then no crash should happen + + Scenario: Clicking on svg element + When I open data/issue2569.html + And I run :click-element id icon + And I wait for "Clicked non-editable element!" in the log + Then no crash should happen + + Scenario: Clicking on li element + When I open data/issue2569.html + And I run :click-element id listitem + And I wait for "Clicked non-editable element!" in the log + Then no crash should happen diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 2260298d7..83de1c822 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -726,24 +726,3 @@ Feature: Various utility commands. Then no crash should happen And the following tabs should be open: - data/numbers/3.txt (active) - - ## Bugs - - # https://github.com/qutebrowser/qutebrowser/issues/2569 - Scenario: Clicking on form element with tagName child - When I open data/issue2569.html - And I run :click-element id theform - And I wait for "Clicked editable element!" in the log - Then no crash should happen - - Scenario: Clicking on svg element - When I open data/issue2569.html - And I run :click-element id icon - And I wait for "Clicked non-editable element!" in the log - Then no crash should happen - - Scenario: Clicking on li element - When I open data/issue2569.html - And I run :click-element id listitem - And I wait for "Clicked non-editable element!" in the log - Then no crash should happen From 20da495376f9131d305b69c90a1b4d32efd136c3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 15:22:29 +0200 Subject: [PATCH 244/299] Add missing file --- tests/end2end/data/issue2569.html | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/end2end/data/issue2569.html diff --git a/tests/end2end/data/issue2569.html b/tests/end2end/data/issue2569.html new file mode 100644 index 000000000..4bff70e14 --- /dev/null +++ b/tests/end2end/data/issue2569.html @@ -0,0 +1,16 @@ + + + Form with tagName child + + +
+ + + + + + + +
  • List item
+ + From 5ed870e0c64149a57d9cbe254d5e2bdf235a3709 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 16:29:44 +0200 Subject: [PATCH 245/299] Fix lint --- qutebrowser/browser/webengine/webengineelem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index b033fc90c..11d663df2 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -53,8 +53,9 @@ class WebEngineElement(webelem.AbstractWebElement): assert set(js_dict.keys()).issubset(js_dict_types.keys()) for name, typ in js_dict_types.items(): if name in js_dict and not isinstance(js_dict[name], typ): - raise TypeError("Got {} for {} from JS but expected {}: {}".format( - type(js_dict[name]), name, typ, js_dict)) + raise TypeError("Got {} for {} from JS but expected {}: " + "{}".format(type(js_dict[name]), name, typ, + js_dict)) for name, value in js_dict['attributes'].items(): if not isinstance(name, str): raise TypeError("Got {} ({}) for attribute name from JS: " From 11383169d83f1ff14d2df40e6963221e46ab29d1 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 28 Apr 2017 17:27:11 +0200 Subject: [PATCH 246/299] Update codecov from 2.0.8 to 2.0.9 --- misc/requirements/requirements-codecov.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 8337d667d..8514ce608 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -codecov==2.0.8 +codecov==2.0.9 coverage==4.3.4 requests==2.13.0 From c8090b5388f6f18c5cfb6ad1f5392eceb11ea7b4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 17:29:19 +0200 Subject: [PATCH 247/299] Don't wait for click message in webelem tests Looks like we get this sometimes: ----> Waiting for 'Clicked non-editable element!' in the log 14:02:14.976 DEBUG webview webkittab:find_at_pos:618 Hit test result element is null! 14:02:14.976 DEBUG mouse mouse:_mousepress_insertmode_cb:149 Got None element, scheduling check on mouse release 14:02:14.977 DEBUG mouse webview:mousePressEvent:299 Normal click, setting normal target 14:02:14.978 DEBUG mouse mouse:mouserelease_insertmode_cb:173 Element vanished! --- tests/end2end/features/javascript.feature | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature index 7c601f4e1..c0c5344d2 100644 --- a/tests/end2end/features/javascript.feature +++ b/tests/end2end/features/javascript.feature @@ -80,17 +80,17 @@ Feature: Javascript stuff Scenario: Clicking on form element with tagName child When I open data/issue2569.html And I run :click-element id theform - And I wait for "Clicked editable element!" in the log + And I wait for "Sending fake click to *" in the log Then no crash should happen Scenario: Clicking on svg element When I open data/issue2569.html And I run :click-element id icon - And I wait for "Clicked non-editable element!" in the log + And I wait for "Sending fake click to *" in the log Then no crash should happen Scenario: Clicking on li element When I open data/issue2569.html And I run :click-element id listitem - And I wait for "Clicked non-editable element!" in the log + And I wait for "Sending fake click to *" in the log Then no crash should happen From 76ec465f6703bc7daa81ccb71a8331474dc99489 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 17:39:36 +0200 Subject: [PATCH 248/299] Allow to set cookies-store at runtime with Qt 5.9 Fixes #2588 --- CHANGELOG.asciidoc | 2 ++ doc/help/settings.asciidoc | 4 ++-- qutebrowser/browser/webengine/webenginesettings.py | 14 +++++++++----- qutebrowser/config/configdata.py | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 5ec1d26be..dd79a1b2b 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -50,6 +50,8 @@ Changed - When ui -> message-timeout is set to 0, messages are now never cleared. - Middle/right-clicking the blank parts of the tab bar (when vertical) now closes the current tab. +- With Qt 5.9, `content -> cookies-store` can now be set on QtWebEngine without + a restart. Fixed ~~~~~ diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 68ca0a118..b2479f959 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -175,7 +175,7 @@ |<>|Whether locally loaded documents are allowed to access remote urls. |<>|Whether locally loaded documents are allowed to access other local urls. |<>|Control which cookies to accept. -|<>|Whether to store cookies. Note this option needs a restart with QtWebEngine. +|<>|Whether to store cookies. Note this option needs a restart with QtWebEngine on Qt < 5.9. |<>|List of URLs of lists which contain hosts to block. |<>|Whether host blocking is enabled. |<>|List of domains that should always be loaded, despite being ad-blocked. @@ -1616,7 +1616,7 @@ This setting is only available with the QtWebKit backend. [[content-cookies-store]] === cookies-store -Whether to store cookies. Note this option needs a restart with QtWebEngine. +Whether to store cookies. Note this option needs a restart with QtWebEngine on Qt < 5.9. Valid values: diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index b029b4fd5..740db489b 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -34,7 +34,8 @@ from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile, from qutebrowser.browser import shared from qutebrowser.config import config, websettings -from qutebrowser.utils import objreg, utils, standarddir, javascript, log +from qutebrowser.utils import (objreg, utils, standarddir, javascript, log, + qtutils) class Attribute(websettings.Attribute): @@ -177,7 +178,8 @@ def init(args): _init_stylesheet(profile) # We need to do this here as a WORKAROUND for # https://bugreports.qt.io/browse/QTBUG-58650 - PersistentCookiePolicy().set(config.get('content', 'cookies-store')) + if not qtutils.version_check('5.9'): + PersistentCookiePolicy().set(config.get('content', 'cookies-store')) Attribute(QWebEngineSettings.FullScreenSupportEnabled).set(True) @@ -221,9 +223,6 @@ MAPPINGS = { Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls), 'local-content-can-access-file-urls': Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls), - # https://bugreports.qt.io/browse/QTBUG-58650 - # 'cookies-store': - # PersistentCookiePolicy(), 'webgl': Attribute(QWebEngineSettings.WebGLEnabled), }, @@ -301,3 +300,8 @@ try: except AttributeError: # Added in Qt 5.8 pass + + +if qtutils.version_check('5.9'): + # https://bugreports.qt.io/browse/QTBUG-58650 + MAPPINGS['content']['cookies-store'] = PersistentCookiePolicy() diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 2de0c3eb2..347edc53f 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -915,7 +915,7 @@ def data(readonly=False): ('cookies-store', SettingValue(typ.Bool(), 'true'), "Whether to store cookies. Note this option needs a restart with " - "QtWebEngine."), + "QtWebEngine on Qt < 5.9."), ('host-block-lists', SettingValue( From 421aa0d319c62c37db6433d2acb0e665250d4c75 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 19:11:02 +0200 Subject: [PATCH 249/299] Also try harder to get text content --- qutebrowser/javascript/webelem.js | 7 ++++++- tests/end2end/data/issue2569.html | 12 +++++++++++- tests/end2end/features/javascript.feature | 8 +++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 3b0e324dd..f9bd7d1af 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -50,7 +50,6 @@ window._qutebrowser.webelem = (function() { var out = { "id": id, - "text": elem.text, "value": elem.value, "outer_xml": elem.outerHTML, "rects": [], // Gets filled up later @@ -72,6 +71,12 @@ window._qutebrowser.webelem = (function() { out.class_name = ""; } + if (typeof elem.textContent === "string") { + out.text = elem.textContent; + } else if (typeof elem.text === "string") { + out.text = elem.text; + } // else: don't add the text at all + var attributes = {}; for (var i = 0; i < elem.attributes.length; ++i) { var attr = elem.attributes[i]; diff --git a/tests/end2end/data/issue2569.html b/tests/end2end/data/issue2569.html index 4bff70e14..8f613be2d 100644 --- a/tests/end2end/data/issue2569.html +++ b/tests/end2end/data/issue2569.html @@ -1,11 +1,21 @@ + Form with tagName child -
+ + + +
+ + + diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature index c0c5344d2..7ea712d15 100644 --- a/tests/end2end/features/javascript.feature +++ b/tests/end2end/features/javascript.feature @@ -79,7 +79,13 @@ Feature: Javascript stuff # https://github.com/qutebrowser/qutebrowser/issues/2569 Scenario: Clicking on form element with tagName child When I open data/issue2569.html - And I run :click-element id theform + And I run :click-element id tagnameform + And I wait for "Sending fake click to *" in the log + Then no crash should happen + + Scenario: Clicking on form element with text child + When I open data/issue2569.html + And I run :click-element id textform And I wait for "Sending fake click to *" in the log Then no crash should happen From a5b1c293a4ad6d20c0c6165868cdb06ad2bec4ea Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 20:29:20 +0200 Subject: [PATCH 250/299] Ignore comment position with eslint --- qutebrowser/javascript/.eslintrc.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutebrowser/javascript/.eslintrc.yaml b/qutebrowser/javascript/.eslintrc.yaml index ef7ed00b5..0fbeea904 100644 --- a/qutebrowser/javascript/.eslintrc.yaml +++ b/qutebrowser/javascript/.eslintrc.yaml @@ -38,3 +38,5 @@ rules: max-len: ["error", {"ignoreUrls": true}] capitalized-comments: "off" prefer-destructuring: "off" + line-comment-position: "off" + no-inline-comments: "off" From 8101fe99a8c483af4d868972c354d3cee63bd1a2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 20:51:38 +0200 Subject: [PATCH 251/299] Fix starting with Python 2 Fixes #2567 --- qutebrowser/qutebrowser.py | 4 ++-- tests/end2end/test_invocations.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index cd9ec5938..01054f14d 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -23,7 +23,6 @@ import sys import json import qutebrowser -from qutebrowser.utils import log try: from qutebrowser.misc.checkpyver import check_python_version except ImportError: @@ -38,6 +37,7 @@ except ImportError: sys.stderr.flush() sys.exit(100) check_python_version() +from qutebrowser.utils import log import argparse from qutebrowser.misc import earlyinit @@ -130,7 +130,7 @@ def directory(arg): raise argparse.ArgumentTypeError("Invalid empty value") -def logfilter_error(logfilter: str): +def logfilter_error(logfilter): """Validate logger names passed to --logfilter. Args: diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index ed0222014..38d0e6eb9 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -19,6 +19,7 @@ """Test starting qutebrowser with special arguments/environments.""" +import subprocess import socket import sys import logging @@ -254,3 +255,15 @@ def test_command_on_start(request, quteproc_new): quteproc_new.start(args) quteproc_new.send_cmd(':quit') quteproc_new.wait_for_quit() + + +def test_launching_with_python2(): + try: + proc = subprocess.Popen(['python2', '-m', 'qutebrowser', + '--no-err-windows'], stderr=subprocess.PIPE) + except FileNotFoundError: + pytest.skip("python2 not found") + _stdout, stderr = proc.communicate() + assert proc.returncode == 1 + error = "At least Python 3.4 is required to run qutebrowser" + assert stderr.decode('ascii').startswith(error) From bffdea671998f66ec0add9a6c88adc2ba3c152a9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 21:36:02 +0200 Subject: [PATCH 252/299] Read qWebKitVersion in qtutils.is_webkit_ng. This means we need to try and import qWebKitVersion in qtutils, but better there than at every place which calls it. --- qutebrowser/browser/qutescheme.py | 9 ++------- qutebrowser/browser/webkit/tabhistory.py | 3 +-- qutebrowser/browser/webkit/webkitsettings.py | 4 ++-- qutebrowser/utils/qtutils.py | 14 +++++++------- qutebrowser/utils/version.py | 3 +-- tests/end2end/conftest.py | 14 ++------------ tests/unit/utils/test_qtutils.py | 5 +++-- tests/unit/utils/test_version.py | 6 +++--- 8 files changed, 21 insertions(+), 37 deletions(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index bca93e3c8..accdab4ba 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -276,15 +276,10 @@ def qute_history(url): return 'text/html', json.dumps(history_data(start_time)) else: - try: - from PyQt5.QtWebKit import qWebKitVersion - is_webkit_ng = qtutils.is_qtwebkit_ng(qWebKitVersion()) - except ImportError: # pragma: no cover - is_webkit_ng = False - if ( config.get('content', 'allow-javascript') and - (objects.backend == usertypes.Backend.QtWebEngine or is_webkit_ng) + (objects.backend == usertypes.Backend.QtWebEngine or + qtutils.is_qtwebkit_ng()) ): return 'text/html', jinja.render( 'history.html', diff --git a/qutebrowser/browser/webkit/tabhistory.py b/qutebrowser/browser/webkit/tabhistory.py index d2efca257..3f80676a9 100644 --- a/qutebrowser/browser/webkit/tabhistory.py +++ b/qutebrowser/browser/webkit/tabhistory.py @@ -21,7 +21,6 @@ from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl -from PyQt5.QtWebKit import qWebKitVersion from qutebrowser.utils import qtutils @@ -181,7 +180,7 @@ def serialize(items): else: current_idx = 0 - if qtutils.is_qtwebkit_ng(qWebKitVersion()): + if qtutils.is_qtwebkit_ng(): _serialize_ng(items, current_idx, stream) else: _serialize_old(items, current_idx, stream) diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index caeef296f..747af9b5d 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -26,7 +26,7 @@ Module attributes: import os.path -from PyQt5.QtWebKit import QWebSettings, qWebKitVersion +from PyQt5.QtWebKit import QWebSettings from qutebrowser.config import config, websettings from qutebrowser.utils import standarddir, objreg, urlutils, qtutils, message @@ -90,7 +90,7 @@ def _set_user_stylesheet(): def _init_private_browsing(): if config.get('general', 'private-browsing'): - if qtutils.is_qtwebkit_ng(qWebKitVersion()): + if qtutils.is_qtwebkit_ng(): message.warning("Private browsing is not fully implemented by " "QtWebKit-NG!") QWebSettings.setIconDatabasePath('') diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index adf0b2737..72092cfee 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -36,6 +36,10 @@ import contextlib from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray, QIODevice, QSaveFile, QT_VERSION_STR) from PyQt5.QtWidgets import QApplication +try: + from PyQt5.QtWebKit import qWebKitVersion +except ImportError: + pass from qutebrowser.utils import log @@ -100,13 +104,9 @@ def version_check(version, exact=False, strict=False): return result -def is_qtwebkit_ng(version): - """Check if the given version is QtWebKit-NG. - - This is typically used as is_webkit_ng(qWebKitVersion) but we don't want to - have QtWebKit imports in here. - """ - return (pkg_resources.parse_version(version) > +def is_qtwebkit_ng(): + """Check if the given version is QtWebKit-NG.""" + return (pkg_resources.parse_version(qWebKitVersion()) > pkg_resources.parse_version('538.1')) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index e6a011f44..0565281bc 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -257,8 +257,7 @@ def _backend(): """Get the backend line with relevant information.""" if objects.backend == usertypes.Backend.QtWebKit: return 'QtWebKit{} (WebKit {})'.format( - '-NG' if qtutils.is_qtwebkit_ng(qWebKitVersion()) else '', - qWebKitVersion()) + '-NG' if qtutils.is_qtwebkit_ng() else '', qWebKitVersion()) else: webengine = usertypes.Backend.QtWebEngine assert objects.backend == webengine, objects.backend diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 7d1c0e90d..b374f53dd 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -136,16 +136,6 @@ if not getattr(sys, 'frozen', False): def pytest_collection_modifyitems(config, items): """Apply @qtwebengine_* markers; skip unittests with QUTE_BDD_WEBENGINE.""" - if config.webengine: - qtwebkit_ng_used = False - else: - try: - from PyQt5.QtWebKit import qWebKitVersion - except ImportError: - qtwebkit_ng_used = False - else: - qtwebkit_ng_used = qtutils.is_qtwebkit_ng(qWebKitVersion()) - markers = [ ('qtwebengine_todo', 'QtWebEngine TODO', pytest.mark.xfail, config.webengine), @@ -154,9 +144,9 @@ def pytest_collection_modifyitems(config, items): ('qtwebkit_skip', 'Skipped with QtWebKit', pytest.mark.skipif, not config.webengine), ('qtwebkit_ng_xfail', 'Failing with QtWebKit-NG', pytest.mark.xfail, - qtwebkit_ng_used), + not config.webengine and qtutils.is_qtwebkit_ng()), ('qtwebkit_ng_skip', 'Skipped with QtWebKit-NG', pytest.mark.skipif, - qtwebkit_ng_used), + not config.webengine and qtutils.is_qtwebkit_ng()), ('qtwebengine_flaky', 'Flaky with QtWebEngine', pytest.mark.skipif, config.webengine), ('qtwebengine_osx_xfail', 'Fails on OS X with QtWebEngine', diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py index 74da2ac4f..e0b4bf934 100644 --- a/tests/unit/utils/test_qtutils.py +++ b/tests/unit/utils/test_qtutils.py @@ -88,8 +88,9 @@ def test_version_check(monkeypatch, qversion, compiled, version, exact, ('538.1', False), # Qt 5.8 ('602.1', True) # QtWebKit-NG TP5 ]) -def test_is_qtwebkit_ng(version, ng): - assert qtutils.is_qtwebkit_ng(version) == ng +def test_is_qtwebkit_ng(monkeypatch, version, ng): + monkeypatch.setattr(qtutils, 'qWebKitVersion', lambda: version) + assert qtutils.is_qtwebkit_ng() == ng class TestCheckOverflow: diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index c6e629ef1..13c0a5d13 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -725,13 +725,13 @@ def test_version_output(git_commit, frozen, style, with_webkit, stubs, patches['objects.backend'] = usertypes.Backend.QtWebKit patches['QWebEngineProfile'] = None if with_webkit == 'ng': - patches['qtutils.is_qtwebkit_ng'] = lambda v: True + patches['qtutils.is_qtwebkit_ng'] = lambda: True substitutions['backend'] = 'QtWebKit-NG (WebKit WEBKIT VERSION)' else: - patches['qtutils.is_qtwebkit_ng'] = lambda v: False + patches['qtutils.is_qtwebkit_ng'] = lambda: False substitutions['backend'] = 'QtWebKit (WebKit WEBKIT VERSION)' else: - patches['qWebKitVersion'] = None + monkeypatch.delattr(version, 'qtutils.qWebKitVersion', raising=False) patches['objects.backend'] = usertypes.Backend.QtWebEngine patches['QWebEngineProfile'] = FakeWebEngineProfile substitutions['backend'] = 'QtWebEngine (Chromium CHROMIUMVERSION)' From 5bbd16c92a2b25574a21b7482c08b6f8d962bd64 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 Apr 2017 22:59:24 +0200 Subject: [PATCH 253/299] Fix qWebKitVersion issues --- qutebrowser/utils/qtutils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index 72092cfee..9bd606f02 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -38,8 +38,8 @@ from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray, from PyQt5.QtWidgets import QApplication try: from PyQt5.QtWebKit import qWebKitVersion -except ImportError: - pass +except ImportError: # pragma: no cover + qWebKitVersion = None from qutebrowser.utils import log @@ -106,6 +106,7 @@ def version_check(version, exact=False, strict=False): def is_qtwebkit_ng(): """Check if the given version is QtWebKit-NG.""" + assert qWebKitVersion is not None return (pkg_resources.parse_version(qWebKitVersion()) > pkg_resources.parse_version('538.1')) From 6e0d138d230ef0fb64e73ea8e900784e0a91f1b4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 29 Apr 2017 13:45:14 +0200 Subject: [PATCH 254/299] flake8 requirements: Pin flake8-docstrings to < 1.1.0 Closes #2593 --- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-flake8.txt-raw | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index d30cb2dd9..f0205d262 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -4,7 +4,7 @@ flake8==2.6.2 # rq.filter: < 3.0.0 flake8-copyright==0.2.0 flake8-debugger==1.4.0 # rq.filter: != 2.0.0 flake8-deprecated==1.1 -flake8-docstrings==1.0.3 +flake8-docstrings==1.0.3 # rq.filter: < 1.1.0 flake8-future-import==0.4.3 flake8-mock==0.3 flake8-pep3101==1.0 diff --git a/misc/requirements/requirements-flake8.txt-raw b/misc/requirements/requirements-flake8.txt-raw index e235df395..aadae5e8b 100644 --- a/misc/requirements/requirements-flake8.txt-raw +++ b/misc/requirements/requirements-flake8.txt-raw @@ -2,7 +2,7 @@ flake8<3.0.0 flake8-copyright flake8-debugger!=2.0.0 flake8-deprecated -flake8-docstrings +flake8-docstrings<1.1.0 flake8-future-import flake8-mock flake8-pep3101 @@ -22,6 +22,7 @@ mccabe==0.6.1 # Waiting until flake8-putty updated #@ filter: flake8 < 3.0.0 #@ filter: pydocstyle < 2.0.0 +#@ filter: flake8-docstrings < 1.1.0 # https://github.com/JBKahn/flake8-debugger/issues/5 #@ filter: flake8-debugger != 2.0.0 From 64a4e33caa539b751acfc7e0eb76c871e8ab423d Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 30 Apr 2017 12:29:12 +0200 Subject: [PATCH 255/299] Update flake8-tuple from 0.2.12 to 0.2.13 --- misc/requirements/requirements-flake8.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index f0205d262..8cd1eb296 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -12,7 +12,7 @@ flake8-polyfill==1.0.1 flake8-putty==0.4.0 flake8-string-format==0.2.3 flake8-tidy-imports==1.0.6 -flake8-tuple==0.2.12 +flake8-tuple==0.2.13 mccabe==0.6.1 pep8-naming==0.4.1 pycodestyle==2.3.1 From 6ae6df9d74b9d94f56489b5d6a4586995aeda340 Mon Sep 17 00:00:00 2001 From: Steve Peak Date: Sun, 30 Apr 2017 14:55:35 -0400 Subject: [PATCH 256/299] Update codecov.yml --- codecov.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/codecov.yml b/codecov.yml index 6d4f1abce..47e3c919c 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,9 +1,7 @@ -status: - project: - enabled: no - patch: - enabled: no - changes: - enabled: no +coverage: + status: + project: off + patch: off + changes: off comment: off From fe02267de2f9bc164cb265c357770d8f56c68554 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 1 May 2017 12:43:14 +0200 Subject: [PATCH 257/299] Update pytest-bdd from 2.18.1 to 2.18.2 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 511300e96..e73d9fdf6 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -18,7 +18,7 @@ parse==1.8.0 parse-type==0.3.4 py==1.4.33 pytest==3.0.7 -pytest-bdd==2.18.1 +pytest-bdd==2.18.2 pytest-benchmark==3.0.0 pytest-catchlog==1.2.2 pytest-cov==2.4.0 From 64e144f3eb0fc0a14b06293b99423389c22e1c0a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 1 May 2017 13:52:46 +0200 Subject: [PATCH 258/299] Make text selectable in prompts --- qutebrowser/mainwindow/prompt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 358bcc80b..db91cc71a 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -475,6 +475,7 @@ class _BasePrompt(QWidget): if question.text is not None: # Not doing any HTML escaping here as the text can be formatted text_label = QLabel(question.text) + text_label.setTextInteractionFlags(Qt.TextSelectableByMouse) self._vbox.addWidget(text_label) def _init_key_label(self): From 7b4ab901e9e2896c02d2b6bb0700d2c687d8c7f3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 2 May 2017 08:24:57 +0200 Subject: [PATCH 259/299] tests: Fix Chromium message matching --- tests/end2end/fixtures/quteprocess.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index cc835693c..13d9c1043 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -77,9 +77,9 @@ def is_ignored_lowlevel_message(message): return True elif 'Unable to locate theme engine in module_path:' in message: return True - elif message == 'getrlimit(RLIMIT_NOFILE) failed': + elif 'getrlimit(RLIMIT_NOFILE) failed' in message: return True - elif message == 'Could not bind NETLINK socket: Address already in use': + elif 'Could not bind NETLINK socket: Address already in use' in message: return True return False From d5c5d09b18abcbc8250dea7c46566a253fce7b49 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 2 May 2017 09:12:06 +0200 Subject: [PATCH 260/299] Hopefully stabilize test_version When using QuteProcess here, we fight with it over who can read the output. Just use a raw QProcess instead. --- tests/end2end/test_invocations.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index 38d0e6eb9..9c1e993cb 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -162,23 +162,24 @@ def test_optimize(request, quteproc_new, capfd, level): quteproc_new.wait_for_quit() +@pytest.mark.not_frozen def test_version(request): """Test invocation with --version argument.""" - args = ['--version'] + _base_args(request.config) + args = ['-m', 'qutebrowser', '--version'] + _base_args(request.config) # can't use quteproc_new here because it's confused by # early process termination - proc = quteprocess.QuteProc(request) - proc.proc.setProcessChannelMode(QProcess.SeparateChannels) + proc = QProcess() + proc.setProcessChannelMode(QProcess.SeparateChannels) - try: - proc.start(args) - proc.wait_for_quit() - except testprocess.ProcessExited: - assert proc.proc.exitStatus() == QProcess.NormalExit - else: - pytest.fail("Process did not exit!") + proc.start(sys.executable, args) + ok = proc.waitForStarted(2000) + assert ok + ok = proc.waitForFinished(2000) + assert ok + assert proc.exitStatus() == QProcess.NormalExit - output = bytes(proc.proc.readAllStandardOutput()).decode('utf-8') + output = bytes(proc.readAllStandardOutput()).decode('utf-8') + print(output) assert re.search(r'^qutebrowser\s+v\d+(\.\d+)', output) is not None From 7c6981e5129f5e514a1a6666485e59a9f7c182df Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 2 May 2017 10:41:51 +0200 Subject: [PATCH 261/299] Remove unused imports --- tests/end2end/test_invocations.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index 9c1e993cb..d683e9c85 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -29,7 +29,6 @@ import pytest from PyQt5.QtCore import QProcess -from end2end.fixtures import quteprocess, testprocess from qutebrowser.utils import qtutils From 3b0bb6a831791c22a77f25aa2f3223c24e6ba628 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 2 May 2017 11:43:35 +0200 Subject: [PATCH 262/299] Update cheroot from 5.4.0 to 5.5.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index e73d9fdf6..627912f34 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py beautifulsoup4==4.5.3 -cheroot==5.4.0 +cheroot==5.5.0 click==6.7 coverage==4.3.4 decorator==4.0.11 From a77cb447234e25c1680b231adc62616f0ad89f3a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 May 2017 08:41:49 +0200 Subject: [PATCH 263/299] Block all request methods in host blocker --- CHANGELOG.asciidoc | 1 + qutebrowser/browser/webengine/interceptor.py | 3 +-- qutebrowser/browser/webkit/network/networkmanager.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index dd79a1b2b..2d7937d22 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -52,6 +52,7 @@ Changed closes the current tab. - With Qt 5.9, `content -> cookies-store` can now be set on QtWebEngine without a restart. +- The adblocker now also blocks non-GET requests (e.g. POST) Fixed ~~~~~ diff --git a/qutebrowser/browser/webengine/interceptor.py b/qutebrowser/browser/webengine/interceptor.py index 350378147..76c29a6eb 100644 --- a/qutebrowser/browser/webengine/interceptor.py +++ b/qutebrowser/browser/webengine/interceptor.py @@ -57,8 +57,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor): info: QWebEngineUrlRequestInfo &info """ # FIXME:qtwebengine only block ads for NavigationTypeOther? - if (bytes(info.requestMethod()) == b'GET' and - self._host_blocker.is_blocked(info.requestUrl())): + if self._host_blocker.is_blocked(info.requestUrl()): log.webview.info("Request to {} blocked by host blocker.".format( info.requestUrl().host())) info.block(True) diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 779d778bc..915a2082f 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -416,8 +416,7 @@ class NetworkManager(QNetworkAccessManager): req.setRawHeader(header, value) host_blocker = objreg.get('host-blocker') - if (op == QNetworkAccessManager.GetOperation and - host_blocker.is_blocked(req.url())): + if host_blocker.is_blocked(req.url()): log.webview.info("Request to {} blocked by host blocker.".format( req.url().host())) return networkreply.ErrorNetworkReply( From 120379dd217d8daaa6b48cb21c8cb3de7679bbec Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Wed, 22 Feb 2017 20:43:48 -0500 Subject: [PATCH 264/299] Benchmark url completion. This benchmark simulates what I expect to be the most common use-case for url completion: opening completion and entering several letters. --- tests/unit/completion/test_models.py | 42 +++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index 84c87b270..a767476a4 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -26,7 +26,8 @@ import pytest from PyQt5.QtCore import QUrl from PyQt5.QtWidgets import QTreeView -from qutebrowser.completion.models import miscmodels, urlmodel, configmodel +from qutebrowser.completion.models import (miscmodels, urlmodel, configmodel, + sortfilter) from qutebrowser.browser import history from qutebrowser.config import sections, value @@ -498,3 +499,42 @@ def test_bind_completion(qtmodeltester, monkeypatch, stubs, config_stub, ('rock', "Alias for 'roll'", 'ro'), ] }) + + +def test_url_completion_benchmark(benchmark, config_stub, + quickmark_manager_stub, + bookmark_manager_stub, + web_history_stub): + """Benchmark url completion.""" + config_stub.data['completion'] = {'timestamp-format': '%Y-%m-%d', + 'web-history-max-items': 1000} + + entries = [history.Entry( + atime=i, + url=QUrl('http://example.com/{}'.format(i)), + title='title{}'.format(i)) + for i in range(100000)] + + web_history_stub.history_dict = collections.OrderedDict( + ((e.url_str(), e) for e in entries)) + + quickmark_manager_stub.marks = collections.OrderedDict( + (e.title, e.url_str()) + for e in entries[0:1000]) + + bookmark_manager_stub.marks = collections.OrderedDict( + (e.url_str(), e.title) + for e in entries[0:1000]) + + def bench(): + model = urlmodel.UrlCompletionModel() + filtermodel = sortfilter.CompletionFilterModel(model) + filtermodel.set_pattern('') + filtermodel.set_pattern('e') + filtermodel.set_pattern('ex') + filtermodel.set_pattern('ex ') + filtermodel.set_pattern('ex 1') + filtermodel.set_pattern('ex 12') + filtermodel.set_pattern('ex 123') + + benchmark(bench) From 2a4af0666bad617707a63c4b22678bf08362bb82 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 May 2017 18:36:32 +0200 Subject: [PATCH 265/299] Regenerate authors --- README.asciidoc | 3 ++- scripts/dev/src2asciidoc.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 7aa1afa66..b3457876b 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -183,7 +183,7 @@ Contributors, sorted by the number of commits in descending order: * Jimmy * Niklas Haas * Maciej Wołczyk -* Spreadyy +* sandrosc * Alexey "Averrin" Nabrodov * pkill9 * nanjekyejoannah @@ -273,6 +273,7 @@ Contributors, sorted by the number of commits in descending order: * Tobias Werth * Tim Harder * Thiago Barroso Perrotta +* Steve Peak * Sorokin Alexei * Simon Désaulniers * Rok Mandeljc diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py index a8f019d9d..1b99022a7 100755 --- a/scripts/dev/src2asciidoc.py +++ b/scripts/dev/src2asciidoc.py @@ -437,6 +437,7 @@ def _get_authors(): 'Claire C.C': 'Claire Cavanaugh', 'Rahid': 'Maciej Wołczyk', 'Fritz V155 Reichwald': 'Fritz Reichwald', + 'Spreadyy': 'sandrosc', } ignored = ['pyup-bot'] commits = subprocess.check_output(['git', 'log', '--format=%aN']) From a320aa5ef7cb02a11ae36304d407f8170fd85364 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 May 2017 21:24:25 +0200 Subject: [PATCH 266/299] Disable renderer process crash workaround on Qt 5.9 --- qutebrowser/mainwindow/tabbedbrowser.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index f56bfc5a6..885c4dc78 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -673,11 +673,11 @@ class TabbedBrowser(tabwidget.TabWidget): else: raise ValueError("Invalid status {}".format(status)) - # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58698 - # FIXME:qtwebengine can we disable this with Qt 5.8.1? - self._remove_tab(tab, crashed=True) - if self.count() == 0: - self.tabopen(QUrl('about:blank')) + if not qtutils.version_check('5.9'): + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58698 + self._remove_tab(tab, crashed=True) + if self.count() == 0: + self.tabopen(QUrl('about:blank')) def resizeEvent(self, e): """Extend resizeEvent of QWidget to emit a resized signal afterwards. From ebf3d208f60f6365d60aa44517365cb2e0b40d8f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 May 2017 21:25:00 +0200 Subject: [PATCH 267/299] Adjust Qt 5.8.1 check There's never going to be a 5.8.1 --- qutebrowser/browser/webengine/webenginedownloads.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index ca6e04c04..26b23d77f 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -147,7 +147,7 @@ def _get_suggested_filename(path): """ filename = os.path.basename(path) filename = re.sub(r'\([0-9]+\)(?=\.|$)', '', filename) - if not qtutils.version_check('5.8.1'): + if not qtutils.version_check('5.9'): # https://bugreports.qt.io/browse/QTBUG-58155 filename = urllib.parse.unquote(filename) # Doing basename a *second* time because there could be a %2F in From ea2d5e97e2b7dd9ec787c6501daa7c3142980e2d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 May 2017 21:31:09 +0200 Subject: [PATCH 268/299] Disable serialization crash check on Qt 5.9 --- qutebrowser/browser/webengine/webenginetab.py | 20 +++++++++---------- tests/end2end/features/tabs.feature | 8 +++++++- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index cedd59b1e..00c205b73 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -378,16 +378,16 @@ class WebEngineHistory(browsertab.AbstractHistory): return self._history.canGoForward() def serialize(self): - # WORKAROUND (remove this when we bump the requirements to 5.9) - # https://bugreports.qt.io/browse/QTBUG-59599 - if self._history.count() == 0: - raise browsertab.WebTabError("Can't serialize page without " - "history!") - # WORKAROUND (FIXME: remove this when we bump the requirements to 5.9?) - # https://github.com/qutebrowser/qutebrowser/issues/2289 - scheme = self._history.currentItem().url().scheme() - if scheme in ['view-source', 'chrome']: - raise browsertab.WebTabError("Can't serialize special URL!") + if not qtutils.version_check('5.9'): + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59599 + if self._history.count() == 0: + raise browsertab.WebTabError("Can't serialize page without " + "history!") + # WORKAROUND for + # https://github.com/qutebrowser/qutebrowser/issues/2289 + scheme = self._history.currentItem().url().scheme() + if scheme in ['view-source', 'chrome']: + raise browsertab.WebTabError("Can't serialize special URL!") return qtutils.serialize(self._history) def deserialize(self, data): diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 5e571d42d..748360bc3 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -607,12 +607,18 @@ Feature: Tab management title: Test title # https://github.com/qutebrowser/qutebrowser/issues/2289 - @qtwebkit_skip @qt>=5.8 + @qtwebkit_skip @qt==5.8.0 Scenario: Cloning a tab with a special URL When I open chrome://gpu And I run :tab-clone Then the error "Can't serialize special URL!" should be shown + @qtwebkit_skip @qt>=5.9 + Scenario: Cloning a tab with a special URL (Qt 5.9) + When I open chrome://gpu + And I run :tab-clone + Then no crash should happen + # :tab-detach Scenario: Detaching a tab From 90b0af97ce4d9f0952c201f47413a5b1bb93877d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 May 2017 21:31:09 +0200 Subject: [PATCH 269/299] Improve serialization crash check It now works correctly with view-source URLs and is disabled on Qt 5.9. Fixes #2289 See #2458 --- CHANGELOG.asciidoc | 1 + qutebrowser/browser/webengine/webenginetab.py | 9 ++++----- tests/end2end/features/conftest.py | 4 +++- tests/end2end/features/tabs.feature | 19 +++++++------------ tests/end2end/fixtures/quteprocess.py | 2 +- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 2d7937d22..7ff888acd 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -68,6 +68,7 @@ Fixed - Crash when unbinding an unbound key in the key config - Crash when using `:debug-log-filter` when `--filter` wasn't given on startup. - Crash with some invalid setting values +- Crash when cloning a view-source tab with QtWebEngine - Various rare crashes - Various styling issues with the tabbar and a crash with qt5ct diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 00c205b73..cc3dcf108 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -379,13 +379,12 @@ class WebEngineHistory(browsertab.AbstractHistory): def serialize(self): if not qtutils.version_check('5.9'): - # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59599 - if self._history.count() == 0: - raise browsertab.WebTabError("Can't serialize page without " - "history!") # WORKAROUND for # https://github.com/qutebrowser/qutebrowser/issues/2289 - scheme = self._history.currentItem().url().scheme() + # Don't use the history's currentItem here, because of + # https://bugreports.qt.io/browse/QTBUG-59599 and because it doesn't + # contain view-source. + scheme = self._tab.url().scheme() if scheme in ['view-source', 'chrome']: raise browsertab.WebTabError("Can't serialize special URL!") return qtutils.serialize(self._history) diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index c65ea33ad..1e9c34462 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -174,13 +174,15 @@ def pdfjs_available(): @bdd.when(bdd.parsers.parse("I open {path}")) -def open_path(quteproc, path): +def open_path(quteproc, httpbin, path): """Open a URL. If used like "When I open ... in a new tab", the URL is opened in a new tab. With "... in a new window", it's opened in a new window. With "... as a URL", it's opened according to new-instance-open-target. """ + path = path.replace('(port)', str(httpbin.port)) + new_tab = False new_bg_tab = False new_window = False diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 748360bc3..19007428c 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -607,12 +607,19 @@ Feature: Tab management title: Test title # https://github.com/qutebrowser/qutebrowser/issues/2289 + @qtwebkit_skip @qt==5.8.0 Scenario: Cloning a tab with a special URL When I open chrome://gpu And I run :tab-clone Then the error "Can't serialize special URL!" should be shown + @qtwebkit_skip @qt<5.9 + Scenario: Cloning a tab with a view-source URL + When I open view-source:http://localhost:(port) + And I run :tab-clone + Then the error "Can't serialize special URL!" should be shown + @qtwebkit_skip @qt>=5.9 Scenario: Cloning a tab with a special URL (Qt 5.9) When I open chrome://gpu @@ -774,18 +781,6 @@ Feature: Tab management - data/numbers/2.txt - data/numbers/3.txt - # https://github.com/qutebrowser/qutebrowser/issues/2289 - @qtwebkit_skip @qt>=5.8 - Scenario: Undoing a tab with a special URL - Given I have a fresh instance - When I open data/numbers/1.txt - And I open chrome://gpu in a new tab - And I run :tab-close - And I run :undo - Then the error "Nothing to undo!" should be shown - And the following tabs should be open: - - data/numbers/1.txt (active) - # last-close # FIXME:qtwebengine diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 13d9c1043..5f99990d8 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -314,7 +314,7 @@ class QuteProc(testprocess.Process): URLs like about:... and qute:... are handled specially and returned verbatim. """ - special_schemes = ['about:', 'qute:', 'chrome:'] + special_schemes = ['about:', 'qute:', 'chrome:', 'view-source:'] if any(path.startswith(scheme) for scheme in special_schemes): return path else: From caa3be2277f33c692c4b1093d87a49128efe2fcb Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 4 May 2017 08:36:25 +0200 Subject: [PATCH 270/299] Update lazy-object-proxy from 1.2.2 to 1.3.0 --- misc/requirements/requirements-pylint-master.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint-master.txt b/misc/requirements/requirements-pylint-master.txt index e438f86ed..9e9609c30 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -4,7 +4,7 @@ editdistance==0.3.1 github3.py==0.9.6 isort==4.2.5 -lazy-object-proxy==1.2.2 +lazy-object-proxy==1.3.0 mccabe==0.6.1 -e git+https://github.com/PyCQA/pylint.git#egg=pylint ./scripts/dev/pylint_checkers From 88545dec4da7b66986e47afb0cc6663c6ce1d33a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 4 May 2017 08:36:26 +0200 Subject: [PATCH 271/299] Update lazy-object-proxy from 1.2.2 to 1.3.0 --- misc/requirements/requirements-pylint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 807ca5643..585b3f1a6 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -3,7 +3,7 @@ astroid==1.5.2 github3.py==0.9.6 isort==4.2.5 -lazy-object-proxy==1.2.2 +lazy-object-proxy==1.3.0 mccabe==0.6.1 pylint==1.7.1 ./scripts/dev/pylint_checkers From 2beba920e725faa74871524f91d67bede6b4859b Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 5 May 2017 15:51:18 +0200 Subject: [PATCH 272/299] Update lazy-object-proxy from 1.3.0 to 1.3.1 --- misc/requirements/requirements-pylint-master.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint-master.txt b/misc/requirements/requirements-pylint-master.txt index 9e9609c30..8c08b4e7b 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -4,7 +4,7 @@ editdistance==0.3.1 github3.py==0.9.6 isort==4.2.5 -lazy-object-proxy==1.3.0 +lazy-object-proxy==1.3.1 mccabe==0.6.1 -e git+https://github.com/PyCQA/pylint.git#egg=pylint ./scripts/dev/pylint_checkers From 00c48427bcee63921705f85251d71795dab980bd Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 5 May 2017 15:51:20 +0200 Subject: [PATCH 273/299] Update lazy-object-proxy from 1.3.0 to 1.3.1 --- misc/requirements/requirements-pylint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 585b3f1a6..647661e2f 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -3,7 +3,7 @@ astroid==1.5.2 github3.py==0.9.6 isort==4.2.5 -lazy-object-proxy==1.3.0 +lazy-object-proxy==1.3.1 mccabe==0.6.1 pylint==1.7.1 ./scripts/dev/pylint_checkers From 7a3651426f0152c6b23ed4f83bab0eab2e763d91 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sun, 7 May 2017 16:42:20 +0200 Subject: [PATCH 274/299] Update beautifulsoup4 from 4.5.3 to 4.6.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 627912f34..a0580f49f 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -beautifulsoup4==4.5.3 +beautifulsoup4==4.6.0 cheroot==5.5.0 click==6.7 coverage==4.3.4 From ee2a6ae6f026985a43d09307bd775991c23882d9 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 8 May 2017 07:26:25 +0200 Subject: [PATCH 275/299] Update coverage from 4.3.4 to 4.4 --- misc/requirements/requirements-codecov.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 8514ce608..6c3864d96 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py codecov==2.0.9 -coverage==4.3.4 +coverage==4.4 requests==2.13.0 From 2b0fc0f52efccd0004b2cd8f90f930852947f403 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 8 May 2017 07:26:27 +0200 Subject: [PATCH 276/299] Update coverage from 4.3.4 to 4.4 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 627912f34..eedfd3f50 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -3,7 +3,7 @@ beautifulsoup4==4.5.3 cheroot==5.5.0 click==6.7 -coverage==4.3.4 +coverage==4.4 decorator==4.0.11 EasyProcess==0.2.3 Flask==0.12.1 From 8052249b1b683c552219b082da5e15271203ba26 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 May 2017 06:13:35 +0200 Subject: [PATCH 277/299] Make check_coverage.py work with coverage 4.4 With coverage 4.4, the source name (qutebrowser/) is not added to the filename anymore. To adjust for that, we remove qutebrowser/ from all paths, and also make sure to remove it from what coverage returns (in case someone is running an older version). --- scripts/dev/check_coverage.py | 118 +++++++++++----------- tests/unit/scripts/test_check_coverage.py | 5 +- 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index c876acd1a..9025d8740 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -41,132 +41,132 @@ MsgType = enum.Enum('MsgType', 'insufficent_coverage, perfect_file') # A list of (test_file, tested_file) tuples. test_file can be None. PERFECT_FILES = [ (None, - 'qutebrowser/commands/cmdexc.py'), + 'commands/cmdexc.py'), ('tests/unit/commands/test_cmdutils.py', - 'qutebrowser/commands/cmdutils.py'), + 'commands/cmdutils.py'), ('tests/unit/commands/test_argparser.py', - 'qutebrowser/commands/argparser.py'), + 'commands/argparser.py'), ('tests/unit/browser/webkit/test_cache.py', - 'qutebrowser/browser/webkit/cache.py'), + 'browser/webkit/cache.py'), ('tests/unit/browser/webkit/test_cookies.py', - 'qutebrowser/browser/webkit/cookies.py'), + 'browser/webkit/cookies.py'), ('tests/unit/browser/webkit/test_history.py', - 'qutebrowser/browser/history.py'), + 'browser/history.py'), ('tests/unit/browser/webkit/test_history.py', - 'qutebrowser/browser/webkit/webkithistory.py'), + 'browser/webkit/webkithistory.py'), ('tests/unit/browser/webkit/http/test_http.py', - 'qutebrowser/browser/webkit/http.py'), + 'browser/webkit/http.py'), ('tests/unit/browser/webkit/http/test_content_disposition.py', - 'qutebrowser/browser/webkit/rfc6266.py'), + 'browser/webkit/rfc6266.py'), # ('tests/unit/browser/webkit/test_webkitelem.py', - # 'qutebrowser/browser/webkit/webkitelem.py'), + # 'browser/webkit/webkitelem.py'), # ('tests/unit/browser/webkit/test_webkitelem.py', - # 'qutebrowser/browser/webelem.py'), + # 'browser/webelem.py'), ('tests/unit/browser/webkit/network/test_schemehandler.py', - 'qutebrowser/browser/webkit/network/schemehandler.py'), + 'browser/webkit/network/schemehandler.py'), ('tests/unit/browser/webkit/network/test_filescheme.py', - 'qutebrowser/browser/webkit/network/filescheme.py'), + 'browser/webkit/network/filescheme.py'), ('tests/unit/browser/webkit/network/test_networkreply.py', - 'qutebrowser/browser/webkit/network/networkreply.py'), + 'browser/webkit/network/networkreply.py'), ('tests/unit/browser/test_signalfilter.py', - 'qutebrowser/browser/signalfilter.py'), + 'browser/signalfilter.py'), (None, - 'qutebrowser/browser/webkit/certificateerror.py'), + 'browser/webkit/certificateerror.py'), # ('tests/unit/browser/test_tab.py', - # 'qutebrowser/browser/tab.py'), + # 'browser/tab.py'), ('tests/unit/keyinput/test_basekeyparser.py', - 'qutebrowser/keyinput/basekeyparser.py'), + 'keyinput/basekeyparser.py'), ('tests/unit/misc/test_autoupdate.py', - 'qutebrowser/misc/autoupdate.py'), + 'misc/autoupdate.py'), ('tests/unit/misc/test_readline.py', - 'qutebrowser/misc/readline.py'), + 'misc/readline.py'), ('tests/unit/misc/test_split.py', - 'qutebrowser/misc/split.py'), + 'misc/split.py'), ('tests/unit/misc/test_msgbox.py', - 'qutebrowser/misc/msgbox.py'), + 'misc/msgbox.py'), ('tests/unit/misc/test_checkpyver.py', - 'qutebrowser/misc/checkpyver.py'), + 'misc/checkpyver.py'), ('tests/unit/misc/test_guiprocess.py', - 'qutebrowser/misc/guiprocess.py'), + 'misc/guiprocess.py'), ('tests/unit/misc/test_editor.py', - 'qutebrowser/misc/editor.py'), + 'misc/editor.py'), ('tests/unit/misc/test_cmdhistory.py', - 'qutebrowser/misc/cmdhistory.py'), + 'misc/cmdhistory.py'), ('tests/unit/misc/test_ipc.py', - 'qutebrowser/misc/ipc.py'), + 'misc/ipc.py'), ('tests/unit/misc/test_keyhints.py', - 'qutebrowser/misc/keyhintwidget.py'), + 'misc/keyhintwidget.py'), ('tests/unit/misc/test_pastebin.py', - 'qutebrowser/misc/pastebin.py'), + 'misc/pastebin.py'), (None, - 'qutebrowser/misc/objects.py'), + 'misc/objects.py'), (None, - 'qutebrowser/mainwindow/statusbar/keystring.py'), + 'mainwindow/statusbar/keystring.py'), ('tests/unit/mainwindow/statusbar/test_percentage.py', - 'qutebrowser/mainwindow/statusbar/percentage.py'), + 'mainwindow/statusbar/percentage.py'), ('tests/unit/mainwindow/statusbar/test_progress.py', - 'qutebrowser/mainwindow/statusbar/progress.py'), + 'mainwindow/statusbar/progress.py'), ('tests/unit/mainwindow/statusbar/test_tabindex.py', - 'qutebrowser/mainwindow/statusbar/tabindex.py'), + 'mainwindow/statusbar/tabindex.py'), ('tests/unit/mainwindow/statusbar/test_textbase.py', - 'qutebrowser/mainwindow/statusbar/textbase.py'), + 'mainwindow/statusbar/textbase.py'), ('tests/unit/mainwindow/statusbar/test_url.py', - 'qutebrowser/mainwindow/statusbar/url.py'), + 'mainwindow/statusbar/url.py'), ('tests/unit/mainwindow/test_messageview.py', - 'qutebrowser/mainwindow/messageview.py'), + 'mainwindow/messageview.py'), ('tests/unit/config/test_configtypes.py', - 'qutebrowser/config/configtypes.py'), + 'config/configtypes.py'), ('tests/unit/config/test_configdata.py', - 'qutebrowser/config/configdata.py'), + 'config/configdata.py'), ('tests/unit/config/test_configexc.py', - 'qutebrowser/config/configexc.py'), + 'config/configexc.py'), ('tests/unit/config/test_textwrapper.py', - 'qutebrowser/config/textwrapper.py'), + 'config/textwrapper.py'), ('tests/unit/config/test_style.py', - 'qutebrowser/config/style.py'), + 'config/style.py'), ('tests/unit/utils/test_qtutils.py', - 'qutebrowser/utils/qtutils.py'), + 'utils/qtutils.py'), ('tests/unit/utils/test_standarddir.py', - 'qutebrowser/utils/standarddir.py'), + 'utils/standarddir.py'), ('tests/unit/utils/test_urlutils.py', - 'qutebrowser/utils/urlutils.py'), + 'utils/urlutils.py'), ('tests/unit/utils/usertypes', - 'qutebrowser/utils/usertypes.py'), + 'utils/usertypes.py'), ('tests/unit/utils/test_utils.py', - 'qutebrowser/utils/utils.py'), + 'utils/utils.py'), ('tests/unit/utils/test_version.py', - 'qutebrowser/utils/version.py'), + 'utils/version.py'), ('tests/unit/utils/test_debug.py', - 'qutebrowser/utils/debug.py'), + 'utils/debug.py'), ('tests/unit/utils/test_jinja.py', - 'qutebrowser/utils/jinja.py'), + 'utils/jinja.py'), ('tests/unit/utils/test_error.py', - 'qutebrowser/utils/error.py'), + 'utils/error.py'), ('tests/unit/utils/test_typing.py', - 'qutebrowser/utils/typing.py'), + 'utils/typing.py'), ('tests/unit/utils/test_javascript.py', - 'qutebrowser/utils/javascript.py'), + 'utils/javascript.py'), ('tests/unit/completion/test_models.py', - 'qutebrowser/completion/models/base.py'), + 'completion/models/base.py'), ('tests/unit/completion/test_sortfilter.py', - 'qutebrowser/completion/models/sortfilter.py'), + 'completion/models/sortfilter.py'), ] # 100% coverage because of end2end tests, but no perfect unit tests yet. WHITELISTED_FILES = [ - 'qutebrowser/browser/webkit/webkitinspector.py', - 'qutebrowser/keyinput/macros.py', - 'qutebrowser/browser/webkit/webkitelem.py', + 'browser/webkit/webkitinspector.py', + 'keyinput/macros.py', + 'browser/webkit/webkitelem.py', ] @@ -187,6 +187,8 @@ def _get_filename(filename): common_path = os.path.commonprefix([basedir, filename]) if common_path: filename = filename[len(common_path):].lstrip('/') + if filename.startswith('qutebrowser/'): + filename = filename.split('/', maxsplit=1)[1] return filename diff --git a/tests/unit/scripts/test_check_coverage.py b/tests/unit/scripts/test_check_coverage.py index 70f429aa2..16182bcf7 100644 --- a/tests/unit/scripts/test_check_coverage.py +++ b/tests/unit/scripts/test_check_coverage.py @@ -185,11 +185,12 @@ def test_skipped_windows(covtest, monkeypatch): def _generate_files(): """Get filenames from WHITELISTED_/PERFECT_FILES.""" - yield from iter(check_coverage.WHITELISTED_FILES) + for src_file in check_coverage.WHITELISTED_FILES: + yield os.path.join('qutebrowser', src_file) for test_file, src_file in check_coverage.PERFECT_FILES: if test_file is not None: yield test_file - yield src_file + yield os.path.join('qutebrowser', src_file) @pytest.mark.parametrize('filename', list(_generate_files())) From 9db92de2d536b1262c6585ef22f8cfd1da699b80 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 May 2017 06:15:21 +0200 Subject: [PATCH 278/299] Add a no cover pragma for certificate error hashing --- qutebrowser/browser/webkit/certificateerror.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/webkit/certificateerror.py b/qutebrowser/browser/webkit/certificateerror.py index 41cf2866f..7ebcb3b2f 100644 --- a/qutebrowser/browser/webkit/certificateerror.py +++ b/qutebrowser/browser/webkit/certificateerror.py @@ -41,7 +41,7 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper): try: # Qt >= 5.4 return hash(self._error) - except TypeError: + except TypeError: # pragma: no cover return hash((self._error.certificate().toDer(), self._error.error())) From e07a1045a8fe2128eedf30051aa9606ebcf20bb1 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 8 May 2017 22:00:11 -0700 Subject: [PATCH 279/299] Add is_link method to webelem --- qutebrowser/browser/webelem.py | 10 +++++++--- qutebrowser/browser/webkit/webkitelem.py | 3 +-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 5dd263da3..de6b9cfda 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -306,6 +306,11 @@ class AbstractWebElement(collections.abc.MutableMapping): qtutils.ensure_valid(url) return url + def is_link(self): + """Return True if this AbstractWebElement is a link.""" + href_tags = ['a', 'area', 'link'] + return self.tag_name() in href_tags + def _mouse_pos(self): """Get the position to click/hover.""" # Click the center of the largest square fitting into the top/left @@ -403,9 +408,8 @@ class AbstractWebElement(collections.abc.MutableMapping): self._click_fake_event(click_target) return - href_tags = ['a', 'area', 'link'] if click_target == usertypes.ClickTarget.normal: - if self.tag_name() in href_tags: + if self.is_link(): log.webelem.debug("Clicking via JS click()") self._click_js(click_target) elif self.is_editable(strict=True): @@ -418,7 +422,7 @@ class AbstractWebElement(collections.abc.MutableMapping): elif click_target in [usertypes.ClickTarget.tab, usertypes.ClickTarget.tab_bg, usertypes.ClickTarget.window]: - if self.tag_name() in href_tags: + if self.is_link(): self._click_href(click_target) else: self._click_fake_event(click_target) diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index ba15e4a66..a570eae3c 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -285,8 +285,7 @@ class WebKitElement(webelem.AbstractWebElement): for _ in range(5): if elem is None: break - tag = elem.tag_name() - if tag == 'a' or tag == 'area': + if elem.is_link(): if elem.get('target', None) == '_blank': elem['target'] = '_top' break From c9953b9f0da094c8a5bde0fd92db8a487db16579 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 8 May 2017 22:31:29 -0700 Subject: [PATCH 280/299] Add support for follow_selected via fake-clicks --- qutebrowser/browser/browsertab.py | 4 ++ qutebrowser/browser/webengine/webenginetab.py | 54 +++++++++++++++++-- qutebrowser/browser/webkit/webkittab.py | 8 +++ qutebrowser/javascript/webelem.js | 9 ++++ 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index ac1f73a63..672f20d68 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -196,6 +196,10 @@ class AbstractSearch(QObject): """ raise NotImplementedError + def searching(self): + """Return True if we are currently searching or not.""" + raise NotImplementedError + class AbstractZoom(QObject): diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index cc3dcf108..c083176fd 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -127,9 +127,12 @@ class WebEngineSearch(browsertab.AbstractSearch): def __init__(self, parent=None): super().__init__(parent) self._flags = QWebEnginePage.FindFlags(0) + self._searching = False def _find(self, text, flags, callback, caller): """Call findText on the widget.""" + self._searching = True + def wrapped_callback(found): """Wrap the callback to do debug logging.""" found_text = 'found' if found else "didn't find" @@ -160,8 +163,12 @@ class WebEngineSearch(browsertab.AbstractSearch): self._find(text, flags, result_cb, 'search') def clear(self): + self._searching = False self._widget.findText('') + def searching(self): + return self._searching + def prev_result(self, *, result_cb=None): # The int() here makes sure we get a copy of the flags. flags = QWebEnginePage.FindFlags(int(self._flags)) @@ -246,8 +253,47 @@ class WebEngineCaret(browsertab.AbstractCaret): raise browsertab.UnsupportedOperationError return self._widget.selectedText() + def _follow_selected_cb(self, js_elem, tab=False): + """Callback for javascript which clicks the selected element. + + Args: + js_elems: The elements serialized from javascript. + tab: Open in a new tab or not. + """ + if js_elem is None: + return + assert isinstance(js_elem, dict), js_elem + elem = webengineelem.WebEngineElement(js_elem, tab=self._tab) + if tab: + click_type = usertypes.ClickTarget.tab + else: + click_type = usertypes.ClickTarget.normal + + # Only click if we see a link + if elem.is_link(): + log.webview.debug("Found link in selection, clicking. Tab: " + + str(click_type) + "Link: " + str(elem)) + elem.click(click_type) + def follow_selected(self, *, tab=False): - log.stub() + if self._tab.search.searching(): + # We are currently in search mode. + # let's click the link via a fake-click + # https://bugreports.qt.io/browse/QTBUG-60673 + self._tab.search.clear() + + log.webview.debug("Clicking a searched link via fake key press.") + # send a fake enter, clicking the orange selection box + if not tab: + self._tab.scroller._key_press(Qt.Key_Enter) + else: + self._tab.scroller._key_press(Qt.Key_Enter, modifier=Qt.ControlModifier) + + else: + # click an existing blue selection + js_code = javascript.assemble('webelem', 'find_selected_link') + self._tab.run_js_async(js_code, lambda jsret: + self._follow_selected_cb(jsret, tab)) class WebEngineScroller(browsertab.AbstractScroller): @@ -265,10 +311,10 @@ class WebEngineScroller(browsertab.AbstractScroller): page = widget.page() page.scrollPositionChanged.connect(self._update_pos) - def _key_press(self, key, count=1): + def _key_press(self, key, count=1, modifier=Qt.NoModifier): for _ in range(min(count, 5000)): - press_evt = QKeyEvent(QEvent.KeyPress, key, Qt.NoModifier, 0, 0, 0) - release_evt = QKeyEvent(QEvent.KeyRelease, key, Qt.NoModifier, + press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0) + release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier, 0, 0, 0) self._tab.send_event(press_evt) self._tab.send_event(release_evt) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 0a844ddb0..54b3d3378 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -103,6 +103,7 @@ class WebKitSearch(browsertab.AbstractSearch): def __init__(self, parent=None): super().__init__(parent) self._flags = QWebPage.FindFlags(0) + self._searching = True def _call_cb(self, callback, found, text, flags, caller): """Call the given callback if it's non-None. @@ -132,12 +133,14 @@ class WebKitSearch(browsertab.AbstractSearch): QTimer.singleShot(0, functools.partial(callback, found)) def clear(self): + self._searching = False # We first clear the marked text, then the highlights self._widget.findText('') self._widget.findText('', QWebPage.HighlightAllOccurrences) def search(self, text, *, ignore_case=False, reverse=False, result_cb=None): + self._searching = True flags = QWebPage.FindWrapsAroundDocument if ignore_case == 'smart': if not text.islower(): @@ -155,10 +158,12 @@ class WebKitSearch(browsertab.AbstractSearch): self._call_cb(result_cb, found, text, flags, 'search') def next_result(self, *, result_cb=None): + self._searching = True found = self._widget.findText(self.text, self._flags) self._call_cb(result_cb, found, self.text, self._flags, 'next_result') def prev_result(self, *, result_cb=None): + self._searching = True # The int() here makes sure we get a copy of the flags. flags = QWebPage.FindFlags(int(self._flags)) if flags & QWebPage.FindBackward: @@ -168,6 +173,9 @@ class WebKitSearch(browsertab.AbstractSearch): found = self._widget.findText(self.text, flags) self._call_cb(result_cb, found, self.text, flags, 'prev_result') + def searching(self): + return self._searching + class WebKitCaret(browsertab.AbstractCaret): diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index f9bd7d1af..8c7883b3b 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -169,6 +169,15 @@ window._qutebrowser.webelem = (function() { return serialize_elem(elem); }; + // Function for returning a selection to python (so we can click it) + funcs.find_selected_link = function() { + var elem = window.getSelection().anchorNode; + if (!elem) { + return null; + } + return serialize_elem(elem.parentNode); + }; + funcs.set_value = function(id, value) { elements[id].value = value; }; From 5ba81e3611ca231728e7114b4e191f6b5189cb8d Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 8 May 2017 22:32:53 -0700 Subject: [PATCH 281/299] Add tests for follow_selected --- tests/end2end/data/search.html | 1 + tests/end2end/features/search.feature | 33 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/tests/end2end/data/search.html b/tests/end2end/data/search.html index 2eec560d6..3ca3e2e22 100644 --- a/tests/end2end/data/search.html +++ b/tests/end2end/data/search.html @@ -16,6 +16,7 @@ BAZ
space travel
/slash
+ follow me!

diff --git a/tests/end2end/features/search.feature b/tests/end2end/features/search.feature index 285b3cbf8..35a99394a 100644 --- a/tests/end2end/features/search.feature +++ b/tests/end2end/features/search.feature @@ -191,3 +191,36 @@ Feature: Searching on a page # TODO: wrapping message with scrolling # TODO: wrapping message without scrolling + + ## follow searched links + Scenario: Follow a searched link + When I run :search follow + And I wait for "search found follow" in the log + And I run :follow-selected + Then data/hello.txt should be loaded + + Scenario: Follow a searched link in a new tab + When I run :window-only + And I run :search follow + And I wait for "search found follow" in the log + And I run :follow-selected -t + And I wait until data/hello.txt is loaded + Then the following tabs should be open: + - data/search.html + - data/hello.txt (active) + + Scenario: Don't follow searched text + When I run :window-only + And I run :search foo + And I wait for "search found foo" in the log + And I run :follow-selected + Then the following tabs should be open: + - data/search.html (active) + + Scenario: Don't follow searched text in a new tab + When I run :window-only + And I run :search foo + And I wait for "search found foo" in the log + And I run :follow-selected -t + Then the following tabs should be open: + - data/search.html (active) From 63cffaf558acb65952a286e9f93d7b36c5edfa6d Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 8 May 2017 22:49:14 -0700 Subject: [PATCH 282/299] Refactor _key_press from WebEngineScroller to WebEngineTab --- qutebrowser/browser/webengine/webenginetab.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index c083176fd..0ffe06664 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -285,9 +285,9 @@ class WebEngineCaret(browsertab.AbstractCaret): log.webview.debug("Clicking a searched link via fake key press.") # send a fake enter, clicking the orange selection box if not tab: - self._tab.scroller._key_press(Qt.Key_Enter) + self._tab.key_press(Qt.Key_Enter) else: - self._tab.scroller._key_press(Qt.Key_Enter, modifier=Qt.ControlModifier) + self._tab.key_press(Qt.Key_Enter, modifier=Qt.ControlModifier) else: # click an existing blue selection @@ -311,14 +311,6 @@ class WebEngineScroller(browsertab.AbstractScroller): page = widget.page() page.scrollPositionChanged.connect(self._update_pos) - def _key_press(self, key, count=1, modifier=Qt.NoModifier): - for _ in range(min(count, 5000)): - press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0) - release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier, - 0, 0, 0) - self._tab.send_event(press_evt) - self._tab.send_event(release_evt) - @pyqtSlot() def _update_pos(self): """Update the scroll position attributes when it changed.""" @@ -374,28 +366,28 @@ class WebEngineScroller(browsertab.AbstractScroller): self._tab.run_js_async(js_code) def up(self, count=1): - self._key_press(Qt.Key_Up, count) + self._tab.key_press(Qt.Key_Up, count) def down(self, count=1): - self._key_press(Qt.Key_Down, count) + self._tab.key_press(Qt.Key_Down, count) def left(self, count=1): - self._key_press(Qt.Key_Left, count) + self._tab.key_press(Qt.Key_Left, count) def right(self, count=1): - self._key_press(Qt.Key_Right, count) + self._tab.key_press(Qt.Key_Right, count) def top(self): - self._key_press(Qt.Key_Home) + self._tab.key_press(Qt.Key_Home) def bottom(self): - self._key_press(Qt.Key_End) + self._tab.key_press(Qt.Key_End) def page_up(self, count=1): - self._key_press(Qt.Key_PageUp, count) + self._tab.key_press(Qt.Key_PageUp, count) def page_down(self, count=1): - self._key_press(Qt.Key_PageDown, count) + self._tab.key_press(Qt.Key_PageDown, count) def at_top(self): return self.pos_px().y() == 0 @@ -655,6 +647,14 @@ class WebEngineTab(browsertab.AbstractTab): def clear_ssl_errors(self): raise browsertab.UnsupportedOperationError + def key_press(self, key, count=1, modifier=Qt.NoModifier): + for _ in range(min(count, 5000)): + press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0) + release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier, + 0, 0, 0) + self.send_event(press_evt) + self.send_event(release_evt) + @pyqtSlot() def _on_history_trigger(self): url = self.url() From 5bdd291d28730a27bc1fbf89a3cde3d00fed4af0 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 8 May 2017 23:11:50 -0700 Subject: [PATCH 283/299] Refactor key_press into _repeated_key_press Also split off generic key pressing ability from WebKitScroller to WebKitTab --- qutebrowser/browser/webengine/webenginetab.py | 29 ++++++++++--------- qutebrowser/browser/webkit/webkittab.py | 13 +++++---- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 0ffe06664..bb3f017ab 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -311,6 +311,10 @@ class WebEngineScroller(browsertab.AbstractScroller): page = widget.page() page.scrollPositionChanged.connect(self._update_pos) + def _repeated_key_press(self, key, count=1, modifier=Qt.NoModifier): + for _ in range(min(count, 5000)): + self._tab.key_press(key, modifier) + @pyqtSlot() def _update_pos(self): """Update the scroll position attributes when it changed.""" @@ -366,16 +370,16 @@ class WebEngineScroller(browsertab.AbstractScroller): self._tab.run_js_async(js_code) def up(self, count=1): - self._tab.key_press(Qt.Key_Up, count) + self._repeated_key_press(Qt.Key_Up, count) def down(self, count=1): - self._tab.key_press(Qt.Key_Down, count) + self._repeated_key_press(Qt.Key_Down, count) def left(self, count=1): - self._tab.key_press(Qt.Key_Left, count) + self._repeated_key_press(Qt.Key_Left, count) def right(self, count=1): - self._tab.key_press(Qt.Key_Right, count) + self._repeated_key_press(Qt.Key_Right, count) def top(self): self._tab.key_press(Qt.Key_Home) @@ -384,10 +388,10 @@ class WebEngineScroller(browsertab.AbstractScroller): self._tab.key_press(Qt.Key_End) def page_up(self, count=1): - self._tab.key_press(Qt.Key_PageUp, count) + self._repeated_key_press(Qt.Key_PageUp, count) def page_down(self, count=1): - self._tab.key_press(Qt.Key_PageDown, count) + self._repeated_key_press(Qt.Key_PageDown, count) def at_top(self): return self.pos_px().y() == 0 @@ -647,13 +651,12 @@ class WebEngineTab(browsertab.AbstractTab): def clear_ssl_errors(self): raise browsertab.UnsupportedOperationError - def key_press(self, key, count=1, modifier=Qt.NoModifier): - for _ in range(min(count, 5000)): - press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0) - release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier, - 0, 0, 0) - self.send_event(press_evt) - self.send_event(release_evt) + def key_press(self, key, modifier=Qt.NoModifier): + press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0) + release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier, + 0, 0, 0) + self.send_event(press_evt) + self.send_event(release_evt) @pyqtSlot() def _on_history_trigger(self): diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 54b3d3378..f1f876a6e 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -468,15 +468,11 @@ class WebKitScroller(browsertab.AbstractScroller): # self._widget.setFocus() for _ in range(min(count, 5000)): - press_evt = QKeyEvent(QEvent.KeyPress, key, Qt.NoModifier, 0, 0, 0) - release_evt = QKeyEvent(QEvent.KeyRelease, key, Qt.NoModifier, - 0, 0, 0) # Abort scrolling if the minimum/maximum was reached. if (getter is not None and frame.scrollBarValue(direction) == getter(direction)): return - self._widget.keyPressEvent(press_evt) - self._widget.keyReleaseEvent(release_evt) + self._tab.key_press(key) def up(self, count=1): self._key_press(Qt.Key_Up, count, 'scrollBarMinimum', Qt.Vertical) @@ -702,6 +698,13 @@ class WebKitTab(browsertab.AbstractTab): def clear_ssl_errors(self): self.networkaccessmanager().clear_all_ssl_errors() + def key_press(self, key, modifier=Qt.NoModifier): + press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0) + release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier, + 0, 0, 0) + self.send_event(press_evt) + self.send_event(release_evt) + @pyqtSlot() def _on_history_trigger(self): url = self.url() From d1aac9e9e932668199c0835890ceb2c31bee038b Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Mon, 8 May 2017 23:43:21 -0700 Subject: [PATCH 284/299] Add docstrings to key_press methods --- qutebrowser/browser/webengine/webenginetab.py | 2 ++ qutebrowser/browser/webkit/webkittab.py | 1 + 2 files changed, 3 insertions(+) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index bb3f017ab..657b6e6da 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -312,6 +312,7 @@ class WebEngineScroller(browsertab.AbstractScroller): page.scrollPositionChanged.connect(self._update_pos) def _repeated_key_press(self, key, count=1, modifier=Qt.NoModifier): + """Send count fake key presses to this scroller's WebEngineTab.""" for _ in range(min(count, 5000)): self._tab.key_press(key, modifier) @@ -652,6 +653,7 @@ class WebEngineTab(browsertab.AbstractTab): raise browsertab.UnsupportedOperationError def key_press(self, key, modifier=Qt.NoModifier): + """Send a fake key event to this WebKitTab.""" press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0) release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier, 0, 0, 0) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index f1f876a6e..44df236a2 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -699,6 +699,7 @@ class WebKitTab(browsertab.AbstractTab): self.networkaccessmanager().clear_all_ssl_errors() def key_press(self, key, modifier=Qt.NoModifier): + """Send a fake key event to this WebKitTab.""" press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0) release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier, 0, 0, 0) From 02f79c2990228d13b8111f5ae6fca6da523408d0 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 9 May 2017 00:11:25 -0700 Subject: [PATCH 285/299] Add tests for manual selection --- tests/end2end/data/search.html | 2 +- tests/end2end/data/search_select.js | 13 +++++++++++++ tests/end2end/features/search.feature | 14 ++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/end2end/data/search_select.js diff --git a/tests/end2end/data/search.html b/tests/end2end/data/search.html index 3ca3e2e22..003751cfd 100644 --- a/tests/end2end/data/search.html +++ b/tests/end2end/data/search.html @@ -16,7 +16,7 @@ BAZ
space travel
/slash
- follow me!
+ follow me!

diff --git a/tests/end2end/data/search_select.js b/tests/end2end/data/search_select.js new file mode 100644 index 000000000..8a816c7b7 --- /dev/null +++ b/tests/end2end/data/search_select.js @@ -0,0 +1,13 @@ +/* Select all elements marked with toselect */ + + +var toSelect = document.getElementsByClassName("toselect"); +var s = window.getSelection(); + +if(s.rangeCount > 0) s.removeAllRanges(); + +for(var i = 0; i < toSelect.length; i++) { + var range = document.createRange(); + range.selectNode(toSelect[i]); + s.addRange(range); +} diff --git a/tests/end2end/features/search.feature b/tests/end2end/features/search.feature index 35a99394a..7e0151976 100644 --- a/tests/end2end/features/search.feature +++ b/tests/end2end/features/search.feature @@ -224,3 +224,17 @@ Feature: Searching on a page And I run :follow-selected -t Then the following tabs should be open: - data/search.html (active) + + Scenario: Follow a manually selected link + When I run :jseval --file (testdata)/search_select.js + And I run :follow-selected + Then data/hello.txt should be loaded + + Scenario: Follow a manually selected link in a new tab + When I run :window-only + And I run :jseval --file (testdata)/search_select.js + And I run :follow-selected -t + And I wait until data/hello.txt is loaded + Then the following tabs should be open: + - data/search.html + - data/hello.txt (active) From a3d41c046774586dccc4b4d1ab1786490eee496c Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 9 May 2017 00:14:59 -0700 Subject: [PATCH 286/299] Refactor search method of AbstractBrowserTab into a field --- qutebrowser/browser/browsertab.py | 6 ++---- qutebrowser/browser/webengine/webenginetab.py | 19 ++++++++----------- qutebrowser/browser/webkit/webkittab.py | 13 +++++-------- tests/end2end/data/search_select.js | 7 +++---- 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 672f20d68..f133a1646 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -163,6 +163,8 @@ class AbstractSearch(QObject): super().__init__(parent) self._widget = None self.text = None + # Implementing classes will set this. + self.search_displayed = False def search(self, text, *, ignore_case=False, reverse=False, result_cb=None): @@ -196,10 +198,6 @@ class AbstractSearch(QObject): """ raise NotImplementedError - def searching(self): - """Return True if we are currently searching or not.""" - raise NotImplementedError - class AbstractZoom(QObject): diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 657b6e6da..e460af049 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -127,11 +127,11 @@ class WebEngineSearch(browsertab.AbstractSearch): def __init__(self, parent=None): super().__init__(parent) self._flags = QWebEnginePage.FindFlags(0) - self._searching = False + self.search_displayed = False def _find(self, text, flags, callback, caller): """Call findText on the widget.""" - self._searching = True + self.search_displayed = True def wrapped_callback(found): """Wrap the callback to do debug logging.""" @@ -163,12 +163,9 @@ class WebEngineSearch(browsertab.AbstractSearch): self._find(text, flags, result_cb, 'search') def clear(self): - self._searching = False + self.search_displayed = False self._widget.findText('') - def searching(self): - return self._searching - def prev_result(self, *, result_cb=None): # The int() here makes sure we get a copy of the flags. flags = QWebEnginePage.FindFlags(int(self._flags)) @@ -256,10 +253,10 @@ class WebEngineCaret(browsertab.AbstractCaret): def _follow_selected_cb(self, js_elem, tab=False): """Callback for javascript which clicks the selected element. - Args: - js_elems: The elements serialized from javascript. - tab: Open in a new tab or not. - """ + Args: + js_elems: The elements serialized from javascript. + tab: Open in a new tab or not. + """ if js_elem is None: return assert isinstance(js_elem, dict), js_elem @@ -276,7 +273,7 @@ class WebEngineCaret(browsertab.AbstractCaret): elem.click(click_type) def follow_selected(self, *, tab=False): - if self._tab.search.searching(): + if self._tab.search.search_displayed: # We are currently in search mode. # let's click the link via a fake-click # https://bugreports.qt.io/browse/QTBUG-60673 diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 44df236a2..0cf83e2a7 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -103,7 +103,7 @@ class WebKitSearch(browsertab.AbstractSearch): def __init__(self, parent=None): super().__init__(parent) self._flags = QWebPage.FindFlags(0) - self._searching = True + self.search_displayed = True def _call_cb(self, callback, found, text, flags, caller): """Call the given callback if it's non-None. @@ -133,14 +133,14 @@ class WebKitSearch(browsertab.AbstractSearch): QTimer.singleShot(0, functools.partial(callback, found)) def clear(self): - self._searching = False + self.search_displayed = False # We first clear the marked text, then the highlights self._widget.findText('') self._widget.findText('', QWebPage.HighlightAllOccurrences) def search(self, text, *, ignore_case=False, reverse=False, result_cb=None): - self._searching = True + self.search_displayed = True flags = QWebPage.FindWrapsAroundDocument if ignore_case == 'smart': if not text.islower(): @@ -158,12 +158,12 @@ class WebKitSearch(browsertab.AbstractSearch): self._call_cb(result_cb, found, text, flags, 'search') def next_result(self, *, result_cb=None): - self._searching = True + self.search_displayed = True found = self._widget.findText(self.text, self._flags) self._call_cb(result_cb, found, self.text, self._flags, 'next_result') def prev_result(self, *, result_cb=None): - self._searching = True + self.search_displayed = True # The int() here makes sure we get a copy of the flags. flags = QWebPage.FindFlags(int(self._flags)) if flags & QWebPage.FindBackward: @@ -173,9 +173,6 @@ class WebKitSearch(browsertab.AbstractSearch): found = self._widget.findText(self.text, flags) self._call_cb(result_cb, found, self.text, flags, 'prev_result') - def searching(self): - return self._searching - class WebKitCaret(browsertab.AbstractCaret): diff --git a/tests/end2end/data/search_select.js b/tests/end2end/data/search_select.js index 8a816c7b7..874e9e9fe 100644 --- a/tests/end2end/data/search_select.js +++ b/tests/end2end/data/search_select.js @@ -1,13 +1,12 @@ /* Select all elements marked with toselect */ - var toSelect = document.getElementsByClassName("toselect"); var s = window.getSelection(); if(s.rangeCount > 0) s.removeAllRanges(); for(var i = 0; i < toSelect.length; i++) { - var range = document.createRange(); - range.selectNode(toSelect[i]); - s.addRange(range); + var range = document.createRange(); + range.selectNode(toSelect[i]); + s.addRange(range); } From e10d636ca09e1385b9bf0661062a62cef6227da6 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Tue, 9 May 2017 08:08:05 -0700 Subject: [PATCH 287/299] Fix a few small issues - Remove an unused warnings - Reverse if statement arguments to simplify logic --- qutebrowser/browser/webengine/webenginetab.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index e460af049..d4e3feaa2 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -17,9 +17,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# FIXME:qtwebengine remove this once the stubs are gone -# pylint: disable=unused-argument - """Wrapper over a QWebEngineView.""" import functools @@ -281,10 +278,10 @@ class WebEngineCaret(browsertab.AbstractCaret): log.webview.debug("Clicking a searched link via fake key press.") # send a fake enter, clicking the orange selection box - if not tab: - self._tab.key_press(Qt.Key_Enter) - else: + if tab: self._tab.key_press(Qt.Key_Enter, modifier=Qt.ControlModifier) + else: + self._tab.key_press(Qt.Key_Enter) else: # click an existing blue selection From 5f2fb2c4fcffa5dc052f308639d87e09925aef79 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 May 2017 17:48:40 +0200 Subject: [PATCH 288/299] Update docs --- CHANGELOG.asciidoc | 1 + README.asciidoc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 7ff888acd..bf01a2a17 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -53,6 +53,7 @@ Changed - With Qt 5.9, `content -> cookies-store` can now be set on QtWebEngine without a restart. - The adblocker now also blocks non-GET requests (e.g. POST) +- `:follow-selected` now also works with QtWebEngine Fixed ~~~~~ diff --git a/README.asciidoc b/README.asciidoc index b3457876b..215e2e437 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -191,6 +191,7 @@ Contributors, sorted by the number of commits in descending order: * ZDarian * Milan Svoboda * John ShaggyTwoDope Jenkins +* Jay Kamat * Clayton Craft * Peter Vilim * Jacob Sword @@ -285,7 +286,6 @@ Contributors, sorted by the number of commits in descending order: * Ján Kobezda * Johannes Martinsson * Jean-Christophe Petkovich -* Jay Kamat * Helen Sherwood-Taylor * HalosGhost * Gregor Pohl From e3eda28d88b959e3fca7c83455deae0d13438d4b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 May 2017 17:49:38 +0200 Subject: [PATCH 289/299] Update docstrings --- qutebrowser/browser/browsertab.py | 3 ++- qutebrowser/browser/webengine/webenginetab.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index f133a1646..21c0a080e 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -155,6 +155,8 @@ class AbstractSearch(QObject): Attributes: text: The last thing this view was searched for. + search_displayed: Whether we're currently displaying search results in + this view. _flags: The flags of the last search (needs to be set by subclasses). _widget: The underlying WebView widget. """ @@ -163,7 +165,6 @@ class AbstractSearch(QObject): super().__init__(parent) self._widget = None self.text = None - # Implementing classes will set this. self.search_displayed = False def search(self, text, *, ignore_case=False, reverse=False, diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index d4e3feaa2..3d6d381cb 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -251,8 +251,8 @@ class WebEngineCaret(browsertab.AbstractCaret): """Callback for javascript which clicks the selected element. Args: - js_elems: The elements serialized from javascript. - tab: Open in a new tab or not. + js_elem: The element serialized from javascript. + tab: Open in a new tab. """ if js_elem is None: return From 76fa126133a1565ebc210ae0a42d03e553450d82 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 May 2017 17:55:51 +0200 Subject: [PATCH 290/299] Simplify debug string --- qutebrowser/browser/webengine/webenginetab.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 3d6d381cb..bc7a5bf35 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -265,8 +265,8 @@ class WebEngineCaret(browsertab.AbstractCaret): # Only click if we see a link if elem.is_link(): - log.webview.debug("Found link in selection, clicking. Tab: " + - str(click_type) + "Link: " + str(elem)) + log.webview.debug("Found link in selection, clicking. ClickTarget " + "{}, elem {}".format(click_type, elem)) elem.click(click_type) def follow_selected(self, *, tab=False): From 4b5e528d0556ca0c86d7ff7f70158aacf2404463 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 May 2017 17:56:07 +0200 Subject: [PATCH 291/299] Add AbstractTab.key_press --- qutebrowser/browser/browsertab.py | 6 +++++- qutebrowser/browser/webengine/webenginetab.py | 1 - qutebrowser/browser/webkit/webkittab.py | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 21c0a080e..aa7025edf 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -21,7 +21,7 @@ import itertools -from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF +from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QWidget, QApplication @@ -743,6 +743,10 @@ class AbstractTab(QWidget): def clear_ssl_errors(self): raise NotImplementedError + def key_press(self, key, modifier=Qt.NoModifier): + """Send a fake key event to this tab.""" + raise NotImplementedError + def dump_async(self, callback, *, plain=False): """Dump the current page to a file ascync. diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index bc7a5bf35..c97035c0b 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -647,7 +647,6 @@ class WebEngineTab(browsertab.AbstractTab): raise browsertab.UnsupportedOperationError def key_press(self, key, modifier=Qt.NoModifier): - """Send a fake key event to this WebKitTab.""" press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0) release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier, 0, 0, 0) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 0cf83e2a7..cf33098e0 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -696,7 +696,6 @@ class WebKitTab(browsertab.AbstractTab): self.networkaccessmanager().clear_all_ssl_errors() def key_press(self, key, modifier=Qt.NoModifier): - """Send a fake key event to this WebKitTab.""" press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0) release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier, 0, 0, 0) From 905032924a2a1b110a62e5fd828dbd356b447ae1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 May 2017 17:58:28 +0200 Subject: [PATCH 292/299] Remove search_displayed initialization in subclasses We set this in BrowserTab anyways, and the value in WebKitTab was wrong. --- qutebrowser/browser/webengine/webenginetab.py | 1 - qutebrowser/browser/webkit/webkittab.py | 1 - 2 files changed, 2 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index c97035c0b..ffecb5686 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -124,7 +124,6 @@ class WebEngineSearch(browsertab.AbstractSearch): def __init__(self, parent=None): super().__init__(parent) self._flags = QWebEnginePage.FindFlags(0) - self.search_displayed = False def _find(self, text, flags, callback, caller): """Call findText on the widget.""" diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index cf33098e0..a5a02338e 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -103,7 +103,6 @@ class WebKitSearch(browsertab.AbstractSearch): def __init__(self, parent=None): super().__init__(parent) self._flags = QWebPage.FindFlags(0) - self.search_displayed = True def _call_cb(self, callback, found, text, flags, caller): """Call the given callback if it's non-None. From 822623f2ed6f0346365ff065fbd7b4c522d933c9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 May 2017 21:37:03 +0200 Subject: [PATCH 293/299] Finally update copyrights... --- misc/userscripts/dmenu_qutebrowser | 1 + misc/userscripts/openfeeds | 1 + misc/userscripts/qutebrowser_viewsource | 1 + qutebrowser/__init__.py | 4 ++-- qutebrowser/__main__.py | 2 +- qutebrowser/app.py | 2 +- qutebrowser/browser/__init__.py | 2 +- qutebrowser/browser/adblock.py | 2 +- qutebrowser/browser/browsertab.py | 2 +- qutebrowser/browser/commands.py | 2 +- qutebrowser/browser/downloads.py | 2 +- qutebrowser/browser/downloadview.py | 2 +- qutebrowser/browser/hints.py | 2 +- qutebrowser/browser/history.py | 2 +- qutebrowser/browser/inspector.py | 2 +- qutebrowser/browser/mouse.py | 2 +- qutebrowser/browser/navigate.py | 2 +- qutebrowser/browser/network/pac.py | 2 +- qutebrowser/browser/network/proxy.py | 2 +- qutebrowser/browser/pdfjs.py | 1 + qutebrowser/browser/qtnetworkdownloads.py | 2 +- qutebrowser/browser/qutescheme.py | 2 +- qutebrowser/browser/shared.py | 2 +- qutebrowser/browser/signalfilter.py | 2 +- qutebrowser/browser/urlmarks.py | 4 ++-- qutebrowser/browser/webelem.py | 2 +- qutebrowser/browser/webengine/__init__.py | 2 +- qutebrowser/browser/webengine/certificateerror.py | 2 +- qutebrowser/browser/webengine/interceptor.py | 2 +- qutebrowser/browser/webengine/tabhistory.py | 2 +- qutebrowser/browser/webengine/webenginedownloads.py | 2 +- qutebrowser/browser/webengine/webengineelem.py | 2 +- qutebrowser/browser/webengine/webengineinspector.py | 2 +- qutebrowser/browser/webengine/webenginequtescheme.py | 2 +- qutebrowser/browser/webengine/webenginesettings.py | 2 +- qutebrowser/browser/webengine/webenginetab.py | 2 +- qutebrowser/browser/webengine/webview.py | 2 +- qutebrowser/browser/webkit/__init__.py | 2 +- qutebrowser/browser/webkit/cache.py | 2 +- qutebrowser/browser/webkit/certificateerror.py | 2 +- qutebrowser/browser/webkit/cookies.py | 2 +- qutebrowser/browser/webkit/http.py | 2 +- qutebrowser/browser/webkit/mhtml.py | 2 +- qutebrowser/browser/webkit/network/filescheme.py | 4 ++-- qutebrowser/browser/webkit/network/networkmanager.py | 2 +- qutebrowser/browser/webkit/network/networkreply.py | 2 +- qutebrowser/browser/webkit/network/schemehandler.py | 2 +- qutebrowser/browser/webkit/network/webkitqutescheme.py | 2 +- qutebrowser/browser/webkit/rfc6266.py | 2 +- qutebrowser/browser/webkit/tabhistory.py | 2 +- qutebrowser/browser/webkit/webkitelem.py | 2 +- qutebrowser/browser/webkit/webkithistory.py | 2 +- qutebrowser/browser/webkit/webkitinspector.py | 2 +- qutebrowser/browser/webkit/webkitsettings.py | 2 +- qutebrowser/browser/webkit/webkittab.py | 2 +- qutebrowser/browser/webkit/webpage.py | 2 +- qutebrowser/browser/webkit/webview.py | 2 +- qutebrowser/commands/__init__.py | 2 +- qutebrowser/commands/argparser.py | 2 +- qutebrowser/commands/cmdexc.py | 2 +- qutebrowser/commands/cmdutils.py | 2 +- qutebrowser/commands/command.py | 2 +- qutebrowser/commands/runners.py | 2 +- qutebrowser/commands/userscripts.py | 2 +- qutebrowser/completion/__init__.py | 2 +- qutebrowser/completion/completer.py | 2 +- qutebrowser/completion/completiondelegate.py | 2 +- qutebrowser/completion/completionwidget.py | 2 +- qutebrowser/completion/models/__init__.py | 2 +- qutebrowser/completion/models/base.py | 2 +- qutebrowser/completion/models/configmodel.py | 2 +- qutebrowser/completion/models/instances.py | 2 +- qutebrowser/completion/models/miscmodels.py | 2 +- qutebrowser/completion/models/sortfilter.py | 2 +- qutebrowser/completion/models/urlmodel.py | 2 +- qutebrowser/config/__init__.py | 2 +- qutebrowser/config/config.py | 2 +- qutebrowser/config/configdata.py | 2 +- qutebrowser/config/configexc.py | 2 +- qutebrowser/config/configtypes.py | 2 +- qutebrowser/config/parsers/__init__.py | 2 +- qutebrowser/config/parsers/ini.py | 2 +- qutebrowser/config/parsers/keyconf.py | 2 +- qutebrowser/config/sections.py | 2 +- qutebrowser/config/style.py | 2 +- qutebrowser/config/textwrapper.py | 2 +- qutebrowser/config/value.py | 2 +- qutebrowser/config/websettings.py | 2 +- qutebrowser/javascript/position_caret.js | 2 +- qutebrowser/javascript/scroll.js | 2 +- qutebrowser/javascript/webelem.js | 2 +- qutebrowser/keyinput/__init__.py | 2 +- qutebrowser/keyinput/basekeyparser.py | 2 +- qutebrowser/keyinput/keyparser.py | 2 +- qutebrowser/keyinput/macros.py | 2 +- qutebrowser/keyinput/modeman.py | 2 +- qutebrowser/keyinput/modeparsers.py | 2 +- qutebrowser/mainwindow/__init__.py | 2 +- qutebrowser/mainwindow/mainwindow.py | 2 +- qutebrowser/mainwindow/messageview.py | 2 +- qutebrowser/mainwindow/prompt.py | 2 +- qutebrowser/mainwindow/statusbar/__init__.py | 2 +- qutebrowser/mainwindow/statusbar/bar.py | 2 +- qutebrowser/mainwindow/statusbar/command.py | 2 +- qutebrowser/mainwindow/statusbar/keystring.py | 2 +- qutebrowser/mainwindow/statusbar/percentage.py | 2 +- qutebrowser/mainwindow/statusbar/progress.py | 2 +- qutebrowser/mainwindow/statusbar/tabindex.py | 2 +- qutebrowser/mainwindow/statusbar/text.py | 2 +- qutebrowser/mainwindow/statusbar/textbase.py | 2 +- qutebrowser/mainwindow/statusbar/url.py | 2 +- qutebrowser/mainwindow/tabbedbrowser.py | 2 +- qutebrowser/mainwindow/tabwidget.py | 2 +- qutebrowser/misc/__init__.py | 2 +- qutebrowser/misc/autoupdate.py | 2 +- qutebrowser/misc/checkpyver.py | 2 +- qutebrowser/misc/cmdhistory.py | 2 +- qutebrowser/misc/consolewidget.py | 2 +- qutebrowser/misc/crashdialog.py | 2 +- qutebrowser/misc/crashsignal.py | 2 +- qutebrowser/misc/earlyinit.py | 2 +- qutebrowser/misc/editor.py | 2 +- qutebrowser/misc/guiprocess.py | 2 +- qutebrowser/misc/httpclient.py | 2 +- qutebrowser/misc/ipc.py | 2 +- qutebrowser/misc/keyhintwidget.py | 2 +- qutebrowser/misc/lineparser.py | 2 +- qutebrowser/misc/miscwidgets.py | 2 +- qutebrowser/misc/msgbox.py | 2 +- qutebrowser/misc/pastebin.py | 2 +- qutebrowser/misc/readline.py | 2 +- qutebrowser/misc/savemanager.py | 2 +- qutebrowser/misc/sessions.py | 2 +- qutebrowser/misc/split.py | 2 +- qutebrowser/misc/utilcmds.py | 2 +- qutebrowser/qutebrowser.py | 2 +- qutebrowser/utils/__init__.py | 2 +- qutebrowser/utils/debug.py | 2 +- qutebrowser/utils/docutils.py | 2 +- qutebrowser/utils/error.py | 2 +- qutebrowser/utils/javascript.py | 2 +- qutebrowser/utils/jinja.py | 2 +- qutebrowser/utils/log.py | 2 +- qutebrowser/utils/message.py | 2 +- qutebrowser/utils/objreg.py | 2 +- qutebrowser/utils/qtutils.py | 2 +- qutebrowser/utils/standarddir.py | 2 +- qutebrowser/utils/typing.py | 2 +- qutebrowser/utils/urlutils.py | 2 +- qutebrowser/utils/usertypes.py | 2 +- qutebrowser/utils/utils.py | 2 +- qutebrowser/utils/version.py | 2 +- scripts/asciidoc2html.py | 2 +- scripts/dev/build_release.py | 2 +- scripts/dev/check_coverage.py | 2 +- scripts/dev/check_doc_changes.py | 2 +- scripts/dev/ci/appveyor_install.py | 2 +- scripts/dev/ci/travis_install.sh | 2 +- scripts/dev/cleanup.py | 2 +- scripts/dev/freeze.py | 2 +- scripts/dev/freeze_tests.py | 2 +- scripts/dev/get_coredumpctl_traces.py | 2 +- scripts/dev/misc_checks.py | 2 +- scripts/dev/pylint_checkers/qute_pylint/config.py | 2 +- scripts/dev/pylint_checkers/qute_pylint/modeline.py | 2 +- scripts/dev/pylint_checkers/qute_pylint/openencoding.py | 2 +- scripts/dev/pylint_checkers/qute_pylint/settrace.py | 2 +- scripts/dev/pylint_checkers/setup.py | 2 +- scripts/dev/recompile_requirements.py | 2 +- scripts/dev/run_frozen_tests.py | 2 +- scripts/dev/run_profile.py | 2 +- scripts/dev/run_pylint_on_tests.py | 2 +- scripts/dev/run_vulture.py | 2 +- scripts/dev/segfault_test.py | 2 +- scripts/dev/src2asciidoc.py | 2 +- scripts/dev/ua_fetch.py | 5 +++-- scripts/dev/update_3rdparty.py | 1 + scripts/hostblock_blame.py | 2 +- scripts/importer.py | 3 ++- scripts/keytester.py | 2 +- scripts/link_pyqt.py | 2 +- scripts/setupcommon.py | 2 +- scripts/testbrowser.py | 2 +- scripts/utils.py | 2 +- setup.py | 2 +- tests/conftest.py | 2 +- tests/end2end/conftest.py | 2 +- tests/end2end/features/conftest.py | 2 +- tests/end2end/features/test_adblock_bdd.py | 2 +- tests/end2end/features/test_backforward_bdd.py | 2 +- tests/end2end/features/test_caret_bdd.py | 2 +- tests/end2end/features/test_completion_bdd.py | 2 +- tests/end2end/features/test_downloads_bdd.py | 2 +- tests/end2end/features/test_editor_bdd.py | 2 +- tests/end2end/features/test_hints_bdd.py | 2 +- tests/end2end/features/test_history_bdd.py | 2 +- tests/end2end/features/test_invoke_bdd.py | 2 +- tests/end2end/features/test_javascript_bdd.py | 2 +- tests/end2end/features/test_keyinput_bdd.py | 2 +- tests/end2end/features/test_marks_bdd.py | 2 +- tests/end2end/features/test_misc_bdd.py | 2 +- tests/end2end/features/test_navigate_bdd.py | 2 +- tests/end2end/features/test_open_bdd.py | 2 +- tests/end2end/features/test_prompts_bdd.py | 2 +- tests/end2end/features/test_scroll_bdd.py | 2 +- tests/end2end/features/test_search_bdd.py | 2 +- tests/end2end/features/test_sessions_bdd.py | 2 +- tests/end2end/features/test_set_bdd.py | 2 +- tests/end2end/features/test_spawn_bdd.py | 2 +- tests/end2end/features/test_tabs_bdd.py | 2 +- tests/end2end/features/test_urlmarks_bdd.py | 2 +- tests/end2end/features/test_utilcmds_bdd.py | 2 +- tests/end2end/features/test_yankpaste_bdd.py | 2 +- tests/end2end/features/test_zoom_bdd.py | 2 +- tests/end2end/fixtures/quteprocess.py | 2 +- tests/end2end/fixtures/test_quteprocess.py | 2 +- tests/end2end/fixtures/test_testprocess.py | 2 +- tests/end2end/fixtures/test_webserver.py | 2 +- tests/end2end/fixtures/testprocess.py | 2 +- tests/end2end/fixtures/webserver.py | 2 +- tests/end2end/fixtures/webserver_sub.py | 2 +- tests/end2end/fixtures/webserver_sub_ssl.py | 2 +- tests/end2end/test_dirbrowser.py | 2 +- tests/end2end/test_hints_html.py | 2 +- tests/end2end/test_insert_mode.py | 2 +- tests/end2end/test_invocations.py | 2 +- tests/end2end/test_mhtml_e2e.py | 2 +- tests/helpers/fixtures.py | 2 +- tests/helpers/logfail.py | 2 +- tests/helpers/messagemock.py | 2 +- tests/helpers/stubs.py | 2 +- tests/helpers/test_helper_utils.py | 2 +- tests/helpers/test_logfail.py | 2 +- tests/helpers/test_stubs.py | 2 +- tests/helpers/utils.py | 2 +- tests/test_conftest.py | 2 +- tests/unit/browser/test_commands.py | 2 +- tests/unit/browser/test_shared.py | 2 +- tests/unit/browser/test_signalfilter.py | 2 +- tests/unit/browser/test_tab.py | 2 +- tests/unit/browser/webengine/test_webenginedownloads.py | 2 +- tests/unit/browser/webkit/http/test_content_disposition.py | 2 +- tests/unit/browser/webkit/http/test_http.py | 2 +- tests/unit/browser/webkit/http/test_http_hypothesis.py | 2 +- tests/unit/browser/webkit/network/test_filescheme.py | 2 +- tests/unit/browser/webkit/network/test_networkmanager.py | 2 +- tests/unit/browser/webkit/network/test_networkreply.py | 2 +- tests/unit/browser/webkit/network/test_pac.py | 2 +- tests/unit/browser/webkit/network/test_schemehandler.py | 2 +- tests/unit/browser/webkit/network/test_webkitqutescheme.py | 3 ++- tests/unit/browser/webkit/test_cache.py | 2 +- tests/unit/browser/webkit/test_cookies.py | 2 +- tests/unit/browser/webkit/test_downloads.py | 2 +- tests/unit/browser/webkit/test_history.py | 2 +- tests/unit/browser/webkit/test_mhtml.py | 2 +- tests/unit/browser/webkit/test_qt_javascript.py | 2 +- tests/unit/browser/webkit/test_tabhistory.py | 2 +- tests/unit/browser/webkit/test_webkitelem.py | 2 +- tests/unit/commands/test_argparser.py | 2 +- tests/unit/commands/test_cmdutils.py | 2 +- tests/unit/commands/test_runners.py | 2 +- tests/unit/commands/test_userscripts.py | 2 +- tests/unit/completion/test_column_widths.py | 2 +- tests/unit/completion/test_completer.py | 2 +- tests/unit/completion/test_completionwidget.py | 2 +- tests/unit/completion/test_models.py | 2 +- tests/unit/completion/test_sortfilter.py | 2 +- tests/unit/config/test_config.py | 2 +- tests/unit/config/test_configdata.py | 2 +- tests/unit/config/test_configexc.py | 2 +- tests/unit/config/test_configtypes.py | 2 +- tests/unit/config/test_configtypes_hypothesis.py | 2 +- tests/unit/config/test_style.py | 2 +- tests/unit/config/test_textwrapper.py | 2 +- tests/unit/javascript/conftest.py | 2 +- tests/unit/javascript/position_caret/test_position_caret.py | 2 +- tests/unit/keyinput/conftest.py | 2 +- tests/unit/keyinput/test_basekeyparser.py | 2 +- tests/unit/keyinput/test_modeman.py | 2 +- tests/unit/keyinput/test_modeparsers.py | 2 +- tests/unit/mainwindow/statusbar/test_percentage.py | 2 +- tests/unit/mainwindow/statusbar/test_progress.py | 2 +- tests/unit/mainwindow/statusbar/test_tabindex.py | 2 +- tests/unit/mainwindow/statusbar/test_textbase.py | 2 +- tests/unit/mainwindow/statusbar/test_url.py | 2 +- tests/unit/mainwindow/test_messageview.py | 2 +- tests/unit/mainwindow/test_prompt.py | 2 +- tests/unit/mainwindow/test_tabwidget.py | 2 +- tests/unit/misc/test_autoupdate.py | 2 +- tests/unit/misc/test_checkpyver.py | 2 +- tests/unit/misc/test_cmdhistory.py | 4 ++-- tests/unit/misc/test_crashdialog.py | 2 +- tests/unit/misc/test_earlyinit.py | 2 +- tests/unit/misc/test_editor.py | 2 +- tests/unit/misc/test_guiprocess.py | 2 +- tests/unit/misc/test_ipc.py | 2 +- tests/unit/misc/test_keyhints.py | 2 +- tests/unit/misc/test_lineparser.py | 2 +- tests/unit/misc/test_miscwidgets.py | 2 +- tests/unit/misc/test_msgbox.py | 2 +- tests/unit/misc/test_pastebin.py | 2 +- tests/unit/misc/test_readline.py | 2 +- tests/unit/misc/test_sessions.py | 2 +- tests/unit/misc/test_split.py | 2 +- tests/unit/misc/test_split_hypothesis.py | 2 +- tests/unit/misc/test_utilcmds.py | 2 +- tests/unit/scripts/test_check_coverage.py | 2 +- tests/unit/scripts/test_run_vulture.py | 2 +- tests/unit/test_app.py | 2 +- tests/unit/utils/overflow_test_cases.py | 2 +- tests/unit/utils/test_debug.py | 2 +- tests/unit/utils/test_error.py | 2 +- tests/unit/utils/test_javascript.py | 2 +- tests/unit/utils/test_jinja.py | 2 +- tests/unit/utils/test_log.py | 2 +- tests/unit/utils/test_qtutils.py | 2 +- tests/unit/utils/test_standarddir.py | 2 +- tests/unit/utils/test_typing.py | 2 +- tests/unit/utils/test_urlutils.py | 2 +- tests/unit/utils/test_utils.py | 2 +- tests/unit/utils/test_version.py | 2 +- tests/unit/utils/usertypes/test_enum.py | 2 +- tests/unit/utils/usertypes/test_misc.py | 2 +- tests/unit/utils/usertypes/test_neighborlist.py | 2 +- tests/unit/utils/usertypes/test_question.py | 2 +- tests/unit/utils/usertypes/test_timer.py | 2 +- 326 files changed, 334 insertions(+), 326 deletions(-) diff --git a/misc/userscripts/dmenu_qutebrowser b/misc/userscripts/dmenu_qutebrowser index dbc7c05bf..6917dae98 100755 --- a/misc/userscripts/dmenu_qutebrowser +++ b/misc/userscripts/dmenu_qutebrowser @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Copyright 2015 Zach-Button +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/misc/userscripts/openfeeds b/misc/userscripts/openfeeds index 085bdbe67..8bc4c2d33 100755 --- a/misc/userscripts/openfeeds +++ b/misc/userscripts/openfeeds @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright 2015 jnphilipp +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/misc/userscripts/qutebrowser_viewsource b/misc/userscripts/qutebrowser_viewsource index b528c41e8..a8ad71de3 100755 --- a/misc/userscripts/qutebrowser_viewsource +++ b/misc/userscripts/qutebrowser_viewsource @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Copyright 2015 Zach-Button +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index 348cf407a..e61419c0c 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -22,7 +22,7 @@ import os.path __author__ = "Florian Bruhin" -__copyright__ = "Copyright 2014-2016 Florian Bruhin (The Compiler)" +__copyright__ = "Copyright 2014-2017 Florian Bruhin (The Compiler)" __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" diff --git a/qutebrowser/__main__.py b/qutebrowser/__main__.py index 4f977d2d2..506039890 100644 --- a/qutebrowser/__main__.py +++ b/qutebrowser/__main__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 8ca322078..cca9e1a76 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/__init__.py b/qutebrowser/browser/__init__.py index dbc790589..c5d5e6c92 100644 --- a/qutebrowser/browser/__init__.py +++ b/qutebrowser/browser/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py index a7d0d43bb..276dec08b 100644 --- a/qutebrowser/browser/adblock.py +++ b/qutebrowser/browser/adblock.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index aa7025edf..8e705d9df 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index d2fd1bca0..12de21b33 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 8182aabfd..c7cb5ad8e 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 715fe2a54..199f4d1d8 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 144b13f76..a7a5941f9 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 2615fd393..294425549 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index a18d1ecf2..e225c31da 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index 79b6816bb..d1cd889d3 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/navigate.py b/qutebrowser/browser/navigate.py index aacec9d3c..b1ab6f9b1 100644 --- a/qutebrowser/browser/navigate.py +++ b/qutebrowser/browser/navigate.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/network/pac.py b/qutebrowser/browser/network/pac.py index 63220d9b5..217cf21d9 100644 --- a/qutebrowser/browser/network/pac.py +++ b/qutebrowser/browser/network/pac.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/network/proxy.py b/qutebrowser/browser/network/proxy.py index 719c33178..1bdbc7b0b 100644 --- a/qutebrowser/browser/network/proxy.py +++ b/qutebrowser/browser/network/proxy.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/pdfjs.py b/qutebrowser/browser/pdfjs.py index d11cf3098..d003cefb1 100644 --- a/qutebrowser/browser/pdfjs.py +++ b/qutebrowser/browser/pdfjs.py @@ -1,6 +1,7 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2015 Daniel Schadt +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 56149a8cc..f3bc4dfe2 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index accdab4ba..919e6e186 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 885e2809d..005d652a3 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/signalfilter.py b/qutebrowser/browser/signalfilter.py index 78aa18b42..90b85c586 100644 --- a/qutebrowser/browser/signalfilter.py +++ b/qutebrowser/browser/signalfilter.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/urlmarks.py b/qutebrowser/browser/urlmarks.py index 991ba7fc5..013de408c 100644 --- a/qutebrowser/browser/urlmarks.py +++ b/qutebrowser/browser/urlmarks.py @@ -1,7 +1,7 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) -# Copyright 2015-2016 Antoni Boucher +# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Antoni Boucher # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index de6b9cfda..dce808871 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/__init__.py b/qutebrowser/browser/webengine/__init__.py index d7c910b36..60d140540 100644 --- a/qutebrowser/browser/webengine/__init__.py +++ b/qutebrowser/browser/webengine/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/certificateerror.py b/qutebrowser/browser/webengine/certificateerror.py index 19b59c522..07c4bbe1b 100644 --- a/qutebrowser/browser/webengine/certificateerror.py +++ b/qutebrowser/browser/webengine/certificateerror.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/interceptor.py b/qutebrowser/browser/webengine/interceptor.py index 76c29a6eb..e02f621a3 100644 --- a/qutebrowser/browser/webengine/interceptor.py +++ b/qutebrowser/browser/webengine/interceptor.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/tabhistory.py b/qutebrowser/browser/webengine/tabhistory.py index ec43aa07b..5db6faeb1 100644 --- a/qutebrowser/browser/webengine/tabhistory.py +++ b/qutebrowser/browser/webengine/tabhistory.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index 26b23d77f..f1ca2e6e3 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 11d663df2..dc170115d 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/webengineinspector.py b/qutebrowser/browser/webengine/webengineinspector.py index b822bd253..cbb258039 100644 --- a/qutebrowser/browser/webengine/webengineinspector.py +++ b/qutebrowser/browser/webengine/webengineinspector.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/webenginequtescheme.py b/qutebrowser/browser/webengine/webenginequtescheme.py index cebf31356..5acf6fcba 100644 --- a/qutebrowser/browser/webengine/webenginequtescheme.py +++ b/qutebrowser/browser/webengine/webenginequtescheme.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 740db489b..e1f4a22c9 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index ffecb5686..5d2c171c3 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 67e5fc259..ee6e099bf 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/__init__.py b/qutebrowser/browser/webkit/__init__.py index b67442be7..93b53cdba 100644 --- a/qutebrowser/browser/webkit/__init__.py +++ b/qutebrowser/browser/webkit/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/cache.py b/qutebrowser/browser/webkit/cache.py index be7dba486..8ae9aa0f2 100644 --- a/qutebrowser/browser/webkit/cache.py +++ b/qutebrowser/browser/webkit/cache.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/certificateerror.py b/qutebrowser/browser/webkit/certificateerror.py index 7ebcb3b2f..d02ded76c 100644 --- a/qutebrowser/browser/webkit/certificateerror.py +++ b/qutebrowser/browser/webkit/certificateerror.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/cookies.py b/qutebrowser/browser/webkit/cookies.py index 113eb661a..79f7a67fa 100644 --- a/qutebrowser/browser/webkit/cookies.py +++ b/qutebrowser/browser/webkit/cookies.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/http.py b/qutebrowser/browser/webkit/http.py index f55542c67..08cad7a44 100644 --- a/qutebrowser/browser/webkit/http.py +++ b/qutebrowser/browser/webkit/http.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index bc9ea2695..ed357f2bd 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Daniel Schadt +# Copyright 2015-2017 Daniel Schadt # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/network/filescheme.py b/qutebrowser/browser/webkit/network/filescheme.py index cd0a6d489..2f3a3ff18 100644 --- a/qutebrowser/browser/webkit/network/filescheme.py +++ b/qutebrowser/browser/webkit/network/filescheme.py @@ -1,7 +1,7 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) -# Copyright 2015-2016 Antoni Boucher (antoyo) +# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Antoni Boucher (antoyo) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 915a2082f..30adf3170 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/network/networkreply.py b/qutebrowser/browser/webkit/network/networkreply.py index dc1f09f0c..a4a4f59ca 100644 --- a/qutebrowser/browser/webkit/network/networkreply.py +++ b/qutebrowser/browser/webkit/network/networkreply.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # Based on the Eric5 helpviewer, # Copyright (c) 2009 - 2014 Detlev Offenbach diff --git a/qutebrowser/browser/webkit/network/schemehandler.py b/qutebrowser/browser/webkit/network/schemehandler.py index 9975db121..c6337efa3 100644 --- a/qutebrowser/browser/webkit/network/schemehandler.py +++ b/qutebrowser/browser/webkit/network/schemehandler.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # Based on the Eric5 helpviewer, # Copyright (c) 2009 - 2014 Detlev Offenbach diff --git a/qutebrowser/browser/webkit/network/webkitqutescheme.py b/qutebrowser/browser/webkit/network/webkitqutescheme.py index 34db29ee9..6e83e60a0 100644 --- a/qutebrowser/browser/webkit/network/webkitqutescheme.py +++ b/qutebrowser/browser/webkit/network/webkitqutescheme.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/rfc6266.py b/qutebrowser/browser/webkit/rfc6266.py index 11a1d76a1..c3c8f7a4b 100644 --- a/qutebrowser/browser/webkit/rfc6266.py +++ b/qutebrowser/browser/webkit/rfc6266.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/tabhistory.py b/qutebrowser/browser/webkit/tabhistory.py index 3f80676a9..19e4ef15c 100644 --- a/qutebrowser/browser/webkit/tabhistory.py +++ b/qutebrowser/browser/webkit/tabhistory.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index a570eae3c..a6fa1ba4f 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webkithistory.py b/qutebrowser/browser/webkit/webkithistory.py index abd80eb39..0f9d64460 100644 --- a/qutebrowser/browser/webkit/webkithistory.py +++ b/qutebrowser/browser/webkit/webkithistory.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webkitinspector.py b/qutebrowser/browser/webkit/webkitinspector.py index 9e612b28e..9a056a896 100644 --- a/qutebrowser/browser/webkit/webkitinspector.py +++ b/qutebrowser/browser/webkit/webkitinspector.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index 747af9b5d..0d4564e7e 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index a5a02338e..3de652887 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index 7fc891c4c..a687dd5e2 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index 0c3aa179e..01abca639 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/commands/__init__.py b/qutebrowser/commands/__init__.py index 70f058d82..7bc59ae40 100644 --- a/qutebrowser/commands/__init__.py +++ b/qutebrowser/commands/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/commands/argparser.py b/qutebrowser/commands/argparser.py index c0e08ccb6..9dfe841ce 100644 --- a/qutebrowser/commands/argparser.py +++ b/qutebrowser/commands/argparser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/commands/cmdexc.py b/qutebrowser/commands/cmdexc.py index 766d20620..51e20fec7 100644 --- a/qutebrowser/commands/cmdexc.py +++ b/qutebrowser/commands/cmdexc.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/commands/cmdutils.py b/qutebrowser/commands/cmdutils.py index 3641c3cb9..9b1539b71 100644 --- a/qutebrowser/commands/cmdutils.py +++ b/qutebrowser/commands/cmdutils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index d46cc5c77..b40f7ab16 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 0a2d6085f..cc967ce86 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index 4533e86b1..f3802d04c 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/__init__.py b/qutebrowser/completion/__init__.py index d3caf8703..8b8b9d88d 100644 --- a/qutebrowser/completion/__init__.py +++ b/qutebrowser/completion/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 74c759c0d..96d937829 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/completiondelegate.py b/qutebrowser/completion/completiondelegate.py index dfb479b3f..1d5dfadf0 100644 --- a/qutebrowser/completion/completiondelegate.py +++ b/qutebrowser/completion/completiondelegate.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index e9fad39fd..490fcd6c0 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/__init__.py b/qutebrowser/completion/models/__init__.py index 99f1954fe..5812545eb 100644 --- a/qutebrowser/completion/models/__init__.py +++ b/qutebrowser/completion/models/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/base.py b/qutebrowser/completion/models/base.py index 1ee45af71..b1cad276a 100644 --- a/qutebrowser/completion/models/base.py +++ b/qutebrowser/completion/models/base.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/configmodel.py b/qutebrowser/completion/models/configmodel.py index 4058a5f00..c9e9850d1 100644 --- a/qutebrowser/completion/models/configmodel.py +++ b/qutebrowser/completion/models/configmodel.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/instances.py b/qutebrowser/completion/models/instances.py index 6359d3771..f7eaaca86 100644 --- a/qutebrowser/completion/models/instances.py +++ b/qutebrowser/completion/models/instances.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 76c1a8997..5ab381c43 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/sortfilter.py b/qutebrowser/completion/models/sortfilter.py index 3df787539..2bb454bf9 100644 --- a/qutebrowser/completion/models/sortfilter.py +++ b/qutebrowser/completion/models/sortfilter.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py index a222df4f6..98f68c08c 100644 --- a/qutebrowser/completion/models/urlmodel.py +++ b/qutebrowser/completion/models/urlmodel.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/__init__.py b/qutebrowser/config/__init__.py index e2a04ee47..bf0bce0ec 100644 --- a/qutebrowser/config/__init__.py +++ b/qutebrowser/config/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 73aa2ae22..ccd5afd56 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 347edc53f..6c3c61187 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py index a8fd0af2e..b19d45d7b 100644 --- a/qutebrowser/config/configexc.py +++ b/qutebrowser/config/configexc.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 2dad85117..9c390b2b0 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/parsers/__init__.py b/qutebrowser/config/parsers/__init__.py index 5e2183794..1c316078d 100644 --- a/qutebrowser/config/parsers/__init__.py +++ b/qutebrowser/config/parsers/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/parsers/ini.py b/qutebrowser/config/parsers/ini.py index 56640e299..0ae485f4b 100644 --- a/qutebrowser/config/parsers/ini.py +++ b/qutebrowser/config/parsers/ini.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/parsers/keyconf.py b/qutebrowser/config/parsers/keyconf.py index 2f0d8df69..53f23d7c0 100644 --- a/qutebrowser/config/parsers/keyconf.py +++ b/qutebrowser/config/parsers/keyconf.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/sections.py b/qutebrowser/config/sections.py index 254348fe9..04a735647 100644 --- a/qutebrowser/config/sections.py +++ b/qutebrowser/config/sections.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/style.py b/qutebrowser/config/style.py index b2697daac..15215c398 100644 --- a/qutebrowser/config/style.py +++ b/qutebrowser/config/style.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/textwrapper.py b/qutebrowser/config/textwrapper.py index a36a73b6f..b5744f60b 100644 --- a/qutebrowser/config/textwrapper.py +++ b/qutebrowser/config/textwrapper.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/value.py b/qutebrowser/config/value.py index 388d8febc..b23674606 100644 --- a/qutebrowser/config/value.py +++ b/qutebrowser/config/value.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index b6e010499..b2a54e58b 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/javascript/position_caret.js b/qutebrowser/javascript/position_caret.js index 09d2301ad..4f6c32380 100644 --- a/qutebrowser/javascript/position_caret.js +++ b/qutebrowser/javascript/position_caret.js @@ -1,6 +1,6 @@ /** * Copyright 2015 Artur Shaik -* Copyright 2015-2016 Florian Bruhin (The Compiler) +* Copyright 2015-2017 Florian Bruhin (The Compiler) * * This file is part of qutebrowser. * diff --git a/qutebrowser/javascript/scroll.js b/qutebrowser/javascript/scroll.js index ac19d175c..35f412783 100644 --- a/qutebrowser/javascript/scroll.js +++ b/qutebrowser/javascript/scroll.js @@ -1,5 +1,5 @@ /** - * Copyright 2016 Florian Bruhin (The Compiler) + * Copyright 2016-2017 Florian Bruhin (The Compiler) * * This file is part of qutebrowser. * diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 8c7883b3b..6d763b8df 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -1,5 +1,5 @@ /** - * Copyright 2016 Florian Bruhin (The Compiler) + * Copyright 2016-2017 Florian Bruhin (The Compiler) * * This file is part of qutebrowser. * diff --git a/qutebrowser/keyinput/__init__.py b/qutebrowser/keyinput/__init__.py index 6fb35e5d6..c6b95b8a9 100644 --- a/qutebrowser/keyinput/__init__.py +++ b/qutebrowser/keyinput/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 7325223a3..670cde853 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/keyinput/keyparser.py b/qutebrowser/keyinput/keyparser.py index 0b46dffc4..f9a64edca 100644 --- a/qutebrowser/keyinput/keyparser.py +++ b/qutebrowser/keyinput/keyparser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/keyinput/macros.py b/qutebrowser/keyinput/macros.py index 8176e5652..9e3667590 100644 --- a/qutebrowser/keyinput/macros.py +++ b/qutebrowser/keyinput/macros.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Jan Verbeek (blyxxyz) +# Copyright 2016-2017 Jan Verbeek (blyxxyz) # # This file is part of qutebrowser. # diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 542081719..5ce55e670 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index db540b58e..4ef393b03 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/__init__.py b/qutebrowser/mainwindow/__init__.py index 178413514..43eb563a9 100644 --- a/qutebrowser/mainwindow/__init__.py +++ b/qutebrowser/mainwindow/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index fbc86d010..96489fbac 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/messageview.py b/qutebrowser/mainwindow/messageview.py index 7b2d64e07..7d0d2b682 100644 --- a/qutebrowser/mainwindow/messageview.py +++ b/qutebrowser/mainwindow/messageview.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index db91cc71a..c38a41caa 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/__init__.py b/qutebrowser/mainwindow/statusbar/__init__.py index c6a25fe0c..eb3ed7193 100644 --- a/qutebrowser/mainwindow/statusbar/__init__.py +++ b/qutebrowser/mainwindow/statusbar/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index eaf8e6ffc..cddf4ec35 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py index a5abaa290..d59c87dbc 100644 --- a/qutebrowser/mainwindow/statusbar/command.py +++ b/qutebrowser/mainwindow/statusbar/command.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/keystring.py b/qutebrowser/mainwindow/statusbar/keystring.py index 0baa8137c..dd9825ab2 100644 --- a/qutebrowser/mainwindow/statusbar/keystring.py +++ b/qutebrowser/mainwindow/statusbar/keystring.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/percentage.py b/qutebrowser/mainwindow/statusbar/percentage.py index 9c2d2dcd9..050b0747a 100644 --- a/qutebrowser/mainwindow/statusbar/percentage.py +++ b/qutebrowser/mainwindow/statusbar/percentage.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/progress.py b/qutebrowser/mainwindow/statusbar/progress.py index 17892fe33..e78d5307c 100644 --- a/qutebrowser/mainwindow/statusbar/progress.py +++ b/qutebrowser/mainwindow/statusbar/progress.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/tabindex.py b/qutebrowser/mainwindow/statusbar/tabindex.py index 7dda5f806..6a4cc987c 100644 --- a/qutebrowser/mainwindow/statusbar/tabindex.py +++ b/qutebrowser/mainwindow/statusbar/tabindex.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/text.py b/qutebrowser/mainwindow/statusbar/text.py index 2791385b7..e99891ecd 100644 --- a/qutebrowser/mainwindow/statusbar/text.py +++ b/qutebrowser/mainwindow/statusbar/text.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/textbase.py b/qutebrowser/mainwindow/statusbar/textbase.py index eb3064286..0ae271191 100644 --- a/qutebrowser/mainwindow/statusbar/textbase.py +++ b/qutebrowser/mainwindow/statusbar/textbase.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/url.py b/qutebrowser/mainwindow/statusbar/url.py index c7bee2ae9..b54e020fa 100644 --- a/qutebrowser/mainwindow/statusbar/url.py +++ b/qutebrowser/mainwindow/statusbar/url.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 885c4dc78..896ea5c9d 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 4e57c2dd6..e1cc2f5ef 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/__init__.py b/qutebrowser/misc/__init__.py index 3dc51e6a9..03ad27aa8 100644 --- a/qutebrowser/misc/__init__.py +++ b/qutebrowser/misc/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/autoupdate.py b/qutebrowser/misc/autoupdate.py index 860064672..15a3b6670 100644 --- a/qutebrowser/misc/autoupdate.py +++ b/qutebrowser/misc/autoupdate.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/checkpyver.py b/qutebrowser/misc/checkpyver.py index 364b8bb57..34183041b 100644 --- a/qutebrowser/misc/checkpyver.py +++ b/qutebrowser/misc/checkpyver.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The-Compiler) +# Copyright 2014-2017 Florian Bruhin (The-Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/cmdhistory.py b/qutebrowser/misc/cmdhistory.py index 7f3dfd52f..b0990a67e 100644 --- a/qutebrowser/misc/cmdhistory.py +++ b/qutebrowser/misc/cmdhistory.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/consolewidget.py b/qutebrowser/misc/consolewidget.py index 485da0fe4..537621b4d 100644 --- a/qutebrowser/misc/consolewidget.py +++ b/qutebrowser/misc/consolewidget.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py index 0add7932a..045406c13 100644 --- a/qutebrowser/misc/crashdialog.py +++ b/qutebrowser/misc/crashdialog.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index d8d8bb385..5f161312a 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 3622dc36a..99fd3e1a3 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The-Compiler) +# Copyright 2014-2017 Florian Bruhin (The-Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index a6f2854d8..58a08daf1 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 8f986d1b1..e8d224f2e 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/httpclient.py b/qutebrowser/misc/httpclient.py index fc6f94f59..4d33d487e 100644 --- a/qutebrowser/misc/httpclient.py +++ b/qutebrowser/misc/httpclient.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index f6567a11f..eb9aa4a3b 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/keyhintwidget.py b/qutebrowser/misc/keyhintwidget.py index d32b24eb9..b42612b1b 100644 --- a/qutebrowser/misc/keyhintwidget.py +++ b/qutebrowser/misc/keyhintwidget.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Ryan Roden-Corrent (rcorre) +# Copyright 2016-2017 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/lineparser.py b/qutebrowser/misc/lineparser.py index 55bae7142..ea9d100b7 100644 --- a/qutebrowser/misc/lineparser.py +++ b/qutebrowser/misc/lineparser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/miscwidgets.py b/qutebrowser/misc/miscwidgets.py index 956d60d4a..48f775b85 100644 --- a/qutebrowser/misc/miscwidgets.py +++ b/qutebrowser/misc/miscwidgets.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/msgbox.py b/qutebrowser/misc/msgbox.py index f6f29c38d..2c8aaf85e 100644 --- a/qutebrowser/misc/msgbox.py +++ b/qutebrowser/misc/msgbox.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/pastebin.py b/qutebrowser/misc/pastebin.py index 40dd77f33..9c30ca067 100644 --- a/qutebrowser/misc/pastebin.py +++ b/qutebrowser/misc/pastebin.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/readline.py b/qutebrowser/misc/readline.py index 0089ebe7c..2bc999c4d 100644 --- a/qutebrowser/misc/readline.py +++ b/qutebrowser/misc/readline.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/savemanager.py b/qutebrowser/misc/savemanager.py index 7e85b013e..509e5489a 100644 --- a/qutebrowser/misc/savemanager.py +++ b/qutebrowser/misc/savemanager.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index 8bf55016f..cba3629ef 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/split.py b/qutebrowser/misc/split.py index cb7a38d81..3f3b2d362 100644 --- a/qutebrowser/misc/split.py +++ b/qutebrowser/misc/split.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index de43c71f4..41b44de1f 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 01054f14d..b4673ecff 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/__init__.py b/qutebrowser/utils/__init__.py index 763345a7b..c28a40b10 100644 --- a/qutebrowser/utils/__init__.py +++ b/qutebrowser/utils/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index a516c43f3..5da5234a9 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/docutils.py b/qutebrowser/utils/docutils.py index 40cb0cb70..1a3b4312d 100644 --- a/qutebrowser/utils/docutils.py +++ b/qutebrowser/utils/docutils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/error.py b/qutebrowser/utils/error.py index 6a818857f..0d045bf19 100644 --- a/qutebrowser/utils/error.py +++ b/qutebrowser/utils/error.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/javascript.py b/qutebrowser/utils/javascript.py index 4fc7e546c..f536fed1f 100644 --- a/qutebrowser/utils/javascript.py +++ b/qutebrowser/utils/javascript.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index 2ad2be448..731dec8e4 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 19b4f506f..1cea959ee 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py index 8f39e8174..35ab604b0 100644 --- a/qutebrowser/utils/message.py +++ b/qutebrowser/utils/message.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index e54502ef8..4df08ba0d 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index 9bd606f02..15c9e72ce 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index 7c756b805..a1cedddd0 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/typing.py b/qutebrowser/utils/typing.py index dca42acd6..358a1a5a3 100644 --- a/qutebrowser/utils/typing.py +++ b/qutebrowser/utils/typing.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 23bf97f9f..4cd0e94d0 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 34148f9af..7d31ba6ac 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index c34df896e..d2de174a8 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 0565281bc..a8e115ca5 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py index 613f622a8..cfdc1b8d5 100755 --- a/scripts/asciidoc2html.py +++ b/scripts/asciidoc2html.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index fb8fe000d..aca2d0ef0 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index 9025d8740..4abb43f8b 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/dev/check_doc_changes.py b/scripts/dev/check_doc_changes.py index d072bfb65..ab879b5ac 100755 --- a/scripts/dev/check_doc_changes.py +++ b/scripts/dev/check_doc_changes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/dev/ci/appveyor_install.py b/scripts/dev/ci/appveyor_install.py index 131906248..60c07bad6 100644 --- a/scripts/dev/ci/appveyor_install.py +++ b/scripts/dev/ci/appveyor_install.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/dev/ci/travis_install.sh b/scripts/dev/ci/travis_install.sh index e752685f5..0ada134c1 100644 --- a/scripts/dev/ci/travis_install.sh +++ b/scripts/dev/ci/travis_install.sh @@ -1,6 +1,6 @@ # vim: ft=sh fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/dev/cleanup.py b/scripts/dev/cleanup.py index f08292017..49832eb3d 100755 --- a/scripts/dev/cleanup.py +++ b/scripts/dev/cleanup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/freeze.py b/scripts/dev/freeze.py index f254f4d90..8f99d2d35 100755 --- a/scripts/dev/freeze.py +++ b/scripts/dev/freeze.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/freeze_tests.py b/scripts/dev/freeze_tests.py index 9f9e2bbd2..97405b446 100755 --- a/scripts/dev/freeze_tests.py +++ b/scripts/dev/freeze_tests.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/get_coredumpctl_traces.py b/scripts/dev/get_coredumpctl_traces.py index d1e962834..8cae2a190 100644 --- a/scripts/dev/get_coredumpctl_traces.py +++ b/scripts/dev/get_coredumpctl_traces.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py index 49ca8e48f..1bb263d15 100644 --- a/scripts/dev/misc_checks.py +++ b/scripts/dev/misc_checks.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/pylint_checkers/qute_pylint/config.py b/scripts/dev/pylint_checkers/qute_pylint/config.py index b93b211f1..be8ac8da8 100644 --- a/scripts/dev/pylint_checkers/qute_pylint/config.py +++ b/scripts/dev/pylint_checkers/qute_pylint/config.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/pylint_checkers/qute_pylint/modeline.py b/scripts/dev/pylint_checkers/qute_pylint/modeline.py index 580837b34..ee3de13c9 100644 --- a/scripts/dev/pylint_checkers/qute_pylint/modeline.py +++ b/scripts/dev/pylint_checkers/qute_pylint/modeline.py @@ -1,4 +1,4 @@ -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # This file is part of qutebrowser. diff --git a/scripts/dev/pylint_checkers/qute_pylint/openencoding.py b/scripts/dev/pylint_checkers/qute_pylint/openencoding.py index 83926fc5e..eccc152ba 100644 --- a/scripts/dev/pylint_checkers/qute_pylint/openencoding.py +++ b/scripts/dev/pylint_checkers/qute_pylint/openencoding.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/dev/pylint_checkers/qute_pylint/settrace.py b/scripts/dev/pylint_checkers/qute_pylint/settrace.py index 9c1196daa..2bfa9f06f 100644 --- a/scripts/dev/pylint_checkers/qute_pylint/settrace.py +++ b/scripts/dev/pylint_checkers/qute_pylint/settrace.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/pylint_checkers/setup.py b/scripts/dev/pylint_checkers/setup.py index eaecf406e..960fdd2b7 100644 --- a/scripts/dev/pylint_checkers/setup.py +++ b/scripts/dev/pylint_checkers/setup.py @@ -2,7 +2,7 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/recompile_requirements.py b/scripts/dev/recompile_requirements.py index 1e3181344..44c56202a 100644 --- a/scripts/dev/recompile_requirements.py +++ b/scripts/dev/recompile_requirements.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/run_frozen_tests.py b/scripts/dev/run_frozen_tests.py index e64325417..1684884a3 100644 --- a/scripts/dev/run_frozen_tests.py +++ b/scripts/dev/run_frozen_tests.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/run_profile.py b/scripts/dev/run_profile.py index 87d2f0ed7..31fe539aa 100755 --- a/scripts/dev/run_profile.py +++ b/scripts/dev/run_profile.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/run_pylint_on_tests.py b/scripts/dev/run_pylint_on_tests.py index e4412708d..b9fb4845d 100644 --- a/scripts/dev/run_pylint_on_tests.py +++ b/scripts/dev/run_pylint_on_tests.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py index f1eed1803..40fe2f591 100755 --- a/scripts/dev/run_vulture.py +++ b/scripts/dev/run_vulture.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/dev/segfault_test.py b/scripts/dev/segfault_test.py index cac8c6a14..c5e7c106f 100755 --- a/scripts/dev/segfault_test.py +++ b/scripts/dev/segfault_test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py index 1b99022a7..7445a99c6 100755 --- a/scripts/dev/src2asciidoc.py +++ b/scripts/dev/src2asciidoc.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/dev/ua_fetch.py b/scripts/dev/ua_fetch.py index f37797041..14f71d525 100644 --- a/scripts/dev/ua_fetch.py +++ b/scripts/dev/ua_fetch.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 lamarpavel -# Copyright 2015-2016 Alexey Nabrodov (Averrin) +# Copyright 2015-2017 lamarpavel +# Copyright 2015-2017 Alexey Nabrodov (Averrin) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/dev/update_3rdparty.py b/scripts/dev/update_3rdparty.py index 4b88b165f..722ce2316 100755 --- a/scripts/dev/update_3rdparty.py +++ b/scripts/dev/update_3rdparty.py @@ -2,6 +2,7 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2015 Daniel Schadt +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/hostblock_blame.py b/scripts/hostblock_blame.py index 7e444793b..dde83d91f 100644 --- a/scripts/hostblock_blame.py +++ b/scripts/hostblock_blame.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/importer.py b/scripts/importer.py index 5e0883cd2..1b3be4d32 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Claude (longneck) +# Copyright 2014-2017 Claude (longneck) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/keytester.py b/scripts/keytester.py index ebed5f62c..b147599b6 100644 --- a/scripts/keytester.py +++ b/scripts/keytester.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/link_pyqt.py b/scripts/link_pyqt.py index fffe27fb3..a7de598cd 100644 --- a/scripts/link_pyqt.py +++ b/scripts/link_pyqt.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/setupcommon.py b/scripts/setupcommon.py index 9a447d280..494ab4c46 100644 --- a/scripts/setupcommon.py +++ b/scripts/setupcommon.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/testbrowser.py b/scripts/testbrowser.py index 75c36bd95..fbe48c451 100755 --- a/scripts/testbrowser.py +++ b/scripts/testbrowser.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/scripts/utils.py b/scripts/utils.py index 55eb679bd..6793bb2a7 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/setup.py b/setup.py index 6e594af12..f594009a0 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2015 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/conftest.py b/tests/conftest.py index 53dbbb752..2fdf8ab9a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index b374f53dd..8dac6a41d 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index 1e9c34462..c2b5865a5 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_adblock_bdd.py b/tests/end2end/features/test_adblock_bdd.py index 9f4ae63b3..069c127aa 100644 --- a/tests/end2end/features/test_adblock_bdd.py +++ b/tests/end2end/features/test_adblock_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_backforward_bdd.py b/tests/end2end/features/test_backforward_bdd.py index ede51988a..187882b67 100644 --- a/tests/end2end/features/test_backforward_bdd.py +++ b/tests/end2end/features/test_backforward_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_caret_bdd.py b/tests/end2end/features/test_caret_bdd.py index aa42241c4..9e4e1dedd 100644 --- a/tests/end2end/features/test_caret_bdd.py +++ b/tests/end2end/features/test_caret_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_completion_bdd.py b/tests/end2end/features/test_completion_bdd.py index 030a16ffc..f4ada848f 100644 --- a/tests/end2end/features/test_completion_bdd.py +++ b/tests/end2end/features/test_completion_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_downloads_bdd.py b/tests/end2end/features/test_downloads_bdd.py index 616c3cb14..25eb52aad 100644 --- a/tests/end2end/features/test_downloads_bdd.py +++ b/tests/end2end/features/test_downloads_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_editor_bdd.py b/tests/end2end/features/test_editor_bdd.py index f26e2956f..7d38be5af 100644 --- a/tests/end2end/features/test_editor_bdd.py +++ b/tests/end2end/features/test_editor_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_hints_bdd.py b/tests/end2end/features/test_hints_bdd.py index b5304cb74..f39a15391 100644 --- a/tests/end2end/features/test_hints_bdd.py +++ b/tests/end2end/features/test_hints_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_history_bdd.py b/tests/end2end/features/test_history_bdd.py index 871dbcb98..1fee533eb 100644 --- a/tests/end2end/features/test_history_bdd.py +++ b/tests/end2end/features/test_history_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_invoke_bdd.py b/tests/end2end/features/test_invoke_bdd.py index 86faf8107..5d463608e 100644 --- a/tests/end2end/features/test_invoke_bdd.py +++ b/tests/end2end/features/test_invoke_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_javascript_bdd.py b/tests/end2end/features/test_javascript_bdd.py index 78d45ab5a..ca65a9d1d 100644 --- a/tests/end2end/features/test_javascript_bdd.py +++ b/tests/end2end/features/test_javascript_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_keyinput_bdd.py b/tests/end2end/features/test_keyinput_bdd.py index d6b3134a3..ef5be0ee9 100644 --- a/tests/end2end/features/test_keyinput_bdd.py +++ b/tests/end2end/features/test_keyinput_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_marks_bdd.py b/tests/end2end/features/test_marks_bdd.py index 5e0e623f2..5b8e352dd 100644 --- a/tests/end2end/features/test_marks_bdd.py +++ b/tests/end2end/features/test_marks_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Ryan Roden-Corrent (rcorre) +# Copyright 2016-2017 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_misc_bdd.py b/tests/end2end/features/test_misc_bdd.py index 9f7ce63e6..e78840d68 100644 --- a/tests/end2end/features/test_misc_bdd.py +++ b/tests/end2end/features/test_misc_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_navigate_bdd.py b/tests/end2end/features/test_navigate_bdd.py index 5f922fedc..03812df83 100644 --- a/tests/end2end/features/test_navigate_bdd.py +++ b/tests/end2end/features/test_navigate_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_open_bdd.py b/tests/end2end/features/test_open_bdd.py index 48b7226f7..e5692c615 100644 --- a/tests/end2end/features/test_open_bdd.py +++ b/tests/end2end/features/test_open_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_prompts_bdd.py b/tests/end2end/features/test_prompts_bdd.py index 3ebd53e8c..7a95d48c8 100644 --- a/tests/end2end/features/test_prompts_bdd.py +++ b/tests/end2end/features/test_prompts_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_scroll_bdd.py b/tests/end2end/features/test_scroll_bdd.py index de1ed3e0a..69199ebf3 100644 --- a/tests/end2end/features/test_scroll_bdd.py +++ b/tests/end2end/features/test_scroll_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_search_bdd.py b/tests/end2end/features/test_search_bdd.py index caf592ce6..1b0c81488 100644 --- a/tests/end2end/features/test_search_bdd.py +++ b/tests/end2end/features/test_search_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_sessions_bdd.py b/tests/end2end/features/test_sessions_bdd.py index d05b4d434..052748f90 100644 --- a/tests/end2end/features/test_sessions_bdd.py +++ b/tests/end2end/features/test_sessions_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_set_bdd.py b/tests/end2end/features/test_set_bdd.py index 2eabd8c56..fa94de88f 100644 --- a/tests/end2end/features/test_set_bdd.py +++ b/tests/end2end/features/test_set_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_spawn_bdd.py b/tests/end2end/features/test_spawn_bdd.py index e9ddf0301..432f95a53 100644 --- a/tests/end2end/features/test_spawn_bdd.py +++ b/tests/end2end/features/test_spawn_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_tabs_bdd.py b/tests/end2end/features/test_tabs_bdd.py index bcae6d60d..e86204ba9 100644 --- a/tests/end2end/features/test_tabs_bdd.py +++ b/tests/end2end/features/test_tabs_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_urlmarks_bdd.py b/tests/end2end/features/test_urlmarks_bdd.py index 170fdd30b..554ede3ec 100644 --- a/tests/end2end/features/test_urlmarks_bdd.py +++ b/tests/end2end/features/test_urlmarks_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_utilcmds_bdd.py b/tests/end2end/features/test_utilcmds_bdd.py index f90d587f6..01bfb1440 100644 --- a/tests/end2end/features/test_utilcmds_bdd.py +++ b/tests/end2end/features/test_utilcmds_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_yankpaste_bdd.py b/tests/end2end/features/test_yankpaste_bdd.py index 9deb4b3cf..8f2f56938 100644 --- a/tests/end2end/features/test_yankpaste_bdd.py +++ b/tests/end2end/features/test_yankpaste_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/features/test_zoom_bdd.py b/tests/end2end/features/test_zoom_bdd.py index 3f8728222..3dc94a8cd 100644 --- a/tests/end2end/features/test_zoom_bdd.py +++ b/tests/end2end/features/test_zoom_bdd.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 5f99990d8..040110e77 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/fixtures/test_quteprocess.py b/tests/end2end/fixtures/test_quteprocess.py index 748310769..b40b5ebb6 100644 --- a/tests/end2end/fixtures/test_quteprocess.py +++ b/tests/end2end/fixtures/test_quteprocess.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/fixtures/test_testprocess.py b/tests/end2end/fixtures/test_testprocess.py index ef5b445ca..b38e79f56 100644 --- a/tests/end2end/fixtures/test_testprocess.py +++ b/tests/end2end/fixtures/test_testprocess.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/fixtures/test_webserver.py b/tests/end2end/fixtures/test_webserver.py index 5f0fa8ab0..c885ea5b6 100644 --- a/tests/end2end/fixtures/test_webserver.py +++ b/tests/end2end/fixtures/test_webserver.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/fixtures/testprocess.py b/tests/end2end/fixtures/testprocess.py index c6f4edd22..4c3a6972e 100644 --- a/tests/end2end/fixtures/testprocess.py +++ b/tests/end2end/fixtures/testprocess.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/fixtures/webserver.py b/tests/end2end/fixtures/webserver.py index c43765bf6..8fc674e42 100644 --- a/tests/end2end/fixtures/webserver.py +++ b/tests/end2end/fixtures/webserver.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py index 51a03e0b5..53c033f5b 100644 --- a/tests/end2end/fixtures/webserver_sub.py +++ b/tests/end2end/fixtures/webserver_sub.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/fixtures/webserver_sub_ssl.py b/tests/end2end/fixtures/webserver_sub_ssl.py index dadcb510e..d8a8f1025 100644 --- a/tests/end2end/fixtures/webserver_sub_ssl.py +++ b/tests/end2end/fixtures/webserver_sub_ssl.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/test_dirbrowser.py b/tests/end2end/test_dirbrowser.py index 2359020d7..3b27eebb3 100644 --- a/tests/end2end/test_dirbrowser.py +++ b/tests/end2end/test_dirbrowser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Daniel Schadt +# Copyright 2015-2017 Daniel Schadt # # This file is part of qutebrowser. # diff --git a/tests/end2end/test_hints_html.py b/tests/end2end/test_hints_html.py index c2d5143bc..d155ffcc4 100644 --- a/tests/end2end/test_hints_html.py +++ b/tests/end2end/test_hints_html.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/test_insert_mode.py b/tests/end2end/test_insert_mode.py index 8b9ed3aca..3bd5d87fd 100644 --- a/tests/end2end/test_insert_mode.py +++ b/tests/end2end/test_insert_mode.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index d683e9c85..e4470ac3a 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/end2end/test_mhtml_e2e.py b/tests/end2end/test_mhtml_e2e.py index 46ece8f0e..f753e6fd4 100644 --- a/tests/end2end/test_mhtml_e2e.py +++ b/tests/end2end/test_mhtml_e2e.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index f9e829e40..3af7195f5 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/helpers/logfail.py b/tests/helpers/logfail.py index 2967e3ccf..3d8e3afb8 100644 --- a/tests/helpers/logfail.py +++ b/tests/helpers/logfail.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/helpers/messagemock.py b/tests/helpers/messagemock.py index 7854aabcc..77116115f 100644 --- a/tests/helpers/messagemock.py +++ b/tests/helpers/messagemock.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index 3e028b0c9..df598e4ed 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/helpers/test_helper_utils.py b/tests/helpers/test_helper_utils.py index 65c7c29a1..d7eadb894 100644 --- a/tests/helpers/test_helper_utils.py +++ b/tests/helpers/test_helper_utils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/helpers/test_logfail.py b/tests/helpers/test_logfail.py index 6bc4f364c..b95dec1d6 100644 --- a/tests/helpers/test_logfail.py +++ b/tests/helpers/test_logfail.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/helpers/test_stubs.py b/tests/helpers/test_stubs.py index d542afe54..10fa9e5db 100644 --- a/tests/helpers/test_stubs.py +++ b/tests/helpers/test_stubs.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index 52a843dbc..1ef469144 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/test_conftest.py b/tests/test_conftest.py index 24fb67097..74db4bf78 100644 --- a/tests/test_conftest.py +++ b/tests/test_conftest.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/test_commands.py b/tests/unit/browser/test_commands.py index e7654a2d7..5a9d7a8da 100644 --- a/tests/unit/browser/test_commands.py +++ b/tests/unit/browser/test_commands.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/test_shared.py b/tests/unit/browser/test_shared.py index da81c2059..8cd1d6704 100644 --- a/tests/unit/browser/test_shared.py +++ b/tests/unit/browser/test_shared.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/test_signalfilter.py b/tests/unit/browser/test_signalfilter.py index bf8651d7f..4c02f89c4 100644 --- a/tests/unit/browser/test_signalfilter.py +++ b/tests/unit/browser/test_signalfilter.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/test_tab.py b/tests/unit/browser/test_tab.py index 7ff1edbde..bfd060956 100644 --- a/tests/unit/browser/test_tab.py +++ b/tests/unit/browser/test_tab.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webengine/test_webenginedownloads.py b/tests/unit/browser/webengine/test_webenginedownloads.py index 735852cd3..4ad146306 100644 --- a/tests/unit/browser/webengine/test_webenginedownloads.py +++ b/tests/unit/browser/webengine/test_webenginedownloads.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/http/test_content_disposition.py b/tests/unit/browser/webkit/http/test_content_disposition.py index 7b817c5e9..e1f78eb74 100644 --- a/tests/unit/browser/webkit/http/test_content_disposition.py +++ b/tests/unit/browser/webkit/http/test_content_disposition.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/http/test_http.py b/tests/unit/browser/webkit/http/test_http.py index ee80baed7..1f9b7d75d 100644 --- a/tests/unit/browser/webkit/http/test_http.py +++ b/tests/unit/browser/webkit/http/test_http.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/http/test_http_hypothesis.py b/tests/unit/browser/webkit/http/test_http_hypothesis.py index 1fb4dd835..42290a8f4 100644 --- a/tests/unit/browser/webkit/http/test_http_hypothesis.py +++ b/tests/unit/browser/webkit/http/test_http_hypothesis.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/network/test_filescheme.py b/tests/unit/browser/webkit/network/test_filescheme.py index c3ef870d6..7a1bd9b19 100644 --- a/tests/unit/browser/webkit/network/test_filescheme.py +++ b/tests/unit/browser/webkit/network/test_filescheme.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Antoni Boucher (antoyo) +# Copyright 2015-2017 Antoni Boucher (antoyo) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/network/test_networkmanager.py b/tests/unit/browser/webkit/network/test_networkmanager.py index 4ac93435d..3e8dd4d13 100644 --- a/tests/unit/browser/webkit/network/test_networkmanager.py +++ b/tests/unit/browser/webkit/network/test_networkmanager.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/network/test_networkreply.py b/tests/unit/browser/webkit/network/test_networkreply.py index 13e060c8a..8d24ebab8 100644 --- a/tests/unit/browser/webkit/network/test_networkreply.py +++ b/tests/unit/browser/webkit/network/test_networkreply.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/network/test_pac.py b/tests/unit/browser/webkit/network/test_pac.py index 2ad63a496..bc914bbfc 100644 --- a/tests/unit/browser/webkit/network/test_pac.py +++ b/tests/unit/browser/webkit/network/test_pac.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/network/test_schemehandler.py b/tests/unit/browser/webkit/network/test_schemehandler.py index d981b8412..1f5464a85 100644 --- a/tests/unit/browser/webkit/network/test_schemehandler.py +++ b/tests/unit/browser/webkit/network/test_schemehandler.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/network/test_webkitqutescheme.py b/tests/unit/browser/webkit/network/test_webkitqutescheme.py index f941abb6d..d45f1a31f 100644 --- a/tests/unit/browser/webkit/network/test_webkitqutescheme.py +++ b/tests/unit/browser/webkit/network/test_webkitqutescheme.py @@ -1,6 +1,7 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Daniel Schadt +# Copyright 2016-2017 Daniel Schadt +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/test_cache.py b/tests/unit/browser/webkit/test_cache.py index bc8efb675..f084a9869 100644 --- a/tests/unit/browser/webkit/test_cache.py +++ b/tests/unit/browser/webkit/test_cache.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 lamarpavel +# Copyright 2015-2017 lamarpavel # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/test_cookies.py b/tests/unit/browser/webkit/test_cookies.py index bcee01e60..85d045763 100644 --- a/tests/unit/browser/webkit/test_cookies.py +++ b/tests/unit/browser/webkit/test_cookies.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Alexander Cogneau (acogneau) : +# Copyright 2015-2017 Alexander Cogneau (acogneau) : # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/test_downloads.py b/tests/unit/browser/webkit/test_downloads.py index acee916b2..72b533cc7 100644 --- a/tests/unit/browser/webkit/test_downloads.py +++ b/tests/unit/browser/webkit/test_downloads.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/test_history.py b/tests/unit/browser/webkit/test_history.py index cc35dc4e4..37176dffe 100644 --- a/tests/unit/browser/webkit/test_history.py +++ b/tests/unit/browser/webkit/test_history.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/test_mhtml.py b/tests/unit/browser/webkit/test_mhtml.py index e5fdf4ffd..ad554cf3d 100644 --- a/tests/unit/browser/webkit/test_mhtml.py +++ b/tests/unit/browser/webkit/test_mhtml.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Daniel Schadt +# Copyright 2015-2017 Daniel Schadt # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/test_qt_javascript.py b/tests/unit/browser/webkit/test_qt_javascript.py index cb7c3a2ae..fd8a0c496 100644 --- a/tests/unit/browser/webkit/test_qt_javascript.py +++ b/tests/unit/browser/webkit/test_qt_javascript.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/test_tabhistory.py b/tests/unit/browser/webkit/test_tabhistory.py index b99d8376d..07b334771 100644 --- a/tests/unit/browser/webkit/test_tabhistory.py +++ b/tests/unit/browser/webkit/test_tabhistory.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/browser/webkit/test_webkitelem.py b/tests/unit/browser/webkit/test_webkitelem.py index c4384fa61..6258508ff 100644 --- a/tests/unit/browser/webkit/test_webkitelem.py +++ b/tests/unit/browser/webkit/test_webkitelem.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/commands/test_argparser.py b/tests/unit/commands/test_argparser.py index fc577fd15..54a584e71 100644 --- a/tests/unit/commands/test_argparser.py +++ b/tests/unit/commands/test_argparser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/commands/test_cmdutils.py b/tests/unit/commands/test_cmdutils.py index 577a85540..06740031c 100644 --- a/tests/unit/commands/test_cmdutils.py +++ b/tests/unit/commands/test_cmdutils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/commands/test_runners.py b/tests/unit/commands/test_runners.py index a7370081b..720fa5209 100644 --- a/tests/unit/commands/test_runners.py +++ b/tests/unit/commands/test_runners.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/commands/test_userscripts.py b/tests/unit/commands/test_userscripts.py index 5212b0d32..c6348d028 100644 --- a/tests/unit/commands/test_userscripts.py +++ b/tests/unit/commands/test_userscripts.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/completion/test_column_widths.py b/tests/unit/completion/test_column_widths.py index aae9c71aa..21456ed37 100644 --- a/tests/unit/completion/test_column_widths.py +++ b/tests/unit/completion/test_column_widths.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Alexander Cogneau +# Copyright 2015-2017 Alexander Cogneau # # This file is part of qutebrowser. # diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index 3f0f021bf..9feff4655 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Ryan Roden-Corrent (rcorre) +# Copyright 2016-2017 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # diff --git a/tests/unit/completion/test_completionwidget.py b/tests/unit/completion/test_completionwidget.py index 5a4ee87c0..9a8de3cad 100644 --- a/tests/unit/completion/test_completionwidget.py +++ b/tests/unit/completion/test_completionwidget.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Ryan Roden-Corrent (rcorre) +# Copyright 2016-2017 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index a767476a4..ff00a11a9 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Ryan Roden-Corrent (rcorre) +# Copyright 2016-2017 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # diff --git a/tests/unit/completion/test_sortfilter.py b/tests/unit/completion/test_sortfilter.py index cef394226..2d4a4e25d 100644 --- a/tests/unit/completion/test_sortfilter.py +++ b/tests/unit/completion/test_sortfilter.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index e98302604..c897b9f16 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -1,5 +1,5 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/tests/unit/config/test_configdata.py b/tests/unit/config/test_configdata.py index 3d0d046b2..50509820e 100644 --- a/tests/unit/config/test_configdata.py +++ b/tests/unit/config/test_configdata.py @@ -1,5 +1,5 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/tests/unit/config/test_configexc.py b/tests/unit/config/test_configexc.py index 3cb8397d4..330ad7b07 100644 --- a/tests/unit/config/test_configexc.py +++ b/tests/unit/config/test_configexc.py @@ -1,5 +1,5 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index d44303278..4cfb9100c 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1,5 +1,5 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/tests/unit/config/test_configtypes_hypothesis.py b/tests/unit/config/test_configtypes_hypothesis.py index ed45ccc0b..96f47622e 100644 --- a/tests/unit/config/test_configtypes_hypothesis.py +++ b/tests/unit/config/test_configtypes_hypothesis.py @@ -1,5 +1,5 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/tests/unit/config/test_style.py b/tests/unit/config/test_style.py index 6b55aebae..2e4d8c1ce 100644 --- a/tests/unit/config/test_style.py +++ b/tests/unit/config/test_style.py @@ -1,5 +1,5 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/tests/unit/config/test_textwrapper.py b/tests/unit/config/test_textwrapper.py index 9fb337091..a654f9367 100644 --- a/tests/unit/config/test_textwrapper.py +++ b/tests/unit/config/test_textwrapper.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py index 88cfecdc4..b9f013ecf 100644 --- a/tests/unit/javascript/conftest.py +++ b/tests/unit/javascript/conftest.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/javascript/position_caret/test_position_caret.py b/tests/unit/javascript/position_caret/test_position_caret.py index 2691c1afe..7be62e3cc 100644 --- a/tests/unit/javascript/position_caret/test_position_caret.py +++ b/tests/unit/javascript/position_caret/test_position_caret.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/keyinput/conftest.py b/tests/unit/keyinput/conftest.py index 75f318b53..28c24b2e2 100644 --- a/tests/unit/keyinput/conftest.py +++ b/tests/unit/keyinput/conftest.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) : +# Copyright 2015-2017 Florian Bruhin (The Compiler) : # # This file is part of qutebrowser. # diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py index da1ecfbdf..0cb0763e7 100644 --- a/tests/unit/keyinput/test_basekeyparser.py +++ b/tests/unit/keyinput/test_basekeyparser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) : +# Copyright 2014-2017 Florian Bruhin (The Compiler) : # # This file is part of qutebrowser. # diff --git a/tests/unit/keyinput/test_modeman.py b/tests/unit/keyinput/test_modeman.py index aa3a2753a..d00ce09f7 100644 --- a/tests/unit/keyinput/test_modeman.py +++ b/tests/unit/keyinput/test_modeman.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/keyinput/test_modeparsers.py b/tests/unit/keyinput/test_modeparsers.py index c01e0e5ba..d7da17129 100644 --- a/tests/unit/keyinput/test_modeparsers.py +++ b/tests/unit/keyinput/test_modeparsers.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) : +# Copyright 2015-2017 Florian Bruhin (The Compiler) : # # This file is part of qutebrowser. # diff --git a/tests/unit/mainwindow/statusbar/test_percentage.py b/tests/unit/mainwindow/statusbar/test_percentage.py index 0974e34ed..6622b06f4 100644 --- a/tests/unit/mainwindow/statusbar/test_percentage.py +++ b/tests/unit/mainwindow/statusbar/test_percentage.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/mainwindow/statusbar/test_progress.py b/tests/unit/mainwindow/statusbar/test_progress.py index 70865e104..f553f8c22 100644 --- a/tests/unit/mainwindow/statusbar/test_progress.py +++ b/tests/unit/mainwindow/statusbar/test_progress.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/mainwindow/statusbar/test_tabindex.py b/tests/unit/mainwindow/statusbar/test_tabindex.py index ca5673da2..99b5d9114 100644 --- a/tests/unit/mainwindow/statusbar/test_tabindex.py +++ b/tests/unit/mainwindow/statusbar/test_tabindex.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/mainwindow/statusbar/test_textbase.py b/tests/unit/mainwindow/statusbar/test_textbase.py index 1fb661296..b976f5c6f 100644 --- a/tests/unit/mainwindow/statusbar/test_textbase.py +++ b/tests/unit/mainwindow/statusbar/test_textbase.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/mainwindow/statusbar/test_url.py b/tests/unit/mainwindow/statusbar/test_url.py index 78f7ab646..fc8ffb705 100644 --- a/tests/unit/mainwindow/statusbar/test_url.py +++ b/tests/unit/mainwindow/statusbar/test_url.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Clayton Craft (craftyguy) +# Copyright 2016-2017 Clayton Craft (craftyguy) # # This file is part of qutebrowser. # diff --git a/tests/unit/mainwindow/test_messageview.py b/tests/unit/mainwindow/test_messageview.py index ba77f3a49..ebecc9398 100644 --- a/tests/unit/mainwindow/test_messageview.py +++ b/tests/unit/mainwindow/test_messageview.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/mainwindow/test_prompt.py b/tests/unit/mainwindow/test_prompt.py index dcc5461a5..6fa8592af 100644 --- a/tests/unit/mainwindow/test_prompt.py +++ b/tests/unit/mainwindow/test_prompt.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py index 7a3fd68af..b8c12eaee 100644 --- a/tests/unit/mainwindow/test_tabwidget.py +++ b/tests/unit/mainwindow/test_tabwidget.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Daniel Schadt +# Copyright 2015-2017 Daniel Schadt # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_autoupdate.py b/tests/unit/misc/test_autoupdate.py index eb7ed6f91..d803e4877 100644 --- a/tests/unit/misc/test_autoupdate.py +++ b/tests/unit/misc/test_autoupdate.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Alexander Cogneau (acogneau) : +# Copyright 2015-2017 Alexander Cogneau (acogneau) : # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_checkpyver.py b/tests/unit/misc/test_checkpyver.py index 4c2cf3b7d..aeab032e7 100644 --- a/tests/unit/misc/test_checkpyver.py +++ b/tests/unit/misc/test_checkpyver.py @@ -1,4 +1,4 @@ -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # This file is part of qutebrowser. diff --git a/tests/unit/misc/test_cmdhistory.py b/tests/unit/misc/test_cmdhistory.py index 47284c075..7da721c2b 100644 --- a/tests/unit/misc/test_cmdhistory.py +++ b/tests/unit/misc/test_cmdhistory.py @@ -1,7 +1,7 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Alexander Cogneau (acogneau) -# Copyright 2015-2016 Florian Bruhin (The-Compiler) +# Copyright 2015-2017 Alexander Cogneau (acogneau) +# Copyright 2015-2017 Florian Bruhin (The-Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_crashdialog.py b/tests/unit/misc/test_crashdialog.py index dd2ebd35e..89ca342ee 100644 --- a/tests/unit/misc/test_crashdialog.py +++ b/tests/unit/misc/test_crashdialog.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_earlyinit.py b/tests/unit/misc/test_earlyinit.py index e61b06475..840936853 100644 --- a/tests/unit/misc/test_earlyinit.py +++ b/tests/unit/misc/test_earlyinit.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The-Compiler) +# Copyright 2016-2017 Florian Bruhin (The-Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index 193f5fa30..de9125c8b 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index 2d804dbe5..3101b7427 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_ipc.py b/tests/unit/misc/test_ipc.py index eaa74b1ff..cf5cc9a3f 100644 --- a/tests/unit/misc/test_ipc.py +++ b/tests/unit/misc/test_ipc.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_keyhints.py b/tests/unit/misc/test_keyhints.py index 34eea85b5..b1255eb2c 100644 --- a/tests/unit/misc/test_keyhints.py +++ b/tests/unit/misc/test_keyhints.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Ryan Roden-Corrent (rcorre) +# Copyright 2016-2017 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_lineparser.py b/tests/unit/misc/test_lineparser.py index 546d53cf9..0d2b3a52e 100644 --- a/tests/unit/misc/test_lineparser.py +++ b/tests/unit/misc/test_lineparser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_miscwidgets.py b/tests/unit/misc/test_miscwidgets.py index dd17ac254..03441d9f2 100644 --- a/tests/unit/misc/test_miscwidgets.py +++ b/tests/unit/misc/test_miscwidgets.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_msgbox.py b/tests/unit/misc/test_msgbox.py index 311680266..2c1268bd8 100644 --- a/tests/unit/misc/test_msgbox.py +++ b/tests/unit/misc/test_msgbox.py @@ -1,4 +1,4 @@ -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # This file is part of qutebrowser. diff --git a/tests/unit/misc/test_pastebin.py b/tests/unit/misc/test_pastebin.py index b60b319a6..5591bfd0f 100644 --- a/tests/unit/misc/test_pastebin.py +++ b/tests/unit/misc/test_pastebin.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Anna Kobak (avk) : +# Copyright 2016-2017 Anna Kobak (avk) : # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_readline.py b/tests/unit/misc/test_readline.py index b0c1403a1..6c168774c 100644 --- a/tests/unit/misc/test_readline.py +++ b/tests/unit/misc/test_readline.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_sessions.py b/tests/unit/misc/test_sessions.py index 4bef9b9a4..c30ac6bdc 100644 --- a/tests/unit/misc/test_sessions.py +++ b/tests/unit/misc/test_sessions.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_split.py b/tests/unit/misc/test_split.py index 7b3c8015a..179084849 100644 --- a/tests/unit/misc/test_split.py +++ b/tests/unit/misc/test_split.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_split_hypothesis.py b/tests/unit/misc/test_split_hypothesis.py index 52eb8cb11..c63ddff09 100644 --- a/tests/unit/misc/test_split_hypothesis.py +++ b/tests/unit/misc/test_split_hypothesis.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/misc/test_utilcmds.py b/tests/unit/misc/test_utilcmds.py index 41c2f1f15..e4b686e31 100644 --- a/tests/unit/misc/test_utilcmds.py +++ b/tests/unit/misc/test_utilcmds.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/scripts/test_check_coverage.py b/tests/unit/scripts/test_check_coverage.py index 16182bcf7..8b80d6e44 100644 --- a/tests/unit/scripts/test_check_coverage.py +++ b/tests/unit/scripts/test_check_coverage.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/tests/unit/scripts/test_run_vulture.py b/tests/unit/scripts/test_run_vulture.py index 91f7a0744..c19d70b29 100644 --- a/tests/unit/scripts/test_run_vulture.py +++ b/tests/unit/scripts/test_run_vulture.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/tests/unit/test_app.py b/tests/unit/test_app.py index ecb3ec46e..080399ecc 100644 --- a/tests/unit/test_app.py +++ b/tests/unit/test_app.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/overflow_test_cases.py b/tests/unit/utils/overflow_test_cases.py index 308e5f9a3..450522d6b 100644 --- a/tests/unit/utils/overflow_test_cases.py +++ b/tests/unit/utils/overflow_test_cases.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/test_debug.py b/tests/unit/utils/test_debug.py index 7bcad59ec..76c84fee1 100644 --- a/tests/unit/utils/test_debug.py +++ b/tests/unit/utils/test_debug.py @@ -1,4 +1,4 @@ -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # diff --git a/tests/unit/utils/test_error.py b/tests/unit/utils/test_error.py index abcc72c44..4f4905365 100644 --- a/tests/unit/utils/test_error.py +++ b/tests/unit/utils/test_error.py @@ -1,4 +1,4 @@ -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # This file is part of qutebrowser. diff --git a/tests/unit/utils/test_javascript.py b/tests/unit/utils/test_javascript.py index 298a75312..54c32ee92 100644 --- a/tests/unit/utils/test_javascript.py +++ b/tests/unit/utils/test_javascript.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/test_jinja.py b/tests/unit/utils/test_jinja.py index 289b2c26b..5d8798b96 100644 --- a/tests/unit/utils/test_jinja.py +++ b/tests/unit/utils/test_jinja.py @@ -1,4 +1,4 @@ -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py index 3c1e82fc0..147e760bb 100644 --- a/tests/unit/utils/test_log.py +++ b/tests/unit/utils/test_log.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py index e0b4bf934..45daa36d8 100644 --- a/tests/unit/utils/test_qtutils.py +++ b/tests/unit/utils/test_qtutils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py index 69acd2884..bc7a58975 100644 --- a/tests/unit/utils/test_standarddir.py +++ b/tests/unit/utils/test_standarddir.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/test_typing.py b/tests/unit/utils/test_typing.py index fc507299c..4a3359a45 100644 --- a/tests/unit/utils/test_typing.py +++ b/tests/unit/utils/test_typing.py @@ -1,4 +1,4 @@ -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 9b3c5d1c3..74c244b7b 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index d42f7a971..7eb4008a6 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 13c0a5d13..96f4bf88f 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/usertypes/test_enum.py b/tests/unit/utils/usertypes/test_enum.py index b78251171..a5d2f33e5 100644 --- a/tests/unit/utils/usertypes/test_enum.py +++ b/tests/unit/utils/usertypes/test_enum.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/usertypes/test_misc.py b/tests/unit/utils/usertypes/test_misc.py index c2c0738e5..d21816ace 100644 --- a/tests/unit/utils/usertypes/test_misc.py +++ b/tests/unit/utils/usertypes/test_misc.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016 Florian Bruhin (The Compiler) +# Copyright 2016-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/usertypes/test_neighborlist.py b/tests/unit/utils/usertypes/test_neighborlist.py index 7bfbe3e2c..751f940a3 100644 --- a/tests/unit/utils/usertypes/test_neighborlist.py +++ b/tests/unit/utils/usertypes/test_neighborlist.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2016 Florian Bruhin (The Compiler) +# Copyright 2014-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/usertypes/test_question.py b/tests/unit/utils/usertypes/test_question.py index 119d4ee3f..031e304cc 100644 --- a/tests/unit/utils/usertypes/test_question.py +++ b/tests/unit/utils/usertypes/test_question.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/tests/unit/utils/usertypes/test_timer.py b/tests/unit/utils/usertypes/test_timer.py index e48bd332b..d120d82e6 100644 --- a/tests/unit/utils/usertypes/test_timer.py +++ b/tests/unit/utils/usertypes/test_timer.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2016 Florian Bruhin (The Compiler) +# Copyright 2015-2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # From b91d4ee9c2598caa40f1705355d3b58f22e5edea Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 May 2017 22:02:30 +0200 Subject: [PATCH 294/299] Clean up :debug-webaction --- doc/help/commands.asciidoc | 2 +- qutebrowser/browser/browsertab.py | 17 ++++++++- qutebrowser/browser/commands.py | 35 ++++--------------- qutebrowser/browser/webengine/webenginetab.py | 10 +++--- qutebrowser/browser/webkit/webkittab.py | 3 ++ 5 files changed, 32 insertions(+), 35 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 1a76e7fc8..c79d83d08 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -1655,7 +1655,7 @@ Syntax: +:debug-webaction 'action'+ Execute a webaction. -See http://doc.qt.io/qt-5/qwebpage.html#WebAction-enum for the available actions. +Available actions: http://doc.qt.io/archives/qt-5.5/qwebpage.html#WebAction-enum (WebKit) http://doc.qt.io/qt-5/qwebenginepage.html#WebAction-enum (WebEngine) ==== positional arguments * +'action'+: The action to execute, e.g. MoveToNextChar. diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 8e705d9df..62fb67453 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -105,7 +105,15 @@ class TabData: class AbstractAction: - """Attribute of AbstractTab for Qt WebActions.""" + """Attribute of AbstractTab for Qt WebActions. + + Class attributes (overridden by subclasses): + action_class: The class actions are defined on (QWeb{Engine,}Page) + action_base: The type of the actions (QWeb{Engine,}Page.WebAction) + """ + + action_class = None + action_base = None def __init__(self): self._widget = None @@ -118,6 +126,13 @@ class AbstractAction: """Save the current page.""" raise NotImplementedError + def run_string(self, name): + """Run a webaction based on its name.""" + member = getattr(self.action_class, name, None) + if not isinstance(member, self.action_base): + raise WebTabError("{} is not a valid web action!".format(name)) + self._widget.triggerPageAction(member) + class AbstractPrinting: diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 12de21b33..6b34e4d30 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -28,14 +28,6 @@ from PyQt5.QtWidgets import QApplication, QTabBar from PyQt5.QtCore import Qt, QUrl, QEvent, QUrlQuery from PyQt5.QtGui import QKeyEvent from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog -try: - from PyQt5.QtWebKitWidgets import QWebPage -except ImportError: - QWebPage = None -try: - from PyQt5.QtWebEngineWidgets import QWebEnginePage -except ImportError: - QWebEnginePage = None import pygments import pygments.lexers import pygments.formatters @@ -1905,33 +1897,20 @@ class CommandDispatcher: def debug_webaction(self, action, count=1): """Execute a webaction. - See http://doc.qt.io/qt-5/qwebpage.html#WebAction-enum for the - available actions. + Available actions: + http://doc.qt.io/archives/qt-5.5/qwebpage.html#WebAction-enum (WebKit) + http://doc.qt.io/qt-5/qwebenginepage.html#WebAction-enum (WebEngine) Args: action: The action to execute, e.g. MoveToNextChar. count: How many times to repeat the action. """ tab = self._current_widget() - - if tab.backend == usertypes.Backend.QtWebKit: - assert QWebPage is not None - member = getattr(QWebPage, action, None) - base = QWebPage.WebAction - elif tab.backend == usertypes.Backend.QtWebEngine: - assert QWebEnginePage is not None - member = getattr(QWebEnginePage, action, None) - base = QWebEnginePage.WebAction - - if not isinstance(member, base): - raise cmdexc.CommandError("{} is not a valid web action!".format( - action)) - for _ in range(count): - # This whole command is backend-specific anyways, so it makes no - # sense to introduce some API for this. - # pylint: disable=protected-access - tab._widget.triggerPageAction(member) + try: + tab.action.run_string(action) + except browsertab.WebTabError as e: + raise cmdexc.CommandError(str(e)) @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_cmd_split=True) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 5d2c171c3..af93786d6 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -79,17 +79,17 @@ _JS_WORLD_MAP = { class WebEngineAction(browsertab.AbstractAction): - """QtWebKit implementations related to web actions.""" + """QtWebEngine implementations related to web actions.""" - def _action(self, action): - self._widget.triggerPageAction(action) + action_class = QWebEnginePage + action_base = QWebEnginePage.WebAction def exit_fullscreen(self): - self._action(QWebEnginePage.ExitFullScreen) + self._widget.triggerPageAction(QWebEnginePage.ExitFullScreen) def save_page(self): """Save the current page.""" - self._action(QWebEnginePage.SavePage) + self._widget.triggerPageAction(QWebEnginePage.SavePage) class WebEnginePrinting(browsertab.AbstractPrinting): diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 3de652887..daf46a503 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -57,6 +57,9 @@ class WebKitAction(browsertab.AbstractAction): """QtWebKit implementations related to web actions.""" + action_class = QWebPage + action_base = QWebPage.WebAction + def exit_fullscreen(self): raise browsertab.UnsupportedOperationError From 56d42b6c82145288e6d6e897889b6f3cf8d86c50 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 9 May 2017 22:16:24 +0200 Subject: [PATCH 295/299] Update requests from 2.13.0 to 2.14.1 --- misc/requirements/requirements-codecov.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 6c3864d96..5fee39d9a 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -2,4 +2,4 @@ codecov==2.0.9 coverage==4.4 -requests==2.13.0 +requests==2.14.1 From a0a9c9d32e841bfef8dc26f4da96b84ac51447c0 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 9 May 2017 22:16:25 +0200 Subject: [PATCH 296/299] Update requests from 2.13.0 to 2.14.1 --- misc/requirements/requirements-pylint-master.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint-master.txt b/misc/requirements/requirements-pylint-master.txt index 8c08b4e7b..22622f14c 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -8,7 +8,7 @@ lazy-object-proxy==1.3.1 mccabe==0.6.1 -e git+https://github.com/PyCQA/pylint.git#egg=pylint ./scripts/dev/pylint_checkers -requests==2.13.0 +requests==2.14.1 uritemplate==3.0.0 uritemplate.py==3.0.2 wrapt==1.10.10 From 3bd7e33c4a5ee609eb962a333068ee820a4b8b77 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 9 May 2017 22:16:27 +0200 Subject: [PATCH 297/299] Update requests from 2.13.0 to 2.14.1 --- misc/requirements/requirements-pylint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 647661e2f..2c6cf7729 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -7,7 +7,7 @@ lazy-object-proxy==1.3.1 mccabe==0.6.1 pylint==1.7.1 ./scripts/dev/pylint_checkers -requests==2.13.0 +requests==2.14.1 uritemplate==3.0.0 uritemplate.py==3.0.2 wrapt==1.10.10 From 1973e61424efb124f453ce0894d1c8b3e5ea99c3 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 10 May 2017 00:08:30 +0200 Subject: [PATCH 298/299] Update hypothesis from 3.8.2 to 3.8.3 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 7717e47d1..eca85541a 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -9,7 +9,7 @@ EasyProcess==0.2.3 Flask==0.12.1 glob2==0.5 httpbin==0.5.0 -hypothesis==3.8.2 +hypothesis==3.8.3 itsdangerous==0.24 # Jinja2==2.9.5 Mako==1.0.6 From 661c0f7b7c74c23db0a8ed2dfa111a711c5f8771 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 10 May 2017 00:08:34 +0200 Subject: [PATCH 299/299] Update pytest-cov from 2.4.0 to 2.5.0 --- misc/requirements/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 7717e47d1..c1108e9b9 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -21,7 +21,7 @@ pytest==3.0.7 pytest-bdd==2.18.2 pytest-benchmark==3.0.0 pytest-catchlog==1.2.2 -pytest-cov==2.4.0 +pytest-cov==2.5.0 pytest-faulthandler==1.3.1 pytest-instafail==0.3.0 pytest-mock==1.6.0
{{curr_date.strftime("%a, %d %B %Y")}}
{{title}} + {{title}} + {{host}} + {{time.strftime("%X")}}

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 literal 27002 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 From a4c3aaaf1d2b06a05d5c7bb4cba2a14bd26db775 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 10 Apr 2017 18:34:47 +0200 Subject: [PATCH 165/299] Update setuptools from 34.4.0 to 34.4.1 --- misc/requirements/requirements-pip.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt index 90c042c8e..5bd2aeac8 100644 --- a/misc/requirements/requirements-pip.txt +++ b/misc/requirements/requirements-pip.txt @@ -3,6 +3,6 @@ appdirs==1.4.3 packaging==16.8 pyparsing==2.2.0 -setuptools==34.4.0 +setuptools==34.4.1 six==1.10.0 wheel==0.29.0 From 13c5150e58fa42b5957da551cf830a868d10c49e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 11 Apr 2017 21:19:36 +0200 Subject: [PATCH 166/299] Update docs --- CHANGELOG.asciidoc | 2 ++ README.asciidoc | 1 + doc/qutebrowser.1.asciidoc | 9 +++------ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 81ad97343..00170f731 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -26,6 +26,8 @@ Added - New `-s` option for `:open` to force a HTTPS scheme. - `:debug-log-filter` now accepts `none` as an argument to clear any log filters. +- New `--debug-flag` argument which replaces `--debug-exit` and + `--pdb-postmortem`. Changed ~~~~~~~ diff --git a/README.asciidoc b/README.asciidoc index 96b795463..64e633067 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -186,6 +186,7 @@ Contributors, sorted by the number of commits in descending order: * John ShaggyTwoDope Jenkins * Clayton Craft * Peter Vilim +* Jacob Sword * knaggita * Oliver Caldwell * Julian Weigt diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc index 36530bffe..cd95bf8e5 100644 --- a/doc/qutebrowser.1.asciidoc +++ b/doc/qutebrowser.1.asciidoc @@ -93,12 +93,6 @@ show it. *--nowindow*:: Don't show the main window. -*--debug-exit*:: - Turn on debugging of late exit. - -*--pdb-postmortem*:: - Drop into pdb on exceptions. - *--temp-basedir*:: Use a temporary basedir. @@ -110,6 +104,9 @@ show it. *--qt-flag* 'QT_FLAG':: Pass an argument to Qt as flag. + +*--debug-flag* 'DEBUG_FLAGS':: + Pass name of debugging feature to be turned on. // QUTE_OPTIONS_END == FILES From 20b17f3fb1cb9b82a874923b3193be44d5a4f8ee Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 11 Apr 2017 21:19:49 +0200 Subject: [PATCH 167/299] Improve --debug-flag error message --- qutebrowser/qutebrowser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 3ce11b07b..4a73c1162 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -156,8 +156,8 @@ def debug_flag_error(flag): if flag in valid_flags: return flag else: - raise argparse.ArgumentTypeError("Invalid flag - valid flags include: " + - str(valid_flags)) + raise argparse.ArgumentTypeError("Invalid debug flag - valid flags: {}" + .format(', '.join(valid_flags))) def main(): From c47da15bb143f889ad703172194cb21cf6ae9de0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 11 Apr 2017 21:26:23 +0200 Subject: [PATCH 168/299] Remove nargs=1 for --debug-flag Otherwisse we get [['foo'], ['bar']] from argparse... --- qutebrowser/qutebrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index 4a73c1162..cd9ec5938 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -116,7 +116,7 @@ def get_argparser(): nargs=1, action='append') debug.add_argument('--debug-flag', type=debug_flag_error, default=[], help="Pass name of debugging feature to be turned on.", - nargs=1, action='append', dest='debug_flags') + action='append', dest='debug_flags') parser.add_argument('command', nargs='*', help="Commands to execute on " "startup.", metavar=':command') # URLs will actually be in command From 75f8d2a1d1bf7375b11f29981b98617906618f1a Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 12 Apr 2017 06:52:26 +0200 Subject: [PATCH 169/299] Test if stdin gets closed when starting QProcess --- tests/end2end/data/userscripts/test_stdinclose.py | 7 +++++++ tests/end2end/features/spawn.feature | 5 +++++ 2 files changed, 12 insertions(+) create mode 100755 tests/end2end/data/userscripts/test_stdinclose.py diff --git a/tests/end2end/data/userscripts/test_stdinclose.py b/tests/end2end/data/userscripts/test_stdinclose.py new file mode 100755 index 000000000..f3f4e4545 --- /dev/null +++ b/tests/end2end/data/userscripts/test_stdinclose.py @@ -0,0 +1,7 @@ +#!/usr/bin/python3 + +import sys +import os +sys.stdin.read() +with open(os.environ['QUTE_FIFO'], 'wb') as fifo: + fifo.write(b':message-info "stdin closed"\n') diff --git a/tests/end2end/features/spawn.feature b/tests/end2end/features/spawn.feature index 8e3f88bd1..f5108b9e7 100644 --- a/tests/end2end/features/spawn.feature +++ b/tests/end2end/features/spawn.feature @@ -55,3 +55,8 @@ Feature: :spawn Then the following tabs should be open: - about:blank - about:blank (active) + + Scenario: Running :spawn with userscript that expects the stdin getting closed + When I open about:blank + And I run :spawn -u (testdata)/userscripts/test_stdinclose.py + Then "stdin closed" should be logged From bc7f8018c0bfada45f53cc885e6e1a21f4d677f7 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 12 Apr 2017 06:56:38 +0200 Subject: [PATCH 170/299] Close stdin after starting QProcess Fixes 2491 --- qutebrowser/misc/guiprocess.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index eb0a036e5..16bbbb3ba 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -142,12 +142,14 @@ class GUIProcess(QObject): self._proc.start(cmd, args) else: self._proc.start(cmd, args, mode) + self._proc.closeWriteChannel(); def start_detached(self, cmd, args, cwd=None): """Convenience wrapper around QProcess::startDetached.""" log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, _pid = self._proc.startDetached(cmd, args, cwd) + self._proc.closeWriteChannel(); if ok: log.procs.debug("Process started.") From 424d0aec5a403cbf23557ea878ef0b1695c21148 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 12 Apr 2017 07:31:24 +0200 Subject: [PATCH 171/299] change test_stdinclose.py to stdinclose.py --- .../data/userscripts/{test_stdinclose.py => stdinclose.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/end2end/data/userscripts/{test_stdinclose.py => stdinclose.py} (100%) diff --git a/tests/end2end/data/userscripts/test_stdinclose.py b/tests/end2end/data/userscripts/stdinclose.py similarity index 100% rename from tests/end2end/data/userscripts/test_stdinclose.py rename to tests/end2end/data/userscripts/stdinclose.py From 3d549bf60709a539bf1ddb4cc3fa0c53c811c631 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 12 Apr 2017 07:32:12 +0200 Subject: [PATCH 172/299] Remove closeWriteChannel from detached start --- qutebrowser/misc/guiprocess.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 16bbbb3ba..8f986d1b1 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -142,14 +142,13 @@ class GUIProcess(QObject): self._proc.start(cmd, args) else: self._proc.start(cmd, args, mode) - self._proc.closeWriteChannel(); + self._proc.closeWriteChannel() def start_detached(self, cmd, args, cwd=None): """Convenience wrapper around QProcess::startDetached.""" log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, _pid = self._proc.startDetached(cmd, args, cwd) - self._proc.closeWriteChannel(); if ok: log.procs.debug("Process started.") From 590a9b4f7833f414ba397c5482c770e3fbab3642 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 12 Apr 2017 07:32:40 +0200 Subject: [PATCH 173/299] Indent with spaces and minor changes --- tests/end2end/features/spawn.feature | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/end2end/features/spawn.feature b/tests/end2end/features/spawn.feature index f5108b9e7..3d69cdd9a 100644 --- a/tests/end2end/features/spawn.feature +++ b/tests/end2end/features/spawn.feature @@ -57,6 +57,5 @@ Feature: :spawn - about:blank (active) Scenario: Running :spawn with userscript that expects the stdin getting closed - When I open about:blank - And I run :spawn -u (testdata)/userscripts/test_stdinclose.py - Then "stdin closed" should be logged + When I run :spawn -u (testdata)/userscripts/stdinclose.py + Then the message "stdin closed" should be shown From b784ddedddc6503bd79f5e4d376d322386bbb839 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 12 Apr 2017 07:40:11 +0200 Subject: [PATCH 174/299] Also test stdin close for detached start --- tests/end2end/features/spawn.feature | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/end2end/features/spawn.feature b/tests/end2end/features/spawn.feature index 3d69cdd9a..bea671f32 100644 --- a/tests/end2end/features/spawn.feature +++ b/tests/end2end/features/spawn.feature @@ -59,3 +59,7 @@ Feature: :spawn Scenario: Running :spawn with userscript that expects the stdin getting closed When I run :spawn -u (testdata)/userscripts/stdinclose.py Then the message "stdin closed" should be shown + + Scenario: Running :spawn -d with userscript that expects the stdin getting closed + When I run :spawn -d -u (testdata)/userscripts/stdinclose.py + Then the message "stdin closed" should be shown From c38dc95c23235a464051a06287bcf95d9f17971d Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 12 Apr 2017 07:59:40 +0200 Subject: [PATCH 175/299] Add posix to stdin test beacause the py script fails on windows --- tests/end2end/features/spawn.feature | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/end2end/features/spawn.feature b/tests/end2end/features/spawn.feature index bea671f32..dc0485391 100644 --- a/tests/end2end/features/spawn.feature +++ b/tests/end2end/features/spawn.feature @@ -56,10 +56,12 @@ Feature: :spawn - about:blank - about:blank (active) + @posix Scenario: Running :spawn with userscript that expects the stdin getting closed When I run :spawn -u (testdata)/userscripts/stdinclose.py Then the message "stdin closed" should be shown - + + @posix Scenario: Running :spawn -d with userscript that expects the stdin getting closed When I run :spawn -d -u (testdata)/userscripts/stdinclose.py Then the message "stdin closed" should be shown From ff767dd9659812c83032f1b1d062b98b75119887 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 12 Apr 2017 08:47:39 +0200 Subject: [PATCH 176/299] Add neccessary metadata to py script --- tests/end2end/data/userscripts/stdinclose.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/end2end/data/userscripts/stdinclose.py b/tests/end2end/data/userscripts/stdinclose.py index f3f4e4545..d1397d18c 100755 --- a/tests/end2end/data/userscripts/stdinclose.py +++ b/tests/end2end/data/userscripts/stdinclose.py @@ -1,4 +1,22 @@ #!/usr/bin/python3 +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2015-2017 Florian Bruhin (The Compiler) +# +# 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 . import sys import os From b00c1dc9060c27e23c7d42ca2d8b1300a510d708 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 12 Apr 2017 09:23:29 +0200 Subject: [PATCH 177/299] Add docstring --- tests/end2end/data/userscripts/stdinclose.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/end2end/data/userscripts/stdinclose.py b/tests/end2end/data/userscripts/stdinclose.py index d1397d18c..9b2ade1c0 100755 --- a/tests/end2end/data/userscripts/stdinclose.py +++ b/tests/end2end/data/userscripts/stdinclose.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2017 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -18,6 +18,8 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . +"""A userscript to check if the stdin gets closed""" + import sys import os sys.stdin.read() From 68c655bd9c62d2a1da4d0294c3cdf6130ff561e8 Mon Sep 17 00:00:00 2001 From: Fritz Reichwald Date: Wed, 12 Apr 2017 10:21:03 +0200 Subject: [PATCH 178/299] Add period at end of docstring to make flake happy --- tests/end2end/data/userscripts/stdinclose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/data/userscripts/stdinclose.py b/tests/end2end/data/userscripts/stdinclose.py index 9b2ade1c0..fa0676f73 100755 --- a/tests/end2end/data/userscripts/stdinclose.py +++ b/tests/end2end/data/userscripts/stdinclose.py @@ -18,7 +18,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""A userscript to check if the stdin gets closed""" +"""A userscript to check if the stdin gets closed.""" import sys import os From fdaff02a582285c991bfaeb5b1d8671d9956015e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 12 Apr 2017 12:43:38 +0200 Subject: [PATCH 179/299] Update docs --- CHANGELOG.asciidoc | 1 + README.asciidoc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 00170f731..9b7b809c9 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -43,6 +43,7 @@ Changed options instead of being before sections. - The HTTP cache is disabled with QtWebKit on Qt 5.8 now as it leads to frequent crashes due to a Qt bug. +- stdin is now closed immediately for processes spawned from qutebrowser Fixed ~~~~~ diff --git a/README.asciidoc b/README.asciidoc index 64e633067..f79812850 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -164,6 +164,7 @@ Contributors, sorted by the number of commits in descending order: * Patric Schmitz * Tarcisio Fedrizzi * Claude +* Fritz Reichwald * Corentin Julé * meles5 * Philipp Hansch @@ -172,7 +173,6 @@ Contributors, sorted by the number of commits in descending order: * Nathan Isom * Thorsten Wißmann * Austin Anderson -* Fritz Reichwald * Jimmy * Niklas Haas * Maciej Wołczyk From 4a480e6f5fd565f7186b3f083c4ff1737341f378 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 12 Apr 2017 13:24:10 +0200 Subject: [PATCH 180/299] Ignore Chromium NETLINK message --- tests/end2end/fixtures/quteprocess.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 15157cc78..80643b131 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -79,6 +79,8 @@ def is_ignored_lowlevel_message(message): return True elif message == 'getrlimit(RLIMIT_NOFILE) failed': return True + elif message == 'Could not bind NETLINK socket: Address already in use': + return True return False From 4511d042a1b0dc2ec3174716da8696dd6a87202c Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 13 Apr 2017 17:30:03 +0200 Subject: [PATCH 181/299] Update astroid from 1.4.9 to 1.5.1 --- misc/requirements/requirements-pylint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 12d94c8eb..6e311ca44 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==1.4.9 +astroid==1.5.1 github3.py==0.9.6 isort==4.2.5 lazy-object-proxy==1.2.2 From 10b1c954b2137e6a5b7581b50795b023942ff142 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 13 Apr 2017 17:30:12 +0200 Subject: [PATCH 182/299] Update pylint from 1.6.5 to 1.7.0 --- misc/requirements/requirements-pylint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 12d94c8eb..615eeb18e 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -5,7 +5,7 @@ github3.py==0.9.6 isort==4.2.5 lazy-object-proxy==1.2.2 mccabe==0.6.1 -pylint==1.6.5 +pylint==1.7.0 ./scripts/dev/pylint_checkers requests==2.13.0 uritemplate==3.0.0 From 1d0f187fab71a8817d2a68e94788b1c9e80462a7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 13 Apr 2017 18:00:36 +0200 Subject: [PATCH 183/299] Adjustments for new pylint version --- qutebrowser/browser/commands.py | 6 +++--- qutebrowser/browser/downloads.py | 2 +- qutebrowser/browser/webengine/webenginedownloads.py | 3 ++- qutebrowser/browser/webengine/webengineelem.py | 2 +- qutebrowser/browser/webengine/webenginetab.py | 2 +- qutebrowser/browser/webkit/rfc6266.py | 3 --- qutebrowser/commands/argparser.py | 8 ++++---- qutebrowser/completion/models/base.py | 2 +- qutebrowser/config/config.py | 6 ++---- qutebrowser/mainwindow/prompt.py | 2 +- qutebrowser/misc/sessions.py | 6 ++---- qutebrowser/utils/message.py | 2 +- scripts/dev/run_pylint_on_tests.py | 6 ++++-- tests/conftest.py | 4 ++-- tests/end2end/fixtures/quteprocess.py | 3 ++- tests/helpers/stubs.py | 9 --------- tests/helpers/utils.py | 2 -- tests/unit/completion/test_completer.py | 1 - 18 files changed, 27 insertions(+), 42 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 12f58d610..254c21126 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -634,7 +634,7 @@ class CommandDispatcher: scope='window') @cmdutils.argument('count', count=True) @cmdutils.argument('horizontal', flag='x') - def scroll_perc(self, perc: float=None, horizontal=False, count=None): + def scroll_perc(self, perc: float = None, horizontal=False, count=None): """Scroll to a specific percentage of the page. The percentage can be given either as argument or as count. @@ -670,7 +670,7 @@ class CommandDispatcher: @cmdutils.argument('bottom_navigate', metavar='ACTION', choices=('next', 'increment')) def scroll_page(self, x: float, y: float, *, - top_navigate: str=None, bottom_navigate: str=None, + top_navigate: str = None, bottom_navigate: str = None, count=1): """Scroll the frame page-wise. @@ -807,7 +807,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('count', count=True) - def zoom(self, zoom: int=None, count=None): + def zoom(self, zoom: int = None, count=None): """Set the zoom level for the current tab. The zoom can be given as argument or as [count]. If neither is diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index f9b246d3a..8182aabfd 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -952,7 +952,7 @@ class DownloadModel(QAbstractListModel): @cmdutils.register(instance='download-model', scope='window', maxsplit=0) @cmdutils.argument('count', count=True) - def download_open(self, cmdline: str=None, count=0): + def download_open(self, cmdline: str = None, count=0): """Open the last/[count]th download. If no specific command is given, this will use the system's default diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index 53cbb82c1..ca6e04c04 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -100,7 +100,8 @@ class DownloadItem(downloads.AbstractDownloadItem): def _get_open_filename(self): return self._filename - def _set_fileobj(self, fileobj): + def _set_fileobj(self, fileobj, *, + autoclose=True): # pylint: disable=unused-argument raise downloads.UnsupportedOperationError def _set_tempfile(self, fileobj): diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 3e145468b..1147b8475 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -18,7 +18,7 @@ # along with qutebrowser. If not, see . # FIXME:qtwebengine remove this once the stubs are gone -# pylint: disable=unused-variable +# pylint: disable=unused-argument """QtWebEngine specific part of the web element API.""" diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 7eef0b03a..821b73a1a 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -18,7 +18,7 @@ # along with qutebrowser. If not, see . # FIXME:qtwebengine remove this once the stubs are gone -# pylint: disable=unused-variable +# pylint: disable=unused-argument """Wrapper over a QWebEngineView.""" diff --git a/qutebrowser/browser/webkit/rfc6266.py b/qutebrowser/browser/webkit/rfc6266.py index 89f4c78c6..11a1d76a1 100644 --- a/qutebrowser/browser/webkit/rfc6266.py +++ b/qutebrowser/browser/webkit/rfc6266.py @@ -286,9 +286,6 @@ def normalize_ws(text): def parse_headers(content_disposition): """Build a _ContentDisposition from header values.""" - # WORKAROUND for https://bitbucket.org/logilab/pylint/issue/492/ - # pylint: disable=no-member - # We allow non-ascii here (it will only be parsed inside of qdtext, and # rejected by the grammar if it appears in other places), although parsing # it can be ambiguous. Parsing it ensures that a non-ambiguous filename* diff --git a/qutebrowser/commands/argparser.py b/qutebrowser/commands/argparser.py index e4c6378bd..c0e08ccb6 100644 --- a/qutebrowser/commands/argparser.py +++ b/qutebrowser/commands/argparser.py @@ -76,11 +76,11 @@ class ArgumentParser(argparse.ArgumentParser): self.name = name super().__init__(*args, add_help=False, prog=name, **kwargs) - def exit(self, status=0, msg=None): - raise ArgumentParserExit(status, msg) + def exit(self, status=0, message=None): + raise ArgumentParserExit(status, message) - def error(self, msg): - raise ArgumentParserError(msg.capitalize()) + def error(self, message): + raise ArgumentParserError(message.capitalize()) def arg_name(name): diff --git a/qutebrowser/completion/models/base.py b/qutebrowser/completion/models/base.py index 88b06a4e0..1ee45af71 100644 --- a/qutebrowser/completion/models/base.py +++ b/qutebrowser/completion/models/base.py @@ -103,7 +103,7 @@ class BaseCompletionModel(QStandardItemModel): nameitem.setData(userdata, Role.userdata) return nameitem, descitem, miscitem - def delete_cur_item(self, win_id): + def delete_cur_item(self, completion): """Delete the selected item.""" raise NotImplementedError diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 82c6aca00..73aa2ae22 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -643,8 +643,7 @@ class ConfigManager(QObject): def _after_set(self, changed_sect, changed_opt): """Clean up caches and emit signals after an option has been set.""" - # WORKAROUND for https://bitbucket.org/logilab/pylint/issues/659/ - self.get.cache_clear() # pylint: disable=no-member + self.get.cache_clear() self._changed(changed_sect, changed_opt) # Options in the same section and ${optname} interpolation. for optname, option in self.sections[changed_sect].items(): @@ -715,8 +714,7 @@ class ConfigManager(QObject): existed = optname in sectdict if existed: sectdict.delete(optname) - # WORKAROUND for https://bitbucket.org/logilab/pylint/issues/659/ - self.get.cache_clear() # pylint: disable=no-member + self.get.cache_clear() return existed @functools.lru_cache() diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 69feab920..358bcc80b 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -387,7 +387,7 @@ class PromptContainer(QWidget): @cmdutils.register(instance='prompt-container', hide=True, scope='window', modes=[usertypes.KeyMode.prompt], maxsplit=0) - def prompt_open_download(self, cmdline: str=None): + def prompt_open_download(self, cmdline: str = None): """Immediately open a download. If no specific command is given, this will use the system's default diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index 6ad8358a6..8bf55016f 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -443,7 +443,7 @@ class SessionManager(QObject): @cmdutils.register(name=['session-save', 'w'], instance='session-manager') @cmdutils.argument('name', completion=usertypes.Completion.sessions) @cmdutils.argument('win_id', win_id=True) - def session_save(self, name: str=default, current=False, quiet=False, + def session_save(self, name: str = default, current=False, quiet=False, force=False, only_active_window=False, win_id=None): """Save a session. @@ -455,9 +455,7 @@ class SessionManager(QObject): force: Force saving internal sessions (starting with an underline). only_active_window: Saves only tabs of the currently active window. """ - if (name is not default and - name.startswith('_') and # pylint: disable=no-member - not force): + if name is not default and name.startswith('_') and not force: raise cmdexc.CommandError("{} is an internal session, use --force " "to save anyways.".format(name)) if current: diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py index bb758d78a..8f39e8174 100644 --- a/qutebrowser/utils/message.py +++ b/qutebrowser/utils/message.py @@ -18,7 +18,7 @@ # along with qutebrowser. If not, see . # Because every method needs to have a log_stack argument -# pylint: disable=unused-variable +# pylint: disable=unused-argument """Message singleton so we don't have to define unneeded signals.""" diff --git a/scripts/dev/run_pylint_on_tests.py b/scripts/dev/run_pylint_on_tests.py index 01dd14ad7..e4412708d 100644 --- a/scripts/dev/run_pylint_on_tests.py +++ b/scripts/dev/run_pylint_on_tests.py @@ -54,7 +54,8 @@ def main(): 'missing-docstring', 'protected-access', # https://bitbucket.org/logilab/pylint/issue/511/ - 'undefined-variable', + #'undefined-variable', + 'len-as-condition', # directories without __init__.py... 'import-error', ] @@ -66,7 +67,8 @@ def main(): no_docstring_rgx = ['^__.*__$', '^setup$'] args = (['--disable={}'.format(','.join(disabled)), - '--no-docstring-rgx=({})'.format('|'.join(no_docstring_rgx))] + + '--no-docstring-rgx=({})'.format('|'.join(no_docstring_rgx)), + '--ignored-modules=helpers,pytest'] + sys.argv[2:] + files) env = os.environ.copy() env['PYTHONPATH'] = os.pathsep.join(pythonpath) diff --git a/tests/conftest.py b/tests/conftest.py index fe2ca9042..53dbbb752 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# pylint: disable=unused-import +# pylint: disable=unused-import,wildcard-import,unused-wildcard-import """The qutebrowser test suite conftest file.""" @@ -34,7 +34,7 @@ pytest.register_assert_rewrite('helpers') from helpers import logfail from helpers.logfail import fail_on_logging from helpers.messagemock import message_mock -from helpers.fixtures import * # pylint: disable=wildcard-import +from helpers.fixtures import * from qutebrowser.utils import qtutils diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 80643b131..cc835693c 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -356,7 +356,8 @@ class QuteProc(testprocess.Process): self.wait_for(category='webview', message='Scroll position changed to ' + point) - def wait_for(self, timeout=None, **kwargs): + def wait_for(self, timeout=None, # pylint: disable=arguments-differ + **kwargs): """Extend wait_for to add divisor if a test is xfailing.""" __tracebackhide__ = (lambda e: e.errisinstance(testprocess.WaitForTimeout)) diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index dfbcc550d..3e028b0c9 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -376,9 +376,6 @@ class InstaTimer(QObject): timeout = pyqtSignal() - def __init__(self, parent=None): - super().__init__(parent) - def start(self): self.timeout.emit() @@ -410,9 +407,6 @@ class StatusBarCommandStub(QLineEdit): show_cmd = pyqtSignal() hide_cmd = pyqtSignal() - def __init__(self, parent=None): - super().__init__(parent) - def prefix(self): return self.text()[0] @@ -594,6 +588,3 @@ class ApplicationStub(QObject): """Stub to insert as the app object in objreg.""" new_window = pyqtSignal(mainwindow.MainWindow) - - def __init__(self): - super().__init__() diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index faafefa82..52a843dbc 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -17,8 +17,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -# pylint: disable=unused-variable - """Partial comparison of dicts/lists.""" diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index a2b3bc7f0..3f0f021bf 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -113,7 +113,6 @@ def cmdutils_patch(monkeypatch, stubs): @cmdutils.argument('command', completion=usertypes.Completion.command) def bind(key, win_id, command=None, *, mode='normal', force=False): """docstring.""" - # pylint: disable=unused-variable pass def tab_detach(): From 7c4e4a58183017b898f228fc4b3d765925fbed89 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 13 Apr 2017 21:10:52 +0200 Subject: [PATCH 184/299] Adjust flake8 config Since we now ignore this on a per-file level for pylint, we need to do the same for flake8 too. --- .flake8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 7bfc34c0a..d967a505b 100644 --- a/.flake8 +++ b/.flake8 @@ -35,9 +35,9 @@ max-complexity = 12 putty-auto-ignore = True putty-ignore = /# pylint: disable=invalid-name/ : +N801,N806 - /# pylint: disable=wildcard-import/ : +F403 /# pragma: no mccabe/ : +C901 tests/*/test_*.py : +D100,D101,D401 + tests/conftest.py : +F403 tests/unit/browser/webkit/test_history.py : +N806 tests/helpers/fixtures.py : +N806 tests/unit/browser/webkit/http/test_content_disposition.py : +D400 From dd24039d64e72d4a79cda9ee3b7d0d0b19f146a4 Mon Sep 17 00:00:00 2001 From: Daniel Jakots Date: Fri, 14 Apr 2017 12:37:41 -0400 Subject: [PATCH 185/299] OpenBSD 6.1 is now released. Also prefer the package than the port. --- INSTALL.asciidoc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/INSTALL.asciidoc b/INSTALL.asciidoc index 933859346..651082bee 100644 --- a/INSTALL.asciidoc +++ b/INSTALL.asciidoc @@ -222,19 +222,19 @@ On OpenBSD qutebrowser is in http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/www/qutebrowser/[OpenBSD ports]. -Manual install: +Install the package: + +---- +# pkg_add qutebrowser +---- + +Or alternatively, use the ports system : ---- # cd /usr/ports/www/qutebrowser # make install ---- -Or alternatively if you're using `-current` (or OpenBSD 6.1 once it's been released): - ----- -# pkg_add qutebrowser ----- - On Windows ---------- From 9050aac71e433dc82d087d47eb31f1f4e764d809 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 15 Apr 2017 17:37:14 +0200 Subject: [PATCH 186/299] Update setuptools from 34.4.1 to 35.0.0 --- misc/requirements/requirements-pip.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt index 5bd2aeac8..9979e87f2 100644 --- a/misc/requirements/requirements-pip.txt +++ b/misc/requirements/requirements-pip.txt @@ -3,6 +3,6 @@ appdirs==1.4.3 packaging==16.8 pyparsing==2.2.0 -setuptools==34.4.1 +setuptools==35.0.0 six==1.10.0 wheel==0.29.0 From 842c2d297e4aaa547113de213ab41e838b6cfdb1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 16 Apr 2017 13:07:33 +0200 Subject: [PATCH 187/299] Allow to set message clear timer to 0 Fixes #2527 --- CHANGELOG.asciidoc | 1 + doc/help/settings.asciidoc | 1 + qutebrowser/config/configdata.py | 5 +++-- qutebrowser/mainwindow/messageview.py | 7 +++++-- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 9b7b809c9..983f8a6d3 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -44,6 +44,7 @@ Changed - The HTTP cache is disabled with QtWebKit on Qt 5.8 now as it leads to frequent crashes due to a Qt bug. - stdin is now closed immediately for processes spawned from qutebrowser +- When ui -> message-timeout is set to 0, messages are now never cleared. Fixed ~~~~~ diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index a2df65ca4..9092ea052 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -581,6 +581,7 @@ Default: +pass:[bottom]+ [[ui-message-timeout]] === message-timeout Time (in ms) to show messages in the statusbar for. +Set to 0 to never clear messages. Default: +pass:[2000]+ diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 2c5b27808..abf704801 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -317,8 +317,9 @@ def data(readonly=False): "The position of the status bar."), ('message-timeout', - SettingValue(typ.Int(), '2000'), - "Time (in ms) to show messages in the statusbar for."), + SettingValue(typ.Int(minval=0), '2000'), + "Time (in ms) to show messages in the statusbar for.\n" + "Set to 0 to never clear messages."), ('message-unfocused', SettingValue(typ.Bool(), 'false'), diff --git a/qutebrowser/mainwindow/messageview.py b/qutebrowser/mainwindow/messageview.py index 5c407b78b..7b2d64e07 100644 --- a/qutebrowser/mainwindow/messageview.py +++ b/qutebrowser/mainwindow/messageview.py @@ -98,7 +98,9 @@ class MessageView(QWidget): @config.change_filter('ui', 'message-timeout') def _set_clear_timer_interval(self): """Configure self._clear_timer according to the config.""" - self._clear_timer.setInterval(config.get('ui', 'message-timeout')) + interval = config.get('ui', 'message-timeout') + if interval != 0: + self._clear_timer.setInterval(interval) @pyqtSlot() def clear_messages(self): @@ -125,7 +127,8 @@ class MessageView(QWidget): widget = Message(level, text, replace=replace, parent=self) self._vbox.addWidget(widget) widget.show() - self._clear_timer.start() + if config.get('ui', 'message-timeout') != 0: + self._clear_timer.start() self._messages.append(widget) self._last_text = text self.show() From 2d45257dcc4a24602baa6689a9adffe25513ff20 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 16 Apr 2017 13:08:15 +0200 Subject: [PATCH 188/299] Remove exclamation mark for bookmark messages --- qutebrowser/browser/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 254c21126..8d49ddef8 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1233,7 +1233,7 @@ class CommandDispatcher: except urlmarks.Error as e: raise cmdexc.CommandError(str(e)) else: - msg = "Bookmarked {}!" if was_added else "Removed bookmark {}!" + msg = "Bookmarked {}" if was_added else "Removed bookmark {}" message.info(msg.format(url.toDisplayString())) @cmdutils.register(instance='command-dispatcher', scope='window', From c82bd837151eb2a2c0bcd0b10ab73955cb40a642 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 16 Apr 2017 13:14:19 +0200 Subject: [PATCH 189/299] Implement RedirectNetworkReply.abort --- qutebrowser/browser/webkit/network/networkreply.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qutebrowser/browser/webkit/network/networkreply.py b/qutebrowser/browser/webkit/network/networkreply.py index ec37e1e84..dc1f09f0c 100644 --- a/qutebrowser/browser/webkit/network/networkreply.py +++ b/qutebrowser/browser/webkit/network/networkreply.py @@ -149,5 +149,9 @@ class RedirectNetworkReply(QNetworkReply): self.setAttribute(QNetworkRequest.RedirectionTargetAttribute, new_url) QTimer.singleShot(0, lambda: self.finished.emit()) + def abort(self): + """Called when there's e.g. a redirection limit.""" + pass + def readData(self, _maxlen): return bytes() From 9d2734ff6208e7ebef4f73ac1361b07e69f159a9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 16 Apr 2017 13:15:15 +0200 Subject: [PATCH 190/299] Make sure host is valid for qute:// redirects --- qutebrowser/browser/qutescheme.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index ccd976d0a..d71b6c135 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -146,7 +146,8 @@ def data_for_url(url): new_url.setScheme('qute') new_url.setHost(path) new_url.setPath('/') - raise Redirect(new_url) + if new_url.host(): # path was a valid host + raise Redirect(new_url) try: handler = _HANDLERS[host] From ad9e82b91ec0636778ff2b29df102ebafc39be7c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 16 Apr 2017 21:13:01 +0200 Subject: [PATCH 191/299] Adjust bookmark tests --- tests/end2end/features/urlmarks.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/urlmarks.feature b/tests/end2end/features/urlmarks.feature index f233215b8..b41359885 100644 --- a/tests/end2end/features/urlmarks.feature +++ b/tests/end2end/features/urlmarks.feature @@ -7,12 +7,12 @@ Feature: quickmarks and bookmarks Scenario: Saving a bookmark When I open data/title.html And I run :bookmark-add - Then the message "Bookmarked http://localhost:*/data/title.html!" should be shown + Then the message "Bookmarked http://localhost:*/data/title.html" should be shown And the bookmark file should contain "http://localhost:*/data/title.html Test title" Scenario: Saving a bookmark with a provided url and title When I run :bookmark-add http://example.com "some example title" - Then the message "Bookmarked http://example.com!" should be shown + Then the message "Bookmarked http://example.com" should be shown And the bookmark file should contain "http://example.com some example title" Scenario: Saving a bookmark with a url but no title From 6fb48a5514f2f5dd6759ccfae0ea66ec8ad8297a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 17 Apr 2017 14:00:24 +0200 Subject: [PATCH 192/299] Update astroid from 1.5.1 to 1.5.2 --- misc/requirements/requirements-pylint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 381c13fd6..de6288832 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==1.5.1 +astroid==1.5.2 github3.py==0.9.6 isort==4.2.5 lazy-object-proxy==1.2.2 From 00e4bf7640692d89e5acedc0521ac6ff66591943 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 17 Apr 2017 15:20:23 +0200 Subject: [PATCH 193/299] Update pylint from 1.7.0 to 1.7.1 --- misc/requirements/requirements-pylint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 381c13fd6..744299808 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -5,7 +5,7 @@ github3.py==0.9.6 isort==4.2.5 lazy-object-proxy==1.2.2 mccabe==0.6.1 -pylint==1.7.0 +pylint==1.7.1 ./scripts/dev/pylint_checkers requests==2.13.0 uritemplate==3.0.0 From db8b6d3e68db1b584c3b46393c5226d6a92bccac Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 17 Apr 2017 16:02:57 +0200 Subject: [PATCH 194/299] Add test for QNetworkReply.abort --- tests/unit/browser/webkit/network/test_networkreply.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/browser/webkit/network/test_networkreply.py b/tests/unit/browser/webkit/network/test_networkreply.py index 7505dbc8c..13e060c8a 100644 --- a/tests/unit/browser/webkit/network/test_networkreply.py +++ b/tests/unit/browser/webkit/network/test_networkreply.py @@ -98,3 +98,4 @@ def test_redirect_network_reply(): reply = networkreply.RedirectNetworkReply(url) assert reply.readData(1) == b'' assert reply.attribute(QNetworkRequest.RedirectionTargetAttribute) == url + reply.abort() # shouldn't do anything From 6151b43c47f1ed0b8a6f0118037ba8bb93447f42 Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Sun, 16 Apr 2017 09:01:46 -0400 Subject: [PATCH 195/299] Fix qute_history benchmark. This benchmark was running very quickly due to an improper setup. The current history implementation expects that a newly inserted entry must be more recent than any existing entries and sorts according to this assumption. The benchmark test inserts increasingly older entries, breaking this invariant. When run in the benchmark, the qute://history/data implementation would see an entry older than the oldest time in the time window and would immediately return with a single "next" entry. This patch inserts data in an order that mantains history's invariant and adds a sanity-check at the end of the test. It does not check for the exact length as not all entries will be within the time window. The length will be some values <= 100000, the check just ensures that there is at least something more than a "next" entry. Before: ---------------------------------------------- benchmark: 1 tests ---------------------------------------------- Name (time in us) Min Max Mean StdDev Median IQR Outliers(*) Rounds Iterations ---------------------------------------------------------------------------------------------------------------- test_qute_history_benchmark 9.3050 21.9250 9.6143 0.2454 9.5880 0.1070 230;360 9930 1 ---------------------------------------------------------------------------------------------------------------- After: -------------------------------------------------- benchmark: 1 tests ------------------------------------------------- Name (time in ms) Min Max Mean StdDev Median IQR Outliers(*) Rounds Iterations ----------------------------------------------------------------------------------------------------------------------- test_qute_history_benchmark 220.7040 223.1900 221.7536 1.1070 221.1939 1.8803 1;0 5 1 ----------------------------------------------------------------------------------------------------------------------- --- tests/unit/browser/test_qutescheme.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index e46038c8d..92ad30574 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -154,7 +154,8 @@ class TestHistoryHandler: assert items[0]["next"] == now - next_time def test_qute_history_benchmark(self, fake_web_history, benchmark, now): - for t in range(100000): # one history per second + # items must be earliest-first to ensure history is sorted properly + for t in range(100000, 0, -1): # one history per second entry = history.Entry( atime=str(now - t), url=QUrl('www.x.com/{}'.format(t)), @@ -162,4 +163,5 @@ class TestHistoryHandler: fake_web_history._add_entry(entry) url = QUrl("qute://history/data?start_time={}".format(now)) - _mimetype, _data = benchmark(qutescheme.qute_history, url) + _mimetype, data = benchmark(qutescheme.qute_history, url) + assert len(json.loads(data)) > 1 From 59a01b860f9e4f636c7911adc0ac370c09e0bd09 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Apr 2017 16:36:14 +0200 Subject: [PATCH 196/299] Add crowdfunding note to README/website --- README.asciidoc | 7 +++++++ www/header.asciidoc | 4 ++++ www/qute.css | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/README.asciidoc b/README.asciidoc index f79812850..d9dbd8c0c 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -24,6 +24,13 @@ on Python and PyQt5 and free software, licensed under the GPL. It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl. +// QUTE_WEB_HIDE +**qutebrowser is currently running a crowdfunding campaign for its new +configuration system, allowing for per-domain settings and much more. + +See the link:https://www.kickstarter.com/projects/the-compiler/qutebrowser-v10-with-per-domain-settings?ref=6zw7qz[Kickstarter campaign] for more information!** +// QUTE_WEB_HIDE_END + Screenshots ----------- diff --git a/www/header.asciidoc b/www/header.asciidoc index dc80a5d00..ff5b26d9e 100644 --- a/www/header.asciidoc +++ b/www/header.asciidoc @@ -17,4 +17,8 @@ Releases Blog +