diff --git a/src/compiler/parser/html-decoder.js b/src/compiler/parser/html-decoder.js new file mode 100644 index 00000000000..e041e4da35b --- /dev/null +++ b/src/compiler/parser/html-decoder.js @@ -0,0 +1,6 @@ +/* @flow */ + +import he from 'he' +import { cached } from 'shared/util' + +export default cached(he.decode) diff --git a/src/compiler/parser/html-parser.js b/src/compiler/parser/html-parser.js index 37c1fa24d58..2025c84422d 100644 --- a/src/compiler/parser/html-parser.js +++ b/src/compiler/parser/html-parser.js @@ -12,6 +12,7 @@ import { makeMap, no } from 'shared/util' import { isNonPhrasingTag } from 'web/compiler/util' import { unicodeRegExp } from 'core/util/lang' +import decodeHTML from './html-decoder' // Regular Expressions for parsing tags and attributes const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/ @@ -30,27 +31,10 @@ const conditionalComment = /^', - '"': '"', - '&': '&', - ' ': '\n', - ' ': '\t', - ''': "'" -} -const encodedAttr = /&(?:lt|gt|quot|amp|#39);/g -const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#39|#10|#9);/g - // #5992 const isIgnoreNewlineTag = makeMap('pre,textarea', true) const shouldIgnoreFirstNewline = (tag, html) => tag && isIgnoreNewlineTag(tag) && html[0] === '\n' -function decodeAttr (value, shouldDecodeNewlines) { - const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr - return value.replace(re, match => decodingMap[match]) -} - export function parseHTML (html, options) { const stack = [] const expectHTML = options.expectHTML @@ -229,12 +213,9 @@ export function parseHTML (html, options) { for (let i = 0; i < l; i++) { const args = match.attrs[i] const value = args[3] || args[4] || args[5] || '' - const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' - ? options.shouldDecodeNewlinesForHref - : options.shouldDecodeNewlines attrs[i] = { name: args[1], - value: decodeAttr(value, shouldDecodeNewlines) + value: decodeHTML(value) } if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) { attrs[i].start = args.start + args[0].match(/^\s*/).length diff --git a/src/compiler/parser/index.js b/src/compiler/parser/index.js index c4753e12996..884c74e383f 100644 --- a/src/compiler/parser/index.js +++ b/src/compiler/parser/index.js @@ -1,12 +1,12 @@ /* @flow */ -import he from 'he' import { parseHTML } from './html-parser' import { parseText } from './text-parser' import { parseFilters } from './filter-parser' import { genAssignmentCode } from '../directives/model' -import { extend, cached, no, camelize, hyphenate } from 'shared/util' +import { extend, no, camelize, hyphenate } from 'shared/util' import { isIE, isEdge, isServerRendering } from 'core/util/env' +import decodeHTML from './html-decoder' import { addProp, @@ -42,8 +42,6 @@ const whitespaceRE = /[ \f\t\r\n]+/g const invalidAttributeRE = /[\s"'<>\/=]/ -const decodeHTMLCached = cached(he.decode) - export const emptySlotScopeToken = `_empty_` // configurable state @@ -210,8 +208,6 @@ export function parse ( expectHTML: options.expectHTML, isUnaryTag: options.isUnaryTag, canBeLeftOpenTag: options.canBeLeftOpenTag, - shouldDecodeNewlines: options.shouldDecodeNewlines, - shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref, shouldKeepComment: options.comments, outputSourceRange: options.outputSourceRange, start (tag, attrs, unary, start, end) { @@ -339,7 +335,7 @@ export function parse ( } const children = currentParent.children if (inPre || text.trim()) { - text = isTextTag(currentParent) ? text : decodeHTMLCached(text) + text = isTextTag(currentParent) ? text : decodeHTML(text) } else if (!children.length) { // remove the whitespace-only node right after an opening tag text = '' diff --git a/src/platforms/web/entry-runtime-with-compiler.js b/src/platforms/web/entry-runtime-with-compiler.js index 2bb46362bf9..186d0d3dd25 100644 --- a/src/platforms/web/entry-runtime-with-compiler.js +++ b/src/platforms/web/entry-runtime-with-compiler.js @@ -7,7 +7,6 @@ import { mark, measure } from 'core/util/perf' import Vue from './runtime/index' import { query } from './util/index' import { compileToFunctions } from './compiler/index' -import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat' const idToTemplate = cached(id => { const el = query(id) @@ -64,8 +63,6 @@ Vue.prototype.$mount = function ( const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', - shouldDecodeNewlines, - shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) diff --git a/src/platforms/web/util/compat.js b/src/platforms/web/util/compat.js deleted file mode 100644 index d95759cce31..00000000000 --- a/src/platforms/web/util/compat.js +++ /dev/null @@ -1,16 +0,0 @@ -/* @flow */ - -import { inBrowser } from 'core/util/index' - -// check whether current browser encodes a char inside attribute values -let div -function getShouldDecode (href: boolean): boolean { - div = div || document.createElement('div') - div.innerHTML = href ? `` : `
` - return div.innerHTML.indexOf(' ') > 0 -} - -// #3663: IE encodes newlines inside attribute values while other browsers don't -export const shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false -// #6828: chrome encodes content in a[href] -export const shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false diff --git a/test/unit/modules/compiler/parser.spec.js b/test/unit/modules/compiler/parser.spec.js index b47de581396..7b9a9b16696 100644 --- a/test/unit/modules/compiler/parser.spec.js +++ b/test/unit/modules/compiler/parser.spec.js @@ -2,6 +2,7 @@ import { parse } from 'compiler/parser/index' import { extend } from 'shared/util' import { baseOptions } from 'web/compiler/options' import { isIE, isEdge } from 'core/util/env' +import Vue from 'vue' describe('parser', () => { it('simple element', () => { @@ -925,4 +926,38 @@ describe('parser', () => { expect(` can only appear at the root level inside the receiving the component`) .not.toHaveBeenWarned() }) + + it(`HTML entities in the value of attribute should be decoded`, () => { + const options = extend({}, baseOptions) + const ast = parse('', options) + expect(ast.attrsList[0].value).toBe('white space,single-' + "'" + '-quote,double-' + '"' + '-quote,an-&-ampersand,less-<-than,great->-than,line-\n-break,tab-\t-space') + }) + + it(`HTML entities in template should be decoded`, () => { + const vm = new Vue({ + template: '