qutebrowser/qutebrowser/browser/shared.py
2017-09-18 23:01:17 +02:00

263 lines
8.8 KiB
Python

# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Various utilities shared between webpage/webview subclasses."""
import html
from qutebrowser.config import config
from qutebrowser.utils import usertypes, message, log, objreg, jinja
from qutebrowser.mainwindow import mainwindow
class CallSuper(Exception):
"""Raised when the caller should call the superclass instead."""
def custom_headers():
"""Get the combined custom headers."""
headers = {}
dnt_config = config.val.content.headers.do_not_track
if dnt_config is not None:
dnt = b'1' if dnt_config else b'0'
headers[b'DNT'] = dnt
headers[b'X-Do-Not-Track'] = dnt
conf_headers = config.val.content.headers.custom
for header, value in conf_headers.items():
headers[header.encode('ascii')] = value.encode('ascii')
accept_language = config.val.content.headers.accept_language
if accept_language is not None:
headers[b'Accept-Language'] = accept_language.encode('ascii')
return sorted(headers.items())
def authentication_required(url, authenticator, abort_on):
"""Ask a prompt for an authentication question."""
realm = authenticator.realm()
if realm:
msg = '<b>{}</b> says:<br/>{}'.format(
html.escape(url.toDisplayString()), html.escape(realm))
else:
msg = '<b>{}</b> needs authentication'.format(
html.escape(url.toDisplayString()))
answer = message.ask(title="Authentication required", text=msg,
mode=usertypes.PromptMode.user_pwd,
abort_on=abort_on)
if answer is not None:
authenticator.setUser(answer.user)
authenticator.setPassword(answer.password)
return answer
def javascript_confirm(url, js_msg, abort_on):
"""Display a javascript confirm prompt."""
log.js.debug("confirm: {}".format(js_msg))
if config.val.content.javascript.modal_dialog:
raise CallSuper
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
html.escape(js_msg))
ans = message.ask('Javascript confirm', msg,
mode=usertypes.PromptMode.yesno,
abort_on=abort_on)
return bool(ans)
def javascript_prompt(url, js_msg, default, abort_on):
"""Display a javascript prompt."""
log.js.debug("prompt: {}".format(js_msg))
if config.val.content.javascript.modal_dialog:
raise CallSuper
if not config.val.content.javascript.prompt:
return (False, "")
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
html.escape(js_msg))
answer = message.ask('Javascript prompt', msg,
mode=usertypes.PromptMode.text,
default=default,
abort_on=abort_on)
if answer is None:
return (False, "")
else:
return (True, answer)
def javascript_alert(url, js_msg, abort_on):
"""Display a javascript alert."""
log.js.debug("alert: {}".format(js_msg))
if config.val.content.javascript.modal_dialog:
raise CallSuper
if not config.val.content.javascript.alert:
return
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
html.escape(js_msg))
message.ask('Javascript alert', msg, mode=usertypes.PromptMode.alert,
abort_on=abort_on)
def javascript_log_message(level, source, line, msg):
"""Display a JavaScript log message."""
logstring = "[{}:{}] {}".format(source, line, msg)
# Needs to line up with the values allowed for the
# content.javascript.log setting.
logmap = {
'none': lambda arg: None,
'debug': log.js.debug,
'info': log.js.info,
'warning': log.js.warning,
'error': log.js.error,
}
logger = logmap[config.val.content.javascript.log[level.name]]
logger(logstring)
def ignore_certificate_errors(url, errors, abort_on):
"""Display a certificate error question.
Args:
url: The URL the errors happened in
errors: A list of QSslErrors or QWebEngineCertificateErrors
Return:
True if the error should be ignored, False otherwise.
"""
ssl_strict = config.val.content.ssl_strict
log.webview.debug("Certificate errors {!r}, strict {}".format(
errors, ssl_strict))
for error in errors:
assert error.is_overridable(), repr(error)
if ssl_strict == 'ask':
err_template = jinja.environment.from_string("""
Errors while loading <b>{{url.toDisplayString()}}</b>:<br/>
<ul>
{% for err in errors %}
<li>{{err}}</li>
{% endfor %}
</ul>
""".strip())
msg = err_template.render(url=url, errors=errors)
ignore = message.ask(title="Certificate errors - continue?", text=msg,
mode=usertypes.PromptMode.yesno, default=False,
abort_on=abort_on)
if ignore is None:
# prompt aborted
ignore = False
return ignore
elif ssl_strict is False:
log.webview.debug("ssl_strict is False, only warning about errors")
for err in errors:
# FIXME we might want to use warn here (non-fatal error)
# https://github.com/qutebrowser/qutebrowser/issues/114
message.error('Certificate error: {}'.format(err))
return True
elif ssl_strict is True:
return False
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: An option name 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.instance.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
def get_tab(win_id, target):
"""Get a tab widget for the given usertypes.ClickTarget.
Args:
win_id: The window ID to open new tabs in
target: A usertypes.ClickTarget
"""
if target == usertypes.ClickTarget.tab:
win_id = win_id
bg_tab = False
elif target == usertypes.ClickTarget.tab_bg:
win_id = win_id
bg_tab = True
elif target == usertypes.ClickTarget.window:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
window = mainwindow.MainWindow(private=tabbed_browser.private)
window.show()
win_id = window.win_id
bg_tab = False
else:
raise ValueError("Invalid ClickTarget {}".format(target))
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
return tabbed_browser.tabopen(url=None, background=bg_tab)
def get_user_stylesheet():
"""Get the combined user-stylesheet."""
css = ''
stylesheets = config.val.content.user_stylesheets
for filename in stylesheets:
with open(filename, 'r', encoding='utf-8') as f:
css += f.read()
if not config.val.scrolling.bar:
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
return css