Handle feature permissions with QtWebEngine
This commit is contained in:
parent
8f55725555
commit
bbcbb24cb5
@ -160,6 +160,8 @@
|
||||
|<<content-hyperlink-auditing,hyperlink-auditing>>|Enable or disable hyperlink auditing (<a ping>).
|
||||
|<<content-geolocation,geolocation>>|Allow websites to request geolocations.
|
||||
|<<content-notifications,notifications>>|Allow websites to show notifications.
|
||||
|<<content-media-capture,media-capture>>|Allow websites to record audio/video.
|
||||
|<<content-mouse-lock,mouse-lock>>|Allow websites to lock the mouse pointer.
|
||||
|<<content-javascript-can-open-windows-automatically,javascript-can-open-windows-automatically>>|Whether JavaScript programs can open new windows without user interaction.
|
||||
|<<content-javascript-can-close-windows,javascript-can-close-windows>>|Whether JavaScript programs can close windows.
|
||||
|<<content-javascript-can-access-clipboard,javascript-can-access-clipboard>>|Whether JavaScript programs can read or write to the clipboard.
|
||||
@ -1450,6 +1452,34 @@ Valid values:
|
||||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
[[content-media-capture]]
|
||||
=== media-capture
|
||||
Allow websites to record audio/video.
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
* +ask+
|
||||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content-mouse-lock]]
|
||||
=== mouse-lock
|
||||
Allow websites to lock the mouse pointer.
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
* +ask+
|
||||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content-javascript-can-open-windows-automatically]]
|
||||
=== javascript-can-open-windows-automatically
|
||||
Whether JavaScript programs can open new windows without user interaction.
|
||||
|
@ -157,3 +157,37 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
else:
|
||||
raise ValueError("Invalid ssl_strict value {!r}".format(ssl_strict))
|
||||
raise AssertionError("Not reached")
|
||||
|
||||
|
||||
def feature_permission(url, option, msg, yes_action, no_action, abort_on):
|
||||
"""Handle a feature permission request.
|
||||
|
||||
Args:
|
||||
url: The URL the request was done for.
|
||||
option: A (section, option) tuple for the option to check.
|
||||
msg: A string like "show notifications"
|
||||
yes_action: A callable to call if the request was approved
|
||||
no_action: A callable to call if the request was denied
|
||||
abort_on: A list of signals which interrupt the question.
|
||||
|
||||
Return:
|
||||
The Question object if a question was asked, None otherwise.
|
||||
"""
|
||||
config_val = config.get(*option)
|
||||
if config_val == 'ask':
|
||||
if url.isValid():
|
||||
text = "Allow the website at <b>{}</b> to {}?".format(
|
||||
html.escape(url.toDisplayString()), msg)
|
||||
else:
|
||||
text = "Allow the website to {}?".format(msg)
|
||||
|
||||
return message.confirm_async(
|
||||
yes_action=yes_action, no_action=no_action,
|
||||
cancel_action=no_action, abort_on=abort_on,
|
||||
title='Permission request', text=text)
|
||||
elif config_val:
|
||||
yes_action()
|
||||
return None
|
||||
else:
|
||||
no_action()
|
||||
return None
|
||||
|
@ -20,8 +20,9 @@
|
||||
"""The main browser widget for QtWebEngine."""
|
||||
|
||||
import os
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl
|
||||
# pylint: disable=no-name-in-module,import-error,useless-suppression
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||
# pylint: enable=no-name-in-module,import-error,useless-suppression
|
||||
@ -121,6 +122,55 @@ class WebEnginePage(QWebEnginePage):
|
||||
super().__init__(parent)
|
||||
self._tabdata = tabdata
|
||||
self._is_shutting_down = False
|
||||
self.featurePermissionRequested.connect(
|
||||
self._on_feature_permission_requested)
|
||||
|
||||
@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'),
|
||||
QWebEnginePage.MouseLock: ('content', 'mouse-lock'),
|
||||
}
|
||||
messages = {
|
||||
QWebEnginePage.Geolocation: 'access your location',
|
||||
QWebEnginePage.MediaAudioCapture: 'record audio',
|
||||
QWebEnginePage.MediaVideoCapture: 'record video',
|
||||
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
|
||||
}
|
||||
yes_action = functools.partial(
|
||||
self.setFeaturePermission, url, feature,
|
||||
QWebEnginePage.PermissionGrantedByUser)
|
||||
no_action = functools.partial(
|
||||
self.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.loadStarted])
|
||||
|
||||
if question is not None:
|
||||
self.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
|
||||
|
||||
def shutdown(self):
|
||||
self._is_shutting_down = True
|
||||
|
@ -82,7 +82,7 @@ class BrowserPage(QWebPage):
|
||||
self.unsupportedContent.connect(self.on_unsupported_content)
|
||||
self.loadStarted.connect(self.on_load_started)
|
||||
self.featurePermissionRequested.connect(
|
||||
self.on_feature_permission_requested)
|
||||
self._on_feature_permission_requested)
|
||||
self.saveFrameStateRequested.connect(
|
||||
self.on_save_frame_state_requested)
|
||||
self.restoreFrameStateRequested.connect(
|
||||
@ -289,7 +289,7 @@ class BrowserPage(QWebPage):
|
||||
self.error_occurred = False
|
||||
|
||||
@pyqtSlot('QWebFrame*', 'QWebPage::Feature')
|
||||
def on_feature_permission_requested(self, frame, feature):
|
||||
def _on_feature_permission_requested(self, frame, feature):
|
||||
"""Ask the user for approval for geolocation/notifications."""
|
||||
if not isinstance(frame, QWebFrame): # pragma: no cover
|
||||
# This makes no sense whatsoever, but someone reported this being
|
||||
@ -302,46 +302,30 @@ class BrowserPage(QWebPage):
|
||||
QWebPage.Notifications: ('content', 'notifications'),
|
||||
QWebPage.Geolocation: ('content', 'geolocation'),
|
||||
}
|
||||
config_val = config.get(*options[feature])
|
||||
if config_val == 'ask':
|
||||
msgs = {
|
||||
QWebPage.Notifications: 'show notifications',
|
||||
QWebPage.Geolocation: 'access your location',
|
||||
}
|
||||
messages = {
|
||||
QWebPage.Notifications: 'show notifications',
|
||||
QWebPage.Geolocation: 'access your location',
|
||||
}
|
||||
yes_action = functools.partial(
|
||||
self.setFeaturePermission, frame, feature,
|
||||
QWebPage.PermissionGrantedByUser)
|
||||
no_action = functools.partial(
|
||||
self.setFeaturePermission, frame, feature,
|
||||
QWebPage.PermissionDeniedByUser)
|
||||
|
||||
host = frame.url().host()
|
||||
if host:
|
||||
text = "Allow the website at <b>{}</b> to {}?".format(
|
||||
html.escape(frame.url().toDisplayString()), msgs[feature])
|
||||
else:
|
||||
text = "Allow the website to {}?".format(msgs[feature])
|
||||
question = shared.feature_permission(
|
||||
url=frame.url(),
|
||||
option=options[feature], msg=messages[feature],
|
||||
yes_action=yes_action, no_action=no_action,
|
||||
abort_on=[self.shutting_down, self.loadStarted])
|
||||
|
||||
yes_action = functools.partial(
|
||||
self.setFeaturePermission, frame, feature,
|
||||
QWebPage.PermissionGrantedByUser)
|
||||
no_action = functools.partial(
|
||||
self.setFeaturePermission, frame, feature,
|
||||
QWebPage.PermissionDeniedByUser)
|
||||
|
||||
question = message.confirm_async(yes_action=yes_action,
|
||||
no_action=no_action,
|
||||
cancel_action=no_action,
|
||||
abort_on=[self.shutting_down,
|
||||
self.loadStarted],
|
||||
title='Permission request',
|
||||
text=text)
|
||||
if question is not None:
|
||||
self.featurePermissionRequestCanceled.connect(
|
||||
functools.partial(self.on_feature_permission_cancelled,
|
||||
functools.partial(self._on_feature_permission_cancelled,
|
||||
question, frame, feature))
|
||||
elif config_val:
|
||||
self.setFeaturePermission(frame, feature,
|
||||
QWebPage.PermissionGrantedByUser)
|
||||
else:
|
||||
self.setFeaturePermission(frame, feature,
|
||||
QWebPage.PermissionDeniedByUser)
|
||||
|
||||
def on_feature_permission_cancelled(self, question, frame, feature,
|
||||
cancelled_frame, cancelled_feature):
|
||||
def _on_feature_permission_cancelled(self, question, frame, feature,
|
||||
cancelled_frame, cancelled_feature):
|
||||
"""Slot invoked when a feature permission request was cancelled.
|
||||
|
||||
To be used with functools.partial.
|
||||
|
@ -821,6 +821,16 @@ def data(readonly=False):
|
||||
SettingValue(typ.BoolAsk(), 'ask'),
|
||||
"Allow websites to show notifications."),
|
||||
|
||||
('media-capture',
|
||||
SettingValue(typ.BoolAsk(), 'ask',
|
||||
backends=[usertypes.Backend.QtWebEngine]),
|
||||
"Allow websites to record audio/video."),
|
||||
|
||||
('mouse-lock',
|
||||
SettingValue(typ.BoolAsk(), 'ask',
|
||||
backends=[usertypes.Backend.QtWebEngine]),
|
||||
"Allow websites to lock the mouse pointer."),
|
||||
|
||||
('javascript-can-open-windows-automatically',
|
||||
SettingValue(typ.Bool(), 'false'),
|
||||
"Whether JavaScript programs can open new windows without user "
|
||||
|
@ -101,7 +101,7 @@ Feature: Prompts
|
||||
Then the javascript message "Alert done" should be logged
|
||||
And the javascript message "notification permission granted" should be logged
|
||||
|
||||
@qtwebengine_todo: Permissions are not implemented yet
|
||||
@qtwebengine_todo: Notifications are not implemented in QtWebEngine
|
||||
Scenario: Async question interrupted by async one
|
||||
When I set content -> notifications to ask
|
||||
And I open data/prompt/notifications.html in a new tab
|
||||
@ -116,7 +116,7 @@ Feature: Prompts
|
||||
Then the javascript message "notification permission granted" should be logged
|
||||
And "Added quickmark test for *" should be logged
|
||||
|
||||
@qtwebengine_todo: Permissions are not implemented yet
|
||||
@qtwebengine_todo: Notifications are not implemented in QtWebEngine
|
||||
Scenario: Async question interrupted by blocking one
|
||||
When I set content -> notifications to ask
|
||||
And I set content -> ignore-javascript-alert to false
|
||||
@ -199,21 +199,20 @@ Feature: Prompts
|
||||
|
||||
# Geolocation
|
||||
|
||||
@qtwebengine_todo: Permissions are not implemented yet
|
||||
Scenario: Always rejecting geolocation
|
||||
When I set content -> geolocation to false
|
||||
And I open data/prompt/geolocation.html in a new tab
|
||||
And I run :click-element id button
|
||||
Then the javascript message "geolocation permission denied" should be logged
|
||||
|
||||
@ci @not_osx @qtwebengine_todo: Permissions are not implemented yet
|
||||
@ci @not_osx
|
||||
Scenario: Always accepting geolocation
|
||||
When I set content -> geolocation to true
|
||||
And I open data/prompt/geolocation.html in a new tab
|
||||
And I run :click-element id button
|
||||
Then the javascript message "geolocation permission denied" should not be logged
|
||||
|
||||
@ci @not_osx @qtwebengine_todo: Permissions are not implemented yet
|
||||
@ci @not_osx
|
||||
Scenario: geolocation with ask -> true
|
||||
When I set content -> geolocation to ask
|
||||
And I open data/prompt/geolocation.html in a new tab
|
||||
@ -222,7 +221,6 @@ Feature: Prompts
|
||||
And I run :prompt-accept yes
|
||||
Then the javascript message "geolocation permission denied" should not be logged
|
||||
|
||||
@qtwebengine_todo: Permissions are not implemented yet
|
||||
Scenario: geolocation with ask -> false
|
||||
When I set content -> geolocation to ask
|
||||
And I open data/prompt/geolocation.html in a new tab
|
||||
@ -231,7 +229,6 @@ Feature: Prompts
|
||||
And I run :prompt-accept no
|
||||
Then the javascript message "geolocation permission denied" should be logged
|
||||
|
||||
@qtwebengine_todo: Permissions are not implemented yet
|
||||
Scenario: geolocation with ask -> abort
|
||||
When I set content -> geolocation to ask
|
||||
And I open data/prompt/geolocation.html in a new tab
|
||||
@ -242,21 +239,21 @@ Feature: Prompts
|
||||
|
||||
# Notifications
|
||||
|
||||
@qtwebengine_todo: Permissions are not implemented yet
|
||||
@qtwebengine_todo: Notifications are not implemented in QtWebEngine
|
||||
Scenario: Always rejecting notifications
|
||||
When I set content -> notifications to false
|
||||
And I open data/prompt/notifications.html in a new tab
|
||||
And I run :click-element id button
|
||||
Then the javascript message "notification permission denied" should be logged
|
||||
|
||||
@qtwebengine_todo: Permissions are not implemented yet
|
||||
@qtwebengine_todo: Notifications are not implemented in QtWebEngine
|
||||
Scenario: Always accepting notifications
|
||||
When I set content -> notifications to true
|
||||
And I open data/prompt/notifications.html in a new tab
|
||||
And I run :click-element id button
|
||||
Then the javascript message "notification permission granted" should be logged
|
||||
|
||||
@qtwebengine_todo: Permissions are not implemented yet
|
||||
@qtwebengine_todo: Notifications are not implemented in QtWebEngine
|
||||
Scenario: notifications with ask -> false
|
||||
When I set content -> notifications to ask
|
||||
And I open data/prompt/notifications.html in a new tab
|
||||
@ -265,7 +262,7 @@ Feature: Prompts
|
||||
And I run :prompt-accept no
|
||||
Then the javascript message "notification permission denied" should be logged
|
||||
|
||||
@qtwebengine_todo: Permissions are not implemented yet
|
||||
@qtwebengine_todo: Notifications are not implemented in QtWebEngine
|
||||
Scenario: notifications with ask -> true
|
||||
When I set content -> notifications to ask
|
||||
And I open data/prompt/notifications.html in a new tab
|
||||
@ -284,7 +281,7 @@ Feature: Prompts
|
||||
And I run :leave-mode
|
||||
Then the javascript message "notification permission aborted" should be logged
|
||||
|
||||
@qtwebengine_todo: Permissions are not implemented yet
|
||||
@qtwebengine_todo: Notifications are not implemented in QtWebEngine
|
||||
Scenario: answering notification after closing tab
|
||||
When I set content -> notifications to ask
|
||||
And I open data/prompt/notifications.html in a new tab
|
||||
@ -467,7 +464,7 @@ Feature: Prompts
|
||||
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/1249#issuecomment-175205531
|
||||
# https://github.com/The-Compiler/qutebrowser/pull/2054#issuecomment-258285544
|
||||
@qtwebengine_todo: Permissions are not implemented yet
|
||||
@qtwebengine_todo: Notifications are not implemented in QtWebEngine
|
||||
Scenario: Interrupting SSL prompt during a notification prompt
|
||||
When I set content -> notifications to ask
|
||||
And I set network -> ssl-strict to ask
|
||||
|
Loading…
Reference in New Issue
Block a user