summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.pri13
-rw-r--r--dist/changes-5.15.28
m---------src/3rdparty0
-rw-r--r--src/buildtools/configure.json40
-rw-r--r--src/core/api/qwebenginepage.cpp5
-rw-r--r--src/core/media_capture_devices_dispatcher.cpp91
-rw-r--r--src/core/media_capture_devices_dispatcher.h2
-rw-r--r--src/core/net/proxying_url_loader_factory_qt.cpp27
-rw-r--r--src/core/ozone/gl_ozone_egl_qt.cpp4
-rw-r--r--src/core/render_widget_host_view_qt.cpp6
-rw-r--r--src/core/render_widget_host_view_qt_delegate_client.cpp199
-rw-r--r--src/core/render_widget_host_view_qt_delegate_client.h6
-rw-r--r--src/core/web_contents_delegate_qt.cpp66
-rw-r--r--src/core/web_contents_delegate_qt.h5
-rw-r--r--src/core/web_event_factory.cpp26
-rw-r--r--src/pdf/config/common.pri29
-rw-r--r--src/pdf/config/ios.pri1
-rw-r--r--src/pdf/configure.json2
-rw-r--r--src/pdf/pdfcore.pro7
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/resources/content.html3
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/resources/content2.html6
-rw-r--r--tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp86
-rw-r--r--tests/auto/quick/qmltests/BLACKLIST2
-rw-r--r--tests/auto/quick/qmltests/data/TestWebEngineView.qml6
-rw-r--r--tests/auto/quick/qmltests/data/test2.html2
-rw-r--r--tests/auto/quick/qmltests/data/tst_loadUrl.qml2
-rw-r--r--tests/auto/quick/qmltests/data/tst_newViewRequest.qml25
-rw-r--r--tests/auto/quick/qmltests/data/tst_viewSource.qml30
-rw-r--r--tests/auto/quick/qmltests/data/tst_viewSoure.qml133
-rw-r--r--tests/auto/quick/qmltests/qmltests.pro3
-rw-r--r--tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp5
-rw-r--r--tests/auto/shared/httpserver.cpp10
-rw-r--r--tests/auto/shared/httpserver.h1
-rw-r--r--tests/auto/widgets/accessibility/tst_accessibility.cpp3
-rw-r--r--tests/auto/widgets/loadsignals/tst_loadsignals.cpp40
-rw-r--r--tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp3
-rw-r--r--tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp9
-rw-r--r--tests/auto/widgets/qwebengineview/BLACKLIST3
-rw-r--r--tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp225
-rw-r--r--tests/auto/widgets/touchinput/touchinput.pro2
-rw-r--r--tests/auto/widgets/touchinput/tst_touchinput.cpp341
-rw-r--r--tests/auto/widgets/util.h37
-rw-r--r--tests/auto/widgets/widgets.pro3
43 files changed, 1087 insertions, 430 deletions
diff --git a/configure.pri b/configure.pri
index b592220a7..2d42613c0 100644
--- a/configure.pri
+++ b/configure.pri
@@ -467,10 +467,17 @@ defineTest(qtwebengine_isMacOsPlatformSupported) {
defineTest(qtwebengine_isGCCVersionSupported) {
# Keep in sync with src/webengine/doc/src/qtwebengine-platform-notes.qdoc
- greaterThan(QMAKE_GCC_MAJOR_VERSION, 4):return(true)
+ lessThan(QMAKE_GCC_MAJOR_VERSION, 5) {
+ qtwebengine_platformError("requires at least gcc version 5, but using gcc version $${QMAKE_GCC_MAJOR_VERSION}.$${QMAKE_GCC_MINOR_VERSION}.")
+ return(false)
+ }
- qtwebengine_platformError("requires at least gcc version 5, but using gcc version $${QMAKE_GCC_MAJOR_VERSION}.$${QMAKE_GCC_MINOR_VERSION}.")
- return(false)
+ equals(QMAKE_GCC_MAJOR_VERSION, 5): equals(QMAKE_GCC_MINOR_VERSION, 4): equals(QMAKE_GCC_PATCH_VERSION, 0) {
+ qtwebengine_platformError("gcc version 5.4.0 is blacklisted due to internal compiler errors.")
+ return(false)
+ }
+
+ return(true)
}
defineTest(qtwebengine_isBuildingOnWin32) {
diff --git a/dist/changes-5.15.2 b/dist/changes-5.15.2
index de6ffc584..792b9afd6 100644
--- a/dist/changes-5.15.2
+++ b/dist/changes-5.15.2
@@ -39,7 +39,7 @@ Chromium
--------
- The Chromium version has been updated to 83.0.4103.122
- - Security fixes from Chromium up to version 86.0.4240.111, including:
+ - Security fixes from Chromium up to version 86.0.4240.183, including:
- CVE-2020-6540: Heap buffer overflow in Skia
- CVE-2020-6557: Inappropriate implementation in networking
- CVE-2020-6561: Inappropriate implementation in Content Security Policy
@@ -75,8 +75,12 @@ Chromium
- CVE-2020-16001: Use after free in media.
- CVE-2020-16002: Use after free in PDFium
- CVE-2020-16003: Use after free in printing
+ - CVE-2020-16005: Insufficient policy enforcement in ANGLE
+ - CVE-2020-16008: Stack buffer overflow in WebRTC
+ - CVE-2020-16009: Inappropriate implementation in V8
+ - CVE-2020-16011: Heap buffer overflow in UI on Windows.
- Security bug 1106091
- Security bug 1107824
- Security bug 1111149
- Security bug 1125199
-
+ - Security bug 1137608
diff --git a/src/3rdparty b/src/3rdparty
-Subproject 138a7203f16cf356e9d4dac697920a22437014b
+Subproject bb90182aa90ddc886c2cb41fa34bad1412ac6ed
diff --git a/src/buildtools/configure.json b/src/buildtools/configure.json
index 2da87a11c..24ffa71aa 100644
--- a/src/buildtools/configure.json
+++ b/src/buildtools/configure.json
@@ -75,9 +75,9 @@
]
},
"webengine-harfbuzz": {
- "label": "harfbuzz >= 2.2.0",
+ "label": "harfbuzz >= 2.4.0",
"sources": [
- { "type": "pkgConfig", "args": "harfbuzz >= 2.2.0" }
+ { "type": "pkgConfig", "args": "harfbuzz >= 2.4.0 harfbuzz-subset >= 2.4.0" }
]
},
"webengine-jpeglib": {
@@ -545,6 +545,11 @@
"condition": "config.unix && features.system-harfbuzz && libs.webengine-harfbuzz",
"output": [ "privateFeature" ]
},
+ "webengine-qt-harfbuzz" : {
+ "label": "qtharfbuzz",
+ "condition": "config.static && !features.system-harfbuzz && features.harfbuzz",
+ "output": [ "privateFeature" ]
+ },
"webengine-system-glib" : {
"label": "glib",
"condition": "config.unix && libs.webengine-glib",
@@ -560,6 +565,11 @@
"condition": "config.unix && features.system-zlib && libs.webengine-zlib",
"output": [ "privateFeature" ]
},
+ "webengine-qt-zlib" : {
+ "label": "QtZlib",
+ "condition": "config.static && !features.system-zlib",
+ "output": [ "privateFeature" ]
+ },
"webengine-system-libevent" : {
"label": "libevent",
"condition": "config.unix && libs.webengine-libevent",
@@ -580,11 +590,21 @@
"condition": "config.unix && features.system-png && libs.webengine-png",
"output": [ "privateFeature" ]
},
+ "webengine-qt-png" : {
+ "label": "qtlibpng",
+ "condition" : "config.static && !features.system-png && features.png",
+ "output": [ "privateFeature" ]
+ },
"webengine-system-jpeg" : {
"label": "JPEG",
"condition": "config.unix && features.system-jpeg && libs.webengine-jpeglib",
"output": [ "privateFeature" ]
},
+ "webengine-qt-jpeg" : {
+ "label": "qtlibjpeg",
+ "condition": "config.static && !features.system-jpeg && features.jpeg",
+ "output": [ "privateFeature" ]
+ },
"webengine-system-re2": {
"label": "re2",
"condition": "config.unix && libs.webengine-re2",
@@ -627,6 +647,11 @@
"condition": "config.unix && features.system-freetype && libs.webengine-freetype",
"output": [ "privateFeature" ]
},
+ "webengine-qt-freetype" : {
+ "label": "qtfreetype",
+ "condition": "config.static && !features.system-freetype && features.freetype",
+ "output": [ "privateFeature" ]
+ },
"webengine-system-libvpx" : {
"label": "libvpx",
"condition": "config.unix && libs.webengine-libvpx",
@@ -787,6 +812,17 @@
"webengine-system-harfbuzz",
"webengine-system-freetype"
]
+ },
+ {
+ "section": "Qt 3rdparty libs",
+ "condition": "config.static",
+ "entries": [
+ "webengine-qt-freetype",
+ "webengine-qt-harfbuzz",
+ "webengine-qt-png",
+ "webengine-qt-jpeg",
+ "webengine-qt-zlib"
+ ]
}
]
}
diff --git a/src/core/api/qwebenginepage.cpp b/src/core/api/qwebenginepage.cpp
index 5e029714d..aad929611 100644
--- a/src/core/api/qwebenginepage.cpp
+++ b/src/core/api/qwebenginepage.cpp
@@ -360,8 +360,13 @@ QWebEnginePagePrivate::adoptNewWindow(QSharedPointer<WebContentsAdapter> newWebC
Q_UNUSED(targetUrl);
QWebEnginePage *newPage = q->createWindow(toWindowType(disposition));
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (!newPage)
return nullptr;
+#else
+ if (!newPage)
+ return adapter;
+#endif
if (!newWebContents->webContents())
return newPage->d_func()->adapter; // Reuse existing adapter
diff --git a/src/core/media_capture_devices_dispatcher.cpp b/src/core/media_capture_devices_dispatcher.cpp
index 62d218625..9600f220e 100644
--- a/src/core/media_capture_devices_dispatcher.cpp
+++ b/src/core/media_capture_devices_dispatcher.cpp
@@ -71,6 +71,12 @@
#include <QtCore/qcoreapplication.h>
+#if defined(WEBRTC_USE_X11)
+#include <dlfcn.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/Xlib.h>
+#endif
+
namespace QtWebEngineCore {
using content::BrowserThread;
@@ -117,25 +123,16 @@ void getDevicesForDesktopCapture(blink::MediaStreamDevices *devices,
content::DesktopMediaID getDefaultScreenId()
{
- // While this function is executing another thread may also want to create a
- // DesktopCapturer [1]. Unfortunately, creating a DesktopCapturer is not
- // thread safe on X11 due to the use of webrtc::XErrorTrap. It's safe to
- // disable this code on X11 since we don't actually need to create a
- // DesktopCapturer to get the screen id anyway
- // (ScreenCapturerLinux::GetSourceList always returns 0 as the id).
- //
- // [1]: webrtc::InProcessVideoCaptureDeviceLauncher::DoStartDesktopCaptureOnDeviceThread
-
-#if QT_CONFIG(webengine_webrtc) && !defined(WEBRTC_USE_X11)
+#if QT_CONFIG(webengine_webrtc)
// Source id patterns are different across platforms.
- // On Linux, the hardcoded value "0" is used.
+ // On Linux and macOS, the source ids are randomish numbers assigned by the OS.
// On Windows, the screens are enumerated consecutively in increasing order from 0.
- // On macOS the source ids are randomish numbers assigned by the OS.
// In order to provide a correct screen id, we query for the available screen ids, and
// select the first one as the main display id.
+#if !defined(WEBRTC_USE_X11)
// The code is based on the file
- // src/chrome/browser/extensions/api/desktop_capture/desktop_capture_base.cc.
+ // chrome/browser/media/webrtc/native_desktop_media_list.cc.
webrtc::DesktopCaptureOptions options =
webrtc::DesktopCaptureOptions::CreateDefault();
options.set_disable_effects(false);
@@ -150,7 +147,60 @@ content::DesktopMediaID getDefaultScreenId()
}
}
}
-#endif
+#else
+ // This is a workaround to avoid thread issues with DesktopCapturer [1]. Unfortunately,
+ // creating a DesktopCapturer is not thread safe on X11 due to the use of webrtc::XErrorTrap.
+ // Can be removed if https://crbug.com/2022 and/or https://crbug.com/570852 are fixed.
+ // The code is based on the file
+ // third_party/webrtc/modules/desktop_capture/linux/screen_capturer_x11.cc.
+ //
+ // [1]: webrtc::InProcessVideoCaptureDeviceLauncher::DoStartDesktopCaptureOnDeviceThread
+ Display *display = XOpenDisplay(nullptr);
+ if (!display) {
+ qWarning("Unable to open display.");
+ return content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, 0);
+ }
+
+ int randrEventBase = 0;
+ int errorBaseIgnored = 0;
+ if (!XRRQueryExtension(display, &randrEventBase, &errorBaseIgnored)) {
+ qWarning("X server does not support XRandR.");
+ return content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, 0);
+ }
+
+ int majorVersion = 0;
+ int minorVersion = 0;
+ if (!XRRQueryVersion(display, &majorVersion, &minorVersion)) {
+ qWarning("X server does not support XRandR.");
+ return content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, 0);
+ }
+
+ if (majorVersion < 1 || (majorVersion == 1 && minorVersion < 5)) {
+ qWarning("XRandR entension is older than v1.5.");
+ return content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, 0);
+ }
+
+ typedef XRRMonitorInfo *(*GetMonitorsFunc)(Display *, Window, Bool, int *);
+ GetMonitorsFunc getMonitors = reinterpret_cast<GetMonitorsFunc>(dlsym(RTLD_DEFAULT, "XRRGetMonitors"));
+ typedef void (*FreeMonitorsFunc)(XRRMonitorInfo*);
+ FreeMonitorsFunc freeMonitors = reinterpret_cast<FreeMonitorsFunc>(dlsym(RTLD_DEFAULT, "XRRFreeMonitors"));
+ if (!getMonitors && !freeMonitors) {
+ qWarning("Unable to link XRandR monitor functions.");
+ return content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, 0);
+ }
+
+ Window rootWindow = RootWindow(display, DefaultScreen(display));
+ if (rootWindow == BadValue) {
+ qWarning("Unable to get the root window.");
+ return content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, 0);
+ }
+
+ int numMonitors = 0;
+ XRRMonitorInfo *monitors = getMonitors(display, rootWindow, true, &numMonitors);
+ if (numMonitors > 0)
+ return content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, monitors[0].name);
+#endif // !defined(WEBRTC_USE_X11)
+#endif // QT_CONFIG(webengine_webrtc)
return content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, 0);
}
@@ -272,7 +322,8 @@ void MediaCaptureDevicesDispatcher::handleMediaAccessPermissionResponse(content:
break;
}
} else if (desktopVideoRequested) {
- getDevicesForDesktopCapture(&devices, getDefaultScreenId(), desktopAudioRequested,
+ bool captureAudio = desktopAudioRequested && m_loopbackAudioSupported;
+ getDevicesForDesktopCapture(&devices, getDefaultScreenId(), captureAudio,
request.video_type, request.audio_type);
}
}
@@ -309,6 +360,10 @@ MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher()
// content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
// possible use after free.
DCHECK_CURRENTLY_ON(BrowserThread::UI);
+#if defined(OS_WIN)
+ // Currently loopback audio capture is supported only on Windows.
+ m_loopbackAudioSupported = true;
+#endif
m_notificationsRegistrar.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
content::NotificationService::AllSources());
}
@@ -383,9 +438,11 @@ void MediaCaptureDevicesDispatcher::processDesktopCaptureAccessRequest(content::
}
// Audio is only supported for screen capture streams.
- bool capture_audio = (mediaId.type == content::DesktopMediaID::TYPE_SCREEN && request.audio_type == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE);
+ bool audioRequested = request.audio_type == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
+ bool audioSupported = (mediaId.type == content::DesktopMediaID::TYPE_SCREEN && m_loopbackAudioSupported);
+ bool captureAudio = (audioRequested && audioSupported);
- getDevicesForDesktopCapture(&devices, mediaId, capture_audio, request.video_type, request.audio_type);
+ getDevicesForDesktopCapture(&devices, mediaId, captureAudio, request.video_type, request.audio_type);
if (devices.empty())
std::move(callback).Run(devices, MediaStreamRequestResult::INVALID_STATE,
diff --git a/src/core/media_capture_devices_dispatcher.h b/src/core/media_capture_devices_dispatcher.h
index 6a67a53e9..17cb5d5c9 100644
--- a/src/core/media_capture_devices_dispatcher.h
+++ b/src/core/media_capture_devices_dispatcher.h
@@ -127,6 +127,8 @@ private:
content::NotificationRegistrar m_notificationsRegistrar;
+ bool m_loopbackAudioSupported = false;
+
DISALLOW_COPY_AND_ASSIGN(MediaCaptureDevicesDispatcher);
};
diff --git a/src/core/net/proxying_url_loader_factory_qt.cpp b/src/core/net/proxying_url_loader_factory_qt.cpp
index 235079c26..2b1472e88 100644
--- a/src/core/net/proxying_url_loader_factory_qt.cpp
+++ b/src/core/net/proxying_url_loader_factory_qt.cpp
@@ -271,9 +271,20 @@ void InterceptedRequest::ContinueAfterIntercept()
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (request_info_.changed()) {
- if (request_info_.d_ptr->shouldBlockRequest)
+ QWebEngineUrlRequestInfoPrivate &info = *request_info_.d_ptr;
+ if (info.shouldBlockRequest)
return SendErrorAndCompleteImmediately(net::ERR_BLOCKED_BY_CLIENT);
- if (request_info_.d_ptr->shouldRedirectRequest) {
+
+ for (auto header = info.extraHeaders.constBegin(); header != info.extraHeaders.constEnd(); ++header) {
+ std::string h = header.key().toStdString();
+ if (base::LowerCaseEqualsASCII(h, "referer")) {
+ request_.referrer = GURL(header.value().toStdString());
+ } else {
+ request_.headers.SetHeader(h, header.value().toStdString());
+ }
+ }
+
+ if (info.shouldRedirectRequest) {
net::URLRequest::FirstPartyURLPolicy first_party_url_policy =
request_.update_first_party_url_on_redirect ? net::URLRequest::UPDATE_FIRST_PARTY_URL_ON_REDIRECT
: net::URLRequest::NEVER_CHANGE_FIRST_PARTY_URL;
@@ -295,18 +306,6 @@ void InterceptedRequest::ContinueAfterIntercept()
target_client_->OnReceiveRedirect(redirectInfo, std::move(current_response_));
return;
}
-
- if (!request_info_.d_ptr->extraHeaders.isEmpty()) {
- auto end = request_info_.d_ptr->extraHeaders.constEnd();
- for (auto header = request_info_.d_ptr->extraHeaders.constBegin(); header != end; ++header) {
- std::string h = header.key().toStdString();
- if (base::LowerCaseEqualsASCII(h, "referer")) {
- request_.referrer = GURL(header.value().toStdString());
- } else {
- request_.headers.SetHeader(h, header.value().toStdString());
- }
- }
- }
}
if (!target_loader_ && target_factory_) {
diff --git a/src/core/ozone/gl_ozone_egl_qt.cpp b/src/core/ozone/gl_ozone_egl_qt.cpp
index a8b7cdfe4..14ba5e8d9 100644
--- a/src/core/ozone/gl_ozone_egl_qt.cpp
+++ b/src/core/ozone/gl_ozone_egl_qt.cpp
@@ -79,8 +79,8 @@ bool GLOzoneEGLQt::LoadGLES2Bindings(gl::GLImplementation /*implementation*/)
"eglGetProcAddress"));
if (!get_proc_address) {
// QTBUG-63341 most likely libgles2 not linked with libegl -> fallback to qpa
- QFunctionPointer address = GLContextHelper::getEglGetProcAddress();
- get_proc_address = reinterpret_cast<gl::GLGetProcAddressProc>(address);
+ get_proc_address =
+ reinterpret_cast<gl::GLGetProcAddressProc>(GLContextHelper::getEglGetProcAddress());
}
if (!get_proc_address) {
diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp
index fbb4c5bd6..05ac76350 100644
--- a/src/core/render_widget_host_view_qt.cpp
+++ b/src/core/render_widget_host_view_qt.cpp
@@ -63,7 +63,9 @@
#include "content/browser/renderer_host/render_view_host_delegate.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_input_event_router.h"
+#include "content/browser/renderer_host/ui_events_helper.h"
#include "content/common/content_switches_internal.h"
+#include "content/browser/renderer_host/ui_events_helper.h"
#include "content/common/cursors/webcursor.h"
#include "content/common/input_messages.h"
#include "third_party/skia/include/core/SkColor.h"
@@ -86,6 +88,7 @@
#include <QGuiApplication>
#include <QPixmap>
+#include <QScopeGuard>
#include <QScreen>
#include <QWindow>
@@ -767,7 +770,8 @@ void RenderWidgetHostViewQt::notifyHidden()
void RenderWidgetHostViewQt::ProcessAckedTouchEvent(const content::TouchEventWithLatencyInfo &touch, content::InputEventAckState ack_result) {
Q_UNUSED(touch);
const bool eventConsumed = ack_result == content::INPUT_EVENT_ACK_STATE_CONSUMED;
- m_gestureProvider.OnTouchEventAck(touch.event.unique_touch_event_id, eventConsumed, /*fixme: ?? */false);
+ const bool isSetNonBlocking = content::InputEventAckStateIsSetNonBlocking(ack_result);
+ m_gestureProvider.OnTouchEventAck(touch.event.unique_touch_event_id, eventConsumed, isSetNonBlocking);
}
void RenderWidgetHostViewQt::processMotionEvent(const ui::MotionEvent &motionEvent)
diff --git a/src/core/render_widget_host_view_qt_delegate_client.cpp b/src/core/render_widget_host_view_qt_delegate_client.cpp
index 1a41e6850..f4933d560 100644
--- a/src/core/render_widget_host_view_qt_delegate_client.cpp
+++ b/src/core/render_widget_host_view_qt_delegate_client.cpp
@@ -51,6 +51,7 @@
#include <QEvent>
#include <QInputMethodEvent>
+#include <QScopeGuard>
#include <QSGNode>
#include <QStyleHints>
#include <QTextFormat>
@@ -72,40 +73,38 @@ static inline int firstAvailableId(const QMap<int, int> &map)
return usedIds.first_unmarked_bit();
}
-static QList<QTouchEvent::TouchPoint>
-mapTouchPointIds(const QList<QTouchEvent::TouchPoint> &inputPoints)
+typedef QPair<int, QTouchEvent::TouchPoint> TouchPoint;
+QList<TouchPoint> RenderWidgetHostViewQtDelegateClient::mapTouchPointIds(const QList<QTouchEvent::TouchPoint> &input)
{
- static QMap<int, int> touchIdMapping;
- QList<QTouchEvent::TouchPoint> outputPoints = inputPoints;
- for (int i = 0; i < outputPoints.size(); ++i) {
- QTouchEvent::TouchPoint &point = outputPoints[i];
+ QList<TouchPoint> output;
+ for (int i = 0; i < input.size(); ++i) {
+ const QTouchEvent::TouchPoint &point = input[i];
int qtId = point.id();
- QMap<int, int>::const_iterator it = touchIdMapping.find(qtId);
- if (it == touchIdMapping.end())
- it = touchIdMapping.insert(qtId, firstAvailableId(touchIdMapping));
- QMutableEventPoint &mut = QMutableEventPoint::from(point);
- mut.setId(it.value());
+ QMap<int, int>::const_iterator it = m_touchIdMapping.find(qtId);
+ if (it == m_touchIdMapping.end()) {
+ Q_ASSERT_X(m_touchIdMapping.size() <= 16, "", "Number of mapped ids can't exceed 16 for velocity tracker");
+ it = m_touchIdMapping.insert(qtId, firstAvailableId(m_touchIdMapping));
+ }
- if (point.state() == QEventPoint::State::Released)
- touchIdMapping.remove(qtId);
+ output.append(qMakePair(it.value(), point));
}
- return outputPoints;
-}
+ Q_ASSERT(output.size() == std::accumulate(output.cbegin(), output.cend(), QSet<int>(),
+ [] (QSet<int> s, const TouchPoint &p) { s.insert(p.second.id()); return s; }).size());
-static inline bool compareTouchPoints(const QTouchEvent::TouchPoint &lhs,
- const QTouchEvent::TouchPoint &rhs)
-{
- // TouchPointPressed < TouchPointMoved < TouchPointReleased
- return lhs.state() < rhs.state();
+ for (auto &&point : qAsConst(input))
+ if (point.state() == Qt::TouchPointReleased)
+ m_touchIdMapping.remove(point.id());
+
+ return output;
}
static uint32_t s_eventId = 0;
class MotionEventQt : public ui::MotionEvent
{
public:
- MotionEventQt(const QList<QTouchEvent::TouchPoint> &touchPoints,
+ MotionEventQt(const QList<TouchPoint> &touchPoints,
const base::TimeTicks &eventTime, Action action,
const Qt::KeyboardModifiers modifiers, int index = -1)
: touchPoints(touchPoints)
@@ -115,8 +114,12 @@ public:
, flags(flagsFromModifiers(modifiers))
, index(index)
{
- // ACTION_DOWN and ACTION_UP must be accesssed through pointer_index 0
- Q_ASSERT((action != Action::DOWN && action != Action::UP) || index == 0);
+ // index is only valid for ACTION_DOWN and ACTION_UP and should correspond to the point causing it
+ // see blink_event_util.cc:ToWebTouchPointState for details
+ Q_ASSERT_X((action != Action::POINTER_DOWN && action != Action::POINTER_UP && index == -1)
+ || (action == Action::POINTER_DOWN && index >= 0 && touchPoint(index).state() == Qt::TouchPointPressed)
+ || (action == Action::POINTER_UP && index >= 0 && touchPoint(index).state() == Qt::TouchPointReleased),
+ "MotionEventQt", qPrintable(QString("action: %1, index: %2, state: %3").arg(int(action)).arg(index).arg(touchPoint(index).state())));
}
uint32_t GetUniqueEventId() const override { return eventId; }
@@ -125,39 +128,39 @@ public:
size_t GetPointerCount() const override { return touchPoints.size(); }
int GetPointerId(size_t pointer_index) const override
{
- return touchPoints.at(pointer_index).id();
+ return touchPoints[pointer_index].first;
}
float GetX(size_t pointer_index) const override
{
- return touchPoints.at(pointer_index).position().x();
+ return touchPoint(pointer_index).position().x();
}
float GetY(size_t pointer_index) const override
{
- return touchPoints.at(pointer_index).position().y();
+ return touchPoint(pointer_index).position().y();
}
float GetRawX(size_t pointer_index) const override
{
- return touchPoints.at(pointer_index).globalPosition().x();
+ return touchPoint(pointer_index).globalPosition().x();
}
float GetRawY(size_t pointer_index) const override
{
- return touchPoints.at(pointer_index).globalPosition().y();
+ return touchPoint(pointer_index).globalPosition().y();
}
float GetTouchMajor(size_t pointer_index) const override
{
- QSizeF diams = touchPoints.at(pointer_index).ellipseDiameters();
+ QSizeF diams = touchPoint(pointer_index).ellipseDiameters();
return std::max(diams.height(), diams.width());
}
float GetTouchMinor(size_t pointer_index) const override
{
- QSizeF diams = touchPoints.at(pointer_index).ellipseDiameters();
+ QSizeF diams = touchPoint(pointer_index).ellipseDiameters();
return std::min(diams.height(), diams.width());
}
float GetOrientation(size_t pointer_index) const override { return 0; }
int GetFlags() const override { return flags; }
float GetPressure(size_t pointer_index) const override
{
- return touchPoints.at(pointer_index).pressure();
+ return touchPoint(pointer_index).pressure();
}
float GetTiltX(size_t pointer_index) const override { return 0; }
float GetTiltY(size_t pointer_index) const override { return 0; }
@@ -184,12 +187,13 @@ public:
int GetButtonState() const override { return 0; }
private:
- QList<QTouchEvent::TouchPoint> touchPoints;
+ QList<TouchPoint> touchPoints;
base::TimeTicks eventTime;
Action action;
const uint32_t eventId;
int flags;
int index;
+ const QTouchEvent::TouchPoint& touchPoint(size_t i) const { return touchPoints[i].second; }
};
RenderWidgetHostViewQtDelegateClient::RenderWidgetHostViewQtDelegateClient(
@@ -567,20 +571,39 @@ void RenderWidgetHostViewQtDelegateClient::handleTouchEvent(QTouchEvent *event)
// Calculate a delta between event timestamps and Now() on the first received event, and
// apply this delta to all successive events. This delta is most likely smaller than it
// should by calculating it here but this will hopefully cause less than one frame of delay.
- base::TimeTicks eventTimestamp =
- base::TimeTicks() + base::TimeDelta::FromMilliseconds(event->timestamp());
- static base::TimeDelta eventsToNowDelta = base::TimeTicks::Now() - eventTimestamp;
- eventTimestamp += eventsToNowDelta;
+ base::TimeTicks eventTimestamp = base::TimeTicks() + base::TimeDelta::FromMilliseconds(event->timestamp());
+ if (m_eventsToNowDelta == 0)
+ m_eventsToNowDelta = (base::TimeTicks::Now() - eventTimestamp).InMicroseconds();
+ eventTimestamp += base::TimeDelta::FromMicroseconds(m_eventsToNowDelta);
+
+ auto touchPoints = mapTouchPointIds(event->touchPoints());
+ // Make sure that POINTER_DOWN action is delivered before MOVE, and MOVE before POINTER_UP
+ std::sort(touchPoints.begin(), touchPoints.end(), [] (const TouchPoint &l, const TouchPoint &r) {
+ return l.second.state() < r.second.state();
+ });
+
+ auto sc = qScopeGuard([&] () {
+ switch (event->type()) {
+ case QEvent::TouchCancel:
+ for (auto &&it : qAsConst(touchPoints))
+ m_touchIdMapping.remove(it.second.id());
+ Q_FALLTHROUGH();
+
+ case QEvent::TouchEnd:
+ m_previousTouchPoints.clear();
+ m_touchMotionStarted = false;
+ break;
- QList<QTouchEvent::TouchPoint> touchPoints = mapTouchPointIds(event->touchPoints());
- // Make sure that ACTION_POINTER_DOWN is delivered before ACTION_MOVE,
- // and ACTION_MOVE before ACTION_POINTER_UP.
- std::sort(touchPoints.begin(), touchPoints.end(), compareTouchPoints);
+ default:
+ m_previousTouchPoints = touchPoints;
+ break;
+ }
+ });
+ ui::MotionEvent::Action action;
// Check first if the touch event should be routed to the selectionController
if (!touchPoints.isEmpty()) {
- ui::MotionEvent::Action action;
- switch (touchPoints[0].state()) {
+ switch (touchPoints[0].second.state()) {
case Qt::TouchPointPressed:
action = ui::MotionEvent::Action::DOWN;
break;
@@ -594,32 +617,24 @@ void RenderWidgetHostViewQtDelegateClient::handleTouchEvent(QTouchEvent *event)
action = ui::MotionEvent::Action::NONE;
break;
}
-
- MotionEventQt motionEvent(touchPoints, eventTimestamp, action, event->modifiers(), 0);
- if (m_rwhv->getTouchSelectionController()->WillHandleTouchEvent(motionEvent)) {
- m_previousTouchPoints = touchPoints;
- event->accept();
- return;
- }
} else {
// An empty touchPoints always corresponds to a TouchCancel event.
// We can't forward touch cancellations without a previously processed touch event,
// as Chromium expects the previous touchPoints for Action::CANCEL.
// If both are empty that means the TouchCancel was sent without an ongoing touch,
// so there's nothing to cancel anyway.
+ Q_ASSERT(event->type() == QEvent::TouchCancel);
touchPoints = m_previousTouchPoints;
if (touchPoints.isEmpty())
return;
- MotionEventQt cancelEvent(touchPoints, eventTimestamp, ui::MotionEvent::Action::CANCEL,
- event->modifiers());
- if (m_rwhv->getTouchSelectionController()->WillHandleTouchEvent(cancelEvent)) {
- m_previousTouchPoints.clear();
- event->accept();
- return;
- }
+ action = ui::MotionEvent::Action::CANCEL;
}
+ MotionEventQt me(touchPoints, eventTimestamp, action, event->modifiers());
+ if (m_rwhv->getTouchSelectionController()->WillHandleTouchEvent(me))
+ return;
+
switch (event->type()) {
case QEvent::TouchBegin:
m_sendMotionActionDown = true;
@@ -629,19 +644,17 @@ void RenderWidgetHostViewQtDelegateClient::handleTouchEvent(QTouchEvent *event)
case QEvent::TouchUpdate:
m_touchMotionStarted = true;
break;
- case QEvent::TouchCancel: {
+ case QEvent::TouchCancel:
+ {
// Only process TouchCancel events received following a TouchBegin or TouchUpdate event
if (m_touchMotionStarted) {
- MotionEventQt cancelEvent(touchPoints, eventTimestamp, ui::MotionEvent::Action::CANCEL,
- event->modifiers());
+ MotionEventQt cancelEvent(touchPoints, eventTimestamp, ui::MotionEvent::Action::CANCEL, event->modifiers());
m_rwhv->processMotionEvent(cancelEvent);
}
- clearPreviousTouchMotionState();
return;
}
case QEvent::TouchEnd:
- clearPreviousTouchMotionState();
m_rwhv->getTouchSelectionControllerClient()->onTouchUp();
break;
default:
@@ -661,34 +674,50 @@ void RenderWidgetHostViewQtDelegateClient::handleTouchEvent(QTouchEvent *event)
#endif
}
- for (int i = 0; i < touchPoints.size(); ++i) {
- ui::MotionEvent::Action action;
- switch (touchPoints[i].state()) {
- case Qt::TouchPointPressed:
- if (m_sendMotionActionDown) {
- action = ui::MotionEvent::Action::DOWN;
- m_sendMotionActionDown = false;
- } else {
- action = ui::MotionEvent::Action::POINTER_DOWN;
+ // MEMO for the basis of this logic look into:
+ // * blink_event_util.cc:ToWebTouchPointState: which is used later to forward touch event
+ // composed from motion event after gesture recognition
+ // * gesture_detector.cc:GestureDetector::OnTouchEvent: contains logic for every motion
+ // event action and corresponding gesture recognition routines
+ // * input_router_imp.cc:InputRouterImp::SetMovementXYForTouchPoints: expectation about
+ // touch event content like number of points for different states
+
+ int lastPressIndex = -1;
+ while ((lastPressIndex + 1) < touchPoints.size() && touchPoints[lastPressIndex + 1].second.state() == Qt::TouchPointPressed)
+ ++lastPressIndex;
+
+ switch (event->type()) {
+ case QEvent::TouchBegin:
+ m_rwhv->processMotionEvent(MotionEventQt(touchPoints.mid(lastPressIndex),
+ eventTimestamp, ui::MotionEvent::Action::DOWN, event->modifiers()));
+ --lastPressIndex;
+ Q_FALLTHROUGH();
+
+ case QEvent::TouchUpdate:
+ for (; lastPressIndex >= 0; --lastPressIndex) {
+ Q_ASSERT(touchPoints[lastPressIndex].second.state() == Qt::TouchPointPressed);
+ MotionEventQt me(touchPoints.mid(lastPressIndex), eventTimestamp, ui::MotionEvent::Action::POINTER_DOWN, event->modifiers(), 0);
+ m_rwhv->processMotionEvent(me);
+ }
+
+ if (event->touchPointStates() & Qt::TouchPointMoved)
+ m_rwhv->processMotionEvent(MotionEventQt(touchPoints, eventTimestamp, ui::MotionEvent::Action::MOVE, event->modifiers()));
+
+ Q_FALLTHROUGH();
+
+ case QEvent::TouchEnd:
+ while (!touchPoints.isEmpty() && touchPoints.back().second.state() == Qt::TouchPointReleased) {
+ auto action = touchPoints.size() > 1 ? ui::MotionEvent::Action::POINTER_UP : ui::MotionEvent::Action::UP;
+ int index = action == ui::MotionEvent::Action::POINTER_UP ? touchPoints.size() - 1 : -1;
+ m_rwhv->processMotionEvent(MotionEventQt(touchPoints, eventTimestamp, action, event->modifiers(), index));
+ touchPoints.pop_back();
}
break;
- case Qt::TouchPointMoved:
- action = ui::MotionEvent::Action::MOVE;
- break;
- case Qt::TouchPointReleased:
- action = touchPoints.size() > 1 ? ui::MotionEvent::Action::POINTER_UP
- : ui::MotionEvent::Action::UP;
- break;
- default:
- // Ignore Qt::TouchPointStationary
- continue;
- }
- MotionEventQt motionEvent(touchPoints, eventTimestamp, action, event->modifiers(), i);
- m_rwhv->processMotionEvent(motionEvent);
+ default:
+ Q_ASSERT_X(false, __FUNCTION__, "Other event types are expected to be already handled.");
+ break;
}
-
- m_previousTouchPoints = touchPoints;
}
#if QT_CONFIG(tabletevent)
diff --git a/src/core/render_widget_host_view_qt_delegate_client.h b/src/core/render_widget_host_view_qt_delegate_client.h
index c338df95e..4ba5da227 100644
--- a/src/core/render_widget_host_view_qt_delegate_client.h
+++ b/src/core/render_widget_host_view_qt_delegate_client.h
@@ -140,9 +140,13 @@ private:
std::string m_editCommand;
// Touch
- QList<QTouchEvent::TouchPoint> m_previousTouchPoints;
+ typedef QPair<int, QTouchEvent::TouchPoint> TouchPoint;
+ QList<TouchPoint> mapTouchPointIds(const QList<QTouchEvent::TouchPoint> &input);
+ QMap<int, int> m_touchIdMapping;
+ QList<TouchPoint> m_previousTouchPoints;
bool m_touchMotionStarted = false;
bool m_sendMotionActionDown = false;
+ int64_t m_eventsToNowDelta = 0; // delta for first touch in microseconds
// IME
bool m_receivedEmptyImeEvent = false;
diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp
index d47d7dfab..2a0484234 100644
--- a/src/core/web_contents_delegate_qt.cpp
+++ b/src/core/web_contents_delegate_qt.cpp
@@ -106,7 +106,6 @@ WebContentsDelegateQt::WebContentsDelegateQt(content::WebContents *webContents,
: m_viewClient(adapterClient)
, m_faviconManager(new FaviconManager(webContents, adapterClient))
, m_findTextHelper(new FindTextHelper(webContents, adapterClient))
- , m_lastLoadProgress(-1)
, m_loadingState(determineLoadingState(webContents))
, m_didStartLoadingSeen(m_loadingState == LoadingState::Loading)
, m_frameFocusedObserver(adapterClient)
@@ -128,7 +127,7 @@ content::WebContents *WebContentsDelegateQt::OpenURLFromTab(content::WebContents
content::SiteInstance *target_site_instance = params.source_site_instance.get();
content::Referrer referrer = params.referrer;
if (params.disposition != WindowOpenDisposition::CURRENT_TAB) {
- QSharedPointer<WebContentsAdapter> targetAdapter = createWindow(0, params.disposition, gfx::Rect(), params.user_gesture);
+ QSharedPointer<WebContentsAdapter> targetAdapter = createWindow(nullptr, params.disposition, gfx::Rect(), toQt(params.url), params.user_gesture);
if (targetAdapter) {
if (targetAdapter->profile() != source->GetBrowserContext()) {
target_site_instance = nullptr;
@@ -137,6 +136,8 @@ content::WebContents *WebContentsDelegateQt::OpenURLFromTab(content::WebContents
if (!targetAdapter->isInitialized())
targetAdapter->initialize(target_site_instance);
target = targetAdapter->webContents();
+ } else {
+ return target;
}
}
Q_ASSERT(target);
@@ -234,8 +235,8 @@ QUrl WebContentsDelegateQt::url(content::WebContents* source) const {
}
void WebContentsDelegateQt::AddNewContents(content::WebContents* source, std::unique_ptr<content::WebContents> new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture, bool* was_blocked)
{
- Q_UNUSED(source);
- QSharedPointer<WebContentsAdapter> newAdapter = createWindow(std::move(new_contents), disposition, initial_pos, user_gesture);
+ Q_UNUSED(source)
+ QSharedPointer<WebContentsAdapter> newAdapter = createWindow(std::move(new_contents), disposition, initial_pos, m_initialTargetUrl, user_gesture);
// Chromium can forget to pass user-agent override settings to new windows (see QTBUG-61774 and QTBUG-76249),
// so set it here. Note the actual value doesn't really matter here. Only the second value does, but we try
// to give the correct user-agent anyway.
@@ -258,14 +259,14 @@ void WebContentsDelegateQt::CloseContents(content::WebContents *source)
void WebContentsDelegateQt::LoadProgressChanged(double progress)
{
- if (!m_loadingErrorFrameList.isEmpty())
- return;
- if (m_lastLoadProgress < 0) // suppress signals that aren't between loadStarted and loadFinished
+ QUrl current_url(m_viewClient->webContentsAdapter()->getNavigationEntryOriginalUrl(m_viewClient->webContentsAdapter()->currentNavigationEntryIndex()));
+ int p = qMin(qRound(progress * 100), 100);
+
+ if (!m_loadingErrorFrameList.isEmpty() || !m_loadProgressMap.contains(current_url) || m_loadProgressMap[current_url] == 100 || p == 100)
return;
- int p = qMin(qRound(progress * 100), 100);
- if (p > m_lastLoadProgress) { // ensure strict monotonic increase
- m_lastLoadProgress = p;
+ if (p > m_loadProgressMap[current_url]) { // ensure strict monotonic increase
+ m_loadProgressMap[current_url] = p;
m_viewClient->loadProgressChanged(p);
}
}
@@ -341,16 +342,37 @@ void WebContentsDelegateQt::RenderViewHostChanged(content::RenderViewHost *, con
void WebContentsDelegateQt::EmitLoadStarted(const QUrl &url, bool isErrorPage)
{
- if (m_lastLoadProgress >= 0 && m_lastLoadProgress < 100) // already running
- return;
for (auto &&wc : m_certificateErrorControllers)
if (auto controller = wc.lock())
controller->deactivate();
m_certificateErrorControllers.clear();
+
m_viewClient->loadStarted(url, isErrorPage);
m_viewClient->updateNavigationActions();
+
+ if ((url.hasFragment() || m_lastLoadedUrl.hasFragment())
+ && url.adjusted(QUrl::RemoveFragment) == m_lastLoadedUrl.adjusted(QUrl::RemoveFragment)
+ && !m_isNavigationCommitted) {
+ m_loadProgressMap.insert(url, 100);
+ m_lastLoadedUrl = url;
+ m_viewClient->loadProgressChanged(100);
+ return;
+ }
+
+ if (!m_loadProgressMap.isEmpty()) {
+ QMap<QUrl, int>::iterator it = m_loadProgressMap.begin();
+ while (it != m_loadProgressMap.end()) {
+ if (it.value() == 100) {
+ it = m_loadProgressMap.erase(it);
+ continue;
+ }
+ ++it;
+ }
+ }
+
+ m_lastLoadedUrl = url;
+ m_loadProgressMap.insert(url, 0);
m_viewClient->loadProgressChanged(0);
- m_lastLoadProgress = 0;
}
void WebContentsDelegateQt::DidStartNavigation(content::NavigationHandle *navigation_handle)
@@ -368,11 +390,15 @@ void WebContentsDelegateQt::DidStartNavigation(content::NavigationHandle *naviga
void WebContentsDelegateQt::EmitLoadFinished(bool success, const QUrl &url, bool isErrorPage, int errorCode, const QString &errorDescription)
{
- if (m_lastLoadProgress < 0) // not currently running
+ // When error page enabled we don't need to send the error page load finished signal
+ if (m_loadProgressMap[url] == 100)
return;
- if (m_lastLoadProgress < 100)
- m_viewClient->loadProgressChanged(100);
- m_lastLoadProgress = -1;
+
+ m_lastLoadedUrl = url;
+ m_loadProgressMap[url] = 100;
+ m_isNavigationCommitted = false;
+ m_viewClient->loadProgressChanged(100);
+
m_viewClient->loadFinished(success, url, isErrorPage, errorCode, errorDescription);
m_viewClient->updateNavigationActions();
}
@@ -397,8 +423,10 @@ void WebContentsDelegateQt::DidFinishNavigation(content::NavigationHandle *navig
profileAdapter->visitedLinksManager()->addUrl(url);
}
+ m_isNavigationCommitted = true;
EmitLoadCommitted();
}
+
// Success is reported by DidFinishLoad, but DidFailLoad is now dead code and needs to be handled below
if (navigation_handle->GetNetErrorCode() == net::OK)
return;
@@ -680,7 +708,7 @@ void WebContentsDelegateQt::overrideWebPreferences(content::WebContents *webCont
QSharedPointer<WebContentsAdapter>
WebContentsDelegateQt::createWindow(std::unique_ptr<content::WebContents> new_contents,
- WindowOpenDisposition disposition, const gfx::Rect &initial_pos,
+ WindowOpenDisposition disposition, const gfx::Rect &initial_pos, const QUrl &url,
bool user_gesture)
{
QSharedPointer<WebContentsAdapter> newAdapter = QSharedPointer<WebContentsAdapter>::create(std::move(new_contents));
@@ -688,7 +716,7 @@ WebContentsDelegateQt::createWindow(std::unique_ptr<content::WebContents> new_co
return m_viewClient->adoptNewWindow(
std::move(newAdapter),
static_cast<WebContentsAdapterClient::WindowOpenDisposition>(disposition), user_gesture,
- toQt(initial_pos), m_initialTargetUrl);
+ toQt(initial_pos), url);
}
void WebContentsDelegateQt::allowCertificateError(
diff --git a/src/core/web_contents_delegate_qt.h b/src/core/web_contents_delegate_qt.h
index 4677a780a..1481c7904 100644
--- a/src/core/web_contents_delegate_qt.h
+++ b/src/core/web_contents_delegate_qt.h
@@ -202,6 +202,7 @@ private:
QSharedPointer<WebContentsAdapter>
createWindow(std::unique_ptr<content::WebContents> new_contents,
WindowOpenDisposition disposition, const gfx::Rect &initial_pos,
+ const QUrl &url,
bool user_gesture);
void EmitLoadStarted(const QUrl &url, bool isErrorPage = false);
void EmitLoadFinished(bool success, const QUrl &url, bool isErrorPage = false, int errorCode = 0, const QString &errorDescription = QString());
@@ -219,7 +220,6 @@ private:
SavePageInfo m_savePageInfo;
QSharedPointer<FilePickerController> m_filePickerController;
QUrl m_initialTargetUrl;
- int m_lastLoadProgress;
LoadingState m_loadingState;
bool m_didStartLoadingSeen;
FrameFocusedObserver m_frameFocusedObserver;
@@ -231,6 +231,9 @@ private:
int m_desktopStreamCount = 0;
mutable bool m_pendingUrlUpdate = false;
+ QMap<QUrl, int> m_loadProgressMap;
+ QUrl m_lastLoadedUrl;
+ bool m_isNavigationCommitted = false;
base::WeakPtrFactory<WebContentsDelegateQt> m_weakPtrFactory { this };
QList<QWeakPointer<CertificateErrorController>> m_certificateErrorControllers;
};
diff --git a/src/core/web_event_factory.cpp b/src/core/web_event_factory.cpp
index e77463930..ca2f13a06 100644
--- a/src/core/web_event_factory.cpp
+++ b/src/core/web_event_factory.cpp
@@ -1280,6 +1280,29 @@ static unsigned mouseButtonsModifiersForEvent(const T* event)
return ret;
}
+static WebInputEvent::Modifiers lockKeyModifiers(const quint32 nativeModifiers)
+{
+ unsigned result = 0;
+ if (keyboardDriver() == KeyboardDriver::Xkb) {
+ if (nativeModifiers & 0x42) /* Caps_Lock */
+ result |= WebInputEvent::kCapsLockOn;
+ if (nativeModifiers & 0x4d) /* Num_Lock */
+ result |= WebInputEvent::kNumLockOn;
+ } else if (keyboardDriver() == KeyboardDriver::Windows) {
+ if (nativeModifiers & 0x100) /* CapsLock */
+ result |= WebInputEvent::kCapsLockOn;
+ if (nativeModifiers & 0x200) /* NumLock */
+ result |= WebInputEvent::kNumLockOn;
+ if (nativeModifiers & 0x400) /* ScrollLock */
+ result |= WebInputEvent::kScrollLockOn;
+ } else if (keyboardDriver() == KeyboardDriver::Cocoa) {
+ if (nativeModifiers & 0x10000) /* NSEventModifierFlagCapsLock */
+ result |= WebInputEvent::kCapsLockOn;
+ }
+
+ return static_cast<WebInputEvent::Modifiers>(result);
+}
+
// If only a modifier key is pressed, Qt only reports the key code.
// But Chromium also expects the modifier being set.
static inline WebInputEvent::Modifiers modifierForKeyCode(int key)
@@ -1328,13 +1351,14 @@ static inline WebInputEvent::Modifiers modifiersForEvent(const QInputEvent* even
if (keyEvent->isAutoRepeat())
result |= WebInputEvent::kIsAutoRepeat;
result |= modifierForKeyCode(qtKeyForKeyEvent(keyEvent));
+ result |= lockKeyModifiers(keyEvent->nativeModifiers());
break;
}
default:
break;
}
- return (WebInputEvent::Modifiers)result;
+ return static_cast<WebInputEvent::Modifiers>(result);
}
static inline Qt::KeyboardModifiers keyboardModifiersForModifier(unsigned int modifier)
diff --git a/src/pdf/config/common.pri b/src/pdf/config/common.pri
index dd5bfa293..303c4a91c 100644
--- a/src/pdf/config/common.pri
+++ b/src/pdf/config/common.pri
@@ -1,6 +1,35 @@
include($$QTWEBENGINE_OUT_ROOT/src/pdf/qtpdf-config.pri)
QT_FOR_CONFIG += pdf-private
+qtConfig(webengine-qt-png) {
+ gn_args += pdfium_use_qt_libpng=true
+ gn_args += "pdfium_qt_libpng_includes=\"$$system_path($$QMAKE_INCDIR_LIBPNG)\""
+}
+
+#qtConfig(webengine-qt-jpeg) {
+# gn_args += use_qt_libjpeg=true
+# gn_args += "qt_libjpeg_includes=\"$$system_path($$QMAKE_INCDIR_LIBJPEG)\""
+#}
+
+qtConfig(webengine-qt-harfbuzz) {
+ gn_args += use_qt_harfbuzz=true
+ gn_args += "qt_harfbuzz_includes=\"$$system_path($$QMAKE_INCDIR_HARFBUZZ)\""
+}
+
+qtConfig(webengine-qt-freetype) {
+ gn_args += use_qt_freetype=true
+ gn_args += "qt_freetype_includes=\"$$system_path($$QMAKE_INCDIR_FREETYPE)\""
+}
+
+qtConfig(webengine-qt-zlib) {
+ gn_args += use_qt_zlib = true
+ gn_args += "qt_zlib_includes=\["\
+ "\"$$system_path($$[QT_INSTALL_HEADERS])\"," \
+ "\"$$system_path($$[QT_INSTALL_HEADERS]/QtCore)\"," \
+ "\"$$system_path($$[QT_INSTALL_HEADERS]/QtZlib)\"\]"
+ gn_args += "qt_zlib=\"$$system_path($$[QT_INSTALL_LIBS]/libQt5Core.a)\""
+}
+
qtConfig(pdf-v8) {
gn_args += pdf_enable_v8=true
} else {
diff --git a/src/pdf/config/ios.pri b/src/pdf/config/ios.pri
index 1dcbeffde..cd7597d85 100644
--- a/src/pdf/config/ios.pri
+++ b/src/pdf/config/ios.pri
@@ -37,6 +37,7 @@ clang_base_path=\"$${clang_dir}\" \
ios_enable_code_signing=false \
target_os=\"ios\" \
ios_deployment_target=\"$${QMAKE_IOS_DEPLOYMENT_TARGET}\" \
+mac_sdk_min=\"$${QMAKE_MAC_SDK_VERSION_MAJOR_MINOR}\" \
enable_ios_bitcode=true \
use_jumbo_build=false
diff --git a/src/pdf/configure.json b/src/pdf/configure.json
index ddc0e99dc..069893660 100644
--- a/src/pdf/configure.json
+++ b/src/pdf/configure.json
@@ -2,7 +2,7 @@
"module": "pdf",
"depends" : [ "buildtools-private" ],
"testDir": "../../config.tests",
- "condition": "features.build-qtpdf && features.webengine-qtpdf-support",
+ "condition": "module.gui && features.webengine-qtpdf-support && features.build-qtpdf",
"libraries": {
},
"tests": {
diff --git a/src/pdf/pdfcore.pro b/src/pdf/pdfcore.pro
index 2dfe39dc0..998cddb7d 100644
--- a/src/pdf/pdfcore.pro
+++ b/src/pdf/pdfcore.pro
@@ -77,4 +77,11 @@ HEADERS += \
api/qpdfselection.h \
api/qpdfselection_p.h \
+
+qtConfig(webengine-qt-freetype): QMAKE_USE_PRIVATE+= freetype
+qtConfig(webengine-qt-png): QMAKE_USE_PRIVATE+= libpng
+qtConfig(webengine-qt-harfbuzz): QMAKE_USE_PRIVATE+= harfbuzz
+#qtConfig(webengine-qt-jpeg): QMAKE_USE_PRIVATE+= libjpeg
+qtConfig(webengine-qt-zlib){} #qtzlib is a part of QtCore
+
load(qt_module)
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/resources/content.html b/tests/auto/core/qwebengineurlrequestinterceptor/resources/content.html
index 360ad65ef..84bf55036 100644
--- a/tests/auto/core/qwebengineurlrequestinterceptor/resources/content.html
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/resources/content.html
@@ -1,5 +1,6 @@
<html>
+<head><link rel="icon" href="data:,"></head>
<body>
-<a>This is test content</a>
+<a>Simple test page without favicon (meaning no separate request from http server)</a>
</body>
</html>
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/resources/content2.html b/tests/auto/core/qwebengineurlrequestinterceptor/resources/content2.html
new file mode 100644
index 000000000..84bf55036
--- /dev/null
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/resources/content2.html
@@ -0,0 +1,6 @@
+<html>
+<head><link rel="icon" href="data:,"></head>
+<body>
+<a>Simple test page without favicon (meaning no separate request from http server)</a>
+</body>
+</html>
diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp
index 199eb4eb6..54546569f 100644
--- a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp
+++ b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp
@@ -64,7 +64,7 @@ private Q_SLOTS:
void requestInterceptorByResourceType_data();
void requestInterceptorByResourceType();
void firstPartyUrlHttp();
- void passRefererHeader();
+ void customHeaders();
void initiator();
void jsServiceWorker();
};
@@ -107,35 +107,39 @@ struct RequestInfo {
int resourceType;
};
-static const QByteArray kHttpHeaderReferrerValue = QByteArrayLiteral("http://somereferrer.com/");
-static const QByteArray kHttpHeaderRefererName = QByteArrayLiteral("referer");
static const QUrl kRedirectUrl = QUrl("qrc:///resources/content.html");
+Q_LOGGING_CATEGORY(lc, "qt.webengine.tests")
+
class TestRequestInterceptor : public QWebEngineUrlRequestInterceptor
{
public:
QList<RequestInfo> requestInfos;
bool shouldRedirect = false;
+ QUrl redirectUrl;
QMap<QUrl, QSet<QUrl>> requestInitiatorUrls;
QMap<QByteArray, QByteArray> headers;
void interceptRequest(QWebEngineUrlRequestInfo &info) override
{
QVERIFY(QThread::currentThread() == QCoreApplication::instance()->thread());
+ qCDebug(lc) << this << "Type:" << info.resourceType() << info.requestMethod() << "Navigation:" << info.navigationType()
+ << info.requestUrl() << "Initiator:" << info.initiator();
+
// Since 63 we also intercept some unrelated blob requests..
if (info.requestUrl().scheme() == QLatin1String("blob"))
return;
bool block = info.requestMethod() != QByteArrayLiteral("GET");
- bool redirect = shouldRedirect && info.requestUrl() != kRedirectUrl;
+ bool redirect = shouldRedirect && info.requestUrl() != redirectUrl;
+
+ // set additional headers if any required by test
+ for (auto it = headers.begin(); it != headers.end(); ++it) info.setHttpHeader(it.key(), it.value());
if (block) {
info.block(true);
} else if (redirect) {
- info.redirect(kRedirectUrl);
- } else {
- // set additional headers if any required by test
- for (auto it = headers.begin(); it != headers.end(); ++it) info.setHttpHeader(it.key(), it.value());
+ info.redirect(redirectUrl);
}
requestInitiatorUrls[info.requestUrl()].insert(info.initiator());
@@ -185,8 +189,8 @@ public:
return false;
}
- TestRequestInterceptor(bool redirect)
- : shouldRedirect(redirect)
+ TestRequestInterceptor(bool redirect = false, const QUrl &url = kRedirectUrl)
+ : shouldRedirect(redirect), redirectUrl(url)
{
}
};
@@ -574,37 +578,63 @@ void tst_QWebEngineUrlRequestInterceptor::firstPartyUrlHttp()
QCOMPARE(info.firstPartyUrl, firstPartyUrl);
}
-void tst_QWebEngineUrlRequestInterceptor::passRefererHeader()
+void tst_QWebEngineUrlRequestInterceptor::customHeaders()
{
// Create HTTP Server to parse the request.
HttpServer httpServer;
-
- if (!httpServer.start())
- QSKIP("Failed to start http server");
-
- bool succeeded = false;
- connect(&httpServer, &HttpServer::newRequest, [&succeeded](HttpReqRep *rr) {
- const QByteArray headerValue = rr->requestHeader(kHttpHeaderRefererName);
- QCOMPARE(headerValue, kHttpHeaderReferrerValue);
- succeeded = headerValue == kHttpHeaderReferrerValue;
- rr->sendResponse();
- });
+ httpServer.setResourceDirs({ TESTS_SOURCE_DIR "qwebengineurlrequestinterceptor/resources" });
+ QVERIFY(httpServer.start());
QWebEngineProfile profile;
TestRequestInterceptor interceptor(false);
- interceptor.headers.insert(kHttpHeaderRefererName, kHttpHeaderReferrerValue);
profile.setUrlRequestInterceptor(&interceptor);
QWebEnginePage page(&profile);
QSignalSpy spy(&page, SIGNAL(loadFinished(bool)));
- QWebEngineHttpRequest httpRequest;
- QUrl requestUrl = httpServer.url();
- httpRequest.setUrl(requestUrl);
- page.load(httpRequest);
+ interceptor.headers = {
+ { "referer", "http://somereferrer.com/" },
+ { "from", "user@example.com" },
+ { "user-agent", "mozilla/5.0 (x11; linux x86_64; rv:12.0) gecko/20100101 firefox/12.0" },
+ };
+
+ QMap<QByteArray, QByteArray> actual, expected;
+ connect(&httpServer, &HttpServer::newRequest, [&] (HttpReqRep *rr) {
+ for (auto it = expected.begin(); it != expected.end(); ++it) {
+ auto headerValue = rr->requestHeader(it.key());
+ actual[it.key()] = headerValue;
+ QCOMPARE(headerValue, it.value());
+ }
+ });
+
+ auto dumpHeaders = [&] () {
+ QString s; QDebug d(&s);
+ for (auto it = expected.begin(); it != expected.end(); ++it)
+ d << "\n\tHeader:" << it.key() << "| actual:" << actual[it.key()] << "expected:" << it.value();
+ return s;
+ };
+
+ expected = interceptor.headers;
+ page.load(httpServer.url("/content.html"));
QVERIFY(spy.wait());
+ QVERIFY2(actual == expected, qPrintable(dumpHeaders()));
+
+ // test that custom headers are also applied on redirect
+ interceptor.shouldRedirect = true;
+ interceptor.redirectUrl = httpServer.url("/content2.html");
+ interceptor.headers = {
+ { "referer", "http://somereferrer2.com/" },
+ { "from", "user2@example.com" },
+ { "user-agent", "mozilla/5.0 (compatible; googlebot/2.1; +http://www.google.com/bot.html)" },
+ };
+
+ actual.clear();
+ expected = interceptor.headers;
+ page.triggerAction(QWebEnginePage::Reload);
+ QVERIFY(spy.wait());
+ QVERIFY2(actual == expected, qPrintable(dumpHeaders()));
+
(void) httpServer.stop();
- QVERIFY(succeeded);
}
void tst_QWebEngineUrlRequestInterceptor::initiator()
diff --git a/tests/auto/quick/qmltests/BLACKLIST b/tests/auto/quick/qmltests/BLACKLIST
deleted file mode 100644
index 46bc65923..000000000
--- a/tests/auto/quick/qmltests/BLACKLIST
+++ /dev/null
@@ -1,2 +0,0 @@
-[WebEngineViewSource::test_viewSourceURL]
-*
diff --git a/tests/auto/quick/qmltests/data/TestWebEngineView.qml b/tests/auto/quick/qmltests/data/TestWebEngineView.qml
index 6db076ae8..f2bc09e4b 100644
--- a/tests/auto/quick/qmltests/data/TestWebEngineView.qml
+++ b/tests/auto/quick/qmltests/data/TestWebEngineView.qml
@@ -85,12 +85,14 @@ WebEngineView {
function getElementCenter(element) {
var center;
- runJavaScript("(function() {" +
+ testCase.tryVerify(function() {
+ runJavaScript("(function() {" +
" var elem = document.getElementById('" + element + "');" +
" var rect = elem.getBoundingClientRect();" +
" return { 'x': (rect.left + rect.right) / 2, 'y': (rect.top + rect.bottom) / 2 };" +
"})();", function(result) { center = result } );
- testCase.tryVerify(function() { return center !== undefined; });
+ return center !== undefined;
+ });
return center;
}
diff --git a/tests/auto/quick/qmltests/data/test2.html b/tests/auto/quick/qmltests/data/test2.html
index 629c2a063..7a02bf1f2 100644
--- a/tests/auto/quick/qmltests/data/test2.html
+++ b/tests/auto/quick/qmltests/data/test2.html
@@ -1,6 +1,6 @@
<html>
<head><title>Test page with huge link area</title></head>
<body>
-<a title="A title" href="test1.html"><img width=200 height=200></a>
+<a id="link" title="A title" href="test1.html"><img width=200 height=200></a>
</body>
</html>
diff --git a/tests/auto/quick/qmltests/data/tst_loadUrl.qml b/tests/auto/quick/qmltests/data/tst_loadUrl.qml
index 872c46641..47dbbc087 100644
--- a/tests/auto/quick/qmltests/data/tst_loadUrl.qml
+++ b/tests/auto/quick/qmltests/data/tst_loadUrl.qml
@@ -301,8 +301,8 @@ TestWebEngineView {
// In-page navigation.
webEngineView.url = Qt.resolvedUrl("test4.html#content");
// In-page navigation doesn't trigger load succeeded, wait for load progress instead.
+ tryCompare(loadRequestArray, "length", 3);
tryCompare(webEngineView, "loadProgress", 100);
- compare(loadRequestArray.length, 3);
compare(loadRequestArray[2].status, WebEngineView.LoadStartedStatus);
// Load after in-page navigation.
diff --git a/tests/auto/quick/qmltests/data/tst_newViewRequest.qml b/tests/auto/quick/qmltests/data/tst_newViewRequest.qml
index 80389e9f8..08d63d956 100644
--- a/tests/auto/quick/qmltests/data/tst_newViewRequest.qml
+++ b/tests/auto/quick/qmltests/data/tst_newViewRequest.qml
@@ -38,6 +38,13 @@ TestWebEngineView {
property var newViewRequest: null
property var dialog: null
property string viewType: ""
+ property var loadRequestArray: []
+
+ onLoadingChanged: {
+ loadRequestArray.push({
+ "status": loadRequest.status,
+ });
+ }
SignalSpy {
id: newViewRequestedSpy
@@ -81,6 +88,7 @@ TestWebEngineView {
newViewRequestedSpy.clear();
newViewRequest = null;
viewType = "";
+ loadRequestArray = [];
}
function cleanup() {
@@ -163,6 +171,23 @@ TestWebEngineView {
}
newViewRequestedSpy.clear();
}
+
+ loadRequestArray = [];
+ compare(loadRequestArray.length, 0);
+ webEngineView.url = Qt.resolvedUrl("test2.html");
+ verify(webEngineView.waitForLoadSucceeded());
+ var center = getElementCenter("link");
+ mouseClick(webEngineView, center.x, center.y, Qt.LeftButton, Qt.ControlModifier);
+ tryCompare(newViewRequestedSpy, "count", 1);
+ compare(newViewRequest.requestedUrl, Qt.resolvedUrl("test1.html"));
+ compare(newViewRequest.destination, WebEngineView.NewViewInBackgroundTab);
+ verify(newViewRequest.userInitiated);
+ if (viewType === "" || viewType === "null") {
+ compare(loadRequestArray[0].status, WebEngineView.LoadStartedStatus);
+ compare(loadRequestArray[1].status, WebEngineView.LoadSucceededStatus);
+ compare(loadRequestArray.length, 2);
+ }
+ newViewRequestedSpy.clear();
}
}
}
diff --git a/tests/auto/quick/qmltests/data/tst_viewSource.qml b/tests/auto/quick/qmltests/data/tst_viewSource.qml
index 4966a052a..22c340c2b 100644
--- a/tests/auto/quick/qmltests/data/tst_viewSource.qml
+++ b/tests/auto/quick/qmltests/data/tst_viewSource.qml
@@ -94,36 +94,6 @@ TestWebEngineView {
compare(webEngineView.url, "view-source:" + Qt.resolvedUrl("test1.html"));
}
- function test_viewSourceURL_data() {
- var testLocalUrl = "view-source:" + Qt.resolvedUrl("test1.html");
- var testLocalUrlWithoutScheme = "view-source:" + Qt.resolvedUrl("test1.html").substring(7);
-
- return [
- { tag: "view-source:", userInputUrl: "view-source:", loadSucceed: true, url: "view-source:", title: "view-source:" },
- { tag: "view-source:about:blank", userInputUrl: "view-source:about:blank", loadSucceed: true, url: "view-source:about:blank", title: "view-source:about:blank" },
- { tag: testLocalUrl, userInputUrl: testLocalUrl, loadSucceed: true, url: testLocalUrl, title: "test1.html" },
- { tag: testLocalUrlWithoutScheme, userInputUrl: testLocalUrlWithoutScheme, loadSucceed: true, url: testLocalUrl, title: "test1.html" },
- { tag: "view-source:http://non.existent", userInputUrl: "view-source:http://non.existent", loadSucceed: false, url: "http://non.existent/", title: "non.existent" },
- { tag: "view-source:non.existent", userInputUrl: "view-source:non.existent", loadSucceed: false, url: "http://non.existent/", title: "non.existent" },
- ];
- }
-
- function test_viewSourceURL(row) {
- WebEngine.settings.errorPageEnabled = true
- webEngineView.url = row.userInputUrl;
-
- if (row.loadSucceed) {
- tryCompare(webEngineView, "loadStatus", WebEngineView.LoadSucceededStatus);
- } else {
- tryCompare(webEngineView, "loadStatus", WebEngineView.LoadFailedStatus, 15000);
- }
- tryVerify(function() { return titleChangedSpy.count == 1; });
-
- compare(webEngineView.url, row.url);
- tryCompare(webEngineView, "title", row.title);
- verify(!webEngineView.action(WebEngineView.ViewSource).enabled);
- }
-
function test_viewSourceCredentials() {
var url = "http://user:passwd@httpbin.org/basic-auth/user/passwd";
diff --git a/tests/auto/quick/qmltests/data/tst_viewSoure.qml b/tests/auto/quick/qmltests/data/tst_viewSoure.qml
new file mode 100644
index 000000000..997582335
--- /dev/null
+++ b/tests/auto/quick/qmltests/data/tst_viewSoure.qml
@@ -0,0 +1,133 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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:GPL-EXCEPT$
+** 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtTest 1.0
+import QtWebEngine 1.4
+import QtWebEngine.testsupport 1.0
+import "../../qmltests/data" 1.0
+
+TestWebEngineView {
+ id: webEngineView
+ width: 200
+ height: 400
+
+ property var viewRequest: null
+ property var loadRequestArray: []
+
+ testSupport: WebEngineTestSupport {
+ errorPage.onLoadingChanged: {
+ loadRequestArray.push({
+ "status": loadRequest.status,
+ })
+ }
+ }
+
+ onLoadingChanged: {
+ loadRequestArray.push({
+ "status": loadRequest.status,
+ });
+ }
+
+ SignalSpy {
+ id: newViewRequestedSpy
+ target: webEngineView
+ signalName: "newViewRequested"
+ }
+
+ SignalSpy {
+ id: titleChangedSpy
+ target: webEngineView
+ signalName: "titleChanged"
+ }
+
+ onNewViewRequested: {
+ viewRequest = {
+ "destination": request.destination,
+ "userInitiated": request.userInitiated
+ };
+
+ request.openIn(webEngineView);
+ }
+
+ TestCase {
+ id: test
+ name: "WebEngineViewSource"
+
+ function init() {
+ webEngineView.loadStatus = null;
+ webEngineView.url = Qt.resolvedUrl("test1.html");
+ tryCompare(webEngineView, "loadStatus", WebEngineView.LoadSucceededStatus);
+ webEngineView.loadStatus = null;
+
+ newViewRequestedSpy.clear();
+ titleChangedSpy.clear();
+ viewRequest = null;
+ }
+
+ function test_viewSourceURL_data() {
+ var testLocalUrl = "view-source:" + Qt.resolvedUrl("test1.html");
+ var testLocalUrlWithoutScheme = "view-source:" + Qt.resolvedUrl("test1.html").toString().substring(7);
+
+ return [
+ { tag: "view-source:", userInputUrl: "view-source:", loadSucceed: true, url: "view-source:", title: "view-source:" },
+ { tag: "view-source:about:blank", userInputUrl: "view-source:about:blank", loadSucceed: true, url: "view-source:about:blank", title: "view-source:about:blank" },
+ { tag: testLocalUrl, userInputUrl: testLocalUrl, loadSucceed: true, url: testLocalUrl, title: "test1.html" },
+ { tag: testLocalUrlWithoutScheme, userInputUrl: testLocalUrlWithoutScheme, loadSucceed: true, url: testLocalUrl, title: "test1.html" },
+ { tag: "view-source:http://non.existent", userInputUrl: "view-source:http://non.existent", loadSucceed: false, url: "http://non.existent/", title: "non.existent" },
+ { tag: "view-source:non.existent", userInputUrl: "view-source:non.existent", loadSucceed: false, url: "http://non.existent/", title: "non.existent" },
+ ];
+ }
+
+ function test_viewSourceURL(row) {
+ loadRequestArray = [];
+ WebEngine.settings.errorPageEnabled = true
+ webEngineView.url = row.userInputUrl;
+
+ if (row.loadSucceed) {
+ tryVerify(function() { return loadRequestArray.length >= 2 });
+ compare(loadRequestArray[1].status, WebEngineView.LoadSucceededStatus);
+ } else {
+ tryVerify(function() { return loadRequestArray.length >= 2 });
+ compare(loadRequestArray[1].status, WebEngineView.LoadFailedStatus);
+ tryVerify(function() { return loadRequestArray.length == 4 });
+ compare(loadRequestArray[3].status, WebEngineView.LoadSucceededStatus);
+ }
+ tryVerify(function() { return titleChangedSpy.count == 1; });
+
+ compare(webEngineView.url, row.url);
+ tryCompare(webEngineView, "title", row.title);
+ if (row.loadSucceed) {
+ verify(!webEngineView.action(WebEngineView.ViewSource).enabled);
+ } else {
+ verify(webEngineView.action(WebEngineView.ViewSource).enabled);
+ }
+ }
+ }
+}
+
diff --git a/tests/auto/quick/qmltests/qmltests.pro b/tests/auto/quick/qmltests/qmltests.pro
index eb53a98bb..5b2ea5d01 100644
--- a/tests/auto/quick/qmltests/qmltests.pro
+++ b/tests/auto/quick/qmltests/qmltests.pro
@@ -55,7 +55,8 @@ qtConfig(webengine-testsupport) {
$$PWD/data/tst_inputMethod.qml \
$$PWD/data/tst_linkHovered.qml \
$$PWD/data/tst_loadFail.qml \
- $$PWD/data/tst_mouseClick.qml
+ $$PWD/data/tst_mouseClick.qml \
+ $$PWD/data/tst_viewSoure.qml
qtHaveModule(quickcontrols): QML_TESTS += $$PWD/data/tst_javaScriptDialogs.qml
} else {
PLUGIN_EXTENSION = .so
diff --git a/tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp b/tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp
index c1a95de97..fd801c824 100644
--- a/tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp
+++ b/tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp
@@ -1001,6 +1001,7 @@ void tst_QQuickWebEngineView::inputEventForwardingDisabledWhenActiveFocusOnPress
void tst_QQuickWebEngineView::changeLocale()
{
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList errorLines;
QUrl url("http://non.existent/");
@@ -1036,6 +1037,7 @@ void tst_QQuickWebEngineView::changeLocale()
QTRY_VERIFY(!evaluateJavaScriptSync(viewDE.data(), "document.body.innerText").isNull());
errorLines = evaluateJavaScriptSync(viewDE.data(), "document.body.innerText").toString().split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts);
QCOMPARE(errorLines.first().toUtf8(), QByteArrayLiteral("Die Website ist nicht erreichbar"));
+#endif
}
void tst_QQuickWebEngineView::userScripts()
@@ -1181,6 +1183,9 @@ void tst_QQuickWebEngineView::focusChild_data()
void tst_QQuickWebEngineView::focusChild()
{
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 1)
+ QSKIP("Requires newer base Qt");
+#endif
auto traverseToWebDocumentAccessibleInterface = [](QAccessibleInterface *iface) -> QAccessibleInterface * {
QFETCH(QList<QAccessible::Role>, ancestorRoles);
for (int i = 0; i < ancestorRoles.size(); ++i) {
diff --git a/tests/auto/shared/httpserver.cpp b/tests/auto/shared/httpserver.cpp
index 67f491fac..69e8cb6cc 100644
--- a/tests/auto/shared/httpserver.cpp
+++ b/tests/auto/shared/httpserver.cpp
@@ -54,6 +54,7 @@ bool HttpServer::start()
{
m_error = false;
m_expectingError = false;
+ m_ignoreNewConnection = false;
if (!m_tcpServer->listen()) {
qCWarning(gHttpServerLog).noquote() << m_tcpServer->errorString();
@@ -84,6 +85,9 @@ QUrl HttpServer::url(const QString &path) const
void HttpServer::handleNewConnection()
{
+ if (m_ignoreNewConnection)
+ return;
+
auto rr = new HttpReqRep(m_tcpServer->nextPendingConnection(), this);
connect(rr, &HttpReqRep::requestReceived, [this, rr]() {
Q_EMIT newRequest(rr);
@@ -122,5 +126,9 @@ void HttpServer::handleNewConnection()
<< error;
m_error = true;
});
- connect(rr, &HttpReqRep::closed, rr, &QObject::deleteLater);
+
+ if (!m_tcpServer->isListening()) {
+ m_ignoreNewConnection = true;
+ connect(rr, &HttpReqRep::closed, rr, &QObject::deleteLater);
+ }
}
diff --git a/tests/auto/shared/httpserver.h b/tests/auto/shared/httpserver.h
index 9764852de..952ead220 100644
--- a/tests/auto/shared/httpserver.h
+++ b/tests/auto/shared/httpserver.h
@@ -90,6 +90,7 @@ private:
QUrl m_url;
QStringList m_dirs;
bool m_error = false;
+ bool m_ignoreNewConnection = false;
bool m_expectingError = false;
};
diff --git a/tests/auto/widgets/accessibility/tst_accessibility.cpp b/tests/auto/widgets/accessibility/tst_accessibility.cpp
index e73f7d89b..989ad0ee9 100644
--- a/tests/auto/widgets/accessibility/tst_accessibility.cpp
+++ b/tests/auto/widgets/accessibility/tst_accessibility.cpp
@@ -160,6 +160,9 @@ void tst_Accessibility::focusChild_data()
void tst_Accessibility::focusChild()
{
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 1)
+ QSKIP("Requires newer base Qt");
+#endif
auto traverseToWebDocumentAccessibleInterface = [](QAccessibleInterface *iface) -> QAccessibleInterface * {
QFETCH(QList<QAccessible::Role>, ancestorRoles);
for (int i = 0; i < ancestorRoles.size(); ++i) {
diff --git a/tests/auto/widgets/loadsignals/tst_loadsignals.cpp b/tests/auto/widgets/loadsignals/tst_loadsignals.cpp
index 143a4a4e5..c1e9013df 100644
--- a/tests/auto/widgets/loadsignals/tst_loadsignals.cpp
+++ b/tests/auto/widgets/loadsignals/tst_loadsignals.cpp
@@ -52,6 +52,7 @@ private Q_SLOTS:
void secondLoadForError_WhenErrorPageEnabled();
void loadAfterInPageNavigation_qtbug66869();
void fileDownloadDoesNotTriggerLoadSignals_qtbug66661();
+ void numberOfStartedAndFinishedSignalsIsSame();
private:
QWebEngineProfile profile;
@@ -243,5 +244,44 @@ void tst_LoadSignals::fileDownloadDoesNotTriggerLoadSignals_qtbug66661()
QCOMPARE(loadFinishedSpy.size(), 1);
}
+void tst_LoadSignals::numberOfStartedAndFinishedSignalsIsSame() {
+
+ HttpServer server;
+ server.setResourceDirs({ TESTS_SOURCE_DIR "/qwebengineprofile/resources" });
+ connect(&server, &HttpServer::newRequest, [] (HttpReqRep *) {
+ QTest::qWait(250); // just add delay to trigger some progress for every sub resource
+ });
+ QVERIFY(server.start());
+
+ view.load(server.url("/hedgehog.png"));
+ QTRY_COMPARE(loadFinishedSpy.size(), 1);
+ QVERIFY(loadFinishedSpy[0][0].toBool());
+
+ loadStartedSpy.clear();
+ loadFinishedSpy.clear();
+ loadProgressSpy.clear();
+
+ view.page()->setHtml("<html><body>"
+ "<img src=\"" + server.url("/hedgehog.png").toEncoded() + "\">"
+ "<form method='GET' name='hiddenform' action='qrc:///resources/page1.html' />"
+ "<script language='javascript'>document.forms[0].submit();</script>"
+ "</body></html>");
+
+ QTRY_COMPARE(loadStartedSpy.size(), 2);
+ QTRY_COMPARE(loadFinishedSpy.size(), 2);
+
+ QTRY_VERIFY(!loadFinishedSpy[0][0].toBool());
+ QTRY_VERIFY(loadFinishedSpy[1][0].toBool());
+
+ view.page()->setHtml("<html><body>"
+ "<form method='GET' name='hiddenform' action='qrc:///resources/page1.html' />"
+ "<script language='javascript'>document.forms[0].submit();</script>"
+ "</body></html>");
+ QTRY_COMPARE(loadStartedSpy.size(), 4);
+ QTRY_COMPARE(loadFinishedSpy.size(), 4);
+ QVERIFY(loadFinishedSpy[2][0].toBool());
+ QVERIFY(loadFinishedSpy[3][0].toBool());
+}
+
QTEST_MAIN(tst_LoadSignals)
#include "tst_loadsignals.moc"
diff --git a/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp b/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp
index bdb486793..72a45379b 100644
--- a/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp
+++ b/tests/auto/widgets/qwebenginehistory/tst_qwebenginehistory.cpp
@@ -320,7 +320,8 @@ void tst_QWebEngineHistory::serialize_2()
hist->forward();
QTRY_COMPARE(loadFinishedSpy->count(), 5);
hist->forward();
- QTRY_COMPARE(loadFinishedSpy->count(), 6);
+ // In-page navigation, the last url was the page5.html
+ QTRY_COMPARE(loadFinishedSpy->count(), 5);
QTRY_COMPARE(hist->currentItemIndex(), initialCurrentIndex);
}
diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
index a7e594d14..41ec977ab 100644
--- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
+++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
@@ -3468,7 +3468,11 @@ void tst_QWebEnginePage::openLinkInNewPage_data()
// the disposition and performing the navigation request normally.
QTest::newRow("BlockPopup") << Decision::ReturnNull << Cause::TargetBlank << Effect::Blocked;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ QTest::newRow("IgnoreIntent") << Decision::ReturnNull << Cause::MiddleClick << Effect::Blocked;
+#else
QTest::newRow("IgnoreIntent") << Decision::ReturnNull << Cause::MiddleClick << Effect::LoadInSelf;
+#endif
QTest::newRow("OverridePopup") << Decision::ReturnSelf << Cause::TargetBlank << Effect::LoadInSelf;
QTest::newRow("OverrideIntent") << Decision::ReturnSelf << Cause::MiddleClick << Effect::LoadInSelf;
QTest::newRow("AcceptPopup") << Decision::ReturnOther << Cause::TargetBlank << Effect::LoadInOther;
@@ -3545,7 +3549,10 @@ void tst_QWebEnginePage::openLinkInNewPage()
switch (effect) {
case Effect::Blocked:
- // Nothing to test
+ // Test nothing new loaded
+ QTest::qWait(500);
+ QCOMPARE(page1.spy.count(), 0);
+ QCOMPARE(page2.spy.count(), 0);
break;
case Effect::LoadInSelf:
QTRY_COMPARE(page1.spy.count(), 1);
diff --git a/tests/auto/widgets/qwebengineview/BLACKLIST b/tests/auto/widgets/qwebengineview/BLACKLIST
index 266f08886..1aff12669 100644
--- a/tests/auto/widgets/qwebengineview/BLACKLIST
+++ b/tests/auto/widgets/qwebengineview/BLACKLIST
@@ -1,8 +1,5 @@
[microFocusCoordinates]
osx
-[textSelectionOutOfInputField]
-*
-
[visibilityState3]
windows
diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp
index 87664cef9..1a0f77b78 100644
--- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp
+++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp
@@ -25,7 +25,6 @@
#include <private/qinputmethod_p.h>
#include <qpainter.h>
#include <qpagelayout.h>
-#include <qpa/qplatforminputcontext.h>
#include <qwebengineview.h>
#include <qwebenginepage.h>
#include <qwebenginesettings.h>
@@ -60,44 +59,6 @@ do { \
QCOMPARE((__expr), __expected); \
} while (0)
-static QPointingDevice* s_touchDevice = nullptr;
-
-static QPoint elementCenter(QWebEnginePage *page, const QString &id)
-{
- const QString jsCode(
- "(function(){"
- " var elem = document.getElementById('" + id + "');"
- " var rect = elem.getBoundingClientRect();"
- " return [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2];"
- "})()");
- QVariantList rectList = evaluateJavaScriptSync(page, jsCode).toList();
-
- if (rectList.count() != 2) {
- qWarning("elementCenter failed.");
- return QPoint();
- }
-
- return QPoint(rectList.at(0).toInt(), rectList.at(1).toInt());
-}
-
-static QRect elementGeometry(QWebEnginePage *page, const QString &id)
-{
- const QString jsCode(
- "(function() {"
- " var elem = document.getElementById('" + id + "');"
- " var rect = elem.getBoundingClientRect();"
- " return [rect.left, rect.top, rect.right, rect.bottom];"
- "})()");
- QVariantList coords = evaluateJavaScriptSync(page, jsCode).toList();
-
- if (coords.count() != 4) {
- qWarning("elementGeometry faield.");
- return QRect();
- }
-
- return QRect(coords[0].toInt(), coords[1].toInt(), coords[2].toInt(), coords[3].toInt());
-}
-
QT_BEGIN_NAMESPACE
namespace QTest {
int Q_TESTLIB_EXPORT defaultMouseDelay();
@@ -167,9 +128,6 @@ private Q_SLOTS:
void keyboardEvents();
void keyboardFocusAfterPopup();
void mouseClick();
- void touchTap();
- void touchTapAndHold();
- void touchTapAndHoldCancelled();
void postData();
void inputFieldOverridesShortcuts();
@@ -216,7 +174,6 @@ private Q_SLOTS:
// It is only called once.
void tst_QWebEngineView::initTestCase()
{
- s_touchDevice = QTest::createTouchDevice();
}
// This will be called after the last test function is executed.
@@ -1203,6 +1160,7 @@ void tst_QWebEngineView::doNotBreakLayout()
void tst_QWebEngineView::changeLocale()
{
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList errorLines;
QUrl url("http://non.existent/");
@@ -1238,6 +1196,7 @@ void tst_QWebEngineView::changeLocale()
QTRY_VERIFY(!toPlainTextSync(viewDE.page()).isEmpty());
errorLines = toPlainTextSync(viewDE.page()).split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts);
QCOMPARE(errorLines.first().toUtf8(), QByteArrayLiteral("Die Website ist nicht erreichbar"));
+#endif
}
void tst_QWebEngineView::inputMethodsTextFormat_data()
@@ -1283,6 +1242,7 @@ void tst_QWebEngineView::inputMethodsTextFormat()
evaluateJavaScriptSync(view.page(), "document.getElementById('input1').focus()");
view.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
QFETCH(QString, string);
QFETCH(int, start);
@@ -1520,174 +1480,9 @@ void tst_QWebEngineView::mouseClick()
QVERIFY(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty());
}
-void tst_QWebEngineView::touchTap()
-{
-#if defined(Q_OS_MACOS)
- QSKIP("Synthetic touch events are not supported on macOS");
-#endif
-
- QWebEngineView view;
- view.show();
- view.resize(200, 200);
- QVERIFY(QTest::qWaitForWindowExposed(&view));
-
- QSignalSpy loadFinishedSpy(&view, &QWebEngineView::loadFinished);
-
- view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false);
- view.setHtml("<html><body>"
- "<p id='text' style='width: 150px;'>The Qt Company</p>"
- "<div id='notext' style='width: 150px; height: 100px; background-color: #f00;'></div>"
- "<form><input id='input' width='150px' type='text' value='The Qt Company2' /></form>"
- "</body></html>");
- QVERIFY(loadFinishedSpy.wait());
- QVERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty());
-
- auto singleTap = [](QWidget* target, const QPoint& tapCoords) -> void {
- QTest::touchEvent(target->window(), s_touchDevice).press(0, tapCoords, target);
- QTest::touchEvent(target->window(), s_touchDevice).stationary(0);
- QTest::touchEvent(target->window(), s_touchDevice).release(0, tapCoords, target);
- };
-
- // Single tap on text doesn't trigger a selection
- singleTap(view.focusProxy(), elementCenter(view.page(), "text"));
- QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty());
- QTRY_VERIFY(!view.hasSelection());
-
- // Single tap inside the input field focuses it without selecting the text
- singleTap(view.focusProxy(), elementCenter(view.page(), "input"));
- QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input"));
- QTRY_VERIFY(!view.hasSelection());
-
- // Single tap on the div clears the input field focus
- singleTap(view.focusProxy(), elementCenter(view.page(), "notext"));
- QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty());
-
- // Double tap on text still doesn't trigger a selection
- singleTap(view.focusProxy(), elementCenter(view.page(), "text"));
- singleTap(view.focusProxy(), elementCenter(view.page(), "text"));
- QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty());
- QTRY_VERIFY(!view.hasSelection());
-
- // Double tap inside the input field focuses it and selects the word under it
- singleTap(view.focusProxy(), elementCenter(view.page(), "input"));
- singleTap(view.focusProxy(), elementCenter(view.page(), "input"));
- QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input"));
- QTRY_COMPARE(view.selectedText(), QStringLiteral("Company2"));
-
- // Double tap outside the input field behaves like a single tap: clears its focus and selection
- singleTap(view.focusProxy(), elementCenter(view.page(), "notext"));
- singleTap(view.focusProxy(), elementCenter(view.page(), "notext"));
- QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty());
- QTRY_VERIFY(!view.hasSelection());
-}
-
-void tst_QWebEngineView::touchTapAndHold()
-{
-#if defined(Q_OS_MACOS)
- QSKIP("Synthetic touch events are not supported on macOS");
-#endif
-
- QWebEngineView view;
- view.show();
- view.resize(200, 200);
- QVERIFY(QTest::qWaitForWindowExposed(&view));
-
- QSignalSpy loadFinishedSpy(&view, &QWebEngineView::loadFinished);
-
- view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false);
- view.setHtml("<html><body>"
- "<p id='text' style='width: 150px;'>The Qt Company</p>"
- "<div id='notext' style='width: 150px; height: 100px; background-color: #f00;'></div>"
- "<form><input id='input' width='150px' type='text' value='The Qt Company2' /></form>"
- "</body></html>");
- QVERIFY(loadFinishedSpy.wait());
- QVERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty());
-
- auto tapAndHold = [](QWidget* target, const QPoint& tapCoords) -> void {
- QTest::touchEvent(target, s_touchDevice).press(0, tapCoords, target);
- QTest::touchEvent(target, s_touchDevice).stationary(0);
- QTest::qWait(1000);
- QTest::touchEvent(target, s_touchDevice).release(0, tapCoords, target);
- };
-
- // Tap-and-hold on text selects the word under it
- tapAndHold(view.focusProxy(), elementCenter(view.page(), "text"));
- QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty());
- QTRY_COMPARE(view.selectedText(), QStringLiteral("Company"));
-
- // Tap-and-hold inside the input field focuses it and selects the word under it
- tapAndHold(view.focusProxy(), elementCenter(view.page(), "input"));
- QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input"));
- QTRY_COMPARE(view.selectedText(), QStringLiteral("Company2"));
-
- // Only test the page context menu on Windows, as Linux doesn't handle context menus consistently
- // and other non-desktop platforms like Android may not even support context menus at all
-#if defined(Q_OS_WIN)
- // Tap-and-hold clears the text selection and shows the page's context menu
- QVERIFY(QApplication::activePopupWidget() == nullptr);
- tapAndHold(view.focusProxy(), elementCenter(view.page(), "notext"));
- QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty());
- QTRY_VERIFY(!view.hasSelection());
- QTRY_VERIFY(QApplication::activePopupWidget() != nullptr);
-
- QApplication::activePopupWidget()->close();
- QVERIFY(QApplication::activePopupWidget() == nullptr);
-#endif
-}
-
-void tst_QWebEngineView::touchTapAndHoldCancelled()
-{
-#if defined(Q_OS_MACOS)
- QSKIP("Synthetic touch events are not supported on macOS");
-#endif
-
- QWebEngineView view;
- view.show();
- view.resize(200, 200);
- QVERIFY(QTest::qWaitForWindowExposed(&view));
-
- QSignalSpy loadFinishedSpy(&view, &QWebEngineView::loadFinished);
-
- view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false);
- view.setHtml("<html><body>"
- "<p id='text' style='width: 150px;'>The Qt Company</p>"
- "<div id='notext' style='width: 150px; height: 100px; background-color: #f00;'></div>"
- "<form><input id='input' width='150px' type='text' value='The Qt Company2' /></form>"
- "</body></html>");
- QVERIFY(loadFinishedSpy.wait());
- QVERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty());
-
- auto cancelledTapAndHold = [](QWidget* target, const QPoint& tapCoords) -> void {
- QTest::touchEvent(target, s_touchDevice).press(1, tapCoords, target);
- QTest::touchEvent(target, s_touchDevice).stationary(1);
- QTest::qWait(1000);
- QWindowSystemInterface::handleTouchCancelEvent(target->windowHandle(), s_touchDevice);
- };
-
- // A cancelled tap-and-hold should cancel text selection, but currently doesn't
- cancelledTapAndHold(view.focusProxy(), elementCenter(view.page(), "text"));
- QEXPECT_FAIL("", "Incorrect Chromium selection behavior when cancelling tap-and-hold on text", Continue);
- QTRY_VERIFY_WITH_TIMEOUT(!view.hasSelection(), 100);
-
- // A cancelled tap-and-hold should cancel input field focusing and selection, but currently doesn't
- cancelledTapAndHold(view.focusProxy(), elementCenter(view.page(), "input"));
- QEXPECT_FAIL("", "Incorrect Chromium selection behavior when cancelling tap-and-hold on input field", Continue);
- QTRY_VERIFY_WITH_TIMEOUT(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty(), 100);
- QEXPECT_FAIL("", "Incorrect Chromium focus behavior when cancelling tap-and-hold on input field", Continue);
- QTRY_VERIFY_WITH_TIMEOUT(!view.hasSelection(), 100);
-
- // Only test the page context menu on Windows, as Linux doesn't handle context menus consistently
- // and other non-desktop platforms like Android may not even support context menus at all
-#if defined(Q_OS_WIN)
- // A cancelled tap-and-hold cancels the context menu
- QVERIFY(QApplication::activePopupWidget() == nullptr);
- cancelledTapAndHold(view.focusProxy(), elementCenter(view.page(), "notext"));
- QVERIFY(QApplication::activePopupWidget() == nullptr);
-#endif
-}
-
void tst_QWebEngineView::postData()
{
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QMap<QString, QString> postData;
// use reserved characters to make the test harder to pass
postData[QStringLiteral("Spä=m")] = QStringLiteral("ëgg:s");
@@ -1815,6 +1610,7 @@ void tst_QWebEngineView::postData()
timeoutGuard.stop();
server.close();
+#endif
}
void tst_QWebEngineView::inputFieldOverridesShortcuts()
@@ -2051,6 +1847,7 @@ void tst_QWebEngineView::inputContextQueryInput()
" <input type='text' id='input1' value='' size='50'/>"
"</body></html>");
QTRY_COMPARE(loadFinishedSpy.count(), 1);
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
QCOMPARE(testContext.infos.count(), 0);
// Set focus on an input field.
@@ -2202,6 +1999,7 @@ void tst_QWebEngineView::inputMethods()
" <input type='text' id='input1' style='font-family: serif' value='' maxlength='20' size='50'/>"
"</body></html>");
QTRY_COMPARE(loadFinishedSpy.size(), 1);
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
QPoint textInputCenter = elementCenter(view.page(), "input1");
QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter);
@@ -2299,6 +2097,7 @@ void tst_QWebEngineView::textSelectionInInputField()
" <input type='text' id='input1' value='QtWebEngine' size='50'/>"
"</body></html>");
QVERIFY(loadFinishedSpy.wait());
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
// Tests for Selection when the Editor is NOT in Composition mode
@@ -2372,6 +2171,7 @@ void tst_QWebEngineView::textSelectionInInputField()
void tst_QWebEngineView::textSelectionOutOfInputField()
{
QWebEngineView view;
+ view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true);
view.resize(640, 480);
view.show();
@@ -2381,6 +2181,7 @@ void tst_QWebEngineView::textSelectionOutOfInputField()
" This is a text"
"</body></html>");
QVERIFY(loadFinishedSpy.wait());
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
QCOMPARE(selectionChangedSpy.count(), 0);
QVERIFY(!view.hasSelection());
@@ -2429,6 +2230,7 @@ void tst_QWebEngineView::textSelectionOutOfInputField()
" <input type='text' id='input1' value='QtWebEngine' size='50'/>"
"</body></html>");
QVERIFY(loadFinishedSpy.wait());
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
QCOMPARE(selectionChangedSpy.count(), 0);
QVERIFY(!view.hasSelection());
@@ -2508,6 +2310,7 @@ void tst_QWebEngineView::emptyInputMethodEvent()
" <input type='text' id='input1' value='QtWebEngine'/>"
"</body></html>");
QVERIFY(loadFinishedSpy.wait());
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
evaluateJavaScriptSync(view.page(), "var inputEle = document.getElementById('input1'); inputEle.focus(); inputEle.select();");
QTRY_COMPARE(selectionChangedSpy.count(), 1);
@@ -2556,6 +2359,7 @@ void tst_QWebEngineView::imeComposition()
" <input type='text' id='input1' value='QtWebEngine inputMethod'/>"
"</body></html>");
QVERIFY(loadFinishedSpy.wait());
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
evaluateJavaScriptSync(view.page(), "var inputEle = document.getElementById('input1'); inputEle.focus(); inputEle.select();");
QTRY_COMPARE(selectionChangedSpy.count(), 1);
@@ -2773,6 +2577,7 @@ void tst_QWebEngineView::newlineInTextarea()
" <textarea rows='5' cols='1' id='input1'></textarea>"
"</body></html>");
QVERIFY(loadFinishedSpy.wait());
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
evaluateJavaScriptSync(view.page(), "var inputEle = document.getElementById('input1'); inputEle.focus(); inputEle.select();");
QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "document.getElementById('input1').value").toString().isEmpty());
@@ -2897,6 +2702,7 @@ void tst_QWebEngineView::imeJSInputEvents()
" <pre id='log'></pre>"
"</body></html>");
QVERIFY(loadFinishedSpy.wait());
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
evaluateJavaScriptSync(view.page(), "document.getElementById('input').focus()");
QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input"));
@@ -3019,6 +2825,7 @@ void tst_QWebEngineView::imeCompositionQueryEvent()
" <input type='text' id='input1' />"
"</body></html>");
QVERIFY(loadFinishedSpy.wait());
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
evaluateJavaScriptSync(view.page(), "document.getElementById('input1').focus()");
QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1"));
diff --git a/tests/auto/widgets/touchinput/touchinput.pro b/tests/auto/widgets/touchinput/touchinput.pro
new file mode 100644
index 000000000..d91c0074b
--- /dev/null
+++ b/tests/auto/widgets/touchinput/touchinput.pro
@@ -0,0 +1,2 @@
+include(../tests.pri)
+QT *= gui-private
diff --git a/tests/auto/widgets/touchinput/tst_touchinput.cpp b/tests/auto/widgets/touchinput/tst_touchinput.cpp
new file mode 100644
index 000000000..6f22e8df8
--- /dev/null
+++ b/tests/auto/widgets/touchinput/tst_touchinput.cpp
@@ -0,0 +1,341 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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:GPL-EXCEPT$
+** 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "../util.h"
+
+#include <QtGui/qpa/qwindowsysteminterface.h>
+#include <QSignalSpy>
+#include <QTest>
+#include <QPointingDevice>
+#include <QWebEngineSettings>
+#include <QWebEngineView>
+
+static QPointingDevice* s_touchDevice = nullptr;
+
+class TouchInputTest : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+ void cleanup();
+
+private Q_SLOTS:
+ void touchTap();
+ void touchTapAndHold();
+ void touchTapAndHoldCancelled();
+ void scrolling();
+ void pinchZoom_data();
+ void pinchZoom();
+ void complexSequence();
+
+private:
+ QWebEngineView view;
+ QSignalSpy loadSpy { &view, &QWebEngineView::loadFinished };
+ QPoint notextCenter, textCenter, inputCenter;
+
+ QString activeElement() { return evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(); }
+
+ void gestureScroll(bool down) {
+ auto target = view.focusProxy();
+ QPoint p(target->width() / 2, target->height() / 4 * (down ? 3 : 1));
+
+ QTest::touchEvent(target, s_touchDevice).press(42, p, target);
+
+ for (int i = 0; i < 3; ++i) {
+ down ? p -= QPoint(5, 15) : p += QPoint(5, 15);
+ QTest::qWait(100); // too fast and events are recognized as fling gesture
+ QTest::touchEvent(target, s_touchDevice).move(42, p, target);
+ }
+
+ QTest::touchEvent(target, s_touchDevice).release(42, p, target);
+ }
+
+ void gesturePinch(bool zoomIn, bool tapOneByOne = false) {
+ auto target = view.focusProxy();
+ QPoint p(target->width() / 2, target->height() / 2);
+ auto t1 = p - QPoint(zoomIn ? 50 : 150, 10), t2 = p + QPoint(zoomIn ? 50 : 150, 10);
+
+ if (tapOneByOne) {
+ QTest::touchEvent(target, s_touchDevice).press(42, t1, target);
+ QTest::touchEvent(target, s_touchDevice).stationary(42).press(24, t2, target);
+ } else {
+ QTest::touchEvent(target, s_touchDevice).press(42, t1, target).press(24, t2, target);
+ }
+
+ for (int i = 0; i < 3; ++i) {
+ if (zoomIn) {
+ t1 -= QPoint(25, 5);
+ t2 += QPoint(25, 5);
+ } else {
+ t1 += QPoint(35, 5);
+ t2 -= QPoint(35, 5);
+ }
+ QTest::qWait(100); // too fast and events are recognized as fling gesture
+ QTest::touchEvent(target, s_touchDevice).move(24, t1, target).move(42, t2, target);
+ }
+
+ if (tapOneByOne) {
+ QTest::touchEvent(target, s_touchDevice).stationary(42).release(24, t2, target);
+ QTest::touchEvent(target, s_touchDevice).release(42, t1, target);
+ } else {
+ QTest::touchEvent(target, s_touchDevice).release(42, t1, target).release(24, t2, target);
+ }
+ }
+
+ int getScrollPosition(int *position = nullptr) {
+ int p = evaluateJavaScriptSync(view.page(), "window.scrollY").toInt();
+ return position ? (*position = p) : p;
+ }
+
+ double getScaleFactor(double *scale = nullptr) {
+ double s = evaluateJavaScriptSync(view.page(), "window.visualViewport.scale").toDouble();
+ return scale ? (*scale = s) : s;
+ }
+};
+
+void TouchInputTest::initTestCase()
+{
+ s_touchDevice = QTest::createTouchDevice();
+
+ view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false);
+
+ view.show(); view.resize(480, 320);
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
+
+ view.setHtml("<html><head><style>.rect { min-width: 240px; min-height: 120px; }</style></head><body>"
+ "<p id='text' style='width: 150px;'>The Qt Company</p>"
+ "<div id='notext' style='width: 150px; height: 100px; background-color: #f00;'></div>"
+ "<form><input id='input' width='150px' type='text' value='The Qt Company2' /></form>"
+ "<table style='width: 100%; padding: 15px; text-align: center;'>"
+ "<tr><td>BEFORE</td><td><div class='rect' style='background-color: #00f;'></div></td><td>AFTER</td></tr>"
+ "<tr><td>BEFORE</td><td><div class='rect' style='background-color: #0f0;'></div></td><td>AFTER</td></tr>"
+ "<tr><td>BEFORE</td><td><div class='rect' style='background-color: #f00;'></div></td><td>AFTER</td></tr></table>"
+ "</body></html>");
+ QVERIFY(loadSpy.wait() && loadSpy.first().first().toBool());
+
+ notextCenter = elementCenter(view.page(), "notext");
+ textCenter = elementCenter(view.page(), "text");
+ inputCenter = elementCenter(view.page(), "input");
+}
+
+void TouchInputTest::init()
+{
+ QCOMPARE(activeElement(), QString());
+}
+
+void TouchInputTest::cleanup()
+{
+ evaluateJavaScriptSync(view.page(), "if (document.activeElement) document.activeElement.blur()");
+ evaluateJavaScriptSync(view.page(), "window.scrollTo(0, 0)");
+ QTRY_COMPARE(getScrollPosition(), 0);
+}
+
+void TouchInputTest::touchTap()
+{
+ auto singleTap = [target = view.focusProxy()] (const QPoint& tapCoords) -> void {
+ QTest::touchEvent(target, s_touchDevice).press(1, tapCoords, target);
+ QTest::touchEvent(target, s_touchDevice).stationary(1);
+ QTest::touchEvent(target, s_touchDevice).release(1, tapCoords, target);
+ };
+
+ // Single tap on text doesn't trigger a selection
+ singleTap(textCenter);
+ QTRY_COMPARE(activeElement(), QString());
+ QTRY_VERIFY(!view.hasSelection());
+
+ // Single tap inside the input field focuses it without selecting the text
+ singleTap(inputCenter);
+ QTRY_COMPARE(activeElement(), QStringLiteral("input"));
+ QTRY_VERIFY(!view.hasSelection());
+
+ // Single tap on the div clears the input field focus
+ singleTap(notextCenter);
+ QTRY_COMPARE(activeElement(), QString());
+
+ // Double tap on text still doesn't trigger a selection
+ singleTap(textCenter);
+ singleTap(textCenter);
+ QTRY_COMPARE(activeElement(), QString());
+ QTRY_VERIFY(!view.hasSelection());
+
+ // Double tap inside the input field focuses it and selects the word under it
+ singleTap(inputCenter);
+ singleTap(inputCenter);
+ QTRY_COMPARE(activeElement(), QStringLiteral("input"));
+ QTRY_COMPARE(view.selectedText(), QStringLiteral("Company2"));
+
+ // Double tap outside the input field behaves like a single tap: clears its focus and selection
+ singleTap(notextCenter);
+ singleTap(notextCenter);
+ QTRY_COMPARE(activeElement(), QString());
+ QTRY_VERIFY(!view.hasSelection());
+}
+
+void TouchInputTest::touchTapAndHold()
+{
+ auto tapAndHold = [target = view.focusProxy()] (const QPoint& tapCoords) -> void {
+ QTest::touchEvent(target, s_touchDevice).press(1, tapCoords, target);
+ QTest::touchEvent(target, s_touchDevice).stationary(1);
+ QTest::qWait(1000);
+ QTest::touchEvent(target, s_touchDevice).release(1, tapCoords, target);
+ };
+
+ // Tap-and-hold on text selects the word under it
+ tapAndHold(textCenter);
+ QTRY_COMPARE(activeElement(), QString());
+ QTRY_COMPARE(view.selectedText(), QStringLiteral("Company"));
+
+ // Tap-and-hold inside the input field focuses it and selects the word under it
+ tapAndHold(inputCenter);
+ QTRY_COMPARE(activeElement(), QStringLiteral("input"));
+ QTRY_COMPARE(view.selectedText(), QStringLiteral("Company2"));
+
+ // Only test the page context menu on Windows, as Linux doesn't handle context menus consistently
+ // and other non-desktop platforms like Android may not even support context menus at all
+#if defined(Q_OS_WIN)
+ // Tap-and-hold clears the text selection and shows the page's context menu
+ QVERIFY(QApplication::activePopupWidget() == nullptr);
+ tapAndHold(notextCenter);
+ QTRY_COMPARE(activeElement(), QString());
+ QTRY_VERIFY(!view.hasSelection());
+ QTRY_VERIFY(QApplication::activePopupWidget() != nullptr);
+
+ QApplication::activePopupWidget()->close();
+ QVERIFY(QApplication::activePopupWidget() == nullptr);
+#endif
+}
+
+void TouchInputTest::touchTapAndHoldCancelled()
+{
+ auto cancelledTapAndHold = [target = view.focusProxy()] (const QPoint& tapCoords) -> void {
+ QTest::touchEvent(target, s_touchDevice).press(1, tapCoords, target);
+ QTest::touchEvent(target, s_touchDevice).stationary(1);
+ QTest::qWait(1000);
+ QWindowSystemInterface::handleTouchCancelEvent(target->windowHandle(), s_touchDevice);
+ };
+
+ // A cancelled tap-and-hold should cancel text selection, but currently doesn't
+ cancelledTapAndHold(textCenter);
+ QEXPECT_FAIL("", "Incorrect Chromium selection behavior when cancelling tap-and-hold on text", Continue);
+ QTRY_VERIFY_WITH_TIMEOUT(!view.hasSelection(), 100);
+
+ // A cancelled tap-and-hold should cancel input field focusing and selection, but currently doesn't
+ cancelledTapAndHold(inputCenter);
+ QEXPECT_FAIL("", "Incorrect Chromium selection behavior when cancelling tap-and-hold on input field", Continue);
+ QTRY_VERIFY_WITH_TIMEOUT(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty(), 100);
+ QEXPECT_FAIL("", "Incorrect Chromium focus behavior when cancelling tap-and-hold on input field", Continue);
+ QTRY_VERIFY_WITH_TIMEOUT(!view.hasSelection(), 100);
+
+ // Only test the page context menu on Windows, as Linux doesn't handle context menus consistently
+ // and other non-desktop platforms like Android may not even support context menus at all
+#if defined(Q_OS_WIN)
+ // A cancelled tap-and-hold cancels the context menu
+ QVERIFY(QApplication::activePopupWidget() == nullptr);
+ cancelledTapAndHold(notextCenter);
+ QVERIFY(QApplication::activePopupWidget() == nullptr);
+#endif
+}
+
+void TouchInputTest::scrolling()
+{
+ int p = getScrollPosition();
+ QCOMPARE(p, 0);
+
+ // scroll a bit down...
+ for (int i = 0; i < 3; ++i) {
+ gestureScroll(/* down = */true);
+ int positionBefore = p;
+ QTRY_VERIFY2(getScrollPosition(&p) > positionBefore, qPrintable(QString("i: %1, position: %2 -> %3").arg(i).arg(positionBefore).arg(p)));
+ }
+
+ // ... and then scroll page again but in opposite direction
+ for (int i = 0; i < 3; ++i) {
+ gestureScroll(/* down = */false);
+ int positionBefore = p;
+ QTRY_VERIFY2(getScrollPosition(&p) < positionBefore, qPrintable(QString("i: %1, position: %2 -> %3").arg(i).arg(positionBefore).arg(p)));
+ }
+
+ QTRY_COMPARE(getScrollPosition(), 0);
+}
+
+void TouchInputTest::pinchZoom_data()
+{
+ QTest::addColumn<bool>("tapOneByOne");
+ QTest::addRow("sequential") << true;
+ QTest::addRow("simultaneous") << false;
+}
+
+void TouchInputTest::pinchZoom()
+{
+ QFETCH(bool, tapOneByOne);
+ double scale = getScaleFactor();
+ QCOMPARE(scale, 1.0);
+
+ for (int i = 0; i < 3; ++i) {
+ gesturePinch(/* zoomIn = */true, tapOneByOne);
+ QTRY_VERIFY2(getScaleFactor(&scale) > 1.5, qPrintable(QString("i: %1, scale: %2").arg(i).arg(scale)));
+ gesturePinch(/* zoomIn = */false, tapOneByOne);
+ QTRY_COMPARE(getScaleFactor(&scale), 1.0);
+ }
+}
+
+void TouchInputTest::complexSequence()
+{
+ auto t = view.focusProxy();
+ QPoint pc(view.width() / 2, view.height() / 2), p1 = pc - QPoint(50, 25), p2 = pc + QPoint(50, 25);
+
+ for (int i = 0; i < 4; ++i) {
+ QTest::touchEvent(t, s_touchDevice).press(42, p1, t); QTest::qWait(50);
+ QTest::touchEvent(t, s_touchDevice).stationary(42).press(24, p2, t); QTest::qWait(50);
+ QTest::touchEvent(t, s_touchDevice).release(42, p1, t).release(24, p2, t);
+
+ // for additional variablity add zooming in on even steps and zooming out on odd steps
+ // MEMO scroll position will always be 0 while viewport scale factor > 1.0, so do zoom in after scroll
+ bool zoomIn = i % 2 == 0;
+
+ if (!zoomIn) {
+ gesturePinch(false);
+ QTRY_COMPARE(getScaleFactor(), 1.0);
+ }
+
+ int p = getScrollPosition(), positionBefore = p;
+ gestureScroll(true);
+ QTRY_VERIFY2_WITH_TIMEOUT(getScrollPosition(&p) > positionBefore, qPrintable(QString("i: %1, position: %2 -> %3").arg(i).arg(positionBefore).arg(p)), 1000);
+
+ if (zoomIn) {
+ double s = getScaleFactor(), scaleBefore = s;
+ gesturePinch(true);
+ QTRY_VERIFY2(getScaleFactor(&s) > scaleBefore, qPrintable(QString("i: %1, scale: %2").arg(i).arg(s)));
+ }
+ }
+}
+
+QTEST_MAIN(TouchInputTest)
+#include "tst_touchinput.moc"
diff --git a/tests/auto/widgets/util.h b/tests/auto/widgets/util.h
index e030d1a2f..3be9a91b9 100644
--- a/tests/auto/widgets/util.h
+++ b/tests/auto/widgets/util.h
@@ -184,6 +184,43 @@ static inline bool loadSync(QWebEngineView *view, const QUrl &url, bool ok = tru
return loadSync(view->page(), url, ok);
}
+static inline QPoint elementCenter(QWebEnginePage *page, const QString &id)
+{
+ const QString jsCode(
+ "(function(){"
+ " var elem = document.getElementById('" + id + "');"
+ " var rect = elem.getBoundingClientRect();"
+ " return [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2];"
+ "})()");
+ QVariantList rectList = evaluateJavaScriptSync(page, jsCode).toList();
+
+ if (rectList.count() != 2) {
+ qWarning("elementCenter failed.");
+ return QPoint();
+ }
+
+ return QPoint(rectList.at(0).toInt(), rectList.at(1).toInt());
+}
+
+static inline QRect elementGeometry(QWebEnginePage *page, const QString &id)
+{
+ const QString jsCode(
+ "(function() {"
+ " var elem = document.getElementById('" + id + "');"
+ " var rect = elem.getBoundingClientRect();"
+ " return [rect.left, rect.top, rect.right, rect.bottom];"
+ "})()");
+ QVariantList coords = evaluateJavaScriptSync(page, jsCode).toList();
+
+ if (coords.count() != 4) {
+ qWarning("elementGeometry faield.");
+ return QRect();
+ }
+
+ return QRect(coords[0].toInt(), coords[1].toInt(), coords[2].toInt(), coords[3].toInt());
+}
+
+
#define W_QSKIP(a, b) QSKIP(a)
#define W_QTEST_MAIN(TestObject, params) \
diff --git a/tests/auto/widgets/widgets.pro b/tests/auto/widgets/widgets.pro
index 947587f5e..4ec9e5d63 100644
--- a/tests/auto/widgets/widgets.pro
+++ b/tests/auto/widgets/widgets.pro
@@ -22,6 +22,9 @@ SUBDIRS += \
qwebenginesettings \
qwebengineview
+# Synthetic touch events are not supported on macOS
+!macos: SUBDIRS += touchinput
+
qtConfig(accessibility) {
SUBDIRS += accessibility
}