Skip to content

Commit 8e854a9

Browse files
committed
only use click for v-model checkbox/radio in Chrome (fix #4796, #4896)
1 parent 29f6902 commit 8e854a9

File tree

4 files changed

+38
-9
lines changed

4 files changed

+38
-9
lines changed

src/core/util/env.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const isIE9 = UA && UA.indexOf('msie 9.0') > 0
1414
export const isEdge = UA && UA.indexOf('edge/') > 0
1515
export const isAndroid = UA && UA.indexOf('android') > 0
1616
export const isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)
17+
export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
1718

1819
// this needs to be lazy-evaled because vue may be required before
1920
// vue-server-renderer can set VUE_ENV

src/entries/web-runtime.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import config from 'core/config'
55
import { patch } from 'web/runtime/patch'
66
import { extend, noop } from 'shared/util'
77
import { mountComponent } from 'core/instance/lifecycle'
8-
import { devtools, inBrowser, isEdge } from 'core/util/index'
8+
import { devtools, inBrowser, isChrome } from 'core/util/index'
99
import platformDirectives from 'web/runtime/directives/index'
1010
import platformComponents from 'web/runtime/components/index'
1111

@@ -45,10 +45,7 @@ setTimeout(() => {
4545
if (config.devtools) {
4646
if (devtools) {
4747
devtools.emit('init', Vue)
48-
} else if (
49-
process.env.NODE_ENV !== 'production' &&
50-
inBrowser && !isEdge && /Chrome\/\d+/.test(window.navigator.userAgent)
51-
) {
48+
} else if (process.env.NODE_ENV !== 'production' && isChrome) {
5249
console[console.info ? 'info' : 'log'](
5350
'Download the Vue Devtools extension for a better development experience:\n' +
5451
'https://github.com/vuejs/vue-devtools'

src/platforms/web/compiler/directives/model.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
/* @flow */
22

33
import config from 'core/config'
4-
import { isIE } from 'core/util/env'
54
import { addHandler, addProp, getBindingAttr } from 'compiler/helpers'
65
import { genComponentModel, genAssignmentCode } from 'compiler/directives/model'
76

87
let warn
98

9+
// in some cases, the event used has to be determined at runtime
10+
// so we used some reserved tokens during compile.
11+
export const RANGE_TOKEN = '__r'
12+
export const CHECKBOX_RADIO_TOKEN = '__c'
13+
1014
export default function model (
1115
el: ASTElement,
1216
dir: ASTDirective,
@@ -86,7 +90,7 @@ function genCheckboxModel (
8690
: `:_q(${value},${trueValueBinding})`
8791
)
8892
)
89-
addHandler(el, 'click',
93+
addHandler(el, CHECKBOX_RADIO_TOKEN,
9094
`var $$a=${value},` +
9195
'$$el=$event.target,' +
9296
`$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
@@ -117,7 +121,7 @@ function genRadioModel (
117121
let valueBinding = getBindingAttr(el, 'value') || 'null'
118122
valueBinding = number ? `_n(${valueBinding})` : valueBinding
119123
addProp(el, 'checked', `_q(${value},${valueBinding})`)
120-
addHandler(el, 'click', genAssignmentCode(value, valueBinding), null, true)
124+
addHandler(el, CHECKBOX_RADIO_TOKEN, genAssignmentCode(value, valueBinding), null, true)
121125
}
122126

123127
function genSelect (
@@ -162,8 +166,12 @@ function genDefaultModel (
162166
): ?boolean {
163167
const type = el.attrsMap.type
164168
const { lazy, number, trim } = modifiers || {}
165-
const event = lazy || (isIE && type === 'range') ? 'change' : 'input'
166169
const needCompositionGuard = !lazy && type !== 'range'
170+
const event = lazy
171+
? 'change'
172+
: type === 'range'
173+
? RANGE_TOKEN
174+
: 'input'
167175

168176
let valueExpression = '$event.target.value'
169177
if (trim) {

src/platforms/web/runtime/modules/events.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
/* @flow */
22

3+
import { isChrome, isIE } from 'core/util/env'
34
import { updateListeners } from 'core/vdom/helpers/index'
5+
import { RANGE_TOKEN, CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model'
6+
7+
// normalize v-model event tokens that can only be determined at runtime.
8+
// it's important to place the event as the first in the array because
9+
// the whole point is ensuring the v-model callback gets called before
10+
// user-attached handlers.
11+
function normalizeEvents (on) {
12+
let event
13+
if (on[RANGE_TOKEN]) {
14+
// IE input[type=range] only supports `change` event
15+
event = isIE ? 'change' : 'input'
16+
on[event] = [].concat(on[RANGE_TOKEN], on[event] || [])
17+
delete on[RANGE_TOKEN]
18+
}
19+
if (on[CHECKBOX_RADIO_TOKEN]) {
20+
// Chrome fires microtasks in between click/change, leads to #4521
21+
event = isChrome ? 'click' : 'change'
22+
on[event] = [].concat(on[CHECKBOX_RADIO_TOKEN], on[event] || [])
23+
delete on[CHECKBOX_RADIO_TOKEN]
24+
}
25+
}
426

527
let target: HTMLElement
628

@@ -41,6 +63,7 @@ function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
4163
const on = vnode.data.on || {}
4264
const oldOn = oldVnode.data.on || {}
4365
target = vnode.elm
66+
normalizeEvents(on)
4467
updateListeners(on, oldOn, add, remove, vnode.context)
4568
}
4669

0 commit comments

Comments
 (0)