OLD | NEW |
| (Empty) |
1 if (!HTMLElement.prototype.createShadowRoot | |
2 || window.__forceShadowDomPolyfill) { | |
3 | |
4 /* | |
5 * Copyright 2013 The Polymer Authors. All rights reserved. | |
6 * Use of this source code is governed by a BSD-style | |
7 * license that can be found in the LICENSE file. | |
8 */ | |
9 (function() { | |
10 // TODO(jmesserly): fix dart:html to use unprefixed name | |
11 if (Element.prototype.webkitCreateShadowRoot) { | |
12 Element.prototype.webkitCreateShadowRoot = function() { | |
13 return window.ShadowDOMPolyfill.wrapIfNeeded(this).createShadowRoot(); | |
14 }; | |
15 } | |
16 })(); | |
17 | |
18 // Copyright 2012 Google Inc. | |
19 // | |
20 // Licensed under the Apache License, Version 2.0 (the "License"); | |
21 // you may not use this file except in compliance with the License. | |
22 // You may obtain a copy of the License at | |
23 // | |
24 // http://www.apache.org/licenses/LICENSE-2.0 | |
25 // | |
26 // Unless required by applicable law or agreed to in writing, software | |
27 // distributed under the License is distributed on an "AS IS" BASIS, | |
28 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
29 // See the License for the specific language governing permissions and | |
30 // limitations under the License. | |
31 | |
32 (function(global) { | |
33 'use strict'; | |
34 | |
35 var PROP_ADD_TYPE = 'add'; | |
36 var PROP_UPDATE_TYPE = 'update'; | |
37 var PROP_RECONFIGURE_TYPE = 'reconfigure'; | |
38 var PROP_DELETE_TYPE = 'delete'; | |
39 var ARRAY_SPLICE_TYPE = 'splice'; | |
40 | |
41 // Detect and do basic sanity checking on Object/Array.observe. | |
42 function detectObjectObserve() { | |
43 if (typeof Object.observe !== 'function' || | |
44 typeof Array.observe !== 'function') { | |
45 return false; | |
46 } | |
47 | |
48 var records = []; | |
49 | |
50 function callback(recs) { | |
51 records = recs; | |
52 } | |
53 | |
54 var test = {}; | |
55 Object.observe(test, callback); | |
56 test.id = 1; | |
57 test.id = 2; | |
58 delete test.id; | |
59 Object.deliverChangeRecords(callback); | |
60 if (records.length !== 3) | |
61 return false; | |
62 | |
63 // TODO(rafaelw): Remove this when new change record type names make it to | |
64 // chrome release. | |
65 if (records[0].type == 'new' && | |
66 records[1].type == 'updated' && | |
67 records[2].type == 'deleted') { | |
68 PROP_ADD_TYPE = 'new'; | |
69 PROP_UPDATE_TYPE = 'updated'; | |
70 PROP_RECONFIGURE_TYPE = 'reconfigured'; | |
71 PROP_DELETE_TYPE = 'deleted'; | |
72 } else if (records[0].type != 'add' || | |
73 records[1].type != 'update' || | |
74 records[2].type != 'delete') { | |
75 console.error('Unexpected change record names for Object.observe. ' + | |
76 'Using dirty-checking instead'); | |
77 return false; | |
78 } | |
79 Object.unobserve(test, callback); | |
80 | |
81 test = [0]; | |
82 Array.observe(test, callback); | |
83 test[1] = 1; | |
84 test.length = 0; | |
85 Object.deliverChangeRecords(callback); | |
86 if (records.length != 2) | |
87 return false; | |
88 if (records[0].type != ARRAY_SPLICE_TYPE || | |
89 records[1].type != ARRAY_SPLICE_TYPE) { | |
90 return false; | |
91 } | |
92 Array.unobserve(test, callback); | |
93 | |
94 return true; | |
95 } | |
96 | |
97 var hasObserve = detectObjectObserve(); | |
98 | |
99 function detectEval() { | |
100 // don't test for eval if document has CSP securityPolicy object and we can
see that | |
101 // eval is not supported. This avoids an error message in console even when
the exception | |
102 // is caught | |
103 if (global.document && | |
104 'securityPolicy' in global.document && | |
105 !global.document.securityPolicy.allowsEval) { | |
106 return false; | |
107 } | |
108 | |
109 try { | |
110 var f = new Function('', 'return true;'); | |
111 return f(); | |
112 } catch (ex) { | |
113 return false; | |
114 } | |
115 } | |
116 | |
117 var hasEval = detectEval(); | |
118 | |
119 function isIndex(s) { | |
120 return +s === s >>> 0; | |
121 } | |
122 | |
123 function toNumber(s) { | |
124 return +s; | |
125 } | |
126 | |
127 function isObject(obj) { | |
128 return obj === Object(obj); | |
129 } | |
130 | |
131 var numberIsNaN = global.Number.isNaN || function isNaN(value) { | |
132 return typeof value === 'number' && global.isNaN(value); | |
133 } | |
134 | |
135 function areSameValue(left, right) { | |
136 if (left === right) | |
137 return left !== 0 || 1 / left === 1 / right; | |
138 if (numberIsNaN(left) && numberIsNaN(right)) | |
139 return true; | |
140 | |
141 return left !== left && right !== right; | |
142 } | |
143 | |
144 var createObject = ('__proto__' in {}) ? | |
145 function(obj) { return obj; } : | |
146 function(obj) { | |
147 var proto = obj.__proto__; | |
148 if (!proto) | |
149 return obj; | |
150 var newObject = Object.create(proto); | |
151 Object.getOwnPropertyNames(obj).forEach(function(name) { | |
152 Object.defineProperty(newObject, name, | |
153 Object.getOwnPropertyDescriptor(obj, name)); | |
154 }); | |
155 return newObject; | |
156 }; | |
157 | |
158 var identStart = '[\$_a-zA-Z]'; | |
159 var identPart = '[\$_a-zA-Z0-9]'; | |
160 var ident = identStart + '+' + identPart + '*'; | |
161 var elementIndex = '(?:[0-9]|[1-9]+[0-9]+)'; | |
162 var identOrElementIndex = '(?:' + ident + '|' + elementIndex + ')'; | |
163 var path = '(?:' + identOrElementIndex + ')(?:\\s*\\.\\s*' + identOrElementInd
ex + ')*'; | |
164 var pathRegExp = new RegExp('^' + path + '$'); | |
165 | |
166 function isPathValid(s) { | |
167 if (typeof s != 'string') | |
168 return false; | |
169 s = s.trim(); | |
170 | |
171 if (s == '') | |
172 return true; | |
173 | |
174 if (s[0] == '.') | |
175 return false; | |
176 | |
177 return pathRegExp.test(s); | |
178 } | |
179 | |
180 var constructorIsPrivate = {}; | |
181 | |
182 function Path(s, privateToken) { | |
183 if (privateToken !== constructorIsPrivate) | |
184 throw Error('Use Path.get to retrieve path objects'); | |
185 | |
186 if (s.trim() == '') | |
187 return this; | |
188 | |
189 if (isIndex(s)) { | |
190 this.push(s); | |
191 return this; | |
192 } | |
193 | |
194 s.split(/\s*\.\s*/).filter(function(part) { | |
195 return part; | |
196 }).forEach(function(part) { | |
197 this.push(part); | |
198 }, this); | |
199 | |
200 if (hasEval && this.length) { | |
201 this.getValueFrom = this.compiledGetValueFromFn(); | |
202 } | |
203 } | |
204 | |
205 // TODO(rafaelw): Make simple LRU cache | |
206 var pathCache = {}; | |
207 | |
208 function getPath(pathString) { | |
209 if (pathString instanceof Path) | |
210 return pathString; | |
211 | |
212 if (pathString == null) | |
213 pathString = ''; | |
214 | |
215 if (typeof pathString !== 'string') | |
216 pathString = String(pathString); | |
217 | |
218 var path = pathCache[pathString]; | |
219 if (path) | |
220 return path; | |
221 if (!isPathValid(pathString)) | |
222 return invalidPath; | |
223 var path = new Path(pathString, constructorIsPrivate); | |
224 pathCache[pathString] = path; | |
225 return path; | |
226 } | |
227 | |
228 Path.get = getPath; | |
229 | |
230 Path.prototype = createObject({ | |
231 __proto__: [], | |
232 valid: true, | |
233 | |
234 toString: function() { | |
235 return this.join('.'); | |
236 }, | |
237 | |
238 getValueFrom: function(obj, directObserver) { | |
239 for (var i = 0; i < this.length; i++) { | |
240 if (obj == null) | |
241 return; | |
242 obj = obj[this[i]]; | |
243 } | |
244 return obj; | |
245 }, | |
246 | |
247 iterateObjects: function(obj, observe) { | |
248 for (var i = 0; i < this.length; i++) { | |
249 if (i) | |
250 obj = obj[this[i - 1]]; | |
251 if (!obj) | |
252 return; | |
253 observe(obj); | |
254 } | |
255 }, | |
256 | |
257 compiledGetValueFromFn: function() { | |
258 var accessors = this.map(function(ident) { | |
259 return isIndex(ident) ? '["' + ident + '"]' : '.' + ident; | |
260 }); | |
261 | |
262 var str = ''; | |
263 var pathString = 'obj'; | |
264 str += 'if (obj != null'; | |
265 var i = 0; | |
266 for (; i < (this.length - 1); i++) { | |
267 var ident = this[i]; | |
268 pathString += accessors[i]; | |
269 str += ' &&\n ' + pathString + ' != null'; | |
270 } | |
271 str += ')\n'; | |
272 | |
273 pathString += accessors[i]; | |
274 | |
275 str += ' return ' + pathString + ';\nelse\n return undefined;'; | |
276 return new Function('obj', str); | |
277 }, | |
278 | |
279 setValueFrom: function(obj, value) { | |
280 if (!this.length) | |
281 return false; | |
282 | |
283 for (var i = 0; i < this.length - 1; i++) { | |
284 if (!isObject(obj)) | |
285 return false; | |
286 obj = obj[this[i]]; | |
287 } | |
288 | |
289 if (!isObject(obj)) | |
290 return false; | |
291 | |
292 obj[this[i]] = value; | |
293 return true; | |
294 } | |
295 }); | |
296 | |
297 var invalidPath = new Path('', constructorIsPrivate); | |
298 invalidPath.valid = false; | |
299 invalidPath.getValueFrom = invalidPath.setValueFrom = function() {}; | |
300 | |
301 var MAX_DIRTY_CHECK_CYCLES = 1000; | |
302 | |
303 function dirtyCheck(observer) { | |
304 var cycles = 0; | |
305 while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) { | |
306 cycles++; | |
307 } | |
308 if (global.testingExposeCycleCount) | |
309 global.dirtyCheckCycleCount = cycles; | |
310 | |
311 return cycles > 0; | |
312 } | |
313 | |
314 function objectIsEmpty(object) { | |
315 for (var prop in object) | |
316 return false; | |
317 return true; | |
318 } | |
319 | |
320 function diffIsEmpty(diff) { | |
321 return objectIsEmpty(diff.added) && | |
322 objectIsEmpty(diff.removed) && | |
323 objectIsEmpty(diff.changed); | |
324 } | |
325 | |
326 function diffObjectFromOldObject(object, oldObject) { | |
327 var added = {}; | |
328 var removed = {}; | |
329 var changed = {}; | |
330 var oldObjectHas = {}; | |
331 | |
332 for (var prop in oldObject) { | |
333 var newValue = object[prop]; | |
334 | |
335 if (newValue !== undefined && newValue === oldObject[prop]) | |
336 continue; | |
337 | |
338 if (!(prop in object)) { | |
339 removed[prop] = undefined; | |
340 continue; | |
341 } | |
342 | |
343 if (newValue !== oldObject[prop]) | |
344 changed[prop] = newValue; | |
345 } | |
346 | |
347 for (var prop in object) { | |
348 if (prop in oldObject) | |
349 continue; | |
350 | |
351 added[prop] = object[prop]; | |
352 } | |
353 | |
354 if (Array.isArray(object) && object.length !== oldObject.length) | |
355 changed.length = object.length; | |
356 | |
357 return { | |
358 added: added, | |
359 removed: removed, | |
360 changed: changed | |
361 }; | |
362 } | |
363 | |
364 var eomTasks = []; | |
365 function runEOMTasks() { | |
366 if (!eomTasks.length) | |
367 return false; | |
368 | |
369 for (var i = 0; i < eomTasks.length; i++) { | |
370 eomTasks[i](); | |
371 } | |
372 eomTasks.length = 0; | |
373 return true; | |
374 } | |
375 | |
376 var runEOM = hasObserve ? (function(){ | |
377 var eomObj = { pingPong: true }; | |
378 var eomRunScheduled = false; | |
379 | |
380 Object.observe(eomObj, function() { | |
381 runEOMTasks(); | |
382 eomRunScheduled = false; | |
383 }); | |
384 | |
385 return function(fn) { | |
386 eomTasks.push(fn); | |
387 if (!eomRunScheduled) { | |
388 eomRunScheduled = true; | |
389 eomObj.pingPong = !eomObj.pingPong; | |
390 } | |
391 }; | |
392 })() : | |
393 (function() { | |
394 return function(fn) { | |
395 eomTasks.push(fn); | |
396 }; | |
397 })(); | |
398 | |
399 var observedObjectCache = []; | |
400 | |
401 function newObservedObject() { | |
402 var observer; | |
403 var object; | |
404 var discardRecords = false; | |
405 var first = true; | |
406 | |
407 function callback(records) { | |
408 if (observer && observer.state_ === OPENED && !discardRecords) | |
409 observer.check_(records); | |
410 } | |
411 | |
412 return { | |
413 open: function(obs) { | |
414 if (observer) | |
415 throw Error('ObservedObject in use'); | |
416 | |
417 if (!first) | |
418 Object.deliverChangeRecords(callback); | |
419 | |
420 observer = obs; | |
421 first = false; | |
422 }, | |
423 observe: function(obj, arrayObserve) { | |
424 object = obj; | |
425 if (arrayObserve) | |
426 Array.observe(object, callback); | |
427 else | |
428 Object.observe(object, callback); | |
429 }, | |
430 deliver: function(discard) { | |
431 discardRecords = discard; | |
432 Object.deliverChangeRecords(callback); | |
433 discardRecords = false; | |
434 }, | |
435 close: function() { | |
436 observer = undefined; | |
437 Object.unobserve(object, callback); | |
438 observedObjectCache.push(this); | |
439 } | |
440 }; | |
441 } | |
442 | |
443 function getObservedObject(observer, object, arrayObserve) { | |
444 var dir = observedObjectCache.pop() || newObservedObject(); | |
445 dir.open(observer); | |
446 dir.observe(object, arrayObserve); | |
447 return dir; | |
448 } | |
449 | |
450 var emptyArray = []; | |
451 var observedSetCache = []; | |
452 | |
453 function newObservedSet() { | |
454 var observers = []; | |
455 var observerCount = 0; | |
456 var objects = []; | |
457 var toRemove = emptyArray; | |
458 var resetNeeded = false; | |
459 var resetScheduled = false; | |
460 | |
461 function observe(obj) { | |
462 if (!isObject(obj)) | |
463 return; | |
464 | |
465 var index = toRemove.indexOf(obj); | |
466 if (index >= 0) { | |
467 toRemove[index] = undefined; | |
468 objects.push(obj); | |
469 } else if (objects.indexOf(obj) < 0) { | |
470 objects.push(obj); | |
471 Object.observe(obj, callback); | |
472 } | |
473 | |
474 observe(Object.getPrototypeOf(obj)); | |
475 } | |
476 | |
477 function reset() { | |
478 resetScheduled = false; | |
479 if (!resetNeeded) | |
480 return; | |
481 | |
482 var objs = toRemove === emptyArray ? [] : toRemove; | |
483 toRemove = objects; | |
484 objects = objs; | |
485 | |
486 var observer; | |
487 for (var id in observers) { | |
488 observer = observers[id]; | |
489 if (!observer || observer.state_ != OPENED) | |
490 continue; | |
491 | |
492 observer.iterateObjects_(observe); | |
493 } | |
494 | |
495 for (var i = 0; i < toRemove.length; i++) { | |
496 var obj = toRemove[i]; | |
497 if (obj) | |
498 Object.unobserve(obj, callback); | |
499 } | |
500 | |
501 toRemove.length = 0; | |
502 } | |
503 | |
504 function scheduleReset() { | |
505 if (resetScheduled) | |
506 return; | |
507 | |
508 resetNeeded = true; | |
509 resetScheduled = true; | |
510 runEOM(reset); | |
511 } | |
512 | |
513 function callback() { | |
514 var observer; | |
515 | |
516 for (var id in observers) { | |
517 observer = observers[id]; | |
518 if (!observer || observer.state_ != OPENED) | |
519 continue; | |
520 | |
521 observer.check_(); | |
522 } | |
523 | |
524 scheduleReset(); | |
525 } | |
526 | |
527 var record = { | |
528 object: undefined, | |
529 objects: objects, | |
530 open: function(obs) { | |
531 observers[obs.id_] = obs; | |
532 observerCount++; | |
533 obs.iterateObjects_(observe); | |
534 }, | |
535 close: function(obs) { | |
536 var anyLeft = false; | |
537 | |
538 observers[obs.id_] = undefined; | |
539 observerCount--; | |
540 | |
541 if (observerCount) { | |
542 scheduleReset(); | |
543 return; | |
544 } | |
545 resetNeeded = false; | |
546 | |
547 for (var i = 0; i < objects.length; i++) { | |
548 Object.unobserve(objects[i], callback); | |
549 Observer.unobservedCount++; | |
550 } | |
551 | |
552 observers.length = 0; | |
553 objects.length = 0; | |
554 observedSetCache.push(this); | |
555 }, | |
556 reset: scheduleReset | |
557 }; | |
558 | |
559 return record; | |
560 } | |
561 | |
562 var lastObservedSet; | |
563 | |
564 function getObservedSet(observer, obj) { | |
565 if (!lastObservedSet || lastObservedSet.object !== obj) { | |
566 lastObservedSet = observedSetCache.pop() || newObservedSet(); | |
567 lastObservedSet.object = obj; | |
568 } | |
569 lastObservedSet.open(observer); | |
570 return lastObservedSet; | |
571 } | |
572 | |
573 var UNOPENED = 0; | |
574 var OPENED = 1; | |
575 var CLOSED = 2; | |
576 var RESETTING = 3; | |
577 | |
578 var nextObserverId = 1; | |
579 | |
580 function Observer() { | |
581 this.state_ = UNOPENED; | |
582 this.callback_ = undefined; | |
583 this.target_ = undefined; // TODO(rafaelw): Should be WeakRef | |
584 this.directObserver_ = undefined; | |
585 this.value_ = undefined; | |
586 this.id_ = nextObserverId++; | |
587 } | |
588 | |
589 Observer.prototype = { | |
590 open: function(callback, target) { | |
591 if (this.state_ != UNOPENED) | |
592 throw Error('Observer has already been opened.'); | |
593 | |
594 addToAll(this); | |
595 this.callback_ = callback; | |
596 this.target_ = target; | |
597 this.state_ = OPENED; | |
598 this.connect_(); | |
599 return this.value_; | |
600 }, | |
601 | |
602 close: function() { | |
603 if (this.state_ != OPENED) | |
604 return; | |
605 | |
606 removeFromAll(this); | |
607 this.state_ = CLOSED; | |
608 this.disconnect_(); | |
609 this.value_ = undefined; | |
610 this.callback_ = undefined; | |
611 this.target_ = undefined; | |
612 }, | |
613 | |
614 deliver: function() { | |
615 if (this.state_ != OPENED) | |
616 return; | |
617 | |
618 dirtyCheck(this); | |
619 }, | |
620 | |
621 report_: function(changes) { | |
622 try { | |
623 this.callback_.apply(this.target_, changes); | |
624 } catch (ex) { | |
625 Observer._errorThrownDuringCallback = true; | |
626 console.error('Exception caught during observer callback: ' + | |
627 (ex.stack || ex)); | |
628 } | |
629 }, | |
630 | |
631 discardChanges: function() { | |
632 this.check_(undefined, true); | |
633 return this.value_; | |
634 } | |
635 } | |
636 | |
637 var collectObservers = !hasObserve; | |
638 var allObservers; | |
639 Observer._allObserversCount = 0; | |
640 | |
641 if (collectObservers) { | |
642 allObservers = []; | |
643 } | |
644 | |
645 function addToAll(observer) { | |
646 Observer._allObserversCount++; | |
647 if (!collectObservers) | |
648 return; | |
649 | |
650 allObservers.push(observer); | |
651 } | |
652 | |
653 function removeFromAll(observer) { | |
654 Observer._allObserversCount--; | |
655 } | |
656 | |
657 var runningMicrotaskCheckpoint = false; | |
658 | |
659 var hasDebugForceFullDelivery = typeof Object.deliverAllChangeRecords == 'func
tion'; | |
660 | |
661 global.Platform = global.Platform || {}; | |
662 | |
663 global.Platform.performMicrotaskCheckpoint = function() { | |
664 if (runningMicrotaskCheckpoint) | |
665 return; | |
666 | |
667 if (hasDebugForceFullDelivery) { | |
668 Object.deliverAllChangeRecords(); | |
669 return; | |
670 } | |
671 | |
672 if (!collectObservers) | |
673 return; | |
674 | |
675 runningMicrotaskCheckpoint = true; | |
676 | |
677 var cycles = 0; | |
678 var anyChanged, toCheck; | |
679 | |
680 do { | |
681 cycles++; | |
682 toCheck = allObservers; | |
683 allObservers = []; | |
684 anyChanged = false; | |
685 | |
686 for (var i = 0; i < toCheck.length; i++) { | |
687 var observer = toCheck[i]; | |
688 if (observer.state_ != OPENED) | |
689 continue; | |
690 | |
691 if (observer.check_()) | |
692 anyChanged = true; | |
693 | |
694 allObservers.push(observer); | |
695 } | |
696 if (runEOMTasks()) | |
697 anyChanged = true; | |
698 } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged); | |
699 | |
700 if (global.testingExposeCycleCount) | |
701 global.dirtyCheckCycleCount = cycles; | |
702 | |
703 runningMicrotaskCheckpoint = false; | |
704 }; | |
705 | |
706 if (collectObservers) { | |
707 global.Platform.clearObservers = function() { | |
708 allObservers = []; | |
709 }; | |
710 } | |
711 | |
712 function ObjectObserver(object) { | |
713 Observer.call(this); | |
714 this.value_ = object; | |
715 this.oldObject_ = undefined; | |
716 } | |
717 | |
718 ObjectObserver.prototype = createObject({ | |
719 __proto__: Observer.prototype, | |
720 | |
721 arrayObserve: false, | |
722 | |
723 connect_: function(callback, target) { | |
724 if (hasObserve) { | |
725 this.directObserver_ = getObservedObject(this, this.value_, | |
726 this.arrayObserve); | |
727 } else { | |
728 this.oldObject_ = this.copyObject(this.value_); | |
729 } | |
730 | |
731 }, | |
732 | |
733 copyObject: function(object) { | |
734 var copy = Array.isArray(object) ? [] : {}; | |
735 for (var prop in object) { | |
736 copy[prop] = object[prop]; | |
737 }; | |
738 if (Array.isArray(object)) | |
739 copy.length = object.length; | |
740 return copy; | |
741 }, | |
742 | |
743 check_: function(changeRecords, skipChanges) { | |
744 var diff; | |
745 var oldValues; | |
746 if (hasObserve) { | |
747 if (!changeRecords) | |
748 return false; | |
749 | |
750 oldValues = {}; | |
751 diff = diffObjectFromChangeRecords(this.value_, changeRecords, | |
752 oldValues); | |
753 } else { | |
754 oldValues = this.oldObject_; | |
755 diff = diffObjectFromOldObject(this.value_, this.oldObject_); | |
756 } | |
757 | |
758 if (diffIsEmpty(diff)) | |
759 return false; | |
760 | |
761 if (!hasObserve) | |
762 this.oldObject_ = this.copyObject(this.value_); | |
763 | |
764 this.report_([ | |
765 diff.added || {}, | |
766 diff.removed || {}, | |
767 diff.changed || {}, | |
768 function(property) { | |
769 return oldValues[property]; | |
770 } | |
771 ]); | |
772 | |
773 return true; | |
774 }, | |
775 | |
776 disconnect_: function() { | |
777 if (hasObserve) { | |
778 this.directObserver_.close(); | |
779 this.directObserver_ = undefined; | |
780 } else { | |
781 this.oldObject_ = undefined; | |
782 } | |
783 }, | |
784 | |
785 deliver: function() { | |
786 if (this.state_ != OPENED) | |
787 return; | |
788 | |
789 if (hasObserve) | |
790 this.directObserver_.deliver(false); | |
791 else | |
792 dirtyCheck(this); | |
793 }, | |
794 | |
795 discardChanges: function() { | |
796 if (this.directObserver_) | |
797 this.directObserver_.deliver(true); | |
798 else | |
799 this.oldObject_ = this.copyObject(this.value_); | |
800 | |
801 return this.value_; | |
802 } | |
803 }); | |
804 | |
805 function ArrayObserver(array) { | |
806 if (!Array.isArray(array)) | |
807 throw Error('Provided object is not an Array'); | |
808 ObjectObserver.call(this, array); | |
809 } | |
810 | |
811 ArrayObserver.prototype = createObject({ | |
812 | |
813 __proto__: ObjectObserver.prototype, | |
814 | |
815 arrayObserve: true, | |
816 | |
817 copyObject: function(arr) { | |
818 return arr.slice(); | |
819 }, | |
820 | |
821 check_: function(changeRecords) { | |
822 var splices; | |
823 if (hasObserve) { | |
824 if (!changeRecords) | |
825 return false; | |
826 splices = projectArraySplices(this.value_, changeRecords); | |
827 } else { | |
828 splices = calcSplices(this.value_, 0, this.value_.length, | |
829 this.oldObject_, 0, this.oldObject_.length); | |
830 } | |
831 | |
832 if (!splices || !splices.length) | |
833 return false; | |
834 | |
835 if (!hasObserve) | |
836 this.oldObject_ = this.copyObject(this.value_); | |
837 | |
838 this.report_([splices]); | |
839 return true; | |
840 } | |
841 }); | |
842 | |
843 ArrayObserver.applySplices = function(previous, current, splices) { | |
844 splices.forEach(function(splice) { | |
845 var spliceArgs = [splice.index, splice.removed.length]; | |
846 var addIndex = splice.index; | |
847 while (addIndex < splice.index + splice.addedCount) { | |
848 spliceArgs.push(current[addIndex]); | |
849 addIndex++; | |
850 } | |
851 | |
852 Array.prototype.splice.apply(previous, spliceArgs); | |
853 }); | |
854 }; | |
855 | |
856 function PathObserver(object, path) { | |
857 Observer.call(this); | |
858 | |
859 this.object_ = object; | |
860 this.path_ = path instanceof Path ? path : getPath(path); | |
861 this.directObserver_ = undefined; | |
862 } | |
863 | |
864 PathObserver.prototype = createObject({ | |
865 __proto__: Observer.prototype, | |
866 | |
867 connect_: function() { | |
868 if (hasObserve) | |
869 this.directObserver_ = getObservedSet(this, this.object_); | |
870 | |
871 this.check_(undefined, true); | |
872 }, | |
873 | |
874 disconnect_: function() { | |
875 this.value_ = undefined; | |
876 | |
877 if (this.directObserver_) { | |
878 this.directObserver_.close(this); | |
879 this.directObserver_ = undefined; | |
880 } | |
881 }, | |
882 | |
883 iterateObjects_: function(observe) { | |
884 this.path_.iterateObjects(this.object_, observe); | |
885 }, | |
886 | |
887 check_: function(changeRecords, skipChanges) { | |
888 var oldValue = this.value_; | |
889 this.value_ = this.path_.getValueFrom(this.object_); | |
890 if (skipChanges || areSameValue(this.value_, oldValue)) | |
891 return false; | |
892 | |
893 this.report_([this.value_, oldValue]); | |
894 return true; | |
895 }, | |
896 | |
897 setValue: function(newValue) { | |
898 if (this.path_) | |
899 this.path_.setValueFrom(this.object_, newValue); | |
900 } | |
901 }); | |
902 | |
903 function CompoundObserver() { | |
904 Observer.call(this); | |
905 | |
906 this.value_ = []; | |
907 this.directObserver_ = undefined; | |
908 this.observed_ = []; | |
909 } | |
910 | |
911 var observerSentinel = {}; | |
912 | |
913 CompoundObserver.prototype = createObject({ | |
914 __proto__: Observer.prototype, | |
915 | |
916 connect_: function() { | |
917 this.check_(undefined, true); | |
918 | |
919 if (!hasObserve) | |
920 return; | |
921 | |
922 var object; | |
923 var needsDirectObserver = false; | |
924 for (var i = 0; i < this.observed_.length; i += 2) { | |
925 object = this.observed_[i] | |
926 if (object !== observerSentinel) { | |
927 needsDirectObserver = true; | |
928 break; | |
929 } | |
930 } | |
931 | |
932 if (this.directObserver_) { | |
933 if (needsDirectObserver) { | |
934 this.directObserver_.reset(); | |
935 return; | |
936 } | |
937 this.directObserver_.close(); | |
938 this.directObserver_ = undefined; | |
939 return; | |
940 } | |
941 | |
942 if (needsDirectObserver) | |
943 this.directObserver_ = getObservedSet(this, object); | |
944 }, | |
945 | |
946 closeObservers_: function() { | |
947 for (var i = 0; i < this.observed_.length; i += 2) { | |
948 if (this.observed_[i] === observerSentinel) | |
949 this.observed_[i + 1].close(); | |
950 } | |
951 this.observed_.length = 0; | |
952 }, | |
953 | |
954 disconnect_: function() { | |
955 this.value_ = undefined; | |
956 | |
957 if (this.directObserver_) { | |
958 this.directObserver_.close(this); | |
959 this.directObserver_ = undefined; | |
960 } | |
961 | |
962 this.closeObservers_(); | |
963 }, | |
964 | |
965 addPath: function(object, path) { | |
966 if (this.state_ != UNOPENED && this.state_ != RESETTING) | |
967 throw Error('Cannot add paths once started.'); | |
968 | |
969 this.observed_.push(object, path instanceof Path ? path : getPath(path)); | |
970 }, | |
971 | |
972 addObserver: function(observer) { | |
973 if (this.state_ != UNOPENED && this.state_ != RESETTING) | |
974 throw Error('Cannot add observers once started.'); | |
975 | |
976 observer.open(this.deliver, this); | |
977 this.observed_.push(observerSentinel, observer); | |
978 }, | |
979 | |
980 startReset: function() { | |
981 if (this.state_ != OPENED) | |
982 throw Error('Can only reset while open'); | |
983 | |
984 this.state_ = RESETTING; | |
985 this.closeObservers_(); | |
986 }, | |
987 | |
988 finishReset: function() { | |
989 if (this.state_ != RESETTING) | |
990 throw Error('Can only finishReset after startReset'); | |
991 this.state_ = OPENED; | |
992 this.connect_(); | |
993 | |
994 return this.value_; | |
995 }, | |
996 | |
997 iterateObjects_: function(observe) { | |
998 var object; | |
999 for (var i = 0; i < this.observed_.length; i += 2) { | |
1000 object = this.observed_[i] | |
1001 if (object !== observerSentinel) | |
1002 this.observed_[i + 1].iterateObjects(object, observe) | |
1003 } | |
1004 }, | |
1005 | |
1006 check_: function(changeRecords, skipChanges) { | |
1007 var oldValues; | |
1008 for (var i = 0; i < this.observed_.length; i += 2) { | |
1009 var pathOrObserver = this.observed_[i+1]; | |
1010 var object = this.observed_[i]; | |
1011 var value = object === observerSentinel ? | |
1012 pathOrObserver.discardChanges() : | |
1013 pathOrObserver.getValueFrom(object) | |
1014 | |
1015 if (skipChanges) { | |
1016 this.value_[i / 2] = value; | |
1017 continue; | |
1018 } | |
1019 | |
1020 if (areSameValue(value, this.value_[i / 2])) | |
1021 continue; | |
1022 | |
1023 oldValues = oldValues || []; | |
1024 oldValues[i / 2] = this.value_[i / 2]; | |
1025 this.value_[i / 2] = value; | |
1026 } | |
1027 | |
1028 if (!oldValues) | |
1029 return false; | |
1030 | |
1031 // TODO(rafaelw): Having observed_ as the third callback arg here is | |
1032 // pretty lame API. Fix. | |
1033 this.report_([this.value_, oldValues, this.observed_]); | |
1034 return true; | |
1035 } | |
1036 }); | |
1037 | |
1038 function identFn(value) { return value; } | |
1039 | |
1040 function ObserverTransform(observable, getValueFn, setValueFn, | |
1041 dontPassThroughSet) { | |
1042 this.callback_ = undefined; | |
1043 this.target_ = undefined; | |
1044 this.value_ = undefined; | |
1045 this.observable_ = observable; | |
1046 this.getValueFn_ = getValueFn || identFn; | |
1047 this.setValueFn_ = setValueFn || identFn; | |
1048 // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this | |
1049 // at the moment because of a bug in it's dependency tracking. | |
1050 this.dontPassThroughSet_ = dontPassThroughSet; | |
1051 } | |
1052 | |
1053 ObserverTransform.prototype = { | |
1054 open: function(callback, target) { | |
1055 this.callback_ = callback; | |
1056 this.target_ = target; | |
1057 this.value_ = | |
1058 this.getValueFn_(this.observable_.open(this.observedCallback_, this)); | |
1059 return this.value_; | |
1060 }, | |
1061 | |
1062 observedCallback_: function(value) { | |
1063 value = this.getValueFn_(value); | |
1064 if (areSameValue(value, this.value_)) | |
1065 return; | |
1066 var oldValue = this.value_; | |
1067 this.value_ = value; | |
1068 this.callback_.call(this.target_, this.value_, oldValue); | |
1069 }, | |
1070 | |
1071 discardChanges: function() { | |
1072 this.value_ = this.getValueFn_(this.observable_.discardChanges()); | |
1073 return this.value_; | |
1074 }, | |
1075 | |
1076 deliver: function() { | |
1077 return this.observable_.deliver(); | |
1078 }, | |
1079 | |
1080 setValue: function(value) { | |
1081 value = this.setValueFn_(value); | |
1082 if (!this.dontPassThroughSet_ && this.observable_.setValue) | |
1083 return this.observable_.setValue(value); | |
1084 }, | |
1085 | |
1086 close: function() { | |
1087 if (this.observable_) | |
1088 this.observable_.close(); | |
1089 this.callback_ = undefined; | |
1090 this.target_ = undefined; | |
1091 this.observable_ = undefined; | |
1092 this.value_ = undefined; | |
1093 this.getValueFn_ = undefined; | |
1094 this.setValueFn_ = undefined; | |
1095 } | |
1096 } | |
1097 | |
1098 var expectedRecordTypes = {}; | |
1099 expectedRecordTypes[PROP_ADD_TYPE] = true; | |
1100 expectedRecordTypes[PROP_UPDATE_TYPE] = true; | |
1101 expectedRecordTypes[PROP_DELETE_TYPE] = true; | |
1102 | |
1103 function notifyFunction(object, name) { | |
1104 if (typeof Object.observe !== 'function') | |
1105 return; | |
1106 | |
1107 var notifier = Object.getNotifier(object); | |
1108 return function(type, oldValue) { | |
1109 var changeRecord = { | |
1110 object: object, | |
1111 type: type, | |
1112 name: name | |
1113 }; | |
1114 if (arguments.length === 2) | |
1115 changeRecord.oldValue = oldValue; | |
1116 notifier.notify(changeRecord); | |
1117 } | |
1118 } | |
1119 | |
1120 Observer.defineComputedProperty = function(target, name, observable) { | |
1121 var notify = notifyFunction(target, name); | |
1122 var value = observable.open(function(newValue, oldValue) { | |
1123 value = newValue; | |
1124 if (notify) | |
1125 notify(PROP_UPDATE_TYPE, oldValue); | |
1126 }); | |
1127 | |
1128 Object.defineProperty(target, name, { | |
1129 get: function() { | |
1130 observable.deliver(); | |
1131 return value; | |
1132 }, | |
1133 set: function(newValue) { | |
1134 observable.setValue(newValue); | |
1135 return newValue; | |
1136 }, | |
1137 configurable: true | |
1138 }); | |
1139 | |
1140 return { | |
1141 close: function() { | |
1142 observable.close(); | |
1143 Object.defineProperty(target, name, { | |
1144 value: value, | |
1145 writable: true, | |
1146 configurable: true | |
1147 }); | |
1148 } | |
1149 }; | |
1150 } | |
1151 | |
1152 function diffObjectFromChangeRecords(object, changeRecords, oldValues) { | |
1153 var added = {}; | |
1154 var removed = {}; | |
1155 | |
1156 for (var i = 0; i < changeRecords.length; i++) { | |
1157 var record = changeRecords[i]; | |
1158 if (!expectedRecordTypes[record.type]) { | |
1159 console.error('Unknown changeRecord type: ' + record.type); | |
1160 console.error(record); | |
1161 continue; | |
1162 } | |
1163 | |
1164 if (!(record.name in oldValues)) | |
1165 oldValues[record.name] = record.oldValue; | |
1166 | |
1167 if (record.type == PROP_UPDATE_TYPE) | |
1168 continue; | |
1169 | |
1170 if (record.type == PROP_ADD_TYPE) { | |
1171 if (record.name in removed) | |
1172 delete removed[record.name]; | |
1173 else | |
1174 added[record.name] = true; | |
1175 | |
1176 continue; | |
1177 } | |
1178 | |
1179 // type = 'delete' | |
1180 if (record.name in added) { | |
1181 delete added[record.name]; | |
1182 delete oldValues[record.name]; | |
1183 } else { | |
1184 removed[record.name] = true; | |
1185 } | |
1186 } | |
1187 | |
1188 for (var prop in added) | |
1189 added[prop] = object[prop]; | |
1190 | |
1191 for (var prop in removed) | |
1192 removed[prop] = undefined; | |
1193 | |
1194 var changed = {}; | |
1195 for (var prop in oldValues) { | |
1196 if (prop in added || prop in removed) | |
1197 continue; | |
1198 | |
1199 var newValue = object[prop]; | |
1200 if (oldValues[prop] !== newValue) | |
1201 changed[prop] = newValue; | |
1202 } | |
1203 | |
1204 return { | |
1205 added: added, | |
1206 removed: removed, | |
1207 changed: changed | |
1208 }; | |
1209 } | |
1210 | |
1211 function newSplice(index, removed, addedCount) { | |
1212 return { | |
1213 index: index, | |
1214 removed: removed, | |
1215 addedCount: addedCount | |
1216 }; | |
1217 } | |
1218 | |
1219 var EDIT_LEAVE = 0; | |
1220 var EDIT_UPDATE = 1; | |
1221 var EDIT_ADD = 2; | |
1222 var EDIT_DELETE = 3; | |
1223 | |
1224 function ArraySplice() {} | |
1225 | |
1226 ArraySplice.prototype = { | |
1227 | |
1228 // Note: This function is *based* on the computation of the Levenshtein | |
1229 // "edit" distance. The one change is that "updates" are treated as two | |
1230 // edits - not one. With Array splices, an update is really a delete | |
1231 // followed by an add. By retaining this, we optimize for "keeping" the | |
1232 // maximum array items in the original array. For example: | |
1233 // | |
1234 // 'xxxx123' -> '123yyyy' | |
1235 // | |
1236 // With 1-edit updates, the shortest path would be just to update all seven | |
1237 // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This | |
1238 // leaves the substring '123' intact. | |
1239 calcEditDistances: function(current, currentStart, currentEnd, | |
1240 old, oldStart, oldEnd) { | |
1241 // "Deletion" columns | |
1242 var rowCount = oldEnd - oldStart + 1; | |
1243 var columnCount = currentEnd - currentStart + 1; | |
1244 var distances = new Array(rowCount); | |
1245 | |
1246 // "Addition" rows. Initialize null column. | |
1247 for (var i = 0; i < rowCount; i++) { | |
1248 distances[i] = new Array(columnCount); | |
1249 distances[i][0] = i; | |
1250 } | |
1251 | |
1252 // Initialize null row | |
1253 for (var j = 0; j < columnCount; j++) | |
1254 distances[0][j] = j; | |
1255 | |
1256 for (var i = 1; i < rowCount; i++) { | |
1257 for (var j = 1; j < columnCount; j++) { | |
1258 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) | |
1259 distances[i][j] = distances[i - 1][j - 1]; | |
1260 else { | |
1261 var north = distances[i - 1][j] + 1; | |
1262 var west = distances[i][j - 1] + 1; | |
1263 distances[i][j] = north < west ? north : west; | |
1264 } | |
1265 } | |
1266 } | |
1267 | |
1268 return distances; | |
1269 }, | |
1270 | |
1271 // This starts at the final weight, and walks "backward" by finding | |
1272 // the minimum previous weight recursively until the origin of the weight | |
1273 // matrix. | |
1274 spliceOperationsFromEditDistances: function(distances) { | |
1275 var i = distances.length - 1; | |
1276 var j = distances[0].length - 1; | |
1277 var current = distances[i][j]; | |
1278 var edits = []; | |
1279 while (i > 0 || j > 0) { | |
1280 if (i == 0) { | |
1281 edits.push(EDIT_ADD); | |
1282 j--; | |
1283 continue; | |
1284 } | |
1285 if (j == 0) { | |
1286 edits.push(EDIT_DELETE); | |
1287 i--; | |
1288 continue; | |
1289 } | |
1290 var northWest = distances[i - 1][j - 1]; | |
1291 var west = distances[i - 1][j]; | |
1292 var north = distances[i][j - 1]; | |
1293 | |
1294 var min; | |
1295 if (west < north) | |
1296 min = west < northWest ? west : northWest; | |
1297 else | |
1298 min = north < northWest ? north : northWest; | |
1299 | |
1300 if (min == northWest) { | |
1301 if (northWest == current) { | |
1302 edits.push(EDIT_LEAVE); | |
1303 } else { | |
1304 edits.push(EDIT_UPDATE); | |
1305 current = northWest; | |
1306 } | |
1307 i--; | |
1308 j--; | |
1309 } else if (min == west) { | |
1310 edits.push(EDIT_DELETE); | |
1311 i--; | |
1312 current = west; | |
1313 } else { | |
1314 edits.push(EDIT_ADD); | |
1315 j--; | |
1316 current = north; | |
1317 } | |
1318 } | |
1319 | |
1320 edits.reverse(); | |
1321 return edits; | |
1322 }, | |
1323 | |
1324 /** | |
1325 * Splice Projection functions: | |
1326 * | |
1327 * A splice map is a representation of how a previous array of items | |
1328 * was transformed into a new array of items. Conceptually it is a list of | |
1329 * tuples of | |
1330 * | |
1331 * <index, removed, addedCount> | |
1332 * | |
1333 * which are kept in ascending index order of. The tuple represents that at | |
1334 * the |index|, |removed| sequence of items were removed, and counting forwa
rd | |
1335 * from |index|, |addedCount| items were added. | |
1336 */ | |
1337 | |
1338 /** | |
1339 * Lacking individual splice mutation information, the minimal set of | |
1340 * splices can be synthesized given the previous state and final state of an | |
1341 * array. The basic approach is to calculate the edit distance matrix and | |
1342 * choose the shortest path through it. | |
1343 * | |
1344 * Complexity: O(l * p) | |
1345 * l: The length of the current array | |
1346 * p: The length of the old array | |
1347 */ | |
1348 calcSplices: function(current, currentStart, currentEnd, | |
1349 old, oldStart, oldEnd) { | |
1350 var prefixCount = 0; | |
1351 var suffixCount = 0; | |
1352 | |
1353 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); | |
1354 if (currentStart == 0 && oldStart == 0) | |
1355 prefixCount = this.sharedPrefix(current, old, minLength); | |
1356 | |
1357 if (currentEnd == current.length && oldEnd == old.length) | |
1358 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); | |
1359 | |
1360 currentStart += prefixCount; | |
1361 oldStart += prefixCount; | |
1362 currentEnd -= suffixCount; | |
1363 oldEnd -= suffixCount; | |
1364 | |
1365 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) | |
1366 return []; | |
1367 | |
1368 if (currentStart == currentEnd) { | |
1369 var splice = newSplice(currentStart, [], 0); | |
1370 while (oldStart < oldEnd) | |
1371 splice.removed.push(old[oldStart++]); | |
1372 | |
1373 return [ splice ]; | |
1374 } else if (oldStart == oldEnd) | |
1375 return [ newSplice(currentStart, [], currentEnd - currentStart) ]; | |
1376 | |
1377 var ops = this.spliceOperationsFromEditDistances( | |
1378 this.calcEditDistances(current, currentStart, currentEnd, | |
1379 old, oldStart, oldEnd)); | |
1380 | |
1381 var splice = undefined; | |
1382 var splices = []; | |
1383 var index = currentStart; | |
1384 var oldIndex = oldStart; | |
1385 for (var i = 0; i < ops.length; i++) { | |
1386 switch(ops[i]) { | |
1387 case EDIT_LEAVE: | |
1388 if (splice) { | |
1389 splices.push(splice); | |
1390 splice = undefined; | |
1391 } | |
1392 | |
1393 index++; | |
1394 oldIndex++; | |
1395 break; | |
1396 case EDIT_UPDATE: | |
1397 if (!splice) | |
1398 splice = newSplice(index, [], 0); | |
1399 | |
1400 splice.addedCount++; | |
1401 index++; | |
1402 | |
1403 splice.removed.push(old[oldIndex]); | |
1404 oldIndex++; | |
1405 break; | |
1406 case EDIT_ADD: | |
1407 if (!splice) | |
1408 splice = newSplice(index, [], 0); | |
1409 | |
1410 splice.addedCount++; | |
1411 index++; | |
1412 break; | |
1413 case EDIT_DELETE: | |
1414 if (!splice) | |
1415 splice = newSplice(index, [], 0); | |
1416 | |
1417 splice.removed.push(old[oldIndex]); | |
1418 oldIndex++; | |
1419 break; | |
1420 } | |
1421 } | |
1422 | |
1423 if (splice) { | |
1424 splices.push(splice); | |
1425 } | |
1426 return splices; | |
1427 }, | |
1428 | |
1429 sharedPrefix: function(current, old, searchLength) { | |
1430 for (var i = 0; i < searchLength; i++) | |
1431 if (!this.equals(current[i], old[i])) | |
1432 return i; | |
1433 return searchLength; | |
1434 }, | |
1435 | |
1436 sharedSuffix: function(current, old, searchLength) { | |
1437 var index1 = current.length; | |
1438 var index2 = old.length; | |
1439 var count = 0; | |
1440 while (count < searchLength && this.equals(current[--index1], old[--index2
])) | |
1441 count++; | |
1442 | |
1443 return count; | |
1444 }, | |
1445 | |
1446 calculateSplices: function(current, previous) { | |
1447 return this.calcSplices(current, 0, current.length, previous, 0, | |
1448 previous.length); | |
1449 }, | |
1450 | |
1451 equals: function(currentValue, previousValue) { | |
1452 return currentValue === previousValue; | |
1453 } | |
1454 }; | |
1455 | |
1456 var arraySplice = new ArraySplice(); | |
1457 | |
1458 function calcSplices(current, currentStart, currentEnd, | |
1459 old, oldStart, oldEnd) { | |
1460 return arraySplice.calcSplices(current, currentStart, currentEnd, | |
1461 old, oldStart, oldEnd); | |
1462 } | |
1463 | |
1464 function intersect(start1, end1, start2, end2) { | |
1465 // Disjoint | |
1466 if (end1 < start2 || end2 < start1) | |
1467 return -1; | |
1468 | |
1469 // Adjacent | |
1470 if (end1 == start2 || end2 == start1) | |
1471 return 0; | |
1472 | |
1473 // Non-zero intersect, span1 first | |
1474 if (start1 < start2) { | |
1475 if (end1 < end2) | |
1476 return end1 - start2; // Overlap | |
1477 else | |
1478 return end2 - start2; // Contained | |
1479 } else { | |
1480 // Non-zero intersect, span2 first | |
1481 if (end2 < end1) | |
1482 return end2 - start1; // Overlap | |
1483 else | |
1484 return end1 - start1; // Contained | |
1485 } | |
1486 } | |
1487 | |
1488 function mergeSplice(splices, index, removed, addedCount) { | |
1489 | |
1490 var splice = newSplice(index, removed, addedCount); | |
1491 | |
1492 var inserted = false; | |
1493 var insertionOffset = 0; | |
1494 | |
1495 for (var i = 0; i < splices.length; i++) { | |
1496 var current = splices[i]; | |
1497 current.index += insertionOffset; | |
1498 | |
1499 if (inserted) | |
1500 continue; | |
1501 | |
1502 var intersectCount = intersect(splice.index, | |
1503 splice.index + splice.removed.length, | |
1504 current.index, | |
1505 current.index + current.addedCount); | |
1506 | |
1507 if (intersectCount >= 0) { | |
1508 // Merge the two splices | |
1509 | |
1510 splices.splice(i, 1); | |
1511 i--; | |
1512 | |
1513 insertionOffset -= current.addedCount - current.removed.length; | |
1514 | |
1515 splice.addedCount += current.addedCount - intersectCount; | |
1516 var deleteCount = splice.removed.length + | |
1517 current.removed.length - intersectCount; | |
1518 | |
1519 if (!splice.addedCount && !deleteCount) { | |
1520 // merged splice is a noop. discard. | |
1521 inserted = true; | |
1522 } else { | |
1523 var removed = current.removed; | |
1524 | |
1525 if (splice.index < current.index) { | |
1526 // some prefix of splice.removed is prepended to current.removed. | |
1527 var prepend = splice.removed.slice(0, current.index - splice.index); | |
1528 Array.prototype.push.apply(prepend, removed); | |
1529 removed = prepend; | |
1530 } | |
1531 | |
1532 if (splice.index + splice.removed.length > current.index + current.add
edCount) { | |
1533 // some suffix of splice.removed is appended to current.removed. | |
1534 var append = splice.removed.slice(current.index + current.addedCount
- splice.index); | |
1535 Array.prototype.push.apply(removed, append); | |
1536 } | |
1537 | |
1538 splice.removed = removed; | |
1539 if (current.index < splice.index) { | |
1540 splice.index = current.index; | |
1541 } | |
1542 } | |
1543 } else if (splice.index < current.index) { | |
1544 // Insert splice here. | |
1545 | |
1546 inserted = true; | |
1547 | |
1548 splices.splice(i, 0, splice); | |
1549 i++; | |
1550 | |
1551 var offset = splice.addedCount - splice.removed.length | |
1552 current.index += offset; | |
1553 insertionOffset += offset; | |
1554 } | |
1555 } | |
1556 | |
1557 if (!inserted) | |
1558 splices.push(splice); | |
1559 } | |
1560 | |
1561 function createInitialSplices(array, changeRecords) { | |
1562 var splices = []; | |
1563 | |
1564 for (var i = 0; i < changeRecords.length; i++) { | |
1565 var record = changeRecords[i]; | |
1566 switch(record.type) { | |
1567 case ARRAY_SPLICE_TYPE: | |
1568 mergeSplice(splices, record.index, record.removed.slice(), record.adde
dCount); | |
1569 break; | |
1570 case PROP_ADD_TYPE: | |
1571 case PROP_UPDATE_TYPE: | |
1572 case PROP_DELETE_TYPE: | |
1573 if (!isIndex(record.name)) | |
1574 continue; | |
1575 var index = toNumber(record.name); | |
1576 if (index < 0) | |
1577 continue; | |
1578 mergeSplice(splices, index, [record.oldValue], 1); | |
1579 break; | |
1580 default: | |
1581 console.error('Unexpected record type: ' + JSON.stringify(record)); | |
1582 break; | |
1583 } | |
1584 } | |
1585 | |
1586 return splices; | |
1587 } | |
1588 | |
1589 function projectArraySplices(array, changeRecords) { | |
1590 var splices = []; | |
1591 | |
1592 createInitialSplices(array, changeRecords).forEach(function(splice) { | |
1593 if (splice.addedCount == 1 && splice.removed.length == 1) { | |
1594 if (splice.removed[0] !== array[splice.index]) | |
1595 splices.push(splice); | |
1596 | |
1597 return | |
1598 }; | |
1599 | |
1600 splices = splices.concat(calcSplices(array, splice.index, splice.index + s
plice.addedCount, | |
1601 splice.removed, 0, splice.removed.len
gth)); | |
1602 }); | |
1603 | |
1604 return splices; | |
1605 } | |
1606 | |
1607 global.Observer = Observer; | |
1608 global.Observer.runEOM_ = runEOM; | |
1609 global.Observer.hasObjectObserve = hasObserve; | |
1610 global.ArrayObserver = ArrayObserver; | |
1611 global.ArrayObserver.calculateSplices = function(current, previous) { | |
1612 return arraySplice.calculateSplices(current, previous); | |
1613 }; | |
1614 | |
1615 global.ArraySplice = ArraySplice; | |
1616 global.ObjectObserver = ObjectObserver; | |
1617 global.PathObserver = PathObserver; | |
1618 global.CompoundObserver = CompoundObserver; | |
1619 global.Path = Path; | |
1620 global.ObserverTransform = ObserverTransform; | |
1621 | |
1622 // TODO(rafaelw): Only needed for testing until new change record names | |
1623 // make it to release. | |
1624 global.Observer.changeRecordTypes = { | |
1625 add: PROP_ADD_TYPE, | |
1626 update: PROP_UPDATE_TYPE, | |
1627 reconfigure: PROP_RECONFIGURE_TYPE, | |
1628 'delete': PROP_DELETE_TYPE, | |
1629 splice: ARRAY_SPLICE_TYPE | |
1630 }; | |
1631 })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && m
odule ? global : this || window); | |
1632 | |
1633 /* | |
1634 * Copyright 2012 The Polymer Authors. All rights reserved. | |
1635 * Use of this source code is governed by a BSD-style | |
1636 * license that can be found in the LICENSE file. | |
1637 */ | |
1638 | |
1639 if (typeof WeakMap === 'undefined') { | |
1640 (function() { | |
1641 var defineProperty = Object.defineProperty; | |
1642 var counter = Date.now() % 1e9; | |
1643 | |
1644 var WeakMap = function() { | |
1645 this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__'); | |
1646 }; | |
1647 | |
1648 WeakMap.prototype = { | |
1649 set: function(key, value) { | |
1650 var entry = key[this.name]; | |
1651 if (entry && entry[0] === key) | |
1652 entry[1] = value; | |
1653 else | |
1654 defineProperty(key, this.name, {value: [key, value], writable: true}); | |
1655 }, | |
1656 get: function(key) { | |
1657 var entry; | |
1658 return (entry = key[this.name]) && entry[0] === key ? | |
1659 entry[1] : undefined; | |
1660 }, | |
1661 delete: function(key) { | |
1662 this.set(key, undefined); | |
1663 } | |
1664 }; | |
1665 | |
1666 window.WeakMap = WeakMap; | |
1667 })(); | |
1668 } | |
1669 | |
1670 // Copyright 2012 The Polymer Authors. All rights reserved. | |
1671 // Use of this source code is goverened by a BSD-style | |
1672 // license that can be found in the LICENSE file. | |
1673 | |
1674 window.ShadowDOMPolyfill = {}; | |
1675 | |
1676 (function(scope) { | |
1677 'use strict'; | |
1678 | |
1679 var constructorTable = new WeakMap(); | |
1680 var nativePrototypeTable = new WeakMap(); | |
1681 var wrappers = Object.create(null); | |
1682 | |
1683 // Don't test for eval if document has CSP securityPolicy object and we can | |
1684 // see that eval is not supported. This avoids an error message in console | |
1685 // even when the exception is caught | |
1686 var hasEval = !('securityPolicy' in document) || | |
1687 document.securityPolicy.allowsEval; | |
1688 if (hasEval) { | |
1689 try { | |
1690 var f = new Function('', 'return true;'); | |
1691 hasEval = f(); | |
1692 } catch (ex) { | |
1693 hasEval = false; | |
1694 } | |
1695 } | |
1696 | |
1697 function assert(b) { | |
1698 if (!b) | |
1699 throw new Error('Assertion failed'); | |
1700 }; | |
1701 | |
1702 var defineProperty = Object.defineProperty; | |
1703 var getOwnPropertyNames = Object.getOwnPropertyNames; | |
1704 var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; | |
1705 | |
1706 function mixin(to, from) { | |
1707 getOwnPropertyNames(from).forEach(function(name) { | |
1708 defineProperty(to, name, getOwnPropertyDescriptor(from, name)); | |
1709 }); | |
1710 return to; | |
1711 }; | |
1712 | |
1713 function mixinStatics(to, from) { | |
1714 getOwnPropertyNames(from).forEach(function(name) { | |
1715 switch (name) { | |
1716 case 'arguments': | |
1717 case 'caller': | |
1718 case 'length': | |
1719 case 'name': | |
1720 case 'prototype': | |
1721 case 'toString': | |
1722 return; | |
1723 } | |
1724 defineProperty(to, name, getOwnPropertyDescriptor(from, name)); | |
1725 }); | |
1726 return to; | |
1727 }; | |
1728 | |
1729 function oneOf(object, propertyNames) { | |
1730 for (var i = 0; i < propertyNames.length; i++) { | |
1731 if (propertyNames[i] in object) | |
1732 return propertyNames[i]; | |
1733 } | |
1734 } | |
1735 | |
1736 // Mozilla's old DOM bindings are bretty busted: | |
1737 // https://bugzilla.mozilla.org/show_bug.cgi?id=855844 | |
1738 // Make sure they are create before we start modifying things. | |
1739 getOwnPropertyNames(window); | |
1740 | |
1741 function getWrapperConstructor(node) { | |
1742 var nativePrototype = node.__proto__ || Object.getPrototypeOf(node); | |
1743 var wrapperConstructor = constructorTable.get(nativePrototype); | |
1744 if (wrapperConstructor) | |
1745 return wrapperConstructor; | |
1746 | |
1747 var parentWrapperConstructor = getWrapperConstructor(nativePrototype); | |
1748 | |
1749 var GeneratedWrapper = createWrapperConstructor(parentWrapperConstructor); | |
1750 registerInternal(nativePrototype, GeneratedWrapper, node); | |
1751 | |
1752 return GeneratedWrapper; | |
1753 } | |
1754 | |
1755 function addForwardingProperties(nativePrototype, wrapperPrototype) { | |
1756 installProperty(nativePrototype, wrapperPrototype, true); | |
1757 } | |
1758 | |
1759 function registerInstanceProperties(wrapperPrototype, instanceObject) { | |
1760 installProperty(instanceObject, wrapperPrototype, false); | |
1761 } | |
1762 | |
1763 var isFirefox = /Firefox/.test(navigator.userAgent); | |
1764 | |
1765 // This is used as a fallback when getting the descriptor fails in | |
1766 // installProperty. | |
1767 var dummyDescriptor = { | |
1768 get: function() {}, | |
1769 set: function(v) {}, | |
1770 configurable: true, | |
1771 enumerable: true | |
1772 }; | |
1773 | |
1774 function isEventHandlerName(name) { | |
1775 return /^on[a-z]+$/.test(name); | |
1776 } | |
1777 | |
1778 function isIdentifierName(name) { | |
1779 return /^\w[a-zA-Z_0-9]*$/.test(name); | |
1780 } | |
1781 | |
1782 function getGetter(name) { | |
1783 return hasEval && isIdentifierName(name) ? | |
1784 new Function('return this.impl.' + name) : | |
1785 function() { return this.impl[name]; }; | |
1786 } | |
1787 | |
1788 function getSetter(name) { | |
1789 return hasEval && isIdentifierName(name) ? | |
1790 new Function('v', 'this.impl.' + name + ' = v') : | |
1791 function(v) { this.impl[name] = v; }; | |
1792 } | |
1793 | |
1794 function getMethod(name) { | |
1795 return hasEval && isIdentifierName(name) ? | |
1796 new Function('return this.impl.' + name + | |
1797 '.apply(this.impl, arguments)') : | |
1798 function() { return this.impl[name].apply(this.impl, arguments); }; | |
1799 } | |
1800 | |
1801 function getDescriptor(source, name) { | |
1802 try { | |
1803 return Object.getOwnPropertyDescriptor(source, name); | |
1804 } catch (ex) { | |
1805 // JSC and V8 both use data properties instead of accessors which can | |
1806 // cause getting the property desciptor to throw an exception. | |
1807 // https://bugs.webkit.org/show_bug.cgi?id=49739 | |
1808 return dummyDescriptor; | |
1809 } | |
1810 } | |
1811 | |
1812 function installProperty(source, target, allowMethod, opt_blacklist) { | |
1813 var names = getOwnPropertyNames(source); | |
1814 for (var i = 0; i < names.length; i++) { | |
1815 var name = names[i]; | |
1816 if (name === 'polymerBlackList_') | |
1817 continue; | |
1818 | |
1819 if (name in target) | |
1820 continue; | |
1821 | |
1822 if (source.polymerBlackList_ && source.polymerBlackList_[name]) | |
1823 continue; | |
1824 | |
1825 if (isFirefox) { | |
1826 // Tickle Firefox's old bindings. | |
1827 source.__lookupGetter__(name); | |
1828 } | |
1829 var descriptor = getDescriptor(source, name); | |
1830 var getter, setter; | |
1831 if (allowMethod && typeof descriptor.value === 'function') { | |
1832 target[name] = getMethod(name); | |
1833 continue; | |
1834 } | |
1835 | |
1836 var isEvent = isEventHandlerName(name); | |
1837 if (isEvent) | |
1838 getter = scope.getEventHandlerGetter(name); | |
1839 else | |
1840 getter = getGetter(name); | |
1841 | |
1842 if (descriptor.writable || descriptor.set) { | |
1843 if (isEvent) | |
1844 setter = scope.getEventHandlerSetter(name); | |
1845 else | |
1846 setter = getSetter(name); | |
1847 } | |
1848 | |
1849 defineProperty(target, name, { | |
1850 get: getter, | |
1851 set: setter, | |
1852 configurable: descriptor.configurable, | |
1853 enumerable: descriptor.enumerable | |
1854 }); | |
1855 } | |
1856 } | |
1857 | |
1858 /** | |
1859 * @param {Function} nativeConstructor | |
1860 * @param {Function} wrapperConstructor | |
1861 * @param {Object=} opt_instance If present, this is used to extract | |
1862 * properties from an instance object. | |
1863 */ | |
1864 function register(nativeConstructor, wrapperConstructor, opt_instance) { | |
1865 var nativePrototype = nativeConstructor.prototype; | |
1866 registerInternal(nativePrototype, wrapperConstructor, opt_instance); | |
1867 mixinStatics(wrapperConstructor, nativeConstructor); | |
1868 } | |
1869 | |
1870 function registerInternal(nativePrototype, wrapperConstructor, opt_instance) { | |
1871 var wrapperPrototype = wrapperConstructor.prototype; | |
1872 assert(constructorTable.get(nativePrototype) === undefined); | |
1873 | |
1874 constructorTable.set(nativePrototype, wrapperConstructor); | |
1875 nativePrototypeTable.set(wrapperPrototype, nativePrototype); | |
1876 | |
1877 addForwardingProperties(nativePrototype, wrapperPrototype); | |
1878 if (opt_instance) | |
1879 registerInstanceProperties(wrapperPrototype, opt_instance); | |
1880 defineProperty(wrapperPrototype, 'constructor', { | |
1881 value: wrapperConstructor, | |
1882 configurable: true, | |
1883 enumerable: false, | |
1884 writable: true | |
1885 }); | |
1886 } | |
1887 | |
1888 function isWrapperFor(wrapperConstructor, nativeConstructor) { | |
1889 return constructorTable.get(nativeConstructor.prototype) === | |
1890 wrapperConstructor; | |
1891 } | |
1892 | |
1893 /** | |
1894 * Creates a generic wrapper constructor based on |object| and its | |
1895 * constructor. | |
1896 * @param {Node} object | |
1897 * @return {Function} The generated constructor. | |
1898 */ | |
1899 function registerObject(object) { | |
1900 var nativePrototype = Object.getPrototypeOf(object); | |
1901 | |
1902 var superWrapperConstructor = getWrapperConstructor(nativePrototype); | |
1903 var GeneratedWrapper = createWrapperConstructor(superWrapperConstructor); | |
1904 registerInternal(nativePrototype, GeneratedWrapper, object); | |
1905 | |
1906 return GeneratedWrapper; | |
1907 } | |
1908 | |
1909 function createWrapperConstructor(superWrapperConstructor) { | |
1910 function GeneratedWrapper(node) { | |
1911 superWrapperConstructor.call(this, node); | |
1912 } | |
1913 GeneratedWrapper.prototype = | |
1914 Object.create(superWrapperConstructor.prototype); | |
1915 GeneratedWrapper.prototype.constructor = GeneratedWrapper; | |
1916 | |
1917 return GeneratedWrapper; | |
1918 } | |
1919 | |
1920 var OriginalDOMImplementation = window.DOMImplementation; | |
1921 var OriginalEventTarget = window.EventTarget; | |
1922 var OriginalEvent = window.Event; | |
1923 var OriginalNode = window.Node; | |
1924 var OriginalWindow = window.Window; | |
1925 var OriginalRange = window.Range; | |
1926 var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D; | |
1927 var OriginalWebGLRenderingContext = window.WebGLRenderingContext; | |
1928 var OriginalSVGElementInstance = window.SVGElementInstance; | |
1929 | |
1930 function isWrapper(object) { | |
1931 return object instanceof wrappers.EventTarget || | |
1932 object instanceof wrappers.Event || | |
1933 object instanceof wrappers.Range || | |
1934 object instanceof wrappers.DOMImplementation || | |
1935 object instanceof wrappers.CanvasRenderingContext2D || | |
1936 wrappers.WebGLRenderingContext && | |
1937 object instanceof wrappers.WebGLRenderingContext; | |
1938 } | |
1939 | |
1940 function isNative(object) { | |
1941 return OriginalEventTarget && object instanceof OriginalEventTarget || | |
1942 object instanceof OriginalNode || | |
1943 object instanceof OriginalEvent || | |
1944 object instanceof OriginalWindow || | |
1945 object instanceof OriginalRange || | |
1946 object instanceof OriginalDOMImplementation || | |
1947 object instanceof OriginalCanvasRenderingContext2D || | |
1948 OriginalWebGLRenderingContext && | |
1949 object instanceof OriginalWebGLRenderingContext || | |
1950 OriginalSVGElementInstance && | |
1951 object instanceof OriginalSVGElementInstance; | |
1952 } | |
1953 | |
1954 /** | |
1955 * Wraps a node in a WrapperNode. If there already exists a wrapper for the | |
1956 * |node| that wrapper is returned instead. | |
1957 * @param {Node} node | |
1958 * @return {WrapperNode} | |
1959 */ | |
1960 function wrap(impl) { | |
1961 if (impl === null) | |
1962 return null; | |
1963 | |
1964 assert(isNative(impl)); | |
1965 return impl.polymerWrapper_ || | |
1966 (impl.polymerWrapper_ = new (getWrapperConstructor(impl))(impl)); | |
1967 } | |
1968 | |
1969 /** | |
1970 * Unwraps a wrapper and returns the node it is wrapping. | |
1971 * @param {WrapperNode} wrapper | |
1972 * @return {Node} | |
1973 */ | |
1974 function unwrap(wrapper) { | |
1975 if (wrapper === null) | |
1976 return null; | |
1977 assert(isWrapper(wrapper)); | |
1978 return wrapper.impl; | |
1979 } | |
1980 | |
1981 /** | |
1982 * Unwraps object if it is a wrapper. | |
1983 * @param {Object} object | |
1984 * @return {Object} The native implementation object. | |
1985 */ | |
1986 function unwrapIfNeeded(object) { | |
1987 return object && isWrapper(object) ? unwrap(object) : object; | |
1988 } | |
1989 | |
1990 /** | |
1991 * Wraps object if it is not a wrapper. | |
1992 * @param {Object} object | |
1993 * @return {Object} The wrapper for object. | |
1994 */ | |
1995 function wrapIfNeeded(object) { | |
1996 return object && !isWrapper(object) ? wrap(object) : object; | |
1997 } | |
1998 | |
1999 /** | |
2000 * Overrides the current wrapper (if any) for node. | |
2001 * @param {Node} node | |
2002 * @param {WrapperNode=} wrapper If left out the wrapper will be created as | |
2003 * needed next time someone wraps the node. | |
2004 */ | |
2005 function rewrap(node, wrapper) { | |
2006 if (wrapper === null) | |
2007 return; | |
2008 assert(isNative(node)); | |
2009 assert(wrapper === undefined || isWrapper(wrapper)); | |
2010 node.polymerWrapper_ = wrapper; | |
2011 } | |
2012 | |
2013 function defineGetter(constructor, name, getter) { | |
2014 defineProperty(constructor.prototype, name, { | |
2015 get: getter, | |
2016 configurable: true, | |
2017 enumerable: true | |
2018 }); | |
2019 } | |
2020 | |
2021 function defineWrapGetter(constructor, name) { | |
2022 defineGetter(constructor, name, function() { | |
2023 return wrap(this.impl[name]); | |
2024 }); | |
2025 } | |
2026 | |
2027 /** | |
2028 * Forwards existing methods on the native object to the wrapper methods. | |
2029 * This does not wrap any of the arguments or the return value since the | |
2030 * wrapper implementation already takes care of that. | |
2031 * @param {Array.<Function>} constructors | |
2032 * @parem {Array.<string>} names | |
2033 */ | |
2034 function forwardMethodsToWrapper(constructors, names) { | |
2035 constructors.forEach(function(constructor) { | |
2036 names.forEach(function(name) { | |
2037 constructor.prototype[name] = function() { | |
2038 var w = wrapIfNeeded(this); | |
2039 return w[name].apply(w, arguments); | |
2040 }; | |
2041 }); | |
2042 }); | |
2043 } | |
2044 | |
2045 scope.assert = assert; | |
2046 scope.constructorTable = constructorTable; | |
2047 scope.defineGetter = defineGetter; | |
2048 scope.defineWrapGetter = defineWrapGetter; | |
2049 scope.forwardMethodsToWrapper = forwardMethodsToWrapper; | |
2050 scope.isWrapper = isWrapper; | |
2051 scope.isWrapperFor = isWrapperFor; | |
2052 scope.mixin = mixin; | |
2053 scope.nativePrototypeTable = nativePrototypeTable; | |
2054 scope.oneOf = oneOf; | |
2055 scope.registerObject = registerObject; | |
2056 scope.registerWrapper = register; | |
2057 scope.rewrap = rewrap; | |
2058 scope.unwrap = unwrap; | |
2059 scope.unwrapIfNeeded = unwrapIfNeeded; | |
2060 scope.wrap = wrap; | |
2061 scope.wrapIfNeeded = wrapIfNeeded; | |
2062 scope.wrappers = wrappers; | |
2063 | |
2064 })(window.ShadowDOMPolyfill); | |
2065 | |
2066 /* | |
2067 * Copyright 2013 The Polymer Authors. All rights reserved. | |
2068 * Use of this source code is goverened by a BSD-style | |
2069 * license that can be found in the LICENSE file. | |
2070 */ | |
2071 | |
2072 (function(context) { | |
2073 'use strict'; | |
2074 | |
2075 var OriginalMutationObserver = window.MutationObserver; | |
2076 var callbacks = []; | |
2077 var pending = false; | |
2078 var timerFunc; | |
2079 | |
2080 function handle() { | |
2081 pending = false; | |
2082 var copies = callbacks.slice(0); | |
2083 callbacks = []; | |
2084 for (var i = 0; i < copies.length; i++) { | |
2085 (0, copies[i])(); | |
2086 } | |
2087 } | |
2088 | |
2089 if (OriginalMutationObserver) { | |
2090 var counter = 1; | |
2091 var observer = new OriginalMutationObserver(handle); | |
2092 var textNode = document.createTextNode(counter); | |
2093 observer.observe(textNode, {characterData: true}); | |
2094 | |
2095 timerFunc = function() { | |
2096 counter = (counter + 1) % 2; | |
2097 textNode.data = counter; | |
2098 }; | |
2099 | |
2100 } else { | |
2101 timerFunc = window.setImmediate || window.setTimeout; | |
2102 } | |
2103 | |
2104 function setEndOfMicrotask(func) { | |
2105 callbacks.push(func); | |
2106 if (pending) | |
2107 return; | |
2108 pending = true; | |
2109 timerFunc(handle, 0); | |
2110 } | |
2111 | |
2112 context.setEndOfMicrotask = setEndOfMicrotask; | |
2113 | |
2114 })(window.ShadowDOMPolyfill); | |
2115 | |
2116 /* | |
2117 * Copyright 2013 The Polymer Authors. All rights reserved. | |
2118 * Use of this source code is goverened by a BSD-style | |
2119 * license that can be found in the LICENSE file. | |
2120 */ | |
2121 | |
2122 (function(scope) { | |
2123 'use strict'; | |
2124 | |
2125 var setEndOfMicrotask = scope.setEndOfMicrotask | |
2126 var wrapIfNeeded = scope.wrapIfNeeded | |
2127 var wrappers = scope.wrappers; | |
2128 | |
2129 var registrationsTable = new WeakMap(); | |
2130 var globalMutationObservers = []; | |
2131 var isScheduled = false; | |
2132 | |
2133 function scheduleCallback(observer) { | |
2134 if (isScheduled) | |
2135 return; | |
2136 setEndOfMicrotask(notifyObservers); | |
2137 isScheduled = true; | |
2138 } | |
2139 | |
2140 // http://dom.spec.whatwg.org/#mutation-observers | |
2141 function notifyObservers() { | |
2142 isScheduled = false; | |
2143 | |
2144 do { | |
2145 var notifyList = globalMutationObservers.slice(); | |
2146 var anyNonEmpty = false; | |
2147 for (var i = 0; i < notifyList.length; i++) { | |
2148 var mo = notifyList[i]; | |
2149 var queue = mo.takeRecords(); | |
2150 removeTransientObserversFor(mo); | |
2151 if (queue.length) { | |
2152 mo.callback_(queue, mo); | |
2153 anyNonEmpty = true; | |
2154 } | |
2155 } | |
2156 } while (anyNonEmpty); | |
2157 } | |
2158 | |
2159 /** | |
2160 * @param {string} type | |
2161 * @param {Node} target | |
2162 * @constructor | |
2163 */ | |
2164 function MutationRecord(type, target) { | |
2165 this.type = type; | |
2166 this.target = target; | |
2167 this.addedNodes = new wrappers.NodeList(); | |
2168 this.removedNodes = new wrappers.NodeList(); | |
2169 this.previousSibling = null; | |
2170 this.nextSibling = null; | |
2171 this.attributeName = null; | |
2172 this.attributeNamespace = null; | |
2173 this.oldValue = null; | |
2174 } | |
2175 | |
2176 /** | |
2177 * Registers transient observers to ancestor and its ancesors for the node | |
2178 * which was removed. | |
2179 * @param {!Node} ancestor | |
2180 * @param {!Node} node | |
2181 */ | |
2182 function registerTransientObservers(ancestor, node) { | |
2183 for (; ancestor; ancestor = ancestor.parentNode) { | |
2184 var registrations = registrationsTable.get(ancestor); | |
2185 if (!registrations) | |
2186 continue; | |
2187 for (var i = 0; i < registrations.length; i++) { | |
2188 var registration = registrations[i]; | |
2189 if (registration.options.subtree) | |
2190 registration.addTransientObserver(node); | |
2191 } | |
2192 } | |
2193 } | |
2194 | |
2195 function removeTransientObserversFor(observer) { | |
2196 for (var i = 0; i < observer.nodes_.length; i++) { | |
2197 var node = observer.nodes_[i]; | |
2198 var registrations = registrationsTable.get(node); | |
2199 if (!registrations) | |
2200 return; | |
2201 for (var j = 0; j < registrations.length; j++) { | |
2202 var registration = registrations[j]; | |
2203 if (registration.observer === observer) | |
2204 registration.removeTransientObservers(); | |
2205 } | |
2206 } | |
2207 } | |
2208 | |
2209 // http://dom.spec.whatwg.org/#queue-a-mutation-record | |
2210 function enqueueMutation(target, type, data) { | |
2211 // 1. | |
2212 var interestedObservers = Object.create(null); | |
2213 var associatedStrings = Object.create(null); | |
2214 | |
2215 // 2. | |
2216 for (var node = target; node; node = node.parentNode) { | |
2217 // 3. | |
2218 var registrations = registrationsTable.get(node); | |
2219 if (!registrations) | |
2220 continue; | |
2221 for (var j = 0; j < registrations.length; j++) { | |
2222 var registration = registrations[j]; | |
2223 var options = registration.options; | |
2224 // 1. | |
2225 if (node !== target && !options.subtree) | |
2226 continue; | |
2227 | |
2228 // 2. | |
2229 if (type === 'attributes' && !options.attributes) | |
2230 continue; | |
2231 | |
2232 // 3. If type is "attributes", options's attributeFilter is present, and | |
2233 // either options's attributeFilter does not contain name or namespace | |
2234 // is non-null, continue. | |
2235 if (type === 'attributes' && options.attributeFilter && | |
2236 (data.namespace !== null || | |
2237 options.attributeFilter.indexOf(data.name) === -1)) { | |
2238 continue; | |
2239 } | |
2240 | |
2241 // 4. | |
2242 if (type === 'characterData' && !options.characterData) | |
2243 continue; | |
2244 | |
2245 // 5. | |
2246 if (type === 'childList' && !options.childList) | |
2247 continue; | |
2248 | |
2249 // 6. | |
2250 var observer = registration.observer; | |
2251 interestedObservers[observer.uid_] = observer; | |
2252 | |
2253 // 7. If either type is "attributes" and options's attributeOldValue is | |
2254 // true, or type is "characterData" and options's characterDataOldValue | |
2255 // is true, set the paired string of registered observer's observer in | |
2256 // interested observers to oldValue. | |
2257 if (type === 'attributes' && options.attributeOldValue || | |
2258 type === 'characterData' && options.characterDataOldValue) { | |
2259 associatedStrings[observer.uid_] = data.oldValue; | |
2260 } | |
2261 } | |
2262 } | |
2263 | |
2264 var anyRecordsEnqueued = false; | |
2265 | |
2266 // 4. | |
2267 for (var uid in interestedObservers) { | |
2268 var observer = interestedObservers[uid]; | |
2269 var record = new MutationRecord(type, target); | |
2270 | |
2271 // 2. | |
2272 if ('name' in data && 'namespace' in data) { | |
2273 record.attributeName = data.name; | |
2274 record.attributeNamespace = data.namespace; | |
2275 } | |
2276 | |
2277 // 3. | |
2278 if (data.addedNodes) | |
2279 record.addedNodes = data.addedNodes; | |
2280 | |
2281 // 4. | |
2282 if (data.removedNodes) | |
2283 record.removedNodes = data.removedNodes; | |
2284 | |
2285 // 5. | |
2286 if (data.previousSibling) | |
2287 record.previousSibling = data.previousSibling; | |
2288 | |
2289 // 6. | |
2290 if (data.nextSibling) | |
2291 record.nextSibling = data.nextSibling; | |
2292 | |
2293 // 7. | |
2294 if (associatedStrings[uid] !== undefined) | |
2295 record.oldValue = associatedStrings[uid]; | |
2296 | |
2297 // 8. | |
2298 observer.records_.push(record); | |
2299 | |
2300 anyRecordsEnqueued = true; | |
2301 } | |
2302 | |
2303 if (anyRecordsEnqueued) | |
2304 scheduleCallback(); | |
2305 } | |
2306 | |
2307 var slice = Array.prototype.slice; | |
2308 | |
2309 /** | |
2310 * @param {!Object} options | |
2311 * @constructor | |
2312 */ | |
2313 function MutationObserverOptions(options) { | |
2314 this.childList = !!options.childList; | |
2315 this.subtree = !!options.subtree; | |
2316 | |
2317 // 1. If either options' attributeOldValue or attributeFilter is present | |
2318 // and options' attributes is omitted, set options' attributes to true. | |
2319 if (!('attributes' in options) && | |
2320 ('attributeOldValue' in options || 'attributeFilter' in options)) { | |
2321 this.attributes = true; | |
2322 } else { | |
2323 this.attributes = !!options.attributes; | |
2324 } | |
2325 | |
2326 // 2. If options' characterDataOldValue is present and options' | |
2327 // characterData is omitted, set options' characterData to true. | |
2328 if ('characterDataOldValue' in options && !('characterData' in options)) | |
2329 this.characterData = true; | |
2330 else | |
2331 this.characterData = !!options.characterData; | |
2332 | |
2333 // 3. & 4. | |
2334 if (!this.attributes && | |
2335 (options.attributeOldValue || 'attributeFilter' in options) || | |
2336 // 5. | |
2337 !this.characterData && options.characterDataOldValue) { | |
2338 throw new TypeError(); | |
2339 } | |
2340 | |
2341 this.characterData = !!options.characterData; | |
2342 this.attributeOldValue = !!options.attributeOldValue; | |
2343 this.characterDataOldValue = !!options.characterDataOldValue; | |
2344 if ('attributeFilter' in options) { | |
2345 if (options.attributeFilter == null || | |
2346 typeof options.attributeFilter !== 'object') { | |
2347 throw new TypeError(); | |
2348 } | |
2349 this.attributeFilter = slice.call(options.attributeFilter); | |
2350 } else { | |
2351 this.attributeFilter = null; | |
2352 } | |
2353 } | |
2354 | |
2355 var uidCounter = 0; | |
2356 | |
2357 /** | |
2358 * The class that maps to the DOM MutationObserver interface. | |
2359 * @param {Function} callback. | |
2360 * @constructor | |
2361 */ | |
2362 function MutationObserver(callback) { | |
2363 this.callback_ = callback; | |
2364 this.nodes_ = []; | |
2365 this.records_ = []; | |
2366 this.uid_ = ++uidCounter; | |
2367 | |
2368 // This will leak. There is no way to implement this without WeakRefs :'( | |
2369 globalMutationObservers.push(this); | |
2370 } | |
2371 | |
2372 MutationObserver.prototype = { | |
2373 // http://dom.spec.whatwg.org/#dom-mutationobserver-observe | |
2374 observe: function(target, options) { | |
2375 target = wrapIfNeeded(target); | |
2376 | |
2377 var newOptions = new MutationObserverOptions(options); | |
2378 | |
2379 // 6. | |
2380 var registration; | |
2381 var registrations = registrationsTable.get(target); | |
2382 if (!registrations) | |
2383 registrationsTable.set(target, registrations = []); | |
2384 | |
2385 for (var i = 0; i < registrations.length; i++) { | |
2386 if (registrations[i].observer === this) { | |
2387 registration = registrations[i]; | |
2388 // 6.1. | |
2389 registration.removeTransientObservers(); | |
2390 // 6.2. | |
2391 registration.options = newOptions; | |
2392 } | |
2393 } | |
2394 | |
2395 // 7. | |
2396 if (!registration) { | |
2397 registration = new Registration(this, target, newOptions); | |
2398 registrations.push(registration); | |
2399 this.nodes_.push(target); | |
2400 } | |
2401 }, | |
2402 | |
2403 // http://dom.spec.whatwg.org/#dom-mutationobserver-disconnect | |
2404 disconnect: function() { | |
2405 this.nodes_.forEach(function(node) { | |
2406 var registrations = registrationsTable.get(node); | |
2407 for (var i = 0; i < registrations.length; i++) { | |
2408 var registration = registrations[i]; | |
2409 if (registration.observer === this) { | |
2410 registrations.splice(i, 1); | |
2411 // Each node can only have one registered observer associated with | |
2412 // this observer. | |
2413 break; | |
2414 } | |
2415 } | |
2416 }, this); | |
2417 this.records_ = []; | |
2418 }, | |
2419 | |
2420 takeRecords: function() { | |
2421 var copyOfRecords = this.records_; | |
2422 this.records_ = []; | |
2423 return copyOfRecords; | |
2424 } | |
2425 }; | |
2426 | |
2427 /** | |
2428 * Class used to represent a registered observer. | |
2429 * @param {MutationObserver} observer | |
2430 * @param {Node} target | |
2431 * @param {MutationObserverOptions} options | |
2432 * @constructor | |
2433 */ | |
2434 function Registration(observer, target, options) { | |
2435 this.observer = observer; | |
2436 this.target = target; | |
2437 this.options = options; | |
2438 this.transientObservedNodes = []; | |
2439 } | |
2440 | |
2441 Registration.prototype = { | |
2442 /** | |
2443 * Adds a transient observer on node. The transient observer gets removed | |
2444 * next time we deliver the change records. | |
2445 * @param {Node} node | |
2446 */ | |
2447 addTransientObserver: function(node) { | |
2448 // Don't add transient observers on the target itself. We already have all | |
2449 // the required listeners set up on the target. | |
2450 if (node === this.target) | |
2451 return; | |
2452 | |
2453 this.transientObservedNodes.push(node); | |
2454 var registrations = registrationsTable.get(node); | |
2455 if (!registrations) | |
2456 registrationsTable.set(node, registrations = []); | |
2457 | |
2458 // We know that registrations does not contain this because we already | |
2459 // checked if node === this.target. | |
2460 registrations.push(this); | |
2461 }, | |
2462 | |
2463 removeTransientObservers: function() { | |
2464 var transientObservedNodes = this.transientObservedNodes; | |
2465 this.transientObservedNodes = []; | |
2466 | |
2467 for (var i = 0; i < transientObservedNodes.length; i++) { | |
2468 var node = transientObservedNodes[i]; | |
2469 var registrations = registrationsTable.get(node); | |
2470 for (var j = 0; j < registrations.length; j++) { | |
2471 if (registrations[j] === this) { | |
2472 registrations.splice(j, 1); | |
2473 // Each node can only have one registered observer associated with | |
2474 // this observer. | |
2475 break; | |
2476 } | |
2477 } | |
2478 } | |
2479 } | |
2480 }; | |
2481 | |
2482 scope.enqueueMutation = enqueueMutation; | |
2483 scope.registerTransientObservers = registerTransientObservers; | |
2484 scope.wrappers.MutationObserver = MutationObserver; | |
2485 scope.wrappers.MutationRecord = MutationRecord; | |
2486 | |
2487 })(window.ShadowDOMPolyfill); | |
2488 | |
2489 // Copyright 2013 The Polymer Authors. All rights reserved. | |
2490 // Use of this source code is goverened by a BSD-style | |
2491 // license that can be found in the LICENSE file. | |
2492 | |
2493 (function(scope) { | |
2494 'use strict'; | |
2495 | |
2496 var forwardMethodsToWrapper = scope.forwardMethodsToWrapper; | |
2497 var mixin = scope.mixin; | |
2498 var registerWrapper = scope.registerWrapper; | |
2499 var unwrap = scope.unwrap; | |
2500 var wrap = scope.wrap; | |
2501 var wrappers = scope.wrappers; | |
2502 | |
2503 var wrappedFuns = new WeakMap(); | |
2504 var listenersTable = new WeakMap(); | |
2505 var handledEventsTable = new WeakMap(); | |
2506 var currentlyDispatchingEvents = new WeakMap(); | |
2507 var targetTable = new WeakMap(); | |
2508 var currentTargetTable = new WeakMap(); | |
2509 var relatedTargetTable = new WeakMap(); | |
2510 var eventPhaseTable = new WeakMap(); | |
2511 var stopPropagationTable = new WeakMap(); | |
2512 var stopImmediatePropagationTable = new WeakMap(); | |
2513 var eventHandlersTable = new WeakMap(); | |
2514 var eventPathTable = new WeakMap(); | |
2515 | |
2516 function isShadowRoot(node) { | |
2517 return node instanceof wrappers.ShadowRoot; | |
2518 } | |
2519 | |
2520 function isInsertionPoint(node) { | |
2521 var localName = node.localName; | |
2522 return localName === 'content' || localName === 'shadow'; | |
2523 } | |
2524 | |
2525 function isShadowHost(node) { | |
2526 return !!node.shadowRoot; | |
2527 } | |
2528 | |
2529 function getEventParent(node) { | |
2530 var dv; | |
2531 return node.parentNode || (dv = node.defaultView) && wrap(dv) || null; | |
2532 } | |
2533 | |
2534 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#df
n-adjusted-parent | |
2535 function calculateParents(node, context, ancestors) { | |
2536 if (ancestors.length) | |
2537 return ancestors.shift(); | |
2538 | |
2539 // 1. | |
2540 if (isShadowRoot(node)) | |
2541 return getInsertionParent(node) || node.host; | |
2542 | |
2543 // 2. | |
2544 var eventParents = scope.eventParentsTable.get(node); | |
2545 if (eventParents) { | |
2546 // Copy over the remaining event parents for next iteration. | |
2547 for (var i = 1; i < eventParents.length; i++) { | |
2548 ancestors[i - 1] = eventParents[i]; | |
2549 } | |
2550 return eventParents[0]; | |
2551 } | |
2552 | |
2553 // 3. | |
2554 if (context && isInsertionPoint(node)) { | |
2555 var parentNode = node.parentNode; | |
2556 if (parentNode && isShadowHost(parentNode)) { | |
2557 var trees = scope.getShadowTrees(parentNode); | |
2558 var p = getInsertionParent(context); | |
2559 for (var i = 0; i < trees.length; i++) { | |
2560 if (trees[i].contains(p)) | |
2561 return p; | |
2562 } | |
2563 } | |
2564 } | |
2565 | |
2566 return getEventParent(node); | |
2567 } | |
2568 | |
2569 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#ev
ent-retargeting | |
2570 function retarget(node) { | |
2571 var stack = []; // 1. | |
2572 var ancestor = node; // 2. | |
2573 var targets = []; | |
2574 var ancestors = []; | |
2575 while (ancestor) { // 3. | |
2576 var context = null; // 3.2. | |
2577 // TODO(arv): Change order of these. If the stack is empty we always end | |
2578 // up pushing ancestor, no matter what. | |
2579 if (isInsertionPoint(ancestor)) { // 3.1. | |
2580 context = topMostNotInsertionPoint(stack); // 3.1.1. | |
2581 var top = stack[stack.length - 1] || ancestor; // 3.1.2. | |
2582 stack.push(top); | |
2583 } else if (!stack.length) { | |
2584 stack.push(ancestor); // 3.3. | |
2585 } | |
2586 var target = stack[stack.length - 1]; // 3.4. | |
2587 targets.push({target: target, currentTarget: ancestor}); // 3.5. | |
2588 if (isShadowRoot(ancestor)) // 3.6. | |
2589 stack.pop(); // 3.6.1. | |
2590 | |
2591 ancestor = calculateParents(ancestor, context, ancestors); // 3.7. | |
2592 } | |
2593 return targets; | |
2594 } | |
2595 | |
2596 function topMostNotInsertionPoint(stack) { | |
2597 for (var i = stack.length - 1; i >= 0; i--) { | |
2598 if (!isInsertionPoint(stack[i])) | |
2599 return stack[i]; | |
2600 } | |
2601 return null; | |
2602 } | |
2603 | |
2604 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#df
n-adjusted-related-target | |
2605 function adjustRelatedTarget(target, related) { | |
2606 var ancestors = []; | |
2607 while (target) { // 3. | |
2608 var stack = []; // 3.1. | |
2609 var ancestor = related; // 3.2. | |
2610 var last = undefined; // 3.3. Needs to be reset every iteration. | |
2611 while (ancestor) { | |
2612 var context = null; | |
2613 if (!stack.length) { | |
2614 stack.push(ancestor); | |
2615 } else { | |
2616 if (isInsertionPoint(ancestor)) { // 3.4.3. | |
2617 context = topMostNotInsertionPoint(stack); | |
2618 // isDistributed is more general than checking whether last is | |
2619 // assigned into ancestor. | |
2620 if (isDistributed(last)) { // 3.4.3.2. | |
2621 var head = stack[stack.length - 1]; | |
2622 stack.push(head); | |
2623 } | |
2624 } | |
2625 } | |
2626 | |
2627 if (inSameTree(ancestor, target)) // 3.4.4. | |
2628 return stack[stack.length - 1]; | |
2629 | |
2630 if (isShadowRoot(ancestor)) // 3.4.5. | |
2631 stack.pop(); | |
2632 | |
2633 last = ancestor; // 3.4.6. | |
2634 ancestor = calculateParents(ancestor, context, ancestors); // 3.4.7. | |
2635 } | |
2636 if (isShadowRoot(target)) // 3.5. | |
2637 target = target.host; | |
2638 else | |
2639 target = target.parentNode; // 3.6. | |
2640 } | |
2641 } | |
2642 | |
2643 function getInsertionParent(node) { | |
2644 return scope.insertionParentTable.get(node); | |
2645 } | |
2646 | |
2647 function isDistributed(node) { | |
2648 return getInsertionParent(node); | |
2649 } | |
2650 | |
2651 function rootOfNode(node) { | |
2652 var p; | |
2653 while (p = node.parentNode) { | |
2654 node = p; | |
2655 } | |
2656 return node; | |
2657 } | |
2658 | |
2659 function inSameTree(a, b) { | |
2660 return rootOfNode(a) === rootOfNode(b); | |
2661 } | |
2662 | |
2663 function enclosedBy(a, b) { | |
2664 if (a === b) | |
2665 return true; | |
2666 if (a instanceof wrappers.ShadowRoot) | |
2667 return enclosedBy(rootOfNode(a.host), b); | |
2668 return false; | |
2669 } | |
2670 | |
2671 | |
2672 function dispatchOriginalEvent(originalEvent) { | |
2673 // Make sure this event is only dispatched once. | |
2674 if (handledEventsTable.get(originalEvent)) | |
2675 return; | |
2676 handledEventsTable.set(originalEvent, true); | |
2677 | |
2678 return dispatchEvent(wrap(originalEvent), wrap(originalEvent.target)); | |
2679 } | |
2680 | |
2681 function dispatchEvent(event, originalWrapperTarget) { | |
2682 if (currentlyDispatchingEvents.get(event)) | |
2683 throw new Error('InvalidStateError') | |
2684 currentlyDispatchingEvents.set(event, true); | |
2685 | |
2686 // Render to ensure that the event path is correct. | |
2687 scope.renderAllPending(); | |
2688 var eventPath = retarget(originalWrapperTarget); | |
2689 | |
2690 // For window load events the load event is dispatched at the window but | |
2691 // the target is set to the document. | |
2692 // | |
2693 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#
the-end | |
2694 // | |
2695 // TODO(arv): Find a less hacky way to do this. | |
2696 if (event.type === 'load' && | |
2697 eventPath.length === 2 && | |
2698 eventPath[0].target instanceof wrappers.Document) { | |
2699 eventPath.shift(); | |
2700 } | |
2701 | |
2702 eventPathTable.set(event, eventPath); | |
2703 | |
2704 if (dispatchCapturing(event, eventPath)) { | |
2705 if (dispatchAtTarget(event, eventPath)) { | |
2706 dispatchBubbling(event, eventPath); | |
2707 } | |
2708 } | |
2709 | |
2710 eventPhaseTable.set(event, Event.NONE); | |
2711 currentTargetTable.delete(event, null); | |
2712 currentlyDispatchingEvents.delete(event); | |
2713 | |
2714 return event.defaultPrevented; | |
2715 } | |
2716 | |
2717 function dispatchCapturing(event, eventPath) { | |
2718 var phase; | |
2719 | |
2720 for (var i = eventPath.length - 1; i > 0; i--) { | |
2721 var target = eventPath[i].target; | |
2722 var currentTarget = eventPath[i].currentTarget; | |
2723 if (target === currentTarget) | |
2724 continue; | |
2725 | |
2726 phase = Event.CAPTURING_PHASE; | |
2727 if (!invoke(eventPath[i], event, phase)) | |
2728 return false; | |
2729 } | |
2730 | |
2731 return true; | |
2732 } | |
2733 | |
2734 function dispatchAtTarget(event, eventPath) { | |
2735 var phase = Event.AT_TARGET; | |
2736 return invoke(eventPath[0], event, phase); | |
2737 } | |
2738 | |
2739 function dispatchBubbling(event, eventPath) { | |
2740 var bubbles = event.bubbles; | |
2741 var phase; | |
2742 | |
2743 for (var i = 1; i < eventPath.length; i++) { | |
2744 var target = eventPath[i].target; | |
2745 var currentTarget = eventPath[i].currentTarget; | |
2746 if (target === currentTarget) | |
2747 phase = Event.AT_TARGET; | |
2748 else if (bubbles && !stopImmediatePropagationTable.get(event)) | |
2749 phase = Event.BUBBLING_PHASE; | |
2750 else | |
2751 continue; | |
2752 | |
2753 if (!invoke(eventPath[i], event, phase)) | |
2754 return; | |
2755 } | |
2756 } | |
2757 | |
2758 function invoke(tuple, event, phase) { | |
2759 var target = tuple.target; | |
2760 var currentTarget = tuple.currentTarget; | |
2761 | |
2762 var listeners = listenersTable.get(currentTarget); | |
2763 if (!listeners) | |
2764 return true; | |
2765 | |
2766 if ('relatedTarget' in event) { | |
2767 var originalEvent = unwrap(event); | |
2768 // X-Tag sets relatedTarget on a CustomEvent. If they do that there is no | |
2769 // way to have relatedTarget return the adjusted target but worse is that | |
2770 // the originalEvent might not have a relatedTarget so we hit an assert | |
2771 // when we try to wrap it. | |
2772 if (originalEvent.relatedTarget) { | |
2773 var relatedTarget = wrap(originalEvent.relatedTarget); | |
2774 | |
2775 var adjusted = adjustRelatedTarget(currentTarget, relatedTarget); | |
2776 if (adjusted === target) | |
2777 return true; | |
2778 | |
2779 relatedTargetTable.set(event, adjusted); | |
2780 } | |
2781 } | |
2782 | |
2783 eventPhaseTable.set(event, phase); | |
2784 var type = event.type; | |
2785 | |
2786 var anyRemoved = false; | |
2787 targetTable.set(event, target); | |
2788 currentTargetTable.set(event, currentTarget); | |
2789 | |
2790 for (var i = 0; i < listeners.length; i++) { | |
2791 var listener = listeners[i]; | |
2792 if (listener.removed) { | |
2793 anyRemoved = true; | |
2794 continue; | |
2795 } | |
2796 | |
2797 if (listener.type !== type || | |
2798 !listener.capture && phase === Event.CAPTURING_PHASE || | |
2799 listener.capture && phase === Event.BUBBLING_PHASE) { | |
2800 continue; | |
2801 } | |
2802 | |
2803 try { | |
2804 if (typeof listener.handler === 'function') | |
2805 listener.handler.call(currentTarget, event); | |
2806 else | |
2807 listener.handler.handleEvent(event); | |
2808 | |
2809 if (stopImmediatePropagationTable.get(event)) | |
2810 return false; | |
2811 | |
2812 } catch (ex) { | |
2813 if (window.onerror) | |
2814 window.onerror(ex.message); | |
2815 else | |
2816 console.error(ex, ex.stack); | |
2817 } | |
2818 } | |
2819 | |
2820 if (anyRemoved) { | |
2821 var copy = listeners.slice(); | |
2822 listeners.length = 0; | |
2823 for (var i = 0; i < copy.length; i++) { | |
2824 if (!copy[i].removed) | |
2825 listeners.push(copy[i]); | |
2826 } | |
2827 } | |
2828 | |
2829 return !stopPropagationTable.get(event); | |
2830 } | |
2831 | |
2832 function Listener(type, handler, capture) { | |
2833 this.type = type; | |
2834 this.handler = handler; | |
2835 this.capture = Boolean(capture); | |
2836 } | |
2837 Listener.prototype = { | |
2838 equals: function(that) { | |
2839 return this.handler === that.handler && this.type === that.type && | |
2840 this.capture === that.capture; | |
2841 }, | |
2842 get removed() { | |
2843 return this.handler === null; | |
2844 }, | |
2845 remove: function() { | |
2846 this.handler = null; | |
2847 } | |
2848 }; | |
2849 | |
2850 var OriginalEvent = window.Event; | |
2851 OriginalEvent.prototype.polymerBlackList_ = { | |
2852 returnValue: true, | |
2853 // TODO(arv): keyLocation is part of KeyboardEvent but Firefox does not | |
2854 // support constructable KeyboardEvent so we keep it here for now. | |
2855 keyLocation: true | |
2856 }; | |
2857 | |
2858 /** | |
2859 * Creates a new Event wrapper or wraps an existin native Event object. | |
2860 * @param {string|Event} type | |
2861 * @param {Object=} options | |
2862 * @constructor | |
2863 */ | |
2864 function Event(type, options) { | |
2865 if (type instanceof OriginalEvent) | |
2866 this.impl = type; | |
2867 else | |
2868 return wrap(constructEvent(OriginalEvent, 'Event', type, options)); | |
2869 } | |
2870 Event.prototype = { | |
2871 get target() { | |
2872 return targetTable.get(this); | |
2873 }, | |
2874 get currentTarget() { | |
2875 return currentTargetTable.get(this); | |
2876 }, | |
2877 get eventPhase() { | |
2878 return eventPhaseTable.get(this); | |
2879 }, | |
2880 get path() { | |
2881 var nodeList = new wrappers.NodeList(); | |
2882 var eventPath = eventPathTable.get(this); | |
2883 if (eventPath) { | |
2884 var index = 0; | |
2885 var lastIndex = eventPath.length - 1; | |
2886 var baseRoot = rootOfNode(currentTargetTable.get(this)); | |
2887 | |
2888 for (var i = 0; i <= lastIndex; i++) { | |
2889 var currentTarget = eventPath[i].currentTarget; | |
2890 var currentRoot = rootOfNode(currentTarget); | |
2891 if (enclosedBy(baseRoot, currentRoot) && | |
2892 // Make sure we do not add Window to the path. | |
2893 (i !== lastIndex || currentTarget instanceof wrappers.Node)) { | |
2894 nodeList[index++] = currentTarget; | |
2895 } | |
2896 } | |
2897 nodeList.length = index; | |
2898 } | |
2899 return nodeList; | |
2900 }, | |
2901 stopPropagation: function() { | |
2902 stopPropagationTable.set(this, true); | |
2903 }, | |
2904 stopImmediatePropagation: function() { | |
2905 stopPropagationTable.set(this, true); | |
2906 stopImmediatePropagationTable.set(this, true); | |
2907 } | |
2908 }; | |
2909 registerWrapper(OriginalEvent, Event, document.createEvent('Event')); | |
2910 | |
2911 function unwrapOptions(options) { | |
2912 if (!options || !options.relatedTarget) | |
2913 return options; | |
2914 return Object.create(options, { | |
2915 relatedTarget: {value: unwrap(options.relatedTarget)} | |
2916 }); | |
2917 } | |
2918 | |
2919 function registerGenericEvent(name, SuperEvent, prototype) { | |
2920 var OriginalEvent = window[name]; | |
2921 var GenericEvent = function(type, options) { | |
2922 if (type instanceof OriginalEvent) | |
2923 this.impl = type; | |
2924 else | |
2925 return wrap(constructEvent(OriginalEvent, name, type, options)); | |
2926 }; | |
2927 GenericEvent.prototype = Object.create(SuperEvent.prototype); | |
2928 if (prototype) | |
2929 mixin(GenericEvent.prototype, prototype); | |
2930 if (OriginalEvent) { | |
2931 // - Old versions of Safari fails on new FocusEvent (and others?). | |
2932 // - IE does not support event constructors. | |
2933 // - createEvent('FocusEvent') throws in Firefox. | |
2934 // => Try the best practice solution first and fallback to the old way | |
2935 // if needed. | |
2936 try { | |
2937 registerWrapper(OriginalEvent, GenericEvent, new OriginalEvent('temp')); | |
2938 } catch (ex) { | |
2939 registerWrapper(OriginalEvent, GenericEvent, | |
2940 document.createEvent(name)); | |
2941 } | |
2942 } | |
2943 return GenericEvent; | |
2944 } | |
2945 | |
2946 var UIEvent = registerGenericEvent('UIEvent', Event); | |
2947 var CustomEvent = registerGenericEvent('CustomEvent', Event); | |
2948 | |
2949 var relatedTargetProto = { | |
2950 get relatedTarget() { | |
2951 return relatedTargetTable.get(this) || wrap(unwrap(this).relatedTarget); | |
2952 } | |
2953 }; | |
2954 | |
2955 function getInitFunction(name, relatedTargetIndex) { | |
2956 return function() { | |
2957 arguments[relatedTargetIndex] = unwrap(arguments[relatedTargetIndex]); | |
2958 var impl = unwrap(this); | |
2959 impl[name].apply(impl, arguments); | |
2960 }; | |
2961 } | |
2962 | |
2963 var mouseEventProto = mixin({ | |
2964 initMouseEvent: getInitFunction('initMouseEvent', 14) | |
2965 }, relatedTargetProto); | |
2966 | |
2967 var focusEventProto = mixin({ | |
2968 initFocusEvent: getInitFunction('initFocusEvent', 5) | |
2969 }, relatedTargetProto); | |
2970 | |
2971 var MouseEvent = registerGenericEvent('MouseEvent', UIEvent, mouseEventProto); | |
2972 var FocusEvent = registerGenericEvent('FocusEvent', UIEvent, focusEventProto); | |
2973 | |
2974 // In case the browser does not support event constructors we polyfill that | |
2975 // by calling `createEvent('Foo')` and `initFooEvent` where the arguments to | |
2976 // `initFooEvent` are derived from the registered default event init dict. | |
2977 var defaultInitDicts = Object.create(null); | |
2978 | |
2979 var supportsEventConstructors = (function() { | |
2980 try { | |
2981 new window.FocusEvent('focus'); | |
2982 } catch (ex) { | |
2983 return false; | |
2984 } | |
2985 return true; | |
2986 })(); | |
2987 | |
2988 /** | |
2989 * Constructs a new native event. | |
2990 */ | |
2991 function constructEvent(OriginalEvent, name, type, options) { | |
2992 if (supportsEventConstructors) | |
2993 return new OriginalEvent(type, unwrapOptions(options)); | |
2994 | |
2995 // Create the arguments from the default dictionary. | |
2996 var event = unwrap(document.createEvent(name)); | |
2997 var defaultDict = defaultInitDicts[name]; | |
2998 var args = [type]; | |
2999 Object.keys(defaultDict).forEach(function(key) { | |
3000 var v = options != null && key in options ? | |
3001 options[key] : defaultDict[key]; | |
3002 if (key === 'relatedTarget') | |
3003 v = unwrap(v); | |
3004 args.push(v); | |
3005 }); | |
3006 event['init' + name].apply(event, args); | |
3007 return event; | |
3008 } | |
3009 | |
3010 if (!supportsEventConstructors) { | |
3011 var configureEventConstructor = function(name, initDict, superName) { | |
3012 if (superName) { | |
3013 var superDict = defaultInitDicts[superName]; | |
3014 initDict = mixin(mixin({}, superDict), initDict); | |
3015 } | |
3016 | |
3017 defaultInitDicts[name] = initDict; | |
3018 }; | |
3019 | |
3020 // The order of the default event init dictionary keys is important, the | |
3021 // arguments to initFooEvent is derived from that. | |
3022 configureEventConstructor('Event', {bubbles: false, cancelable: false}); | |
3023 configureEventConstructor('CustomEvent', {detail: null}, 'Event'); | |
3024 configureEventConstructor('UIEvent', {view: null, detail: 0}, 'Event'); | |
3025 configureEventConstructor('MouseEvent', { | |
3026 screenX: 0, | |
3027 screenY: 0, | |
3028 clientX: 0, | |
3029 clientY: 0, | |
3030 ctrlKey: false, | |
3031 altKey: false, | |
3032 shiftKey: false, | |
3033 metaKey: false, | |
3034 button: 0, | |
3035 relatedTarget: null | |
3036 }, 'UIEvent'); | |
3037 configureEventConstructor('FocusEvent', {relatedTarget: null}, 'UIEvent'); | |
3038 } | |
3039 | |
3040 function BeforeUnloadEvent(impl) { | |
3041 Event.call(this); | |
3042 } | |
3043 BeforeUnloadEvent.prototype = Object.create(Event.prototype); | |
3044 mixin(BeforeUnloadEvent.prototype, { | |
3045 get returnValue() { | |
3046 return this.impl.returnValue; | |
3047 }, | |
3048 set returnValue(v) { | |
3049 this.impl.returnValue = v; | |
3050 } | |
3051 }); | |
3052 | |
3053 function isValidListener(fun) { | |
3054 if (typeof fun === 'function') | |
3055 return true; | |
3056 return fun && fun.handleEvent; | |
3057 } | |
3058 | |
3059 function isMutationEvent(type) { | |
3060 switch (type) { | |
3061 case 'DOMAttrModified': | |
3062 case 'DOMAttributeNameChanged': | |
3063 case 'DOMCharacterDataModified': | |
3064 case 'DOMElementNameChanged': | |
3065 case 'DOMNodeInserted': | |
3066 case 'DOMNodeInsertedIntoDocument': | |
3067 case 'DOMNodeRemoved': | |
3068 case 'DOMNodeRemovedFromDocument': | |
3069 case 'DOMSubtreeModified': | |
3070 return true; | |
3071 } | |
3072 return false; | |
3073 } | |
3074 | |
3075 var OriginalEventTarget = window.EventTarget; | |
3076 | |
3077 /** | |
3078 * This represents a wrapper for an EventTarget. | |
3079 * @param {!EventTarget} impl The original event target. | |
3080 * @constructor | |
3081 */ | |
3082 function EventTarget(impl) { | |
3083 this.impl = impl; | |
3084 } | |
3085 | |
3086 // Node and Window have different internal type checks in WebKit so we cannot | |
3087 // use the same method as the original function. | |
3088 var methodNames = [ | |
3089 'addEventListener', | |
3090 'removeEventListener', | |
3091 'dispatchEvent' | |
3092 ]; | |
3093 | |
3094 [Node, Window].forEach(function(constructor) { | |
3095 var p = constructor.prototype; | |
3096 methodNames.forEach(function(name) { | |
3097 Object.defineProperty(p, name + '_', {value: p[name]}); | |
3098 }); | |
3099 }); | |
3100 | |
3101 function getTargetToListenAt(wrapper) { | |
3102 if (wrapper instanceof wrappers.ShadowRoot) | |
3103 wrapper = wrapper.host; | |
3104 return unwrap(wrapper); | |
3105 } | |
3106 | |
3107 EventTarget.prototype = { | |
3108 addEventListener: function(type, fun, capture) { | |
3109 if (!isValidListener(fun) || isMutationEvent(type)) | |
3110 return; | |
3111 | |
3112 var listener = new Listener(type, fun, capture); | |
3113 var listeners = listenersTable.get(this); | |
3114 if (!listeners) { | |
3115 listeners = []; | |
3116 listenersTable.set(this, listeners); | |
3117 } else { | |
3118 // Might have a duplicate. | |
3119 for (var i = 0; i < listeners.length; i++) { | |
3120 if (listener.equals(listeners[i])) | |
3121 return; | |
3122 } | |
3123 } | |
3124 | |
3125 listeners.push(listener); | |
3126 | |
3127 var target = getTargetToListenAt(this); | |
3128 target.addEventListener_(type, dispatchOriginalEvent, true); | |
3129 }, | |
3130 removeEventListener: function(type, fun, capture) { | |
3131 capture = Boolean(capture); | |
3132 var listeners = listenersTable.get(this); | |
3133 if (!listeners) | |
3134 return; | |
3135 var count = 0, found = false; | |
3136 for (var i = 0; i < listeners.length; i++) { | |
3137 if (listeners[i].type === type && listeners[i].capture === capture) { | |
3138 count++; | |
3139 if (listeners[i].handler === fun) { | |
3140 found = true; | |
3141 listeners[i].remove(); | |
3142 } | |
3143 } | |
3144 } | |
3145 | |
3146 if (found && count === 1) { | |
3147 var target = getTargetToListenAt(this); | |
3148 target.removeEventListener_(type, dispatchOriginalEvent, true); | |
3149 } | |
3150 }, | |
3151 dispatchEvent: function(event) { | |
3152 // We want to use the native dispatchEvent because it triggers the default | |
3153 // actions (like checking a checkbox). However, if there are no listeners | |
3154 // in the composed tree then there are no events that will trigger and | |
3155 // listeners in the non composed tree that are part of the event path are | |
3156 // not notified. | |
3157 // | |
3158 // If we find out that there are no listeners in the composed tree we add | |
3159 // a temporary listener to the target which makes us get called back even | |
3160 // in that case. | |
3161 | |
3162 var nativeEvent = unwrap(event); | |
3163 var eventType = nativeEvent.type; | |
3164 | |
3165 // Allow dispatching the same event again. This is safe because if user | |
3166 // code calls this during an existing dispatch of the same event the | |
3167 // native dispatchEvent throws (that is required by the spec). | |
3168 handledEventsTable.set(nativeEvent, false); | |
3169 | |
3170 // Force rendering since we prefer native dispatch and that works on the | |
3171 // composed tree. | |
3172 scope.renderAllPending(); | |
3173 | |
3174 var tempListener; | |
3175 if (!hasListenerInAncestors(this, eventType)) { | |
3176 tempListener = function() {}; | |
3177 this.addEventListener(eventType, tempListener, true); | |
3178 } | |
3179 | |
3180 try { | |
3181 return unwrap(this).dispatchEvent_(nativeEvent); | |
3182 } finally { | |
3183 if (tempListener) | |
3184 this.removeEventListener(eventType, tempListener, true); | |
3185 } | |
3186 } | |
3187 }; | |
3188 | |
3189 function hasListener(node, type) { | |
3190 var listeners = listenersTable.get(node); | |
3191 if (listeners) { | |
3192 for (var i = 0; i < listeners.length; i++) { | |
3193 if (!listeners[i].removed && listeners[i].type === type) | |
3194 return true; | |
3195 } | |
3196 } | |
3197 return false; | |
3198 } | |
3199 | |
3200 function hasListenerInAncestors(target, type) { | |
3201 for (var node = unwrap(target); node; node = node.parentNode) { | |
3202 if (hasListener(wrap(node), type)) | |
3203 return true; | |
3204 } | |
3205 return false; | |
3206 } | |
3207 | |
3208 if (OriginalEventTarget) | |
3209 registerWrapper(OriginalEventTarget, EventTarget); | |
3210 | |
3211 function wrapEventTargetMethods(constructors) { | |
3212 forwardMethodsToWrapper(constructors, methodNames); | |
3213 } | |
3214 | |
3215 var originalElementFromPoint = document.elementFromPoint; | |
3216 | |
3217 function elementFromPoint(self, document, x, y) { | |
3218 scope.renderAllPending(); | |
3219 | |
3220 var element = wrap(originalElementFromPoint.call(document.impl, x, y)); | |
3221 var targets = retarget(element, this) | |
3222 for (var i = 0; i < targets.length; i++) { | |
3223 var target = targets[i]; | |
3224 if (target.currentTarget === self) | |
3225 return target.target; | |
3226 } | |
3227 return null; | |
3228 } | |
3229 | |
3230 /** | |
3231 * Returns a function that is to be used as a getter for `onfoo` properties. | |
3232 * @param {string} name | |
3233 * @return {Function} | |
3234 */ | |
3235 function getEventHandlerGetter(name) { | |
3236 return function() { | |
3237 var inlineEventHandlers = eventHandlersTable.get(this); | |
3238 return inlineEventHandlers && inlineEventHandlers[name] && | |
3239 inlineEventHandlers[name].value || null; | |
3240 }; | |
3241 } | |
3242 | |
3243 /** | |
3244 * Returns a function that is to be used as a setter for `onfoo` properties. | |
3245 * @param {string} name | |
3246 * @return {Function} | |
3247 */ | |
3248 function getEventHandlerSetter(name) { | |
3249 var eventType = name.slice(2); | |
3250 return function(value) { | |
3251 var inlineEventHandlers = eventHandlersTable.get(this); | |
3252 if (!inlineEventHandlers) { | |
3253 inlineEventHandlers = Object.create(null); | |
3254 eventHandlersTable.set(this, inlineEventHandlers); | |
3255 } | |
3256 | |
3257 var old = inlineEventHandlers[name]; | |
3258 if (old) | |
3259 this.removeEventListener(eventType, old.wrapped, false); | |
3260 | |
3261 if (typeof value === 'function') { | |
3262 var wrapped = function(e) { | |
3263 var rv = value.call(this, e); | |
3264 if (rv === false) | |
3265 e.preventDefault(); | |
3266 else if (name === 'onbeforeunload' && typeof rv === 'string') | |
3267 e.returnValue = rv; | |
3268 // mouseover uses true for preventDefault but preventDefault for | |
3269 // mouseover is ignored by browsers these day. | |
3270 }; | |
3271 | |
3272 this.addEventListener(eventType, wrapped, false); | |
3273 inlineEventHandlers[name] = { | |
3274 value: value, | |
3275 wrapped: wrapped | |
3276 }; | |
3277 } | |
3278 }; | |
3279 } | |
3280 | |
3281 scope.adjustRelatedTarget = adjustRelatedTarget; | |
3282 scope.elementFromPoint = elementFromPoint; | |
3283 scope.getEventHandlerGetter = getEventHandlerGetter; | |
3284 scope.getEventHandlerSetter = getEventHandlerSetter; | |
3285 scope.wrapEventTargetMethods = wrapEventTargetMethods; | |
3286 scope.wrappers.BeforeUnloadEvent = BeforeUnloadEvent; | |
3287 scope.wrappers.CustomEvent = CustomEvent; | |
3288 scope.wrappers.Event = Event; | |
3289 scope.wrappers.EventTarget = EventTarget; | |
3290 scope.wrappers.FocusEvent = FocusEvent; | |
3291 scope.wrappers.MouseEvent = MouseEvent; | |
3292 scope.wrappers.UIEvent = UIEvent; | |
3293 | |
3294 })(window.ShadowDOMPolyfill); | |
3295 | |
3296 // Copyright 2012 The Polymer Authors. All rights reserved. | |
3297 // Use of this source code is goverened by a BSD-style | |
3298 // license that can be found in the LICENSE file. | |
3299 | |
3300 (function(scope) { | |
3301 'use strict'; | |
3302 | |
3303 var wrap = scope.wrap; | |
3304 | |
3305 function nonEnum(obj, prop) { | |
3306 Object.defineProperty(obj, prop, {enumerable: false}); | |
3307 } | |
3308 | |
3309 function NodeList() { | |
3310 this.length = 0; | |
3311 nonEnum(this, 'length'); | |
3312 } | |
3313 NodeList.prototype = { | |
3314 item: function(index) { | |
3315 return this[index]; | |
3316 } | |
3317 }; | |
3318 nonEnum(NodeList.prototype, 'item'); | |
3319 | |
3320 function wrapNodeList(list) { | |
3321 if (list == null) | |
3322 return list; | |
3323 var wrapperList = new NodeList(); | |
3324 for (var i = 0, length = list.length; i < length; i++) { | |
3325 wrapperList[i] = wrap(list[i]); | |
3326 } | |
3327 wrapperList.length = length; | |
3328 return wrapperList; | |
3329 } | |
3330 | |
3331 function addWrapNodeListMethod(wrapperConstructor, name) { | |
3332 wrapperConstructor.prototype[name] = function() { | |
3333 return wrapNodeList(this.impl[name].apply(this.impl, arguments)); | |
3334 }; | |
3335 } | |
3336 | |
3337 scope.wrappers.NodeList = NodeList; | |
3338 scope.addWrapNodeListMethod = addWrapNodeListMethod; | |
3339 scope.wrapNodeList = wrapNodeList; | |
3340 | |
3341 })(window.ShadowDOMPolyfill); | |
3342 | |
3343 // Copyright 2012 The Polymer Authors. All rights reserved. | |
3344 // Use of this source code is goverened by a BSD-style | |
3345 // license that can be found in the LICENSE file. | |
3346 | |
3347 (function(scope) { | |
3348 'use strict'; | |
3349 | |
3350 var EventTarget = scope.wrappers.EventTarget; | |
3351 var NodeList = scope.wrappers.NodeList; | |
3352 var assert = scope.assert; | |
3353 var defineWrapGetter = scope.defineWrapGetter; | |
3354 var enqueueMutation = scope.enqueueMutation; | |
3355 var isWrapper = scope.isWrapper; | |
3356 var mixin = scope.mixin; | |
3357 var registerTransientObservers = scope.registerTransientObservers; | |
3358 var registerWrapper = scope.registerWrapper; | |
3359 var unwrap = scope.unwrap; | |
3360 var wrap = scope.wrap; | |
3361 var wrapIfNeeded = scope.wrapIfNeeded; | |
3362 | |
3363 function assertIsNodeWrapper(node) { | |
3364 assert(node instanceof Node); | |
3365 } | |
3366 | |
3367 function createOneElementNodeList(node) { | |
3368 var nodes = new NodeList(); | |
3369 nodes[0] = node; | |
3370 nodes.length = 1; | |
3371 return nodes; | |
3372 } | |
3373 | |
3374 var surpressMutations = false; | |
3375 | |
3376 /** | |
3377 * Called before node is inserted into a node to enqueue its removal from its | |
3378 * old parent. | |
3379 * @param {!Node} node The node that is about to be removed. | |
3380 * @param {!Node} parent The parent node that the node is being removed from. | |
3381 * @param {!NodeList} nodes The collected nodes. | |
3382 */ | |
3383 function enqueueRemovalForInsertedNodes(node, parent, nodes) { | |
3384 enqueueMutation(parent, 'childList', { | |
3385 removedNodes: nodes, | |
3386 previousSibling: node.previousSibling, | |
3387 nextSibling: node.nextSibling | |
3388 }); | |
3389 } | |
3390 | |
3391 function enqueueRemovalForInsertedDocumentFragment(df, nodes) { | |
3392 enqueueMutation(df, 'childList', { | |
3393 removedNodes: nodes | |
3394 }); | |
3395 } | |
3396 | |
3397 /** | |
3398 * Collects nodes from a DocumentFragment or a Node for removal followed | |
3399 * by an insertion. | |
3400 * | |
3401 * This updates the internal pointers for node, previousNode and nextNode. | |
3402 */ | |
3403 function collectNodes(node, parentNode, previousNode, nextNode) { | |
3404 if (node instanceof DocumentFragment) { | |
3405 var nodes = collectNodesForDocumentFragment(node); | |
3406 | |
3407 // The extra loop is to work around bugs with DocumentFragments in IE. | |
3408 surpressMutations = true; | |
3409 for (var i = nodes.length - 1; i >= 0; i--) { | |
3410 node.removeChild(nodes[i]); | |
3411 nodes[i].parentNode_ = parentNode; | |
3412 } | |
3413 surpressMutations = false; | |
3414 | |
3415 for (var i = 0; i < nodes.length; i++) { | |
3416 nodes[i].previousSibling_ = nodes[i - 1] || previousNode; | |
3417 nodes[i].nextSibling_ = nodes[i + 1] || nextNode; | |
3418 } | |
3419 | |
3420 if (previousNode) | |
3421 previousNode.nextSibling_ = nodes[0]; | |
3422 if (nextNode) | |
3423 nextNode.previousSibling_ = nodes[nodes.length - 1]; | |
3424 | |
3425 return nodes; | |
3426 } | |
3427 | |
3428 var nodes = createOneElementNodeList(node); | |
3429 var oldParent = node.parentNode; | |
3430 if (oldParent) { | |
3431 // This will enqueue the mutation record for the removal as needed. | |
3432 oldParent.removeChild(node); | |
3433 } | |
3434 | |
3435 node.parentNode_ = parentNode; | |
3436 node.previousSibling_ = previousNode; | |
3437 node.nextSibling_ = nextNode; | |
3438 if (previousNode) | |
3439 previousNode.nextSibling_ = node; | |
3440 if (nextNode) | |
3441 nextNode.previousSibling_ = node; | |
3442 | |
3443 return nodes; | |
3444 } | |
3445 | |
3446 function collectNodesNative(node) { | |
3447 if (node instanceof DocumentFragment) | |
3448 return collectNodesForDocumentFragment(node); | |
3449 | |
3450 var nodes = createOneElementNodeList(node); | |
3451 var oldParent = node.parentNode; | |
3452 if (oldParent) | |
3453 enqueueRemovalForInsertedNodes(node, oldParent, nodes); | |
3454 return nodes; | |
3455 } | |
3456 | |
3457 function collectNodesForDocumentFragment(node) { | |
3458 var nodes = new NodeList(); | |
3459 var i = 0; | |
3460 for (var child = node.firstChild; child; child = child.nextSibling) { | |
3461 nodes[i++] = child; | |
3462 } | |
3463 nodes.length = i; | |
3464 enqueueRemovalForInsertedDocumentFragment(node, nodes); | |
3465 return nodes; | |
3466 } | |
3467 | |
3468 function snapshotNodeList(nodeList) { | |
3469 // NodeLists are not live at the moment so just return the same object. | |
3470 return nodeList; | |
3471 } | |
3472 | |
3473 // http://dom.spec.whatwg.org/#node-is-inserted | |
3474 function nodeWasAdded(node) { | |
3475 node.nodeIsInserted_(); | |
3476 } | |
3477 | |
3478 function nodesWereAdded(nodes) { | |
3479 for (var i = 0; i < nodes.length; i++) { | |
3480 nodeWasAdded(nodes[i]); | |
3481 } | |
3482 } | |
3483 | |
3484 // http://dom.spec.whatwg.org/#node-is-removed | |
3485 function nodeWasRemoved(node) { | |
3486 // Nothing at this point in time. | |
3487 } | |
3488 | |
3489 function nodesWereRemoved(nodes) { | |
3490 // Nothing at this point in time. | |
3491 } | |
3492 | |
3493 function ensureSameOwnerDocument(parent, child) { | |
3494 var ownerDoc = parent.nodeType === Node.DOCUMENT_NODE ? | |
3495 parent : parent.ownerDocument; | |
3496 if (ownerDoc !== child.ownerDocument) | |
3497 ownerDoc.adoptNode(child); | |
3498 } | |
3499 | |
3500 function adoptNodesIfNeeded(owner, nodes) { | |
3501 if (!nodes.length) | |
3502 return; | |
3503 | |
3504 var ownerDoc = owner.ownerDocument; | |
3505 | |
3506 // All nodes have the same ownerDocument when we get here. | |
3507 if (ownerDoc === nodes[0].ownerDocument) | |
3508 return; | |
3509 | |
3510 for (var i = 0; i < nodes.length; i++) { | |
3511 scope.adoptNodeNoRemove(nodes[i], ownerDoc); | |
3512 } | |
3513 } | |
3514 | |
3515 function unwrapNodesForInsertion(owner, nodes) { | |
3516 adoptNodesIfNeeded(owner, nodes); | |
3517 var length = nodes.length; | |
3518 | |
3519 if (length === 1) | |
3520 return unwrap(nodes[0]); | |
3521 | |
3522 var df = unwrap(owner.ownerDocument.createDocumentFragment()); | |
3523 for (var i = 0; i < length; i++) { | |
3524 df.appendChild(unwrap(nodes[i])); | |
3525 } | |
3526 return df; | |
3527 } | |
3528 | |
3529 function clearChildNodes(wrapper) { | |
3530 if (wrapper.firstChild_ !== undefined) { | |
3531 var child = wrapper.firstChild_; | |
3532 while (child) { | |
3533 var tmp = child; | |
3534 child = child.nextSibling_; | |
3535 tmp.parentNode_ = tmp.previousSibling_ = tmp.nextSibling_ = undefined; | |
3536 } | |
3537 } | |
3538 wrapper.firstChild_ = wrapper.lastChild_ = undefined; | |
3539 } | |
3540 | |
3541 function removeAllChildNodes(wrapper) { | |
3542 if (wrapper.invalidateShadowRenderer()) { | |
3543 var childWrapper = wrapper.firstChild; | |
3544 while (childWrapper) { | |
3545 assert(childWrapper.parentNode === wrapper); | |
3546 var nextSibling = childWrapper.nextSibling; | |
3547 var childNode = unwrap(childWrapper); | |
3548 var parentNode = childNode.parentNode; | |
3549 if (parentNode) | |
3550 originalRemoveChild.call(parentNode, childNode); | |
3551 childWrapper.previousSibling_ = childWrapper.nextSibling_ = | |
3552 childWrapper.parentNode_ = null; | |
3553 childWrapper = nextSibling; | |
3554 } | |
3555 wrapper.firstChild_ = wrapper.lastChild_ = null; | |
3556 } else { | |
3557 var node = unwrap(wrapper); | |
3558 var child = node.firstChild; | |
3559 var nextSibling; | |
3560 while (child) { | |
3561 nextSibling = child.nextSibling; | |
3562 originalRemoveChild.call(node, child); | |
3563 child = nextSibling; | |
3564 } | |
3565 } | |
3566 } | |
3567 | |
3568 function invalidateParent(node) { | |
3569 var p = node.parentNode; | |
3570 return p && p.invalidateShadowRenderer(); | |
3571 } | |
3572 | |
3573 function cleanupNodes(nodes) { | |
3574 for (var i = 0, n; i < nodes.length; i++) { | |
3575 n = nodes[i]; | |
3576 n.parentNode.removeChild(n); | |
3577 } | |
3578 } | |
3579 | |
3580 var OriginalNode = window.Node; | |
3581 | |
3582 /** | |
3583 * This represents a wrapper of a native DOM node. | |
3584 * @param {!Node} original The original DOM node, aka, the visual DOM node. | |
3585 * @constructor | |
3586 * @extends {EventTarget} | |
3587 */ | |
3588 function Node(original) { | |
3589 assert(original instanceof OriginalNode); | |
3590 | |
3591 EventTarget.call(this, original); | |
3592 | |
3593 // These properties are used to override the visual references with the | |
3594 // logical ones. If the value is undefined it means that the logical is the | |
3595 // same as the visual. | |
3596 | |
3597 /** | |
3598 * @type {Node|undefined} | |
3599 * @private | |
3600 */ | |
3601 this.parentNode_ = undefined; | |
3602 | |
3603 /** | |
3604 * @type {Node|undefined} | |
3605 * @private | |
3606 */ | |
3607 this.firstChild_ = undefined; | |
3608 | |
3609 /** | |
3610 * @type {Node|undefined} | |
3611 * @private | |
3612 */ | |
3613 this.lastChild_ = undefined; | |
3614 | |
3615 /** | |
3616 * @type {Node|undefined} | |
3617 * @private | |
3618 */ | |
3619 this.nextSibling_ = undefined; | |
3620 | |
3621 /** | |
3622 * @type {Node|undefined} | |
3623 * @private | |
3624 */ | |
3625 this.previousSibling_ = undefined; | |
3626 } | |
3627 | |
3628 var OriginalDocumentFragment = window.DocumentFragment; | |
3629 var originalAppendChild = OriginalNode.prototype.appendChild; | |
3630 var originalCompareDocumentPosition = | |
3631 OriginalNode.prototype.compareDocumentPosition; | |
3632 var originalInsertBefore = OriginalNode.prototype.insertBefore; | |
3633 var originalRemoveChild = OriginalNode.prototype.removeChild; | |
3634 var originalReplaceChild = OriginalNode.prototype.replaceChild; | |
3635 | |
3636 var isIe = /Trident/.test(navigator.userAgent); | |
3637 | |
3638 var removeChildOriginalHelper = isIe ? | |
3639 function(parent, child) { | |
3640 try { | |
3641 originalRemoveChild.call(parent, child); | |
3642 } catch (ex) { | |
3643 if (!(parent instanceof OriginalDocumentFragment)) | |
3644 throw ex; | |
3645 } | |
3646 } : | |
3647 function(parent, child) { | |
3648 originalRemoveChild.call(parent, child); | |
3649 }; | |
3650 | |
3651 Node.prototype = Object.create(EventTarget.prototype); | |
3652 mixin(Node.prototype, { | |
3653 appendChild: function(childWrapper) { | |
3654 return this.insertBefore(childWrapper, null); | |
3655 }, | |
3656 | |
3657 insertBefore: function(childWrapper, refWrapper) { | |
3658 assertIsNodeWrapper(childWrapper); | |
3659 | |
3660 var refNode; | |
3661 if (refWrapper) { | |
3662 if (isWrapper(refWrapper)) { | |
3663 refNode = unwrap(refWrapper); | |
3664 } else { | |
3665 refNode = refWrapper; | |
3666 refWrapper = wrap(refNode); | |
3667 } | |
3668 } else { | |
3669 refWrapper = null; | |
3670 refNode = null; | |
3671 } | |
3672 | |
3673 refWrapper && assert(refWrapper.parentNode === this); | |
3674 | |
3675 var nodes; | |
3676 var previousNode = | |
3677 refWrapper ? refWrapper.previousSibling : this.lastChild; | |
3678 | |
3679 var useNative = !this.invalidateShadowRenderer() && | |
3680 !invalidateParent(childWrapper); | |
3681 | |
3682 if (useNative) | |
3683 nodes = collectNodesNative(childWrapper); | |
3684 else | |
3685 nodes = collectNodes(childWrapper, this, previousNode, refWrapper); | |
3686 | |
3687 if (useNative) { | |
3688 ensureSameOwnerDocument(this, childWrapper); | |
3689 clearChildNodes(this); | |
3690 originalInsertBefore.call(this.impl, unwrap(childWrapper), refNode); | |
3691 } else { | |
3692 if (!previousNode) | |
3693 this.firstChild_ = nodes[0]; | |
3694 if (!refWrapper) | |
3695 this.lastChild_ = nodes[nodes.length - 1]; | |
3696 | |
3697 var parentNode = refNode ? refNode.parentNode : this.impl; | |
3698 | |
3699 // insertBefore refWrapper no matter what the parent is? | |
3700 if (parentNode) { | |
3701 originalInsertBefore.call(parentNode, | |
3702 unwrapNodesForInsertion(this, nodes), refNode); | |
3703 } else { | |
3704 adoptNodesIfNeeded(this, nodes); | |
3705 } | |
3706 } | |
3707 | |
3708 enqueueMutation(this, 'childList', { | |
3709 addedNodes: nodes, | |
3710 nextSibling: refWrapper, | |
3711 previousSibling: previousNode | |
3712 }); | |
3713 | |
3714 nodesWereAdded(nodes); | |
3715 | |
3716 return childWrapper; | |
3717 }, | |
3718 | |
3719 removeChild: function(childWrapper) { | |
3720 assertIsNodeWrapper(childWrapper); | |
3721 if (childWrapper.parentNode !== this) { | |
3722 // IE has invalid DOM trees at times. | |
3723 var found = false; | |
3724 var childNodes = this.childNodes; | |
3725 for (var ieChild = this.firstChild; ieChild; | |
3726 ieChild = ieChild.nextSibling) { | |
3727 if (ieChild === childWrapper) { | |
3728 found = true; | |
3729 break; | |
3730 } | |
3731 } | |
3732 if (!found) { | |
3733 // TODO(arv): DOMException | |
3734 throw new Error('NotFoundError'); | |
3735 } | |
3736 } | |
3737 | |
3738 var childNode = unwrap(childWrapper); | |
3739 var childWrapperNextSibling = childWrapper.nextSibling; | |
3740 var childWrapperPreviousSibling = childWrapper.previousSibling; | |
3741 | |
3742 if (this.invalidateShadowRenderer()) { | |
3743 // We need to remove the real node from the DOM before updating the | |
3744 // pointers. This is so that that mutation event is dispatched before | |
3745 // the pointers have changed. | |
3746 var thisFirstChild = this.firstChild; | |
3747 var thisLastChild = this.lastChild; | |
3748 | |
3749 var parentNode = childNode.parentNode; | |
3750 if (parentNode) | |
3751 removeChildOriginalHelper(parentNode, childNode); | |
3752 | |
3753 if (thisFirstChild === childWrapper) | |
3754 this.firstChild_ = childWrapperNextSibling; | |
3755 if (thisLastChild === childWrapper) | |
3756 this.lastChild_ = childWrapperPreviousSibling; | |
3757 if (childWrapperPreviousSibling) | |
3758 childWrapperPreviousSibling.nextSibling_ = childWrapperNextSibling; | |
3759 if (childWrapperNextSibling) { | |
3760 childWrapperNextSibling.previousSibling_ = | |
3761 childWrapperPreviousSibling; | |
3762 } | |
3763 | |
3764 childWrapper.previousSibling_ = childWrapper.nextSibling_ = | |
3765 childWrapper.parentNode_ = undefined; | |
3766 } else { | |
3767 clearChildNodes(this); | |
3768 removeChildOriginalHelper(this.impl, childNode); | |
3769 } | |
3770 | |
3771 if (!surpressMutations) { | |
3772 enqueueMutation(this, 'childList', { | |
3773 removedNodes: createOneElementNodeList(childWrapper), | |
3774 nextSibling: childWrapperNextSibling, | |
3775 previousSibling: childWrapperPreviousSibling | |
3776 }); | |
3777 } | |
3778 | |
3779 registerTransientObservers(this, childWrapper); | |
3780 | |
3781 return childWrapper; | |
3782 }, | |
3783 | |
3784 replaceChild: function(newChildWrapper, oldChildWrapper) { | |
3785 assertIsNodeWrapper(newChildWrapper); | |
3786 | |
3787 var oldChildNode; | |
3788 if (isWrapper(oldChildWrapper)) { | |
3789 oldChildNode = unwrap(oldChildWrapper); | |
3790 } else { | |
3791 oldChildNode = oldChildWrapper; | |
3792 oldChildWrapper = wrap(oldChildNode); | |
3793 } | |
3794 | |
3795 if (oldChildWrapper.parentNode !== this) { | |
3796 // TODO(arv): DOMException | |
3797 throw new Error('NotFoundError'); | |
3798 } | |
3799 | |
3800 var nextNode = oldChildWrapper.nextSibling; | |
3801 var previousNode = oldChildWrapper.previousSibling; | |
3802 var nodes; | |
3803 | |
3804 var useNative = !this.invalidateShadowRenderer() && | |
3805 !invalidateParent(newChildWrapper); | |
3806 | |
3807 if (useNative) { | |
3808 nodes = collectNodesNative(newChildWrapper); | |
3809 } else { | |
3810 if (nextNode === newChildWrapper) | |
3811 nextNode = newChildWrapper.nextSibling; | |
3812 nodes = collectNodes(newChildWrapper, this, previousNode, nextNode); | |
3813 } | |
3814 | |
3815 if (!useNative) { | |
3816 if (this.firstChild === oldChildWrapper) | |
3817 this.firstChild_ = nodes[0]; | |
3818 if (this.lastChild === oldChildWrapper) | |
3819 this.lastChild_ = nodes[nodes.length - 1]; | |
3820 | |
3821 oldChildWrapper.previousSibling_ = oldChildWrapper.nextSibling_ = | |
3822 oldChildWrapper.parentNode_ = undefined; | |
3823 | |
3824 // replaceChild no matter what the parent is? | |
3825 if (oldChildNode.parentNode) { | |
3826 originalReplaceChild.call( | |
3827 oldChildNode.parentNode, | |
3828 unwrapNodesForInsertion(this, nodes), | |
3829 oldChildNode); | |
3830 } | |
3831 } else { | |
3832 ensureSameOwnerDocument(this, newChildWrapper); | |
3833 clearChildNodes(this); | |
3834 originalReplaceChild.call(this.impl, unwrap(newChildWrapper), | |
3835 oldChildNode); | |
3836 } | |
3837 | |
3838 enqueueMutation(this, 'childList', { | |
3839 addedNodes: nodes, | |
3840 removedNodes: createOneElementNodeList(oldChildWrapper), | |
3841 nextSibling: nextNode, | |
3842 previousSibling: previousNode | |
3843 }); | |
3844 | |
3845 nodeWasRemoved(oldChildWrapper); | |
3846 nodesWereAdded(nodes); | |
3847 | |
3848 return oldChildWrapper; | |
3849 }, | |
3850 | |
3851 /** | |
3852 * Called after a node was inserted. Subclasses override this to invalidate | |
3853 * the renderer as needed. | |
3854 * @private | |
3855 */ | |
3856 nodeIsInserted_: function() { | |
3857 for (var child = this.firstChild; child; child = child.nextSibling) { | |
3858 child.nodeIsInserted_(); | |
3859 } | |
3860 }, | |
3861 | |
3862 hasChildNodes: function() { | |
3863 return this.firstChild !== null; | |
3864 }, | |
3865 | |
3866 /** @type {Node} */ | |
3867 get parentNode() { | |
3868 // If the parentNode has not been overridden, use the original parentNode. | |
3869 return this.parentNode_ !== undefined ? | |
3870 this.parentNode_ : wrap(this.impl.parentNode); | |
3871 }, | |
3872 | |
3873 /** @type {Node} */ | |
3874 get firstChild() { | |
3875 return this.firstChild_ !== undefined ? | |
3876 this.firstChild_ : wrap(this.impl.firstChild); | |
3877 }, | |
3878 | |
3879 /** @type {Node} */ | |
3880 get lastChild() { | |
3881 return this.lastChild_ !== undefined ? | |
3882 this.lastChild_ : wrap(this.impl.lastChild); | |
3883 }, | |
3884 | |
3885 /** @type {Node} */ | |
3886 get nextSibling() { | |
3887 return this.nextSibling_ !== undefined ? | |
3888 this.nextSibling_ : wrap(this.impl.nextSibling); | |
3889 }, | |
3890 | |
3891 /** @type {Node} */ | |
3892 get previousSibling() { | |
3893 return this.previousSibling_ !== undefined ? | |
3894 this.previousSibling_ : wrap(this.impl.previousSibling); | |
3895 }, | |
3896 | |
3897 get parentElement() { | |
3898 var p = this.parentNode; | |
3899 while (p && p.nodeType !== Node.ELEMENT_NODE) { | |
3900 p = p.parentNode; | |
3901 } | |
3902 return p; | |
3903 }, | |
3904 | |
3905 get textContent() { | |
3906 // TODO(arv): This should fallback to this.impl.textContent if there | |
3907 // are no shadow trees below or above the context node. | |
3908 var s = ''; | |
3909 for (var child = this.firstChild; child; child = child.nextSibling) { | |
3910 if (child.nodeType != Node.COMMENT_NODE) { | |
3911 s += child.textContent; | |
3912 } | |
3913 } | |
3914 return s; | |
3915 }, | |
3916 set textContent(textContent) { | |
3917 var removedNodes = snapshotNodeList(this.childNodes); | |
3918 | |
3919 if (this.invalidateShadowRenderer()) { | |
3920 removeAllChildNodes(this); | |
3921 if (textContent !== '') { | |
3922 var textNode = this.impl.ownerDocument.createTextNode(textContent); | |
3923 this.appendChild(textNode); | |
3924 } | |
3925 } else { | |
3926 clearChildNodes(this); | |
3927 this.impl.textContent = textContent; | |
3928 } | |
3929 | |
3930 var addedNodes = snapshotNodeList(this.childNodes); | |
3931 | |
3932 enqueueMutation(this, 'childList', { | |
3933 addedNodes: addedNodes, | |
3934 removedNodes: removedNodes | |
3935 }); | |
3936 | |
3937 nodesWereRemoved(removedNodes); | |
3938 nodesWereAdded(addedNodes); | |
3939 }, | |
3940 | |
3941 get childNodes() { | |
3942 var wrapperList = new NodeList(); | |
3943 var i = 0; | |
3944 for (var child = this.firstChild; child; child = child.nextSibling) { | |
3945 wrapperList[i++] = child; | |
3946 } | |
3947 wrapperList.length = i; | |
3948 return wrapperList; | |
3949 }, | |
3950 | |
3951 cloneNode: function(deep) { | |
3952 var clone = wrap(this.impl.cloneNode(false)); | |
3953 if (deep) { | |
3954 for (var child = this.firstChild; child; child = child.nextSibling) { | |
3955 clone.appendChild(child.cloneNode(true)); | |
3956 } | |
3957 } | |
3958 // TODO(arv): Some HTML elements also clone other data like value. | |
3959 return clone; | |
3960 }, | |
3961 | |
3962 contains: function(child) { | |
3963 if (!child) | |
3964 return false; | |
3965 | |
3966 child = wrapIfNeeded(child); | |
3967 | |
3968 // TODO(arv): Optimize using ownerDocument etc. | |
3969 if (child === this) | |
3970 return true; | |
3971 var parentNode = child.parentNode; | |
3972 if (!parentNode) | |
3973 return false; | |
3974 return this.contains(parentNode); | |
3975 }, | |
3976 | |
3977 compareDocumentPosition: function(otherNode) { | |
3978 // This only wraps, it therefore only operates on the composed DOM and not | |
3979 // the logical DOM. | |
3980 return originalCompareDocumentPosition.call(this.impl, unwrap(otherNode)); | |
3981 }, | |
3982 | |
3983 normalize: function() { | |
3984 var nodes = snapshotNodeList(this.childNodes); | |
3985 var remNodes = []; | |
3986 var s = ''; | |
3987 var modNode; | |
3988 | |
3989 for (var i = 0, n; i < nodes.length; i++) { | |
3990 n = nodes[i]; | |
3991 if (n.nodeType === Node.TEXT_NODE) { | |
3992 if (!modNode && !n.data.length) | |
3993 this.removeNode(n); | |
3994 else if (!modNode) | |
3995 modNode = n; | |
3996 else { | |
3997 s += n.data; | |
3998 remNodes.push(n); | |
3999 } | |
4000 } else { | |
4001 if (modNode && remNodes.length) { | |
4002 modNode.data += s; | |
4003 cleanUpNodes(remNodes); | |
4004 } | |
4005 remNodes = []; | |
4006 s = ''; | |
4007 modNode = null; | |
4008 if (n.childNodes.length) | |
4009 n.normalize(); | |
4010 } | |
4011 } | |
4012 | |
4013 // handle case where >1 text nodes are the last children | |
4014 if (modNode && remNodes.length) { | |
4015 modNode.data += s; | |
4016 cleanupNodes(remNodes); | |
4017 } | |
4018 } | |
4019 }); | |
4020 | |
4021 defineWrapGetter(Node, 'ownerDocument'); | |
4022 | |
4023 // We use a DocumentFragment as a base and then delete the properties of | |
4024 // DocumentFragment.prototype from the wrapper Node. Since delete makes | |
4025 // objects slow in some JS engines we recreate the prototype object. | |
4026 registerWrapper(OriginalNode, Node, document.createDocumentFragment()); | |
4027 delete Node.prototype.querySelector; | |
4028 delete Node.prototype.querySelectorAll; | |
4029 Node.prototype = mixin(Object.create(EventTarget.prototype), Node.prototype); | |
4030 | |
4031 scope.nodeWasAdded = nodeWasAdded; | |
4032 scope.nodeWasRemoved = nodeWasRemoved; | |
4033 scope.nodesWereAdded = nodesWereAdded; | |
4034 scope.nodesWereRemoved = nodesWereRemoved; | |
4035 scope.snapshotNodeList = snapshotNodeList; | |
4036 scope.wrappers.Node = Node; | |
4037 | |
4038 })(window.ShadowDOMPolyfill); | |
4039 | |
4040 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4041 // Use of this source code is governed by a BSD-style | |
4042 // license that can be found in the LICENSE file. | |
4043 | |
4044 (function(scope) { | |
4045 'use strict'; | |
4046 | |
4047 function findOne(node, selector) { | |
4048 var m, el = node.firstElementChild; | |
4049 while (el) { | |
4050 if (el.matches(selector)) | |
4051 return el; | |
4052 m = findOne(el, selector); | |
4053 if (m) | |
4054 return m; | |
4055 el = el.nextElementSibling; | |
4056 } | |
4057 return null; | |
4058 } | |
4059 | |
4060 function findAll(node, selector, results) { | |
4061 var el = node.firstElementChild; | |
4062 while (el) { | |
4063 if (el.matches(selector)) | |
4064 results[results.length++] = el; | |
4065 findAll(el, selector, results); | |
4066 el = el.nextElementSibling; | |
4067 } | |
4068 return results; | |
4069 } | |
4070 | |
4071 // find and findAll will only match Simple Selectors, | |
4072 // Structural Pseudo Classes are not guarenteed to be correct | |
4073 // http://www.w3.org/TR/css3-selectors/#simple-selectors | |
4074 | |
4075 var SelectorsInterface = { | |
4076 querySelector: function(selector) { | |
4077 return findOne(this, selector); | |
4078 }, | |
4079 querySelectorAll: function(selector) { | |
4080 return findAll(this, selector, new NodeList()) | |
4081 } | |
4082 }; | |
4083 | |
4084 var GetElementsByInterface = { | |
4085 getElementsByTagName: function(tagName) { | |
4086 // TODO(arv): Check tagName? | |
4087 return this.querySelectorAll(tagName); | |
4088 }, | |
4089 getElementsByClassName: function(className) { | |
4090 // TODO(arv): Check className? | |
4091 return this.querySelectorAll('.' + className); | |
4092 }, | |
4093 getElementsByTagNameNS: function(ns, tagName) { | |
4094 if (ns === '*') | |
4095 return this.getElementsByTagName(tagName); | |
4096 | |
4097 // TODO(arv): Check tagName? | |
4098 var result = new NodeList; | |
4099 var els = this.getElementsByTagName(tagName); | |
4100 for (var i = 0, j = 0; i < els.length; i++) { | |
4101 if (els[i].namespaceURI === ns) | |
4102 result[j++] = els[i]; | |
4103 } | |
4104 result.length = j; | |
4105 return result; | |
4106 } | |
4107 }; | |
4108 | |
4109 scope.GetElementsByInterface = GetElementsByInterface; | |
4110 scope.SelectorsInterface = SelectorsInterface; | |
4111 | |
4112 })(window.ShadowDOMPolyfill); | |
4113 | |
4114 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4115 // Use of this source code is goverened by a BSD-style | |
4116 // license that can be found in the LICENSE file. | |
4117 | |
4118 (function(scope) { | |
4119 'use strict'; | |
4120 | |
4121 var NodeList = scope.wrappers.NodeList; | |
4122 | |
4123 function forwardElement(node) { | |
4124 while (node && node.nodeType !== Node.ELEMENT_NODE) { | |
4125 node = node.nextSibling; | |
4126 } | |
4127 return node; | |
4128 } | |
4129 | |
4130 function backwardsElement(node) { | |
4131 while (node && node.nodeType !== Node.ELEMENT_NODE) { | |
4132 node = node.previousSibling; | |
4133 } | |
4134 return node; | |
4135 } | |
4136 | |
4137 var ParentNodeInterface = { | |
4138 get firstElementChild() { | |
4139 return forwardElement(this.firstChild); | |
4140 }, | |
4141 | |
4142 get lastElementChild() { | |
4143 return backwardsElement(this.lastChild); | |
4144 }, | |
4145 | |
4146 get childElementCount() { | |
4147 var count = 0; | |
4148 for (var child = this.firstElementChild; | |
4149 child; | |
4150 child = child.nextElementSibling) { | |
4151 count++; | |
4152 } | |
4153 return count; | |
4154 }, | |
4155 | |
4156 get children() { | |
4157 var wrapperList = new NodeList(); | |
4158 var i = 0; | |
4159 for (var child = this.firstElementChild; | |
4160 child; | |
4161 child = child.nextElementSibling) { | |
4162 wrapperList[i++] = child; | |
4163 } | |
4164 wrapperList.length = i; | |
4165 return wrapperList; | |
4166 } | |
4167 }; | |
4168 | |
4169 var ChildNodeInterface = { | |
4170 get nextElementSibling() { | |
4171 return forwardElement(this.nextSibling); | |
4172 }, | |
4173 | |
4174 get previousElementSibling() { | |
4175 return backwardsElement(this.previousSibling); | |
4176 } | |
4177 }; | |
4178 | |
4179 scope.ChildNodeInterface = ChildNodeInterface; | |
4180 scope.ParentNodeInterface = ParentNodeInterface; | |
4181 | |
4182 })(window.ShadowDOMPolyfill); | |
4183 | |
4184 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4185 // Use of this source code is goverened by a BSD-style | |
4186 // license that can be found in the LICENSE file. | |
4187 | |
4188 (function(scope) { | |
4189 'use strict'; | |
4190 | |
4191 var ChildNodeInterface = scope.ChildNodeInterface; | |
4192 var Node = scope.wrappers.Node; | |
4193 var enqueueMutation = scope.enqueueMutation; | |
4194 var mixin = scope.mixin; | |
4195 var registerWrapper = scope.registerWrapper; | |
4196 | |
4197 var OriginalCharacterData = window.CharacterData; | |
4198 | |
4199 function CharacterData(node) { | |
4200 Node.call(this, node); | |
4201 } | |
4202 CharacterData.prototype = Object.create(Node.prototype); | |
4203 mixin(CharacterData.prototype, { | |
4204 get textContent() { | |
4205 return this.data; | |
4206 }, | |
4207 set textContent(value) { | |
4208 this.data = value; | |
4209 }, | |
4210 get data() { | |
4211 return this.impl.data; | |
4212 }, | |
4213 set data(value) { | |
4214 var oldValue = this.impl.data; | |
4215 enqueueMutation(this, 'characterData', { | |
4216 oldValue: oldValue | |
4217 }); | |
4218 this.impl.data = value; | |
4219 } | |
4220 }); | |
4221 | |
4222 mixin(CharacterData.prototype, ChildNodeInterface); | |
4223 | |
4224 registerWrapper(OriginalCharacterData, CharacterData, | |
4225 document.createTextNode('')); | |
4226 | |
4227 scope.wrappers.CharacterData = CharacterData; | |
4228 })(window.ShadowDOMPolyfill); | |
4229 | |
4230 // Copyright 2014 The Polymer Authors. All rights reserved. | |
4231 // Use of this source code is goverened by a BSD-style | |
4232 // license that can be found in the LICENSE file. | |
4233 | |
4234 (function(scope) { | |
4235 'use strict'; | |
4236 | |
4237 var CharacterData = scope.wrappers.CharacterData; | |
4238 var enqueueMutation = scope.enqueueMutation; | |
4239 var mixin = scope.mixin; | |
4240 var registerWrapper = scope.registerWrapper; | |
4241 | |
4242 function toUInt32(x) { | |
4243 return x >>> 0; | |
4244 } | |
4245 | |
4246 var OriginalText = window.Text; | |
4247 | |
4248 function Text(node) { | |
4249 CharacterData.call(this, node); | |
4250 } | |
4251 Text.prototype = Object.create(CharacterData.prototype); | |
4252 mixin(Text.prototype, { | |
4253 splitText: function(offset) { | |
4254 offset = toUInt32(offset); | |
4255 var s = this.data; | |
4256 if (offset > s.length) | |
4257 throw new Error('IndexSizeError'); | |
4258 var head = s.slice(0, offset); | |
4259 var tail = s.slice(offset); | |
4260 this.data = head; | |
4261 var newTextNode = this.ownerDocument.createTextNode(tail); | |
4262 if (this.parentNode) | |
4263 this.parentNode.insertBefore(newTextNode, this.nextSibling); | |
4264 return newTextNode; | |
4265 } | |
4266 }); | |
4267 | |
4268 registerWrapper(OriginalText, Text, document.createTextNode('')); | |
4269 | |
4270 scope.wrappers.Text = Text; | |
4271 })(window.ShadowDOMPolyfill); | |
4272 | |
4273 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4274 // Use of this source code is goverened by a BSD-style | |
4275 // license that can be found in the LICENSE file. | |
4276 | |
4277 (function(scope) { | |
4278 'use strict'; | |
4279 | |
4280 var ChildNodeInterface = scope.ChildNodeInterface; | |
4281 var GetElementsByInterface = scope.GetElementsByInterface; | |
4282 var Node = scope.wrappers.Node; | |
4283 var ParentNodeInterface = scope.ParentNodeInterface; | |
4284 var SelectorsInterface = scope.SelectorsInterface; | |
4285 var addWrapNodeListMethod = scope.addWrapNodeListMethod; | |
4286 var enqueueMutation = scope.enqueueMutation; | |
4287 var mixin = scope.mixin; | |
4288 var oneOf = scope.oneOf; | |
4289 var registerWrapper = scope.registerWrapper; | |
4290 var wrappers = scope.wrappers; | |
4291 | |
4292 var OriginalElement = window.Element; | |
4293 | |
4294 var matchesNames = [ | |
4295 'matches', // needs to come first. | |
4296 'mozMatchesSelector', | |
4297 'msMatchesSelector', | |
4298 'webkitMatchesSelector', | |
4299 ].filter(function(name) { | |
4300 return OriginalElement.prototype[name]; | |
4301 }); | |
4302 | |
4303 var matchesName = matchesNames[0]; | |
4304 | |
4305 var originalMatches = OriginalElement.prototype[matchesName]; | |
4306 | |
4307 function invalidateRendererBasedOnAttribute(element, name) { | |
4308 // Only invalidate if parent node is a shadow host. | |
4309 var p = element.parentNode; | |
4310 if (!p || !p.shadowRoot) | |
4311 return; | |
4312 | |
4313 var renderer = scope.getRendererForHost(p); | |
4314 if (renderer.dependsOnAttribute(name)) | |
4315 renderer.invalidate(); | |
4316 } | |
4317 | |
4318 function enqueAttributeChange(element, name, oldValue) { | |
4319 // This is not fully spec compliant. We should use localName (which might | |
4320 // have a different case than name) and the namespace (which requires us | |
4321 // to get the Attr object). | |
4322 enqueueMutation(element, 'attributes', { | |
4323 name: name, | |
4324 namespace: null, | |
4325 oldValue: oldValue | |
4326 }); | |
4327 } | |
4328 | |
4329 function Element(node) { | |
4330 Node.call(this, node); | |
4331 } | |
4332 Element.prototype = Object.create(Node.prototype); | |
4333 mixin(Element.prototype, { | |
4334 createShadowRoot: function() { | |
4335 var newShadowRoot = new wrappers.ShadowRoot(this); | |
4336 this.impl.polymerShadowRoot_ = newShadowRoot; | |
4337 | |
4338 var renderer = scope.getRendererForHost(this); | |
4339 renderer.invalidate(); | |
4340 | |
4341 return newShadowRoot; | |
4342 }, | |
4343 | |
4344 get shadowRoot() { | |
4345 return this.impl.polymerShadowRoot_ || null; | |
4346 }, | |
4347 | |
4348 setAttribute: function(name, value) { | |
4349 var oldValue = this.impl.getAttribute(name); | |
4350 this.impl.setAttribute(name, value); | |
4351 enqueAttributeChange(this, name, oldValue); | |
4352 invalidateRendererBasedOnAttribute(this, name); | |
4353 }, | |
4354 | |
4355 removeAttribute: function(name) { | |
4356 var oldValue = this.impl.getAttribute(name); | |
4357 this.impl.removeAttribute(name); | |
4358 enqueAttributeChange(this, name, oldValue); | |
4359 invalidateRendererBasedOnAttribute(this, name); | |
4360 }, | |
4361 | |
4362 matches: function(selector) { | |
4363 return originalMatches.call(this.impl, selector); | |
4364 } | |
4365 }); | |
4366 | |
4367 matchesNames.forEach(function(name) { | |
4368 if (name !== 'matches') { | |
4369 Element.prototype[name] = function(selector) { | |
4370 return this.matches(selector); | |
4371 }; | |
4372 } | |
4373 }); | |
4374 | |
4375 if (OriginalElement.prototype.webkitCreateShadowRoot) { | |
4376 Element.prototype.webkitCreateShadowRoot = | |
4377 Element.prototype.createShadowRoot; | |
4378 } | |
4379 | |
4380 /** | |
4381 * Useful for generating the accessor pair for a property that reflects an | |
4382 * attribute. | |
4383 */ | |
4384 function setterDirtiesAttribute(prototype, propertyName, opt_attrName) { | |
4385 var attrName = opt_attrName || propertyName; | |
4386 Object.defineProperty(prototype, propertyName, { | |
4387 get: function() { | |
4388 return this.impl[propertyName]; | |
4389 }, | |
4390 set: function(v) { | |
4391 this.impl[propertyName] = v; | |
4392 invalidateRendererBasedOnAttribute(this, attrName); | |
4393 }, | |
4394 configurable: true, | |
4395 enumerable: true | |
4396 }); | |
4397 } | |
4398 | |
4399 setterDirtiesAttribute(Element.prototype, 'id'); | |
4400 setterDirtiesAttribute(Element.prototype, 'className', 'class'); | |
4401 | |
4402 mixin(Element.prototype, ChildNodeInterface); | |
4403 mixin(Element.prototype, GetElementsByInterface); | |
4404 mixin(Element.prototype, ParentNodeInterface); | |
4405 mixin(Element.prototype, SelectorsInterface); | |
4406 | |
4407 registerWrapper(OriginalElement, Element, | |
4408 document.createElementNS(null, 'x')); | |
4409 | |
4410 // TODO(arv): Export setterDirtiesAttribute and apply it to more bindings | |
4411 // that reflect attributes. | |
4412 scope.matchesNames = matchesNames; | |
4413 scope.wrappers.Element = Element; | |
4414 })(window.ShadowDOMPolyfill); | |
4415 | |
4416 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4417 // Use of this source code is goverened by a BSD-style | |
4418 // license that can be found in the LICENSE file. | |
4419 | |
4420 (function(scope) { | |
4421 'use strict'; | |
4422 | |
4423 var Element = scope.wrappers.Element; | |
4424 var defineGetter = scope.defineGetter; | |
4425 var enqueueMutation = scope.enqueueMutation; | |
4426 var mixin = scope.mixin; | |
4427 var nodesWereAdded = scope.nodesWereAdded; | |
4428 var nodesWereRemoved = scope.nodesWereRemoved; | |
4429 var registerWrapper = scope.registerWrapper; | |
4430 var snapshotNodeList = scope.snapshotNodeList; | |
4431 var unwrap = scope.unwrap; | |
4432 var wrap = scope.wrap; | |
4433 | |
4434 ///////////////////////////////////////////////////////////////////////////// | |
4435 // innerHTML and outerHTML | |
4436 | |
4437 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#es
capingString | |
4438 var escapeAttrRegExp = /[&\u00A0"]/g; | |
4439 var escapeDataRegExp = /[&\u00A0<>]/g; | |
4440 | |
4441 function escapeReplace(c) { | |
4442 switch (c) { | |
4443 case '&': | |
4444 return '&'; | |
4445 case '<': | |
4446 return '<'; | |
4447 case '>': | |
4448 return '>'; | |
4449 case '"': | |
4450 return '"' | |
4451 case '\u00A0': | |
4452 return ' '; | |
4453 } | |
4454 } | |
4455 | |
4456 function escapeAttr(s) { | |
4457 return s.replace(escapeAttrRegExp, escapeReplace); | |
4458 } | |
4459 | |
4460 function escapeData(s) { | |
4461 return s.replace(escapeDataRegExp, escapeReplace); | |
4462 } | |
4463 | |
4464 function makeSet(arr) { | |
4465 var set = {}; | |
4466 for (var i = 0; i < arr.length; i++) { | |
4467 set[arr[i]] = true; | |
4468 } | |
4469 return set; | |
4470 } | |
4471 | |
4472 // http://www.whatwg.org/specs/web-apps/current-work/#void-elements | |
4473 var voidElements = makeSet([ | |
4474 'area', | |
4475 'base', | |
4476 'br', | |
4477 'col', | |
4478 'command', | |
4479 'embed', | |
4480 'hr', | |
4481 'img', | |
4482 'input', | |
4483 'keygen', | |
4484 'link', | |
4485 'meta', | |
4486 'param', | |
4487 'source', | |
4488 'track', | |
4489 'wbr' | |
4490 ]); | |
4491 | |
4492 var plaintextParents = makeSet([ | |
4493 'style', | |
4494 'script', | |
4495 'xmp', | |
4496 'iframe', | |
4497 'noembed', | |
4498 'noframes', | |
4499 'plaintext', | |
4500 'noscript' | |
4501 ]); | |
4502 | |
4503 function getOuterHTML(node, parentNode) { | |
4504 switch (node.nodeType) { | |
4505 case Node.ELEMENT_NODE: | |
4506 var tagName = node.tagName.toLowerCase(); | |
4507 var s = '<' + tagName; | |
4508 var attrs = node.attributes; | |
4509 for (var i = 0, attr; attr = attrs[i]; i++) { | |
4510 s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"'; | |
4511 } | |
4512 s += '>'; | |
4513 if (voidElements[tagName]) | |
4514 return s; | |
4515 | |
4516 return s + getInnerHTML(node) + '</' + tagName + '>'; | |
4517 | |
4518 case Node.TEXT_NODE: | |
4519 var data = node.data; | |
4520 if (parentNode && plaintextParents[parentNode.localName]) | |
4521 return data; | |
4522 return escapeData(data); | |
4523 | |
4524 case Node.COMMENT_NODE: | |
4525 return '<!--' + node.data + '-->'; | |
4526 | |
4527 default: | |
4528 console.error(node); | |
4529 throw new Error('not implemented'); | |
4530 } | |
4531 } | |
4532 | |
4533 function getInnerHTML(node) { | |
4534 var s = ''; | |
4535 for (var child = node.firstChild; child; child = child.nextSibling) { | |
4536 s += getOuterHTML(child, node); | |
4537 } | |
4538 return s; | |
4539 } | |
4540 | |
4541 function setInnerHTML(node, value, opt_tagName) { | |
4542 var tagName = opt_tagName || 'div'; | |
4543 node.textContent = ''; | |
4544 var tempElement = unwrap(node.ownerDocument.createElement(tagName)); | |
4545 tempElement.innerHTML = value; | |
4546 var firstChild; | |
4547 while (firstChild = tempElement.firstChild) { | |
4548 node.appendChild(wrap(firstChild)); | |
4549 } | |
4550 } | |
4551 | |
4552 // IE11 does not have MSIE in the user agent string. | |
4553 var oldIe = /MSIE/.test(navigator.userAgent); | |
4554 | |
4555 var OriginalHTMLElement = window.HTMLElement; | |
4556 | |
4557 function HTMLElement(node) { | |
4558 Element.call(this, node); | |
4559 } | |
4560 HTMLElement.prototype = Object.create(Element.prototype); | |
4561 mixin(HTMLElement.prototype, { | |
4562 get innerHTML() { | |
4563 // TODO(arv): This should fallback to this.impl.innerHTML if there | |
4564 // are no shadow trees below or above the context node. | |
4565 return getInnerHTML(this); | |
4566 }, | |
4567 set innerHTML(value) { | |
4568 // IE9 does not handle set innerHTML correctly on plaintextParents. It | |
4569 // creates element children. For example | |
4570 // | |
4571 // scriptElement.innerHTML = '<a>test</a>' | |
4572 // | |
4573 // Creates a single HTMLAnchorElement child. | |
4574 if (oldIe && plaintextParents[this.localName]) { | |
4575 this.textContent = value; | |
4576 return; | |
4577 } | |
4578 | |
4579 var removedNodes = snapshotNodeList(this.childNodes); | |
4580 | |
4581 if (this.invalidateShadowRenderer()) | |
4582 setInnerHTML(this, value, this.tagName); | |
4583 else | |
4584 this.impl.innerHTML = value; | |
4585 var addedNodes = snapshotNodeList(this.childNodes); | |
4586 | |
4587 enqueueMutation(this, 'childList', { | |
4588 addedNodes: addedNodes, | |
4589 removedNodes: removedNodes | |
4590 }); | |
4591 | |
4592 nodesWereRemoved(removedNodes); | |
4593 nodesWereAdded(addedNodes); | |
4594 }, | |
4595 | |
4596 get outerHTML() { | |
4597 return getOuterHTML(this, this.parentNode); | |
4598 }, | |
4599 set outerHTML(value) { | |
4600 var p = this.parentNode; | |
4601 if (p) { | |
4602 p.invalidateShadowRenderer(); | |
4603 var df = frag(p, value); | |
4604 p.replaceChild(df, this); | |
4605 } | |
4606 }, | |
4607 | |
4608 insertAdjacentHTML: function(position, text) { | |
4609 var contextElement, refNode; | |
4610 switch (String(position).toLowerCase()) { | |
4611 case 'beforebegin': | |
4612 contextElement = this.parentNode; | |
4613 refNode = this; | |
4614 break; | |
4615 case 'afterend': | |
4616 contextElement = this.parentNode; | |
4617 refNode = this.nextSibling; | |
4618 break; | |
4619 case 'afterbegin': | |
4620 contextElement = this; | |
4621 refNode = this.firstChild; | |
4622 break; | |
4623 case 'beforeend': | |
4624 contextElement = this; | |
4625 refNode = null; | |
4626 break; | |
4627 default: | |
4628 return; | |
4629 } | |
4630 | |
4631 var df = frag(contextElement, text); | |
4632 contextElement.insertBefore(df, refNode); | |
4633 } | |
4634 }); | |
4635 | |
4636 function frag(contextElement, html) { | |
4637 // TODO(arv): This does not work with SVG and other non HTML elements. | |
4638 var p = unwrap(contextElement.cloneNode(false)); | |
4639 p.innerHTML = html; | |
4640 var df = unwrap(document.createDocumentFragment()); | |
4641 var c; | |
4642 while (c = p.firstChild) { | |
4643 df.appendChild(c); | |
4644 } | |
4645 return wrap(df); | |
4646 } | |
4647 | |
4648 function getter(name) { | |
4649 return function() { | |
4650 scope.renderAllPending(); | |
4651 return this.impl[name]; | |
4652 }; | |
4653 } | |
4654 | |
4655 function getterRequiresRendering(name) { | |
4656 defineGetter(HTMLElement, name, getter(name)); | |
4657 } | |
4658 | |
4659 [ | |
4660 'clientHeight', | |
4661 'clientLeft', | |
4662 'clientTop', | |
4663 'clientWidth', | |
4664 'offsetHeight', | |
4665 'offsetLeft', | |
4666 'offsetTop', | |
4667 'offsetWidth', | |
4668 'scrollHeight', | |
4669 'scrollWidth', | |
4670 ].forEach(getterRequiresRendering); | |
4671 | |
4672 function getterAndSetterRequiresRendering(name) { | |
4673 Object.defineProperty(HTMLElement.prototype, name, { | |
4674 get: getter(name), | |
4675 set: function(v) { | |
4676 scope.renderAllPending(); | |
4677 this.impl[name] = v; | |
4678 }, | |
4679 configurable: true, | |
4680 enumerable: true | |
4681 }); | |
4682 } | |
4683 | |
4684 [ | |
4685 'scrollLeft', | |
4686 'scrollTop', | |
4687 ].forEach(getterAndSetterRequiresRendering); | |
4688 | |
4689 function methodRequiresRendering(name) { | |
4690 Object.defineProperty(HTMLElement.prototype, name, { | |
4691 value: function() { | |
4692 scope.renderAllPending(); | |
4693 return this.impl[name].apply(this.impl, arguments); | |
4694 }, | |
4695 configurable: true, | |
4696 enumerable: true | |
4697 }); | |
4698 } | |
4699 | |
4700 [ | |
4701 'getBoundingClientRect', | |
4702 'getClientRects', | |
4703 'scrollIntoView' | |
4704 ].forEach(methodRequiresRendering); | |
4705 | |
4706 // HTMLElement is abstract so we use a subclass that has no members. | |
4707 registerWrapper(OriginalHTMLElement, HTMLElement, | |
4708 document.createElement('b')); | |
4709 | |
4710 scope.wrappers.HTMLElement = HTMLElement; | |
4711 | |
4712 // TODO: Find a better way to share these two with WrapperShadowRoot. | |
4713 scope.getInnerHTML = getInnerHTML; | |
4714 scope.setInnerHTML = setInnerHTML | |
4715 })(window.ShadowDOMPolyfill); | |
4716 | |
4717 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4718 // Use of this source code is goverened by a BSD-style | |
4719 // license that can be found in the LICENSE file. | |
4720 | |
4721 (function(scope) { | |
4722 'use strict'; | |
4723 | |
4724 var HTMLElement = scope.wrappers.HTMLElement; | |
4725 var mixin = scope.mixin; | |
4726 var registerWrapper = scope.registerWrapper; | |
4727 var wrap = scope.wrap; | |
4728 | |
4729 var OriginalHTMLCanvasElement = window.HTMLCanvasElement; | |
4730 | |
4731 function HTMLCanvasElement(node) { | |
4732 HTMLElement.call(this, node); | |
4733 } | |
4734 HTMLCanvasElement.prototype = Object.create(HTMLElement.prototype); | |
4735 | |
4736 mixin(HTMLCanvasElement.prototype, { | |
4737 getContext: function() { | |
4738 var context = this.impl.getContext.apply(this.impl, arguments); | |
4739 return context && wrap(context); | |
4740 } | |
4741 }); | |
4742 | |
4743 registerWrapper(OriginalHTMLCanvasElement, HTMLCanvasElement, | |
4744 document.createElement('canvas')); | |
4745 | |
4746 scope.wrappers.HTMLCanvasElement = HTMLCanvasElement; | |
4747 })(window.ShadowDOMPolyfill); | |
4748 | |
4749 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4750 // Use of this source code is goverened by a BSD-style | |
4751 // license that can be found in the LICENSE file. | |
4752 | |
4753 (function(scope) { | |
4754 'use strict'; | |
4755 | |
4756 var HTMLElement = scope.wrappers.HTMLElement; | |
4757 var mixin = scope.mixin; | |
4758 var registerWrapper = scope.registerWrapper; | |
4759 | |
4760 var OriginalHTMLContentElement = window.HTMLContentElement; | |
4761 | |
4762 function HTMLContentElement(node) { | |
4763 HTMLElement.call(this, node); | |
4764 } | |
4765 HTMLContentElement.prototype = Object.create(HTMLElement.prototype); | |
4766 mixin(HTMLContentElement.prototype, { | |
4767 get select() { | |
4768 return this.getAttribute('select'); | |
4769 }, | |
4770 set select(value) { | |
4771 this.setAttribute('select', value); | |
4772 }, | |
4773 | |
4774 setAttribute: function(n, v) { | |
4775 HTMLElement.prototype.setAttribute.call(this, n, v); | |
4776 if (String(n).toLowerCase() === 'select') | |
4777 this.invalidateShadowRenderer(true); | |
4778 } | |
4779 | |
4780 // getDistributedNodes is added in ShadowRenderer | |
4781 | |
4782 // TODO: attribute boolean resetStyleInheritance; | |
4783 }); | |
4784 | |
4785 if (OriginalHTMLContentElement) | |
4786 registerWrapper(OriginalHTMLContentElement, HTMLContentElement); | |
4787 | |
4788 scope.wrappers.HTMLContentElement = HTMLContentElement; | |
4789 })(window.ShadowDOMPolyfill); | |
4790 | |
4791 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4792 // Use of this source code is goverened by a BSD-style | |
4793 // license that can be found in the LICENSE file. | |
4794 | |
4795 (function(scope) { | |
4796 'use strict'; | |
4797 | |
4798 var HTMLElement = scope.wrappers.HTMLElement; | |
4799 var registerWrapper = scope.registerWrapper; | |
4800 var unwrap = scope.unwrap; | |
4801 var rewrap = scope.rewrap; | |
4802 | |
4803 var OriginalHTMLImageElement = window.HTMLImageElement; | |
4804 | |
4805 function HTMLImageElement(node) { | |
4806 HTMLElement.call(this, node); | |
4807 } | |
4808 HTMLImageElement.prototype = Object.create(HTMLElement.prototype); | |
4809 | |
4810 registerWrapper(OriginalHTMLImageElement, HTMLImageElement, | |
4811 document.createElement('img')); | |
4812 | |
4813 function Image(width, height) { | |
4814 if (!(this instanceof Image)) { | |
4815 throw new TypeError( | |
4816 'DOM object constructor cannot be called as a function.'); | |
4817 } | |
4818 | |
4819 var node = unwrap(document.createElement('img')); | |
4820 HTMLElement.call(this, node); | |
4821 rewrap(node, this); | |
4822 | |
4823 if (width !== undefined) | |
4824 node.width = width; | |
4825 if (height !== undefined) | |
4826 node.height = height; | |
4827 } | |
4828 | |
4829 Image.prototype = HTMLImageElement.prototype; | |
4830 | |
4831 scope.wrappers.HTMLImageElement = HTMLImageElement; | |
4832 scope.wrappers.Image = Image; | |
4833 })(window.ShadowDOMPolyfill); | |
4834 | |
4835 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4836 // Use of this source code is goverened by a BSD-style | |
4837 // license that can be found in the LICENSE file. | |
4838 | |
4839 (function(scope) { | |
4840 'use strict'; | |
4841 | |
4842 var HTMLElement = scope.wrappers.HTMLElement; | |
4843 var mixin = scope.mixin; | |
4844 var registerWrapper = scope.registerWrapper; | |
4845 | |
4846 var OriginalHTMLShadowElement = window.HTMLShadowElement; | |
4847 | |
4848 function HTMLShadowElement(node) { | |
4849 HTMLElement.call(this, node); | |
4850 } | |
4851 HTMLShadowElement.prototype = Object.create(HTMLElement.prototype); | |
4852 mixin(HTMLShadowElement.prototype, { | |
4853 // TODO: attribute boolean resetStyleInheritance; | |
4854 }); | |
4855 | |
4856 if (OriginalHTMLShadowElement) | |
4857 registerWrapper(OriginalHTMLShadowElement, HTMLShadowElement); | |
4858 | |
4859 scope.wrappers.HTMLShadowElement = HTMLShadowElement; | |
4860 })(window.ShadowDOMPolyfill); | |
4861 | |
4862 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4863 // Use of this source code is goverened by a BSD-style | |
4864 // license that can be found in the LICENSE file. | |
4865 | |
4866 (function(scope) { | |
4867 'use strict'; | |
4868 | |
4869 var HTMLElement = scope.wrappers.HTMLElement; | |
4870 var getInnerHTML = scope.getInnerHTML; | |
4871 var mixin = scope.mixin; | |
4872 var registerWrapper = scope.registerWrapper; | |
4873 var setInnerHTML = scope.setInnerHTML; | |
4874 var unwrap = scope.unwrap; | |
4875 var wrap = scope.wrap; | |
4876 | |
4877 var contentTable = new WeakMap(); | |
4878 var templateContentsOwnerTable = new WeakMap(); | |
4879 | |
4880 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner | |
4881 function getTemplateContentsOwner(doc) { | |
4882 if (!doc.defaultView) | |
4883 return doc; | |
4884 var d = templateContentsOwnerTable.get(doc); | |
4885 if (!d) { | |
4886 // TODO(arv): This should either be a Document or HTMLDocument depending | |
4887 // on doc. | |
4888 d = doc.implementation.createHTMLDocument(''); | |
4889 while (d.lastChild) { | |
4890 d.removeChild(d.lastChild); | |
4891 } | |
4892 templateContentsOwnerTable.set(doc, d); | |
4893 } | |
4894 return d; | |
4895 } | |
4896 | |
4897 function extractContent(templateElement) { | |
4898 // templateElement is not a wrapper here. | |
4899 var doc = getTemplateContentsOwner(templateElement.ownerDocument); | |
4900 var df = unwrap(doc.createDocumentFragment()); | |
4901 var child; | |
4902 while (child = templateElement.firstChild) { | |
4903 df.appendChild(child); | |
4904 } | |
4905 return df; | |
4906 } | |
4907 | |
4908 var OriginalHTMLTemplateElement = window.HTMLTemplateElement; | |
4909 | |
4910 function HTMLTemplateElement(node) { | |
4911 HTMLElement.call(this, node); | |
4912 if (!OriginalHTMLTemplateElement) { | |
4913 var content = extractContent(node); | |
4914 contentTable.set(this, wrap(content)); | |
4915 } | |
4916 } | |
4917 HTMLTemplateElement.prototype = Object.create(HTMLElement.prototype); | |
4918 | |
4919 mixin(HTMLTemplateElement.prototype, { | |
4920 get content() { | |
4921 if (OriginalHTMLTemplateElement) | |
4922 return wrap(this.impl.content); | |
4923 return contentTable.get(this); | |
4924 }, | |
4925 | |
4926 get innerHTML() { | |
4927 return getInnerHTML(this.content); | |
4928 }, | |
4929 set innerHTML(value) { | |
4930 setInnerHTML(this.content, value); | |
4931 } | |
4932 | |
4933 // TODO(arv): cloneNode needs to clone content. | |
4934 | |
4935 }); | |
4936 | |
4937 if (OriginalHTMLTemplateElement) | |
4938 registerWrapper(OriginalHTMLTemplateElement, HTMLTemplateElement); | |
4939 | |
4940 scope.wrappers.HTMLTemplateElement = HTMLTemplateElement; | |
4941 })(window.ShadowDOMPolyfill); | |
4942 | |
4943 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4944 // Use of this source code is goverened by a BSD-style | |
4945 // license that can be found in the LICENSE file. | |
4946 | |
4947 (function(scope) { | |
4948 'use strict'; | |
4949 | |
4950 var HTMLElement = scope.wrappers.HTMLElement; | |
4951 var registerWrapper = scope.registerWrapper; | |
4952 | |
4953 var OriginalHTMLMediaElement = window.HTMLMediaElement; | |
4954 | |
4955 function HTMLMediaElement(node) { | |
4956 HTMLElement.call(this, node); | |
4957 } | |
4958 HTMLMediaElement.prototype = Object.create(HTMLElement.prototype); | |
4959 | |
4960 registerWrapper(OriginalHTMLMediaElement, HTMLMediaElement, | |
4961 document.createElement('audio')); | |
4962 | |
4963 scope.wrappers.HTMLMediaElement = HTMLMediaElement; | |
4964 })(window.ShadowDOMPolyfill); | |
4965 | |
4966 // Copyright 2013 The Polymer Authors. All rights reserved. | |
4967 // Use of this source code is goverened by a BSD-style | |
4968 // license that can be found in the LICENSE file. | |
4969 | |
4970 (function(scope) { | |
4971 'use strict'; | |
4972 | |
4973 var HTMLMediaElement = scope.wrappers.HTMLMediaElement; | |
4974 var registerWrapper = scope.registerWrapper; | |
4975 var unwrap = scope.unwrap; | |
4976 var rewrap = scope.rewrap; | |
4977 | |
4978 var OriginalHTMLAudioElement = window.HTMLAudioElement; | |
4979 | |
4980 function HTMLAudioElement(node) { | |
4981 HTMLMediaElement.call(this, node); | |
4982 } | |
4983 HTMLAudioElement.prototype = Object.create(HTMLMediaElement.prototype); | |
4984 | |
4985 registerWrapper(OriginalHTMLAudioElement, HTMLAudioElement, | |
4986 document.createElement('audio')); | |
4987 | |
4988 function Audio(src) { | |
4989 if (!(this instanceof Audio)) { | |
4990 throw new TypeError( | |
4991 'DOM object constructor cannot be called as a function.'); | |
4992 } | |
4993 | |
4994 var node = unwrap(document.createElement('audio')); | |
4995 HTMLMediaElement.call(this, node); | |
4996 rewrap(node, this); | |
4997 | |
4998 node.setAttribute('preload', 'auto'); | |
4999 if (src !== undefined) | |
5000 node.setAttribute('src', src); | |
5001 } | |
5002 | |
5003 Audio.prototype = HTMLAudioElement.prototype; | |
5004 | |
5005 scope.wrappers.HTMLAudioElement = HTMLAudioElement; | |
5006 scope.wrappers.Audio = Audio; | |
5007 })(window.ShadowDOMPolyfill); | |
5008 | |
5009 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5010 // Use of this source code is goverened by a BSD-style | |
5011 // license that can be found in the LICENSE file. | |
5012 | |
5013 (function(scope) { | |
5014 'use strict'; | |
5015 | |
5016 var HTMLElement = scope.wrappers.HTMLElement; | |
5017 var mixin = scope.mixin; | |
5018 var registerWrapper = scope.registerWrapper; | |
5019 var rewrap = scope.rewrap; | |
5020 var unwrap = scope.unwrap; | |
5021 var wrap = scope.wrap; | |
5022 | |
5023 var OriginalHTMLOptionElement = window.HTMLOptionElement; | |
5024 | |
5025 function trimText(s) { | |
5026 return s.replace(/\s+/g, ' ').trim(); | |
5027 } | |
5028 | |
5029 function HTMLOptionElement(node) { | |
5030 HTMLElement.call(this, node); | |
5031 } | |
5032 HTMLOptionElement.prototype = Object.create(HTMLElement.prototype); | |
5033 mixin(HTMLOptionElement.prototype, { | |
5034 get text() { | |
5035 return trimText(this.textContent); | |
5036 }, | |
5037 set text(value) { | |
5038 this.textContent = trimText(String(value)); | |
5039 }, | |
5040 get form() { | |
5041 return wrap(unwrap(this).form); | |
5042 } | |
5043 }); | |
5044 | |
5045 registerWrapper(OriginalHTMLOptionElement, HTMLOptionElement, | |
5046 document.createElement('option')); | |
5047 | |
5048 function Option(text, value, defaultSelected, selected) { | |
5049 if (!(this instanceof Option)) { | |
5050 throw new TypeError( | |
5051 'DOM object constructor cannot be called as a function.'); | |
5052 } | |
5053 | |
5054 var node = unwrap(document.createElement('option')); | |
5055 HTMLElement.call(this, node); | |
5056 rewrap(node, this); | |
5057 | |
5058 if (text !== undefined) | |
5059 node.text = text; | |
5060 if (value !== undefined) | |
5061 node.setAttribute('value', value); | |
5062 if (defaultSelected === true) | |
5063 node.setAttribute('selected', ''); | |
5064 node.selected = selected === true; | |
5065 } | |
5066 | |
5067 Option.prototype = HTMLOptionElement.prototype; | |
5068 | |
5069 scope.wrappers.HTMLOptionElement = HTMLOptionElement; | |
5070 scope.wrappers.Option = Option; | |
5071 })(window.ShadowDOMPolyfill); | |
5072 | |
5073 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5074 // Use of this source code is goverened by a BSD-style | |
5075 // license that can be found in the LICENSE file. | |
5076 | |
5077 (function(scope) { | |
5078 'use strict'; | |
5079 | |
5080 var HTMLContentElement = scope.wrappers.HTMLContentElement; | |
5081 var HTMLElement = scope.wrappers.HTMLElement; | |
5082 var HTMLShadowElement = scope.wrappers.HTMLShadowElement; | |
5083 var HTMLTemplateElement = scope.wrappers.HTMLTemplateElement; | |
5084 var mixin = scope.mixin; | |
5085 var registerWrapper = scope.registerWrapper; | |
5086 | |
5087 var OriginalHTMLUnknownElement = window.HTMLUnknownElement; | |
5088 | |
5089 function HTMLUnknownElement(node) { | |
5090 switch (node.localName) { | |
5091 case 'content': | |
5092 return new HTMLContentElement(node); | |
5093 case 'shadow': | |
5094 return new HTMLShadowElement(node); | |
5095 case 'template': | |
5096 return new HTMLTemplateElement(node); | |
5097 } | |
5098 HTMLElement.call(this, node); | |
5099 } | |
5100 HTMLUnknownElement.prototype = Object.create(HTMLElement.prototype); | |
5101 registerWrapper(OriginalHTMLUnknownElement, HTMLUnknownElement); | |
5102 scope.wrappers.HTMLUnknownElement = HTMLUnknownElement; | |
5103 })(window.ShadowDOMPolyfill); | |
5104 | |
5105 // Copyright 2014 The Polymer Authors. All rights reserved. | |
5106 // Use of this source code is goverened by a BSD-style | |
5107 // license that can be found in the LICENSE file. | |
5108 | |
5109 (function(scope) { | |
5110 'use strict'; | |
5111 | |
5112 var registerObject = scope.registerObject; | |
5113 | |
5114 var SVG_NS = 'http://www.w3.org/2000/svg'; | |
5115 var svgTitleElement = document.createElementNS(SVG_NS, 'title'); | |
5116 var SVGTitleElement = registerObject(svgTitleElement); | |
5117 var SVGElement = Object.getPrototypeOf(SVGTitleElement.prototype).constructor; | |
5118 | |
5119 scope.wrappers.SVGElement = SVGElement; | |
5120 })(window.ShadowDOMPolyfill); | |
5121 | |
5122 // Copyright 2014 The Polymer Authors. All rights reserved. | |
5123 // Use of this source code is goverened by a BSD-style | |
5124 // license that can be found in the LICENSE file. | |
5125 | |
5126 (function(scope) { | |
5127 'use strict'; | |
5128 | |
5129 var mixin = scope.mixin; | |
5130 var registerWrapper = scope.registerWrapper; | |
5131 var unwrap = scope.unwrap; | |
5132 var wrap = scope.wrap; | |
5133 | |
5134 var OriginalSVGUseElement = window.SVGUseElement; | |
5135 | |
5136 // IE uses SVGElement as parent interface, SVG2 (Blink & Gecko) uses | |
5137 // SVGGraphicsElement. Use the <g> element to get the right prototype. | |
5138 | |
5139 var SVG_NS = 'http://www.w3.org/2000/svg'; | |
5140 var gWrapper = wrap(document.createElementNS(SVG_NS, 'g')); | |
5141 var useElement = document.createElementNS(SVG_NS, 'use'); | |
5142 var SVGGElement = gWrapper.constructor; | |
5143 var parentInterfacePrototype = Object.getPrototypeOf(SVGGElement.prototype); | |
5144 var parentInterface = parentInterfacePrototype.constructor; | |
5145 | |
5146 function SVGUseElement(impl) { | |
5147 parentInterface.call(this, impl); | |
5148 } | |
5149 | |
5150 SVGUseElement.prototype = Object.create(parentInterfacePrototype); | |
5151 | |
5152 // Firefox does not expose instanceRoot. | |
5153 if ('instanceRoot' in useElement) { | |
5154 mixin(SVGUseElement.prototype, { | |
5155 get instanceRoot() { | |
5156 return wrap(unwrap(this).instanceRoot); | |
5157 }, | |
5158 get animatedInstanceRoot() { | |
5159 return wrap(unwrap(this).animatedInstanceRoot); | |
5160 }, | |
5161 }); | |
5162 } | |
5163 | |
5164 registerWrapper(OriginalSVGUseElement, SVGUseElement, useElement); | |
5165 | |
5166 scope.wrappers.SVGUseElement = SVGUseElement; | |
5167 })(window.ShadowDOMPolyfill); | |
5168 | |
5169 // Copyright 2014 The Polymer Authors. All rights reserved. | |
5170 // Use of this source code is goverened by a BSD-style | |
5171 // license that can be found in the LICENSE file. | |
5172 | |
5173 (function(scope) { | |
5174 'use strict'; | |
5175 | |
5176 var EventTarget = scope.wrappers.EventTarget; | |
5177 var mixin = scope.mixin; | |
5178 var registerWrapper = scope.registerWrapper; | |
5179 var wrap = scope.wrap; | |
5180 | |
5181 var OriginalSVGElementInstance = window.SVGElementInstance; | |
5182 if (!OriginalSVGElementInstance) | |
5183 return; | |
5184 | |
5185 function SVGElementInstance(impl) { | |
5186 EventTarget.call(this, impl); | |
5187 } | |
5188 | |
5189 SVGElementInstance.prototype = Object.create(EventTarget.prototype); | |
5190 mixin(SVGElementInstance.prototype, { | |
5191 /** @type {SVGElement} */ | |
5192 get correspondingElement() { | |
5193 return wrap(this.impl.correspondingElement); | |
5194 }, | |
5195 | |
5196 /** @type {SVGUseElement} */ | |
5197 get correspondingUseElement() { | |
5198 return wrap(this.impl.correspondingUseElement); | |
5199 }, | |
5200 | |
5201 /** @type {SVGElementInstance} */ | |
5202 get parentNode() { | |
5203 return wrap(this.impl.parentNode); | |
5204 }, | |
5205 | |
5206 /** @type {SVGElementInstanceList} */ | |
5207 get childNodes() { | |
5208 throw new Error('Not implemented'); | |
5209 }, | |
5210 | |
5211 /** @type {SVGElementInstance} */ | |
5212 get firstChild() { | |
5213 return wrap(this.impl.firstChild); | |
5214 }, | |
5215 | |
5216 /** @type {SVGElementInstance} */ | |
5217 get lastChild() { | |
5218 return wrap(this.impl.lastChild); | |
5219 }, | |
5220 | |
5221 /** @type {SVGElementInstance} */ | |
5222 get previousSibling() { | |
5223 return wrap(this.impl.previousSibling); | |
5224 }, | |
5225 | |
5226 /** @type {SVGElementInstance} */ | |
5227 get nextSibling() { | |
5228 return wrap(this.impl.nextSibling); | |
5229 } | |
5230 }); | |
5231 | |
5232 registerWrapper(OriginalSVGElementInstance, SVGElementInstance); | |
5233 | |
5234 scope.wrappers.SVGElementInstance = SVGElementInstance; | |
5235 })(window.ShadowDOMPolyfill); | |
5236 | |
5237 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5238 // Use of this source code is goverened by a BSD-style | |
5239 // license that can be found in the LICENSE file. | |
5240 | |
5241 (function(scope) { | |
5242 'use strict'; | |
5243 | |
5244 var mixin = scope.mixin; | |
5245 var registerWrapper = scope.registerWrapper; | |
5246 var unwrap = scope.unwrap; | |
5247 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
5248 var wrap = scope.wrap; | |
5249 | |
5250 var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D; | |
5251 | |
5252 function CanvasRenderingContext2D(impl) { | |
5253 this.impl = impl; | |
5254 } | |
5255 | |
5256 mixin(CanvasRenderingContext2D.prototype, { | |
5257 get canvas() { | |
5258 return wrap(this.impl.canvas); | |
5259 }, | |
5260 | |
5261 drawImage: function() { | |
5262 arguments[0] = unwrapIfNeeded(arguments[0]); | |
5263 this.impl.drawImage.apply(this.impl, arguments); | |
5264 }, | |
5265 | |
5266 createPattern: function() { | |
5267 arguments[0] = unwrap(arguments[0]); | |
5268 return this.impl.createPattern.apply(this.impl, arguments); | |
5269 } | |
5270 }); | |
5271 | |
5272 registerWrapper(OriginalCanvasRenderingContext2D, CanvasRenderingContext2D, | |
5273 document.createElement('canvas').getContext('2d')); | |
5274 | |
5275 scope.wrappers.CanvasRenderingContext2D = CanvasRenderingContext2D; | |
5276 })(window.ShadowDOMPolyfill); | |
5277 | |
5278 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5279 // Use of this source code is goverened by a BSD-style | |
5280 // license that can be found in the LICENSE file. | |
5281 | |
5282 (function(scope) { | |
5283 'use strict'; | |
5284 | |
5285 var mixin = scope.mixin; | |
5286 var registerWrapper = scope.registerWrapper; | |
5287 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
5288 var wrap = scope.wrap; | |
5289 | |
5290 var OriginalWebGLRenderingContext = window.WebGLRenderingContext; | |
5291 | |
5292 // IE10 does not have WebGL. | |
5293 if (!OriginalWebGLRenderingContext) | |
5294 return; | |
5295 | |
5296 function WebGLRenderingContext(impl) { | |
5297 this.impl = impl; | |
5298 } | |
5299 | |
5300 mixin(WebGLRenderingContext.prototype, { | |
5301 get canvas() { | |
5302 return wrap(this.impl.canvas); | |
5303 }, | |
5304 | |
5305 texImage2D: function() { | |
5306 arguments[5] = unwrapIfNeeded(arguments[5]); | |
5307 this.impl.texImage2D.apply(this.impl, arguments); | |
5308 }, | |
5309 | |
5310 texSubImage2D: function() { | |
5311 arguments[6] = unwrapIfNeeded(arguments[6]); | |
5312 this.impl.texSubImage2D.apply(this.impl, arguments); | |
5313 } | |
5314 }); | |
5315 | |
5316 // Blink/WebKit has broken DOM bindings. Usually we would create an instance | |
5317 // of the object and pass it into registerWrapper as a "blueprint" but | |
5318 // creating WebGL contexts is expensive and might fail so we use a dummy | |
5319 // object with dummy instance properties for these broken browsers. | |
5320 var instanceProperties = /WebKit/.test(navigator.userAgent) ? | |
5321 {drawingBufferHeight: null, drawingBufferWidth: null} : {}; | |
5322 | |
5323 registerWrapper(OriginalWebGLRenderingContext, WebGLRenderingContext, | |
5324 instanceProperties); | |
5325 | |
5326 scope.wrappers.WebGLRenderingContext = WebGLRenderingContext; | |
5327 })(window.ShadowDOMPolyfill); | |
5328 | |
5329 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5330 // Use of this source code is goverened by a BSD-style | |
5331 // license that can be found in the LICENSE file. | |
5332 | |
5333 (function(scope) { | |
5334 'use strict'; | |
5335 | |
5336 var registerWrapper = scope.registerWrapper; | |
5337 var unwrap = scope.unwrap; | |
5338 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
5339 var wrap = scope.wrap; | |
5340 | |
5341 var OriginalRange = window.Range; | |
5342 | |
5343 function Range(impl) { | |
5344 this.impl = impl; | |
5345 } | |
5346 Range.prototype = { | |
5347 get startContainer() { | |
5348 return wrap(this.impl.startContainer); | |
5349 }, | |
5350 get endContainer() { | |
5351 return wrap(this.impl.endContainer); | |
5352 }, | |
5353 get commonAncestorContainer() { | |
5354 return wrap(this.impl.commonAncestorContainer); | |
5355 }, | |
5356 setStart: function(refNode,offset) { | |
5357 this.impl.setStart(unwrapIfNeeded(refNode), offset); | |
5358 }, | |
5359 setEnd: function(refNode,offset) { | |
5360 this.impl.setEnd(unwrapIfNeeded(refNode), offset); | |
5361 }, | |
5362 setStartBefore: function(refNode) { | |
5363 this.impl.setStartBefore(unwrapIfNeeded(refNode)); | |
5364 }, | |
5365 setStartAfter: function(refNode) { | |
5366 this.impl.setStartAfter(unwrapIfNeeded(refNode)); | |
5367 }, | |
5368 setEndBefore: function(refNode) { | |
5369 this.impl.setEndBefore(unwrapIfNeeded(refNode)); | |
5370 }, | |
5371 setEndAfter: function(refNode) { | |
5372 this.impl.setEndAfter(unwrapIfNeeded(refNode)); | |
5373 }, | |
5374 selectNode: function(refNode) { | |
5375 this.impl.selectNode(unwrapIfNeeded(refNode)); | |
5376 }, | |
5377 selectNodeContents: function(refNode) { | |
5378 this.impl.selectNodeContents(unwrapIfNeeded(refNode)); | |
5379 }, | |
5380 compareBoundaryPoints: function(how, sourceRange) { | |
5381 return this.impl.compareBoundaryPoints(how, unwrap(sourceRange)); | |
5382 }, | |
5383 extractContents: function() { | |
5384 return wrap(this.impl.extractContents()); | |
5385 }, | |
5386 cloneContents: function() { | |
5387 return wrap(this.impl.cloneContents()); | |
5388 }, | |
5389 insertNode: function(node) { | |
5390 this.impl.insertNode(unwrapIfNeeded(node)); | |
5391 }, | |
5392 surroundContents: function(newParent) { | |
5393 this.impl.surroundContents(unwrapIfNeeded(newParent)); | |
5394 }, | |
5395 cloneRange: function() { | |
5396 return wrap(this.impl.cloneRange()); | |
5397 }, | |
5398 isPointInRange: function(node, offset) { | |
5399 return this.impl.isPointInRange(unwrapIfNeeded(node), offset); | |
5400 }, | |
5401 comparePoint: function(node, offset) { | |
5402 return this.impl.comparePoint(unwrapIfNeeded(node), offset); | |
5403 }, | |
5404 intersectsNode: function(node) { | |
5405 return this.impl.intersectsNode(unwrapIfNeeded(node)); | |
5406 }, | |
5407 toString: function() { | |
5408 return this.impl.toString(); | |
5409 } | |
5410 }; | |
5411 | |
5412 // IE9 does not have createContextualFragment. | |
5413 if (OriginalRange.prototype.createContextualFragment) { | |
5414 Range.prototype.createContextualFragment = function(html) { | |
5415 return wrap(this.impl.createContextualFragment(html)); | |
5416 }; | |
5417 } | |
5418 | |
5419 registerWrapper(window.Range, Range, document.createRange()); | |
5420 | |
5421 scope.wrappers.Range = Range; | |
5422 | |
5423 })(window.ShadowDOMPolyfill); | |
5424 | |
5425 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5426 // Use of this source code is goverened by a BSD-style | |
5427 // license that can be found in the LICENSE file. | |
5428 | |
5429 (function(scope) { | |
5430 'use strict'; | |
5431 | |
5432 var GetElementsByInterface = scope.GetElementsByInterface; | |
5433 var ParentNodeInterface = scope.ParentNodeInterface; | |
5434 var SelectorsInterface = scope.SelectorsInterface; | |
5435 var mixin = scope.mixin; | |
5436 var registerObject = scope.registerObject; | |
5437 | |
5438 var DocumentFragment = registerObject(document.createDocumentFragment()); | |
5439 mixin(DocumentFragment.prototype, ParentNodeInterface); | |
5440 mixin(DocumentFragment.prototype, SelectorsInterface); | |
5441 mixin(DocumentFragment.prototype, GetElementsByInterface); | |
5442 | |
5443 var Comment = registerObject(document.createComment('')); | |
5444 | |
5445 scope.wrappers.Comment = Comment; | |
5446 scope.wrappers.DocumentFragment = DocumentFragment; | |
5447 | |
5448 })(window.ShadowDOMPolyfill); | |
5449 | |
5450 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5451 // Use of this source code is goverened by a BSD-style | |
5452 // license that can be found in the LICENSE file. | |
5453 | |
5454 (function(scope) { | |
5455 'use strict'; | |
5456 | |
5457 var DocumentFragment = scope.wrappers.DocumentFragment; | |
5458 var elementFromPoint = scope.elementFromPoint; | |
5459 var getInnerHTML = scope.getInnerHTML; | |
5460 var mixin = scope.mixin; | |
5461 var rewrap = scope.rewrap; | |
5462 var setInnerHTML = scope.setInnerHTML; | |
5463 var unwrap = scope.unwrap; | |
5464 | |
5465 var shadowHostTable = new WeakMap(); | |
5466 var nextOlderShadowTreeTable = new WeakMap(); | |
5467 | |
5468 var spaceCharRe = /[ \t\n\r\f]/; | |
5469 | |
5470 function ShadowRoot(hostWrapper) { | |
5471 var node = unwrap(hostWrapper.impl.ownerDocument.createDocumentFragment()); | |
5472 DocumentFragment.call(this, node); | |
5473 | |
5474 // createDocumentFragment associates the node with a wrapper | |
5475 // DocumentFragment instance. Override that. | |
5476 rewrap(node, this); | |
5477 | |
5478 var oldShadowRoot = hostWrapper.shadowRoot; | |
5479 nextOlderShadowTreeTable.set(this, oldShadowRoot); | |
5480 | |
5481 shadowHostTable.set(this, hostWrapper); | |
5482 } | |
5483 ShadowRoot.prototype = Object.create(DocumentFragment.prototype); | |
5484 mixin(ShadowRoot.prototype, { | |
5485 get innerHTML() { | |
5486 return getInnerHTML(this); | |
5487 }, | |
5488 set innerHTML(value) { | |
5489 setInnerHTML(this, value); | |
5490 this.invalidateShadowRenderer(); | |
5491 }, | |
5492 | |
5493 get olderShadowRoot() { | |
5494 return nextOlderShadowTreeTable.get(this) || null; | |
5495 }, | |
5496 | |
5497 get host() { | |
5498 return shadowHostTable.get(this) || null; | |
5499 }, | |
5500 | |
5501 invalidateShadowRenderer: function() { | |
5502 return shadowHostTable.get(this).invalidateShadowRenderer(); | |
5503 }, | |
5504 | |
5505 elementFromPoint: function(x, y) { | |
5506 return elementFromPoint(this, this.ownerDocument, x, y); | |
5507 }, | |
5508 | |
5509 getElementById: function(id) { | |
5510 if (spaceCharRe.test(id)) | |
5511 return null; | |
5512 return this.querySelector('[id="' + id + '"]'); | |
5513 } | |
5514 }); | |
5515 | |
5516 scope.wrappers.ShadowRoot = ShadowRoot; | |
5517 | |
5518 })(window.ShadowDOMPolyfill); | |
5519 | |
5520 // Copyright 2013 The Polymer Authors. All rights reserved. | |
5521 // Use of this source code is governed by a BSD-style | |
5522 // license that can be found in the LICENSE file. | |
5523 | |
5524 (function(scope) { | |
5525 'use strict'; | |
5526 | |
5527 var Element = scope.wrappers.Element; | |
5528 var HTMLContentElement = scope.wrappers.HTMLContentElement; | |
5529 var HTMLShadowElement = scope.wrappers.HTMLShadowElement; | |
5530 var Node = scope.wrappers.Node; | |
5531 var ShadowRoot = scope.wrappers.ShadowRoot; | |
5532 var assert = scope.assert; | |
5533 var mixin = scope.mixin; | |
5534 var oneOf = scope.oneOf; | |
5535 var unwrap = scope.unwrap; | |
5536 var wrap = scope.wrap; | |
5537 | |
5538 /** | |
5539 * Updates the fields of a wrapper to a snapshot of the logical DOM as needed. | |
5540 * Up means parentNode | |
5541 * Sideways means previous and next sibling. | |
5542 * @param {!Node} wrapper | |
5543 */ | |
5544 function updateWrapperUpAndSideways(wrapper) { | |
5545 wrapper.previousSibling_ = wrapper.previousSibling; | |
5546 wrapper.nextSibling_ = wrapper.nextSibling; | |
5547 wrapper.parentNode_ = wrapper.parentNode; | |
5548 } | |
5549 | |
5550 /** | |
5551 * Updates the fields of a wrapper to a snapshot of the logical DOM as needed. | |
5552 * Down means first and last child | |
5553 * @param {!Node} wrapper | |
5554 */ | |
5555 function updateWrapperDown(wrapper) { | |
5556 wrapper.firstChild_ = wrapper.firstChild; | |
5557 wrapper.lastChild_ = wrapper.lastChild; | |
5558 } | |
5559 | |
5560 function updateAllChildNodes(parentNodeWrapper) { | |
5561 assert(parentNodeWrapper instanceof Node); | |
5562 for (var childWrapper = parentNodeWrapper.firstChild; | |
5563 childWrapper; | |
5564 childWrapper = childWrapper.nextSibling) { | |
5565 updateWrapperUpAndSideways(childWrapper); | |
5566 } | |
5567 updateWrapperDown(parentNodeWrapper); | |
5568 } | |
5569 | |
5570 function insertBefore(parentNodeWrapper, newChildWrapper, refChildWrapper) { | |
5571 var parentNode = unwrap(parentNodeWrapper); | |
5572 var newChild = unwrap(newChildWrapper); | |
5573 var refChild = refChildWrapper ? unwrap(refChildWrapper) : null; | |
5574 | |
5575 remove(newChildWrapper); | |
5576 updateWrapperUpAndSideways(newChildWrapper); | |
5577 | |
5578 if (!refChildWrapper) { | |
5579 parentNodeWrapper.lastChild_ = parentNodeWrapper.lastChild; | |
5580 if (parentNodeWrapper.lastChild === parentNodeWrapper.firstChild) | |
5581 parentNodeWrapper.firstChild_ = parentNodeWrapper.firstChild; | |
5582 | |
5583 var lastChildWrapper = wrap(parentNode.lastChild); | |
5584 if (lastChildWrapper) | |
5585 lastChildWrapper.nextSibling_ = lastChildWrapper.nextSibling; | |
5586 } else { | |
5587 if (parentNodeWrapper.firstChild === refChildWrapper) | |
5588 parentNodeWrapper.firstChild_ = refChildWrapper; | |
5589 | |
5590 refChildWrapper.previousSibling_ = refChildWrapper.previousSibling; | |
5591 } | |
5592 | |
5593 parentNode.insertBefore(newChild, refChild); | |
5594 } | |
5595 | |
5596 function remove(nodeWrapper) { | |
5597 var node = unwrap(nodeWrapper) | |
5598 var parentNode = node.parentNode; | |
5599 if (!parentNode) | |
5600 return; | |
5601 | |
5602 var parentNodeWrapper = wrap(parentNode); | |
5603 updateWrapperUpAndSideways(nodeWrapper); | |
5604 | |
5605 if (nodeWrapper.previousSibling) | |
5606 nodeWrapper.previousSibling.nextSibling_ = nodeWrapper; | |
5607 if (nodeWrapper.nextSibling) | |
5608 nodeWrapper.nextSibling.previousSibling_ = nodeWrapper; | |
5609 | |
5610 if (parentNodeWrapper.lastChild === nodeWrapper) | |
5611 parentNodeWrapper.lastChild_ = nodeWrapper; | |
5612 if (parentNodeWrapper.firstChild === nodeWrapper) | |
5613 parentNodeWrapper.firstChild_ = nodeWrapper; | |
5614 | |
5615 parentNode.removeChild(node); | |
5616 } | |
5617 | |
5618 var distributedChildNodesTable = new WeakMap(); | |
5619 var eventParentsTable = new WeakMap(); | |
5620 var insertionParentTable = new WeakMap(); | |
5621 var rendererForHostTable = new WeakMap(); | |
5622 | |
5623 function distributeChildToInsertionPoint(child, insertionPoint) { | |
5624 getDistributedChildNodes(insertionPoint).push(child); | |
5625 assignToInsertionPoint(child, insertionPoint); | |
5626 | |
5627 var eventParents = eventParentsTable.get(child); | |
5628 if (!eventParents) | |
5629 eventParentsTable.set(child, eventParents = []); | |
5630 eventParents.push(insertionPoint); | |
5631 } | |
5632 | |
5633 function resetDistributedChildNodes(insertionPoint) { | |
5634 distributedChildNodesTable.set(insertionPoint, []); | |
5635 } | |
5636 | |
5637 function getDistributedChildNodes(insertionPoint) { | |
5638 return distributedChildNodesTable.get(insertionPoint); | |
5639 } | |
5640 | |
5641 function getChildNodesSnapshot(node) { | |
5642 var result = [], i = 0; | |
5643 for (var child = node.firstChild; child; child = child.nextSibling) { | |
5644 result[i++] = child; | |
5645 } | |
5646 return result; | |
5647 } | |
5648 | |
5649 /** | |
5650 * Visits all nodes in the tree that fulfils the |predicate|. If the |visitor| | |
5651 * function returns |false| the traversal is aborted. | |
5652 * @param {!Node} tree | |
5653 * @param {function(!Node) : boolean} predicate | |
5654 * @param {function(!Node) : *} visitor | |
5655 */ | |
5656 function visit(tree, predicate, visitor) { | |
5657 // This operates on logical DOM. | |
5658 for (var node = tree.firstChild; node; node = node.nextSibling) { | |
5659 if (predicate(node)) { | |
5660 if (visitor(node) === false) | |
5661 return; | |
5662 } else { | |
5663 visit(node, predicate, visitor); | |
5664 } | |
5665 } | |
5666 } | |
5667 | |
5668 // Matching Insertion Points | |
5669 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#mat
ching-insertion-points | |
5670 | |
5671 // TODO(arv): Verify this... I don't remember why I picked this regexp. | |
5672 var selectorMatchRegExp = /^[*.:#[a-zA-Z_|]/; | |
5673 | |
5674 var allowedPseudoRegExp = new RegExp('^:(' + [ | |
5675 'link', | |
5676 'visited', | |
5677 'target', | |
5678 'enabled', | |
5679 'disabled', | |
5680 'checked', | |
5681 'indeterminate', | |
5682 'nth-child', | |
5683 'nth-last-child', | |
5684 'nth-of-type', | |
5685 'nth-last-of-type', | |
5686 'first-child', | |
5687 'last-child', | |
5688 'first-of-type', | |
5689 'last-of-type', | |
5690 'only-of-type', | |
5691 ].join('|') + ')'); | |
5692 | |
5693 | |
5694 /** | |
5695 * @param {Element} node | |
5696 * @oaram {Element} point The insertion point element. | |
5697 * @return {boolean} Whether the node matches the insertion point. | |
5698 */ | |
5699 function matchesCriteria(node, point) { | |
5700 var select = point.getAttribute('select'); | |
5701 if (!select) | |
5702 return true; | |
5703 | |
5704 // Here we know the select attribute is a non empty string. | |
5705 select = select.trim(); | |
5706 if (!select) | |
5707 return true; | |
5708 | |
5709 if (!(node instanceof Element)) | |
5710 return false; | |
5711 | |
5712 // The native matches function in IE9 does not correctly work with elements | |
5713 // that are not in the document. | |
5714 // TODO(arv): Implement matching in JS. | |
5715 // https://github.com/Polymer/ShadowDOM/issues/361 | |
5716 if (select === '*' || select === node.localName) | |
5717 return true; | |
5718 | |
5719 // TODO(arv): This does not seem right. Need to check for a simple selector. | |
5720 if (!selectorMatchRegExp.test(select)) | |
5721 return false; | |
5722 | |
5723 // TODO(arv): This no longer matches the spec. | |
5724 if (select[0] === ':' && !allowedPseudoRegExp.test(select)) | |
5725 return false; | |
5726 | |
5727 try { | |
5728 return node.matches(select); | |
5729 } catch (ex) { | |
5730 // Invalid selector. | |
5731 return false; | |
5732 } | |
5733 } | |
5734 | |
5735 var request = oneOf(window, [ | |
5736 'requestAnimationFrame', | |
5737 'mozRequestAnimationFrame', | |
5738 'webkitRequestAnimationFrame', | |
5739 'setTimeout' | |
5740 ]); | |
5741 | |
5742 var pendingDirtyRenderers = []; | |
5743 var renderTimer; | |
5744 | |
5745 function renderAllPending() { | |
5746 for (var i = 0; i < pendingDirtyRenderers.length; i++) { | |
5747 pendingDirtyRenderers[i].render(); | |
5748 } | |
5749 pendingDirtyRenderers = []; | |
5750 } | |
5751 | |
5752 function handleRequestAnimationFrame() { | |
5753 renderTimer = null; | |
5754 renderAllPending(); | |
5755 } | |
5756 | |
5757 /** | |
5758 * Returns existing shadow renderer for a host or creates it if it is needed. | |
5759 * @params {!Element} host | |
5760 * @return {!ShadowRenderer} | |
5761 */ | |
5762 function getRendererForHost(host) { | |
5763 var renderer = rendererForHostTable.get(host); | |
5764 if (!renderer) { | |
5765 renderer = new ShadowRenderer(host); | |
5766 rendererForHostTable.set(host, renderer); | |
5767 } | |
5768 return renderer; | |
5769 } | |
5770 | |
5771 function getShadowRootAncestor(node) { | |
5772 for (; node; node = node.parentNode) { | |
5773 if (node instanceof ShadowRoot) | |
5774 return node; | |
5775 } | |
5776 return null; | |
5777 } | |
5778 | |
5779 function getRendererForShadowRoot(shadowRoot) { | |
5780 return getRendererForHost(shadowRoot.host); | |
5781 } | |
5782 | |
5783 var spliceDiff = new ArraySplice(); | |
5784 spliceDiff.equals = function(renderNode, rawNode) { | |
5785 return unwrap(renderNode.node) === rawNode; | |
5786 }; | |
5787 | |
5788 /** | |
5789 * RenderNode is used as an in memory "render tree". When we render the | |
5790 * composed tree we create a tree of RenderNodes, then we diff this against | |
5791 * the real DOM tree and make minimal changes as needed. | |
5792 */ | |
5793 function RenderNode(node) { | |
5794 this.skip = false; | |
5795 this.node = node; | |
5796 this.childNodes = []; | |
5797 } | |
5798 | |
5799 RenderNode.prototype = { | |
5800 append: function(node) { | |
5801 var rv = new RenderNode(node); | |
5802 this.childNodes.push(rv); | |
5803 return rv; | |
5804 }, | |
5805 | |
5806 sync: function(opt_added) { | |
5807 if (this.skip) | |
5808 return; | |
5809 | |
5810 var nodeWrapper = this.node; | |
5811 // plain array of RenderNodes | |
5812 var newChildren = this.childNodes; | |
5813 // plain array of real nodes. | |
5814 var oldChildren = getChildNodesSnapshot(unwrap(nodeWrapper)); | |
5815 var added = opt_added || new WeakMap(); | |
5816 | |
5817 var splices = spliceDiff.calculateSplices(newChildren, oldChildren); | |
5818 | |
5819 var newIndex = 0, oldIndex = 0; | |
5820 var lastIndex = 0; | |
5821 for (var i = 0; i < splices.length; i++) { | |
5822 var splice = splices[i]; | |
5823 for (; lastIndex < splice.index; lastIndex++) { | |
5824 oldIndex++; | |
5825 newChildren[newIndex++].sync(added); | |
5826 } | |
5827 | |
5828 var removedCount = splice.removed.length; | |
5829 for (var j = 0; j < removedCount; j++) { | |
5830 var wrapper = wrap(oldChildren[oldIndex++]); | |
5831 if (!added.get(wrapper)) | |
5832 remove(wrapper); | |
5833 } | |
5834 | |
5835 var addedCount = splice.addedCount; | |
5836 var refNode = oldChildren[oldIndex] && wrap(oldChildren[oldIndex]); | |
5837 for (var j = 0; j < addedCount; j++) { | |
5838 var newChildRenderNode = newChildren[newIndex++]; | |
5839 var newChildWrapper = newChildRenderNode.node; | |
5840 insertBefore(nodeWrapper, newChildWrapper, refNode); | |
5841 | |
5842 // Keep track of added so that we do not remove the node after it | |
5843 // has been added. | |
5844 added.set(newChildWrapper, true); | |
5845 | |
5846 newChildRenderNode.sync(added); | |
5847 } | |
5848 | |
5849 lastIndex += addedCount; | |
5850 } | |
5851 | |
5852 for (var i = lastIndex; i < newChildren.length; i++) { | |
5853 newChildren[i].sync(added); | |
5854 } | |
5855 } | |
5856 }; | |
5857 | |
5858 function ShadowRenderer(host) { | |
5859 this.host = host; | |
5860 this.dirty = false; | |
5861 this.invalidateAttributes(); | |
5862 this.associateNode(host); | |
5863 } | |
5864 | |
5865 ShadowRenderer.prototype = { | |
5866 | |
5867 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#r
endering-shadow-trees | |
5868 render: function(opt_renderNode) { | |
5869 if (!this.dirty) | |
5870 return; | |
5871 | |
5872 this.invalidateAttributes(); | |
5873 this.treeComposition(); | |
5874 | |
5875 var host = this.host; | |
5876 var shadowRoot = host.shadowRoot; | |
5877 | |
5878 this.associateNode(host); | |
5879 var topMostRenderer = !renderNode; | |
5880 var renderNode = opt_renderNode || new RenderNode(host); | |
5881 | |
5882 for (var node = shadowRoot.firstChild; node; node = node.nextSibling) { | |
5883 this.renderNode(shadowRoot, renderNode, node, false); | |
5884 } | |
5885 | |
5886 if (topMostRenderer) | |
5887 renderNode.sync(); | |
5888 | |
5889 this.dirty = false; | |
5890 }, | |
5891 | |
5892 invalidate: function() { | |
5893 if (!this.dirty) { | |
5894 this.dirty = true; | |
5895 pendingDirtyRenderers.push(this); | |
5896 if (renderTimer) | |
5897 return; | |
5898 renderTimer = window[request](handleRequestAnimationFrame, 0); | |
5899 } | |
5900 }, | |
5901 | |
5902 renderNode: function(shadowRoot, renderNode, node, isNested) { | |
5903 if (isShadowHost(node)) { | |
5904 renderNode = renderNode.append(node); | |
5905 var renderer = getRendererForHost(node); | |
5906 renderer.dirty = true; // Need to rerender due to reprojection. | |
5907 renderer.render(renderNode); | |
5908 } else if (isInsertionPoint(node)) { | |
5909 this.renderInsertionPoint(shadowRoot, renderNode, node, isNested); | |
5910 } else if (isShadowInsertionPoint(node)) { | |
5911 this.renderShadowInsertionPoint(shadowRoot, renderNode, node); | |
5912 } else { | |
5913 this.renderAsAnyDomTree(shadowRoot, renderNode, node, isNested); | |
5914 } | |
5915 }, | |
5916 | |
5917 renderAsAnyDomTree: function(shadowRoot, renderNode, node, isNested) { | |
5918 renderNode = renderNode.append(node); | |
5919 | |
5920 if (isShadowHost(node)) { | |
5921 var renderer = getRendererForHost(node); | |
5922 renderNode.skip = !renderer.dirty; | |
5923 renderer.render(renderNode); | |
5924 } else { | |
5925 for (var child = node.firstChild; child; child = child.nextSibling) { | |
5926 this.renderNode(shadowRoot, renderNode, child, isNested); | |
5927 } | |
5928 } | |
5929 }, | |
5930 | |
5931 renderInsertionPoint: function(shadowRoot, renderNode, insertionPoint, | |
5932 isNested) { | |
5933 var distributedChildNodes = getDistributedChildNodes(insertionPoint); | |
5934 if (distributedChildNodes.length) { | |
5935 this.associateNode(insertionPoint); | |
5936 | |
5937 for (var i = 0; i < distributedChildNodes.length; i++) { | |
5938 var child = distributedChildNodes[i]; | |
5939 if (isInsertionPoint(child) && isNested) | |
5940 this.renderInsertionPoint(shadowRoot, renderNode, child, isNested); | |
5941 else | |
5942 this.renderAsAnyDomTree(shadowRoot, renderNode, child, isNested); | |
5943 } | |
5944 } else { | |
5945 this.renderFallbackContent(shadowRoot, renderNode, insertionPoint); | |
5946 } | |
5947 this.associateNode(insertionPoint.parentNode); | |
5948 }, | |
5949 | |
5950 renderShadowInsertionPoint: function(shadowRoot, renderNode, | |
5951 shadowInsertionPoint) { | |
5952 var nextOlderTree = shadowRoot.olderShadowRoot; | |
5953 if (nextOlderTree) { | |
5954 assignToInsertionPoint(nextOlderTree, shadowInsertionPoint); | |
5955 this.associateNode(shadowInsertionPoint.parentNode); | |
5956 for (var node = nextOlderTree.firstChild; | |
5957 node; | |
5958 node = node.nextSibling) { | |
5959 this.renderNode(nextOlderTree, renderNode, node, true); | |
5960 } | |
5961 } else { | |
5962 this.renderFallbackContent(shadowRoot, renderNode, | |
5963 shadowInsertionPoint); | |
5964 } | |
5965 }, | |
5966 | |
5967 renderFallbackContent: function(shadowRoot, renderNode, fallbackHost) { | |
5968 this.associateNode(fallbackHost); | |
5969 this.associateNode(fallbackHost.parentNode); | |
5970 for (var node = fallbackHost.firstChild; node; node = node.nextSibling) { | |
5971 this.renderAsAnyDomTree(shadowRoot, renderNode, node, false); | |
5972 } | |
5973 }, | |
5974 | |
5975 /** | |
5976 * Invalidates the attributes used to keep track of which attributes may | |
5977 * cause the renderer to be invalidated. | |
5978 */ | |
5979 invalidateAttributes: function() { | |
5980 this.attributes = Object.create(null); | |
5981 }, | |
5982 | |
5983 /** | |
5984 * Parses the selector and makes this renderer dependent on the attribute | |
5985 * being used in the selector. | |
5986 * @param {string} selector | |
5987 */ | |
5988 updateDependentAttributes: function(selector) { | |
5989 if (!selector) | |
5990 return; | |
5991 | |
5992 var attributes = this.attributes; | |
5993 | |
5994 // .class | |
5995 if (/\.\w+/.test(selector)) | |
5996 attributes['class'] = true; | |
5997 | |
5998 // #id | |
5999 if (/#\w+/.test(selector)) | |
6000 attributes['id'] = true; | |
6001 | |
6002 selector.replace(/\[\s*([^\s=\|~\]]+)/g, function(_, name) { | |
6003 attributes[name] = true; | |
6004 }); | |
6005 | |
6006 // Pseudo selectors have been removed from the spec. | |
6007 }, | |
6008 | |
6009 dependsOnAttribute: function(name) { | |
6010 return this.attributes[name]; | |
6011 }, | |
6012 | |
6013 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#d
fn-distribution-algorithm | |
6014 distribute: function(tree, pool) { | |
6015 var self = this; | |
6016 | |
6017 visit(tree, isActiveInsertionPoint, | |
6018 function(insertionPoint) { | |
6019 resetDistributedChildNodes(insertionPoint); | |
6020 self.updateDependentAttributes( | |
6021 insertionPoint.getAttribute('select')); | |
6022 | |
6023 for (var i = 0; i < pool.length; i++) { // 1.2 | |
6024 var node = pool[i]; // 1.2.1 | |
6025 if (node === undefined) // removed | |
6026 continue; | |
6027 if (matchesCriteria(node, insertionPoint)) { // 1.2.2 | |
6028 distributeChildToInsertionPoint(node, insertionPoint); // 1.2.2
.1 | |
6029 pool[i] = undefined; // 1.2.2.2 | |
6030 } | |
6031 } | |
6032 }); | |
6033 }, | |
6034 | |
6035 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#d
fn-tree-composition | |
6036 treeComposition: function () { | |
6037 var shadowHost = this.host; | |
6038 var tree = shadowHost.shadowRoot; // 1. | |
6039 var pool = []; // 2. | |
6040 | |
6041 for (var child = shadowHost.firstChild; | |
6042 child; | |
6043 child = child.nextSibling) { // 3. | |
6044 if (isInsertionPoint(child)) { // 3.2. | |
6045 var reprojected = getDistributedChildNodes(child); // 3.2.1. | |
6046 // if reprojected is undef... reset it? | |
6047 if (!reprojected || !reprojected.length) // 3.2.2. | |
6048 reprojected = getChildNodesSnapshot(child); | |
6049 pool.push.apply(pool, reprojected); // 3.2.3. | |
6050 } else { | |
6051 pool.push(child); // 3.3. | |
6052 } | |
6053 } | |
6054 | |
6055 var shadowInsertionPoint, point; | |
6056 while (tree) { // 4. | |
6057 // 4.1. | |
6058 shadowInsertionPoint = undefined; // Reset every iteration. | |
6059 visit(tree, isActiveShadowInsertionPoint, function(point) { | |
6060 shadowInsertionPoint = point; | |
6061 return false; | |
6062 }); | |
6063 point = shadowInsertionPoint; | |
6064 | |
6065 this.distribute(tree, pool); // 4.2. | |
6066 if (point) { // 4.3. | |
6067 var nextOlderTree = tree.olderShadowRoot; // 4.3.1. | |
6068 if (!nextOlderTree) { | |
6069 break; // 4.3.1.1. | |
6070 } else { | |
6071 tree = nextOlderTree; // 4.3.2.2. | |
6072 assignToInsertionPoint(tree, point); // 4.3.2.2. | |
6073 continue; // 4.3.2.3. | |
6074 } | |
6075 } else { | |
6076 break; // 4.4. | |
6077 } | |
6078 } | |
6079 }, | |
6080 | |
6081 associateNode: function(node) { | |
6082 node.impl.polymerShadowRenderer_ = this; | |
6083 } | |
6084 }; | |
6085 | |
6086 function isInsertionPoint(node) { | |
6087 // Should this include <shadow>? | |
6088 return node instanceof HTMLContentElement; | |
6089 } | |
6090 | |
6091 function isActiveInsertionPoint(node) { | |
6092 // <content> inside another <content> or <shadow> is considered inactive. | |
6093 return node instanceof HTMLContentElement; | |
6094 } | |
6095 | |
6096 function isShadowInsertionPoint(node) { | |
6097 return node instanceof HTMLShadowElement; | |
6098 } | |
6099 | |
6100 function isActiveShadowInsertionPoint(node) { | |
6101 // <shadow> inside another <content> or <shadow> is considered inactive. | |
6102 return node instanceof HTMLShadowElement; | |
6103 } | |
6104 | |
6105 function isShadowHost(shadowHost) { | |
6106 return shadowHost.shadowRoot; | |
6107 } | |
6108 | |
6109 function getShadowTrees(host) { | |
6110 var trees = []; | |
6111 | |
6112 for (var tree = host.shadowRoot; tree; tree = tree.olderShadowRoot) { | |
6113 trees.push(tree); | |
6114 } | |
6115 return trees; | |
6116 } | |
6117 | |
6118 function assignToInsertionPoint(tree, point) { | |
6119 insertionParentTable.set(tree, point); | |
6120 } | |
6121 | |
6122 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#ren
dering-shadow-trees | |
6123 function render(host) { | |
6124 new ShadowRenderer(host).render(); | |
6125 }; | |
6126 | |
6127 // Need to rerender shadow host when: | |
6128 // | |
6129 // - a direct child to the ShadowRoot is added or removed | |
6130 // - a direct child to the host is added or removed | |
6131 // - a new shadow root is created | |
6132 // - a direct child to a content/shadow element is added or removed | |
6133 // - a sibling to a content/shadow element is added or removed | |
6134 // - content[select] is changed | |
6135 // - an attribute in a direct child to a host is modified | |
6136 | |
6137 /** | |
6138 * This gets called when a node was added or removed to it. | |
6139 */ | |
6140 Node.prototype.invalidateShadowRenderer = function(force) { | |
6141 var renderer = this.impl.polymerShadowRenderer_; | |
6142 if (renderer) { | |
6143 renderer.invalidate(); | |
6144 return true; | |
6145 } | |
6146 | |
6147 return false; | |
6148 }; | |
6149 | |
6150 HTMLContentElement.prototype.getDistributedNodes = function() { | |
6151 // TODO(arv): We should only rerender the dirty ancestor renderers (from | |
6152 // the root and down). | |
6153 renderAllPending(); | |
6154 return getDistributedChildNodes(this); | |
6155 }; | |
6156 | |
6157 HTMLShadowElement.prototype.nodeIsInserted_ = | |
6158 HTMLContentElement.prototype.nodeIsInserted_ = function() { | |
6159 // Invalidate old renderer if any. | |
6160 this.invalidateShadowRenderer(); | |
6161 | |
6162 var shadowRoot = getShadowRootAncestor(this); | |
6163 var renderer; | |
6164 if (shadowRoot) | |
6165 renderer = getRendererForShadowRoot(shadowRoot); | |
6166 this.impl.polymerShadowRenderer_ = renderer; | |
6167 if (renderer) | |
6168 renderer.invalidate(); | |
6169 }; | |
6170 | |
6171 scope.eventParentsTable = eventParentsTable; | |
6172 scope.getRendererForHost = getRendererForHost; | |
6173 scope.getShadowTrees = getShadowTrees; | |
6174 scope.insertionParentTable = insertionParentTable; | |
6175 scope.renderAllPending = renderAllPending; | |
6176 | |
6177 // Exposed for testing | |
6178 scope.visual = { | |
6179 insertBefore: insertBefore, | |
6180 remove: remove, | |
6181 }; | |
6182 | |
6183 })(window.ShadowDOMPolyfill); | |
6184 | |
6185 // Copyright 2013 The Polymer Authors. All rights reserved. | |
6186 // Use of this source code is goverened by a BSD-style | |
6187 // license that can be found in the LICENSE file. | |
6188 | |
6189 (function(scope) { | |
6190 'use strict'; | |
6191 | |
6192 var HTMLElement = scope.wrappers.HTMLElement; | |
6193 var assert = scope.assert; | |
6194 var mixin = scope.mixin; | |
6195 var registerWrapper = scope.registerWrapper; | |
6196 var unwrap = scope.unwrap; | |
6197 var wrap = scope.wrap; | |
6198 | |
6199 var elementsWithFormProperty = [ | |
6200 'HTMLButtonElement', | |
6201 'HTMLFieldSetElement', | |
6202 'HTMLInputElement', | |
6203 'HTMLKeygenElement', | |
6204 'HTMLLabelElement', | |
6205 'HTMLLegendElement', | |
6206 'HTMLObjectElement', | |
6207 // HTMLOptionElement is handled in HTMLOptionElement.js | |
6208 'HTMLOutputElement', | |
6209 'HTMLSelectElement', | |
6210 'HTMLTextAreaElement', | |
6211 ]; | |
6212 | |
6213 function createWrapperConstructor(name) { | |
6214 if (!window[name]) | |
6215 return; | |
6216 | |
6217 // Ensure we are not overriding an already existing constructor. | |
6218 assert(!scope.wrappers[name]); | |
6219 | |
6220 var GeneratedWrapper = function(node) { | |
6221 // At this point all of them extend HTMLElement. | |
6222 HTMLElement.call(this, node); | |
6223 } | |
6224 GeneratedWrapper.prototype = Object.create(HTMLElement.prototype); | |
6225 mixin(GeneratedWrapper.prototype, { | |
6226 get form() { | |
6227 return wrap(unwrap(this).form); | |
6228 }, | |
6229 }); | |
6230 | |
6231 registerWrapper(window[name], GeneratedWrapper, | |
6232 document.createElement(name.slice(4, -7))); | |
6233 scope.wrappers[name] = GeneratedWrapper; | |
6234 } | |
6235 | |
6236 elementsWithFormProperty.forEach(createWrapperConstructor); | |
6237 | |
6238 })(window.ShadowDOMPolyfill); | |
6239 | |
6240 // Copyright 2014 The Polymer Authors. All rights reserved. | |
6241 // Use of this source code is goverened by a BSD-style | |
6242 // license that can be found in the LICENSE file. | |
6243 | |
6244 (function(scope) { | |
6245 'use strict'; | |
6246 | |
6247 var registerWrapper = scope.registerWrapper; | |
6248 var unwrap = scope.unwrap; | |
6249 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
6250 var wrap = scope.wrap; | |
6251 | |
6252 var OriginalSelection = window.Selection; | |
6253 | |
6254 function Selection(impl) { | |
6255 this.impl = impl; | |
6256 } | |
6257 Selection.prototype = { | |
6258 get anchorNode() { | |
6259 return wrap(this.impl.anchorNode); | |
6260 }, | |
6261 get focusNode() { | |
6262 return wrap(this.impl.focusNode); | |
6263 }, | |
6264 addRange: function(range) { | |
6265 this.impl.addRange(unwrap(range)); | |
6266 }, | |
6267 collapse: function(node, index) { | |
6268 this.impl.collapse(unwrapIfNeeded(node), index); | |
6269 }, | |
6270 containsNode: function(node, allowPartial) { | |
6271 return this.impl.containsNode(unwrapIfNeeded(node), allowPartial); | |
6272 }, | |
6273 extend: function(node, offset) { | |
6274 this.impl.extend(unwrapIfNeeded(node), offset); | |
6275 }, | |
6276 getRangeAt: function(index) { | |
6277 return wrap(this.impl.getRangeAt(index)); | |
6278 }, | |
6279 removeRange: function(range) { | |
6280 this.impl.removeRange(unwrap(range)); | |
6281 }, | |
6282 selectAllChildren: function(node) { | |
6283 this.impl.selectAllChildren(unwrapIfNeeded(node)); | |
6284 }, | |
6285 toString: function() { | |
6286 return this.impl.toString(); | |
6287 } | |
6288 }; | |
6289 | |
6290 // WebKit extensions. Not implemented. | |
6291 // readonly attribute Node baseNode; | |
6292 // readonly attribute long baseOffset; | |
6293 // readonly attribute Node extentNode; | |
6294 // readonly attribute long extentOffset; | |
6295 // [RaisesException] void setBaseAndExtent([Default=Undefined] optional Node b
aseNode, | |
6296 // [Default=Undefined] optional long baseOffset, | |
6297 // [Default=Undefined] optional Node extentNode, | |
6298 // [Default=Undefined] optional long extentOffset); | |
6299 // [RaisesException, ImplementedAs=collapse] void setPosition([Default=Undefin
ed] optional Node node, | |
6300 // [Default=Undefined] optional long offset); | |
6301 | |
6302 registerWrapper(window.Selection, Selection, window.getSelection()); | |
6303 | |
6304 scope.wrappers.Selection = Selection; | |
6305 | |
6306 })(window.ShadowDOMPolyfill); | |
6307 | |
6308 // Copyright 2013 The Polymer Authors. All rights reserved. | |
6309 // Use of this source code is goverened by a BSD-style | |
6310 // license that can be found in the LICENSE file. | |
6311 | |
6312 (function(scope) { | |
6313 'use strict'; | |
6314 | |
6315 var GetElementsByInterface = scope.GetElementsByInterface; | |
6316 var Node = scope.wrappers.Node; | |
6317 var ParentNodeInterface = scope.ParentNodeInterface; | |
6318 var Selection = scope.wrappers.Selection; | |
6319 var SelectorsInterface = scope.SelectorsInterface; | |
6320 var ShadowRoot = scope.wrappers.ShadowRoot; | |
6321 var defineWrapGetter = scope.defineWrapGetter; | |
6322 var elementFromPoint = scope.elementFromPoint; | |
6323 var forwardMethodsToWrapper = scope.forwardMethodsToWrapper; | |
6324 var matchesNames = scope.matchesNames; | |
6325 var mixin = scope.mixin; | |
6326 var registerWrapper = scope.registerWrapper; | |
6327 var renderAllPending = scope.renderAllPending; | |
6328 var rewrap = scope.rewrap; | |
6329 var unwrap = scope.unwrap; | |
6330 var wrap = scope.wrap; | |
6331 var wrapEventTargetMethods = scope.wrapEventTargetMethods; | |
6332 var wrapNodeList = scope.wrapNodeList; | |
6333 | |
6334 var implementationTable = new WeakMap(); | |
6335 | |
6336 function Document(node) { | |
6337 Node.call(this, node); | |
6338 } | |
6339 Document.prototype = Object.create(Node.prototype); | |
6340 | |
6341 defineWrapGetter(Document, 'documentElement'); | |
6342 | |
6343 // Conceptually both body and head can be in a shadow but suporting that seems | |
6344 // overkill at this point. | |
6345 defineWrapGetter(Document, 'body'); | |
6346 defineWrapGetter(Document, 'head'); | |
6347 | |
6348 // document cannot be overridden so we override a bunch of its methods | |
6349 // directly on the instance. | |
6350 | |
6351 function wrapMethod(name) { | |
6352 var original = document[name]; | |
6353 Document.prototype[name] = function() { | |
6354 return wrap(original.apply(this.impl, arguments)); | |
6355 }; | |
6356 } | |
6357 | |
6358 [ | |
6359 'createComment', | |
6360 'createDocumentFragment', | |
6361 'createElement', | |
6362 'createElementNS', | |
6363 'createEvent', | |
6364 'createEventNS', | |
6365 'createRange', | |
6366 'createTextNode', | |
6367 'getElementById' | |
6368 ].forEach(wrapMethod); | |
6369 | |
6370 var originalAdoptNode = document.adoptNode; | |
6371 | |
6372 function adoptNodeNoRemove(node, doc) { | |
6373 originalAdoptNode.call(doc.impl, unwrap(node)); | |
6374 adoptSubtree(node, doc); | |
6375 } | |
6376 | |
6377 function adoptSubtree(node, doc) { | |
6378 if (node.shadowRoot) | |
6379 doc.adoptNode(node.shadowRoot); | |
6380 if (node instanceof ShadowRoot) | |
6381 adoptOlderShadowRoots(node, doc); | |
6382 for (var child = node.firstChild; child; child = child.nextSibling) { | |
6383 adoptSubtree(child, doc); | |
6384 } | |
6385 } | |
6386 | |
6387 function adoptOlderShadowRoots(shadowRoot, doc) { | |
6388 var oldShadowRoot = shadowRoot.olderShadowRoot; | |
6389 if (oldShadowRoot) | |
6390 doc.adoptNode(oldShadowRoot); | |
6391 } | |
6392 | |
6393 var originalImportNode = document.importNode; | |
6394 var originalGetSelection = document.getSelection; | |
6395 | |
6396 mixin(Document.prototype, { | |
6397 adoptNode: function(node) { | |
6398 if (node.parentNode) | |
6399 node.parentNode.removeChild(node); | |
6400 adoptNodeNoRemove(node, this); | |
6401 return node; | |
6402 }, | |
6403 elementFromPoint: function(x, y) { | |
6404 return elementFromPoint(this, this, x, y); | |
6405 }, | |
6406 importNode: function(node, deep) { | |
6407 // We need to manually walk the tree to ensure we do not include rendered | |
6408 // shadow trees. | |
6409 var clone = wrap(originalImportNode.call(this.impl, unwrap(node), false)); | |
6410 if (deep) { | |
6411 for (var child = node.firstChild; child; child = child.nextSibling) { | |
6412 clone.appendChild(this.importNode(child, true)); | |
6413 } | |
6414 } | |
6415 return clone; | |
6416 }, | |
6417 getSelection: function() { | |
6418 renderAllPending(); | |
6419 return new Selection(originalGetSelection.call(unwrap(this))); | |
6420 } | |
6421 }); | |
6422 | |
6423 if (document.registerElement) { | |
6424 var originalRegisterElement = document.registerElement; | |
6425 Document.prototype.registerElement = function(tagName, object) { | |
6426 var prototype = object.prototype; | |
6427 | |
6428 // If we already used the object as a prototype for another custom | |
6429 // element. | |
6430 if (scope.nativePrototypeTable.get(prototype)) { | |
6431 // TODO(arv): DOMException | |
6432 throw new Error('NotSupportedError'); | |
6433 } | |
6434 | |
6435 // Find first object on the prototype chain that already have a native | |
6436 // prototype. Keep track of all the objects before that so we can create | |
6437 // a similar structure for the native case. | |
6438 var proto = Object.getPrototypeOf(prototype); | |
6439 var nativePrototype; | |
6440 var prototypes = []; | |
6441 while (proto) { | |
6442 nativePrototype = scope.nativePrototypeTable.get(proto); | |
6443 if (nativePrototype) | |
6444 break; | |
6445 prototypes.push(proto); | |
6446 proto = Object.getPrototypeOf(proto); | |
6447 } | |
6448 | |
6449 if (!nativePrototype) { | |
6450 // TODO(arv): DOMException | |
6451 throw new Error('NotSupportedError'); | |
6452 } | |
6453 | |
6454 // This works by creating a new prototype object that is empty, but has | |
6455 // the native prototype as its proto. The original prototype object | |
6456 // passed into register is used as the wrapper prototype. | |
6457 | |
6458 var newPrototype = Object.create(nativePrototype); | |
6459 for (var i = prototypes.length - 1; i >= 0; i--) { | |
6460 newPrototype = Object.create(newPrototype); | |
6461 } | |
6462 | |
6463 // Add callbacks if present. | |
6464 // Names are taken from: | |
6465 // https://code.google.com/p/chromium/codesearch#chromium/src/third_part
y/WebKit/Source/bindings/v8/CustomElementConstructorBuilder.cpp&sq=package:chrom
ium&type=cs&l=156 | |
6466 // and not from the spec since the spec is out of date. | |
6467 [ | |
6468 'createdCallback', | |
6469 'attachedCallback', | |
6470 'detachedCallback', | |
6471 'attributeChangedCallback', | |
6472 ].forEach(function(name) { | |
6473 var f = prototype[name]; | |
6474 if (!f) | |
6475 return; | |
6476 newPrototype[name] = function() { | |
6477 // if this element has been wrapped prior to registration, | |
6478 // the wrapper is stale; in this case rewrap | |
6479 if (!(wrap(this) instanceof CustomElementConstructor)) { | |
6480 rewrap(this); | |
6481 } | |
6482 f.apply(wrap(this), arguments); | |
6483 }; | |
6484 }); | |
6485 | |
6486 var p = {prototype: newPrototype}; | |
6487 if (object.extends) | |
6488 p.extends = object.extends; | |
6489 | |
6490 function CustomElementConstructor(node) { | |
6491 if (!node) { | |
6492 if (object.extends) { | |
6493 return document.createElement(object.extends, tagName); | |
6494 } else { | |
6495 return document.createElement(tagName); | |
6496 } | |
6497 } | |
6498 this.impl = node; | |
6499 } | |
6500 CustomElementConstructor.prototype = prototype; | |
6501 CustomElementConstructor.prototype.constructor = CustomElementConstructor; | |
6502 | |
6503 scope.constructorTable.set(newPrototype, CustomElementConstructor); | |
6504 scope.nativePrototypeTable.set(prototype, newPrototype); | |
6505 | |
6506 // registration is synchronous so do it last | |
6507 var nativeConstructor = originalRegisterElement.call(unwrap(this), | |
6508 tagName, p); | |
6509 return CustomElementConstructor; | |
6510 }; | |
6511 | |
6512 forwardMethodsToWrapper([ | |
6513 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocume
nt | |
6514 ], [ | |
6515 'registerElement', | |
6516 ]); | |
6517 } | |
6518 | |
6519 // We also override some of the methods on document.body and document.head | |
6520 // for convenience. | |
6521 forwardMethodsToWrapper([ | |
6522 window.HTMLBodyElement, | |
6523 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument | |
6524 window.HTMLHeadElement, | |
6525 window.HTMLHtmlElement, | |
6526 ], [ | |
6527 'appendChild', | |
6528 'compareDocumentPosition', | |
6529 'contains', | |
6530 'getElementsByClassName', | |
6531 'getElementsByTagName', | |
6532 'getElementsByTagNameNS', | |
6533 'insertBefore', | |
6534 'querySelector', | |
6535 'querySelectorAll', | |
6536 'removeChild', | |
6537 'replaceChild', | |
6538 ].concat(matchesNames)); | |
6539 | |
6540 forwardMethodsToWrapper([ | |
6541 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument | |
6542 ], [ | |
6543 'adoptNode', | |
6544 'importNode', | |
6545 'contains', | |
6546 'createComment', | |
6547 'createDocumentFragment', | |
6548 'createElement', | |
6549 'createElementNS', | |
6550 'createEvent', | |
6551 'createEventNS', | |
6552 'createRange', | |
6553 'createTextNode', | |
6554 'elementFromPoint', | |
6555 'getElementById', | |
6556 'getSelection', | |
6557 ]); | |
6558 | |
6559 mixin(Document.prototype, GetElementsByInterface); | |
6560 mixin(Document.prototype, ParentNodeInterface); | |
6561 mixin(Document.prototype, SelectorsInterface); | |
6562 | |
6563 mixin(Document.prototype, { | |
6564 get implementation() { | |
6565 var implementation = implementationTable.get(this); | |
6566 if (implementation) | |
6567 return implementation; | |
6568 implementation = | |
6569 new DOMImplementation(unwrap(this).implementation); | |
6570 implementationTable.set(this, implementation); | |
6571 return implementation; | |
6572 } | |
6573 }); | |
6574 | |
6575 registerWrapper(window.Document, Document, | |
6576 document.implementation.createHTMLDocument('')); | |
6577 | |
6578 // Both WebKit and Gecko uses HTMLDocument for document. HTML5/DOM only has | |
6579 // one Document interface and IE implements the standard correctly. | |
6580 if (window.HTMLDocument) | |
6581 registerWrapper(window.HTMLDocument, Document); | |
6582 | |
6583 wrapEventTargetMethods([ | |
6584 window.HTMLBodyElement, | |
6585 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument | |
6586 window.HTMLHeadElement, | |
6587 ]); | |
6588 | |
6589 function DOMImplementation(impl) { | |
6590 this.impl = impl; | |
6591 } | |
6592 | |
6593 function wrapImplMethod(constructor, name) { | |
6594 var original = document.implementation[name]; | |
6595 constructor.prototype[name] = function() { | |
6596 return wrap(original.apply(this.impl, arguments)); | |
6597 }; | |
6598 } | |
6599 | |
6600 function forwardImplMethod(constructor, name) { | |
6601 var original = document.implementation[name]; | |
6602 constructor.prototype[name] = function() { | |
6603 return original.apply(this.impl, arguments); | |
6604 }; | |
6605 } | |
6606 | |
6607 wrapImplMethod(DOMImplementation, 'createDocumentType'); | |
6608 wrapImplMethod(DOMImplementation, 'createDocument'); | |
6609 wrapImplMethod(DOMImplementation, 'createHTMLDocument'); | |
6610 forwardImplMethod(DOMImplementation, 'hasFeature'); | |
6611 | |
6612 registerWrapper(window.DOMImplementation, DOMImplementation); | |
6613 | |
6614 forwardMethodsToWrapper([ | |
6615 window.DOMImplementation, | |
6616 ], [ | |
6617 'createDocumentType', | |
6618 'createDocument', | |
6619 'createHTMLDocument', | |
6620 'hasFeature', | |
6621 ]); | |
6622 | |
6623 scope.adoptNodeNoRemove = adoptNodeNoRemove; | |
6624 scope.wrappers.DOMImplementation = DOMImplementation; | |
6625 scope.wrappers.Document = Document; | |
6626 | |
6627 })(window.ShadowDOMPolyfill); | |
6628 | |
6629 // Copyright 2013 The Polymer Authors. All rights reserved. | |
6630 // Use of this source code is goverened by a BSD-style | |
6631 // license that can be found in the LICENSE file. | |
6632 | |
6633 (function(scope) { | |
6634 'use strict'; | |
6635 | |
6636 var EventTarget = scope.wrappers.EventTarget; | |
6637 var Selection = scope.wrappers.Selection; | |
6638 var mixin = scope.mixin; | |
6639 var registerWrapper = scope.registerWrapper; | |
6640 var renderAllPending = scope.renderAllPending; | |
6641 var unwrap = scope.unwrap; | |
6642 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
6643 var wrap = scope.wrap; | |
6644 | |
6645 var OriginalWindow = window.Window; | |
6646 var originalGetComputedStyle = window.getComputedStyle; | |
6647 var originalGetSelection = window.getSelection; | |
6648 | |
6649 function Window(impl) { | |
6650 EventTarget.call(this, impl); | |
6651 } | |
6652 Window.prototype = Object.create(EventTarget.prototype); | |
6653 | |
6654 OriginalWindow.prototype.getComputedStyle = function(el, pseudo) { | |
6655 return wrap(this || window).getComputedStyle(unwrapIfNeeded(el), pseudo); | |
6656 }; | |
6657 | |
6658 OriginalWindow.prototype.getSelection = function() { | |
6659 return wrap(this || window).getSelection(); | |
6660 }; | |
6661 | |
6662 // Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065 | |
6663 delete window.getComputedStyle; | |
6664 delete window.getSelection; | |
6665 | |
6666 ['addEventListener', 'removeEventListener', 'dispatchEvent'].forEach( | |
6667 function(name) { | |
6668 OriginalWindow.prototype[name] = function() { | |
6669 var w = wrap(this || window); | |
6670 return w[name].apply(w, arguments); | |
6671 }; | |
6672 | |
6673 // Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065 | |
6674 delete window[name]; | |
6675 }); | |
6676 | |
6677 mixin(Window.prototype, { | |
6678 getComputedStyle: function(el, pseudo) { | |
6679 renderAllPending(); | |
6680 return originalGetComputedStyle.call(unwrap(this), unwrapIfNeeded(el), | |
6681 pseudo); | |
6682 }, | |
6683 getSelection: function() { | |
6684 renderAllPending(); | |
6685 return new Selection(originalGetSelection.call(unwrap(this))); | |
6686 }, | |
6687 }); | |
6688 | |
6689 registerWrapper(OriginalWindow, Window); | |
6690 | |
6691 scope.wrappers.Window = Window; | |
6692 | |
6693 })(window.ShadowDOMPolyfill); | |
6694 | |
6695 // Copyright 2013 The Polymer Authors. All rights reserved. | |
6696 // Use of this source code is goverened by a BSD-style | |
6697 // license that can be found in the LICENSE file. | |
6698 | |
6699 (function(scope) { | |
6700 'use strict'; | |
6701 | |
6702 var isWrapperFor = scope.isWrapperFor; | |
6703 | |
6704 // This is a list of the elements we currently override the global constructor | |
6705 // for. | |
6706 var elements = { | |
6707 'a': 'HTMLAnchorElement', | |
6708 | |
6709 // Do not create an applet element by default since it shows a warning in | |
6710 // IE. | |
6711 // https://github.com/Polymer/polymer/issues/217 | |
6712 // 'applet': 'HTMLAppletElement', | |
6713 | |
6714 'area': 'HTMLAreaElement', | |
6715 'br': 'HTMLBRElement', | |
6716 'base': 'HTMLBaseElement', | |
6717 'body': 'HTMLBodyElement', | |
6718 'button': 'HTMLButtonElement', | |
6719 // 'command': 'HTMLCommandElement', // Not fully implemented in Gecko. | |
6720 'dl': 'HTMLDListElement', | |
6721 'datalist': 'HTMLDataListElement', | |
6722 'data': 'HTMLDataElement', | |
6723 'dir': 'HTMLDirectoryElement', | |
6724 'div': 'HTMLDivElement', | |
6725 'embed': 'HTMLEmbedElement', | |
6726 'fieldset': 'HTMLFieldSetElement', | |
6727 'font': 'HTMLFontElement', | |
6728 'form': 'HTMLFormElement', | |
6729 'frame': 'HTMLFrameElement', | |
6730 'frameset': 'HTMLFrameSetElement', | |
6731 'hr': 'HTMLHRElement', | |
6732 'head': 'HTMLHeadElement', | |
6733 'h1': 'HTMLHeadingElement', | |
6734 'html': 'HTMLHtmlElement', | |
6735 'iframe': 'HTMLIFrameElement', | |
6736 'input': 'HTMLInputElement', | |
6737 'li': 'HTMLLIElement', | |
6738 'label': 'HTMLLabelElement', | |
6739 'legend': 'HTMLLegendElement', | |
6740 'link': 'HTMLLinkElement', | |
6741 'map': 'HTMLMapElement', | |
6742 'marquee': 'HTMLMarqueeElement', | |
6743 'menu': 'HTMLMenuElement', | |
6744 'menuitem': 'HTMLMenuItemElement', | |
6745 'meta': 'HTMLMetaElement', | |
6746 'meter': 'HTMLMeterElement', | |
6747 'del': 'HTMLModElement', | |
6748 'ol': 'HTMLOListElement', | |
6749 'object': 'HTMLObjectElement', | |
6750 'optgroup': 'HTMLOptGroupElement', | |
6751 'option': 'HTMLOptionElement', | |
6752 'output': 'HTMLOutputElement', | |
6753 'p': 'HTMLParagraphElement', | |
6754 'param': 'HTMLParamElement', | |
6755 'pre': 'HTMLPreElement', | |
6756 'progress': 'HTMLProgressElement', | |
6757 'q': 'HTMLQuoteElement', | |
6758 'script': 'HTMLScriptElement', | |
6759 'select': 'HTMLSelectElement', | |
6760 'source': 'HTMLSourceElement', | |
6761 'span': 'HTMLSpanElement', | |
6762 'style': 'HTMLStyleElement', | |
6763 'time': 'HTMLTimeElement', | |
6764 'caption': 'HTMLTableCaptionElement', | |
6765 // WebKit and Moz are wrong: | |
6766 // https://bugs.webkit.org/show_bug.cgi?id=111469 | |
6767 // https://bugzilla.mozilla.org/show_bug.cgi?id=848096 | |
6768 // 'td': 'HTMLTableCellElement', | |
6769 'col': 'HTMLTableColElement', | |
6770 'table': 'HTMLTableElement', | |
6771 'tr': 'HTMLTableRowElement', | |
6772 'thead': 'HTMLTableSectionElement', | |
6773 'tbody': 'HTMLTableSectionElement', | |
6774 'textarea': 'HTMLTextAreaElement', | |
6775 'track': 'HTMLTrackElement', | |
6776 'title': 'HTMLTitleElement', | |
6777 'ul': 'HTMLUListElement', | |
6778 'video': 'HTMLVideoElement', | |
6779 }; | |
6780 | |
6781 function overrideConstructor(tagName) { | |
6782 var nativeConstructorName = elements[tagName]; | |
6783 var nativeConstructor = window[nativeConstructorName]; | |
6784 if (!nativeConstructor) | |
6785 return; | |
6786 var element = document.createElement(tagName); | |
6787 var wrapperConstructor = element.constructor; | |
6788 window[nativeConstructorName] = wrapperConstructor; | |
6789 } | |
6790 | |
6791 Object.keys(elements).forEach(overrideConstructor); | |
6792 | |
6793 Object.getOwnPropertyNames(scope.wrappers).forEach(function(name) { | |
6794 window[name] = scope.wrappers[name] | |
6795 }); | |
6796 | |
6797 // Export for testing. | |
6798 scope.knownElements = elements; | |
6799 | |
6800 })(window.ShadowDOMPolyfill); | |
6801 | |
6802 /* | |
6803 * Copyright 2013 The Polymer Authors. All rights reserved. | |
6804 * Use of this source code is governed by a BSD-style | |
6805 * license that can be found in the LICENSE file. | |
6806 */ | |
6807 (function() { | |
6808 var ShadowDOMPolyfill = window.ShadowDOMPolyfill; | |
6809 var wrap = ShadowDOMPolyfill.wrap; | |
6810 | |
6811 // patch in prefixed name | |
6812 Object.defineProperties(HTMLElement.prototype, { | |
6813 //TODO(sjmiles): review accessor alias with Arv | |
6814 webkitShadowRoot: { | |
6815 get: function() { | |
6816 return this.shadowRoot; | |
6817 } | |
6818 } | |
6819 }); | |
6820 | |
6821 // ShadowCSS needs this: | |
6822 window.wrap = window.ShadowDOMPolyfill.wrap; | |
6823 window.unwrap = window.ShadowDOMPolyfill.unwrap; | |
6824 | |
6825 //TODO(sjmiles): review method alias with Arv | |
6826 HTMLElement.prototype.webkitCreateShadowRoot = | |
6827 HTMLElement.prototype.createShadowRoot; | |
6828 | |
6829 // TODO(jmesserly): we need to wrap document somehow (a dart:html hook?) | |
6830 window.dartExperimentalFixupGetTag = function(originalGetTag) { | |
6831 var NodeList = ShadowDOMPolyfill.wrappers.NodeList; | |
6832 var ShadowRoot = ShadowDOMPolyfill.wrappers.ShadowRoot; | |
6833 var unwrapIfNeeded = ShadowDOMPolyfill.unwrapIfNeeded; | |
6834 function getTag(obj) { | |
6835 // TODO(jmesserly): do we still need these? | |
6836 if (obj instanceof NodeList) return 'NodeList'; | |
6837 if (obj instanceof ShadowRoot) return 'ShadowRoot'; | |
6838 if (window.MutationRecord && (obj instanceof MutationRecord)) | |
6839 return 'MutationRecord'; | |
6840 if (window.MutationObserver && (obj instanceof MutationObserver)) | |
6841 return 'MutationObserver'; | |
6842 | |
6843 // TODO(jmesserly): this prevents incorrect interaction between ShadowDOM | |
6844 // and dart:html's <template> polyfill. Essentially, ShadowDOM is | |
6845 // polyfilling native template, but our Dart polyfill fails to detect this | |
6846 // because the unwrapped node is an HTMLUnknownElement, leading it to | |
6847 // think the node has no content. | |
6848 if (obj instanceof HTMLTemplateElement) return 'HTMLTemplateElement'; | |
6849 | |
6850 var unwrapped = unwrapIfNeeded(obj); | |
6851 if (obj !== unwrapped) { | |
6852 // Fix up class names for Firefox. | |
6853 // For some of them (like HTMLFormElement and HTMLInputElement), | |
6854 // the "constructor" property of the unwrapped nodes points at the | |
6855 // same constructor as the wrapper. | |
6856 var ctor = obj.constructor | |
6857 if (ctor === unwrapped.constructor) { | |
6858 var name = ctor._ShadowDOMPolyfill$cacheTag_; | |
6859 if (!name) { | |
6860 name = Object.prototype.toString.call(unwrapped); | |
6861 name = name.substring(8, name.length - 1); | |
6862 ctor._ShadowDOMPolyfill$cacheTag_ = name; | |
6863 } | |
6864 return name; | |
6865 } | |
6866 | |
6867 obj = unwrapped; | |
6868 } | |
6869 return originalGetTag(obj); | |
6870 } | |
6871 | |
6872 return getTag; | |
6873 }; | |
6874 })(); | |
6875 | |
6876 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
6877 // for details. All rights reserved. Use of this source code is governed by a | |
6878 // BSD-style license that can be found in the LICENSE file. | |
6879 | |
6880 var Platform = {}; | |
6881 | |
6882 /* | |
6883 * Copyright 2012 The Polymer Authors. All rights reserved. | |
6884 * Use of this source code is governed by a BSD-style | |
6885 * license that can be found in the LICENSE file. | |
6886 */ | |
6887 | |
6888 /* | |
6889 This is a limited shim for ShadowDOM css styling. | |
6890 https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#style
s | |
6891 | |
6892 The intention here is to support only the styling features which can be | |
6893 relatively simply implemented. The goal is to allow users to avoid the | |
6894 most obvious pitfalls and do so without compromising performance significantly
. | |
6895 For ShadowDOM styling that's not covered here, a set of best practices | |
6896 can be provided that should allow users to accomplish more complex styling. | |
6897 | |
6898 The following is a list of specific ShadowDOM styling features and a brief | |
6899 discussion of the approach used to shim. | |
6900 | |
6901 Shimmed features: | |
6902 | |
6903 * @host: ShadowDOM allows styling of the shadowRoot's host element using the | |
6904 @host rule. To shim this feature, the @host styles are reformatted and | |
6905 prefixed with a given scope name and promoted to a document level stylesheet. | |
6906 For example, given a scope name of .foo, a rule like this: | |
6907 | |
6908 @host { | |
6909 * { | |
6910 background: red; | |
6911 } | |
6912 } | |
6913 | |
6914 becomes: | |
6915 | |
6916 .foo { | |
6917 background: red; | |
6918 } | |
6919 | |
6920 * encapsultion: Styles defined within ShadowDOM, apply only to | |
6921 dom inside the ShadowDOM. Polymer uses one of two techniques to imlement | |
6922 this feature. | |
6923 | |
6924 By default, rules are prefixed with the host element tag name | |
6925 as a descendant selector. This ensures styling does not leak out of the 'top' | |
6926 of the element's ShadowDOM. For example, | |
6927 | |
6928 div { | |
6929 font-weight: bold; | |
6930 } | |
6931 | |
6932 becomes: | |
6933 | |
6934 x-foo div { | |
6935 font-weight: bold; | |
6936 } | |
6937 | |
6938 becomes: | |
6939 | |
6940 | |
6941 Alternatively, if Platform.ShadowCSS.strictStyling is set to true then | |
6942 selectors are scoped by adding an attribute selector suffix to each | |
6943 simple selector that contains the host element tag name. Each element | |
6944 in the element's ShadowDOM template is also given the scope attribute. | |
6945 Thus, these rules match only elements that have the scope attribute. | |
6946 For example, given a scope name of x-foo, a rule like this: | |
6947 | |
6948 div { | |
6949 font-weight: bold; | |
6950 } | |
6951 | |
6952 becomes: | |
6953 | |
6954 div[x-foo] { | |
6955 font-weight: bold; | |
6956 } | |
6957 | |
6958 Note that elements that are dynamically added to a scope must have the scope | |
6959 selector added to them manually. | |
6960 | |
6961 * ::pseudo: These rules are converted to rules that take advantage of the | |
6962 pseudo attribute. For example, a shadowRoot like this inside an x-foo | |
6963 | |
6964 <div pseudo="x-special">Special</div> | |
6965 | |
6966 with a rule like this: | |
6967 | |
6968 x-foo::x-special { ... } | |
6969 | |
6970 becomes: | |
6971 | |
6972 x-foo [pseudo=x-special] { ... } | |
6973 | |
6974 * ::part(): These rules are converted to rules that take advantage of the | |
6975 part attribute. For example, a shadowRoot like this inside an x-foo | |
6976 | |
6977 <div part="special">Special</div> | |
6978 | |
6979 with a rule like this: | |
6980 | |
6981 x-foo::part(special) { ... } | |
6982 | |
6983 becomes: | |
6984 | |
6985 x-foo [part=special] { ... } | |
6986 | |
6987 Unaddressed ShadowDOM styling features: | |
6988 | |
6989 * upper/lower bound encapsulation: Styles which are defined outside a | |
6990 shadowRoot should not cross the ShadowDOM boundary and should not apply | |
6991 inside a shadowRoot. | |
6992 | |
6993 This styling behavior is not emulated. Some possible ways to do this that | |
6994 were rejected due to complexity and/or performance concerns include: (1) reset | |
6995 every possible property for every possible selector for a given scope name; | |
6996 (2) re-implement css in javascript. | |
6997 | |
6998 As an alternative, users should make sure to use selectors | |
6999 specific to the scope in which they are working. | |
7000 | |
7001 * ::distributed: This behavior is not emulated. It's often not necessary | |
7002 to style the contents of a specific insertion point and instead, descendants | |
7003 of the host element can be styled selectively. Users can also create an | |
7004 extra node around an insertion point and style that node's contents | |
7005 via descendent selectors. For example, with a shadowRoot like this: | |
7006 | |
7007 <style> | |
7008 content::-webkit-distributed(div) { | |
7009 background: red; | |
7010 } | |
7011 </style> | |
7012 <content></content> | |
7013 | |
7014 could become: | |
7015 | |
7016 <style> | |
7017 / *@polyfill .content-container div * / | |
7018 content::-webkit-distributed(div) { | |
7019 background: red; | |
7020 } | |
7021 </style> | |
7022 <div class="content-container"> | |
7023 <content></content> | |
7024 </div> | |
7025 | |
7026 Note the use of @polyfill in the comment above a ShadowDOM specific style | |
7027 declaration. This is a directive to the styling shim to use the selector | |
7028 in comments in lieu of the next selector when running under polyfill. | |
7029 */ | |
7030 (function(scope) { | |
7031 | |
7032 var loader = scope.loader; | |
7033 | |
7034 var ShadowCSS = { | |
7035 strictStyling: false, | |
7036 registry: {}, | |
7037 // Shim styles for a given root associated with a name and extendsName | |
7038 // 1. cache root styles by name | |
7039 // 2. optionally tag root nodes with scope name | |
7040 // 3. shim polyfill directives /* @polyfill */ and /* @polyfill-rule */ | |
7041 // 4. shim @host and scoping | |
7042 shimStyling: function(root, name, extendsName) { | |
7043 var typeExtension = this.isTypeExtension(extendsName); | |
7044 // use caching to make working with styles nodes easier and to facilitate | |
7045 // lookup of extendee | |
7046 var def = this.registerDefinition(root, name, extendsName); | |
7047 // find styles and apply shimming... | |
7048 if (this.strictStyling) { | |
7049 this.applyScopeToContent(root, name); | |
7050 } | |
7051 var cssText = this.stylesToShimmedCssText(def.rootStyles, def.scopeStyles, | |
7052 name, typeExtension); | |
7053 // provide shimmedStyle for user extensibility | |
7054 def.shimmedStyle = cssTextToStyle(cssText); | |
7055 if (root) { | |
7056 root.shimmedStyle = def.shimmedStyle; | |
7057 } | |
7058 // remove existing style elements | |
7059 for (var i=0, l=def.rootStyles.length, s; (i<l) && (s=def.rootStyles[i]); | |
7060 i++) { | |
7061 s.parentNode.removeChild(s); | |
7062 } | |
7063 // add style to document | |
7064 addCssToDocument(cssText); | |
7065 }, | |
7066 // apply @polyfill rules + @host and scope shimming | |
7067 stylesToShimmedCssText: function(rootStyles, scopeStyles, name, | |
7068 typeExtension) { | |
7069 name = name || ''; | |
7070 // insert @polyfill and @polyfill-rule rules into style elements | |
7071 // scoping process takes care of shimming these | |
7072 this.insertPolyfillDirectives(rootStyles); | |
7073 this.insertPolyfillRules(rootStyles); | |
7074 var cssText = this.shimAtHost(scopeStyles, name, typeExtension) + | |
7075 this.shimScoping(scopeStyles, name, typeExtension); | |
7076 // note: we only need to do rootStyles since these are unscoped. | |
7077 cssText += this.extractPolyfillUnscopedRules(rootStyles); | |
7078 return cssText; | |
7079 }, | |
7080 registerDefinition: function(root, name, extendsName) { | |
7081 var def = this.registry[name] = { | |
7082 root: root, | |
7083 name: name, | |
7084 extendsName: extendsName | |
7085 } | |
7086 var styles = root ? root.querySelectorAll('style') : []; | |
7087 styles = styles ? Array.prototype.slice.call(styles, 0) : []; | |
7088 def.rootStyles = styles; | |
7089 def.scopeStyles = def.rootStyles; | |
7090 var extendee = this.registry[def.extendsName]; | |
7091 if (extendee && (!root || root.querySelector('shadow'))) { | |
7092 def.scopeStyles = extendee.scopeStyles.concat(def.scopeStyles); | |
7093 } | |
7094 return def; | |
7095 }, | |
7096 isTypeExtension: function(extendsName) { | |
7097 return extendsName && extendsName.indexOf('-') < 0; | |
7098 }, | |
7099 applyScopeToContent: function(root, name) { | |
7100 if (root) { | |
7101 // add the name attribute to each node in root. | |
7102 Array.prototype.forEach.call(root.querySelectorAll('*'), | |
7103 function(node) { | |
7104 node.setAttribute(name, ''); | |
7105 }); | |
7106 // and template contents too | |
7107 Array.prototype.forEach.call(root.querySelectorAll('template'), | |
7108 function(template) { | |
7109 this.applyScopeToContent(template.content, name); | |
7110 }, | |
7111 this); | |
7112 } | |
7113 }, | |
7114 /* | |
7115 * Process styles to convert native ShadowDOM rules that will trip | |
7116 * up the css parser; we rely on decorating the stylesheet with comments. | |
7117 * | |
7118 * For example, we convert this rule: | |
7119 * | |
7120 * (comment start) @polyfill :host menu-item (comment end) | |
7121 * shadow::-webkit-distributed(menu-item) { | |
7122 * | |
7123 * to this: | |
7124 * | |
7125 * scopeName menu-item { | |
7126 * | |
7127 **/ | |
7128 insertPolyfillDirectives: function(styles) { | |
7129 if (styles) { | |
7130 Array.prototype.forEach.call(styles, function(s) { | |
7131 s.textContent = this.insertPolyfillDirectivesInCssText(s.textContent); | |
7132 }, this); | |
7133 } | |
7134 }, | |
7135 insertPolyfillDirectivesInCssText: function(cssText) { | |
7136 return cssText.replace(cssPolyfillCommentRe, function(match, p1) { | |
7137 // remove end comment delimiter and add block start | |
7138 return p1.slice(0, -2) + '{'; | |
7139 }); | |
7140 }, | |
7141 /* | |
7142 * Process styles to add rules which will only apply under the polyfill | |
7143 * | |
7144 * For example, we convert this rule: | |
7145 * | |
7146 * (comment start) @polyfill-rule :host menu-item { | |
7147 * ... } (comment end) | |
7148 * | |
7149 * to this: | |
7150 * | |
7151 * scopeName menu-item {...} | |
7152 * | |
7153 **/ | |
7154 insertPolyfillRules: function(styles) { | |
7155 if (styles) { | |
7156 Array.prototype.forEach.call(styles, function(s) { | |
7157 s.textContent = this.insertPolyfillRulesInCssText(s.textContent); | |
7158 }, this); | |
7159 } | |
7160 }, | |
7161 insertPolyfillRulesInCssText: function(cssText) { | |
7162 return cssText.replace(cssPolyfillRuleCommentRe, function(match, p1) { | |
7163 // remove end comment delimiter | |
7164 return p1.slice(0, -1); | |
7165 }); | |
7166 }, | |
7167 /* | |
7168 * Process styles to add rules which will only apply under the polyfill | |
7169 * and do not process via CSSOM. (CSSOM is destructive to rules on rare | |
7170 * occasions, e.g. -webkit-calc on Safari.) | |
7171 * For example, we convert this rule: | |
7172 * | |
7173 * (comment start) @polyfill-unscoped-rule menu-item { | |
7174 * ... } (comment end) | |
7175 * | |
7176 * to this: | |
7177 * | |
7178 * menu-item {...} | |
7179 * | |
7180 **/ | |
7181 extractPolyfillUnscopedRules: function(styles) { | |
7182 var cssText = ''; | |
7183 if (styles) { | |
7184 Array.prototype.forEach.call(styles, function(s) { | |
7185 cssText += this.extractPolyfillUnscopedRulesFromCssText( | |
7186 s.textContent) + '\n\n'; | |
7187 }, this); | |
7188 } | |
7189 return cssText; | |
7190 }, | |
7191 extractPolyfillUnscopedRulesFromCssText: function(cssText) { | |
7192 var r = '', matches; | |
7193 while (matches = cssPolyfillUnscopedRuleCommentRe.exec(cssText)) { | |
7194 r += matches[1].slice(0, -1) + '\n\n'; | |
7195 } | |
7196 return r; | |
7197 }, | |
7198 // form: @host { .foo { declarations } } | |
7199 // becomes: scopeName.foo { declarations } | |
7200 shimAtHost: function(styles, name, typeExtension) { | |
7201 if (styles) { | |
7202 return this.convertAtHostStyles(styles, name, typeExtension); | |
7203 } | |
7204 }, | |
7205 convertAtHostStyles: function(styles, name, typeExtension) { | |
7206 var cssText = stylesToCssText(styles), self = this; | |
7207 cssText = cssText.replace(hostRuleRe, function(m, p1) { | |
7208 return self.scopeHostCss(p1, name, typeExtension); | |
7209 }); | |
7210 cssText = rulesToCss(this.findAtHostRules(cssToRules(cssText), | |
7211 this.makeScopeMatcher(name, typeExtension))); | |
7212 return cssText; | |
7213 }, | |
7214 scopeHostCss: function(cssText, name, typeExtension) { | |
7215 var self = this; | |
7216 return cssText.replace(selectorRe, function(m, p1, p2) { | |
7217 return self.scopeHostSelector(p1, name, typeExtension) + ' ' + p2 + '\n\t'
; | |
7218 }); | |
7219 }, | |
7220 // supports scopig by name and [is=name] syntax | |
7221 scopeHostSelector: function(selector, name, typeExtension) { | |
7222 var r = [], parts = selector.split(','), is = '[is=' + name + ']'; | |
7223 parts.forEach(function(p) { | |
7224 p = p.trim(); | |
7225 // selector: *|:scope -> name | |
7226 if (p.match(hostElementRe)) { | |
7227 p = p.replace(hostElementRe, typeExtension ? is + '$1$3' : | |
7228 name + '$1$3'); | |
7229 // selector: .foo -> name.foo (OR) [bar] -> name[bar] | |
7230 } else if (p.match(hostFixableRe)) { | |
7231 p = typeExtension ? is + p : name + p; | |
7232 } | |
7233 r.push(p); | |
7234 }, this); | |
7235 return r.join(', '); | |
7236 }, | |
7237 // consider styles that do not include component name in the selector to be | |
7238 // unscoped and in need of promotion; | |
7239 // for convenience, also consider keyframe rules this way. | |
7240 findAtHostRules: function(cssRules, matcher) { | |
7241 return Array.prototype.filter.call(cssRules, | |
7242 this.isHostRule.bind(this, matcher)); | |
7243 }, | |
7244 isHostRule: function(matcher, cssRule) { | |
7245 return (cssRule.selectorText && cssRule.selectorText.match(matcher)) || | |
7246 (cssRule.cssRules && this.findAtHostRules(cssRule.cssRules, matcher).lengt
h) || | |
7247 (cssRule.type == CSSRule.WEBKIT_KEYFRAMES_RULE); | |
7248 }, | |
7249 /* Ensure styles are scoped. Pseudo-scoping takes a rule like: | |
7250 * | |
7251 * .foo {... } | |
7252 * | |
7253 * and converts this to | |
7254 * | |
7255 * scopeName .foo { ... } | |
7256 */ | |
7257 shimScoping: function(styles, name, typeExtension) { | |
7258 if (styles) { | |
7259 return this.convertScopedStyles(styles, name, typeExtension); | |
7260 } | |
7261 }, | |
7262 convertScopedStyles: function(styles, name, typeExtension) { | |
7263 var cssText = stylesToCssText(styles).replace(hostRuleRe, ''); | |
7264 cssText = this.insertPolyfillHostInCssText(cssText); | |
7265 cssText = this.convertColonHost(cssText); | |
7266 cssText = this.convertColonAncestor(cssText); | |
7267 // TODO(sorvell): deprecated, remove | |
7268 cssText = this.convertPseudos(cssText); | |
7269 // TODO(sorvell): deprecated, remove | |
7270 cssText = this.convertParts(cssText); | |
7271 cssText = this.convertCombinators(cssText); | |
7272 var rules = cssToRules(cssText); | |
7273 if (name) { | |
7274 cssText = this.scopeRules(rules, name, typeExtension); | |
7275 } | |
7276 return cssText; | |
7277 }, | |
7278 convertPseudos: function(cssText) { | |
7279 return cssText.replace(cssPseudoRe, ' [pseudo=$1]'); | |
7280 }, | |
7281 convertParts: function(cssText) { | |
7282 return cssText.replace(cssPartRe, ' [part=$1]'); | |
7283 }, | |
7284 /* | |
7285 * convert a rule like :host(.foo) > .bar { } | |
7286 * | |
7287 * to | |
7288 * | |
7289 * scopeName.foo > .bar | |
7290 */ | |
7291 convertColonHost: function(cssText) { | |
7292 return this.convertColonRule(cssText, cssColonHostRe, | |
7293 this.colonHostPartReplacer); | |
7294 }, | |
7295 /* | |
7296 * convert a rule like :ancestor(.foo) > .bar { } | |
7297 * | |
7298 * to | |
7299 * | |
7300 * scopeName.foo > .bar, .foo scopeName > .bar { } | |
7301 * | |
7302 * and | |
7303 * | |
7304 * :ancestor(.foo:host) .bar { ... } | |
7305 * | |
7306 * to | |
7307 * | |
7308 * scopeName.foo .bar { ... } | |
7309 */ | |
7310 convertColonAncestor: function(cssText) { | |
7311 return this.convertColonRule(cssText, cssColonAncestorRe, | |
7312 this.colonAncestorPartReplacer); | |
7313 }, | |
7314 convertColonRule: function(cssText, regExp, partReplacer) { | |
7315 // p1 = :host, p2 = contents of (), p3 rest of rule | |
7316 return cssText.replace(regExp, function(m, p1, p2, p3) { | |
7317 p1 = polyfillHostNoCombinator; | |
7318 if (p2) { | |
7319 var parts = p2.split(','), r = []; | |
7320 for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) { | |
7321 p = p.trim(); | |
7322 r.push(partReplacer(p1, p, p3)); | |
7323 } | |
7324 return r.join(','); | |
7325 } else { | |
7326 return p1 + p3; | |
7327 } | |
7328 }); | |
7329 }, | |
7330 colonAncestorPartReplacer: function(host, part, suffix) { | |
7331 if (part.match(polyfillHost)) { | |
7332 return this.colonHostPartReplacer(host, part, suffix); | |
7333 } else { | |
7334 return host + part + suffix + ', ' + part + ' ' + host + suffix; | |
7335 } | |
7336 }, | |
7337 colonHostPartReplacer: function(host, part, suffix) { | |
7338 return host + part.replace(polyfillHost, '') + suffix; | |
7339 }, | |
7340 /* | |
7341 * Convert ^ and ^^ combinators by replacing with space. | |
7342 */ | |
7343 convertCombinators: function(cssText) { | |
7344 return cssText.replace(/\^\^/g, ' ').replace(/\^/g, ' '); | |
7345 }, | |
7346 // change a selector like 'div' to 'name div' | |
7347 scopeRules: function(cssRules, name, typeExtension) { | |
7348 var cssText = ''; | |
7349 Array.prototype.forEach.call(cssRules, function(rule) { | |
7350 if (rule.selectorText && (rule.style && rule.style.cssText)) { | |
7351 cssText += this.scopeSelector(rule.selectorText, name, typeExtension, | |
7352 this.strictStyling) + ' {\n\t'; | |
7353 cssText += this.propertiesFromRule(rule) + '\n}\n\n'; | |
7354 } else if (rule.media) { | |
7355 cssText += '@media ' + rule.media.mediaText + ' {\n'; | |
7356 cssText += this.scopeRules(rule.cssRules, name, typeExtension); | |
7357 cssText += '\n}\n\n'; | |
7358 } else if (rule.cssText) { | |
7359 cssText += rule.cssText + '\n\n'; | |
7360 } | |
7361 }, this); | |
7362 return cssText; | |
7363 }, | |
7364 scopeSelector: function(selector, name, typeExtension, strict) { | |
7365 var r = [], parts = selector.split(','); | |
7366 parts.forEach(function(p) { | |
7367 p = p.trim(); | |
7368 if (this.selectorNeedsScoping(p, name, typeExtension)) { | |
7369 p = (strict && !p.match(polyfillHostNoCombinator)) ? | |
7370 this.applyStrictSelectorScope(p, name) : | |
7371 this.applySimpleSelectorScope(p, name, typeExtension); | |
7372 } | |
7373 r.push(p); | |
7374 }, this); | |
7375 return r.join(', '); | |
7376 }, | |
7377 selectorNeedsScoping: function(selector, name, typeExtension) { | |
7378 var re = this.makeScopeMatcher(name, typeExtension); | |
7379 return !selector.match(re); | |
7380 }, | |
7381 makeScopeMatcher: function(name, typeExtension) { | |
7382 var matchScope = typeExtension ? '\\[is=[\'"]?' + name + '[\'"]?\\]' : name; | |
7383 return new RegExp('^(' + matchScope + ')' + selectorReSuffix, 'm'); | |
7384 }, | |
7385 // scope via name and [is=name] | |
7386 applySimpleSelectorScope: function(selector, name, typeExtension) { | |
7387 var scoper = typeExtension ? '[is=' + name + ']' : name; | |
7388 if (selector.match(polyfillHostRe)) { | |
7389 selector = selector.replace(polyfillHostNoCombinator, scoper); | |
7390 return selector.replace(polyfillHostRe, scoper + ' '); | |
7391 } else { | |
7392 return scoper + ' ' + selector; | |
7393 } | |
7394 }, | |
7395 // return a selector with [name] suffix on each simple selector | |
7396 // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] | |
7397 applyStrictSelectorScope: function(selector, name) { | |
7398 var splits = [' ', '>', '+', '~'], | |
7399 scoped = selector, | |
7400 attrName = '[' + name + ']'; | |
7401 splits.forEach(function(sep) { | |
7402 var parts = scoped.split(sep); | |
7403 scoped = parts.map(function(p) { | |
7404 // remove :host since it should be unnecessary | |
7405 var t = p.trim().replace(polyfillHostRe, ''); | |
7406 if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) { | |
7407 p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3') | |
7408 } | |
7409 return p; | |
7410 }).join(sep); | |
7411 }); | |
7412 return scoped; | |
7413 }, | |
7414 insertPolyfillHostInCssText: function(selector) { | |
7415 return selector.replace(hostRe, polyfillHost).replace(colonHostRe, | |
7416 polyfillHost).replace(colonAncestorRe, polyfillAncestor); | |
7417 }, | |
7418 propertiesFromRule: function(rule) { | |
7419 // TODO(sorvell): Safari cssom incorrectly removes quotes from the content | |
7420 // property. (https://bugs.webkit.org/show_bug.cgi?id=118045) | |
7421 if (rule.style.content && !rule.style.content.match(/['"]+/)) { | |
7422 return rule.style.cssText.replace(/content:[^;]*;/g, 'content: \'' + | |
7423 rule.style.content + '\';'); | |
7424 } | |
7425 return rule.style.cssText; | |
7426 } | |
7427 }; | |
7428 | |
7429 var hostRuleRe = /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim, | |
7430 selectorRe = /([^{]*)({[\s\S]*?})/gim, | |
7431 hostElementRe = /(.*)((?:\*)|(?:\:scope))(.*)/, | |
7432 hostFixableRe = /^[.\[:]/, | |
7433 cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim, | |
7434 cssPolyfillCommentRe = /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?
){/gim, | |
7435 cssPolyfillRuleCommentRe = /\/\*\s@polyfill-rule([^*]*\*+([^/*][^*]*\*+)*)\/
/gim, | |
7436 cssPolyfillUnscopedRuleCommentRe = /\/\*\s@polyfill-unscoped-rule([^*]*\*+([
^/*][^*]*\*+)*)\//gim, | |
7437 cssPseudoRe = /::(x-[^\s{,(]*)/gim, | |
7438 cssPartRe = /::part\(([^)]*)\)/gim, | |
7439 // note: :host pre-processed to -shadowcsshost. | |
7440 polyfillHost = '-shadowcsshost', | |
7441 // note: :ancestor pre-processed to -shadowcssancestor. | |
7442 polyfillAncestor = '-shadowcssancestor', | |
7443 parenSuffix = ')(?:\\((' + | |
7444 '(?:\\([^)(]*\\)|[^)(]*)+?' + | |
7445 ')\\))?([^,{]*)'; | |
7446 cssColonHostRe = new RegExp('(' + polyfillHost + parenSuffix, 'gim'), | |
7447 cssColonAncestorRe = new RegExp('(' + polyfillAncestor + parenSuffix, 'gim')
, | |
7448 selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$', | |
7449 hostRe = /@host/gim, | |
7450 colonHostRe = /\:host/gim, | |
7451 colonAncestorRe = /\:ancestor/gim, | |
7452 /* host name without combinator */ | |
7453 polyfillHostNoCombinator = polyfillHost + '-no-combinator', | |
7454 polyfillHostRe = new RegExp(polyfillHost, 'gim'); | |
7455 polyfillAncestorRe = new RegExp(polyfillAncestor, 'gim'); | |
7456 | |
7457 function stylesToCssText(styles, preserveComments) { | |
7458 var cssText = ''; | |
7459 Array.prototype.forEach.call(styles, function(s) { | |
7460 cssText += s.textContent + '\n\n'; | |
7461 }); | |
7462 // strip comments for easier processing | |
7463 if (!preserveComments) { | |
7464 cssText = cssText.replace(cssCommentRe, ''); | |
7465 } | |
7466 return cssText; | |
7467 } | |
7468 | |
7469 function cssTextToStyle(cssText) { | |
7470 var style = document.createElement('style'); | |
7471 style.textContent = cssText; | |
7472 return style; | |
7473 } | |
7474 | |
7475 function cssToRules(cssText) { | |
7476 var style = cssTextToStyle(cssText); | |
7477 document.head.appendChild(style); | |
7478 var rules = style.sheet.cssRules; | |
7479 style.parentNode.removeChild(style); | |
7480 return rules; | |
7481 } | |
7482 | |
7483 function rulesToCss(cssRules) { | |
7484 for (var i=0, css=[]; i < cssRules.length; i++) { | |
7485 css.push(cssRules[i].cssText); | |
7486 } | |
7487 return css.join('\n\n'); | |
7488 } | |
7489 | |
7490 function addCssToDocument(cssText) { | |
7491 if (cssText) { | |
7492 getSheet().appendChild(document.createTextNode(cssText)); | |
7493 } | |
7494 } | |
7495 | |
7496 var sheet; | |
7497 function getSheet() { | |
7498 if (!sheet) { | |
7499 sheet = document.createElement("style"); | |
7500 sheet.setAttribute('ShadowCSSShim', ''); | |
7501 sheet.shadowCssShim = true; | |
7502 } | |
7503 return sheet; | |
7504 } | |
7505 | |
7506 // add polyfill stylesheet to document | |
7507 if (window.ShadowDOMPolyfill) { | |
7508 addCssToDocument('style { display: none !important; }\n'); | |
7509 var doc = wrap(document); | |
7510 var head = doc.querySelector('head'); | |
7511 head.insertBefore(getSheet(), head.childNodes[0]); | |
7512 | |
7513 document.addEventListener('DOMContentLoaded', function() { | |
7514 if (window.HTMLImports && !HTMLImports.useNative) { | |
7515 HTMLImports.importer.preloadSelectors += | |
7516 ', link[rel=stylesheet]:not([nopolyfill])'; | |
7517 HTMLImports.parser.parseGeneric = function(elt) { | |
7518 if (elt.shadowCssShim) { | |
7519 return; | |
7520 } | |
7521 var style = elt; | |
7522 if (!elt.hasAttribute('nopolyfill')) { | |
7523 if (elt.__resource) { | |
7524 style = elt.ownerDocument.createElement('style'); | |
7525 style.textContent = Platform.loader.resolveUrlsInCssText( | |
7526 elt.__resource, elt.href); | |
7527 // remove links from main document | |
7528 if (elt.ownerDocument === doc) { | |
7529 elt.parentNode.removeChild(elt); | |
7530 } | |
7531 } else { | |
7532 Platform.loader.resolveUrlsInStyle(style); | |
7533 } | |
7534 var styles = [style]; | |
7535 style.textContent = ShadowCSS.stylesToShimmedCssText(styles, styles); | |
7536 style.shadowCssShim = true; | |
7537 } | |
7538 // place in document | |
7539 if (style.parentNode !== head) { | |
7540 head.appendChild(style); | |
7541 } | |
7542 } | |
7543 } | |
7544 }); | |
7545 } | |
7546 | |
7547 // exports | |
7548 scope.ShadowCSS = ShadowCSS; | |
7549 | |
7550 })(window.Platform); | |
7551 } | |
OLD | NEW |