Skip to content
This repository has been archived by the owner on Mar 13, 2018. It is now read-only.

Commit

Permalink
factoring and comments.
Browse files Browse the repository at this point in the history
  • Loading branch information
sorvell committed Feb 4, 2014
1 parent cde4a8a commit 0461b94
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 49 deletions.
81 changes: 45 additions & 36 deletions src/HTMLImports.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,34 @@
(function(scope) {

var hasNative = ('import' in document.createElement('link'));
var useNative = hasNative;
var flags = scope.flags;
var useNative = !flags.imports && hasNative;

var IMPORT_LINK_TYPE = 'import';

// TODO(sorvell): SD polyfill intrusion
var mainDoc = window.ShadowDOMPolyfill ?
ShadowDOMPolyfill.wrapIfNeeded(document) : document;

if (!useNative) {

// imports
var xhr = scope.xhr;
var Loader = scope.Loader;
var parser = scope.parser;

// importer
// highlander object represents a primary document (the argument to 'load')
// at the root of a tree of documents
// highlander object to manage loading of imports

// for any document, importer:
// - loads any linked documents (with deduping), modifies paths and feeds them back into importer
// - loads any linked import documents (with deduping)
// for any import document, importer also:
// - loads text of external script tags
// - loads text of external style tags inside of <element>, modifies paths

// when importer 'modifies paths' in a document, this includes
// - href/src/action in node attributes
// - paths in inline stylesheets
// - all content inside templates

// linked style sheets in an import have their own path fixed up when their containing import modifies paths
// linked style sheets in an <element> are loaded, and the content gets path fixups
// inline style sheets get path fixups when their containing import modifies paths

var STYLE_LINK_TYPE = 'stylesheet';

var importer = {
documents: {},
// nodes to load in the mian document
documentPreloadSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']',
// nodes to load in imports
importsPreloadSelectors: [
'link[rel=' + IMPORT_LINK_TYPE + ']',
'script[src]:not([type])',
Expand All @@ -51,6 +43,7 @@ if (!useNative) {
loadNode: function(node) {
importLoader.addNode(node);
},
// load all loadable elements within the parent element
loadSubtree: function(parent) {
var nodes = this.marshalNodes(parent);
// add these nodes to loader's queue
Expand All @@ -60,6 +53,7 @@ if (!useNative) {
// all preloadable nodes in inDocument
return parent.querySelectorAll(this.loadSelectorsForNode(parent));
},
// find the proper set of load selectors for a given node
loadSelectorsForNode: function(node) {
var doc = node.ownerDocument || node;
return doc === mainDoc ? this.documentPreloadSelectors :
Expand Down Expand Up @@ -100,17 +94,14 @@ if (!useNative) {
}
};

// loader singleton
var importLoader = new Loader(importer.loaded.bind(importer),
importer.loadedAll.bind(importer));

function isDocumentLink(elt) {
return isLinkRel(elt, IMPORT_LINK_TYPE);
}

function isStylesheetLink(elt) {
return isLinkRel(elt, STYLE_LINK_TYPE);
}

function isLinkRel(elt, rel) {
return elt.localName === 'link' && elt.getAttribute('rel') === rel;
}
Expand Down Expand Up @@ -183,25 +174,44 @@ if (!document.baseURI) {
Object.defineProperty(mainDoc, 'baseURI', baseURIDescriptor);
}

// TODO(sorvell): multiple calls will install multiple event listeners
// which may not be desireable; calls should resolve in the correct order,
// however.
// call a callback when all HTMLImports in the document at call (or at least
// document ready) time have loaded.
// 1. ensure the document is in a ready state (has dom), then
// 2. watch for loading of imports and call callback when done
function whenImportsReady(callback, doc) {
doc = doc || mainDoc;
// if document is loading, wait and try again
var requiredState = HTMLImports.isIE ? 'complete' : 'interactive';
var isReady = (doc.readyState === 'complete' ||
doc.readyState === requiredState);
if (!isReady) {
var checkReady = function(e) {
if (doc.readyState === 'complete' || doc.readyState === requiredState) {
doc.removeEventListener('readystatechange', checkReady)
whenImportsReady(callback, doc);
whenDocumentReady(function() {
watchImportsLoad(callback, doc);
}, doc);
}

// call the callback when the document is in a ready state (has dom)
var requiredReadyState = HTMLImports.isIE ? 'complete' : 'interactive';
var READY_EVENT = 'readystatechange';
function isDocumentReady(doc) {
return (doc.readyState === 'complete' ||
doc.readyState === requiredReadyState);
}

// call <callback> when we ensure the document is in a ready state
function whenDocumentReady(callback, doc) {
if (!isDocumentReady(doc)) {
var checkReady = function() {
if (doc.readyState === 'complete' ||
doc.readyState === requiredReadyState) {
doc.removeEventListener(READY_EVENT, checkReady);
whenDocumentReady(callback, doc);
}
}
doc.addEventListener('readystatechange', checkReady)
return;
doc.addEventListener(READY_EVENT, checkReady);
} else if (callback) {
callback();
}
}

// call <callback> when we ensure all imports have loaded
function watchImportsLoad(callback, doc) {
var imports = doc.querySelectorAll('link[rel=import]');
var loaded = 0, l = imports.length;
function checkDone(d) {
Expand All @@ -210,8 +220,7 @@ function whenImportsReady(callback, doc) {
requestAnimationFrame(callback);
}
}
// called in context of import
function loadedImport() {
function loadedImport(e) {
loaded++;
checkDone();
}
Expand Down
4 changes: 4 additions & 0 deletions src/Loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
var xhr = scope.xhr;
var flags = scope.flags;

// TODO(sorvell): this loader supports a dynamic list of urls
// and an oncomplete callback that is called when the loader is done.
// The polyfill currently does *not* need this dynamism or the onComplete
// concept. Because of this, the loader could be simplified quite a bit.
var Loader = function(onLoad, onComplete) {
this.cache = {};
this.onload = onLoad;
Expand Down
17 changes: 10 additions & 7 deletions src/Observer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,9 @@ license that can be found in the LICENSE file.

var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE;
var importSelector = 'link[rel=' + IMPORT_LINK_TYPE + ']';

var matches = HTMLElement.prototype.matches ||
HTMLElement.prototype.matchesSelector ||
HTMLElement.prototype.webkitMatchesSelector ||
HTMLElement.prototype.mozMatchesSelector ||
HTMLElement.prototype.msMatchesSelector;

var importer = scope.importer;

// we track mutations for addedNodes, looking for imports
function handler(mutations) {
for (var i=0, l=mutations.length, m; (i<l) && (m=mutations[i]); i++) {
if (m.type === 'childList' && m.addedNodes.length) {
Expand All @@ -25,6 +19,7 @@ function handler(mutations) {
}
}

// find loadable elements and add them to the importer
function addedNodes(nodes) {
for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
if (shouldLoadNode(n)) {
Expand All @@ -41,8 +36,16 @@ function shouldLoadNode(node) {
importer.loadSelectorsForNode(node));
}

// x-plat matches
var matches = HTMLElement.prototype.matches ||
HTMLElement.prototype.matchesSelector ||
HTMLElement.prototype.webkitMatchesSelector ||
HTMLElement.prototype.mozMatchesSelector ||
HTMLElement.prototype.msMatchesSelector;

var observer = new MutationObserver(handler);

// observe the given root for loadable elements
function observe(root) {
observer.observe(root, {childList: true, subtree: true});
}
Expand Down
33 changes: 27 additions & 6 deletions src/Parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,18 @@ var isIe = /Trident/.test(navigator.userAgent);
var mainDoc = window.ShadowDOMPolyfill ?
window.ShadowDOMPolyfill.wrapIfNeeded(document) : document;

// importParser
// highlander object to manage parsing of imports
// parses import related elements
// and ensures proper parse order
// parse order is enforced by crawling the tree and monitoring which elements
// have been parsed; async parsing is also supported.

// highlander object for parsing a document tree
var importParser = {
// parse selectors for main document elements
documentSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']',
// parse selectors for import document elements
importsSelectors: [
'link[rel=' + IMPORT_LINK_TYPE + ']',
'link[rel=stylesheet]',
Expand All @@ -29,9 +37,9 @@ var importParser = {
script: 'parseScript',
style: 'parseStyle'
},
// try to parse the next import in the tree
parseNext: function() {
var next = this.nextToParse();
//console.log('parseNext', next);
if (next) {
this.parse(next);
}
Expand All @@ -47,6 +55,9 @@ var importParser = {
fn.call(this, elt);
}
},
// only 1 element may be parsed at a time; parsing is async so, each
// parsing implementation must inform the system that parsing is complete
// via markParsingComplete.
markParsing: function(elt) {
flags.parse && console.log('parsing', elt);
this.parsingElement = elt;
Expand All @@ -62,7 +73,12 @@ var importParser = {
},
parseImport: function(elt) {
elt.import.__importParsed = true;
// TODO(sorvell): onerror
// TODO(sorvell): consider if there's a better way to do this;
// expose an imports parsing hook; this is needed, for example, by the
// CustomElements polyfill.
if (HTMLImports.__importsParsingHook) {
HTMLImports.__importsParsingHook(elt);
}
// fire load event
if (elt.__resource) {
elt.dispatchEvent(new CustomEvent('load', {bubbles: false}));
Expand Down Expand Up @@ -102,6 +118,7 @@ var importParser = {
this.trackElement(elt);
document.head.appendChild(elt);
},
// tracks when a loadable element has loaded
trackElement: function(elt) {
var self = this;
var done = function() {
Expand Down Expand Up @@ -162,6 +179,7 @@ var importParser = {
}
this.markParsingComplete(scriptElt);
},
// determine the next element in the tree which should be parsed
nextToParse: function() {
return !this.parsingElement && this.nextToParseInDoc(mainDoc);
},
Expand All @@ -179,6 +197,7 @@ var importParser = {
// all nodes have been parsed, ready to parse import, if any
return link;
},
// return the set of parse selectors relevant for this node.
parseSelectorsForNode: function(node) {
var doc = node.ownerDocument || node;
return doc === mainDoc ? this.documentSelectors : this.importsSelectors;
Expand All @@ -197,7 +216,11 @@ var importParser = {
}
};

function nodeIsImport(elt) {
return (elt.localName === 'link') && (elt.rel === IMPORT_LINK_TYPE);
}

// style/stylesheet handling

// clone style with proper path resolution for main document
// NOTE: styles are the only elements that require direct path fixup.
Expand All @@ -208,6 +231,8 @@ function cloneStyle(style) {
return clone;
}

// path fixup: style elements in imports must be made relative to the main
// document. We fixup url's in url() and @import.
var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g;
var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g;

Expand All @@ -233,10 +258,6 @@ var path = {
}
}

function nodeIsImport(elt) {
return (elt.localName === 'link') && (elt.rel === IMPORT_LINK_TYPE);
}

// exports
scope.parser = importParser;
scope.path = path;
Expand Down
6 changes: 6 additions & 0 deletions src/boot.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ if (typeof window.CustomEvent !== 'function') {
var doc = window.ShadowDOMPolyfill ?
window.ShadowDOMPolyfill.wrapIfNeeded(document) : document;

// Fire the 'HTMLImportsLoaded' event when imports in document at load time
// have loaded. This event is required to simulate the script blocking
// behavior of native imports. A main document script that needs to be sure
// imports have loaded should wait for this event.
HTMLImports.whenImportsReady(function() {
HTMLImports.ready = true;
HTMLImports.readyTime = new Date().getTime();
Expand All @@ -31,6 +35,8 @@ HTMLImports.whenImportsReady(function() {
);
});


// no need to bootstrap the polyfill when native imports is available.
if (!HTMLImports.useNative) {
function bootstrap() {
HTMLImports.importer.bootDocument(doc);
Expand Down

0 comments on commit 0461b94

Please sign in to comment.