diff options
Diffstat (limited to 'qutebrowser/browser')
21 files changed, 200 insertions, 106 deletions
diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 661c5f68b..699fe1b0b 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -24,7 +24,7 @@ import itertools import functools import dataclasses from typing import (cast, TYPE_CHECKING, Any, Callable, Iterable, List, Optional, - Sequence, Set, Type, Union) + Sequence, Set, Type, Union, Tuple) from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt, QEvent, QPoint, QRect) @@ -35,12 +35,12 @@ from PyQt5.QtNetwork import QNetworkAccessManager if TYPE_CHECKING: from PyQt5.QtWebKit import QWebHistory, QWebHistoryItem - from PyQt5.QtWebKitWidgets import QWebPage + from PyQt5.QtWebKitWidgets import QWebPage, QWebView from PyQt5.QtWebEngineWidgets import ( - QWebEngineHistory, QWebEngineHistoryItem, QWebEnginePage) + QWebEngineHistory, QWebEngineHistoryItem, QWebEnginePage, QWebEngineView) from qutebrowser.keyinput import modeman -from qutebrowser.config import config +from qutebrowser.config import config, websettings from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils, urlutils, message, jinja) from qutebrowser.misc import miscwidgets, objects, sessions @@ -53,6 +53,7 @@ if TYPE_CHECKING: tab_id_gen = itertools.count(0) +_WidgetType = Union["QWebView", "QWebEngineView"] def create(win_id: int, @@ -156,7 +157,7 @@ class AbstractAction: action_base: Type[Union['QWebPage.WebAction', 'QWebEnginePage.WebAction']] def __init__(self, tab: 'AbstractTab') -> None: - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab def exit_fullscreen(self) -> None: @@ -172,6 +173,7 @@ class AbstractAction: member = getattr(self.action_class, name, None) if not isinstance(member, self.action_base): raise WebTabError("{} is not a valid web action!".format(name)) + assert member is not None # for mypy self._widget.triggerPageAction(member) def show_source(self, pygments: bool = False) -> None: @@ -229,7 +231,7 @@ class AbstractPrinting: """Attribute ``printing`` of AbstractTab for printing the page.""" def __init__(self, tab: 'AbstractTab') -> None: - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab def check_pdf_support(self) -> None: @@ -308,7 +310,7 @@ class AbstractSearch(QObject): def __init__(self, tab: 'AbstractTab', parent: QWidget = None): super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self.text: Optional[str] = None self.search_displayed = False @@ -372,7 +374,7 @@ class AbstractZoom(QObject): def __init__(self, tab: 'AbstractTab', parent: QWidget = None) -> None: super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) # Whether zoom was changed from the default. self._default_zoom_changed = False self._init_neighborlist() @@ -466,7 +468,7 @@ class AbstractCaret(QObject): mode_manager: modeman.ModeManager, parent: QWidget = None) -> None: super().__init__(parent) - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._mode_manager = mode_manager mode_manager.entered.connect(self._on_mode_entered) mode_manager.left.connect(self._on_mode_left) @@ -559,7 +561,7 @@ class AbstractScroller(QObject): def __init__(self, tab: 'AbstractTab', parent: QWidget = None): super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) if 'log-scroll-pos' in objects.debug_flags: self.perc_changed.connect(self._log_scroll_pos_change) @@ -568,16 +570,16 @@ class AbstractScroller(QObject): log.webview.vdebug( # type: ignore[attr-defined] "Scroll position changed to {}".format(self.pos_px())) - def _init_widget(self, widget: QWidget) -> None: + def _init_widget(self, widget: _WidgetType) -> None: self._widget = widget - def pos_px(self) -> int: + def pos_px(self) -> QPoint: raise NotImplementedError - def pos_perc(self) -> int: + def pos_perc(self) -> Tuple[int, int]: raise NotImplementedError - def to_perc(self, x: int = None, y: int = None) -> None: + def to_perc(self, x: float = None, y: float = None) -> None: raise NotImplementedError def to_point(self, point: QPoint) -> None: @@ -627,6 +629,8 @@ class AbstractHistoryPrivate: """Private API related to the history.""" + _history: Union["QWebHistory", "QWebEngineHistory"] + def serialize(self) -> bytes: """Serialize into an opaque format understood by self.deserialize.""" raise NotImplementedError @@ -711,7 +715,7 @@ class AbstractElements: _ErrorCallback = Callable[[Exception], None] def __init__(self, tab: 'AbstractTab') -> None: - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab def find_css(self, selector: str, @@ -772,7 +776,7 @@ class AbstractAudio(QObject): def __init__(self, tab: 'AbstractTab', parent: QWidget = None) -> None: super().__init__(parent) - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab def set_muted(self, muted: bool, override: bool = False) -> None: @@ -804,11 +808,11 @@ class AbstractTabPrivate: def __init__(self, mode_manager: modeman.ModeManager, tab: 'AbstractTab') -> None: - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab self._mode_manager = mode_manager - def event_target(self) -> QWidget: + def event_target(self) -> Optional[QWidget]: """Return the widget events should be sent to.""" raise NotImplementedError @@ -848,7 +852,7 @@ class AbstractTabPrivate: def shutdown(self) -> None: raise NotImplementedError - def run_js_sync(self, code: str) -> None: + def run_js_sync(self, code: str) -> Any: """Run javascript sync. Result will be returned when running JS is complete. @@ -867,7 +871,7 @@ class AbstractTabPrivate: self._tab.data.inspector = None self.toggle_inspector(inspector.Position.window) - def toggle_inspector(self, position: inspector.Position) -> None: + def toggle_inspector(self, position: Optional[inspector.Position]) -> None: """Show/hide (and if needed, create) the web inspector for this tab.""" tabdata = self._tab.data if tabdata.inspector is None: @@ -944,6 +948,19 @@ class AbstractTab(QWidget): # for a given hostname anyways. _insecure_hosts: Set[str] = set() + # Sub-APIs initialized by subclasses + history: AbstractHistory + scroller: AbstractScroller + caret: AbstractCaret + zoom: AbstractZoom + search: AbstractSearch + printing: AbstractPrinting + action: AbstractAction + elements: AbstractElements + audio: AbstractAudio + private_api: AbstractTabPrivate + settings: websettings.AbstractSettings + def __init__(self, *, win_id: int, mode_manager: 'modeman.ModeManager', private: bool, @@ -962,7 +979,7 @@ class AbstractTab(QWidget): self.data = TabData() self._layout = miscwidgets.WrapperLayout(self) - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._progress = 0 self._load_status = usertypes.LoadStatus.none self._tab_event_filter = eventfilter.TabEventFilter( @@ -976,7 +993,7 @@ class AbstractTab(QWidget): self.before_load_started.connect(self._on_before_load_started) - def _set_widget(self, widget: QWidget) -> None: + def _set_widget(self, widget: Union["QWebView", "QWebEngineView"]) -> None: # pylint: disable=protected-access self._widget = widget self.data.splitter = miscwidgets.InspectorSplitter( @@ -1195,7 +1212,7 @@ class AbstractTab(QWidget): def title(self) -> str: raise NotImplementedError - def icon(self) -> None: + def icon(self) -> QIcon: raise NotImplementedError def set_html(self, html: str, base_url: QUrl = QUrl()) -> None: diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 00d5e521f..7f9c4810d 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -913,6 +913,7 @@ class CommandDispatcher: # Not sure how you enter a command without an active window... raise cmdutils.CommandError( "No window specified and couldn't find active window!") + assert isinstance(active_win, mainwindow.MainWindow) win_id = active_win.win_id if win_id not in objreg.window_registry: diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 69c58741a..8187a1002 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -86,6 +86,15 @@ class DownloadView(QListView): count = model.rowCount() return utils.get_repr(self, count=count) + def _model(self) -> downloads.DownloadModel: + """Get the current download model. + + Ensures the model is not None. + """ + model = self.model() + assert isinstance(model, downloads.DownloadModel), model + return model + @pyqtSlot() def _update_geometry(self): """Wrapper to call updateGeometry. @@ -112,7 +121,7 @@ class DownloadView(QListView): """ if not index.isValid(): return - item = self.model().data(index, downloads.ModelRole.item) + item = self._model().data(index, downloads.ModelRole.item) if item.done and item.successful: item.open_file() item.remove() @@ -126,7 +135,7 @@ class DownloadView(QListView): Args: item: The DownloadItem to get the actions for, or None. """ - model = self.model() + model = self._model() actions: _ActionListType = [] if item is None: pass @@ -154,7 +163,7 @@ class DownloadView(QListView): """Show the context menu.""" index = self.indexAt(point) if index.isValid(): - item = self.model().data(index, downloads.ModelRole.item) + item = self._model().data(index, downloads.ModelRole.item) else: item = None self._menu = QMenu(self) @@ -176,7 +185,7 @@ class DownloadView(QListView): def sizeHint(self): """Return sizeHint based on the view contents.""" - idx = self.model().last_index() + idx = self._model().last_index() bottom = self.visualRect(idx).bottom() if bottom != -1: margins = self.contentsMargins() diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 2e4e8e4b4..335508b3d 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -255,7 +255,7 @@ class HintActions: flags = QUrl.FullyEncoded | QUrl.RemovePassword if url.scheme() == 'mailto': flags |= QUrl.RemoveScheme - urlstr = url.toString(flags) # type: ignore[arg-type] + urlstr = url.toString(flags) new_content = urlstr @@ -356,8 +356,7 @@ class HintActions: url: The URL to open as a QUrl. context: The HintContext to use. """ - urlstr = url.toString( - QUrl.FullyEncoded | QUrl.RemovePassword) # type: ignore[arg-type] + urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword) args = context.get_args(urlstr) commandrunner = runners.CommandRunner(self._win_id) commandrunner.run_safely('spawn ' + ' '.join(args)) @@ -657,6 +656,7 @@ class HintManager(QObject): self._context.labels[string] = label keyparser = self._get_keyparser(usertypes.KeyMode.hint) + assert isinstance(keyparser, modeparsers.HintKeyParser) keyparser.update_bindings(strings) modeman.enter(self._win_id, usertypes.KeyMode.hint, @@ -852,6 +852,7 @@ class HintManager(QObject): # apply auto_follow_timeout timeout = config.val.hints.auto_follow_timeout normal_parser = self._get_keyparser(usertypes.KeyMode.normal) + assert isinstance(normal_parser, modeparsers.NormalKeyParser) normal_parser.set_inhibited_timeout(timeout) # unpacking gets us the first (and only) key in the dict. self._fire(*visible) @@ -927,6 +928,7 @@ class HintManager(QObject): self._context.labels[string] = label keyparser = self._get_keyparser(usertypes.KeyMode.hint) + assert isinstance(keyparser, modeparsers.HintKeyParser) keyparser.update_bindings(strings, preserve_filter=True) # Note: filter_hints can be called with non-None filterstr only diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index 2b40e97e4..51cad3709 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -22,7 +22,7 @@ import base64 import binascii import enum -from typing import cast, Optional +from typing import cast, Optional, Union, TYPE_CHECKING from PyQt5.QtWidgets import QWidget from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent @@ -34,6 +34,14 @@ from qutebrowser.utils import log, usertypes from qutebrowser.keyinput import modeman from qutebrowser.misc import miscwidgets +if TYPE_CHECKING: + from PyQt5.QtWebKitWidgets import QWebInspector, QWebPage + from PyQt5.QtWebEngineWidgets import QWebEnginePage + from qutebrowser.browser.webengine import webengineinspector + + +_WidgetType = Union["QWebInspector", "webengineinspector.WebEngineInspectorView"] + class Position(enum.Enum): @@ -93,7 +101,7 @@ class AbstractWebInspector(QWidget): win_id: int, parent: QWidget = None) -> None: super().__init__(parent) - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._layout = miscwidgets.WrapperLayout(self) self._splitter = splitter self._position: Optional[Position] = None @@ -105,7 +113,7 @@ class AbstractWebInspector(QWidget): eventfilter=self._event_filter, parent=self) - def _set_widget(self, widget: QWidget) -> None: + def _set_widget(self, widget: _WidgetType) -> None: self._widget = widget self._widget.setWindowTitle("Web Inspector") self._widget.installEventFilter(self._child_event_filter) @@ -198,7 +206,7 @@ class AbstractWebInspector(QWidget): geom = base64.b64encode(data).decode('ASCII') configfiles.state['inspector']['window'] = geom - def inspect(self, page: QWidget) -> None: + def inspect(self, page: Union["QWebPage", "QWebEnginePage"]) -> None: """Inspect the given QWeb(Engine)Page.""" raise NotImplementedError diff --git a/qutebrowser/browser/network/pac.py b/qutebrowser/browser/network/pac.py index 3a544c78f..99078ab20 100644 --- a/qutebrowser/browser/network/pac.py +++ b/qutebrowser/browser/network/pac.py @@ -270,8 +270,7 @@ class PACFetcher(QObject): """Fetch the proxy from the remote URL.""" assert self._manager is not None self._reply = self._manager.get(QNetworkRequest(self._pac_url)) - self._reply.finished.connect( # type: ignore[attr-defined] - self._finish) + self._reply.finished.connect(self._finish) @pyqtSlot() def _finish(self): diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 8adb7ea20..a77289efb 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -24,7 +24,7 @@ import os.path import shutil import functools import dataclasses -from typing import Dict, IO, Optional +from typing import Dict, IO, Optional, List from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer, QUrl from PyQt5.QtWidgets import QApplication @@ -601,6 +601,7 @@ class DownloadManager(downloads.AbstractDownloadManager): """ assert nam.adopted_downloads == 0 for download in self.downloads: + assert isinstance(download, DownloadItem) if download._uses_nam(nam): # pylint: disable=protected-access nam.adopt_download(download) return nam.adopted_downloads diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 68e36d249..c0da8ac94 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -128,9 +128,7 @@ def data_for_url(url: QUrl) -> Tuple[str, bytes]: Return: A (mimetype, data) tuple. """ - norm_url = url.adjusted( - QUrl.NormalizePathSegments | # type: ignore[arg-type] - QUrl.StripTrailingSlash) + norm_url = url.adjusted(QUrl.NormalizePathSegments | QUrl.StripTrailingSlash) if norm_url != url: raise Redirect(norm_url) diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index 2b77a5ac4..57b593ce1 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -288,7 +288,7 @@ class NotificationBridgePresenter(QObject): qt_notification.show() self._active_notifications[notification_id] = qt_notification - qt_notification.closed.connect( # type: ignore[attr-defined] + qt_notification.closed.connect( functools.partial(self._adapter.on_web_closed, notification_id)) def _find_replaces_id( @@ -632,6 +632,7 @@ class HerbeNotificationAdapter(AbstractNotificationAdapter): self.close_id.emit(pid) else: proc = self.sender() + assert isinstance(proc, QProcess), proc stderr = proc.readAllStandardError() raise Error(f'herbe exited with status {code}: {stderr}') @@ -757,8 +758,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): QDBusServiceWatcher.WatchForUnregistration, self, ) - self._watcher.serviceUnregistered.connect( # type: ignore[attr-defined] - self._on_service_unregistered) + self._watcher.serviceUnregistered.connect(self._on_service_unregistered) test_service = 'test-notification-service' in objects.debug_flags service = self.TEST_SERVICE if test_service else self.SERVICE diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index a6a2a1b93..a96f49d6b 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -44,10 +44,8 @@ class DownloadItem(downloads.AbstractDownloadItem): parent: QObject = None) -> None: super().__init__(manager=manager, parent=manager) self._qt_item = qt_item - qt_item.downloadProgress.connect( # type: ignore[attr-defined] - self.stats.on_download_progress) - qt_item.stateChanged.connect( # type: ignore[attr-defined] - self._on_state_changed) + qt_item.downloadProgress.connect(self.stats.on_download_progress) + qt_item.stateChanged.connect(self._on_state_changed) # Ensure wrapped qt_item is deleted manually when the wrapper object # is deleted. See https://github.com/qutebrowser/qutebrowser/issues/3373 @@ -92,7 +90,7 @@ class DownloadItem(downloads.AbstractDownloadItem): def _do_die(self): progress_signal = self._qt_item.downloadProgress - progress_signal.disconnect() # type: ignore[attr-defined] + progress_signal.disconnect() if self._qt_item.state() != QWebEngineDownloadItem.DownloadInterrupted: self._qt_item.cancel() diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 5d4c6ad9a..75b7a51ba 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -37,6 +37,8 @@ class WebEngineElement(webelem.AbstractWebElement): """A web element for QtWebEngine, using JS under the hood.""" + _tab: "webenginetab.WebEngineTab" + def __init__(self, js_dict: Dict[str, Any], tab: 'webenginetab.WebEngineTab') -> None: super().__init__(tab) @@ -248,7 +250,7 @@ class WebEngineElement(webelem.AbstractWebElement): # (it does so with a 0ms QTimer...) # This is also used in Qt's tests: # https://github.com/qt/qtwebengine/commit/5e572e88efa7ba7c2b9138ec19e606d3e345ac90 - QApplication.processEvents( # type: ignore[call-overload] + QApplication.processEvents( QEventLoop.ExcludeSocketNotifiers | QEventLoop.ExcludeUserInputEvents) diff --git a/qutebrowser/browser/webengine/webengineinspector.py b/qutebrowser/browser/webengine/webengineinspector.py index ae31c0bee..d30a716de 100644 --- a/qutebrowser/browser/webengine/webengineinspector.py +++ b/qutebrowser/browser/webengine/webengineinspector.py @@ -49,13 +49,17 @@ class WebEngineInspectorView(QWebEngineView): See WebEngineView.createWindow for details. """ - return self.page().inspectedPage().view().createWindow(wintype) + view = self.page().inspectedPage().view() + assert isinstance(view, QWebEngineView) + return view.createWindow(wintype) class WebEngineInspector(inspector.AbstractWebInspector): """A web inspector for QtWebEngine with Qt API support.""" + _widget: WebEngineInspectorView + def __init__(self, splitter: miscwidgets.InspectorSplitter, win_id: int, parent: QWidget = None) -> None: @@ -66,8 +70,7 @@ class WebEngineInspector(inspector.AbstractWebInspector): self._settings = webenginesettings.WebEngineSettings(view.settings()) self._set_widget(view) page = view.page() - page.windowCloseRequested.connect( # type: ignore[attr-defined] - self._on_window_close_requested) + page.windowCloseRequested.connect(self._on_window_close_requested) def _on_window_close_requested(self) -> None: """Called when the 'x' was clicked in the devtools.""" @@ -96,7 +99,7 @@ class WebEngineInspector(inspector.AbstractWebInspector): "please install the qt5-qtwebengine-devtools " "Fedora package.") - def inspect(self, page: QWebEnginePage) -> None: # type: ignore[override] + def inspect(self, page: QWebEnginePage) -> None: inspector_page = self._widget.page() inspector_page.setInspectedPage(page) self._settings.update_for_url(inspector_page.requestedUrl()) diff --git a/qutebrowser/browser/webengine/webenginequtescheme.py b/qutebrowser/browser/webengine/webenginequtescheme.py index 64361f7c4..9e073951a 100644 --- a/qutebrowser/browser/webengine/webenginequtescheme.py +++ b/qutebrowser/browser/webengine/webenginequtescheme.py @@ -138,6 +138,6 @@ def init(): assert not QWebEngineUrlScheme.schemeByName(b'qute').name() scheme = QWebEngineUrlScheme(b'qute') scheme.setFlags( - QWebEngineUrlScheme.LocalScheme | # type: ignore[arg-type] + QWebEngineUrlScheme.LocalScheme | QWebEngineUrlScheme.LocalAccessAllowed) QWebEngineUrlScheme.registerScheme(scheme) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 5430cec77..0b25726c0 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -332,9 +332,9 @@ class ProfileSetter: def _update_settings(option): """Update global settings when qwebsettings changed.""" _global_settings.update_setting(option) - default_profile.setter.update_setting(option) + default_profile.setter.update_setting(option) # type: ignore[attr-defined] if private_profile: - private_profile.setter.update_setting(option) + private_profile.setter.update_setting(option) # type: ignore[attr-defined] def _init_user_agent_str(ua): @@ -352,8 +352,9 @@ def _init_profile(profile: QWebEngineProfile) -> None: This currently only contains the steps which are shared between a private and a non-private profile (at the moment, only the default profile). """ + # FIXME:mypy subclass QWebEngineProfile instead? profile.setter = ProfileSetter(profile) # type: ignore[attr-defined] - profile.setter.init_profile() + profile.setter.init_profile() # type: ignore[attr-defined] _qute_scheme_handler.install(profile) _req_interceptor.install(profile) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 7d355d10e..28182d1b6 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -30,7 +30,7 @@ from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QTimer, QUr QObject) from PyQt5.QtNetwork import QAuthenticator from PyQt5.QtWidgets import QWidget -from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngineHistory +from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineView, QWebEngineScript, QWebEngineHistory from qutebrowser.config import config from qutebrowser.browser import browsertab, eventfilter, shared, webelem, greasemonkey @@ -57,6 +57,8 @@ class WebEngineAction(browsertab.AbstractAction): """QtWebEngine implementations related to web actions.""" + _widget: webview.WebEngineView + action_class = QWebEnginePage action_base = QWebEnginePage.WebAction @@ -79,6 +81,8 @@ class WebEnginePrinting(browsertab.AbstractPrinting): """QtWebEngine implementations related to printing.""" + _widget: webview.WebEngineView + def check_pdf_support(self): pass @@ -190,6 +194,8 @@ class WebEngineSearch(browsertab.AbstractSearch): _pending_searches: How many searches have been started but not called back yet. """ + + _widget: webview.WebEngineView def __init__(self, tab, parent=None): super().__init__(tab, parent) @@ -199,7 +205,7 @@ class WebEngineSearch(browsertab.AbstractSearch): self._wrap_handler = _WebEngineSearchWrapHandler() def _empty_flags(self): - return QWebEnginePage.FindFlags(0) # type: ignore[call-overload] + return QWebEnginePage.FindFlags(0) def _args_to_flags(self, reverse, ignore_case): flags = self._empty_flags() @@ -275,8 +281,7 @@ class WebEngineSearch(browsertab.AbstractSearch): def prev_result(self, *, result_cb=None): # The int() here makes sure we get a copy of the flags. - flags = QWebEnginePage.FindFlags( - int(self._flags)) # type: ignore[call-overload] + flags = QWebEnginePage.FindFlags(int(self._flags)) if flags & QWebEnginePage.FindBackward: if self._wrap_handler.prevent_wrapping(going_up=False): return @@ -493,6 +498,8 @@ class WebEngineScroller(browsertab.AbstractScroller): """QtWebEngine implementations related to scrolling.""" + _widget: webview.WebEngineView + def __init__(self, tab, parent=None): super().__init__(tab, parent) self._pos_perc = (0, 0) @@ -713,6 +720,8 @@ class WebEngineZoom(browsertab.AbstractZoom): """QtWebEngine implementations related to zooming.""" + _widget: webview.WebEngineView + def _set_factor_internal(self, factor): self._widget.setZoomFactor(factor) @@ -799,6 +808,8 @@ class WebEngineAudio(browsertab.AbstractAudio): If that's the case, we leave it alone. """ + _widget: webview.WebEngineView + def __init__(self, tab, parent=None): super().__init__(tab, parent) self._overridden = False @@ -870,6 +881,8 @@ class _WebEnginePermissions(QObject): """Handling of various permission-related signals.""" + _widget: webview.WebEngineView + # Using 0 as WORKAROUND for: # https://www.riverbankcomputing.com/pipermail/pyqt/2019-July/041903.html @@ -898,7 +911,7 @@ class _WebEnginePermissions(QObject): def __init__(self, tab, parent=None): super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(webview.WebEngineView, None) assert self._options.keys() == self._messages.keys() def connect_signals(self): @@ -1033,10 +1046,12 @@ class _Quirk: class _WebEngineScripts(QObject): + _widget: webview.WebEngineView + def __init__(self, tab, parent=None): super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(webview.WebEngineView, None) self._greasemonkey = greasemonkey.gm_manager def connect_signals(self): @@ -1240,6 +1255,8 @@ class WebEngineTabPrivate(browsertab.AbstractTabPrivate): """QtWebEngine-related methods which aren't part of the public API.""" + _widget: webview.WebEngineView + def networkaccessmanager(self): return None @@ -1275,6 +1292,10 @@ class WebEngineTab(browsertab.AbstractTab): abort_questions = pyqtSignal() + _widget: QWebEngineView + search: WebEngineSearch + audio: WebEngineAudio + def __init__(self, *, win_id, mode_manager, private, parent=None): super().__init__(win_id=win_id, mode_manager=mode_manager, @@ -1685,6 +1706,7 @@ class WebEngineTab(browsertab.AbstractTab): def _connect_signals(self): view = self._widget page = view.page() + assert isinstance(page, webview.WebEnginePage) page.windowCloseRequested.connect(self.window_close_requested) page.linkHovered.connect(self.link_hovered) diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 76ce1a42e..6e72df014 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -70,7 +70,9 @@ class WebEngineView(QWebEngineView): return self.focusProxy() def shutdown(self): - self.page().shutdown() + page = self.page() + assert isinstance(page, WebEnginePage) + page.shutdown() def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. diff --git a/qutebrowser/browser/webkit/network/networkreply.py b/qutebrowser/browser/webkit/network/networkreply.py index c1ead3209..d5c724d63 100644 --- a/qutebrowser/browser/webkit/network/networkreply.py +++ b/qutebrowser/browser/webkit/network/networkreply.py @@ -59,15 +59,9 @@ class FixedDataNetworkReply(QNetworkReply): # For some reason, a segfault will be triggered if these lambdas aren't # there. # pylint: disable=unnecessary-lambda - QTimer.singleShot( - 0, - lambda: self.metaDataChanged.emit()) # type: ignore[attr-defined] - QTimer.singleShot( - 0, - lambda: self.readyRead.emit()) # type: ignore[attr-defined] - QTimer.singleShot( - 0, - lambda: self.finished.emit()) # type: ignore[attr-defined] + QTimer.singleShot(0, lambda: self.metaDataChanged.emit()) + QTimer.singleShot(0, lambda: self.readyRead.emit()) + QTimer.singleShot(0, lambda: self.finished.emit()) @pyqtSlot() def abort(self): @@ -122,10 +116,8 @@ class ErrorNetworkReply(QNetworkReply): # the device to avoid getting a warning. self.setOpenMode(QIODevice.ReadOnly) self.setError(error, errorstring) - QTimer.singleShot(0, lambda: - self.error.emit(error)) # type: ignore[attr-defined] - QTimer.singleShot(0, lambda: - self.finished.emit()) # type: ignore[attr-defined] + QTimer.singleShot(0, lambda: self.error.emit(error)) + QTimer.singleShot(0, lambda: self.finished.emit()) def abort(self): """Do nothing since it's a fake reply.""" @@ -152,8 +144,7 @@ class RedirectNetworkReply(QNetworkReply): def __init__(self, new_url, parent=None): super().__init__(parent) self.setAttribute(QNetworkRequest.RedirectionTargetAttribute, new_url) - QTimer.singleShot(0, lambda: - self.finished.emit()) # type: ignore[attr-defined] + QTimer.singleShot(0, lambda: self.finished.emit()) def abort(self): """Called when there's e.g. a redirection limit.""" diff --git a/qutebrowser/browser/webkit/webkitinspector.py b/qutebrowser/browser/webkit/webkitinspector.py index 57bcd40ea..2d4b95caf 100644 --- a/qutebrowser/browser/webkit/webkitinspector.py +++ b/qutebrowser/browser/webkit/webkitinspector.py @@ -31,6 +31,8 @@ class WebKitInspector(inspector.AbstractWebInspector): """A web inspector for QtWebKit.""" + _widget = QWebInspector + def __init__(self, splitter: miscwidgets.InspectorSplitter, win_id: int, parent: QWidget = None) -> None: @@ -38,7 +40,7 @@ class WebKitInspector(inspector.AbstractWebInspector): qwebinspector = QWebInspector() self._set_widget(qwebinspector) - def inspect(self, page: QWebPage) -> None: # type: ignore[override] + def inspect(self, page: QWebPage) -> None: settings = QWebSettings.globalSettings() settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) self._widget.setPage(page) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 7a41b995c..6be096acf 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -32,8 +32,9 @@ from PyQt5.QtWebKit import QWebSettings, QWebHistory, QWebElement from PyQt5.QtPrintSupport import QPrinter from qutebrowser.browser import browsertab, shared -from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem, +from qutebrowser.browser.webkit import (webview, webpage, tabhistory, webkitelem, webkitsettings, webkitinspector) +from qutebrowser.browser.webkit.network import networkmanager from qutebrowser.utils import qtutils, usertypes, utils, log, debug, resources from qutebrowser.keyinput import modeman from qutebrowser.qt import sip @@ -46,6 +47,8 @@ class WebKitAction(browsertab.AbstractAction): action_class = QWebPage action_base = QWebPage.WebAction + _widget: webview.WebView + def exit_fullscreen(self): raise browsertab.UnsupportedOperationError @@ -69,7 +72,7 @@ class WebKitAction(browsertab.AbstractAction): 'Unselect': QWebPage.ToggleVideoFullscreen + 2, } if name in new_actions: - self._widget.triggerPageAction(new_actions[name]) + self._widget.triggerPageAction(new_actions[name]) # type: ignore[arg-type] return super().run_string(name) @@ -79,6 +82,8 @@ class WebKitPrinting(browsertab.AbstractPrinting): """QtWebKit implementations related to printing.""" + _widget: webview.WebView + def check_pdf_support(self): pass @@ -101,6 +106,8 @@ class WebKitSearch(browsertab.AbstractSearch): """QtWebKit implementations related to searching on the page.""" + _widget: webview.WebView + def __init__(self, tab, parent=None): super().__init__(tab, parent) self._flags = self._empty_flags() @@ -153,7 +160,8 @@ class WebKitSearch(browsertab.AbstractSearch): self.search_displayed = False # We first clear the marked text, then the highlights self._widget.findText('') - self._widget.findText('', QWebPage.HighlightAllOccurrences) + self._widget.findText( + '', QWebPage.HighlightAllOccurrences) # type: ignore[arg-type] def search(self, text, *, ignore_case=usertypes.IgnoreCase.never, reverse=False, wrap=True, result_cb=None): @@ -179,7 +187,7 @@ class WebKitSearch(browsertab.AbstractSearch): def next_result(self, *, result_cb=None): self.search_displayed = True - found = self._widget.findText(self.text, self._flags) + found = self._widget.findText(self.text, self._flags) # type: ignore[arg-type] self._call_cb(result_cb, found, self.text, self._flags, 'next_result') def prev_result(self, *, result_cb=None): @@ -191,7 +199,7 @@ class WebKitSearch(browsertab.AbstractSearch): flags &= ~QWebPage.FindBackward else: flags |= QWebPage.FindBackward - found = self._widget.findText(self.text, flags) + found = self._widget.findText(self.text, flags) # type: ignore[arg-type] self._call_cb(result_cb, found, self.text, flags, 'prev_result') @@ -199,6 +207,8 @@ class WebKitCaret(browsertab.AbstractCaret): """QtWebKit implementations related to moving the cursor/selection.""" + _widget: webview.WebView + def __init__(self, tab: 'WebKitTab', mode_manager: modeman.ModeManager, @@ -515,6 +525,8 @@ class WebKitZoom(browsertab.AbstractZoom): """QtWebKit implementations related to zooming.""" + _widget: webview.WebView + def _set_factor_internal(self, factor): self._widget.setZoomFactor(factor) @@ -525,6 +537,8 @@ class WebKitScroller(browsertab.AbstractScroller): # FIXME:qtwebengine When to use the main frame, when the current one? + _widget: webview.WebView + def pos_px(self): return self._widget.page().mainFrame().scrollPosition() @@ -624,6 +638,8 @@ class WebKitHistoryPrivate(browsertab.AbstractHistoryPrivate): """History-related methods which are not part of the extension API.""" + _history: QWebHistory + def __init__(self, tab: 'WebKitTab') -> None: self._tab = tab self._history = cast(QWebHistory, None) @@ -695,6 +711,7 @@ class WebKitElements(browsertab.AbstractElements): """QtWebKit implementations related to elements on the page.""" _tab: 'WebKitTab' + _widget: webview.WebView def find_css(self, selector, callback, error_cb, *, only_visible=False): utils.unused(error_cb) @@ -730,7 +747,7 @@ class WebKitElements(browsertab.AbstractElements): self.find_css('#' + elem_id, find_id_cb, error_cb=lambda exc: None) def find_focused(self, callback): - frame = self._widget.page().currentFrame() + frame = cast(Optional[QWebFrame], self._widget.page().currentFrame()) if frame is None: callback(None) return @@ -744,7 +761,7 @@ class WebKitElements(browsertab.AbstractElements): def find_at_pos(self, pos, callback): assert pos.x() >= 0 assert pos.y() >= 0 - frame = self._widget.page().frameAt(pos) + frame = cast(Optional[QWebFrame], self._widget.page().frameAt(pos)) if frame is None: # This happens when we click inside the webview, but not actually # on the QWebPage - for example when clicking the scrollbar @@ -796,6 +813,8 @@ class WebKitTabPrivate(browsertab.AbstractTabPrivate): """QtWebKit-related methods which aren't part of the public API.""" + _widget: webview.WebView + def networkaccessmanager(self): return self._widget.page().networkAccessManager() @@ -821,6 +840,8 @@ class WebKitTab(browsertab.AbstractTab): """A QtWebKit tab in the browser.""" + _widget: webview.WebView + def __init__(self, *, win_id, mode_manager, private, parent=None): super().__init__(win_id=win_id, mode_manager=mode_manager, @@ -912,6 +933,7 @@ class WebKitTab(browsertab.AbstractTab): def _on_load_started(self): super()._on_load_started() nam = self._widget.page().networkAccessManager() + assert isinstance(nam, networkmanager.NetworkManager) nam.netrc_used = False # Make sure the icon is cleared when navigating to a page without one. self.icon_changed.emit(QIcon()) @@ -929,7 +951,9 @@ class WebKitTab(browsertab.AbstractTab): when using error pages... See https://github.com/qutebrowser/qutebrowser/issues/84 """ - self._on_load_finished(not self._widget.page().error_occurred) + page = self._widget.page() + assert isinstance(page, webpage.BrowserPage) + self._on_load_finished(not page.error_occurred) @pyqtSlot() def _on_webkit_icon_changed(self): @@ -984,18 +1008,30 @@ class WebKitTab(browsertab.AbstractTab): view = self._widget page = view.page() frame = page.mainFrame() - page.windowCloseRequested.connect(self.window_close_requested) - page.linkHovered.connect(self.link_hovered) - page.loadProgress.connect(self._on_load_progress) - frame.loadStarted.connect(self._on_load_started) + page.windowCloseRequested.connect( # type: ignore[attr-defined] + self.window_close_requested) + page.linkHovered.connect( # type: ignore[attr-defined] + self.link_hovered) + page.loadProgress.connect( # type: ignore[attr-defined] + self._on_load_progress) + frame.loadStarted.connect( # type: ignore[attr-defined] + self._on_load_started) view.scroll_pos_changed.connect(self.scroller.perc_changed) - view.titleChanged.connect(self.title_changed) - view.urlChanged.connect(self._on_url_changed) + view.titleChanged.connect( # type: ignore[attr-defined] + self.title_changed) + view.urlChanged.connect( # type: ignore[attr-defined] + self._on_url_changed) view.shutting_down.connect(self.shutting_down) page.networkAccessManager().sslErrors.connect(self._on_ssl_errors) - frame.loadFinished.connect(self._on_frame_load_finished) - view.iconChanged.connect(self._on_webkit_icon_changed) - page.frameCreated.connect(self._on_frame_created) - frame.contentsSizeChanged.connect(self._on_contents_size_changed) - frame.initialLayoutCompleted.connect(self._on_history_trigger) - page.navigation_request.connect(self._on_navigation_request) + frame.loadFinished.connect( # type: ignore[attr-defined] + self._on_frame_load_finished) + view.iconChanged.connect( # type: ignore[attr-defined] + self._on_webkit_icon_changed) + page.frameCreated.connect( # type: ignore[attr-defined] + self._on_frame_created) + frame.contentsSizeChanged.connect( # type: ignore[attr-defined] + self._on_contents_size_changed) + frame.initialLayoutCompleted.connect( # type: ignore[attr-defined] + self._on_history_trigger) + page.navigation_request.connect( # type: ignore[attr-defined] + self._on_navigation_request) diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index ddbd78de2..0adc003d1 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -241,6 +241,7 @@ class BrowserPage(QWebPage): if download_manager.has_downloads_with_nam(nam): nam.setParent(download_manager) else: + assert isinstance(nam, networkmanager.NetworkManager) nam.shutdown() def display_content(self, reply, mimetype): @@ -371,11 +372,10 @@ class BrowserPage(QWebPage): self.setFeaturePermission, frame, feature, QWebPage.PermissionDeniedByUser) - url = frame.url().adjusted(cast(QUrl.FormattingOptions, - QUrl.RemoveUserInfo | - QUrl.RemovePath | - QUrl.RemoveQuery | - QUrl.RemoveFragment)) + url = frame.url().adjusted(QUrl.RemoveUserInfo | # type: ignore[operator] + QUrl.RemovePath | + QUrl.RemoveQuery | + QUrl.RemoveFragment) question = shared.feature_permission( url=url, option=options[feature], msg=messages[feature], diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index 289e29920..e54c4af50 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -109,7 +109,9 @@ class WebView(QWebView): settings = self.settings() settings.setAttribute(QWebSettings.JavascriptEnabled, False) self.stop() - self.page().shutdown() + page = self.page() + assert isinstance(page, webpage.BrowserPage) + page.shutdown() def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. |