"Fossies" - the Fresh Open Source Software Archive

Member "eric6-20.9/eric/eric6/WebBrowser/WebBrowserView.py" (1 Aug 2020, 81212 Bytes) of package /linux/misc/eric6-20.9.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "WebBrowserView.py" see the Fossies "Dox" file reference documentation.

    1 # -*- coding: utf-8 -*-
    2 
    3 # Copyright (c) 2008 - 2020 Detlev Offenbach <detlev@die-offenbachs.de>
    4 #
    5 
    6 
    7 """
    8 Module implementing the web browser using QWebEngineView.
    9 """
   10 
   11 
   12 import os
   13 
   14 from PyQt5.QtCore import (
   15     pyqtSignal, pyqtSlot, Qt, QUrl, QFileInfo, QTimer, QEvent, QPoint,
   16     QPointF, QDateTime, QStandardPaths, QByteArray, QIODevice, QDataStream
   17 )
   18 from PyQt5.QtGui import (
   19     QDesktopServices, QClipboard, QIcon, QContextMenuEvent, QPixmap, QCursor
   20 )
   21 from PyQt5.QtWidgets import qApp, QStyle, QMenu, QApplication, QDialog
   22 from PyQt5.QtWebEngineWidgets import (
   23     QWebEngineView, QWebEnginePage, QWebEngineDownloadItem
   24 )
   25 
   26 from E5Gui import E5MessageBox, E5FileDialog
   27 
   28 from WebBrowser.WebBrowserWindow import WebBrowserWindow
   29 from .WebBrowserPage import WebBrowserPage
   30 
   31 from .Tools.WebIconLoader import WebIconLoader
   32 from .Tools import Scripts
   33 
   34 from . import WebInspector
   35 from .Tools.WebBrowserTools import readAllFileContents, pixmapToDataUrl
   36 
   37 import Preferences
   38 import UI.PixmapCache
   39 import Utilities
   40 from Globals import qVersionTuple
   41 
   42 
   43 class WebBrowserView(QWebEngineView):
   44     """
   45     Class implementing the web browser view widget.
   46     
   47     @signal sourceChanged(QUrl) emitted after the current URL has changed
   48     @signal forwardAvailable(bool) emitted after the current URL has changed
   49     @signal backwardAvailable(bool) emitted after the current URL has changed
   50     @signal highlighted(str) emitted, when the mouse hovers over a link
   51     @signal search(QUrl) emitted, when a search is requested
   52     @signal zoomValueChanged(int) emitted to signal a change of the zoom value
   53     @signal faviconChanged() emitted to signal a changed web site icon
   54     @signal safeBrowsingAbort() emitted to indicate an abort due to a safe
   55         browsing event
   56     @signal safeBrowsingBad(threatType, threatMessages) emitted to indicate a
   57         malicious web site as determined by safe browsing
   58     @signal showMessage(str) emitted to show a message in the main window
   59         status bar
   60     """
   61     sourceChanged = pyqtSignal(QUrl)
   62     forwardAvailable = pyqtSignal(bool)
   63     backwardAvailable = pyqtSignal(bool)
   64     highlighted = pyqtSignal(str)
   65     search = pyqtSignal(QUrl)
   66     zoomValueChanged = pyqtSignal(int)
   67     faviconChanged = pyqtSignal()
   68     safeBrowsingAbort = pyqtSignal()
   69     safeBrowsingBad = pyqtSignal(str, str)
   70     showMessage = pyqtSignal(str)
   71     
   72     ZoomLevels = [
   73         30, 40, 50, 67, 80, 90,
   74         100,
   75         110, 120, 133, 150, 170, 200, 220, 233, 250, 270, 285, 300,
   76     ]
   77     ZoomLevelDefault = 100
   78     
   79     def __init__(self, mainWindow, parent=None, name=""):
   80         """
   81         Constructor
   82         
   83         @param mainWindow reference to the main window (WebBrowserWindow)
   84         @param parent parent widget of this window (QWidget)
   85         @param name name of this window (string)
   86         """
   87         super(WebBrowserView, self).__init__(parent)
   88         self.setObjectName(name)
   89         
   90         self.__rwhvqt = None
   91         self.installEventFilter(self)
   92         
   93         self.__speedDial = WebBrowserWindow.speedDial()
   94         
   95         self.__page = None
   96         self.__createNewPage()
   97         
   98         self.__mw = mainWindow
   99         self.__tabWidget = parent
  100         self.__isLoading = False
  101         self.__progress = 0
  102         self.__siteIconLoader = None
  103         self.__siteIcon = QIcon()
  104         self.__menu = QMenu(self)
  105         self.__clickedPos = QPoint()
  106         self.__firstLoad = False
  107         self.__preview = QPixmap()
  108         
  109         self.__currentZoom = 100
  110         self.__zoomLevels = WebBrowserView.ZoomLevels[:]
  111         
  112         self.iconUrlChanged.connect(self.__iconUrlChanged)
  113         self.urlChanged.connect(self.__urlChanged)
  114         self.page().linkHovered.connect(self.__linkHovered)
  115         
  116         self.loadStarted.connect(self.__loadStarted)
  117         self.loadProgress.connect(self.__loadProgress)
  118         self.loadFinished.connect(self.__loadFinished)
  119         self.renderProcessTerminated.connect(self.__renderProcessTerminated)
  120         
  121         self.__mw.openSearchManager().currentEngineChanged.connect(
  122             self.__currentEngineChanged)
  123         
  124         self.setAcceptDrops(True)
  125         
  126         self.__rss = []
  127         
  128         self.__clickedFrame = None
  129         
  130         self.__mw.personalInformationManager().connectPage(self.page())
  131         
  132         self.__inspector = None
  133         WebInspector.registerView(self)
  134         
  135         self.__restoreData = None
  136         
  137         if self.parentWidget() is not None:
  138             self.parentWidget().installEventFilter(self)
  139         
  140         if qVersionTuple() < (5, 11, 0):
  141             lay = self.layout()
  142             lay.currentChanged.connect(
  143                 lambda: QTimer.singleShot(0, self.__setRwhvqt))
  144             self.__setRwhvqt()
  145         
  146         self.grabGesture(Qt.PinchGesture)
  147     
  148     def __createNewPage(self):
  149         """
  150         Private method to create a new page object.
  151         """
  152         self.__page = WebBrowserPage(self)
  153         self.setPage(self.__page)
  154         
  155         self.__page.safeBrowsingAbort.connect(self.safeBrowsingAbort)
  156         self.__page.safeBrowsingBad.connect(self.safeBrowsingBad)
  157         self.__page.printPageRequested.connect(self.__printPage)
  158         try:
  159             self.__page.quotaRequested.connect(self.__quotaRequested)
  160             # The registerProtocolHandlerRequested signal is handled in
  161             # WebBrowserPage.
  162         except AttributeError:
  163             # pre Qt 5.11
  164             pass
  165         try:
  166             self.__page.selectClientCertificate.connect(
  167                 self.__selectClientCertificate)
  168         except AttributeError:
  169             # pre Qt 5.12
  170             pass
  171         try:
  172             from PyQt5.QtWebEngineCore import QWebEngineFindTextResult
  173             # __IGNORE_WARNING__
  174             
  175             self.__page.findTextFinished.connect(
  176                 self.__findTextFinished)
  177         except (AttributeError, ImportError):
  178             # pre Qt 5.14
  179             pass
  180     
  181     def __setRwhvqt(self):
  182         """
  183         Private slot to set widget that receives input events.
  184         """
  185         self.grabGesture(Qt.PinchGesture)
  186         self.__rwhvqt = self.focusProxy()
  187         if self.__rwhvqt:
  188             self.__rwhvqt.grabGesture(Qt.PinchGesture)
  189             self.__rwhvqt.installEventFilter(self)
  190         else:
  191             print("Focus proxy is null!")   # __IGNORE_WARNING_M801__
  192     
  193     def __currentEngineChanged(self):
  194         """
  195         Private slot to track a change of the current search engine.
  196         """
  197         if self.url().toString() == "eric:home":
  198             self.reload()
  199     
  200     def mainWindow(self):
  201         """
  202         Public method to get a reference to the main window.
  203         
  204         @return reference to the main window
  205         @rtype WebBrowserWindow
  206         """
  207         return self.__mw
  208     
  209     def tabWidget(self):
  210         """
  211         Public method to get a reference to the tab widget containing this
  212         view.
  213         
  214         @return reference to the tab widget
  215         @rtype WebBrowserTabWidget
  216         """
  217         return self.__tabWidget
  218     
  219     def load(self, url):
  220         """
  221         Public method to load a web site.
  222         
  223         @param url URL to be loaded
  224         @type QUrl
  225         """
  226         if (
  227             self.__page is not None and
  228             not self.__page.acceptNavigationRequest(
  229                 url, QWebEnginePage.NavigationTypeTyped, True)
  230         ):
  231             return
  232         
  233         super(WebBrowserView, self).load(url)
  234         
  235         if not self.__firstLoad:
  236             self.__firstLoad = True
  237             WebInspector.pushView(self)
  238     
  239     def setSource(self, name, newTab=False):
  240         """
  241         Public method used to set the source to be displayed.
  242         
  243         @param name filename to be shown (QUrl)
  244         @param newTab flag indicating to open the URL in a new tab (bool)
  245         """
  246         if name is None or not name.isValid():
  247             return
  248         
  249         if newTab:
  250             # open in a new tab
  251             self.__mw.newTab(name)
  252             return
  253         
  254         if not name.scheme():
  255             if not os.path.exists(name.toString()):
  256                 name.setScheme(Preferences.getWebBrowser("DefaultScheme"))
  257             else:
  258                 if Utilities.isWindowsPlatform():
  259                     name.setUrl("file:///" + Utilities.fromNativeSeparators(
  260                         name.toString()))
  261                 else:
  262                     name.setUrl("file://" + name.toString())
  263         
  264         if (
  265             len(name.scheme()) == 1 or
  266             name.scheme() == "file"
  267         ):
  268             # name is a local file
  269             if name.scheme() and len(name.scheme()) == 1:
  270                 # it is a local path on win os
  271                 name = QUrl.fromLocalFile(name.toString())
  272             
  273             if not QFileInfo(name.toLocalFile()).exists():
  274                 E5MessageBox.critical(
  275                     self,
  276                     self.tr("eric6 Web Browser"),
  277                     self.tr(
  278                         """<p>The file <b>{0}</b> does not exist.</p>""")
  279                     .format(name.toLocalFile()))
  280                 return
  281 
  282             if name.toLocalFile().lower().endswith((".pdf", ".chm")):
  283                 started = QDesktopServices.openUrl(name)
  284                 if not started:
  285                     E5MessageBox.critical(
  286                         self,
  287                         self.tr("eric6 Web Browser"),
  288                         self.tr(
  289                             """<p>Could not start a viewer"""
  290                             """ for file <b>{0}</b>.</p>""")
  291                         .format(name.path()))
  292                 return
  293         elif name.scheme() in ["mailto"]:
  294             started = QDesktopServices.openUrl(name)
  295             if not started:
  296                 E5MessageBox.critical(
  297                     self,
  298                     self.tr("eric6 Web Browser"),
  299                     self.tr(
  300                         """<p>Could not start an application"""
  301                         """ for URL <b>{0}</b>.</p>""")
  302                     .format(name.toString()))
  303             return
  304         else:
  305             if name.toString().lower().endswith((".pdf", ".chm")):
  306                 started = QDesktopServices.openUrl(name)
  307                 if not started:
  308                     E5MessageBox.critical(
  309                         self,
  310                         self.tr("eric6 Web Browser"),
  311                         self.tr(
  312                             """<p>Could not start a viewer"""
  313                             """ for file <b>{0}</b>.</p>""")
  314                         .format(name.path()))
  315                 return
  316         
  317         self.load(name)
  318 
  319     def source(self):
  320         """
  321         Public method to return the URL of the loaded page.
  322         
  323         @return URL loaded in the help browser (QUrl)
  324         """
  325         return self.url()
  326     
  327     def documentTitle(self):
  328         """
  329         Public method to return the title of the loaded page.
  330         
  331         @return title (string)
  332         """
  333         return self.title()
  334     
  335     def backward(self):
  336         """
  337         Public slot to move backwards in history.
  338         """
  339         self.triggerPageAction(QWebEnginePage.Back)
  340         self.__urlChanged(self.history().currentItem().url())
  341     
  342     def forward(self):
  343         """
  344         Public slot to move forward in history.
  345         """
  346         self.triggerPageAction(QWebEnginePage.Forward)
  347         self.__urlChanged(self.history().currentItem().url())
  348     
  349     def home(self):
  350         """
  351         Public slot to move to the first page loaded.
  352         """
  353         homeUrl = QUrl(Preferences.getWebBrowser("HomePage"))
  354         self.setSource(homeUrl)
  355         self.__urlChanged(self.history().currentItem().url())
  356     
  357     def reload(self):
  358         """
  359         Public slot to reload the current page.
  360         """
  361         self.triggerPageAction(QWebEnginePage.Reload)
  362     
  363     def reloadBypassingCache(self):
  364         """
  365         Public slot to reload the current page bypassing the cache.
  366         """
  367         self.triggerPageAction(QWebEnginePage.ReloadAndBypassCache)
  368     
  369     def copy(self):
  370         """
  371         Public slot to copy the selected text.
  372         """
  373         self.triggerPageAction(QWebEnginePage.Copy)
  374     
  375     def cut(self):
  376         """
  377         Public slot to cut the selected text.
  378         """
  379         self.triggerPageAction(QWebEnginePage.Cut)
  380     
  381     def paste(self):
  382         """
  383         Public slot to paste text from the clipboard.
  384         """
  385         self.triggerPageAction(QWebEnginePage.Paste)
  386     
  387     def undo(self):
  388         """
  389         Public slot to undo the last edit action.
  390         """
  391         self.triggerPageAction(QWebEnginePage.Undo)
  392     
  393     def redo(self):
  394         """
  395         Public slot to redo the last edit action.
  396         """
  397         self.triggerPageAction(QWebEnginePage.Redo)
  398     
  399     def selectAll(self):
  400         """
  401         Public slot to select all text.
  402         """
  403         self.triggerPageAction(QWebEnginePage.SelectAll)
  404     
  405     def unselect(self):
  406         """
  407         Public slot to clear the current selection.
  408         """
  409         try:
  410             self.triggerPageAction(QWebEnginePage.Unselect)
  411         except AttributeError:
  412             # prior to 5.7.0
  413             self.page().runJavaScript(
  414                 "window.getSelection().empty()",
  415                 WebBrowserPage.SafeJsWorld)
  416     
  417     def isForwardAvailable(self):
  418         """
  419         Public method to determine, if a forward move in history is possible.
  420         
  421         @return flag indicating move forward is possible (boolean)
  422         """
  423         return self.history().canGoForward()
  424     
  425     def isBackwardAvailable(self):
  426         """
  427         Public method to determine, if a backwards move in history is possible.
  428         
  429         @return flag indicating move backwards is possible (boolean)
  430         """
  431         return self.history().canGoBack()
  432     
  433     def __levelForZoom(self, zoom):
  434         """
  435         Private method determining the zoom level index given a zoom factor.
  436         
  437         @param zoom zoom factor (integer)
  438         @return index of zoom factor (integer)
  439         """
  440         try:
  441             index = self.__zoomLevels.index(zoom)
  442         except ValueError:
  443             for index in range(len(self.__zoomLevels)):
  444                 if zoom <= self.__zoomLevels[index]:
  445                     break
  446         return index
  447     
  448     def setZoomValue(self, value, saveValue=True):
  449         """
  450         Public method to set the zoom value.
  451         
  452         @param value zoom value (integer)
  453         @keyparam saveValue flag indicating to save the zoom value with the
  454             zoom manager
  455         @type bool
  456         """
  457         if value != self.__currentZoom:
  458             self.setZoomFactor(value / 100.0)
  459             self.__currentZoom = value
  460             if saveValue and not self.__mw.isPrivate():
  461                 from .ZoomManager import ZoomManager
  462                 ZoomManager.instance().setZoomValue(self.url(), value)
  463             self.zoomValueChanged.emit(value)
  464     
  465     def zoomValue(self):
  466         """
  467         Public method to get the current zoom value.
  468         
  469         @return zoom value (integer)
  470         """
  471         val = self.zoomFactor() * 100
  472         return int(val)
  473     
  474     def zoomIn(self):
  475         """
  476         Public slot to zoom into the page.
  477         """
  478         index = self.__levelForZoom(self.__currentZoom)
  479         if index < len(self.__zoomLevels) - 1:
  480             self.setZoomValue(self.__zoomLevels[index + 1])
  481     
  482     def zoomOut(self):
  483         """
  484         Public slot to zoom out of the page.
  485         """
  486         index = self.__levelForZoom(self.__currentZoom)
  487         if index > 0:
  488             self.setZoomValue(self.__zoomLevels[index - 1])
  489     
  490     def zoomReset(self):
  491         """
  492         Public method to reset the zoom factor.
  493         """
  494         index = self.__levelForZoom(WebBrowserView.ZoomLevelDefault)
  495         self.setZoomValue(self.__zoomLevels[index])
  496     
  497     def mapToViewport(self, pos):
  498         """
  499         Public method to map a position to the viewport.
  500         
  501         @param pos position to be mapped
  502         @type QPoint
  503         @return viewport position
  504         @rtype QPoint
  505         """
  506         return self.page().mapToViewport(pos)
  507     
  508     def hasSelection(self):
  509         """
  510         Public method to determine, if there is some text selected.
  511         
  512         @return flag indicating text has been selected (boolean)
  513         """
  514         return self.selectedText() != ""
  515     
  516     def findNextPrev(self, txt, case, backwards, callback):
  517         """
  518         Public slot to find the next occurrence of a text.
  519         
  520         @param txt text to search for (string)
  521         @param case flag indicating a case sensitive search (boolean)
  522         @param backwards flag indicating a backwards search (boolean)
  523         @param callback reference to a function with a bool parameter
  524         @type function(bool) or None
  525         """
  526         findFlags = QWebEnginePage.FindFlags()
  527         if case:
  528             findFlags |= QWebEnginePage.FindCaseSensitively
  529         if backwards:
  530             findFlags |= QWebEnginePage.FindBackward
  531         
  532         if callback is None:
  533             self.findText(txt, findFlags)
  534         else:
  535             self.findText(txt, findFlags, callback)
  536     
  537     def __findTextFinished(self, result):
  538         """
  539         Private slot handling the findTextFinished signal of the web page.
  540         
  541         @param result reference to the QWebEngineFindTextResult object of the
  542             last search
  543         @type QWebEngineFindTextResult
  544         """
  545         self.showMessage.emit(self.tr("Match {0} of {1}").format(
  546             result.activeMatch(), result.numberOfMatches())
  547         )
  548     
  549     def contextMenuEvent(self, evt):
  550         """
  551         Protected method called to create a context menu.
  552         
  553         This method is overridden from QWebEngineView.
  554         
  555         @param evt reference to the context menu event object
  556             (QContextMenuEvent)
  557         """
  558         pos = evt.pos()
  559         reason = evt.reason()
  560         QTimer.singleShot(
  561             0,
  562             lambda: self._contextMenuEvent(QContextMenuEvent(reason, pos)))
  563         # needs to be done this way because contextMenuEvent is blocking
  564         # the main loop
  565     
  566     def _contextMenuEvent(self, evt):
  567         """
  568         Protected method called to create a context menu.
  569         
  570         This method is overridden from QWebEngineView.
  571         
  572         @param evt reference to the context menu event object
  573             (QContextMenuEvent)
  574         """
  575         self.__menu.clear()
  576         
  577         hitTest = self.page().hitTestContent(evt.pos())
  578         
  579         self.__createContextMenu(self.__menu, hitTest)
  580         
  581         if not hitTest.isContentEditable() and not hitTest.isContentSelected():
  582             self.__menu.addSeparator()
  583             self.__menu.addAction(self.__mw.adBlockIcon().menuAction())
  584         
  585         if (
  586             qVersionTuple() >= (5, 11, 0) or
  587             Preferences.getWebBrowser("WebInspectorEnabled")
  588         ):
  589             self.__menu.addSeparator()
  590             self.__menu.addAction(
  591                 UI.PixmapCache.getIcon("webInspector"),
  592                 self.tr("Inspect Element..."), self.__webInspector)
  593         
  594         if not self.__menu.isEmpty():
  595             pos = evt.globalPos()
  596             self.__menu.popup(QPoint(pos.x(), pos.y() + 1))
  597     
  598     def __createContextMenu(self, menu, hitTest):
  599         """
  600         Private method to populate the context menu.
  601         
  602         @param menu reference to the menu to be populated
  603         @type QMenu
  604         @param hitTest reference to the hit test object
  605         @type WebHitTestResult
  606         """
  607         spellCheckActionCount = 0
  608         contextMenuData = self.page().contextMenuData()
  609         hitTest.updateWithContextMenuData(contextMenuData)
  610         
  611         if bool(contextMenuData.misspelledWord()):
  612             boldFont = menu.font()
  613             boldFont.setBold(True)
  614             
  615             for suggestion in contextMenuData.spellCheckerSuggestions():
  616                 act = menu.addAction(suggestion)
  617                 act.setFont(boldFont)
  618                 act.triggered.connect(
  619                     lambda: self.__replaceMisspelledWord)(act)
  620             
  621             if not bool(menu.actions()):
  622                 menu.addAction(self.tr("No suggestions")).setEnabled(False)
  623             
  624             menu.addSeparator()
  625             spellCheckActionCount = len(menu.actions())
  626         
  627         if (
  628             not hitTest.linkUrl().isEmpty() and
  629             hitTest.linkUrl().scheme() != "javascript"
  630         ):
  631             self.__createLinkContextMenu(menu, hitTest)
  632         
  633         if not hitTest.imageUrl().isEmpty():
  634             self.__createImageContextMenu(menu, hitTest)
  635         
  636         if not hitTest.mediaUrl().isEmpty():
  637             self.__createMediaContextMenu(menu, hitTest)
  638         
  639         if hitTest.isContentEditable():
  640             # check, if only spell checker actions were added
  641             if len(menu.actions()) == spellCheckActionCount:
  642                 menu.addAction(self.__mw.undoAct)
  643                 menu.addAction(self.__mw.redoAct)
  644                 menu.addSeparator()
  645                 menu.addAction(self.__mw.cutAct)
  646                 menu.addAction(self.__mw.copyAct)
  647                 menu.addAction(self.__mw.pasteAct)
  648                 menu.addSeparator()
  649                 self.__mw.personalInformationManager().createSubMenu(
  650                     menu, self, hitTest)
  651             
  652             if hitTest.tagName() == "input":
  653                 menu.addSeparator()
  654                 act = menu.addAction("")
  655                 act.setVisible(False)
  656                 self.__checkForForm(act, hitTest.pos())
  657         
  658         if self.selectedText():
  659             self.__createSelectedTextContextMenu(menu, hitTest)
  660         
  661         if self.__menu.isEmpty():
  662             self.__createPageContextMenu(menu)
  663     
  664     def __createLinkContextMenu(self, menu, hitTest):
  665         """
  666         Private method to populate the context menu for URLs.
  667         
  668         @param menu reference to the menu to be populated
  669         @type QMenu
  670         @param hitTest reference to the hit test object
  671         @type WebHitTestResult
  672         """
  673         if not menu.isEmpty():
  674             menu.addSeparator()
  675         
  676         act = menu.addAction(
  677             UI.PixmapCache.getIcon("openNewTab"),
  678             self.tr("Open Link in New Tab\tCtrl+LMB"))
  679         act.setData(hitTest.linkUrl())
  680         act.triggered.connect(
  681             lambda: self.__openLinkInNewTab(act))
  682         act = menu.addAction(
  683             UI.PixmapCache.getIcon("newWindow"),
  684             self.tr("Open Link in New Window"))
  685         act.setData(hitTest.linkUrl())
  686         act.triggered.connect(
  687             lambda: self.__openLinkInNewWindow(act))
  688         act = menu.addAction(
  689             UI.PixmapCache.getIcon("privateMode"),
  690             self.tr("Open Link in New Private Window"))
  691         act.setData(hitTest.linkUrl())
  692         act.triggered.connect(
  693             lambda: self.__openLinkInNewPrivateWindow(act))
  694         menu.addSeparator()
  695         menu.addAction(
  696             UI.PixmapCache.getIcon("download"),
  697             self.tr("Save Lin&k"), self.__downloadLink)
  698         act = menu.addAction(
  699             UI.PixmapCache.getIcon("bookmark22"),
  700             self.tr("Bookmark this Link"))
  701         act.setData(hitTest.linkUrl())
  702         act.triggered.connect(
  703             lambda: self.__bookmarkLink(act))
  704         menu.addSeparator()
  705         act = menu.addAction(
  706             UI.PixmapCache.getIcon("editCopy"),
  707             self.tr("Copy Link to Clipboard"))
  708         act.setData(hitTest.linkUrl())
  709         act.triggered.connect(
  710             lambda: self.__copyLink(act))
  711         act = menu.addAction(
  712             UI.PixmapCache.getIcon("mailSend"),
  713             self.tr("Send Link"))
  714         act.setData(hitTest.linkUrl())
  715         act.triggered.connect(
  716             lambda: self.__sendLink(act))
  717         if (
  718             Preferences.getWebBrowser("VirusTotalEnabled") and
  719             Preferences.getWebBrowser("VirusTotalServiceKey") != ""
  720         ):
  721             act = menu.addAction(
  722                 UI.PixmapCache.getIcon("virustotal"),
  723                 self.tr("Scan Link with VirusTotal"))
  724             act.setData(hitTest.linkUrl())
  725             act.triggered.connect(
  726                 lambda: self.__virusTotal(act))
  727         
  728     def __createImageContextMenu(self, menu, hitTest):
  729         """
  730         Private method to populate the context menu for images.
  731         
  732         @param menu reference to the menu to be populated
  733         @type QMenu
  734         @param hitTest reference to the hit test object
  735         @type WebHitTestResult
  736         """
  737         if not menu.isEmpty():
  738             menu.addSeparator()
  739         
  740         act = menu.addAction(
  741             UI.PixmapCache.getIcon("openNewTab"),
  742             self.tr("Open Image in New Tab"))
  743         act.setData(hitTest.imageUrl())
  744         act.triggered.connect(
  745             lambda: self.__openLinkInNewTab(act))
  746         menu.addSeparator()
  747         menu.addAction(
  748             UI.PixmapCache.getIcon("download"),
  749             self.tr("Save Image"), self.__downloadImage)
  750         menu.addAction(
  751             self.tr("Copy Image to Clipboard"), self.__copyImage)
  752         act = menu.addAction(
  753             UI.PixmapCache.getIcon("editCopy"),
  754             self.tr("Copy Image Location to Clipboard"))
  755         act.setData(hitTest.imageUrl())
  756         act.triggered.connect(
  757             lambda: self.__copyLink(act))
  758         act = menu.addAction(
  759             UI.PixmapCache.getIcon("mailSend"),
  760             self.tr("Send Image Link"))
  761         act.setData(hitTest.imageUrl())
  762         act.triggered.connect(
  763             lambda: self.__sendLink(act))
  764         
  765         if hitTest.imageUrl().scheme() in ["http", "https"]:
  766             menu.addSeparator()
  767             engine = WebBrowserWindow.imageSearchEngine()
  768             searchEngineName = engine.searchEngine()
  769             act = menu.addAction(
  770                 UI.PixmapCache.getIcon("{0}".format(
  771                     searchEngineName.lower())),
  772                 self.tr("Search image in {0}").format(searchEngineName))
  773             act.setData(engine.getSearchQuery(hitTest.imageUrl()))
  774             act.triggered.connect(
  775                 lambda: self.__searchImage(act))
  776             self.__imageSearchMenu = menu.addMenu(
  777                 self.tr("Search image with..."))
  778             for searchEngineName in engine.searchEngineNames():
  779                 act = self.__imageSearchMenu.addAction(
  780                     UI.PixmapCache.getIcon("{0}".format(
  781                         searchEngineName.lower())),
  782                     self.tr("Search image in {0}").format(searchEngineName))
  783                 act.setData(engine.getSearchQuery(
  784                     hitTest.imageUrl(), searchEngineName))
  785                 act.triggered.connect(
  786                     lambda: self.__searchImage(act))
  787         
  788         menu.addSeparator()
  789         act = menu.addAction(
  790             UI.PixmapCache.getIcon("adBlockPlus"),
  791             self.tr("Block Image"))
  792         act.setData(hitTest.imageUrl().toString())
  793         act.triggered.connect(
  794             lambda: self.__blockImage(act))
  795         if (
  796             Preferences.getWebBrowser("VirusTotalEnabled") and
  797             Preferences.getWebBrowser("VirusTotalServiceKey") != ""
  798         ):
  799             act = menu.addAction(
  800                 UI.PixmapCache.getIcon("virustotal"),
  801                 self.tr("Scan Image with VirusTotal"))
  802             act.setData(hitTest.imageUrl())
  803             act.triggered.connect(
  804                 lambda: self.__virusTotal(act))
  805     
  806     def __createMediaContextMenu(self, menu, hitTest):
  807         """
  808         Private method to populate the context menu for media elements.
  809         
  810         @param menu reference to the menu to be populated
  811         @type QMenu
  812         @param hitTest reference to the hit test object
  813         @type WebHitTestResult
  814         """
  815         if not menu.isEmpty():
  816             menu.addSeparator()
  817         
  818         if hitTest.mediaPaused():
  819             menu.addAction(
  820                 UI.PixmapCache.getIcon("mediaPlaybackStart"),
  821                 self.tr("Play"), self.__pauseMedia)
  822         else:
  823             menu.addAction(
  824                 UI.PixmapCache.getIcon("mediaPlaybackPause"),
  825                 self.tr("Pause"), self.__pauseMedia)
  826         if hitTest.mediaMuted():
  827             menu.addAction(
  828                 UI.PixmapCache.getIcon("audioVolumeHigh"),
  829                 self.tr("Unmute"), self.__muteMedia)
  830         else:
  831             menu.addAction(
  832                 UI.PixmapCache.getIcon("audioVolumeMuted"),
  833                 self.tr("Mute"), self.__muteMedia)
  834         menu.addSeparator()
  835         act = menu.addAction(
  836             UI.PixmapCache.getIcon("editCopy"),
  837             self.tr("Copy Media Address to Clipboard"))
  838         act.setData(hitTest.mediaUrl())
  839         act.triggered.connect(
  840             lambda: self.__copyLink(act))
  841         act = menu.addAction(
  842             UI.PixmapCache.getIcon("mailSend"),
  843             self.tr("Send Media Address"))
  844         act.setData(hitTest.mediaUrl())
  845         act.triggered.connect(
  846             lambda: self.__sendLink(act))
  847         menu.addAction(
  848             UI.PixmapCache.getIcon("download"),
  849             self.tr("Save Media"), self.__downloadMedia)
  850     
  851     def __createSelectedTextContextMenu(self, menu, hitTest):
  852         """
  853         Private method to populate the context menu for selected text.
  854         
  855         @param menu reference to the menu to be populated
  856         @type QMenu
  857         @param hitTest reference to the hit test object
  858         @type WebHitTestResult
  859         """
  860         if not menu.isEmpty():
  861             menu.addSeparator()
  862         
  863         menu.addAction(self.__mw.copyAct)
  864         menu.addSeparator()
  865         act = menu.addAction(
  866             UI.PixmapCache.getIcon("mailSend"),
  867             self.tr("Send Text"))
  868         act.setData(self.selectedText())
  869         act.triggered.connect(
  870             lambda: self.__sendLink(act))
  871         
  872         engineName = self.__mw.openSearchManager().currentEngineName()
  873         if engineName:
  874             menu.addAction(self.tr("Search with '{0}'").format(engineName),
  875                            self.__searchDefaultRequested)
  876         
  877         from .OpenSearch.OpenSearchEngineAction import (
  878             OpenSearchEngineAction
  879         )
  880         
  881         self.__searchMenu = menu.addMenu(self.tr("Search with..."))
  882         engineNames = self.__mw.openSearchManager().allEnginesNames()
  883         for engineName in engineNames:
  884             engine = self.__mw.openSearchManager().engine(engineName)
  885             act = OpenSearchEngineAction(engine, self.__searchMenu)
  886             act.setData(engineName)
  887             self.__searchMenu.addAction(act)
  888         self.__searchMenu.triggered.connect(self.__searchRequested)
  889         
  890         menu.addSeparator()
  891         
  892         from .WebBrowserLanguagesDialog import WebBrowserLanguagesDialog
  893         languages = Preferences.toList(
  894             Preferences.Prefs.settings.value(
  895                 "WebBrowser/AcceptLanguages",
  896                 WebBrowserLanguagesDialog.defaultAcceptLanguages()))
  897         if languages:
  898             language = languages[0]
  899             langCode = language.split("[")[1][:2]
  900             googleTranslatorUrl = QUrl(
  901                 "http://translate.google.com/#auto/{0}/{1}".format(
  902                     langCode, self.selectedText()))
  903             act = menu.addAction(
  904                 UI.PixmapCache.getIcon("translate"),
  905                 self.tr("Google Translate"))
  906             act.setData(googleTranslatorUrl)
  907             act.triggered.connect(
  908                 lambda: self.__openLinkInNewTab(act))
  909             wiktionaryUrl = QUrl(
  910                 "http://{0}.wiktionary.org/wiki/Special:Search?search={1}"
  911                 .format(langCode, self.selectedText()))
  912             act = menu.addAction(
  913                 UI.PixmapCache.getIcon("wikipedia"),
  914                 self.tr("Dictionary"))
  915             act.setData(wiktionaryUrl)
  916             act.triggered.connect(
  917                 lambda: self.__openLinkInNewTab(act))
  918             menu.addSeparator()
  919         
  920         guessedUrl = QUrl.fromUserInput(self.selectedText().strip())
  921         if self.__isUrlValid(guessedUrl):
  922             act = menu.addAction(self.tr("Go to web address"))
  923             act.setData(guessedUrl)
  924             act.triggered.connect(
  925                 lambda: self.__openLinkInNewTab(act))
  926     
  927     def __createPageContextMenu(self, menu):
  928         """
  929         Private method to populate the basic context menu.
  930         
  931         @param menu reference to the menu to be populated
  932         @type QMenu
  933         """
  934         menu.addAction(self.__mw.newTabAct)
  935         menu.addAction(self.__mw.newAct)
  936         menu.addSeparator()
  937         if self.__mw.saveAsAct is not None:
  938             menu.addAction(self.__mw.saveAsAct)
  939         menu.addAction(self.__mw.saveVisiblePageScreenAct)
  940         menu.addSeparator()
  941         
  942         if self.url().toString() == "eric:speeddial":
  943             # special menu for the spedd dial page
  944             menu.addAction(self.__mw.backAct)
  945             menu.addAction(self.__mw.forwardAct)
  946             menu.addSeparator()
  947             menu.addAction(
  948                 UI.PixmapCache.getIcon("plus"),
  949                 self.tr("Add New Page"), self.__addSpeedDial)
  950             menu.addAction(
  951                 UI.PixmapCache.getIcon("preferences-general"),
  952                 self.tr("Configure Speed Dial"), self.__configureSpeedDial)
  953             menu.addSeparator()
  954             menu.addAction(
  955                 UI.PixmapCache.getIcon("reload"),
  956                 self.tr("Reload All Dials"), self.__reloadAllSpeedDials)
  957             menu.addSeparator()
  958             menu.addAction(
  959                 self.tr("Reset to Default Dials"), self.__resetSpeedDials)
  960             return
  961         
  962         menu.addAction(
  963             UI.PixmapCache.getIcon("bookmark22"),
  964             self.tr("Bookmark this Page"), self.addBookmark)
  965         act = menu.addAction(
  966             UI.PixmapCache.getIcon("editCopy"),
  967             self.tr("Copy Page Link"))
  968         act.setData(self.url())
  969         act.triggered.connect(
  970             lambda: self.__copyLink(act))
  971         act = menu.addAction(
  972             UI.PixmapCache.getIcon("mailSend"),
  973             self.tr("Send Page Link"))
  974         act.setData(self.url())
  975         act.triggered.connect(
  976             lambda: self.__sendLink(act))
  977         menu.addSeparator()
  978         
  979         from .UserAgent.UserAgentMenu import UserAgentMenu
  980         self.__userAgentMenu = UserAgentMenu(self.tr("User Agent"),
  981                                              url=self.url())
  982         menu.addMenu(self.__userAgentMenu)
  983         menu.addSeparator()
  984         menu.addAction(self.__mw.backAct)
  985         menu.addAction(self.__mw.forwardAct)
  986         menu.addAction(self.__mw.homeAct)
  987         menu.addAction(self.__mw.reloadAct)
  988         menu.addAction(self.__mw.stopAct)
  989         menu.addSeparator()
  990         menu.addAction(self.__mw.zoomInAct)
  991         menu.addAction(self.__mw.zoomResetAct)
  992         menu.addAction(self.__mw.zoomOutAct)
  993         menu.addSeparator()
  994         menu.addAction(self.__mw.selectAllAct)
  995         menu.addSeparator()
  996         menu.addAction(self.__mw.findAct)
  997         menu.addSeparator()
  998         menu.addAction(self.__mw.pageSourceAct)
  999         menu.addSeparator()
 1000         menu.addAction(self.__mw.siteInfoAct)
 1001         if self.url().scheme() in ["http", "https"]:
 1002             menu.addSeparator()
 1003             
 1004             w3url = QUrl.fromEncoded(
 1005                 b"http://validator.w3.org/check?uri=" +
 1006                 QUrl.toPercentEncoding(bytes(self.url().toEncoded()).decode()))
 1007             act = menu.addAction(
 1008                 UI.PixmapCache.getIcon("w3"),
 1009                 self.tr("Validate Page"))
 1010             act.setData(w3url)
 1011             act.triggered.connect(
 1012                 lambda: self.__openLinkInNewTab(act))
 1013             
 1014             from .WebBrowserLanguagesDialog import WebBrowserLanguagesDialog
 1015             languages = Preferences.toList(
 1016                 Preferences.Prefs.settings.value(
 1017                     "WebBrowser/AcceptLanguages",
 1018                     WebBrowserLanguagesDialog.defaultAcceptLanguages()))
 1019             if languages:
 1020                 language = languages[0]
 1021                 langCode = language.split("[")[1][:2]
 1022                 googleTranslatorUrl = QUrl.fromEncoded(
 1023                     b"http://translate.google.com/translate?sl=auto&tl=" +
 1024                     langCode.encode() +
 1025                     b"&u=" +
 1026                     QUrl.toPercentEncoding(
 1027                         bytes(self.url().toEncoded()).decode()))
 1028                 act = menu.addAction(
 1029                     UI.PixmapCache.getIcon("translate"),
 1030                     self.tr("Google Translate"))
 1031                 act.setData(googleTranslatorUrl)
 1032             act.triggered.connect(
 1033                 lambda: self.__openLinkInNewTab(act))
 1034     
 1035     def __checkForForm(self, act, pos):
 1036         """
 1037         Private method to check the given position for an open search form.
 1038         
 1039         @param act reference to the action to be populated upon success
 1040         @type QAction
 1041         @param pos position to be tested
 1042         @type QPoint
 1043         """
 1044         self.__clickedPos = self.mapToViewport(pos)
 1045         
 1046         from .Tools import Scripts
 1047         script = Scripts.getFormData(self.__clickedPos)
 1048         self.page().runJavaScript(
 1049             script,
 1050             WebBrowserPage.SafeJsWorld,
 1051             lambda res: self.__checkForFormCallback(res, act))
 1052     
 1053     def __checkForFormCallback(self, res, act):
 1054         """
 1055         Private method handling the __checkForForm result.
 1056         
 1057         @param res result dictionary generated by JavaScript
 1058         @type dict
 1059         @param act reference to the action to be populated upon success
 1060         @type QAction
 1061         """
 1062         if act is None or not bool(res):
 1063             return
 1064         
 1065         url = QUrl(res["action"])
 1066         method = res["method"]
 1067         
 1068         if not url.isEmpty() and method in ["get", "post"]:
 1069             act.setVisible(True)
 1070             act.setText(self.tr("Add to web search toolbar"))
 1071             act.triggered.connect(self.__addSearchEngine)
 1072     
 1073     def __isUrlValid(self, url):
 1074         """
 1075         Private method to check a URL for validity.
 1076         
 1077         @param url URL to be checked (QUrl)
 1078         @return flag indicating a valid URL (boolean)
 1079         """
 1080         return (
 1081             url.isValid() and
 1082             bool(url.host()) and
 1083             bool(url.scheme()) and
 1084             "." in url.host()
 1085         )
 1086     
 1087     def __replaceMisspelledWord(self, act):
 1088         """
 1089         Private slot to replace a misspelled word under the context menu.
 1090         
 1091         @param act reference to the action that triggered
 1092         @type QAction
 1093         """
 1094         suggestion = act.text()
 1095         self.page().replaceMisspelledWord(suggestion)
 1096     
 1097     def __openLinkInNewTab(self, act):
 1098         """
 1099         Private method called by the context menu to open a link in a new
 1100         tab.
 1101         
 1102         @param act reference to the action that triggered
 1103         @type QAction
 1104         """
 1105         url = act.data()
 1106         if url.isEmpty():
 1107             return
 1108         
 1109         self.setSource(url, newTab=True)
 1110     
 1111     def __openLinkInNewWindow(self, act):
 1112         """
 1113         Private slot called by the context menu to open a link in a new
 1114         window.
 1115         
 1116         @param act reference to the action that triggered
 1117         @type QAction
 1118         """
 1119         url = act.data()
 1120         if url.isEmpty():
 1121             return
 1122         
 1123         self.__mw.newWindow(url)
 1124     
 1125     def __openLinkInNewPrivateWindow(self, act):
 1126         """
 1127         Private slot called by the context menu to open a link in a new
 1128         private window.
 1129         
 1130         @param act reference to the action that triggered
 1131         @type QAction
 1132         """
 1133         url = act.data()
 1134         if url.isEmpty():
 1135             return
 1136         
 1137         self.__mw.newPrivateWindow(url)
 1138     
 1139     def __bookmarkLink(self, act):
 1140         """
 1141         Private slot to bookmark a link via the context menu.
 1142         
 1143         @param act reference to the action that triggered
 1144         @type QAction
 1145         """
 1146         url = act.data()
 1147         if url.isEmpty():
 1148             return
 1149         
 1150         from .Bookmarks.AddBookmarkDialog import AddBookmarkDialog
 1151         dlg = AddBookmarkDialog()
 1152         dlg.setUrl(bytes(url.toEncoded()).decode())
 1153         dlg.exec_()
 1154     
 1155     def __sendLink(self, act):
 1156         """
 1157         Private slot to send a link via email.
 1158         
 1159         @param act reference to the action that triggered
 1160         @type QAction
 1161         """
 1162         data = act.data()
 1163         if isinstance(data, QUrl) and data.isEmpty():
 1164             return
 1165         
 1166         if isinstance(data, QUrl):
 1167             data = data.toString()
 1168         QDesktopServices.openUrl(QUrl("mailto:?body=" + data))
 1169     
 1170     def __copyLink(self, act):
 1171         """
 1172         Private slot to copy a link to the clipboard.
 1173         
 1174         @param act reference to the action that triggered
 1175         @type QAction
 1176         """
 1177         data = act.data()
 1178         if isinstance(data, QUrl) and data.isEmpty():
 1179             return
 1180         
 1181         if isinstance(data, QUrl):
 1182             data = data.toString()
 1183         QApplication.clipboard().setText(data)
 1184     
 1185     def __downloadLink(self):
 1186         """
 1187         Private slot to download a link and save it to disk.
 1188         """
 1189         self.triggerPageAction(QWebEnginePage.DownloadLinkToDisk)
 1190     
 1191     def __downloadImage(self):
 1192         """
 1193         Private slot to download an image and save it to disk.
 1194         """
 1195         self.triggerPageAction(QWebEnginePage.DownloadImageToDisk)
 1196     
 1197     def __copyImage(self):
 1198         """
 1199         Private slot to copy an image to the clipboard.
 1200         """
 1201         self.triggerPageAction(QWebEnginePage.CopyImageToClipboard)
 1202     
 1203     def __blockImage(self, act):
 1204         """
 1205         Private slot to add a block rule for an image URL.
 1206         
 1207         @param act reference to the action that triggered
 1208         @type QAction
 1209         """
 1210         url = act.data()
 1211         dlg = WebBrowserWindow.adBlockManager().showDialog()
 1212         dlg.addCustomRule(url)
 1213     
 1214     def __searchImage(self, act):
 1215         """
 1216         Private slot to search for an image URL.
 1217         
 1218         @param act reference to the action that triggered
 1219         @type QAction
 1220         """
 1221         url = act.data()
 1222         self.setSource(url, newTab=True)
 1223     
 1224     def __downloadMedia(self):
 1225         """
 1226         Private slot to download a media and save it to disk.
 1227         """
 1228         self.triggerPageAction(QWebEnginePage.DownloadMediaToDisk)
 1229     
 1230     def __pauseMedia(self):
 1231         """
 1232         Private slot to pause or play the selected media.
 1233         """
 1234         self.triggerPageAction(QWebEnginePage.ToggleMediaPlayPause)
 1235     
 1236     def __muteMedia(self):
 1237         """
 1238         Private slot to (un)mute the selected media.
 1239         """
 1240         self.triggerPageAction(QWebEnginePage.ToggleMediaMute)
 1241     
 1242     def __virusTotal(self, act):
 1243         """
 1244         Private slot to scan the selected URL with VirusTotal.
 1245         
 1246         @param act reference to the action that triggered
 1247         @type QAction
 1248         """
 1249         url = act.data()
 1250         self.__mw.requestVirusTotalScan(url)
 1251     
 1252     def __searchDefaultRequested(self):
 1253         """
 1254         Private slot to search for some text with the current search engine.
 1255         """
 1256         searchText = self.selectedText()
 1257         
 1258         if not searchText:
 1259             return
 1260         
 1261         engine = self.__mw.openSearchManager().currentEngine()
 1262         if engine:
 1263             self.search.emit(engine.searchUrl(searchText))
 1264     
 1265     def __searchRequested(self, act):
 1266         """
 1267         Private slot to search for some text with a selected search engine.
 1268         
 1269         @param act reference to the action that triggered this slot (QAction)
 1270         """
 1271         searchText = self.selectedText()
 1272         
 1273         if not searchText:
 1274             return
 1275         
 1276         engineName = act.data()
 1277         if engineName:
 1278             engine = self.__mw.openSearchManager().engine(engineName)
 1279         else:
 1280             engine = self.__mw.openSearchManager().currentEngine()
 1281         if engine:
 1282             self.search.emit(engine.searchUrl(searchText))
 1283     
 1284     def __addSearchEngine(self):
 1285         """
 1286         Private slot to add a new search engine.
 1287         """
 1288         from .Tools import Scripts
 1289         script = Scripts.getFormData(self.__clickedPos)
 1290         self.page().runJavaScript(
 1291             script,
 1292             WebBrowserPage.SafeJsWorld,
 1293             lambda res: self.__mw.openSearchManager().addEngineFromForm(
 1294                 res, self))
 1295     
 1296     def __webInspector(self):
 1297         """
 1298         Private slot to show the web inspector window.
 1299         """
 1300         from .WebInspector import WebInspector
 1301         if WebInspector.isEnabled():
 1302             if self.__inspector is None:
 1303                 self.__inspector = WebInspector()
 1304                 self.__inspector.setView(self, True)
 1305                 self.__inspector.inspectorClosed.connect(
 1306                     self.closeWebInspector)
 1307                 self.__inspector.show()
 1308             else:
 1309                 self.closeWebInspector()
 1310     
 1311     def closeWebInspector(self):
 1312         """
 1313         Public slot to close the web inspector.
 1314         """
 1315         if self.__inspector is not None:
 1316             if self.__inspector.isVisible():
 1317                 self.__inspector.hide()
 1318             WebInspector.unregisterView(self.__inspector)
 1319             self.__inspector.deleteLater()
 1320             self.__inspector = None
 1321     
 1322     def addBookmark(self):
 1323         """
 1324         Public slot to bookmark the current page.
 1325         """
 1326         from .Tools import Scripts
 1327         script = Scripts.getAllMetaAttributes()
 1328         self.page().runJavaScript(
 1329             script, WebBrowserPage.SafeJsWorld, self.__addBookmarkCallback)
 1330     
 1331     def __addBookmarkCallback(self, res):
 1332         """
 1333         Private callback method of __addBookmark().
 1334         
 1335         @param res reference to the result list containing all
 1336             meta attributes
 1337         @type list
 1338         """
 1339         description = ""
 1340         for meta in res:
 1341             if meta["name"] == "description":
 1342                 description = meta["content"]
 1343         
 1344         from .Bookmarks.AddBookmarkDialog import AddBookmarkDialog
 1345         dlg = AddBookmarkDialog()
 1346         dlg.setUrl(bytes(self.url().toEncoded()).decode())
 1347         dlg.setTitle(self.title())
 1348         dlg.setDescription(description)
 1349         dlg.exec_()
 1350     
 1351     def dragEnterEvent(self, evt):
 1352         """
 1353         Protected method called by a drag enter event.
 1354         
 1355         @param evt reference to the drag enter event (QDragEnterEvent)
 1356         """
 1357         evt.acceptProposedAction()
 1358     
 1359     def dragMoveEvent(self, evt):
 1360         """
 1361         Protected method called by a drag move event.
 1362         
 1363         @param evt reference to the drag move event (QDragMoveEvent)
 1364         """
 1365         evt.ignore()
 1366         if evt.source() != self:
 1367             if len(evt.mimeData().urls()) > 0:
 1368                 evt.acceptProposedAction()
 1369             else:
 1370                 url = QUrl(evt.mimeData().text())
 1371                 if url.isValid():
 1372                     evt.acceptProposedAction()
 1373         
 1374         if not evt.isAccepted():
 1375             super(WebBrowserView, self).dragMoveEvent(evt)
 1376     
 1377     def dropEvent(self, evt):
 1378         """
 1379         Protected method called by a drop event.
 1380         
 1381         @param evt reference to the drop event (QDropEvent)
 1382         """
 1383         super(WebBrowserView, self).dropEvent(evt)
 1384         if (
 1385             not evt.isAccepted() and
 1386             evt.source() != self and
 1387             evt.possibleActions() & Qt.CopyAction
 1388         ):
 1389             url = QUrl()
 1390             if len(evt.mimeData().urls()) > 0:
 1391                 url = evt.mimeData().urls()[0]
 1392             if not url.isValid():
 1393                 url = QUrl(evt.mimeData().text())
 1394             if url.isValid():
 1395                 self.setSource(url)
 1396                 evt.acceptProposedAction()
 1397     
 1398     def _mousePressEvent(self, evt):
 1399         """
 1400         Protected method called by a mouse press event.
 1401         
 1402         @param evt reference to the mouse event (QMouseEvent)
 1403         """
 1404         if WebBrowserWindow.autoScroller().mousePress(self, evt):
 1405             evt.accept()
 1406             return
 1407         
 1408         self.__mw.setEventMouseButtons(evt.buttons())
 1409         self.__mw.setEventKeyboardModifiers(evt.modifiers())
 1410         
 1411         if evt.button() == Qt.XButton1:
 1412             self.pageAction(QWebEnginePage.Back).trigger()
 1413             evt.accept()
 1414         elif evt.button() == Qt.XButton2:
 1415             self.pageAction(QWebEnginePage.Forward).trigger()
 1416             evt.accept()
 1417     
 1418     def _mouseReleaseEvent(self, evt):
 1419         """
 1420         Protected method called by a mouse release event.
 1421         
 1422         @param evt reference to the mouse event (QMouseEvent)
 1423         """
 1424         if WebBrowserWindow.autoScroller().mouseRelease(evt):
 1425             evt.accept()
 1426             return
 1427         
 1428         accepted = evt.isAccepted()
 1429         self.__page.event(evt)
 1430         if (
 1431             not evt.isAccepted() and
 1432             self.__mw.eventMouseButtons() & Qt.MidButton
 1433         ):
 1434             url = QUrl(QApplication.clipboard().text(QClipboard.Selection))
 1435             if (
 1436                 not url.isEmpty() and
 1437                 url.isValid() and
 1438                 url.scheme() != ""
 1439             ):
 1440                 self.__mw.setEventMouseButtons(Qt.NoButton)
 1441                 self.__mw.setEventKeyboardModifiers(Qt.NoModifier)
 1442                 self.setSource(url)
 1443         evt.setAccepted(accepted)
 1444     
 1445     def _mouseMoveEvent(self, evt):
 1446         """
 1447         Protected method to handle mouse move events.
 1448         
 1449         @param evt reference to the mouse event (QMouseEvent)
 1450         """
 1451         if self.__mw and self.__mw.isFullScreen():
 1452             if self.__mw.isFullScreenNavigationVisible():
 1453                 self.__mw.hideFullScreenNavigation()
 1454             elif evt.y() < 10:
 1455                 # mouse is within 10px to the top
 1456                 self.__mw.showFullScreenNavigation()
 1457         
 1458         if WebBrowserWindow.autoScroller().mouseMove(evt):
 1459             evt.accept()
 1460     
 1461     def _wheelEvent(self, evt):
 1462         """
 1463         Protected method to handle wheel events.
 1464         
 1465         @param evt reference to the wheel event (QWheelEvent)
 1466         """
 1467         if WebBrowserWindow.autoScroller().wheel():
 1468             evt.accept()
 1469             return
 1470         
 1471         delta = evt.angleDelta().y()
 1472         if evt.modifiers() & Qt.ControlModifier:
 1473             if delta < 0:
 1474                 self.zoomOut()
 1475             elif delta > 0:
 1476                 self.zoomIn()
 1477             evt.accept()
 1478         
 1479         elif evt.modifiers() & Qt.ShiftModifier:
 1480             if delta < 0:
 1481                 self.backward()
 1482             elif delta > 0:
 1483                 self.forward()
 1484             evt.accept()
 1485     
 1486     def _keyPressEvent(self, evt):
 1487         """
 1488         Protected method called by a key press.
 1489         
 1490         @param evt reference to the key event (QKeyEvent)
 1491         """
 1492         if self.__mw.personalInformationManager().viewKeyPressEvent(self, evt):
 1493             evt.accept()
 1494             return
 1495         
 1496         if evt.key() == Qt.Key_ZoomIn:
 1497             self.zoomIn()
 1498             evt.accept()
 1499         elif evt.key() == Qt.Key_ZoomOut:
 1500             self.zoomOut()
 1501             evt.accept()
 1502         elif evt.key() == Qt.Key_Plus:
 1503             if evt.modifiers() & Qt.ControlModifier:
 1504                 self.zoomIn()
 1505                 evt.accept()
 1506         elif evt.key() == Qt.Key_Minus:
 1507             if evt.modifiers() & Qt.ControlModifier:
 1508                 self.zoomOut()
 1509                 evt.accept()
 1510         elif evt.key() == Qt.Key_0:
 1511             if evt.modifiers() & Qt.ControlModifier:
 1512                 self.zoomReset()
 1513                 evt.accept()
 1514         elif evt.key() == Qt.Key_M:
 1515             if evt.modifiers() & Qt.ControlModifier:
 1516                 self.__muteMedia()
 1517                 evt.accept()
 1518         elif evt.key() == Qt.Key_Backspace:
 1519             pos = QCursor.pos()
 1520             pos = self.mapFromGlobal(pos)
 1521             hitTest = self.page().hitTestContent(pos)
 1522             if not hitTest.isContentEditable():
 1523                 self.pageAction(QWebEnginePage.Back).trigger()
 1524                 evt.accept()
 1525     
 1526     def _keyReleaseEvent(self, evt):
 1527         """
 1528         Protected method called by a key release.
 1529         
 1530         @param evt reference to the key event (QKeyEvent)
 1531         """
 1532         if evt.key() == Qt.Key_Escape:
 1533             if self.isFullScreen():
 1534                 self.triggerPageAction(QWebEnginePage.ExitFullScreen)
 1535                 evt.accept()
 1536                 self.requestFullScreen(False)
 1537     
 1538     def _gestureEvent(self, evt):
 1539         """
 1540         Protected method handling gesture events.
 1541         
 1542         @param evt reference to the gesture event (QGestureEvent
 1543         """
 1544         pinch = evt.gesture(Qt.PinchGesture)
 1545         if pinch:
 1546             if pinch.state() == Qt.GestureStarted:
 1547                 pinch.setTotalScaleFactor(self.__currentZoom / 100.0)
 1548             elif pinch.state() == Qt.GestureUpdated:
 1549                 scaleFactor = pinch.totalScaleFactor()
 1550                 self.setZoomValue(int(scaleFactor * 100))
 1551             evt.accept()
 1552     
 1553     def eventFilter(self, obj, evt):
 1554         """
 1555         Public method to process event for other objects.
 1556         
 1557         @param obj reference to object to process events for
 1558         @type QObject
 1559         @param evt reference to event to be processed
 1560         @type QEvent
 1561         @return flag indicating that the event should be filtered out
 1562         @rtype bool
 1563         """
 1564         if (
 1565             obj is self and
 1566             evt.type() == QEvent.ParentChange and
 1567             self.parentWidget() is not None
 1568         ):
 1569             self.parentWidget().installEventFilter(self)
 1570         
 1571         # find the render widget receiving events for the web page
 1572         if obj is self and evt.type() == QEvent.ChildAdded:
 1573             if qVersionTuple() >= (5, 11, 0):
 1574                 QTimer.singleShot(0, self.__setRwhvqt)
 1575         
 1576         # forward events to WebBrowserView
 1577         if (
 1578             obj is self.__rwhvqt and
 1579             evt.type() in [QEvent.KeyPress, QEvent.KeyRelease,
 1580                            QEvent.MouseButtonPress, QEvent.MouseButtonRelease,
 1581                            QEvent.MouseMove, QEvent.Wheel, QEvent.Gesture]
 1582         ):
 1583             wasAccepted = evt.isAccepted()
 1584             evt.setAccepted(False)
 1585             if evt.type() == QEvent.KeyPress:
 1586                 self._keyPressEvent(evt)
 1587             elif evt.type() == QEvent.KeyRelease:
 1588                 self._keyReleaseEvent(evt)
 1589             elif evt.type() == QEvent.MouseButtonPress:
 1590                 self._mousePressEvent(evt)
 1591             elif evt.type() == QEvent.MouseButtonRelease:
 1592                 self._mouseReleaseEvent(evt)
 1593             elif evt.type() == QEvent.MouseMove:
 1594                 self._mouseMoveEvent(evt)
 1595             elif evt.type() == QEvent.Wheel:
 1596                 self._wheelEvent(evt)
 1597             elif evt.type() == QEvent.Gesture:
 1598                 self._gestureEvent(evt)
 1599             ret = evt.isAccepted()
 1600             evt.setAccepted(wasAccepted)
 1601             return ret
 1602         
 1603         if (
 1604             obj is self.parentWidget() and
 1605             evt.type() in [QEvent.KeyPress, QEvent.KeyRelease]
 1606         ):
 1607             wasAccepted = evt.isAccepted()
 1608             evt.setAccepted(False)
 1609             if evt.type() == QEvent.KeyPress:
 1610                 self._keyPressEvent(evt)
 1611             elif evt.type() == QEvent.KeyRelease:
 1612                 self._keyReleaseEvent(evt)
 1613             ret = evt.isAccepted()
 1614             evt.setAccepted(wasAccepted)
 1615             return ret
 1616         
 1617         # block already handled events
 1618         if obj is self:
 1619             if evt.type() in [QEvent.KeyPress, QEvent.KeyRelease,
 1620                               QEvent.MouseButtonPress,
 1621                               QEvent.MouseButtonRelease,
 1622                               QEvent.MouseMove, QEvent.Wheel, QEvent.Gesture]:
 1623                 return True
 1624             
 1625             elif evt.type() == QEvent.Hide:
 1626                 if self.isFullScreen():
 1627                     self.triggerPageAction(QWebEnginePage.ExitFullScreen)
 1628         
 1629         return super(WebBrowserView, self).eventFilter(obj, evt)
 1630     
 1631     def event(self, evt):
 1632         """
 1633         Public method handling events.
 1634         
 1635         @param evt reference to the event (QEvent)
 1636         @return flag indicating, if the event was handled (boolean)
 1637         """
 1638         if evt.type() == QEvent.Gesture:
 1639             self._gestureEvent(evt)
 1640             return True
 1641         
 1642         return super(WebBrowserView, self).event(evt)
 1643     
 1644     def inputWidget(self):
 1645         """
 1646         Public method to get a reference to the render widget.
 1647         
 1648         @return reference to the render widget
 1649         @rtype QWidget
 1650         """
 1651         return self.__rwhvqt
 1652     
 1653     def clearHistory(self):
 1654         """
 1655         Public slot to clear the history.
 1656         """
 1657         self.history().clear()
 1658         self.__urlChanged(self.history().currentItem().url())
 1659     
 1660     ###########################################################################
 1661     ## Signal converters below
 1662     ###########################################################################
 1663     
 1664     def __urlChanged(self, url):
 1665         """
 1666         Private slot to handle the urlChanged signal.
 1667         
 1668         @param url the new url (QUrl)
 1669         """
 1670         self.sourceChanged.emit(url)
 1671         
 1672         self.forwardAvailable.emit(self.isForwardAvailable())
 1673         self.backwardAvailable.emit(self.isBackwardAvailable())
 1674     
 1675     def __iconUrlChanged(self, url):
 1676         """
 1677         Private slot to handle the iconUrlChanged signal.
 1678         
 1679         @param url URL to get web site icon from
 1680         @type QUrl
 1681         """
 1682         self.__siteIcon = QIcon()
 1683         if self.__siteIconLoader is not None:
 1684             self.__siteIconLoader.deleteLater()
 1685         self.__siteIconLoader = WebIconLoader(url, self)
 1686         self.__siteIconLoader.iconLoaded.connect(self.__iconLoaded)
 1687     
 1688     def __iconLoaded(self, icon):
 1689         """
 1690         Private slot handling the loaded web site icon.
 1691         
 1692         @param icon web site icon
 1693         @type QIcon
 1694         """
 1695         self.__siteIcon = icon
 1696         
 1697         from .Tools import WebIconProvider
 1698         WebIconProvider.instance().saveIcon(self)
 1699         
 1700         self.faviconChanged.emit()
 1701     
 1702     def icon(self):
 1703         """
 1704         Public method to get the web site icon.
 1705         
 1706         @return web site icon
 1707         @rtype QIcon
 1708         """
 1709         if not self.__siteIcon.isNull():
 1710             return QIcon(self.__siteIcon)
 1711         
 1712         from .Tools import WebIconProvider
 1713         return WebIconProvider.instance().iconForUrl(self.url())
 1714     
 1715     def title(self):
 1716         """
 1717         Public method to get the view title.
 1718         
 1719         @return view title
 1720         @rtype str
 1721         """
 1722         titleStr = super(WebBrowserView, self).title()
 1723         if not titleStr:
 1724             if self.url().isEmpty():
 1725                 url = self.__page.requestedUrl()
 1726             else:
 1727                 url = self.url()
 1728             
 1729             titleStr = url.host()
 1730             if not titleStr:
 1731                 titleStr = url.toString(QUrl.RemoveFragment)
 1732             
 1733         if not titleStr or titleStr == "about:blank":
 1734             titleStr = self.tr("Empty Page")
 1735         
 1736         return titleStr
 1737     
 1738     def __linkHovered(self, link):
 1739         """
 1740         Private slot to handle the linkHovered signal.
 1741         
 1742         @param link the URL of the link (string)
 1743         """
 1744         self.highlighted.emit(link)
 1745     
 1746     ###########################################################################
 1747     ## Signal handlers below
 1748     ###########################################################################
 1749     
 1750     def __renderProcessTerminated(self, status, exitCode):
 1751         """
 1752         Private slot handling a crash of the web page render process.
 1753         
 1754         @param status termination status
 1755         @type QWebEnginePage.RenderProcessTerminationStatus
 1756         @param exitCode exit code of the process
 1757         @type int
 1758         """
 1759         if status == QWebEnginePage.NormalTerminationStatus:
 1760             return
 1761         
 1762         QTimer.singleShot(0, lambda: self.__showTabCrashPage(status))
 1763     
 1764     def __showTabCrashPage(self, status):
 1765         """
 1766         Private slot to show the tab crash page.
 1767         
 1768         @param status termination status
 1769         @type QWebEnginePage.RenderProcessTerminationStatus
 1770         """
 1771         self.page().deleteLater()
 1772         self.__createNewPage()
 1773         
 1774         html = readAllFileContents(":/html/tabCrashPage.html")
 1775         html = html.replace("@IMAGE@", pixmapToDataUrl(
 1776             qApp.style().standardIcon(QStyle.SP_MessageBoxWarning).pixmap(
 1777                 48, 48)).toString())
 1778         html = html.replace("@FAVICON@", pixmapToDataUrl(
 1779             qApp.style() .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(
 1780                 16, 16)).toString())
 1781         html = html.replace(
 1782             "@TITLE@", self.tr("Render Process terminated abnormally"))
 1783         html = html.replace(
 1784             "@H1@", self.tr("Render Process terminated abnormally"))
 1785         if status == QWebEnginePage.CrashedTerminationStatus:
 1786             msg = self.tr("The render process crashed while"
 1787                           " loading this page.")
 1788         elif status == QWebEnginePage.KilledTerminationStatus:
 1789             msg = self.tr("The render process was killed.")
 1790         else:
 1791             msg = self.tr("The render process terminated while"
 1792                           " loading this page.")
 1793         html = html.replace("@LI-1@", msg)
 1794         html = html.replace(
 1795             "@LI-2@",
 1796             self.tr(
 1797                 "Try reloading the page or closing some tabs to make more"
 1798                 " memory available."))
 1799         self.page().setHtml(html, self.url())
 1800     
 1801     def __loadStarted(self):
 1802         """
 1803         Private method to handle the loadStarted signal.
 1804         """
 1805         # reset search
 1806         self.findText("")
 1807         self.__isLoading = True
 1808         self.__progress = 0
 1809     
 1810     def __loadProgress(self, progress):
 1811         """
 1812         Private method to handle the loadProgress signal.
 1813         
 1814         @param progress progress value (integer)
 1815         """
 1816         self.__progress = progress
 1817     
 1818     def __loadFinished(self, ok):
 1819         """
 1820         Private method to handle the loadFinished signal.
 1821         
 1822         @param ok flag indicating the result (boolean)
 1823         """
 1824         self.__isLoading = False
 1825         self.__progress = 0
 1826         
 1827         QApplication.processEvents()
 1828         QTimer.singleShot(200, self.__renderPreview)
 1829         
 1830         from .ZoomManager import ZoomManager
 1831         zoomValue = ZoomManager.instance().zoomValue(self.url())
 1832         self.setZoomValue(zoomValue)
 1833         
 1834         if ok:
 1835             self.__mw.historyManager().addHistoryEntry(self)
 1836             self.__mw.adBlockManager().page().hideBlockedPageEntries(
 1837                 self.page())
 1838             self.__mw.passwordManager().completePage(self.page())
 1839             
 1840             self.page().runJavaScript(
 1841                 "document.lastModified", WebBrowserPage.SafeJsWorld,
 1842                 lambda res: self.__adjustBookmark(res))
 1843     
 1844     def __adjustBookmark(self, lastModified):
 1845         """
 1846         Private slot to adjust the 'lastModified' value of bookmarks.
 1847         
 1848         @param lastModified last modified value
 1849         @type str
 1850         """
 1851         modified = QDateTime.fromString(lastModified, "MM/dd/yyyy hh:mm:ss")
 1852         if modified.isValid():
 1853             from WebBrowser.WebBrowserWindow import WebBrowserWindow
 1854             from .Bookmarks.BookmarkNode import BookmarkNode
 1855             manager = WebBrowserWindow.bookmarksManager()
 1856             for bookmark in manager.bookmarksForUrl(self.url()):
 1857                 manager.setTimestamp(bookmark, BookmarkNode.TsModified,
 1858                                      modified)
 1859     
 1860     def isLoading(self):
 1861         """
 1862         Public method to get the loading state.
 1863         
 1864         @return flag indicating the loading state (boolean)
 1865         """
 1866         return self.__isLoading
 1867     
 1868     def progress(self):
 1869         """
 1870         Public method to get the load progress.
 1871         
 1872         @return load progress (integer)
 1873         """
 1874         return self.__progress
 1875     
 1876     def __renderPreview(self):
 1877         """
 1878         Private slot to render a preview pixmap after the page was loaded.
 1879         """
 1880         from .WebBrowserSnap import renderTabPreview
 1881         w = 600     # some default width, the preview gets scaled when shown
 1882         h = int(w * self.height() / self.width())
 1883         self.__preview = renderTabPreview(self, w, h)
 1884     
 1885     def getPreview(self):
 1886         """
 1887         Public method to get the preview pixmap.
 1888         
 1889         @return preview pixmap
 1890         @rtype QPixmap
 1891         """
 1892         return self.__preview
 1893     
 1894     def saveAs(self):
 1895         """
 1896         Public method to save the current page to a file.
 1897         """
 1898         url = self.url()
 1899         if url.isEmpty():
 1900             return
 1901         
 1902         fileName, savePageFormat = self.__getSavePageFileNameAndFormat()
 1903         if fileName:
 1904             self.page().save(fileName, savePageFormat)
 1905     
 1906     def __getSavePageFileNameAndFormat(self):
 1907         """
 1908         Private method to get the file name to save the page to.
 1909         
 1910         @return tuple containing the file name to save to and the
 1911             save page format
 1912         @rtype tuple of (str, QWebEngineDownloadItem.SavePageFormat)
 1913         """
 1914         documentLocation = QStandardPaths.writableLocation(
 1915             QStandardPaths.DocumentsLocation)
 1916         filterList = [
 1917             self.tr("Web Archive (*.mhtml *.mht)"),
 1918             self.tr("HTML File (*.html *.htm)"),
 1919             self.tr("HTML File with all resources (*.html *.htm)"),
 1920         ]
 1921         extensionsList = [
 1922             # tuple of extensions for *nix and Windows
 1923             # keep in sync with filters list
 1924             (".mhtml", ".mht"),
 1925             (".html", ".htm"),
 1926             (".html", ".htm"),
 1927         ]
 1928         if self.url().fileName():
 1929             defaultFileName = os.path.join(documentLocation,
 1930                                            self.url().fileName())
 1931         else:
 1932             defaultFileName = os.path.join(documentLocation,
 1933                                            self.page().title())
 1934             if Utilities.isWindowsPlatform():
 1935                 defaultFileName += ".mht"
 1936             else:
 1937                 defaultFileName += ".mhtml"
 1938 
 1939         fileName = ""
 1940         saveFormat = QWebEngineDownloadItem.MimeHtmlSaveFormat
 1941         
 1942         fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
 1943             None,
 1944             self.tr("Save Web Page"),
 1945             defaultFileName,
 1946             ";;".join(filterList),
 1947             None)
 1948         if fileName:
 1949             index = filterList.index(selectedFilter)
 1950             if index == 0:
 1951                 saveFormat = QWebEngineDownloadItem.MimeHtmlSaveFormat
 1952             elif index == 1:
 1953                 saveFormat = QWebEngineDownloadItem.SingleHtmlSaveFormat
 1954             else:
 1955                 saveFormat = QWebEngineDownloadItem.CompleteHtmlSaveFormat
 1956             
 1957             extension = os.path.splitext(fileName)[1]
 1958             if not extension:
 1959                 # add the platform specific default extension
 1960                 if Utilities.isWindowsPlatform():
 1961                     extensionsIndex = 1
 1962                 else:
 1963                     extensionsIndex = 0
 1964                 extensions = extensionsList[index]
 1965                 fileName += extensions[extensionsIndex]
 1966         
 1967         return fileName, saveFormat
 1968     
 1969     ###########################################################################
 1970     ## Miscellaneous methods below
 1971     ###########################################################################
 1972     
 1973     def createWindow(self, windowType):
 1974         """
 1975         Public method called, when a new window should be created.
 1976         
 1977         @param windowType type of the requested window
 1978             (QWebEnginePage.WebWindowType)
 1979         @return reference to the created browser window (WebBrowserView)
 1980         """
 1981         if windowType in [QWebEnginePage.WebBrowserTab,
 1982                           QWebEnginePage.WebDialog]:
 1983             return self.__mw.newTab(addNextTo=self)
 1984         elif windowType == QWebEnginePage.WebBrowserWindow:
 1985             return self.__mw.newWindow().currentBrowser()
 1986         else:
 1987             return self.__mw.newTab(addNextTo=self, background=True)
 1988     
 1989     def preferencesChanged(self):
 1990         """
 1991         Public method to indicate a change of the settings.
 1992         """
 1993         self.reload()
 1994     
 1995     ###########################################################################
 1996     ## RSS related methods below
 1997     ###########################################################################
 1998     
 1999     def checkRSS(self):
 2000         """
 2001         Public method to check, if the loaded page contains feed links.
 2002         
 2003         @return flag indicating the existence of feed links (boolean)
 2004         """
 2005         self.__rss = []
 2006         
 2007         script = Scripts.getFeedLinks()
 2008         feeds = self.page().execJavaScript(script)
 2009         
 2010         if feeds is not None:
 2011             for feed in feeds:
 2012                 if feed["url"] and feed["title"]:
 2013                     self.__rss.append((feed["title"], feed["url"]))
 2014         
 2015         return len(self.__rss) > 0
 2016     
 2017     def getRSS(self):
 2018         """
 2019         Public method to get the extracted RSS feeds.
 2020         
 2021         @return list of RSS feeds (list of tuples of two strings)
 2022         """
 2023         return self.__rss
 2024     
 2025     def hasRSS(self):
 2026         """
 2027         Public method to check, if the loaded page has RSS links.
 2028         
 2029         @return flag indicating the presence of RSS links (boolean)
 2030         """
 2031         return len(self.__rss) > 0
 2032     
 2033     ###########################################################################
 2034     ## Full Screen handling below
 2035     ###########################################################################
 2036     
 2037     def isFullScreen(self):
 2038         """
 2039         Public method to check, if full screen mode is active.
 2040         
 2041         @return flag indicating full screen mode
 2042         @rtype bool
 2043         """
 2044         return self.__mw.isFullScreen()
 2045     
 2046     def requestFullScreen(self, enable):
 2047         """
 2048         Public method to request full screen mode.
 2049         
 2050         @param enable flag indicating full screen mode on or off
 2051         @type bool
 2052         """
 2053         if enable:
 2054             self.__mw.enterHtmlFullScreen()
 2055         else:
 2056             self.__mw.showNormal()
 2057     
 2058     ###########################################################################
 2059     ## Speed Dial slots below
 2060     ###########################################################################
 2061     
 2062     def __addSpeedDial(self):
 2063         """
 2064         Private slot to add a new speed dial.
 2065         """
 2066         self.__page.runJavaScript("addSpeedDial();",
 2067                                   WebBrowserPage.SafeJsWorld)
 2068     
 2069     def __configureSpeedDial(self):
 2070         """
 2071         Private slot to configure the speed dial.
 2072         """
 2073         self.page().runJavaScript("configureSpeedDial();",
 2074                                   WebBrowserPage.SafeJsWorld)
 2075     
 2076     def __reloadAllSpeedDials(self):
 2077         """
 2078         Private slot to reload all speed dials.
 2079         """
 2080         self.page().runJavaScript("reloadAll();", WebBrowserPage.SafeJsWorld)
 2081     
 2082     def __resetSpeedDials(self):
 2083         """
 2084         Private slot to reset all speed dials to the default pages.
 2085         """
 2086         self.__speedDial.resetDials()
 2087     
 2088     ###########################################################################
 2089     ## Methods below implement session related functions
 2090     ###########################################################################
 2091     
 2092     def storeSessionData(self, data):
 2093         """
 2094         Public method to store session data to be restored later on.
 2095         
 2096         @param data dictionary with session data to be restored
 2097         @type dict
 2098         """
 2099         self.__restoreData = data
 2100     
 2101     def __showEventSlot(self):
 2102         """
 2103         Private slot to perform actions when the view is shown and the event
 2104         loop is running.
 2105         """
 2106         if self.__restoreData:
 2107             sessionData, self.__restoreData = self.__restoreData, None
 2108             self.loadFromSessionData(sessionData)
 2109     
 2110     def showEvent(self, evt):
 2111         """
 2112         Protected method to handle show events.
 2113         
 2114         @param evt reference to the show event object
 2115         @type QShowEvent
 2116         """
 2117         super(WebBrowserView, self).showEvent(evt)
 2118         self.activateSession()
 2119     
 2120     def activateSession(self):
 2121         """
 2122         Public slot to activate a restored session.
 2123         """
 2124         if self.__restoreData and not self.__mw.isClosing():
 2125             QTimer.singleShot(0, self.__showEventSlot)
 2126     
 2127     def getSessionData(self):
 2128         """
 2129         Public method to populate the session data.
 2130         
 2131         @return dictionary containing the session data
 2132         @rtype dict
 2133         """
 2134         if self.__restoreData:
 2135             # page has not been shown yet
 2136             return self.__restoreData
 2137         
 2138         sessionData = {}
 2139         page = self.page()
 2140         
 2141         # 1. zoom factor
 2142         sessionData["ZoomFactor"] = page.zoomFactor()
 2143         
 2144         # 2. scroll position
 2145         scrollPos = page.scrollPosition()
 2146         sessionData["ScrollPosition"] = {
 2147             "x": scrollPos.x(),
 2148             "y": scrollPos.y(),
 2149         }
 2150         
 2151         # 3. page history
 2152         historyArray = QByteArray()
 2153         stream = QDataStream(historyArray, QIODevice.WriteOnly)
 2154         stream << page.history()
 2155         sessionData["History"] = str(
 2156             historyArray.toBase64(QByteArray.Base64UrlEncoding),
 2157             encoding="ascii")
 2158         sessionData["HistoryIndex"] = page.history().currentItemIndex()
 2159         
 2160         # 4. current URL and title
 2161         sessionData["Url"] = self.url().toString()
 2162         sessionData["Title"] = self.title()
 2163         
 2164         # 5. web icon
 2165         iconArray = QByteArray()
 2166         stream = QDataStream(iconArray, QIODevice.WriteOnly)
 2167         stream << page.icon()
 2168         sessionData["Icon"] = str(iconArray.toBase64(), encoding="ascii")
 2169         
 2170         return sessionData
 2171     
 2172     def loadFromSessionData(self, sessionData):
 2173         """
 2174         Public method to load the session data.
 2175         
 2176         @param sessionData dictionary containing the session data as
 2177             generated by getSessionData()
 2178         @type dict
 2179         """
 2180         page = self.page()
 2181         # blank the page
 2182         page.setUrl(QUrl("about:blank"))
 2183         
 2184         # 1. page history
 2185         if "History" in sessionData:
 2186             historyArray = QByteArray.fromBase64(
 2187                 sessionData["History"].encode("ascii"),
 2188                 QByteArray.Base64UrlEncoding)
 2189             stream = QDataStream(historyArray, QIODevice.ReadOnly)
 2190             stream >> page.history()
 2191             
 2192             if "HistoryIndex" in sessionData:
 2193                 item = page.history().itemAt(sessionData["HistoryIndex"])
 2194                 if item is not None:
 2195                     page.history().goToItem(item)
 2196         
 2197         # 2. zoom factor
 2198         if "ZoomFactor" in sessionData:
 2199             page.setZoomFactor(sessionData["ZoomFactor"])
 2200         
 2201         # 3. scroll position
 2202         if "ScrollPosition" in sessionData:
 2203             scrollPos = sessionData["ScrollPosition"]
 2204             page.scrollTo(QPointF(scrollPos["x"], scrollPos["y"]))
 2205     
 2206     def extractSessionMetaData(self, sessionData):
 2207         """
 2208         Public method to extract some session meta data elements needed by the
 2209         tab widget in case of deferred loading.
 2210         
 2211         @param sessionData dictionary containing the session data as
 2212             generated by getSessionData()
 2213         @type dict
 2214         @return tuple containing the title, URL and web icon
 2215         @rtype tuple of (str, str, QIcon)
 2216         """
 2217         if "Title" in sessionData:
 2218             title = sessionData["Title"]
 2219         else:
 2220             title = ""
 2221         
 2222         if "Url" in sessionData:
 2223             urlStr = sessionData["Url"]
 2224         else:
 2225             urlStr = ""
 2226         
 2227         if "Icon" in sessionData:
 2228             iconArray = QByteArray.fromBase64(
 2229                 sessionData["Icon"].encode("ascii"))
 2230             stream = QDataStream(iconArray, QIODevice.ReadOnly)
 2231             icon = QIcon()
 2232             stream >> icon
 2233         else:
 2234             from .Tools import WebIconProvider
 2235             icon = WebIconProvider.instance().iconForUrl(
 2236                 QUrl.fromUserInput(urlStr))
 2237         
 2238         return title, urlStr, icon
 2239     
 2240     ###########################################################################
 2241     ## Methods below implement safe browsing related functions
 2242     ###########################################################################
 2243     
 2244     def getSafeBrowsingStatus(self):
 2245         """
 2246         Public method to get the safe browsing status of the current page.
 2247         
 2248         @return flag indicating a safe site
 2249         @rtype bool
 2250         """
 2251         if self.__page:
 2252             return self.__page.getSafeBrowsingStatus()
 2253         else:
 2254             return True
 2255     
 2256     ###########################################################################
 2257     ## Methods below implement print support from the page
 2258     ###########################################################################
 2259     
 2260     @pyqtSlot()
 2261     def __printPage(self):
 2262         """
 2263         Private slot to support printing from the web page.
 2264         """
 2265         self.__mw.tabWidget.printBrowser(browser=self)
 2266     
 2267     ###########################################################################
 2268     ## Methods below implement slots for Qt 5.11+
 2269     ###########################################################################
 2270     
 2271     if qVersionTuple() >= (5, 11, 0):
 2272         @pyqtSlot("QWebEngineQuotaRequest")
 2273         def __quotaRequested(self, quotaRequest):
 2274             """
 2275             Private slot to handle quota requests of the web page.
 2276             
 2277             @param quotaRequest reference to the quota request object
 2278             @type QWebEngineQuotaRequest
 2279             """
 2280             acceptRequest = Preferences.getWebBrowser("AcceptQuotaRequest")
 2281             # map yes/no/ask from (0, 1, 2)
 2282             if acceptRequest == 0:
 2283                 # always yes
 2284                 ok = True
 2285             elif acceptRequest == 1:
 2286                 # always no
 2287                 ok = False
 2288             else:
 2289                 # ask user
 2290                 from .Download.DownloadUtilities import dataString
 2291                 sizeStr = dataString(quotaRequest.requestedSize())
 2292                 
 2293                 ok = E5MessageBox.yesNo(
 2294                     self,
 2295                     self.tr("Quota Request"),
 2296                     self.tr("""<p> Allow the website at <b>{0}</b> to use"""
 2297                             """ <b>{1}</b> of persistent storage?</p>""")
 2298                     .format(quotaRequest.origin().host(), sizeStr)
 2299                 )
 2300             
 2301             if ok:
 2302                 quotaRequest.accept()
 2303             else:
 2304                 quotaRequest.reject()
 2305     
 2306     ###########################################################################
 2307     ## Methods below implement slots for Qt 5.12+
 2308     ###########################################################################
 2309     
 2310     if qVersionTuple() >= (5, 12, 0):
 2311         @pyqtSlot("QWebEngineClientCertificateSelection")
 2312         def __selectClientCertificate(self, clientCertificateSelection):
 2313             """
 2314             Private slot to handle the client certificate selection request.
 2315             
 2316             @param clientCertificateSelection list of client SSL certificates
 2317                 found in system's client certificate store
 2318             @type QWebEngineClientCertificateSelection
 2319             """
 2320             certificates = clientCertificateSelection.certificates()
 2321             if len(certificates) == 0:
 2322                 clientCertificateSelection.selectNone()
 2323             elif len(certificates) == 1:
 2324                 clientCertificateSelection.select(certificates[0])
 2325             else:
 2326                 certificate = None
 2327                 from E5Network.E5SslCertificateSelectionDialog import (
 2328                     E5SslCertificateSelectionDialog
 2329                 )
 2330                 dlg = E5SslCertificateSelectionDialog(certificates, self)
 2331                 if dlg.exec_() == QDialog.Accepted:
 2332                     certificate = dlg.getSelectedCertificate()
 2333                 
 2334                 if certificate is None:
 2335                     clientCertificateSelection.selectNone()
 2336                 else:
 2337                     clientCertificateSelection.select(certificate)