Skip to content

Commit

Permalink
feat: add no-non-passive-high-frequency-events rule
Browse files Browse the repository at this point in the history
Co-authored-by: Kristján Oddsson <koddsson@gmail.com>
  • Loading branch information
keithamus and koddsson committed Jul 14, 2020
1 parent 85d8b08 commit 6fd9570
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 0 deletions.
21 changes: 21 additions & 0 deletions docs/rules/no-non-passive-high-frequency-events.md
@@ -0,0 +1,21 @@
# No Non Passive High Frequency Events

This rule disallows adding high frequency event listeners (`touchstart`, `touchmove`, `wheel`, `mousewheel`) without the `passive: true` option.

```js
// bad
window.addEventListener('touchstart', () => { /* ... */ })

// good
window.addEventListener('touchstart', () => { /* ... */ }, { passive: true })
```

## Why?

Adding these events listeners can block the main thread as it waits to find out if the callbacks call `preventDefault`. This can cause large amounts UI lag, which will be noticeable for users.

Adding `passive: true` informs the browser that this event will not be calling `preventDefault` and as such is safe to asynchronously dispatch, freeing up the main thread for lag-free operation.

## See Also

https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners
22 changes: 22 additions & 0 deletions lib/rules/no-non-passive-high-frequency-events.js
@@ -0,0 +1,22 @@
const passiveEventListenerNames = new Set(['touchstart', 'touchmove', 'wheel', 'mousewheel'])

const propIsPassiveTrue = prop => prop.key && prop.key.name === 'passive' && prop.value && prop.value.value === true

module.exports = {
meta: {
docs: {}
},

create(context) {
return {
['CallExpression[callee.property.name="addEventListener"]']: function(node) {
const [name, listener, options] = node.arguments
if (!listener) return
if (name.type !== 'Literal') return
if (!passiveEventListenerNames.has(name.value)) return
if (options && options.type === 'ObjectExpression' && options.properties.some(propIsPassiveTrue)) return
context.report(node, `High Frequency Events like "${name.value}" should be \`passive: true\``)
}
}
}
}
69 changes: 69 additions & 0 deletions tests/no-non-passive-high-frequency-events.js
@@ -0,0 +1,69 @@
const rule = require('../lib/rules/no-non-passive-high-frequency-events')
const RuleTester = require('eslint').RuleTester

const ruleTester = new RuleTester()

ruleTester.run('no-non-passive-high-frequency-events', rule, {
valid: [
{
code: 'document.addEventListener("load", function(event) {})'
},
{
code: 'document.addEventListener("click", function(event) {})'
},
{
code: 'document.addEventListener("touchstart", function(event) {}, { passive: true })'
},
{
code: 'el.addEventListener("touchstart", function(event) {}, { passive: true })'
}
],
invalid: [
{
code: 'document.addEventListener("touchstart", function(event) {})',
errors: [
{
message: 'High Frequency Events like "touchstart" should be `passive: true`',
type: 'CallExpression'
}
]
},
{
code: 'el.addEventListener("wheel", function(event) {})',
errors: [
{
message: 'High Frequency Events like "wheel" should be `passive: true`',
type: 'CallExpression'
}
]
},
{
code: 'document.addEventListener("wheel", function(event) {})',
errors: [
{
message: 'High Frequency Events like "wheel" should be `passive: true`',
type: 'CallExpression'
}
]
},
{
// Intentionally mispelled!
code: 'document.addEventListener("wheel", function(event) {}, { pssive: true })',
errors: [
{
message: 'High Frequency Events like "wheel" should be `passive: true`',
type: 'CallExpression'
}
]
},
{
code: 'document.addEventListener("wheel", function(event) {}, { passive: false })',
errors: [
{
message: 'High Frequency Events like "wheel" should be `passive: true`',
type: 'CallExpression'
}
]
}
]
})

0 comments on commit 6fd9570

Please sign in to comment.