Skip to content

Commit 36c4dfa

Browse files
committed
Updates based on code review.
1 parent a515c99 commit 36c4dfa

7 files changed

Lines changed: 190 additions & 48 deletions

File tree

lib/elements/dom-if.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,9 @@ export class DomIf extends PolymerElement {
251251
if (c$ && c$.length) {
252252
// use first child parent, for case when dom-if may have been detached
253253
let parent = c$[0].parentNode;
254-
if (parent) {
254+
// Instance children may be disconnected from parents when dom-if
255+
// detaches if a tree was innerHTML'ed
256+
if (parent) {
255257
for (let i=0, n; (i<c$.length) && (n=c$[i]); i++) {
256258
parent.removeChild(n);
257259
}

lib/elements/dom-module.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ import { strictTemplatePolicy } from '../utils/settings.js';
1414

1515
let modules = {};
1616
let lcModules = {};
17+
function setModule(id, module) {
18+
// store id separate from lowercased id so that
19+
// in all cases mixedCase id will stored distinctly
20+
// and lowercase version is a fallback
21+
modules[id] = lcModules[id.toLowerCase()] = module;
22+
}
1723
function findModule(id) {
1824
return modules[id] || lcModules[id.toLowerCase()];
1925
}
@@ -122,16 +128,14 @@ export class DomModule extends HTMLElement {
122128
register(id) {
123129
id = id || this.id;
124130
if (id) {
125-
if (strictTemplatePolicy && findModule(id)) {
126-
modules[id] = lcModules[id.toLowerCase()] = null;
127-
throw new Error(`strictTemplatePolicy: dom-module ${id} registered twice`);
131+
// Under strictTemplatePolicy, reject and null out any re-registered
132+
// dom-module since it is ambiguous whether first-in or last-in is trusted
133+
if (strictTemplatePolicy && findModule(id) !== undefined) {
134+
setModule(id, null);
135+
throw new Error(`strictTemplatePolicy: dom-module ${id} re-registered`);
128136
}
129137
this.id = id;
130-
// store id separate from lowercased id so that
131-
// in all cases mixedCase id will stored distinctly
132-
// and lowercase version is a fallback
133-
modules[id] = this;
134-
lcModules[id.toLowerCase()] = this;
138+
setModule(id, this);
135139
styleOutsideTemplateCheck(this);
136140
}
137141
}

lib/legacy/class.js

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -150,22 +150,6 @@ function GenerateClassFromInfo(info, Base) {
150150
return info.observers;
151151
}
152152

153-
/**
154-
* @return {HTMLTemplateElement} template for this class
155-
*/
156-
static get template() {
157-
// get template first from any imperative set in `info._template`
158-
return info._template ||
159-
// next look in dom-module associated with this element's is.
160-
(!strictTemplatePolicy && (DomModule && DomModule.import(this.is, 'template'))) ||
161-
// next look for superclass template (note: use superclass symbol
162-
// to ensure correct `this.is`)
163-
Base.template ||
164-
// finally fall back to `_template` in element's prototype.
165-
this.prototype._template ||
166-
null;
167-
}
168-
169153
/**
170154
* @return {void}
171155
*/
@@ -363,5 +347,10 @@ export const Class = function(info) {
363347
LegacyElementMixin(HTMLElement));
364348
// decorate klass with registration info
365349
klass.is = info.is;
366-
return klass;
350+
// if user provided template on info, make sure the static _template
351+
// is set so the static template getter uses it
352+
if (info._template !== undefined) {
353+
klass._template = info._template;
354+
}
355+
return klass;
367356
};

lib/mixins/element-mixin.js

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { pathFromUrl, resolveCss, resolveUrl as resolveUrl$0 } from '../utils/re
1616
import { DomModule } from '../elements/dom-module.js';
1717
import { PropertyEffects } from './property-effects.js';
1818
import { PropertiesMixin } from './properties-mixin.js';
19-
import { strictTemplatePolicy } from '../utils/settings.js';
19+
import { strictTemplatePolicy, allowTemplateFromDomModule } from '../utils/settings.js';
2020

2121
/**
2222
* Element class mixin that provides the core API for Polymer's meta-programming
@@ -270,6 +270,27 @@ export const ElementMixin = dedupingMixin(base => {
270270
}
271271
}
272272

273+
/**
274+
* Look up template from dom-module for element
275+
*
276+
* @param {!string} is Element name to look up
277+
* @return {!HTMLTemplateElement} Template found in dom module, or
278+
* undefined if not found
279+
* @private
280+
*/
281+
function getTemplateFromDomModule(is) {
282+
let template = null;
283+
if (is && allowTemplateFromDomModule) {
284+
template = DomModule.import(is, 'template')
285+
// Under strictTemplatePolicy, require any element with an `is`
286+
// specified to have a dom-module
287+
if (strictTemplatePolicy && !template) {
288+
throw new Error(`strictTemplatePolicy: expecting dom-module or null template for ${is}`);
289+
}
290+
}
291+
return template;
292+
}
293+
273294
/**
274295
* @polymer
275296
* @mixinClass
@@ -379,15 +400,18 @@ export const ElementMixin = dedupingMixin(base => {
379400
*/
380401
static get template() {
381402
if (!this.hasOwnProperty(JSCompiler_renameProperty('_template', this))) {
382-
this._template = (!strictTemplatePolicy && DomModule && DomModule.import(
383-
/** @type {PolymerElementConstructor}*/ (this).is, 'template')) ||
384-
// note: implemented so a subclass can retrieve the super
385-
// template; call the super impl this way so that `this` points
386-
// to the superclass.
387-
Object.getPrototypeOf(/** @type {PolymerElementConstructor}*/ (this).prototype).constructor.template;
403+
this._template =
404+
// Look in dom-module associated with this element's is
405+
getTemplateFromDomModule(/** @type {PolymerElementConstructor}*/ (this).is) ||
406+
// Next look for superclass template (call the super impl this
407+
// way so that `this` points to the superclass)
408+
Object.getPrototypeOf(/** @type {PolymerElementConstructor}*/ (this).prototype).constructor.template ||
409+
// Finally, fall back to any _template set on prototype, e.g.
410+
// via registered callback
411+
this.prototype._template;
388412
}
389413
return this._template;
390-
}
414+
}
391415

392416
/**
393417
* Path matching the url from which the element was imported.
@@ -414,7 +438,7 @@ export const ElementMixin = dedupingMixin(base => {
414438
if (meta) {
415439
this._importPath = pathFromUrl(meta.url);
416440
} else {
417-
const module = DomModule && DomModule.import(/** @type {PolymerElementConstructor} */ (this).is);
441+
const module = DomModule.import(/** @type {PolymerElementConstructor} */ (this).is);
418442
this._importPath = (module && module.assetpath) ||
419443
Object.getPrototypeOf(/** @type {PolymerElementConstructor}*/ (this).prototype).constructor.importPath;
420444
}

lib/utils/settings.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ export const setPassiveTouchGestures = function(usePassive) {
8686

8787
/**
8888
* Setting to ensure Polymer template evaluation only occurs based on tempates
89-
* defined in trusted script. When true, `<dom-module>` based template lookup
90-
* is disabled, `<dom-bind>` is disabled, and `<dom-if>`/`<dom-repeat>`
89+
* defined in trusted script. When true, `<dom-module>` re-registration is
90+
* disallowed, `<dom-bind>` is disabled, and `<dom-if>`/`<dom-repeat>`
9191
* templates will only evaluate in the context of a trusted element template.
9292
*/
9393
export let strictTemplatePolicy = false;
@@ -102,3 +102,22 @@ export let strictTemplatePolicy = false;
102102
export const setStrictTemplatePolicy = function(useStrictPolicy) {
103103
strictTemplatePolicy = useStrictPolicy;
104104
};
105+
106+
/**
107+
* Setting to enable dom-module lookup from Polymer.Element. By default,
108+
* templates must be defined in script using the `static get template()`
109+
* getter and the `html` tag function. To enable legacy loading of templates
110+
* via dom-module, set this flag to true.
111+
*/
112+
export let allowTemplateFromDomModule = false;
113+
114+
/**
115+
* Sets `lookupTemplateFromDomModule` globally for all elements
116+
*
117+
* @param {boolean} allowDomModule enable or disable template lookup
118+
* globally
119+
* @return {void}
120+
*/
121+
export const setAllowTemplateFromDomModule = function(allowDomModule) {
122+
allowTemplateFromDomModule = allowDomModule;
123+
};

lib/utils/templatize.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,9 @@ and this string can then be deleted`;
496496
* @suppress {invalidCasts}
497497
*/
498498
export function templatize(template, owner, options) {
499+
// Under strictTemplatePolicy, the templatized element must be owned
500+
// by a (trusted) Polymer element, indicated by existence of _methodHost;
501+
// e.g. for dom-if & dom-repeat in main document, _methodHost is null
499502
if (strictTemplatePolicy && !owner._methodHost) {
500503
throw new Error('strictTemplatePolicy: template owner not trusted');
501504
}

test/unit/strict-template-policy.html

Lines changed: 112 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,34 +37,57 @@
3737
</head>
3838
<body>
3939

40-
<dom-module id="trusted-element">
40+
<dom-module id="no-dm">
4141
<template>Should never be used</template>
4242
<script type="module">
4343
import {PolymerElement} from '../../polymer-element.js';
4444
class TrustedElement extends PolymerElement {
45-
static get is() { return 'trusted-element'; }
45+
static get is() { return 'no-dm'; }
4646
}
4747
customElements.define(TrustedElement.is, TrustedElement);
4848
</script>
4949
</dom-module>
5050

51-
<dom-module id="trusted-element-legacy">
51+
<dom-module id="no-dm-legacy">
5252
<template>Should never be used</template>
5353
<script type="module">
5454
import {Polymer} from '../../polymer-legacy.js';
55-
Polymer({is: 'trusted-element-legacy'});
55+
Polymer({is: 'no-dm-legacy'});
5656
</script>
5757
</dom-module>
5858

59+
<dom-module id="trusted-element">
60+
<template>Trusted</template>
61+
<script type="module">
62+
import {PolymerElement} from '../../polymer-element.js';
63+
class TrustedElement extends PolymerElement {
64+
static get is() { return 'trusted-element'; }
65+
}
66+
customElements.define(TrustedElement.is, TrustedElement);
67+
</script>
68+
</dom-module>
69+
70+
<dom-module id="trusted-element-legacy">
71+
<template>Trusted</template>
72+
<script type="module">
73+
import {Polymer} from '../../polymer-legacy.js';
74+
Polymer({is: 'trusted-element-legacy'});
75+
</script>
76+
</dom-module>
77+
5978
<div id="target"></div>
60-
79+
6180
<script type="module">
81+
import {PolymerElement} from '../../polymer-element.js';
82+
import {Polymer} from '../../polymer-legacy.js';
6283
import {flush} from '../../lib/utils/flush.js';
84+
import {setAllowTemplateFromDomModule} from '../../lib/utils/settings.js';
6385

6486
suite('strictTemplatePolicy', function() {
6587

6688
teardown(function() {
6789
window.uncaughtErrorFilter = window.top.uncaughtErrorFilter = null;
90+
document.getElementById('target').textContent = '';
6891
});
6992

7093
// Errors thrown in custom element reactions are not thrown up
@@ -76,7 +99,9 @@
7699
// Safari errors are thrown on the top window, sometimes not, so
77100
// catch in both places
78101
window.uncaughtErrorFilter = window.top.uncaughtErrorFilter = function(err) {
79-
uncaughtError = err;
102+
if (!uncaughtError) {
103+
uncaughtError = err;
104+
}
80105
return true;
81106
};
82107
assert.throws(function() {
@@ -131,28 +156,104 @@
131156
});
132157

133158
test('dom-module never used', function() {
134-
var el = document.createElement('trusted-element');
159+
var el = document.createElement('no-dm');
135160
document.getElementById('target').appendChild(el);
136161
assert.notOk(el.shadowRoot);
137162
});
138163

139164
test('dom-module never used (legacy)', function() {
140-
var el = document.createElement('trusted-element-legacy');
165+
var el = document.createElement('no-dm-legacy');
141166
document.getElementById('target').appendChild(el);
142167
assert.notOk(el.shadowRoot);
143168
});
144169

145-
test('dom-module re-registration throws', function() {
170+
// From this point down, dom-module lookup is globally enabled
171+
test('setAllowTemplateFromDomModule', function() {
172+
setAllowTemplateFromDomModule(true);
173+
});
174+
175+
test('dom-module after registration', function() {
146176
assertThrows(function() {
147177
document.getElementById('target').innerHTML =
148178
'<dom-module id="trusted-element">' +
149179
' <template>' +
150180
' <div id="injected"></div>'+
151181
' </template>`' +
152-
'</dom-module>';
153-
}, /trusted-element registered twice/);
182+
'</dom-module>' +
183+
'<trusted-element></trusted-element>';
184+
}, /trusted-element re-registered/);
185+
const el = document.querySelector('trusted-element');
186+
assert.notOk(el && el.shadowRoot && el.shadowRoot.querySelector('#injected'));
187+
});
188+
189+
test('dom-module before registration', function() {
190+
document.getElementById('target').innerHTML =
191+
'<dom-module id="has-no-template">' +
192+
' <template>' +
193+
' <div id="injected"></div>'+
194+
' </template>`' +
195+
'</dom-module>';
196+
class HasNoTemplate extends PolymerElement {
197+
static get is() { return 'has-no-template'; }
198+
static get template() { return null; }
199+
}
200+
customElements.define(HasNoTemplate.is, HasNoTemplate);
201+
let el = document.createElement('has-no-template');
202+
document.getElementById('target').appendChild(el);
203+
assert.notOk(el.shadowRoot);
204+
});
205+
206+
test('dom-module after registration (legacy)', function() {
207+
assertThrows(function() {
208+
document.getElementById('target').innerHTML =
209+
'<dom-module id="trusted-element-legacy">' +
210+
' <template>' +
211+
' <div id="injected"></div>'+
212+
' </template>`' +
213+
'</dom-module>' +
214+
'<trusted-element-legacy></trusted-element-legacy>';
215+
}, /trusted-element-legacy re-registered/);
216+
const el = document.querySelector('trusted-element-legacy');
217+
assert.notOk(el && el.shadowRoot && el.shadowRoot.querySelector('#injected'));
218+
});
219+
220+
test('dom-module before registration (legacy)', function() {
221+
document.getElementById('target').innerHTML =
222+
'<dom-module id="has-no-template-legacy">' +
223+
' <template>' +
224+
' <div id="injected"></div>'+
225+
' </template>`' +
226+
'</dom-module>';
227+
Polymer({
228+
is: 'has-no-template-legacy',
229+
_template: null
230+
});
231+
let el = document.createElement('has-no-template-legacy');
232+
document.getElementById('target').appendChild(el);
233+
assert.notOk(document.querySelector('has-no-template-legacy').shadowRoot);
154234
});
155235

236+
test('element without explicit template throws', function() {
237+
assertThrows(function() {
238+
class HasNoTemplateThrows extends PolymerElement {
239+
static get is() { return 'has-no-template-throws'; }
240+
}
241+
customElements.define(HasNoTemplateThrows.is, HasNoTemplateThrows);
242+
var el = document.createElement('has-no-template-throws');
243+
document.getElementById('target').appendChild(el);
244+
}, /expecting dom-module or null template/);
245+
});
246+
247+
test('element without explicit template throws (legacy)', function() {
248+
assertThrows(function() {
249+
Polymer({
250+
is: 'has-no-template-throws-legacy'
251+
});
252+
var el = document.createElement('has-no-template-throws-legacy');
253+
document.getElementById('target').appendChild(el);
254+
}, /expecting dom-module or null template/);
255+
});
256+
156257
});
157258
</script>
158259

0 commit comments

Comments
 (0)