Skip to content

Commit

Permalink
Merge pull request #5304 from Polymer/strict-template-policy
Browse files Browse the repository at this point in the history
[3.x] Introduce strictTemplatePolicy
  • Loading branch information
kevinpschaaf authored Aug 14, 2018
2 parents 60beab8 + b94c28a commit 589ee49
Show file tree
Hide file tree
Showing 12 changed files with 2,278 additions and 606 deletions.
4 changes: 4 additions & 0 deletions lib/elements/dom-bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import '../utils/boot.js';
import { PropertyEffects } from '../mixins/property-effects.js';
import { OptionalMutableData } from '../mixins/mutable-data.js';
import { GestureEventListeners } from '../mixins/gesture-event-listeners.js';
import { strictTemplatePolicy } from '../utils/settings.js';

/**
* @constructor
Expand Down Expand Up @@ -51,6 +52,9 @@ export class DomBind extends domBindBase {

constructor() {
super();
if (strictTemplatePolicy) {
throw new Error(`strictTemplatePolicy: dom-bind not allowed`);
}
this.root = null;
this.$ = null;
this.__children = null;
Expand Down
8 changes: 6 additions & 2 deletions lib/elements/dom-if.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,12 @@ export class DomIf extends PolymerElement {
if (c$ && c$.length) {
// use first child parent, for case when dom-if may have been detached
let parent = c$[0].parentNode;
for (let i=0, n; (i<c$.length) && (n=c$[i]); i++) {
parent.removeChild(n);
// Instance children may be disconnected from parents when dom-if
// detaches if a tree was innerHTML'ed
if (parent) {
for (let i=0, n; (i<c$.length) && (n=c$[i]); i++) {
parent.removeChild(n);
}
}
}
this.__instance = null;
Expand Down
19 changes: 14 additions & 5 deletions lib/elements/dom-module.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,16 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
import '../utils/boot.js';

import { resolveUrl, pathFromUrl } from '../utils/resolve-url.js';
import { strictTemplatePolicy } from '../utils/settings.js';

let modules = {};
let lcModules = {};
function setModule(id, module) {
// store id separate from lowercased id so that
// in all cases mixedCase id will stored distinctly
// and lowercase version is a fallback
modules[id] = lcModules[id.toLowerCase()] = module;
}
function findModule(id) {
return modules[id] || lcModules[id.toLowerCase()];
}
Expand Down Expand Up @@ -122,12 +129,14 @@ export class DomModule extends HTMLElement {
register(id) {
id = id || this.id;
if (id) {
// Under strictTemplatePolicy, reject and null out any re-registered
// dom-module since it is ambiguous whether first-in or last-in is trusted
if (strictTemplatePolicy && findModule(id) !== undefined) {
setModule(id, null);
throw new Error(`strictTemplatePolicy: dom-module ${id} re-registered`);
}
this.id = id;
// store id separate from lowercased id so that
// in all cases mixedCase id will stored distinctly
// and lowercase version is a fallback
modules[id] = this;
lcModules[id.toLowerCase()] = this;
setModule(id, this);
styleOutsideTemplateCheck(this);
}
}
Expand Down
19 changes: 1 addition & 18 deletions lib/legacy/class.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ The complete set of contributors may be found at http://polymer.github.io/CONTRI
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
import { LegacyElementMixin } from './legacy-element-mixin.js';

import { DomModule } from '../elements/dom-module.js';
import { LegacyElementMixin } from './legacy-element-mixin.js';

let metaProps = {
attached: true,
Expand Down Expand Up @@ -150,22 +149,6 @@ function GenerateClassFromInfo(info, Base) {
return info.observers;
}

/**
* @return {HTMLTemplateElement} template for this class
*/
static get template() {
// get template first from any imperative set in `info._template`
return info._template ||
// next look in dom-module associated with this element's is.
DomModule && DomModule.import(this.is, 'template') ||
// next look for superclass template (note: use superclass symbol
// to ensure correct `this.is`)
Base.template ||
// finally fall back to `_template` in element's prototype.
this.prototype._template ||
null;
}

/**
* @return {void}
*/
Expand Down
55 changes: 48 additions & 7 deletions lib/mixins/element-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { pathFromUrl, resolveCss, resolveUrl as resolveUrl$0 } from '../utils/re
import { DomModule } from '../elements/dom-module.js';
import { PropertyEffects } from './property-effects.js';
import { PropertiesMixin } from './properties-mixin.js';
import { strictTemplatePolicy, allowTemplateFromDomModule } from '../utils/settings.js';

/**
* Element class mixin that provides the core API for Polymer's meta-programming
Expand Down Expand Up @@ -270,6 +271,29 @@ export const ElementMixin = dedupingMixin(base => {
}
}

/**
* Look up template from dom-module for element
*
* @param {!string} is Element name to look up
* @return {!HTMLTemplateElement} Template found in dom module, or
* undefined if not found
* @protected
*/
function getTemplateFromDomModule(is) {
let template = null;
// Under strictTemplatePolicy in 3.x+, dom-module lookup is only allowed
// when opted-in via allowTemplateFromDomModule
if (is && (!strictTemplatePolicy || allowTemplateFromDomModule)) {
template = DomModule.import(is, 'template');
// Under strictTemplatePolicy, require any element with an `is`
// specified to have a dom-module
if (strictTemplatePolicy && !template) {
throw new Error(`strictTemplatePolicy: expecting dom-module or null template for ${is}`);
}
}
return template;
}

/**
* @polymer
* @mixinClass
Expand Down Expand Up @@ -378,13 +402,30 @@ export const ElementMixin = dedupingMixin(base => {
* @return {!HTMLTemplateElement|string} Template to be stamped
*/
static get template() {
// Explanation of template-related properties:
// - constructor.template (this getter): the template for the class.
// This can come from the prototype (for legacy elements), from a
// dom-module, or from the super class's template (or can be overridden
// altogether by the user)
// - constructor._template: memoized version of constructor.template
// - prototype._template: working template for the element, which will be
// parsed and modified in place. It is a cloned version of
// constructor.template, saved in _finalizeClass(). Note that before
// this getter is called, for legacy elements this could be from a
// _template field on the info object passed to Polymer(), a behavior,
// or set in registered(); once the static getter runs, a clone of it
// will overwrite it on the prototype as the working template.
if (!this.hasOwnProperty(JSCompiler_renameProperty('_template', this))) {
this._template = DomModule && DomModule.import(
/** @type {PolymerElementConstructor}*/ (this).is, 'template') ||
// note: implemented so a subclass can retrieve the super
// template; call the super impl this way so that `this` points
// to the superclass.
Object.getPrototypeOf(/** @type {PolymerElementConstructor}*/ (this).prototype).constructor.template;
this._template =
// If user has put template on prototype (e.g. in legacy via registered
// callback or info object), prefer that first
this.prototype.hasOwnProperty(JSCompiler_renameProperty('_template', this.prototype)) ?
this.prototype._template :
// Look in dom-module associated with this element's is
(getTemplateFromDomModule(/** @type {PolymerElementConstructor}*/ (this).is) ||
// Next look for superclass template (call the super impl this
// way so that `this` points to the superclass)
Object.getPrototypeOf(/** @type {PolymerElementConstructor}*/ (this).prototype).constructor.template);
}
return this._template;
}
Expand Down Expand Up @@ -423,7 +464,7 @@ export const ElementMixin = dedupingMixin(base => {
if (meta) {
this._importPath = pathFromUrl(meta.url);
} else {
const module = DomModule && DomModule.import(/** @type {PolymerElementConstructor} */ (this).is);
const module = DomModule.import(/** @type {PolymerElementConstructor} */ (this).is);
this._importPath = (module && module.assetpath) ||
Object.getPrototypeOf(/** @type {PolymerElementConstructor}*/ (this).prototype).constructor.importPath;
}
Expand Down
46 changes: 42 additions & 4 deletions lib/utils/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,10 @@ export const setRootPath = function(path) {
};

/**
* A global callback used to sanitize any value before inserting it into the DOM. The callback signature is:
* A global callback used to sanitize any value before inserting it into the DOM.
* The callback signature is:
*
* Polymer = {
* sanitizeDOMValue: function(value, name, type, node) { ... }
* }
* function sanitizeDOMValue(value, name, type, node) { ... }
*
* Where:
*
Expand All @@ -66,6 +65,7 @@ export const setSanitizeDOMValue = function(newSanitizeDOMValue) {
sanitizeDOMValue = newSanitizeDOMValue;
};


/**
* Globally settable property to make Polymer Gestures use passive TouchEvent listeners when recognizing gestures.
* When set to `true`, gestures made from touch will not be able to prevent scrolling, allowing for smoother
Expand All @@ -83,3 +83,41 @@ export let passiveTouchGestures = false;
export const setPassiveTouchGestures = function(usePassive) {
passiveTouchGestures = usePassive;
};

/**
* Setting to ensure Polymer template evaluation only occurs based on tempates
* defined in trusted script. When true, `<dom-module>` re-registration is
* disallowed, `<dom-bind>` is disabled, and `<dom-if>`/`<dom-repeat>`
* templates will only evaluate in the context of a trusted element template.
*/
export let strictTemplatePolicy = false;

/**
* Sets `strictTemplatePolicy` globally for all elements
*
* @param {boolean} useStrictPolicy enable or disable strict template policy
* globally
* @return {void}
*/
export const setStrictTemplatePolicy = function(useStrictPolicy) {
strictTemplatePolicy = useStrictPolicy;
};

/**
* Setting to enable dom-module lookup from Polymer.Element. By default,
* templates must be defined in script using the `static get template()`
* getter and the `html` tag function. To enable legacy loading of templates
* via dom-module, set this flag to true.
*/
export let allowTemplateFromDomModule = false;

/**
* Sets `lookupTemplateFromDomModule` globally for all elements
*
* @param {boolean} allowDomModule enable or disable template lookup
* globally
* @return {void}
*/
export const setAllowTemplateFromDomModule = function(allowDomModule) {
allowTemplateFromDomModule = allowDomModule;
};
7 changes: 7 additions & 0 deletions lib/utils/templatize.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import './boot.js';

import { PropertyEffects } from '../mixins/property-effects.js';
import { MutableData } from '../mixins/mutable-data.js';
import { strictTemplatePolicy } from '../utils/settings.js';

// Base class for HTMLTemplateElement extension that has property effects
// machinery for propagating host properties to children. This is an ES5
Expand Down Expand Up @@ -504,6 +505,12 @@ and this string can then be deleted`;
* @suppress {invalidCasts}
*/
export function templatize(template, owner, options) {
// Under strictTemplatePolicy, the templatized element must be owned
// by a (trusted) Polymer element, indicated by existence of _methodHost;
// e.g. for dom-if & dom-repeat in main document, _methodHost is null
if (strictTemplatePolicy && !findMethodHost(template)) {
throw new Error('strictTemplatePolicy: template owner not trusted');
}
options = /** @type {!TemplatizeOptions} */(options || {});
if (template.__templatizeOwner) {
throw new Error('A <template> can only be templatized once');
Expand Down
Loading

0 comments on commit 589ee49

Please sign in to comment.