summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <git@the-compiler.org>2016-01-05 19:12:57 +0100
committerFlorian Bruhin <git@the-compiler.org>2016-01-05 19:12:57 +0100
commit2ccb4342a4d0fb471629c4630ccdaf3b6d77af93 (patch)
treefbeb6eca20615c5e3711c4b6c75dcf47d0600d5e
parente60347026051e7d1af1c1d5aec4799475f36ecbb (diff)
parent0d6d72273283403b1f180a5bfc6da097d1c8a442 (diff)
downloadqutebrowser-2ccb4342a4d0fb471629c4630ccdaf3b6d77af93.tar.gz
qutebrowser-2ccb4342a4d0fb471629c4630ccdaf3b6d77af93.zip
Merge branch 'Kingdread-pdfjs'
-rw-r--r--.eslintignore1
-rw-r--r--CHANGELOG.asciidoc3
-rw-r--r--MANIFEST.in2
-rw-r--r--README.asciidoc11
-rw-r--r--doc/help/settings.asciidoc14
-rw-r--r--qutebrowser/browser/network/qutescheme.py14
-rw-r--r--qutebrowser/browser/pdfjs.py175
-rw-r--r--qutebrowser/browser/webpage.py19
-rw-r--r--qutebrowser/config/configdata.py5
-rw-r--r--qutebrowser/html/no_pdfjs.html129
-rw-r--r--qutebrowser/utils/version.py22
-rwxr-xr-xscripts/dev/build_release.py4
-rwxr-xr-xscripts/dev/freeze.py5
-rw-r--r--scripts/dev/misc_checks.py9
-rwxr-xr-xscripts/dev/run_vulture.py1
-rwxr-xr-xscripts/dev/update_3rdparty.py64
-rw-r--r--tests/integration/data/misc/test.pdfbin0 -> 16667 bytes
-rw-r--r--tests/integration/features/misc.feature14
-rw-r--r--tests/integration/features/test_misc.py11
-rw-r--r--tests/unit/browser/test_pdfjs.py76
-rw-r--r--tests/unit/utils/test_version.py36
21 files changed, 611 insertions, 4 deletions
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 000000000..7e2863113
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+qutebrowser/3rdparty/pdfjs/*
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 018febe51..a41d3b4c4 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -20,6 +20,9 @@ v0.5.0 (unreleased)
Added
~~~~~
+- Ability to preview PDFs using pdf.js in the browser if it's installed. This
+ is disabled by default and can be enabled using the
+ `content -> pdfjs-enabled` setting.
- New setting `ui -> hide-wayland-decoration` to hide the window decoration
when using wayland.
- New userscripts in `misc/userscripts`:
diff --git a/MANIFEST.in b/MANIFEST.in
index 07726eca7..e2dd15509 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -3,6 +3,7 @@ recursive-include qutebrowser/html *.html
recursive-include qutebrowser/img *.svg *.png
recursive-include qutebrowser/test *.py
recursive-include qutebrowser/javascript *.js
+graft qutebrowser/3rdparty
graft icons
graft doc/img
graft misc
@@ -27,6 +28,7 @@ exclude qutebrowser.rcc
exclude .coveragerc
exclude .pylintrc
exclude .eslintrc
+exclude .eslintignore
exclude doc/help
exclude .appveyor.yml
exclude .travis.yml
diff --git a/README.asciidoc b/README.asciidoc
index ff903ce99..51c0f5803 100644
--- a/README.asciidoc
+++ b/README.asciidoc
@@ -272,3 +272,14 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+pdf.js
+------
+
+qutebrowser optionally uses https://github.com/mozilla/pdf.js/[pdf.js] to
+display PDF files in the browser. Windows releases come with a bundled pdf.js.
+
+pdf.js is distributed under the terms of the Apache License. You can
+find a copy of the license in `qutebrowser/3rdparty/pdfjs/LICENSE` (in the
+Windows release or after running `scripts/dev/update_3rdparty.py`), or online
+http://www.apache.org/licenses/LICENSE-2.0.html[here].
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 8b63eb9d5..0dc8a9e3b 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -165,6 +165,7 @@
|<<content-host-block-lists,host-block-lists>>|List of URLs of lists which contain hosts to block.
|<<content-host-blocking-enabled,host-blocking-enabled>>|Whether host blocking is enabled.
|<<content-host-blocking-whitelist,host-blocking-whitelist>>|List of domains that should always be loaded, despite being ad-blocked.
+|<<content-enable-pdfjs,enable-pdfjs>>|Enable pdf.js to view PDF files in the browser.
|==============
.Quick reference for section ``hints''
@@ -1501,6 +1502,19 @@ Local domains are always exempt from hostblocking.
Default: +pass:[piwik.org]+
+[[content-enable-pdfjs]]
+=== enable-pdfjs
+Enable pdf.js to view PDF files in the browser.
+
+Note that the files can still be downloaded by clicking the download button in the pdf.js viewer.
+
+Valid values:
+
+ * +true+
+ * +false+
+
+Default: +pass:[false]+
+
== hints
Hinting settings.
diff --git a/qutebrowser/browser/network/qutescheme.py b/qutebrowser/browser/network/qutescheme.py
index e74bdd2bc..876d85e1f 100644
--- a/qutebrowser/browser/network/qutescheme.py
+++ b/qutebrowser/browser/network/qutescheme.py
@@ -31,11 +31,13 @@ Module attributes:
import functools
import configparser
+import mimetypes
from PyQt5.QtCore import pyqtSlot, QObject
from PyQt5.QtNetwork import QNetworkReply
import qutebrowser
+from qutebrowser.browser import pdfjs
from qutebrowser.browser.network import schemehandler, networkreply
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
objreg)
@@ -93,8 +95,11 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
return networkreply.ErrorNetworkReply(
request, str(e), QNetworkReply.ContentNotFoundError,
self.parent())
+ mimetype, _encoding = mimetypes.guess_type(request.url().fileName())
+ if mimetype is None:
+ mimetype = 'text/html'
return networkreply.FixedDataNetworkReply(
- request, data, 'text/html', self.parent())
+ request, data, mimetype, self.parent())
class JSBridge(QObject):
@@ -201,3 +206,10 @@ def qute_settings(win_id, _request):
win_id=win_id, title='settings', config=configdata,
confget=config_getter)
return html.encode('UTF-8', errors='xmlcharrefreplace')
+
+
+@add_handler('pdfjs')
+def qute_pdfjs(_win_id, request):
+ """Handler for qute://pdfjs. Return the pdf.js viewer."""
+ urlpath = request.url().path()
+ return pdfjs.get_pdfjs_res(urlpath)
diff --git a/qutebrowser/browser/pdfjs.py b/qutebrowser/browser/pdfjs.py
new file mode 100644
index 000000000..83b89330c
--- /dev/null
+++ b/qutebrowser/browser/pdfjs.py
@@ -0,0 +1,175 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2015 Daniel Schadt
+#
+# 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/>.
+
+"""pdf.js integration for qutebrowser."""
+
+import os
+
+from PyQt5.QtCore import QUrl
+
+from qutebrowser.browser import webelem
+from qutebrowser.utils import utils
+
+
+class PDFJSNotFound(Exception):
+
+ """Raised when no pdf.js installation is found."""
+
+ pass
+
+
+def generate_pdfjs_page(url):
+ """Return the html content of a page that displays url with pdfjs.
+
+ Returns a string.
+
+ Args:
+ url: The url of the pdf as QUrl.
+ """
+ viewer = get_pdfjs_res('web/viewer.html').decode('utf-8')
+ script = _generate_pdfjs_script(url)
+ html_page = viewer.replace(
+ '</body>', '</body><script>{}</script>'.format(script)
+ )
+ return html_page
+
+
+def _generate_pdfjs_script(url):
+ """Generate the script that shows the pdf with pdf.js.
+
+ Args:
+ url: The url of the pdf page as QUrl.
+ """
+ return (
+ 'PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
+ 'PDFView.open("{url}");\n'
+ ).format(url=webelem.javascript_escape(url.toString(QUrl.FullyEncoded)))
+
+
+def fix_urls(asset):
+ """Take a html page and replace each relative URL wth 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.items():
+ asset = asset.replace(original, new)
+ return asset
+
+
+SYSTEM_PDFJS_PATHS = [
+ '/usr/share/pdf.js/', # Debian pdf.js-common
+ '/usr/share/javascript/pdf/', # Debian libjs-pdf
+ os.path.expanduser('~/.local/share/qutebrowser/pdfjs/'), # fallback
+]
+
+
+def get_pdfjs_res(path):
+ """Get a pdf.js resource in binary format.
+
+ Args:
+ path: The path inside the pdfjs directory.
+ """
+ path = path.lstrip('/')
+ content = None
+
+ # First try a system wide installation
+ # System installations might strip off the 'build/' or 'web/' prefixes.
+ # qute expects them, so we need to adjust for it.
+ names_to_try = [path, _remove_prefix(path)]
+ for system_path in SYSTEM_PDFJS_PATHS:
+ content = _read_from_system(system_path, names_to_try)
+ if content is not None:
+ break
+
+ # Fallback to bundled pdf.js
+ if content is None:
+ res_path = '3rdparty/pdfjs/{}'.format(path)
+ try:
+ content = utils.read_file(res_path, binary=True)
+ except FileNotFoundError:
+ raise PDFJSNotFound
+
+ try:
+ # Might be script/html or might be binary
+ text_content = content.decode('utf-8')
+ except UnicodeDecodeError:
+ return content
+ text_content = fix_urls(text_content)
+ return text_content.encode('utf-8')
+
+
+def _remove_prefix(path):
+ """Remove the web/ or build/ prefix of a pdfjs-file-path.
+
+ Args:
+ path: Path as string where the prefix should be stripped off.
+ """
+ prefixes = {'web/', 'build/'}
+ if any(path.startswith(prefix) for prefix in prefixes):
+ return path.split('/', maxsplit=1)[1]
+ # Return the unchanged path if no prefix is found
+ return path
+
+
+def _read_from_system(system_path, names):
+ """Try to read a file with one of the given names in system_path.
+
+ Each file in names is considered equal, the first file that is found
+ is read and its binary content returned.
+
+ Returns None if no file could be found
+
+ Args:
+ system_path: The folder where the file should be searched.
+ names: List of possible file names.
+ """
+ for name in names:
+ try:
+ with open(os.path.join(system_path, name), 'rb') as f:
+ return f.read()
+ except OSError:
+ continue
+ return None
+
+
+def is_available():
+ """Return true if a pdfjs installation is available."""
+ try:
+ get_pdfjs_res('build/pdf.js')
+ except PDFJSNotFound:
+ return False
+ else:
+ return True
diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py
index e375f721d..49a77cc8c 100644
--- a/qutebrowser/browser/webpage.py
+++ b/qutebrowser/browser/webpage.py
@@ -30,7 +30,7 @@ from PyQt5.QtPrintSupport import QPrintDialog
from PyQt5.QtWebKitWidgets import QWebPage
from qutebrowser.config import config
-from qutebrowser.browser import http, tabhistory
+from qutebrowser.browser import http, tabhistory, pdfjs
from qutebrowser.browser.network import networkmanager
from qutebrowser.utils import (message, usertypes, log, jinja, qtutils, utils,
objreg, debug)
@@ -218,6 +218,19 @@ class BrowserPage(QWebPage):
q.deleteLater()
return q.answer
+ def _show_pdfjs(self, reply):
+ """Show the reply with pdfjs."""
+ try:
+ page = pdfjs.generate_pdfjs_page(reply.url()).encode('utf-8')
+ except pdfjs.PDFJSNotFound:
+ # pylint: disable=no-member
+ # WORKAROUND for https://bitbucket.org/logilab/pylint/issue/490/
+ page = (jinja.env.get_template('no_pdfjs.html')
+ .render(url=reply.url().toDisplayString())
+ .encode('utf-8'))
+ self.mainFrame().setContent(page, 'text/html', reply.url())
+ reply.deleteLater()
+
def shutdown(self):
"""Prepare the web page for being deleted."""
self._is_shutting_down = True
@@ -305,6 +318,10 @@ 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.get('content', 'enable-pdfjs')):
+ # Use pdf.js to display the page
+ self._show_pdfjs(reply)
else:
# Unknown mimetype, so download anyways.
download_manager.fetch(reply,
diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py
index 9114d7662..a9fbb2669 100644
--- a/qutebrowser/config/configdata.py
+++ b/qutebrowser/config/configdata.py
@@ -841,6 +841,11 @@ def data(readonly=False):
"required to exactly match the requested domain.\n\n"
"Local domains are always exempt from hostblocking."),
+ ('enable-pdfjs', SettingValue(typ.Bool(), 'false'),
+ "Enable pdf.js to view PDF files in the browser.\n\n"
+ "Note that the files can still be downloaded by clicking"
+ " the download button in the pdf.js viewer."),
+
readonly=readonly
)),
diff --git a/qutebrowser/html/no_pdfjs.html b/qutebrowser/html/no_pdfjs.html
new file mode 100644
index 000000000..694affc4d
--- /dev/null
+++ b/qutebrowser/html/no_pdfjs.html
@@ -0,0 +1,129 @@
+{% extends "base.html" %}
+{% block style %}
+{{ super() }}
+* {
+ margin: 0px 0px;
+ padding: 0px 0px;
+}
+
+body {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ -webkit-text-size-adjust: none;
+ color: #333333;
+ background-color: #EEEEEE;
+ font-size: 1.2em;
+}
+
+#error-container {
+ margin-left: 20px;
+ margin-right: 20px;
+ margin-top: 20px;
+ border: 1px solid #CCCCCC;
+ box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.20);
+ border-radius: 5px;
+ background-color: #FFFFFF;
+ padding: 20px 20px;
+}
+
+#header {
+ border-bottom: 1px solid #CCC;
+}
+
+.qutebrowser-broken {
+ display: block;
+ width: 100%;
+}
+
+span.warning {
+ text-weigth: bold;
+ color: red;
+}
+
+td {
+ margin-top: 20px;
+ color: #555;
+}
+
+h1, h2 {
+ font-weight: normal;
+ color: #1e89c6;
+ margin-bottom: 10px;
+}
+
+ul {
+ margin-left: 20px;
+ margin-top: 20px;
+ margin-bottom: 20px;
+}
+
+li {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+{% endblock %}
+
+{% block content %}
+<div id="error-container">
+ <table>
+ <tr>
+ <td style="width: 10%; vertical-align: top;">
+ <img style="width: 100%; display: block; max-width: 256px;" src="{{ resource_url('img/broken_qutebrowser_logo.png') }}" />
+ </td>
+ <td style="padding-left: 40px;">
+ <h1>No pdf.js installation found</h1>
+ <p>Error while opening {{ url }}: <br>
+ <p id="error-message-text" style="color: #a31a1a;">qutebrowser can't find a suitable pdf.js installation</p></p>
+
+ <p>It looks like you set <code>content -> enable-pdfjs</code>
+ to <em>true</em> but qutebrowser can't find the required files.</p>
+
+ <br>
+
+ <h2>Possible fixes</h2>
+ <ul>
+ <li>
+ Disable <code>content -> enable-pdfjs</code> and reload the page.
+ You will need to download the pdf-file and open it with an external
+ tool instead.
+ </li>
+
+ <li>
+ If you have installed a packaged version of qutebrowser, make sure
+ the required packages for pdf.js are also installed.
+ </li>
+
+ <li>
+ If you have installed a pdf.js package and qutebrowser still can't
+ find it, please send us a report with your system and the package
+ name, so we can add it to the list of supported packages.
+ </li>
+
+ <li>
+ If you're running a self-built version or the source version, make
+ sure you have pdf.js in <code>qutebrowser/3rdparty/pdfjs</code>.
+ You can use the <code>scripts/dev/update_3rdparty.py</code> script
+ to download the latest version.
+ </li>
+
+ <li>
+ You can manually download the pdf.js archive
+ <a href="https://mozilla.github.io/pdf.js/getting_started/#download">here</a>
+ and extract it to <code>~/.local/share/qutebrowser/pdfjs</code>
+ <br>
+ <span class="warning">Warning:</span> Using this method you are
+ responsible for yourself to keep the installation updated! If a
+ vulnerability is found in pdf.js, neither qutebrowser nor your
+ system's package manager will update your pdf.js installation.
+ Use it at your own risk!
+ </li>
+ </ul>
+
+ <p>
+ If none of these fixes work for you, please send us a bug report so
+ we can fix the issue.
+ </p>
+ </td>
+ </tr>
+ </table>
+</div>
+{% endblock %}
diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
index 7e5a185da..e96be6131 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -19,6 +19,7 @@
"""Utilities to show various version informations."""
+import re
import sys
import glob
import os.path
@@ -34,6 +35,7 @@ from PyQt5.QtWidgets import QApplication
import qutebrowser
from qutebrowser.utils import log, utils
+from qutebrowser.browser import pdfjs
GPL_BOILERPLATE = """
@@ -183,6 +185,25 @@ def _os_info():
return lines
+def _pdfjs_version():
+ """Get the pdf.js version.
+
+ Return:
+ A string with the version number.
+ """
+ try:
+ pdfjs_file = pdfjs.get_pdfjs_res('build/pdf.js').decode('utf-8')
+ except pdfjs.PDFJSNotFound:
+ return 'no'
+ else:
+ version_re = re.compile(r"^PDFJS\.version = '([^']+)';$", re.MULTILINE)
+ match = version_re.search(pdfjs_file)
+ if not match:
+ return 'unknown'
+ else:
+ return match.group(1)
+
+
def version(short=False):
"""Return a string with various version informations.
@@ -211,6 +232,7 @@ def version(short=False):
lines += _module_versions()
lines += [
+ 'pdf.js: {}'.format(_pdfjs_version()),
'Webkit: {}'.format(qWebKitVersion()),
'Harfbuzz: {}'.format(os.environ.get('QT_HARFBUZZ', 'system')),
'SSL: {}'.format(QSslSocket.sslLibraryVersionString()),
diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py
index b4bc831b9..a90a285a6 100755
--- a/scripts/dev/build_release.py
+++ b/scripts/dev/build_release.py
@@ -35,6 +35,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir,
import qutebrowser
from scripts import utils
+from scripts.dev import update_3rdparty
def call_script(name, *args, python=sys.executable):
@@ -89,6 +90,9 @@ def smoke_test(executable):
def build_windows():
"""Build windows executables/setups."""
+ utils.print_title("Updating 3rdparty content")
+ update_3rdparty.main()
+
utils.print_title("Building Windows binaries")
parts = str(sys.version_info.major), str(sys.version_info.minor)
ver = ''.join(parts)
diff --git a/scripts/dev/freeze.py b/scripts/dev/freeze.py
index b651dd194..00011afc7 100755
--- a/scripts/dev/freeze.py
+++ b/scripts/dev/freeze.py
@@ -67,6 +67,11 @@ def get_build_exe_options(skip_html=False):
('qutebrowser/html', 'html'),
]
+ if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
+ include_files.append(('qutebrowser/3rdparty/pdfjs', '3rdparty/pdfjs'))
+ else:
+ print("Warning: excluding pdfjs as it's not present!")
+
if not skip_html:
include_files += [
('qutebrowser/html/doc', 'html/doc'),
diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py
index ef32be41d..e629f2152 100644
--- a/scripts/dev/misc_checks.py
+++ b/scripts/dev/misc_checks.py
@@ -96,12 +96,19 @@ def check_spelling():
'[Ss]tatemachine', '[Mm]etaobject', '[Ll]ogrecord',
'[Ff]iletype'}
+ # Files which should be ignored, e.g. because they come from another
+ # package
+ ignored = [
+ os.path.join('.', 'scripts', 'dev', 'misc_checks.py'),
+ os.path.join('.', 'qutebrowser', '3rdparty', 'pdfjs'),
+ ]
+
seen = collections.defaultdict(list)
try:
ok = True
for fn in _get_files():
with tokenize.open(fn) as f:
- if fn == os.path.join('.', 'scripts', 'dev', 'misc_checks.py'):
+ if any(fn.startswith(i) for i in ignored):
continue
for line in f:
for w in words:
diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py
index 2ee3a776a..db520f924 100755
--- a/scripts/dev/run_vulture.py
+++ b/scripts/dev/run_vulture.py
@@ -89,6 +89,7 @@ def whitelist_generator():
yield 'qutebrowser.utils.log.VDEBUG'
yield 'qutebrowser.utils.log.QtWarningFilter.filter'
yield 'logging.LogRecord.log_color'
+ yield 'qutebrowser.browser.pdfjs.is_available'
# vulture doesn't notice the hasattr() and thus thinks netrc_used is unused
# in NetworkManager.on_authentication_required
yield 'PyQt5.QtNetwork.QNetworkReply.netrc_used'
diff --git a/scripts/dev/update_3rdparty.py b/scripts/dev/update_3rdparty.py
new file mode 100755
index 000000000..6db01bc11
--- /dev/null
+++ b/scripts/dev/update_3rdparty.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2015 Daniel Schadt
+#
+# 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/>.
+
+"""Update all third-party-modules."""
+
+import urllib.request
+import shutil
+import json
+import os
+
+
+def get_latest_pdfjs_url():
+ """Get the URL of the latest pdf.js prebuilt package.
+
+ Returns a (version, url)-tuple."""
+ github_api = 'https://api.github.com'
+ endpoint = 'repos/mozilla/pdf.js/releases/latest'
+ request_url = '{}/{}'.format(github_api, endpoint)
+ with urllib.request.urlopen(request_url) as fp:
+ data = json.loads(fp.read().decode('utf-8'))
+
+ download_url = data['assets'][0]['browser_download_url']
+ version_name = data['name']
+ return (version_name, download_url)
+
+
+def update_pdfjs():
+ """Download and extract the latest pdf.js version."""
+ version, url = get_latest_pdfjs_url()
+ target_path = os.path.join('qutebrowser', '3rdparty', 'pdfjs')
+ print("=> Downloading pdf.js {}".format(version))
+ (archive_path, _headers) = urllib.request.urlretrieve(url)
+ if os.path.isdir(target_path):
+ print("Removing old version in {}".format(target_path))
+ shutil.rmtree(target_path)
+ os.makedirs(target_path)
+ print("Extracting new version")
+ with open(archive_path, 'rb') as archive:
+ shutil.unpack_archive(archive, target_path, 'zip')
+ urllib.request.urlcleanup()
+
+
+def main():
+ update_pdfjs()
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/integration/data/misc/test.pdf b/tests/integration/data/misc/test.pdf
new file mode 100644
index 000000000..6bd5f16be
--- /dev/null
+++ b/tests/integration/data/misc/test.pdf
Binary files differ
diff --git a/tests/integration/features/misc.feature b/tests/integration/features/misc.feature
index 1df5d99a3..b31ef0f67 100644
--- a/tests/integration/features/misc.feature
+++ b/tests/integration/features/misc.feature
@@ -245,3 +245,17 @@ Feature: Various utility commands.
When I set general -> startpage to http://localhost:(port)/data/numbers/1.txt,http://localhost:(port)/data/numbers/2.txt
And I run :home
Then data/numbers/1.txt should be loaded
+
+ # pdfjs support
+
+ Scenario: pdfjs is used for pdf files
+ Given pdfjs is available
+ When I set content -> enable-pdfjs to true
+ And I open data/misc/test.pdf
+ Then the javascript message "PDF * [*] (PDF.js: *)" should be logged
+
+ Scenario: pdfjs is not used when disabled
+ When I set content -> enable-pdfjs to false
+ And I set storage -> prompt-download-directory to false
+ And I open data/misc/test.pdf
+ Then "Download finished" should be logged
diff --git a/tests/integration/features/test_misc.py b/tests/integration/features/test_misc.py
index dc3cebdfa..a19d3c908 100644
--- a/tests/integration/features/test_misc.py
+++ b/tests/integration/features/test_misc.py
@@ -23,10 +23,13 @@ import subprocess
import pytest
import pytest_bdd as bdd
-bdd.scenarios('misc.feature')
import qutebrowser
from qutebrowser.utils import docutils
+from qutebrowser.browser import pdfjs
+
+
+bdd.scenarios('misc.feature')
@bdd.when("the documentation is up to date")
@@ -51,3 +54,9 @@ def update_documentation():
update_script = os.path.join(script_path, 'asciidoc2html.py')
subprocess.call([sys.executable, update_script])
+
+
+@bdd.given('pdfjs is available')
+def pdfjs_available():
+ if not pdfjs.is_available():
+ pytest.skip("No pdfjs installation found.")
diff --git a/tests/unit/browser/test_pdfjs.py b/tests/unit/browser/test_pdfjs.py
new file mode 100644
index 000000000..f3bf0413f
--- /dev/null
+++ b/tests/unit/browser/test_pdfjs.py
@@ -0,0 +1,76 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2015 Daniel Schadt
+#
+# 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/>.
+
+"""Tests for qutebrowser.browser.pdfjs"""
+
+import textwrap
+
+import pytest
+from PyQt5.QtCore import QUrl
+
+from qutebrowser.browser import pdfjs
+
+
+# Note that we got double protection, once because we use QUrl.FullyEncoded and
+# because we use qutebrowser.browser.webelem.javascript_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);'),
+])
+def test_generate_pdfjs_script(url, expected):
+ expected_code = ('PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
+ 'PDFView.open("{}");\n'.format(expected))
+ url = QUrl(url)
+ actual = pdfjs._generate_pdfjs_script(url)
+ assert actual == expected_code
+
+
+def test_fix_urls():
+ page = textwrap.dedent("""
+ <html>
+ <script src="viewer.js"></script>
+ <link href="viewer.css">
+ <script src="unrelated.js"></script>
+ </html>
+ """).strip()
+
+ 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()
+
+ actual = pdfjs.fix_urls(page)
+ assert actual == expected
+
+
+@pytest.mark.parametrize('path, expected', [
+ ('web/viewer.js', 'viewer.js'),
+ ('build/locale/foo.bar', 'locale/foo.bar'),
+ ('viewer.js', 'viewer.js'),
+ ('foo/viewer.css', 'foo/viewer.css'),
+])
+def test_remove_prefix(path, expected):
+ assert pdfjs._remove_prefix(path) == expected
diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py
index 3ad45ce34..f8c1a88e3 100644
--- a/tests/unit/utils/test_version.py
+++ b/tests/unit/utils/test_version.py
@@ -34,6 +34,7 @@ import pytest
import qutebrowser
from qutebrowser.utils import version
+from qutebrowser.browser import pdfjs
class GitStrSubprocessFake:
@@ -529,6 +530,39 @@ class TestOsInfo:
version._os_info()
+class TestPDFJSVersion:
+
+ """Tests for _pdfjs_version."""
+
+ def test_not_found(self, mocker):
+ mocker.patch('qutebrowser.utils.version.pdfjs.get_pdfjs_res',
+ side_effect=pdfjs.PDFJSNotFound)
+ assert version._pdfjs_version() == 'no'
+
+ def test_unknown(self, monkeypatch):
+ monkeypatch.setattr('qutebrowser.utils.version.pdfjs.get_pdfjs_res',
+ lambda path: b'foobar')
+ assert version._pdfjs_version() == 'unknown'
+
+ def test_known(self, monkeypatch):
+ pdfjs_code = textwrap.dedent("""
+ // Initializing PDFJS global object (if still undefined)
+ if (typeof PDFJS === 'undefined') {
+ (typeof window !== 'undefined' ? window : this).PDFJS = {};
+ }
+
+ PDFJS.version = '1.2.109';
+ PDFJS.build = '875588d';
+
+ (function pdfjsWrapper() {
+ // Use strict in our context only - users might not want it
+ 'use strict';
+ """).strip().encode('utf-8')
+ monkeypatch.setattr('qutebrowser.utils.version.pdfjs.get_pdfjs_res',
+ lambda path: pdfjs_code)
+ assert version._pdfjs_version() == '1.2.109'
+
+
class FakeQSslSocket:
"""Fake for the QSslSocket Qt class.
@@ -571,6 +605,7 @@ def test_version_output(git_commit, harfbuzz, frozen, short, stubs,
'qVersion': lambda: 'QT RUNTIME VERSION',
'PYQT_VERSION_STR': 'PYQT VERSION',
'_module_versions': lambda: ['MODULE VERSION 1', 'MODULE VERSION 2'],
+ '_pdfjs_version': lambda: 'PDFJS VERSION',
'qWebKitVersion': lambda: 'WEBKIT VERSION',
'QSslSocket': FakeQSslSocket('SSL VERSION'),
'platform.platform': lambda: 'PLATFORM',
@@ -613,6 +648,7 @@ def test_version_output(git_commit, harfbuzz, frozen, short, stubs,
Desktop: DESKTOP
MODULE VERSION 1
MODULE VERSION 2
+ pdf.js: PDFJS VERSION
Webkit: WEBKIT VERSION
Harfbuzz: {harfbuzz}
SSL: SSL VERSION