├── demo
├── dependencies.css
├── import.html
├── index.html
└── dependencies.js
├── .gitignore
├── .gitmodules
├── README.md
├── bower.json
├── package.json
├── xtag.json
├── Gruntfile.js
└── src
└── html-imports.js
/demo/dependencies.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bower_components
3 |
--------------------------------------------------------------------------------
/demo/import.html:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "HTMLImports"]
2 | path = HTMLImports
3 | url = https://github.com/Polymer/HTMLImports.git
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # About HTMLImports
2 |
3 | This project is a Bower wrapper around https://github.com/Polymer/HTMLImports so that it is easily consumable without relying on the polymer module loader.
4 |
5 |
6 | ```
7 | bower install HTMLImports
8 | ```
9 |
10 |
11 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "HTML-Imports",
3 | "description": "HTML Imports polyfill for Web Components.",
4 | "version": "0.2.3",
5 | "author": "Arron Schaar",
6 | "keywords": [
7 | "polyfill",
8 | "web-components",
9 | "x-tag"
10 | ],
11 | "main": [
12 | "src/html-imports.js"
13 | ],
14 | "dependencies": {
15 | "MutationObserver": "~0.2.0",
16 | "WeakMap": "~0.2.1"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "htmlimports",
3 | "description": "Makes Polymer/HTMLImports Bower friendly",
4 | "version": "0.2.3",
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-smush-components": "~0.2.0",
15 | "grunt-tagrelease": "~0.2.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/xtag.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "htmlimports",
3 | "tagName": "h",
4 | "version": "0.2.3",
5 | "author": "Arron Schaar",
6 | "description": "Makes Polymer/HTMLImports Bower friendly",
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": "Makes Polymer/HTMLImports bower friendly",
18 | "attributes": {},
19 | "events": {},
20 | "methods": {},
21 | "getters": {},
22 | "setters": {}
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | HTMLImports - X-Tag
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/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 | 'HTMLImports/src/scope.js',
38 | 'HTMLImports/src/Loader.js',
39 | 'HTMLImports/src/Parser.js',
40 | 'HTMLImports/src/HTMLImports.js',
41 | 'HTMLImports/src/Observer.js',
42 | 'HTMLImports/src/boot.js'
43 | ],
44 | dest: 'src/html-imports.js',
45 | }
46 | }
47 | });
48 |
49 | grunt.loadNpmTasks('grunt-contrib-concat');
50 | grunt.loadNpmTasks('grunt-contrib-connect');
51 | grunt.loadNpmTasks('grunt-contrib-jshint');
52 | grunt.loadNpmTasks('grunt-smush-components');
53 | grunt.loadNpmTasks('grunt-tagrelease');
54 | grunt.loadNpmTasks('grunt-bumpup');
55 |
56 | grunt.registerTask('build', ['smush-components','concat:dist']);
57 |
58 | };
59 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/html-imports.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 | window.HTMLImports = window.HTMLImports || {flags:{}};
7 | /*
8 | * Copyright 2013 The Polymer Authors. All rights reserved.
9 | * Use of this source code is governed by a BSD-style
10 | * license that can be found in the LICENSE file.
11 | */
12 |
13 | (function(scope) {
14 |
15 | // imports
16 | var path = scope.path;
17 | var xhr = scope.xhr;
18 | var flags = scope.flags;
19 |
20 | // TODO(sorvell): this loader supports a dynamic list of urls
21 | // and an oncomplete callback that is called when the loader is done.
22 | // The polyfill currently does *not* need this dynamism or the onComplete
23 | // concept. Because of this, the loader could be simplified quite a bit.
24 | var Loader = function(onLoad, onComplete) {
25 | this.cache = {};
26 | this.onload = onLoad;
27 | this.oncomplete = onComplete;
28 | this.inflight = 0;
29 | this.pending = {};
30 | };
31 |
32 | Loader.prototype = {
33 | addNodes: function(nodes) {
34 | // number of transactions to complete
35 | this.inflight += nodes.length;
36 | // commence transactions
37 | for (var i=0, l=nodes.length, n; (i -1) {
91 | body = atob(body);
92 | } else {
93 | body = decodeURIComponent(body);
94 | }
95 | setTimeout(function() {
96 | this.receive(url, elt, null, body);
97 | }.bind(this), 0);
98 | } else {
99 | var receiveXhr = function(err, resource) {
100 | this.receive(url, elt, err, resource);
101 | }.bind(this);
102 | xhr.load(url, receiveXhr);
103 | // TODO(sorvell): blocked on)
104 | // https://code.google.com/p/chromium/issues/detail?id=257221
105 | // xhr'ing for a document makes scripts in imports runnable; otherwise
106 | // they are not; however, it requires that we have doctype=html in
107 | // the import which is unacceptable. This is only needed on Chrome
108 | // to avoid the bug above.
109 | /*
110 | if (isDocumentLink(elt)) {
111 | xhr.loadDocument(url, receiveXhr);
112 | } else {
113 | xhr.load(url, receiveXhr);
114 | }
115 | */
116 | }
117 | },
118 | receive: function(url, elt, err, resource) {
119 | this.cache[url] = resource;
120 | var $p = this.pending[url];
121 | for (var i=0, l=$p.length, p; (i= 200 && request.status < 300)
144 | || (request.status === 304)
145 | || (request.status === 0);
146 | },
147 | load: function(url, next, nextContext) {
148 | var request = new XMLHttpRequest();
149 | if (scope.flags.debug || scope.flags.bust) {
150 | url += '?' + Math.random();
151 | }
152 | request.open('GET', url, xhr.async);
153 | request.addEventListener('readystatechange', function(e) {
154 | if (request.readyState === 4) {
155 | next.call(nextContext, !xhr.ok(request) && request,
156 | request.response || request.responseText, url);
157 | }
158 | });
159 | request.send();
160 | return request;
161 | },
162 | loadDocument: function(url, next, nextContext) {
163 | this.load(url, next, nextContext).responseType = 'document';
164 | }
165 | };
166 |
167 | // exports
168 | scope.xhr = xhr;
169 | scope.Loader = Loader;
170 |
171 | })(window.HTMLImports);
172 |
173 | /*
174 | * Copyright 2013 The Polymer Authors. All rights reserved.
175 | * Use of this source code is governed by a BSD-style
176 | * license that can be found in the LICENSE file.
177 | */
178 |
179 | (function(scope) {
180 |
181 | var IMPORT_LINK_TYPE = 'import';
182 | var flags = scope.flags;
183 | var isIe = /Trident/.test(navigator.userAgent);
184 | // TODO(sorvell): SD polyfill intrusion
185 | var mainDoc = window.ShadowDOMPolyfill ?
186 | window.ShadowDOMPolyfill.wrapIfNeeded(document) : document;
187 |
188 | // importParser
189 | // highlander object to manage parsing of imports
190 | // parses import related elements
191 | // and ensures proper parse order
192 | // parse order is enforced by crawling the tree and monitoring which elements
193 | // have been parsed; async parsing is also supported.
194 |
195 | // highlander object for parsing a document tree
196 | var importParser = {
197 | // parse selectors for main document elements
198 | documentSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']',
199 | // parse selectors for import document elements
200 | importsSelectors: [
201 | 'link[rel=' + IMPORT_LINK_TYPE + ']',
202 | 'link[rel=stylesheet]',
203 | 'style',
204 | 'script:not([type])',
205 | 'script[type="text/javascript"]'
206 | ].join(','),
207 | map: {
208 | link: 'parseLink',
209 | script: 'parseScript',
210 | style: 'parseStyle'
211 | },
212 | // try to parse the next import in the tree
213 | parseNext: function() {
214 | var next = this.nextToParse();
215 | if (next) {
216 | this.parse(next);
217 | }
218 | },
219 | parse: function(elt) {
220 | if (this.isParsed(elt)) {
221 | flags.parse && console.log('[%s] is already parsed', elt.localName);
222 | return;
223 | }
224 | var fn = this[this.map[elt.localName]];
225 | if (fn) {
226 | this.markParsing(elt);
227 | fn.call(this, elt);
228 | }
229 | },
230 | // only 1 element may be parsed at a time; parsing is async so, each
231 | // parsing implementation must inform the system that parsing is complete
232 | // via markParsingComplete.
233 | markParsing: function(elt) {
234 | flags.parse && console.log('parsing', elt);
235 | this.parsingElement = elt;
236 | },
237 | markParsingComplete: function(elt) {
238 | elt.__importParsed = true;
239 | if (elt.__importElement) {
240 | elt.__importElement.__importParsed = true;
241 | }
242 | this.parsingElement = null;
243 | flags.parse && console.log('completed', elt);
244 | this.parseNext();
245 | },
246 | parseImport: function(elt) {
247 | elt.import.__importParsed = true;
248 | // TODO(sorvell): consider if there's a better way to do this;
249 | // expose an imports parsing hook; this is needed, for example, by the
250 | // CustomElements polyfill.
251 | if (HTMLImports.__importsParsingHook) {
252 | HTMLImports.__importsParsingHook(elt);
253 | }
254 | // fire load event
255 | if (elt.__resource) {
256 | elt.dispatchEvent(new CustomEvent('load', {bubbles: false}));
257 | } else {
258 | elt.dispatchEvent(new CustomEvent('error', {bubbles: false}));
259 | }
260 | // TODO(sorvell): workaround for Safari addEventListener not working
261 | // for elements not in the main document.
262 | if (elt.__pending) {
263 | var fn;
264 | while (elt.__pending.length) {
265 | fn = elt.__pending.shift();
266 | if (fn) {
267 | fn({target: elt});
268 | }
269 | }
270 | }
271 | this.markParsingComplete(elt);
272 | },
273 | parseLink: function(linkElt) {
274 | if (nodeIsImport(linkElt)) {
275 | this.parseImport(linkElt);
276 | } else {
277 | // make href absolute
278 | linkElt.href = linkElt.href;
279 | this.parseGeneric(linkElt);
280 | }
281 | },
282 | parseStyle: function(elt) {
283 | // TODO(sorvell): style element load event can just not fire so clone styles
284 | var src = elt;
285 | elt = cloneStyle(elt);
286 | elt.__importElement = src;
287 | this.parseGeneric(elt);
288 | },
289 | parseGeneric: function(elt) {
290 | this.trackElement(elt);
291 | document.head.appendChild(elt);
292 | },
293 | // tracks when a loadable element has loaded
294 | trackElement: function(elt, callback) {
295 | var self = this;
296 | var done = function(e) {
297 | if (callback) {
298 | callback(e);
299 | }
300 | self.markParsingComplete(elt);
301 | };
302 | elt.addEventListener('load', done);
303 | elt.addEventListener('error', done);
304 |
305 | // NOTE: IE does not fire "load" event for styles that have already loaded
306 | // This is in violation of the spec, so we try our hardest to work around it
307 | if (isIe && elt.localName === 'style') {
308 | var fakeLoad = false;
309 | // If there's not @import in the textContent, assume it has loaded
310 | if (elt.textContent.indexOf('@import') == -1) {
311 | fakeLoad = true;
312 | // if we have a sheet, we have been parsed
313 | } else if (elt.sheet) {
314 | fakeLoad = true;
315 | var csr = elt.sheet.cssRules;
316 | var len = csr ? csr.length : 0;
317 | // search the rules for @import's
318 | for (var i = 0, r; (i < len) && (r = csr[i]); i++) {
319 | if (r.type === CSSRule.IMPORT_RULE) {
320 | // if every @import has resolved, fake the load
321 | fakeLoad = fakeLoad && Boolean(r.styleSheet);
322 | }
323 | }
324 | }
325 | // dispatch a fake load event and continue parsing
326 | if (fakeLoad) {
327 | elt.dispatchEvent(new CustomEvent('load', {bubbles: false}));
328 | }
329 | }
330 | },
331 | // NOTE: execute scripts by injecting them and watching for the load/error
332 | // event. Inline scripts are handled via dataURL's because browsers tend to
333 | // provide correct parsing errors in this case. If this has any compatibility
334 | // issues, we can switch to injecting the inline script with textContent.
335 | // Scripts with dataURL's do not appear to generate load events and therefore
336 | // we assume they execute synchronously.
337 | parseScript: function(scriptElt) {
338 | var script = document.createElement('script');
339 | script.__importElement = scriptElt;
340 | script.src = scriptElt.src ? scriptElt.src :
341 | generateScriptDataUrl(scriptElt);
342 | scope.currentScript = scriptElt;
343 | this.trackElement(script, function(e) {
344 | script.parentNode.removeChild(script);
345 | scope.currentScript = null;
346 | });
347 | document.head.appendChild(script);
348 | },
349 | // determine the next element in the tree which should be parsed
350 | nextToParse: function() {
351 | return !this.parsingElement && this.nextToParseInDoc(mainDoc);
352 | },
353 | nextToParseInDoc: function(doc, link) {
354 | var nodes = doc.querySelectorAll(this.parseSelectorsForNode(doc));
355 | for (var i=0, l=nodes.length, p=0, n; (i.content
526 | // see https://code.google.com/p/chromium/issues/detail?id=249381.
527 | elt.__resource = resource;
528 | if (isDocumentLink(elt)) {
529 | var doc = this.documents[url];
530 | // if we've never seen a document at this url
531 | if (!doc) {
532 | // generate an HTMLDocument from data
533 | doc = makeDocument(resource, url);
534 | doc.__importLink = elt;
535 | // TODO(sorvell): we cannot use MO to detect parsed nodes because
536 | // SD polyfill does not report these as mutations.
537 | this.bootDocument(doc);
538 | // cache document
539 | this.documents[url] = doc;
540 | }
541 | // don't store import record until we're actually loaded
542 | // store document resource
543 | elt.import = doc;
544 | }
545 | parser.parseNext();
546 | },
547 | bootDocument: function(doc) {
548 | this.loadSubtree(doc);
549 | this.observe(doc);
550 | parser.parseNext();
551 | },
552 | loadedAll: function() {
553 | parser.parseNext();
554 | }
555 | };
556 |
557 | // loader singleton
558 | var importLoader = new Loader(importer.loaded.bind(importer),
559 | importer.loadedAll.bind(importer));
560 |
561 | function isDocumentLink(elt) {
562 | return isLinkRel(elt, IMPORT_LINK_TYPE);
563 | }
564 |
565 | function isLinkRel(elt, rel) {
566 | return elt.localName === 'link' && elt.getAttribute('rel') === rel;
567 | }
568 |
569 | function isScript(elt) {
570 | return elt.localName === 'script';
571 | }
572 |
573 | function makeDocument(resource, url) {
574 | // create a new HTML document
575 | var doc = resource;
576 | if (!(doc instanceof Document)) {
577 | doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE);
578 | }
579 | // cache the new document's source url
580 | doc._URL = url;
581 | // establish a relative path via
582 | var base = doc.createElement('base');
583 | base.setAttribute('href', url);
584 | // add baseURI support to browsers (IE) that lack it.
585 | if (!doc.baseURI) {
586 | doc.baseURI = url;
587 | }
588 | // ensure UTF-8 charset
589 | var meta = doc.createElement('meta');
590 | meta.setAttribute('charset', 'utf-8');
591 |
592 | doc.head.appendChild(meta);
593 | doc.head.appendChild(base);
594 | // install HTML last as it may trigger CustomElement upgrades
595 | // TODO(sjmiles): problem wrt to template boostrapping below,
596 | // template bootstrapping must (?) come before element upgrade
597 | // but we cannot bootstrap templates until they are in a document
598 | // which is too late
599 | if (!(resource instanceof Document)) {
600 | // install html
601 | doc.body.innerHTML = resource;
602 | }
603 | // TODO(sorvell): ideally this code is not aware of Template polyfill,
604 | // but for now the polyfill needs help to bootstrap these templates
605 | if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) {
606 | HTMLTemplateElement.bootstrap(doc);
607 | }
608 | return doc;
609 | }
610 | } else {
611 | // do nothing if using native imports
612 | var importer = {};
613 | }
614 |
615 | // NOTE: We cannot polyfill document.currentScript because it's not possible
616 | // both to override and maintain the ability to capture the native value;
617 | // therefore we choose to expose _currentScript both when native imports
618 | // and the polyfill are in use.
619 | var currentScriptDescriptor = {
620 | get: function() {
621 | return HTMLImports.currentScript || document.currentScript;
622 | },
623 | configurable: true
624 | };
625 |
626 | Object.defineProperty(document, '_currentScript', currentScriptDescriptor);
627 | Object.defineProperty(mainDoc, '_currentScript', currentScriptDescriptor);
628 |
629 | // Polyfill document.baseURI for browsers without it.
630 | if (!document.baseURI) {
631 | var baseURIDescriptor = {
632 | get: function() {
633 | return window.location.href;
634 | },
635 | configurable: true
636 | };
637 |
638 | Object.defineProperty(document, 'baseURI', baseURIDescriptor);
639 | Object.defineProperty(mainDoc, 'baseURI', baseURIDescriptor);
640 | }
641 |
642 | // call a callback when all HTMLImports in the document at call (or at least
643 | // document ready) time have loaded.
644 | // 1. ensure the document is in a ready state (has dom), then
645 | // 2. watch for loading of imports and call callback when done
646 | function whenImportsReady(callback, doc) {
647 | doc = doc || mainDoc;
648 | // if document is loading, wait and try again
649 | whenDocumentReady(function() {
650 | watchImportsLoad(callback, doc);
651 | }, doc);
652 | }
653 |
654 | // call the callback when the document is in a ready state (has dom)
655 | var requiredReadyState = HTMLImports.isIE ? 'complete' : 'interactive';
656 | var READY_EVENT = 'readystatechange';
657 | function isDocumentReady(doc) {
658 | return (doc.readyState === 'complete' ||
659 | doc.readyState === requiredReadyState);
660 | }
661 |
662 | // call when we ensure the document is in a ready state
663 | function whenDocumentReady(callback, doc) {
664 | if (!isDocumentReady(doc)) {
665 | var checkReady = function() {
666 | if (doc.readyState === 'complete' ||
667 | doc.readyState === requiredReadyState) {
668 | doc.removeEventListener(READY_EVENT, checkReady);
669 | whenDocumentReady(callback, doc);
670 | }
671 | }
672 | doc.addEventListener(READY_EVENT, checkReady);
673 | } else if (callback) {
674 | callback();
675 | }
676 | }
677 |
678 | // call when we ensure all imports have loaded
679 | function watchImportsLoad(callback, doc) {
680 | var imports = doc.querySelectorAll('link[rel=import]');
681 | var loaded = 0, l = imports.length;
682 | function checkDone(d) {
683 | if (loaded == l) {
684 | // go async to ensure parser isn't stuck on a script tag
685 | requestAnimationFrame(callback);
686 | }
687 | }
688 | function loadedImport(e) {
689 | loaded++;
690 | checkDone();
691 | }
692 | if (l) {
693 | for (var i=0, imp; (i