diff --git a/lib/legacy/legacy-element-mixin.html b/lib/legacy/legacy-element-mixin.html index 6272b1cecf..f3770414aa 100644 --- a/lib/legacy/legacy-element-mixin.html +++ b/lib/legacy/legacy-element-mixin.html @@ -286,8 +286,9 @@ * template content. */ instanceTemplate(template) { + let content = this.constructor._contentForTemplate(template); let dom = /** @type {DocumentFragment} */ - (document.importNode(template._content || template.content, true)); + (document.importNode(content, true)); return dom; } diff --git a/lib/mixins/property-effects.html b/lib/mixins/property-effects.html index 259bd86ba3..26a30824fa 100644 --- a/lib/mixins/property-effects.html +++ b/lib/mixins/property-effects.html @@ -683,16 +683,9 @@ * @private */ function addAnnotatedListener(model, index, property, path, event, negate) { - let eventName = event || - (CaseMap.camelToDashCase(property) + '-changed'); + event = event || (CaseMap.camelToDashCase(property) + '-changed'); model.__notifyListeners = model.__notifyListeners || []; - model.__notifyListeners.push({ - index: index, - property: property, - path: path, - event: eventName, - negate: negate - }); + model.__notifyListeners.push({ index, property, path, event, negate }); } /** @@ -711,7 +704,7 @@ } /** - * On the `inst` element that was previously bound, uses `inst.__templateNotes` + * On the `inst` element that was previously bound, uses `inst.__templateNodeInfo` * to setup compound binding storage structures onto the bound * nodes (`inst.__templateNodes`). * (`inst._, and 2-way binding event listeners are also added.) @@ -720,14 +713,15 @@ * @private */ function setupBindings(inst) { - let notes = inst.__templateNotes; - if (notes.length) { - for (let i=0; i < notes.length; i++) { - let note = notes[i]; + let nodeInfo = inst.__templateNodeInfo; + if (nodeInfo.length) { + for (let i=0; i < nodeInfo.length; i++) { + let info = nodeInfo[i]; let node = inst.__templateNodes[i]; node.__dataHost = inst; - if (note.bindings) { - setupCompoundBinding(note, node); + let bindings = info.bindings; + if (bindings) { + setupCompoundBinding(bindings, node); } } } @@ -833,7 +827,7 @@ // m[1]: '{{' '[[' // m[2]: '' '!' // m[3]: 'prop' 'compute(foo,bar)' - while ((m = bindingRegex.exec(text)) !== null) { + while ((m = bindingRegex.exec(text))) { // Add literal part if (m.index > lastIndex) { parts.push({literal: text.slice(lastIndex, m.index)}); @@ -843,9 +837,9 @@ let mode = m[1][0]; let negate = Boolean(m[2]); let value = m[3].trim(); - let customEvent, notifyEvent, colon; + let customEvent, event, colon; if (mode == '{' && (colon = value.indexOf('::')) > 0) { - notifyEvent = value.substring(colon + 2); + event = value.substring(colon + 2); value = value.substring(0, colon); customEvent = true; } @@ -856,14 +850,8 @@ hostProps[rootProperty] = true; } parts.push({ - compoundIndex: parts.length, - value, - mode, - negate, - event: notifyEvent, - customEvent, - signature, - rootProperty + value, mode, negate, event, customEvent, signature, rootProperty, + compoundIndex: parts.length }); lastIndex = bindingRegex.lastIndex; } @@ -871,9 +859,7 @@ if (lastIndex && lastIndex < text.length) { let literal = text.substring(lastIndex); if (literal) { - parts.push({ - literal: literal - }); + parts.push({ literal }); } } if (parts.length) { @@ -935,8 +921,9 @@ let arg = parseArg(rawArg); if (!arg.literal) { sig.static = false; - } else if (hostProps) { - hostProps[arg.rootProperty] = true; + if (hostProps) { + hostProps[arg.rootProperty] = true; + } } return arg; }, this); @@ -1064,12 +1051,11 @@ * storage array for that property, and then the array is joined to result in * the final value set to the property/attribute. * - * @param {Object} note Annotation metadata + * @param {Object} bindings Binding metadata * @param {Node} node Bound node to initialize * @private */ - function setupCompoundBinding(note, node) { - let bindings = note.bindings; + function setupCompoundBinding(bindings, node) { for (let i=0; i="expression" + * Template-specific metadata are stored in the object returned, and node- + * specific metadata are stored in objects in its flattened `nodeInfoList` + * array. Only nodes in the template that were parsed as nodes of + * interest contain an object in `nodeInfoList`. Each `nodeInfo` object + * contains an `index` (`childNodes` index in parent) and optionally + * `parent`, which points to node info of its parent (including its index). + * + * `nodeInfo` metadata captured by this library include the following: * - * Generated data-structure: * [ * { - * id: '', + * id: '', // `id` * events: [ * { - * mode: ['auto'|''], * name: '' * value: '' * }, ... @@ -161,134 +154,116 @@ * value: '' * }, ... * ], - * parent: , - * index: + * parent: , + * index: * }, * ... * ] * * @param {HTMLTemplateElement} template - * @param {boolean=} stripWhiteSpace - * @param {Document=} ownerDocument - * @return {Array} + * @param {Object=} outerTemplateInfo + * @return {Object} Template metadata */ - static _prepareTemplate(template, stripWhiteSpace, ownerDocument) { - // TODO(kschaaf): File issue and/or remove when fixed - // hold a reference to content as _content to prevent odd Chrome gc issue - // nested templates also may receive their content as _content - let content = (template._content = template._content || template.content); + static _parseTemplate(template, outerTemplateInfo) { // since a template may be re-used, memo-ize notes. - if (!content._notes) { - let notes = content._notes = []; - notes.ownerDocument = ownerDocument || template.ownerDocument; - notes.stripWhiteSpace = stripWhiteSpace || template.hasAttribute('strip-whitespace'); - this._parseTemplateNode(content, {notes}); + if (!template._templateInfo) { + let templateInfo = template._templateInfo || (template._templateInfo = {}); + templateInfo.nodeInfo = []; + templateInfo.stripWhiteSpace = + (outerTemplateInfo && outerTemplateInfo.stripWhiteSpace) || + template.hasAttribute('strip-whitespace'); + this._parseTemplateNode(template.content, templateInfo, {parent: null}); } - return content._notes; + return template._templateInfo; } // add annotations gleaned from subtree at `node` to `notes` - static _parseTemplateNode(node, note) { + static _parseTemplateNode(node, templateInfo, nodeInfo) { let noted; - if (node.localName == 'template' && - !node.hasAttribute('preserve-content')) { - noted |= this._parseTemplate(node, note); + if (node.localName == 'template' && !node.hasAttribute('preserve-content')) { + noted = this._parseTemplateNestedContent(node, templateInfo, nodeInfo) || noted; } else if (node.localName === 'slot') { - node._hasInsertionPoint = true; + // For ShadyDom optimization, indicating there is an insertion point + templateInfo.hasInsertionPoint = true; } if (node.firstChild) { - noted |= this._parseTemplateChildNodes(node, note); + noted = this._parseTemplateChildNodes(node, templateInfo, nodeInfo) || noted; } - if (node.attributes) { - noted |= this._parseTemplateNodeAttributes(node, note); + if (node.hasAttributes && node.hasAttributes()) { + noted = this._parseTemplateNodeAttributes(node, templateInfo, nodeInfo) || noted; } return noted; } - // add annotations gleaned from children of `root` to `list`, `root`'s - // `note` is supplied as it is the note.parent of added annotations - static _parseTemplateChildNodes(root, note) { - let node = root.firstChild; - if (node) { - let i = 0; - while (node) { - // Wrap templates - if (node.localName == 'template') { - node = wrapTemplateExtension(node); - } - // collapse adjacent textNodes: fixes an IE issue that can cause - // text nodes to be inexplicably split =( - // note that root.normalize() should work but does not so we do this - // manually. - let next = node.nextSibling; - if (node.nodeType === Node.TEXT_NODE) { - let n = next; - while (n && (n.nodeType === Node.TEXT_NODE)) { - node.textContent += n.textContent; - next = n.nextSibling; - root.removeChild(n); - n = next; - } - // optionally strip whitespace - if (note.notes.stripWhiteSpace && !node.textContent.trim()) { - root.removeChild(node); - // decrement index since node is removed - i--; - } + static _parseTemplateChildNodes(root, templateInfo, nodeInfo) { + for (let node=root.firstChild, index=0, next; node; node=next) { + // Wrap templates + if (node.localName == 'template') { + node = wrapTemplateExtension(node); + } + // collapse adjacent textNodes: fixes an IE issue that can cause + // text nodes to be inexplicably split =( + // note that root.normalize() should work but does not so we do this + // manually. + next = node.nextSibling; + if (node.nodeType === Node.TEXT_NODE) { + let n = next; + while (n && (n.nodeType === Node.TEXT_NODE)) { + node.textContent += n.textContent; + next = n.nextSibling; + root.removeChild(n); + n = next; } - // if this node didn't get evacipated, parse it. - if (node.parentNode) { - let childNote = { - notes: note.notes, - parent: note, - index: i - } - if (this._parseTemplateNode(node, childNote)) { - note.notes.push(childNote); - } + // optionally strip whitespace + if (templateInfo.stripWhiteSpace && !node.textContent.trim()) { + root.removeChild(node); + continue; } - node = next; - i++; } + let childInfo = { index, parent: nodeInfo }; + if (this._parseTemplateNode(node, templateInfo, childInfo)) { + templateInfo.nodeInfo.push(childInfo); + } + index++; } } // Recurse into nested templates // 1. Parse annotations from the template and memoize them on - // content._notes (recurses into nested templates) + // content._templateInfo (recurses into nested templates) // 2. Remove template.content and store it in annotation list, where it // will be the responsibility of the host to set it back to the template // (this is both an optimization to avoid re-stamping nested template // children and avoids a bug in Chrome where nested template children // upgrade) - static _parseTemplate(node, note) { - let content = node.content.ownerDocument.createDocumentFragment(); - content._notes = this._prepareTemplate(node, - note.notes.stripWhiteSpace, note.notes.ownerDocument); + static _parseTemplateNestedContent(node, outerTemplateInfo, nodeInfo) { + let templateInfo = this._parseTemplate(node, outerTemplateInfo); + let content = templateInfo.content = + node.content.ownerDocument.createDocumentFragment(); content.appendChild(node.content); - note.templateContent = content; + nodeInfo.templateInfo = templateInfo; return true; } // add annotation data from attributes to the `annotation` for node `node` - static _parseTemplateNodeAttributes(node, note) { + static _parseTemplateNodeAttributes(node, templateInfo, nodeInfo) { // Make copy of original attribute list, since the order may change // as attributes are added and removed - let attrs = Array.prototype.slice.call(node.attributes); let noted; + let attrs = Array.from(node.attributes); for (let i=attrs.length-1, a; (a=attrs[i]); i--) { - noted |= this._parseTemplateNodeAttribute(node, note, a.name, a.value) + noted = this._parseTemplateNodeAttribute(node, templateInfo, nodeInfo, a.name, a.value) || noted; } return noted; } // construct annotation data from a generic attribute, or undefined - static _parseTemplateNodeAttribute(node, note, name, value) { + static _parseTemplateNodeAttribute(node, templateInfo, nodeInfo, name, value) { // events (on-*) if (name.slice(0, 3) === 'on-') { node.removeAttribute(name); - note.events = note.events || []; - note.events.push({ + nodeInfo.events = nodeInfo.events || []; + nodeInfo.events.push({ name: name.slice(3), value }); @@ -296,27 +271,28 @@ } // static id else if (name === 'id') { - note.id = value; + nodeInfo.id = value; return true; } } + static _contentForTemplate(template) { + let templateInfo = template.__templateInfo; + return (templateInfo && templateInfo.content) || template.content; + } + /** * Clones the provided template content and returns a document fragment * containing the cloned dom. * * The template is parsed (once and memoized) using this library's - * template parsing features, which identify nodes with declarative - * event listeners (`on-...``), `id`'s, `