Make closing/crashing much more reliable.

This commit is contained in:
Florian Bruhin 2014-02-17 14:17:56 +01:00
parent 966ceba1e6
commit 8c37e1c33a
2 changed files with 84 additions and 16 deletions

View File

@ -70,6 +70,7 @@ class QuteBrowser(QApplication):
keyparser = None
args = None # ArgumentParser
timer = None # QTimer for python hacks
shutting_down = False
def __init__(self):
super().__init__(sys.argv)
@ -94,7 +95,8 @@ class QuteBrowser(QApplication):
self._init_cmds()
self.mainwindow = MainWindow()
self.aboutToQuit.connect(self._shutdown)
self.setQuitOnLastWindowClosed(False)
self.lastWindowClosed.connect(self.shutdown)
self.mainwindow.tabs.keypress.connect(self.keyparser.handle)
self.keyparser.set_cmd_text.connect(
self.mainwindow.status.cmd.on_set_cmd_text)
@ -171,6 +173,15 @@ class QuteBrowser(QApplication):
except Exception:
history = []
# Try to shutdown gracefully
try:
self.shutdown(do_quit=False)
except Exception:
pass
try:
self.lastWindowClosed.disconnect(self.shutdown)
except TypeError:
logging.exception("Preventing shutdown failed.")
QApplication.closeAllWindows()
dlg = CrashDialog(pages, history, exc)
ret = dlg.exec_()
@ -182,7 +193,10 @@ class QuteBrowser(QApplication):
logging.debug('Running {} with args {}'.format(sys.executable,
argv))
subprocess.Popen(argv)
sys.exit(1)
try:
self.quit()
except Exception:
sys.exit(1)
def _python_hacks(self):
"""Get around some PyQt-oddities by evil hacks.
@ -246,10 +260,24 @@ class QuteBrowser(QApplication):
pass
@pyqtSlot()
def _shutdown(self):
"""Try to shutdown everything cleanly."""
def shutdown(self, do_quit=True):
"""Try to shutdown everything cleanly.
For some reason lastWindowClosing sometimes seem to get emitted twice,
so we make sure we only run once here.
quit -- Whether to quit after shutting down.
"""
if self.shutting_down:
return
self.shutting_down = True
logging.debug("Shutting down...")
config.config.save()
self.mainwindow.tabs.shutdown_complete.connect(self.quit)
self.mainwindow.tabs.shutdown()
if do_quit:
self.quit()
@pyqtSlot(tuple)
def cmd_handler(self, tpl):
@ -272,7 +300,7 @@ class QuteBrowser(QApplication):
'opencur': self.mainwindow.tabs.opencur,
'tabopen': self.mainwindow.tabs.tabopen,
'tabopencur': self.mainwindow.tabs.tabopencur,
'quit': self.quit,
'quit': self.shutdown,
'tabclose': self.mainwindow.tabs.cur_close,
'tabprev': self.mainwindow.tabs.switch_prev,
'tabnext': self.mainwindow.tabs.switch_next,

View File

@ -67,6 +67,7 @@ class TabbedBrowser(TabWidget):
cur_scroll_perc_changed = pyqtSignal(int, int)
set_cmd_text = pyqtSignal(str) # Set commandline to a given text
keypress = pyqtSignal('QKeyEvent')
shutdown_complete = pyqtSignal() # All tabs have been shut down.
_url_stack = [] # Stack of URLs of closed tabs
_space = None # Space QShortcut
_tabs = None
@ -154,15 +155,22 @@ class TabbedBrowser(TabWidget):
# FIXME maybe we actually should store the webview objects here
self._url_stack.append(tab.url())
self.removeTab(idx)
try:
self._tabs.remove(tab)
except ValueError:
pass
tab.shutdown()
tab.shutdown(callback=functools.partial(self._cb_tab_shutdown,
tab))
else:
# FIXME
pass
def _cb_tab_shutdown(self, tab):
"""Called after a tab has been shut down completely."""
try:
self._tabs.remove(tab)
except ValueError:
logging.error("tab {} could not be removed from tabs {}.".format(
tab, self._tabs))
if not self._tabs: # all tabs shut down
self.shutdown_complete.emit()
def cur_reload(self, count=None):
"""Reload the current/[count]th tab.
@ -433,9 +441,14 @@ class TabbedBrowser(TabWidget):
def shutdown(self):
"""Try to shut down all tabs cleanly."""
self.currentChanged.disconnect()
try:
self.currentChanged.disconnect()
except TypeError:
pass
for tabidx in range(self.count()):
self.widget(tabidx).shutdown()
tab = self.widget(tabidx)
tab.shutdown(callback=functools.partial(self._cb_tab_shutdown,
tab))
class BrowserTab(QWebView):
@ -451,12 +464,15 @@ class BrowserTab(QWebView):
open_tab = pyqtSignal('QUrl')
linkHovered = pyqtSignal(str, str, str)
_scroll_pos = (-1, -1)
_shutdown_callback = None # callback to be called after shutdown
_open_new_tab = False # open new tab for the next action
_destroyed = None # Dict of all items to be destroyed.
# dict of tab specific signals, and the values we last got from them.
signal_cache = None
def __init__(self, parent=None):
super().__init__(parent)
self._destroyed = {}
self.setPage(BrowserPage())
self.signal_cache = SignalCache(uncached=['linkHovered'])
self.loadProgress.connect(self.on_load_progress)
@ -513,7 +529,7 @@ class BrowserTab(QWebView):
"""
self.progress = prog
def shutdown(self):
def shutdown(self, callback=None):
"""Shut down the tab cleanly and remove it.
Inspired by [1].
@ -521,13 +537,37 @@ class BrowserTab(QWebView):
[1] https://github.com/integricho/path-of-a-pyqter/tree/master/qttut08
"""
page = self.page()
netman = page.networkAccessManager()
self._shutdown_callback = callback
try:
# Avoid loading finished signal when stopping
self.loadFinished.disconnect()
except TypeError:
logging.exception("This should never happen.")
self.stop()
self.close()
self.settings().setAttribute(QWebSettings.JavascriptEnabled, False)
self.page().deleteLater()
self._destroyed[page] = False
page.destroyed.connect(functools.partial(self.on_destroyed, page))
page.deleteLater()
self._destroyed[self] = False
self.destroyed.connect(functools.partial(self.on_destroyed, self))
self.deleteLater()
self.page().networkAccessManager().abort_requests()
self.page().networkAccessManager().deleteLater()
self._destroyed[netman] = False
netman.abort_requests()
netman.destroyed.connect(functools.partial(self.on_destroyed, netman))
netman.deleteLater()
def on_destroyed(self, sender):
"""Called when a subsystem has been destroyed during shutdown."""
self._destroyed[sender] = True
if all(self._destroyed.values()):
if self._shutdown_callback is not None:
self._shutdown_callback()
def eventFilter(self, watched, e):
"""Dirty hack to emit a signal if the scroll position changed.