mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-05-17 05:19:37 +03:00
fix: use timers instead of requestAnimationFrame
The issue with using requestAnimationFrame is that its meant to be used for visual effects. Therefore when a tab is hidden the browser might decide to not perform animation frame updates until the tab becomes visible, this is confirmed behaviour for Firefox. Due to this title updates would not be triggered while document titles are normally visible in the tabs title. For now we batch updates by setting/clearing timeouts with a 10ms interval Resolves: #313
This commit is contained in:
@@ -1,24 +0,0 @@
|
||||
import { hasGlobalWindow } from '../utils/window'
|
||||
|
||||
// fallback to timers if rAF not present
|
||||
const stopUpdate = (hasGlobalWindow ? window.cancelAnimationFrame : null) || clearTimeout
|
||||
const startUpdate = (hasGlobalWindow ? window.requestAnimationFrame : null) || (cb => setTimeout(cb, 0))
|
||||
|
||||
/**
|
||||
* Performs a batched update. Uses requestAnimationFrame to prevent
|
||||
* calling a function too many times in quick succession.
|
||||
* You need to pass it an ID (which can initially be `null`),
|
||||
* but be sure to overwrite that ID with the return value of batchUpdate.
|
||||
*
|
||||
* @param {(null|Number)} id - the ID of this update
|
||||
* @param {Function} callback - the update to perform
|
||||
* @return {Number} id - a new ID
|
||||
*/
|
||||
export default function batchUpdate(id, callback) {
|
||||
stopUpdate(id)
|
||||
|
||||
return startUpdate(() => {
|
||||
id = null
|
||||
callback()
|
||||
})
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import batchUpdate from './batchUpdate'
|
||||
|
||||
// store an id to keep track of DOM updates
|
||||
let batchId = null
|
||||
|
||||
export default function triggerUpdate(vm, hookName) {
|
||||
if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) {
|
||||
// batch potential DOM updates to prevent extraneous re-rendering
|
||||
batchId = batchUpdate(batchId, () => {
|
||||
vm.$meta().refresh()
|
||||
batchId = null
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// store an id to keep track of DOM updates
|
||||
let batchId = null
|
||||
|
||||
export function triggerUpdate(vm, hookName) {
|
||||
if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) {
|
||||
// batch potential DOM updates to prevent extraneous re-rendering
|
||||
batchUpdate(() => vm.$meta().refresh())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a batched update.
|
||||
*
|
||||
* @param {(null|Number)} id - the ID of this update
|
||||
* @param {Function} callback - the update to perform
|
||||
* @return {Number} id - a new ID
|
||||
*/
|
||||
export function batchUpdate(callback, timeout = 10) {
|
||||
clearTimeout(batchId)
|
||||
|
||||
batchId = setTimeout(() => {
|
||||
callback()
|
||||
}, timeout)
|
||||
|
||||
return batchId
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import triggerUpdate from '../client/triggerUpdate'
|
||||
import { triggerUpdate } from '../client/update'
|
||||
import { isUndefined, isFunction } from '../utils/is-type'
|
||||
import { ensuredPush } from '../utils/ensure'
|
||||
import { hasMetaInfo } from './meta-helpers'
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import triggerUpdate from '../../src/client/triggerUpdate'
|
||||
import batchUpdate from '../../src/client/batchUpdate'
|
||||
import { triggerUpdate, batchUpdate } from '../../src/client/update'
|
||||
import { mount, vmTick, VueMetaBrowserPlugin, loadVueMetaPlugin } from '../utils'
|
||||
import { defaultOptions } from '../../src/shared/constants'
|
||||
|
||||
jest.mock('../../src/client/triggerUpdate')
|
||||
jest.mock('../../src/client/batchUpdate')
|
||||
jest.mock('../../src/client/update')
|
||||
jest.mock('../../package.json', () => ({
|
||||
version: 'test-version'
|
||||
}))
|
||||
@@ -48,11 +46,16 @@ describe('plugin', () => {
|
||||
})
|
||||
|
||||
test('updates can be paused and resumed', async () => {
|
||||
const _triggerUpdate = jest.requireActual('../../src/client/triggerUpdate').default
|
||||
const _batchUpdate = jest.requireActual('../../src/client/batchUpdate').default
|
||||
|
||||
const triggerUpdateSpy = triggerUpdate.mockImplementation(_triggerUpdate)
|
||||
const { batchUpdate: _batchUpdate } = jest.requireActual('../../src/client/update')
|
||||
const batchUpdateSpy = batchUpdate.mockImplementation(_batchUpdate)
|
||||
// because triggerUpdate & batchUpdate reside in the same file we cant mock them both,
|
||||
// so just recreate the triggerUpdate fn by copying its implementation
|
||||
const triggerUpdateSpy = triggerUpdate.mockImplementation((vm, hookName) => {
|
||||
if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) {
|
||||
// batch potential DOM updates to prevent extraneous re-rendering
|
||||
batchUpdateSpy(() => vm.$meta().refresh())
|
||||
}
|
||||
})
|
||||
|
||||
const Component = Vue.component('test-component', {
|
||||
metaInfo() {
|
||||
@@ -109,4 +112,52 @@ describe('plugin', () => {
|
||||
const { metaInfo } = wrapper.vm.$meta().resume()
|
||||
expect(metaInfo.title).toBe(title)
|
||||
})
|
||||
|
||||
test('updates are batched', async () => {
|
||||
jest.useFakeTimers()
|
||||
|
||||
const { batchUpdate: _batchUpdate } = jest.requireActual('../../src/client/update')
|
||||
const batchUpdateSpy = batchUpdate.mockImplementation(_batchUpdate)
|
||||
const refreshSpy = jest.fn()
|
||||
// because triggerUpdate & batchUpdate reside in the same file we cant mock them both,
|
||||
// so just recreate the triggerUpdate fn by copying its implementation
|
||||
triggerUpdate.mockImplementation((vm, hookName) => {
|
||||
if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) {
|
||||
// batch potential DOM updates to prevent extraneous re-rendering
|
||||
batchUpdateSpy(refreshSpy)
|
||||
}
|
||||
})
|
||||
|
||||
const Component = Vue.component('test-component', {
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.title
|
||||
}
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
template: '<div>Test</div>'
|
||||
})
|
||||
|
||||
let title = 'first title'
|
||||
const wrapper = mount(Component, {
|
||||
localVue: Vue,
|
||||
propsData: {
|
||||
title
|
||||
}
|
||||
})
|
||||
await vmTick(wrapper.vm)
|
||||
jest.clearAllMocks()
|
||||
|
||||
title = 'second title'
|
||||
wrapper.setProps({ title })
|
||||
jest.advanceTimersByTime(2)
|
||||
expect(refreshSpy).not.toHaveBeenCalled()
|
||||
jest.advanceTimersByTime(10)
|
||||
expect(refreshSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user