1
mirror of https://invent.kde.org/network/falkon.git synced 2024-11-11 01:22:10 +01:00

AutoFill: Initial port to QtWebEngine

There are still issues with completing forms in frames
This commit is contained in:
David Rosca 2015-08-31 18:56:52 +02:00
parent a04efdc0be
commit 32a0db31b3
21 changed files with 262 additions and 445 deletions

View File

@ -24,7 +24,6 @@
#include "mainapplication.h" #include "mainapplication.h"
#include "webpage.h" #include "webpage.h"
#include "qztools.h" #include "qztools.h"
#include "networkmanager.h"
#include "browserwindow.h" #include "browserwindow.h"
#include "settings.h" #include "settings.h"

View File

@ -33,7 +33,6 @@
#include "bookmarkstoolbar.h" #include "bookmarkstoolbar.h"
#include "clearprivatedata.h" #include "clearprivatedata.h"
#include "sourceviewer.h" #include "sourceviewer.h"
#include "networkmanager.h"
#include "autofill.h" #include "autofill.h"
#include "networkmanagerproxy.h" #include "networkmanagerproxy.h"
#include "mainapplication.h" #include "mainapplication.h"

View File

@ -33,7 +33,6 @@
#include "pluginproxy.h" #include "pluginproxy.h"
#include "iconprovider.h" #include "iconprovider.h"
#include "browserwindow.h" #include "browserwindow.h"
#include "networkmanager.h"
#include "checkboxdialog.h" #include "checkboxdialog.h"
#include "profilemanager.h" #include "profilemanager.h"
#include "adblockmanager.h" #include "adblockmanager.h"
@ -268,6 +267,7 @@ MainApplication::MainApplication(int &argc, char** argv)
loadSettings(); loadSettings();
m_plugins = new PluginProxy; m_plugins = new PluginProxy;
m_autoFill = new AutoFill(this);
if (!noAddons) if (!noAddons)
m_plugins->loadPlugins(); m_plugins->loadPlugins();
@ -503,9 +503,6 @@ Bookmarks* MainApplication::bookmarks()
AutoFill* MainApplication::autoFill() AutoFill* MainApplication::autoFill()
{ {
if (!m_autoFill) {
m_autoFill = new AutoFill(this);
}
return m_autoFill; return m_autoFill;
} }

View File

@ -23,14 +23,16 @@
#include "popupwebview.h" #include "popupwebview.h"
#include "mainapplication.h" #include "mainapplication.h"
#include "autofillnotification.h" #include "autofillnotification.h"
#include "pageformcompleter.h"
#include "settings.h" #include "settings.h"
#include "passwordmanager.h" #include "passwordmanager.h"
#include "qztools.h" #include "qztools.h"
#include "scripts.h"
#include <QXmlStreamWriter> #include <QXmlStreamWriter>
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QWebEngineProfile>
#include <QWebEngineScriptCollection>
#include <QUrlQuery> #include <QUrlQuery>
@ -40,6 +42,15 @@ AutoFill::AutoFill(QObject* parent)
, m_isStoring(false) , m_isStoring(false)
{ {
loadSettings(); loadSettings();
// Setup AutoFill userscript
QWebEngineScript script;
script.setName(QSL("_qupzilla_autofill"));
script.setInjectionPoint(QWebEngineScript::DocumentReady);
script.setWorldId(QWebEngineScript::MainWorld);
script.setRunsOnSubFrames(true);
script.setSourceCode(Scripts::setupFormObserver());
mApp->webProfile()->scripts()->insert(script);
} }
PasswordManager* AutoFill::passwordManager() const PasswordManager* AutoFill::passwordManager() const
@ -166,75 +177,18 @@ void AutoFill::removeAllEntries()
m_manager->removeAllEntries(); m_manager->removeAllEntries();
} }
// If password was filled in the page, returns all saved passwords on this page void AutoFill::saveForm(QWebEnginePage *page, const QUrl &frameUrl, const PageFormData &formData)
QVector<PasswordEntry> AutoFill::completeFrame(QWebEngineFrame* frame)
{ {
bool completed = false;
QVector<PasswordEntry> list;
if (!frame) {
return list;
}
#if QTWEBENGINE_DISABLED
const QUrl frameUrl = QzTools::frameUrl(frame);
if (!isStored(frameUrl)) {
return list;
}
list = getFormData(frameUrl);
#endif
if (!list.isEmpty()) {
const PasswordEntry entry = list.first();
PageFormCompleter completer;
completed = completer.completeFormData(frame, entry.data);
}
if (!completed) {
list.clear();
}
return list;
}
void AutoFill::post(const QNetworkRequest &request, const QByteArray &outgoingData)
{
Q_UNUSED(request)
Q_UNUSED(outgoingData)
#if QTWEBENGINE_DISABLED
// Don't save in private browsing // Don't save in private browsing
if (mApp->isPrivate()) { if (mApp->isPrivate() || !page)
return; return;
}
QWebEngineFrame* frame = qobject_cast<QWebEngineFrame*>(request.originatingObject()); WebView* webView = qobject_cast<WebView*>(page->view());
if (!frame) { if (!webView)
return; return;
}
WebPage* webPage = qobject_cast<WebPage*>(frame->page()); if (!isStoringEnabled(frameUrl))
if (!webPage) {
return; return;
}
WebView* webView = qobject_cast<WebView*>(webPage->view());
if (!webView) {
return;
}
const QUrl frameUrl = QzTools::frameUrl(frame);
if (!isStoringEnabled(frameUrl)) {
return;
}
PageFormCompleter completer;
const PageFormData formData = completer.extractFormData(frame, outgoingData);
if (!formData.isValid()) {
return;
}
PasswordEntry updateData; PasswordEntry updateData;
@ -261,7 +215,24 @@ void AutoFill::post(const QNetworkRequest &request, const QByteArray &outgoingDa
AutoFillNotification* aWidget = new AutoFillNotification(frameUrl, formData, updateData); AutoFillNotification* aWidget = new AutoFillNotification(frameUrl, formData, updateData);
webView->addNotification(aWidget); webView->addNotification(aWidget);
#endif }
// Returns all saved passwords on this page
QVector<PasswordEntry> AutoFill::completePage(QWebEnginePage *page, const QUrl &frameUrl)
{
QVector<PasswordEntry> list;
if (!page || !isStored(frameUrl))
return list;
list = getFormData(frameUrl);
if (!list.isEmpty()) {
const PasswordEntry entry = list.first();
page->runJavaScript(Scripts::completeFormData(entry.data));
}
return list;
} }
QByteArray AutoFill::exportPasswords() QByteArray AutoFill::exportPasswords()

View File

@ -23,8 +23,7 @@
#include "qzcommon.h" #include "qzcommon.h"
class QUrl; class QUrl;
class QWebEngineFrame; class QWebEnginePage;
class QWebElement;
class QNetworkRequest; class QNetworkRequest;
class BrowserWindow; class BrowserWindow;
@ -32,6 +31,16 @@ class PasswordManager;
struct PageFormData; struct PageFormData;
struct PasswordEntry; struct PasswordEntry;
struct PageFormData {
QString username;
QString password;
QByteArray postData;
bool isValid() const {
return !password.isEmpty();
}
};
class QUPZILLA_EXPORT AutoFill : public QObject class QUPZILLA_EXPORT AutoFill : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -60,8 +69,8 @@ public:
void removeEntry(const PasswordEntry &entry); void removeEntry(const PasswordEntry &entry);
void removeAllEntries(); void removeAllEntries();
void post(const QNetworkRequest &request, const QByteArray &outgoingData); void saveForm(QWebEnginePage *page, const QUrl &frameUrl, const PageFormData &formData);
QVector<PasswordEntry> completeFrame(QWebEngineFrame *frame); QVector<PasswordEntry> completePage(QWebEnginePage *page, const QUrl &frameUrl);
QByteArray exportPasswords(); QByteArray exportPasswords();
bool importPasswords(const QByteArray &data); bool importPasswords(const QByteArray &data);

View File

@ -22,7 +22,6 @@
#include "qzcommon.h" #include "qzcommon.h"
#include "animatedwidget.h" #include "animatedwidget.h"
#include "pageformcompleter.h"
#include "passwordmanager.h" #include "passwordmanager.h"
#include "autofill.h" #include "autofill.h"

View File

@ -17,11 +17,11 @@
* ============================================================ */ * ============================================================ */
#include "autofillwidget.h" #include "autofillwidget.h"
#include "ui_autofillwidget.h" #include "ui_autofillwidget.h"
#include "pageformcompleter.h"
#include "autofill.h" #include "autofill.h"
#include "qztools.h" #include "qztools.h"
#include "webview.h" #include "webview.h"
#include "webpage.h" #include "webpage.h"
#include "scripts.h"
#include <QPushButton> #include <QPushButton>
@ -67,9 +67,7 @@ void AutoFillWidget::loginToPage()
if (ok && QzTools::containsIndex(m_data, index)) { if (ok && QzTools::containsIndex(m_data, index)) {
const PasswordEntry entry = m_data.at(index); const PasswordEntry entry = m_data.at(index);
m_view->page()->runJavaScript(Scripts::completeFormData(entry.data));
PageFormCompleter completer;
completer.completeFormData(m_view->page(), entry.data);
} }
close(); close();

View File

@ -1,284 +0,0 @@
/* ============================================================
* QupZilla - WebKit based browser
* Copyright (C) 2013-2014 David Rosca <nowrep@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#include "pageformcompleter.h"
#include "qzregexp.h"
#include <QWebEnginePage>
#include <QUrlQuery>
PageFormCompleter::PageFormCompleter()
: m_page(0)
, m_frame(0)
{
}
PageFormData PageFormCompleter::extractFormData(QWebEnginePage* page, const QByteArray &postData)
{
m_page = page;
return extractFormData(postData);
}
PageFormData PageFormCompleter::extractFormData(QWebEngineFrame* frame, const QByteArray &postData)
{
m_frame = frame;
return extractFormData(postData);
}
bool PageFormCompleter::completeFormData(QWebEnginePage* page, const QByteArray &data)
{
m_page = page;
return completeFormData(data);
}
bool PageFormCompleter::completeFormData(QWebEngineFrame* frame, const QByteArray &data)
{
m_frame = frame;
return completeFormData(data);
}
PageFormData PageFormCompleter::extractFormData(const QByteArray &postData) const
{
QString usernameValue;
QString passwordValue;
QByteArray data = convertWebKitFormBoundaryIfNecessary(postData);
PageFormData formData = {QString(), QString(), data};
if (data.isEmpty() || !data.contains('=')) {
return formData;
}
const QueryItems queryItems = createQueryItems(data);
if (queryItems.isEmpty()) {
return formData;
}
#if QTWEBENGINE_DISABLED
const QWebElementCollection allForms = getAllElementsFromPage("form");
// Find form that contains password value sent in data
foreach (const QWebElement &formElement, allForms) {
bool found = false;
const QWebElementCollection inputs = formElement.findAll("input[type=\"password\"]");
foreach (QWebElement inputElement, inputs) {
const QString passName = inputElement.attribute("name");
const QString passValue = inputElement.evaluateJavaScript("this.value").toString();
if (queryItemsContains(queryItems, passName, passValue)) {
// Set passwordValue if not empty (to make it possible extract forms without username field)
passwordValue = passValue;
const QueryItem item = findUsername(formElement);
if (queryItemsContains(queryItems, item.first, item.second)) {
usernameValue = item.second;
found = true;
break;
}
}
}
if (found) {
break;
}
}
// It is necessary only to find password, as there may be form without username field
if (passwordValue.isEmpty()) {
return formData;
}
formData.username = usernameValue;
formData.password = passwordValue;
#endif
return formData;
}
// Returns if any data was actually filled in page
bool PageFormCompleter::completeFormData(const QByteArray &data) const
{
bool completed = false;
const QueryItems queryItems = createQueryItems(data);
// Input types that are being completed
QStringList inputTypes;
inputTypes << "text" << "password" << "email";
#if QTWEBENGINE_DISABLED
// Find all input elements in the page
const QWebElementCollection inputs = getAllElementsFromPage("input");
for (int i = 0; i < queryItems.count(); i++) {
const QString key = queryItems.at(i).first;
const QString value = queryItems.at(i).second;
for (int i = 0; i < inputs.count(); i++) {
QWebElement element = inputs.at(i);
const QString typeAttr = element.attribute("type");
if (!inputTypes.contains(typeAttr) && !typeAttr.isEmpty()) {
continue;
}
if (key == element.attribute("name")) {
completed = true;
element.setAttribute("value", value);
}
}
}
#endif
return completed;
}
bool PageFormCompleter::queryItemsContains(const QueryItems &queryItems, const QString &attributeName,
const QString &attributeValue) const
{
if (attributeName.isEmpty() || attributeValue.isEmpty()) {
return false;
}
for (int i = 0; i < queryItems.count(); i++) {
const QueryItem item = queryItems.at(i);
if (item.first == attributeName) {
return item.second == attributeValue;
}
}
return false;
}
QByteArray PageFormCompleter::convertWebKitFormBoundaryIfNecessary(const QByteArray &data) const
{
/* Sometimes, data are passed in this format:
*
* ------WebKitFormBoundary0bBp3bFMdGwqanMp
* Content-Disposition: form-data; name="name-of-attribute"
*
* value-of-attribute
* ------WebKitFormBoundary0bBp3bFMdGwqanMp--
*
* So this function converts this format into name=value& format
*/
if (!data.contains(QByteArray("------WebKitFormBoundary"))) {
return data;
}
QByteArray formatedData;
QzRegExp rx("name=\"(.*)------WebKitFormBoundary");
rx.setMinimal(true);
int pos = 0;
while ((pos = rx.indexIn(data, pos)) != -1) {
QString string = rx.cap(1);
pos += rx.matchedLength();
int endOfAttributeName = string.indexOf(QLatin1Char('"'));
if (endOfAttributeName == -1) {
continue;
}
QString attrName = string.left(endOfAttributeName);
QString attrValue = string.mid(endOfAttributeName + 1).trimmed().remove(QLatin1Char('\n'));
if (attrName.isEmpty() || attrValue.isEmpty()) {
continue;
}
formatedData.append(attrName + "=" + attrValue + "&");
}
return formatedData;
}
PageFormCompleter::QueryItem PageFormCompleter::findUsername(const QWebElement &form) const
{
Q_UNUSED(form)
#if QTWEBENGINE_DISABLED
// Try to find username (or email) field in the form.
QStringList selectors;
selectors << "input[type=\"text\"][name*=\"user\"]"
<< "input[type=\"text\"][name*=\"name\"]"
<< "input[type=\"text\"]"
<< "input[type=\"email\"]"
<< "input:not([type=\"hidden\"][type=\"password\"])";
foreach (const QString &selector, selectors) {
const QWebElementCollection inputs = form.findAll(selector);
foreach (QWebElement element, inputs) {
const QString name = element.attribute("name");
const QString value = element.evaluateJavaScript("this.value").toString();
if (!name.isEmpty() && !value.isEmpty()) {
QueryItem item;
item.first = name;
item.second = value;
return item;
}
}
}
#endif
return QueryItem();
}
PageFormCompleter::QueryItems PageFormCompleter::createQueryItems(QByteArray data) const
{
// QUrlQuery/QUrl never encodes/decodes + and spaces
data.replace('+', ' ');
QUrlQuery query;
query.setQuery(data);
QueryItems arguments = query.queryItems(QUrl::FullyDecoded);
return arguments;
}
#if QTWEBENGINE_DISABLED
QWebElementCollection PageFormCompleter::getAllElementsFromPage(const QString &selector) const
{
QWebElementCollection list;
if (!m_page && !m_frame)
return list;
if (m_frame)
return m_frame->findAllElements(selector);
if (!m_page->mainFrame())
return list;
QList<QWebFrame*> frames;
frames.append(m_page->mainFrame());
while (!frames.isEmpty()) {
QWebFrame* frame = frames.takeFirst();
if (frame && !frame->documentElement().isNull()) {
list.append(frame->findAllElements(selector));
frames += frame->childFrames();
}
}
return list;
}
#endif

View File

@ -1,73 +0,0 @@
/* ============================================================
* QupZilla - WebKit based browser
* Copyright (C) 2013-2014 David Rosca <nowrep@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#ifndef PAGEFORMCOMPLETER_H
#define PAGEFORMCOMPLETER_H
#include <QPair>
#include <QString>
#include <QByteArray>
#include "qzcommon.h"
class QWebEnginePage;
class QWebEngineFrame;
class QWebElement;
class QWebElementCollection;
struct PageFormData {
QString username;
QString password;
QByteArray postData;
bool isValid() const {
return !password.isEmpty();
}
};
class QUPZILLA_EXPORT PageFormCompleter
{
public:
explicit PageFormCompleter();
PageFormData extractFormData(QWebEnginePage* page, const QByteArray &postData);
PageFormData extractFormData(QWebEngineFrame* frame, const QByteArray &postData);
bool completeFormData(QWebEnginePage* page, const QByteArray &data);
bool completeFormData(QWebEngineFrame* frame, const QByteArray &data);
private:
typedef QPair<QString, QString> QueryItem;
typedef QList<QPair<QString, QString> > QueryItems;
PageFormData extractFormData(const QByteArray &postData) const;
bool completeFormData(const QByteArray &data) const;
bool queryItemsContains(const QueryItems &queryItems, const QString &attributeName,
const QString &attributeValue) const;
QByteArray convertWebKitFormBoundaryIfNecessary(const QByteArray &data) const;
QueryItem findUsername(const QWebElement &form) const;
QueryItems createQueryItems(QByteArray data) const;
#if QTWEBENGINE_DISABLED
QWebElementCollection getAllElementsFromPage(const QString &selector) const;
#endif
QWebEnginePage* m_page;
QWebEngineFrame* m_frame;
};
#endif // PAGEFORMCOMPLETER_H

View File

@ -76,7 +76,6 @@ SOURCES += \
autofill/autofillicon.cpp \ autofill/autofillicon.cpp \
autofill/autofillnotification.cpp \ autofill/autofillnotification.cpp \
autofill/autofillwidget.cpp \ autofill/autofillwidget.cpp \
autofill/pageformcompleter.cpp \
autofill/passwordbackends/databaseencryptedpasswordbackend.cpp \ autofill/passwordbackends/databaseencryptedpasswordbackend.cpp \
autofill/passwordbackends/databasepasswordbackend.cpp \ autofill/passwordbackends/databasepasswordbackend.cpp \
autofill/passwordbackends/passwordbackend.cpp \ autofill/passwordbackends/passwordbackend.cpp \
@ -237,7 +236,8 @@ SOURCES += \
webtab/tabbedwebview.cpp \ webtab/tabbedwebview.cpp \
webtab/webtab.cpp \ webtab/webtab.cpp \
adblock/adblockmatcher.cpp \ adblock/adblockmatcher.cpp \
tools/scripts.cpp tools/scripts.cpp \
webengine/javascript/autofilljsobject.cpp
HEADERS += \ HEADERS += \
3rdparty/ecwin7.h \ 3rdparty/ecwin7.h \
@ -272,7 +272,6 @@ HEADERS += \
autofill/autofillicon.h \ autofill/autofillicon.h \
autofill/autofillnotification.h \ autofill/autofillnotification.h \
autofill/autofillwidget.h \ autofill/autofillwidget.h \
autofill/pageformcompleter.h \
autofill/passwordbackends/databaseencryptedpasswordbackend.h \ autofill/passwordbackends/databaseencryptedpasswordbackend.h \
autofill/passwordbackends/databasepasswordbackend.h \ autofill/passwordbackends/databasepasswordbackend.h \
autofill/passwordbackends/passwordbackend.h \ autofill/passwordbackends/passwordbackend.h \
@ -326,7 +325,7 @@ HEADERS += \
navigation/siteicon.h \ navigation/siteicon.h \
navigation/websearchbar.h \ navigation/websearchbar.h \
#network/cabundleupdater.h \ #network/cabundleupdater.h \
network/networkmanager.h \ #network/networkmanager.h \
network/networkmanagerproxy.h \ network/networkmanagerproxy.h \
network/networkproxyfactory.h \ network/networkproxyfactory.h \
network/pac/pacdatetime.h \ network/pac/pacdatetime.h \
@ -437,7 +436,8 @@ HEADERS += \
webtab/tabbedwebview.h \ webtab/tabbedwebview.h \
webtab/webtab.h \ webtab/webtab.h \
adblock/adblockmatcher.h \ adblock/adblockmatcher.h \
tools/scripts.h tools/scripts.h \
webengine/javascript/autofilljsobject.h
FORMS += \ FORMS += \
adblock/adblockaddsubscriptiondialog.ui \ adblock/adblockaddsubscriptiondialog.ui \

View File

@ -15,7 +15,6 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */ * ============================================================ */
#include "networkmanager.h"
#include "browserwindow.h" #include "browserwindow.h"
#include "autofill.h" #include "autofill.h"
#include "networkmanagerproxy.h" #include "networkmanagerproxy.h"

View File

@ -20,7 +20,6 @@
#include "editsearchengine.h" #include "editsearchengine.h"
#include "iconprovider.h" #include "iconprovider.h"
#include "mainapplication.h" #include "mainapplication.h"
#include "networkmanager.h"
#include "opensearchreader.h" #include "opensearchreader.h"
#include "opensearchengine.h" #include "opensearchengine.h"
#include "settings.h" #include "settings.h"

View File

@ -49,6 +49,57 @@ QString Scripts::setupWebChannel()
return source.arg(QzTools::readAllFileContents(QSL(":/html/qwebchannel.js"))); return source.arg(QzTools::readAllFileContents(QSL(":/html/qwebchannel.js")));
} }
QString Scripts::setupFormObserver()
{
QString source = QL1S("(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]);"
"})()");
return source;
}
QString Scripts::setCss(const QString &css) QString Scripts::setCss(const QString &css)
{ {
QString source = QL1S("(function() {" QString source = QL1S("(function() {"
@ -94,3 +145,36 @@ QString Scripts::sendPostData(const QUrl &url, const QByteArray &data)
return source.arg(url.toString(), values); return source.arg(url.toString(), values);
} }
QString Scripts::completeFormData(const QByteArray &data)
{
QString source = QL1S("(function() {"
"var data = '%1'.split('&');"
"var inputs = [];"
"var frames = [window, window.frames];"
"for (var i = 0; i < frames.length; ++i) {"
" var finputs = frames[i].document.getElementsByTagName('input');"
" for (var j = 0; j < finputs.length; ++j) {"
" var type = finputs[j].type.toLowerCase();"
" if (type == 'text' || type == 'password' || type == 'email')"
" inputs.push(finputs[j]);"
" }"
"}"
"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];"
" if (input.name == key)"
" input.value = val;"
" }"
"}"
"})()");
QString d = data;
d.replace(QL1S("'"), QL1S("\\'"));
return source.arg(d);
}

View File

@ -29,8 +29,11 @@ class QUPZILLA_EXPORT Scripts
{ {
public: public:
static QString setupWebChannel(); static QString setupWebChannel();
static QString setupFormObserver();
static QString setCss(const QString &css); static QString setCss(const QString &css);
static QString sendPostData(const QUrl &url, const QByteArray &data); static QString sendPostData(const QUrl &url, const QByteArray &data);
static QString completeFormData(const QByteArray &data);
}; };
#endif // SCRIPTS_H #endif // SCRIPTS_H

View File

@ -0,0 +1,40 @@
/* ============================================================
* QupZilla - QtWebEngine based browser
* Copyright (C) 2015 David Rosca <nowrep@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#include "autofilljsobject.h"
#include "externaljsobject.h"
#include "mainapplication.h"
#include "autofill.h"
#include "webpage.h"
AutoFillJsObject::AutoFillJsObject(ExternalJsObject *parent)
: QObject(parent)
, m_jsObject(parent)
{
}
void AutoFillJsObject::formSubmitted(const QString &frameUrl, const QString &username, const QString &password, const QByteArray &data)
{
PageFormData formData;
formData.username = username;
formData.password = password;
formData.postData = data;
mApp->autoFill()->saveForm(m_jsObject->page(), QUrl(frameUrl), formData);
}

View File

@ -0,0 +1,39 @@
/* ============================================================
* QupZilla - QtWebEngine based browser
* Copyright (C) 2015 David Rosca <nowrep@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#ifndef AUTOFILLJSOBJECT_H
#define AUTOFILLJSOBJECT_H
#include <QObject>
class ExternalJsObject;
class AutoFillJsObject : public QObject
{
Q_OBJECT
public:
explicit AutoFillJsObject(ExternalJsObject *parent);
public slots:
void formSubmitted(const QString &frameUrl, const QString &username, const QString &password, const QByteArray &data);
private:
ExternalJsObject *m_jsObject;
};
#endif // AUTOFILLJSOBJECT_H

View File

@ -21,13 +21,20 @@
#include "speeddial.h" #include "speeddial.h"
#include "webpage.h" #include "webpage.h"
#include "searchenginesmanager.h" #include "searchenginesmanager.h"
#include "autofilljsobject.h"
ExternalJsObject::ExternalJsObject(WebPage *page) ExternalJsObject::ExternalJsObject(WebPage *page)
: QObject(page) : QObject(page)
, m_page(page) , m_page(page)
, m_autoFill(new AutoFillJsObject(this))
{ {
} }
WebPage *ExternalJsObject::page() const
{
return m_page;
}
void ExternalJsObject::AddSearchProvider(const QString &engineUrl) void ExternalJsObject::AddSearchProvider(const QString &engineUrl)
{ {
mApp->searchEnginesManager()->addEngine(QUrl(engineUrl)); mApp->searchEnginesManager()->addEngine(QUrl(engineUrl));
@ -46,3 +53,8 @@ QObject *ExternalJsObject::speedDial() const
return mApp->plugins()->speedDial(); return mApp->plugins()->speedDial();
} }
QObject *ExternalJsObject::autoFill() const
{
return m_autoFill;
}

View File

@ -21,24 +21,29 @@
#include <QObject> #include <QObject>
class WebPage; class WebPage;
class AutoFillJsObject;
class ExternalJsObject : public QObject class ExternalJsObject : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QObject* speedDial READ speedDial CONSTANT) Q_PROPERTY(QObject* speedDial READ speedDial CONSTANT)
Q_PROPERTY(QObject* autoFill READ autoFill CONSTANT)
public: public:
explicit ExternalJsObject(WebPage *page); explicit ExternalJsObject(WebPage *page);
WebPage *page() const;
public slots: public slots:
void AddSearchProvider(const QString &engineUrl); void AddSearchProvider(const QString &engineUrl);
int IsSearchProviderInstalled(const QString &engineURL); int IsSearchProviderInstalled(const QString &engineURL);
private: private:
QObject *speedDial() const; QObject *speedDial() const;
QObject *autoFill() const;
WebPage *m_page; WebPage *m_page;
AutoFillJsObject *m_autoFill;
}; };
#endif // EXTERNALJSOBJECT_H #endif // EXTERNALJSOBJECT_H

View File

@ -211,6 +211,9 @@ void WebPage::finished()
// AdBlock // AdBlock
cleanBlockedObjects(); cleanBlockedObjects();
// AutoFill
m_passwordEntries = mApp->autoFill()->completePage(this, url());
} }
void WebPage::watchedFileChanged(const QString &file) void WebPage::watchedFileChanged(const QString &file)
@ -293,9 +296,17 @@ void WebPage::desktopServicesOpen(const QUrl &url)
void WebPage::setupWebChannel() void WebPage::setupWebChannel()
{ {
QWebChannel *old = webChannel();
const QString objectName = QSL("qz_object");
QWebChannel *channel = new QWebChannel(this); QWebChannel *channel = new QWebChannel(this);
channel->registerObject(QSL("qz_object"), new ExternalJsObject(this)); channel->registerObject(QSL("qz_object"), new ExternalJsObject(this));
setWebChannel(channel); setWebChannel(channel);
if (old) {
delete old->registeredObjects().value(objectName);
delete old;
}
} }
void WebPage::windowCloseRequested() void WebPage::windowCloseRequested()

View File

@ -19,7 +19,6 @@
#include "browserwindow.h" #include "browserwindow.h"
#include "webpage.h" #include "webpage.h"
#include "tabwidget.h" #include "tabwidget.h"
#include "networkmanager.h"
#include "mainapplication.h" #include "mainapplication.h"
#include "tabbar.h" #include "tabbar.h"
#include "webtab.h" #include "webtab.h"

11
tests/form-iframe.html Normal file
View File

@ -0,0 +1,11 @@
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Form completion test in iframe</title>
</head>
<body>
<h2>Form completion test in iframe</h2>
<iframe width="100%" height="80%" src="form.html"></iframe>
</body>
</html>