From b0505eb642ebdb04aeefb06afe54c3ed80fe0d75 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 1 Jun 2026 23:27:59 +0400 Subject: [PATCH] Use correct message view as host of media. --- .../history/view/history_view_element.cpp | 21 ++- .../history/view/history_view_message.cpp | 5 + Telegram/SourceFiles/iv/iv_cached_media.cpp | 46 +++++ Telegram/SourceFiles/iv/iv_cached_media.h | 11 ++ .../iv/markdown/iv_markdown_article.cpp | 33 ++++ .../iv/markdown/iv_markdown_article.h | 2 + .../iv_markdown_history_view_media.cpp | 161 +++++++++++------- .../markdown/iv_markdown_history_view_media.h | 9 +- .../iv/markdown/iv_markdown_media_block.cpp | 14 ++ .../iv/markdown/iv_markdown_media_block.h | 3 + 10 files changed, 241 insertions(+), 64 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 874001c6e7..5cf131adda 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1841,7 +1841,9 @@ auto Element::contextDependentServiceText() -> TextWithLinks { void Element::validateText() { const auto clearRichPage = [&] { if (Has()) { - RemoveComponents(HistoryMessageRichPage::Bit()); + RemoveComponents(0 + | HistoryMessageRichPage::Bit() + | InstantViewMediaRuntime::Bit()); invalidateTextSizeCache(); } }; @@ -1853,7 +1855,9 @@ void Element::validateText() { return; } if (!Has()) { - AddComponents(HistoryMessageRichPage::Bit()); + AddComponents(0 + | HistoryMessageRichPage::Bit() + | InstantViewMediaRuntime::Bit()); } const auto runtime = Get(); const auto needsBinding = (runtime->article.mediaBlockHost() @@ -1898,7 +1902,7 @@ void Element::validateText() { runtime->page = std::move(page); runtime->mediaRuntime = Iv::CreateMessageMediaRuntime( session, - data()->fullId(), + not_null{ this }, [](QString) {}, // openChannel [](QString) {}); // joinChannel auto prepared = Iv::Markdown::TryPrepareNativeInstantView({ @@ -2566,8 +2570,10 @@ auto Element::verticalRepaintRange() const -> VerticalRepaintRange { } bool Element::hasHeavyPart() const { + const auto rich = richpage(); return (_flags & Flag::HeavyCustomEmoji) - || (_media && _media->hasHeavyPart()); + || (_media && _media->hasHeavyPart()) + || (rich && rich->article.hasHeavyPart()); } void Element::checkHeavyPart() { @@ -2738,6 +2744,9 @@ void Element::unloadHeavyPart() { if (_media) { _media->unloadHeavyPart(); } + if (const auto rich = richpage()) { + rich->article.unloadHeavyPart(); + } if (_flags & Flag::HeavyCustomEmoji) { _flags &= ~Flag::HeavyCustomEmoji; _text.unloadPersistentAnimation(); @@ -3164,6 +3173,10 @@ Element::~Element() { _text.unloadPersistentAnimation(); checkHeavyPart(); } + if (const auto rich = richpage(); rich && rich->article.hasHeavyPart()) { + rich->article.unloadHeavyPart(); + checkHeavyPart(); + } if (_data->mainView() == this) { _data->clearMainView(); } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index e4a4a446c7..dbb00685c3 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -581,6 +581,11 @@ HistoryMessageRichPage::HistoryMessageRichPage() } void Message::setInstantViewMediaRuntime(QString pageUrl) { + Expects(Has() + || !Has()); + // We mustn't add the component here if we're in rich message, + // because we call this method where we remember RichPage reference. + AddComponents(InstantViewMediaRuntime::Bit()); Get()->pageUrl = std::move(pageUrl); } diff --git a/Telegram/SourceFiles/iv/iv_cached_media.cpp b/Telegram/SourceFiles/iv/iv_cached_media.cpp index e5f3b909e9..5f19648317 100644 --- a/Telegram/SourceFiles/iv/iv_cached_media.cpp +++ b/Telegram/SourceFiles/iv/iv_cached_media.cpp @@ -927,6 +927,11 @@ public: FullMsgId itemId, Fn openChannel, Fn joinChannel); + CachedPageMediaRuntime( + not_null session, + not_null view, + Fn openChannel, + Fn joinChannel); [[nodiscard]] std::shared_ptr resolveInlineImage( uint64 documentId, @@ -972,6 +977,8 @@ private: const ::Data::FileOrigin _origin; const FullMsgId _itemId; const QString _pageUrl; + const bool _useExistingView = false; + const base::weak_ptr _view; const Fn _openChannel; const Fn _joinChannel; mutable std::shared_ptr _hostedMediaHost; @@ -1006,6 +1013,20 @@ CachedPageMediaRuntime::CachedPageMediaRuntime( , _joinChannel(std::move(joinChannel)) { } +CachedPageMediaRuntime::CachedPageMediaRuntime( + not_null session, + not_null view, + Fn openChannel, + Fn joinChannel) +: _session(session) +, _origin(view->data()->fullId()) +, _itemId(view->data()->fullId()) +, _useExistingView(true) +, _view(base::make_weak(view.get())) +, _openChannel(std::move(openChannel)) +, _joinChannel(std::move(joinChannel)) { +} + std::shared_ptr CachedPageMediaRuntime::resolveInlineImage( uint64 documentId, QSize size) const { @@ -1094,6 +1115,18 @@ QString CachedPageMediaRuntime::mentionNameEntityData(uint64 userId) const { auto CachedPageMediaRuntime::hostedMediaHost( not_null controller) const -> std::shared_ptr { + if (_useExistingView) { + const auto view = _view.get(); + if (!view) { + return nullptr; + } + if (!_hostedMediaHost) { + _hostedMediaHost + = std::make_shared( + not_null{ view }); + } + return _hostedMediaHost; + } if (_itemId) { const auto item = _session->data().message(_itemId); if (!item) { @@ -1337,4 +1370,17 @@ auto CreateMessageMediaRuntime( std::move(joinChannel)); } +auto CreateMessageMediaRuntime( + not_null session, + not_null view, + Fn openChannel, + Fn joinChannel) +-> std::shared_ptr { + return std::make_shared( + session, + view, + std::move(openChannel), + std::move(joinChannel)); +} + } // namespace Iv diff --git a/Telegram/SourceFiles/iv/iv_cached_media.h b/Telegram/SourceFiles/iv/iv_cached_media.h index 4dd77f854e..97e5dacd87 100644 --- a/Telegram/SourceFiles/iv/iv_cached_media.h +++ b/Telegram/SourceFiles/iv/iv_cached_media.h @@ -16,6 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL struct WebPageData; +namespace HistoryView { +class Element; +} // namespace HistoryView + namespace Main { class Session; } // namespace Main @@ -40,4 +44,11 @@ namespace Iv { Fn joinChannel) -> std::shared_ptr; +[[nodiscard]] auto CreateMessageMediaRuntime( + not_null session, + not_null view, + Fn openChannel, + Fn joinChannel) +-> std::shared_ptr; + } // namespace Iv diff --git a/Telegram/SourceFiles/iv/markdown/iv_markdown_article.cpp b/Telegram/SourceFiles/iv/markdown/iv_markdown_article.cpp index 33a94db7ca..53e1c64462 100644 --- a/Telegram/SourceFiles/iv/markdown/iv_markdown_article.cpp +++ b/Telegram/SourceFiles/iv/markdown/iv_markdown_article.cpp @@ -1785,6 +1785,10 @@ public: void invalidateRasterCache(); + [[nodiscard]] bool hasHeavyPart() const; + + void unloadHeavyPart(); + [[nodiscard]] MediaBlockHost *mediaBlockHost() const; void setPlaceholderLoading(PreparedPlaceholderBlockId id); @@ -1990,6 +1994,9 @@ void MarkdownArticle::Impl::setTextRepaintCallbacks( } void MarkdownArticle::Impl::setContent(MarkdownArticleContent content) { + if (hasHeavyPart()) { + unloadHeavyPart(); + } auto reusedMediaBlocks = MediaBlockStorage(); const auto reuseMediaBlocks = (_content.mediaRuntime == content.mediaRuntime); if (reuseMediaBlocks) { @@ -2435,6 +2442,24 @@ void MarkdownArticle::Impl::invalidateRasterCache() { ClearColorizedFormulaImages(&_blocks); } +bool MarkdownArticle::Impl::hasHeavyPart() const { + for (const auto &entry : _mediaBlocks) { + const auto &block = entry.second; + if (block && block->hasHeavyPart()) { + return true; + } + } + return false; +} + +void MarkdownArticle::Impl::unloadHeavyPart() { + for (const auto &entry : _mediaBlocks) { + if (const auto &block = entry.second) { + block->unloadHeavyPart(); + } + } +} + MediaBlockHost *MarkdownArticle::Impl::mediaBlockHost() const { return _mediaBlockHost; } @@ -3613,6 +3638,14 @@ void MarkdownArticle::invalidateRasterCache() { _impl->invalidateRasterCache(); } +bool MarkdownArticle::hasHeavyPart() const { + return _impl->hasHeavyPart(); +} + +void MarkdownArticle::unloadHeavyPart() { + _impl->unloadHeavyPart(); +} + MediaBlockHost *MarkdownArticle::mediaBlockHost() const { return _impl->mediaBlockHost(); } diff --git a/Telegram/SourceFiles/iv/markdown/iv_markdown_article.h b/Telegram/SourceFiles/iv/markdown/iv_markdown_article.h index edfee5efb7..daaf55c47e 100644 --- a/Telegram/SourceFiles/iv/markdown/iv_markdown_article.h +++ b/Telegram/SourceFiles/iv/markdown/iv_markdown_article.h @@ -348,6 +348,8 @@ public: Spellchecker::HighlightProcessId processId); void invalidatePaletteCache(); void invalidateRasterCache(); + [[nodiscard]] bool hasHeavyPart() const; + void unloadHeavyPart(); [[nodiscard]] MediaBlockHost *mediaBlockHost() const; void setPlaceholderLoading(PreparedPlaceholderBlockId id); void clearPlaceholderLoading(PreparedPlaceholderBlockId id); diff --git a/Telegram/SourceFiles/iv/markdown/iv_markdown_history_view_media.cpp b/Telegram/SourceFiles/iv/markdown/iv_markdown_history_view_media.cpp index 595932e7ff..1fd165c685 100644 --- a/Telegram/SourceFiles/iv/markdown/iv_markdown_history_view_media.cpp +++ b/Telegram/SourceFiles/iv/markdown/iv_markdown_history_view_media.cpp @@ -189,6 +189,10 @@ public: [[nodiscard]] MediaBlockSelectionData selectionData() const override; + [[nodiscard]] bool hasHeavyPart() const override; + + void unloadHeavyPart() override; + private: [[nodiscard]] IvHistoryViewHit resolveHit(QPoint point) const; @@ -204,11 +208,7 @@ private: [[nodiscard]] bool supportsHitClassification(); - void handleViewRepaint(QRect rect); - - void handleItemRepaint(); - - void handleViewResize(); + void hostUpdated() override; const uint64 _stableId = 0; const IvHistoryViewMediaKind _kind = IvHistoryViewMediaKind::Map; @@ -218,12 +218,11 @@ private: const std::shared_ptr _documentRuntime; const std::shared_ptr _host; const std::vector> _keepAlive; - const not_null<::Data::Session*> _session; std::unique_ptr _media; - rpl::lifetime _lifetime; QRect _geometry; int _requestedWidth = 0; bool _supported = false; + MediaBlockHost *_registeredBridgeHost = nullptr; }; IvHistoryViewBlock::IvHistoryViewBlock( @@ -235,8 +234,7 @@ IvHistoryViewBlock::IvHistoryViewBlock( , _photoRuntime(std::move(descriptor.photo)) , _documentRuntime(std::move(descriptor.document)) , _host(std::move(descriptor.host)) -, _keepAlive(std::move(descriptor.keepAlive)) -, _session(_host->session()) { +, _keepAlive(std::move(descriptor.keepAlive)) { if (descriptor.mediaFactory) { _media = descriptor.mediaFactory(_host->view()); } @@ -244,30 +242,6 @@ IvHistoryViewBlock::IvHistoryViewBlock( _media->initDimensions(); } _supported = _media && probeSupport(); - _session->itemRepaintRequest( - ) | rpl::filter([=](not_null item) { - return (item == _host->item()); - }) | rpl::on_next([=](not_null) { - handleItemRepaint(); - }, _lifetime); - _session->itemResizeRequest( - ) | rpl::filter([=](not_null item) { - return (item == _host->item()); - }) | rpl::on_next([=](not_null) { - handleViewResize(); - }, _lifetime); - _session->viewRepaintRequest( - ) | rpl::filter([=](::Data::RequestViewRepaint data) { - return (data.view == _host->view()); - }) | rpl::on_next([=](::Data::RequestViewRepaint data) { - handleViewRepaint(data.rect); - }, _lifetime); - _session->viewResizeRequest( - ) | rpl::filter([=](not_null view) { - return (view == _host->view()); - }) | rpl::on_next([=](not_null) { - handleViewResize(); - }, _lifetime); } uint64 IvHistoryViewBlock::stableId() const { @@ -339,6 +313,20 @@ MediaBlockSelectionData IvHistoryViewBlock::selectionData() const { }; } +bool IvHistoryViewBlock::hasHeavyPart() const { + return _media && _media->hasHeavyPart(); +} + +void IvHistoryViewBlock::unloadHeavyPart() { + const auto had = hasHeavyPart(); + if (_media) { + _media->unloadHeavyPart(); + } + if (had) { + _host->view()->checkHeavyPart(); + } +} + IvHistoryViewHit IvHistoryViewBlock::resolveHit(QPoint point) const { auto result = IvHistoryViewHit(); if (!_supported || !_media || !_geometry.contains(point)) { @@ -466,30 +454,17 @@ bool IvHistoryViewBlock::supportsHitClassification() { return true; } -void IvHistoryViewBlock::handleViewRepaint(QRect rect) { - Q_UNUSED(rect); - requestRepaint(QRect()); -} - -void IvHistoryViewBlock::handleItemRepaint() { - requestRepaint(QRect()); -} - -void IvHistoryViewBlock::handleViewResize() { - if (!_media) { +void IvHistoryViewBlock::hostUpdated() { + const auto current = host(); + if (_registeredBridgeHost == current) { return; } - const auto previous = _media->currentSize(); - if (_requestedWidth > 0) { - _media->resizeGetHeight(_requestedWidth); + if (_registeredBridgeHost) { + _host->unregisterViewRequestBridge(_registeredBridgeHost); } - if (_geometry.isEmpty()) { - return; - } - if (_media->currentSize() != previous) { - requestRelayout(_geometry); - } else { - requestRepaint(QRect()); + _registeredBridgeHost = current; + if (_registeredBridgeHost) { + _host->registerViewRequestBridge(_registeredBridgeHost); } } @@ -503,6 +478,7 @@ struct IvHistoryViewMediaHost::State { State( not_null controller, not_null item); + explicit State(not_null view); const not_null<::Data::Session*> session; const QString pageUrl; @@ -510,7 +486,11 @@ struct IvHistoryViewMediaHost::State { const not_null item; AdminLog::OwnedItem owned; std::unique_ptr realView; - HistoryView::Message *view = nullptr; + HistoryView::Element *view = nullptr; + bool needsViewRequestBridge = true; + MediaBlockHost *bridgeHost = nullptr; + int bridgeHostReferences = 0; + rpl::lifetime bridgeLifetime; }; IvHistoryViewMediaHost::State::State( @@ -530,7 +510,8 @@ IvHistoryViewMediaHost::State::State( , item(CreateIvHostMessage(history, this->pageUrl)) , owned(delegate.get(), item) , view(static_cast(owned.get())) { - view->setInstantViewMediaRuntime(this->pageUrl); + static_cast(view)->setInstantViewMediaRuntime( + this->pageUrl); } IvHistoryViewMediaHost::State::State( @@ -548,7 +529,16 @@ IvHistoryViewMediaHost::State::State( , item(item) , realView(this->item->createView(delegate.get())) , view(static_cast(realView.get())) { - view->setInstantViewMediaRuntime(this->pageUrl); + static_cast(view)->setInstantViewMediaRuntime( + this->pageUrl); +} + +IvHistoryViewMediaHost::State::State( + not_null view) +: session(&view->history()->owner()) +, item(view->data()) +, view(view.get()) +, needsViewRequestBridge(false) { } IvHistoryViewMediaHost::IvHistoryViewMediaHost( @@ -567,6 +557,11 @@ IvHistoryViewMediaHost::IvHistoryViewMediaHost( : _state(std::make_unique(controller, item)) { } +IvHistoryViewMediaHost::IvHistoryViewMediaHost( + not_null view) +: _state(std::make_unique(view)) { +} + IvHistoryViewMediaHost::~IvHistoryViewMediaHost() = default; not_null<::Data::Session*> IvHistoryViewMediaHost::session() const { @@ -577,14 +572,62 @@ not_null IvHistoryViewMediaHost::item() const { return _state->item; } -not_null IvHistoryViewMediaHost::view() const { - return not_null{ _state->view }; +not_null IvHistoryViewMediaHost::view() const { + return not_null{ _state->view }; } const QString &IvHistoryViewMediaHost::pageUrl() const { return _state->pageUrl; } +bool IvHistoryViewMediaHost::needsViewRequestBridge() const { + return _state->needsViewRequestBridge; +} + +void IvHistoryViewMediaHost::registerViewRequestBridge(MediaBlockHost *host) { + if (!host || !_state->needsViewRequestBridge) { + return; + } + if (_state->bridgeHost == host) { + ++_state->bridgeHostReferences; + return; + } + _state->bridgeLifetime.destroy(); + _state->bridgeHost = host; + _state->bridgeHostReferences = 1; + _state->session->viewRepaintRequest( + ) | rpl::filter([=](::Data::RequestViewRepaint data) { + return (data.view == _state->view); + }) | rpl::on_next([=](::Data::RequestViewRepaint) { + if (_state->bridgeHost) { + _state->bridgeHost->requestRepaint(QRect()); + } + }, _state->bridgeLifetime); + _state->session->viewResizeRequest( + ) | rpl::filter([=](not_null view) { + return (view == _state->view); + }) | rpl::on_next([=](not_null) { + if (_state->bridgeHost) { + _state->bridgeHost->requestRelayout(QRect()); + } + }, _state->bridgeLifetime); +} + +void IvHistoryViewMediaHost::unregisterViewRequestBridge(MediaBlockHost *host) { + if (!host + || !_state->needsViewRequestBridge + || _state->bridgeHost != host) { + return; + } + --_state->bridgeHostReferences; + if (_state->bridgeHostReferences > 0) { + return; + } + _state->bridgeHostReferences = 0; + _state->bridgeHost = nullptr; + _state->bridgeLifetime.destroy(); +} + void IvHistoryViewMediaHost::registerPhoto(not_null photo) const { _state->item->addPhotoForInstantView(photo); } diff --git a/Telegram/SourceFiles/iv/markdown/iv_markdown_history_view_media.h b/Telegram/SourceFiles/iv/markdown/iv_markdown_history_view_media.h index ef32103954..07b674230b 100644 --- a/Telegram/SourceFiles/iv/markdown/iv_markdown_history_view_media.h +++ b/Telegram/SourceFiles/iv/markdown/iv_markdown_history_view_media.h @@ -35,6 +35,8 @@ class Session; namespace Iv::Markdown { +class MediaBlockHost; + class IvHistoryViewMediaHost final { public: IvHistoryViewMediaHost( @@ -44,12 +46,17 @@ public: IvHistoryViewMediaHost( not_null controller, not_null item); + explicit IvHistoryViewMediaHost( + not_null view); ~IvHistoryViewMediaHost(); [[nodiscard]] not_null<::Data::Session*> session() const; [[nodiscard]] not_null item() const; - [[nodiscard]] not_null view() const; + [[nodiscard]] not_null view() const; [[nodiscard]] const QString &pageUrl() const; + [[nodiscard]] bool needsViewRequestBridge() const; + void registerViewRequestBridge(MediaBlockHost *host); + void unregisterViewRequestBridge(MediaBlockHost *host); void registerPhoto(not_null photo) const; void registerDocument(not_null document) const; diff --git a/Telegram/SourceFiles/iv/markdown/iv_markdown_media_block.cpp b/Telegram/SourceFiles/iv/markdown/iv_markdown_media_block.cpp index 06a56b429c..7cf1c928b4 100644 --- a/Telegram/SourceFiles/iv/markdown/iv_markdown_media_block.cpp +++ b/Telegram/SourceFiles/iv/markdown/iv_markdown_media_block.cpp @@ -1770,13 +1770,24 @@ const GroupedMediaBlock::ItemState *GroupedMediaBlock::activeItem() const { MediaBlock::~MediaBlock() = default; void MediaBlock::setHost(MediaBlockHost *host) { + if (_host == host) { + return; + } _host = host; + hostUpdated(); } MediaBlockHost *MediaBlock::host() const { return _host; } +bool MediaBlock::hasHeavyPart() const { + return false; +} + +void MediaBlock::unloadHeavyPart() { +} + void MediaBlock::setLayoutStyle(const style::Markdown &st) { if (_st == &st) { return; @@ -1804,6 +1815,9 @@ void MediaBlock::requestRelayout(QRect articleRect) const { void MediaBlock::layoutStyleUpdated() { } +void MediaBlock::hostUpdated() { +} + std::shared_ptr CreatePhotoMediaBlock( const PreparedPhotoBlockData &prepared, const std::shared_ptr &mediaRuntime, diff --git a/Telegram/SourceFiles/iv/markdown/iv_markdown_media_block.h b/Telegram/SourceFiles/iv/markdown/iv_markdown_media_block.h index c7ac39209a..76c0ffc9ba 100644 --- a/Telegram/SourceFiles/iv/markdown/iv_markdown_media_block.h +++ b/Telegram/SourceFiles/iv/markdown/iv_markdown_media_block.h @@ -65,12 +65,15 @@ public: [[nodiscard]] virtual ClickHandlerPtr linkAt(QPoint point) const = 0; [[nodiscard]] virtual MediaActivation activationAt(QPoint point) const = 0; [[nodiscard]] virtual MediaBlockSelectionData selectionData() const = 0; + [[nodiscard]] virtual bool hasHeavyPart() const; + virtual void unloadHeavyPart(); protected: void requestRepaint(QRect articleRect) const; void requestRelayout(QRect articleRect) const; [[nodiscard]] const style::Markdown &layoutStyle() const; virtual void layoutStyleUpdated(); + virtual void hostUpdated(); private: MediaBlockHost *_host = nullptr;