webenginetab: Move permissions to separate object
This commit is contained in:
parent
4186577928
commit
b3749df009
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user