├── demo
├── dependencies.css
├── index.html
└── dependencies.js
├── .gitignore
├── .gitmodules
├── src
├── bootstrap.js
└── custom-elements.js
├── bower.json
├── package.json
├── xtag.json
├── README.md
└── Gruntfile.js
/demo/dependencies.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bower_components
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "CustomElements"]
2 | path = CustomElements
3 | url = https://github.com/Polymer/CustomElements.git
4 |
--------------------------------------------------------------------------------
/src/bootstrap.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2013 The Polymer Authors. All rights reserved.
3 | Use of this source code is governed by a BSD-style
4 | license that can be found in the LICENSE file.
5 | */
6 |
7 | window.CustomElements = window.CustomElements || {flags:{}};
8 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Custom-Elements",
3 | "description": "Custom Element polyfill for Web Components.",
4 | "version": "0.2.4",
5 | "author": "Arron Schaar",
6 | "keywords": [
7 | "polyfill",
8 | "web-components",
9 | "x-tag"
10 | ],
11 | "main": "src/custom-elements.js",
12 | "dependencies": {
13 | "MutationObserver": "~0.2.0",
14 | "classlist": "*"
15 | },
16 | "ignore": [
17 | ".gitignore",
18 | ".gitmodules",
19 | "CustomElements/",
20 | "Gruntfile.js",
21 | "demo/",
22 | "package.json",
23 | "xtag.json"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "custom-elements-polyfill",
3 | "description": "Wraps the CustomElement polyfill and makes it Bower consumable.",
4 | "version": "0.2.4",
5 | "author": "Arron Schaar",
6 | "dependencies": {},
7 | "devDependencies": {
8 | "bower": "~0.7.0",
9 | "grunt": "~0.4.0",
10 | "grunt-bumpup": "~0.2.0",
11 | "grunt-contrib-concat": "~0.4.0",
12 | "grunt-contrib-connect": "~0.7.0",
13 | "grunt-contrib-jshint": "~0.10.0",
14 | "grunt-contrib-stylus": "~0.7.0",
15 | "grunt-smush-components": "~0.2.1",
16 | "grunt-tagrelease": "~0.2.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/xtag.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "custom-elements-polyfill",
3 | "tagName": "custom-elements",
4 | "version": "0.2.4",
5 | "author": "Arron Schaar",
6 | "description": "Wraps the CustomElement polyfill and makes it Bower consumable.",
7 | "demo": "demo/index.html",
8 | "compatibility": {
9 | "firefox": 5,
10 | "chrome": 4,
11 | "ie": 9,
12 | "opera": 12,
13 | "android": 2.1,
14 | "ios": 4
15 | },
16 | "documentation": {
17 | "description": "Wraps the CustomElement polyfill and makes it Bower consumable.",
18 | "attributes": {},
19 | "events": {},
20 | "methods": {},
21 | "getters": {},
22 | "setters": {}
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # About Custom Elements Polyfill
2 |
3 | This repo is a wrapper for https://github.com/Polymer/CustomElements.
4 |
5 | # Bower
6 |
7 | ```
8 | bower install CustomElements
9 | ```
10 |
11 | # Example
12 |
13 | ```
14 |
15 | // Basic usage
16 |
17 |
18 |
19 |
20 |
21 |
41 |
42 | ```
43 |
44 |
45 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Custom Elements Polyfill - X-Tag
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 |
3 | // Project configuration.
4 | grunt.initConfig({
5 | connect: {
6 | demo: {
7 | options:{
8 | port: 3001,
9 | base: '',
10 | keepalive: true
11 | }
12 | }
13 | },
14 | jshint:{
15 | options:{
16 | jshintrc: true
17 | },
18 | all: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js']
19 | },
20 | 'smush-components': {
21 | options: {
22 | fileMap: {
23 | js: 'demo/dependencies.js',
24 | css: 'demo/dependencies.css'
25 | }
26 | }
27 | },
28 | bumpup: ['bower.json', 'package.json', 'xtag.json'],
29 | tagrelease: {
30 | file: 'package.json',
31 | prefix: '',
32 | commit: true
33 | },
34 | concat:{
35 | dist: {
36 | src: [
37 | 'src/bootstrap.js',
38 | 'CustomElements/src/Observer.js',
39 | 'CustomElements/src/CustomElements.js',
40 | 'CustomElements/src/Parser.js',
41 | 'CustomElements/src/boot.js'
42 | ],
43 | dest: 'src/custom-elements.js',
44 | }
45 | }
46 | });
47 |
48 | grunt.loadNpmTasks('grunt-contrib-concat');
49 | grunt.loadNpmTasks('grunt-contrib-connect');
50 | grunt.loadNpmTasks('grunt-contrib-jshint');
51 | grunt.loadNpmTasks('grunt-smush-components');
52 | grunt.loadNpmTasks('grunt-tagrelease');
53 | grunt.loadNpmTasks('grunt-bumpup');
54 |
55 | grunt.registerTask('build', ['smush-components','concat:dist']);
56 |
57 | };
58 |
--------------------------------------------------------------------------------
/demo/dependencies.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 The Polymer Authors. All rights reserved.
3 | * Use of this source code is governed by a BSD-style
4 | * license that can be found in the LICENSE file.
5 | */
6 |
7 | if (typeof WeakMap === 'undefined') {
8 | (function() {
9 | var defineProperty = Object.defineProperty;
10 | var counter = Date.now() % 1e9;
11 |
12 | var WeakMap = function() {
13 | this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
14 | };
15 |
16 | WeakMap.prototype = {
17 | set: function(key, value) {
18 | var entry = key[this.name];
19 | if (entry && entry[0] === key)
20 | entry[1] = value;
21 | else
22 | defineProperty(key, this.name, {value: [key, value], writable: true});
23 | },
24 | get: function(key) {
25 | var entry;
26 | return (entry = key[this.name]) && entry[0] === key ?
27 | entry[1] : undefined;
28 | },
29 | delete: function(key) {
30 | this.set(key, undefined);
31 | }
32 | };
33 |
34 | window.WeakMap = WeakMap;
35 | })();
36 | }
37 |
38 | /*
39 | * Copyright 2012 The Polymer Authors. All rights reserved.
40 | * Use of this source code is goverened by a BSD-style
41 | * license that can be found in the LICENSE file.
42 | */
43 |
44 | (function(global) {
45 |
46 | var registrationsTable = new WeakMap();
47 |
48 | // We use setImmediate or postMessage for our future callback.
49 | var setImmediate = window.msSetImmediate;
50 |
51 | // Use post message to emulate setImmediate.
52 | if (!setImmediate) {
53 | var setImmediateQueue = [];
54 | var sentinel = String(Math.random());
55 | window.addEventListener('message', function(e) {
56 | if (e.data === sentinel) {
57 | var queue = setImmediateQueue;
58 | setImmediateQueue = [];
59 | queue.forEach(function(func) {
60 | func();
61 | });
62 | }
63 | });
64 | setImmediate = function(func) {
65 | setImmediateQueue.push(func);
66 | window.postMessage(sentinel, '*');
67 | };
68 | }
69 |
70 | // This is used to ensure that we never schedule 2 callas to setImmediate
71 | var isScheduled = false;
72 |
73 | // Keep track of observers that needs to be notified next time.
74 | var scheduledObservers = [];
75 |
76 | /**
77 | * Schedules |dispatchCallback| to be called in the future.
78 | * @param {MutationObserver} observer
79 | */
80 | function scheduleCallback(observer) {
81 | scheduledObservers.push(observer);
82 | if (!isScheduled) {
83 | isScheduled = true;
84 | setImmediate(dispatchCallbacks);
85 | }
86 | }
87 |
88 | function wrapIfNeeded(node) {
89 | return window.ShadowDOMPolyfill &&
90 | window.ShadowDOMPolyfill.wrapIfNeeded(node) ||
91 | node;
92 | }
93 |
94 | function dispatchCallbacks() {
95 | // http://dom.spec.whatwg.org/#mutation-observers
96 |
97 | isScheduled = false; // Used to allow a new setImmediate call above.
98 |
99 | var observers = scheduledObservers;
100 | scheduledObservers = [];
101 | // Sort observers based on their creation UID (incremental).
102 | observers.sort(function(o1, o2) {
103 | return o1.uid_ - o2.uid_;
104 | });
105 |
106 | var anyNonEmpty = false;
107 | observers.forEach(function(observer) {
108 |
109 | // 2.1, 2.2
110 | var queue = observer.takeRecords();
111 | // 2.3. Remove all transient registered observers whose observer is mo.
112 | removeTransientObserversFor(observer);
113 |
114 | // 2.4
115 | if (queue.length) {
116 | observer.callback_(queue, observer);
117 | anyNonEmpty = true;
118 | }
119 | });
120 |
121 | // 3.
122 | if (anyNonEmpty)
123 | dispatchCallbacks();
124 | }
125 |
126 | function removeTransientObserversFor(observer) {
127 | observer.nodes_.forEach(function(node) {
128 | var registrations = registrationsTable.get(node);
129 | if (!registrations)
130 | return;
131 | registrations.forEach(function(registration) {
132 | if (registration.observer === observer)
133 | registration.removeTransientObservers();
134 | });
135 | });
136 | }
137 |
138 | /**
139 | * This function is used for the "For each registered observer observer (with
140 | * observer's options as options) in target's list of registered observers,
141 | * run these substeps:" and the "For each ancestor ancestor of target, and for
142 | * each registered observer observer (with options options) in ancestor's list
143 | * of registered observers, run these substeps:" part of the algorithms. The
144 | * |options.subtree| is checked to ensure that the callback is called
145 | * correctly.
146 | *
147 | * @param {Node} target
148 | * @param {function(MutationObserverInit):MutationRecord} callback
149 | */
150 | function forEachAncestorAndObserverEnqueueRecord(target, callback) {
151 | for (var node = target; node; node = node.parentNode) {
152 | var registrations = registrationsTable.get(node);
153 |
154 | if (registrations) {
155 | for (var j = 0; j < registrations.length; j++) {
156 | var registration = registrations[j];
157 | var options = registration.options;
158 |
159 | // Only target ignores subtree.
160 | if (node !== target && !options.subtree)
161 | continue;
162 |
163 | var record = callback(options);
164 | if (record)
165 | registration.enqueue(record);
166 | }
167 | }
168 | }
169 | }
170 |
171 | var uidCounter = 0;
172 |
173 | /**
174 | * The class that maps to the DOM MutationObserver interface.
175 | * @param {Function} callback.
176 | * @constructor
177 | */
178 | function JsMutationObserver(callback) {
179 | this.callback_ = callback;
180 | this.nodes_ = [];
181 | this.records_ = [];
182 | this.uid_ = ++uidCounter;
183 | }
184 |
185 | JsMutationObserver.prototype = {
186 | observe: function(target, options) {
187 | target = wrapIfNeeded(target);
188 |
189 | // 1.1
190 | if (!options.childList && !options.attributes && !options.characterData ||
191 |
192 | // 1.2
193 | options.attributeOldValue && !options.attributes ||
194 |
195 | // 1.3
196 | options.attributeFilter && options.attributeFilter.length &&
197 | !options.attributes ||
198 |
199 | // 1.4
200 | options.characterDataOldValue && !options.characterData) {
201 |
202 | throw new SyntaxError();
203 | }
204 |
205 | var registrations = registrationsTable.get(target);
206 | if (!registrations)
207 | registrationsTable.set(target, registrations = []);
208 |
209 | // 2
210 | // If target's list of registered observers already includes a registered
211 | // observer associated with the context object, replace that registered
212 | // observer's options with options.
213 | var registration;
214 | for (var i = 0; i < registrations.length; i++) {
215 | if (registrations[i].observer === this) {
216 | registration = registrations[i];
217 | registration.removeListeners();
218 | registration.options = options;
219 | break;
220 | }
221 | }
222 |
223 | // 3.
224 | // Otherwise, add a new registered observer to target's list of registered
225 | // observers with the context object as the observer and options as the
226 | // options, and add target to context object's list of nodes on which it
227 | // is registered.
228 | if (!registration) {
229 | registration = new Registration(this, target, options);
230 | registrations.push(registration);
231 | this.nodes_.push(target);
232 | }
233 |
234 | registration.addListeners();
235 | },
236 |
237 | disconnect: function() {
238 | this.nodes_.forEach(function(node) {
239 | var registrations = registrationsTable.get(node);
240 | for (var i = 0; i < registrations.length; i++) {
241 | var registration = registrations[i];
242 | if (registration.observer === this) {
243 | registration.removeListeners();
244 | registrations.splice(i, 1);
245 | // Each node can only have one registered observer associated with
246 | // this observer.
247 | break;
248 | }
249 | }
250 | }, this);
251 | this.records_ = [];
252 | },
253 |
254 | takeRecords: function() {
255 | var copyOfRecords = this.records_;
256 | this.records_ = [];
257 | return copyOfRecords;
258 | }
259 | };
260 |
261 | /**
262 | * @param {string} type
263 | * @param {Node} target
264 | * @constructor
265 | */
266 | function MutationRecord(type, target) {
267 | this.type = type;
268 | this.target = target;
269 | this.addedNodes = [];
270 | this.removedNodes = [];
271 | this.previousSibling = null;
272 | this.nextSibling = null;
273 | this.attributeName = null;
274 | this.attributeNamespace = null;
275 | this.oldValue = null;
276 | }
277 |
278 | function copyMutationRecord(original) {
279 | var record = new MutationRecord(original.type, original.target);
280 | record.addedNodes = original.addedNodes.slice();
281 | record.removedNodes = original.removedNodes.slice();
282 | record.previousSibling = original.previousSibling;
283 | record.nextSibling = original.nextSibling;
284 | record.attributeName = original.attributeName;
285 | record.attributeNamespace = original.attributeNamespace;
286 | record.oldValue = original.oldValue;
287 | return record;
288 | };
289 |
290 | // We keep track of the two (possibly one) records used in a single mutation.
291 | var currentRecord, recordWithOldValue;
292 |
293 | /**
294 | * Creates a record without |oldValue| and caches it as |currentRecord| for
295 | * later use.
296 | * @param {string} oldValue
297 | * @return {MutationRecord}
298 | */
299 | function getRecord(type, target) {
300 | return currentRecord = new MutationRecord(type, target);
301 | }
302 |
303 | /**
304 | * Gets or creates a record with |oldValue| based in the |currentRecord|
305 | * @param {string} oldValue
306 | * @return {MutationRecord}
307 | */
308 | function getRecordWithOldValue(oldValue) {
309 | if (recordWithOldValue)
310 | return recordWithOldValue;
311 | recordWithOldValue = copyMutationRecord(currentRecord);
312 | recordWithOldValue.oldValue = oldValue;
313 | return recordWithOldValue;
314 | }
315 |
316 | function clearRecords() {
317 | currentRecord = recordWithOldValue = undefined;
318 | }
319 |
320 | /**
321 | * @param {MutationRecord} record
322 | * @return {boolean} Whether the record represents a record from the current
323 | * mutation event.
324 | */
325 | function recordRepresentsCurrentMutation(record) {
326 | return record === recordWithOldValue || record === currentRecord;
327 | }
328 |
329 | /**
330 | * Selects which record, if any, to replace the last record in the queue.
331 | * This returns |null| if no record should be replaced.
332 | *
333 | * @param {MutationRecord} lastRecord
334 | * @param {MutationRecord} newRecord
335 | * @param {MutationRecord}
336 | */
337 | function selectRecord(lastRecord, newRecord) {
338 | if (lastRecord === newRecord)
339 | return lastRecord;
340 |
341 | // Check if the the record we are adding represents the same record. If
342 | // so, we keep the one with the oldValue in it.
343 | if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord))
344 | return recordWithOldValue;
345 |
346 | return null;
347 | }
348 |
349 | /**
350 | * Class used to represent a registered observer.
351 | * @param {MutationObserver} observer
352 | * @param {Node} target
353 | * @param {MutationObserverInit} options
354 | * @constructor
355 | */
356 | function Registration(observer, target, options) {
357 | this.observer = observer;
358 | this.target = target;
359 | this.options = options;
360 | this.transientObservedNodes = [];
361 | }
362 |
363 | Registration.prototype = {
364 | enqueue: function(record) {
365 | var records = this.observer.records_;
366 | var length = records.length;
367 |
368 | // There are cases where we replace the last record with the new record.
369 | // For example if the record represents the same mutation we need to use
370 | // the one with the oldValue. If we get same record (this can happen as we
371 | // walk up the tree) we ignore the new record.
372 | if (records.length > 0) {
373 | var lastRecord = records[length - 1];
374 | var recordToReplaceLast = selectRecord(lastRecord, record);
375 | if (recordToReplaceLast) {
376 | records[length - 1] = recordToReplaceLast;
377 | return;
378 | }
379 | } else {
380 | scheduleCallback(this.observer);
381 | }
382 |
383 | records[length] = record;
384 | },
385 |
386 | addListeners: function() {
387 | this.addListeners_(this.target);
388 | },
389 |
390 | addListeners_: function(node) {
391 | var options = this.options;
392 | if (options.attributes)
393 | node.addEventListener('DOMAttrModified', this, true);
394 |
395 | if (options.characterData)
396 | node.addEventListener('DOMCharacterDataModified', this, true);
397 |
398 | if (options.childList)
399 | node.addEventListener('DOMNodeInserted', this, true);
400 |
401 | if (options.childList || options.subtree)
402 | node.addEventListener('DOMNodeRemoved', this, true);
403 | },
404 |
405 | removeListeners: function() {
406 | this.removeListeners_(this.target);
407 | },
408 |
409 | removeListeners_: function(node) {
410 | var options = this.options;
411 | if (options.attributes)
412 | node.removeEventListener('DOMAttrModified', this, true);
413 |
414 | if (options.characterData)
415 | node.removeEventListener('DOMCharacterDataModified', this, true);
416 |
417 | if (options.childList)
418 | node.removeEventListener('DOMNodeInserted', this, true);
419 |
420 | if (options.childList || options.subtree)
421 | node.removeEventListener('DOMNodeRemoved', this, true);
422 | },
423 |
424 | /**
425 | * Adds a transient observer on node. The transient observer gets removed
426 | * next time we deliver the change records.
427 | * @param {Node} node
428 | */
429 | addTransientObserver: function(node) {
430 | // Don't add transient observers on the target itself. We already have all
431 | // the required listeners set up on the target.
432 | if (node === this.target)
433 | return;
434 |
435 | this.addListeners_(node);
436 | this.transientObservedNodes.push(node);
437 | var registrations = registrationsTable.get(node);
438 | if (!registrations)
439 | registrationsTable.set(node, registrations = []);
440 |
441 | // We know that registrations does not contain this because we already
442 | // checked if node === this.target.
443 | registrations.push(this);
444 | },
445 |
446 | removeTransientObservers: function() {
447 | var transientObservedNodes = this.transientObservedNodes;
448 | this.transientObservedNodes = [];
449 |
450 | transientObservedNodes.forEach(function(node) {
451 | // Transient observers are never added to the target.
452 | this.removeListeners_(node);
453 |
454 | var registrations = registrationsTable.get(node);
455 | for (var i = 0; i < registrations.length; i++) {
456 | if (registrations[i] === this) {
457 | registrations.splice(i, 1);
458 | // Each node can only have one registered observer associated with
459 | // this observer.
460 | break;
461 | }
462 | }
463 | }, this);
464 | },
465 |
466 | handleEvent: function(e) {
467 | // Stop propagation since we are managing the propagation manually.
468 | // This means that other mutation events on the page will not work
469 | // correctly but that is by design.
470 | e.stopImmediatePropagation();
471 |
472 | switch (e.type) {
473 | case 'DOMAttrModified':
474 | // http://dom.spec.whatwg.org/#concept-mo-queue-attributes
475 |
476 | var name = e.attrName;
477 | var namespace = e.relatedNode.namespaceURI;
478 | var target = e.target;
479 |
480 | // 1.
481 | var record = new getRecord('attributes', target);
482 | record.attributeName = name;
483 | record.attributeNamespace = namespace;
484 |
485 | // 2.
486 | var oldValue =
487 | e.attrChange === MutationEvent.ADDITION ? null : e.prevValue;
488 |
489 | forEachAncestorAndObserverEnqueueRecord(target, function(options) {
490 | // 3.1, 4.2
491 | if (!options.attributes)
492 | return;
493 |
494 | // 3.2, 4.3
495 | if (options.attributeFilter && options.attributeFilter.length &&
496 | options.attributeFilter.indexOf(name) === -1 &&
497 | options.attributeFilter.indexOf(namespace) === -1) {
498 | return;
499 | }
500 | // 3.3, 4.4
501 | if (options.attributeOldValue)
502 | return getRecordWithOldValue(oldValue);
503 |
504 | // 3.4, 4.5
505 | return record;
506 | });
507 |
508 | break;
509 |
510 | case 'DOMCharacterDataModified':
511 | // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata
512 | var target = e.target;
513 |
514 | // 1.
515 | var record = getRecord('characterData', target);
516 |
517 | // 2.
518 | var oldValue = e.prevValue;
519 |
520 |
521 | forEachAncestorAndObserverEnqueueRecord(target, function(options) {
522 | // 3.1, 4.2
523 | if (!options.characterData)
524 | return;
525 |
526 | // 3.2, 4.3
527 | if (options.characterDataOldValue)
528 | return getRecordWithOldValue(oldValue);
529 |
530 | // 3.3, 4.4
531 | return record;
532 | });
533 |
534 | break;
535 |
536 | case 'DOMNodeRemoved':
537 | this.addTransientObserver(e.target);
538 | // Fall through.
539 | case 'DOMNodeInserted':
540 | // http://dom.spec.whatwg.org/#concept-mo-queue-childlist
541 | var target = e.relatedNode;
542 | var changedNode = e.target;
543 | var addedNodes, removedNodes;
544 | if (e.type === 'DOMNodeInserted') {
545 | addedNodes = [changedNode];
546 | removedNodes = [];
547 | } else {
548 |
549 | addedNodes = [];
550 | removedNodes = [changedNode];
551 | }
552 | var previousSibling = changedNode.previousSibling;
553 | var nextSibling = changedNode.nextSibling;
554 |
555 | // 1.
556 | var record = getRecord('childList', target);
557 | record.addedNodes = addedNodes;
558 | record.removedNodes = removedNodes;
559 | record.previousSibling = previousSibling;
560 | record.nextSibling = nextSibling;
561 |
562 | forEachAncestorAndObserverEnqueueRecord(target, function(options) {
563 | // 2.1, 3.2
564 | if (!options.childList)
565 | return;
566 |
567 | // 2.2, 3.3
568 | return record;
569 | });
570 |
571 | }
572 |
573 | clearRecords();
574 | }
575 | };
576 |
577 | global.JsMutationObserver = JsMutationObserver;
578 |
579 | if (!global.MutationObserver)
580 | global.MutationObserver = JsMutationObserver;
581 |
582 |
583 | })(this);
584 |
585 | /*
586 | * classList.js: Cross-browser full element.classList implementation.
587 | * 2012-11-15
588 | *
589 | * By Eli Grey, http://eligrey.com
590 | * Public Domain.
591 | * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
592 | */
593 |
594 | /*global self, document, DOMException */
595 |
596 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
597 |
598 | if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) {
599 |
600 | (function (view) {
601 |
602 | "use strict";
603 |
604 | if (!('HTMLElement' in view) && !('Element' in view)) return;
605 |
606 | var
607 | classListProp = "classList"
608 | , protoProp = "prototype"
609 | , elemCtrProto = (view.HTMLElement || view.Element)[protoProp]
610 | , objCtr = Object
611 | , strTrim = String[protoProp].trim || function () {
612 | return this.replace(/^\s+|\s+$/g, "");
613 | }
614 | , arrIndexOf = Array[protoProp].indexOf || function (item) {
615 | var
616 | i = 0
617 | , len = this.length
618 | ;
619 | for (; i < len; i++) {
620 | if (i in this && this[i] === item) {
621 | return i;
622 | }
623 | }
624 | return -1;
625 | }
626 | // Vendors: please allow content code to instantiate DOMExceptions
627 | , DOMEx = function (type, message) {
628 | this.name = type;
629 | this.code = DOMException[type];
630 | this.message = message;
631 | }
632 | , checkTokenAndGetIndex = function (classList, token) {
633 | if (token === "") {
634 | throw new DOMEx(
635 | "SYNTAX_ERR"
636 | , "An invalid or illegal string was specified"
637 | );
638 | }
639 | if (/\s/.test(token)) {
640 | throw new DOMEx(
641 | "INVALID_CHARACTER_ERR"
642 | , "String contains an invalid character"
643 | );
644 | }
645 | return arrIndexOf.call(classList, token);
646 | }
647 | , ClassList = function (elem) {
648 | var
649 | trimmedClasses = strTrim.call(elem.className)
650 | , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
651 | , i = 0
652 | , len = classes.length
653 | ;
654 | for (; i < len; i++) {
655 | this.push(classes[i]);
656 | }
657 | this._updateClassName = function () {
658 | elem.className = this.toString();
659 | };
660 | }
661 | , classListProto = ClassList[protoProp] = []
662 | , classListGetter = function () {
663 | return new ClassList(this);
664 | }
665 | ;
666 | // Most DOMException implementations don't allow calling DOMException's toString()
667 | // on non-DOMExceptions. Error's toString() is sufficient here.
668 | DOMEx[protoProp] = Error[protoProp];
669 | classListProto.item = function (i) {
670 | return this[i] || null;
671 | };
672 | classListProto.contains = function (token) {
673 | token += "";
674 | return checkTokenAndGetIndex(this, token) !== -1;
675 | };
676 | classListProto.add = function () {
677 | var
678 | tokens = arguments
679 | , i = 0
680 | , l = tokens.length
681 | , token
682 | , updated = false
683 | ;
684 | do {
685 | token = tokens[i] + "";
686 | if (checkTokenAndGetIndex(this, token) === -1) {
687 | this.push(token);
688 | updated = true;
689 | }
690 | }
691 | while (++i < l);
692 |
693 | if (updated) {
694 | this._updateClassName();
695 | }
696 | };
697 | classListProto.remove = function () {
698 | var
699 | tokens = arguments
700 | , i = 0
701 | , l = tokens.length
702 | , token
703 | , updated = false
704 | ;
705 | do {
706 | token = tokens[i] + "";
707 | var index = checkTokenAndGetIndex(this, token);
708 | if (index !== -1) {
709 | this.splice(index, 1);
710 | updated = true;
711 | }
712 | }
713 | while (++i < l);
714 |
715 | if (updated) {
716 | this._updateClassName();
717 | }
718 | };
719 | classListProto.toggle = function (token, forse) {
720 | token += "";
721 |
722 | var
723 | result = this.contains(token)
724 | , method = result ?
725 | forse !== true && "remove"
726 | :
727 | forse !== false && "add"
728 | ;
729 |
730 | if (method) {
731 | this[method](token);
732 | }
733 |
734 | return !result;
735 | };
736 | classListProto.toString = function () {
737 | return this.join(" ");
738 | };
739 |
740 | if (objCtr.defineProperty) {
741 | var classListPropDesc = {
742 | get: classListGetter
743 | , enumerable: true
744 | , configurable: true
745 | };
746 | try {
747 | objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
748 | } catch (ex) { // IE 8 doesn't support enumerable:true
749 | if (ex.number === -0x7FF5EC54) {
750 | classListPropDesc.enumerable = false;
751 | objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
752 | }
753 | }
754 | } else if (objCtr[protoProp].__defineGetter__) {
755 | elemCtrProto.__defineGetter__(classListProp, classListGetter);
756 | }
757 |
758 | }(self));
759 |
760 | }
761 |
--------------------------------------------------------------------------------
/src/custom-elements.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2013 The Polymer Authors. All rights reserved.
3 | Use of this source code is governed by a BSD-style
4 | license that can be found in the LICENSE file.
5 | */
6 |
7 | window.CustomElements = window.CustomElements || {flags:{}};
8 |
9 | /*
10 | Copyright 2013 The Polymer Authors. All rights reserved.
11 | Use of this source code is governed by a BSD-style
12 | license that can be found in the LICENSE file.
13 | */
14 |
15 | (function(scope){
16 |
17 | var logFlags = window.logFlags || {};
18 | var IMPORT_LINK_TYPE = window.HTMLImports ? HTMLImports.IMPORT_LINK_TYPE : 'none';
19 |
20 | // walk the subtree rooted at node, applying 'find(element, data)' function
21 | // to each element
22 | // if 'find' returns true for 'element', do not search element's subtree
23 | function findAll(node, find, data) {
24 | var e = node.firstElementChild;
25 | if (!e) {
26 | e = node.firstChild;
27 | while (e && e.nodeType !== Node.ELEMENT_NODE) {
28 | e = e.nextSibling;
29 | }
30 | }
31 | while (e) {
32 | if (find(e, data) !== true) {
33 | findAll(e, find, data);
34 | }
35 | e = e.nextElementSibling;
36 | }
37 | return null;
38 | }
39 |
40 | // walk all shadowRoots on a given node.
41 | function forRoots(node, cb) {
42 | var root = node.shadowRoot;
43 | while(root) {
44 | forSubtree(root, cb);
45 | root = root.olderShadowRoot;
46 | }
47 | }
48 |
49 | // walk the subtree rooted at node, including descent into shadow-roots,
50 | // applying 'cb' to each element
51 | function forSubtree(node, cb) {
52 | //logFlags.dom && node.childNodes && node.childNodes.length && console.group('subTree: ', node);
53 | findAll(node, function(e) {
54 | if (cb(e)) {
55 | return true;
56 | }
57 | forRoots(e, cb);
58 | });
59 | forRoots(node, cb);
60 | //logFlags.dom && node.childNodes && node.childNodes.length && console.groupEnd();
61 | }
62 |
63 | // manage lifecycle on added node
64 | function added(node) {
65 | if (upgrade(node)) {
66 | insertedNode(node);
67 | return true;
68 | }
69 | inserted(node);
70 | }
71 |
72 | // manage lifecycle on added node's subtree only
73 | function addedSubtree(node) {
74 | forSubtree(node, function(e) {
75 | if (added(e)) {
76 | return true;
77 | }
78 | });
79 | }
80 |
81 | // manage lifecycle on added node and it's subtree
82 | function addedNode(node) {
83 | return added(node) || addedSubtree(node);
84 | }
85 |
86 | // upgrade custom elements at node, if applicable
87 | function upgrade(node) {
88 | if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) {
89 | var type = node.getAttribute('is') || node.localName;
90 | var definition = scope.registry[type];
91 | if (definition) {
92 | logFlags.dom && console.group('upgrade:', node.localName);
93 | scope.upgrade(node);
94 | logFlags.dom && console.groupEnd();
95 | return true;
96 | }
97 | }
98 | }
99 |
100 | function insertedNode(node) {
101 | inserted(node);
102 | if (inDocument(node)) {
103 | forSubtree(node, function(e) {
104 | inserted(e);
105 | });
106 | }
107 | }
108 |
109 | // TODO(sorvell): on platforms without MutationObserver, mutations may not be
110 | // reliable and therefore attached/detached are not reliable.
111 | // To make these callbacks less likely to fail, we defer all inserts and removes
112 | // to give a chance for elements to be inserted into dom.
113 | // This ensures attachedCallback fires for elements that are created and
114 | // immediately added to dom.
115 | var hasPolyfillMutations = (!window.MutationObserver ||
116 | (window.MutationObserver === window.JsMutationObserver));
117 | scope.hasPolyfillMutations = hasPolyfillMutations;
118 |
119 | var isPendingMutations = false;
120 | var pendingMutations = [];
121 | function deferMutation(fn) {
122 | pendingMutations.push(fn);
123 | if (!isPendingMutations) {
124 | isPendingMutations = true;
125 | var async = (window.Platform && window.Platform.endOfMicrotask) ||
126 | setTimeout;
127 | async(takeMutations);
128 | }
129 | }
130 |
131 | function takeMutations() {
132 | isPendingMutations = false;
133 | var $p = pendingMutations;
134 | for (var i=0, l=$p.length, p; (i 1) {
172 | logFlags.dom && console.warn('inserted:', element.localName,
173 | 'insert/remove count:', element.__inserted)
174 | } else if (element.attachedCallback) {
175 | logFlags.dom && console.log('inserted:', element.localName);
176 | element.attachedCallback();
177 | }
178 | }
179 | logFlags.dom && console.groupEnd();
180 | }
181 | }
182 |
183 | function removedNode(node) {
184 | removed(node);
185 | forSubtree(node, function(e) {
186 | removed(e);
187 | });
188 | }
189 |
190 | function removed(element) {
191 | if (hasPolyfillMutations) {
192 | deferMutation(function() {
193 | _removed(element);
194 | });
195 | } else {
196 | _removed(element);
197 | }
198 | }
199 |
200 | function _removed(element) {
201 | // TODO(sjmiles): temporary: do work on all custom elements so we can track
202 | // behavior even when callbacks not defined
203 | if (element.attachedCallback || element.detachedCallback || (element.__upgraded__ && logFlags.dom)) {
204 | logFlags.dom && console.group('removed:', element.localName);
205 | if (!inDocument(element)) {
206 | element.__inserted = (element.__inserted || 0) - 1;
207 | // if we are in a 'inserted' state, bluntly adjust to an 'removed' state
208 | if (element.__inserted > 0) {
209 | element.__inserted = 0;
210 | }
211 | // if we are 'over removed', squelch the callback
212 | if (element.__inserted < 0) {
213 | logFlags.dom && console.warn('removed:', element.localName,
214 | 'insert/remove count:', element.__inserted)
215 | } else if (element.detachedCallback) {
216 | element.detachedCallback();
217 | }
218 | }
219 | logFlags.dom && console.groupEnd();
220 | }
221 | }
222 |
223 | // SD polyfill intrustion due mainly to the fact that 'document'
224 | // is not entirely wrapped
225 | function wrapIfNeeded(node) {
226 | return window.ShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node)
227 | : node;
228 | }
229 |
230 | function inDocument(element) {
231 | var p = element;
232 | var doc = wrapIfNeeded(document);
233 | while (p) {
234 | if (p == doc) {
235 | return true;
236 | }
237 | p = p.parentNode || p.host;
238 | }
239 | }
240 |
241 | function watchShadow(node) {
242 | if (node.shadowRoot && !node.shadowRoot.__watched) {
243 | logFlags.dom && console.log('watching shadow-root for: ', node.localName);
244 | // watch all unwatched roots...
245 | var root = node.shadowRoot;
246 | while (root) {
247 | watchRoot(root);
248 | root = root.olderShadowRoot;
249 | }
250 | }
251 | }
252 |
253 | function watchRoot(root) {
254 | if (!root.__watched) {
255 | observe(root);
256 | root.__watched = true;
257 | }
258 | }
259 |
260 | function handler(mutations) {
261 | //
262 | if (logFlags.dom) {
263 | var mx = mutations[0];
264 | if (mx && mx.type === 'childList' && mx.addedNodes) {
265 | if (mx.addedNodes) {
266 | var d = mx.addedNodes[0];
267 | while (d && d !== document && !d.host) {
268 | d = d.parentNode;
269 | }
270 | var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || '';
271 | u = u.split('/?').shift().split('/').pop();
272 | }
273 | }
274 | console.group('mutations (%d) [%s]', mutations.length, u || '');
275 | }
276 | //
277 | mutations.forEach(function(mx) {
278 | //logFlags.dom && console.group('mutation');
279 | if (mx.type === 'childList') {
280 | forEach(mx.addedNodes, function(n) {
281 | //logFlags.dom && console.log(n.localName);
282 | if (!n.localName) {
283 | return;
284 | }
285 | // nodes added may need lifecycle management
286 | addedNode(n);
287 | });
288 | // removed nodes may need lifecycle management
289 | forEach(mx.removedNodes, function(n) {
290 | //logFlags.dom && console.log(n.localName);
291 | if (!n.localName) {
292 | return;
293 | }
294 | removedNode(n);
295 | });
296 | }
297 | //logFlags.dom && console.groupEnd();
298 | });
299 | logFlags.dom && console.groupEnd();
300 | };
301 |
302 | var observer = new MutationObserver(handler);
303 |
304 | function takeRecords() {
305 | // TODO(sjmiles): ask Raf why we have to call handler ourselves
306 | handler(observer.takeRecords());
307 | takeMutations();
308 | }
309 |
310 | var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
311 |
312 | function observe(inRoot) {
313 | observer.observe(inRoot, {childList: true, subtree: true});
314 | }
315 |
316 | function observeDocument(doc) {
317 | observe(doc);
318 | }
319 |
320 | function upgradeDocument(doc) {
321 | logFlags.dom && console.group('upgradeDocument: ', (doc.baseURI).split('/').pop());
322 | addedNode(doc);
323 | logFlags.dom && console.groupEnd();
324 | }
325 |
326 | function upgradeDocumentTree(doc) {
327 | doc = wrapIfNeeded(doc);
328 | //console.log('upgradeDocumentTree: ', (doc.baseURI).split('/').pop());
329 | // upgrade contained imported documents
330 | var imports = doc.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']');
331 | for (var i=0, l=imports.length, n; (i= 0) {
739 | implement(element, HTMLElement);
740 | }
741 | return element;
742 | }
743 |
744 | function upgradeElement(element) {
745 | if (!element.__upgraded__ && (element.nodeType === Node.ELEMENT_NODE)) {
746 | var is = element.getAttribute('is');
747 | var definition = getRegisteredDefinition(is || element.localName);
748 | if (definition) {
749 | if (is && definition.tag == element.localName) {
750 | return upgrade(element, definition);
751 | } else if (!is && !definition.extends) {
752 | return upgrade(element, definition);
753 | }
754 | }
755 | }
756 | }
757 |
758 | function cloneNode(deep) {
759 | // call original clone
760 | var n = domCloneNode.call(this, deep);
761 | // upgrade the element and subtree
762 | scope.upgradeAll(n);
763 | // return the clone
764 | return n;
765 | }
766 | // capture native createElement before we override it
767 |
768 | var domCreateElement = document.createElement.bind(document);
769 | var domCreateElementNS = document.createElementNS.bind(document);
770 |
771 | // capture native cloneNode before we override it
772 |
773 | var domCloneNode = Node.prototype.cloneNode;
774 |
775 | // exports
776 |
777 | document.registerElement = register;
778 | document.createElement = createElement; // override
779 | document.createElementNS = createElementNS; // override
780 | Node.prototype.cloneNode = cloneNode; // override
781 |
782 | scope.registry = registry;
783 |
784 | /**
785 | * Upgrade an element to a custom element. Upgrading an element
786 | * causes the custom prototype to be applied, an `is` attribute
787 | * to be attached (as needed), and invocation of the `readyCallback`.
788 | * `upgrade` does nothing if the element is already upgraded, or
789 | * if it matches no registered custom tag name.
790 | *
791 | * @method ugprade
792 | * @param {Element} element The element to upgrade.
793 | * @return {Element} The upgraded element.
794 | */
795 | scope.upgrade = upgradeElement;
796 | }
797 |
798 | // Create a custom 'instanceof'. This is necessary when CustomElements
799 | // are implemented via a mixin strategy, as for example on IE10.
800 | var isInstance;
801 | if (!Object.__proto__ && !useNative) {
802 | isInstance = function(obj, ctor) {
803 | var p = obj;
804 | while (p) {
805 | // NOTE: this is not technically correct since we're not checking if
806 | // an object is an instance of a constructor; however, this should
807 | // be good enough for the mixin strategy.
808 | if (p === ctor.prototype) {
809 | return true;
810 | }
811 | p = p.__proto__;
812 | }
813 | return false;
814 | }
815 | } else {
816 | isInstance = function(obj, base) {
817 | return obj instanceof base;
818 | }
819 | }
820 |
821 | // exports
822 | scope.instanceof = isInstance;
823 | scope.reservedTagList = reservedTagList;
824 |
825 | // bc
826 | document.register = document.registerElement;
827 |
828 | scope.hasNative = hasNative;
829 | scope.useNative = useNative;
830 |
831 | })(window.CustomElements);
832 |
833 | /*
834 | * Copyright 2013 The Polymer Authors. All rights reserved.
835 | * Use of this source code is governed by a BSD-style
836 | * license that can be found in the LICENSE file.
837 | */
838 |
839 | (function(scope) {
840 |
841 | // import
842 |
843 | var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE;
844 |
845 | // highlander object for parsing a document tree
846 |
847 | var parser = {
848 | selectors: [
849 | 'link[rel=' + IMPORT_LINK_TYPE + ']'
850 | ],
851 | map: {
852 | link: 'parseLink'
853 | },
854 | parse: function(inDocument) {
855 | if (!inDocument.__parsed) {
856 | // only parse once
857 | inDocument.__parsed = true;
858 | // all parsable elements in inDocument (depth-first pre-order traversal)
859 | var elts = inDocument.querySelectorAll(parser.selectors);
860 | // for each parsable node type, call the mapped parsing method
861 | forEach(elts, function(e) {
862 | parser[parser.map[e.localName]](e);
863 | });
864 | // upgrade all upgradeable static elements, anything dynamically
865 | // created should be caught by observer
866 | CustomElements.upgradeDocument(inDocument);
867 | // observe document for dom changes
868 | CustomElements.observeDocument(inDocument);
869 | }
870 | },
871 | parseLink: function(linkElt) {
872 | // imports
873 | if (isDocumentLink(linkElt)) {
874 | this.parseImport(linkElt);
875 | }
876 | },
877 | parseImport: function(linkElt) {
878 | if (linkElt.import) {
879 | parser.parse(linkElt.import);
880 | }
881 | }
882 | };
883 |
884 | function isDocumentLink(inElt) {
885 | return (inElt.localName === 'link'
886 | && inElt.getAttribute('rel') === IMPORT_LINK_TYPE);
887 | }
888 |
889 | var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
890 |
891 | // exports
892 |
893 | scope.parser = parser;
894 | scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
895 |
896 | })(window.CustomElements);
897 | /*
898 | * Copyright 2013 The Polymer Authors. All rights reserved.
899 | * Use of this source code is governed by a BSD-style
900 | * license that can be found in the LICENSE file.
901 | */
902 | (function(scope){
903 |
904 | // bootstrap parsing
905 | function bootstrap() {
906 | // parse document
907 | CustomElements.parser.parse(document);
908 | // one more pass before register is 'live'
909 | CustomElements.upgradeDocument(document);
910 | // choose async
911 | var async = window.Platform && Platform.endOfMicrotask ?
912 | Platform.endOfMicrotask :
913 | setTimeout;
914 | async(function() {
915 | // set internal 'ready' flag, now document.registerElement will trigger
916 | // synchronous upgrades
917 | CustomElements.ready = true;
918 | // capture blunt profiling data
919 | CustomElements.readyTime = Date.now();
920 | if (window.HTMLImports) {
921 | CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime;
922 | }
923 | // notify the system that we are bootstrapped
924 | document.dispatchEvent(
925 | new CustomEvent('WebComponentsReady', {bubbles: true})
926 | );
927 |
928 | // install upgrade hook if HTMLImports are available
929 | if (window.HTMLImports) {
930 | HTMLImports.__importsParsingHook = function(elt) {
931 | CustomElements.parser.parse(elt.import);
932 | }
933 | }
934 | });
935 | }
936 |
937 | // CustomEvent shim for IE
938 | if (typeof window.CustomEvent !== 'function') {
939 | window.CustomEvent = function(inType) {
940 | var e = document.createEvent('HTMLEvents');
941 | e.initEvent(inType, true, true);
942 | return e;
943 | };
944 | }
945 |
946 | // When loading at readyState complete time (or via flag), boot custom elements
947 | // immediately.
948 | // If relevant, HTMLImports must already be loaded.
949 | if (document.readyState === 'complete' || scope.flags.eager) {
950 | bootstrap();
951 | // When loading at readyState interactive time, bootstrap only if HTMLImports
952 | // are not pending. Also avoid IE as the semantics of this state are unreliable.
953 | } else if (document.readyState === 'interactive' && !window.attachEvent &&
954 | (!window.HTMLImports || window.HTMLImports.ready)) {
955 | bootstrap();
956 | // When loading at other readyStates, wait for the appropriate DOM event to
957 | // bootstrap.
958 | } else {
959 | var loadEvent = window.HTMLImports && !HTMLImports.ready ?
960 | 'HTMLImportsLoaded' : 'DOMContentLoaded';
961 | window.addEventListener(loadEvent, bootstrap);
962 | }
963 |
964 | })(window.CustomElements);
965 |
--------------------------------------------------------------------------------