diff --git a/examples/webengine/quicknanobrowser/BrowserWindow.qml b/examples/webengine/quicknanobrowser/BrowserWindow.qml index 4d7513fae98c612056bc96eae244f7a4f33460c7..123a7cc8d712e2a76d67cf4ea5f34b7b75283c57 100644 --- a/examples/webengine/quicknanobrowser/BrowserWindow.qml +++ b/examples/webengine/quicknanobrowser/BrowserWindow.qml @@ -118,10 +118,7 @@ ApplicationWindow { Action { shortcut: StandardKey.Close onTriggered: { - if (tabs.count == 1) - browserWindow.close() - else - tabs.removeTab(tabs.currentIndex) + currentWebView.triggerWebAction(WebEngineView.RequestClose); } } Action { @@ -415,6 +412,13 @@ ApplicationWindow { reloadTimer.running = true } + onWindowCloseRequested: { + if (tabs.count == 1) + browserWindow.close() + else + tabs.removeTab(tabs.currentIndex) + } + Timer { id: reloadTimer interval: 0 diff --git a/examples/webenginewidgets/demobrowser/tabwidget.cpp b/examples/webenginewidgets/demobrowser/tabwidget.cpp index 9210e3147b69fc75805d59bb86662330eef53e38..9e08426f1f4c5b4d7e06c4cf4839c8d0bdcafa60 100644 --- a/examples/webenginewidgets/demobrowser/tabwidget.cpp +++ b/examples/webenginewidgets/demobrowser/tabwidget.cpp @@ -223,7 +223,7 @@ TabWidget::TabWidget(QWidget *parent) setElideMode(Qt::ElideRight); connect(m_tabBar, SIGNAL(newTab()), this, SLOT(newTab())); - connect(m_tabBar, SIGNAL(closeTab(int)), this, SLOT(closeTab(int))); + connect(m_tabBar, SIGNAL(closeTab(int)), this, SLOT(requestCloseTab(int))); connect(m_tabBar, SIGNAL(cloneTab(int)), this, SLOT(cloneTab(int))); connect(m_tabBar, SIGNAL(closeOtherTabs(int)), this, SLOT(closeOtherTabs(int))); connect(m_tabBar, SIGNAL(reloadTab(int)), this, SLOT(reloadTab(int))); @@ -241,7 +241,7 @@ TabWidget::TabWidget(QWidget *parent) m_closeTabAction = new QAction(QIcon(QLatin1String(":closetab.png")), tr("&Close Tab"), this); m_closeTabAction->setShortcuts(QKeySequence::Close); m_closeTabAction->setIconVisibleInMenu(false); - connect(m_closeTabAction, SIGNAL(triggered()), this, SLOT(closeTab())); + connect(m_closeTabAction, SIGNAL(triggered()), this, SLOT(requestCloseTab())); m_nextTabAction = new QAction(tr("Show Next Tab"), this); QList<QKeySequence> shortcuts; @@ -552,12 +552,8 @@ void TabWidget::windowCloseRequested() WebPage *webPage = qobject_cast<WebPage*>(sender()); WebView *webView = qobject_cast<WebView*>(webPage->view()); int index = webViewIndex(webView); - if (index >= 0) { - if (count() == 1) - webView->webPage()->mainWindow()->close(); - else - closeTab(index); - } + if (index >= 0) + closeTab(index); } void TabWidget::closeOtherTabs(int index) @@ -582,12 +578,22 @@ void TabWidget::cloneTab(int index) } // When index is -1 index chooses the current tab -void TabWidget::closeTab(int index) +void TabWidget::requestCloseTab(int index) { if (index < 0) index = currentIndex(); if (index < 0 || index >= count()) return; + WebView *tab = webView(index); + if (!tab) + return; + tab->page()->triggerAction(QWebEnginePage::RequestClose); +} + +void TabWidget::closeTab(int index) +{ + if (index < 0 || index >= count()) + return; bool hasFocus = false; if (WebView *tab = webView(index)) { diff --git a/examples/webenginewidgets/demobrowser/tabwidget.h b/examples/webenginewidgets/demobrowser/tabwidget.h index f6c4edba264540a4fc67ec0bbcc2f01bc96ea820..0f2a20c34d5463370cd846b3e92ff4d465ccbae6 100644 --- a/examples/webenginewidgets/demobrowser/tabwidget.h +++ b/examples/webenginewidgets/demobrowser/tabwidget.h @@ -196,7 +196,8 @@ public slots: void loadUrlInCurrentTab(const QUrl &url); WebView *newTab(bool makeCurrent = true); void cloneTab(int index = -1); - void closeTab(int index = -1); + void requestCloseTab(int index = -1); + void closeTab(int index); void closeOtherTabs(int index); void reloadTab(int index = -1); void reloadAllTabs(); diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp index 2b2512fc3b51dab7943f5f723b18fca4cafa1af6..92c831f94516b7b39697c1b32eb07e8fdbef6cfb 100644 --- a/src/core/web_contents_adapter.cpp +++ b/src/core/web_contents_adapter.cpp @@ -576,6 +576,12 @@ void WebContentsAdapter::selectAll() d->webContents->SelectAll(); } +void WebContentsAdapter::requestClose() +{ + Q_D(WebContentsAdapter); + d->webContents->DispatchBeforeUnload(false); +} + void WebContentsAdapter::navigateToIndex(int offset) { Q_D(WebContentsAdapter); diff --git a/src/core/web_contents_adapter.h b/src/core/web_contents_adapter.h index ecb084e56efeddda0abeff66f622aca00759a630..54bec9adf21fef7281bebb113d7cfee1768f92d8 100644 --- a/src/core/web_contents_adapter.h +++ b/src/core/web_contents_adapter.h @@ -128,6 +128,7 @@ public: void inspectElementAt(const QPoint &location); bool hasInspector() const; void exitFullScreen(); + void requestClose(); void wasShown(); void wasHidden(); diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h index 2b9b6222987fe46422d63e74ab8c63cf0aca2db1..6b203fc904fc2f8b12318e5793f4e3e4e40b415a 100644 --- a/src/core/web_contents_adapter_client.h +++ b/src/core/web_contents_adapter_client.h @@ -208,6 +208,7 @@ public: virtual void unhandledKeyEvent(QKeyEvent *event) = 0; virtual void adoptNewWindow(WebContentsAdapter *newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect & initialGeometry) = 0; virtual void close() = 0; + virtual void windowCloseRejected() = 0; virtual bool contextMenuRequested(const WebEngineContextMenuData&) = 0; virtual void navigationRequested(int navigationType, const QUrl &url, int &navigationRequestAction, bool isMainFrame) = 0; virtual void requestFullScreen(bool) = 0; diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp index 1c37f62dfab65638021643729ddc6f61dccc80ff..497910a9c73efb96e27e7fe08f638f00139a837b 100644 --- a/src/core/web_contents_delegate_qt.cpp +++ b/src/core/web_contents_delegate_qt.cpp @@ -390,4 +390,13 @@ void WebContentsDelegateQt::MoveValidationMessage(content::WebContents *web_cont m_viewClient->moveValidationMessage(toQt(anchor_in_root_view)); } +void WebContentsDelegateQt::BeforeUnloadFired(content::WebContents *tab, bool proceed, bool *proceed_to_fire_unload) +{ + Q_UNUSED(tab); + Q_ASSERT(proceed_to_fire_unload); + *proceed_to_fire_unload = proceed; + if (!proceed) + m_viewClient->windowCloseRejected(); +} + } // namespace QtWebEngineCore diff --git a/src/core/web_contents_delegate_qt.h b/src/core/web_contents_delegate_qt.h index 14421d060eaf39255a04ac3d5c55a3bee2b475aa..abdf75fe55d5bc24c4a590951d1babd844da238e 100644 --- a/src/core/web_contents_delegate_qt.h +++ b/src/core/web_contents_delegate_qt.h @@ -91,6 +91,7 @@ public: virtual void ShowValidationMessage(content::WebContents *web_contents, const gfx::Rect &anchor_in_root_view, const base::string16 &main_text, const base::string16 &sub_text) Q_DECL_OVERRIDE; virtual void HideValidationMessage(content::WebContents *web_contents) Q_DECL_OVERRIDE; virtual void MoveValidationMessage(content::WebContents *web_contents, const gfx::Rect &anchor_in_root_view) Q_DECL_OVERRIDE; + void BeforeUnloadFired(content::WebContents* tab, bool proceed, bool* proceed_to_fire_unload) Q_DECL_OVERRIDE; // WebContentsObserver overrides virtual void RenderFrameDeleted(content::RenderFrameHost *render_frame_host) Q_DECL_OVERRIDE; diff --git a/src/webengine/api/qquickwebenginetestsupport_p.h b/src/webengine/api/qquickwebenginetestsupport_p.h index 9690e538d0be9d704886a6a74f6d12b4132d73da..d4b50ac2db0a199b4a1ad2a5428f2dd2b1696d4a 100644 --- a/src/webengine/api/qquickwebenginetestsupport_p.h +++ b/src/webengine/api/qquickwebenginetestsupport_p.h @@ -80,6 +80,7 @@ public: Q_SIGNALS: void validationMessageShown(const QString &mainText, const QString &subText); + void windowCloseRejected(); private: QScopedPointer<QQuickWebEngineErrorPage> m_errorPage; diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp index 96a80a940f30322f3d06df616321589c81cc536a..3a05477a13e2a33fafe1dfb4de2d08d03f6ccfb3 100644 --- a/src/webengine/api/qquickwebengineview.cpp +++ b/src/webengine/api/qquickwebengineview.cpp @@ -508,6 +508,14 @@ void QQuickWebEngineViewPrivate::close() emit q->windowCloseRequested(); } +void QQuickWebEngineViewPrivate::windowCloseRejected() +{ +#ifdef ENABLE_QML_TESTSUPPORT_API + if (m_testSupport) + Q_EMIT m_testSupport->windowCloseRejected(); +#endif +} + void QQuickWebEngineViewPrivate::requestFullScreen(bool fullScreen) { Q_Q(QQuickWebEngineView); @@ -1298,6 +1306,9 @@ void QQuickWebEngineView::triggerWebAction(WebAction action) case ExitFullScreen: d->adapter->exitFullScreen(); break; + case RequestClose: + d->adapter->requestClose(); + break; default: Q_UNREACHABLE(); } diff --git a/src/webengine/api/qquickwebengineview_p.h b/src/webengine/api/qquickwebengineview_p.h index 6efb2b6f9fd5ca43a6a053a6b13384caa0f4c7bc..d1d8dacbbb351d3aa99068ec1e01029aa8060671 100644 --- a/src/webengine/api/qquickwebengineview_p.h +++ b/src/webengine/api/qquickwebengineview_p.h @@ -226,6 +226,7 @@ public: InspectElement, ExitFullScreen, + RequestClose, WebActionCount }; diff --git a/src/webengine/api/qquickwebengineview_p_p.h b/src/webengine/api/qquickwebengineview_p_p.h index 0140111b924a60bfa01bc4adc14114787ed23ede..271ab63beb99636d39fe55dc8f3054a1d9a8f299 100644 --- a/src/webengine/api/qquickwebengineview_p_p.h +++ b/src/webengine/api/qquickwebengineview_p_p.h @@ -143,6 +143,7 @@ public: virtual void unhandledKeyEvent(QKeyEvent *event) Q_DECL_OVERRIDE; virtual void adoptNewWindow(QtWebEngineCore::WebContentsAdapter *newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect &) Q_DECL_OVERRIDE; virtual void close() Q_DECL_OVERRIDE; + virtual void windowCloseRejected() Q_DECL_OVERRIDE; virtual void requestFullScreen(bool) Q_DECL_OVERRIDE; virtual bool isFullScreen() const Q_DECL_OVERRIDE; virtual bool contextMenuRequested(const QtWebEngineCore::WebEngineContextMenuData &) Q_DECL_OVERRIDE; diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp index 5d91b3b7fa872cc843e2e839e141452d95b1540e..bb0917918c0570e53129569223947bc6bec3972c 100644 --- a/src/webenginewidgets/api/qwebenginepage.cpp +++ b/src/webenginewidgets/api/qwebenginepage.cpp @@ -224,6 +224,11 @@ void QWebEnginePagePrivate::close() Q_EMIT q->windowCloseRequested(); } +void QWebEnginePagePrivate::windowCloseRejected() +{ + // Do nothing for now. +} + void QWebEnginePagePrivate::didRunJavaScript(quint64 requestId, const QVariant& result) { m_callbacks.invoke(requestId, result); @@ -639,6 +644,9 @@ QAction *QWebEnginePage::action(WebAction action) const case ExitFullScreen: text = tr("Exit Full Screen Mode"); break; + case RequestClose: + text = tr("Close Page"); + break; default: break; } @@ -809,6 +817,9 @@ void QWebEnginePage::triggerAction(WebAction action, bool) case ExitFullScreen: d->adapter->exitFullScreen(); break; + case RequestClose: + d->adapter->requestClose(); + break; default: Q_UNREACHABLE(); } diff --git a/src/webenginewidgets/api/qwebenginepage.h b/src/webenginewidgets/api/qwebenginepage.h index 90fa62f97d57b50e7a43ab8e157c482e08756e1c..4b5e6456d1b7454583a8838e862166fe05739f77 100644 --- a/src/webenginewidgets/api/qwebenginepage.h +++ b/src/webenginewidgets/api/qwebenginepage.h @@ -108,6 +108,7 @@ public: InspectElement, ExitFullScreen, + RequestClose, WebActionCount }; diff --git a/src/webenginewidgets/api/qwebenginepage_p.h b/src/webenginewidgets/api/qwebenginepage_p.h index 8d9bc25177a06e04729bbc8db93d48e527c25467..f5c01ce1b38109c9288ee174b016dc66bad68566 100644 --- a/src/webenginewidgets/api/qwebenginepage_p.h +++ b/src/webenginewidgets/api/qwebenginepage_p.h @@ -95,6 +95,7 @@ public: virtual void unhandledKeyEvent(QKeyEvent *event) Q_DECL_OVERRIDE; virtual void adoptNewWindow(QtWebEngineCore::WebContentsAdapter *newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect &initialGeometry) Q_DECL_OVERRIDE; virtual void close() Q_DECL_OVERRIDE; + virtual void windowCloseRejected() Q_DECL_OVERRIDE; virtual bool contextMenuRequested(const QtWebEngineCore::WebEngineContextMenuData &data) Q_DECL_OVERRIDE; virtual void navigationRequested(int navigationType, const QUrl &url, int &navigationRequestAction, bool isMainFrame) Q_DECL_OVERRIDE; virtual void requestFullScreen(bool) Q_DECL_OVERRIDE; diff --git a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc index 4a7545c140c2c6ace307bce9c342f927fb48c4da..b7b3bf0222955276b6828c15f8fee7ae1efcf9c6 100644 --- a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc +++ b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc @@ -119,6 +119,9 @@ \value InspectElement Trigger any attached Web Inspector to inspect the highlighed element. (Added in Qt 5.6) \value ExitFullScreen Exit the fullscreen mode. (Added in Qt 5.6) + \value RequestClose Request to close the web page. If defined, the \c{window.onbeforeunload} + handler is run, and the user can confirm or reject to close the page. If the close + request is confirmed, \c windowCloseRequested is emitted. (Added in Qt 5.6) \omitvalue WebActionCount @@ -482,6 +485,8 @@ This signal is emitted whenever the page requests the web browser window to be closed, for example through the JavaScript \c{window.close()} call. + + \sa QWebEnginePage::RequestClose */ /*! diff --git a/tests/auto/quick/qmltests/data/TestWebEngineView.qml b/tests/auto/quick/qmltests/data/TestWebEngineView.qml index 8a01dfa0967d5e8519e2db338d03d750fa955877..e2c5c90096a1c057b541030fde74f6122aaf9ba1 100644 --- a/tests/auto/quick/qmltests/data/TestWebEngineView.qml +++ b/tests/auto/quick/qmltests/data/TestWebEngineView.qml @@ -47,6 +47,7 @@ import QtWebEngine.experimental 1.0 WebEngineView { property var loadStatus: null property var viewportReady: false + property bool windowCloseRequestedSignalEmitted: false function waitForLoadSucceeded() { var success = _waitFor(function() { return loadStatus == WebEngineView.LoadSucceededStatus }) @@ -69,6 +70,9 @@ WebEngineView { loadStatus = null return stop } + function waitForWindowCloseRequested() { + return _waitFor(function() { return windowCloseRequestedSignalEmitted; }); + } function _waitFor(predicate) { var timeout = 5000 var i = 0 @@ -87,5 +91,8 @@ WebEngineView { viewportReady = false } + onWindowCloseRequested: { + windowCloseRequestedSignalEmitted = true; + } } diff --git a/tests/auto/quick/qmltests/data/confirmclose.html b/tests/auto/quick/qmltests/data/confirmclose.html new file mode 100644 index 0000000000000000000000000000000000000000..ba11da7a4f5ffe0a583d9987e8502836b0054c75 --- /dev/null +++ b/tests/auto/quick/qmltests/data/confirmclose.html @@ -0,0 +1,5 @@ +<html> +<body onbeforeunload="return 'You are about to miss out on some awesome content.';"> + Be greeted, precious viewer! +</body> +</html> diff --git a/tests/auto/quick/qmltests/data/tst_javaScriptDialogs.qml b/tests/auto/quick/qmltests/data/tst_javaScriptDialogs.qml index 75b45bfac35197d420dde3f40dad4d8926ac983c..4294c5ba369229f77e99d557236dd447bb8b13f1 100644 --- a/tests/auto/quick/qmltests/data/tst_javaScriptDialogs.qml +++ b/tests/auto/quick/qmltests/data/tst_javaScriptDialogs.qml @@ -42,11 +42,26 @@ import QtQuick 2.0 import QtTest 1.0 import QtWebEngine 1.2 +import QtWebEngine.testsupport 1.0 import "../mock-delegates/TestParams" 1.0 TestWebEngineView { id: webEngineView + testSupport: WebEngineTestSupport { + property bool windowCloseRejectedSignalEmitted: false + + function waitForWindowCloseRejected() { + return _waitFor(function () { + return testSupport.windowCloseRejectedSignalEmitted; + }); + } + + onWindowCloseRejected: { + windowCloseRejectedSignalEmitted = true; + } + } + TestCase { id: test name: "WebEngineViewJavaScriptDialogs" @@ -80,6 +95,24 @@ TestWebEngineView { } + function test_confirmClose() { + webEngineView.url = Qt.resolvedUrl("confirmclose.html"); + verify(webEngineView.waitForLoadSucceeded()); + webEngineView.windowCloseRequestedSignalEmitted = false; + JSDialogParams.shouldAcceptDialog = true; + webEngineView.triggerWebAction(WebEngineView.RequestClose); + verify(webEngineView.waitForWindowCloseRequested()); + } + + function test_rejectClose() { + webEngineView.url = Qt.resolvedUrl("confirmclose.html"); + verify(webEngineView.waitForLoadSucceeded()); + webEngineView.testSupport.windowCloseRejectedSignalEmitted = false; + JSDialogParams.shouldAcceptDialog = false; + webEngineView.triggerWebAction(WebEngineView.RequestClose); + verify(webEngineView.testSupport.waitForWindowCloseRejected()); + } + function test_prompt() { JSDialogParams.inputForPrompt = "tQ olleH" webEngineView.url = Qt.resolvedUrl("prompt.html") diff --git a/tests/auto/quick/qmltests/qmltests.pro b/tests/auto/quick/qmltests/qmltests.pro index 01517af47b9d8c66e964657ed007dcb62e622315..57649384d49f93d17c40cb74122f31c6186fd60e 100644 --- a/tests/auto/quick/qmltests/qmltests.pro +++ b/tests/auto/quick/qmltests/qmltests.pro @@ -12,6 +12,7 @@ OTHER_FILES += \ $$PWD/data/change-document-title.js \ $$PWD/data/download.zip \ $$PWD/data/confirm.html \ + $$PWD/data/confirmclose.html \ $$PWD/data/directoryupload.html \ $$PWD/data/favicon.html \ $$PWD/data/favicon.png \