diff --git a/bower.json b/bower.json index ae2bcf5033..ea5e637ce2 100644 --- a/bower.json +++ b/bower.json @@ -26,7 +26,8 @@ "devDependencies": { "web-component-tester": "^6.0.0", "test-fixture": "PolymerElements/test-fixture#3.0.0-rc.1", - "iron-component-page": "PolymerElements/iron-component-page#^3.0.1" + "iron-component-page": "PolymerElements/iron-component-page#^3.0.1", + "perf-tester": "polymerlabs/perf-tester" }, "private": true, "resolutions": { diff --git a/externs/closure-types.js b/externs/closure-types.js index e97a702165..8e7f3dd19a 100644 --- a/externs/closure-types.js +++ b/externs/closure-types.js @@ -1379,6 +1379,17 @@ Polymer_ArraySelectorMixin.prototype.select = function(item){}; Polymer_ArraySelectorMixin.prototype.selectIndex = function(idx){}; /** * @interface +* @extends {Polymer_PropertyEffects} +*/ +function Polymer_StrictBindingParser(){} +/** +* @param {string} text Text to parse from attribute or textContent +* @param {Object} templateInfo Current template metadata +* @return {Array.} +*/ +Polymer_StrictBindingParser._parseBindings = function(text, templateInfo){}; +/** +* @interface * @extends {Polymer_ElementMixin} */ function Polymer_DisableUpgradeMixin(){} diff --git a/lib/mixins/property-effects.html b/lib/mixins/property-effects.html index 0feb7c9e41..d5c60613e2 100644 --- a/lib/mixins/property-effects.html +++ b/lib/mixins/property-effects.html @@ -2629,6 +2629,17 @@ * - Inline computed method (supports negation): * `[[compute(a, 'literal', b)]]`, `[[!compute(a, 'literal', b)]]` * + * The default implementation uses a regular expression for best + * performance. However, the regular expression uses a white-list of + * allowed characters in a data-binding, which causes problems for + * data-bindings that do use characters not in this white-list. + * + * Instead of updating the white-list with all allowed characters, + * there is a StrictBindingParser (see lib/mixins/strict-binding-parser) + * that uses a state machine instead. This state machine is able to handle + * all characters. However, it is slightly less performant, therefore we + * extracted it into a separate optional mixin. + * * @param {string} text Text to parse from attribute or textContent * @param {Object} templateInfo Current template metadata * @return {Array} Array of binding part metadata diff --git a/lib/mixins/strict-binding-parser.html b/lib/mixins/strict-binding-parser.html new file mode 100644 index 0000000000..03a3b3d278 --- /dev/null +++ b/lib/mixins/strict-binding-parser.html @@ -0,0 +1,419 @@ + + + + + + + diff --git a/test/perf/binding-expressions.html b/test/perf/binding-expressions.html new file mode 100644 index 0000000000..177a41f249 --- /dev/null +++ b/test/perf/binding-expressions.html @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/test/perf/perf-tests.html b/test/perf/perf-tests.html new file mode 100644 index 0000000000..f8da4dbe1a --- /dev/null +++ b/test/perf/perf-tests.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + Perf is go. + + + + + \ No newline at end of file diff --git a/test/unit/property-effects-elements.html b/test/unit/property-effects-elements.html index ecfca10c0c..f9144d74f1 100644 --- a/test/unit/property-effects-elements.html +++ b/test/unit/property-effects-elements.html @@ -8,6 +8,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt --> + diff --git a/test/unit/property-effects.html b/test/unit/property-effects.html index efe4bf6b34..d4ed2e4442 100644 --- a/test/unit/property-effects.html +++ b/test/unit/property-effects.html @@ -409,6 +409,13 @@ } }); + test('only calls dynamic functions once', function() { + el.dynamicFn = function() { + assert.isFalse(this._dynamicFnCalled); + this._dynamicFnCalled = true; + }; + }); + suite('observer inheritance', function() { setup(function() { el = document.createElement('sub-observer-element'); @@ -436,6 +443,48 @@ assert.equal(el.$.svg.getAttribute('viewBox'), el.value); }); }); + + suite('can work with strict binding parser', function() { + setup(function() { + document.body.removeChild(el); + el = document.createElement('x-basic-strict-binding-parser'); + document.body.appendChild(el); + }); + + test('binding with slash', function() { + el.objectWithSlash = { + 'binding/with/slash': 'yes' + }; + assert.equal(el.$.boundWithSlash.textContent, 'yes'); + }); + + test('json should not be a binding', function() { + assert.equal(el.$.jsonContent.textContent, '[["Jan", 31],["Feb", 28],["Mar", 31]]'); + }); + + test('binding with non-English unicode', function() { + el.objectWithNonEnglishUnicode = { + 商品名: 'yes' + }; + assert.equal(el.$.nonEnglishUnicode.textContent, 'yes'); + }); + + test('binding with booleans', function() { + el.otherValue = 10; + assert.equal(el.$.booleanTrue.textContent, 'foo(field, true): 10'); + assert.equal(el.$.booleanFalse.textContent, 'foo(field, false): 20'); + }); + + suite('equivalent behavior as regex', function() { + // Loop over the suite "single-element binding effects" (parent of the parent of this suite) + // And make sure that the tests there also pass on the binding-parser + // + // t.title is the name of the test and t.fn contains the test body + this.parent.parent.tests.forEach(t => { + test(t.title, t.fn); + }); + }); + }); }); suite('computed bindings with dynamic functions', function() { diff --git a/types/lib/mixins/property-effects.d.ts b/types/lib/mixins/property-effects.d.ts index 572674abe3..52aec3a068 100644 --- a/types/lib/mixins/property-effects.d.ts +++ b/types/lib/mixins/property-effects.d.ts @@ -257,6 +257,17 @@ declare namespace Polymer { * - Inline computed method (supports negation): * `[[compute(a, 'literal', b)]]`, `[[!compute(a, 'literal', b)]]` * + * The default implementation uses a regular expression for best + * performance. However, the regular expression uses a white-list of + * allowed characters in a data-binding, which causes problems for + * data-bindings that do use characters not in this white-list. + * + * Instead of updating the white-list with all allowed characters, + * there is a StrictBindingParser (see lib/mixins/strict-binding-parser) + * that uses a state machine instead. This state machine is able to handle + * all characters. However, it is slightly less performant, therefore we + * extracted it into a separate optional mixin. + * * @param text Text to parse from attribute or textContent * @param templateInfo Current template metadata * @returns Array of binding part metadata diff --git a/types/lib/mixins/strict-binding-parser.d.ts b/types/lib/mixins/strict-binding-parser.d.ts new file mode 100644 index 0000000000..b4d9406b95 --- /dev/null +++ b/types/lib/mixins/strict-binding-parser.d.ts @@ -0,0 +1,67 @@ +/** + * DO NOT EDIT + * + * This file was automatically generated by + * https://github.com/Polymer/gen-typescript-declarations + * + * To modify these typings, edit the source file(s): + * lib/mixins/strict-binding-parser.html + */ + +/// +/// +/// +/// + +declare namespace Polymer { + + + /** + * Mixin that parses binding expressions and generates corresponding metadata. + * The implementation is different than in `property-effects`, as it uses a + * state machine instead of a regex. As such, this implementation is able to + * handle more cases, with the potential performance hit. + */ + function StrictBindingParser {}>(base: T): T & StrictBindingParserConstructor & Polymer.PropertyEffectsConstructor & Polymer.TemplateStampConstructor & Polymer.PropertyAccessorsConstructor & Polymer.PropertiesChangedConstructor; + + interface StrictBindingParserConstructor { + new(...args: any[]): StrictBindingParser; + + /** + * Called to parse text in a template (either attribute values or + * textContent) into binding metadata. + * + * Any overrides of this method should return an array of binding part + * metadata representing one or more bindings found in the provided text + * and any "literal" text in between. Any non-literal parts will be passed + * to `_evaluateBinding` when any dependencies change. The only required + * fields of each "part" in the returned array are as follows: + * + * - `dependencies` - Array containing trigger metadata for each property + * that should trigger the binding to update + * - `literal` - String containing text if the part represents a literal; + * in this case no `dependencies` are needed + * + * Additional metadata for use by `_evaluateBinding` may be provided in + * each part object as needed. + * + * The default implementation handles the following types of bindings + * (one or more may be intermixed with literal strings): + * - Property binding: `[[prop]]` + * - Path binding: `[[object.prop]]` + * - Negated property or path bindings: `[[!prop]]` or `[[!object.prop]]` + * - Two-way property or path bindings (supports negation): + * `{{prop}}`, `{{object.prop}}`, `{{!prop}}` or `{{!object.prop}}` + * - Inline computed method (supports negation): + * `[[compute(a, 'literal', b)]]`, `[[!compute(a, 'literal', b)]]` + * + * @param text Text to parse from attribute or textContent + * @param templateInfo Current template metadata + * @returns Array of binding part metadata + */ + _parseBindings(text: string, templateInfo: object|null): BindingPart[]|null; + } + + interface StrictBindingParser { + } +}