Skip to content
Permalink
Browse files

QtWebEngine: Implement mouse opentarget handling

This moves various stuff around and out of QtWebKit code:

- open_target and hint_target are now in TabData, not on the WebPage

- As much as possible got extracted from WebPage.acceptNavigationRequest
  to AbstractTab._on_link_clicked, with a new link_clicked signal added
  to WebPage. However, we need to decide whether to handle the request
  in this tab or not inside acceptNavigationRequest, so we have some
  code duplicated there and in WebEnginePage.acceptNavigationRequest.

- _mousepress_opentarget (i.e., setting the open_target) is now handled
  in MouseEventFilter, not in WebView.
  • Loading branch information
The-Compiler committed Aug 11, 2016
1 parent 8b0028c commit 3bffb71b551edcd4c39e9226e9a3691b3f2dba75
@@ -27,7 +27,8 @@

from qutebrowser.keyinput import modeman
from qutebrowser.config import config
from qutebrowser.utils import utils, objreg, usertypes, message, log, qtutils
from qutebrowser.utils import (utils, objreg, usertypes, message, log, qtutils,
debug, urlutils)
from qutebrowser.misc import miscwidgets
from qutebrowser.browser import mouse

@@ -68,14 +69,25 @@ class TabData:
load.
inspector: The QWebInspector used for this webview.
viewing_source: Set if we're currently showing a source view.
open_target: How the next clicked link should be opened.
hint_target: Override for open_target for hints.
"""

__slots__ = ['keep_icon', 'viewing_source', 'inspector']
__slots__ = ['keep_icon', 'viewing_source', 'inspector', 'open_target',
'hint_target']

def __init__(self):
self.keep_icon = False
self.viewing_source = False
self.inspector = None
self.open_target = usertypes.ClickTarget.normal
self.hint_target = None

def combined_target(self):
if self.hint_target is not None:
return self.hint_target
else:
return self.open_target


class AbstractPrinting:
@@ -495,6 +507,43 @@ def _set_load_status(self, val):
self._load_status = val
self.load_status_changed.emit(val.name)

@pyqtSlot(QUrl)
def _on_link_clicked(self, url):
log.webview.debug("link clicked: url {}, hint target {}, "
"open_target {}".format(
url.toDisplayString(),
self.data.hint_target, self.data.open_target))

if not url.isValid():
msg = urlutils.get_errstring(url, "Invalid link clicked")
message.error(self.win_id, msg)
self.data.open_target = usertypes.ClickTarget.normal
return False

target = self.data.combined_target()

if target == usertypes.ClickTarget.normal:
return
elif target == usertypes.ClickTarget.tab:
win_id = self.win_id
bg_tab = False
elif target == usertypes.ClickTarget.tab_bg:
win_id = self.win_id
bg_tab = True
elif target == usertypes.ClickTarget.window:
from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow()
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)
tabbed_browser.tabopen(url, background=bg_tab)
self.data.open_target = usertypes.ClickTarget.normal

@pyqtSlot(QUrl)
def _on_url_changed(self, url):
"""Update title when URL has changed and no title is available."""
@@ -21,7 +21,7 @@


from qutebrowser.config import config
from qutebrowser.utils import message, log
from qutebrowser.utils import message, log, usertypes


from PyQt5.QtCore import QObject, QEvent, Qt
@@ -85,7 +85,10 @@ def _handle_mouse_press(self, _obj, e):
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
self._mousepress_backforward(e)
return True

self._ignore_wheel_event = True
self._mousepress_opentarget(e)

return False

def _handle_wheel(self, _obj, e):
@@ -131,6 +134,27 @@ def _mousepress_backforward(self, e):
message.error(self._tab.win_id, "At end of history.",
immediately=True)

def _mousepress_opentarget(self, e):
"""Set the open target when something was clicked.
Args:
e: The QMouseEvent.
"""
if e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier:
background_tabs = config.get('tabs', 'background-tabs')
if e.modifiers() & Qt.ShiftModifier:
background_tabs = not background_tabs
if background_tabs:
target = usertypes.ClickTarget.tab_bg
else:
target = usertypes.ClickTarget.tab
self._tab.data.open_target = target
log.mouse.debug("Ctrl/Middle click, setting target: {}".format(
target))
else:
self._tab.data.open_target = usertypes.ClickTarget.normal
log.mouse.debug("Normal click, setting normal target")

def eventFilter(self, obj, event):
"""Filter events going to a QWeb(Engine)View."""
evtype = event.type()
@@ -322,7 +322,7 @@ class WebEngineTab(browsertab.AbstractTab):

def __init__(self, win_id, mode_manager, parent=None):
super().__init__(win_id)
widget = webview.WebEngineView()
widget = webview.WebEngineView(tabdata=self.data)
self.history = WebEngineHistory(self)
self.scroller = WebEngineScroller(self, parent=self)
self.caret = WebEngineCaret(win_id=win_id, mode_manager=mode_manager,
@@ -499,6 +499,7 @@ def _connect_signals(self):
view.urlChanged.connect(self._on_url_changed)
page.loadFinished.connect(self._on_load_finished)
page.certificate_error.connect(self._on_ssl_errors)
page.link_clicked.connect(self._on_link_clicked)
try:
view.iconChanged.connect(self.icon_changed)
except AttributeError:
@@ -20,29 +20,39 @@
"""The main browser widget for QtWebEngine."""


from PyQt5.QtCore import pyqtSignal, Qt, QPoint
from PyQt5.QtCore import pyqtSignal, Qt, QPoint, 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

from qutebrowser.config import config
from qutebrowser.utils import log
from qutebrowser.utils import log, debug, qtutils, usertypes


class WebEngineView(QWebEngineView):

"""Custom QWebEngineView subclass with qutebrowser-specific features."""

def __init__(self, parent=None):
def __init__(self, tabdata, parent=None):
super().__init__(parent)
self.setPage(WebEnginePage(self))
self.setPage(WebEnginePage(tabdata, parent=self))


class WebEnginePage(QWebEnginePage):

"""Custom QWebEnginePage subclass with qutebrowser-specific features."""
"""Custom QWebEnginePage subclass with qutebrowser-specific features.
Signals:
certificate_error: FIXME:qtwebengine
link_clicked: Emitted when a link was clicked on a page.
"""

certificate_error = pyqtSignal()
link_clicked = pyqtSignal(QUrl)

def __init__(self, tabdata, parent=None):
super().__init__(parent)
self._tabdata = tabdata

def certificateError(self, error):
self.certificate_error.emit()
@@ -68,3 +78,31 @@ def createWindow(self, _typ):
"""Handle new windows via JS."""
log.stub()
return None

def acceptNavigationRequest(self,
url: QUrl,
typ: QWebEnginePage.NavigationType,
_is_main_frame: bool):
"""Override acceptNavigationRequest to handle clicked links.
Setting linkDelegationPolicy to DelegateAllLinks and using a slot bound
to linkClicked won't work correctly, because when in a frameset, we
have no idea in which frame the link should be opened.
Checks if it should open it in a tab (middle-click or control) or not,
and then conditionally opens the URL. Opening it in a new tab/window
is handled in the slot connected to link_clicked.
"""
target = self._tabdata.combined_target()
log.webview.debug("navigation request: url {}, type {}, "
"target {}".format(
url.toDisplayString(),
debug.qenum_key(QWebEnginePage, typ),
target))

if typ != QWebEnginePage.NavigationTypeLinkClicked:
return True

self.link_clicked.emit(url)

return url.isValid() and target == usertypes.ClickTarget.normal
@@ -638,3 +638,4 @@ def _connect_signals(self):
page.frameCreated.connect(self._on_frame_created)
frame.contentsSizeChanged.connect(self._on_contents_size_changed)
frame.initialLayoutCompleted.connect(self._on_history_trigger)
page.link_clicked.connect(self._on_link_clicked)
@@ -26,14 +26,14 @@
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtPrintSupport import QPrintDialog
from PyQt5.QtWebKitWidgets import QWebPage
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame

from qutebrowser.config import config
from qutebrowser.browser import pdfjs
from qutebrowser.browser.webkit import http
from qutebrowser.browser.webkit.network import networkmanager
from qutebrowser.utils import (message, usertypes, log, jinja, qtutils, utils,
objreg, debug, urlutils)
objreg, debug)


class BrowserPage(QWebPage):
@@ -42,36 +42,35 @@ class BrowserPage(QWebPage):
Attributes:
error_occurred: Whether an error occurred while loading.
open_target: Where to open the next navigation request.
("normal", "tab", "tab_bg")
_hint_target: Override for open_target while hinting, or None.
_extension_handlers: Mapping of QWebPage extensions to their handlers.
_networkmanager: The NetworkManager used.
_win_id: The window ID this BrowserPage is associated with.
_ignore_load_started: Whether to ignore the next loadStarted signal.
_is_shutting_down: Whether the page is currently shutting down.
_tabdata: The TabData object of the tab this page is in.
Signals:
shutting_down: Emitted when the page is currently shutting down.
reloading: Emitted before a web page reloads.
arg: The URL which gets reloaded.
link_clicked: Emitted when a link was clicked on a page.
"""

shutting_down = pyqtSignal()
reloading = pyqtSignal(QUrl)
link_clicked = pyqtSignal(QUrl)

def __init__(self, win_id, tab_id, parent=None):
def __init__(self, win_id, tab_id, tabdata, parent=None):
super().__init__(parent)
self._win_id = win_id
self._tabdata = tabdata
self._is_shutting_down = False
self._extension_handlers = {
QWebPage.ErrorPageExtension: self._handle_errorpage,
QWebPage.ChooseMultipleFilesExtension: self._handle_multiple_files,
}
self._ignore_load_started = False
self.error_occurred = False
self.open_target = usertypes.ClickTarget.normal
self._hint_target = None
self._networkmanager = networkmanager.NetworkManager(
win_id, tab_id, self)
self.setNetworkAccessManager(self._networkmanager)
@@ -429,14 +428,16 @@ def on_start_hinting(self, hint_target):
Args:
hint_target: A ClickTarget member to set self._hint_target to.
"""
# FIXME move this away
log.webview.debug("Setting force target to {}".format(hint_target))
self._hint_target = hint_target
self._tabdata.hint_target = hint_target

@pyqtSlot()
def on_stop_hinting(self):
"""Emitted when hinting is finished."""
# FIXME move this away
log.webview.debug("Finishing hinting.")
self._hint_target = None
self._tabdata.hint_target = None

def userAgentForUrl(self, url):
"""Override QWebPage::userAgentForUrl to customize the user agent."""
@@ -531,56 +532,34 @@ def shouldInterruptJavaScript(self):
answer = True
return answer

def acceptNavigationRequest(self, _frame, request, typ):
def acceptNavigationRequest(self,
_frame: QWebFrame,
request: QNetworkRequest,
typ: QWebPage.NavigationType):
"""Override acceptNavigationRequest to handle clicked links.
Setting linkDelegationPolicy to DelegateAllLinks and using a slot bound
to linkClicked won't work correctly, because when in a frameset, we
have no idea in which frame the link should be opened.
Checks if it should open it in a tab (middle-click or control) or not,
and then opens the URL.
Args:
_frame: QWebFrame (target frame)
request: QNetworkRequest
typ: QWebPage::NavigationType
and then conditionally opens the URL. Opening it in a new tab/window
is handled in the slot connected to link_clicked.
"""
url = request.url()
urlstr = url.toDisplayString()
target = self._tabdata.combined_target()
log.webview.debug("navigation request: url {}, type {}, "
"target {}".format(
url.toDisplayString(),
debug.qenum_key(QWebPage, typ),
target))

if typ == QWebPage.NavigationTypeReload:
self.reloading.emit(url)
if typ != QWebPage.NavigationTypeLinkClicked:
return True
if not url.isValid():
msg = urlutils.get_errstring(url, "Invalid link clicked")
message.error(self._win_id, msg)
self.open_target = usertypes.ClickTarget.normal
return False
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
log.webview.debug("acceptNavigationRequest, url {}, type {}, hint "
"target {}, open_target {}".format(
urlstr, debug.qenum_key(QWebPage, typ),
self._hint_target, self.open_target))
if self._hint_target is not None:
target = self._hint_target
else:
target = self.open_target
self.open_target = usertypes.ClickTarget.normal
if target == usertypes.ClickTarget.tab:
tabbed_browser.tabopen(url, False)
return False
elif target == usertypes.ClickTarget.tab_bg:
tabbed_browser.tabopen(url, True)
return False
elif target == usertypes.ClickTarget.window:
from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow()
window.show()
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=window.win_id)
tabbed_browser.tabopen(url, False)
return False
else:
elif typ != QWebPage.NavigationTypeLinkClicked:
return True

self.link_clicked.emit(url)

return url.isValid() and target == usertypes.ClickTarget.normal

0 comments on commit 3bffb71

Please sign in to comment.