fix(frontend): タブが不可視なあいだのpaginationのアップデートを停止するように

This commit is contained in:
kakkokari-gtyih 2025-07-03 10:49:43 +09:00
parent 09a5e4b10a
commit 504f7a403c
2 changed files with 91 additions and 5 deletions

View File

@ -59,6 +59,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, watch, onUnmounted, provide, useTemplateRef, TransitionGroup, onMounted, shallowRef, ref, markRaw } from 'vue';
import * as Misskey from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
import { getScrollContainer, scrollToTop } from '@@/js/scroll.js';
import type { BasicTimelineType } from '@/timelines.js';
import type { SoundStore } from '@/preferences/def.js';
@ -77,6 +78,8 @@ import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-sep
import { Paginator } from '@/utility/paginator.js';
import type { IPaginator, MisskeyEntity } from '@/utility/paginator.js';
const BACKGROUND_PAUSE_WAIT_SEC = 10;
const props = withDefaults(defineProps<{
src: BasicTimelineType | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role';
list?: string;
@ -224,6 +227,29 @@ onUnmounted(() => {
}
});
const visibility = useDocumentVisibility();
let isPausingUpdate = false;
let timerForSetPause: number | null = null;
watch(visibility, () => {
if (visibility.value === 'hidden') {
timerForSetPause = window.setTimeout(() => {
isPausingUpdate = true;
timerForSetPause = null;
}, BACKGROUND_PAUSE_WAIT_SEC * 1000);
} else { // 'visible'
if (timerForSetPause) {
clearTimeout(timerForSetPause);
timerForSetPause = null;
} else {
isPausingUpdate = false;
if (isTop()) {
releaseQueue();
}
}
}
});
let adInsertionCounter = 0;
const MIN_POLLING_INTERVAL = 1000 * 10;
@ -237,7 +263,7 @@ if (!store.s.realtimeMode) {
// TODO: 1TL
useInterval(async () => {
paginator.fetchNewer({
toQueue: !isTop(),
toQueue: !isTop() || isPausingUpdate,
});
}, POLLING_INTERVAL, {
immediate: false,
@ -246,7 +272,7 @@ if (!store.s.realtimeMode) {
useGlobalEvent('notePosted', (note) => {
paginator.fetchNewer({
toQueue: !isTop(),
toQueue: !isTop() || isPausingUpdate,
});
});
}
@ -257,7 +283,7 @@ useGlobalEvent('noteDeleted', (noteId) => {
function releaseQueue() {
paginator.releaseQueue();
scrollToTop(rootEl.value);
scrollToTop(rootEl.value!);
}
function prepend(note: Misskey.entities.Note & MisskeyEntity) {
@ -267,7 +293,7 @@ function prepend(note: Misskey.entities.Note & MisskeyEntity) {
note._shouldInsertAd_ = true;
}
if (isTop()) {
if (isTop() && !isPausingUpdate) {
paginator.prepend(note);
} else {
paginator.enqueue(note);

View File

@ -45,7 +45,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup, markRaw, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
import type { notificationTypes } from '@@/js/const.js';
import { getScrollContainer, scrollToTop } from '@@/js/scroll.js';
import XNotification from '@/components/MkNotification.vue';
import MkNote from '@/components/MkNote.vue';
import { useStream } from '@/stream.js';
@ -56,6 +58,8 @@ import { store } from '@/store.js';
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
import { Paginator } from '@/utility/paginator.js';
const BACKGROUND_PAUSE_WAIT_SEC = 10;
const props = defineProps<{
excludeTypes?: typeof notificationTypes[number][];
}>();
@ -92,6 +96,58 @@ if (!store.s.realtimeMode) {
});
}
function isTop() {
if (scrollContainer == null) return true;
if (rootEl.value == null) return true;
const scrollTop = scrollContainer.scrollTop;
const tlTop = rootEl.value.offsetTop - scrollContainer.offsetTop;
return scrollTop <= tlTop;
}
function releaseQueue() {
paginator.releaseQueue();
scrollToTop(rootEl.value!);
}
let scrollContainer: HTMLElement | null = null;
function onScrollContainerScroll() {
if (isTop()) {
paginator.releaseQueue();
}
}
watch(rootEl, (el) => {
if (el && scrollContainer == null) {
scrollContainer = getScrollContainer(el);
if (scrollContainer == null) return;
scrollContainer.addEventListener('scroll', onScrollContainerScroll, { passive: true }); // scrollendios
}
}, { immediate: true });
const visibility = useDocumentVisibility();
let isPausingUpdate = false;
let timerForSetPause: number | null = null;
watch(visibility, () => {
if (visibility.value === 'hidden') {
timerForSetPause = window.setTimeout(() => {
isPausingUpdate = true;
timerForSetPause = null;
}, BACKGROUND_PAUSE_WAIT_SEC * 1000);
} else { // 'visible'
if (timerForSetPause) {
clearTimeout(timerForSetPause);
timerForSetPause = null;
} else {
isPausingUpdate = false;
if (isTop()) {
releaseQueue();
}
}
}
});
function onNotification(notification) {
const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false;
if (isMuted || window.document.visibilityState === 'visible') {
@ -101,7 +157,11 @@ function onNotification(notification) {
}
if (!isMuted) {
paginator.prepend(notification);
if (isTop() && !isPausingUpdate) {
paginator.prepend(notification);
} else {
paginator.enqueue(notification);
}
}
}