summaryrefslogtreecommitdiff
path: root/src/webenginequick
diff options
context:
space:
mode:
authorPeter Varga <pvarga@inf.u-szeged.hu>2021-06-14 16:20:26 +0200
committerPeter Varga <pvarga@inf.u-szeged.hu>2022-06-19 03:08:06 +0200
commit19c6b0a22f20a33c8c1fd4bdea29468fa8b4e10e (patch)
tree03631f39583e2b18b06b373f364f00f3d5ab17de /src/webenginequick
parente2d3beb65653bb96342a80b436d8935e903fdd70 (diff)
downloadqtwebengine-19c6b0a22f20a33c8c1fd4bdea29468fa8b4e10e.tar.gz
Support HTML5 <datalist> element
The datalist uses Chromium's autofill component to fetch and filter predefined options in the list and autocomplete the field with the selected option. Autofill component is added to build and bound to WebEngine. All the unnecessary autofill features for datalist are supposed to be disabled: payment/credit card support, password manager, save profile data, store suggestions in database etc. Custom popups for the dropdown are implemented for Widget and Quick. Scrolling in dropdown is not implemented in this change. Fixes: QTBUG-54433 Pick-to: 6.4 Change-Id: I155d02d6dbc9d88fbca4bc5f55b76c19d0ba7a9d Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'src/webenginequick')
-rw-r--r--src/webenginequick/api/qquickwebengineview.cpp14
-rw-r--r--src/webenginequick/api/qquickwebengineview_p_p.h4
-rw-r--r--src/webenginequick/ui/AutofillPopup.qml77
-rw-r--r--src/webenginequick/ui/CMakeLists.txt1
-rw-r--r--src/webenginequick/ui_delegates_manager.cpp158
-rw-r--r--src/webenginequick/ui_delegates_manager.h53
6 files changed, 292 insertions, 15 deletions
diff --git a/src/webenginequick/api/qquickwebengineview.cpp b/src/webenginequick/api/qquickwebengineview.cpp
index babd0ade7..015f916b3 100644
--- a/src/webenginequick/api/qquickwebengineview.cpp
+++ b/src/webenginequick/api/qquickwebengineview.cpp
@@ -55,6 +55,7 @@
#include "qquickwebengineview_p_p.h"
#include "authentication_dialog_controller.h"
+#include "autofill_popup_controller.h"
#include "profile_adapter.h"
#include "file_picker_controller.h"
#include "find_text_helper.h"
@@ -737,6 +738,19 @@ void QQuickWebEngineViewPrivate::findTextFinished(const QWebEngineFindTextResult
Q_EMIT q->findTextFinished(result);
}
+void QQuickWebEngineViewPrivate::showAutofillPopup(
+ QtWebEngineCore::AutofillPopupController *controller, const QRect &bounds,
+ bool autoselectFirstSuggestion)
+{
+ ui()->showAutofillPopup(controller, bounds.bottomLeft(), bounds.width() + 2,
+ autoselectFirstSuggestion);
+}
+
+void QQuickWebEngineViewPrivate::hideAutofillPopup()
+{
+ ui()->hideAutofillPopup();
+}
+
QWebEngineSettings *QQuickWebEngineViewPrivate::webEngineSettings() const
{
return m_settings->d_ptr.data();
diff --git a/src/webenginequick/api/qquickwebengineview_p_p.h b/src/webenginequick/api/qquickwebengineview_p_p.h
index fe9f78322..9f066af90 100644
--- a/src/webenginequick/api/qquickwebengineview_p_p.h
+++ b/src/webenginequick/api/qquickwebengineview_p_p.h
@@ -167,6 +167,10 @@ public:
QtWebEngineCore::WebContentsAdapter *webContentsAdapter() override;
void printRequested() override;
void findTextFinished(const QWebEngineFindTextResult &result) override;
+ void showAutofillPopup(QtWebEngineCore::AutofillPopupController *controller,
+ const QRect &bounds, bool autoselectFirstSuggestion) override;
+ void hideAutofillPopup() override;
+
void updateAction(QQuickWebEngineView::WebAction) const;
bool adoptWebContents(QtWebEngineCore::WebContentsAdapter *webContents);
void setProfile(QQuickWebEngineProfile *profile);
diff --git a/src/webenginequick/ui/AutofillPopup.qml b/src/webenginequick/ui/AutofillPopup.qml
new file mode 100644
index 000000000..28b274bb6
--- /dev/null
+++ b/src/webenginequick/ui/AutofillPopup.qml
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtWebEngine module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import QtQuick.Controls
+
+Popup {
+ id: root
+ // Let Chromium close the popup.
+ closePolicy: Popup.NoAutoClose
+
+ property variant controller: null
+ property int itemHeight: 0
+
+ signal selected(int index)
+ signal accepted()
+
+ function setCurrentIndex(index)
+ {
+ listView.currentIndex = index;
+ }
+
+ ListView {
+ id: listView
+ anchors.fill: parent
+ clip: true
+
+ model: controller.model
+ currentIndex: -1
+
+ delegate: ItemDelegate {
+ width: listView.width
+ height: root.itemHeight
+ text: model.display
+ highlighted: ListView.isCurrentItem
+
+ onHoveredChanged: if (hovered) selected(index);
+ onClicked: accepted();
+ }
+ }
+}
diff --git a/src/webenginequick/ui/CMakeLists.txt b/src/webenginequick/ui/CMakeLists.txt
index aa5832ba6..c24d8da8d 100644
--- a/src/webenginequick/ui/CMakeLists.txt
+++ b/src/webenginequick/ui/CMakeLists.txt
@@ -1,6 +1,7 @@
set(qml_files
"AlertDialog.qml"
"AuthenticationDialog.qml"
+ "AutofillPopup.qml"
"ColorDialog.qml"
"ConfirmDialog.qml"
"FilePicker.qml"
diff --git a/src/webenginequick/ui_delegates_manager.cpp b/src/webenginequick/ui_delegates_manager.cpp
index 5a01ea2f0..06b72348c 100644
--- a/src/webenginequick/ui_delegates_manager.cpp
+++ b/src/webenginequick/ui_delegates_manager.cpp
@@ -43,6 +43,7 @@
#include "api/qquickwebengineview_p_p.h"
#include <authentication_dialog_controller.h>
+#include <autofill_popup_controller.h>
#include <color_chooser_controller.h>
#include <file_picker_controller.h>
#include <javascript_dialog_controller.h>
@@ -58,6 +59,9 @@
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlproperty.h>
+#include <QtQuick/qquickwindow.h>
+
+#include <algorithm>
// Uncomment for QML debugging
//#define UI_DELEGATES_DEBUG
@@ -129,7 +133,7 @@ UIDelegatesManager::UIDelegatesManager(QQuickWebEngineView *view)
: m_view(view)
, m_toolTip(nullptr)
, m_touchSelectionMenu(nullptr)
- FOR_EACH_COMPONENT_TYPE(COMPONENT_MEMBER_INIT, NO_SEPARATOR)
+ , m_autofillPopup(nullptr) FOR_EACH_COMPONENT_TYPE(COMPONENT_MEMBER_INIT, NO_SEPARATOR)
{
}
@@ -567,6 +571,158 @@ void UIDelegatesManager::hideTouchSelectionMenu()
QTimer::singleShot(0, m_view, [this] { m_touchSelectionMenu.reset(); });
}
+bool AutofillPopupEventFilter::eventFilter(QObject *object, QEvent *event)
+{
+ if (event->type() == QEvent::ShortcutOverride) {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
+ if (keyEvent->key() == Qt::Key_Escape) {
+ m_manager->hideAutofillPopup();
+ return true;
+ }
+
+ // Ignore shortcuts while the popup is open. It may result unwanted
+ // edit commands sent to Chromium that blocks the key press.
+ event->ignore();
+ return true;
+ }
+
+ // AutofillPopupControllerImpl::HandleKeyPressEvent()
+ // chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
+
+ if (event->type() == QEvent::KeyPress) {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
+ switch (keyEvent->key()) {
+ case Qt::Key_Up:
+ m_controller->selectPreviousSuggestion();
+ return true;
+ case Qt::Key_Down:
+ m_controller->selectNextSuggestion();
+ return true;
+ case Qt::Key_PageUp:
+ m_controller->selectFirstSuggestion();
+ return true;
+ case Qt::Key_PageDown:
+ m_controller->selectLastSuggestion();
+ return true;
+ case Qt::Key_Escape:
+ m_manager->hideAutofillPopup();
+ return true;
+ case Qt::Key_Enter:
+ case Qt::Key_Return:
+ m_controller->acceptSuggestion();
+ return true;
+ case Qt::Key_Delete:
+ // Remove suggestion is not supported for datalist.
+ // Forward delete to view to be able to remove selected text.
+ break;
+ case Qt::Key_Tab:
+ m_controller->acceptSuggestion();
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Do not forward release events of the overridden key presses.
+ if (event->type() == QEvent::KeyRelease) {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
+ switch (keyEvent->key()) {
+ case Qt::Key_Up:
+ case Qt::Key_Down:
+ case Qt::Key_PageUp:
+ case Qt::Key_PageDown:
+ case Qt::Key_Escape:
+ case Qt::Key_Enter:
+ case Qt::Key_Return:
+ return true;
+ default:
+ break;
+ }
+ }
+
+ return QObject::eventFilter(object, event);
+}
+
+void UIDelegatesManager::showAutofillPopup(QtWebEngineCore::AutofillPopupController *controller,
+ QPointF pos, int width, bool autoselectFirstSuggestion)
+{
+ static const int padding = 1;
+ static const int itemHeight = 20;
+ const int proposedHeight = itemHeight * (controller->model()->rowCount()) + padding * 2;
+
+ bool popupWasNull = false;
+ if (m_autofillPopup.isNull()) {
+ popupWasNull = true;
+ if (!ensureComponentLoaded(AutofillPopup))
+ return;
+
+ QQmlContext *context = qmlContext(m_view);
+ m_autofillPopup.reset(autofillPopupComponent->beginCreate(context));
+ if (QQuickItem *item = qobject_cast<QQuickItem *>(m_autofillPopup.data()))
+ item->setParentItem(m_view);
+ m_autofillPopup->setParent(m_view);
+ }
+
+ m_autofillPopup->setProperty("controller", QVariant::fromValue(controller));
+ m_autofillPopup->setProperty("x", pos.x());
+ m_autofillPopup->setProperty("y", pos.y());
+ m_autofillPopup->setProperty("width", width);
+ m_autofillPopup->setProperty("height",
+ std::min(proposedHeight, qRound(m_view->height() - pos.y())));
+ m_autofillPopup->setProperty("padding", padding);
+ m_autofillPopup->setProperty("itemHeight", itemHeight);
+
+ if (popupWasNull) {
+ QQmlProperty selectedSignal(m_autofillPopup.data(), QStringLiteral("onSelected"));
+ CHECK_QML_SIGNAL_PROPERTY(selectedSignal, autofillPopupComponent->url());
+ static int selectSuggestionIndex =
+ controller->metaObject()->indexOfSlot("selectSuggestion(int)");
+ QObject::connect(m_autofillPopup.data(), selectedSignal.method(), controller,
+ controller->metaObject()->method(selectSuggestionIndex));
+
+ QQmlProperty acceptedSignal(m_autofillPopup.data(), QStringLiteral("onAccepted"));
+ CHECK_QML_SIGNAL_PROPERTY(acceptedSignal, autofillPopupComponent->url());
+ static int acceptSuggestionIndex =
+ controller->metaObject()->indexOfSlot("acceptSuggestion()");
+ QObject::connect(m_autofillPopup.data(), acceptedSignal.method(), controller,
+ controller->metaObject()->method(acceptSuggestionIndex));
+
+ QObject::connect(controller, &QtWebEngineCore::AutofillPopupController::currentIndexChanged,
+ [this](const QModelIndex &index) {
+ QMetaObject::invokeMethod(m_autofillPopup.data(), "setCurrentIndex",
+ Qt::DirectConnection,
+ Q_ARG(QVariant, index.row()));
+ });
+
+ autofillPopupComponent->completeCreate();
+
+ m_view->window()->installEventFilter(
+ new AutofillPopupEventFilter(controller, this, m_autofillPopup.data()));
+
+ QMetaObject::invokeMethod(m_autofillPopup.data(), "open");
+ controller->notifyPopupShown();
+ }
+
+ if (autoselectFirstSuggestion)
+ controller->selectFirstSuggestion();
+}
+
+void UIDelegatesManager::hideAutofillPopup()
+{
+ if (!m_autofillPopup)
+ return;
+
+ QTimer::singleShot(0, m_view, [this] {
+ if (m_autofillPopup) {
+ QtWebEngineCore::AutofillPopupController *controller =
+ m_autofillPopup->property("controller")
+ .value<QtWebEngineCore::AutofillPopupController *>();
+ m_autofillPopup.reset();
+ controller->notifyPopupHidden();
+ }
+ });
+}
+
bool UIDelegatesManager::initializeImportDirs(QStringList &dirs, QQmlEngine *engine)
{
const QStringList paths = engine->importPathList();
diff --git a/src/webenginequick/ui_delegates_manager.h b/src/webenginequick/ui_delegates_manager.h
index 0f515d4be..218d403d4 100644
--- a/src/webenginequick/ui_delegates_manager.h
+++ b/src/webenginequick/ui_delegates_manager.h
@@ -47,19 +47,20 @@
#include <QtCore/qstring.h>
#include <QtCore/qstringlist.h>
-#define FOR_EACH_COMPONENT_TYPE(F, SEPARATOR) \
- F(Menu, menu) SEPARATOR \
- F(MenuItem, menuItem) SEPARATOR \
- F(MenuSeparator, menuSeparator) SEPARATOR \
- F(AlertDialog, alertDialog) SEPARATOR \
- F(ColorDialog, colorDialog) SEPARATOR \
- F(ConfirmDialog, confirmDialog) SEPARATOR \
- F(PromptDialog, promptDialog) SEPARATOR \
- F(FilePicker, filePicker) SEPARATOR \
- F(AuthenticationDialog, authenticationDialog) SEPARATOR \
- F(ToolTip, toolTip) SEPARATOR \
- F(TouchHandle, touchHandle) SEPARATOR \
- F(TouchSelectionMenu, touchSelectionMenu) SEPARATOR \
+#define FOR_EACH_COMPONENT_TYPE(F, SEPARATOR) \
+ F(Menu, menu) SEPARATOR F(MenuItem, menuItem) \
+ SEPARATOR \
+ F(MenuSeparator, menuSeparator) SEPARATOR F(AlertDialog, alertDialog) \
+ SEPARATOR \
+ F(ColorDialog, colorDialog) SEPARATOR F(ConfirmDialog, confirmDialog) \
+ SEPARATOR \
+ F(PromptDialog, promptDialog) SEPARATOR F(FilePicker, filePicker) \
+ SEPARATOR \
+ F(AuthenticationDialog, authenticationDialog) SEPARATOR F(ToolTip, toolTip) \
+ SEPARATOR \
+ F(TouchHandle, touchHandle) SEPARATOR F(TouchSelectionMenu, touchSelectionMenu) \
+ SEPARATOR \
+ F(AutofillPopup, autofillPopup) SEPARATOR
#define COMMA_SEPARATOR ,
#define SEMICOLON_SEPARATOR ;
@@ -79,6 +80,7 @@ QT_END_NAMESPACE
namespace QtWebEngineCore {
class AuthenticationDialogController;
+class AutofillPopupController;
class ColorChooserController;
class FilePickerController;
class JavaScriptDialogController;
@@ -115,14 +117,18 @@ public:
QQuickItem *createTouchHandle();
void showTouchSelectionMenu(TouchSelectionMenuController *, const QRect &, const int spacing);
void hideTouchSelectionMenu();
+ void showAutofillPopup(QtWebEngineCore::AutofillPopupController *controller, QPointF pos,
+ int width, bool autoselectFirstSuggestion);
+ void hideAutofillPopup();
private:
bool ensureComponentLoaded(ComponentType);
QQuickWebEngineView *m_view;
- QScopedPointer<QObject> m_toolTip;
QStringList m_importDirs;
+ QScopedPointer<QObject> m_toolTip;
QScopedPointer<QObject> m_touchSelectionMenu;
+ QScopedPointer<QObject> m_autofillPopup;
FOR_EACH_COMPONENT_TYPE(MEMBER_DECLARATION, SEMICOLON_SEPARATOR)
@@ -130,6 +136,25 @@ private:
};
+class AutofillPopupEventFilter : public QObject
+{
+ Q_OBJECT
+
+public:
+ AutofillPopupEventFilter(QtWebEngineCore::AutofillPopupController *controller,
+ UIDelegatesManager *manager, QObject *parent)
+ : QObject(parent), m_controller(controller), m_manager(manager)
+ {
+ }
+
+protected:
+ bool eventFilter(QObject *object, QEvent *event) override;
+
+private:
+ QtWebEngineCore::AutofillPopupController *m_controller;
+ UIDelegatesManager *m_manager;
+};
+
} // namespace QtWebEngineCore
#endif // UI_DELEGATES_MANAGER_H