From 5f5f20209833dfcbbd2d09bc5ae378956863d7a8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 28 Nov 2018 17:59:27 +0100 Subject: [PATCH] Move private tab API into an own object --- qutebrowser/browser/browsertab.py | 116 ++++++++++-------- qutebrowser/browser/commands.py | 4 +- qutebrowser/browser/hints.py | 2 +- qutebrowser/browser/mouse.py | 2 +- qutebrowser/browser/webengine/webenginetab.py | 44 ++++--- qutebrowser/browser/webkit/webkittab.py | 45 ++++--- qutebrowser/mainwindow/tabbedbrowser.py | 2 +- tests/helpers/stubs.py | 10 +- 8 files changed, 127 insertions(+), 98 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index ab186e56a..86cca09f1 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -734,6 +734,67 @@ class AbstractAudio(QObject): raise NotImplementedError +class AbstractTabPrivate: + + """Tab-related methods which are only needed in the core. + + Those methods are not part of the API which is exposed to extensions, and + should ideally be removed at some point in the future. + """ + + def __init__(self, mode_manager: modeman.ModeManager, + tab: 'AbstractTab') -> None: + self._widget = None # type: typing.Optional[QWidget] + self._tab = tab + self._mode_manager = mode_manager + + def event_target(self) -> QWidget: + """Return the widget events should be sent to.""" + raise NotImplementedError + + def handle_auto_insert_mode(self, ok: bool) -> None: + """Handle `input.insert_mode.auto_load` after loading finished.""" + if not config.val.input.insert_mode.auto_load or not ok: + return + + cur_mode = self._mode_manager.mode + if cur_mode == usertypes.KeyMode.insert: + return + + def _auto_insert_mode_cb(elem: webelem.AbstractWebElement) -> None: + """Called from JS after finding the focused element.""" + if elem is None: + log.webview.debug("No focused element!") + return + if elem.is_editable(): + modeman.enter(self._tab.win_id, usertypes.KeyMode.insert, + 'load finished', only_if_normal=True) + + self._tab.elements.find_focused(_auto_insert_mode_cb) + + def clear_ssl_errors(self) -> None: + raise NotImplementedError + + def networkaccessmanager(self) -> typing.Optional[QNetworkAccessManager]: + """Get the QNetworkAccessManager for this tab. + + This is only implemented for QtWebKit. + For QtWebEngine, always returns None. + """ + raise NotImplementedError + + def user_agent(self) -> typing.Optional[str]: + """Get the user agent for this tab. + + This is only implemented for QtWebKit. + For QtWebEngine, always returns None. + """ + raise NotImplementedError + + def shutdown(self) -> None: + raise NotImplementedError + + class AbstractTab(QWidget): """A wrapper over the given widget to hide its API and expose another one. @@ -785,10 +846,7 @@ class AbstractTab(QWidget): renderer_process_terminated = pyqtSignal(TerminationStatus, int) predicted_navigation = pyqtSignal(QUrl) - def __init__(self, *, - win_id: int, - mode_manager: modeman.ModeManager, - private: bool, + def __init__(self, *, win_id: int, private: bool, parent: QWidget = None) -> None: self.is_private = private self.win_id = win_id @@ -806,7 +864,6 @@ class AbstractTab(QWidget): self._widget = None # type: typing.Optional[QWidget] self._progress = 0 self._has_ssl_errors = False - self._mode_manager = mode_manager self._load_status = usertypes.LoadStatus.none self._mouse_event_filter = mouse.MouseEventFilter( self, parent=self) @@ -833,6 +890,7 @@ class AbstractTab(QWidget): self.action._widget = widget self.elements._widget = widget self.audio._widget = widget + self.private_api._widget = widget self.settings._settings = widget.settings() self._install_event_filter() @@ -849,10 +907,6 @@ class AbstractTab(QWidget): self._load_status = val self.load_status_changed.emit(val.name) - def event_target(self) -> QWidget: - """Return the widget events should be sent to.""" - raise NotImplementedError - def send_event(self, evt: QEvent) -> None: """Send the given event to the underlying widget. @@ -865,7 +919,7 @@ class AbstractTab(QWidget): raise utils.Unreachable("Can't re-use an event which was already " "posted!") - recipient = self.event_target() + recipient = self.private_api.event_target() if recipient is None: # https://github.com/qutebrowser/qutebrowser/issues/3888 log.webview.warning("Unable to find event target!") @@ -925,26 +979,6 @@ class AbstractTab(QWidget): navigation.url.errorString())) navigation.accepted = False - def handle_auto_insert_mode(self, ok: bool) -> None: - """Handle `input.insert_mode.auto_load` after loading finished.""" - if not config.val.input.insert_mode.auto_load or not ok: - return - - cur_mode = self._mode_manager.mode - if cur_mode == usertypes.KeyMode.insert: - return - - def _auto_insert_mode_cb(elem: webelem.AbstractWebElement) -> None: - """Called from JS after finding the focused element.""" - if elem is None: - log.webview.debug("No focused element!") - return - if elem.is_editable(): - modeman.enter(self.win_id, usertypes.KeyMode.insert, - 'load finished', only_if_normal=True) - - self.elements.find_focused(_auto_insert_mode_cb) - @pyqtSlot(bool) def _on_load_finished(self, ok: bool) -> None: assert self._widget is not None @@ -1010,9 +1044,6 @@ class AbstractTab(QWidget): def stop(self) -> None: raise NotImplementedError - def clear_ssl_errors(self) -> None: - raise NotImplementedError - def key_press(self, key: Qt.Key, modifier: Qt.KeyboardModifier = Qt.NoModifier) -> None: @@ -1048,9 +1079,6 @@ class AbstractTab(QWidget): """ raise NotImplementedError - def shutdown(self) -> None: - raise NotImplementedError - def title(self) -> str: raise NotImplementedError @@ -1060,22 +1088,6 @@ class AbstractTab(QWidget): def set_html(self, html: str, base_url: QUrl = QUrl()) -> None: raise NotImplementedError - def networkaccessmanager(self) -> typing.Optional[QNetworkAccessManager]: - """Get the QNetworkAccessManager for this tab. - - This is only implemented for QtWebKit. - For QtWebEngine, always returns None. - """ - raise NotImplementedError - - def user_agent(self) -> typing.Optional[str]: - """Get the user agent for this tab. - - This is only implemented for QtWebKit. - For QtWebEngine, always returns None. - """ - raise NotImplementedError - def __repr__(self) -> str: try: qurl = self.url() diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 2b4165364..1a69ef137 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1516,7 +1516,7 @@ class CommandDispatcher: else: download_manager.get_mhtml(tab, target) else: - qnam = tab.networkaccessmanager() + qnam = tab.private_api.networkaccessmanager() suggested_fn = downloads.suggested_fn_from_title( self._current_url().path(), tab.title() @@ -2165,7 +2165,7 @@ class CommandDispatcher: debug=True, backend=usertypes.Backend.QtWebKit) def debug_clear_ssl_errors(self): """Clear remembered SSL error answers.""" - self._current_widget().clear_ssl_errors() + self._current_widget().private_api.clear_ssl_errors() @cmdutils.register(instance='command-dispatcher', scope='window') def edit_url(self, url=None, bg=False, tab=False, window=False, diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index aa5b5f34c..0374c7f1f 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -304,7 +304,7 @@ class HintActions: raise HintingError("No suitable link found for this element.") prompt = False if context.rapid else None - qnam = context.tab.networkaccessmanager() + qnam = context.tab.private_api.networkaccessmanager() user_agent = context.tab.user_agent() # FIXME:qtwebengine do this with QtWebEngine downloads? diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index 7c405a57e..a73f28203 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -240,7 +240,7 @@ class MouseEventFilter(QObject): evtype = event.type() if evtype not in self._handlers: return False - if obj is not self._tab.event_target(): + if obj is not self._tab.private_api.event_target(): log.mouse.debug("Ignoring {} to {}".format( event.__class__.__name__, obj)) return False diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 47bacd60e..63f1201da 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -1038,6 +1038,28 @@ class _WebEngineScripts(QObject): page_scripts.insert(new_script) +class WebEngineTabPrivate(browsertab.AbstractTabPrivate): + + """QtWebEngine-related methods which aren't part of the public API.""" + + def networkaccessmanager(self): + return None + + def user_agent(self): + return None + + def clear_ssl_errors(self): + raise browsertab.UnsupportedOperationError + + def event_target(self): + return self._widget.render_widget() + + def shutdown(self): + self._tab.shutting_down.emit() + self._tab.action.exit_fullscreen() + self._widget.shutdown() + + class WebEngineTab(browsertab.AbstractTab): """A QtWebEngine tab in the browser. @@ -1051,8 +1073,7 @@ class WebEngineTab(browsertab.AbstractTab): _load_finished_fake = pyqtSignal(bool) def __init__(self, *, win_id, mode_manager, private, parent=None): - super().__init__(win_id=win_id, mode_manager=mode_manager, - private=private, parent=parent) + super().__init__(win_id=win_id, private=private, parent=parent) widget = webview.WebEngineView(tabdata=self.data, win_id=win_id, private=private) self.history = WebEngineHistory(tab=self) @@ -1065,6 +1086,8 @@ class WebEngineTab(browsertab.AbstractTab): self.elements = WebEngineElements(tab=self) self.action = WebEngineAction(tab=self) self.audio = WebEngineAudio(tab=self, parent=self) + self.private_api = WebEngineTabPrivate(mode_manager=mode_manager, + tab=self) self._permissions = _WebEnginePermissions(tab=self, parent=self) self._scripts = _WebEngineScripts(tab=self, parent=self) # We're assigning settings in _set_widget @@ -1146,11 +1169,6 @@ class WebEngineTab(browsertab.AbstractTab): else: self._widget.page().runJavaScript(code, world_id, callback) - def shutdown(self): - self.shutting_down.emit() - self.action.exit_fullscreen() - self._widget.shutdown() - def reload(self, *, force=False): if force: action = QWebEnginePage.ReloadAndBypassCache @@ -1175,15 +1193,6 @@ class WebEngineTab(browsertab.AbstractTab): # percent encoded content is 2 megabytes minus 30 bytes. self._widget.setHtml(html, base_url) - def networkaccessmanager(self): - return None - - def user_agent(self): - return None - - def clear_ssl_errors(self): - raise browsertab.UnsupportedOperationError - def key_press(self, key, modifier=Qt.NoModifier): press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0) release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier, @@ -1485,6 +1494,3 @@ class WebEngineTab(browsertab.AbstractTab): self.audio._connect_signals() self._permissions.connect_signals() self._scripts.connect_signals() - - def event_target(self): - return self._widget.render_widget() diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 51e3f385e..dcac1a3cb 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -657,13 +657,33 @@ class WebKitAudio(browsertab.AbstractAudio): return False +class WebKitTabPrivate(browsertab.AbstractTabPrivate): + + """QtWebKit-related methods which aren't part of the public API.""" + + def networkaccessmanager(self): + return self._widget.page().networkAccessManager() + + def user_agent(self): + page = self._widget.page() + return page.userAgentForUrl(self._tab.url()) + + def clear_ssl_errors(self): + self.networkaccessmanager().clear_all_ssl_errors() + + def event_target(self): + return self._widget + + def shutdown(self): + self._widget.shutdown() + + class WebKitTab(browsertab.AbstractTab): """A QtWebKit tab in the browser.""" def __init__(self, *, win_id, mode_manager, private, parent=None): - super().__init__(win_id=win_id, mode_manager=mode_manager, - private=private, parent=parent) + super().__init__(win_id=win_id, private=private, parent=parent) widget = webview.WebView(win_id=win_id, tab_id=self.tab_id, private=private, tab=self) if private: @@ -678,6 +698,8 @@ class WebKitTab(browsertab.AbstractTab): self.elements = WebKitElements(tab=self) self.action = WebKitAction(tab=self) self.audio = WebKitAudio(tab=self, parent=self) + self.private_api = WebKitTabPrivate(mode_manager=mode_manager, + tab=self) # We're assigning settings in _set_widget self.settings = webkitsettings.WebKitSettings(settings=None) self._set_widget(widget) @@ -720,9 +742,6 @@ class WebKitTab(browsertab.AbstractTab): def icon(self): return self._widget.icon() - def shutdown(self): - self._widget.shutdown() - def reload(self, *, force=False): if force: action = QWebPage.ReloadAndBypassCache @@ -736,9 +755,6 @@ class WebKitTab(browsertab.AbstractTab): def title(self): return self._widget.title() - def clear_ssl_errors(self): - self.networkaccessmanager().clear_all_ssl_errors() - def key_press(self, key, modifier=Qt.NoModifier): press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0) release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier, @@ -755,17 +771,11 @@ class WebKitTab(browsertab.AbstractTab): def set_html(self, html, base_url=QUrl()): self._widget.setHtml(html, base_url) - def networkaccessmanager(self): - return self._widget.page().networkAccessManager() - - def user_agent(self): - page = self._widget.page() - return page.userAgentForUrl(self.url()) - @pyqtSlot() def _on_load_started(self): super()._on_load_started() - self.networkaccessmanager().netrc_used = False + nam = self._widget.page().networkAccessManager() + nam.netrc_used = False # Make sure the icon is cleared when navigating to a page without one. self.icon_changed.emit(QIcon()) @@ -847,6 +857,3 @@ class WebKitTab(browsertab.AbstractTab): frame.contentsSizeChanged.connect(self._on_contents_size_changed) frame.initialLayoutCompleted.connect(self._on_history_trigger) page.navigation_request.connect(self._on_navigation_request) - - def event_target(self): - return self._widget diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 15b8f2c7b..28f911aa9 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -753,7 +753,7 @@ class TabbedBrowser(QWidget): self.widget.update_tab_title(idx) if idx == self.widget.currentIndex(): self._update_window_title() - tab.handle_auto_insert_mode(ok) + tab.private_api.handle_auto_insert_mode(ok) @pyqtSlot() def on_scroll_pos_changed(self): diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index e050e1fb5..6475e21d8 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -241,6 +241,12 @@ class FakeWebTabAudio(browsertab.AbstractAudio): return False +class FakeWebTabPrivate: + + def shutdown(self): + pass + + class FakeWebTab(browsertab.AbstractTab): """Fake AbstractTab to use in tests.""" @@ -258,6 +264,7 @@ class FakeWebTab(browsertab.AbstractTab): can_go_forward=can_go_forward) self.scroller = FakeWebTabScroller(self, scroll_pos_perc) self.audio = FakeWebTabAudio(self) + self.private_api = FakeWebTabPrivate() wrapped = QWidget() self._layout.wrap(self, wrapped) @@ -274,9 +281,6 @@ class FakeWebTab(browsertab.AbstractTab): def load_status(self): return self._load_status - def shutdown(self): - pass - def icon(self): return QIcon()