Skip to content

Commit

Permalink
Avoids using mixins for behaviors.
Browse files Browse the repository at this point in the history
(WIP) Experimental optimization to avoid using mixins for behaviors.
  • Loading branch information
Steven Orvell committed Oct 17, 2018
1 parent ce85eb9 commit 624513f
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 28 deletions.
191 changes: 164 additions & 27 deletions lib/legacy/class.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
*/

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

let metaProps = {
attached: true,
Expand All @@ -22,6 +23,20 @@ let metaProps = {
behaviors: true
};

function copyProperties(source, target) {
for (let p in source) {
// NOTE: cannot copy `metaProps` methods onto prototype at least because
// `super.ready` must be called and is not included in the user fn.
if (!(p in metaProps)) {
let pd = Object.getOwnPropertyDescriptor(source, p);
if (pd) {
Object.defineProperty(target, p, pd);
}
}
}
}

// TODO(sorvell): this breaks `Polymer.mixinBehaviors`; should fix via factoring
/**
* Applies a "legacy" behavior or array of behaviors to the provided class.
*
Expand Down Expand Up @@ -96,8 +111,8 @@ function _mixinBehaviors(behaviors, klass) {
for (let i=0; i<behaviors.length; i++) {
let b = behaviors[i];
if (b) {
klass = Array.isArray(b) ? _mixinBehaviors(b, klass) :
GenerateClassFromInfo(b, klass);
Array.isArray(b) ? _mixinBehaviors(b, klass) :
copyProperties(b, klass.prototype);
}
}
return klass;
Expand Down Expand Up @@ -129,6 +144,52 @@ function flattenBehaviors(behaviors, list, exclude) {
return list;
}

function mergePropertyInfo(a, b) {
if (typeof b === 'function') {
a.type = b;
return;
}
if ('type' in b) {
a.type = b.type;
}
if ('value' in b) {
a.value = b.value;
}
// readOnly: cannot become false and takes on `computed` value
a.readOnly = a.readOnly || Boolean(a.computed) || b.readOnly || Boolean(b.computed);
// computed: first in wins
if ('computed' in b) {
a.computed = a.computed || b.computed;
}
// reflectToAttribute: cannot become false
if ('reflectToAttribute' in b) {
a.reflectToAttribute = a.reflectToAttribute || b.reflectToAttribute;
}
// notify: cannot become false (only install 1x)
if ('notify' in b) {
a.notify = a.notify || b.notify;
}
// make this an array
if ('observer' in b) {
if (!a.observer) {
a.observer = b.observer;
// we have both: make array and push onto
} else {
if (!Array.isArray(a.observer)) {
a.observer = [a.observer];
}
a.observer.push(b.observer);
}
}
}

function mergeProperties(a, b) {
for (let p in b) {
a[p] = a[p] || {};
mergePropertyInfo(a[p], b[p]);
}
}

/**
* @param {!PolymerInit} info Polymer info object
* @param {function(new:HTMLElement)} Base base class to extend with info object
Expand All @@ -142,18 +203,45 @@ function GenerateClassFromInfo(info, Base) {
class PolymerGenerated extends Base {

static get properties() {
return info.properties;
const properties = {};
if (this.prototype.behaviors) {
for (let i=0, b; i < this.prototype.behaviors.length; i++) {
b = this.prototype.behaviors[i];
mergeProperties(properties, b.properties);
}
}
mergeProperties(properties, info.properties);
return properties;
}

static get observers() {
return info.observers;
let observers = [];
if (this.prototype.behaviors) {
for (let i=0, b; i < this.prototype.behaviors.length; i++) {
b = this.prototype.behaviors[i];
if (b.observers) {
observers = observers.concat(b.observers);
}
}
}
if (info.observers) {
observers = observers.concat(info.observers);
}
return observers;
}

/**
* @return {void}
*/
created() {
super.created();
if (this.behaviors) {
for (let i=0, b; i < this.behaviors.length; i++) {
b = this.behaviors[i];
if (b.created) {
b.created.call(this);
}
}
}
if (info.created) {
info.created.call(this);
}
Expand All @@ -163,14 +251,24 @@ function GenerateClassFromInfo(info, Base) {
* @return {void}
*/
_registered() {
super._registered();
/* NOTE: `beforeRegister` is called here for bc, but the behavior
is different than in 1.x. In 1.0, the method was called *after*
mixing prototypes together but *before* processing of meta-objects.
However, dynamic effects can still be set here and can be done either
in `beforeRegister` or `registered`. It is no longer possible to set
`is` in `beforeRegister` as you could in 1.x.
*/
if (this.behaviors) {
for (let i=0, b; i < this.behaviors.length; i++) {
b = this.behaviors[i];
if (b.beforeRegister) {
b.beforeRegister.call(Object.getPrototypeOf(this));
}
if (b.registered) {
b.registered.call(Object.getPrototypeOf(this));
}
}
}
if (info.beforeRegister) {
info.beforeRegister.call(Object.getPrototypeOf(this));
}
Expand All @@ -183,7 +281,16 @@ function GenerateClassFromInfo(info, Base) {
* @return {void}
*/
_applyListeners() {
super._applyListeners();
if (this.behaviors) {
for (let i=0, b; i < this.behaviors.length; i++) {
b = this.behaviors[i];
if (b.listeners) {
for (let l in b.listeners) {
this._addMethodEventListenerToNode(this, l, b.listeners[l]);
}
}
}
}
if (info.listeners) {
for (let l in info.listeners) {
this._addMethodEventListenerToNode(this, l, info.listeners[l]);
Expand All @@ -203,14 +310,31 @@ function GenerateClassFromInfo(info, Base) {
this._ensureAttribute(a, info.hostAttributes[a]);
}
}
super._ensureAttributes();
if (this.behaviors) {
for (let i=this.behaviors.length-1, b; i >= 0; i--) {
b = this.behaviors[i];
if (b.hostAttributes) {
for (let a in b.hostAttributes) {
this._ensureAttribute(a, b.hostAttributes[a]);
}
}
}
}
}

/**
* @return {void}
*/
ready() {
super.ready();
if (this.behaviors) {
for (let i=0, b; i < this.behaviors.length; i++) {
b = this.behaviors[i];
if (b.ready) {
b.ready.call(this);
}
}
}
if (info.ready) {
info.ready.call(this);
}
Expand All @@ -220,7 +344,14 @@ function GenerateClassFromInfo(info, Base) {
* @return {void}
*/
attached() {
super.attached();
if (this.behaviors) {
for (let i=0, b; i < this.behaviors.length; i++) {
b = this.behaviors[i];
if (b.attached) {
b.attached.call(this);
}
}
}
if (info.attached) {
info.attached.call(this);
}
Expand All @@ -230,7 +361,14 @@ function GenerateClassFromInfo(info, Base) {
* @return {void}
*/
detached() {
super.detached();
if (this.behaviors) {
for (let i=0, b; i < this.behaviors.length; i++) {
b = this.behaviors[i];
if (b.detached) {
b.detached.call(this);
}
}
}
if (info.detached) {
info.detached.call(this);
}
Expand All @@ -246,7 +384,14 @@ function GenerateClassFromInfo(info, Base) {
* @return {void}
*/
attributeChanged(name, old, value) {
super.attributeChanged(name, old, value);
if (this.behaviors) {
for (let i=0, b; i < this.behaviors.length; i++) {
b = this.behaviors[i];
if (b.attributeChanged) {
b.attributeChanged.call(this, name, old, value);
}
}
}
if (info.attributeChanged) {
info.attributeChanged.call(this, name, old, value);
}
Expand All @@ -255,16 +400,7 @@ function GenerateClassFromInfo(info, Base) {

PolymerGenerated.generatedFrom = info;

for (let p in info) {
// NOTE: cannot copy `metaProps` methods onto prototype at least because
// `super.ready` must be called and is not included in the user fn.
if (!(p in metaProps)) {
let pd = Object.getOwnPropertyDescriptor(info, p);
if (pd) {
Object.defineProperty(PolymerGenerated.prototype, p, pd);
}
}
}
copyProperties(info, PolymerGenerated.prototype);

return PolymerGenerated;
}
Expand Down Expand Up @@ -343,12 +479,13 @@ export const Class = function(info, mixin) {
if (!info) {
console.warn(`Polymer's Class function requires \`info\` argument`);
}
const baseWithBehaviors = info.behaviors ?
// note: mixinBehaviors ensures `LegacyElementMixin`.
mixinBehaviors(info.behaviors, HTMLElement) :
LegacyElementMixin(HTMLElement);
const baseWithMixin = mixin ? mixin(baseWithBehaviors) : baseWithBehaviors;
const klass = GenerateClassFromInfo(info, baseWithMixin);
const base = mixin ? mixin(LegacyElementMixin(HTMLElement)) :
LegacyElementMixin(HTMLElement);
let klass = class extends base {};
if (info.behaviors) {
mixinBehaviors(info.behaviors, klass);
}
klass = GenerateClassFromInfo(info, klass);
// decorate klass with registration info
klass.is = info.is;
return klass;
Expand Down
9 changes: 8 additions & 1 deletion lib/mixins/element-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,14 @@ export const ElementMixin = dedupingMixin(base => {
}
// always add observer
if (info.observer) {
proto._createPropertyObserver(name, info.observer, allProps[info.observer]);
if (Array.isArray(info.observer)) {
for (let i=0; i < info.observer.length; i++) {
const o = info.observer[i];
proto._createPropertyObserver(name, o, allProps[o]);
}
} else {
proto._createPropertyObserver(name, info.observer, allProps[info.observer]);
}
}
// always create the mapping from attribute back to property for deserialization.
proto._addPropertyToAttributeMap(name);
Expand Down

0 comments on commit 624513f

Please sign in to comment.