diff --git a/src/3rdparty b/src/3rdparty
index 0f35665bef3fc26a423ee7770356749c9a6d7dc9..d23df0c25c334dbde96ef5eb65cc098eee47859b 160000
--- a/src/3rdparty
+++ b/src/3rdparty
@@ -1 +1 @@
-Subproject commit 0f35665bef3fc26a423ee7770356749c9a6d7dc9
+Subproject commit d23df0c25c334dbde96ef5eb65cc098eee47859b
diff --git a/src/core/config/linux.pri b/src/core/config/linux.pri
index c2d41b4d0cd299b570f454b94ab8154dc8a4c192..c0f2f6289a199eea6611b021ba40d77eebbbd8be 100644
--- a/src/core/config/linux.pri
+++ b/src/core/config/linux.pri
@@ -41,7 +41,7 @@ cross_compile:!host_build {
     !isEmpty(TOOLCHAIN_SYSROOT): gn_args += target_sysroot=\"$${TOOLCHAIN_SYSROOT}\"
 }
 
-contains(QT_ARCH, "arm"):!host_build {
+contains(QT_ARCH, "arm") {
     # Extract ARM specific compiler options that we have to pass to gn,
     # but let gn figure out a default if an option is not present.
     MTUNE = $$extractCFlag("-mtune=.*")
@@ -78,7 +78,7 @@ contains(QT_ARCH, "arm"):!host_build {
     else: contains(QMAKE_CFLAGS, "-mthumb"): gn_args += arm_use_thumb=true
 }
 
-contains(QT_ARCH, "mips"):!host_build {
+contains(QT_ARCH, "mips") {
     MARCH = $$extractCFlag("-march=.*")
     !isEmpty(MARCH) {
         equals(MARCH, "mips32r6"): gn_args += mips_arch_variant=\"r6\"
diff --git a/src/core/gl_surface_qt.cpp b/src/core/gl_surface_qt.cpp
index 69609f4d4c1c021cea807105b4272beb44cb789b..93c1c52930f127274fb60900ff3ee98ccca692c5 100644
--- a/src/core/gl_surface_qt.cpp
+++ b/src/core/gl_surface_qt.cpp
@@ -48,6 +48,7 @@
 #include <QGuiApplication>
 #include "gl_context_qt.h"
 #include "qtwebenginecoreglobal_p.h"
+#include "web_engine_context.h"
 
 #include "base/logging.h"
 #include "gpu/ipc/service/image_transport_surface.h"
@@ -652,6 +653,11 @@ bool InitializeGLOneOffPlatform()
     return false;
 }
 
+bool usingSoftwareDynamicGL()
+{
+    return QtWebEngineCore::usingSoftwareDynamicGL();
+}
+
 scoped_refptr<GLSurface>
 CreateOffscreenGLSurfaceWithFormat(const gfx::Size& size, GLSurfaceFormat format)
 {
diff --git a/src/core/renderer_host/user_resource_controller_host.cpp b/src/core/renderer_host/user_resource_controller_host.cpp
index f90aebda64b5b18ca1416a691007f87b820a1248..2799d5d8556667fa69e1255107f8e189eb7e3bd1 100644
--- a/src/core/renderer_host/user_resource_controller_host.cpp
+++ b/src/core/renderer_host/user_resource_controller_host.cpp
@@ -88,11 +88,6 @@ void UserResourceControllerHost::WebContentsObserverHelper::RenderFrameHostChang
 {
     if (oldHost)
         oldHost->Send(new RenderFrameObserverHelper_ClearScripts(oldHost->GetRoutingID()));
-
-    content::WebContents *contents = web_contents();
-    Q_FOREACH (const UserScript &script, m_controllerHost->m_perContentsScripts.value(contents))
-        newHost->Send(new RenderFrameObserverHelper_AddScript(newHost->GetRoutingID(),
-                                                              script.data()));
 }
 
 void UserResourceControllerHost::WebContentsObserverHelper::WebContentsDestroyed()
diff --git a/src/core/renderer_host/web_channel_ipc_transport_host.cpp b/src/core/renderer_host/web_channel_ipc_transport_host.cpp
index 1cd4e4063a7848fc9909d44225a7638f624a8636..c47b255b7b20ff06df666dc41e659622c859543d 100644
--- a/src/core/renderer_host/web_channel_ipc_transport_host.cpp
+++ b/src/core/renderer_host/web_channel_ipc_transport_host.cpp
@@ -40,6 +40,7 @@
 #include "web_channel_ipc_transport_host.h"
 
 #include "base/strings/string16.h"
+#include "content/public/browser/render_view_host.h"
 
 #include "common/qt_messages.h"
 #include "type_conversion.h"
@@ -61,11 +62,16 @@ WebChannelIPCTransportHost::~WebChannelIPCTransportHost()
 {
 }
 
-void WebChannelIPCTransportHost::RenderViewHostChanged(content::RenderViewHost *, content::RenderViewHost *)
+void WebChannelIPCTransportHost::RenderViewHostChanged(content::RenderViewHost *oldHost, content::RenderViewHost *)
 {
-    // This means that we were moved into a different RenderView, possibly in a different
-    // render process and that we lost our WebChannelIPCTransport object and its state.
-    Send(new WebChannelIPCTransport_Install(routing_id(), m_worldId));
+    if (oldHost)
+        oldHost->Send(new WebChannelIPCTransport_Uninstall(oldHost->GetRoutingID(), m_worldId));
+}
+
+void WebChannelIPCTransportHost::RenderViewCreated(content::RenderViewHost *view_host)
+{
+    // Make sure the new view knows a webchannel is installed and in which world.
+    view_host->Send(new WebChannelIPCTransport_Install(view_host->GetRoutingID(), m_worldId));
 }
 
 void WebChannelIPCTransportHost::setWorldId(uint worldId)
diff --git a/src/core/renderer_host/web_channel_ipc_transport_host.h b/src/core/renderer_host/web_channel_ipc_transport_host.h
index aa406471ce59bf854e503bbd4a5d84e2bc63371c..a1e697a910ad84dd5cc8c5beb78ad616f6ccfaa2 100644
--- a/src/core/renderer_host/web_channel_ipc_transport_host.h
+++ b/src/core/renderer_host/web_channel_ipc_transport_host.h
@@ -60,6 +60,7 @@ public:
 
     // WebContentsObserver
     void RenderViewHostChanged(content::RenderViewHost* old_host, content::RenderViewHost* new_host) override;
+    void RenderViewCreated(content::RenderViewHost* render_view_host) override;
 
     // QWebChannelAbstractTransport
     void sendMessage(const QJsonObject &message) override;
diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp
index 6181c0b72a10db5698282cc9fbcac4750924fbe5..82734911593faa6b39890c36b3ae77aa81cd6aa6 100644
--- a/src/core/web_contents_adapter.cpp
+++ b/src/core/web_contents_adapter.cpp
@@ -926,7 +926,9 @@ void WebContentsAdapter::updateWebPreferences(const content::WebPreferences & we
     d->webContents->GetRenderViewHost()->UpdateWebkitPreferences(webPreferences);
 }
 
-void WebContentsAdapter::download(const QUrl &url, const QString &suggestedFileName)
+void WebContentsAdapter::download(const QUrl &url, const QString &suggestedFileName,
+                                  const QUrl &referrerUrl,
+                                  ReferrerPolicy referrerPolicy)
 {
     Q_D(WebContentsAdapter);
     content::BrowserContext *bctx = webContents()->GetBrowserContext();
@@ -939,9 +941,19 @@ void WebContentsAdapter::download(const QUrl &url, const QString &suggestedFileN
     dlmd->setDownloadType(BrowserContextAdapterClient::UserRequested);
     dlm->SetDelegate(dlmd);
 
+    GURL gurl = toGurl(url);
     std::unique_ptr<content::DownloadUrlParameters> params(
-            content::DownloadUrlParameters::CreateForWebContentsMainFrame(webContents(), toGurl(url)));
+        content::DownloadUrlParameters::CreateForWebContentsMainFrame(webContents(), gurl));
+
     params->set_suggested_name(toString16(suggestedFileName));
+
+    // referrer logic based on chrome/browser/renderer_context_menu/render_view_context_menu.cc:
+    params->set_referrer(
+        content::Referrer::SanitizeForRequest(
+            gurl,
+            content::Referrer(toGurl(referrerUrl).GetAsReferrer(),
+                              static_cast<blink::WebReferrerPolicy>(referrerPolicy))));
+
     dlm->DownloadUrl(std::move(params));
 }
 
@@ -1473,4 +1485,13 @@ ASSERT_ENUMS_MATCH(WebContentsAdapterClient::SaveToDiskDisposition, WindowOpenDi
 ASSERT_ENUMS_MATCH(WebContentsAdapterClient::OffTheRecordDisposition, WindowOpenDisposition::OFF_THE_RECORD)
 ASSERT_ENUMS_MATCH(WebContentsAdapterClient::IgnoreActionDisposition, WindowOpenDisposition::IGNORE_ACTION)
 
+ASSERT_ENUMS_MATCH(ReferrerPolicy::Always, blink::kWebReferrerPolicyAlways)
+ASSERT_ENUMS_MATCH(ReferrerPolicy::Default, blink::kWebReferrerPolicyDefault)
+ASSERT_ENUMS_MATCH(ReferrerPolicy::NoReferrerWhenDowngrade, blink::kWebReferrerPolicyNoReferrerWhenDowngrade)
+ASSERT_ENUMS_MATCH(ReferrerPolicy::Never, blink::kWebReferrerPolicyNever)
+ASSERT_ENUMS_MATCH(ReferrerPolicy::Origin, blink::kWebReferrerPolicyOrigin)
+ASSERT_ENUMS_MATCH(ReferrerPolicy::OriginWhenCrossOrigin, blink::kWebReferrerPolicyOriginWhenCrossOrigin)
+ASSERT_ENUMS_MATCH(ReferrerPolicy::NoReferrerWhenDowngradeOriginWhenCrossOrigin, blink::kWebReferrerPolicyNoReferrerWhenDowngradeOriginWhenCrossOrigin)
+ASSERT_ENUMS_MATCH(ReferrerPolicy::Last, blink::kWebReferrerPolicyLast)
+
 } // namespace QtWebEngineCore
diff --git a/src/core/web_contents_adapter.h b/src/core/web_contents_adapter.h
index 46c8d2604c9399bc77128eec892ec14c42c04202..67fcbe7af50594cc0a772772acb77ea5932c210d 100644
--- a/src/core/web_contents_adapter.h
+++ b/src/core/web_contents_adapter.h
@@ -124,7 +124,9 @@ public:
     quint64 findText(const QString &subString, bool caseSensitively, bool findBackward);
     void stopFinding();
     void updateWebPreferences(const content::WebPreferences &webPreferences);
-    void download(const QUrl &url, const QString &suggestedFileName);
+    void download(const QUrl &url, const QString &suggestedFileName,
+                  const QUrl &referrerUrl = QUrl(),
+                  ReferrerPolicy referrerPolicy = ReferrerPolicy::Default);
     bool isAudioMuted() const;
     void setAudioMuted(bool mute);
     bool recentlyAudible();
diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h
index 8b73653429ca7436c3da948c6be0d6486484e4ee..4280dbfe1f8ce96ad67d9ac15bee70d07253be19 100644
--- a/src/core/web_contents_adapter_client.h
+++ b/src/core/web_contents_adapter_client.h
@@ -71,6 +71,17 @@ class WebContentsAdapter;
 class WebContentsDelegateQt;
 class WebEngineSettings;
 
+// Must match blink::WebReferrerPolicy
+enum class ReferrerPolicy {
+    Always,
+    Default,
+    NoReferrerWhenDowngrade,
+    Never,
+    Origin,
+    OriginWhenCrossOrigin,
+    NoReferrerWhenDowngradeOriginWhenCrossOrigin,
+    Last = NoReferrerWhenDowngradeOriginWhenCrossOrigin,
+};
 
 class WebEngineContextMenuSharedData : public QSharedData {
 
@@ -97,6 +108,9 @@ public:
     QString suggestedFileName;
     QString misspelledWord;
     QStringList spellCheckerSuggestions;
+    QUrl pageUrl;
+    QUrl frameUrl;
+    ReferrerPolicy referrerPolicy = ReferrerPolicy::Default;
     // Some likely candidates for future additions as we add support for the related actions:
     //    bool isImageBlocked;
     //    <enum tbd> mediaType;
@@ -253,6 +267,34 @@ public:
         return d->spellCheckerSuggestions;
     }
 
+    void setFrameUrl(const QUrl &url) {
+        d->frameUrl = url;
+    }
+
+    QUrl frameUrl() const {
+        return d->frameUrl;
+    }
+
+    void setPageUrl(const QUrl &url) {
+        d->pageUrl = url;
+    }
+
+    QUrl pageUrl() const {
+        return d->pageUrl;
+    }
+
+    QUrl referrerUrl() const {
+        return !d->frameUrl.isEmpty() ? d->frameUrl : d->pageUrl;
+    }
+
+    void setReferrerPolicy(ReferrerPolicy referrerPolicy) {
+        d->referrerPolicy = referrerPolicy;
+    }
+
+    ReferrerPolicy referrerPolicy() const {
+        return d->referrerPolicy;
+    }
+
 private:
     QSharedDataPointer<WebEngineContextMenuSharedData> d;
 };
diff --git a/src/core/web_contents_view_qt.cpp b/src/core/web_contents_view_qt.cpp
index f9435ac34d902232f474117b7612139afa2d1204..28f202e24288b7879b870db0c297a3bf12d500b8 100644
--- a/src/core/web_contents_view_qt.cpp
+++ b/src/core/web_contents_view_qt.cpp
@@ -176,6 +176,9 @@ static inline WebEngineContextMenuData fromParams(const content::ContextMenuPara
     ret.setMisspelledWord(toQt(params.misspelled_word));
     ret.setSpellCheckerSuggestions(fromVector(params.dictionary_suggestions));
 #endif
+    ret.setFrameUrl(toQt(params.frame_url));
+    ret.setPageUrl(toQt(params.page_url));
+    ret.setReferrerPolicy((ReferrerPolicy)params.referrer_policy);
     return ret;
 }
 
diff --git a/src/core/web_engine_context.cpp b/src/core/web_engine_context.cpp
index 4df4dbfae11b6d2854f86f29f591d72d9647e446..83b7c2d36a35fee29081a38ecf525b6ba04632a3 100644
--- a/src/core/web_engine_context.cpp
+++ b/src/core/web_engine_context.cpp
@@ -135,21 +135,6 @@ bool usingANGLE()
 #endif
 }
 
-bool usingSoftwareDynamicGL()
-{
-    if (QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL))
-        return true;
-#if defined(Q_OS_WIN)
-    HMODULE handle = static_cast<HMODULE>(QOpenGLContext::openGLModuleHandle());
-    wchar_t path[MAX_PATH];
-    DWORD size = GetModuleFileName(handle, path, MAX_PATH);
-    QFileInfo openGLModule(QString::fromWCharArray(path, size));
-    return openGLModule.fileName() == QLatin1String("opengl32sw.dll");
-#else
-    return false;
-#endif
-}
-
 bool usingQtQuick2DRenderer()
 {
     const QStringList args = QGuiApplication::arguments();
@@ -186,6 +171,21 @@ void dummyGetPluginCallback(const std::vector<content::WebPluginInfo>&)
 
 namespace QtWebEngineCore {
 
+bool usingSoftwareDynamicGL()
+{
+    if (QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL))
+        return true;
+#if defined(Q_OS_WIN)
+    HMODULE handle = static_cast<HMODULE>(QOpenGLContext::openGLModuleHandle());
+    wchar_t path[MAX_PATH];
+    DWORD size = GetModuleFileName(handle, path, MAX_PATH);
+    QFileInfo openGLModule(QString::fromWCharArray(path, size));
+    return openGLModule.fileName() == QLatin1String("opengl32sw.dll");
+#else
+    return false;
+#endif
+}
+
 void WebEngineContext::destroyBrowserContext()
 {
     m_defaultBrowserContext.reset();
@@ -287,6 +287,9 @@ WebEngineContext::WebEngineContext()
         appArgs.append(QString::fromLocal8Bit(qgetenv(kChromiumFlagsEnv)).split(' '));
     }
 
+    bool enableWebGLSoftwareRendering =
+            appArgs.removeAll(QStringLiteral("--enable-webgl-software-rendering"));
+
     bool useEmbeddedSwitches = false;
 #if defined(QTWEBENGINE_EMBEDDED_SWITCHES)
     useEmbeddedSwitches = !appArgs.removeAll(QStringLiteral("--disable-embedded-switches"));
@@ -371,7 +374,20 @@ WebEngineContext::WebEngineContext()
 
     const char *glType = 0;
 #ifndef QT_NO_OPENGL
-    if (!usingANGLE() && !usingSoftwareDynamicGL() && !usingQtQuick2DRenderer()) {
+
+    bool tryGL =
+            !usingANGLE()
+            && (!usingSoftwareDynamicGL()
+#ifdef Q_OS_WIN
+                // If user requested WebGL support on Windows, instead of using Skia rendering to
+                // bitmaps, use software rendering via opengl32sw.dll. This might be less
+                // performant, but at least provides WebGL support.
+                || enableWebGLSoftwareRendering
+#endif
+                )
+            && !usingQtQuick2DRenderer();
+
+    if (tryGL) {
         if (qt_gl_global_share_context() && qt_gl_global_share_context()->isValid()) {
             // If the native handle is QEGLNativeContext try to use GL ES/2, if there is no native handle
             // assume we are using wayland and try GL ES/2, and finally Ozone demands GL ES/2 too.
diff --git a/src/core/web_engine_context.h b/src/core/web_engine_context.h
index 386f43035972f2627a388b036f343beedd2c1904..92c45e260fea851ced89a6b18ecd4a52fcdc67dd 100644
--- a/src/core/web_engine_context.h
+++ b/src/core/web_engine_context.h
@@ -74,6 +74,8 @@ class ContentMainDelegateQt;
 class DevToolsServerQt;
 class SurfaceFactoryQt;
 
+bool usingSoftwareDynamicGL();
+
 class WebEngineContext : public base::RefCounted<WebEngineContext> {
 public:
     static scoped_refptr<WebEngineContext> current();
diff --git a/src/core/web_event_factory.cpp b/src/core/web_event_factory.cpp
index e6ac63c0dd4e323b8d83fa736c8dda3d7c6ea2e6..18f8d393f0d76d057f65ce3610fdd8898b28558d 100644
--- a/src/core/web_event_factory.cpp
+++ b/src/core/web_event_factory.cpp
@@ -144,7 +144,6 @@ static int windowsKeyCodeForKeyEvent(unsigned int keycode, bool isKeypad)
             return VK_SHIFT; // (10) SHIFT key
         case Qt::Key_Control:
             return VK_CONTROL; // (11) CTRL key
-        case Qt::Key_Menu:
         case Qt::Key_Alt:
             return VK_MENU; // (12) ALT key
         default:
@@ -167,7 +166,6 @@ static int windowsKeyCodeForKeyEvent(unsigned int keycode, bool isKeypad)
             return VK_SHIFT; // (10) SHIFT key
         case Qt::Key_Control:
             return VK_CONTROL; // (11) CTRL key
-        case Qt::Key_Menu:
         case Qt::Key_Alt:
             return VK_MENU; // (12) ALT key
 
@@ -357,7 +355,8 @@ static int windowsKeyCodeForKeyEvent(unsigned int keycode, bool isKeypad)
             return VK_LWIN; // (5B) Left Windows key (Microsoft Natural keyboard)
             // case Qt::Key_Meta_R: FIXME: What to do here?
             //    return VK_RWIN; // (5C) Right Windows key (Natural keyboard)
-            // VK_APPS (5D) Applications key (Natural keyboard)
+        case Qt::Key_Menu: // (5D) Applications key (Natural keyboard)
+            return VK_APPS;
             // VK_SLEEP (5F) Computer Sleep key
             // VK_SEPARATOR (6C) Separator key
             // VK_SUBTRACT (6D) Subtract key
diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp
index 123acb20c842cb627391137975b74b503e0b4103..78fdd8fa819183b177eec00b047812ffad4269a0 100644
--- a/src/webengine/api/qquickwebengineview.cpp
+++ b/src/webengine/api/qquickwebengineview.cpp
@@ -1639,7 +1639,8 @@ void QQuickWebEngineView::triggerWebAction(WebAction action)
         break;
     case DownloadLinkToDisk:
         if (d->m_contextMenuData.linkUrl().isValid())
-            d->adapter->download(d->m_contextMenuData.linkUrl(), d->m_contextMenuData.suggestedFileName());
+            d->adapter->download(d->m_contextMenuData.linkUrl(), d->m_contextMenuData.suggestedFileName(),
+                                 d->m_contextMenuData.referrerUrl(), d->m_contextMenuData.referrerPolicy());
         break;
     case CopyImageToClipboard:
         if (d->m_contextMenuData.hasImageContent() &&
@@ -1666,7 +1667,8 @@ void QQuickWebEngineView::triggerWebAction(WebAction action)
     case DownloadImageToDisk:
     case DownloadMediaToDisk:
         if (d->m_contextMenuData.mediaUrl().isValid())
-            d->adapter->download(d->m_contextMenuData.mediaUrl(), d->m_contextMenuData.suggestedFileName());
+            d->adapter->download(d->m_contextMenuData.mediaUrl(), d->m_contextMenuData.suggestedFileName(),
+                                 d->m_contextMenuData.referrerUrl(), d->m_contextMenuData.referrerPolicy());
         break;
     case CopyMediaUrlToClipboard:
         if (d->m_contextMenuData.mediaUrl().isValid() &&
diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp
index 178f6ec9868aa579c98964ada04c19e5f3a261bc..76c705c6e8c08725ee4a6737e4fc0accae80202e 100644
--- a/src/webenginewidgets/api/qwebenginepage.cpp
+++ b/src/webenginewidgets/api/qwebenginepage.cpp
@@ -1275,7 +1275,9 @@ void QWebEnginePage::triggerAction(WebAction action, bool)
         break;
     case DownloadLinkToDisk:
         if (menuData.linkUrl().isValid())
-            d->adapter->download(menuData.linkUrl(), menuData.suggestedFileName());
+            d->adapter->download(menuData.linkUrl(), menuData.suggestedFileName(),
+                                 menuData.referrerUrl(), menuData.referrerPolicy());
+
         break;
     case CopyImageToClipboard:
         if (menuData.hasImageContent() &&
@@ -1302,7 +1304,8 @@ void QWebEnginePage::triggerAction(WebAction action, bool)
     case DownloadImageToDisk:
     case DownloadMediaToDisk:
         if (menuData.mediaUrl().isValid())
-            d->adapter->download(menuData.mediaUrl(), menuData.suggestedFileName());
+            d->adapter->download(menuData.mediaUrl(), menuData.suggestedFileName(),
+                                 menuData.referrerUrl(), menuData.referrerPolicy());
         break;
     case CopyMediaUrlToClipboard:
         if (menuData.mediaUrl().isValid() &&
@@ -1466,7 +1469,7 @@ void QWebEnginePagePrivate::wasHidden()
 
 bool QWebEnginePagePrivate::contextMenuRequested(const WebEngineContextMenuData &data)
 {
-    if (!view || !view->d_func()->m_pendingContextMenuEvent)
+    if (!view)
         return false;
 
     contextData.reset();
@@ -1492,7 +1495,6 @@ bool QWebEnginePagePrivate::contextMenuRequested(const WebEngineContextMenuData
         event.ignore();
         return false;
     }
-    view->d_func()->m_pendingContextMenuEvent = false;
     return true;
 }
 
diff --git a/src/webenginewidgets/api/qwebengineview.cpp b/src/webenginewidgets/api/qwebengineview.cpp
index 56948bb18a9d5ba7768c798ae36237e0bb8d1413..0037f7e5001c7febfbbf155f202330b7f947116e 100644
--- a/src/webenginewidgets/api/qwebengineview.cpp
+++ b/src/webenginewidgets/api/qwebengineview.cpp
@@ -106,7 +106,6 @@ static QAccessibleInterface *webAccessibleFactory(const QString &, QObject *obje
 
 QWebEngineViewPrivate::QWebEngineViewPrivate()
     : page(0)
-    , m_pendingContextMenuEvent(false)
     , m_dragEntered(false)
 {
 #ifndef QT_NO_ACCESSIBILITY
@@ -316,12 +315,10 @@ void QWebEngineView::setZoomFactor(qreal factor)
  */
 bool QWebEngineView::event(QEvent *ev)
 {
-    Q_D(QWebEngineView);
     // We swallow spontaneous contextMenu events and synthethize those back later on when we get the
     // HandleContextMenu callback from chromium
     if (ev->type() == QEvent::ContextMenu) {
         ev->accept();
-        d->m_pendingContextMenuEvent = true;
         return true;
     }
     return QWidget::event(ev);
diff --git a/src/webenginewidgets/api/qwebengineview_p.h b/src/webenginewidgets/api/qwebengineview_p.h
index 45b3e266eb0668c93cfc92baa8e36ae13162b536..f3a37225b619b42a1eb2054134d7249bf2f028c5 100644
--- a/src/webenginewidgets/api/qwebengineview_p.h
+++ b/src/webenginewidgets/api/qwebengineview_p.h
@@ -70,7 +70,6 @@ public:
     QWebEngineViewPrivate();
 
     QWebEnginePage *page;
-    bool m_pendingContextMenuEvent;
     bool m_dragEntered;
 };
 
diff --git a/tests/auto/shared/http.pri b/tests/auto/shared/http.pri
new file mode 100644
index 0000000000000000000000000000000000000000..5236e9d26935e2ddee1cc1c5e2db9f75fb1de47b
--- /dev/null
+++ b/tests/auto/shared/http.pri
@@ -0,0 +1,3 @@
+HEADERS += $$PWD/httpserver.h $$PWD/httpreqrep.h
+SOURCES += $$PWD/httpserver.cpp $$PWD/httpreqrep.cpp
+INCLUDEPATH += $$PWD
diff --git a/tests/auto/shared/httpreqrep.cpp b/tests/auto/shared/httpreqrep.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..eb2db68901ac4bb72cf2c9f45440c893c44b8a35
--- /dev/null
+++ b/tests/auto/shared/httpreqrep.cpp
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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 "httpreqrep.h"
+
+HttpReqRep::HttpReqRep(QTcpSocket *socket, QObject *parent)
+    : QObject(parent), m_socket(socket)
+{
+    m_socket->setParent(this);
+    connect(m_socket, &QIODevice::readyRead, this, &HttpReqRep::handleReadyRead);
+}
+
+void HttpReqRep::sendResponse()
+{
+    m_socket->write("HTTP/1.1 ");
+    m_socket->write(QByteArray::number(m_responseStatusCode));
+    m_socket->write(" OK?\r\n");
+    for (const auto & kv : m_responseHeaders) {
+        m_socket->write(kv.first);
+        m_socket->write(": ");
+        m_socket->write(kv.second);
+        m_socket->write("\r\n");
+    }
+    m_socket->write("\r\n");
+    m_socket->write(m_responseBody);
+    m_socket->close();
+}
+
+QByteArray HttpReqRep::requestHeader(const QByteArray &key) const
+{
+    auto it = m_requestHeaders.find(key);
+    if (it != m_requestHeaders.end())
+        return it->second;
+    return {};
+}
+
+void HttpReqRep::handleReadyRead()
+{
+    const auto requestLine = m_socket->readLine();
+    const auto requestLineParts = requestLine.split(' ');
+    if (requestLineParts.size() != 3 || !requestLineParts[2].toUpper().startsWith("HTTP/")) {
+        qWarning("HttpReqRep: invalid request line");
+        Q_EMIT readFinished(false);
+        return;
+    }
+
+    decltype(m_requestHeaders) headers;
+    for (;;) {
+        const auto headerLine = m_socket->readLine();
+        if (headerLine == QByteArrayLiteral("\r\n"))
+            break;
+        int colonIndex = headerLine.indexOf(':');
+        if (colonIndex < 0) {
+            qWarning("HttpReqRep: invalid header line");
+            Q_EMIT readFinished(false);
+            return;
+        }
+        auto headerKey = headerLine.left(colonIndex).trimmed().toLower();
+        auto headerValue = headerLine.mid(colonIndex + 1).trimmed().toLower();
+        headers.emplace(headerKey, headerValue);
+    }
+
+    m_requestMethod = requestLineParts[0];
+    m_requestPath = requestLineParts[1];
+    m_requestHeaders = headers;
+    Q_EMIT readFinished(true);
+}
diff --git a/tests/auto/shared/httpreqrep.h b/tests/auto/shared/httpreqrep.h
new file mode 100644
index 0000000000000000000000000000000000000000..4e9f10dff3c5b56b3d1e2c9f3ba87c54938d35dd
--- /dev/null
+++ b/tests/auto/shared/httpreqrep.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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$
+**
+****************************************************************************/
+#ifndef HTTPREQREP_H
+#define HTTPREQREP_H
+
+#include <QTcpSocket>
+
+#include <utility>
+
+// Represents an HTTP request-response exchange.
+class HttpReqRep : public QObject
+{
+    Q_OBJECT
+public:
+    HttpReqRep(QTcpSocket *socket, QObject *parent = nullptr);
+    void sendResponse();
+    QByteArray requestMethod() const { return m_requestMethod; }
+    QByteArray requestPath() const { return m_requestPath; }
+    QByteArray requestHeader(const QByteArray &key) const;
+    void setResponseStatus(int statusCode)
+    {
+        m_responseStatusCode = statusCode;
+    }
+    void setResponseHeader(const QByteArray &key, QByteArray value)
+    {
+        m_responseHeaders[key.toLower()] = std::move(value);
+    }
+    void setResponseBody(QByteArray content)
+    {
+        m_responseHeaders["content-length"] = QByteArray::number(content.size());
+        m_responseBody = std::move(content);
+    }
+
+Q_SIGNALS:
+    void readFinished(bool ok);
+
+private Q_SLOTS:
+    void handleReadyRead();
+
+private:
+    QTcpSocket *m_socket = nullptr;
+    QByteArray m_requestMethod;
+    QByteArray m_requestPath;
+    std::map<QByteArray, QByteArray> m_requestHeaders;
+    int m_responseStatusCode = 200;
+    std::map<QByteArray, QByteArray> m_responseHeaders;
+    QByteArray m_responseBody;
+};
+
+#endif // !HTTPREQREP_H
diff --git a/tests/auto/shared/httpserver.cpp b/tests/auto/shared/httpserver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6012379f2a68d63039f4808656d42db551166842
--- /dev/null
+++ b/tests/auto/shared/httpserver.cpp
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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 "httpserver.h"
+
+#include "waitforsignal.h"
+
+HttpServer::HttpServer(QObject *parent) : QObject(parent)
+{
+    connect(&m_tcpServer, &QTcpServer::newConnection, this, &HttpServer::handleNewConnection);
+    if (!m_tcpServer.listen())
+        qWarning("HttpServer: listen() failed");
+    m_url = QStringLiteral("http://127.0.0.1:") + QString::number(m_tcpServer.serverPort());
+}
+
+QUrl HttpServer::url(const QString &path) const
+{
+    auto copy = m_url;
+    copy.setPath(path);
+    return copy;
+}
+
+void HttpServer::handleNewConnection()
+{
+    auto reqRep = new HttpReqRep(m_tcpServer.nextPendingConnection(), this);
+    connect(reqRep, &HttpReqRep::readFinished, this, &HttpServer::handleReadFinished);
+}
+
+void HttpServer::handleReadFinished(bool ok)
+{
+    auto reqRep = qobject_cast<HttpReqRep *>(sender());
+    if (ok)
+        Q_EMIT newRequest(reqRep);
+    else
+        reqRep->deleteLater();
+}
+
+std::unique_ptr<HttpReqRep> waitForRequest(HttpServer *server)
+{
+    std::unique_ptr<HttpReqRep> result;
+    waitForSignal(server, &HttpServer::newRequest, [&](HttpReqRep *rr) {
+        rr->setParent(nullptr);
+        result.reset(rr);
+    });
+    return result;
+}
diff --git a/tests/auto/shared/httpserver.h b/tests/auto/shared/httpserver.h
new file mode 100644
index 0000000000000000000000000000000000000000..e45743b7b0e19aba95e7eca43d2f176182a746d9
--- /dev/null
+++ b/tests/auto/shared/httpserver.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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$
+**
+****************************************************************************/
+#ifndef HTTPSERVER_H
+#define HTTPSERVER_H
+
+#include "httpreqrep.h"
+
+#include <QTcpServer>
+#include <QUrl>
+
+#include <memory>
+
+// Listens on a TCP socket and creates HttpReqReps for each connection.
+class HttpServer : public QObject
+{
+    Q_OBJECT
+
+    QTcpServer m_tcpServer;
+    QUrl m_url;
+
+public:
+    HttpServer(QObject *parent = nullptr);
+    QUrl url(const QString &path = QStringLiteral("/")) const;
+
+Q_SIGNALS:
+    void newRequest(HttpReqRep *reqRep);
+
+private Q_SLOTS:
+    void handleNewConnection();
+    void handleReadFinished(bool ok);
+};
+
+// Wait for an HTTP request to be received.
+std::unique_ptr<HttpReqRep> waitForRequest(HttpServer *server);
+
+#endif // !HTTPSERVER_H
diff --git a/tests/auto/shared/waitforsignal.h b/tests/auto/shared/waitforsignal.h
new file mode 100644
index 0000000000000000000000000000000000000000..9b2daf7af03c5033df955227517de5773927c5f7
--- /dev/null
+++ b/tests/auto/shared/waitforsignal.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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$
+**
+****************************************************************************/
+#ifndef WAITFORSIGNAL_H
+#define WAITFORSIGNAL_H
+
+#include <QObject>
+#include <QTestEventLoop>
+
+#include <utility>
+
+// Implementation details of waitForSignal.
+namespace {
+    // Wraps a functor to set a flag and exit from event loop if called.
+    template <class SignalHandler>
+    struct waitForSignal_SignalHandlerWrapper {
+        waitForSignal_SignalHandlerWrapper(SignalHandler &&sh)
+            : signalHandler(std::forward<SignalHandler>(sh)) {}
+
+        template <class... Args>
+        void operator()(Args && ... args) {
+            signalHandler(std::forward<Args>(args)...);
+            hasBeenCalled = true;
+            loop.exitLoop();
+        }
+
+        SignalHandler &&signalHandler;
+        QTestEventLoop loop;
+        bool hasBeenCalled = false;
+    };
+
+    // No-op functor.
+    struct waitForSignal_DefaultSignalHandler {
+        template <class... Args>
+        void operator()(Args && ...) {}
+    };
+} // namespace
+
+// Wait for a signal to be emitted.
+//
+// The given functor is called with the signal arguments allowing the arguments
+// to be modified before returning from the signal handler (unlike QSignalSpy).
+template <class Sender, class Signal, class SignalHandler>
+bool waitForSignal(Sender &&sender, Signal &&signal, SignalHandler &&signalHandler, int timeout = 15000)
+{
+    waitForSignal_SignalHandlerWrapper<SignalHandler> signalHandlerWrapper(
+        std::forward<SignalHandler>(signalHandler));
+    auto connection = QObject::connect(
+        std::forward<Sender>(sender),
+        std::forward<Signal>(signal),
+        std::ref(signalHandlerWrapper));
+    signalHandlerWrapper.loop.enterLoopMSecs(timeout);
+    QObject::disconnect(connection);
+    return signalHandlerWrapper.hasBeenCalled;
+}
+
+template <class Sender, class Signal>
+bool waitForSignal(Sender &&sender, Signal &&signal, int timeout = 15000)
+{
+    return waitForSignal(std::forward<Sender>(sender),
+                         std::forward<Signal>(signal),
+                         waitForSignal_DefaultSignalHandler(),
+                         timeout);
+}
+
+#endif // !WAITFORSIGNAL_H
diff --git a/tests/auto/widgets/qwebenginedownloads/qwebenginedownloads.pro b/tests/auto/widgets/qwebenginedownloads/qwebenginedownloads.pro
new file mode 100644
index 0000000000000000000000000000000000000000..18a66c466abe0f69b56995124f258e3d97f0b157
--- /dev/null
+++ b/tests/auto/widgets/qwebenginedownloads/qwebenginedownloads.pro
@@ -0,0 +1,3 @@
+include(../tests.pri)
+include(../../shared/http.pri)
+QT *= core-private
diff --git a/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp b/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6ba6d78a438c7569911d1db81897474d7c79773d
--- /dev/null
+++ b/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp
@@ -0,0 +1,595 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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 <QCoreApplication>
+#include <QStandardPaths>
+#include <QTemporaryDir>
+#include <QTest>
+#include <QWebEngineDownloadItem>
+#include <QWebEnginePage>
+#include <QWebEngineProfile>
+#include <QWebEngineView>
+#include <httpserver.h>
+#include <waitforsignal.h>
+
+class tst_QWebEngineDownloads : public QObject
+{
+    Q_OBJECT
+private Q_SLOTS:
+    void downloadLink_data();
+    void downloadLink();
+    void downloadTwoLinks();
+    void downloadPage_data();
+    void downloadPage();
+};
+
+enum DownloadTestUserAction {
+    SaveLink,
+    Navigate,
+};
+
+enum DownloadTestFileAction {
+    FileIsDownloaded,
+    FileIsDisplayed,
+};
+
+Q_DECLARE_METATYPE(DownloadTestUserAction);
+Q_DECLARE_METATYPE(DownloadTestFileAction);
+
+void tst_QWebEngineDownloads::downloadLink_data()
+{
+    QTest::addColumn<DownloadTestUserAction>("userAction");
+    QTest::addColumn<bool>("anchorHasDownloadAttribute");
+    QTest::addColumn<QByteArray>("fileName");
+    QTest::addColumn<QByteArray>("fileContents");
+    QTest::addColumn<QByteArray>("fileMimeTypeDeclared");
+    QTest::addColumn<QByteArray>("fileMimeTypeDetected");
+    QTest::addColumn<QByteArray>("fileDisposition");
+    QTest::addColumn<bool>("fileHasReferer");
+    QTest::addColumn<DownloadTestFileAction>("fileAction");
+    QTest::addColumn<QWebEngineDownloadItem::DownloadType>("downloadType");
+
+    // SaveLink should always trigger a download, even for empty files.
+    QTest::newRow("save link to empty file")
+        /* userAction                 */ << SaveLink
+        /* anchorHasDownloadAttribute */ << false
+        /* fileName                   */ << QByteArrayLiteral("foo.txt")
+        /* fileContents               */ << QByteArrayLiteral("")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("")
+        /* fileDisposition            */ << QByteArrayLiteral("")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDownloaded
+        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+
+    // SaveLink should always trigger a download, also for text files.
+    QTest::newRow("save link to text file")
+        /* userAction                 */ << SaveLink
+        /* anchorHasDownloadAttribute */ << false
+        /* fileName                   */ << QByteArrayLiteral("foo.txt")
+        /* fileContents               */ << QByteArrayLiteral("bar")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("text/plain")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
+        /* fileDisposition            */ << QByteArrayLiteral("")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDownloaded
+        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+
+    // ... adding the "download" attribute should have no effect.
+    QTest::newRow("save link to text file (attribute)")
+        /* userAction                 */ << SaveLink
+        /* anchorHasDownloadAttribute */ << true
+        /* fileName                   */ << QByteArrayLiteral("foo.txt")
+        /* fileContents               */ << QByteArrayLiteral("bar")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("text/plain")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
+        /* fileDisposition            */ << QByteArrayLiteral("")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDownloaded
+        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+
+    // ... adding the "attachment" content disposition should also have no effect.
+    QTest::newRow("save link to text file (attachment)")
+        /* userAction                 */ << SaveLink
+        /* anchorHasDownloadAttribute */ << false
+        /* fileName                   */ << QByteArrayLiteral("foo.txt")
+        /* fileContents               */ << QByteArrayLiteral("bar")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("text/plain")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
+        /* fileDisposition            */ << QByteArrayLiteral("attachment")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDownloaded
+        /* downloadType               */ << QWebEngineDownloadItem::UserRequested;
+
+    // ... even adding both should have no effect.
+    QTest::newRow("save link to text file (attribute+attachment)")
+        /* userAction                 */ << SaveLink
+        /* anchorHasDownloadAttribute */ << true
+        /* fileName                   */ << QByteArrayLiteral("foo.txt")
+        /* fileContents               */ << QByteArrayLiteral("bar")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("text/plain")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
+        /* fileDisposition            */ << QByteArrayLiteral("attachment")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDownloaded
+        /* downloadType               */ << QWebEngineDownloadItem::UserRequested;
+
+    // Navigating to an empty file should show an empty page.
+    QTest::newRow("navigate to empty file")
+        /* userAction                 */ << Navigate
+        /* anchorHasDownloadAttribute */ << false
+        /* fileName                   */ << QByteArrayLiteral("foo.txt")
+        /* fileContents               */ << QByteArrayLiteral("")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("")
+        /* fileDisposition            */ << QByteArrayLiteral("")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDisplayed
+        /* downloadType               */ << /* unused */ QWebEngineDownloadItem::DownloadAttribute;
+
+    // Navigating to a text file should show the text file.
+    QTest::newRow("navigate to text file")
+        /* userAction                 */ << Navigate
+        /* anchorHasDownloadAttribute */ << false
+        /* fileName                   */ << QByteArrayLiteral("foo.txt")
+        /* fileContents               */ << QByteArrayLiteral("bar")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("text/plain")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
+        /* fileDisposition            */ << QByteArrayLiteral("")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDisplayed
+        /* downloadType               */ << /* unused */ QWebEngineDownloadItem::DownloadAttribute;
+
+    // ... unless the link has the "download" attribute: then the file should be downloaded.
+    QTest::newRow("navigate to text file (attribute)")
+        /* userAction                 */ << Navigate
+        /* anchorHasDownloadAttribute */ << true
+        /* fileName                   */ << QByteArrayLiteral("foo.txt")
+        /* fileContents               */ << QByteArrayLiteral("bar")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("text/plain")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
+        /* fileDisposition            */ << QByteArrayLiteral("")
+        /* fileHasReferer             */ << false // crbug.com/455987
+        /* fileAction                 */ << FileIsDownloaded
+        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+
+    // ... same with the content disposition header save for the download type.
+    QTest::newRow("navigate to text file (attachment)")
+        /* userAction                 */ << Navigate
+        /* anchorHasDownloadAttribute */ << false
+        /* fileName                   */ << QByteArrayLiteral("foo.txt")
+        /* fileContents               */ << QByteArrayLiteral("bar")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("text/plain")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
+        /* fileDisposition            */ << QByteArrayLiteral("attachment")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDownloaded
+        /* downloadType               */ << QWebEngineDownloadItem::Attachment;
+
+    // ... and both.
+    QTest::newRow("navigate to text file (attribute+attachment)")
+        /* userAction                 */ << Navigate
+        /* anchorHasDownloadAttribute */ << true
+        /* fileName                   */ << QByteArrayLiteral("foo.txt")
+        /* fileContents               */ << QByteArrayLiteral("bar")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("text/plain")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
+        /* fileDisposition            */ << QByteArrayLiteral("attachment")
+        /* fileHasReferer             */ << false // crbug.com/455987
+        /* fileAction                 */ << FileIsDownloaded
+        /* downloadType               */ << QWebEngineDownloadItem::Attachment;
+
+    // The file's extension has no effect.
+    QTest::newRow("navigate to supposed zip file")
+        /* userAction                 */ << Navigate
+        /* anchorHasDownloadAttribute */ << false
+        /* fileName                   */ << QByteArrayLiteral("foo.zip")
+        /* fileContents               */ << QByteArrayLiteral("bar")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("")
+        /* fileDisposition            */ << QByteArrayLiteral("")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDisplayed
+        /* downloadType               */ << /* unused */ QWebEngineDownloadItem::DownloadAttribute;
+
+    // ... the file's mime type however does.
+    QTest::newRow("navigate to supposed zip file (application/zip)")
+        /* userAction                 */ << Navigate
+        /* anchorHasDownloadAttribute */ << false
+        /* fileName                   */ << QByteArrayLiteral("foo.zip")
+        /* fileContents               */ << QByteArrayLiteral("bar")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("application/zip")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("application/zip")
+        /* fileDisposition            */ << QByteArrayLiteral("")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDownloaded
+        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+
+    // ... but we're not very picky about the exact type.
+    QTest::newRow("navigate to supposed zip file (application/octet-stream)")
+        /* userAction                 */ << Navigate
+        /* anchorHasDownloadAttribute */ << false
+        /* fileName                   */ << QByteArrayLiteral("foo.zip")
+        /* fileContents               */ << QByteArrayLiteral("bar")
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("application/octet-stream")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("application/octet-stream")
+        /* fileDisposition            */ << QByteArrayLiteral("")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDownloaded
+        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+
+    // empty zip file (consisting only of "end of central directory record")
+    QByteArray zipFile = QByteArrayLiteral("PK\x05\x06") + QByteArray(18, 0);
+
+    // The mime type is guessed automatically if not provided by the server.
+    QTest::newRow("navigate to actual zip file")
+        /* userAction                 */ << Navigate
+        /* anchorHasDownloadAttribute */ << false
+        /* fileName                   */ << QByteArrayLiteral("foo.zip")
+        /* fileContents               */ << zipFile
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("application/octet-stream")
+        /* fileDisposition            */ << QByteArrayLiteral("")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDownloaded
+        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+
+    // The mime type is not guessed automatically if provided by the server.
+    QTest::newRow("navigate to actual zip file (application/zip)")
+        /* userAction                 */ << Navigate
+        /* anchorHasDownloadAttribute */ << false
+        /* fileName                   */ << QByteArrayLiteral("foo.zip")
+        /* fileContents               */ << zipFile
+        /* fileMimeTypeDeclared       */ << QByteArrayLiteral("application/zip")
+        /* fileMimeTypeDetected       */ << QByteArrayLiteral("application/zip")
+        /* fileDisposition            */ << QByteArrayLiteral("")
+        /* fileHasReferer             */ << true
+        /* fileAction                 */ << FileIsDownloaded
+        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+}
+
+void tst_QWebEngineDownloads::downloadLink()
+{
+    QFETCH(DownloadTestUserAction, userAction);
+    QFETCH(bool, anchorHasDownloadAttribute);
+    QFETCH(QByteArray, fileName);
+    QFETCH(QByteArray, fileContents);
+    QFETCH(QByteArray, fileMimeTypeDeclared);
+    QFETCH(QByteArray, fileMimeTypeDetected);
+    QFETCH(QByteArray, fileDisposition);
+    QFETCH(bool, fileHasReferer);
+    QFETCH(DownloadTestFileAction, fileAction);
+    QFETCH(QWebEngineDownloadItem::DownloadType, downloadType);
+
+    HttpServer server;
+    QWebEngineProfile profile;
+    QWebEnginePage page(&profile);
+    QWebEngineView view;
+    view.setPage(&page);
+
+    // 1. Load an HTML page with a link
+    //
+    // The only variation being whether the <a> element has a "download"
+    // attribute or not.
+    view.load(server.url());
+    view.show();
+    auto indexRR = waitForRequest(&server);
+    QVERIFY(indexRR);
+    QCOMPARE(indexRR->requestMethod(), QByteArrayLiteral("GET"));
+    QCOMPARE(indexRR->requestPath(), QByteArrayLiteral("/"));
+    indexRR->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html"));
+    QByteArray html;
+    html += QByteArrayLiteral("<html><body><a href=\"");
+    html += fileName;
+    html += QByteArrayLiteral("\" ");
+    if (anchorHasDownloadAttribute)
+        html += QByteArrayLiteral("download");
+    html += QByteArrayLiteral(">Link</a></body></html>");
+    indexRR->setResponseBody(html);
+    indexRR->sendResponse();
+    bool loadOk = false;
+    QVERIFY(waitForSignal(&page, &QWebEnginePage::loadFinished, [&](bool ok) { loadOk = ok; }));
+    QVERIFY(loadOk);
+
+    // 1.1. Ignore favicon request
+    auto favIconRR = waitForRequest(&server);
+    QVERIFY(favIconRR);
+    QCOMPARE(favIconRR->requestMethod(), QByteArrayLiteral("GET"));
+    QCOMPARE(favIconRR->requestPath(), QByteArrayLiteral("/favicon.ico"));
+    favIconRR->setResponseStatus(404);
+    favIconRR->sendResponse();
+
+    // 2. Simulate user action
+    //
+    // - Navigate: user left-clicks on link
+    // - SaveLink: user right-clicks on link and chooses "save link as" from menu
+    QWidget *renderWidget = view.focusWidget();
+    QPoint linkPos(10, 10);
+    if (userAction == SaveLink) {
+        view.setContextMenuPolicy(Qt::CustomContextMenu);
+        auto event1 = new QContextMenuEvent(QContextMenuEvent::Mouse, linkPos);
+        auto event2 = new QMouseEvent(QEvent::MouseButtonPress, linkPos, Qt::RightButton, {}, {});
+        auto event3 = new QMouseEvent(QEvent::MouseButtonRelease, linkPos, Qt::RightButton, {}, {});
+        QCoreApplication::postEvent(renderWidget, event1);
+        QCoreApplication::postEvent(renderWidget, event2);
+        QCoreApplication::postEvent(renderWidget, event3);
+        QVERIFY(waitForSignal(&view, &QWidget::customContextMenuRequested));
+        page.triggerAction(QWebEnginePage::DownloadLinkToDisk);
+    } else
+        QTest::mouseClick(renderWidget, Qt::LeftButton, {}, linkPos);
+
+    // 3. Deliver requested file
+    //
+    // Request/response headers vary.
+    auto fileRR = waitForRequest(&server);
+    QVERIFY(fileRR);
+    QCOMPARE(fileRR->requestMethod(), QByteArrayLiteral("GET"));
+    QCOMPARE(fileRR->requestPath(), QByteArrayLiteral("/") + fileName);
+    if (fileHasReferer)
+        QCOMPARE(fileRR->requestHeader(QByteArrayLiteral("referer")), server.url().toEncoded());
+    else
+        QCOMPARE(fileRR->requestHeader(QByteArrayLiteral("referer")), QByteArray());
+    if (!fileDisposition.isEmpty())
+        fileRR->setResponseHeader(QByteArrayLiteral("content-disposition"), fileDisposition);
+    if (!fileMimeTypeDeclared.isEmpty())
+        fileRR->setResponseHeader(QByteArrayLiteral("content-type"), fileMimeTypeDeclared);
+    fileRR->setResponseBody(fileContents);
+    fileRR->sendResponse();
+
+    // 4a. File is displayed and not downloaded - end test
+    if (fileAction == FileIsDisplayed) {
+        QVERIFY(waitForSignal(&page, &QWebEnginePage::loadFinished, [&](bool ok) { loadOk = ok; }));
+        QVERIFY(loadOk);
+        return;
+    }
+
+    // 4b. File is downloaded - check QWebEngineDownloadItem attributes
+    QTemporaryDir tmpDir;
+    QVERIFY(tmpDir.isValid());
+    QByteArray slashFileName = QByteArrayLiteral("/") + fileName;
+    QString suggestedPath =
+        QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + slashFileName;
+    QString downloadPath = tmpDir.path() + slashFileName;
+    QUrl downloadUrl = server.url(slashFileName);
+    QWebEngineDownloadItem *downloadItem = nullptr;
+    QVERIFY(waitForSignal(&profile, &QWebEngineProfile::downloadRequested,
+                          [&](QWebEngineDownloadItem *item) {
+        QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested);
+        QCOMPARE(item->isFinished(), false);
+        QCOMPARE(item->totalBytes(), -1);
+        QCOMPARE(item->receivedBytes(), 0);
+        QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
+        QCOMPARE(item->type(), downloadType);
+        QCOMPARE(item->mimeType(), QString(fileMimeTypeDetected));
+        QCOMPARE(item->path(), suggestedPath);
+        QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat);
+        QCOMPARE(item->url(), downloadUrl);
+        item->setPath(downloadPath);
+        item->accept();
+        downloadItem = item;
+    }));
+    QVERIFY(downloadItem);
+    bool finishOk = false;
+    QVERIFY(waitForSignal(downloadItem, &QWebEngineDownloadItem::finished, [&]() {
+        auto item = downloadItem;
+        QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted);
+        QCOMPARE(item->isFinished(), true);
+        QCOMPARE(item->totalBytes(), fileContents.size());
+        QCOMPARE(item->receivedBytes(), fileContents.size());
+        QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
+        QCOMPARE(item->type(), downloadType);
+        QCOMPARE(item->mimeType(), QString(fileMimeTypeDetected));
+        QCOMPARE(item->path(), downloadPath);
+        QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat);
+        QCOMPARE(item->url(), downloadUrl);
+        finishOk = true;
+    }));
+    QVERIFY(finishOk);
+
+    // 5. Check actual file contents
+    QFile file(downloadPath);
+    QVERIFY(file.open(QIODevice::ReadOnly));
+    QCOMPARE(file.readAll(), fileContents);
+}
+
+void tst_QWebEngineDownloads::downloadTwoLinks()
+{
+    HttpServer server;
+    QWebEngineProfile profile;
+    QWebEnginePage page(&profile);
+    QWebEngineView view;
+    view.setPage(&page);
+
+    view.load(server.url());
+    view.show();
+    auto indexRR = waitForRequest(&server);
+    QVERIFY(indexRR);
+    QCOMPARE(indexRR->requestMethod(), QByteArrayLiteral("GET"));
+    QCOMPARE(indexRR->requestPath(), QByteArrayLiteral("/"));
+    indexRR->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html"));
+    indexRR->setResponseBody(QByteArrayLiteral("<html><body><a href=\"file1\" download>Link1</a><br/><a href=\"file2\">Link2</a></body></html>"));
+    indexRR->sendResponse();
+    bool loadOk = false;
+    QVERIFY(waitForSignal(&page, &QWebEnginePage::loadFinished, [&](bool ok){ loadOk = ok; }));
+    QVERIFY(loadOk);
+
+    auto favIconRR = waitForRequest(&server);
+    QVERIFY(favIconRR);
+    QCOMPARE(favIconRR->requestMethod(), QByteArrayLiteral("GET"));
+    QCOMPARE(favIconRR->requestPath(), QByteArrayLiteral("/favicon.ico"));
+    favIconRR->setResponseStatus(404);
+    favIconRR->sendResponse();
+
+    QWidget *renderWidget = view.focusWidget();
+    QTest::mouseClick(renderWidget, Qt::LeftButton, {}, QPoint(10, 10));
+    QTest::mouseClick(renderWidget, Qt::LeftButton, {}, QPoint(10, 30));
+
+    auto file1RR = waitForRequest(&server);
+    QVERIFY(file1RR);
+    QCOMPARE(file1RR->requestMethod(), QByteArrayLiteral("GET"));
+    QCOMPARE(file1RR->requestPath(), QByteArrayLiteral("/file1"));
+    auto file2RR = waitForRequest(&server);
+    QVERIFY(file2RR);
+    QCOMPARE(file2RR->requestMethod(), QByteArrayLiteral("GET"));
+    QCOMPARE(file2RR->requestPath(), QByteArrayLiteral("/file2"));
+
+    file1RR->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/plain"));
+    file1RR->setResponseBody(QByteArrayLiteral("file1"));
+    file1RR->sendResponse();
+    file2RR->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/plain"));
+    file2RR->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment"));
+    file2RR->setResponseBody(QByteArrayLiteral("file2"));
+    file2RR->sendResponse();
+
+    QTemporaryDir tmpDir;
+    QVERIFY(tmpDir.isValid());
+    QString standardDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+    QWebEngineDownloadItem *item1 = nullptr;
+    QVERIFY(waitForSignal(&profile, &QWebEngineProfile::downloadRequested,
+                          [&](QWebEngineDownloadItem *item) {
+        QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested);
+        QCOMPARE(item->isFinished(), false);
+        QCOMPARE(item->totalBytes(), -1);
+        QCOMPARE(item->receivedBytes(), 0);
+        QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
+        QCOMPARE(item->type(), QWebEngineDownloadItem::DownloadAttribute);
+        QCOMPARE(item->mimeType(), QStringLiteral("text/plain"));
+        QCOMPARE(item->path(), standardDir + QByteArrayLiteral("/file1"));
+        QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat);
+        QCOMPARE(item->url(), server.url(QByteArrayLiteral("/file1")));
+        item->setPath(tmpDir.path() + QByteArrayLiteral("/file1"));
+        item->accept();
+        item1 = item;
+    }));
+    QVERIFY(item1);
+
+    QWebEngineDownloadItem *item2 = nullptr;
+    QVERIFY(waitForSignal(&profile, &QWebEngineProfile::downloadRequested,
+                          [&](QWebEngineDownloadItem *item) {
+        QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested);
+        QCOMPARE(item->isFinished(), false);
+        QCOMPARE(item->totalBytes(), -1);
+        QCOMPARE(item->receivedBytes(), 0);
+        QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
+        QCOMPARE(item->type(), QWebEngineDownloadItem::DownloadAttribute);
+        QCOMPARE(item->mimeType(), QStringLiteral("text/plain"));
+        QCOMPARE(item->path(), standardDir + QByteArrayLiteral("/file2"));
+        QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat);
+        QCOMPARE(item->url(), server.url(QByteArrayLiteral("/file2")));
+        item->setPath(tmpDir.path() + QByteArrayLiteral("/file2"));
+        item->accept();
+        item2 = item;
+    }));
+    QVERIFY(item2);
+}
+
+void tst_QWebEngineDownloads::downloadPage_data()
+{
+    QTest::addColumn<QWebEngineDownloadItem::SavePageFormat>("savePageFormat");
+    QTest::newRow("SingleHtmlSaveFormat") << QWebEngineDownloadItem::SingleHtmlSaveFormat;
+    QTest::newRow("CompleteHtmlSaveFormat") << QWebEngineDownloadItem::CompleteHtmlSaveFormat;
+    QTest::newRow("MimeHtmlSaveFormat") << QWebEngineDownloadItem::MimeHtmlSaveFormat;
+}
+
+void tst_QWebEngineDownloads::downloadPage()
+{
+    QFETCH(QWebEngineDownloadItem::SavePageFormat, savePageFormat);
+
+    HttpServer server;
+    QWebEngineProfile profile;
+    QWebEnginePage page(&profile);
+    QWebEngineView view;
+    view.setPage(&page);
+
+    view.load(server.url());
+    view.show();
+    auto indexRR = waitForRequest(&server);
+    QVERIFY(indexRR);
+    QCOMPARE(indexRR->requestMethod(), QByteArrayLiteral("GET"));
+    QCOMPARE(indexRR->requestPath(), QByteArrayLiteral("/"));
+    indexRR->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html"));
+    indexRR->setResponseBody(QByteArrayLiteral("<html><body>Hello</body></html>"));
+    indexRR->sendResponse();
+    bool loadOk = false;
+    QVERIFY(waitForSignal(&page, &QWebEnginePage::loadFinished, [&](bool ok){ loadOk = ok; }));
+    QVERIFY(loadOk);
+
+    auto favIconRR = waitForRequest(&server);
+    QVERIFY(favIconRR);
+    QCOMPARE(favIconRR->requestMethod(), QByteArrayLiteral("GET"));
+    QCOMPARE(favIconRR->requestPath(), QByteArrayLiteral("/favicon.ico"));
+    favIconRR->setResponseStatus(404);
+    favIconRR->sendResponse();
+
+    QTemporaryDir tmpDir;
+    QVERIFY(tmpDir.isValid());
+    QString downloadPath = tmpDir.path() + QStringLiteral("/test.html");
+    page.save(downloadPath, savePageFormat);
+
+    QWebEngineDownloadItem *downloadItem = nullptr;
+    QUrl downloadUrl = server.url("/");
+    QVERIFY(waitForSignal(&profile, &QWebEngineProfile::downloadRequested,
+                        [&](QWebEngineDownloadItem *item) {
+        QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadInProgress);
+        QCOMPARE(item->isFinished(), false);
+        QCOMPARE(item->totalBytes(), -1);
+        QCOMPARE(item->receivedBytes(), 0);
+        QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
+        QCOMPARE(item->type(), QWebEngineDownloadItem::SavePage);
+        // FIXME why is mimeType always the same?
+        QCOMPARE(item->mimeType(), QStringLiteral("application/x-mimearchive"));
+        QCOMPARE(item->path(), downloadPath);
+        QCOMPARE(item->savePageFormat(), savePageFormat);
+        QCOMPARE(item->url(), downloadUrl);
+        // no need to call item->accept()
+        downloadItem = item;
+    }));
+    QVERIFY(downloadItem);
+    bool finishOk = false;
+    QVERIFY(waitForSignal(downloadItem, &QWebEngineDownloadItem::finished, [&]() {
+        auto item = downloadItem;
+        QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted);
+        QCOMPARE(item->isFinished(), true);
+        QCOMPARE(item->totalBytes(), item->receivedBytes());
+        QVERIFY(item->receivedBytes() > 0);
+        QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
+        QCOMPARE(item->type(), QWebEngineDownloadItem::SavePage);
+        QCOMPARE(item->mimeType(), QStringLiteral("application/x-mimearchive"));
+        QCOMPARE(item->path(), downloadPath);
+        QCOMPARE(item->savePageFormat(), savePageFormat);
+        QCOMPARE(item->url(), downloadUrl);
+        finishOk = true;
+    }));
+    QVERIFY(finishOk);
+
+    QFile file(downloadPath);
+    QVERIFY(file.exists());
+}
+
+QTEST_MAIN(tst_QWebEngineDownloads)
+#include "tst_qwebenginedownloads.moc"
diff --git a/tests/auto/widgets/widgets.pro b/tests/auto/widgets/widgets.pro
index 90352310e0a2fb8443f3853d04f36faf53858144..441eea0fae8aff04bfc857bf71e5369d55908b0f 100644
--- a/tests/auto/widgets/widgets.pro
+++ b/tests/auto/widgets/widgets.pro
@@ -3,6 +3,7 @@ TEMPLATE = subdirs
 SUBDIRS += \
     qwebengineaccessibility \
     qwebenginedefaultsurfaceformat \
+    qwebenginedownloads \
     qwebenginefaviconmanager \
     qwebenginepage \
     qwebenginehistory \
@@ -24,4 +25,5 @@ contains(WEBENGINE_CONFIG, use_spellchecker):!cross_compile {
 # QTBUG-60268
 boot2qt: SUBDIRS -= qwebengineaccessibility qwebenginedefaultsurfaceformat \
                     qwebenginefaviconmanager qwebenginepage qwebenginehistory \
-                    qwebengineprofile qwebenginescript qwebengineview
+                    qwebengineprofile qwebenginescript qwebengineview \
+                    qwebenginedownloads