705 lines
22 KiB
Python
705 lines
22 KiB
Python
import asyncio, functools, json, mimetypes, ftplib
|
|
|
|
from izzylib.exceptions import HttpClientError
|
|
from jinja2.exceptions import TemplateNotFound
|
|
from json.decoder import JSONDecodeError
|
|
from urllib.parse import urlparse, quote, unquote
|
|
from ssl import SSLCertVerificationError
|
|
|
|
from .. import cache, var
|
|
from ..enums import EditAction, Javascript, WebviewContextActions
|
|
from ..functions import ComponentBase, Thread, connect, run_in_gui_thread
|
|
from ..widgets import FileChooser
|
|
|
|
|
|
#context_menu_actions = {
|
|
#'audio_copy': WebKit2.ContextMenuAction.COPY_AUDIO_LINK_TO_CLIPBOARD,
|
|
#'audio_download': WebKit2.ContextMenuAction.DOWNLOAD_AUDIO_TO_DISK,
|
|
#'frame_open': WebKit2.ContextMenuAction.OPEN_FRAME_IN_NEW_WINDOW,
|
|
#'audio_tab': WebKit2.ContextMenuAction.OPEN_AUDIO_IN_NEW_WINDOW,
|
|
#'go_back': WebKit2.ContextMenuAction.GO_BACK,
|
|
#'go_forward': WebKit2.ContextMenuAction.GO_FORWARD,
|
|
#'go_reload': WebKit2.ContextMenuAction.RELOAD,
|
|
#'go_stop': WebKit2.ContextMenuAction.STOP,
|
|
#'image_copy': WebKit2.ContextMenuAction.COPY_IMAGE_URL_TO_CLIPBOARD,
|
|
#'image_copy-full': WebKit2.ContextMenuAction.COPY_IMAGE_TO_CLIPBOARD,
|
|
#'image_download': WebKit2.ContextMenuAction.DOWNLOAD_IMAGE_TO_DISK,
|
|
#'image_tab': WebKit2.ContextMenuAction.OPEN_IMAGE_IN_NEW_WINDOW,
|
|
#'inspect_element': WebKit2.ContextMenuAction.INSPECT_ELEMENT,
|
|
#'link_copy': WebKit2.ContextMenuAction.COPY_LINK_TO_CLIPBOARD,
|
|
#'link_download': WebKit2.ContextMenuAction.DOWNLOAD_LINK_TO_DISK,
|
|
#'link_open': WebKit2.ContextMenuAction.OPEN_LINK,
|
|
#'link_tab': WebKit2.ContextMenuAction.OPEN_LINK_IN_NEW_WINDOW,
|
|
#'media_control': WebKit2.ContextMenuAction.TOGGLE_MEDIA_CONTROLS,
|
|
#'media_mute': WebKit2.ContextMenuAction.MEDIA_MUTE,
|
|
#'media_loop': WebKit2.ContextMenuAction.TOGGLE_MEDIA_LOOP,
|
|
#'media_pause': WebKit2.ContextMenuAction.MEDIA_PAUSE,
|
|
#'spell_add': WebKit2.ContextMenuAction.LEARN_SPELLING,
|
|
#'media_play': WebKit2.ContextMenuAction.MEDIA_PLAY,
|
|
#'spell_guess': WebKit2.ContextMenuAction.SPELLING_GUESS,
|
|
#'spell_ignore-grammar': WebKit2.ContextMenuAction.IGNORE_GRAMMAR,
|
|
#'spell_ignore-spell': WebKit2.ContextMenuAction.IGNORE_SPELLING,
|
|
#'spell_none': WebKit2.ContextMenuAction.NO_GUESSES_FOUND,
|
|
#'text_bold': WebKit2.ContextMenuAction.BOLD,
|
|
#'text_copy': WebKit2.ContextMenuAction.COPY,
|
|
#'text_cut': WebKit2.ContextMenuAction.CUT,
|
|
#'text_delete': WebKit2.ContextMenuAction.DELETE,
|
|
#'text_emoji': WebKit2.ContextMenuAction.INSERT_EMOJI,
|
|
#'text_italic': WebKit2.ContextMenuAction.ITALIC,
|
|
#'text_paste': WebKit2.ContextMenuAction.PASTE,
|
|
#'text_paste-plain': WebKit2.ContextMenuAction.PASTE_AS_PLAIN_TEXT,
|
|
#'text_select': WebKit2.ContextMenuAction.SELECT_ALL,
|
|
#'text_underline': WebKit2.ContextMenuAction.UNDERLINE,
|
|
#'video_copy': WebKit2.ContextMenuAction.COPY_VIDEO_LINK_TO_CLIPBOARD,
|
|
#'video_download': WebKit2.ContextMenuAction.DOWNLOAD_VIDEO_TO_DISK,
|
|
#'video_fullscreen': WebKit2.ContextMenuAction.ENTER_VIDEO_FULLSCREEN,
|
|
#'video_tab': WebKit2.ContextMenuAction.OPEN_VIDEO_IN_NEW_WINDOW,
|
|
#}
|
|
|
|
cert_error_msg = {
|
|
'unknown-ca': 'Unknown certificate authority for url: {url}',
|
|
'bad-identity': 'Certificate domain does not match: {url}'
|
|
}
|
|
|
|
|
|
class WebviewHandler(ComponentBase):
|
|
def __init__(self, tab):
|
|
ComponentBase.__init__(self)
|
|
|
|
self.tab = tab
|
|
self.inspector_open = False
|
|
|
|
signals = {
|
|
'close': {'handler': self.tab.close},
|
|
'context-menu': {'handler': self.handle_context_menu},
|
|
'create': {'handler': self.handle_new_window},
|
|
'decide-policy': {'handler': self.handle_decide_policy},
|
|
'enter-fullscreen': {'handler': self.handle_fullscreen, 'args': ['enter']},
|
|
#'insecure-content-detected': {'handler': self.handle_insecure_content},
|
|
'leave-fullscreen': {'handler': self.handle_fullscreen, 'args': ['exit']},
|
|
'load-changed': {'handler': self.handle_load_changed},
|
|
'load-failed-with-tls-errors': {'handler': self.handle_tls_error},
|
|
'mouse-target-changed': {'handler': self.handle_mouse_hover},
|
|
'resource-load-started': {'handler': self.handle_resource_load},
|
|
'script-dialog': {'handler': self.handle_script_dialog},
|
|
#'show-notification': {'handler': self.handle_notification},
|
|
#'submit-form': {'handler': self.handle_submit_form},
|
|
'permission-request': {'handler': self.handle_permission_request},
|
|
'web-process-terminated': {'handler': self.handle_tab_crashed},
|
|
'notify::estimated-load-progress': {'handler': self.handle_load_progress},
|
|
'notify::favicon': {'handler': self.tab.set_favicon, 'kwargs': {'original_args': False}},
|
|
'notify::title': {'handler': self.tab.set_title, 'kwargs': {'original_args': False}},
|
|
'notify::uri': {'handler': self.tab.set_url, 'kwargs': {'original_args': False}}
|
|
}
|
|
|
|
for signal, data in signals.items():
|
|
if data.get('kwargs', {}).get('original_args') == None:
|
|
data['kwargs'] = {'original_args': True}
|
|
|
|
connect(self.webview, signal, data['handler'], *data.get('args', ''), **data['kwargs'])
|
|
|
|
connect(self.inspector, 'attach', self.handle_inspector, 'attach', original_args=True)
|
|
connect(self.inspector, 'detach', self.handle_inspector, 'detach', original_args=True)
|
|
connect(self.inspector, 'closed', self.handle_inspector, 'close', original_args=True)
|
|
|
|
|
|
@property
|
|
def inspector(self):
|
|
return self.webview.get_inspector()
|
|
|
|
|
|
@property
|
|
def settings(self):
|
|
return self.tab.settings
|
|
|
|
|
|
@property
|
|
def webview(self):
|
|
return self.tab.webview
|
|
|
|
|
|
def handle_context_menu(self, webview, context_menu, event, hit):
|
|
menu = ContextMenuClass(context_menu, self.tab)
|
|
url = DotDict(
|
|
page = webview.get_uri(),
|
|
link = hit.get_link_uri(),
|
|
image = hit.get_image_uri(),
|
|
media = hit.get_media_uri()
|
|
)
|
|
data = DotDict(
|
|
link_text = hit.get_link_label(),
|
|
selection = hit.context_is_selection(),
|
|
editable = hit.context_is_editable(),
|
|
link = hit.context_is_link(),
|
|
media = hit.context_is_media(),
|
|
image = hit.context_is_image(),
|
|
scrollbar = hit.context_is_scrollbar()
|
|
)
|
|
|
|
for key, value in url.items():
|
|
if not value:
|
|
continue
|
|
|
|
url[key] = Url(value)
|
|
|
|
|
|
if data.link:
|
|
menu.new_action('link_open', 'Open Link', self.tab.load_url, url.link)
|
|
menu.new_action('link_new', 'Open Link In New Tab', self.window.tab_new, url.link, switch=True)
|
|
menu.new_action('link_bg', 'Open Link In Background Tab', self.window.tab_new, url.link)
|
|
menu.new_stock('link_copy', 'Copy Link Url', WebviewContextActions.LINK_COPY)
|
|
menu.new_stock('link_dl', 'Download Link', WebviewContextActions.LINK_DOWNLOAD)
|
|
menu.new_sep()
|
|
|
|
if data.media:
|
|
menu.new_stock('media_play', 'Play', WebviewContextActions.MEDIA_PLAY)
|
|
menu.new_stock('media_pause', 'Pause', WebviewContextActions.MEDIA_PAUSE)
|
|
menu.new_stock('media_loop', 'Toggle Loop', WebviewContextActions.MEDIA_LOOP)
|
|
menu.new_stock('media_ctrl', 'Toggle Controls', WebviewContextActions.MEDIA_TOGGLE)
|
|
menu.new_action('media_open', 'Open Media', self.tab.load_url, url.media)
|
|
menu.new_action('media_new', 'Open Media In New Tab', self.window.tab_new, url.media, switch=True)
|
|
menu.new_action('media_bg', 'Open Media In Background Tab', self.window.tab_new, url.media)
|
|
menu.new_action('media_dl', 'Download Media', self.app.context.download_uri, url.media)
|
|
menu.new_sep()
|
|
|
|
if data.image:
|
|
menu.new_action('image_open', 'Open Image', self.tab.load_url, url.image)
|
|
menu.new_action('image_new', 'Open Image In New Tab', self.window.tab_new, url.image, switch=True)
|
|
menu.new_action('image_bg', 'Open Image In Background Tab', self.window.tab_new, url.image)
|
|
menu.new_stock('image_dl', 'Download Image', WebviewContextActions.IMAGE_DOWNLOAD)
|
|
menu.new_stock('image_url', 'Copy Image Url', WebviewContextActions.IMAGE_COPY)
|
|
menu.new_stock('image_copy', 'Copy Image', WebviewContextActions.IMAGE_COPY_FULL)
|
|
menu.new_sep()
|
|
|
|
if data.editable:
|
|
menu.new_webview_action('edit_undo', 'Undo', 'Undo')
|
|
menu.new_webview_action('edit_redo', 'Redo', 'Redo')
|
|
menu.new_stock('edit_paste', 'Paste', WebviewContextActions.TEXT_PASTE)
|
|
menu.new_stock('edit_plain', 'Paste Plain Text', WebviewContextActions.TEXT_PASTE_PLAIN)
|
|
|
|
if data.selection:
|
|
menu.new_stock('edit_copy', 'Copy', WebviewContextActions.TEXT_COPY)
|
|
|
|
if data.selection and data.editable:
|
|
menu.new_stock('edit_cut', 'Cut', WebviewContextActions.TEXT_CUT)
|
|
menu.new_stock('edit_delete', 'Delete', WebviewContextActions.TEXT_DELETE)
|
|
|
|
menu.new_stock('edit_select', 'Select All', WebviewContextActions.TEXT_SELECT)
|
|
|
|
if data.selection or data.editable:
|
|
menu.new_sep()
|
|
|
|
if data.selection:
|
|
menu.new_action(
|
|
'search',
|
|
'Search or Go',
|
|
self.tab.run_js,
|
|
Javascript.SELECTION,
|
|
self.callback_new_tab
|
|
)
|
|
|
|
search_menu = menu.new_sub('search_menu', 'Search via..')
|
|
|
|
with self.app.db.session as s:
|
|
default = s.get_config('default_search')
|
|
|
|
for row in sorted(s.fetch('search'), key=lambda x:x['name']):
|
|
search_menu.new_action(
|
|
row.keyword,
|
|
row.name,
|
|
self.tab.run_js,
|
|
Javascript.SELECTION,
|
|
self.callback_search,
|
|
row.keyword
|
|
)
|
|
|
|
menu.new_sep()
|
|
|
|
if url.page.proto != var.local:
|
|
menu.new_action('print', 'Print Page', self.tab.page_action, 'print')
|
|
|
|
if url.page.proto in ['http', 'https']:
|
|
menu.new_action('source', 'View Page Source', self.tab.page_action, 'source')
|
|
|
|
menu.new_stock('inspect', 'Inspect Element', WebviewContextActions.INSPECT)
|
|
|
|
return False
|
|
|
|
|
|
def handle_decide_policy(self, webview, decision, decision_type):
|
|
url = self.tab.url
|
|
|
|
if decision_type == WebKit2.PolicyDecisionType.NAVIGATION_ACTION:
|
|
if url.proto == 'file':
|
|
decision.ignore()
|
|
webview.load_uri(url.replace_property('proto', 'local'))
|
|
return True
|
|
|
|
elif url.proto == 'ftp':
|
|
decision.ignore()
|
|
webview.load_uri(url.replace_property('proto', 'filetp'))
|
|
return True
|
|
|
|
## Can't just pass oauth:// urls to mastodon's oauth, so here's a workaround
|
|
if url.startswith('https://pyweb-oauth'):
|
|
url = url.replace('https://pyweb-oauth', 'oauth://', 1)
|
|
webview.load_uri(url)
|
|
decision.ignore()
|
|
return True
|
|
|
|
try:
|
|
if self.tab.url.top in ['facebook.com']:
|
|
decision.ignore()
|
|
return True
|
|
|
|
except:
|
|
pass
|
|
|
|
if not url.startswith(var.local):
|
|
with self.app.db.session as s:
|
|
permissions = s.get_permission(url.hostname())
|
|
decision.use_with_policies(WebKit2.WebsitePolicies(autoplay = bool(permissions.autoplay)))
|
|
|
|
|
|
def handle_fullscreen(self, webview, state):
|
|
domain = self.tab.url.domain
|
|
navbar = self.tab['navbar']
|
|
statusbar = self.window['statusbar']
|
|
tabs = self.window['tabs']
|
|
|
|
with self.app.db.session as s:
|
|
row = s.get_permission(domain)
|
|
|
|
if not row.fullscreen:
|
|
self.window.notification(f'Denied fullscreen for domain: {domain}', 'error')
|
|
return True
|
|
|
|
# the navbar and status bars don't hide for some reason
|
|
if state == 'enter':
|
|
self.window.fullscreen()
|
|
navbar.hide()
|
|
statusbar.hide()
|
|
tabs.set_show_tabs(False)
|
|
|
|
else:
|
|
self.window.unfullscreen()
|
|
navbar.show()
|
|
statusbar.show()
|
|
tabs.set_show_tabs(True)
|
|
|
|
|
|
def handle_insecure_content(self, webview, event):
|
|
logging.debug('insecure-content:', event.value_nick)
|
|
return True
|
|
|
|
|
|
def handle_inspector(self, inspector, action):
|
|
with self.app.db.session as s:
|
|
config = s.get_config('detach_inspector')
|
|
|
|
if action == 'attach':
|
|
if config and inspector.is_attached():
|
|
inspector.detach()
|
|
return True
|
|
|
|
elif action == 'detach':
|
|
if not config and not inspector.is_attached():
|
|
inspector.attach()
|
|
return True
|
|
|
|
else:
|
|
self.inspector_open = False
|
|
return
|
|
|
|
self.inspector_open = True
|
|
|
|
|
|
def handle_load_changed(self, webview, event):
|
|
if not (url := self.tab.url):
|
|
return
|
|
|
|
## Only enable the page cache for http(s) urls
|
|
self.settings.set('enable-page-cache', url.proto in ['http', 'https'])
|
|
|
|
try:
|
|
host = url.hostname()
|
|
except AttributeError:
|
|
host = url
|
|
|
|
with self.app.db.session as s:
|
|
permissions = s.get_permission(host)
|
|
|
|
if event.value_nick == 'started':
|
|
self.tab._data.favicon = False
|
|
self.settings.set('media-playback-requires-user-gesture', not permissions.autoplay)
|
|
|
|
self.tab['navbar-stop'].set_sensitive(True)
|
|
self.tab['navbar-refresh'].set_sensitive(False)
|
|
self.tab.search_action('close')
|
|
|
|
elif event.value_nick == 'committed':
|
|
self.tab.set_button_state()
|
|
|
|
elif event.value_nick == 'redirected':
|
|
logging.debug(f'handle_load_changed: redirect {url}')
|
|
pass
|
|
|
|
elif event.value_nick == 'finished':
|
|
if isinstance(url, Url):
|
|
if url.proto == 'https':
|
|
FediverseCheck(None, self.tab, permissions).start()
|
|
|
|
elif url.proto in ['http', 'sftp', 'filetp']:
|
|
with self.app.db.session as s:
|
|
s.put_history_from_tab(self.tab)
|
|
|
|
if self.window.active_tab == self.tab:
|
|
self.window.set_button_state(self.tab.id)
|
|
|
|
if not self.tab._data.favicon:
|
|
self.tab.set_favicon()
|
|
|
|
|
|
def handle_load_failed(self, webview, event, uri, error):
|
|
logging.error('Load failed:', uri, error.value_nick)
|
|
|
|
|
|
def handle_load_progress(self, webview, _):
|
|
progress = webview.get_estimated_load_progress()
|
|
finished = True if progress == 1.0 else False
|
|
|
|
self.tab['navbar-url'].set_progress_fraction(0 if progress == 1.0 else progress)
|
|
self.tab['navbar-stop'].set_sensitive(not finished)
|
|
self.tab['navbar-refresh'].set_sensitive(finished)
|
|
|
|
|
|
def handle_mouse_hover(self, webview, hit, _):
|
|
link_url = hit.get_property('link-uri')
|
|
self.window['statusbar-url'].set_text(link_url if link_url else '')
|
|
|
|
|
|
def handle_resource_load(self, webview, resource, request):
|
|
resource.connect('failed', self.handle_resource_failed)
|
|
resource.connect('finished', self.handle_resource_finished)
|
|
|
|
|
|
def handle_resource_failed(self, resource, error):
|
|
try:
|
|
url = Url(resource.get_uri())
|
|
|
|
except Exception as e:
|
|
print(f'{class_name(e)}: {e}')
|
|
return
|
|
|
|
if error.domain == 'WebKitPolicyError' and 'interrupted' in error.message.lower():
|
|
if not url.mimetype:
|
|
self.window.notification(f'Cannot open url: {url}', 'warning')
|
|
|
|
elif not self.webview.can_show_mime_type(url.mimetype):
|
|
self.app.context.download_uri(url)
|
|
|
|
return
|
|
|
|
if error.domain not in ['HandlerError', 'soup-http-error-quark']:
|
|
if error.domain == 'WebKitNetworkError' and error.message.lower() == 'load request cancelled':
|
|
return
|
|
|
|
logging.debug(f'''Resource failed to load: {resource.get_uri()}
|
|
- domain: {error.domain}
|
|
- message: {error.message}''')
|
|
|
|
|
|
def handle_resource_finished(self, resource):
|
|
return
|
|
|
|
|
|
def handle_new_window(self, webview, action):
|
|
navtype = action.get_navigation_type()
|
|
user_action = action.is_user_gesture()
|
|
url = action.get_request().get_uri()
|
|
|
|
if not user_action:
|
|
logging.debug('handle_new_window: Not a user action: "{url}"')
|
|
return
|
|
|
|
if navtype in [WebKit2.NavigationType.FORM_SUBMITTED, WebKit2.NavigationType.LINK_CLICKED, WebKit2.NavigationType.OTHER]:
|
|
self.window.tab_new(url, switch=True)
|
|
|
|
else:
|
|
logging.debug('handle_new_window: unhandled nav type:', navtype)
|
|
|
|
|
|
def handle_notification(self, webview, notif):
|
|
try:
|
|
domain = self.tab.url.domain
|
|
|
|
## Not a url, so deny permission
|
|
except AttributeError:
|
|
return True
|
|
|
|
with self.app.db.session as s:
|
|
row = s.get_permission(domain)
|
|
|
|
if not row.notification:
|
|
logging.debug(f'Denied notification for domain: {domain}')
|
|
return True
|
|
|
|
|
|
def handle_permission_request(self, webview, permrequest):
|
|
try:
|
|
domain = self.tab.url.domain
|
|
|
|
## Not a url, so deny perlmission
|
|
except AttributeError:
|
|
permrequest.deny()
|
|
return
|
|
|
|
with self.db.session as s:
|
|
row = s.get_permission(domain)
|
|
|
|
if type(permrequest) == WebKit2.NotificationPermissionRequest and row.notification:
|
|
logging.verbose(f'Allowed notification permission request from {domain}')
|
|
permrequest.allow()
|
|
|
|
elif type(permrequest) == WebKit2.GeolocationPermissionRequest and row.location:
|
|
logging.verbose(f'Allowed location permission request from {domain}')
|
|
permrequest.allow()
|
|
|
|
elif type(permrequest) == WebKit2.UserMediaPermissionRequest:
|
|
if permrequest.props.is_for_audio_device and row.microphone:
|
|
logging.verbose(f'Allowed microphone permission request from {domain}')
|
|
permrequest.allow()
|
|
|
|
elif permrequest.props.is_for_video_device and row.camera:
|
|
logging.verbose(f'Allowed webcam permission request from {domain}')
|
|
permrequest.allow()
|
|
|
|
else:
|
|
logging.verbose(f'Rejected permission request from {domain}: {type(permrequest)}')
|
|
permrequest.deny()
|
|
|
|
|
|
def handle_script_dialog(self, webview, dialog):
|
|
try:
|
|
domain = self.tab.url.domain
|
|
|
|
except:
|
|
return True
|
|
|
|
with self.db.session as s:
|
|
row = s.get_permission(domain)
|
|
|
|
if not row.dialog:
|
|
logging.verbose('JS dialogs not allowed for domain:', domain)
|
|
return True
|
|
|
|
|
|
## calling list_text_fields causes a crash half the time
|
|
def handle_submit_form(self, webview, form):
|
|
form_data = form.list_text_fields()
|
|
data = DotDict()
|
|
|
|
if form_data[0]:
|
|
keys = form_data[1]
|
|
values = form_data[2]
|
|
|
|
for index, key in enumerate(keys):
|
|
data[key] = values[index]
|
|
|
|
form.submit()
|
|
|
|
|
|
def handle_tab_crashed(self, webview, reason):
|
|
reasons = {
|
|
'crashed': 'Web process crashed. Reload the page.',
|
|
'exceeded_memory_limit': 'Web process exceeded the memory limit. Reload the page.'
|
|
}
|
|
|
|
self.window.notification(reasons[reason.value_nick], 'ERROR')
|
|
|
|
|
|
def handle_tls_error(self, webview, url, cert, error):
|
|
url = Url(url)
|
|
err = error.first_value_nick
|
|
|
|
with self.db.session as s:
|
|
if url.address.is_type('private') and s.get_config('allow_local_unsigned'):
|
|
self.tab.context.allow_tls_certificate_for_host(cert, url.domain)
|
|
webview.load_uri(url)
|
|
return True
|
|
|
|
msg = cert_error_msg.get(err, 'TLS error: {url}')
|
|
|
|
if not msg:
|
|
logging.debug('handlers.signal.TlsError: Unhandled error:', err)
|
|
|
|
context = {'error_message': msg.format(url=url), 'title': 'TLS Error'}
|
|
webview.load_html(self.app.template.render('error.haml', context))
|
|
|
|
return True
|
|
|
|
|
|
def handle_set_url(self, webview, url):
|
|
self.tab.set_url()
|
|
|
|
|
|
def handle_set_favicon(self, *_):
|
|
favicondb = self.window.context.get_favicon_database()
|
|
favicondb.get_favicon(self.tab.url, None, self.callback_set_favicon)
|
|
|
|
|
|
def handle_set_title(self, *_):
|
|
self.tab.set_title()
|
|
|
|
|
|
def callback_new_tab(self, url, switch=True):
|
|
if not url:
|
|
self.window.notification('No selection found', 'error')
|
|
return
|
|
|
|
self.window.new_tab(url, switch=switch)
|
|
|
|
|
|
def callback_search(self, text, key=None):
|
|
if not text:
|
|
self.window.notification('No selection found', 'error')
|
|
return
|
|
|
|
with self.app.db.session as s:
|
|
keyword = key or s.get_config('default_search')
|
|
search = s.get_search(keyword)
|
|
|
|
self.window.new_tab(search.compile(text), switch=True)
|
|
|
|
|
|
def callback_set_favicon(self, favicondb, task):
|
|
try:
|
|
favicon = favicondb.get_favicon_finish(task)
|
|
|
|
except GLib.Error:
|
|
favicon = None
|
|
|
|
self.tab.favicon = favicon
|
|
|
|
|
|
## I'd like to subclass WebKit2.ContextMenu if possible
|
|
class ContextMenuClass(DotDict):
|
|
def __init__(self, menu, tab):
|
|
super().__init__()
|
|
|
|
self.menu = menu or WebKit2.ContextMenu.new()
|
|
self.tab = tab
|
|
self.window = tab.window
|
|
self.webview = tab.webview
|
|
|
|
self.menu.remove_all()
|
|
|
|
|
|
def new_action(self, name, label, callback, *args, **kwargs):
|
|
action = Gio.SimpleAction(name=name)
|
|
connect(action, 'activate', callback, *args, **kwargs)
|
|
|
|
item = WebKit2.ContextMenuItem().new_from_gaction(action, label, None)
|
|
|
|
self[name] = item
|
|
self.menu.append(item)
|
|
|
|
|
|
def new_stock(self, name, label, action):
|
|
if not isinstance(action, WebviewContextActions):
|
|
raise TypeError(f'Webview context action must be a WebviewContextActions, not {class_name(action)}')
|
|
#action = WebviewContextActions[action.upper()]
|
|
|
|
item = WebKit2.ContextMenuItem.new_from_stock_action_with_label(action.value, label)
|
|
|
|
self[name] = item
|
|
self.menu.append(item)
|
|
|
|
|
|
def new_webview_action(self, name, label, action):
|
|
action = Gio.SimpleAction(name=name)
|
|
connect(action, 'activate', self.webview.execute_editing_command, action)
|
|
|
|
item = WebKit2.ContextMenuItem().new_from_gaction(action, label, None)
|
|
|
|
self[name] = item
|
|
self.menu.append(item)
|
|
|
|
|
|
## I'll probably have to mess with the class itself to use this properly
|
|
def new_sub(self, name, label):
|
|
menu = WebKit2.ContextMenu.new()
|
|
menu_class = ContextMenuClass(menu, self.tab)
|
|
|
|
self[name] = menu_class
|
|
self.menu.append(WebKit2.ContextMenuItem.new_with_submenu(label, menu))
|
|
|
|
return menu_class
|
|
|
|
|
|
def new_sep(self):
|
|
self.menu.append(WebKit2.ContextMenuItem.new_separator())
|
|
|
|
|
|
class FediverseCheck(Thread):
|
|
def run_func(self, tab, permissions):
|
|
acct = tab.app.get_default_account()
|
|
url = tab.url
|
|
post = {}
|
|
|
|
with tab.db.session as s:
|
|
history_row = s.get_history(url)
|
|
is_post = history_row.post if history_row else None
|
|
nodeinfo = None
|
|
|
|
if not acct:
|
|
s.put_history_from_tab(tab)
|
|
return
|
|
|
|
if s.get_config('ap_check') and permissions.instance == None:
|
|
try:
|
|
nodeinfo = tab.app.http_client.fetch_nodeinfo(permissions.domain)
|
|
|
|
except SSLCertVerificationError:
|
|
logging.verbose(f'FediverseCheck: SSL Error for domain: {url.domain}')
|
|
return
|
|
|
|
except ConnectionRefusedError:
|
|
logging.verbose(f'FediverseCheck: Failed to connect to domain: {url.domain}')
|
|
return
|
|
|
|
except HttpClientError as e:
|
|
if e.status in range(500, 600):
|
|
logging.verbose(f'FediverseCheck: Server error: {e.status}: {e.message}')
|
|
return
|
|
|
|
if e.status == 404:
|
|
is_post = False
|
|
|
|
except JSONDecodeError:
|
|
is_post = False
|
|
|
|
except Exception as e:
|
|
logging.error(f'FediverseCheck: {type(e).__name__}: {e}')
|
|
return
|
|
|
|
is_instance = True if nodeinfo and nodeinfo.get('software') else False
|
|
logging.debug(f'Result of instance check for {permissions.domain}: {is_instance}')
|
|
permissions = s.put_permission(permissions.domain, 'instance', is_instance)
|
|
|
|
if s.get_config('post_check') and permissions and permissions.instance and (is_post in [None, True]):
|
|
if url.path.startswith('/web'):
|
|
is_post = False
|
|
|
|
elif not (post := cache.posts.fetch(url)):
|
|
post = acct.fetch_post(url)
|
|
cache.posts.store(url, post)
|
|
|
|
is_post = True if post else False
|
|
|
|
logging.verbose(f'Result of post check for {url}: {is_post}')
|
|
|
|
s.put_history(url, tab.title, is_post)
|
|
|
|
tab._data.post = post
|