Skip to content

Commit f62755d

Browse files
author
Steven Orvell
committed
Changed based on review feedback.
1 parent 5bd4afd commit f62755d

3 files changed

Lines changed: 248 additions & 109 deletions

File tree

lib/legacy/class.html

Lines changed: 94 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,20 @@
2424
attributeChanged: true,
2525
};
2626

27-
const noBehaviorCopyProps = Object.assign({
27+
const noCopyProps = Object.assign({
2828
behaviors: true
2929
}, metaProps);
3030

31-
const memoizedProps = Object.assign({
31+
const filteredProps = Object.assign({
3232
listeners: true,
3333
hostAttributes: true
3434
}, metaProps);
3535

3636
function copyProperties(source, target) {
3737
for (let p in source) {
38-
// NOTE: cannot copy `noBehaviorCopyProps` methods onto prototype at least because
38+
// NOTE: cannot copy `noCopyProps` methods onto prototype at least because
3939
// `super.ready` must be called and is not included in the user fn.
40-
if (!(p in noBehaviorCopyProps)) {
40+
if (!(p in noCopyProps)) {
4141
let pd = Object.getOwnPropertyDescriptor(source, p);
4242
if (pd) {
4343
Object.defineProperty(target, p, pd);
@@ -95,24 +95,19 @@
9595
// If lifecycle is called (super then me), order is
9696
// (1) C.created, (2) A.created, (3) B.created, (4) element.created
9797
// (again same as 1.x)
98-
function copyBehaviorProperties(behaviors, klass) {
99-
const meta = {};
100-
const superMeta = klass.prototype.__behaviorMetaProps;
101-
if (behaviors) {
102-
klass.prototype.__behaviorMetaProps = meta;
103-
for (let i=0; i<behaviors.length; i++) {
104-
copyProperties(behaviors[i], klass.prototype);
105-
memoizeBehaviorMetaProps(meta, behaviors[i], superMeta);
106-
}
98+
function copyAndFilterBehaviors(proto, behaviors, lifecycle) {
99+
for (let i=0; i<behaviors.length; i++) {
100+
copyAndFilterProperties(proto, behaviors[i], lifecycle);
107101
}
108-
klass.prototype.__behaviorMetaProps = meta;
102+
return lifecycle;
109103
}
110104

111-
function memoizeBehaviorMetaProps(meta, behavior, superMeta) {
112-
for (let p in memoizedProps) {
113-
if (behavior[p]) {
114-
meta[p] = meta[p] || (superMeta && superMeta[p] ? superMeta[p].slice() : []);
115-
meta[p].push(behavior[p]);
105+
function copyAndFilterProperties(proto, infoOrBehavior, lifecycle) {
106+
copyProperties(infoOrBehavior, proto);
107+
for (let p in filteredProps) {
108+
if (infoOrBehavior[p]) {
109+
lifecycle[p] = lifecycle[p] || [];
110+
lifecycle[p].push(infoOrBehavior[p]);
116111
}
117112
}
118113
}
@@ -182,13 +177,42 @@
182177
}
183178
}
184179

185-
function mergeProperties(a, b) {
180+
// Note, the properties in `b` are normalized before being merged
181+
// into `a`. This means that given `a == {}` and `b == {foo: String}`,
182+
// the result is `a == {foo: {type: String}}`.
183+
function mergeElementProperties(a, b) {
186184
for (let p in b) {
187185
a[p] = a[p] || {};
188186
mergePropertyInfo(a[p], b[p]);
189187
}
190188
}
191189

190+
/* Note about construction and extension of legacy classes.
191+
[Changed in Q4 2018 to optimize performance.]
192+
193+
When calling `Polymer` or `mixinBehaviors`, the generated class below is
194+
made. The list of behaviors was previously made into one generated class per
195+
behavior, but this is no longer the case as behaviors are now called
196+
manually. Note, there may *still* be multiple generated classes in the
197+
element's prototype chain if extension is used with `mixinBehaviors`.
198+
199+
The generated class is directly tied to the info object and behaviors
200+
used to create it. That list of behaviors is filtered so it's only the
201+
behaviors not active on the superclass. In order to call through to the
202+
entire list of lifecycle methods, it's important to call `super`.
203+
204+
The element's `properties`, `observers`, and the `_registered` method
205+
are controlled via the finalization mechanism provided by `Properties-Mixin`.
206+
207+
`Properties` and `observers` are collected by manually traversing the prototype
208+
chain and merging.
209+
210+
The `_registered` method is called via `LegacyElementMixin._finalizeClass`
211+
and is called on each prototype in the element's chain. Because a non-legacy
212+
element may extend a legacy one, it's important that work in `_registered`
213+
carefully act only once.
214+
215+
*/
192216
/**
193217
* @param {!PolymerInit} info Polymer info object
194218
* @param {function(new:HTMLElement)} Base base class to extend with info object
@@ -199,26 +223,31 @@
199223
*/
200224
function GenerateClassFromInfo(info, Base, behaviors) {
201225

226+
// manages behavior and lifecycle processing (filled in after class definition)
227+
let registered = false;
228+
let activeBehaviors;
229+
const lifecycle = {};
230+
202231
/** @private */
203232
class PolymerGenerated extends Base {
204233

205234
static get properties() {
206235
const properties = {};
207-
if (this.prototype.__behaviors) {
208-
for (let i=0, b; i < this.prototype.__behaviors.length; i++) {
209-
b = this.prototype.__behaviors[i];
210-
mergeProperties(properties, b.properties);
236+
if (activeBehaviors) {
237+
for (let i=0, b; i < activeBehaviors.length; i++) {
238+
b = activeBehaviors[i];
239+
mergeElementProperties(properties, b.properties);
211240
}
212241
}
213-
mergeProperties(properties, info.properties);
242+
mergeElementProperties(properties, info.properties);
214243
return properties;
215244
}
216245

217246
static get observers() {
218247
let observers = [];
219-
if (this.prototype.__behaviors) {
220-
for (let i=0, b; i < this.prototype.__behaviors.length; i++) {
221-
b = this.prototype.__behaviors[i];
248+
if (activeBehaviors) {
249+
for (let i=0, b; i < activeBehaviors.length; i++) {
250+
b = activeBehaviors[i];
222251
if (b.observers) {
223252
observers = observers.concat(b.observers);
224253
}
@@ -234,15 +263,13 @@
234263
* @return {void}
235264
*/
236265
created() {
237-
const list = this.__behaviorMetaProps.created;
266+
super.created();
267+
const list = lifecycle.created;
238268
if (list) {
239269
for (let i=0; i < list.length; i++) {
240270
list[i].call(this);
241271
}
242272
}
243-
if (info.created) {
244-
info.created.call(this);
245-
}
246273
}
247274

248275
/**
@@ -258,37 +285,37 @@
258285
`is` in `beforeRegister` as you could in 1.x.
259286
*/
260287
const proto = this;
261-
if (proto.hasOwnProperty('__behaviors')) {
262-
copyBehaviorProperties(proto.__behaviors, proto.constructor);
263-
}
264-
proto.__behaviorMetaProps = proto.__behaviorMetaProps || {};
265-
copyProperties(info, proto);
266-
// Note, previously these were interleaved.
267-
let list = proto.__behaviorMetaProps.beforeRegister;
268-
if (list) {
269-
for (let i=0; i < list.length; i++) {
270-
list[i].call(proto);
288+
// NOTE: this `registered` flag is required so that extensions
289+
// that do not override `_registered` do not try to "re-register"
290+
// this data. Only extensions that use `mixinBehaviors` will normally
291+
// have this implementation.
292+
if (!registered) {
293+
registered = true;
294+
if (activeBehaviors) {
295+
copyAndFilterBehaviors(proto, activeBehaviors, lifecycle);
271296
}
272-
}
273-
list = proto.__behaviorMetaProps.registered;
274-
if (list) {
275-
for (let i=0; i < list.length; i++) {
276-
list[i].call(proto);
297+
copyAndFilterProperties(proto, info, lifecycle);
298+
let list = lifecycle.beforeRegister;
299+
if (list) {
300+
for (let i=0; i < list.length; i++) {
301+
list[i].call(proto);
302+
}
303+
}
304+
list = lifecycle.registered;
305+
if (list) {
306+
for (let i=0; i < list.length; i++) {
307+
list[i].call(proto);
308+
}
277309
}
278-
}
279-
if (info.beforeRegister) {
280-
info.beforeRegister.call(proto);
281-
}
282-
if (info.registered) {
283-
info.registered.call(proto);
284310
}
285311
}
286312

287313
/**
288314
* @return {void}
289315
*/
290316
_applyListeners() {
291-
const list = this.__behaviorMetaProps.listeners;
317+
super._applyListeners();
318+
const list = lifecycle.listeners;
292319
if (list) {
293320
for (let i=0; i < list.length; i++) {
294321
const listeners = list[i];
@@ -299,11 +326,6 @@
299326
}
300327
}
301328
}
302-
if (info.listeners) {
303-
for (let l in info.listeners) {
304-
this._addMethodEventListenerToNode(this, l, info.listeners[l]);
305-
}
306-
}
307329
}
308330

309331
// note: exception to "super then me" rule;
@@ -313,12 +335,7 @@
313335
* @return {void}
314336
*/
315337
_ensureAttributes() {
316-
if (info.hostAttributes) {
317-
for (let a in info.hostAttributes) {
318-
this._ensureAttribute(a, info.hostAttributes[a]);
319-
}
320-
}
321-
const list = this.__behaviorMetaProps.hostAttributes;
338+
const list = lifecycle.hostAttributes;
322339
if (list) {
323340
for (let i=list.length-1; i >= 0; i--) {
324341
const hostAttributes = list[i];
@@ -327,52 +344,46 @@
327344
}
328345
}
329346
}
347+
super._ensureAttributes();
330348
}
331349

332350
/**
333351
* @return {void}
334352
*/
335353
ready() {
336354
super.ready();
337-
let list = this.__behaviorMetaProps.ready;
355+
let list = lifecycle.ready;
338356
if (list) {
339357
for (let i=0; i < list.length; i++) {
340358
list[i].call(this);
341359
}
342360
}
343-
if (info.ready) {
344-
info.ready.call(this);
345-
}
346361
}
347362

348363
/**
349364
* @return {void}
350365
*/
351366
attached() {
352-
let list = this.__behaviorMetaProps.attached;
367+
super.attached();
368+
let list = lifecycle.attached;
353369
if (list) {
354370
for (let i=0; i < list.length; i++) {
355371
list[i].call(this);
356372
}
357373
}
358-
if (info.attached) {
359-
info.attached.call(this);
360-
}
361374
}
362375

363376
/**
364377
* @return {void}
365378
*/
366379
detached() {
367-
let list = this.__behaviorMetaProps.detached;
380+
super.detached();
381+
let list = lifecycle.detached;
368382
if (list) {
369383
for (let i=0; i < list.length; i++) {
370384
list[i].call(this);
371385
}
372386
}
373-
if (info.detached) {
374-
info.detached.call(this);
375-
}
376387
}
377388

378389
/**
@@ -385,32 +396,29 @@
385396
* @return {void}
386397
*/
387398
attributeChanged(name, old, value) {
388-
let list = this.__behaviorMetaProps.attributeChanged;
399+
super.attributeChanged();
400+
let list = lifecycle.attributeChanged;
389401
if (list) {
390402
for (let i=0; i < list.length; i++) {
391403
list[i].call(this, name, old, value);
392404
}
393405
}
394-
if (info.attributeChanged) {
395-
info.attributeChanged.call(this, name, old, value);
396-
}
397406
}
398407
}
399408

400-
// apply behaviors
409+
// apply behaviors, note actual copying is done lazily at first instance creation
401410
if (behaviors) {
402411
// NOTE: ensure the behavior is extending a class with
403412
// legacy element api. This is necessary since behaviors expect to be able
404413
// to access 1.x legacy api.
405414
if (!Array.isArray(behaviors)) {
406415
behaviors = [behaviors];
407416
}
408-
let superBehaviors = PolymerGenerated.prototype.behaviors;
417+
let superBehaviors = Base.prototype.behaviors;
409418
// get flattened, deduped list of behaviors *not* already on super class
410-
behaviors = flattenBehaviors(behaviors, null, superBehaviors);
419+
activeBehaviors = flattenBehaviors(behaviors, null, superBehaviors);
411420
PolymerGenerated.prototype.behaviors = superBehaviors ?
412-
superBehaviors.concat(behaviors) : behaviors;
413-
PolymerGenerated.prototype.__behaviors = behaviors;
421+
superBehaviors.concat(behaviors) : activeBehaviors;
414422
}
415423

416424
PolymerGenerated.generatedFrom = info;

0 commit comments

Comments
 (0)