|
24 | 24 | attributeChanged: true, |
25 | 25 | }; |
26 | 26 |
|
27 | | - const noBehaviorCopyProps = Object.assign({ |
| 27 | + const noCopyProps = Object.assign({ |
28 | 28 | behaviors: true |
29 | 29 | }, metaProps); |
30 | 30 |
|
31 | | - const memoizedProps = Object.assign({ |
| 31 | + const filteredProps = Object.assign({ |
32 | 32 | listeners: true, |
33 | 33 | hostAttributes: true |
34 | 34 | }, metaProps); |
35 | 35 |
|
36 | 36 | function copyProperties(source, target) { |
37 | 37 | 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 |
39 | 39 | // `super.ready` must be called and is not included in the user fn. |
40 | | - if (!(p in noBehaviorCopyProps)) { |
| 40 | + if (!(p in noCopyProps)) { |
41 | 41 | let pd = Object.getOwnPropertyDescriptor(source, p); |
42 | 42 | if (pd) { |
43 | 43 | Object.defineProperty(target, p, pd); |
|
95 | 95 | // If lifecycle is called (super then me), order is |
96 | 96 | // (1) C.created, (2) A.created, (3) B.created, (4) element.created |
97 | 97 | // (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); |
107 | 101 | } |
108 | | - klass.prototype.__behaviorMetaProps = meta; |
| 102 | + return lifecycle; |
109 | 103 | } |
110 | 104 |
|
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]); |
116 | 111 | } |
117 | 112 | } |
118 | 113 | } |
|
182 | 177 | } |
183 | 178 | } |
184 | 179 |
|
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) { |
186 | 184 | for (let p in b) { |
187 | 185 | a[p] = a[p] || {}; |
188 | 186 | mergePropertyInfo(a[p], b[p]); |
189 | 187 | } |
190 | 188 | } |
191 | 189 |
|
| 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 | + */ |
192 | 216 | /** |
193 | 217 | * @param {!PolymerInit} info Polymer info object |
194 | 218 | * @param {function(new:HTMLElement)} Base base class to extend with info object |
|
199 | 223 | */ |
200 | 224 | function GenerateClassFromInfo(info, Base, behaviors) { |
201 | 225 |
|
| 226 | + // manages behavior and lifecycle processing (filled in after class definition) |
| 227 | + let registered = false; |
| 228 | + let activeBehaviors; |
| 229 | + const lifecycle = {}; |
| 230 | + |
202 | 231 | /** @private */ |
203 | 232 | class PolymerGenerated extends Base { |
204 | 233 |
|
205 | 234 | static get properties() { |
206 | 235 | 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); |
211 | 240 | } |
212 | 241 | } |
213 | | - mergeProperties(properties, info.properties); |
| 242 | + mergeElementProperties(properties, info.properties); |
214 | 243 | return properties; |
215 | 244 | } |
216 | 245 |
|
217 | 246 | static get observers() { |
218 | 247 | 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]; |
222 | 251 | if (b.observers) { |
223 | 252 | observers = observers.concat(b.observers); |
224 | 253 | } |
|
234 | 263 | * @return {void} |
235 | 264 | */ |
236 | 265 | created() { |
237 | | - const list = this.__behaviorMetaProps.created; |
| 266 | + super.created(); |
| 267 | + const list = lifecycle.created; |
238 | 268 | if (list) { |
239 | 269 | for (let i=0; i < list.length; i++) { |
240 | 270 | list[i].call(this); |
241 | 271 | } |
242 | 272 | } |
243 | | - if (info.created) { |
244 | | - info.created.call(this); |
245 | | - } |
246 | 273 | } |
247 | 274 |
|
248 | 275 | /** |
|
258 | 285 | `is` in `beforeRegister` as you could in 1.x. |
259 | 286 | */ |
260 | 287 | 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); |
271 | 296 | } |
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 | + } |
277 | 309 | } |
278 | | - } |
279 | | - if (info.beforeRegister) { |
280 | | - info.beforeRegister.call(proto); |
281 | | - } |
282 | | - if (info.registered) { |
283 | | - info.registered.call(proto); |
284 | 310 | } |
285 | 311 | } |
286 | 312 |
|
287 | 313 | /** |
288 | 314 | * @return {void} |
289 | 315 | */ |
290 | 316 | _applyListeners() { |
291 | | - const list = this.__behaviorMetaProps.listeners; |
| 317 | + super._applyListeners(); |
| 318 | + const list = lifecycle.listeners; |
292 | 319 | if (list) { |
293 | 320 | for (let i=0; i < list.length; i++) { |
294 | 321 | const listeners = list[i]; |
|
299 | 326 | } |
300 | 327 | } |
301 | 328 | } |
302 | | - if (info.listeners) { |
303 | | - for (let l in info.listeners) { |
304 | | - this._addMethodEventListenerToNode(this, l, info.listeners[l]); |
305 | | - } |
306 | | - } |
307 | 329 | } |
308 | 330 |
|
309 | 331 | // note: exception to "super then me" rule; |
|
313 | 335 | * @return {void} |
314 | 336 | */ |
315 | 337 | _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; |
322 | 339 | if (list) { |
323 | 340 | for (let i=list.length-1; i >= 0; i--) { |
324 | 341 | const hostAttributes = list[i]; |
|
327 | 344 | } |
328 | 345 | } |
329 | 346 | } |
| 347 | + super._ensureAttributes(); |
330 | 348 | } |
331 | 349 |
|
332 | 350 | /** |
333 | 351 | * @return {void} |
334 | 352 | */ |
335 | 353 | ready() { |
336 | 354 | super.ready(); |
337 | | - let list = this.__behaviorMetaProps.ready; |
| 355 | + let list = lifecycle.ready; |
338 | 356 | if (list) { |
339 | 357 | for (let i=0; i < list.length; i++) { |
340 | 358 | list[i].call(this); |
341 | 359 | } |
342 | 360 | } |
343 | | - if (info.ready) { |
344 | | - info.ready.call(this); |
345 | | - } |
346 | 361 | } |
347 | 362 |
|
348 | 363 | /** |
349 | 364 | * @return {void} |
350 | 365 | */ |
351 | 366 | attached() { |
352 | | - let list = this.__behaviorMetaProps.attached; |
| 367 | + super.attached(); |
| 368 | + let list = lifecycle.attached; |
353 | 369 | if (list) { |
354 | 370 | for (let i=0; i < list.length; i++) { |
355 | 371 | list[i].call(this); |
356 | 372 | } |
357 | 373 | } |
358 | | - if (info.attached) { |
359 | | - info.attached.call(this); |
360 | | - } |
361 | 374 | } |
362 | 375 |
|
363 | 376 | /** |
364 | 377 | * @return {void} |
365 | 378 | */ |
366 | 379 | detached() { |
367 | | - let list = this.__behaviorMetaProps.detached; |
| 380 | + super.detached(); |
| 381 | + let list = lifecycle.detached; |
368 | 382 | if (list) { |
369 | 383 | for (let i=0; i < list.length; i++) { |
370 | 384 | list[i].call(this); |
371 | 385 | } |
372 | 386 | } |
373 | | - if (info.detached) { |
374 | | - info.detached.call(this); |
375 | | - } |
376 | 387 | } |
377 | 388 |
|
378 | 389 | /** |
|
385 | 396 | * @return {void} |
386 | 397 | */ |
387 | 398 | attributeChanged(name, old, value) { |
388 | | - let list = this.__behaviorMetaProps.attributeChanged; |
| 399 | + super.attributeChanged(); |
| 400 | + let list = lifecycle.attributeChanged; |
389 | 401 | if (list) { |
390 | 402 | for (let i=0; i < list.length; i++) { |
391 | 403 | list[i].call(this, name, old, value); |
392 | 404 | } |
393 | 405 | } |
394 | | - if (info.attributeChanged) { |
395 | | - info.attributeChanged.call(this, name, old, value); |
396 | | - } |
397 | 406 | } |
398 | 407 | } |
399 | 408 |
|
400 | | - // apply behaviors |
| 409 | + // apply behaviors, note actual copying is done lazily at first instance creation |
401 | 410 | if (behaviors) { |
402 | 411 | // NOTE: ensure the behavior is extending a class with |
403 | 412 | // legacy element api. This is necessary since behaviors expect to be able |
404 | 413 | // to access 1.x legacy api. |
405 | 414 | if (!Array.isArray(behaviors)) { |
406 | 415 | behaviors = [behaviors]; |
407 | 416 | } |
408 | | - let superBehaviors = PolymerGenerated.prototype.behaviors; |
| 417 | + let superBehaviors = Base.prototype.behaviors; |
409 | 418 | // get flattened, deduped list of behaviors *not* already on super class |
410 | | - behaviors = flattenBehaviors(behaviors, null, superBehaviors); |
| 419 | + activeBehaviors = flattenBehaviors(behaviors, null, superBehaviors); |
411 | 420 | PolymerGenerated.prototype.behaviors = superBehaviors ? |
412 | | - superBehaviors.concat(behaviors) : behaviors; |
413 | | - PolymerGenerated.prototype.__behaviors = behaviors; |
| 421 | + superBehaviors.concat(behaviors) : activeBehaviors; |
414 | 422 | } |
415 | 423 |
|
416 | 424 | PolymerGenerated.generatedFrom = info; |
|
0 commit comments