diff options
27 files changed, 713 insertions, 21 deletions
diff --git a/examples/webengine/quicknanobrowser/BrowserWindow.qml b/examples/webengine/quicknanobrowser/BrowserWindow.qml index 088b23e59..39a13df59 100644 --- a/examples/webengine/quicknanobrowser/BrowserWindow.qml +++ b/examples/webengine/quicknanobrowser/BrowserWindow.qml @@ -57,7 +57,7 @@ import QtQuick.Controls.Styles 1.0 import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.0 import QtQuick.Window 2.1 -import QtWebEngine 1.10 +import QtWebEngine 1.11 ApplicationWindow { id: browserWindow @@ -73,6 +73,10 @@ ApplicationWindow { // Make sure the Qt.WindowFullscreenButtonHint is set on OS X. Component.onCompleted: flags = flags | Qt.WindowFullscreenButtonHint + onCurrentWebViewChanged: { + findBar.reset(); + } + // Create a styleItem to determine the platform. // When using style "mac", ToolButtons are not supposed to accept focus. QQCPrivate.StyleItem { id: styleItem } @@ -136,6 +140,9 @@ ApplicationWindow { fullScreenNotification.hide(); currentWebView.triggerWebAction(WebEngineView.ExitFullScreen); } + + if (findBar.visible) + findBar.visible = false; } } Action { @@ -187,6 +194,21 @@ ApplicationWindow { shortcut: StandardKey.Forward onTriggered: currentWebView.triggerWebAction(WebEngineView.Forward) } + Action { + shortcut: StandardKey.Find + onTriggered: { + if (!findBar.visible) + findBar.visible = true; + } + } + Action { + shortcut: StandardKey.FindNext + onTriggered: findBar.findNext() + } + Action { + shortcut: StandardKey.FindPrevious + onTriggered: findBar.findPrevious() + } toolBar: ToolBar { id: navigationBar @@ -553,6 +575,19 @@ ApplicationWindow { selection.certificates[0].select(); } + onFindTextFinished: function(result) { + if (!findBar.visible) + findBar.visible = true; + + findBar.numberOfMatches = result.numberOfMatches; + findBar.activeMatchOrdinal = result.activeMatchOrdinal; + } + + onLoadingChanged: function(loadRequest) { + if (loadRequest.status == WebEngineView.LoadStartedStatus) + findBar.reset(); + } + Timer { id: reloadTimer interval: 0 @@ -625,6 +660,28 @@ ApplicationWindow { download.accept(); } + FindBar { + id: findBar + visible: false + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.top: parent.top + + onFindNext: { + if (text) + currentWebView && currentWebView.findText(text); + else if (!visible) + visible = true; + } + onFindPrevious: { + if (text) + currentWebView && currentWebView.findText(text, WebEngineView.FindBackward); + else if (!visible) + visible = true; + } + } + + Rectangle { id: statusBubble color: "oldlace" diff --git a/examples/webengine/quicknanobrowser/FindBar.qml b/examples/webengine/quicknanobrowser/FindBar.qml new file mode 100644 index 000000000..2d673592a --- /dev/null +++ b/examples/webengine/quicknanobrowser/FindBar.qml @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.0 + +Rectangle { + id: root + + property int numberOfMatches: 0 + property int activeMatchOrdinal: 0 + property alias text: findTextField.text + + function reset() { + numberOfMatches = 0; + activeMatchOrdinal = 0; + visible = false; + } + + signal findNext() + signal findPrevious() + + width: 250 + height: 35 + radius: 2 + + border.width: 1 + border.color: "black" + color: "white" + + onVisibleChanged: { + if (visible) + findTextField.forceActiveFocus(); + } + + + RowLayout { + anchors.fill: parent + anchors.topMargin: 5 + anchors.bottomMargin: 5 + anchors.leftMargin: 10 + anchors.rightMargin: 10 + + spacing: 5 + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + + TextField { + id: findTextField + anchors.fill: parent + + style: TextFieldStyle { + background: Rectangle { + color: "transparent" + } + } + + onAccepted: root.findNext() + onTextChanged: root.findNext() + onActiveFocusChanged: activeFocus ? selectAll() : deselect() + } + } + + Label { + text: activeMatchOrdinal + "/" + numberOfMatches + visible: findTextField.text != "" + } + + Rectangle { + border.width: 1 + border.color: "#ddd" + width: 2 + height: parent.height + anchors.topMargin: 5 + anchors.bottomMargin: 5 + } + + ToolButton { + text: "<" + enabled: numberOfMatches > 0 + onClicked: root.findPrevious() + } + + ToolButton { + text: ">" + enabled: numberOfMatches > 0 + onClicked: root.findNext() + } + + ToolButton { + text: "x" + onClicked: root.visible = false + } + } +} diff --git a/examples/webengine/quicknanobrowser/quicknanobrowser.pro b/examples/webengine/quicknanobrowser/quicknanobrowser.pro index 5a27f5fd4..922cf79e2 100644 --- a/examples/webengine/quicknanobrowser/quicknanobrowser.pro +++ b/examples/webengine/quicknanobrowser/quicknanobrowser.pro @@ -10,6 +10,7 @@ OTHER_FILES += ApplicationRoot.qml \ BrowserDialog.qml \ BrowserWindow.qml \ DownloadView.qml \ + FindBar.qml \ FullScreenNotification.qml RESOURCES += resources.qrc diff --git a/examples/webengine/quicknanobrowser/resources.qrc b/examples/webengine/quicknanobrowser/resources.qrc index c6270897d..50ea05f5e 100644 --- a/examples/webengine/quicknanobrowser/resources.qrc +++ b/examples/webengine/quicknanobrowser/resources.qrc @@ -4,6 +4,7 @@ <file>BrowserDialog.qml</file> <file>BrowserWindow.qml</file> <file>DownloadView.qml</file> + <file>FindBar.qml</file> <file>FullScreenNotification.qml</file> </qresource> <qresource prefix="icons"> diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.cpp b/examples/webenginewidgets/simplebrowser/browserwindow.cpp index 5d00cd19a..2bb9045b0 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.cpp +++ b/examples/webenginewidgets/simplebrowser/browserwindow.cpp @@ -66,6 +66,7 @@ #include <QStatusBar> #include <QToolBar> #include <QVBoxLayout> +#include <QWebEngineFindTextResult> #include <QWebEngineProfile> BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool forDevTools) @@ -129,6 +130,7 @@ BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool connect(m_urlLineEdit, &QLineEdit::returnPressed, [this]() { m_tabWidget->setUrl(QUrl::fromUserInput(m_urlLineEdit->text())); }); + connect(m_tabWidget, &TabWidget::findTextFinished, this, &BrowserWindow::handleFindTextFinished); QAction *focusUrlLineEditAction = new QAction(this); addAction(focusUrlLineEditAction); @@ -460,10 +462,7 @@ void BrowserWindow::handleFindActionTriggered() m_lastSearch, &ok); if (ok && !search.isEmpty()) { m_lastSearch = search; - currentTab()->findText(m_lastSearch, 0, [this](bool found) { - if (!found) - statusBar()->showMessage(tr("\"%1\" not found.").arg(m_lastSearch)); - }); + currentTab()->findText(m_lastSearch); } } @@ -526,3 +525,14 @@ void BrowserWindow::handleDevToolsRequested(QWebEnginePage *source) source->setDevToolsPage(m_browser->createDevToolsWindow()->currentTab()->page()); source->triggerAction(QWebEnginePage::InspectElement); } + +void BrowserWindow::handleFindTextFinished(const QWebEngineFindTextResult &result) +{ + if (result.numberOfMatches() == 0) { + statusBar()->showMessage(tr("\"%1\" not found.").arg(m_lastSearch)); + } else { + statusBar()->showMessage(tr("\"%1\" found: %2/%3").arg(m_lastSearch, + QString::number(result.activeMatchOrdinal()), + QString::number(result.numberOfMatches()))); + } +} diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.h b/examples/webenginewidgets/simplebrowser/browserwindow.h index 8f328b751..11a655469 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.h +++ b/examples/webenginewidgets/simplebrowser/browserwindow.h @@ -88,6 +88,7 @@ private slots: void handleWebViewTitleChanged(const QString &title); void handleWebActionEnabledChanged(QWebEnginePage::WebAction action, bool enabled); void handleDevToolsRequested(QWebEnginePage *source); + void handleFindTextFinished(const QWebEngineFindTextResult &result); private: QMenu *createFileMenu(TabWidget *tabWidget); diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.cpp b/examples/webenginewidgets/simplebrowser/tabwidget.cpp index 369bebfd9..3b6d84ebe 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.cpp +++ b/examples/webenginewidgets/simplebrowser/tabwidget.cpp @@ -200,6 +200,10 @@ void TabWidget::setupView(WebView *webView) closeTab(index); }); connect(webView, &WebView::devToolsRequested, this, &TabWidget::devToolsRequested); + connect(webPage, &QWebEnginePage::findTextFinished, [this, webView](const QWebEngineFindTextResult &result) { + if (currentIndex() == indexOf(webView)) + emit findTextFinished(result); + }); } WebView *TabWidget::createTab() diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.h b/examples/webenginewidgets/simplebrowser/tabwidget.h index bf83781df..fba61d44f 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.h +++ b/examples/webenginewidgets/simplebrowser/tabwidget.h @@ -78,6 +78,7 @@ signals: void favIconChanged(const QIcon &icon); void webActionEnabledChanged(QWebEnginePage::WebAction action, bool enabled); void devToolsRequested(QWebEnginePage *source); + void findTextFinished(const QWebEngineFindTextResult &result); public slots: // current tab/page slots diff --git a/src/core/api/core_api.pro b/src/core/api/core_api.pro index 326d4481f..5e8b8387e 100644 --- a/src/core/api/core_api.pro +++ b/src/core/api/core_api.pro @@ -37,6 +37,7 @@ HEADERS = \ qtwebenginecoreglobal_p.h \ qwebenginecookiestore.h \ qwebenginecookiestore_p.h \ + qwebenginefindtextresult.h \ qwebenginehttprequest.h \ qwebenginemessagepumpscheduler_p.h \ qwebenginenotification.h \ @@ -53,6 +54,7 @@ SOURCES = \ qtwebenginecoreglobal.cpp \ qwebengineclientcertificatestore.cpp \ qwebenginecookiestore.cpp \ + qwebenginefindtextresult.cpp \ qwebenginehttprequest.cpp \ qwebenginemessagepumpscheduler.cpp \ qwebenginenotification.cpp \ diff --git a/src/core/api/qwebenginefindtextresult.cpp b/src/core/api/qwebenginefindtextresult.cpp new file mode 100644 index 000000000..ce1be359e --- /dev/null +++ b/src/core/api/qwebenginefindtextresult.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +#include "qwebenginefindtextresult.h" + +QT_BEGIN_NAMESPACE + +class QWebEngineFindTextResultPrivate : public QSharedData { +public: + int numberOfMatches = 0; + int activeMatchOrdinal = 0; +}; + +/*! + \class QWebEngineFindTextResult + \brief The QWebEngineFindTextResult class encapsulates the result of a string search on a page. + \since 5.14 + + \inmodule QtWebEngineCore + + Results are passed to the user in the + \l QWebEnginePage::findTextFinished() and + \l{WebEngineView::findTextFinished()}{WebEngineView.findTextFinished()} signals. +*/ + +/*! \internal +*/ +QWebEngineFindTextResult::QWebEngineFindTextResult() + : d(new QWebEngineFindTextResultPrivate) +{} + +/*! \internal +*/ +QWebEngineFindTextResult::QWebEngineFindTextResult(int numberOfMatches, int activeMatchOrdinal) + : d(new QWebEngineFindTextResultPrivate) +{ + d->numberOfMatches = numberOfMatches; + d->activeMatchOrdinal = activeMatchOrdinal; +} + +/*! \internal +*/ +QWebEngineFindTextResult::QWebEngineFindTextResult(const QWebEngineFindTextResult &other) + : d(other.d) +{} + +/*! \internal +*/ +QWebEngineFindTextResult &QWebEngineFindTextResult::operator=(const QWebEngineFindTextResult &other) +{ + d = other.d; + return *this; +} + +/*! \internal +*/ +QWebEngineFindTextResult::~QWebEngineFindTextResult() +{} + +/*! + \property QWebEngineFindTextResult::numberOfMatches + \brief The number of matches found. +*/ +int QWebEngineFindTextResult::numberOfMatches() const +{ + return d->numberOfMatches; +} + +/*! + \property QWebEngineFindTextResult::activeMatchOrdinal + \brief The index of the currently highlighted match. +*/ +int QWebEngineFindTextResult::activeMatchOrdinal() const +{ + return d->activeMatchOrdinal; +} + +QT_END_NAMESPACE + +#include "moc_qwebenginefindtextresult.cpp" diff --git a/src/core/api/qwebenginefindtextresult.h b/src/core/api/qwebenginefindtextresult.h new file mode 100644 index 000000000..073a8135f --- /dev/null +++ b/src/core/api/qwebenginefindtextresult.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +#ifndef QWEBENGINEFINDTEXTRESULT_H +#define QWEBENGINEFINDTEXTRESULT_H + +#include <QtWebEngineCore/qtwebenginecoreglobal.h> +#include <QtCore/QObject> +#include <QtCore/QSharedData> + +namespace QtWebEngineCore { +class FindTextHelper; +} + +QT_BEGIN_NAMESPACE + +class QWebEngineFindTextResultPrivate; + +class Q_WEBENGINECORE_EXPORT QWebEngineFindTextResult { + Q_GADGET + Q_PROPERTY(int numberOfMatches READ numberOfMatches CONSTANT FINAL) + Q_PROPERTY(int activeMatchOrdinal READ activeMatchOrdinal CONSTANT FINAL) + +public: + int numberOfMatches() const; + int activeMatchOrdinal() const; + + QWebEngineFindTextResult(); + QWebEngineFindTextResult(const QWebEngineFindTextResult &other); + QWebEngineFindTextResult &operator=(const QWebEngineFindTextResult &other); + ~QWebEngineFindTextResult(); + +private: + QWebEngineFindTextResult(int numberOfMatches, int activeMatchOrdinal); + + QSharedDataPointer<QWebEngineFindTextResultPrivate> d; + + friend class QtWebEngineCore::FindTextHelper; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QWebEngineFindTextResult) + +#endif // QWEBENGINEFINDTEXTRESULT_H diff --git a/src/core/find_text_helper.cpp b/src/core/find_text_helper.cpp index 5fb56dacc..effda529f 100644 --- a/src/core/find_text_helper.cpp +++ b/src/core/find_text_helper.cpp @@ -38,7 +38,9 @@ ****************************************************************************/ #include "find_text_helper.h" +#include "qwebenginefindtextresult.h" #include "type_conversion.h" +#include "web_contents_adapter_client.h" #include "content/public/browser/web_contents.h" #include "third_party/blink/public/mojom/frame/find_in_page.mojom.h" @@ -48,8 +50,9 @@ namespace QtWebEngineCore { // static int FindTextHelper::m_findRequestIdCounter = -1; -FindTextHelper::FindTextHelper(content::WebContents *webContents) +FindTextHelper::FindTextHelper(content::WebContents *webContents, WebContentsAdapterClient *viewClient) : m_webContents(webContents) + , m_viewClient(viewClient) , m_currentFindRequestId(m_findRequestIdCounter++) , m_lastCompletedFindRequestId(m_currentFindRequestId) { @@ -65,6 +68,7 @@ void FindTextHelper::startFinding(const QString &findText, bool caseSensitively, { if (findText.isEmpty()) { stopFinding(); + m_viewClient->findTextFinished(QWebEngineFindTextResult()); m_widgetCallbacks.invokeEmpty(resultCallback); return; } @@ -77,6 +81,7 @@ void FindTextHelper::startFinding(const QString &findText, bool caseSensitively, { if (findText.isEmpty()) { stopFinding(); + m_viewClient->findTextFinished(QWebEngineFindTextResult()); if (!resultCallback.isUndefined()) { QJSValueList args; args.append(QJSValue(0)); @@ -103,6 +108,7 @@ void FindTextHelper::startFinding(const QString &findText, bool caseSensitively, // waiting for it forever. // Assume that any unfinished find has been unsuccessful when a new one is started // to cover that case. + m_viewClient->findTextFinished(QWebEngineFindTextResult()); invokeResultCallback(m_currentFindRequestId, 0); } @@ -132,7 +138,6 @@ void FindTextHelper::handleFindReply(content::WebContents *source, int requestId const gfx::Rect &selectionRect, int activeMatchOrdinal, bool finalUpdate) { Q_UNUSED(selectionRect); - Q_UNUSED(activeMatchOrdinal); Q_ASSERT(source == m_webContents); @@ -141,6 +146,7 @@ void FindTextHelper::handleFindReply(content::WebContents *source, int requestId Q_ASSERT(m_currentFindRequestId == requestId); m_lastCompletedFindRequestId = requestId; + m_viewClient->findTextFinished(QWebEngineFindTextResult(numberOfMatches, activeMatchOrdinal)); invokeResultCallback(requestId, numberOfMatches); } diff --git a/src/core/find_text_helper.h b/src/core/find_text_helper.h index 17e76ecc7..e8f186272 100644 --- a/src/core/find_text_helper.h +++ b/src/core/find_text_helper.h @@ -66,9 +66,11 @@ class Rect; namespace QtWebEngineCore { +class WebContentsAdapterClient; + class Q_WEBENGINECORE_PRIVATE_EXPORT FindTextHelper { public: - FindTextHelper(content::WebContents *webContents); + FindTextHelper(content::WebContents *webContents, WebContentsAdapterClient *viewClient); ~FindTextHelper(); void startFinding(const QString &findText, bool caseSensitively, bool findBackward, const QWebEngineCallback<bool> resultCallback); @@ -83,6 +85,7 @@ private: void invokeResultCallback(int requestId, int numberOfMatches); content::WebContents *m_webContents; + WebContentsAdapterClient *m_viewClient; static int m_findRequestIdCounter; int m_currentFindRequestId; diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h index 4743c1ed7..4bdb55b4c 100644 --- a/src/core/web_contents_adapter_client.h +++ b/src/core/web_contents_adapter_client.h @@ -64,6 +64,7 @@ QT_FORWARD_DECLARE_CLASS(CertificateErrorController) QT_FORWARD_DECLARE_CLASS(ClientCertSelectController) QT_FORWARD_DECLARE_CLASS(QKeyEvent) QT_FORWARD_DECLARE_CLASS(QVariant) +QT_FORWARD_DECLARE_CLASS(QWebEngineFindTextResult) QT_FORWARD_DECLARE_CLASS(QWebEngineQuotaRequest) QT_FORWARD_DECLARE_CLASS(QWebEngineRegisterProtocolHandlerRequest) QT_FORWARD_DECLARE_CLASS(QWebEngineUrlRequestInfo) @@ -498,6 +499,7 @@ public: virtual TouchHandleDrawableClient *createTouchHandle(const QMap<int, QImage> &images) = 0; virtual void showTouchSelectionMenu(TouchSelectionMenuController *menuController, const QRect &bounds, const QSize &handleSize) = 0; virtual void hideTouchSelectionMenu() = 0; + virtual void findTextFinished(const QWebEngineFindTextResult &result) = 0; virtual ProfileAdapter *profileAdapter() = 0; virtual WebContentsAdapter* webContentsAdapter() = 0; diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp index e3015d5f6..9855e3859 100644 --- a/src/core/web_contents_delegate_qt.cpp +++ b/src/core/web_contents_delegate_qt.cpp @@ -103,7 +103,7 @@ static WebContentsAdapterClient::JavaScriptConsoleMessageLevel mapToJavascriptCo WebContentsDelegateQt::WebContentsDelegateQt(content::WebContents *webContents, WebContentsAdapterClient *adapterClient) : m_viewClient(adapterClient) , m_faviconManager(new FaviconManager(webContents, adapterClient)) - , m_findTextHelper(new FindTextHelper(webContents)) + , m_findTextHelper(new FindTextHelper(webContents, adapterClient)) , m_lastLoadProgress(-1) , m_loadingState(determineLoadingState(webContents)) , m_didStartLoadingSeen(m_loadingState == LoadingState::Loading) diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp index f340ebd33..58d950cd9 100644 --- a/src/webengine/api/qquickwebengineview.cpp +++ b/src/webengine/api/qquickwebengineview.cpp @@ -62,6 +62,7 @@ #include "qquickwebenginesettings_p.h" #include "qquickwebenginescript_p.h" #include "qquickwebenginetouchhandleprovider_p_p.h" +#include "qwebenginefindtextresult.h" #include "qwebenginequotarequest.h" #include "qwebengineregisterprotocolhandlerrequest.h" @@ -699,6 +700,12 @@ void QQuickWebEngineViewPrivate::widgetChanged(RenderWidgetHostViewQtDelegate *n bindViewAndWidget(q, static_cast<RenderWidgetHostViewQtDelegateQuick *>(newWidgetBase)); } +void QQuickWebEngineViewPrivate::findTextFinished(const QWebEngineFindTextResult &result) +{ + Q_Q(QQuickWebEngineView); + Q_EMIT q->findTextFinished(result); +} + WebEngineSettings *QQuickWebEngineViewPrivate::webEngineSettings() const { return m_settings->d_ptr.data(); diff --git a/src/webengine/api/qquickwebengineview_p.h b/src/webengine/api/qquickwebengineview_p.h index 3c8e1d9ec..4a88e3c28 100644 --- a/src/webengine/api/qquickwebengineview_p.h +++ b/src/webengine/api/qquickwebengineview_p.h @@ -79,6 +79,7 @@ class QQuickWebEngineSettings; class QQuickWebEngineTooltipRequest; class QQuickWebEngineFormValidationMessageRequest; class QQuickWebEngineViewPrivate; +class QWebEngineFindTextResult; class QWebEngineQuotaRequest; class QWebEngineRegisterProtocolHandlerRequest; @@ -574,6 +575,7 @@ Q_SIGNALS: Q_REVISION(10) void tooltipRequested(QQuickWebEngineTooltipRequest *request); Q_REVISION(11) void lifecycleStateChanged(LifecycleState state); Q_REVISION(11) void recommendedStateChanged(LifecycleState state); + Q_REVISION(11) void findTextFinished(const QWebEngineFindTextResult &result); #if QT_CONFIG(webengine_testsupport) void testSupportChanged(); diff --git a/src/webengine/api/qquickwebengineview_p_p.h b/src/webengine/api/qquickwebengineview_p_p.h index 7a1916d82..df6843ac3 100644 --- a/src/webengine/api/qquickwebengineview_p_p.h +++ b/src/webengine/api/qquickwebengineview_p_p.h @@ -80,6 +80,7 @@ class QQuickWebEngineSettings; class QQuickWebEngineFaviconProvider; class QQuickWebEngineProfilePrivate; class QQuickWebEngineTouchHandleProvider; +class QWebEngineFindTextResult; QQuickWebEngineView::WebAction editorActionForKeyEvent(QKeyEvent* event); @@ -169,6 +170,7 @@ public: QtWebEngineCore::WebContentsAdapter *webContentsAdapter() override; void printRequested() override; void widgetChanged(QtWebEngineCore::RenderWidgetHostViewQtDelegate *newWidgetBase) override; + void findTextFinished(const QWebEngineFindTextResult &result) override; void updateAction(QQuickWebEngineView::WebAction) const; void adoptWebContents(QtWebEngineCore::WebContentsAdapter *webContents); diff --git a/src/webengine/doc/src/webengineview_lgpl.qdoc b/src/webengine/doc/src/webengineview_lgpl.qdoc index 1c4328d01..8f03774c8 100644 --- a/src/webengine/doc/src/webengineview_lgpl.qdoc +++ b/src/webengine/doc/src/webengineview_lgpl.qdoc @@ -414,26 +414,33 @@ \qmlmethod void WebEngineView::findText(string subString) \since QtWebEngine 1.1 Finds the specified string, \a subString, in the page. + The findTextFinished() signal is emitted when a string search is completed. To clear the search highlight, just pass an empty string. + + \sa findTextFinished() */ /*! \qmlmethod void WebEngineView::findText(string subString, FindFlags options) \since QtWebEngine 1.1 Finds the specified string, \a subString, in the page, using the given \a options. + The findTextFinished() signal is emitted when a string search is completed. To clear the search highlight, just pass an empty string. \code findText("Qt", WebEngineView.FindBackward | WebEngineView.FindCaseSensitively); \endcode + + \sa findTextFinished() */ /*! \qmlmethod void WebEngineView::findText(string subString, FindFlags options, variant resultCallback) \since QtWebEngine 1.1 Finds the specified string, \a subString, in the page, using the given \a options. + The findTextFinished() signal is emitted when a string search is completed. To clear the search highlight, just pass an empty string. @@ -447,6 +454,8 @@ console.log("Qt was found!"); }); \endcode + + \sa findTextFinished() */ /*! @@ -1569,5 +1578,39 @@ resource state is however completely safe. \sa lifecycleState, {WebEngine Lifecycle Example} +*/ + +/*! + \qmltype FindTextResult + \instantiates QWebEngineFindTextResult + \inqmlmodule QtWebEngine + \since QtWebEngine 1.11 + + \brief A utility type for encapsulating the result of a string search on a page. + + \sa WebEngineView::findTextFinished() +*/ + +/*! + \qmlproperty int FindTextResult::numberOfMatches + \readonly + + \brief The number of matches found. +*/ + +/*! + \qmlproperty int FindTextResult::activeMatchOrdinal + \readonly + + \brief The index of the currently highlighted match. +*/ + +/*! + \qmlsignal WebEngineView::findTextFinished(FindTextResult result) + \since QtWebEngine 1.11 + + This signal is emitted when a string search on a page is completed. \a result is + the result of the string search. + \sa findText(), FindTextResult */ diff --git a/src/webengine/plugin/plugin.cpp b/src/webengine/plugin/plugin.cpp index ad49d6543..e47a46a95 100644 --- a/src/webengine/plugin/plugin.cpp +++ b/src/webengine/plugin/plugin.cpp @@ -55,6 +55,7 @@ #include <QtWebEngine/private/qquickwebenginetouchhandleprovider_p_p.h> #include <QtWebEngine/private/qquickwebengineview_p.h> #include <QtWebEngine/private/qquickwebengineaction_p.h> +#include <QtWebEngineCore/qwebenginefindtextresult.h> #include <QtWebEngineCore/qwebenginenotification.h> #include <QtWebEngineCore/qwebenginequotarequest.h> #include <QtWebEngineCore/qwebengineregisterprotocolhandlerrequest.h> @@ -170,6 +171,8 @@ public: qmlRegisterUncreatableType<QWebEngineNotification>(uri, 1, 9, "WebEngineNotification", msgUncreatableType("WebEngineNotification")); qmlRegisterUncreatableType<QQuickWebEngineTooltipRequest>(uri, 1, 10, "TooltipRequest", msgUncreatableType("TooltipRequest")); + qRegisterMetaType<QWebEngineFindTextResult>(); + qmlRegisterUncreatableType<QWebEngineFindTextResult>(uri, 1, 11, "FindTextResult", msgUncreatableType("FindTextResult")); } private: diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp index 29566f021..c9e9177b7 100644 --- a/src/webenginewidgets/api/qwebenginepage.cpp +++ b/src/webenginewidgets/api/qwebenginepage.cpp @@ -52,6 +52,7 @@ #include "printer_worker.h" #endif #include "qwebenginecertificateerror.h" +#include "qwebenginefindtextresult.h" #include "qwebenginefullscreenrequest.h" #include "qwebenginehistory.h" #include "qwebenginehistory_p.h" @@ -171,6 +172,7 @@ QWebEnginePagePrivate::QWebEnginePagePrivate(QWebEngineProfile *_profile) qRegisterMetaType<QWebEngineQuotaRequest>(); qRegisterMetaType<QWebEngineRegisterProtocolHandlerRequest>(); + qRegisterMetaType<QWebEngineFindTextResult>(); // See setVisible(). wasShownTimer.setSingleShot(true); @@ -698,6 +700,12 @@ void QWebEnginePagePrivate::widgetChanged(RenderWidgetHostViewQtDelegate *newWid bindPageAndWidget(q, static_cast<RenderWidgetHostViewQtDelegateWidget *>(newWidgetBase)); } +void QWebEnginePagePrivate::findTextFinished(const QWebEngineFindTextResult &result) +{ + Q_Q(QWebEnginePage); + Q_EMIT q->findTextFinished(result); +} + void QWebEnginePagePrivate::ensureInitialized() const { if (!adapter->isInitialized()) @@ -798,6 +806,16 @@ QWebEnginePage::QWebEnginePage(QObject* parent) } /*! + \fn void QWebEnginePage::findTextFinished(const QWebEngineFindTextResult &result) + \since 5.14 + + This signal is emitted when a search string search on a page is completed. \a result is + the result of the string search. + + \sa findText() +*/ + +/*! \fn void QWebEnginePage::printRequested() \since 5.12 diff --git a/src/webenginewidgets/api/qwebenginepage.h b/src/webenginewidgets/api/qwebenginepage.h index 736d7ed69..cd012ea70 100644 --- a/src/webenginewidgets/api/qwebenginepage.h +++ b/src/webenginewidgets/api/qwebenginepage.h @@ -62,6 +62,7 @@ class QWebChannel; class QWebEngineCertificateError; class QWebEngineClientCertificateSelection; class QWebEngineContextMenuData; +class QWebEngineFindTextResult; class QWebEngineFullScreenRequest; class QWebEngineHistory; class QWebEnginePage; @@ -369,6 +370,8 @@ Q_SIGNALS: void lifecycleStateChanged(LifecycleState state); void recommendedStateChanged(LifecycleState state); + void findTextFinished(const QWebEngineFindTextResult &result); + protected: virtual QWebEnginePage *createWindow(WebWindowType type); virtual QStringList chooseFiles(FileSelectionMode mode, const QStringList &oldFiles, const QStringList &acceptedMimeTypes); diff --git a/src/webenginewidgets/api/qwebenginepage_p.h b/src/webenginewidgets/api/qwebenginepage_p.h index a8cde8199..fae97b9fa 100644 --- a/src/webenginewidgets/api/qwebenginepage_p.h +++ b/src/webenginewidgets/api/qwebenginepage_p.h @@ -72,6 +72,7 @@ class WebContentsAdapter; } QT_BEGIN_NAMESPACE +class QWebEngineFindTextResult; class QWebEngineHistory; class QWebEnginePage; class QWebEngineProfile; @@ -162,6 +163,7 @@ public: ClientType clientType() override { return QtWebEngineCore::WebContentsAdapterClient::WidgetsClient; } void interceptRequest(QWebEngineUrlRequestInfo &) override; void widgetChanged(QtWebEngineCore::RenderWidgetHostViewQtDelegate *newWidget) override; + void findTextFinished(const QWebEngineFindTextResult &result) override; QtWebEngineCore::ProfileAdapter *profileAdapter() override; QtWebEngineCore::WebContentsAdapter *webContentsAdapter() override; diff --git a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc index 99bbc5162..64fe4c9cd 100644 --- a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc +++ b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc @@ -489,6 +489,7 @@ /*! \fn void QWebEnginePage::findText(const QString &subString, QWebEnginePage::FindFlags options = FindFlags(), const QWebEngineCallback<bool> &resultCallback = QWebEngineCallback<bool>()) Finds the specified string, \a subString, in the page, using the given \a options. + The findTextFinished() signal is emitted when a string search is completed. To clear the search highlight, just pass an empty string. @@ -501,6 +502,8 @@ For example: \snippet qtwebengine_qwebenginepage_snippet.cpp 0 + + \sa findTextFinished() */ /*! diff --git a/tests/auto/quick/publicapi/tst_publicapi.cpp b/tests/auto/quick/publicapi/tst_publicapi.cpp index f464d58d0..9f7dfa8ad 100644 --- a/tests/auto/quick/publicapi/tst_publicapi.cpp +++ b/tests/auto/quick/publicapi/tst_publicapi.cpp @@ -35,6 +35,7 @@ #include <QtTest/QtTest> #include <QtWebEngine/QQuickWebEngineProfile> #include <QtWebEngine/QQuickWebEngineScript> +#include <QtWebEngineCore/QWebEngineFindTextResult> #include <QtWebEngineCore/QWebEngineNotification> #include <QtWebEngineCore/QWebEngineQuotaRequest> #include <QtWebEngineCore/QWebEngineRegisterProtocolHandlerRequest> @@ -85,6 +86,7 @@ static const QList<const QMetaObject *> typesToCheck = QList<const QMetaObject * << &QWebEngineQuotaRequest::staticMetaObject << &QWebEngineRegisterProtocolHandlerRequest::staticMetaObject << &QWebEngineNotification::staticMetaObject + << &QWebEngineFindTextResult::staticMetaObject ; static QList<const char *> knownEnumNames = QList<const char *>(); @@ -276,6 +278,8 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineFileDialogRequest.dialogAccept(QStringList) --> void" << "QQuickWebEngineFileDialogRequest.dialogReject() --> void" << "QQuickWebEngineFileDialogRequest.mode --> FileMode" + << "QWebEngineFindTextResult.numberOfMatches --> int" + << "QWebEngineFindTextResult.activeMatchOrdinal --> int" << "QQuickWebEngineFormValidationMessageRequest.Hide --> RequestType" << "QQuickWebEngineFormValidationMessageRequest.Move --> RequestType" << "QQuickWebEngineFormValidationMessageRequest.Show --> RequestType" @@ -694,6 +698,7 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineView.findText(QString) --> void" << "QQuickWebEngineView.findText(QString,FindFlags) --> void" << "QQuickWebEngineView.findText(QString,FindFlags,QJSValue) --> void" + << "QQuickWebEngineView.findTextFinished(QWebEngineFindTextResult) --> void" << "QQuickWebEngineView.formValidationMessageRequested(QQuickWebEngineFormValidationMessageRequest*) --> void" << "QQuickWebEngineView.fullScreenCancelled() --> void" << "QQuickWebEngineView.fullScreenRequested(QQuickWebEngineFullScreenRequest) --> void" diff --git a/tests/auto/quick/qmltests/data/tst_findText.qml b/tests/auto/quick/qmltests/data/tst_findText.qml index 14053a675..040d324e6 100644 --- a/tests/auto/quick/qmltests/data/tst_findText.qml +++ b/tests/auto/quick/qmltests/data/tst_findText.qml @@ -38,9 +38,16 @@ TestWebEngineView { property int matchCount: 0 property bool findFailed: false + SignalSpy { + id: findTextSpy + target: webEngineView + signalName: "findTextFinished" + } + function clear() { findFailed = false matchCount = -1 + findTextSpy.clear() } function findCallbackCalled() { return matchCount != -1 } @@ -104,6 +111,9 @@ TestWebEngineView { webEngineView.findText("Hello", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 1) verify(!findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 1) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1) } function test_findTextCaseInsensitive() { @@ -115,6 +125,9 @@ TestWebEngineView { webEngineView.findText("heLLo", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 1) verify(!findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 1) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1) } function test_findTextManyMatches() { @@ -126,6 +139,9 @@ TestWebEngineView { webEngineView.findText("bla", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 100, 20000) verify(!findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 100) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1) } @@ -138,6 +154,9 @@ TestWebEngineView { webEngineView.findText("heLLo", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 0) verify(findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 0) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 0) } function test_findTextNotFound() { @@ -149,6 +168,9 @@ TestWebEngineView { webEngineView.findText("string-that-is-not-threre", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 0) verify(findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 0) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 0) } function test_findTextAfterNotFound() { @@ -160,6 +182,9 @@ TestWebEngineView { webEngineView.findText("hello", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 0) verify(findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 0) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 0) webEngineView.url = Qt.resolvedUrl("test1.html") verify(webEngineView.waitForLoadSucceeded()) @@ -168,6 +193,9 @@ TestWebEngineView { webEngineView.findText("hello", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 1) verify(!findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 1) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1) } function test_findTextInModifiedDOMAfterNotFound() { @@ -182,6 +210,9 @@ TestWebEngineView { webEngineView.findText("hello", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 0, 20000) verify(findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 0) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 0) runJavaScript("document.body.innerHTML = 'blahellobla'"); tryVerify(function() { return getBodyInnerHTML() == "blahellobla"; }, 20000); @@ -190,6 +221,9 @@ TestWebEngineView { webEngineView.findText("hello", findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, "matchCount", 1) verify(!findFailed) + tryCompare(findTextSpy, "count", 1) + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 1) + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1) } function test_findTextInterruptedByLoad() { @@ -227,5 +261,55 @@ TestWebEngineView { webEngineView.findText('New page', findFlags, webEngineView.findTextCallback) tryCompare(webEngineView, 'matchCount', 1) } + + function test_findTextActiveMatchOrdinal() { + webEngineView.loadHtml( + "<html><body>" + + "foo bar foo bar foo" + + "</body></html>"); + verify(webEngineView.waitForLoadSucceeded()); + + // Iterate over all "foo" matches. + webEngineView.clear(); + for (var i = 1; i <= 3; ++i) { + webEngineView.findText("foo"); + findTextSpy.wait(); + compare(findTextSpy.count, i); + compare(findTextSpy.signalArguments[i-1][0].numberOfMatches, 3); + compare(findTextSpy.signalArguments[i-1][0].activeMatchOrdinal, i); + } + + // The last match is followed by the fist one. + webEngineView.clear(); + webEngineView.findText("foo"); + findTextSpy.wait(); + compare(findTextSpy.count, 1); + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 3); + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1); + + // The first match is preceded by the last one. + webEngineView.clear(); + webEngineView.findText("foo", WebEngineView.FindBackward); + findTextSpy.wait(); + compare(findTextSpy.count, 1); + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 3); + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 3); + + // Finding another word resets the activeMatchOrdinal. + webEngineView.clear(); + webEngineView.findText("bar"); + findTextSpy.wait(); + compare(findTextSpy.count, 1); + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 2); + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 1); + + // If no match activeMatchOrdinal is 0. + webEngineView.clear(); + webEngineView.findText("bla"); + findTextSpy.wait(); + compare(findTextSpy.count, 1); + compare(findTextSpy.signalArguments[0][0].numberOfMatches, 0); + compare(findTextSpy.signalArguments[0][0].activeMatchOrdinal, 0); + } } } diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index 7e9815298..dbb15ba10 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -47,6 +47,7 @@ #include <qnetworkreply.h> #include <qnetworkrequest.h> #include <qwebenginedownloaditem.h> +#include <qwebenginefindtextresult.h> #include <qwebenginefullscreenrequest.h> #include <qwebenginehistory.h> #include <qwebenginenotification.h> @@ -128,6 +129,7 @@ private Q_SLOTS: void findTextResult(); void findTextSuccessiveShouldCallAllCallbacks(); void findTextCalledOnMatch(); + void findTextActiveMatchOrdinal(); void deleteQWebEngineViewTwice(); void loadSignalsOrder_data(); void loadSignalsOrder(); @@ -942,18 +944,24 @@ void tst_QWebEnginePage::findText() // Invoking a stopFinding operation will not change or clear the currently selected text, // if nothing was found beforehand. { - CallbackSpy<bool> spy; - m_view->findText("", 0, spy.ref()); - QVERIFY(spy.wasCalled()); + CallbackSpy<bool> callbackSpy; + QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); + m_view->findText("", 0, callbackSpy.ref()); + QVERIFY(callbackSpy.wasCalled()); + QCOMPARE(signalSpy.count(), 1); QTRY_COMPARE(m_view->selectedText(), QString("foo bar")); } // Invoking a startFinding operation with text that won't be found, will clear the current // selection. { - CallbackSpy<bool> spy; - m_view->findText("Will not be found", 0, spy.ref()); - QCOMPARE(spy.waitForResult(), false); + CallbackSpy<bool> callbackSpy; + QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); + m_view->findText("Will not be found", 0, callbackSpy.ref()); + QCOMPARE(callbackSpy.waitForResult(), false); + QTRY_COMPARE(signalSpy.count(), 1); + auto result = signalSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + QCOMPARE(result.numberOfMatches(), 0); QTRY_VERIFY(m_view->selectedText().isEmpty()); } @@ -964,24 +972,36 @@ void tst_QWebEnginePage::findText() // Invoking a startFinding operation with text that will be found, will clear the current // selection as well. { - CallbackSpy<bool> spy; - m_view->findText("foo", 0, spy.ref()); - QVERIFY(spy.waitForResult()); + CallbackSpy<bool> callbackSpy; + QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); + m_view->findText("foo", 0, callbackSpy.ref()); + QVERIFY(callbackSpy.waitForResult()); + QTRY_COMPARE(signalSpy.count(), 1); QTRY_VERIFY(m_view->selectedText().isEmpty()); } // Invoking a stopFinding operation after text was found, will set the selected text to the // found text. { - CallbackSpy<bool> spy; - m_view->findText("", 0, spy.ref()); - QTRY_VERIFY(spy.wasCalled()); + CallbackSpy<bool> callbackSpy; + QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); + m_view->findText("", 0, callbackSpy.ref()); + QTRY_VERIFY(callbackSpy.wasCalled()); + QTRY_COMPARE(signalSpy.count(), 1); QTRY_COMPARE(m_view->selectedText(), QString("foo")); } } void tst_QWebEnginePage::findTextResult() { + QSignalSpy findTextSpy(m_view->page(), &QWebEnginePage::findTextFinished); + auto signalResult = [&findTextSpy]() -> QVector<int> { + if (findTextSpy.count() != 1) + return QVector<int>({-1, -1}); + auto r = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + return QVector<int>({ r.numberOfMatches(), r.activeMatchOrdinal() }); + }; + // findText will abort in blink if the view has an empty size. m_view->resize(800, 600); m_view->show(); @@ -991,15 +1011,21 @@ void tst_QWebEnginePage::findTextResult() QTRY_COMPARE(loadSpy.count(), 1); QCOMPARE(findTextSync(m_page, ""), false); + QCOMPARE(signalResult(), QVector<int>({0, 0})); const QStringList words = { "foo", "bar" }; for (const QString &subString : words) { QCOMPARE(findTextSync(m_page, subString), true); + QCOMPARE(signalResult(), QVector<int>({1, 1})); + QCOMPARE(findTextSync(m_page, ""), false); + QCOMPARE(signalResult(), QVector<int>({0, 0})); } QCOMPARE(findTextSync(m_page, "blahhh"), false); + QCOMPARE(signalResult(), QVector<int>({0, 0})); QCOMPARE(findTextSync(m_page, ""), false); + QCOMPARE(signalResult(), QVector<int>({0, 0})); } void tst_QWebEnginePage::findTextSuccessiveShouldCallAllCallbacks() @@ -1035,6 +1061,7 @@ void tst_QWebEnginePage::findTextCalledOnMatch() m_view->setHtml(QString("<html><head></head><body><div>foo bar</div></body></html>")); QTRY_COMPARE(loadSpy.count(), 1); + // CALLBACK bool callbackCalled = false; m_view->page()->findText("foo", 0, [this, &callbackCalled](bool found) { QVERIFY(found); @@ -1045,6 +1072,68 @@ void tst_QWebEnginePage::findTextCalledOnMatch() }); }); QTRY_VERIFY(callbackCalled); + + // SIGNAL + int findTextFinishedCount = 0; + connect(m_view->page(), &QWebEnginePage::findTextFinished, [this, &findTextFinishedCount](QWebEngineFindTextResult result) { + QCOMPARE(result.numberOfMatches(), 1); + if (findTextFinishedCount == 0) + m_view->page()->findText("bar"); + findTextFinishedCount++; + }); + + m_view->page()->findText("foo"); + QTRY_COMPARE(findTextFinishedCount, 2); +} + +void tst_QWebEnginePage::findTextActiveMatchOrdinal() +{ + QSignalSpy loadSpy(m_view->page(), &QWebEnginePage::loadFinished); + QSignalSpy findTextSpy(m_view->page(), &QWebEnginePage::findTextFinished); + QWebEngineFindTextResult result; + + // findText will abort in blink if the view has an empty size. + m_view->resize(800, 600); + m_view->show(); + m_view->setHtml(QString("<html><head></head><body><div>foo bar foo bar foo</div></body></html>")); + QTRY_COMPARE(loadSpy.count(), 1); + + // Iterate over all "foo" matches. + for (int i = 1; i <= 3; ++i) { + m_view->page()->findText("foo", 0); + QTRY_COMPARE(findTextSpy.count(), 1); + result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + QCOMPARE(result.numberOfMatches(), 3); + QCOMPARE(result.activeMatchOrdinal(), i); + } + + // The last match is followed by the fist one. + m_view->page()->findText("foo", 0); + QTRY_COMPARE(findTextSpy.count(), 1); + result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + QCOMPARE(result.numberOfMatches(), 3); + QCOMPARE(result.activeMatchOrdinal(), 1); + + // The first match is preceded by the last one. + m_view->page()->findText("foo", QWebEnginePage::FindBackward); + QTRY_COMPARE(findTextSpy.count(), 1); + result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + QCOMPARE(result.numberOfMatches(), 3); + QCOMPARE(result.activeMatchOrdinal(), 3); + + // Finding another word resets the activeMatchOrdinal. + m_view->page()->findText("bar", 0); + QTRY_COMPARE(findTextSpy.count(), 1); + result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + QCOMPARE(result.numberOfMatches(), 2); + QCOMPARE(result.activeMatchOrdinal(), 1); + + // If no match activeMatchOrdinal is 0. + m_view->page()->findText("bla", 0); + QTRY_COMPARE(findTextSpy.count(), 1); + result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); + QCOMPARE(result.numberOfMatches(), 0); + QCOMPARE(result.activeMatchOrdinal(), 0); } static QWindow *findNewTopLevelWindow(const QWindowList &oldTopLevelWindows) |