--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/WebBrowserPage.py Mon Feb 01 20:03:22 2016 +0100 @@ -0,0 +1,716 @@ +# -*- 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: re-enable once User Agent Manager is done +## 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)