diff options
author | Peter Varga <pvarga@inf.u-szeged.hu> | 2016-07-13 17:28:15 +0200 |
---|---|---|
committer | Peter Varga <pvarga@inf.u-szeged.hu> | 2016-08-02 14:08:32 +0000 |
commit | 9b8c1020f2752ab5095086577ab98fa80926c43d (patch) | |
tree | a0780e2dd66d2dd3eeff727cf81d97c0632aa36a | |
parent | ea616d1047325fed629eb3bfef85550daff56ff7 (diff) |
Add View Source API and make the feature available from context menu
[ChangeLog][QtWebEngineQML][QQuickWebEngineView] View Source feature is
now supported
[ChangeLog][QtWebEngineWidgets][QWebEnginePage] View Source feature is
now supported
Change-Id: Icc16da71fc6ec95880897fc9744dd8be8c004e00
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
-rw-r--r-- | examples/webenginewidgets/demobrowser/browsermainwindow.cpp | 19 | ||||
-rw-r--r-- | examples/webenginewidgets/demobrowser/browsermainwindow.h | 1 | ||||
-rw-r--r-- | src/core/web_contents_adapter.cpp | 12 | ||||
-rw-r--r-- | src/core/web_contents_adapter.h | 3 | ||||
-rw-r--r-- | src/webengine/api/qquickwebengineview.cpp | 16 | ||||
-rw-r--r-- | src/webengine/api/qquickwebengineview_p.h | 3 | ||||
-rw-r--r-- | src/webengine/doc/src/webengineview.qdoc | 17 | ||||
-rw-r--r-- | src/webenginewidgets/api/qwebenginepage.cpp | 46 | ||||
-rw-r--r-- | src/webenginewidgets/api/qwebenginepage.h | 4 | ||||
-rw-r--r-- | src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc | 1 | ||||
-rw-r--r-- | tests/auto/quick/qmltests/data/tst_viewSource.qml | 93 | ||||
-rw-r--r-- | tests/auto/quick/qmltests/qmltests.pro | 1 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/resources/test1.html | 7 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp | 21 |
14 files changed, 227 insertions, 17 deletions
diff --git a/examples/webenginewidgets/demobrowser/browsermainwindow.cpp b/examples/webenginewidgets/demobrowser/browsermainwindow.cpp index 14ed10dfc..b93c538d8 100644 --- a/examples/webenginewidgets/demobrowser/browsermainwindow.cpp +++ b/examples/webenginewidgets/demobrowser/browsermainwindow.cpp @@ -403,7 +403,10 @@ void BrowserMainWindow::setupMenu() viewMenu->addAction(tr("Reset &Zoom"), this, SLOT(slotViewResetZoom()), QKeySequence(Qt::CTRL | Qt::Key_0)); viewMenu->addSeparator(); - viewMenu->addAction(tr("Page S&ource"), this, SLOT(slotViewPageSource()), tr("Ctrl+Alt+U")); + QAction *m_pageSource = viewMenu->addAction(tr("Page S&ource")); + m_pageSource->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_U)); + m_tabWidget->addWebAction(m_pageSource, QWebEnginePage::ViewSource); + QAction *a = viewMenu->addAction(tr("&Full Screen"), this, SLOT(slotViewFullScreen(bool)), Qt::Key_F11); a->setCheckable(true); @@ -849,20 +852,6 @@ void BrowserMainWindow::slotViewFullScreen(bool makeFullScreen) } } -void BrowserMainWindow::slotViewPageSource() -{ - if (!currentTab()) - return; - - QPlainTextEdit *view = new QPlainTextEdit; - view->setWindowTitle(tr("Page Source of %1").arg(currentTab()->title())); - view->setMinimumWidth(640); - view->setAttribute(Qt::WA_DeleteOnClose); - view->show(); - - currentTab()->page()->toHtml(invoke(view, &QPlainTextEdit::setPlainText)); -} - void BrowserMainWindow::slotHome() { QSettings settings; diff --git a/examples/webenginewidgets/demobrowser/browsermainwindow.h b/examples/webenginewidgets/demobrowser/browsermainwindow.h index 9fb6b0851..7bd1ffaf1 100644 --- a/examples/webenginewidgets/demobrowser/browsermainwindow.h +++ b/examples/webenginewidgets/demobrowser/browsermainwindow.h @@ -123,7 +123,6 @@ private slots: void slotViewToolbar(); void slotViewBookmarksBar(); void slotViewStatusbar(); - void slotViewPageSource(); void slotViewFullScreen(bool enable); void slotWebSearch(); diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp index 364d4d5b2..c3789318a 100644 --- a/src/core/web_contents_adapter.cpp +++ b/src/core/web_contents_adapter.cpp @@ -1281,4 +1281,16 @@ FaviconManager *WebContentsAdapter::faviconManager() return d->webContentsDelegate->faviconManager(); } +void WebContentsAdapter::viewSource() +{ + Q_D(WebContentsAdapter); + d->webContents->ViewSource(); +} + +bool WebContentsAdapter::canViewSource() +{ + Q_D(WebContentsAdapter); + return d->webContents->GetController().CanViewSource(); +} + } // namespace QtWebEngineCore diff --git a/src/core/web_contents_adapter.h b/src/core/web_contents_adapter.h index 63b99d013..72e1f6447 100644 --- a/src/core/web_contents_adapter.h +++ b/src/core/web_contents_adapter.h @@ -178,6 +178,9 @@ public: content::WebContents *webContents() const; void replaceMisspelling(const QString &word); + void viewSource(); + bool canViewSource(); + private: Q_DISABLE_COPY(WebContentsAdapter) Q_DECLARE_PRIVATE(WebContentsAdapter) diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp index 983e8c30e..0170018e7 100644 --- a/src/webengine/api/qquickwebengineview.cpp +++ b/src/webengine/api/qquickwebengineview.cpp @@ -240,6 +240,10 @@ bool QQuickWebEngineViewPrivate::contextMenuRequested(const WebEngineContextMenu item = new MenuItemHandler(menu); QObject::connect(item, &MenuItemHandler::triggered, q, &QQuickWebEngineView::reload); ui()->addMenuItem(item, QQuickWebEngineView::tr("Reload"), QStringLiteral("view-refresh")); + + item = new MenuItemHandler(menu); + QObject::connect(item, &MenuItemHandler::triggered, q, &QQuickWebEngineView::viewSource); + ui()->addMenuItem(item, QQuickWebEngineView::tr("View Page Source"), QStringLiteral("view-source"), q->canViewSource()); } else { item = new MenuItemHandler(menu); QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::Copy); }); @@ -1278,6 +1282,18 @@ void QQuickWebEngineView::replaceMisspelledWord(const QString &replacement) d->adapter->replaceMisspelling(replacement); } +void QQuickWebEngineView::viewSource() +{ + Q_D(QQuickWebEngineView); + d->adapter->viewSource(); +} + +bool QQuickWebEngineView::canViewSource() const +{ + Q_D(const QQuickWebEngineView); + return d->adapter->canViewSource(); +} + bool QQuickWebEngineView::isFullScreen() const { Q_D(const QQuickWebEngineView); diff --git a/src/webengine/api/qquickwebengineview_p.h b/src/webengine/api/qquickwebengineview_p.h index 367497da5..dc693a94c 100644 --- a/src/webengine/api/qquickwebengineview_p.h +++ b/src/webengine/api/qquickwebengineview_p.h @@ -119,6 +119,7 @@ class Q_WEBENGINE_PRIVATE_EXPORT QQuickWebEngineView : public QQuickItem { Q_PROPERTY(bool audioMuted READ isAudioMuted WRITE setAudioMuted NOTIFY audioMutedChanged FINAL REVISION 3) Q_PROPERTY(bool recentlyAudible READ recentlyAudible NOTIFY recentlyAudibleChanged FINAL REVISION 3) Q_PROPERTY(uint webChannelWorld READ webChannelWorld WRITE setWebChannelWorld NOTIFY webChannelWorldChanged REVISION 3) + Q_PROPERTY(bool canViewSource READ canViewSource FINAL REVISION 4) #ifdef ENABLE_QML_TESTSUPPORT_API Q_PROPERTY(QQuickWebEngineTestSupport *testSupport READ testSupport WRITE setTestSupport FINAL) @@ -145,6 +146,7 @@ public: void setBackgroundColor(const QColor &color); QSizeF contentsSize() const; QPointF scrollPosition() const; + bool canViewSource() const; QQuickWebEngineViewExperimental *experimental() const; @@ -473,6 +475,7 @@ public Q_SLOTS: Q_REVISION(3) void printToPdf(const QString &filePath, PrintedPageSizeId pageSizeId = PrintedPageSizeId::A4, PrintedPageOrientation orientation = PrintedPageOrientation::Portrait); Q_REVISION(3) void printToPdf(const QJSValue &callback, PrintedPageSizeId pageSizeId = PrintedPageSizeId::A4, PrintedPageOrientation orientation = PrintedPageOrientation::Portrait); Q_REVISION(4) void replaceMisspelledWord(const QString &replacement); + Q_REVISION(4) void viewSource(); private Q_SLOTS: void lazyInitialize(); diff --git a/src/webengine/doc/src/webengineview.qdoc b/src/webengine/doc/src/webengineview.qdoc index 0f83d0d23..83eadd563 100644 --- a/src/webengine/doc/src/webengineview.qdoc +++ b/src/webengine/doc/src/webengineview.qdoc @@ -1128,3 +1128,20 @@ Also if the audio is paused, this signal is emitted with an approximate \b{2 second delay}, from the moment the audio is paused. */ + +/*! + \qmlmethod void WebEngineView::viewSource() + \since QtWebEngine 1.4 + + Shows the source of the current page in a new tab. + + \sa canViewSource +*/ + +/*! + \qmlproperty bool WebEngineView::canViewSource + \brief This property holds whether the source for the current page can be viewed. + \since QtWebEngine 1.4 + + \sa viewSource() +*/ diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp index 3f2323334..be5a39e83 100644 --- a/src/webenginewidgets/api/qwebenginepage.cpp +++ b/src/webenginewidgets/api/qwebenginepage.cpp @@ -81,6 +81,7 @@ #include <QMimeData> #include <QStandardPaths> #include <QStyle> +#include <QTimer> #include <QUrl> QT_BEGIN_NAMESPACE @@ -422,6 +423,9 @@ void QWebEnginePagePrivate::updateAction(QWebEnginePage::WebAction action) const case QWebEnginePage::ReloadAndBypassCache: enabled = !isLoading; break; + case QWebEnginePage::ViewSource: + enabled = adapter->canViewSource(); + break; default: break; } @@ -437,6 +441,7 @@ void QWebEnginePagePrivate::updateNavigationActions() updateAction(QWebEnginePage::Stop); updateAction(QWebEnginePage::Reload); updateAction(QWebEnginePage::ReloadAndBypassCache); + updateAction(QWebEnginePage::ViewSource); } #ifndef QT_NO_ACTION @@ -931,6 +936,9 @@ QAction *QWebEnginePage::action(WebAction action) const case SavePage: text = tr("Save &Page"); break; + case ViewSource: + text = tr("&View Page Source"); + break; case NoWebAction: case WebActionCount: Q_UNREACHABLE(); @@ -1121,6 +1129,17 @@ void QWebEnginePage::triggerAction(WebAction action, bool) case SavePage: d->adapter->save(); break; + case ViewSource: + // This is a workaround to make the ViewSource action working in a context menu. + // The WebContentsAdapter::viewSource() method deletes a + // RenderWidgetHostViewQtDelegateWidget instance which passes the control to the event + // loop. If the QMenu::aboutToHide() signal is connected to the QObject::deleteLater() + // slot the QMenu is deleted by the event handler while the ViewSource action is still not + // completed. This may lead to a crash. To avoid this the WebContentsAdapter::viewSource() + // method is called indirectly via the QTimer::singleShot() function which schedules the + // the viewSource() call after the QMenu's destruction. + QTimer::singleShot(0, this, [d](){ d->adapter->viewSource(); }); + break; case NoWebAction: break; case WebActionCount: @@ -1378,6 +1397,8 @@ QMenu *QWebEnginePage::createStandardContextMenu() action = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), tr("&Reload"), menu); connect(action, &QAction::triggered, d->view, &QWebEngineView::reload); menu->addAction(action); + + menu->addAction(QWebEnginePage::action(ViewSource)); } else { menu->addAction(QWebEnginePage::action(Copy)); menu->addAction(QWebEnginePage::action(Unselect)); @@ -1788,6 +1809,31 @@ const QWebEngineContextMenuData &QWebEnginePage::contextMenuData() const return d->contextData; } +/*! + \since 5.8 + + Shows the source of the current page in a new tab. + + \sa canViewSource +*/ +void QWebEnginePage::viewSource() +{ + triggerAction(QWebEnginePage::ViewSource); +} + +/*! + \property QWebEnginePage::canViewSource + \brief whether the source for the current page can be viewed. + \since 5.8 + + \sa viewSource() +*/ +bool QWebEnginePage::canViewSource() const +{ + Q_D(const QWebEnginePage); + return d->adapter->canViewSource(); +} + QT_END_NAMESPACE #include "moc_qwebenginepage.cpp" diff --git a/src/webenginewidgets/api/qwebenginepage.h b/src/webenginewidgets/api/qwebenginepage.h index ad32f169d..bb46cd9fb 100644 --- a/src/webenginewidgets/api/qwebenginepage.h +++ b/src/webenginewidgets/api/qwebenginepage.h @@ -81,6 +81,7 @@ class QWEBENGINEWIDGETS_EXPORT QWebEnginePage : public QObject { Q_PROPERTY(QPointF scrollPosition READ scrollPosition NOTIFY scrollPositionChanged) Q_PROPERTY(bool audioMuted READ isAudioMuted WRITE setAudioMuted NOTIFY audioMutedChanged) Q_PROPERTY(bool recentlyAudible READ recentlyAudible NOTIFY recentlyAudibleChanged) + Q_PROPERTY(bool canViewSource READ canViewSource) public: enum WebAction { @@ -124,6 +125,7 @@ public: Unselect, SavePage, OpenLinkInNewBackgroundTab, + ViewSource, WebActionCount }; @@ -279,6 +281,8 @@ public: #endif const QWebEngineContextMenuData &contextMenuData() const; + void viewSource(); + bool canViewSource() const; Q_SIGNALS: void loadStarted(); diff --git a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc index 539e809d4..a0dc49d0a 100644 --- a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc +++ b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc @@ -152,6 +152,7 @@ \value Unselect Clear the current selection. (Added in Qt 5.7) \value SavePage Save the current page to disk. MHTML is the default format that is used to store the web page on disk. (Added in Qt 5.7) + \value ViewSource Shows the source of the current page in a new tab. (Added in Qt 5.8) \omitvalue WebActionCount diff --git a/tests/auto/quick/qmltests/data/tst_viewSource.qml b/tests/auto/quick/qmltests/data/tst_viewSource.qml new file mode 100644 index 000000000..7c7dd34e0 --- /dev/null +++ b/tests/auto/quick/qmltests/data/tst_viewSource.qml @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtWebEngine 1.4 + +TestWebEngineView { + id: webEngineView + width: 200 + height: 400 + + property var viewRequest: null + + SignalSpy { + id: newViewRequestedSpy + target: webEngineView + signalName: "newViewRequested" + } + + SignalSpy { + id: titleChangedSpy + target: webEngineView + signalName: "titleChanged" + } + + onNewViewRequested: { + viewRequest = { + "destination": request.destination, + "userInitiated": request.userInitiated + }; + + request.openIn(webEngineView); + } + + TestCase { + id: test + name: "WebEngineViewSource" + + function init() { + newViewRequestedSpy.clear(); + titleChangedSpy.clear(); + viewRequest = null; + } + + function test_viewSource() { + webEngineView.url = Qt.resolvedUrl("test1.html"); + verify(webEngineView.waitForLoadSucceeded()); + compare(webEngineView.title, "Test page 1"); + verify(webEngineView.canViewSource, true); + + titleChangedSpy.clear(); + webEngineView.viewSource(); + tryCompare(newViewRequestedSpy, "count", 1); + verify(webEngineView.waitForLoadSucceeded()); + // The first titleChanged signal is emitted by adoptWebContents() + tryCompare(titleChangedSpy, "count", 2); + + compare(viewRequest.destination, WebEngineView.NewViewInTab); + verify(viewRequest.userInitiated); + verify(!webEngineView.canViewSource); + + compare(webEngineView.title, "test1.html"); + compare(webEngineView.url, "view-source:" + Qt.resolvedUrl("test1.html")); + } + } +} + diff --git a/tests/auto/quick/qmltests/qmltests.pro b/tests/auto/quick/qmltests/qmltests.pro index 0158a7268..abb0a86fb 100644 --- a/tests/auto/quick/qmltests/qmltests.pro +++ b/tests/auto/quick/qmltests/qmltests.pro @@ -63,6 +63,7 @@ OTHER_FILES += \ $$PWD/data/tst_titleChanged.qml \ $$PWD/data/tst_unhandledKeyEventPropagation.qml \ $$PWD/data/tst_userScripts.qml \ + $$PWD/data/tst_viewSource.qml \ $$PWD/data/tst_webchannel.qml \ $$PWD/data/tst_settings.qml \ $$PWD/data/tst_keyboardModifierMapping.qml \ diff --git a/tests/auto/widgets/qwebenginepage/resources/test1.html b/tests/auto/widgets/qwebenginepage/resources/test1.html index b323f966e..5c09f06ed 100644 --- a/tests/auto/widgets/qwebenginepage/resources/test1.html +++ b/tests/auto/widgets/qwebenginepage/resources/test1.html @@ -1 +1,6 @@ -<html><body><p>Some text 1</p></body></html> +<html> +<head><title>Test page 1</title></head> +<body> +Hello. +</body> +</html> diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index 697f45a83..874581f1c 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -239,6 +239,7 @@ private Q_SLOTS: void mouseButtonTranslation(); void printToPdf(); + void viewSource(); private: QWebEngineView* m_view; @@ -4938,5 +4939,25 @@ void tst_QWebEnginePage::mouseButtonTranslation() delete view; } +void tst_QWebEnginePage::viewSource() +{ + TestPage page; + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + const QUrl url("qrc:/resources/test1.html"); + + page.load(url); + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QCOMPARE(page.title(), QStringLiteral("Test page 1")); + QVERIFY(page.canViewSource()); + + page.viewSource(); + QTest::qWait(200); + QTRY_COMPARE(page.createdWindows.size(), 1); + + QTRY_COMPARE(page.createdWindows[0]->url().toString(), QStringLiteral("view-source:%1").arg(url.toString())); + QTRY_COMPARE(page.createdWindows[0]->title(), QStringLiteral("view-source:%1").arg(url.toString())); + QVERIFY(!page.createdWindows[0]->canViewSource()); +} + QTEST_MAIN(tst_QWebEnginePage) #include "tst_qwebenginepage.moc" |