WebBrowser/WebBrowserPage.py

Thu, 04 Feb 2016 19:39:54 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 04 Feb 2016 19:39:54 +0100
branch
QtWebEngine
changeset 4715
79009bc4acd5
parent 4710
370a38e03efe
child 4717
5841f229baf7
permissions
-rw-r--r--

Continued getting the basic web browser functions going.

# -*- coding: utf-8 -*-

# Copyright (c) 2008 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
#


"""
Module implementing the helpbrowser using QWebView.
"""

from __future__ import unicode_literals
try:
    str = unicode
except NameError:
    pass

from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QT_TRANSLATE_NOOP, \
    QUrl, QBuffer, QIODevice, QFileInfo, Qt, QTimer, QEvent, \
    QRect, QFile, QPoint, QByteArray, qVersion
from PyQt5.QtGui import QDesktopServices, QClipboard, QMouseEvent, QColor, \
    QPalette
from PyQt5.QtWidgets import qApp, QStyle, QMenu, QApplication, QInputDialog, \
    QLineEdit, QLabel, QToolTip, QFrame, QDialog
from PyQt5.QtPrintSupport import QPrinter, QPrintDialog
##from PyQt5.QtWebKit import QWebSettings
##from PyQt5.QtWebKitWidgets import QWebView, QWebPage
from PyQt5.QtWebEngineWidgets import QWebEnginePage
try:
    from PyQt5.QtWebKit import QWebElement
except ImportError:
    pass
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
import sip

from E5Gui import E5MessageBox, E5FileDialog

import WebBrowser
import WebBrowser.WebBrowserWindow

import Preferences
import UI.PixmapCache
import Globals

try:
    from PyQt5.QtNetwork import QSslCertificate
    SSL_AVAILABLE = True
except ImportError:
    SSL_AVAILABLE = False

###############################################################################
##
##
##class JavaScriptExternalObject(QObject):
##    """
##    Class implementing an external javascript object to add search providers.
##    """
##    def __init__(self, mw, parent=None):
##        """
##        Constructor
##        
##        @param mw reference to the main window 8HelpWindow)
##        @param parent reference to the parent object (QObject)
##        """
##        super(JavaScriptExternalObject, self).__init__(parent)
##        
##        self.__mw = mw
##    
##    @pyqtSlot(str)
##    def AddSearchProvider(self, url):
##        """
##        Public slot to add a search provider.
##        
##        @param url url of the XML file defining the search provider (string)
##        """
##        self.__mw.openSearchManager().addEngine(QUrl(url))
##
##
##class LinkedResource(object):
##    """
##    Class defining a data structure for linked resources.
##    """
##    def __init__(self):
##        """
##        Constructor
##        """
##        self.rel = ""
##        self.type_ = ""
##        self.href = ""
##        self.title = ""
##
###############################################################################
##
##
##class JavaScriptEricObject(QObject):
##    """
##    Class implementing an external javascript object to search via the
##    startpage.
##    """
##    # these must be in line with the strings used by the javascript part of
##    # the start page
##    translations = [
##        QT_TRANSLATE_NOOP("JavaScriptEricObject",
##                          "Welcome to eric6 Web Browser!"),
##        QT_TRANSLATE_NOOP("JavaScriptEricObject", "eric6 Web Browser"),
##        QT_TRANSLATE_NOOP("JavaScriptEricObject", "Search!"),
##        QT_TRANSLATE_NOOP("JavaScriptEricObject", "About eric6"),
##    ]
##    
##    def __init__(self, mw, parent=None):
##        """
##        Constructor
##        
##        @param mw reference to the main window 8HelpWindow)
##        @param parent reference to the parent object (QObject)
##        """
##        super(JavaScriptEricObject, self).__init__(parent)
##        
##        self.__mw = mw
##    
##    @pyqtSlot(str, result=str)
##    def translate(self, trans):
##        """
##        Public method to translate the given string.
##        
##        @param trans string to be translated (string)
##        @return translation (string)
##        """
##        if trans == "QT_LAYOUT_DIRECTION":
##            # special handling to detect layout direction
##            if qApp.isLeftToRight():
##                return "LTR"
##            else:
##                return "RTL"
##        
##        return self.tr(trans)
##    
##    @pyqtSlot(result=str)
##    def providerString(self):
##        """
##        Public method to get a string for the search provider.
##        
##        @return string for the search provider (string)
##        """
##        return self.tr("Search results provided by {0}")\
##            .format(self.__mw.openSearchManager().currentEngineName())
##    
##    @pyqtSlot(str, result=str)
##    def searchUrl(self, searchStr):
##        """
##        Public method to get the search URL for the given search term.
##        
##        @param searchStr search term (string)
##        @return search URL (string)
##        """
##        return bytes(
##            self.__mw.openSearchManager().currentEngine()
##            .searchUrl(searchStr).toEncoded()).decode()
##
###############################################################################


class WebBrowserPage(QWebEnginePage):
    """
    Class implementing an enhanced web page.
    """
##    _webPluginFactory = None
##    
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent parent widget of this window (QWidget)
        """
        super(WebBrowserPage, self).__init__(parent)
        
##        self.setPluginFactory(self.webPluginFactory())
##        
##        self.__lastRequest = None
##        self.__lastRequestType = QWebPage.NavigationTypeOther
##        
##        import Helpviewer.HelpWindow
##        from .Network.NetworkAccessManagerProxy import \
##            NetworkAccessManagerProxy
##        self.__proxy = NetworkAccessManagerProxy(self)
##        self.__proxy.setWebPage(self)
##        self.__proxy.setPrimaryNetworkAccessManager(
##            Helpviewer.HelpWindow.HelpWindow.networkAccessManager())
##        self.setNetworkAccessManager(self.__proxy)
##        
##        self.__sslConfiguration = None
##        self.__proxy.finished.connect(self.__managerFinished)
##        
        self.__adBlockedEntries = []
        self.loadStarted.connect(self.__loadStarted)
##        
##        self.saveFrameStateRequested.connect(
##            self.__saveFrameStateRequested)
##        self.restoreFrameStateRequested.connect(
##            self.__restoreFrameStateRequested)
        self.featurePermissionRequested.connect(
            self.__featurePermissionRequested)
    
    def acceptNavigationRequest(self, url, type_, isMainFrame):
        """
        Protected method to determine, if a request may be accepted.
        
        @param url URL to navigate to
        @type QUrl
        @param type_ type of the navigation request
        @type QWebEnginePage.NavigationType
        @param isMainFrame flag indicating, that the request originated from
            the main frame
        @type bool
        @return flag indicating acceptance
        @rtype bool
        """
##        self.__lastRequest = request
##        if self.__lastRequest.url() != request.url() or \
##           type_ != QWebPage.NavigationTypeOther:
##            self.__lastRequestType = type_
        
        # TODO: Qt 5.6: move to handleUnknownProtocol
        scheme = url.scheme()
        if scheme == "mailto":
            QDesktopServices.openUrl(url)
            return False
        
        # AdBlock
        if url.scheme() == "abp":
            if WebBrowser.WebBrowserWindow.WebBrowserWindow.adBlockManager()\
                    .addSubscriptionFromUrl(url):
                return False
##        
##        if type_ == QWebPage.NavigationTypeFormResubmitted:
##            res = E5MessageBox.yesNo(
##                self.view(),
##                self.tr("Resending POST request"),
##                self.tr(
##                    """In order to display the site, the request along with"""
##                    """ all the data must be sent once again, which may lead"""
##                    """ to some unexpected behaviour of the site e.g. the"""
##                    """ same action might be performed once again. Do you"""
##                    """ want to continue anyway?"""),
##                icon=E5MessageBox.Warning)
##            if not res:
##                return False
        
        return QWebEnginePage.acceptNavigationRequest(self, url, type_,
                                                      isMainFrame)
##    
##    def populateNetworkRequest(self, request):
##        """
##        Public method to add data to a network request.
##        
##        @param request reference to the network request object
##            (QNetworkRequest)
##        """
##        try:
##            request.setAttribute(QNetworkRequest.User + 100, self)
##            if self.__lastRequest.url() == request.url():
##                request.setAttribute(QNetworkRequest.User + 101,
##                                     self.__lastRequestType)
##                if self.__lastRequestType == \
##                        QWebPage.NavigationTypeLinkClicked:
##                    request.setRawHeader(b"X-Eric6-UserLoadAction",
##                                         QByteArray(b"1"))
##        except TypeError:
##            pass
##    
##    def pageAttributeId(self):
##        """
##        Public method to get the attribute id of the page attribute.
##        
##        @return attribute id of the page attribute (integer)
##        """
##        return QNetworkRequest.User + 100
##    
##    def supportsExtension(self, extension):
##        """
##        Public method to check the support for an extension.
##        
##        @param extension extension to test for (QWebPage.Extension)
##        @return flag indicating the support of extension (boolean)
##        """
##        try:
##            if extension in [QWebPage.ErrorPageExtension,
##                             QWebPage.ChooseMultipleFilesExtension]:
##                return True
##        except AttributeError:
##            pass
##        
##        return QWebPage.supportsExtension(self, extension)
##    
##    def extension(self, extension, option, output):
##        """
##        Public method to implement a specific extension.
##        
##        @param extension extension to be executed (QWebPage.Extension)
##        @param option provides input to the extension
##            (QWebPage.ExtensionOption)
##        @param output stores the output results (QWebPage.ExtensionReturn)
##        @return flag indicating a successful call of the extension (boolean)
##        """
##        if extension == QWebPage.ChooseMultipleFilesExtension:
##            info = sip.cast(option,
##                            QWebPage.ChooseMultipleFilesExtensionOption)
##            files = sip.cast(output,
##                             QWebPage.ChooseMultipleFilesExtensionReturn)
##            if info is None or files is None:
##                return super(HelpWebPage, self).extension(
##                    extension, option, output)
##            
##            suggestedFileName = ""
##            if info.suggestedFileNames:
##                suggestedFileName = info.suggestedFileNames[0]
##            
##            files.fileNames = E5FileDialog.getOpenFileNames(
##                None,
##                self.tr("Select files to upload..."),
##                suggestedFileName)
##            return True
##        
##        if extension == QWebPage.ErrorPageExtension:
##            info = sip.cast(option, QWebPage.ErrorPageExtensionOption)
##            
##            errorPage = sip.cast(output, QWebPage.ErrorPageExtensionReturn)
##            urlString = bytes(info.url.toEncoded()).decode()
##            errorPage.baseUrl = info.url
##            if info.domain == QWebPage.QtNetwork and \
##               info.error == QNetworkReply.ProtocolUnknownError:
##                url = QUrl(info.url)
##                res = E5MessageBox.yesNo(
##                    None,
##                    self.tr("Protocol Error"),
##                    self.tr("""Open external application for {0}-link?\n"""
##                            """URL: {1}""").format(
##                        url.scheme(), url.toString(
##                            QUrl.PrettyDecoded | QUrl.RemovePassword)),
##                    yesDefault=True)
##                
##                if res:
##                    QDesktopServices.openUrl(url)
##                return True
##            elif info.domain == QWebPage.QtNetwork and \
##                info.error == QNetworkReply.ContentAccessDenied and \
##                    info.errorString.startswith("AdBlockRule:"):
##                if info.frame != info.frame.page().mainFrame():
##                    # content in <iframe>
##                    docElement = info.frame.page().mainFrame()\
##                        .documentElement()
##                    for element in docElement.findAll("iframe"):
##                        src = element.attribute("src")
##                        if src in info.url.toString():
##                            element.setAttribute("style", "display:none;")
##                    return False
##                else:
##                    # the whole page is blocked
##                    rule = info.errorString.replace("AdBlockRule:", "")
##                    title = self.tr("Content blocked by AdBlock Plus")
##                    message = self.tr(
##                        "Blocked by rule: <i>{0}</i>").format(rule)
##                    
##                    htmlFile = QFile(":/html/adblockPage.html")
##                    htmlFile.open(QFile.ReadOnly)
##                    html = htmlFile.readAll()
##                    html = html.replace(
##                        "@FAVICON@", "qrc:icons/adBlockPlus16.png")
##                    html = html.replace(
##                        "@IMAGE@", "qrc:icons/adBlockPlus64.png")
##                    html = html.replace("@TITLE@", title.encode("utf8"))
##                    html = html.replace("@MESSAGE@", message.encode("utf8"))
##                    errorPage.content = html
##                    return True
##            
##            if info.domain == QWebPage.QtNetwork and \
##               info.error == QNetworkReply.OperationCanceledError and \
##               info.errorString == "eric6:No Error":
##                return False
##            
##            if info.domain == QWebPage.WebKit and info.error == 203:
##                # "Loading is handled by the media engine"
##                return False
##            
##            title = self.tr("Error loading page: {0}").format(urlString)
##            htmlFile = QFile(":/html/notFoundPage.html")
##            htmlFile.open(QFile.ReadOnly)
##            html = htmlFile.readAll()
##            pixmap = qApp.style()\
##                .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(48, 48)
##            imageBuffer = QBuffer()
##            imageBuffer.open(QIODevice.ReadWrite)
##            if pixmap.save(imageBuffer, "PNG"):
##                html = html.replace("@IMAGE@", imageBuffer.buffer().toBase64())
##            pixmap = qApp.style()\
##                .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(16, 16)
##            imageBuffer = QBuffer()
##            imageBuffer.open(QIODevice.ReadWrite)
##            if pixmap.save(imageBuffer, "PNG"):
##                html = html.replace(
##                    "@FAVICON@", imageBuffer.buffer().toBase64())
##            html = html.replace("@TITLE@", title.encode("utf8"))
##            html = html.replace("@H1@", info.errorString.encode("utf8"))
##            html = html.replace(
##                "@H2@", self.tr("When connecting to: {0}.")
##                .format(urlString).encode("utf8"))
##            html = html.replace(
##                "@LI-1@",
##                self.tr("Check the address for errors such as "
##                        "<b>ww</b>.example.org instead of "
##                        "<b>www</b>.example.org").encode("utf8"))
##            html = html.replace(
##                "@LI-2@",
##                self.tr(
##                    "If the address is correct, try checking the network "
##                    "connection.").encode("utf8"))
##            html = html.replace(
##                "@LI-3@",
##                self.tr(
##                    "If your computer or network is protected by a firewall "
##                    "or proxy, make sure that the browser is permitted to "
##                    "access the network.").encode("utf8"))
##            html = html.replace(
##                "@LI-4@",
##                self.tr("If your cache policy is set to offline browsing,"
##                        "only pages in the local cache are available.")
##                .encode("utf8"))
##            html = html.replace(
##                "@BUTTON@", self.tr("Try Again").encode("utf8"))
##            errorPage.content = html
##            return True
##        
##        return QWebPage.extension(self, extension, option, output)
    
    def __loadStarted(self):
        """
        Private method to handle the loadStarted signal.
        """
        self.__adBlockedEntries = []
##    
##    def addAdBlockRule(self, rule, url):
##        """
##        Public slot to add an AdBlock rule to the page.
##        
##        @param rule AdBlock rule to add (AdBlockRule)
##        @param url URL that matched the rule (QUrl)
##        """
##        from .AdBlock.AdBlockPage import AdBlockedPageEntry
##        entry = AdBlockedPageEntry(rule, url)
##        if entry not in self.__adBlockedEntries:
##            self.__adBlockedEntries.append(entry)
##    
##    def getAdBlockedPageEntries(self):
##        """
##        Public method to get the list of AdBlock page entries.
##        
##        @return list of AdBlock page entries (list of AdBlockedPageEntry)
##        """
##        return self.__adBlockedEntries
    
    # TODO: User Agent Manager
##    def userAgent(self, resolveEmpty=False):
##        """
##        Public method to get the global user agent setting.
##        
##        @param resolveEmpty flag indicating to resolve an empty
##            user agent (boolean)
##        @return user agent string (string)
##        """
##        agent = Preferences.getWebBrowser("UserAgent")
##        if agent == "" and resolveEmpty:
##            agent = self.userAgentForUrl(QUrl())
##        return agent
##    
##    def setUserAgent(self, agent):
##        """
##        Public method to set the global user agent string.
##        
##        @param agent new current user agent string (string)
##        """
##        Preferences.setHelp("UserAgent", agent)
##    
##    def userAgentForUrl(self, url):
##        """
##        Public method to determine the user agent for the given URL.
##        
##        @param url URL to determine user agent for (QUrl)
##        @return user agent string (string)
##        """
##        import Helpviewer.HelpWindow
##        agent = Helpviewer.HelpWindow.HelpWindow.userAgentsManager()\
##            .userAgentForUrl(url)
##        if agent == "":
##            # no agent string specified for the given host -> use global one
##            agent = Preferences.getHelp("UserAgent")
##            if agent == "":
##                # no global agent string specified -> use default one
##                agent = QWebPage.userAgentForUrl(self, url)
##        return agent
##    
##    def __managerFinished(self, reply):
##        """
##        Private slot to handle a finished reply.
##        
##        This slot is used to get SSL related information for a reply.
##        
##        @param reply reference to the finished reply (QNetworkReply)
##        """
##        try:
##            frame = reply.request().originatingObject()
##        except AttributeError:
##            frame = None
##        
##        mainFrameRequest = frame == self.mainFrame()
##        
##        if mainFrameRequest and \
##           self.__sslConfiguration is not None and \
##           reply.url() == self.mainFrame().url():
##            self.__sslConfiguration = None
##        
##        if reply.error() == QNetworkReply.NoError and \
##           mainFrameRequest and \
##           self.__sslConfiguration is None and \
##           reply.url().scheme().lower() == "https" and \
##           reply.url() == self.mainFrame().url():
##            self.__sslConfiguration = reply.sslConfiguration()
##            self.__sslConfiguration.url = QUrl(reply.url())
##        
##        if reply.error() == QNetworkReply.NoError and \
##           mainFrameRequest and \
##           reply.url() == self.mainFrame().url():
##            modified = reply.header(QNetworkRequest.LastModifiedHeader)
##            if modified and modified.isValid():
##                import Helpviewer.HelpWindow
##                manager = Helpviewer.HelpWindow.HelpWindow.bookmarksManager()
##                from .Bookmarks.BookmarkNode import BookmarkNode
##                for bookmark in manager.bookmarksForUrl(reply.url()):
##                    manager.setTimestamp(bookmark, BookmarkNode.TsModified,
##                                         modified)
    
##    def getSslCertificate(self):
##        """
##        Public method to get a reference to the SSL certificate.
##        
##        @return amended SSL certificate (QSslCertificate)
##        """
##        if self.__sslConfiguration is None:
##            return None
##        
##        sslInfo = self.__sslConfiguration.peerCertificate()
##        sslInfo.url = QUrl(self.__sslConfiguration.url)
##        return sslInfo
##    
##    def getSslCertificateChain(self):
##        """
##        Public method to get a reference to the SSL certificate chain.
##        
##        @return SSL certificate chain (list of QSslCertificate)
##        """
##        if self.__sslConfiguration is None:
##            return []
##        
##        chain = self.__sslConfiguration.peerCertificateChain()
##        return chain
##    
##    def getSslConfiguration(self):
##        """
##        Public method to return a reference to the current SSL configuration.
##        
##        @return reference to the SSL configuration in use (QSslConfiguration)
##        """
##        return self.__sslConfiguration
##    
##    def showSslInfo(self, pos):
##        """
##        Public slot to show some SSL information for the loaded page.
##        
##        @param pos position to show the info at (QPoint)
##        """
##        if SSL_AVAILABLE and self.__sslConfiguration is not None:
##            from E5Network.E5SslInfoWidget import E5SslInfoWidget
##            widget = E5SslInfoWidget(
##                self.mainFrame().url(), self.__sslConfiguration, self.view())
##            widget.showAt(pos)
##        else:
##            E5MessageBox.warning(
##                self.view(),
##                self.tr("SSL Info"),
##                self.tr("""This site does not contain SSL information."""))
##    
##    def hasValidSslInfo(self):
##        """
##        Public method to check, if the page has a valid SSL certificate.
##        
##        @return flag indicating a valid SSL certificate (boolean)
##        """
##        if self.__sslConfiguration is None:
##            return False
##        
##        certList = self.__sslConfiguration.peerCertificateChain()
##        if not certList:
##            return False
##        
##        certificateDict = Globals.toDict(
##            Preferences.Prefs.settings.value("Ssl/CaCertificatesDict"))
##        for server in certificateDict:
##            localCAList = QSslCertificate.fromData(certificateDict[server])
##            for cert in certList:
##                if cert in localCAList:
##                    return True
##        
##        if qVersion() >= "5.0.0":
##            for cert in certList:
##                if cert.isBlacklisted():
##                    return False
##        else:
##            for cert in certList:
##                if not cert.isValid():
##                    return False
##        
##        return True
    
##    @classmethod
##    def webPluginFactory(cls):
##        """
##        Class method to get a reference to the web plug-in factory
##        instance.
##        
##        @return reference to the web plug-in factory instance (WebPluginFactory
##        """
##        if cls._webPluginFactory is None:
##            from .WebPlugins.WebPluginFactory import WebPluginFactory
##            cls._webPluginFactory = WebPluginFactory()
##        
##        return cls._webPluginFactory
##    
##    def event(self, evt):
##        """
##        Public method implementing the event handler.
##        
##        @param evt reference to the event (QEvent)
##        @return flag indicating that the event was handled (boolean)
##        """
##        if evt.type() == QEvent.Leave:
##            # Fake a mouse move event just outside of the widget to trigger
##            # the WebKit event handler's mouseMoved function. This implements
##            # the interesting mouse-out behavior like invalidating scrollbars.
##            fakeEvent = QMouseEvent(QEvent.MouseMove, QPoint(0, -1),
##                                    Qt.NoButton, Qt.NoButton, Qt.NoModifier)
##            return super(HelpWebPage, self).event(fakeEvent)
##        
##        return super(HelpWebPage, self).event(evt)
##    
##    def __saveFrameStateRequested(self, frame, itm):
##        """
##        Private slot to save the page state (i.e. zoom level and scroll
##        position).
##        
##        Note: Code is based on qutebrowser.
##        
##        @param frame frame to be saved
##        @type QWebFrame
##        @param itm web history item to be saved
##        @type QWebHistoryItem
##        """
##        try:
##            if frame != self.mainFrame():
##                return
##        except RuntimeError:
##            # With Qt 5.2.1 (Ubuntu Trusty) we get this when closing a tab:
##            #     RuntimeError: wrapped C/C++ object of type BrowserPage has
##            #     been deleted
##            # Since the information here isn't that important for closing web
##            # views anyways, we ignore this error.
##            return
##        data = {
##            'zoom': frame.zoomFactor(),
##            'scrollPos': frame.scrollPosition(),
##        }
##        itm.setUserData(data)
##    
##    def __restoreFrameStateRequested(self, frame):
##        """
##        Private slot to restore scroll position and zoom level from
##        history.
##        
##        Note: Code is based on qutebrowser.
##        
##        @param frame frame to be restored
##        @type QWebFrame
##        """
##        if frame != self.mainFrame():
##            return
##        
##        data = self.history().currentItem().userData()
##        if data is None:
##            return
##        
##        if 'zoom' in data:
##            frame.page().view().setZoomValue(int(data['zoom'] * 100),
##                                             saveValue=False)
##        
##        if 'scrollPos' in data and frame.scrollPosition() == QPoint(0, 0):
##            frame.setScrollPosition(data['scrollPos'])
    
    def __featurePermissionRequested(self, url, feature):
        """
        Private slot handling a feature permission request.
        
        @param url url requesting the feature
        @type QUrl
        @param feature requested feature
        @type QWebEnginePage.Feature
        """
        manager = WebBrowser.WebBrowserWindow.WebBrowserWindow\
            .featurePermissionManager()
        manager.requestFeaturePermission(self, frame, feature)

eric ide

mercurial