Merge branch 'master' into master
This commit is contained in:
commit
25e396faea
@ -27,6 +27,11 @@ Added
|
||||
- New `content.mouse_lock` setting to handle HTML5 pointer locking.
|
||||
- New `completion.web_history.exclude` setting which hides a list of URL
|
||||
patterns from the completion.
|
||||
- Rewritten PDF.js support:
|
||||
* PDF.js support and the `content.pdfjs` setting are now available with
|
||||
QtWebEngine.
|
||||
* Opening a PDF file now doesn't start a second request anymore.
|
||||
* Opening PDFs on https:// sites now works properly.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
@ -48,6 +53,13 @@ Changed
|
||||
- Various performance improvements when many tabs are opened.
|
||||
- Regenerating completion history now shows a progress dialog.
|
||||
- Make qute:// pages work properly on Qt 5.11.2
|
||||
- The `content.autoplay` setting now supports URL patterns on Qt >= 5.11.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Error when passing a substring with spaces to `:tab-take`.
|
||||
- Greasemonkey scripts which start with an UTF-8 BOM are now handled correctly.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
@ -1625,7 +1637,7 @@ Changed
|
||||
`tabs.bg/fg.selected.odd/even`.
|
||||
- `:spawn --userscript` and `:hint` with the `userscript` target now look up
|
||||
relative paths in `~/.local/share/qutebrowser/userscripts` or
|
||||
`$XDG_DATA_DIR`. Using a binary in `$PATH` won't work anymore with
|
||||
`$XDG_DATA_HOME`. Using a binary in `$PATH` won't work anymore with
|
||||
`--userscript`.
|
||||
- New design for error pages
|
||||
- Link filtering for hints now checks if the text is contained anywhere in
|
||||
|
@ -576,7 +576,7 @@ Start hinting.
|
||||
- With `userscript`: The userscript to execute. Either store
|
||||
the userscript in
|
||||
`~/.local/share/qutebrowser/userscripts`
|
||||
(or `$XDG_DATA_DIR`), or use an absolute
|
||||
(or `$XDG_DATA_HOME`), or use an absolute
|
||||
path.
|
||||
- With `fill`: The command to fill the statusbar with.
|
||||
`{hint-url}` will get replaced by the selected
|
||||
@ -1193,7 +1193,7 @@ Spawn a command in a shell.
|
||||
* +*-u*+, +*--userscript*+: Run the command as a userscript. You can use an absolute path, or store the userscript in one of those
|
||||
locations:
|
||||
- `~/.local/share/qutebrowser/userscripts`
|
||||
(or `$XDG_DATA_DIR`)
|
||||
(or `$XDG_DATA_HOME`)
|
||||
- `/usr/share/qutebrowser/userscripts`
|
||||
|
||||
* +*-v*+, +*--verbose*+: Show notifications when the command started/exited.
|
||||
@ -1342,6 +1342,9 @@ Take a tab from another window.
|
||||
* +'index'+: The [win_id/]index of the tab to take. Or a substring in which case the closest match will be taken.
|
||||
|
||||
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
|
||||
[[unbind]]
|
||||
=== unbind
|
||||
Syntax: +:unbind [*--mode* 'mode'] 'key'+
|
||||
|
@ -1488,7 +1488,9 @@ Default:
|
||||
[[content.autoplay]]
|
||||
=== content.autoplay
|
||||
Automatically start playing `<video>` elements.
|
||||
Note this option needs a restart with QtWebEngine on Qt < 5.11.
|
||||
Note: On Qt < 5.11, this option needs a restart and does not support URL patterns.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
@ -1941,8 +1943,6 @@ Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
[[content.persistent_storage]]
|
||||
=== content.persistent_storage
|
||||
Allow websites to request persistent storage quota via `navigator.webkitPersistentStorage.requestQuota`.
|
||||
|
@ -485,7 +485,8 @@ class CommandDispatcher:
|
||||
new_tabbed_browser.widget.set_tab_pinned(newtab, curtab.data.pinned)
|
||||
return newtab
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0)
|
||||
@cmdutils.argument('index', completion=miscmodels.other_buffer)
|
||||
def tab_take(self, index):
|
||||
"""Take a tab from another window.
|
||||
@ -1183,7 +1184,7 @@ class CommandDispatcher:
|
||||
absolute path, or store the userscript in one of those
|
||||
locations:
|
||||
- `~/.local/share/qutebrowser/userscripts`
|
||||
(or `$XDG_DATA_DIR`)
|
||||
(or `$XDG_DATA_HOME`)
|
||||
- `/usr/share/qutebrowser/userscripts`
|
||||
verbose: Show notifications when the command started/exited.
|
||||
output: Whether the output should be shown in a new tab.
|
||||
|
@ -32,10 +32,11 @@ import enum
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
|
||||
QTimer, QAbstractListModel, QUrl)
|
||||
|
||||
from qutebrowser.browser import pdfjs
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (usertypes, standarddir, utils, message, log,
|
||||
qtutils)
|
||||
qtutils, objreg)
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
@ -224,9 +225,6 @@ class _DownloadTarget:
|
||||
|
||||
"""Abstract base class for different download targets."""
|
||||
|
||||
def __init__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def suggested_filename(self):
|
||||
"""Get the suggested filename for this download target."""
|
||||
raise NotImplementedError
|
||||
@ -243,7 +241,6 @@ class FileDownloadTarget(_DownloadTarget):
|
||||
"""
|
||||
|
||||
def __init__(self, filename, force_overwrite=False):
|
||||
# pylint: disable=super-init-not-called
|
||||
self.filename = filename
|
||||
self.force_overwrite = force_overwrite
|
||||
|
||||
@ -263,7 +260,6 @@ class FileObjDownloadTarget(_DownloadTarget):
|
||||
"""
|
||||
|
||||
def __init__(self, fileobj):
|
||||
# pylint: disable=super-init-not-called
|
||||
self.fileobj = fileobj
|
||||
|
||||
def suggested_filename(self):
|
||||
@ -290,7 +286,6 @@ class OpenFileDownloadTarget(_DownloadTarget):
|
||||
"""
|
||||
|
||||
def __init__(self, cmdline=None):
|
||||
# pylint: disable=super-init-not-called
|
||||
self.cmdline = cmdline
|
||||
|
||||
def suggested_filename(self):
|
||||
@ -300,6 +295,17 @@ class OpenFileDownloadTarget(_DownloadTarget):
|
||||
return 'temporary file'
|
||||
|
||||
|
||||
class PDFJSDownloadTarget(_DownloadTarget):
|
||||
|
||||
"""Open the download via PDF.js."""
|
||||
|
||||
def suggested_filename(self):
|
||||
raise NoFilenameError
|
||||
|
||||
def __str__(self):
|
||||
return 'temporary PDF.js file'
|
||||
|
||||
|
||||
class DownloadItemStats(QObject):
|
||||
|
||||
"""Statistics (bytes done, total bytes, time, etc.) about a download.
|
||||
@ -405,6 +411,8 @@ class AbstractDownloadItem(QObject):
|
||||
arg: The error message as string.
|
||||
remove_requested: Emitted when the removal of this download was
|
||||
requested.
|
||||
pdfjs_requested: Emitted when PDF.js should be opened with the given
|
||||
filename.
|
||||
"""
|
||||
|
||||
data_changed = pyqtSignal()
|
||||
@ -412,6 +420,7 @@ class AbstractDownloadItem(QObject):
|
||||
error = pyqtSignal(str)
|
||||
cancelled = pyqtSignal()
|
||||
remove_requested = pyqtSignal()
|
||||
pdfjs_requested = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@ -730,6 +739,19 @@ class AbstractDownloadItem(QObject):
|
||||
return
|
||||
self.open_file(cmdline)
|
||||
|
||||
def _pdfjs_if_successful(self):
|
||||
"""Open the file via PDF.js if downloading was successful."""
|
||||
if not self.successful:
|
||||
log.downloads.debug("{} finished but not successful, not opening!"
|
||||
.format(self))
|
||||
return
|
||||
|
||||
filename = self._get_open_filename()
|
||||
if filename is None: # pragma: no cover
|
||||
log.downloads.error("No filename to open the download!")
|
||||
return
|
||||
self.pdfjs_requested.emit(os.path.basename(filename))
|
||||
|
||||
def set_target(self, target):
|
||||
"""Set the target for a given download.
|
||||
|
||||
@ -741,7 +763,7 @@ class AbstractDownloadItem(QObject):
|
||||
elif isinstance(target, FileDownloadTarget):
|
||||
self._set_filename(
|
||||
target.filename, force_overwrite=target.force_overwrite)
|
||||
elif isinstance(target, OpenFileDownloadTarget):
|
||||
elif isinstance(target, (OpenFileDownloadTarget, PDFJSDownloadTarget)):
|
||||
try:
|
||||
fobj = temp_download_manager.get_tmpfile(self.basename)
|
||||
except OSError as exc:
|
||||
@ -749,8 +771,15 @@ class AbstractDownloadItem(QObject):
|
||||
message.error(msg)
|
||||
self.cancel()
|
||||
return
|
||||
self.finished.connect(
|
||||
functools.partial(self._open_if_successful, target.cmdline))
|
||||
|
||||
if isinstance(target, OpenFileDownloadTarget):
|
||||
self.finished.connect(functools.partial(
|
||||
self._open_if_successful, target.cmdline))
|
||||
elif isinstance(target, PDFJSDownloadTarget):
|
||||
self.finished.connect(self._pdfjs_if_successful)
|
||||
else:
|
||||
raise utils.Unreachable
|
||||
|
||||
self._set_tempfile(fobj)
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Unsupported download target: {}".format(target))
|
||||
@ -797,6 +826,13 @@ class AbstractDownloadManager(QObject):
|
||||
dl.stats.update_speed()
|
||||
self.data_changed.emit(-1)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _on_pdfjs_requested(self, filename):
|
||||
"""Open PDF.js when a download requests it."""
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window='last-focused')
|
||||
tabbed_browser.tabopen(pdfjs.get_main_url(filename))
|
||||
|
||||
def _init_item(self, download, auto_remove, suggested_filename):
|
||||
"""Initialize a newly created DownloadItem."""
|
||||
download.cancelled.connect(download.remove)
|
||||
@ -813,6 +849,8 @@ class AbstractDownloadManager(QObject):
|
||||
download.data_changed.connect(
|
||||
functools.partial(self._on_data_changed, download))
|
||||
download.error.connect(self._on_error)
|
||||
download.pdfjs_requested.connect(self._on_pdfjs_requested)
|
||||
|
||||
download.basename = suggested_filename
|
||||
idx = len(self.downloads)
|
||||
download.index = idx + 1 # "Human readable" index
|
||||
@ -1195,7 +1233,7 @@ class TempDownloadManager:
|
||||
"directory")
|
||||
self._tmpdir = None
|
||||
|
||||
def _get_tmpdir(self):
|
||||
def get_tmpdir(self):
|
||||
"""Return the temporary directory that is used for downloads.
|
||||
|
||||
The directory is created lazily on first access.
|
||||
@ -1221,13 +1259,13 @@ class TempDownloadManager:
|
||||
Return:
|
||||
A tempfile.NamedTemporaryFile that should be used to save the file.
|
||||
"""
|
||||
tmpdir = self._get_tmpdir()
|
||||
tmpdir = self.get_tmpdir()
|
||||
encoding = sys.getfilesystemencoding()
|
||||
suggested_name = utils.force_encoding(suggested_name, encoding)
|
||||
# Make sure that the filename is not too long
|
||||
suggested_name = utils.elide_filename(suggested_name, 50)
|
||||
fobj = tempfile.NamedTemporaryFile(dir=tmpdir.name, delete=False,
|
||||
suffix=suggested_name)
|
||||
suffix='_' + suggested_name)
|
||||
self.files.append(fobj)
|
||||
return fobj
|
||||
|
||||
|
@ -247,11 +247,10 @@ class GreasemonkeyManager(QObject):
|
||||
if not os.path.isfile(script_filename):
|
||||
continue
|
||||
script_path = os.path.join(scripts_dir, script_filename)
|
||||
with open(script_path, encoding='utf-8') as script_file:
|
||||
script = GreasemonkeyScript.parse(
|
||||
script_file.read(),
|
||||
filename=script_filename,
|
||||
)
|
||||
with open(script_path, encoding='utf-8-sig') as script_file:
|
||||
script = GreasemonkeyScript.parse(script_file.read())
|
||||
if not script.name:
|
||||
script.name = script_filename
|
||||
self.add_script(script, force)
|
||||
self.scripts_reloaded.emit()
|
||||
|
||||
|
@ -693,7 +693,7 @@ class HintManager(QObject):
|
||||
- With `userscript`: The userscript to execute. Either store
|
||||
the userscript in
|
||||
`~/.local/share/qutebrowser/userscripts`
|
||||
(or `$XDG_DATA_DIR`), or use an absolute
|
||||
(or `$XDG_DATA_HOME`), or use an absolute
|
||||
path.
|
||||
- With `fill`: The command to fill the statusbar with.
|
||||
`{hint-url}` will get replaced by the selected
|
||||
|
@ -22,9 +22,11 @@
|
||||
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtCore import QUrl, QUrlQuery
|
||||
|
||||
from qutebrowser.utils import utils, javascript
|
||||
from qutebrowser.utils import utils, javascript, jinja, qtutils, usertypes
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.config import config
|
||||
|
||||
|
||||
class PDFJSNotFound(Exception):
|
||||
@ -41,60 +43,54 @@ class PDFJSNotFound(Exception):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
def generate_pdfjs_page(url):
|
||||
"""Return the html content of a page that displays url with pdfjs.
|
||||
def generate_pdfjs_page(filename, url):
|
||||
"""Return the html content of a page that displays a file with pdfjs.
|
||||
|
||||
Returns a string.
|
||||
|
||||
Args:
|
||||
url: The url of the pdf as QUrl.
|
||||
filename: The filename of the PDF to open.
|
||||
url: The URL being opened.
|
||||
"""
|
||||
if not is_available():
|
||||
return jinja.render('no_pdfjs.html',
|
||||
url=url.toDisplayString(),
|
||||
title="PDF.js not found")
|
||||
viewer = get_pdfjs_res('web/viewer.html').decode('utf-8')
|
||||
script = _generate_pdfjs_script(url)
|
||||
script = _generate_pdfjs_script(filename)
|
||||
html_page = viewer.replace('</body>',
|
||||
'</body><script>{}</script>'.format(script))
|
||||
return html_page
|
||||
|
||||
|
||||
def _generate_pdfjs_script(url):
|
||||
def _generate_pdfjs_script(filename):
|
||||
"""Generate the script that shows the pdf with pdf.js.
|
||||
|
||||
Args:
|
||||
url: The url of the pdf page as QUrl.
|
||||
filename: The name of the file to open.
|
||||
"""
|
||||
return (
|
||||
'document.addEventListener("DOMContentLoaded", function() {{\n'
|
||||
' PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
|
||||
' (window.PDFView || window.PDFViewerApplication).open("{url}");\n'
|
||||
'}});\n'
|
||||
).format(url=javascript.string_escape(url.toString(QUrl.FullyEncoded)))
|
||||
url = QUrl('qute://pdfjs/file')
|
||||
url_query = QUrlQuery()
|
||||
url_query.addQueryItem('filename', filename)
|
||||
url.setQuery(url_query)
|
||||
|
||||
return jinja.js_environment.from_string("""
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
{% if disable_create_object_url %}
|
||||
PDFJS.disableCreateObjectURL = true;
|
||||
{% endif %}
|
||||
PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;
|
||||
|
||||
def fix_urls(asset):
|
||||
"""Take an html page and replace each relative URL with an absolute.
|
||||
|
||||
This is specialized for pdf.js files and not a general purpose function.
|
||||
|
||||
Args:
|
||||
asset: js file or html page as string.
|
||||
"""
|
||||
new_urls = [
|
||||
('viewer.css', 'qute://pdfjs/web/viewer.css'),
|
||||
('compatibility.js', 'qute://pdfjs/web/compatibility.js'),
|
||||
('locale/locale.properties',
|
||||
'qute://pdfjs/web/locale/locale.properties'),
|
||||
('l10n.js', 'qute://pdfjs/web/l10n.js'),
|
||||
('../build/pdf.js', 'qute://pdfjs/build/pdf.js'),
|
||||
('debugger.js', 'qute://pdfjs/web/debugger.js'),
|
||||
('viewer.js', 'qute://pdfjs/web/viewer.js'),
|
||||
('compressed.tracemonkey-pldi-09.pdf', ''),
|
||||
('./images/', 'qute://pdfjs/web/images/'),
|
||||
('../build/pdf.worker.js', 'qute://pdfjs/build/pdf.worker.js'),
|
||||
('../web/cmaps/', 'qute://pdfjs/web/cmaps/'),
|
||||
]
|
||||
for original, new in new_urls:
|
||||
asset = asset.replace(original, new)
|
||||
return asset
|
||||
const viewer = window.PDFView || window.PDFViewerApplication;
|
||||
viewer.open("{{ url }}");
|
||||
});
|
||||
""").render(
|
||||
url=javascript.string_escape(url.toString(QUrl.FullyEncoded)),
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70420
|
||||
disable_create_object_url=(
|
||||
not qtutils.version_check('5.12') and
|
||||
not qtutils.version_check('5.7.1', exact=True, compiled=False) and
|
||||
objects.backend == usertypes.Backend.QtWebEngine))
|
||||
|
||||
|
||||
SYSTEM_PDFJS_PATHS = [
|
||||
@ -141,13 +137,7 @@ def get_pdfjs_res_and_path(path):
|
||||
except FileNotFoundError:
|
||||
raise PDFJSNotFound(path) from None
|
||||
|
||||
try:
|
||||
# Might be script/html or might be binary
|
||||
text_content = content.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
return (content, file_path)
|
||||
text_content = fix_urls(text_content)
|
||||
return (text_content.encode('utf-8'), file_path)
|
||||
return content, file_path
|
||||
|
||||
|
||||
def get_pdfjs_res(path):
|
||||
@ -206,3 +196,22 @@ def is_available():
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def should_use_pdfjs(mimetype, url):
|
||||
"""Check whether PDF.js should be used."""
|
||||
# e.g. 'blob:qute%3A///b45250b3-787e-44d1-a8d8-c2c90f81f981'
|
||||
is_download_url = (url.scheme() == 'blob' and
|
||||
QUrl(url.path()).scheme() == 'qute')
|
||||
is_pdf = mimetype in ['application/pdf', 'application/x-pdf']
|
||||
return is_pdf and not is_download_url and config.val.content.pdfjs
|
||||
|
||||
|
||||
def get_main_url(filename):
|
||||
"""Get the URL to be opened to view a local PDF."""
|
||||
url = QUrl('qute://pdfjs/web/viewer.html')
|
||||
query = QUrlQuery()
|
||||
query.addQueryItem('filename', filename) # read from our JS
|
||||
query.addQueryItem('file', '') # to avoid pdfjs opening the default PDF
|
||||
url.setQuery(query)
|
||||
return url
|
||||
|
@ -29,7 +29,6 @@ import json
|
||||
import os
|
||||
import time
|
||||
import textwrap
|
||||
import mimetypes
|
||||
import urllib
|
||||
import collections
|
||||
import base64
|
||||
@ -44,10 +43,10 @@ import pkg_resources
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.browser import pdfjs, downloads
|
||||
from qutebrowser.config import config, configdata, configexc, configdiff
|
||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||
objreg, urlutils)
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
@ -113,12 +112,10 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
||||
|
||||
Attributes:
|
||||
_name: The 'foo' part of qute://foo
|
||||
backend: Limit which backends the handler can run with.
|
||||
"""
|
||||
|
||||
def __init__(self, name, backend=None):
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
self._backend = backend
|
||||
self._function = None
|
||||
|
||||
def __call__(self, function):
|
||||
@ -128,19 +125,7 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
"""Call the underlying function."""
|
||||
if self._backend is not None and objects.backend != self._backend:
|
||||
return self.wrong_backend_handler(*args, **kwargs)
|
||||
else:
|
||||
return self._function(*args, **kwargs)
|
||||
|
||||
def wrong_backend_handler(self, url):
|
||||
"""Show an error page about using the invalid backend."""
|
||||
src = jinja.render('error.html',
|
||||
title="Error while opening qute://url",
|
||||
url=url.toDisplayString(),
|
||||
error='{} is not available with this '
|
||||
'backend'.format(url.toDisplayString()))
|
||||
return 'text/html', src
|
||||
return self._function(*args, **kwargs)
|
||||
|
||||
|
||||
def data_for_url(url):
|
||||
@ -382,8 +367,7 @@ def qute_help(url):
|
||||
bdata = utils.read_file(path, binary=True)
|
||||
except OSError as e:
|
||||
raise SchemeOSError(e)
|
||||
mimetype, _encoding = mimetypes.guess_type(urlpath)
|
||||
assert mimetype is not None, url
|
||||
mimetype = utils.guess_mimetype(urlpath)
|
||||
return mimetype, bdata
|
||||
|
||||
try:
|
||||
@ -531,3 +515,43 @@ def qute_pastebin_version(_url):
|
||||
"""Handler that pastebins the version string."""
|
||||
version.pastebin_version()
|
||||
return 'text/plain', b'Paste called.'
|
||||
|
||||
|
||||
@add_handler('pdfjs')
|
||||
def qute_pdfjs(url):
|
||||
"""Handler for qute://pdfjs. Return the pdf.js viewer."""
|
||||
if url.path() == '/file':
|
||||
filename = QUrlQuery(url).queryItemValue('filename')
|
||||
if not filename:
|
||||
raise UrlInvalidError("Missing filename")
|
||||
if '/' in filename or os.sep in filename:
|
||||
raise RequestDeniedError("Path separator in filename.")
|
||||
|
||||
path = os.path.join(downloads.temp_download_manager.get_tmpdir().name,
|
||||
filename)
|
||||
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
|
||||
mimetype = utils.guess_mimetype(filename, fallback=True)
|
||||
return mimetype, data
|
||||
|
||||
if url.path() == '/web/viewer.html':
|
||||
filename = QUrlQuery(url).queryItemValue("filename")
|
||||
if not filename:
|
||||
raise UrlInvalidError("Missing filename")
|
||||
data = pdfjs.generate_pdfjs_page(filename, url)
|
||||
return 'text/html', data
|
||||
|
||||
try:
|
||||
data = pdfjs.get_pdfjs_res(url.path())
|
||||
except pdfjs.PDFJSNotFound as e:
|
||||
# Logging as the error might get lost otherwise since we're not showing
|
||||
# the error page if a single asset is missing. This way we don't lose
|
||||
# information, as the failed pdfjs requests are still in the log.
|
||||
log.misc.warning(
|
||||
"pdfjs resource requested but not found: {}".format(e.path))
|
||||
raise NotFoundError("Can't find pdfjs resource '{}'".format(e.path))
|
||||
else:
|
||||
mimetype = utils.guess_mimetype(url.fileName(), fallback=True)
|
||||
return mimetype, data
|
||||
|
@ -27,7 +27,7 @@ import functools
|
||||
from PyQt5.QtCore import pyqtSlot, Qt
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineDownloadItem
|
||||
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.browser import downloads, pdfjs
|
||||
from qutebrowser.utils import debug, usertypes, message, log, qtutils
|
||||
|
||||
|
||||
@ -221,6 +221,9 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
download.set_target(self._mhtml_target)
|
||||
self._mhtml_target = None
|
||||
return
|
||||
if pdfjs.should_use_pdfjs(qt_item.mimeType(), qt_item.url()):
|
||||
download.set_target(downloads.PDFJSDownloadTarget())
|
||||
return
|
||||
|
||||
filename = downloads.immediate_download_path()
|
||||
if filename is not None:
|
||||
|
@ -19,14 +19,12 @@
|
||||
|
||||
"""QtWebKit specific qute://* handlers and glue code."""
|
||||
|
||||
import mimetypes
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkAccessManager
|
||||
|
||||
from qutebrowser.browser import pdfjs, qutescheme
|
||||
from qutebrowser.browser import qutescheme
|
||||
from qutebrowser.browser.webkit.network import networkreply
|
||||
from qutebrowser.utils import log, usertypes, qtutils
|
||||
from qutebrowser.utils import log, qtutils
|
||||
|
||||
|
||||
def handler(request, operation, current_url):
|
||||
@ -81,22 +79,3 @@ def handler(request, operation, current_url):
|
||||
return networkreply.RedirectNetworkReply(e.url)
|
||||
|
||||
return networkreply.FixedDataNetworkReply(request, data, mimetype)
|
||||
|
||||
|
||||
@qutescheme.add_handler('pdfjs', backend=usertypes.Backend.QtWebKit)
|
||||
def qute_pdfjs(url):
|
||||
"""Handler for qute://pdfjs. Return the pdf.js viewer."""
|
||||
try:
|
||||
data = pdfjs.get_pdfjs_res(url.path())
|
||||
except pdfjs.PDFJSNotFound as e:
|
||||
# Logging as the error might get lost otherwise since we're not showing
|
||||
# the error page if a single asset is missing. This way we don't lose
|
||||
# information, as the failed pdfjs requests are still in the log.
|
||||
log.misc.warning(
|
||||
"pdfjs resource requested but not found: {}".format(e.path))
|
||||
raise qutescheme.NotFoundError("Can't find pdfjs resource '{}'".format(
|
||||
e.path))
|
||||
else:
|
||||
mimetype, _encoding = mimetypes.guess_type(url.fileName())
|
||||
assert mimetype is not None, url
|
||||
return mimetype, data
|
||||
|
@ -30,7 +30,7 @@ from PyQt5.QtPrintSupport import QPrintDialog
|
||||
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.browser import pdfjs, shared
|
||||
from qutebrowser.browser import pdfjs, shared, downloads
|
||||
from qutebrowser.browser.webkit import http
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
from qutebrowser.utils import message, usertypes, log, jinja, objreg
|
||||
@ -206,18 +206,6 @@ class BrowserPage(QWebPage):
|
||||
suggested_file)
|
||||
return True
|
||||
|
||||
def _show_pdfjs(self, reply):
|
||||
"""Show the reply with pdfjs."""
|
||||
try:
|
||||
page = pdfjs.generate_pdfjs_page(reply.url())
|
||||
except pdfjs.PDFJSNotFound:
|
||||
page = jinja.render('no_pdfjs.html',
|
||||
url=reply.url().toDisplayString(),
|
||||
title="PDF.js not found")
|
||||
self.mainFrame().setContent(page.encode('utf-8'), 'text/html',
|
||||
reply.url())
|
||||
reply.deleteLater()
|
||||
|
||||
def shutdown(self):
|
||||
"""Prepare the web page for being deleted."""
|
||||
self._is_shutting_down = True
|
||||
@ -280,10 +268,9 @@ class BrowserPage(QWebPage):
|
||||
else:
|
||||
reply.finished.connect(functools.partial(
|
||||
self.display_content, reply, 'image/jpeg'))
|
||||
elif (mimetype in ['application/pdf', 'application/x-pdf'] and
|
||||
config.val.content.pdfjs):
|
||||
# Use pdf.js to display the page
|
||||
self._show_pdfjs(reply)
|
||||
elif pdfjs.should_use_pdfjs(mimetype, reply.url()):
|
||||
download_manager.fetch(reply,
|
||||
target=downloads.PDFJSDownloadTarget())
|
||||
else:
|
||||
# Unknown mimetype, so download anyways.
|
||||
download_manager.fetch(reply,
|
||||
|
@ -432,7 +432,7 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
|
||||
cmd_path = os.path.expanduser(cmd)
|
||||
|
||||
# if cmd is not given as an absolute path, look it up
|
||||
# ~/.local/share/qutebrowser/userscripts (or $XDG_DATA_DIR)
|
||||
# ~/.local/share/qutebrowser/userscripts (or $XDG_DATA_HOME)
|
||||
if not os.path.isabs(cmd_path):
|
||||
log.misc.debug("{} is no absolute path".format(cmd_path))
|
||||
cmd_path = _lookup_path(cmd)
|
||||
|
@ -220,10 +220,12 @@ content.autoplay:
|
||||
backend:
|
||||
QtWebEngine: Qt 5.10
|
||||
QtWebKit: false
|
||||
supports_pattern: true
|
||||
desc: >-
|
||||
Automatically start playing `<video>` elements.
|
||||
|
||||
Note this option needs a restart with QtWebEngine on Qt < 5.11.
|
||||
Note: On Qt < 5.11, this option needs a restart and does not support URL
|
||||
patterns.
|
||||
|
||||
content.cache.size:
|
||||
default: null
|
||||
@ -639,7 +641,6 @@ content.notifications:
|
||||
content.pdfjs:
|
||||
default: false
|
||||
type: Bool
|
||||
backend: QtWebKit
|
||||
desc: >-
|
||||
Allow pdf.js to view PDF files in the browser.
|
||||
|
||||
|
@ -35,6 +35,7 @@ class SqliteErrorCode:
|
||||
in qutebrowser here.
|
||||
"""
|
||||
|
||||
UNKNOWN = '-1'
|
||||
BUSY = '5' # database is locked
|
||||
READONLY = '8' # attempt to write a readonly database
|
||||
IOERR = '10' # disk I/O error
|
||||
@ -86,12 +87,17 @@ class SqlBugError(SqlError):
|
||||
|
||||
def raise_sqlite_error(msg, error):
|
||||
"""Raise either a SqlBugError or SqlEnvironmentError."""
|
||||
error_code = error.nativeErrorCode()
|
||||
database_text = error.databaseText()
|
||||
driver_text = error.driverText()
|
||||
|
||||
log.sql.debug("SQL error:")
|
||||
log.sql.debug("type: {}".format(
|
||||
debug.qenum_key(QSqlError, error.type())))
|
||||
log.sql.debug("database text: {}".format(error.databaseText()))
|
||||
log.sql.debug("driver text: {}".format(error.driverText()))
|
||||
log.sql.debug("error code: {}".format(error.nativeErrorCode()))
|
||||
log.sql.debug("database text: {}".format(database_text))
|
||||
log.sql.debug("driver text: {}".format(driver_text))
|
||||
log.sql.debug("error code: {}".format(error_code))
|
||||
|
||||
environmental_errors = [
|
||||
SqliteErrorCode.BUSY,
|
||||
SqliteErrorCode.READONLY,
|
||||
@ -100,17 +106,15 @@ def raise_sqlite_error(msg, error):
|
||||
SqliteErrorCode.FULL,
|
||||
SqliteErrorCode.CANTOPEN,
|
||||
]
|
||||
# At least in init(), we can get errors like this:
|
||||
# > type: ConnectionError
|
||||
# > database text: out of memory
|
||||
# > driver text: Error opening database
|
||||
# > error code: -1
|
||||
environmental_strings = [
|
||||
"out of memory",
|
||||
]
|
||||
errcode = error.nativeErrorCode()
|
||||
if (errcode in environmental_errors or
|
||||
(errcode == -1 and error.databaseText() in environmental_strings)):
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70506
|
||||
# We don't know what the actual error was, but let's assume it's not us to
|
||||
# blame... Usually this is something like an unreadable database file.
|
||||
qtbug_70506 = (error_code == SqliteErrorCode.UNKNOWN and
|
||||
driver_text == "Error opening database" and
|
||||
database_text == "out of memory")
|
||||
|
||||
if error_code in environmental_errors or qtbug_70506:
|
||||
raise SqlEnvironmentError(msg, error)
|
||||
else:
|
||||
raise SqlBugError(msg, error)
|
||||
|
@ -22,7 +22,6 @@
|
||||
import os
|
||||
import os.path
|
||||
import contextlib
|
||||
import mimetypes
|
||||
import html
|
||||
|
||||
import jinja2
|
||||
@ -108,9 +107,8 @@ class Environment(jinja2.Environment):
|
||||
"""Get a data: url for the broken qutebrowser logo."""
|
||||
data = utils.read_file(path, binary=True)
|
||||
filename = utils.resource_filename(path)
|
||||
mimetype = mimetypes.guess_type(filename)
|
||||
assert mimetype is not None, path
|
||||
return urlutils.data_url(mimetype[0], data).toString()
|
||||
mimetype = utils.guess_mimetype(filename)
|
||||
return urlutils.data_url(mimetype, data).toString()
|
||||
|
||||
def getattr(self, obj, attribute):
|
||||
"""Override jinja's getattr() to be less clever.
|
||||
|
@ -33,6 +33,7 @@ import contextlib
|
||||
import socket
|
||||
import shlex
|
||||
import glob
|
||||
import mimetypes
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QColor, QClipboard, QDesktopServices
|
||||
@ -683,3 +684,19 @@ def chunk(elems, n):
|
||||
raise ValueError("n needs to be at least 1!")
|
||||
for i in range(0, len(elems), n):
|
||||
yield elems[i:i + n]
|
||||
|
||||
|
||||
def guess_mimetype(filename, fallback=False):
|
||||
"""Guess a mimetype based on a filename.
|
||||
|
||||
Args:
|
||||
filename: The filename to check.
|
||||
fallback: Fall back to application/octet-stream if unknown.
|
||||
"""
|
||||
mimetype, _encoding = mimetypes.guess_type(filename)
|
||||
if mimetype is None:
|
||||
if fallback:
|
||||
return 'application/octet-stream'
|
||||
else:
|
||||
raise ValueError("Got None mimetype for {}".format(filename))
|
||||
return mimetype
|
||||
|
@ -64,6 +64,8 @@ PERFECT_FILES = [
|
||||
'browser/webkit/cookies.py'),
|
||||
('tests/unit/browser/test_history.py',
|
||||
'browser/history.py'),
|
||||
('tests/unit/browser/test_pdfjs.py',
|
||||
'browser/pdfjs.py'),
|
||||
('tests/unit/browser/webkit/http/test_http.py',
|
||||
'browser/webkit/http.py'),
|
||||
('tests/unit/browser/webkit/http/test_content_disposition.py',
|
||||
|
@ -17,51 +17,117 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.browser import pdfjs
|
||||
from qutebrowser.utils import usertypes, utils
|
||||
|
||||
|
||||
@pytest.mark.parametrize('available, snippet', [
|
||||
pytest.param(True, '<title>PDF.js viewer</title>',
|
||||
marks=pytest.mark.skipif(not pdfjs.is_available(),
|
||||
reason='PDF.js unavailable')),
|
||||
(False, '<h1>No pdf.js installation found</h1>'),
|
||||
('force', 'fake PDF.js'),
|
||||
])
|
||||
def test_generate_pdfjs_page(available, snippet, monkeypatch):
|
||||
if available == 'force':
|
||||
monkeypatch.setattr(pdfjs, 'is_available', lambda: True)
|
||||
monkeypatch.setattr(pdfjs, 'get_pdfjs_res',
|
||||
lambda filename: b'fake PDF.js')
|
||||
else:
|
||||
monkeypatch.setattr(pdfjs, 'is_available', lambda: available)
|
||||
|
||||
content = pdfjs.generate_pdfjs_page('example.pdf', QUrl())
|
||||
print(content)
|
||||
assert snippet in content
|
||||
|
||||
|
||||
# Note that we got double protection, once because we use QUrl.FullyEncoded and
|
||||
# because we use qutebrowser.utils.javascript.string_escape. Characters
|
||||
# like " are already replaced by QUrl.
|
||||
@pytest.mark.parametrize('url, expected', [
|
||||
('http://foo.bar', "http://foo.bar"),
|
||||
('http://"', ''),
|
||||
('\0', '%00'),
|
||||
('http://foobar/");alert("attack!");',
|
||||
'http://foobar/%22);alert(%22attack!%22);'),
|
||||
@pytest.mark.parametrize('filename, expected', [
|
||||
('foo.bar', "foo.bar"),
|
||||
('foo"bar', "foo%22bar"),
|
||||
('foo\0bar', 'foo%00bar'),
|
||||
('foobar");alert("attack!");',
|
||||
'foobar%22);alert(%22attack!%22);'),
|
||||
])
|
||||
def test_generate_pdfjs_script(url, expected):
|
||||
expected_open = 'open("{}");'.format(expected)
|
||||
url = QUrl(url)
|
||||
actual = pdfjs._generate_pdfjs_script(url)
|
||||
def test_generate_pdfjs_script(filename, expected):
|
||||
expected_open = 'open("qute://pdfjs/file?filename={}");'.format(expected)
|
||||
actual = pdfjs._generate_pdfjs_script(filename)
|
||||
assert expected_open in actual
|
||||
assert 'PDFView' in actual
|
||||
|
||||
|
||||
def test_fix_urls():
|
||||
page = textwrap.dedent("""
|
||||
<html>
|
||||
<script src="viewer.js"></script>
|
||||
<link href="viewer.css">
|
||||
<script src="unrelated.js"></script>
|
||||
</html>
|
||||
""").strip()
|
||||
@pytest.mark.parametrize('qt, backend, expected', [
|
||||
('new', usertypes.Backend.QtWebEngine, False),
|
||||
('new', usertypes.Backend.QtWebKit, False),
|
||||
('old', usertypes.Backend.QtWebEngine, True),
|
||||
('old', usertypes.Backend.QtWebKit, False),
|
||||
('5.7', usertypes.Backend.QtWebEngine, False),
|
||||
('5.7', usertypes.Backend.QtWebKit, False),
|
||||
])
|
||||
def test_generate_pdfjs_script_disable_object_url(monkeypatch,
|
||||
qt, backend, expected):
|
||||
if qt == 'new':
|
||||
monkeypatch.setattr(pdfjs.qtutils, 'version_check',
|
||||
lambda version, exact=False, compiled=True:
|
||||
False if version == '5.7.1' else True)
|
||||
elif qt == 'old':
|
||||
monkeypatch.setattr(pdfjs.qtutils, 'version_check',
|
||||
lambda version, exact=False, compiled=True: False)
|
||||
elif qt == '5.7':
|
||||
monkeypatch.setattr(pdfjs.qtutils, 'version_check',
|
||||
lambda version, exact=False, compiled=True:
|
||||
True if version == '5.7.1' else False)
|
||||
else:
|
||||
raise utils.Unreachable
|
||||
|
||||
expected = textwrap.dedent("""
|
||||
<html>
|
||||
<script src="qute://pdfjs/web/viewer.js"></script>
|
||||
<link href="qute://pdfjs/web/viewer.css">
|
||||
<script src="unrelated.js"></script>
|
||||
</html>
|
||||
""").strip()
|
||||
monkeypatch.setattr(pdfjs.objects, 'backend', backend)
|
||||
|
||||
actual = pdfjs.fix_urls(page)
|
||||
assert actual == expected
|
||||
script = pdfjs._generate_pdfjs_script('testfile')
|
||||
assert ('PDFJS.disableCreateObjectURL' in script) == expected
|
||||
|
||||
|
||||
class TestResources:
|
||||
|
||||
@pytest.fixture
|
||||
def read_system_mock(self, mocker):
|
||||
return mocker.patch.object(pdfjs, '_read_from_system', autospec=True)
|
||||
|
||||
@pytest.fixture
|
||||
def read_file_mock(self, mocker):
|
||||
return mocker.patch.object(pdfjs.utils, 'read_file', autospec=True)
|
||||
|
||||
def test_get_pdfjs_res_system(self, read_system_mock):
|
||||
read_system_mock.return_value = (b'content', 'path')
|
||||
|
||||
assert pdfjs.get_pdfjs_res_and_path('web/test') == (b'content', 'path')
|
||||
assert pdfjs.get_pdfjs_res('web/test') == b'content'
|
||||
|
||||
read_system_mock.assert_called_with('/usr/share/pdf.js/',
|
||||
['web/test', 'test'])
|
||||
|
||||
def test_get_pdfjs_res_bundled(self, read_system_mock, read_file_mock):
|
||||
read_system_mock.return_value = (None, None)
|
||||
|
||||
read_file_mock.return_value = b'content'
|
||||
|
||||
assert pdfjs.get_pdfjs_res_and_path('web/test') == (b'content', None)
|
||||
assert pdfjs.get_pdfjs_res('web/test') == b'content'
|
||||
|
||||
for path in pdfjs.SYSTEM_PDFJS_PATHS:
|
||||
read_system_mock.assert_any_call(path, ['web/test', 'test'])
|
||||
|
||||
def test_get_pdfjs_res_not_found(self, read_system_mock, read_file_mock):
|
||||
read_system_mock.return_value = (None, None)
|
||||
read_file_mock.side_effect = FileNotFoundError
|
||||
|
||||
with pytest.raises(pdfjs.PDFJSNotFound,
|
||||
match="Path 'web/test' not found"):
|
||||
pdfjs.get_pdfjs_res_and_path('web/test')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('path, expected', [
|
||||
@ -72,3 +138,58 @@ def test_fix_urls():
|
||||
])
|
||||
def test_remove_prefix(path, expected):
|
||||
assert pdfjs._remove_prefix(path) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('names, expected_name', [
|
||||
(['one'], 'one'),
|
||||
(['doesnotexist', 'two'], 'two'),
|
||||
(['one', 'two'], 'one'),
|
||||
(['does', 'not', 'onexist'], None),
|
||||
])
|
||||
def test_read_from_system(names, expected_name, tmpdir):
|
||||
file1 = tmpdir / 'one'
|
||||
file1.write_text('text1', encoding='ascii')
|
||||
file2 = tmpdir / 'two'
|
||||
file2.write_text('text2', encoding='ascii')
|
||||
|
||||
if expected_name == 'one':
|
||||
expected = (b'text1', str(file1))
|
||||
elif expected_name == 'two':
|
||||
expected = (b'text2', str(file2))
|
||||
elif expected_name is None:
|
||||
expected = (None, None)
|
||||
|
||||
assert pdfjs._read_from_system(str(tmpdir), names) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('available', [True, False])
|
||||
def test_is_available(available, mocker):
|
||||
mock = mocker.patch.object(pdfjs, 'get_pdfjs_res', autospec=True)
|
||||
if available:
|
||||
mock.return_value = b'foo'
|
||||
else:
|
||||
mock.side_effect = pdfjs.PDFJSNotFound('build/pdf.js')
|
||||
|
||||
assert pdfjs.is_available() == available
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mimetype, url, enabled, expected', [
|
||||
# PDF files
|
||||
('application/pdf', 'http://www.example.com', True, True),
|
||||
('application/x-pdf', 'http://www.example.com', True, True),
|
||||
# Not a PDF
|
||||
('application/octet-stream', 'http://www.example.com', True, False),
|
||||
# PDF.js disabled
|
||||
('application/pdf', 'http://www.example.com', False, False),
|
||||
# Download button in PDF.js
|
||||
('application/pdf', 'blob:qute%3A///b45250b3', True, False),
|
||||
])
|
||||
def test_should_use_pdfjs(mimetype, url, enabled, expected, config_stub):
|
||||
config_stub.val.content.pdfjs = enabled
|
||||
assert pdfjs.should_use_pdfjs(mimetype, QUrl(url)) == expected
|
||||
|
||||
|
||||
def test_get_main_url():
|
||||
expected = ('qute://pdfjs/web/viewer.html?filename='
|
||||
'hello?world.pdf&file=')
|
||||
assert pdfjs.get_main_url('hello?world.pdf') == QUrl(expected)
|
||||
|
@ -20,11 +20,13 @@
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
import py.path # pylint: disable=no-name-in-module
|
||||
from PyQt5.QtCore import QUrl, QUrlQuery
|
||||
import pytest
|
||||
|
||||
from qutebrowser.browser import qutescheme
|
||||
from qutebrowser.browser import qutescheme, pdfjs, downloads
|
||||
|
||||
|
||||
class TestJavascriptHandler:
|
||||
@ -169,3 +171,68 @@ class TestHelpHandler:
|
||||
mimetype, data = qutescheme.qute_help(QUrl('qute://help/foo.bin'))
|
||||
assert mimetype == 'application/octet-stream'
|
||||
assert data == b'\xff'
|
||||
|
||||
|
||||
class TestPDFJSHandler:
|
||||
|
||||
"""Test the qute://pdfjs endpoint."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def fake_pdfjs(self, monkeypatch):
|
||||
def get_pdfjs_res(path):
|
||||
if path == '/existing/file.html':
|
||||
return b'foobar'
|
||||
raise pdfjs.PDFJSNotFound(path)
|
||||
|
||||
monkeypatch.setattr(pdfjs, 'get_pdfjs_res', get_pdfjs_res)
|
||||
|
||||
@pytest.fixture
|
||||
def download_tmpdir(self):
|
||||
tdir = downloads.temp_download_manager.get_tmpdir()
|
||||
yield py.path.local(tdir.name) # pylint: disable=no-member
|
||||
tdir.cleanup()
|
||||
|
||||
def test_existing_resource(self):
|
||||
"""Test with a resource that exists."""
|
||||
_mimetype, data = qutescheme.data_for_url(
|
||||
QUrl('qute://pdfjs/existing/file.html'))
|
||||
assert data == b'foobar'
|
||||
|
||||
def test_nonexisting_resource(self, caplog):
|
||||
"""Test with a resource that does not exist."""
|
||||
with caplog.at_level(logging.WARNING, 'misc'):
|
||||
with pytest.raises(qutescheme.NotFoundError):
|
||||
qutescheme.data_for_url(QUrl('qute://pdfjs/no/file.html'))
|
||||
assert len(caplog.records) == 1
|
||||
assert (caplog.records[0].message ==
|
||||
'pdfjs resource requested but not found: /no/file.html')
|
||||
|
||||
def test_viewer_page(self):
|
||||
"""Load the /web/viewer.html page."""
|
||||
_mimetype, data = qutescheme.data_for_url(
|
||||
QUrl('qute://pdfjs/web/viewer.html?filename=foobar'))
|
||||
assert b'PDF.js' in data
|
||||
|
||||
def test_viewer_no_filename(self):
|
||||
with pytest.raises(qutescheme.UrlInvalidError):
|
||||
qutescheme.data_for_url(QUrl('qute://pdfjs/web/viewer.html'))
|
||||
|
||||
def test_file(self, download_tmpdir):
|
||||
"""Load a file via qute://pdfjs/file."""
|
||||
(download_tmpdir / 'testfile').write_binary(b'foo')
|
||||
_mimetype, data = qutescheme.data_for_url(
|
||||
QUrl('qute://pdfjs/file?filename=testfile'))
|
||||
assert data == b'foo'
|
||||
|
||||
def test_file_no_filename(self):
|
||||
with pytest.raises(qutescheme.UrlInvalidError):
|
||||
qutescheme.data_for_url(QUrl('qute://pdfjs/file'))
|
||||
|
||||
@pytest.mark.parametrize('sep', ['/', os.sep])
|
||||
def test_file_pathsep(self, sep):
|
||||
url = QUrl('qute://pdfjs/file')
|
||||
query = QUrlQuery()
|
||||
query.addQueryItem('filename', 'foo{}bar'.format(sep))
|
||||
url.setQuery(query)
|
||||
with pytest.raises(qutescheme.RequestDeniedError):
|
||||
qutescheme.data_for_url(url)
|
||||
|
@ -1,63 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2018 Daniel Schadt
|
||||
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.utils import usertypes
|
||||
from qutebrowser.browser import pdfjs, qutescheme
|
||||
# pylint: disable=unused-import
|
||||
from qutebrowser.browser.webkit.network import webkitqutescheme
|
||||
# pylint: enable=unused-import
|
||||
|
||||
|
||||
class TestPDFJSHandler:
|
||||
"""Test the qute://pdfjs endpoint."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def fake_pdfjs(self, monkeypatch):
|
||||
def get_pdfjs_res(path):
|
||||
if path == '/existing/file.html':
|
||||
return b'foobar'
|
||||
raise pdfjs.PDFJSNotFound(path)
|
||||
|
||||
monkeypatch.setattr(pdfjs, 'get_pdfjs_res', get_pdfjs_res)
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch_backend(self, monkeypatch):
|
||||
monkeypatch.setattr(qutescheme.objects, 'backend',
|
||||
usertypes.Backend.QtWebKit)
|
||||
|
||||
def test_existing_resource(self):
|
||||
"""Test with a resource that exists."""
|
||||
_mimetype, data = qutescheme.data_for_url(
|
||||
QUrl('qute://pdfjs/existing/file.html'))
|
||||
assert data == b'foobar'
|
||||
|
||||
def test_nonexisting_resource(self, caplog):
|
||||
"""Test with a resource that does not exist."""
|
||||
with caplog.at_level(logging.WARNING, 'misc'):
|
||||
with pytest.raises(qutescheme.NotFoundError):
|
||||
qutescheme.data_for_url(QUrl('qute://pdfjs/no/file.html'))
|
||||
assert len(caplog.records) == 1
|
||||
assert (caplog.records[0].message ==
|
||||
'pdfjs resource requested but not found: /no/file.html')
|
@ -68,10 +68,6 @@ def test_page_titles(url, title, out):
|
||||
|
||||
class TestDownloadTarget:
|
||||
|
||||
def test_base(self):
|
||||
with pytest.raises(NotImplementedError):
|
||||
downloads._DownloadTarget()
|
||||
|
||||
def test_filename(self):
|
||||
target = downloads.FileDownloadTarget("/foo/bar")
|
||||
assert target.filename == "/foo/bar"
|
||||
|
@ -145,6 +145,26 @@ def test_load_emits_signal(qtbot):
|
||||
gm_manager.load_scripts()
|
||||
|
||||
|
||||
def test_utf8_bom():
|
||||
"""Make sure UTF-8 BOMs are stripped from scripts.
|
||||
|
||||
If we don't strip them, we'll have a BOM in the middle of the file, causing
|
||||
QtWebEngine to not catch the "// ==UserScript==" line.
|
||||
"""
|
||||
script = textwrap.dedent("""
|
||||
\N{BYTE ORDER MARK}// ==UserScript==
|
||||
// @name qutebrowser test userscript
|
||||
// ==/UserScript==
|
||||
""".lstrip('\n'))
|
||||
_save_script(script, 'bom.user.js')
|
||||
gm_manager = greasemonkey.GreasemonkeyManager()
|
||||
|
||||
scripts = gm_manager.all_scripts()
|
||||
assert len(scripts) == 1
|
||||
script = scripts[0]
|
||||
assert '// ==UserScript==' in script.code().splitlines()
|
||||
|
||||
|
||||
def test_required_scripts_are_included(download_stub, tmpdir):
|
||||
test_require_script = textwrap.dedent("""
|
||||
// ==UserScript==
|
||||
|
@ -49,6 +49,19 @@ class TestSqlError:
|
||||
with pytest.raises(exception):
|
||||
sql.raise_sqlite_error("Message", sql_err)
|
||||
|
||||
def test_qtbug_70506(self):
|
||||
"""Test Qt's wrong handling of errors while opening the database.
|
||||
|
||||
Due to https://bugreports.qt.io/browse/QTBUG-70506 we get an error with
|
||||
"out of memory" as string and -1 as error code.
|
||||
"""
|
||||
sql_err = QSqlError("Error opening database",
|
||||
"out of memory",
|
||||
QSqlError.UnknownError,
|
||||
sql.SqliteErrorCode.UNKNOWN)
|
||||
with pytest.raises(sql.SqlEnvironmentError):
|
||||
sql.raise_sqlite_error("Message", sql_err)
|
||||
|
||||
def test_logging(self, caplog):
|
||||
sql_err = QSqlError("driver text", "db text", QSqlError.UnknownError,
|
||||
'23')
|
||||
|
@ -816,3 +816,16 @@ def test_chunk(elems, n, expected):
|
||||
def test_chunk_invalid(n):
|
||||
with pytest.raises(ValueError):
|
||||
list(utils.chunk([], n))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('filename, expected', [
|
||||
('test.jpg', 'image/jpeg'),
|
||||
('test.blabla', 'application/octet-stream'),
|
||||
])
|
||||
def test_guess_mimetype(filename, expected):
|
||||
assert utils.guess_mimetype(filename, fallback=True) == expected
|
||||
|
||||
|
||||
def test_guess_mimetype_no_fallback():
|
||||
with pytest.raises(ValueError):
|
||||
utils.guess_mimetype('test.blabla')
|
||||
|
Loading…
Reference in New Issue
Block a user