Refactor acceptNavigationRequest handling to use signals

This commit is contained in:
Florian Bruhin 2018-02-19 04:24:40 +01:00
parent 2a7998847f
commit 7c1fb1d215
9 changed files with 116 additions and 56 deletions

View File

@ -30,7 +30,8 @@ from PyQt5.QtWidgets import QWidget, QApplication
from qutebrowser.keyinput import modeman
from qutebrowser.config import config
from qutebrowser.utils import utils, objreg, usertypes, log, qtutils
from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils, urlutils,
message)
from qutebrowser.misc import miscwidgets, objects
from qutebrowser.browser import mouse, hints
@ -94,6 +95,8 @@ class TabData:
keep_icon: Whether the (e.g. cloned) icon should not be cleared on page
load.
inspector: The QWebInspector used for this webview.
open_target: Where to open the next link.
Only used for QtWebKit.
override_target: Override for open_target for fake clicks (like hints).
Only used for QtWebKit.
pinned: Flag to pin the tab.
@ -104,6 +107,7 @@ class TabData:
keep_icon = attr.ib(False)
inspector = attr.ib(None)
open_target = attr.ib(usertypes.ClickTarget.normal)
override_target = attr.ib(None)
pinned = attr.ib(False)
fullscreen = attr.ib(False)
@ -719,6 +723,22 @@ class AbstractTab(QWidget):
self._set_load_status(usertypes.LoadStatus.loading)
self.load_started.emit()
@pyqtSlot(usertypes.NavigationRequest)
def _on_navigation_request(self, navigation):
"""Handle common acceptNavigationRequest code."""
log.webview.debug("navigation request: url {}, type {}, is_main_frame "
"{}".format(navigation.url.toDisplayString(),
navigation.navigation_type,
navigation.is_main_frame))
if (navigation.navigation_type == navigation.Type.link_clicked and
not navigation.url.isValid()):
msg = urlutils.get_errstring(navigation.url,
"Invalid link clicked")
message.error(msg)
self.data.open_target = usertypes.ClickTarget.normal
navigation.accepted = False
def handle_auto_insert_mode(self, ok):
"""Handle `input.insert_mode.auto_load` after loading finished."""
if not config.val.input.insert_mode.auto_load or not ok:

View File

@ -886,6 +886,7 @@ class WebEngineTab(browsertab.AbstractTab):
self._on_proxy_authentication_required)
page.fullScreenRequested.connect(self._on_fullscreen_requested)
page.contentsSizeChanged.connect(self.contents_size_changed)
page.navigation_request.connect(self._on_navigation_request)
view.titleChanged.connect(self.title_changed)
view.urlChanged.connect(self._on_url_changed)

View File

@ -124,10 +124,12 @@ class WebEnginePage(QWebEnginePage):
Signals:
certificate_error: Emitted on certificate errors.
shutting_down: Emitted when the page is shutting down.
navigation_request: Emitted on acceptNavigationRequest.
"""
certificate_error = pyqtSignal()
shutting_down = pyqtSignal()
navigation_request = pyqtSignal(usertypes.NavigationRequest)
def __init__(self, *, theme_color, profile, parent=None):
super().__init__(profile, parent)
@ -288,21 +290,26 @@ class WebEnginePage(QWebEnginePage):
url: QUrl,
typ: QWebEnginePage.NavigationType,
is_main_frame: bool):
"""Override acceptNavigationRequest to handle clicked links.
This only show an error on invalid links - everything else is handled
in createWindow.
"""
log.webview.debug("navigation request: url {}, type {}, is_main_frame "
"{}".format(url.toDisplayString(),
debug.qenum_key(QWebEnginePage, typ),
is_main_frame))
if (typ == QWebEnginePage.NavigationTypeLinkClicked and
not url.isValid()):
msg = urlutils.get_errstring(url, "Invalid link clicked")
message.error(msg)
return False
return True
"""Override acceptNavigationRequest to forward it to the tab API."""
type_map = {
QWebEnginePage.NavigationTypeLinkClicked:
usertypes.NavigationRequest.Type.link_clicked,
QWebEnginePage.NavigationTypeTyped:
usertypes.NavigationRequest.Type.typed,
QWebEnginePage.NavigationTypeFormSubmitted:
usertypes.NavigationRequest.Type.form_submitted,
QWebEnginePage.NavigationTypeBackForward:
usertypes.NavigationRequest.Type.back_forward,
QWebEnginePage.NavigationTypeReload:
usertypes.NavigationRequest.Type.reloaded,
QWebEnginePage.NavigationTypeOther:
usertypes.NavigationRequest.Type.other,
}
navigation = usertypes.NavigationRequest(url=url,
navigation_type=type_map[typ],
is_main_frame=is_main_frame)
self.navigation_request.emit(navigation)
return navigation.accepted
@pyqtSlot('QUrl')
def _inject_userjs(self, url):

View File

@ -35,7 +35,7 @@ from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtPrintSupport import QPrinter
from qutebrowser.browser import browsertab
from qutebrowser.browser import browsertab, shared
from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
webkitsettings)
from qutebrowser.utils import qtutils, objreg, usertypes, utils, log, debug
@ -762,6 +762,25 @@ class WebKitTab(browsertab.AbstractTab):
def _on_contents_size_changed(self, size):
self.contents_size_changed.emit(QSizeF(size))
@pyqtSlot(usertypes.NavigationRequest)
def _on_navigation_request(self, navigation):
super()._on_navigation_request(navigation)
log.webview.debug("target {} override {}".format(
self.data.open_target, self.data.override_target))
if self.data.override_target is not None:
target = self.data.override_target
self.data.override_target = None
else:
target = self.data.open_target
if (navigation.navigation_type == navigation.Type.link_clicked and
target != usertypes.ClickTarget.normal):
tab = shared.get_tab(self.win_id, target)
tab.openurl(navigation.url)
self.data.open_target = usertypes.ClickTarget.normal
navigation.accepted = False
def _connect_signals(self):
view = self._widget
page = view.page()
@ -780,6 +799,7 @@ class WebKitTab(browsertab.AbstractTab):
page.frameCreated.connect(self._on_frame_created)
frame.contentsSizeChanged.connect(self._on_contents_size_changed)
frame.initialLayoutCompleted.connect(self._on_history_trigger)
page.navigation_request.connect(self._on_navigation_request)
self.url_changed.connect(
functools.partial(webkitsettings.update_for_tab, self))

View File

@ -54,10 +54,12 @@ class BrowserPage(QWebPage):
shutting_down: Emitted when the page is currently shutting down.
reloading: Emitted before a web page reloads.
arg: The URL which gets reloaded.
navigation_request: Emitted on acceptNavigationRequest.
"""
shutting_down = pyqtSignal()
reloading = pyqtSignal(QUrl)
navigation_request = pyqtSignal(usertypes.NavigationRequest)
def __init__(self, win_id, tab_id, tabdata, private, parent=None):
super().__init__(parent)
@ -70,7 +72,6 @@ class BrowserPage(QWebPage):
}
self._ignore_load_started = False
self.error_occurred = False
self.open_target = usertypes.ClickTarget.normal
self._networkmanager = networkmanager.NetworkManager(
win_id=win_id, tab_id=tab_id, private=private, parent=self)
self.setNetworkAccessManager(self._networkmanager)
@ -474,7 +475,7 @@ class BrowserPage(QWebPage):
source, line, msg)
def acceptNavigationRequest(self,
_frame: QWebFrame,
frame: QWebFrame,
request: QNetworkRequest,
typ: QWebPage.NavigationType):
"""Override acceptNavigationRequest to handle clicked links.
@ -486,36 +487,27 @@ class BrowserPage(QWebPage):
Checks if it should open it in a tab (middle-click or control) or not,
and then conditionally opens the URL here or in another tab/window.
"""
url = request.url()
log.webview.debug("navigation request: url {}, type {}, "
"target {} override {}".format(
url.toDisplayString(),
debug.qenum_key(QWebPage, typ),
self.open_target,
self._tabdata.override_target))
type_map = {
QWebPage.NavigationTypeLinkClicked:
usertypes.NavigationRequest.Type.link_clicked,
QWebPage.NavigationTypeFormSubmitted:
usertypes.NavigationRequest.Type.form_submitted,
QWebPage.NavigationTypeFormResubmitted:
usertypes.NavigationRequest.Type.form_resubmitted,
QWebPage.NavigationTypeBackOrForward:
usertypes.NavigationRequest.Type.back_forward,
QWebPage.NavigationTypeReload:
usertypes.NavigationRequest.Type.reloaded,
QWebPage.NavigationTypeOther:
usertypes.NavigationRequest.Type.other,
}
is_main_frame = frame is self.mainFrame()
navigation = usertypes.NavigationRequest(url=request.url(),
navigation_type=type_map[typ],
is_main_frame=is_main_frame)
if self._tabdata.override_target is not None:
target = self._tabdata.override_target
self._tabdata.override_target = None
else:
target = self.open_target
if navigation.navigation_type == navigation.Type.reloaded:
self.reloading.emit(navigation.url)
if typ == QWebPage.NavigationTypeReload:
self.reloading.emit(url)
return True
elif typ != QWebPage.NavigationTypeLinkClicked:
return True
if not url.isValid():
msg = urlutils.get_errstring(url, "Invalid link clicked")
message.error(msg)
self.open_target = usertypes.ClickTarget.normal
return False
if target == usertypes.ClickTarget.normal:
return True
tab = shared.get_tab(self._win_id, target)
tab.openurl(url)
self.open_target = usertypes.ClickTarget.normal
return False
self.navigation_request.emit(navigation)
return navigation.accepted

View File

@ -262,10 +262,10 @@ class WebView(QWebView):
target = usertypes.ClickTarget.tab_bg
else:
target = usertypes.ClickTarget.tab
self.page().open_target = target
self._tabdata.open_target = target
log.mouse.debug("Ctrl/Middle click, setting target: {}".format(
target))
else:
self.page().open_target = usertypes.ClickTarget.normal
self._tabdata.open_target = usertypes.ClickTarget.normal
log.mouse.debug("Normal click, setting normal target")
super().mousePressEvent(e)

View File

@ -27,6 +27,7 @@ import operator
import collections.abc
import enum
import attr
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QTimer
from qutebrowser.utils import log, qtutils, utils
@ -394,3 +395,22 @@ class AbstractCertificateErrorWrapper:
def is_overridable(self):
raise NotImplementedError
@attr.s
class NavigationRequest:
Type = enum.Enum('Type', [
'link_clicked',
'typed', # QtWebEngine only
'form_submitted',
'form_resubmitted', # QtWebKit only
'back_forward',
'reloaded',
'other'
])
url = attr.ib()
navigation_type = attr.ib()
is_main_frame = attr.ib()
accepted = attr.ib(default=True)

View File

@ -207,7 +207,7 @@ Feature: Using hints
Scenario: Using :follow-hint inside an iframe
When I open data/hints/iframe.html
And I hint with args "links normal" and follow a
Then "navigation request: url http://localhost:*/data/hello.txt, type NavigationTypeLinkClicked, *" should be logged
Then "navigation request: url http://localhost:*/data/hello.txt, type Type.link_clicked, *" should be logged
Scenario: Using :follow-hint inside an iframe button
When I open data/hints/iframe_button.html
@ -228,12 +228,12 @@ Feature: Using hints
And I hint with args "all normal" and follow a
And I run :scroll bottom
And I hint with args "links normal" and follow a
Then "navigation request: url http://localhost:*/data/hello2.txt, type NavigationTypeLinkClicked, *" should be logged
Then "navigation request: url http://localhost:*/data/hello2.txt, type Type.link_clicked, *" should be logged
Scenario: Opening a link inside a specific iframe
When I open data/hints/iframe_target.html
And I hint with args "links normal" and follow a
Then "navigation request: url http://localhost:*/data/hello.txt, type NavigationTypeLinkClicked, *" should be logged
Then "navigation request: url http://localhost:*/data/hello.txt, type Type.link_clicked, *" should be logged
Scenario: Opening a link with specific target frame in a new tab
When I open data/hints/iframe_target.html

View File

@ -250,7 +250,7 @@ Feature: Searching on a page
And I run :search follow
And I wait for "search found follow" in the log
And I run :follow-selected
Then "navigation request: url http://localhost:*/data/hello.txt, type NavigationTypeLinkClicked, is_main_frame False" should be logged
Then "navigation request: url http://localhost:*/data/hello.txt, type Type.link_clicked, is_main_frame False" should be logged
@qtwebkit_skip: Not supported in qtwebkit
Scenario: Follow a tabbed searched link in an iframe