Use correct message view as host of media.

This commit is contained in:
John Preston
2026-06-01 23:27:59 +04:00
parent 43c4a2ea60
commit b0505eb642
10 changed files with 241 additions and 64 deletions
@@ -1841,7 +1841,9 @@ auto Element::contextDependentServiceText() -> TextWithLinks {
void Element::validateText() {
const auto clearRichPage = [&] {
if (Has<HistoryMessageRichPage>()) {
RemoveComponents(HistoryMessageRichPage::Bit());
RemoveComponents(0
| HistoryMessageRichPage::Bit()
| InstantViewMediaRuntime::Bit());
invalidateTextSizeCache();
}
};
@@ -1853,7 +1855,9 @@ void Element::validateText() {
return;
}
if (!Has<HistoryMessageRichPage>()) {
AddComponents(HistoryMessageRichPage::Bit());
AddComponents(0
| HistoryMessageRichPage::Bit()
| InstantViewMediaRuntime::Bit());
}
const auto runtime = Get<HistoryMessageRichPage>();
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<Element*>{ 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();
}
@@ -581,6 +581,11 @@ HistoryMessageRichPage::HistoryMessageRichPage()
}
void Message::setInstantViewMediaRuntime(QString pageUrl) {
Expects(Has<InstantViewMediaRuntime>()
|| !Has<HistoryMessageRichPage>());
// 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<InstantViewMediaRuntime>()->pageUrl = std::move(pageUrl);
}
@@ -927,6 +927,11 @@ public:
FullMsgId itemId,
Fn<void(QString)> openChannel,
Fn<void(QString)> joinChannel);
CachedPageMediaRuntime(
not_null<Main::Session*> session,
not_null<HistoryView::Element*> view,
Fn<void(QString)> openChannel,
Fn<void(QString)> joinChannel);
[[nodiscard]] std::shared_ptr<Ui::DynamicImage> 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<HistoryView::Element> _view;
const Fn<void(QString)> _openChannel;
const Fn<void(QString)> _joinChannel;
mutable std::shared_ptr<Markdown::IvHistoryViewMediaHost> _hostedMediaHost;
@@ -1006,6 +1013,20 @@ CachedPageMediaRuntime::CachedPageMediaRuntime(
, _joinChannel(std::move(joinChannel)) {
}
CachedPageMediaRuntime::CachedPageMediaRuntime(
not_null<Main::Session*> session,
not_null<HistoryView::Element*> view,
Fn<void(QString)> openChannel,
Fn<void(QString)> 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<Ui::DynamicImage> CachedPageMediaRuntime::resolveInlineImage(
uint64 documentId,
QSize size) const {
@@ -1094,6 +1115,18 @@ QString CachedPageMediaRuntime::mentionNameEntityData(uint64 userId) const {
auto CachedPageMediaRuntime::hostedMediaHost(
not_null<Window::SessionController*> controller) const
-> std::shared_ptr<Markdown::IvHistoryViewMediaHost> {
if (_useExistingView) {
const auto view = _view.get();
if (!view) {
return nullptr;
}
if (!_hostedMediaHost) {
_hostedMediaHost
= std::make_shared<Markdown::IvHistoryViewMediaHost>(
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<Main::Session*> session,
not_null<HistoryView::Element*> view,
Fn<void(QString)> openChannel,
Fn<void(QString)> joinChannel)
-> std::shared_ptr<Markdown::MediaRuntime> {
return std::make_shared<CachedPageMediaRuntime>(
session,
view,
std::move(openChannel),
std::move(joinChannel));
}
} // namespace Iv
+11
View File
@@ -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<void(QString)> joinChannel)
-> std::shared_ptr<Markdown::MediaRuntime>;
[[nodiscard]] auto CreateMessageMediaRuntime(
not_null<Main::Session*> session,
not_null<HistoryView::Element*> view,
Fn<void(QString)> openChannel,
Fn<void(QString)> joinChannel)
-> std::shared_ptr<Markdown::MediaRuntime>;
} // namespace Iv
@@ -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();
}
@@ -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);
@@ -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> _documentRuntime;
const std::shared_ptr<IvHistoryViewMediaHost> _host;
const std::vector<std::shared_ptr<void>> _keepAlive;
const not_null<::Data::Session*> _session;
std::unique_ptr<HistoryView::Media> _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<const HistoryItem*> item) {
return (item == _host->item());
}) | rpl::on_next([=](not_null<const HistoryItem*>) {
handleItemRepaint();
}, _lifetime);
_session->itemResizeRequest(
) | rpl::filter([=](not_null<const HistoryItem*> item) {
return (item == _host->item());
}) | rpl::on_next([=](not_null<const HistoryItem*>) {
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<HistoryView::Element*> view) {
return (view == _host->view());
}) | rpl::on_next([=](not_null<HistoryView::Element*>) {
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<Window::SessionController*> controller,
not_null<HistoryItem*> item);
explicit State(not_null<HistoryView::Element*> view);
const not_null<::Data::Session*> session;
const QString pageUrl;
@@ -510,7 +486,11 @@ struct IvHistoryViewMediaHost::State {
const not_null<HistoryItem*> item;
AdminLog::OwnedItem owned;
std::unique_ptr<HistoryView::Element> 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<HistoryView::Message*>(owned.get())) {
view->setInstantViewMediaRuntime(this->pageUrl);
static_cast<HistoryView::Message*>(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<HistoryView::Message*>(realView.get())) {
view->setInstantViewMediaRuntime(this->pageUrl);
static_cast<HistoryView::Message*>(view)->setInstantViewMediaRuntime(
this->pageUrl);
}
IvHistoryViewMediaHost::State::State(
not_null<HistoryView::Element*> 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<State>(controller, item)) {
}
IvHistoryViewMediaHost::IvHistoryViewMediaHost(
not_null<HistoryView::Element*> view)
: _state(std::make_unique<State>(view)) {
}
IvHistoryViewMediaHost::~IvHistoryViewMediaHost() = default;
not_null<::Data::Session*> IvHistoryViewMediaHost::session() const {
@@ -577,14 +572,62 @@ not_null<HistoryItem*> IvHistoryViewMediaHost::item() const {
return _state->item;
}
not_null<HistoryView::Message*> IvHistoryViewMediaHost::view() const {
return not_null<HistoryView::Message*>{ _state->view };
not_null<HistoryView::Element*> IvHistoryViewMediaHost::view() const {
return not_null<HistoryView::Element*>{ _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<HistoryView::Element*> view) {
return (view == _state->view);
}) | rpl::on_next([=](not_null<HistoryView::Element*>) {
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<PhotoData*> photo) const {
_state->item->addPhotoForInstantView(photo);
}
@@ -35,6 +35,8 @@ class Session;
namespace Iv::Markdown {
class MediaBlockHost;
class IvHistoryViewMediaHost final {
public:
IvHistoryViewMediaHost(
@@ -44,12 +46,17 @@ public:
IvHistoryViewMediaHost(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item);
explicit IvHistoryViewMediaHost(
not_null<HistoryView::Element*> view);
~IvHistoryViewMediaHost();
[[nodiscard]] not_null<::Data::Session*> session() const;
[[nodiscard]] not_null<HistoryItem*> item() const;
[[nodiscard]] not_null<HistoryView::Message*> view() const;
[[nodiscard]] not_null<HistoryView::Element*> view() const;
[[nodiscard]] const QString &pageUrl() const;
[[nodiscard]] bool needsViewRequestBridge() const;
void registerViewRequestBridge(MediaBlockHost *host);
void unregisterViewRequestBridge(MediaBlockHost *host);
void registerPhoto(not_null<PhotoData*> photo) const;
void registerDocument(not_null<DocumentData*> document) const;
@@ -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<MediaBlock> CreatePhotoMediaBlock(
const PreparedPhotoBlockData &prepared,
const std::shared_ptr<MediaRuntime> &mediaRuntime,
@@ -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;