From a69029cf9fcfd0c1fcdaafe5cbcbff2d5dd6b5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCri=20Valdmann?= Date: Fri, 12 Apr 2019 16:44:18 +0200 Subject: 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 Reviewed-by: Peter Varga --- src/core/devtools_frontend_qt.cpp | 4 + src/core/favicon_manager.cpp | 5 + src/core/favicon_manager.h | 1 + src/core/media_capture_devices_dispatcher.cpp | 51 +++- src/core/render_widget_host_view_qt.cpp | 3 +- src/core/renderer/web_channel_ipc_transport.cpp | 4 +- src/core/web_contents_adapter.cpp | 320 +++++++++++++++++++++--- src/core/web_contents_adapter.h | 30 ++- src/core/web_contents_adapter_client.h | 15 ++ src/core/web_contents_delegate_qt.cpp | 133 ++++++++++ src/core/web_contents_delegate_qt.h | 34 ++- 11 files changed, 559 insertions(+), 41 deletions(-) (limited to 'src/core') diff --git a/src/core/devtools_frontend_qt.cpp b/src/core/devtools_frontend_qt.cpp index 9eedee42a..c2d79331f 100644 --- a/src/core/devtools_frontend_qt.cpp +++ b/src/core/devtools_frontend_qt.cpp @@ -190,6 +190,8 @@ DevToolsFrontendQt *DevToolsFrontendQt::Show(QSharedPointer frontendAdapter->initialize(site.get()); } + frontendAdapter->setInspector(true); + content::WebContents *contents = frontendAdapter->webContents(); if (contents == inspectedContents) { qWarning() << "You can not inspect youself"; @@ -232,6 +234,8 @@ DevToolsFrontendQt::DevToolsFrontendQt(QSharedPointer webCon DevToolsFrontendQt::~DevToolsFrontendQt() { + if (QSharedPointer p = m_webContentsAdapter) + p->setInspector(false); } void DevToolsFrontendQt::Activate() diff --git a/src/core/favicon_manager.cpp b/src/core/favicon_manager.cpp index f7ba858c1..489e23d99 100644 --- a/src/core/favicon_manager.cpp +++ b/src/core/favicon_manager.cpp @@ -362,6 +362,11 @@ void FaviconManager::generateCandidateIcon(bool touchIconsEnabled) } } +void FaviconManager::copyStateFrom(FaviconManager *source) +{ + m_faviconInfoMap = source->m_faviconInfoMap; + m_icons = source->m_icons; +} FaviconInfo::FaviconInfo() : url(QUrl()) diff --git a/src/core/favicon_manager.h b/src/core/favicon_manager.h index 75d6aa75b..df74f6303 100644 --- a/src/core/favicon_manager.h +++ b/src/core/favicon_manager.h @@ -118,6 +118,7 @@ public: QIcon getIcon(const QUrl &url = QUrl()) const; FaviconInfo getFaviconInfo(const QUrl &) const; QList getFaviconInfoList(bool) const; + void copyStateFrom(FaviconManager *source); private: void update(const QList &); diff --git a/src/core/media_capture_devices_dispatcher.cpp b/src/core/media_capture_devices_dispatcher.cpp index ecc46f244..55c0bb39b 100644 --- a/src/core/media_capture_devices_dispatcher.cpp +++ b/src/core/media_capture_devices_dispatcher.cpp @@ -45,6 +45,7 @@ #include "javascript_dialog_manager_qt.h" #include "type_conversion.h" +#include "web_contents_delegate_qt.h" #include "web_contents_view_qt.h" #include "web_engine_settings.h" @@ -166,6 +167,40 @@ WebContentsAdapterClient::MediaRequestFlags mediaRequestFlagsForRequest(const co return requestFlags; } +// Based on MediaStreamCaptureIndicator::UIDelegate +class MediaStreamUIQt : public content::MediaStreamUI +{ +public: + MediaStreamUIQt(content::WebContents *webContents, const blink::MediaStreamDevices &devices) + : m_delegate(static_cast(webContents->GetDelegate())->AsWeakPtr()) + , m_devices(devices) + { + DCHECK(!m_devices.empty()); + } + + ~MediaStreamUIQt() override + { + if (m_started && m_delegate) + m_delegate->removeDevices(m_devices); + } + +private: + gfx::NativeViewId OnStarted(base::OnceClosure, base::RepeatingClosure) override + { + DCHECK(!m_started); + m_started = true; + if (m_delegate) + m_delegate->addDevices(m_devices); + return 0; + } + + base::WeakPtr m_delegate; + const blink::MediaStreamDevices m_devices; + bool m_started = false; + + DISALLOW_COPY_AND_ASSIGN(MediaStreamUIQt); +}; + } // namespace MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(const content::MediaStreamRequest &request, @@ -237,8 +272,12 @@ void MediaCaptureDevicesDispatcher::handleMediaAccessPermissionResponse(content: base::Unretained(this), webContents)); } - std::move(callback).Run(devices, devices.empty() ? blink::MEDIA_DEVICE_INVALID_STATE : blink::MEDIA_DEVICE_OK, - std::unique_ptr()); + if (devices.empty()) + std::move(callback).Run(devices, blink::MEDIA_DEVICE_INVALID_STATE, + std::unique_ptr()); + else + std::move(callback).Run(devices, blink::MEDIA_DEVICE_OK, + std::make_unique(webContents, devices)); } MediaCaptureDevicesDispatcher *MediaCaptureDevicesDispatcher::GetInstance() @@ -336,8 +375,12 @@ void MediaCaptureDevicesDispatcher::processDesktopCaptureAccessRequest(content:: getDevicesForDesktopCapture(&devices, mediaId, capture_audio); - std::move(callback).Run(devices, devices.empty() ? blink::MEDIA_DEVICE_INVALID_STATE : blink::MEDIA_DEVICE_OK, - std::unique_ptr()); + if (devices.empty()) + std::move(callback).Run(devices, blink::MEDIA_DEVICE_INVALID_STATE, + std::unique_ptr()); + else + std::move(callback).Run(devices, blink::MEDIA_DEVICE_OK, + std::make_unique(webContents, devices)); } void MediaCaptureDevicesDispatcher::enqueueMediaAccessRequest(content::WebContents *webContents, diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index 994e3a3d6..091171f90 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -634,7 +634,8 @@ void RenderWidgetHostViewQt::ImeCompositionRangeChanged(const gfx::Range&, const void RenderWidgetHostViewQt::RenderProcessGone(base::TerminationStatus terminationStatus, int exitCode) { - if (m_adapterClient) { + // RenderProcessHost::FastShutdownIfPossible results in TERMINATION_STATUS_STILL_RUNNING + if (m_adapterClient && terminationStatus != base::TERMINATION_STATUS_STILL_RUNNING) { m_adapterClient->renderProcessTerminated( m_adapterClient->renderProcessExitStatus(terminationStatus), exitCode); diff --git a/src/core/renderer/web_channel_ipc_transport.cpp b/src/core/renderer/web_channel_ipc_transport.cpp index 3b9c17b6a..745fe8b1e 100644 --- a/src/core/renderer/web_channel_ipc_transport.cpp +++ b/src/core/renderer/web_channel_ipc_transport.cpp @@ -214,9 +214,11 @@ void WebChannelIPCTransport::ResetWorldId() void WebChannelIPCTransport::DispatchWebChannelMessage(const std::vector &binaryJson, uint32_t worldId) { - DCHECK(m_canUseContext); DCHECK(m_worldId == worldId); + if (!m_canUseContext) + return; + QJsonDocument doc = QJsonDocument::fromRawData(reinterpret_cast(binaryJson.data()), binaryJson.size(), QJsonDocument::BypassValidation); DCHECK(doc.isObject()); diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp index c4f4591e3..3c6e651a7 100644 --- a/src/core/web_contents_adapter.cpp +++ b/src/core/web_contents_adapter.cpp @@ -478,32 +478,7 @@ void WebContentsAdapter::initialize(content::SiteInstance *site) m_webContents = content::WebContents::Create(create_params); } - content::RendererPreferences* rendererPrefs = m_webContents->GetMutableRendererPrefs(); - rendererPrefs->use_custom_colors = true; - // Qt returns a flash time (the whole cycle) in ms, chromium expects just the interval in seconds - const int qtCursorFlashTime = QGuiApplication::styleHints()->cursorFlashTime(); - rendererPrefs->caret_blink_interval = base::TimeDelta::FromMillisecondsD(0.5 * static_cast(qtCursorFlashTime)); - rendererPrefs->user_agent_override = m_profileAdapter->httpUserAgent().toStdString(); - rendererPrefs->accept_languages = m_profileAdapter->httpAcceptLanguageWithoutQualities().toStdString(); -#if QT_CONFIG(webengine_webrtc) - base::CommandLine* commandLine = base::CommandLine::ForCurrentProcess(); - if (commandLine->HasSwitch(switches::kForceWebRtcIPHandlingPolicy)) - rendererPrefs->webrtc_ip_handling_policy = commandLine->GetSwitchValueASCII(switches::kForceWebRtcIPHandlingPolicy); - else - rendererPrefs->webrtc_ip_handling_policy = m_adapterClient->webEngineSettings()->testAttribute(WebEngineSettings::WebRTCPublicInterfacesOnly) - ? content::kWebRTCIPHandlingDefaultPublicInterfaceOnly - : content::kWebRTCIPHandlingDefault; -#endif - // Set web-contents font settings to the default font settings as Chromium constantly overrides - // the global font defaults with the font settings of the latest web-contents created. - static const gfx::FontRenderParams params = gfx::GetFontRenderParams(gfx::FontRenderParamsQuery(), nullptr); - rendererPrefs->should_antialias_text = params.antialiasing; - rendererPrefs->use_subpixel_positioning = params.subpixel_positioning; - rendererPrefs->hinting = params.hinting; - rendererPrefs->use_autohinter = params.autohinter; - rendererPrefs->use_bitmaps = params.use_bitmaps; - rendererPrefs->subpixel_rendering = params.subpixel_rendering; - m_webContents->GetRenderViewHost()->SyncRendererPrefs(); + initializeRenderPrefs(); // Create and attach observers to the WebContents. m_webContentsDelegate.reset(new WebContentsDelegateQt(m_webContents.get(), m_adapterClient)); @@ -540,6 +515,40 @@ void WebContentsAdapter::initialize(content::SiteInstance *site) m_adapterClient->initializationFinished(); } +void WebContentsAdapter::initializeRenderPrefs() +{ + content::RendererPreferences *rendererPrefs = m_webContents->GetMutableRendererPrefs(); + rendererPrefs->use_custom_colors = true; + // Qt returns a flash time (the whole cycle) in ms, chromium expects just the interval in + // seconds + const int qtCursorFlashTime = QGuiApplication::styleHints()->cursorFlashTime(); + rendererPrefs->caret_blink_interval = + base::TimeDelta::FromMillisecondsD(0.5 * static_cast(qtCursorFlashTime)); + rendererPrefs->user_agent_override = m_profileAdapter->httpUserAgent().toStdString(); + rendererPrefs->accept_languages = m_profileAdapter->httpAcceptLanguageWithoutQualities().toStdString(); +#if QT_CONFIG(webengine_webrtc) + base::CommandLine* commandLine = base::CommandLine::ForCurrentProcess(); + if (commandLine->HasSwitch(switches::kForceWebRtcIPHandlingPolicy)) + rendererPrefs->webrtc_ip_handling_policy = + commandLine->GetSwitchValueASCII(switches::kForceWebRtcIPHandlingPolicy); + else + rendererPrefs->webrtc_ip_handling_policy = + m_adapterClient->webEngineSettings()->testAttribute(WebEngineSettings::WebRTCPublicInterfacesOnly) + ? content::kWebRTCIPHandlingDefaultPublicInterfaceOnly + : content::kWebRTCIPHandlingDefault; +#endif + // Set web-contents font settings to the default font settings as Chromium constantly overrides + // the global font defaults with the font settings of the latest web-contents created. + static const gfx::FontRenderParams params = gfx::GetFontRenderParams(gfx::FontRenderParamsQuery(), nullptr); + rendererPrefs->should_antialias_text = params.antialiasing; + rendererPrefs->use_subpixel_positioning = params.subpixel_positioning; + rendererPrefs->hinting = params.hinting; + rendererPrefs->use_autohinter = params.autohinter; + rendererPrefs->use_bitmaps = params.use_bitmaps; + rendererPrefs->subpixel_rendering = params.subpixel_rendering; + m_webContents->GetRenderViewHost()->SyncRendererPrefs(); +} + bool WebContentsAdapter::canGoBack() const { CHECK_INITIALIZED(false); @@ -568,16 +577,22 @@ void WebContentsAdapter::stop() void WebContentsAdapter::reload() { CHECK_INITIALIZED(); + bool wasDiscarded = (m_lifecycleState == LifecycleState::Discarded); + setLifecycleState(LifecycleState::Active); CHECK_VALID_RENDER_WIDGET_HOST_VIEW(m_webContents->GetRenderViewHost()); - m_webContents->GetController().Reload(content::ReloadType::NORMAL, /*checkRepost = */false); + if (!wasDiscarded) // undiscard() already triggers a reload + m_webContents->GetController().Reload(content::ReloadType::NORMAL, /*checkRepost = */false); focusIfNecessary(); } void WebContentsAdapter::reloadAndBypassCache() { CHECK_INITIALIZED(); + bool wasDiscarded = (m_lifecycleState == LifecycleState::Discarded); + setLifecycleState(LifecycleState::Active); CHECK_VALID_RENDER_WIDGET_HOST_VIEW(m_webContents->GetRenderViewHost()); - m_webContents->GetController().Reload(content::ReloadType::BYPASSING_CACHE, /*checkRepost = */false); + if (!wasDiscarded) // undiscard() already triggers a reload + m_webContents->GetController().Reload(content::ReloadType::BYPASSING_CACHE, /*checkRepost = */false); focusIfNecessary(); } @@ -600,6 +615,8 @@ void WebContentsAdapter::load(const QWebEngineHttpRequest &request) scoped_refptr site = content::SiteInstance::CreateForURL(m_profileAdapter->profile(), gurl); initialize(site.get()); + } else { + setLifecycleState(LifecycleState::Active); } CHECK_VALID_RENDER_WIDGET_HOST_VIEW(m_webContents->GetRenderViewHost()); @@ -691,6 +708,8 @@ void WebContentsAdapter::setContent(const QByteArray &data, const QString &mimeT { if (!isInitialized()) loadDefault(); + else + setLifecycleState(LifecycleState::Active); CHECK_VALID_RENDER_WIDGET_HOST_VIEW(m_webContents->GetRenderViewHost()); @@ -1106,7 +1125,7 @@ void WebContentsAdapter::setAudioMuted(bool muted) m_webContents->SetAudioMuted(muted); } -bool WebContentsAdapter::recentlyAudible() +bool WebContentsAdapter::recentlyAudible() const { CHECK_INITIALIZED(false); return m_webContents->IsCurrentlyAudible(); @@ -1167,6 +1186,18 @@ bool WebContentsAdapter::hasInspector() const return false; } +bool WebContentsAdapter::isInspector() const +{ + return m_inspector; +} + +void WebContentsAdapter::setInspector(bool inspector) +{ + m_inspector = inspector; + if (inspector) + setLifecycleState(LifecycleState::Active); +} + void WebContentsAdapter::openDevToolsFrontend(QSharedPointer frontendAdapter) { Q_ASSERT(isInitialized()); @@ -1179,7 +1210,10 @@ void WebContentsAdapter::openDevToolsFrontend(QSharedPointer m_devToolsFrontend->Close(); } + setLifecycleState(LifecycleState::Active); + m_devToolsFrontend = DevToolsFrontendQt::Show(frontendAdapter, m_webContents.get()); + updateRecommendedState(); } void WebContentsAdapter::closeDevToolsFrontend() @@ -1195,6 +1229,7 @@ void WebContentsAdapter::devToolsFrontendDestroyed(DevToolsFrontendQt *frontend) Q_ASSERT(frontend == m_devToolsFrontend); Q_UNUSED(frontend); m_devToolsFrontend = nullptr; + updateRecommendedState(); } void WebContentsAdapter::exitFullScreen() @@ -1654,8 +1689,7 @@ WebContentsAdapterClient::renderProcessExitStatus(int terminationStatus) { break; case base::TERMINATION_STATUS_STILL_RUNNING: case base::TERMINATION_STATUS_MAX_ENUM: - // should be unreachable since Chromium asserts status != TERMINATION_STATUS_STILL_RUNNING - // before calling this method + Q_UNREACHABLE(); break; } @@ -1680,6 +1714,230 @@ bool WebContentsAdapter::canViewSource() return m_webContents->GetController().CanViewSource(); } +WebContentsAdapter::LifecycleState WebContentsAdapter::lifecycleState() const +{ + return m_lifecycleState; +} + +void WebContentsAdapter::setLifecycleState(LifecycleState state) +{ + CHECK_INITIALIZED(); + + LifecycleState from = m_lifecycleState; + LifecycleState to = state; + + const auto warn = [from, to](const char *reason) { + static const char *names[] { "Active", "Frozen", "Discarded" }; + qWarning("setLifecycleState: failed to transition from %s to %s state: %s", + names[(int)from], names[(int)to], reason); + }; + + if (from == to) + return; + + if (from == LifecycleState::Active) { + if (isVisible()) { + warn("page is visible"); + return; + } + if (hasInspector() || isInspector()) { + warn("DevTools open"); + return; + } + } + + if (from == LifecycleState::Discarded && to != LifecycleState::Active) { + warn("illegal transition"); + return; + } + + // Prevent recursion due to initializationFinished() in undiscard(). + m_lifecycleState = to; + + switch (to) { + case LifecycleState::Active: + if (from == LifecycleState::Frozen) + unfreeze(); + else + undiscard(); + break; + case LifecycleState::Frozen: + freeze(); + break; + case LifecycleState::Discarded: + discard(); + break; + } + + m_adapterClient->lifecycleStateChanged(to); + updateRecommendedState(); +} + +WebContentsAdapter::LifecycleState WebContentsAdapter::recommendedState() const +{ + return m_recommendedState; +} + +WebContentsAdapter::LifecycleState WebContentsAdapter::determineRecommendedState() const +{ + CHECK_INITIALIZED(LifecycleState::Active); + + if (m_lifecycleState == LifecycleState::Discarded) + return LifecycleState::Discarded; + + if (isVisible()) + return LifecycleState::Active; + + if (m_webContentsDelegate->loadingState() != WebContentsDelegateQt::LoadingState::Loaded) + return LifecycleState::Active; + + if (recentlyAudible()) + return LifecycleState::Active; + + if (m_webContents->GetSiteInstance()->GetRelatedActiveContentsCount() > 1U) + return LifecycleState::Active; + + if (hasInspector() || isInspector()) + return LifecycleState::Active; + + if (m_webContentsDelegate->isCapturingAudio() || m_webContentsDelegate->isCapturingVideo() + || m_webContentsDelegate->isMirroring() || m_webContentsDelegate->isCapturingDesktop()) + return LifecycleState::Active; + + if (m_webContents->IsCrashed()) + return LifecycleState::Active; + + if (m_lifecycleState == LifecycleState::Active) + return LifecycleState::Frozen; + + // Form input is not saved. + if (m_webContents->GetPageImportanceSignals().had_form_interaction) + return LifecycleState::Frozen; + + // Do not discard PDFs as they might contain entry that is not saved and they + // don't remember their scrolling positions. See crbug.com/547286 and + // crbug.com/65244. + if (m_webContents->GetContentsMimeType() == "application/pdf") + return LifecycleState::Frozen; + + return LifecycleState::Discarded; +} + +void WebContentsAdapter::updateRecommendedState() +{ + LifecycleState newState = determineRecommendedState(); + if (m_recommendedState == newState) + return; + + m_recommendedState = newState; + m_adapterClient->recommendedStateChanged(newState); +} + +bool WebContentsAdapter::isVisible() const +{ + CHECK_INITIALIZED(false); + + // Visibility::OCCLUDED is not used + return m_webContents->GetVisibility() == content::Visibility::VISIBLE; +} + +void WebContentsAdapter::setVisible(bool visible) +{ + CHECK_INITIALIZED(); + + if (isVisible() == visible) + return; + + if (visible) { + setLifecycleState(LifecycleState::Active); + wasShown(); + } else { + Q_ASSERT(m_lifecycleState == LifecycleState::Active); + wasHidden(); + } + + m_adapterClient->visibleChanged(visible); + updateRecommendedState(); +} + +void WebContentsAdapter::freeze() +{ + m_webContents->SetPageFrozen(true); +} + +void WebContentsAdapter::unfreeze() +{ + m_webContents->SetPageFrozen(false); +} + +void WebContentsAdapter::discard() +{ + // Based on TabLifecycleUnitSource::TabLifecycleUnit::FinishDiscard + + if (m_webContents->IsLoading()) { + m_webContentsDelegate->didFailLoad(m_webContentsDelegate->url(), net::Error::ERR_ABORTED, + QStringLiteral("Discarded")); + } + + content::WebContents::CreateParams createParams(m_profileAdapter->profile()); + createParams.initially_hidden = true; + createParams.desired_renderer_state = content::WebContents::CreateParams::kNoRendererProcess; + createParams.last_active_time = m_webContents->GetLastActiveTime(); + std::unique_ptr nullContents = content::WebContents::Create(createParams); + std::unique_ptr nullDelegate(new WebContentsDelegateQt(nullContents.get(), m_adapterClient)); + nullContents->GetController().CopyStateFrom(&m_webContents->GetController(), + /* needs_reload */ false); + nullDelegate->copyStateFrom(m_webContentsDelegate.get()); + nullContents->SetWasDiscarded(true); + + // Kill render process if this is the only page it's got. + content::RenderProcessHost *renderProcessHost = m_webContents->GetMainFrame()->GetProcess(); + renderProcessHost->FastShutdownIfPossible(/* page_count */ 1u, + /* skip_unload_handlers */ false); + +#if QT_CONFIG(webengine_webchannel) + if (m_webChannel) + m_webChannel->disconnectFrom(m_webChannelTransport.get()); + m_webChannelTransport.reset(); + m_webChannel = nullptr; + m_webChannelWorld = 0; +#endif + m_renderViewObserverHost.reset(); + m_webContentsDelegate.reset(); + m_webContents.reset(); + + m_webContents = std::move(nullContents); + initializeRenderPrefs(); + m_webContentsDelegate = std::move(nullDelegate); + m_renderViewObserverHost.reset(new RenderViewObserverHostQt(m_webContents.get(), m_adapterClient)); + WebContentsViewQt *contentsView = + static_cast(static_cast(m_webContents.get())->GetView()); + contentsView->setClient(m_adapterClient); +#if QT_CONFIG(webengine_printing_and_pdf) + PrintViewManagerQt::CreateForWebContents(webContents()); +#endif +#if BUILDFLAG(ENABLE_EXTENSIONS) + extensions::ExtensionWebContentsObserverQt::CreateForWebContents(webContents()); +#endif +} + +void WebContentsAdapter::undiscard() +{ + m_webContents->GetController().SetNeedsReload(); + m_webContents->GetController().LoadIfNecessary(); + // Create a RenderView with the initial empty document + content::RenderViewHost *rvh = m_webContents->GetRenderViewHost(); + Q_ASSERT(rvh); + if (!rvh->IsRenderViewLive()) + static_cast(m_webContents.get()) + ->CreateRenderViewForRenderManager(rvh, MSG_ROUTING_NONE, MSG_ROUTING_NONE, + base::UnguessableToken::Create(), + content::FrameReplicationState()); + m_webContentsDelegate->RenderViewHostChanged(nullptr, rvh); + m_adapterClient->initializationFinished(); + m_adapterClient->selectionChanged(); +} + ASSERT_ENUMS_MATCH(WebContentsAdapterClient::UnknownDisposition, WindowOpenDisposition::UNKNOWN) ASSERT_ENUMS_MATCH(WebContentsAdapterClient::CurrentTabDisposition, WindowOpenDisposition::CURRENT_TAB) ASSERT_ENUMS_MATCH(WebContentsAdapterClient::SingletonTabDisposition, WindowOpenDisposition::SINGLETON_TAB) diff --git a/src/core/web_contents_adapter.h b/src/core/web_contents_adapter.h index 79aa68456..ab9ec5b81 100644 --- a/src/core/web_contents_adapter.h +++ b/src/core/web_contents_adapter.h @@ -109,6 +109,14 @@ public: void load(const QWebEngineHttpRequest &request); void setContent(const QByteArray &data, const QString &mimeType, const QUrl &baseUrl); + using LifecycleState = WebContentsAdapterClient::LifecycleState; + LifecycleState lifecycleState() const; + void setLifecycleState(LifecycleState state); + LifecycleState recommendedState() const; + + bool isVisible() const; + void setVisible(bool visible); + bool canGoBack() const; bool canGoForward() const; void stop(); @@ -155,7 +163,7 @@ public: ReferrerPolicy referrerPolicy = ReferrerPolicy::Default); bool isAudioMuted() const; void setAudioMuted(bool mute); - bool recentlyAudible(); + bool recentlyAudible() const; // Must match blink::WebMediaPlayerAction::Type. enum MediaPlayerAction { @@ -171,6 +179,8 @@ public: void inspectElementAt(const QPoint &location); bool hasInspector() const; + bool isInspector() const; + void setInspector(bool inspector); void exitFullScreen(); void requestClose(); void changedFullScreen(); @@ -178,8 +188,6 @@ public: void closeDevToolsFrontend(); void devToolsFrontendDestroyed(DevToolsFrontendQt *frontend); - void wasShown(); - void wasHidden(); void grantMediaAccessPermission(const QUrl &securityOrigin, WebContentsAdapterClient::MediaRequestFlags flags); void runGeolocationRequestCallback(const QUrl &securityOrigin, bool allowed); void grantMouseLockPermission(bool granted); @@ -221,12 +229,25 @@ public: // meant to be used within WebEngineCore only void initialize(content::SiteInstance *site); content::WebContents *webContents() const; + void updateRecommendedState(); private: Q_DISABLE_COPY(WebContentsAdapter) void waitForUpdateDragActionCalled(); bool handleDropDataFileContents(const content::DropData &dropData, QMimeData *mimeData); + void wasShown(); + void wasHidden(); + + LifecycleState determineRecommendedState() const; + + void freeze(); + void unfreeze(); + void discard(); + void undiscard(); + + void initializeRenderPrefs(); + ProfileAdapter *m_profileAdapter; std::unique_ptr m_webContents; std::unique_ptr m_webContentsDelegate; @@ -246,6 +267,9 @@ private: QPointF m_lastDragScreenPos; std::unique_ptr m_dndTmpDir; DevToolsFrontendQt *m_devToolsFrontend; + LifecycleState m_lifecycleState = LifecycleState::Active; + LifecycleState m_recommendedState = LifecycleState::Active; + bool m_inspector = false; }; } // namespace QtWebEngineCore diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h index 7ba45aea8..780c14466 100644 --- a/src/core/web_contents_adapter_client.h +++ b/src/core/web_contents_adapter_client.h @@ -412,11 +412,26 @@ public: }; Q_DECLARE_FLAGS(MediaRequestFlags, MediaRequestFlag) + enum class LifecycleState { + Active, + Frozen, + Discarded, + }; + + enum class LoadingState { + Unloaded, + Loading, + Loaded, + }; + virtual ~WebContentsAdapterClient() { } virtual RenderWidgetHostViewQtDelegate* CreateRenderWidgetHostViewQtDelegate(RenderWidgetHostViewQtDelegateClient *client) = 0; virtual RenderWidgetHostViewQtDelegate* CreateRenderWidgetHostViewQtDelegateForPopup(RenderWidgetHostViewQtDelegateClient *client) = 0; virtual void initializationFinished() = 0; + virtual void lifecycleStateChanged(LifecycleState) = 0; + virtual void recommendedStateChanged(LifecycleState) = 0; + virtual void visibleChanged(bool) = 0; virtual void titleChanged(const QString&) = 0; virtual void urlChanged(const QUrl&) = 0; virtual void iconChanged(const QUrl&) = 0; diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp index 021044a71..f4d794de5 100644 --- a/src/core/web_contents_delegate_qt.cpp +++ b/src/core/web_contents_delegate_qt.cpp @@ -104,6 +104,8 @@ WebContentsDelegateQt::WebContentsDelegateQt(content::WebContents *webContents, , m_lastReceivedFindReply(0) , m_faviconManager(new FaviconManager(webContents, adapterClient)) , m_lastLoadProgress(-1) + , m_loadingState(determineLoadingState(webContents)) + , m_didStartLoadingSeen(m_loadingState == LoadingState::Loading) { webContents->SetDelegate(this); Observe(webContents); @@ -267,6 +269,18 @@ void WebContentsDelegateQt::RenderFrameDeleted(content::RenderFrameHost *render_ m_loadingErrorFrameList.removeOne(render_frame_host->GetRoutingID()); } +void WebContentsDelegateQt::RenderProcessGone(base::TerminationStatus status) +{ + // Based one TabLoadTracker::RenderProcessGone + + if (status == base::TerminationStatus::TERMINATION_STATUS_NORMAL_TERMINATION + || status == base::TerminationStatus::TERMINATION_STATUS_STILL_RUNNING) { + return; + } + + setLoadingState(LoadingState::Unloaded); +} + void WebContentsDelegateQt::RenderViewHostChanged(content::RenderViewHost *, content::RenderViewHost *newHost) { if (newHost && newHost->GetWidget() && newHost->GetWidget()->GetView()) { @@ -354,6 +368,46 @@ void WebContentsDelegateQt::DidFinishNavigation(content::NavigationHandle *navig } } +void WebContentsDelegateQt::DidStartLoading() +{ + // Based on TabLoadTracker::DidStartLoading + + if (!web_contents()->IsLoadingToDifferentDocument()) + return; + if (m_loadingState == LoadingState::Loading) { + DCHECK(m_didStartLoadingSeen); + return; + } + m_didStartLoadingSeen = true; +} + +void WebContentsDelegateQt::DidReceiveResponse() +{ + // Based on TabLoadTracker::DidReceiveResponse + + if (m_loadingState == LoadingState::Loading) { + DCHECK(m_didStartLoadingSeen); + return; + } + + // A transition to loading requires both DidStartLoading (navigation + // committed) and DidReceiveResponse (data has been transmitted over the + // network) events to occur. This is because NavigationThrottles can block + // actual network requests, but not the rest of the state machinery. + if (m_didStartLoadingSeen) + setLoadingState(LoadingState::Loading); +} + +void WebContentsDelegateQt::DidStopLoading() +{ + // Based on TabLoadTracker::DidStopLoading + + // NOTE: PageAlmostIdle feature not implemented + + if (m_loadingState == LoadingState::Loading) + setLoadingState(LoadingState::Loaded); +} + void WebContentsDelegateQt::didFailLoad(const QUrl &url, int errorCode, const QString &errorDescription) { m_viewClient->iconChanged(QUrl()); @@ -362,6 +416,9 @@ void WebContentsDelegateQt::didFailLoad(const QUrl &url, int errorCode, const QS void WebContentsDelegateQt::DidFailLoad(content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, const base::string16& error_description) { + if (m_loadingState == LoadingState::Loading) + setLoadingState(LoadingState::Loaded); + if (render_frame_host != web_contents()->GetMainFrame()) return; @@ -724,4 +781,80 @@ WebContentsAdapter *WebContentsDelegateQt::webContentsAdapter() const return m_viewClient->webContentsAdapter(); } +void WebContentsDelegateQt::copyStateFrom(WebContentsDelegateQt *source) +{ + m_url = source->m_url; + m_title = source->m_title; + NavigationStateChanged(web_contents(), content::INVALIDATE_TYPE_URL); + m_faviconManager->copyStateFrom(source->m_faviconManager.data()); +} + +WebContentsDelegateQt::LoadingState WebContentsDelegateQt::determineLoadingState(content::WebContents *contents) +{ + // Based on TabLoadTracker::DetermineLoadingState + + if (contents->IsLoadingToDifferentDocument() && !contents->IsWaitingForResponse()) + return LoadingState::Loading; + + content::NavigationController &controller = contents->GetController(); + if (controller.GetLastCommittedEntry() != nullptr && !controller.IsInitialNavigation() && !controller.NeedsReload()) + return LoadingState::Loaded; + + return LoadingState::Unloaded; +} + +void WebContentsDelegateQt::setLoadingState(LoadingState state) +{ + if (m_loadingState == state) + return; + + m_loadingState = state; + + webContentsAdapter()->updateRecommendedState(); +} + +int &WebContentsDelegateQt::streamCount(blink::MediaStreamType type) +{ + // Based on MediaStreamCaptureIndicator::WebContentsDeviceUsage::GetStreamCount + switch (type) { + case blink::MEDIA_DEVICE_AUDIO_CAPTURE: + return m_audioStreamCount; + + case blink::MEDIA_DEVICE_VIDEO_CAPTURE: + return m_videoStreamCount; + + case blink::MEDIA_GUM_TAB_AUDIO_CAPTURE: + case blink::MEDIA_GUM_TAB_VIDEO_CAPTURE: + return m_mirroringStreamCount; + + case blink::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE: + case blink::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE: + case blink::MEDIA_DISPLAY_VIDEO_CAPTURE: + return m_desktopStreamCount; + + case blink::MEDIA_NO_SERVICE: + case blink::NUM_MEDIA_TYPES: + NOTREACHED(); + return m_videoStreamCount; + } + NOTREACHED(); + return m_videoStreamCount; +} + +void WebContentsDelegateQt::addDevices(const blink::MediaStreamDevices &devices) +{ + for (const auto &device : devices) + ++streamCount(device.type); + + webContentsAdapter()->updateRecommendedState(); +} + +void WebContentsDelegateQt::removeDevices(const blink::MediaStreamDevices &devices) +{ + for (const auto &device : devices) + ++streamCount(device.type); + + webContentsAdapter()->updateRecommendedState(); +} + } // namespace QtWebEngineCore diff --git a/src/core/web_contents_delegate_qt.h b/src/core/web_contents_delegate_qt.h index 1629222c2..2ef4f22fc 100644 --- a/src/core/web_contents_delegate_qt.h +++ b/src/core/web_contents_delegate_qt.h @@ -40,6 +40,7 @@ #ifndef WEB_CONTENTS_DELEGATE_QT_H #define WEB_CONTENTS_DELEGATE_QT_H +#include "content/public/browser/media_capture_devices.h" #include "content/public/browser/web_contents_delegate.h" #include "content/public/browser/web_contents_observer.h" #include "third_party/skia/include/core/SkColor.h" @@ -133,9 +134,13 @@ public: // WebContentsObserver overrides void RenderFrameDeleted(content::RenderFrameHost *render_frame_host) override; + void RenderProcessGone(base::TerminationStatus status) override; void RenderViewHostChanged(content::RenderViewHost *old_host, content::RenderViewHost *new_host) override; void DidStartNavigation(content::NavigationHandle *navigation_handle) override; void DidFinishNavigation(content::NavigationHandle *navigation_handle) override; + void DidStartLoading() override; + void DidReceiveResponse() override; + void DidStopLoading() override; void DidFailLoad(content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, const base::string16& error_description) override; void DidFinishLoad(content::RenderFrameHost *render_frame_host, const GURL &validated_url) override; void BeforeUnloadFired(bool proceed, const base::TimeTicks& proceed_time) override; @@ -160,12 +165,32 @@ public: WebContentsAdapter *webContentsAdapter() const; WebContentsAdapterClient *adapterClient() const { return m_viewClient; } + void copyStateFrom(WebContentsDelegateQt *source); + + using LoadingState = WebContentsAdapterClient::LoadingState; + LoadingState loadingState() const { return m_loadingState; } + + void addDevices(const blink::MediaStreamDevices &devices); + void removeDevices(const blink::MediaStreamDevices &devices); + + bool isCapturingAudio() const { return m_audioStreamCount > 0; } + bool isCapturingVideo() const { return m_videoStreamCount > 0; } + bool isMirroring() const { return m_mirroringStreamCount > 0; } + bool isCapturingDesktop() const { return m_desktopStreamCount > 0; } + + base::WeakPtr AsWeakPtr() { return m_weakPtrFactory.GetWeakPtr(); } + private: QWeakPointer createWindow(std::unique_ptr new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture); void EmitLoadStarted(const QUrl &url, bool isErrorPage = false); void EmitLoadFinished(bool success, const QUrl &url, bool isErrorPage = false, int errorCode = 0, const QString &errorDescription = QString()); void EmitLoadCommitted(); + LoadingState determineLoadingState(content::WebContents *contents); + void setLoadingState(LoadingState state); + + int &streamCount(blink::MediaStreamType type); + WebContentsAdapterClient *m_viewClient; QString m_lastSearchedString; int m_lastReceivedFindReply; @@ -175,9 +200,16 @@ private: QSharedPointer m_filePickerController; QUrl m_initialTargetUrl; int m_lastLoadProgress; - + LoadingState m_loadingState; + bool m_didStartLoadingSeen; QUrl m_url; QString m_title; + int m_audioStreamCount = 0; + int m_videoStreamCount = 0; + int m_mirroringStreamCount = 0; + int m_desktopStreamCount = 0; + + base::WeakPtrFactory m_weakPtrFactory { this }; }; } // namespace QtWebEngineCore -- cgit v1.2.3