Continued porting the web browser. QtWebEngine

Mon, 15 Feb 2016 20:01:02 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 15 Feb 2016 20:01:02 +0100
branch
QtWebEngine
changeset 4743
f9e2e536d130
parent 4742
f9d1090f6ab9
child 4744
ad3f6c1caf8d

Continued porting the web browser.

- started adding the passwords stuff

WebBrowser/Network/NetworkManager.py file | annotate | diff | comparison | revisions
WebBrowser/Passwords/LoginForm.py file | annotate | diff | comparison | revisions
WebBrowser/Passwords/PasswordManager.py file | annotate | diff | comparison | revisions
WebBrowser/Passwords/PasswordModel.py file | annotate | diff | comparison | revisions
WebBrowser/Passwords/PasswordReader.py file | annotate | diff | comparison | revisions
WebBrowser/Passwords/PasswordWriter.py file | annotate | diff | comparison | revisions
WebBrowser/Passwords/PasswordsDialog.py file | annotate | diff | comparison | revisions
WebBrowser/Passwords/PasswordsDialog.ui file | annotate | diff | comparison | revisions
WebBrowser/Passwords/__init__.py file | annotate | diff | comparison | revisions
WebBrowser/Tools/Scripts.py file | annotate | diff | comparison | revisions
WebBrowser/WebBrowserPage.py file | annotate | diff | comparison | revisions
WebBrowser/WebBrowserWindow.py file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
--- a/WebBrowser/Network/NetworkManager.py	Sun Feb 14 19:34:05 2016 +0100
+++ b/WebBrowser/Network/NetworkManager.py	Mon Feb 15 20:01:02 2016 +0100
@@ -9,10 +9,15 @@
 
 from __future__ import unicode_literals
 
-from PyQt5.QtNetwork import QNetworkAccessManager
+from PyQt5.QtWidgets import QDialog
+from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkProxy
 
 from E5Gui import E5MessageBox
 
+from E5Network.E5NetworkProxyFactory import proxyAuthenticationRequired
+
+import Preferences
+
 
 class NetworkManager(QNetworkAccessManager):
     """
@@ -29,10 +34,11 @@
         self.__ignoredSslErrors = {}
         # dictionary of temporarily ignore SSL errors
         
-        # TODO: Proxy Authentication
-##        self.proxyAuthenticationRequired.connect(proxyAuthenticationRequired)
-        # TODO: Authentication
-##        self.authenticationRequired.connect(self.authenticationRequired)
+        self.proxyAuthenticationRequired.connect(
+            lambda proxy, auth: self.proxyAuthentication(
+                proxy.hostName(), auth))
+        self.authenticationRequired.connect(
+            lambda reply, auth: self.authentication(reply.url(), auth))
     
     def certificateError(self, error, view):
         """
@@ -68,3 +74,62 @@
             return True
         
         return False
+    
+    def authentication(self, url, auth):
+        """
+        Public slot to handle an authentication request.
+        
+        @param url URL requesting authentication (QUrl)
+        @param auth reference to the authenticator object (QAuthenticator)
+        """
+        urlRoot = "{0}://{1}"\
+            .format(url.scheme(), url.authority())
+        realm = auth.realm()
+        if not realm and 'realm' in auth.options():
+            realm = auth.option("realm")
+        if realm:
+            info = self.tr("<b>Enter username and password for '{0}', "
+                           "realm '{1}'</b>").format(urlRoot, realm)
+        else:
+            info = self.tr("<b>Enter username and password for '{0}'</b>")\
+                .format(urlRoot)
+        
+        from UI.AuthenticationDialog import AuthenticationDialog
+        # TODO: Password Manager
+##        import WebBrowser.WebBrowserWindow
+        
+        dlg = AuthenticationDialog(info, auth.user(),
+                                   Preferences.getUser("SavePasswords"),
+                                   Preferences.getUser("SavePasswords"))
+        # TODO: Password Manager
+##        if Preferences.getUser("SavePasswords"):
+##            username, password = \
+##                WebBrowser.WebBrowserWindow.WebBrowserWindow.passwordManager()\
+##                .getLogin(url, realm)
+##            if username:
+##                dlg.setData(username, password)
+        if dlg.exec_() == QDialog.Accepted:
+            username, password = dlg.getData()
+            auth.setUser(username)
+            auth.setPassword(password)
+            # TODO: Password Manager
+##            if Preferences.getUser("SavePasswords"):
+##                WebBrowser.WebBrowserWindow.WebBrowserWindow.passwordManager()\
+##                .setLogin(url, realm, username, password)
+    
+    def proxyAuthentication(self, hostname, auth):
+        """
+        Public slot to handle a proxy authentication request.
+        
+        @param hostname name of the proxy host
+        @type str
+        @param auth reference to the authenticator object
+        @type QAuthenticator
+        """
+        proxy = QNetworkProxy.applicationProxy()
+        if proxy.user() and proxy.password():
+            auth.setUser(proxy.user())
+            auth.setPassword(proxy.password())
+            return
+        
+        proxyAuthenticationRequired(proxy, auth)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Passwords/LoginForm.py	Mon Feb 15 20:01:02 2016 +0100
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a data structure for login forms.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import QUrl
+
+
+class LoginForm(object):
+    """
+    Class implementing a data structure for login forms.
+    """
+    def __init__(self):
+        """
+        Constructor
+        """
+        self.url = QUrl()
+        self.name = ""
+        self.hasAPassword = False
+        self.elements = []
+        # list of tuples of element name and value (string, string)
+        self.elementTypes = {}
+        # dict of element name as key and type as value
+    
+    def isValid(self):
+        """
+        Public method to test for validity.
+        
+        @return flag indicating a valid form (boolean)
+        """
+        return len(self.elements) > 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Passwords/PasswordManager.py	Mon Feb 15 20:01:02 2016 +0100
@@ -0,0 +1,568 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the password manager.
+"""
+
+from __future__ import unicode_literals
+
+import os
+
+from PyQt5.QtCore import pyqtSignal, QObject, QByteArray, QUrl, \
+    QCoreApplication, QXmlStreamReader, qVersion
+from PyQt5.QtWidgets import QApplication
+from PyQt5.QtNetwork import QNetworkRequest
+##from PyQt5.QtWebKit import QWebSettings
+##from PyQt5.QtWebKitWidgets import QWebPage
+
+from E5Gui import E5MessageBox
+from E5Gui.E5ProgressDialog import E5ProgressDialog
+
+from Utilities.AutoSaver import AutoSaver
+import Utilities
+import Utilities.crypto
+import Preferences
+
+
+class PasswordManager(QObject):
+    """
+    Class implementing the password manager.
+    
+    @signal changed() emitted to indicate a change
+    @signal passwordsSaved() emitted after the passwords were saved
+    """
+    changed = pyqtSignal()
+    passwordsSaved = pyqtSignal()
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent object (QObject)
+        """
+        super(PasswordManager, self).__init__(parent)
+        
+        self.__logins = {}
+        self.__loginForms = {}
+        self.__never = []
+        self.__loaded = False
+        self.__saveTimer = AutoSaver(self, self.save)
+        
+        self.changed.connect(self.__saveTimer.changeOccurred)
+    
+    def clear(self):
+        """
+        Public slot to clear the saved passwords.
+        """
+        if not self.__loaded:
+            self.__load()
+        
+        self.__logins = {}
+        self.__loginForms = {}
+        self.__never = []
+        self.__saveTimer.changeOccurred()
+        self.__saveTimer.saveIfNeccessary()
+        
+        self.changed.emit()
+    
+    def getLogin(self, url, realm):
+        """
+        Public method to get the login credentials.
+        
+        @param url URL to get the credentials for (QUrl)
+        @param realm realm to get the credentials for (string)
+        @return tuple containing the user name (string) and password (string)
+        """
+        if not self.__loaded:
+            self.__load()
+        
+        key = self.__createKey(url, realm)
+        try:
+            return self.__logins[key][0], Utilities.crypto.pwConvert(
+                self.__logins[key][1], encode=False)
+        except KeyError:
+            return "", ""
+    
+    def setLogin(self, url, realm, username, password):
+        """
+        Public method to set the login credentials.
+        
+        @param url URL to set the credentials for (QUrl)
+        @param realm realm to set the credentials for (string)
+        @param username username for the login (string)
+        @param password password for the login (string)
+        """
+        if not self.__loaded:
+            self.__load()
+        
+        key = self.__createKey(url, realm)
+        self.__logins[key] = (
+            username,
+            Utilities.crypto.pwConvert(password, encode=True)
+        )
+        self.changed.emit()
+    
+    def __createKey(self, url, realm):
+        """
+        Private method to create the key string for the login credentials.
+        
+        @param url URL to get the credentials for (QUrl)
+        @param realm realm to get the credentials for (string)
+        @return key string (string)
+        """
+        authority = url.authority()
+        if authority.startswith("@"):
+            authority = authority[1:]
+        if realm:
+            key = "{0}://{1} ({2})".format(
+                url.scheme(), authority, realm)
+        else:
+            key = "{0}://{1}".format(url.scheme(), authority)
+        return key
+    
+    def getFileName(self):
+        """
+        Public method to get the file name of the passwords file.
+        
+        @return name of the passwords file (string)
+        """
+        return os.path.join(Utilities.getConfigDir(),
+                            "web_browser", "logins.xml")
+    
+    def save(self):
+        """
+        Public slot to save the login entries to disk.
+        """
+        if not self.__loaded:
+            return
+        
+        from .PasswordWriter import PasswordWriter
+        loginFile = self.getFileName()
+        writer = PasswordWriter()
+        if not writer.write(
+                loginFile, self.__logins, self.__loginForms, self.__never):
+            E5MessageBox.critical(
+                None,
+                self.tr("Saving login data"),
+                self.tr(
+                    """<p>Login data could not be saved to <b>{0}</b></p>"""
+                ).format(loginFile))
+        else:
+            self.passwordsSaved.emit()
+    
+    def __load(self):
+        """
+        Private method to load the saved login credentials.
+        """
+        if self.__loaded:
+            return
+        
+        loginFile = self.getFileName()
+        if os.path.exists(loginFile):
+            from .PasswordReader import PasswordReader
+            reader = PasswordReader()
+            self.__logins, self.__loginForms, self.__never = \
+                reader.read(loginFile)
+            if reader.error() != QXmlStreamReader.NoError:
+                E5MessageBox.warning(
+                    None,
+                    self.tr("Loading login data"),
+                    self.tr("""Error when loading login data on"""
+                            """ line {0}, column {1}:\n{2}""")
+                    .format(reader.lineNumber(),
+                            reader.columnNumber(),
+                            reader.errorString()))
+        
+        self.__loaded = True
+    
+    def reload(self):
+        """
+        Public method to reload the login data.
+        """
+        if not self.__loaded:
+            return
+        
+        self.__loaded = False
+        self.__load()
+    
+    def close(self):
+        """
+        Public method to close the passwords manager.
+        """
+        self.__saveTimer.saveIfNeccessary()
+    
+    def removePassword(self, site):
+        """
+        Public method to remove a password entry.
+        
+        @param site web site name (string)
+        """
+        if site in self.__logins:
+            del self.__logins[site]
+            if site in self.__loginForms:
+                del self.__loginForms[site]
+            self.changed.emit()
+    
+    def allSiteNames(self):
+        """
+        Public method to get a list of all site names.
+        
+        @return sorted list of all site names (list of strings)
+        """
+        if not self.__loaded:
+            self.__load()
+        
+        return sorted(self.__logins.keys())
+    
+    def sitesCount(self):
+        """
+        Public method to get the number of available sites.
+        
+        @return number of sites (integer)
+        """
+        if not self.__loaded:
+            self.__load()
+        
+        return len(self.__logins)
+    
+    def siteInfo(self, site):
+        """
+        Public method to get a reference to the named site.
+        
+        @param site web site name (string)
+        @return tuple containing the user name (string) and password (string)
+        """
+        if not self.__loaded:
+            self.__load()
+        
+        if site not in self.__logins:
+            return None
+        
+        return self.__logins[site][0], Utilities.crypto.pwConvert(
+            self.__logins[site][1], encode=False)
+    
+    # TODO: Password Manager: processing of form data
+    def post(self, request, data):
+        """
+        Public method to check, if the data to be sent contains login data.
+        
+        @param request reference to the network request (QNetworkRequest)
+        @param data data to be sent (QByteArray)
+        """
+##        # shall passwords be saved?
+##        if not Preferences.getUser("SavePasswords"):
+##            return
+##        
+##        # observe privacy
+##        # TODO: Privacy, i.e. isPrivate()
+####        if QWebSettings.globalSettings().testAttribute(
+####                QWebSettings.PrivateBrowsingEnabled):
+####            return
+##        
+##        if not self.__loaded:
+##            self.__load()
+##        
+##        # determine the url
+##        refererHeader = request.rawHeader(b"Referer")
+##        if refererHeader.isEmpty():
+##            return
+##        url = QUrl.fromEncoded(refererHeader)
+##        url = self.__stripUrl(url)
+##        
+##        # check that url isn't in __never
+##        if url.toString() in self.__never:
+##            return
+##        
+##        # check the request type
+##        navType = request.attribute(QNetworkRequest.User + 101)
+##        if navType is None:
+##            return
+##        if navType != QWebPage.NavigationTypeFormSubmitted:
+##            return
+##        
+##        # determine the QWebPage
+##        webPage = request.attribute(QNetworkRequest.User + 100)
+##        if webPage is None:
+##            return
+##        
+##        # determine the requests content type
+##        contentTypeHeader = request.rawHeader(b"Content-Type")
+##        if contentTypeHeader.isEmpty():
+##            return
+##        multipart = contentTypeHeader.startsWith(b"multipart/form-data")
+##        if multipart:
+##            boundary = contentTypeHeader.split(" ")[1].split("=")[1]
+##        else:
+##            boundary = None
+##        
+##        # find the matching form on the web page
+##        form = self.__findForm(webPage, data, boundary=boundary)
+##        if not form.isValid():
+##            return
+##        form.url = QUrl(url)
+##        
+##        # check, if the form has a password
+##        if not form.hasAPassword:
+##            return
+##        
+##        # prompt, if the form has never be seen
+##        key = self.__createKey(url, "")
+##        if key not in self.__loginForms:
+##            mb = E5MessageBox.E5MessageBox(
+##                E5MessageBox.Question,
+##                self.tr("Save password"),
+##                self.tr(
+##                    """<b>Would you like to save this password?</b><br/>"""
+##                    """To review passwords you have saved and remove them, """
+##                    """use the password management dialog of the Settings"""
+##                    """ menu."""),
+##                modal=True)
+##            neverButton = mb.addButton(
+##                self.tr("Never for this site"),
+##                E5MessageBox.DestructiveRole)
+##            noButton = mb.addButton(
+##                self.tr("Not now"), E5MessageBox.RejectRole)
+##            mb.addButton(E5MessageBox.Yes)
+##            mb.exec_()
+##            if mb.clickedButton() == neverButton:
+##                self.__never.append(url.toString())
+##                return
+##            elif mb.clickedButton() == noButton:
+##                return
+##        
+##        # extract user name and password
+##        user = ""
+##        password = ""
+##        for index in range(len(form.elements)):
+##            element = form.elements[index]
+##            type_ = form.elementTypes[element[0]]
+##            if user == "" and \
+##               type_ == "text":
+##                user = element[1]
+##            elif password == "" and \
+##                    type_ == "password":
+##                password = element[1]
+##                form.elements[index] = (element[0], "--PASSWORD--")
+##        if user and password:
+##            self.__logins[key] = \
+##                (user, Utilities.crypto.pwConvert(password, encode=True))
+##            self.__loginForms[key] = form
+##            self.changed.emit()
+    
+    def __stripUrl(self, url):
+        """
+        Private method to strip off all unneeded parts of a URL.
+        
+        @param url URL to be stripped (QUrl)
+        @return stripped URL (QUrl)
+        """
+        cleanUrl = QUrl(url)
+        cleanUrl.setQuery("")
+        cleanUrl.setUserInfo("")
+        
+        authority = cleanUrl.authority()
+        if authority.startswith("@"):
+            authority = authority[1:]
+        cleanUrl = QUrl("{0}://{1}{2}".format(
+            cleanUrl.scheme(), authority, cleanUrl.path()))
+        cleanUrl.setFragment("")
+        return cleanUrl
+    
+    # TODO: Password Manager: processing of form data
+##    def __findForm(self, webPage, data, boundary=None):
+##        """
+##        Private method to find the form used for logging in.
+##        
+##        @param webPage reference to the web page (QWebPage)
+##        @param data data to be sent (QByteArray)
+##        @keyparam boundary boundary string (QByteArray) for multipart
+##            encoded data, None for urlencoded data
+##        @return parsed form (LoginForm)
+##        """
+##        from .LoginForm import LoginForm
+##        form = LoginForm()
+##        if boundary is not None:
+##            args = self.__extractMultipartQueryItems(data, boundary)
+##        else:
+##            if qVersion() >= "5.0.0":
+##                from PyQt5.QtCore import QUrlQuery
+##                argsUrl = QUrl.fromEncoded(
+##                    QByteArray(b"foo://bar.com/?" + QUrl.fromPercentEncoding(
+##                        data.replace(b"+", b"%20")).encode("utf-8")))
+##                encodedArgs = QUrlQuery(argsUrl).queryItems()
+##            else:
+##                argsUrl = QUrl.fromEncoded(
+##                    QByteArray(b"foo://bar.com/?" + data.replace(b"+", b"%20"))
+##                )
+##                encodedArgs = argsUrl.queryItems()
+##            args = set()
+##            for arg in encodedArgs:
+##                key = arg[0]
+##                value = arg[1]
+##                args.add((key, value))
+##        
+##        # extract the forms
+##        from Helpviewer.JavaScriptResources import parseForms_js
+##        lst = webPage.mainFrame().evaluateJavaScript(parseForms_js)
+##        for map in lst:
+##            formHasPasswords = False
+##            formName = map["name"]
+##            formIndex = map["index"]
+##            if isinstance(formIndex, float) and formIndex.is_integer():
+##                formIndex = int(formIndex)
+##            elements = map["elements"]
+##            formElements = set()
+##            formElementTypes = {}
+##            deadElements = set()
+##            for elementMap in elements:
+##                try:
+##                    name = elementMap["name"]
+##                    value = elementMap["value"]
+##                    type_ = elementMap["type"]
+##                except KeyError:
+##                    continue
+##                if type_ == "password":
+##                    formHasPasswords = True
+##                t = (name, value)
+##                try:
+##                    if elementMap["autocomplete"] == "off":
+##                        deadElements.add(t)
+##                except KeyError:
+##                    pass
+##                if name:
+##                    formElements.add(t)
+##                    formElementTypes[name] = type_
+##            if formElements.intersection(args) == args:
+##                form.hasAPassword = formHasPasswords
+##                if not formName:
+##                    form.name = formIndex
+##                else:
+##                    form.name = formName
+##                args.difference_update(deadElements)
+##                for elt in deadElements:
+##                    if elt[0] in formElementTypes:
+##                        del formElementTypes[elt[0]]
+##                form.elements = list(args)
+##                form.elementTypes = formElementTypes
+##                break
+##        
+##        return form
+##    
+##    def __extractMultipartQueryItems(self, data, boundary):
+##        """
+##        Private method to extract the query items for a post operation.
+##        
+##        @param data data to be sent (QByteArray)
+##        @param boundary boundary string (QByteArray)
+##        @return set of name, value pairs (set of tuple of string, string)
+##        """
+##        args = set()
+##        
+##        dataStr = bytes(data).decode()
+##        boundaryStr = bytes(boundary).decode()
+##        
+##        parts = dataStr.split(boundaryStr + "\r\n")
+##        for part in parts:
+##            if part.startswith("Content-Disposition"):
+##                lines = part.split("\r\n")
+##                name = lines[0].split("=")[1][1:-1]
+##                value = lines[2]
+##                args.add((name, value))
+##        
+##        return args
+##    
+##    def fill(self, page):
+##        """
+##        Public slot to fill login forms with saved data.
+##        
+##        @param page reference to the web page (QWebPage)
+##        """
+##        if page is None or page.mainFrame() is None:
+##            return
+##        
+##        if not self.__loaded:
+##            self.__load()
+##        
+##        url = page.mainFrame().url()
+##        url = self.__stripUrl(url)
+##        key = self.__createKey(url, "")
+##        if key not in self.__loginForms or \
+##           key not in self.__logins:
+##            return
+##        
+##        form = self.__loginForms[key]
+##        if form.url != url:
+##            return
+##        
+##        if form.name == "":
+##            formName = "0"
+##        else:
+##            try:
+##                formName = "{0:d}".format(int(form.name))
+##            except ValueError:
+##                formName = '"{0}"'.format(form.name)
+##        for element in form.elements:
+##            name = element[0]
+##            value = element[1]
+##            
+##            disabled = page.mainFrame().evaluateJavaScript(
+##                'document.forms[{0}].elements["{1}"].disabled'.format(
+##                    formName, name))
+##            if disabled:
+##                continue
+##            
+##            readOnly = page.mainFrame().evaluateJavaScript(
+##                'document.forms[{0}].elements["{1}"].readOnly'.format(
+##                    formName, name))
+##            if readOnly:
+##                continue
+##            
+##            type_ = page.mainFrame().evaluateJavaScript(
+##                'document.forms[{0}].elements["{1}"].type'.format(
+##                    formName, name))
+##            if type_ == "" or \
+##               type_ in ["hidden", "reset", "submit"]:
+##                continue
+##            if type_ == "password":
+##                value = Utilities.crypto.pwConvert(
+##                    self.__logins[key][1], encode=False)
+##            setType = type_ == "checkbox" and "checked" or "value"
+##            value = value.replace("\\", "\\\\")
+##            value = value.replace('"', '\\"')
+##            javascript = \
+##                'document.forms[{0}].elements["{1}"].{2}="{3}";'.format(
+##                    formName, name, setType, value)
+##            page.mainFrame().evaluateJavaScript(javascript)
+    
+    def masterPasswordChanged(self, oldPassword, newPassword):
+        """
+        Public slot to handle the change of the master password.
+        
+        @param oldPassword current master password (string)
+        @param newPassword new master password (string)
+        """
+        if not self.__loaded:
+            self.__load()
+        
+        progress = E5ProgressDialog(
+            self.tr("Re-encoding saved passwords..."),
+            None, 0, len(self.__logins), self.tr("%v/%m Passwords"),
+            QApplication.activeModalWidget())
+        progress.setMinimumDuration(0)
+        progress.setWindowTitle(self.tr("Passwords"))
+        count = 0
+        
+        for key in self.__logins:
+            progress.setValue(count)
+            QCoreApplication.processEvents()
+            username, hash = self.__logins[key]
+            hash = Utilities.crypto.pwRecode(hash, oldPassword, newPassword)
+            self.__logins[key] = (username, hash)
+            count += 1
+        
+        progress.setValue(len(self.__logins))
+        QCoreApplication.processEvents()
+        self.changed.emit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Passwords/PasswordModel.py	Mon Feb 15 20:01:02 2016 +0100
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a model for password management.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import Qt, QModelIndex, QAbstractTableModel
+
+
+class PasswordModel(QAbstractTableModel):
+    """
+    Class implementing a model for password management.
+    """
+    def __init__(self, manager, parent=None):
+        """
+        Constructor
+        
+        @param manager reference to the password manager (PasswordManager)
+        @param parent reference to the parent object (QObject)
+        """
+        super(PasswordModel, self).__init__(parent)
+        
+        self.__manager = manager
+        manager.changed.connect(self.__passwordsChanged)
+        
+        self.__headers = [
+            self.tr("Website"),
+            self.tr("Username"),
+            self.tr("Password")
+        ]
+        
+        self.__showPasswords = False
+    
+    def setShowPasswords(self, on):
+        """
+        Public methods to show passwords.
+        
+        @param on flag indicating if passwords shall be shown (boolean)
+        """
+        self.__showPasswords = on
+        self.beginResetModel()
+        self.endResetModel()
+    
+    def showPasswords(self):
+        """
+        Public method to indicate, if passwords shall be shown.
+        
+        @return flag indicating if passwords shall be shown (boolean)
+        """
+        return self.__showPasswords
+    
+    def __passwordsChanged(self):
+        """
+        Private slot handling a change of the registered passwords.
+        """
+        self.beginResetModel()
+        self.endResetModel()
+    
+    def removeRows(self, row, count, parent=QModelIndex()):
+        """
+        Public method to remove entries from the model.
+        
+        @param row start row (integer)
+        @param count number of rows to remove (integer)
+        @param parent parent index (QModelIndex)
+        @return flag indicating success (boolean)
+        """
+        if parent.isValid():
+            return False
+        
+        if count <= 0:
+            return False
+        
+        lastRow = row + count - 1
+        
+        self.beginRemoveRows(parent, row, lastRow)
+        
+        siteList = self.__manager.allSiteNames()
+        for index in range(row, lastRow + 1):
+            self.__manager.removePassword(siteList[index])
+        
+        # removeEngine emits changed()
+        #self.endRemoveRows()
+        
+        return True
+    
+    def rowCount(self, parent=QModelIndex()):
+        """
+        Public method to get the number of rows of the model.
+        
+        @param parent parent index (QModelIndex)
+        @return number of rows (integer)
+        """
+        if parent.isValid():
+            return 0
+        else:
+            return self.__manager.sitesCount()
+    
+    def columnCount(self, parent=QModelIndex()):
+        """
+        Public method to get the number of columns of the model.
+        
+        @param parent parent index (QModelIndex)
+        @return number of columns (integer)
+        """
+        if self.__showPasswords:
+            return 3
+        else:
+            return 2
+    
+    def data(self, index, role):
+        """
+        Public method to get data from the model.
+        
+        @param index index to get data for (QModelIndex)
+        @param role role of the data to retrieve (integer)
+        @return requested data
+        """
+        if index.row() >= self.__manager.sitesCount() or index.row() < 0:
+            return None
+        
+        site = self.__manager.allSiteNames()[index.row()]
+        siteInfo = self.__manager.siteInfo(site)
+        
+        if siteInfo is None:
+            return None
+        
+        if role == Qt.DisplayRole:
+            if index.column() == 0:
+                return site
+            elif index.column() in [1, 2]:
+                return siteInfo[index.column() - 1]
+        
+        return None
+    
+    def headerData(self, section, orientation, role=Qt.DisplayRole):
+        """
+        Public method to get the header data.
+        
+        @param section section number (integer)
+        @param orientation header orientation (Qt.Orientation)
+        @param role data role (integer)
+        @return header data
+        """
+        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
+            try:
+                return self.__headers[section]
+            except IndexError:
+                pass
+        
+        return None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Passwords/PasswordReader.py	Mon Feb 15 20:01:02 2016 +0100
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a class to read login data files.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import QXmlStreamReader, QIODevice, QFile, \
+    QCoreApplication, QUrl
+
+
+class PasswordReader(QXmlStreamReader):
+    """
+    Class implementing a reader object for login data files.
+    """
+    def __init__(self):
+        """
+        Constructor
+        """
+        super(PasswordReader, self).__init__()
+    
+    def read(self, fileNameOrDevice):
+        """
+        Public method to read a login data file.
+        
+        @param fileNameOrDevice name of the file to read (string)
+            or reference to the device to read (QIODevice)
+        @return tuple containing the logins, forms and never URLs
+        """
+        self.__logins = {}
+        self.__loginForms = {}
+        self.__never = []
+        
+        if isinstance(fileNameOrDevice, QIODevice):
+            self.setDevice(fileNameOrDevice)
+        else:
+            f = QFile(fileNameOrDevice)
+            if not f.exists():
+                return self.__logins, self.__loginForms, self.__never
+            f.open(QFile.ReadOnly)
+            self.setDevice(f)
+        
+        while not self.atEnd():
+            self.readNext()
+            if self.isStartElement():
+                version = self.attributes().value("version")
+                if self.name() == "Password" and \
+                   (not version or version == "1.0"):
+                    self.__readPasswords()
+                else:
+                    self.raiseError(QCoreApplication.translate(
+                        "PasswordReader",
+                        "The file is not a Passwords version 1.0 file."))
+        
+        return self.__logins, self.__loginForms, self.__never
+    
+    def __readPasswords(self):
+        """
+        Private method to read and parse the login data file.
+        """
+        if not self.isStartElement() and self.name() != "Password":
+            return
+        
+        while not self.atEnd():
+            self.readNext()
+            if self.isEndElement():
+                break
+            
+            if self.isStartElement():
+                if self.name() == "Logins":
+                    self.__readLogins()
+                elif self.name() == "Forms":
+                    self.__readForms()
+                elif self.name() == "Nevers":
+                    self.__readNevers()
+                else:
+                    self.__skipUnknownElement()
+    
+    def __readLogins(self):
+        """
+        Private method to read the login information.
+        """
+        if not self.isStartElement() and self.name() != "Logins":
+            return
+        
+        while not self.atEnd():
+            self.readNext()
+            if self.isEndElement():
+                if self.name() == "Login":
+                    continue
+                else:
+                    break
+            
+            if self.isStartElement():
+                if self.name() == "Login":
+                    attributes = self.attributes()
+                    key = attributes.value("key")
+                    user = attributes.value("user")
+                    password = attributes.value("password")
+                    self.__logins[key] = (user, password)
+                else:
+                    self.__skipUnknownElement()
+    
+    def __readForms(self):
+        """
+        Private method to read the forms information.
+        """
+        if not self.isStartElement() and self.name() != "Forms":
+            return
+        
+        while not self.atEnd():
+            self.readNext()
+            if self.isStartElement():
+                if self.name() == "Form":
+                    from .LoginForm import LoginForm
+                    attributes = self.attributes()
+                    key = attributes.value("key")
+                    form = LoginForm()
+                    form.url = QUrl(attributes.value("url"))
+                    form.name = attributes.value("name")
+                    form.hasAPassword = attributes.value("password") == "yes"
+                elif self.name() == "Elements":
+                    continue
+                elif self.name() == "Element":
+                    attributes = self.attributes()
+                    name = attributes.value("name")
+                    value = attributes.value("value")
+                    form.elements.append((name, value))
+                else:
+                    self.__skipUnknownElement()
+            
+            if self.isEndElement():
+                if self.name() == "Form":
+                    self.__loginForms[key] = form
+                    continue
+                elif self.name() in ["Elements", "Element"]:
+                    continue
+                else:
+                    break
+    
+    def __readNevers(self):
+        """
+        Private method to read the never URLs.
+        """
+        if not self.isStartElement() and self.name() != "Nevers":
+            return
+        
+        while not self.atEnd():
+            self.readNext()
+            if self.isEndElement():
+                if self.name() == "Never":
+                    continue
+                else:
+                    break
+            
+            if self.isStartElement():
+                if self.name() == "Never":
+                    self.__never.append(self.attributes().value("url"))
+                else:
+                    self.__skipUnknownElement()
+    
+    def __skipUnknownElement(self):
+        """
+        Private method to skip over all unknown elements.
+        """
+        if not self.isStartElement():
+            return
+        
+        while not self.atEnd():
+            self.readNext()
+            if self.isEndElement():
+                break
+            
+            if self.isStartElement():
+                self.__skipUnknownElement()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Passwords/PasswordWriter.py	Mon Feb 15 20:01:02 2016 +0100
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a class to write login data files.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import QXmlStreamWriter, QIODevice, QFile
+
+
+class PasswordWriter(QXmlStreamWriter):
+    """
+    Class implementing a writer object to generate login data files.
+    """
+    def __init__(self):
+        """
+        Constructor
+        """
+        super(PasswordWriter, self).__init__()
+        
+        self.setAutoFormatting(True)
+    
+    def write(self, fileNameOrDevice, logins, forms, nevers):
+        """
+        Public method to write an login data file.
+        
+        @param fileNameOrDevice name of the file to write (string)
+            or device to write to (QIODevice)
+        @param logins dictionary with login data (user name, password)
+        @param forms list of forms data (list of LoginForm)
+        @param nevers list of URLs to never store data for (list of strings)
+        @return flag indicating success (boolean)
+        """
+        if isinstance(fileNameOrDevice, QIODevice):
+            f = fileNameOrDevice
+        else:
+            f = QFile(fileNameOrDevice)
+            if not f.open(QFile.WriteOnly):
+                return False
+        
+        self.setDevice(f)
+        return self.__write(logins, forms, nevers)
+    
+    def __write(self, logins, forms, nevers):
+        """
+        Private method to write an login data file.
+        
+        @param logins dictionary with login data (user name, password)
+        @param forms list of forms data (list of LoginForm)
+        @param nevers list of URLs to never store data for (list of strings)
+        @return flag indicating success (boolean)
+        """
+        self.writeStartDocument()
+        self.writeDTD("<!DOCTYPE passwords>")
+        self.writeStartElement("Password")
+        self.writeAttribute("version", "1.0")
+        
+        if logins:
+            self.__writeLogins(logins)
+        if forms:
+            self.__writeForms(forms)
+        if nevers:
+            self.__writeNevers(nevers)
+        
+        self.writeEndDocument()
+        return True
+    
+    def __writeLogins(self, logins):
+        """
+        Private method to write the login data.
+        
+        @param logins dictionary with login data (user name, password)
+        """
+        self.writeStartElement("Logins")
+        for key, login in logins.items():
+            self.writeEmptyElement("Login")
+            self.writeAttribute("key", key)
+            self.writeAttribute("user", login[0])
+            self.writeAttribute("password", login[1])
+        self.writeEndElement()
+    
+    def __writeForms(self, forms):
+        """
+        Private method to write forms data.
+        
+        @param forms list of forms data (list of LoginForm)
+        """
+        self.writeStartElement("Forms")
+        for key, form in forms.items():
+            self.writeStartElement("Form")
+            self.writeAttribute("key", key)
+            self.writeAttribute("url", form.url.toString())
+            self.writeAttribute("name", str(form.name))
+            self.writeAttribute(
+                "password", "yes" if form.hasAPassword else "no")
+            if form.elements:
+                self.writeStartElement("Elements")
+                for element in form.elements:
+                    self.writeEmptyElement("Element")
+                    self.writeAttribute("name", element[0])
+                    self.writeAttribute("value", element[1])
+                self.writeEndElement()
+            self.writeEndElement()
+        self.writeEndElement()
+    
+    def __writeNevers(self, nevers):
+        """
+        Private method to write the URLs never to store login data for.
+        
+        @param nevers list of URLs to never store data for (list of strings)
+        """
+        self.writeStartElement("Nevers")
+        for never in nevers:
+            self.writeEmptyElement("Never")
+            self.writeAttribute("url", never)
+        self.writeEndElement()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Passwords/PasswordsDialog.py	Mon Feb 15 20:01:02 2016 +0100
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to show all saved logins.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import pyqtSlot, QSortFilterProxyModel
+from PyQt5.QtGui import QFont, QFontMetrics
+from PyQt5.QtWidgets import QDialog
+
+from E5Gui import E5MessageBox
+
+from .Ui_PasswordsDialog import Ui_PasswordsDialog
+
+
+class PasswordsDialog(QDialog, Ui_PasswordsDialog):
+    """
+    Class implementing a dialog to show all saved logins.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        """
+        super(PasswordsDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.__showPasswordsText = self.tr("Show Passwords")
+        self.__hidePasswordsText = self.tr("Hide Passwords")
+        self.passwordsButton.setText(self.__showPasswordsText)
+        
+        self.removeButton.clicked.connect(
+            self.passwordsTable.removeSelected)
+        self.removeAllButton.clicked.connect(self.passwordsTable.removeAll)
+        
+        import Helpviewer.HelpWindow
+        from .PasswordModel import PasswordModel
+        
+        self.passwordsTable.verticalHeader().hide()
+        self.__passwordModel = PasswordModel(
+            Helpviewer.HelpWindow.HelpWindow.passwordManager(), self)
+        self.__proxyModel = QSortFilterProxyModel(self)
+        self.__proxyModel.setSourceModel(self.__passwordModel)
+        self.searchEdit.textChanged.connect(
+            self.__proxyModel.setFilterFixedString)
+        self.passwordsTable.setModel(self.__proxyModel)
+        
+        fm = QFontMetrics(QFont())
+        height = fm.height() + fm.height() // 3
+        self.passwordsTable.verticalHeader().setDefaultSectionSize(height)
+        self.passwordsTable.verticalHeader().setMinimumSectionSize(-1)
+        
+        self.__calculateHeaderSizes()
+    
+    def __calculateHeaderSizes(self):
+        """
+        Private method to calculate the section sizes of the horizontal header.
+        """
+        fm = QFontMetrics(QFont())
+        for section in range(self.__passwordModel.columnCount()):
+            header = self.passwordsTable.horizontalHeader()\
+                .sectionSizeHint(section)
+            if section == 0:
+                header = fm.width("averagebiglongsitename")
+            elif section == 1:
+                header = fm.width("averagelongusername")
+            elif section == 2:
+                header = fm.width("averagelongpassword")
+            buffer = fm.width("mm")
+            header += buffer
+            self.passwordsTable.horizontalHeader()\
+                .resizeSection(section, header)
+        self.passwordsTable.horizontalHeader().setStretchLastSection(True)
+    
+    @pyqtSlot()
+    def on_passwordsButton_clicked(self):
+        """
+        Private slot to switch the password display mode.
+        """
+        if self.__passwordModel.showPasswords():
+            self.__passwordModel.setShowPasswords(False)
+            self.passwordsButton.setText(self.__showPasswordsText)
+        else:
+            res = E5MessageBox.yesNo(
+                self,
+                self.tr("Saved Passwords"),
+                self.tr("""Do you really want to show passwords?"""))
+            if res:
+                self.__passwordModel.setShowPasswords(True)
+                self.passwordsButton.setText(self.__hidePasswordsText)
+        self.__calculateHeaderSizes()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Passwords/PasswordsDialog.ui	Mon Feb 15 20:01:02 2016 +0100
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PasswordsDialog</class>
+ <widget class="QDialog" name="PasswordsDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>500</width>
+    <height>350</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Saved Passwords</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout">
+       <property name="spacing">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="E5ClearableLineEdit" name="searchEdit">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>300</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="toolTip">
+          <string>Enter search term</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="E5TableView" name="passwordsTable">
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="selectionBehavior">
+      <enum>QAbstractItemView::SelectRows</enum>
+     </property>
+     <property name="textElideMode">
+      <enum>Qt::ElideMiddle</enum>
+     </property>
+     <property name="showGrid">
+      <bool>false</bool>
+     </property>
+     <property name="sortingEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_3">
+     <item>
+      <widget class="QPushButton" name="removeButton">
+       <property name="toolTip">
+        <string>Press to remove the selected entries</string>
+       </property>
+       <property name="text">
+        <string>&amp;Remove</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="removeAllButton">
+       <property name="toolTip">
+        <string>Press to remove all entries</string>
+       </property>
+       <property name="text">
+        <string>Remove &amp;All</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer_2">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>208</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="passwordsButton">
+       <property name="toolTip">
+        <string>Press to toggle the display of passwords</string>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5ClearableLineEdit</class>
+   <extends>QLineEdit</extends>
+   <header>E5Gui/E5LineEdit.h</header>
+  </customwidget>
+  <customwidget>
+   <class>E5TableView</class>
+   <extends>QTableView</extends>
+   <header>E5Gui/E5TableView.h</header>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>searchEdit</tabstop>
+  <tabstop>passwordsTable</tabstop>
+  <tabstop>removeButton</tabstop>
+  <tabstop>removeAllButton</tabstop>
+  <tabstop>passwordsButton</tabstop>
+  <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>PasswordsDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>237</x>
+     <y>340</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>PasswordsDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>325</x>
+     <y>340</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/Passwords/__init__.py	Mon Feb 15 20:01:02 2016 +0100
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing the password management interface.
+"""
--- a/WebBrowser/Tools/Scripts.py	Sun Feb 14 19:34:05 2016 +0100
+++ b/WebBrowser/Tools/Scripts.py	Mon Feb 15 20:01:02 2016 +0100
@@ -243,3 +243,112 @@
         values += valueSource.format(name, value)
     
     return source.format(url.toString(), values)
+
+
+def setupFormObserver():
+    """
+    Function generating a script to monitor a web form for user entries.
+    
+    @return script to monitor a web page
+    @rtype str
+    """
+    source = """
+        (function() {
+            function findUsername(inputs) {
+                for (var i = 0; i < inputs.length; ++i)
+                    if (inputs[i].type == 'text' && inputs[i].value.length &&
+                        inputs[i].name.indexOf('user') != -1)
+                        return inputs[i].value;
+                for (var i = 0; i < inputs.length; ++i)
+                    if (inputs[i].type == 'text' && inputs[i].value.length &&
+                        inputs[i].name.indexOf('name') != -1)
+                        return inputs[i].value;
+                for (var i = 0; i < inputs.length; ++i)
+                    if (inputs[i].type == 'text' && inputs[i].value.length)
+                        return inputs[i].value;
+                for (var i = 0; i < inputs.length; ++i)
+                    if (inputs[i].type == 'email' && inputs[i].value.length)
+                        return inputs[i].value;
+                return '';
+            }
+            
+            function registerForm(form) {
+                form.addEventListener('submit', function() {
+                    var form = this;
+                    var data = '';
+                    var password = '';
+                    var inputs = form.getElementsByTagName('input');
+                    for (var i = 0; i < inputs.length; ++i) {
+                        var input = inputs[i];
+                        var type = input.type.toLowerCase();
+                        if (type != 'text' && type != 'password' &&
+                            type != 'email')
+                            continue;
+                        if (!password && type == 'password')
+                            password = input.value;
+                        data += encodeURIComponent(input.name);
+                        data += '=';
+                        data += encodeURIComponent(input.value);
+                        data += '&';
+                    }
+                    if (!password)
+                        return;
+                    data = data.substring(0, data.length - 1);
+                    var url = window.location.href;
+                    var username = findUsername(inputs);
+                    external.autoFill.formSubmitted(url, username, password,
+                                                    data);
+                }, true);
+            }
+            
+            for (var i = 0; i < document.forms.length; ++i)
+                registerForm(document.forms[i]);
+            
+            var observer = new MutationObserver(function(mutations) {
+                for (var i = 0; i < mutations.length; ++i)
+                    for (var j = 0; j < mutations[i].addedNodes.length; ++j)
+                        if (mutations[i].addedNodes[j].tagName == 'form')
+                            registerForm(mutations[i].addedNodes[j]);
+            });
+            observer.observe(document.documentElement, { childList: true });
+            
+            })()"""
+    return source
+
+
+def completeFormData(data):
+    """
+    Function generating a script to fill in form data.
+    
+    @param data data to be filled into the form
+    @type QByteArray
+    @return script to fill a form
+    @rtype str
+    """
+    source = """
+        (function() {{
+            var data = '{0}'.split('&');
+            var inputs = document.getElementsByTagName('input');
+            
+            for (var i = 0; i < data.length; ++i) {{
+                var pair = data[i].split('=');
+                if (pair.length != 2)
+                    continue;
+                var key = decodeURIComponent(pair[0]);
+                var val = decodeURIComponent(pair[1]);
+                for (var j = 0; j < inputs.length; ++j) {{
+                    var input = inputs[j];
+                    var type = input.type.toLowerCase();
+                    if (type != 'text' && type != 'password' &&
+                        type != 'email')
+                        continue;
+                    if (input.name == key)
+                        input.value = val;
+                }}
+            }}
+            
+            }})()"""
+    
+    data = bytes(data).decode("utf-8")
+    data = data.replace("'", "\\'")
+    return source.format(data)
--- a/WebBrowser/WebBrowserPage.py	Sun Feb 14 19:34:05 2016 +0100
+++ b/WebBrowser/WebBrowserPage.py	Mon Feb 15 20:01:02 2016 +0100
@@ -201,6 +201,10 @@
 ##            self.__restoreFrameStateRequested)
         self.featurePermissionRequested.connect(
             self.__featurePermissionRequested)
+        
+        self.authenticationRequired.connect(
+            WebBrowser.WebBrowserWindow.WebBrowserWindow.networkManager()
+            .authentication)
     
     def acceptNavigationRequest(self, url, type_, isMainFrame):
         """
--- a/WebBrowser/WebBrowserWindow.py	Sun Feb 14 19:34:05 2016 +0100
+++ b/WebBrowser/WebBrowserWindow.py	Mon Feb 15 20:01:02 2016 +0100
@@ -85,7 +85,7 @@
 ##    _helpEngine = None
     _bookmarksManager = None
     _historyManager = None
-##    _passwordManager = None
+    _passwordManager = None
 ##    _adblockManager = None
 ##    _downloadManager = None
 ##    _feedsManager = None
@@ -1541,22 +1541,21 @@
                 self.__showEnginesConfigurationDialog)
         self.__actions.append(self.searchEnginesAct)
         
-        # TODO: Passwords
-##        self.passwordsAct = E5Action(
-##            self.tr('Manage Saved Passwords'),
-##            UI.PixmapCache.getIcon("passwords.png"),
-##            self.tr('Manage Saved Passwords...'),
-##            0, 0,
-##            self, 'webbrowser_manage_passwords')
-##        self.passwordsAct.setStatusTip(self.tr(
-##            'Manage the saved passwords'))
-##        self.passwordsAct.setWhatsThis(self.tr(
-##            """<b>Manage Saved Passwords...</b>"""
-##            """<p>Opens a dialog to manage the saved passwords.</p>"""
-##        ))
-##        if not self.__initShortcutsOnly:
-##            self.passwordsAct.triggered.connect(self.__showPasswordsDialog)
-##        self.__actions.append(self.passwordsAct)
+        self.passwordsAct = E5Action(
+            self.tr('Manage Saved Passwords'),
+            UI.PixmapCache.getIcon("passwords.png"),
+            self.tr('Manage Saved Passwords...'),
+            0, 0,
+            self, 'webbrowser_manage_passwords')
+        self.passwordsAct.setStatusTip(self.tr(
+            'Manage the saved passwords'))
+        self.passwordsAct.setWhatsThis(self.tr(
+            """<b>Manage Saved Passwords...</b>"""
+            """<p>Opens a dialog to manage the saved passwords.</p>"""
+        ))
+        if not self.__initShortcutsOnly:
+            self.passwordsAct.triggered.connect(self.__showPasswordsDialog)
+        self.__actions.append(self.passwordsAct)
         
         # TODO: AdBlock
 ##        self.adblockAct = E5Action(
@@ -1862,8 +1861,8 @@
         menu.addAction(self.editMessageFilterAct)
         menu.addSeparator()
         menu.addAction(self.searchEnginesAct)
-##        menu.addSeparator()
-##        menu.addAction(self.passwordsAct)
+        menu.addSeparator()
+        menu.addAction(self.passwordsAct)
 ##        if SSL_AVAILABLE:
 ##            menu.addAction(self.certificatesAct)
 ##        menu.addSeparator()
@@ -2481,8 +2480,8 @@
         
 ##        self.historyManager().close()
 ##        
-##        self.passwordManager().close()
-##        
+        self.passwordManager().close()
+        
 ##        self.adBlockManager().close()
 ##        
 ##        self.userAgentsManager().close()
@@ -2821,13 +2820,12 @@
         @param oldPassword current master password (string)
         @param newPassword new master password (string)
         """
-        # TODO: PasswordManager
-##        from Preferences.ConfigurationDialog import ConfigurationDialog
-##        self.passwordManager().masterPasswordChanged(oldPassword, newPassword)
-##        if self.__fromEric and isinstance(self.sender(), ConfigurationDialog):
-##            # we were called from our local configuration dialog
-##            Preferences.convertPasswords(oldPassword, newPassword)
-##            Utilities.crypto.changeRememberedMaster(newPassword)
+        from Preferences.ConfigurationDialog import ConfigurationDialog
+        self.passwordManager().masterPasswordChanged(oldPassword, newPassword)
+        if self.__fromEric and isinstance(self.sender(), ConfigurationDialog):
+            # we were called from our local configuration dialog
+            Preferences.convertPasswords(oldPassword, newPassword)
+            Utilities.crypto.changeRememberedMaster(newPassword)
     
 ##    def __showAcceptedLanguages(self):
 ##        """
@@ -3239,7 +3237,7 @@
             item = backItems[index]
             act = QAction(self)
             act.setData(-1 * (index + 1))
-            icon = HelpWindow.icon(item.url())
+            icon = WebBrowserWindow.icon(item.url())
             act.setIcon(icon)
             act.setText(item.title())
             self.backMenu.addAction(act)
@@ -3256,7 +3254,7 @@
             item = forwardItems[index]
             act = QAction(self)
             act.setData(index + 1)
-            icon = HelpWindow.icon(item.url())
+            icon = WebBrowserWindow.icon(item.url())
             act.setIcon(icon)
             act.setText(item.title())
             self.forwardMenu.addAction(act)
@@ -3311,9 +3309,8 @@
             # TODO: Cookies
 ##            if cookies:
 ##                self.cookieJar().clear()
-            # TODO: Passwords
-##            if passwords:
-##                self.passwordManager().clear()
+            if passwords:
+                self.passwordManager().clear()
             # TODO: Web Databases
 ##            if databases:
 ##                if hasattr(QWebDatabase, "removeAllDatabases"):
@@ -3357,15 +3354,15 @@
         """
         return self.searchEnginesAct
         
-##    def __showPasswordsDialog(self):
-##        """
-##        Private slot to show the passwords management dialog.
-##        """
-##        from .Passwords.PasswordsDialog import PasswordsDialog
-##        
-##        dlg = PasswordsDialog(self)
-##        dlg.exec_()
-##        
+    def __showPasswordsDialog(self):
+        """
+        Private slot to show the passwords management dialog.
+        """
+        from .Passwords.PasswordsDialog import PasswordsDialog
+        
+        dlg = PasswordsDialog(self)
+        dlg.exec_()
+        
 ##    def __showCertificatesDialog(self):
 ##        """
 ##        Private slot to show the certificates management dialog.
@@ -3512,19 +3509,19 @@
         
         return cls._historyManager
         
-##    @classmethod
-##    def passwordManager(cls):
-##        """
-##        Class method to get a reference to the password manager.
-##        
-##        @return reference to the password manager (PasswordManager)
-##        """
-##        if cls._passwordManager is None:
-##            from .Passwords.PasswordManager import PasswordManager
-##            cls._passwordManager = PasswordManager()
-##        
-##        return cls._passwordManager
-##        
+    @classmethod
+    def passwordManager(cls):
+        """
+        Class method to get a reference to the password manager.
+        
+        @return reference to the password manager (PasswordManager)
+        """
+        if cls._passwordManager is None:
+            from .Passwords.PasswordManager import PasswordManager
+            cls._passwordManager = PasswordManager()
+        
+        return cls._passwordManager
+        
 ##    @classmethod
 ##    def adBlockManager(cls):
 ##        """
@@ -3551,7 +3548,7 @@
 ##        """
 ##        Class method to get a reference to the download manager.
 ##        
-##        @return reference to the password manager (DownloadManager)
+##        @return reference to the download manager (DownloadManager)
 ##        """
 ##        if cls._downloadManager is None:
 ##            from .Download.DownloadManager import DownloadManager
--- a/eric6.e4p	Sun Feb 14 19:34:05 2016 +0100
+++ b/eric6.e4p	Mon Feb 15 20:01:02 2016 +0100
@@ -1319,6 +1319,13 @@
     <Source>WebBrowser/OpenSearch/OpenSearchReader.py</Source>
     <Source>WebBrowser/OpenSearch/OpenSearchWriter.py</Source>
     <Source>WebBrowser/OpenSearch/__init__.py</Source>
+    <Source>WebBrowser/Passwords/LoginForm.py</Source>
+    <Source>WebBrowser/Passwords/PasswordManager.py</Source>
+    <Source>WebBrowser/Passwords/PasswordModel.py</Source>
+    <Source>WebBrowser/Passwords/PasswordReader.py</Source>
+    <Source>WebBrowser/Passwords/PasswordWriter.py</Source>
+    <Source>WebBrowser/Passwords/PasswordsDialog.py</Source>
+    <Source>WebBrowser/Passwords/__init__.py</Source>
     <Source>WebBrowser/SearchWidget.py</Source>
     <Source>WebBrowser/Tools/Scripts.py</Source>
     <Source>WebBrowser/Tools/WebBrowserTools.py</Source>
@@ -1750,6 +1757,7 @@
     <Form>WebBrowser/History/HistoryDialog.ui</Form>
     <Form>WebBrowser/OpenSearch/OpenSearchDialog.ui</Form>
     <Form>WebBrowser/OpenSearch/OpenSearchEditDialog.ui</Form>
+    <Form>WebBrowser/Passwords/PasswordsDialog.ui</Form>
     <Form>WebBrowser/SearchWidget.ui</Form>
     <Form>WebBrowser/UrlBar/BookmarkActionSelectionDialog.ui</Form>
     <Form>WebBrowser/UrlBar/BookmarkInfoDialog.ui</Form>

eric ide

mercurial