Skip to content

Commit

Permalink
Manually merge changes from #5418
Browse files Browse the repository at this point in the history
  • Loading branch information
Steven Orvell committed Nov 6, 2018
1 parent 0ca8407 commit d5e0043
Show file tree
Hide file tree
Showing 4 changed files with 733 additions and 601 deletions.
154 changes: 74 additions & 80 deletions lib/legacy/class.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@ const metaProps = {
attributeChanged: true,
};

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

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

function copyProperties(source, target) {
for (let p in source) {
// NOTE: cannot copy `noBehaviorCopyProps` methods onto prototype at least because
// NOTE: cannot copy `excludeProps` methods onto prototype at least because
// `super.ready` must be called and is not included in the user fn.
if (!(p in noBehaviorCopyProps)) {
if (!(p in excludeProps)) {
let pd = Object.getOwnPropertyDescriptor(source, p);
if (pd) {
Object.defineProperty(target, p, pd);
Expand Down Expand Up @@ -91,24 +91,18 @@ export function mixinBehaviors(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 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);
}
function applyBehaviors(proto, behaviors, lifecycle) {
for (let i=0; i<behaviors.length; i++) {
applyInfo(proto, behaviors[i], lifecycle);
}
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]);
function applyInfo(proto, info, lifecycle) {
copyProperties(info, proto);
for (let p in lifecycleProps) {
if (info[p]) {
lifecycle[p] = lifecycle[p] || [];
lifecycle[p].push(info[p]);
}
}
}
Expand Down Expand Up @@ -178,13 +172,38 @@ function mergePropertyInfo(a, b) {
}
}

function mergeProperties(a, b) {
// Note, the properties in `b` are normalized before being merged
// into `a`. This means that given `a == {}` and `b == {foo: String}`,
// the result is `a == {foo: {type: String}}`.
function mergeElementProperties(a, b) {
for (let p in b) {
a[p] = a[p] || {};
mergePropertyInfo(a[p], b[p]);
}
}

/* Note about construction and extension of legacy classes.
[Changed in Q4 2018 to optimize performance.]
When calling `Polymer` or `mixinBehaviors`, the generated class below is
made. The list of behaviors was previously made into one generated class per
behavior, but this is no longer the case as behaviors are now called
manually. Note, there may *still* be multiple generated classes in the
element's prototype chain if extension is used with `mixinBehaviors`.
The generated class is directly tied to the info object and behaviors
used to create it. That list of behaviors is filtered so it's only the
behaviors not active on the superclass. In order to call through to the
entire list of lifecycle methods, it's important to call `super`.
The element's `properties`, `observers`, and the `_registered` method
are controlled via the finalization mechanism provided by `PropertiesMixin`.
`Properties` and `observers` are collected by manually traversing the prototype
chain and merging. The `_registered` method is called via
`LegacyElementMixin._finalizeClass`.
*/
/**
* @param {!PolymerInit} info Polymer info object
* @param {function(new:HTMLElement)} Base base class to extend with info object
Expand All @@ -195,25 +214,30 @@ function mergeProperties(a, b) {
*/
function GenerateClassFromInfo(info, Base, behaviors) {

// manages behavior and lifecycle processing (filled in after class definition)
let behaviorList;
const lifecycle = {};

/** @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];
mergeProperties(properties, b.properties);
if (behaviorList) {
for (let i=0, b; i < behaviorList.length; i++) {
b = behaviorList[i];
mergeElementProperties(properties, b.properties);
}
}
mergeProperties(properties, info.properties);
mergeElementProperties(properties, info.properties);
return properties;
}

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 (behaviorList) {
for (let i=0, b; i < behaviorList.length; i++) {
b = behaviorList[i];
if (b.observers) {
observers = observers.concat(b.observers);
}
Expand All @@ -229,15 +253,13 @@ function GenerateClassFromInfo(info, Base, behaviors) {
* @return {void}
*/
created() {
const list = this.__behaviorMetaProps.created;
super.created();
const list = lifecycle.created;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this);
}
}
if (info.created) {
info.created.call(this);
}
}

/**
Expand All @@ -253,37 +275,30 @@ function GenerateClassFromInfo(info, Base, behaviors) {
`is` in `beforeRegister` as you could in 1.x.
*/
const proto = this;
if (proto.hasOwnProperty('__behaviors')) {
copyBehaviorProperties(proto.__behaviors, proto.constructor);
if (behaviorList) {
applyBehaviors(proto, behaviorList, lifecycle);
}
proto.__behaviorMetaProps = proto.__behaviorMetaProps || {};
copyProperties(info, proto);
// Note, previously these were interleaved.
let list = proto.__behaviorMetaProps.beforeRegister;
applyInfo(proto, info, lifecycle);
let list = lifecycle.beforeRegister;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(proto);
}
}
list = proto.__behaviorMetaProps.registered;
list = lifecycle.registered;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(proto);
}
}
if (info.beforeRegister) {
info.beforeRegister.call(proto);
}
if (info.registered) {
info.registered.call(proto);
}
}

/**
* @return {void}
*/
_applyListeners() {
const list = this.__behaviorMetaProps.listeners;
super._applyListeners();
const list = lifecycle.listeners;
if (list) {
for (let i=0; i < list.length; i++) {
const listeners = list[i];
Expand All @@ -294,11 +309,6 @@ function GenerateClassFromInfo(info, Base, behaviors) {
}
}
}
if (info.listeners) {
for (let l in info.listeners) {
this._addMethodEventListenerToNode(this, l, info.listeners[l]);
}
}
}

// note: exception to "super then me" rule;
Expand All @@ -308,12 +318,7 @@ function GenerateClassFromInfo(info, Base, behaviors) {
* @return {void}
*/
_ensureAttributes() {
if (info.hostAttributes) {
for (let a in info.hostAttributes) {
this._ensureAttribute(a, info.hostAttributes[a]);
}
}
const list = this.__behaviorMetaProps.hostAttributes;
const list = lifecycle.hostAttributes;
if (list) {
for (let i=list.length-1; i >= 0; i--) {
const hostAttributes = list[i];
Expand All @@ -322,56 +327,49 @@ function GenerateClassFromInfo(info, Base, behaviors) {
}
}
}
super._ensureAttributes();
}

/**
* @return {void}
*/
ready() {
super.ready();
let list = this.__behaviorMetaProps.ready;
let list = lifecycle.ready;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this);
}
}
if (info.ready) {
info.ready.call(this);
}
}

/**
* @return {void}
*/
attached() {
let list = this.__behaviorMetaProps.attached;
super.attached();
let list = lifecycle.attached;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this);
}
}
if (info.attached) {
info.attached.call(this);
}
}

/**
* @return {void}
*/
detached() {
let list = this.__behaviorMetaProps.detached;
super.detached();
let list = lifecycle.detached;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this);
}
}
if (info.detached) {
info.detached.call(this);
}
}

/**
*
* Implements native Custom Elements `attributeChangedCallback` to
* set an attribute value to a property via `_attributeToProperty`.
*
Expand All @@ -381,33 +379,29 @@ function GenerateClassFromInfo(info, Base, behaviors) {
* @return {void}
*/
attributeChanged(name, old, value) {
let list = this.__behaviorMetaProps.attributeChanged;
super.attributeChanged();
let list = lifecycle.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);
}
}

}

// apply behaviors
// apply behaviors, note actual copying is done lazily at first instance creation
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;
let superBehaviors = Base.prototype.behaviors;
// get flattened, deduped list of behaviors *not* already on super class
behaviors = flattenBehaviors(behaviors, null, superBehaviors);
behaviorList = flattenBehaviors(behaviors, null, superBehaviors);
PolymerGenerated.prototype.behaviors = superBehaviors ?
superBehaviors.concat(behaviors) : behaviors;
PolymerGenerated.prototype.__behaviors = behaviors;
superBehaviors.concat(behaviors) : behaviorList;
}

PolymerGenerated.generatedFrom = info;
Expand Down
7 changes: 6 additions & 1 deletion lib/legacy/legacy-element-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,12 @@ export const LegacyElementMixin = dedupingMixin((base) => {
}

static _finalizeClass() {
this.prototype._registered();
// Note, call `_registered` only if this specific prototype has
// an implementation; this ensures `_registered` is not called
// on extenders that do not implement it.
if (this.prototype.hasOwnProperty('_registered')) {
this.prototype._registered();
}
super._finalizeClass();
}

Expand Down
Loading

0 comments on commit d5e0043

Please sign in to comment.