diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index c82fc9b2d3..e7d9d470ea 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -146,6 +146,7 @@ class Boss(QObject): self.gui.preview.split_start_requested.connect(self.split_start_requested) self.gui.preview.split_requested.connect(self.split_requested) 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.check_requested.connect(self.check_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.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 def currently_editing(self): ' Return the name of the file being edited currently or None if no file is being edited ' diff --git a/src/calibre/gui2/tweak_book/preview.py b/src/calibre/gui2/tweak_book/preview.py index 2dcddc35e1..8006d7f7c9 100644 --- a/src/calibre/gui2/tweak_book/preview.py +++ b/src/calibre/gui2/tweak_book/preview.py @@ -14,8 +14,8 @@ from functools import partial from threading import Thread from PyQt5.Qt import ( - QApplication, QBuffer, QByteArray, QIcon, QMenu, QSize, QTimer, - QToolBar, QUrl, QVBoxLayout, QWidget, pyqtSignal + QApplication, QBuffer, QByteArray, QIcon, QMenu, QSize, QTimer, QToolBar, QUrl, + QVBoxLayout, QWidget, pyqtSignal ) from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler 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.gui2 import NO_URL_FORMATTING, error_dialog, open_url 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.utils.ipc.simple_worker import offload_worker from polyglot.builtins import unicode_type @@ -325,10 +328,10 @@ class WebPage(QWebEnginePage): self.bridge.set_split_mode.emit(1 if enabled else 0) -class WebView(QWebEngineView): +class WebView(RestartingWebEngineView): def __init__(self, parent=None): - QWebEngineView.__init__(self, parent) + RestartingWebEngineView.__init__(self, parent) self.inspector = QWebEngineView(self) w = QApplication.instance().desktop().availableGeometry(self).width() self._size_hint = QSize(int(w/3), int(w/2)) @@ -337,12 +340,13 @@ class WebView(QWebEngineView): self.setPage(self._page) self.clear() 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'), _( - 'The Qt WebEngine Render process has crashed so Preview/Live css' - ' will not work. You should try restarting the editor.'), show=True) + 'The Qt WebEngine Render process has crashed so Preview/Live css will not work.' + ' You should try restarting the editor.') +, show=True) def sizeHint(self): return self._size_hint @@ -393,6 +397,7 @@ class Preview(QWidget): link_clicked = pyqtSignal(object, object) refresh_starting = pyqtSignal() refreshed = pyqtSignal() + render_process_restarted = pyqtSignal() def __init__(self, parent=None): 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_split.connect(self.request_split) 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.inspector = self.view.inspector l.addWidget(self.view) diff --git a/src/calibre/gui2/tweak_book/reports.py b/src/calibre/gui2/tweak_book/reports.py index c1e921389f..323ed21602 100644 --- a/src/calibre/gui2/tweak_book/reports.py +++ b/src/calibre/gui2/tweak_book/reports.py @@ -21,7 +21,6 @@ from PyQt5.Qt import ( QStyledItemDelegate, QModelIndex, QRect, QStyle, QPalette, QTimer, QMenu, QAbstractItemModel, QTreeView, QFont, QRadioButton, QHBoxLayout, QFontDatabase, QComboBox, QUrl) -from PyQt5.QtWebEngineWidgets import QWebEngineView from calibre import human_readable, fit_image from calibre.constants import DEBUG @@ -29,7 +28,7 @@ from calibre.ebooks.oeb.polish.report import ( gather_data, CSSEntry, CSSFileMatch, MatchLocation, ClassEntry, ClassFileMatch, ClassElement, CSSRule, LinkLocation) 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.widgets import Dialog from calibre.gui2.progress_indicator import ProgressIndicator @@ -578,7 +577,7 @@ class LinksModel(FileCollection): pass -class WebView(QWebEngineView): +class WebView(RestartingWebEngineView): def sizeHint(self): return QSize(600, 200) @@ -604,11 +603,8 @@ class LinksWidget(QWidget): e.textChanged.connect(f.proxy.filter_text) s.addWidget(f) self.links.restore_table('links-table', sort_column=1) - self.view = WebView(self) - secure_webengine(self.view) + self.view = None self.setContextMenuPolicy(Qt.NoContextMenu) - self.view.setContextMenuPolicy(Qt.NoContextMenu) - s.addWidget(self.view) self.ignore_current_change = False self.current_url = None f.current_changed.connect(self.current_changed) @@ -616,10 +612,16 @@ class LinksWidget(QWidget): s.restoreState(read_state('links-view-splitter')) except TypeError: pass - s.setCollapsible(0, False), s.setCollapsible(1, True) + s.setCollapsible(0, False) s.setStretchFactor(0, 10) 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.model(data) self.filter_edit.clear() @@ -644,11 +646,13 @@ class LinksWidget(QWidget): if link.anchor.id: url.setFragment(link.anchor.id) if url is None: - self.view.setHtml('

' + _('No destination found for this link')) + if self.view: + self.view.setHtml('

' + _('No destination found for this link')) self.current_url = url elif url != self.current_url: self.current_url = url - self.view.setUrl(url) + if self.view: + self.view.setUrl(url) def double_clicked(self, index): link = index.data(Qt.UserRole) diff --git a/src/calibre/gui2/webengine.py b/src/calibre/gui2/webengine.py index a7edb4410f..dbd8aaa791 100644 --- a/src/calibre/gui2/webengine.py +++ b/src/calibre/gui2/webengine.py @@ -6,10 +6,11 @@ from __future__ import absolute_import, division, print_function, unicode_litera import json -from PyQt5.Qt import QObject, pyqtSignal +from PyQt5.Qt import QObject, Qt, pyqtSignal from PyQt5.QtWebEngineWidgets import QWebEngineScript, QWebEngineView from calibre import prints +from calibre.utils.monotonic import monotonic from calibre.utils.rapydscript import special_title @@ -125,6 +126,27 @@ class Bridge(QObject): 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__': from calibre.gui2 import Application from calibre.gui2.tweak_book.preview import WebPage