Cache HTML/JS resource files when starting

This mostly reverts 9edc5a665e (see #1362).
Fixes #1943
This commit is contained in:
Florian Bruhin 2018-03-05 09:03:58 +01:00
parent 78623f4ec8
commit b4a2352833
7 changed files with 30 additions and 45 deletions

View File

@ -117,6 +117,8 @@ Fixed
- Crash when opening an invalid URL from an application on macOS.
- Crash with an empty `completion.timestamp_format`.
- Crash when `completion.min_chars` is set in some cases.
- HTML/JS resource files are now read into RAM on start to avoid crashes when
changing qutebrowser versions while it's open.
Removed
~~~~~~~

View File

@ -95,6 +95,7 @@ def run(args):
log.init.debug("Initializing directories...")
standarddir.init(args)
utils.preload_resources()
log.init.debug("Initializing config...")
configinit.early_init(args)

View File

@ -1,22 +0,0 @@
<!DOCTYPE html>
<!--
vim: ft=html fileencoding=utf-8 sts=4 sw=4 et:
-->
<html>
<head>
<meta charset="utf-8">
<title>Error while rendering HTML</title>
</head>
<body>
<h1>Error while rendering internal qutebrowser page</h1>
<p>There was an error while rendering {pagename}.</p>
<p>This most likely happened because you updated qutebrowser but didn't restart yet.</p>
<p>If you believe this isn't the case and this is a bug, please do :report.<p>
<h2>Traceback</h2>
<pre>{traceback}</pre>
</body>
</html>

View File

@ -22,12 +22,10 @@
import os
import os.path
import contextlib
import traceback
import mimetypes
import html
import jinja2
import jinja2.exceptions
from PyQt5.QtCore import QUrl
from qutebrowser.utils import utils, urlutils, log
@ -125,14 +123,7 @@ class Environment(jinja2.Environment):
def render(template, **kwargs):
"""Render the given template and pass the given arguments to it."""
try:
return environment.get_template(template).render(**kwargs)
except jinja2.exceptions.UndefinedError:
log.misc.exception("UndefinedError while rendering " + template)
err_path = os.path.join('html', 'undef_error.html')
err_template = utils.read_file(err_path)
tb = traceback.format_exc()
return err_template.format(pagename=template, traceback=tb)
return environment.get_template(template).render(**kwargs)
environment = Environment()

View File

@ -32,6 +32,7 @@ import functools
import contextlib
import socket
import shlex
import glob
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QColor, QClipboard, QDesktopServices
@ -51,6 +52,7 @@ from qutebrowser.utils import qtutils, log
fake_clipboard = None
log_clipboard = False
_resource_cache = {}
is_mac = sys.platform.startswith('darwin')
is_linux = sys.platform.startswith('linux')
@ -140,6 +142,15 @@ def compact_text(text, elidelength=None):
return out
def preload_resources():
"""Load resource files into the cache."""
for subdir, pattern in [('html', '*.html'), ('javascript', '*.js')]:
path = resource_filename(subdir)
for full_path in glob.glob(os.path.join(path, pattern)):
sub_path = os.path.join(subdir, os.path.basename(full_path))
_resource_cache[sub_path] = read_file(sub_path)
def read_file(filename, binary=False):
"""Get the contents of a file contained with qutebrowser.
@ -151,6 +162,9 @@ def read_file(filename, binary=False):
Return:
The file contents as string.
"""
if not binary and filename in _resource_cache:
return _resource_cache[filename]
if hasattr(sys, 'frozen'):
# PyInstaller doesn't support pkg_resources :(
# https://github.com/pyinstaller/pyinstaller/wiki/FAQ#misc

View File

@ -23,6 +23,7 @@ import os
import os.path
import logging
import jinja2.exceptions
import pytest
from PyQt5.QtCore import QUrl
@ -32,7 +33,6 @@ from qutebrowser.utils import utils, jinja
@pytest.fixture(autouse=True)
def patch_read_file(monkeypatch):
"""pytest fixture to patch utils.read_file."""
real_read_file = utils.read_file
real_resource_filename = utils.resource_filename
def _read_file(path, binary=False):
@ -52,9 +52,6 @@ def patch_read_file(monkeypatch):
elif path == os.path.join('html', 'undef.html'):
assert not binary
return """{{ does_not_exist() }}"""
elif path == os.path.join('html', 'undef_error.html'):
assert not binary
return real_read_file(path)
elif path == os.path.join('html', 'attributeerror.html'):
assert not binary
return """{{ obj.foobar }}"""
@ -129,15 +126,9 @@ def test_utf8():
def test_undefined_function(caplog):
"""Make sure we don't crash if an undefined function is called."""
with caplog.at_level(logging.ERROR):
data = jinja.render('undef.html')
assert 'There was an error while rendering undef.html' in data
assert "'does_not_exist' is undefined" in data
assert data.startswith('<!DOCTYPE html>')
assert len(caplog.records) == 1
assert caplog.records[0].msg == "UndefinedError while rendering undef.html"
"""Make sure undefined attributes crash since we preload resources.."""
with pytest.raises(jinja2.exceptions.UndefinedError):
jinja.render('undef.html')
def test_attribute_error():

View File

@ -133,6 +133,14 @@ class TestReadFile:
content = utils.read_file(os.path.join('utils', 'testfile'))
assert content.splitlines()[0] == "Hello World!"
@pytest.mark.parametrize('filename', ['javascript/scroll.js',
'html/error.html'])
def test_read_cached_file(self, mocker, filename):
utils.preload_resources()
m = mocker.patch('pkg_resources.resource_string')
utils.read_file(filename)
m.assert_not_called()
def test_readfile_binary(self):
"""Read a test file in binary mode."""
content = utils.read_file(os.path.join('utils', 'testfile'),