Add type hints for qutebrowser.browser.browsertab

This commit is contained in:
Florian Bruhin 2018-11-27 20:35:58 +01:00
parent f36a98ec76
commit d7786c694f
4 changed files with 229 additions and 163 deletions

View File

@ -49,3 +49,6 @@ ignore_missing_imports = True
[mypy-qutebrowser.browser.webkit.rfc6266] [mypy-qutebrowser.browser.webkit.rfc6266]
# subclasses dynamic PyPEG2 classes # subclasses dynamic PyPEG2 classes
disallow_subclassing_any = False disallow_subclassing_any = False
[mypy-qutebrowser.browser.browsertab]
disallow_untyped_defs = True

View File

@ -21,12 +21,19 @@
import enum import enum
import itertools import itertools
import typing
import attr import attr
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt,
QEvent, QPoint)
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QApplication, QDialog from PyQt5.QtWidgets import QWidget, QApplication, QDialog
from PyQt5.QtPrintSupport import QPrintDialog from PyQt5.QtPrintSupport import QPrintDialog, QPrinter
from PyQt5.QtNetwork import QNetworkAccessManager
MYPY = False
if MYPY:
# pylint: disable=unused-import
from PyQt5.QtWebEngineWidgets import QWebEngineView
import pygments import pygments
import pygments.lexers import pygments.lexers
@ -37,14 +44,19 @@ from qutebrowser.config import config
from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils, from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils,
urlutils, message) urlutils, message)
from qutebrowser.misc import miscwidgets, objects from qutebrowser.misc import miscwidgets, objects
from qutebrowser.browser import mouse, hints from qutebrowser.browser import mouse, hints, webelem
from qutebrowser.qt import sip from qutebrowser.qt import sip
if MYPY:
# pylint: disable=unused-import
from qutebrowser.browser.inspector import AbstractWebInspector
tab_id_gen = itertools.count(0) tab_id_gen = itertools.count(0)
def create(win_id, private, parent=None): def create(win_id: int,
private: bool,
parent: QWidget = None) -> 'AbstractTab':
"""Get a QtWebKit/QtWebEngine tab object. """Get a QtWebKit/QtWebEngine tab object.
Args: Args:
@ -65,7 +77,7 @@ def create(win_id, private, parent=None):
parent=parent) parent=parent)
def init(): def init() -> None:
"""Initialize backend-specific modules.""" """Initialize backend-specific modules."""
if objects.backend == usertypes.Backend.QtWebEngine: if objects.backend == usertypes.Backend.QtWebEngine:
from qutebrowser.browser.webengine import webenginetab from qutebrowser.browser.webengine import webenginetab
@ -112,17 +124,18 @@ class TabData:
input_mode: current input mode for the tab. input_mode: current input mode for the tab.
""" """
keep_icon = attr.ib(False) keep_icon = attr.ib(False) # type: bool
viewing_source = attr.ib(False) viewing_source = attr.ib(False) # type: bool
inspector = attr.ib(None) inspector = attr.ib(None) # type: typing.Optional[AbstractWebInspector]
open_target = attr.ib(usertypes.ClickTarget.normal) open_target = attr.ib(
override_target = attr.ib(None) usertypes.ClickTarget.normal) # type: usertypes.ClickTarget
pinned = attr.ib(False) override_target = attr.ib(None) # type: usertypes.ClickTarget
fullscreen = attr.ib(False) pinned = attr.ib(False) # type: bool
netrc_used = attr.ib(False) fullscreen = attr.ib(False) # type: bool
input_mode = attr.ib(usertypes.KeyMode.normal) netrc_used = attr.ib(False) # type: bool
input_mode = attr.ib(usertypes.KeyMode.normal) # type: usertypes.KeyMode
def should_show_icon(self): def should_show_icon(self) -> bool:
return (config.val.tabs.favicons.show == 'always' or return (config.val.tabs.favicons.show == 'always' or
config.val.tabs.favicons.show == 'pinned' and self.pinned) config.val.tabs.favicons.show == 'pinned' and self.pinned)
@ -139,33 +152,35 @@ class AbstractAction:
action_class = None # type: type action_class = None # type: type
action_base = None # type: type action_base = None # type: type
def __init__(self, tab): def __init__(self, tab: 'AbstractTab') -> None:
self._widget = None self._widget = typing.cast('QWebEngineView', None)
self._tab = tab self._tab = tab
def exit_fullscreen(self): def exit_fullscreen(self) -> None:
"""Exit the fullscreen mode.""" """Exit the fullscreen mode."""
raise NotImplementedError raise NotImplementedError
def save_page(self): def save_page(self) -> None:
"""Save the current page.""" """Save the current page."""
raise NotImplementedError raise NotImplementedError
def run_string(self, name): def run_string(self, name: str) -> None:
"""Run a webaction based on its name.""" """Run a webaction based on its name."""
member = getattr(self.action_class, name, None) member = getattr(self.action_class, name, None)
if not isinstance(member, self.action_base): if not isinstance(member, self.action_base):
raise WebTabError("{} is not a valid web action!".format(name)) raise WebTabError("{} is not a valid web action!".format(name))
self._widget.triggerPageAction(member) self._widget.triggerPageAction(member) # type: ignore
def show_source(self, def show_source(
pygments=False): # pylint: disable=redefined-outer-name self,
pygments: bool = False # pylint: disable=redefined-outer-name
) -> None:
"""Show the source of the current page in a new tab.""" """Show the source of the current page in a new tab."""
raise NotImplementedError raise NotImplementedError
def _show_source_pygments(self): def _show_source_pygments(self) -> None:
def show_source_cb(source): def show_source_cb(source: str) -> None:
"""Show source as soon as it's ready.""" """Show source as soon as it's ready."""
# WORKAROUND for https://github.com/PyCQA/pylint/issues/491 # WORKAROUND for https://github.com/PyCQA/pylint/issues/491
# pylint: disable=no-member # pylint: disable=no-member
@ -188,23 +203,24 @@ class AbstractPrinting:
"""Attribute of AbstractTab for printing the page.""" """Attribute of AbstractTab for printing the page."""
def __init__(self, tab): def __init__(self, tab: 'AbstractTab') -> None:
self._widget = None self._widget = None
self._tab = tab self._tab = tab
def check_pdf_support(self): def check_pdf_support(self) -> bool:
raise NotImplementedError raise NotImplementedError
def check_printer_support(self): def check_printer_support(self) -> bool:
raise NotImplementedError raise NotImplementedError
def check_preview_support(self): def check_preview_support(self) -> bool:
raise NotImplementedError raise NotImplementedError
def to_pdf(self, filename): def to_pdf(self, filename: str) -> bool:
raise NotImplementedError raise NotImplementedError
def to_printer(self, printer, callback=None): def to_printer(self, printer: QPrinter,
callback: typing.Callable[[bool], None] = None) -> None:
"""Print the tab. """Print the tab.
Args: Args:
@ -214,17 +230,17 @@ class AbstractPrinting:
""" """
raise NotImplementedError raise NotImplementedError
def show_dialog(self): def show_dialog(self) -> None:
"""Print with a QPrintDialog.""" """Print with a QPrintDialog."""
self.check_printer_support() self.check_printer_support()
def print_callback(ok): def print_callback(ok: bool) -> None:
"""Called when printing finished.""" """Called when printing finished."""
if not ok: if not ok:
message.error("Printing failed!") message.error("Printing failed!")
diag.deleteLater() diag.deleteLater()
def do_print(): def do_print() -> None:
"""Called when the dialog was closed.""" """Called when the dialog was closed."""
self.to_printer(diag.printer(), print_callback) self.to_printer(diag.printer(), print_callback)
@ -257,15 +273,16 @@ class AbstractSearch(QObject):
finished = pyqtSignal(bool) finished = pyqtSignal(bool)
cleared = pyqtSignal() cleared = pyqtSignal()
_Callback = typing.Callable[[bool], None]
def __init__(self, tab, parent=None): def __init__(self, tab: 'AbstractTab', parent: QWidget = None):
super().__init__(parent) super().__init__(parent)
self._tab = tab self._tab = tab
self._widget = None self._widget = None
self.text = None self.text = None # type: typing.Optional[str]
self.search_displayed = False self.search_displayed = False
def _is_case_sensitive(self, ignore_case): def _is_case_sensitive(self, ignore_case: str) -> bool:
"""Check if case-sensitivity should be used. """Check if case-sensitivity should be used.
This assumes self.text is already set properly. This assumes self.text is already set properly.
@ -273,6 +290,7 @@ class AbstractSearch(QObject):
Arguments: Arguments:
ignore_case: The ignore_case value from the config. ignore_case: The ignore_case value from the config.
""" """
assert self.text is not None
mapping = { mapping = {
'smart': not self.text.islower(), 'smart': not self.text.islower(),
'never': True, 'never': True,
@ -280,8 +298,10 @@ class AbstractSearch(QObject):
} }
return mapping[ignore_case] return mapping[ignore_case]
def search(self, text, *, ignore_case='never', reverse=False, def search(self, text: str, *,
result_cb=None): ignore_case: str = 'never',
reverse: bool = False,
result_cb: _Callback = None) -> None:
"""Find the given text on the page. """Find the given text on the page.
Args: Args:
@ -292,11 +312,11 @@ class AbstractSearch(QObject):
""" """
raise NotImplementedError raise NotImplementedError
def clear(self): def clear(self) -> None:
"""Clear the current search.""" """Clear the current search."""
raise NotImplementedError raise NotImplementedError
def prev_result(self, *, result_cb=None): def prev_result(self, *, result_cb: _Callback = None) -> None:
"""Go to the previous result of the current search. """Go to the previous result of the current search.
Args: Args:
@ -304,7 +324,7 @@ class AbstractSearch(QObject):
""" """
raise NotImplementedError raise NotImplementedError
def next_result(self, *, result_cb=None): def next_result(self, *, result_cb: _Callback = None) -> None:
"""Go to the next result of the current search. """Go to the next result of the current search.
Args: Args:
@ -322,7 +342,7 @@ class AbstractZoom(QObject):
_default_zoom_changed: Whether the zoom was changed from the default. _default_zoom_changed: Whether the zoom was changed from the default.
""" """
def __init__(self, tab, parent=None): def __init__(self, tab: 'AbstractTab', parent: QWidget = None) -> None:
super().__init__(parent) super().__init__(parent)
self._tab = tab self._tab = tab
self._widget = None self._widget = None
@ -339,21 +359,21 @@ class AbstractZoom(QObject):
# cfg.changed.disconnect, self.init_neighborlist)) # cfg.changed.disconnect, self.init_neighborlist))
@pyqtSlot(str) @pyqtSlot(str)
def _on_config_changed(self, option): def _on_config_changed(self, option: str) -> None:
if option in ['zoom.levels', 'zoom.default']: if option in ['zoom.levels', 'zoom.default']:
if not self._default_zoom_changed: if not self._default_zoom_changed:
factor = float(config.val.zoom.default) / 100 factor = float(config.val.zoom.default) / 100
self.set_factor(factor) self.set_factor(factor)
self._init_neighborlist() self._init_neighborlist()
def _init_neighborlist(self): def _init_neighborlist(self) -> None:
"""Initialize self._neighborlist.""" """Initialize self._neighborlist."""
levels = config.val.zoom.levels levels = config.val.zoom.levels
self._neighborlist = usertypes.NeighborList( self._neighborlist = usertypes.NeighborList(
levels, mode=usertypes.NeighborList.Modes.edge) levels, mode=usertypes.NeighborList.Modes.edge)
self._neighborlist.fuzzyval = config.val.zoom.default self._neighborlist.fuzzyval = config.val.zoom.default
def offset(self, offset): def offset(self, offset: int) -> None:
"""Increase/Decrease the zoom level by the given offset. """Increase/Decrease the zoom level by the given offset.
Args: Args:
@ -366,10 +386,10 @@ class AbstractZoom(QObject):
self.set_factor(float(level) / 100, fuzzyval=False) self.set_factor(float(level) / 100, fuzzyval=False)
return level return level
def _set_factor_internal(self, factor): def _set_factor_internal(self, factor: float) -> None:
raise NotImplementedError raise NotImplementedError
def set_factor(self, factor, *, fuzzyval=True): def set_factor(self, factor: float, *, fuzzyval: bool = True) -> None:
"""Zoom to a given zoom factor. """Zoom to a given zoom factor.
Args: Args:
@ -387,13 +407,13 @@ class AbstractZoom(QObject):
self._zoom_factor = factor self._zoom_factor = factor
self._set_factor_internal(factor) self._set_factor_internal(factor)
def factor(self): def factor(self) -> float:
return self._zoom_factor return self._zoom_factor
def set_default(self): def set_default(self) -> None:
self._set_factor_internal(float(config.val.zoom.default) / 100) self._set_factor_internal(float(config.val.zoom.default) / 100)
def set_current(self): def set_current(self) -> None:
self._set_factor_internal(self._zoom_factor) self._set_factor_internal(self._zoom_factor)
@ -410,7 +430,10 @@ class AbstractCaret(QObject):
selection_toggled = pyqtSignal(bool) selection_toggled = pyqtSignal(bool)
follow_selected_done = pyqtSignal() follow_selected_done = pyqtSignal()
def __init__(self, tab, mode_manager, parent=None): def __init__(self,
tab: 'AbstractTab',
mode_manager: modeman.ModeManager,
parent: QWidget = None) -> None:
super().__init__(parent) super().__init__(parent)
self._tab = tab self._tab = tab
self._widget = None self._widget = None
@ -418,74 +441,74 @@ class AbstractCaret(QObject):
mode_manager.entered.connect(self._on_mode_entered) mode_manager.entered.connect(self._on_mode_entered)
mode_manager.left.connect(self._on_mode_left) mode_manager.left.connect(self._on_mode_left)
def _on_mode_entered(self, mode): def _on_mode_entered(self, mode: usertypes.KeyMode) -> None:
raise NotImplementedError raise NotImplementedError
def _on_mode_left(self, mode): def _on_mode_left(self, mode: usertypes.KeyMode) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_next_line(self, count=1): def move_to_next_line(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_prev_line(self, count=1): def move_to_prev_line(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_next_char(self, count=1): def move_to_next_char(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_prev_char(self, count=1): def move_to_prev_char(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_end_of_word(self, count=1): def move_to_end_of_word(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_next_word(self, count=1): def move_to_next_word(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_prev_word(self, count=1): def move_to_prev_word(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_start_of_line(self): def move_to_start_of_line(self) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_end_of_line(self): def move_to_end_of_line(self) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_start_of_next_block(self, count=1): def move_to_start_of_next_block(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_start_of_prev_block(self, count=1): def move_to_start_of_prev_block(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_end_of_next_block(self, count=1): def move_to_end_of_next_block(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_end_of_prev_block(self, count=1): def move_to_end_of_prev_block(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_start_of_document(self): def move_to_start_of_document(self) -> None:
raise NotImplementedError raise NotImplementedError
def move_to_end_of_document(self): def move_to_end_of_document(self) -> None:
raise NotImplementedError raise NotImplementedError
def toggle_selection(self): def toggle_selection(self) -> None:
raise NotImplementedError raise NotImplementedError
def drop_selection(self): def drop_selection(self) -> None:
raise NotImplementedError raise NotImplementedError
def selection(self, callback): def selection(self, callback: typing.Callable[[str], None]) -> None:
raise NotImplementedError raise NotImplementedError
def _follow_enter(self, tab): def _follow_enter(self, tab: bool) -> None:
"""Follow a link by faking an enter press.""" """Follow a link by faking an enter press."""
if tab: if tab:
self._tab.key_press(Qt.Key_Enter, modifier=Qt.ControlModifier) self._tab.key_press(Qt.Key_Enter, modifier=Qt.ControlModifier)
else: else:
self._tab.key_press(Qt.Key_Enter) self._tab.key_press(Qt.Key_Enter)
def follow_selected(self, *, tab=False): def follow_selected(self, *, tab: bool = False) -> None:
raise NotImplementedError raise NotImplementedError
@ -495,69 +518,69 @@ class AbstractScroller(QObject):
perc_changed = pyqtSignal(int, int) perc_changed = pyqtSignal(int, int)
def __init__(self, tab, parent=None): def __init__(self, tab: 'AbstractTab', parent: QWidget = None):
super().__init__(parent) super().__init__(parent)
self._tab = tab self._tab = tab
self._widget = None self._widget = None # type: typing.Optional[QWebEngineView]
self.perc_changed.connect(self._log_scroll_pos_change) self.perc_changed.connect(self._log_scroll_pos_change)
@pyqtSlot() @pyqtSlot()
def _log_scroll_pos_change(self): def _log_scroll_pos_change(self) -> None:
log.webview.vdebug("Scroll position changed to {}".format( log.webview.vdebug( # type: ignore
self.pos_px())) "Scroll position changed to {}".format(self.pos_px()))
def _init_widget(self, widget): def _init_widget(self, widget: 'QWebEngineView') -> None:
self._widget = widget self._widget = widget
def pos_px(self): def pos_px(self) -> int:
raise NotImplementedError raise NotImplementedError
def pos_perc(self): def pos_perc(self) -> int:
raise NotImplementedError raise NotImplementedError
def to_perc(self, x=None, y=None): def to_perc(self, x: int = None, y: int = None) -> None:
raise NotImplementedError raise NotImplementedError
def to_point(self, point): def to_point(self, point: QPoint) -> None:
raise NotImplementedError raise NotImplementedError
def to_anchor(self, name): def to_anchor(self, name: str) -> None:
raise NotImplementedError raise NotImplementedError
def delta(self, x=0, y=0): def delta(self, x: int = 0, y: int = 0) -> None:
raise NotImplementedError raise NotImplementedError
def delta_page(self, x=0, y=0): def delta_page(self, x: float = 0, y: float = 0) -> None:
raise NotImplementedError raise NotImplementedError
def up(self, count=1): def up(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def down(self, count=1): def down(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def left(self, count=1): def left(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def right(self, count=1): def right(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def top(self): def top(self) -> None:
raise NotImplementedError raise NotImplementedError
def bottom(self): def bottom(self) -> None:
raise NotImplementedError raise NotImplementedError
def page_up(self, count=1): def page_up(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def page_down(self, count=1): def page_down(self, count: int = 1) -> None:
raise NotImplementedError raise NotImplementedError
def at_top(self): def at_top(self) -> bool:
raise NotImplementedError raise NotImplementedError
def at_bottom(self): def at_bottom(self) -> bool:
raise NotImplementedError raise NotImplementedError
@ -565,20 +588,20 @@ class AbstractHistory:
"""The history attribute of a AbstractTab.""" """The history attribute of a AbstractTab."""
def __init__(self, tab): def __init__(self, tab: 'AbstractTab') -> None:
self._tab = tab self._tab = tab
self._history = None self._history = None
def __len__(self): def __len__(self) -> int:
return len(self._history)
def __iter__(self):
return iter(self._history.items())
def current_idx(self):
raise NotImplementedError raise NotImplementedError
def back(self, count=1): def __iter__(self) -> typing.Iterable:
raise NotImplementedError
def current_idx(self) -> int:
raise NotImplementedError
def back(self, count: int = 1) -> None:
"""Go back in the tab's history.""" """Go back in the tab's history."""
idx = self.current_idx() - count idx = self.current_idx() - count
if idx >= 0: if idx >= 0:
@ -587,7 +610,7 @@ class AbstractHistory:
self._go_to_item(self._item_at(0)) self._go_to_item(self._item_at(0))
raise WebTabError("At beginning of history.") raise WebTabError("At beginning of history.")
def forward(self, count=1): def forward(self, count: int = 1) -> None:
"""Go forward in the tab's history.""" """Go forward in the tab's history."""
idx = self.current_idx() + count idx = self.current_idx() + count
if idx < len(self): if idx < len(self):
@ -596,27 +619,27 @@ class AbstractHistory:
self._go_to_item(self._item_at(len(self) - 1)) self._go_to_item(self._item_at(len(self) - 1))
raise WebTabError("At end of history.") raise WebTabError("At end of history.")
def can_go_back(self): def can_go_back(self) -> bool:
raise NotImplementedError raise NotImplementedError
def can_go_forward(self): def can_go_forward(self) -> bool:
raise NotImplementedError raise NotImplementedError
def _item_at(self, i): def _item_at(self, i: int) -> typing.Any:
raise NotImplementedError raise NotImplementedError
def _go_to_item(self, item): def _go_to_item(self, item: typing.Any) -> None:
raise NotImplementedError raise NotImplementedError
def serialize(self): def serialize(self) -> bytes:
"""Serialize into an opaque format understood by self.deserialize.""" """Serialize into an opaque format understood by self.deserialize."""
raise NotImplementedError raise NotImplementedError
def deserialize(self, data): def deserialize(self, data: bytes) -> None:
"""Serialize from a format produced by self.serialize.""" """Deserialize from a format produced by self.serialize."""
raise NotImplementedError raise NotImplementedError
def load_items(self, items): def load_items(self, items: typing.Sequence) -> None:
"""Deserialize from a list of WebHistoryItems.""" """Deserialize from a list of WebHistoryItems."""
raise NotImplementedError raise NotImplementedError
@ -625,11 +648,18 @@ class AbstractElements:
"""Finding and handling of elements on the page.""" """Finding and handling of elements on the page."""
def __init__(self, tab): _MultiCallback = typing.Callable[
[typing.Sequence[webelem.AbstractWebElement]], None]
_SingleCallback = typing.Callable[
[typing.Optional[webelem.AbstractWebElement]], None]
def __init__(self, tab: 'AbstractTab') -> None:
self._widget = None self._widget = None
self._tab = tab self._tab = tab
def find_css(self, selector, callback, *, only_visible=False): def find_css(self, selector: str,
callback: _MultiCallback, *,
only_visible: bool = False) -> None:
"""Find all HTML elements matching a given selector async. """Find all HTML elements matching a given selector async.
If there's an error, the callback is called with a webelem.Error If there's an error, the callback is called with a webelem.Error
@ -642,7 +672,7 @@ class AbstractElements:
""" """
raise NotImplementedError raise NotImplementedError
def find_id(self, elem_id, callback): def find_id(self, elem_id: str, callback: _SingleCallback) -> None:
"""Find the HTML element with the given ID async. """Find the HTML element with the given ID async.
Args: Args:
@ -651,7 +681,7 @@ class AbstractElements:
""" """
raise NotImplementedError raise NotImplementedError
def find_focused(self, callback): def find_focused(self, callback: _SingleCallback) -> None:
"""Find the focused element on the page async. """Find the focused element on the page async.
Args: Args:
@ -660,7 +690,7 @@ class AbstractElements:
""" """
raise NotImplementedError raise NotImplementedError
def find_at_pos(self, pos, callback): def find_at_pos(self, pos: QPoint, callback: _SingleCallback) -> None:
"""Find the element at the given position async. """Find the element at the given position async.
This is also called "hit test" elsewhere. This is also called "hit test" elsewhere.
@ -680,9 +710,9 @@ class AbstractAudio(QObject):
muted_changed = pyqtSignal(bool) muted_changed = pyqtSignal(bool)
recently_audible_changed = pyqtSignal(bool) recently_audible_changed = pyqtSignal(bool)
def __init__(self, tab, parent=None): def __init__(self, tab: 'AbstractTab', parent: QWidget = None) -> None:
super().__init__(parent) super().__init__(parent)
self._widget = None self._widget = None # type: typing.Optional[QWebEngineView]
self._tab = tab self._tab = tab
def set_muted(self, muted: bool, override: bool = False) -> None: def set_muted(self, muted: bool, override: bool = False) -> None:
@ -695,14 +725,14 @@ class AbstractAudio(QObject):
""" """
raise NotImplementedError raise NotImplementedError
def is_muted(self): def is_muted(self) -> bool:
"""Whether this tab is muted.""" """Whether this tab is muted."""
raise NotImplementedError raise NotImplementedError
def toggle_muted(self, *, override: bool = False) -> None: def toggle_muted(self, *, override: bool = False) -> None:
self.set_muted(not self.is_muted(), override=override) self.set_muted(not self.is_muted(), override=override)
def is_recently_audible(self): def is_recently_audible(self) -> bool:
"""Whether this tab has had audio playing recently.""" """Whether this tab has had audio playing recently."""
raise NotImplementedError raise NotImplementedError
@ -758,7 +788,11 @@ class AbstractTab(QWidget):
renderer_process_terminated = pyqtSignal(TerminationStatus, int) renderer_process_terminated = pyqtSignal(TerminationStatus, int)
predicted_navigation = pyqtSignal(QUrl) predicted_navigation = pyqtSignal(QUrl)
def __init__(self, *, win_id, mode_manager, private, parent=None): def __init__(self, *,
win_id: int,
mode_manager: modeman.ModeManager,
private: bool,
parent: QWidget = None) -> None:
self.private = private self.private = private
self.win_id = win_id self.win_id = win_id
self.tab_id = next(tab_id_gen) self.tab_id = next(tab_id_gen)
@ -772,7 +806,7 @@ class AbstractTab(QWidget):
self.data = TabData() self.data = TabData()
self._layout = miscwidgets.WrapperLayout(self) self._layout = miscwidgets.WrapperLayout(self)
self._widget = None self._widget = None # type: typing.Optional[QWebEngineView]
self._progress = 0 self._progress = 0
self._has_ssl_errors = False self._has_ssl_errors = False
self._mode_manager = mode_manager self._mode_manager = mode_manager
@ -789,7 +823,7 @@ class AbstractTab(QWidget):
self.predicted_navigation.connect(self._on_predicted_navigation) self.predicted_navigation.connect(self._on_predicted_navigation)
def _set_widget(self, widget): def _set_widget(self, widget: 'QWebEngineView') -> None:
# pylint: disable=protected-access # pylint: disable=protected-access
self._widget = widget self._widget = widget
self._layout.wrap(self, widget) self._layout.wrap(self, widget)
@ -807,10 +841,10 @@ class AbstractTab(QWidget):
self._install_event_filter() self._install_event_filter()
self.zoom.set_default() self.zoom.set_default()
def _install_event_filter(self): def _install_event_filter(self) -> None:
raise NotImplementedError raise NotImplementedError
def _set_load_status(self, val): def _set_load_status(self, val: usertypes.LoadStatus) -> None:
"""Setter for load_status.""" """Setter for load_status."""
if not isinstance(val, usertypes.LoadStatus): if not isinstance(val, usertypes.LoadStatus):
raise TypeError("Type {} is no LoadStatus member!".format(val)) raise TypeError("Type {} is no LoadStatus member!".format(val))
@ -818,11 +852,11 @@ class AbstractTab(QWidget):
self._load_status = val self._load_status = val
self.load_status_changed.emit(val.name) self.load_status_changed.emit(val.name)
def event_target(self): def event_target(self) -> QWidget:
"""Return the widget events should be sent to.""" """Return the widget events should be sent to."""
raise NotImplementedError raise NotImplementedError
def send_event(self, evt): def send_event(self, evt: QEvent) -> None:
"""Send the given event to the underlying widget. """Send the given event to the underlying widget.
The event will be sent via QApplication.postEvent. The event will be sent via QApplication.postEvent.
@ -844,7 +878,7 @@ class AbstractTab(QWidget):
QApplication.postEvent(recipient, evt) QApplication.postEvent(recipient, evt)
@pyqtSlot(QUrl) @pyqtSlot(QUrl)
def _on_predicted_navigation(self, url): def _on_predicted_navigation(self, url: QUrl) -> None:
"""Adjust the title if we are going to visit a URL soon.""" """Adjust the title if we are going to visit a URL soon."""
qtutils.ensure_valid(url) qtutils.ensure_valid(url)
url_string = url.toDisplayString() url_string = url.toDisplayString()
@ -852,14 +886,14 @@ class AbstractTab(QWidget):
self.title_changed.emit(url_string) self.title_changed.emit(url_string)
@pyqtSlot(QUrl) @pyqtSlot(QUrl)
def _on_url_changed(self, url): def _on_url_changed(self, url: QUrl) -> None:
"""Update title when URL has changed and no title is available.""" """Update title when URL has changed and no title is available."""
if url.isValid() and not self.title(): if url.isValid() and not self.title():
self.title_changed.emit(url.toDisplayString()) self.title_changed.emit(url.toDisplayString())
self.url_changed.emit(url) self.url_changed.emit(url)
@pyqtSlot() @pyqtSlot()
def _on_load_started(self): def _on_load_started(self) -> None:
self._progress = 0 self._progress = 0
self._has_ssl_errors = False self._has_ssl_errors = False
self.data.viewing_source = False self.data.viewing_source = False
@ -867,7 +901,10 @@ class AbstractTab(QWidget):
self.load_started.emit() self.load_started.emit()
@pyqtSlot(usertypes.NavigationRequest) @pyqtSlot(usertypes.NavigationRequest)
def _on_navigation_request(self, navigation): def _on_navigation_request(
self,
navigation: usertypes.NavigationRequest
) -> None:
"""Handle common acceptNavigationRequest code.""" """Handle common acceptNavigationRequest code."""
url = utils.elide(navigation.url.toDisplayString(), 100) url = utils.elide(navigation.url.toDisplayString(), 100)
log.webview.debug("navigation request: url {}, type {}, is_main_frame " log.webview.debug("navigation request: url {}, type {}, is_main_frame "
@ -891,7 +928,7 @@ class AbstractTab(QWidget):
navigation.url.errorString())) navigation.url.errorString()))
navigation.accepted = False navigation.accepted = False
def handle_auto_insert_mode(self, ok): def handle_auto_insert_mode(self, ok: bool) -> None:
"""Handle `input.insert_mode.auto_load` after loading finished.""" """Handle `input.insert_mode.auto_load` after loading finished."""
if not config.val.input.insert_mode.auto_load or not ok: if not config.val.input.insert_mode.auto_load or not ok:
return return
@ -900,7 +937,7 @@ class AbstractTab(QWidget):
if cur_mode == usertypes.KeyMode.insert: if cur_mode == usertypes.KeyMode.insert:
return return
def _auto_insert_mode_cb(elem): def _auto_insert_mode_cb(elem: webelem.AbstractWebElement) -> None:
"""Called from JS after finding the focused element.""" """Called from JS after finding the focused element."""
if elem is None: if elem is None:
log.webview.debug("No focused element!") log.webview.debug("No focused element!")
@ -912,7 +949,8 @@ class AbstractTab(QWidget):
self.elements.find_focused(_auto_insert_mode_cb) self.elements.find_focused(_auto_insert_mode_cb)
@pyqtSlot(bool) @pyqtSlot(bool)
def _on_load_finished(self, ok): def _on_load_finished(self, ok: bool) -> None:
assert self._widget is not None
if sip.isdeleted(self._widget): if sip.isdeleted(self._widget):
# https://github.com/qutebrowser/qutebrowser/issues/3498 # https://github.com/qutebrowser/qutebrowser/issues/3498
return return
@ -943,46 +981,50 @@ class AbstractTab(QWidget):
self.zoom.set_current() self.zoom.set_current()
@pyqtSlot() @pyqtSlot()
def _on_history_trigger(self): def _on_history_trigger(self) -> None:
"""Emit add_history_item when triggered by backend-specific signal.""" """Emit add_history_item when triggered by backend-specific signal."""
raise NotImplementedError raise NotImplementedError
@pyqtSlot(int) @pyqtSlot(int)
def _on_load_progress(self, perc): def _on_load_progress(self, perc: int) -> None:
self._progress = perc self._progress = perc
self.load_progress.emit(perc) self.load_progress.emit(perc)
def url(self, requested=False): def url(self, requested: bool = False) -> QUrl:
raise NotImplementedError raise NotImplementedError
def progress(self): def progress(self) -> int:
return self._progress return self._progress
def load_status(self): def load_status(self) -> usertypes.LoadStatus:
return self._load_status return self._load_status
def _openurl_prepare(self, url, *, predict=True): def _openurl_prepare(self, url: QUrl, *, predict: bool = True) -> None:
qtutils.ensure_valid(url) qtutils.ensure_valid(url)
if predict: if predict:
self.predicted_navigation.emit(url) self.predicted_navigation.emit(url)
def openurl(self, url, *, predict=True): def openurl(self, url: QUrl, *, predict: bool = True) -> None:
raise NotImplementedError raise NotImplementedError
def reload(self, *, force=False): def reload(self, *, force: bool = False) -> None:
raise NotImplementedError raise NotImplementedError
def stop(self): def stop(self) -> None:
raise NotImplementedError raise NotImplementedError
def clear_ssl_errors(self): def clear_ssl_errors(self) -> None:
raise NotImplementedError raise NotImplementedError
def key_press(self, key, modifier=Qt.NoModifier): def key_press(self,
key: Qt.Key,
modifier: Qt.KeyboardModifier = Qt.NoModifier) -> None:
"""Send a fake key event to this tab.""" """Send a fake key event to this tab."""
raise NotImplementedError raise NotImplementedError
def dump_async(self, callback, *, plain=False): def dump_async(self,
callback: typing.Callable[[str], None], *,
plain: bool = False) -> None:
"""Dump the current page's html asynchronously. """Dump the current page's html asynchronously.
The given callback will be called with the result when dumping is The given callback will be called with the result when dumping is
@ -990,7 +1032,12 @@ class AbstractTab(QWidget):
""" """
raise NotImplementedError raise NotImplementedError
def run_js_async(self, code, callback=None, *, world=None): def run_js_async(
self,
code: str,
callback: typing.Callable[[typing.Any], None] = None, *,
world: typing.Union[usertypes.JsWorld, int] = None
) -> None:
"""Run javascript async. """Run javascript async.
The given callback will be called with the result when running JS is The given callback will be called with the result when running JS is
@ -1004,19 +1051,19 @@ class AbstractTab(QWidget):
""" """
raise NotImplementedError raise NotImplementedError
def shutdown(self): def shutdown(self) -> None:
raise NotImplementedError raise NotImplementedError
def title(self): def title(self) -> str:
raise NotImplementedError raise NotImplementedError
def icon(self): def icon(self) -> None:
raise NotImplementedError raise NotImplementedError
def set_html(self, html, base_url=QUrl()): def set_html(self, html: str, base_url: QUrl = QUrl()) -> None:
raise NotImplementedError raise NotImplementedError
def networkaccessmanager(self): def networkaccessmanager(self) -> typing.Optional[QNetworkAccessManager]:
"""Get the QNetworkAccessManager for this tab. """Get the QNetworkAccessManager for this tab.
This is only implemented for QtWebKit. This is only implemented for QtWebKit.
@ -1024,7 +1071,7 @@ class AbstractTab(QWidget):
""" """
raise NotImplementedError raise NotImplementedError
def user_agent(self): def user_agent(self) -> typing.Optional[str]:
"""Get the user agent for this tab. """Get the user agent for this tab.
This is only implemented for QtWebKit. This is only implemented for QtWebKit.
@ -1032,13 +1079,16 @@ class AbstractTab(QWidget):
""" """
raise NotImplementedError raise NotImplementedError
def __repr__(self): def __repr__(self) -> str:
try: try:
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode), qurl = self.url()
100) url = qurl.toDisplayString(QUrl.EncodeUnicode) # type: ignore
except (AttributeError, RuntimeError) as exc: except (AttributeError, RuntimeError) as exc:
url = '<{}>'.format(exc.__class__.__name__) url = '<{}>'.format(exc.__class__.__name__)
else:
url = utils.elide(url, 100)
return utils.get_repr(self, tab_id=self.tab_id, url=url) return utils.get_repr(self, tab_id=self.tab_id, url=url)
def is_deleted(self): def is_deleted(self) -> bool:
assert self._widget is not None
return sip.isdeleted(self._widget) return sip.isdeleted(self._widget)

View File

@ -522,6 +522,12 @@ class WebEngineHistory(browsertab.AbstractHistory):
"""QtWebEngine implementations related to page history.""" """QtWebEngine implementations related to page history."""
def __len__(self):
return len(self._history)
def __iter__(self):
return iter(self._history.items())
def current_idx(self): def current_idx(self):
return self._history.currentItemIndex() return self._history.currentItemIndex()
@ -551,7 +557,7 @@ class WebEngineHistory(browsertab.AbstractHistory):
return qtutils.serialize(self._history) return qtutils.serialize(self._history)
def deserialize(self, data): def deserialize(self, data):
return qtutils.deserialize(data, self._history) qtutils.deserialize(data, self._history)
def load_items(self, items): def load_items(self, items):
if items: if items:
@ -672,6 +678,7 @@ class WebEngineAudio(browsertab.AbstractAudio):
def set_muted(self, muted: bool, override: bool = False) -> None: def set_muted(self, muted: bool, override: bool = False) -> None:
self._overridden = override self._overridden = override
assert self._widget is not None
page = self._widget.page() page = self._widget.page()
page.setAudioMuted(muted) page.setAudioMuted(muted)

View File

@ -513,6 +513,12 @@ class WebKitHistory(browsertab.AbstractHistory):
"""QtWebKit implementations related to page history.""" """QtWebKit implementations related to page history."""
def __len__(self):
return len(self._history)
def __iter__(self):
return iter(self._history.items())
def current_idx(self): def current_idx(self):
return self._history.currentItemIndex() return self._history.currentItemIndex()
@ -533,7 +539,7 @@ class WebKitHistory(browsertab.AbstractHistory):
return qtutils.serialize(self._history) return qtutils.serialize(self._history)
def deserialize(self, data): def deserialize(self, data):
return qtutils.deserialize(data, self._history) qtutils.deserialize(data, self._history)
def load_items(self, items): def load_items(self, items):
if items: if items: