From 23ccec45d089555ec7ca57a41b87ca66268ee3b7 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sat, 23 Jun 2018 10:11:14 -0700 Subject: [PATCH] fix notifications in a background tab (#402) Part of the way to improving #390. Before this fix, if you recieved a notification while Pinafore was in a background tab, nothing would happen, because most browsers (Edge, Firefox, Chrome) don't run rAF in background tabs. Furthermore, Chrome doesn't run rIC. In this PR we detect if we're in a background tab and then avoid rAF/rIC in that case. --- routes/_actions/addStatusOrNotification.js | 7 +++---- routes/_store/observers/notificationObservers.js | 4 ++-- routes/_store/observers/observers.js | 2 ++ .../_store/observers/pageVisibilityObservers.js | 9 +++++++++ routes/_utils/runMediumPriorityTask.js | 16 ++++++++++++++++ 5 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 routes/_store/observers/pageVisibilityObservers.js create mode 100644 routes/_utils/runMediumPriorityTask.js diff --git a/routes/_actions/addStatusOrNotification.js b/routes/_actions/addStatusOrNotification.js index 0f60780..06a6564 100644 --- a/routes/_actions/addStatusOrNotification.js +++ b/routes/_actions/addStatusOrNotification.js @@ -1,14 +1,13 @@ import throttle from 'lodash-es/throttle' import { mark, stop } from '../_utils/marks' import { store } from '../_store/store' -import { scheduleIdleTask } from '../_utils/scheduleIdleTask' import uniqBy from 'lodash-es/uniqBy' import uniq from 'lodash-es/uniq' import isEqual from 'lodash-es/isEqual' -import { isMobile } from '../_utils/isMobile' import { insertTimelineItems as insertTimelineItemsInDatabase } from '../_database/timelines/insertion' +import { runMediumPriorityTask } from '../_utils/runMediumPriorityTask' const STREAMING_THROTTLE_DELAY = 3000 @@ -82,8 +81,7 @@ async function processFreshUpdates (instanceName, timelineName) { } const lazilyProcessFreshUpdates = throttle((instanceName, timelineName) => { - const runTask = isMobile() ? scheduleIdleTask : requestAnimationFrame - runTask(() => { + runMediumPriorityTask(() => { /* no await */ processFreshUpdates(instanceName, timelineName) }) }, STREAMING_THROTTLE_DELAY) @@ -93,6 +91,7 @@ export function addStatusOrNotification (instanceName, timelineName, newStatusOr } export function addStatusesOrNotifications (instanceName, timelineName, newStatusesOrNotifications) { + console.log('addStatusesOrNotifications', Date.now()) let freshUpdates = store.getForTimeline(instanceName, timelineName, 'freshUpdates') || [] freshUpdates = [].concat(freshUpdates).concat(newStatusesOrNotifications) freshUpdates = uniqBy(freshUpdates, _ => _.id) diff --git a/routes/_store/observers/notificationObservers.js b/routes/_store/observers/notificationObservers.js index a162338..790d37a 100644 --- a/routes/_store/observers/notificationObservers.js +++ b/routes/_store/observers/notificationObservers.js @@ -1,5 +1,5 @@ import { setFavicon } from '../../_utils/setFavicon' -import { scheduleIdleTask } from '../../_utils/scheduleIdleTask' +import { runMediumPriorityTask } from '../../_utils/runMediumPriorityTask' let currentFaviconHasNotifications = false @@ -8,7 +8,7 @@ export function notificationObservers (store) { if (!process.browser) { return } - scheduleIdleTask(() => { + runMediumPriorityTask(() => { if (currentFaviconHasNotifications === hasNotifications) { return } diff --git a/routes/_store/observers/observers.js b/routes/_store/observers/observers.js index aaa8697..7066a9d 100644 --- a/routes/_store/observers/observers.js +++ b/routes/_store/observers/observers.js @@ -4,6 +4,7 @@ import { notificationObservers } from './notificationObservers' import { onlineObservers } from './onlineObservers' import { navObservers } from './navObservers' import { autosuggestObservers } from './autosuggestObservers' +import { pageVisibilityObservers } from './pageVisibilityObservers' export function observers (store) { instanceObservers(store) @@ -12,4 +13,5 @@ export function observers (store) { onlineObservers(store) navObservers(store) autosuggestObservers(store) + pageVisibilityObservers(store) } diff --git a/routes/_store/observers/pageVisibilityObservers.js b/routes/_store/observers/pageVisibilityObservers.js new file mode 100644 index 0000000..33294bb --- /dev/null +++ b/routes/_store/observers/pageVisibilityObservers.js @@ -0,0 +1,9 @@ +export function pageVisibilityObservers (store) { + if (!process.browser) { + return + } + + document.addEventListener('visibilitychange', () => { + store.set({pageVisibilityHidden: document.hidden}) + }) +} diff --git a/routes/_utils/runMediumPriorityTask.js b/routes/_utils/runMediumPriorityTask.js new file mode 100644 index 0000000..9696b39 --- /dev/null +++ b/routes/_utils/runMediumPriorityTask.js @@ -0,0 +1,16 @@ +import { scheduleIdleTask } from './scheduleIdleTask' +import { store } from '../_store/store' +import { isMobile } from './isMobile' + +// Run a task that doesn't need to be processed immediately, but should +// probably be delayed if we're on a mobile device. Also run it sooner +// if we're in a hidden tab, since browsers throttle or don't run setTimeout/rAF/etc. +export function runMediumPriorityTask (fn) { + if (store.get().pageVisibilityHidden) { + fn() + } else if (isMobile()) { + scheduleIdleTask(fn) + } else { + requestAnimationFrame(fn) + } +}