webenginetab: Move permissions to separate object

This commit is contained in:
Florian Bruhin 2018-06-11 17:30:09 +02:00
parent 4186577928
commit b3749df009

View File

@ -27,7 +27,7 @@ import html as html_utils
import sip import sip
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF, from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF,
QUrl, QTimer) QUrl, QTimer, QObject)
from PyQt5.QtGui import QKeyEvent, QIcon from PyQt5.QtGui import QKeyEvent, QIcon
from PyQt5.QtNetwork import QAuthenticator from PyQt5.QtNetwork import QAuthenticator
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
@ -643,6 +643,126 @@ class WebEngineAudio(browsertab.AbstractAudio):
return page.recentlyAudible() return page.recentlyAudible()
class _WebEnginePermissions(QObject):
"""Handling of various permission-related signals."""
_abort_questions = pyqtSignal()
def __init__(self, tab, parent=None):
super().__init__(parent)
self._tab = tab
self._widget = None
def connect_signals(self):
page = self._widget.page()
page.fullScreenRequested.connect(
self._on_fullscreen_requested)
page.featurePermissionRequested.connect(
self._on_feature_permission_requested)
try:
page.quotaRequested.connect(
self._on_quota_requested)
page.registerProtocolHandlerRequested.connect(
self._on_register_protocol_handler_requested)
except AttributeError:
# Added in Qt 5.11
pass
self._tab.shutting_down.connect(self._abort_questions)
self._tab.load_started.connect(self._abort_questions)
@pyqtSlot('QWebEngineFullScreenRequest')
def _on_fullscreen_requested(self, request):
request.accept()
on = request.toggleOn()
self._tab.data.fullscreen = on
self._tab.fullscreen_requested.emit(on)
if on:
notification = miscwidgets.FullscreenNotification(self)
notification.show()
notification.set_timeout(3000)
@pyqtSlot(QUrl, 'QWebEnginePage::Feature')
def _on_feature_permission_requested(self, url, feature):
"""Ask the user for approval for geolocation/media/etc.."""
options = {
QWebEnginePage.Geolocation: 'content.geolocation',
QWebEnginePage.MediaAudioCapture: 'content.media_capture',
QWebEnginePage.MediaVideoCapture: 'content.media_capture',
QWebEnginePage.MediaAudioVideoCapture: 'content.media_capture',
}
messages = {
QWebEnginePage.Geolocation: 'access your location',
QWebEnginePage.MediaAudioCapture: 'record audio',
QWebEnginePage.MediaVideoCapture: 'record video',
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
}
assert options.keys() == messages.keys()
page = self._widget.page()
if feature not in options:
log.webview.error("Unhandled feature permission {}".format(
debug.qenum_key(QWebEnginePage, feature)))
page.setFeaturePermission(url, feature,
QWebEnginePage.PermissionDeniedByUser)
return
yes_action = functools.partial(
page.setFeaturePermission, url, feature,
QWebEnginePage.PermissionGrantedByUser)
no_action = functools.partial(
page.setFeaturePermission, url, feature,
QWebEnginePage.PermissionDeniedByUser)
question = shared.feature_permission(
url=url, option=options[feature], msg=messages[feature],
yes_action=yes_action, no_action=no_action,
abort_on=[self._abort_questions])
if question is not None:
page.featurePermissionRequestCanceled.connect(
functools.partial(self._on_feature_permission_cancelled,
question, url, feature))
def _on_feature_permission_cancelled(self, question, url, feature,
cancelled_url, cancelled_feature):
"""Slot invoked when a feature permission request was cancelled.
To be used with functools.partial.
"""
if url == cancelled_url and feature == cancelled_feature:
try:
question.abort()
except RuntimeError:
# The question could already be deleted, e.g. because it was
# aborted after a loadStarted signal.
pass
@pyqtSlot('QWebEngineQuotaRequest')
def _on_quota_requested(self, request):
size = utils.format_size(request.requestedSize())
shared.feature_permission(
url=request.origin(),
option='content.persistent_storage',
msg='use {} of persistent storage'.format(size),
yes_action=request.accept, no_action=request.reject,
abort_on=[self._abort_questions],
blocking=True)
@pyqtSlot('QWebEngineRegisterProtocolHandlerRequest')
def _on_register_protocol_handler_requested(self, request):
shared.feature_permission(
url=request.origin(),
option='content.register_protocol_handler',
msg='open all {} links'.format(request.scheme()),
yes_action=request.accept, no_action=request.reject,
abort_on=[self._abort_questions],
blocking=True)
class WebEngineTab(browsertab.AbstractTab): class WebEngineTab(browsertab.AbstractTab):
"""A QtWebEngine tab in the browser. """A QtWebEngine tab in the browser.
@ -670,6 +790,7 @@ class WebEngineTab(browsertab.AbstractTab):
self.elements = WebEngineElements(tab=self) self.elements = WebEngineElements(tab=self)
self.action = WebEngineAction(tab=self) self.action = WebEngineAction(tab=self)
self.audio = WebEngineAudio() self.audio = WebEngineAudio()
self._permissions = _WebEnginePermissions(tab=self)
# We're assigning settings in _set_widget # We're assigning settings in _set_widget
self.settings = webenginesettings.WebEngineSettings(settings=None) self.settings = webenginesettings.WebEngineSettings(settings=None)
self._set_widget(widget) self._set_widget(widget)
@ -681,6 +802,11 @@ class WebEngineTab(browsertab.AbstractTab):
config.instance.changed.connect(self._on_config_changed) config.instance.changed.connect(self._on_config_changed)
self._init_js() self._init_js()
def _set_widget(self, widget):
# pylint: disable=protected-access
super()._set_widget(widget)
self._permissions._widget = widget
@pyqtSlot(str) @pyqtSlot(str)
def _on_config_changed(self, option): def _on_config_changed(self, option):
if option in ['scrolling.bar', 'content.user_stylesheets']: if option in ['scrolling.bar', 'content.user_stylesheets']:
@ -977,96 +1103,6 @@ class WebEngineTab(browsertab.AbstractTab):
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html # https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html
self._show_error_page(url, "Authentication required") self._show_error_page(url, "Authentication required")
@pyqtSlot('QWebEngineFullScreenRequest')
def _on_fullscreen_requested(self, request):
request.accept()
on = request.toggleOn()
self.data.fullscreen = on
self.fullscreen_requested.emit(on)
if on:
notification = miscwidgets.FullscreenNotification(self)
notification.show()
notification.set_timeout(3000)
@pyqtSlot(QUrl, 'QWebEnginePage::Feature')
def _on_feature_permission_requested(self, url, feature):
"""Ask the user for approval for geolocation/media/etc.."""
options = {
QWebEnginePage.Geolocation: 'content.geolocation',
QWebEnginePage.MediaAudioCapture: 'content.media_capture',
QWebEnginePage.MediaVideoCapture: 'content.media_capture',
QWebEnginePage.MediaAudioVideoCapture: 'content.media_capture',
}
messages = {
QWebEnginePage.Geolocation: 'access your location',
QWebEnginePage.MediaAudioCapture: 'record audio',
QWebEnginePage.MediaVideoCapture: 'record video',
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
}
assert options.keys() == messages.keys()
page = self._widget.page()
if feature not in options:
log.webview.error("Unhandled feature permission {}".format(
debug.qenum_key(QWebEnginePage, feature)))
page.setFeaturePermission(url, feature,
QWebEnginePage.PermissionDeniedByUser)
return
yes_action = functools.partial(
page.setFeaturePermission, url, feature,
QWebEnginePage.PermissionGrantedByUser)
no_action = functools.partial(
page.setFeaturePermission, url, feature,
QWebEnginePage.PermissionDeniedByUser)
question = shared.feature_permission(
url=url, option=options[feature], msg=messages[feature],
yes_action=yes_action, no_action=no_action,
abort_on=[self.shutting_down, self.load_started])
if question is not None:
page.featurePermissionRequestCanceled.connect(
functools.partial(self._on_feature_permission_cancelled,
question, url, feature))
def _on_feature_permission_cancelled(self, question, url, feature,
cancelled_url, cancelled_feature):
"""Slot invoked when a feature permission request was cancelled.
To be used with functools.partial.
"""
if url == cancelled_url and feature == cancelled_feature:
try:
question.abort()
except RuntimeError:
# The question could already be deleted, e.g. because it was
# aborted after a loadStarted signal.
pass
@pyqtSlot('QWebEngineQuotaRequest')
def _on_quota_requested(self, request):
size = utils.format_size(request.requestedSize())
shared.feature_permission(
url=request.origin(),
option='content.persistent_storage',
msg='use {} of persistent storage'.format(size),
yes_action=request.accept, no_action=request.reject,
abort_on=[self.shutting_down, self.load_started],
blocking=True)
@pyqtSlot('QWebEngineRegisterProtocolHandlerRequest')
def _on_register_protocol_handler_requested(self, request):
shared.feature_permission(
url=request.origin(),
option='content.register_protocol_handler',
msg='open all {} links'.format(request.scheme()),
yes_action=request.accept, no_action=request.reject,
abort_on=[self.shutting_down, self.load_started],
blocking=True)
@pyqtSlot() @pyqtSlot()
def _on_load_started(self): def _on_load_started(self):
"""Clear search when a new load is started if needed.""" """Clear search when a new load is started if needed."""
@ -1220,18 +1256,8 @@ class WebEngineTab(browsertab.AbstractTab):
page.authenticationRequired.connect(self._on_authentication_required) page.authenticationRequired.connect(self._on_authentication_required)
page.proxyAuthenticationRequired.connect( page.proxyAuthenticationRequired.connect(
self._on_proxy_authentication_required) self._on_proxy_authentication_required)
page.fullScreenRequested.connect(self._on_fullscreen_requested)
page.contentsSizeChanged.connect(self.contents_size_changed) page.contentsSizeChanged.connect(self.contents_size_changed)
page.navigation_request.connect(self._on_navigation_request) page.navigation_request.connect(self._on_navigation_request)
page.featurePermissionRequested.connect(
self._on_feature_permission_requested)
try:
page.quotaRequested.connect(self._on_quota_requested)
page.registerProtocolHandlerRequested.connect(
self._on_register_protocol_handler_requested)
except AttributeError:
# Added in Qt 5.11
pass
view.titleChanged.connect(self.title_changed) view.titleChanged.connect(self.title_changed)
view.urlChanged.connect(self._on_url_changed) view.urlChanged.connect(self._on_url_changed)
@ -1253,7 +1279,10 @@ class WebEngineTab(browsertab.AbstractTab):
page.loadFinished.connect(self._on_load_finished) page.loadFinished.connect(self._on_load_finished)
self.predicted_navigation.connect(self._on_predicted_navigation) self.predicted_navigation.connect(self._on_predicted_navigation)
self.audio._connect_signals() # pylint: disable=protected-access
# pylint: disable=protected-access
self.audio._connect_signals()
self._permissions.connect_signals()
def event_target(self): def event_target(self):
return self._widget.render_widget() return self._widget.render_widget()