diff options
author | Jüri Valdmann <juri.valdmann@qt.io> | 2019-04-12 16:44:18 +0200 |
---|---|---|
committer | Jüri Valdmann <juri.valdmann@qt.io> | 2019-05-24 16:14:51 +0200 |
commit | a69029cf9fcfd0c1fcdaafe5cbcbff2d5dd6b5c5 (patch) | |
tree | a38cae2c082ee3c9a0108942b8406947d826a782 /src/webenginewidgets | |
parent | 0f081baa31facec779057de29eec14c6f458f6a6 (diff) |
Implement page lifecycle API
[ChangeLog][QtWebEngine][WebEngineView] WebEngineView now supports lifecycle
states that can be used for reducing CPU and memory consumption of invisible
views.
[ChangeLog][QtWebEngineWidgets][QWebEnginePage] QWebEnginePage now supports
lifecycle states that can be used for reducing CPU and memory consumption of
invisible pages.
Fixes: QTBUG-74166
Fixes: QTBUG-55079
Change-Id: I7d70c85dc995bd17c9fe91385a8e2750dbc0a627
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: Peter Varga <pvarga@inf.u-szeged.hu>
Diffstat (limited to 'src/webenginewidgets')
-rw-r--r-- | src/webenginewidgets/api/qwebenginepage.cpp | 176 | ||||
-rw-r--r-- | src/webenginewidgets/api/qwebenginepage.h | 24 | ||||
-rw-r--r-- | src/webenginewidgets/api/qwebenginepage_p.h | 6 | ||||
-rw-r--r-- | src/webenginewidgets/api/qwebengineview.cpp | 14 | ||||
-rw-r--r-- | src/webenginewidgets/api/qwebengineview.h | 1 |
5 files changed, 187 insertions, 34 deletions
diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp index 86736d42b..893200826 100644 --- a/src/webenginewidgets/api/qwebenginepage.cpp +++ b/src/webenginewidgets/api/qwebenginepage.cpp @@ -171,11 +171,11 @@ QWebEnginePagePrivate::QWebEnginePagePrivate(QWebEngineProfile *_profile) qRegisterMetaType<QWebEngineQuotaRequest>(); qRegisterMetaType<QWebEngineRegisterProtocolHandlerRequest>(); - // See wasShown() and wasHidden(). + // See setVisible(). wasShownTimer.setSingleShot(true); QObject::connect(&wasShownTimer, &QTimer::timeout, [this](){ ensureInitialized(); - wasShown(); + adapter->setVisible(true); }); profile->d_ptr->addWebContentsAdapterClient(this); @@ -214,6 +214,8 @@ void QWebEnginePagePrivate::initializationFinished() adapter->setAudioMuted(defaultAudioMuted); if (!qFuzzyCompare(adapter->currentZoomFactor(), defaultZoomFactor)) adapter->setZoomFactor(defaultZoomFactor); + if (view) + adapter->setVisible(view->isVisible()); scriptCollection.d->initializationFinished(adapter); @@ -627,8 +629,6 @@ void QWebEnginePagePrivate::recreateFromSerializedHistory(QDataStream &input) adapter = std::move(newWebContents); adapter->setClient(this); adapter->loadDefault(); - if (view && view->isVisible()) - wasShown(); } } @@ -1585,31 +1585,6 @@ bool QWebEnginePage::event(QEvent *e) return QObject::event(e); } -void QWebEnginePagePrivate::wasShown() -{ - if (!adapter->isInitialized()) { - // On the one hand, it is too early to initialize here. The application - // may call show() before load(), or it may call show() from - // createWindow(), and then we would create an unnecessary blank - // WebContents here. On the other hand, if the application calls show() - // then it expects something to be shown, so we have to initialize. - // Therefore we have to delay the initialization via the event loop. - wasShownTimer.start(); - return; - } - adapter->wasShown(); -} - -void QWebEnginePagePrivate::wasHidden() -{ - if (!adapter->isInitialized()) { - // Cancel timer from wasShown() above. - wasShownTimer.stop(); - return; - } - adapter->wasHidden(); -} - void QWebEnginePagePrivate::contextMenuRequested(const WebEngineContextMenuData &data) { #if QT_CONFIG(action) @@ -1820,6 +1795,26 @@ void QWebEnginePagePrivate::printRequested() }); } +void QWebEnginePagePrivate::lifecycleStateChanged(LifecycleState state) +{ + Q_Q(QWebEnginePage); + Q_EMIT q->lifecycleStateChanged(static_cast<QWebEnginePage::LifecycleState>(state)); +} + +void QWebEnginePagePrivate::recommendedStateChanged(LifecycleState state) +{ + Q_Q(QWebEnginePage); + QTimer::singleShot(0, q, [q, state]() { + Q_EMIT q->recommendedStateChanged(static_cast<QWebEnginePage::LifecycleState>(state)); + }); +} + +void QWebEnginePagePrivate::visibleChanged(bool visible) +{ + Q_Q(QWebEnginePage); + Q_EMIT q->visibleChanged(visible); +} + /*! \since 5.13 @@ -2105,6 +2100,10 @@ void QWebEnginePage::runJavaScript(const QString &scriptSource) { Q_D(QWebEnginePage); d->ensureInitialized(); + if (d->adapter->lifecycleState() == WebContentsAdapter::LifecycleState::Discarded) { + qWarning("runJavaScript: disabled in Discarded state"); + return; + } d->adapter->runJavaScript(scriptSource, QWebEngineScript::MainWorld); } @@ -2112,6 +2111,11 @@ void QWebEnginePage::runJavaScript(const QString& scriptSource, const QWebEngine { Q_D(QWebEnginePage); d->ensureInitialized(); + if (d->adapter->lifecycleState() == WebContentsAdapter::LifecycleState::Discarded) { + qWarning("runJavaScript: disabled in Discarded state"); + d->m_callbacks.invokeEmpty(resultCallback); + return; + } quint64 requestId = d->adapter->runJavaScriptCallbackResult(scriptSource, QWebEngineScript::MainWorld); d->m_callbacks.registerCallback(requestId, resultCallback); } @@ -2490,6 +2494,120 @@ const QWebEngineContextMenuData &QWebEnginePage::contextMenuData() const return d->contextData; } +/*! + \enum QWebEnginePage::LifecycleState + \since 5.14 + + This enum describes the lifecycle state of the page: + + \value Active + Normal state. + \value Frozen + Low CPU usage state where most HTML task sources are suspended. + \value Discarded + Very low resource usage state where the entire browsing context is discarded. + + \sa lifecycleState, {WebEngine Lifecycle Example} +*/ + +/*! + \property QWebEnginePage::lifecycleState + \since 5.14 + + \brief The current lifecycle state of the page. + + The following restrictions are enforced by the setter: + + \list + \li A \l{visible} page must remain in the \c{Active} state. + \li If the page is being inspected by a \l{devToolsPage} then both pages must + remain in the \c{Active} states. + \li A page in the \c{Discarded} state can only transition to the \c{Active} + state. This will cause a reload of the page. + \endlist + + These are the only hard limits on the lifecycle state, but see also + \l{recommendedState} for the recommended soft limits. + + \sa recommendedState, {WebEngine Lifecycle Example} +*/ + +QWebEnginePage::LifecycleState QWebEnginePage::lifecycleState() const +{ + Q_D(const QWebEnginePage); + return static_cast<LifecycleState>(d->adapter->lifecycleState()); +} + +void QWebEnginePage::setLifecycleState(LifecycleState state) +{ + Q_D(QWebEnginePage); + d->adapter->setLifecycleState(static_cast<WebContentsAdapterClient::LifecycleState>(state)); +} + +/*! + \property QWebEnginePage::recommendedState + \since 5.14 + + \brief The recommended limit for the lifecycle state of the page. + + Setting the lifecycle state to a lower resource usage state than the + recommended state may cause side-effects such as stopping background audio + playback or loss of HTML form input. Setting the lifecycle state to a higher + resource state is however completely safe. + + \sa lifecycleState +*/ + +QWebEnginePage::LifecycleState QWebEnginePage::recommendedState() const +{ + Q_D(const QWebEnginePage); + return static_cast<LifecycleState>(d->adapter->recommendedState()); +} + +/*! + \property QWebEnginePage::visible + \since 5.14 + + \brief Whether the page is considered visible in the Page Visibility API. + + Setting this property changes the \c{Document.hidden} and the + \c{Document.visibilityState} properties in JavaScript which web sites can use + to voluntarily reduce their resource usage if they are not visible to the + user. + + If the page is connected to a \l{view} then this property will be managed + automatically by the view according to it's own visibility. + + \sa lifecycleState +*/ + +bool QWebEnginePage::isVisible() const +{ + Q_D(const QWebEnginePage); + return d->adapter->isVisible(); +} + +void QWebEnginePage::setVisible(bool visible) +{ + Q_D(QWebEnginePage); + + if (!d->adapter->isInitialized()) { + // On the one hand, it is too early to initialize here. The application + // may call show() before load(), or it may call show() from + // createWindow(), and then we would create an unnecessary blank + // WebContents here. On the other hand, if the application calls show() + // then it expects something to be shown, so we have to initialize. + // Therefore we have to delay the initialization via the event loop. + if (visible) + d->wasShownTimer.start(); + else + d->wasShownTimer.stop(); + return; + } + + d->adapter->setVisible(visible); +} + #if QT_CONFIG(action) QContextMenuBuilder::QContextMenuBuilder(const QtWebEngineCore::WebEngineContextMenuData &data, QWebEnginePage *page, diff --git a/src/webenginewidgets/api/qwebenginepage.h b/src/webenginewidgets/api/qwebenginepage.h index dae41d0ec..736d7ed69 100644 --- a/src/webenginewidgets/api/qwebenginepage.h +++ b/src/webenginewidgets/api/qwebenginepage.h @@ -88,6 +88,9 @@ 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 visible READ isVisible WRITE setVisible NOTIFY visibleChanged) + Q_PROPERTY(LifecycleState lifecycleState READ lifecycleState WRITE setLifecycleState NOTIFY lifecycleStateChanged) + Q_PROPERTY(LifecycleState recommendedState READ recommendedState NOTIFY recommendedStateChanged) public: enum WebAction { @@ -222,6 +225,14 @@ public: }; Q_ENUM(RenderProcessTerminationStatus) + // must match WebContentsAdapterClient::LifecycleState + enum class LifecycleState { + Active, + Frozen, + Discarded, + }; + Q_ENUM(LifecycleState) + explicit QWebEnginePage(QObject *parent = Q_NULLPTR); QWebEnginePage(QWebEngineProfile *profile, QObject *parent = Q_NULLPTR); ~QWebEnginePage(); @@ -307,6 +318,14 @@ public: const QWebEngineContextMenuData &contextMenuData() const; + LifecycleState lifecycleState() const; + void setLifecycleState(LifecycleState state); + + LifecycleState recommendedState() const; + + bool isVisible() const; + void setVisible(bool visible); + Q_SIGNALS: void loadStarted(); void loadProgress(int progress); @@ -345,6 +364,11 @@ Q_SIGNALS: void pdfPrintingFinished(const QString &filePath, bool success); void printRequested(); + void visibleChanged(bool visible); + + void lifecycleStateChanged(LifecycleState state); + void recommendedStateChanged(LifecycleState state); + protected: virtual QWebEnginePage *createWindow(WebWindowType type); virtual QStringList chooseFiles(FileSelectionMode mode, const QStringList &oldFiles, const QStringList &acceptedMimeTypes); diff --git a/src/webenginewidgets/api/qwebenginepage_p.h b/src/webenginewidgets/api/qwebenginepage_p.h index 5feefeb0e..059da8e4c 100644 --- a/src/webenginewidgets/api/qwebenginepage_p.h +++ b/src/webenginewidgets/api/qwebenginepage_p.h @@ -92,6 +92,9 @@ public: QtWebEngineCore::RenderWidgetHostViewQtDelegate* CreateRenderWidgetHostViewQtDelegate(QtWebEngineCore::RenderWidgetHostViewQtDelegateClient *client) override; QtWebEngineCore::RenderWidgetHostViewQtDelegate* CreateRenderWidgetHostViewQtDelegateForPopup(QtWebEngineCore::RenderWidgetHostViewQtDelegateClient *client) override { return CreateRenderWidgetHostViewQtDelegate(client); } void initializationFinished() override; + void lifecycleStateChanged(LifecycleState state) override; + void recommendedStateChanged(LifecycleState state) override; + void visibleChanged(bool visible) override; void titleChanged(const QString&) override; void urlChanged(const QUrl&) override; void iconChanged(const QUrl&) override; @@ -166,9 +169,6 @@ public: void updateAction(QWebEnginePage::WebAction) const; void _q_webActionTriggered(bool checked); - void wasShown(); - void wasHidden(); - QtWebEngineCore::WebContentsAdapter *webContents() { return adapter.data(); } void recreateFromSerializedHistory(QDataStream &input); diff --git a/src/webenginewidgets/api/qwebengineview.cpp b/src/webenginewidgets/api/qwebengineview.cpp index 6c08df343..ac979e766 100644 --- a/src/webenginewidgets/api/qwebengineview.cpp +++ b/src/webenginewidgets/api/qwebengineview.cpp @@ -378,7 +378,7 @@ void QWebEngineView::contextMenuEvent(QContextMenuEvent *event) void QWebEngineView::showEvent(QShowEvent *event) { QWidget::showEvent(event); - page()->d_ptr->wasShown(); + page()->setVisible(true); } /*! @@ -387,7 +387,17 @@ void QWebEngineView::showEvent(QShowEvent *event) void QWebEngineView::hideEvent(QHideEvent *event) { QWidget::hideEvent(event); - page()->d_ptr->wasHidden(); + page()->setVisible(false); +} + +/*! + * \reimp + */ +void QWebEngineView::closeEvent(QCloseEvent *event) +{ + QWidget::closeEvent(event); + page()->setVisible(false); + page()->setLifecycleState(QWebEnginePage::LifecycleState::Discarded); } #if QT_CONFIG(draganddrop) diff --git a/src/webenginewidgets/api/qwebengineview.h b/src/webenginewidgets/api/qwebengineview.h index e3cb7ad75..63a68f46c 100644 --- a/src/webenginewidgets/api/qwebengineview.h +++ b/src/webenginewidgets/api/qwebengineview.h @@ -126,6 +126,7 @@ protected: bool event(QEvent*) override; void showEvent(QShowEvent *) override; void hideEvent(QHideEvent *) override; + void closeEvent(QCloseEvent *) override; #if QT_CONFIG(draganddrop) void dragEnterEvent(QDragEnterEvent *e) override; void dragLeaveEvent(QDragLeaveEvent *e) override; |