Try restarting the render process on crash

Also only load the link reports web view on demand
This commit is contained in:
Kovid Goyal 2018-07-29 16:43:36 +05:30
parent c9b2578b33
commit c6d6716965
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 56 additions and 20 deletions

View File

@ -146,6 +146,7 @@ class Boss(QObject):
self.gui.preview.split_start_requested.connect(self.split_start_requested) self.gui.preview.split_start_requested.connect(self.split_start_requested)
self.gui.preview.split_requested.connect(self.split_requested) self.gui.preview.split_requested.connect(self.split_requested)
self.gui.preview.link_clicked.connect(self.link_clicked) self.gui.preview.link_clicked.connect(self.link_clicked)
self.gui.preview.render_process_restarted.connect(self.report_render_process_restart)
self.gui.check_book.item_activated.connect(self.check_item_activated) self.gui.check_book.item_activated.connect(self.check_item_activated)
self.gui.check_book.check_requested.connect(self.check_requested) self.gui.check_book.check_requested.connect(self.check_requested)
self.gui.check_book.fix_requested.connect(self.fix_requested) self.gui.check_book.fix_requested.connect(self.fix_requested)
@ -170,6 +171,9 @@ class Boss(QObject):
self.gui.reports.refresh_starting.connect(self.commit_all_editors_to_container) self.gui.reports.refresh_starting.connect(self.commit_all_editors_to_container)
self.gui.reports.delete_requested.connect(self.delete_requested) self.gui.reports.delete_requested.connect(self.delete_requested)
def report_render_process_restart(self):
self.gui.show_status_message(_('The Qt WebEngine Render process crashed and has been restarted'))
@property @property
def currently_editing(self): def currently_editing(self):
' Return the name of the file being edited currently or None if no file is being edited ' ' Return the name of the file being edited currently or None if no file is being edited '

View File

@ -14,8 +14,8 @@ from functools import partial
from threading import Thread from threading import Thread
from PyQt5.Qt import ( from PyQt5.Qt import (
QApplication, QBuffer, QByteArray, QIcon, QMenu, QSize, QTimer, QApplication, QBuffer, QByteArray, QIcon, QMenu, QSize, QTimer, QToolBar, QUrl,
QToolBar, QUrl, QVBoxLayout, QWidget, pyqtSignal QVBoxLayout, QWidget, pyqtSignal
) )
from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler
from PyQt5.QtWebEngineWidgets import ( from PyQt5.QtWebEngineWidgets import (
@ -30,7 +30,10 @@ from calibre.ebooks.oeb.base import OEB_DOCS, XHTML_MIME, serialize
from calibre.ebooks.oeb.polish.parsing import parse from calibre.ebooks.oeb.polish.parsing import parse
from calibre.gui2 import NO_URL_FORMATTING, error_dialog, open_url from calibre.gui2 import NO_URL_FORMATTING, error_dialog, open_url
from calibre.gui2.tweak_book import TOP, actions, current_container, editors, tprefs from calibre.gui2.tweak_book import TOP, actions, current_container, editors, tprefs
from calibre.gui2.webengine import create_script, insert_scripts, secure_webengine, Bridge, from_js, to_js from calibre.gui2.webengine import (
Bridge, RestartingWebEngineView, create_script, from_js, insert_scripts,
secure_webengine, to_js
)
from calibre.gui2.widgets2 import HistoryLineEdit2 from calibre.gui2.widgets2 import HistoryLineEdit2
from calibre.utils.ipc.simple_worker import offload_worker from calibre.utils.ipc.simple_worker import offload_worker
from polyglot.builtins import unicode_type from polyglot.builtins import unicode_type
@ -325,10 +328,10 @@ class WebPage(QWebEnginePage):
self.bridge.set_split_mode.emit(1 if enabled else 0) self.bridge.set_split_mode.emit(1 if enabled else 0)
class WebView(QWebEngineView): class WebView(RestartingWebEngineView):
def __init__(self, parent=None): def __init__(self, parent=None):
QWebEngineView.__init__(self, parent) RestartingWebEngineView.__init__(self, parent)
self.inspector = QWebEngineView(self) self.inspector = QWebEngineView(self)
w = QApplication.instance().desktop().availableGeometry(self).width() w = QApplication.instance().desktop().availableGeometry(self).width()
self._size_hint = QSize(int(w/3), int(w/2)) self._size_hint = QSize(int(w/3), int(w/2))
@ -337,12 +340,13 @@ class WebView(QWebEngineView):
self.setPage(self._page) self.setPage(self._page)
self.clear() self.clear()
self.setAcceptDrops(False) self.setAcceptDrops(False)
self.renderProcessTerminated.connect(self.render_process_terminated) self.render_process_failed.connect(self.render_process_died)
def render_process_terminated(self): def render_process_died(self):
error_dialog(self, _('Render process crashed'), _( error_dialog(self, _('Render process crashed'), _(
'The Qt WebEngine Render process has crashed so Preview/Live css' 'The Qt WebEngine Render process has crashed so Preview/Live css will not work.'
' will not work. You should try restarting the editor.'), show=True) ' You should try restarting the editor.')
, show=True)
def sizeHint(self): def sizeHint(self):
return self._size_hint return self._size_hint
@ -393,6 +397,7 @@ class Preview(QWidget):
link_clicked = pyqtSignal(object, object) link_clicked = pyqtSignal(object, object)
refresh_starting = pyqtSignal() refresh_starting = pyqtSignal()
refreshed = pyqtSignal() refreshed = pyqtSignal()
render_process_restarted = pyqtSignal()
def __init__(self, parent=None): def __init__(self, parent=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
@ -403,6 +408,7 @@ class Preview(QWidget):
self.view._page.bridge.request_sync.connect(self.request_sync) self.view._page.bridge.request_sync.connect(self.request_sync)
self.view._page.bridge.request_split.connect(self.request_split) self.view._page.bridge.request_split.connect(self.request_split)
self.view._page.loadFinished.connect(self.load_finished) self.view._page.loadFinished.connect(self.load_finished)
self.view.render_process_restarted.connect(self.render_process_restarted)
self.pending_go_to_anchor = None self.pending_go_to_anchor = None
self.inspector = self.view.inspector self.inspector = self.view.inspector
l.addWidget(self.view) l.addWidget(self.view)

View File

@ -21,7 +21,6 @@ from PyQt5.Qt import (
QStyledItemDelegate, QModelIndex, QRect, QStyle, QPalette, QTimer, QMenu, QStyledItemDelegate, QModelIndex, QRect, QStyle, QPalette, QTimer, QMenu,
QAbstractItemModel, QTreeView, QFont, QRadioButton, QHBoxLayout, QAbstractItemModel, QTreeView, QFont, QRadioButton, QHBoxLayout,
QFontDatabase, QComboBox, QUrl) QFontDatabase, QComboBox, QUrl)
from PyQt5.QtWebEngineWidgets import QWebEngineView
from calibre import human_readable, fit_image from calibre import human_readable, fit_image
from calibre.constants import DEBUG from calibre.constants import DEBUG
@ -29,7 +28,7 @@ from calibre.ebooks.oeb.polish.report import (
gather_data, CSSEntry, CSSFileMatch, MatchLocation, ClassEntry, gather_data, CSSEntry, CSSFileMatch, MatchLocation, ClassEntry,
ClassFileMatch, ClassElement, CSSRule, LinkLocation) ClassFileMatch, ClassElement, CSSRule, LinkLocation)
from calibre.gui2 import error_dialog, question_dialog, choose_save_file, open_url from calibre.gui2 import error_dialog, question_dialog, choose_save_file, open_url
from calibre.gui2.webengine import secure_webengine from calibre.gui2.webengine import secure_webengine, RestartingWebEngineView
from calibre.gui2.tweak_book import current_container, tprefs, dictionaries from calibre.gui2.tweak_book import current_container, tprefs, dictionaries
from calibre.gui2.tweak_book.widgets import Dialog from calibre.gui2.tweak_book.widgets import Dialog
from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.progress_indicator import ProgressIndicator
@ -578,7 +577,7 @@ class LinksModel(FileCollection):
pass pass
class WebView(QWebEngineView): class WebView(RestartingWebEngineView):
def sizeHint(self): def sizeHint(self):
return QSize(600, 200) return QSize(600, 200)
@ -604,11 +603,8 @@ class LinksWidget(QWidget):
e.textChanged.connect(f.proxy.filter_text) e.textChanged.connect(f.proxy.filter_text)
s.addWidget(f) s.addWidget(f)
self.links.restore_table('links-table', sort_column=1) self.links.restore_table('links-table', sort_column=1)
self.view = WebView(self) self.view = None
secure_webengine(self.view)
self.setContextMenuPolicy(Qt.NoContextMenu) self.setContextMenuPolicy(Qt.NoContextMenu)
self.view.setContextMenuPolicy(Qt.NoContextMenu)
s.addWidget(self.view)
self.ignore_current_change = False self.ignore_current_change = False
self.current_url = None self.current_url = None
f.current_changed.connect(self.current_changed) f.current_changed.connect(self.current_changed)
@ -616,10 +612,16 @@ class LinksWidget(QWidget):
s.restoreState(read_state('links-view-splitter')) s.restoreState(read_state('links-view-splitter'))
except TypeError: except TypeError:
pass pass
s.setCollapsible(0, False), s.setCollapsible(1, True) s.setCollapsible(0, False)
s.setStretchFactor(0, 10) s.setStretchFactor(0, 10)
def __call__(self, data): def __call__(self, data):
if self.view is None:
self.view = WebView(self)
secure_webengine(self.view)
self.view.setContextMenuPolicy(Qt.NoContextMenu)
self.splitter.addWidget(self.view)
self.splitter.setCollapsible(1, True)
self.ignore_current_change = True self.ignore_current_change = True
self.model(data) self.model(data)
self.filter_edit.clear() self.filter_edit.clear()
@ -644,11 +646,13 @@ class LinksWidget(QWidget):
if link.anchor.id: if link.anchor.id:
url.setFragment(link.anchor.id) url.setFragment(link.anchor.id)
if url is None: if url is None:
self.view.setHtml('<p>' + _('No destination found for this link')) if self.view:
self.view.setHtml('<p>' + _('No destination found for this link'))
self.current_url = url self.current_url = url
elif url != self.current_url: elif url != self.current_url:
self.current_url = url self.current_url = url
self.view.setUrl(url) if self.view:
self.view.setUrl(url)
def double_clicked(self, index): def double_clicked(self, index):
link = index.data(Qt.UserRole) link = index.data(Qt.UserRole)

View File

@ -6,10 +6,11 @@ from __future__ import absolute_import, division, print_function, unicode_litera
import json import json
from PyQt5.Qt import QObject, pyqtSignal from PyQt5.Qt import QObject, Qt, pyqtSignal
from PyQt5.QtWebEngineWidgets import QWebEngineScript, QWebEngineView from PyQt5.QtWebEngineWidgets import QWebEngineScript, QWebEngineView
from calibre import prints from calibre import prints
from calibre.utils.monotonic import monotonic
from calibre.utils.rapydscript import special_title from calibre.utils.rapydscript import special_title
@ -125,6 +126,27 @@ class Bridge(QObject):
traceback.print_exc() traceback.print_exc()
class RestartingWebEngineView(QWebEngineView):
render_process_restarted = pyqtSignal()
render_process_failed = pyqtSignal()
def __init__(self, parent=None):
QWebEngineView.__init__(self, parent)
self._last_reload_at = None
self.renderProcessTerminated.connect(self.render_process_terminated)
self.render_process_restarted.connect(self.reload, type=Qt.QueuedConnection)
def render_process_terminated(self):
if self._last_reload_at is not None and monotonic() - self._last_reload_at < 2:
self.render_process_failed.emit()
print('The Qt WebEngine Render process crashed too often')
else:
self._last_reload_at = monotonic()
self.render_process_restarted.emit()
prints('The Qt WebEngine Render process crashed, restarting it')
if __name__ == '__main__': if __name__ == '__main__':
from calibre.gui2 import Application from calibre.gui2 import Application
from calibre.gui2.tweak_book.preview import WebPage from calibre.gui2.tweak_book.preview import WebPage