diff options
Diffstat (limited to 'src/core/api')
-rw-r--r-- | src/core/api/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/core/api/qwebenginenewwindowrequest.cpp | 206 | ||||
-rw-r--r-- | src/core/api/qwebenginenewwindowrequest.h | 99 | ||||
-rw-r--r-- | src/core/api/qwebenginepage.cpp | 195 | ||||
-rw-r--r-- | src/core/api/qwebenginepage.h | 4 | ||||
-rw-r--r-- | src/core/api/qwebenginepage_p.h | 2 |
6 files changed, 465 insertions, 42 deletions
diff --git a/src/core/api/CMakeLists.txt b/src/core/api/CMakeLists.txt index 5df3ba7b9..bf2008ebd 100644 --- a/src/core/api/CMakeLists.txt +++ b/src/core/api/CMakeLists.txt @@ -26,6 +26,7 @@ qt_internal_add_module(WebEngineCore qwebenginehttprequest.cpp qwebenginehttprequest.h qwebengineloadrequest.cpp qwebengineloadrequest.h qwebenginemessagepumpscheduler.cpp qwebenginemessagepumpscheduler_p.h + qwebenginenewwindowrequest.cpp qwebenginenewwindowrequest.h qwebenginenotification.cpp qwebenginenotification.h qwebenginepage.cpp qwebenginepage.h qwebenginepage_p.h qwebengineprofile.cpp qwebengineprofile.h qwebengineprofile_p.h diff --git a/src/core/api/qwebenginenewwindowrequest.cpp b/src/core/api/qwebenginenewwindowrequest.cpp new file mode 100644 index 000000000..7ef7601d7 --- /dev/null +++ b/src/core/api/qwebenginenewwindowrequest.cpp @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwebenginenewwindowrequest.h" + +#include "web_contents_adapter.h" + +QT_BEGIN_NAMESPACE + +struct QWebEngineNewWindowRequestPrivate +{ + QWebEngineNewWindowRequest::DestinationType destination; + QRect requestedGeometry; + QUrl requestedUrl; + QSharedPointer<QtWebEngineCore::WebContentsAdapter> adapter; + bool isUserInitiated; + bool isRequestHandled = false; +}; + +/*! + \class QWebEngineNewWindowRequest + \brief A utility type for the QWebEnginePage::newPageRequested signal. + \since 6.2 + + \inmodule QtWebEngineCore + + Contains information about a request to load a page in a separate web engine view. + + \sa QWebEnginePage::newPageRequested +*/ + +/*! + \qmltype WebEngineNewViewRequest + \instantiates QWebEngineNewWindowRequest + \inqmlmodule QtWebEngine + \since QtWebEngine 1.1 + + \brief A utility type for the WebEngineView::newViewRequested signal. + + Contains information about a request to load a page in a separate web engine view. + + \sa WebEngineView::newViewRequested +*/ + +/*! + \enum QWebEngineNewWindowRequest::DestinationType + + This enum describes the type of window requested: + + \value InNewWindow + In a separate window. + \value InNewTab + In a tab of the same window. + \value InNewDialog + In a window without a tab bar, toolbar, or URL bar. + \value InNewBackgroundTab + In a tab of the same window, without hiding the currently visible web engine view. +*/ + +/*! + \qmlproperty enumeration WebEngineNewViewRequest::DestinationType + + Describes how to open a new view: + + \value WebEngineNewViewRequest.InNewWindow + In a separate window. + \value WebEngineNewViewRequest.InNewTab + In a tab of the same window. + \value WebEngineNewViewRequest.InNewDialog + In a window without a tab bar, toolbar, or URL bar. + \value WebEngineNewViewRequest.InNewBackgroundTab + In a tab of the same window, without hiding the currently visible web engine view. +*/ + +QWebEngineNewWindowRequest::QWebEngineNewWindowRequest(QWebEngineNewWindowRequest::DestinationType destination, + const QRect &geometry, + const QUrl &url, + bool userInitiated, + QSharedPointer<QtWebEngineCore::WebContentsAdapter> adapter, + QObject *parent) + : QObject(parent) + , d_ptr(new QWebEngineNewWindowRequestPrivate{destination, geometry, url, adapter, userInitiated}) +{ +} + +QWebEngineNewWindowRequest::~QWebEngineNewWindowRequest() +{ +} + +/*! + \property QWebEngineNewWindowRequest::destination + \brief The type of window that is requested. +*/ +/*! + \qmlproperty WebEngineNewViewRequest::DestinationType WebEngineNewViewRequest::destination + \brief The type of window that is requested. +*/ +QWebEngineNewWindowRequest::DestinationType QWebEngineNewWindowRequest::destination() const +{ + return d_ptr->destination; +} + +/*! + \property QWebEngineNewWindowRequest::requestedUrl + \brief The URL that is requested for the new page. +*/ +/*! + \qmlproperty QUrl WebEngineNewViewRequest::requestedUrl + \brief The URL that is requested for the new page. + \since QtWebEngine 1.5 + */ +QUrl QWebEngineNewWindowRequest::requestedUrl() const +{ + return d_ptr->requestedUrl; +} + +/*! + \property QWebEngineNewWindowRequest::requestedGeometry + \brief The size that is requested for the new page. +*/ +/*! + \qmlproperty QRect WebEngineNewViewRequest::requestedGeometry + \brief The size that is requested for the new page. + \since QtWebEngine 2.0 + */ +QRect QWebEngineNewWindowRequest::requestedGeometry() const +{ + return d_ptr->requestedGeometry; +} + +/*! + \property QWebEngineNewWindowRequest::userInitiated + Whether this page request was directly triggered as the result of a keyboard or mouse event. + + You can use this property to block automatic \e popups. +*/ +/*! + \qmlproperty bool WebEngineNewViewRequest::userInitiated + Whether this window request was directly triggered as the result of a keyboard or mouse event. + + You can use this property to block automatic \e popups. + */ +bool QWebEngineNewWindowRequest::isUserInitiated() const +{ + return d_ptr->isUserInitiated; +} + +/*! \internal +*/ +QSharedPointer<QtWebEngineCore::WebContentsAdapter> QWebEngineNewWindowRequest::adapter() +{ + return d_ptr->adapter; +} + +/*! \internal +*/ +bool QWebEngineNewWindowRequest::isHandled() const +{ + return d_ptr->isRequestHandled; +} + +/*! \internal +*/ +void QWebEngineNewWindowRequest::setHandled() +{ + d_ptr->isRequestHandled = true; + d_ptr->adapter.reset(); +} + +QT_END_NAMESPACE diff --git a/src/core/api/qwebenginenewwindowrequest.h b/src/core/api/qwebenginenewwindowrequest.h new file mode 100644 index 000000000..b419a0202 --- /dev/null +++ b/src/core/api/qwebenginenewwindowrequest.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWEBENGINENEWWINDOWREQUEST_P_H +#define QWEBENGINENEWWINDOWREQUEST_P_H + +#include <qtwebenginecoreglobal.h> +#include <QtCore/QObject> +#include <QtCore/QRect> +#include <QtCore/QScopedPointer> +#include <QtCore/QSharedPointer> +#include <QtCore/QUrl> + +namespace QtWebEngineCore { +class WebContentsAdapter; +} + +QT_BEGIN_NAMESPACE + +struct QWebEngineNewWindowRequestPrivate; + +class Q_WEBENGINECORE_EXPORT QWebEngineNewWindowRequest : public QObject +{ + Q_OBJECT + Q_PROPERTY(DestinationType destination READ destination CONSTANT FINAL) + Q_PROPERTY(QUrl requestedUrl READ requestedUrl CONSTANT FINAL) + Q_PROPERTY(QRect requestedGeometry READ requestedGeometry CONSTANT FINAL) + Q_PROPERTY(bool userInitiated READ isUserInitiated CONSTANT FINAL) +public: + ~QWebEngineNewWindowRequest(); + + enum DestinationType { + InNewWindow, + InNewTab, + InNewDialog, + InNewBackgroundTab + }; + Q_ENUM(DestinationType) + + DestinationType destination() const; + QUrl requestedUrl() const; + QRect requestedGeometry() const; + bool isUserInitiated() const; + +private: + QWebEngineNewWindowRequest(DestinationType, const QRect &, const QUrl &, bool, + QSharedPointer<QtWebEngineCore::WebContentsAdapter>, + QObject * = nullptr); + + QSharedPointer<QtWebEngineCore::WebContentsAdapter> adapter(); + bool isHandled() const; + void setHandled(); + + QScopedPointer<QWebEngineNewWindowRequestPrivate> d_ptr; + friend class QWebEnginePage; + friend class QWebEnginePagePrivate; + friend class QQuickWebEngineView; + friend class QQuickWebEngineViewPrivate; +}; + +QT_END_NAMESPACE + +#endif // QWEBENGINENEWWINDOWREQUEST_P_H diff --git a/src/core/api/qwebenginepage.cpp b/src/core/api/qwebenginepage.cpp index e96763f5d..548457349 100644 --- a/src/core/api/qwebenginepage.cpp +++ b/src/core/api/qwebenginepage.cpp @@ -56,6 +56,7 @@ #include "qwebenginefullscreenrequest.h" #include "qwebenginehistory.h" #include "qwebenginehistory_p.h" +#include "qwebenginenewwindowrequest.h" #include "qwebenginenotification.h" #include "qwebengineprofile.h" #include "qwebengineprofile_p.h" @@ -150,6 +151,22 @@ static QWebEnginePage::WebWindowType toWindowType(WebContentsAdapterClient::Wind } } +static QWebEngineNewWindowRequest::DestinationType toDestinationType(WebContentsAdapterClient::WindowOpenDisposition disposition) +{ + switch (disposition) { + case WebContentsAdapterClient::NewForegroundTabDisposition: + return QWebEngineNewWindowRequest::InNewTab; + case WebContentsAdapterClient::NewBackgroundTabDisposition: + return QWebEngineNewWindowRequest::InNewBackgroundTab; + case WebContentsAdapterClient::NewPopupDisposition: + return QWebEngineNewWindowRequest::InNewDialog; + case WebContentsAdapterClient::NewWindowDisposition: + return QWebEngineNewWindowRequest::InNewWindow; + default: + Q_UNREACHABLE(); + } +} + QWebEnginePagePrivate::QWebEnginePagePrivate(QWebEngineProfile *_profile) : adapter(QSharedPointer<WebContentsAdapter>::create()) , history(new QWebEngineHistory(new QWebEngineHistoryPrivate(this))) @@ -347,40 +364,93 @@ QWebEnginePagePrivate::adoptNewWindow(QSharedPointer<WebContentsAdapter> newWebC const QRect &initialGeometry, const QUrl &targetUrl) { Q_Q(QWebEnginePage); - Q_UNUSED(userGesture); - Q_UNUSED(targetUrl); + Q_ASSERT(newWebContents); + QWebEnginePage *newPage = q->createWindow(toWindowType(disposition)); + if (newPage) { + if (!newWebContents->webContents()) + return newPage->d_func()->adapter; // Reuse existing adapter + + // Mark the new page as being in the process of being adopted, so that a second mouse move event + // sent by newWebContents->initialize() gets filtered in RenderWidgetHostViewQt::forwardEvent. + // The first mouse move event is being sent by q->createWindow(). This is necessary because + // Chromium does not get a mouse move acknowledgment message between the two events, and + // InputRouterImpl::ProcessMouseAck is not executed, thus all subsequent mouse move events + // get coalesced together, and don't get processed at all. + // The mouse move events are actually sent as a result of show() being called on + // RenderWidgetHostViewQtDelegateWidget, both when creating the window and when initialize is + // called. + newPage->d_func()->m_isBeingAdopted = true; + + // Overwrite the new page's WebContents with ours. + newPage->d_func()->adapter = newWebContents; + newWebContents->setClient(newPage->d_func()); + + if (!initialGeometry.isEmpty()) + emit newPage->geometryChangeRequested(initialGeometry); + + return newWebContents; + } + + QWebEngineNewWindowRequest request(toDestinationType(disposition), initialGeometry, + targetUrl, userGesture, newWebContents); + + Q_EMIT q->newWindowRequested(request); + + if (request.isHandled()) + return newWebContents; + return nullptr; +} +void QWebEnginePagePrivate::createNewWindow(WindowOpenDisposition disposition, bool userGesture, const QUrl &targetUrl) +{ + Q_Q(QWebEnginePage); 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 (newPage) { + newPage->setUrl(targetUrl); + return; + } - if (!newWebContents->webContents()) - return newPage->d_func()->adapter; // Reuse existing adapter + QWebEngineNewWindowRequest request(toDestinationType(disposition), QRect(), + targetUrl, userGesture, nullptr); - // Mark the new page as being in the process of being adopted, so that a second mouse move event - // sent by newWebContents->initialize() gets filtered in RenderWidgetHostViewQt::forwardEvent. - // The first mouse move event is being sent by q->createWindow(). This is necessary because - // Chromium does not get a mouse move acknowledgment message between the two events, and - // InputRouterImpl::ProcessMouseAck is not executed, thus all subsequent mouse move events - // get coalesced together, and don't get processed at all. - // The mouse move events are actually sent as a result of show() being called on - // RenderWidgetHostViewQtDelegateWidget, both when creating the window and when initialize is - // called. - newPage->d_func()->m_isBeingAdopted = true; + Q_EMIT q->newWindowRequested(request); +} - // Overwrite the new page's WebContents with ours. - newPage->d_func()->adapter = newWebContents; - newWebContents->setClient(newPage->d_func()); +class WebContentsAdapterOwner : public QObject +{ +public: + typedef QSharedPointer<QtWebEngineCore::WebContentsAdapter> AdapterPtr; + WebContentsAdapterOwner(const AdapterPtr &ptr) + : adapter(ptr) + {} - if (!initialGeometry.isEmpty()) - emit newPage->geometryChangeRequested(initialGeometry); +private: + AdapterPtr adapter; +}; - return newWebContents; +void QWebEnginePagePrivate::adoptWebContents(WebContentsAdapter *webContents) +{ + if (!webContents) { + qWarning("Trying to open an empty request, it was either already used or was invalidated." + "\nYou must complete the request synchronously within the newPageRequested signal handler." + " If a view hasn't been adopted before returning, the request will be invalidated."); + return; + } + + if (webContents->profileAdapter() && profileAdapter() != webContents->profileAdapter()) { + qWarning("Can not adopt content from a different WebEngineProfile."); + return; + } + + m_isBeingAdopted = true; + + // This throws away the WebContentsAdapter that has been used until now. + // All its states, particularly the loading URL, are replaced by the adopted WebContentsAdapter. + WebContentsAdapterOwner *adapterOwner = new WebContentsAdapterOwner(adapter->sharedFromThis()); + adapterOwner->deleteLater(); + + adapter = webContents->sharedFromThis(); + adapter->setClient(this); } bool QWebEnginePagePrivate::isBeingAdopted() @@ -1299,25 +1369,19 @@ void QWebEnginePage::triggerAction(WebAction action, bool) setUrl(d->view->lastContextMenuRequest()->filteredLinkUrl()); break; case OpenLinkInNewWindow: - if (d->view && d->view->lastContextMenuRequest()->filteredLinkUrl().isValid()) { - QWebEnginePage *newPage = createWindow(WebBrowserWindow); - if (newPage) - newPage->setUrl(d->view->lastContextMenuRequest()->filteredLinkUrl()); - } + if (d->view && d->view->lastContextMenuRequest()->filteredLinkUrl().isValid()) + d->createNewWindow(WebContentsAdapterClient::NewWindowDisposition, true, + d->view->lastContextMenuRequest()->filteredLinkUrl()); break; case OpenLinkInNewTab: - if (d->view && d->view->lastContextMenuRequest()->filteredLinkUrl().isValid()) { - QWebEnginePage *newPage = createWindow(WebBrowserTab); - if (newPage) - newPage->setUrl(d->view->lastContextMenuRequest()->filteredLinkUrl()); - } + if (d->view && d->view->lastContextMenuRequest()->filteredLinkUrl().isValid()) + d->createNewWindow(WebContentsAdapterClient::NewForegroundTabDisposition, true, + d->view->lastContextMenuRequest()->filteredLinkUrl()); break; case OpenLinkInNewBackgroundTab: - if (d->view && d->view->lastContextMenuRequest()->filteredLinkUrl().isValid()) { - QWebEnginePage *newPage = createWindow(WebBrowserBackgroundTab); - if (newPage) - newPage->setUrl(d->view->lastContextMenuRequest()->filteredLinkUrl()); - } + if (d->view && d->view->lastContextMenuRequest()->filteredLinkUrl().isValid()) + d->createNewWindow(WebContentsAdapterClient::NewBackgroundTabDisposition, true, + d->view->lastContextMenuRequest()->filteredLinkUrl()); break; case CopyLinkToClipboard: if (d->view && !d->view->lastContextMenuRequest()->linkUrl().isEmpty()) { @@ -2313,6 +2377,53 @@ void QWebEnginePage::print(QPrinter *printer, const QWebEngineCallback<bool> &re /*! + \fn void QWebEnginePage::newWindowRequested(WebEngineNewViewRequest &request) + \since 6.2 + + This signal is emitted when \a request is issued to load a page in a separate + web engine window. This can either be because the current page requested it explicitly + through a JavaScript call to \c window.open, or because the user clicked on a link + while holding Shift, Ctrl, or a built-in combination that triggers the page to open + in a new window. + + The signal is handled by calling acceptAsNewWindow() on the destination page. + If this signal is not handled, the requested load will fail. + + \note This signal is not emitted if \l createWindow() handled the request first. + + \sa createWindow() +*/ + +/*! + Handles the newWindowRequested() signal by opening the \a request in this page. + \since 6.2 + \sa newWindowRequested +*/ +void QWebEnginePage::acceptAsNewWindow(QWebEngineNewWindowRequest &request) +{ + Q_D(QWebEnginePage); + auto adapter = request.adapter(); + QUrl url = request.requestedUrl(); + if ((!adapter && !url.isValid()) || request.isHandled()) { + qWarning("Trying to open an empty request, it was either already used or was invalidated." + "\nYou must complete the request synchronously within the newWindowRequested signal handler." + " If a view hasn't been adopted before returning, the request will be invalidated."); + return; + } + + if (adapter) + d->adoptWebContents(adapter.data()); + else + setUrl(url); + + QRect geometry = request.requestedGeometry(); + if (!geometry.isEmpty()) + emit geometryChangeRequested(geometry); + + request.setHandled(); +} + +/*! \enum QWebEnginePage::LifecycleState \since 5.14 diff --git a/src/core/api/qwebenginepage.h b/src/core/api/qwebenginepage.h index d1838e51c..c0c970207 100644 --- a/src/core/api/qwebenginepage.h +++ b/src/core/api/qwebenginepage.h @@ -64,6 +64,7 @@ class QWebEngineClientCertificateSelection; class QWebEngineFindTextResult; class QWebEngineFullScreenRequest; class QWebEngineHistory; +class QWebEngineNewWindowRequest; class QWebEnginePage; class QWebEnginePagePrivate; class QWebEngineProfile; @@ -320,6 +321,8 @@ public: bool isVisible() const; void setVisible(bool visible); + void acceptAsNewWindow(QWebEngineNewWindowRequest &request); + static QWebEnginePage* fromDownloadRequest(QWebEngineDownloadRequest * request); Q_SIGNALS: @@ -343,6 +346,7 @@ Q_SIGNALS: void renderProcessTerminated(RenderProcessTerminationStatus terminationStatus, int exitCode); void certificateError(const QWebEngineCertificateError &certificateError); + void newWindowRequested(QWebEngineNewWindowRequest &request); // Ex-QWebFrame signals void titleChanged(const QString &title); diff --git a/src/core/api/qwebenginepage_p.h b/src/core/api/qwebenginepage_p.h index 081287889..d2c246397 100644 --- a/src/core/api/qwebenginepage_p.h +++ b/src/core/api/qwebenginepage_p.h @@ -193,6 +193,8 @@ public: void updateAction(QWebEnginePage::WebAction) const; void _q_webActionTriggered(bool checked); + void createNewWindow(WindowOpenDisposition disposition, bool userGesture, const QUrl &targetUrl); + void adoptWebContents(QtWebEngineCore::WebContentsAdapter *webContents); QtWebEngineCore::WebContentsAdapter *webContents() { return adapter.data(); } void recreateFromSerializedHistory(QDataStream &input); |