Skip to content

Commit

Permalink
Updates ported from perf-opt branch
Browse files Browse the repository at this point in the history
Merging manually from #5418.
  • Loading branch information
Steven Orvell committed Nov 1, 2018
1 parent b6ae42d commit a08c984
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 206 deletions.
237 changes: 118 additions & 119 deletions lib/legacy/class.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,30 @@ 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 = {
const metaProps = {
attached: true,
detached: true,
ready: true,
created: true,
beforeRegister: true,
registered: true,
attributeChanged: true,
// meta objects
behaviors: true
};

const noBehaviorCopyProps = Object.assign({
behaviors: true
}, metaProps);

const memoizedProps = Object.assign({
listeners: true,
hostAttributes: true
}, metaProps);

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

// TODO(sorvell): this breaks `Polymer.mixinBehaviors`; should fix via factoring
/**
* Applies a "legacy" behavior or array of behaviors to the provided class.
*
Expand All @@ -53,37 +59,7 @@ function copyProperties(source, target) {
* @suppress {invalidCasts, checkTypes}
*/
export function mixinBehaviors(behaviors, klass) {
if (behaviors) {
klass = applyBehaviors(behaviors, klass);
}
// provides behaviors functionality
return GenerateClassFromInfo({}, klass);
}


function applyBehaviors(behaviors, klass) {
if (!behaviors) {
klass = /** @type {HTMLElement} */(klass); // eslint-disable-line no-self-assign
return klass;
}
// NOTE: ensure the behavior is extending a class with
// legacy element api. This is necessary since behaviors expect to be able
// to access 1.x legacy api.
klass = class extends LegacyElementMixin(klass) {};
if (!Array.isArray(behaviors)) {
behaviors = [behaviors];
}
let superBehaviors = klass.prototype.behaviors;
// get flattened, deduped list of behaviors *not* already on super class
behaviors = flattenBehaviors(behaviors, null, superBehaviors);
// mixin new behaviors
klass = _applyBehaviors(behaviors, klass);
if (superBehaviors) {
behaviors = superBehaviors.concat(behaviors);
}
// Set behaviors on prototype for BC...
klass.prototype.behaviors = behaviors;
return klass;
return GenerateClassFromInfo({}, LegacyElementMixin(klass), behaviors);
}

// NOTE:
Expand Down Expand Up @@ -116,15 +92,26 @@ function applyBehaviors(behaviors, klass) {
// If lifecycle is called (super then me), order is
// (1) C.created, (2) A.created, (3) B.created, (4) element.created
// (again same as 1.x)
function _applyBehaviors(behaviors, klass) {
for (let i=0; i<behaviors.length; i++) {
let b = behaviors[i];
if (b) {
Array.isArray(b) ? _applyBehaviors(b, klass) :
copyProperties(b, klass.prototype);
function copyBehaviorProperties(behaviors, klass) {
const meta = {};
const superMeta = klass.prototype.__behaviorMetaProps;
if (behaviors) {
klass.prototype.__behaviorMetaProps = meta;
for (let i=0; i<behaviors.length; i++) {
copyProperties(behaviors[i], klass.prototype);
memoizeBehaviorMetaProps(meta, behaviors[i], superMeta);
}
}
klass.prototype.__behaviorMetaProps = meta;
}

function memoizeBehaviorMetaProps(meta, behavior, superMeta) {
for (let p in memoizedProps) {
if (behavior[p]) {
meta[p] = meta[p] || (superMeta && superMeta[p] ? superMeta[p].slice() : []);
meta[p].push(behavior[p]);
}
}
return klass;
}

/**
Expand Down Expand Up @@ -202,20 +189,20 @@ function mergeProperties(a, b) {
/**
* @param {!PolymerInit} info Polymer info object
* @param {function(new:HTMLElement)} Base base class to extend with info object
* @param {Object} behaviors behaviors to copy into the element
* @return {function(new:HTMLElement)} Generated class
* @suppress {checkTypes}
* @private
*/
function GenerateClassFromInfo(info, Base) {
function GenerateClassFromInfo(info, Base, behaviors) {

/** @private */
class PolymerGenerated extends Base {

static get properties() {
const properties = {};
if (this.prototype.behaviors) {
for (let i=0, b; i < this.prototype.behaviors.length; i++) {
b = this.prototype.behaviors[i];
if (this.prototype.__behaviors) {
for (let i=0, b; i < this.prototype.__behaviors.length; i++) {
b = this.prototype.__behaviors[i];
mergeProperties(properties, b.properties);
}
}
Expand All @@ -225,9 +212,9 @@ function GenerateClassFromInfo(info, Base) {

static get observers() {
let observers = [];
if (this.prototype.behaviors) {
for (let i=0, b; i < this.prototype.behaviors.length; i++) {
b = this.prototype.behaviors[i];
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);
}
Expand All @@ -243,12 +230,10 @@ function GenerateClassFromInfo(info, Base) {
* @return {void}
*/
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);
}
const list = this.__behaviorMetaProps.created;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this);
}
}
if (info.created) {
Expand All @@ -259,43 +244,53 @@ function GenerateClassFromInfo(info, Base) {
/**
* @return {void}
*/
// Called on element prototype
_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.
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));
}
const proto = this;
if (proto.hasOwnProperty('__behaviors')) {
copyBehaviorProperties(proto.__behaviors, proto.constructor);
}
proto.__behaviorMetaProps = proto.__behaviorMetaProps || {};
copyProperties(info, proto);
// Note, previously these were interleaved.
let list = proto.__behaviorMetaProps.beforeRegister;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(proto);
}
}
list = proto.__behaviorMetaProps.registered;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(proto);
}
}
if (info.beforeRegister) {
info.beforeRegister.call(Object.getPrototypeOf(this));
info.beforeRegister.call(proto);
}
if (info.registered) {
info.registered.call(Object.getPrototypeOf(this));
info.registered.call(proto);
}
}

/**
* @return {void}
*/
_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]);
const list = this.__behaviorMetaProps.listeners;
if (list) {
for (let i=0; i < list.length; i++) {
const listeners = list[i];
if (listeners) {
for (let l in listeners) {
this._addMethodEventListenerToNode(this, l, listeners[l]);
}
}
}
Expand All @@ -319,14 +314,13 @@ function GenerateClassFromInfo(info, Base) {
this._ensureAttribute(a, info.hostAttributes[a]);
}
}
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]);
const list = this.__behaviorMetaProps.hostAttributes;
if (list) {
for (let i=list.length-1; i >= 0; i--) {
const hostAttributes = list[i];
for (let a in hostAttributes) {
this._ensureAttribute(a, hostAttributes[a]);
}
}
}
}
}
Expand All @@ -336,12 +330,10 @@ function GenerateClassFromInfo(info, Base) {
*/
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);
}
let list = this.__behaviorMetaProps.ready;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this);
}
}
if (info.ready) {
Expand All @@ -353,12 +345,10 @@ function GenerateClassFromInfo(info, Base) {
* @return {void}
*/
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);
}
let list = this.__behaviorMetaProps.attached;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this);
}
}
if (info.attached) {
Expand All @@ -370,12 +360,10 @@ function GenerateClassFromInfo(info, Base) {
* @return {void}
*/
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);
}
let list = this.__behaviorMetaProps.detached;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this);
}
}
if (info.detached) {
Expand All @@ -384,6 +372,7 @@ function GenerateClassFromInfo(info, Base) {
}

/**
*
* Implements native Custom Elements `attributeChangedCallback` to
* set an attribute value to a property via `_attributeToProperty`.
*
Expand All @@ -393,23 +382,36 @@ function GenerateClassFromInfo(info, Base) {
* @return {void}
*/
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);
}
let list = this.__behaviorMetaProps.attributeChanged;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this, name, old, value);
}
}
if (info.attributeChanged) {
info.attributeChanged.call(this, name, old, value);
}
}
}

}

PolymerGenerated.generatedFrom = info;
// apply behaviors
if (behaviors) {
// NOTE: ensure the behavior is extending a class with
// legacy element api. This is necessary since behaviors expect to be able
// to access 1.x legacy api.
if (!Array.isArray(behaviors)) {
behaviors = [behaviors];
}
let superBehaviors = PolymerGenerated.prototype.behaviors;
// get flattened, deduped list of behaviors *not* already on super class
behaviors = flattenBehaviors(behaviors, null, superBehaviors);
PolymerGenerated.prototype.behaviors = superBehaviors ?
superBehaviors.concat(behaviors) : behaviors;
PolymerGenerated.prototype.__behaviors = behaviors;
}

copyProperties(info, PolymerGenerated.prototype);
PolymerGenerated.generatedFrom = info;

return PolymerGenerated;
}
Expand Down Expand Up @@ -486,14 +488,11 @@ function GenerateClassFromInfo(info, Base) {
*/
export const Class = function(info, mixin) {
if (!info) {
console.warn(`Polymer's Class function requires \`info\` argument`);
console.warn('Polymer.Class requires `info` argument');
}
let klass = mixin ? mixin(LegacyElementMixin(HTMLElement)) :
LegacyElementMixin(HTMLElement);
if (info.behaviors) {
klass = applyBehaviors(info.behaviors, klass);
}
klass = GenerateClassFromInfo(info, klass);
klass = GenerateClassFromInfo(info, klass, info.behaviors);
// decorate klass with registration info
klass.is = info.is;
return klass;
Expand Down
Loading

0 comments on commit a08c984

Please sign in to comment.