Skip to content

Commit

Permalink
fix: use timers instead of requestAnimationFrame
Browse files Browse the repository at this point in the history
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
  • Loading branch information
pimlie committed Apr 23, 2019
1 parent e80643b commit c040de7
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 47 deletions.
24 changes: 0 additions & 24 deletions src/client/batchUpdate.js

This file was deleted.

14 changes: 0 additions & 14 deletions src/client/triggerUpdate.js

This file was deleted.

26 changes: 26 additions & 0 deletions src/client/update.js
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion src/shared/mixin.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
67 changes: 59 additions & 8 deletions test/unit/plugin-browser.test.js
Original file line number Diff line number Diff line change
@@ -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'
}))
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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()
})
})

0 comments on commit c040de7

Please sign in to comment.