Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
- Fix: 一部の画像のみセンシティブなとき、ビューワー内で画像を切り替えるとセンシティブな画像がそのまま表示される問題を修正
- Fix: 一部の画像をビューワーで読み込んだ際に正しく表示されない問題を修正
- Enhance: タイムラインの読み込みパフォーマンスを改善
- Enhance: タブがバックグラウンドの間は必要ない定期更新処理を停止するように
- Fix: 「画像を新しいタブで開く」が機能しなくなっていた問題を修正
- Fix: デバイスタイプをスマートフォンに固定している状態で画面幅が広いとき、画面左上のアイコンが表示されない問題を修正
- Fix: チャットでIMEの変換を確定するEnterでメッセージが送信されてしまうことがある問題を修正
Expand Down
59 changes: 59 additions & 0 deletions packages/frontend-shared/js/interval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

/** ドキュメントがアクティブになっていない間は実行を止めるsetInterval。戻り値の関数でdisposeする */
export function createVisibilityAwareInterval(fn: () => void, interval: number, options: {
immediate?: boolean;
} = {}): () => void {
let intervalId: number | null = null;
let lastCalledAt: number | null = null;

const tick = () => {
lastCalledAt = Date.now();
fn();
};

const start = () => {
if (intervalId != null) return;
if (lastCalledAt == null) {
if (options.immediate) {
tick();
} else {
lastCalledAt = Date.now();
}
} else if (Date.now() - lastCalledAt >= interval) {
// もし前回の呼び出しからinterval以上の時間が経過していたら、即座に呼び出す
// (非アクティブ→アクティブに戻った場合など)
tick();
}
intervalId = window.setInterval(tick, interval);
};

const stop = () => {
if (intervalId != null) {
window.clearInterval(intervalId);
intervalId = null;
}
};

const onVisibilityChange = () => {
if (window.document.visibilityState === 'visible') {
start();
} else {
stop();
}
};

window.document.addEventListener('visibilitychange', onVisibilityChange);

if (window.document.visibilityState === 'visible') {
start();
}

return () => {
window.document.removeEventListener('visibilitychange', onVisibilityChange);
stop();
};
}
39 changes: 26 additions & 13 deletions packages/frontend-shared/js/use-interval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,47 @@
*/

import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue';
import { createVisibilityAwareInterval } from './interval.js';

export function useInterval(fn: () => void, interval: number, options: {
immediate: boolean;
afterMounted: boolean;
keepRunningWhenHidden?: boolean;
}): (() => void) | undefined {
if (Number.isNaN(interval)) return;

let intervalId: number | null = null;
let disposer: (() => void) | null = null;

const start = () => {
if (options.keepRunningWhenHidden) {
if (options.immediate) fn();
const intervalId = window.setInterval(fn, interval);
disposer = () => {
window.clearInterval(intervalId);
};
} else {
disposer = createVisibilityAwareInterval(fn, interval, { immediate: options.immediate });
}
};

const clear = () => {
if (disposer) {
disposer();
disposer = null;
}
};

if (options.afterMounted) {
onMounted(() => {
if (options.immediate) fn();
intervalId = window.setInterval(fn, interval);
start();
});
} else {
if (options.immediate) fn();
intervalId = window.setInterval(fn, interval);
start();
}

const clear = () => {
if (intervalId) window.clearInterval(intervalId);
intervalId = null;
};

onActivated(() => {
if (intervalId) return;
if (options.immediate) fn();
intervalId = window.setInterval(fn, interval);
if (disposer) return;
start();
});

onDeactivated(() => {
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/components/MkChatHistories.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ onDeactivated(() => {

useInterval(() => {
// TODO: DOM的にバックグラウンドになっていないかどうかも考慮する
if (!window.document.hidden && isActivated) {
if (isActivated) {
fetchHistory();
}
}, 1000 * 10, {
Expand Down
3 changes: 2 additions & 1 deletion packages/frontend/src/composables/use-lowres-time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import { ref, readonly, computed } from 'vue';
import { createVisibilityAwareInterval } from '@@/js/interval.js';

const time = ref(Date.now());

Expand All @@ -29,6 +30,6 @@ export function useLowresTime() {
return computed(() => Math.max(time.value, now));
}

window.setInterval(() => {
createVisibilityAwareInterval(() => {
time.value = Date.now();
}, TIME_UPDATE_INTERVAL);
5 changes: 3 additions & 2 deletions packages/frontend/src/composables/use-note-capture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { onUnmounted, reactive } from 'vue';
import * as Misskey from 'misskey-js';
import { EventEmitter } from 'eventemitter3';
import { createVisibilityAwareInterval } from '@@/js/interval.js';
import type { Reactive } from 'vue';
import type { NoteUpdatedEvent } from 'misskey-js/streaming.types.js';
import { useStream } from '@/stream.js';
Expand Down Expand Up @@ -68,15 +69,15 @@ const POLLING_INTERVAL =
prefer.s.pollingInterval === 3 ? MIN_POLLING_INTERVAL :
MIN_POLLING_INTERVAL;

window.setInterval(() => {
// documentが非表示の間はポーリングを停止する
createVisibilityAwareInterval(() => {
const ids = [...pollingQueue.entries()]
.filter(([k, v]) => Date.now() - v.lastAddedAt < 1000 * 60 * 5) // 追加されてから一定時間経過したものは省く
.map(([k, v]) => k)
.sort((a, b) => (a > b ? -1 : 1)) // 新しいものを優先するためにIDで降順ソート
.slice(0, CAPTURE_MAX);

if (ids.length === 0) return;
if (window.document.hidden) return;

// まとめてリクエストするのではなく、個別にHTTPリクエスト投げてCDNにキャッシュさせた方がサーバーの負荷低減には良いかもしれない?
misskeyApi('notes/show-partial-bulk', {
Expand Down
6 changes: 5 additions & 1 deletion packages/frontend/src/pages/reversi/game.board.vue
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,11 @@ if (!props.game.isEnded) {
props.connection!.send('claimTimeIsUp', {});
}
}
}, TIMER_INTERVAL_SEC * 1000, { immediate: false, afterMounted: true });
}, TIMER_INTERVAL_SEC * 1000, {
immediate: false,
afterMounted: true,
keepRunningWhenHidden: true, // 対局の制限時間管理のため、バックグラウンドでも止めない
});
Comment thread
kakkokari-gtyih marked this conversation as resolved.
}

async function onStreamLog(log: Reversi.Serializer.Log & { id: string | null }) {
Expand Down
6 changes: 5 additions & 1 deletion packages/frontend/src/pages/reversi/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,11 @@ async function accept(user: Misskey.entities.UserLite) {
}
}

useInterval(matchHeatbeat, 1000 * 5, { immediate: false, afterMounted: true });
useInterval(matchHeatbeat, 1000 * 5, {
immediate: false,
afterMounted: true,
keepRunningWhenHidden: true, // バックグラウンドタブでもマッチング待機を維持する必要がある
});

onMounted(() => {
misskeyApi('reversi/invitations').then(_invitations => {
Expand Down
5 changes: 3 additions & 2 deletions packages/frontend/src/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import { BroadcastChannel } from 'broadcast-channel';
import { createVisibilityAwareInterval } from '@@/js/interval.js';
import type { StorageProvider } from '@/preferences/manager.js';
import { cloudBackup } from '@/preferences/utility.js';
import { miLocalStorage } from '@/local-storage.js';
Expand Down Expand Up @@ -143,10 +144,10 @@ preferencesChannel.addEventListener('message', (msg) => {
//#region 定期クラウドバックアップ
let latestBackupAt = 0;

window.setInterval(() => {
// documentが非表示の間は実行されない (同期されていない古い値がバックアップされるのを防ぐ意味もある)
createVisibilityAwareInterval(() => {
if ($i == null) return;
if (!store.s.enablePreferencesAutoCloudBackup) return;
if (window.document.visibilityState !== 'visible') return; // 同期されていない古い値がバックアップされるのを防ぐ
if (prefer.profile.modifiedAt <= latestBackupAt) return;

cloudBackup().then(() => {
Expand Down
10 changes: 4 additions & 6 deletions packages/frontend/src/widgets/WidgetRss.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,9 @@ const fetchEndpoint = computed(() => {
url.searchParams.set('url', widgetProps.url);
return url.toString();
});
const intervalClear = ref<(() => void) | undefined>();
let intervalClear: (() => void) | null | undefined = null;

const tick = () => {
if (window.document.visibilityState === 'hidden' && rawItems.value.length !== 0) return;

window.fetch(fetchEndpoint.value, {})
.then(res => res.json())
.then((feed: Misskey.entities.FetchRssResponse) => {
Expand All @@ -90,10 +88,10 @@ const tick = () => {

watch(fetchEndpoint, tick);
watch(() => widgetProps.refreshIntervalSec, () => {
if (intervalClear.value) {
intervalClear.value();
if (intervalClear != null) {
intervalClear();
}
intervalClear.value = useInterval(tick, Math.max(10000, widgetProps.refreshIntervalSec * 1000), {
intervalClear = useInterval(tick, Math.max(10000, widgetProps.refreshIntervalSec * 1000), {
immediate: true,
afterMounted: true,
});
Expand Down
10 changes: 4 additions & 6 deletions packages/frontend/src/widgets/WidgetRssTicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,11 @@ const fetchEndpoint = computed(() => {
url.searchParams.set('url', widgetProps.url);
return url;
});
const intervalClear = ref<(() => void) | undefined>();
let intervalClear: (() => void) | null | undefined = null;

const key = ref(0);

const tick = () => {
if (window.document.visibilityState === 'hidden' && rawItems.value.length !== 0) return;

window.fetch(fetchEndpoint.value, {})
.then(res => res.json())
.then((feed: Misskey.entities.FetchRssResponse) => {
Expand All @@ -131,10 +129,10 @@ const tick = () => {

watch(fetchEndpoint, tick);
watch(() => widgetProps.refreshIntervalSec, () => {
if (intervalClear.value) {
intervalClear.value();
if (intervalClear != null) {
intervalClear();
}
intervalClear.value = useInterval(tick, Math.max(10000, widgetProps.refreshIntervalSec * 1000), {
intervalClear = useInterval(tick, Math.max(10000, widgetProps.refreshIntervalSec * 1000), {
immediate: true,
afterMounted: true,
});
Expand Down
12 changes: 7 additions & 5 deletions packages/frontend/src/widgets/WidgetUnixClock.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only

<script lang="ts" setup>
import { onUnmounted, ref, watch } from 'vue';
import { createVisibilityAwareInterval } from '@@/js/interval.js';
import { useWidgetPropsManager } from './widget.js';
import { i18n } from '@/i18n.js';
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
Expand Down Expand Up @@ -59,7 +60,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
emit,
);

let intervalId: number | null = null;
let disposeInterval: (() => void) | null = null;
let rafRequestId: number | null = null;
const ss = ref('');
const ms = ref('');
Expand All @@ -83,9 +84,9 @@ const tick = () => {
};

const clearTimers = () => {
if (intervalId) {
window.clearInterval(intervalId);
intervalId = null;
if (disposeInterval) {
disposeInterval();
disposeInterval = null;
}
if (rafRequestId) {
window.cancelAnimationFrame(rafRequestId);
Expand All @@ -99,12 +100,13 @@ watch(() => widgetProps.showMs, (to) => {
clearTimers();

if (to) {
// rafはdocumentが非表示の間はブラウザによって自動的に停止される
rafRequestId = window.requestAnimationFrame(function loop() {
tick();
rafRequestId = window.requestAnimationFrame(loop);
});
} else {
intervalId = window.setInterval(tick, 1000);
disposeInterval = createVisibilityAwareInterval(tick, 1000);
}
}, { immediate: true });

Expand Down
Loading