Skip to content

Instantly share code, notes, and snippets.

@pennyfx
Created May 22, 2013 20:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pennyfx/5630529 to your computer and use it in GitHub Desktop.
Save pennyfx/5630529 to your computer and use it in GitHub Desktop.
latest platform polyfill
/*
* Copyright 2012 The Polymer Authors. All rights reserved.
* Use of this source code is goverened by a BSD-style
* license that can be found in the LICENSE file.
*/
// SideTable is a weak map where possible. If WeakMap is not available the
// association is stored as an expando property.
var SideTable;
// TODO(arv): WeakMap does not allow for Node etc to be keys in Firefox
if (typeof WeakMap !== 'undefined' && navigator.userAgent.indexOf('Firefox/') < 0) {
SideTable = WeakMap;
} else {
(function() {
var defineProperty = Object.defineProperty;
var hasOwnProperty = Object.hasOwnProperty;
var counter = new Date().getTime() % 1e9;
SideTable = function() {
this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
};
SideTable.prototype = {
set: function(key, value) {
defineProperty(key, this.name, {value: value, writable: true});
},
get: function(key) {
return hasOwnProperty.call(key, this.name) ? key[this.name] : undefined;
},
delete: function(key) {
this.set(key, undefined);
}
}
})();
}
window.Platform = {};
var logFlags = {};
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
// poor man's adapter for template.content on various platform scenarios
window.templateContent = window.templateContent || function(inTemplate) {
return inTemplate.content;
};
// so we can call wrap/unwrap without testing for ShadowDOMPolyfill
window.wrap = window.unwrap = function(n){
return n;
}
window.createShadowRoot = function(inElement) {
return inElement.webkitCreateShadowRoot();
};
window.templateContent = function(inTemplate) {
// if MDV exists, it may need to boostrap this template to reveal content
if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) {
HTMLTemplateElement.bootstrap(inTemplate);
}
// fallback when there is no Shadow DOM polyfill, no MDV polyfill, and no
// native template support
if (!inTemplate.content && !inTemplate._content) {
var frag = document.createDocumentFragment();
while (inTemplate.firstChild) {
frag.appendChild(inTemplate.firstChild);
}
inTemplate._content = frag;
}
return inTemplate.content || inTemplate._content;
};
})();
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function(scope) {
// Old versions of iOS do not have bind.
if (!Function.prototype.bind) {
Function.prototype.bind = function(scope) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var args2 = args.slice();
args2.push.apply(args2, arguments);
return self.apply(scope, args2);
};
};
}
// namespace an import from CustomElements
// TODO(sjmiles): clean up this global
scope.mixin = window.mixin;
})(window.Platform);
// Copyright 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(scope) {
'use strict';
// polyfill DOMTokenList
// * add/remove: allow these methods to take multiple classNames
// * toggle: add a 2nd argument which forces the given state rather
// than toggling.
var add = DOMTokenList.prototype.add;
var remove = DOMTokenList.prototype.remove;
DOMTokenList.prototype.add = function() {
for (var i = 0; i < arguments.length; i++) {
add.call(this, arguments[i]);
}
};
DOMTokenList.prototype.remove = function() {
for (var i = 0; i < arguments.length; i++) {
remove.call(this, arguments[i]);
}
};
DOMTokenList.prototype.toggle = function(name, bool) {
if (arguments.length == 1) {
bool = !this.contains(name);
}
bool ? this.add(name) : this.remove(name);
};
DOMTokenList.prototype.switch = function(oldName, newName) {
oldName && this.remove(oldName);
newName && this.add(newName);
};
// make forEach work on NodeList
NodeList.prototype.forEach = function(cb, context) {
Array.prototype.slice.call(this).forEach(cb, context);
};
HTMLCollection.prototype.forEach = function(cb, context) {
Array.prototype.slice.call(this).forEach(cb, context);
};
// polyfill performance.now
if (!window.performance) {
var start = Date.now();
// only at millisecond precision
window.performance = {now: function(){ return Date.now() - start }};
}
// polyfill for requestAnimationFrame
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = (function() {
var nativeRaf = window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame;
return nativeRaf ?
function(callback) {
return nativeRaf(function() {
callback(performance.now());
});
} :
function( callback ){
return window.setTimeout(callback, 1000 / 60);
};
})();
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = (function() {
return window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
function(id) {
clearTimeout(id);
};
})();
}
// utility
function createDOM(inTagOrNode, inHTML, inAttrs) {
var dom = typeof inTagOrNode == 'string' ?
document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true);
dom.innerHTML = inHTML;
if (inAttrs) {
for (var n in inAttrs) {
dom.setAttribute(n, inAttrs[n]);
}
}
return dom;
}
// exports
scope.createDOM = createDOM;
})(window.Platform);
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
// poor man's adapter for template.content on various platform scenarios
window.templateContent = window.templateContent || function(inTemplate) {
return inTemplate.content;
};
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function(scope) {
if (!scope) {
scope = window.HTMLImports = {flags:{}};
}
var IMPORT_LINK_TYPE = 'import';
// highlander object represents a primary document (the argument to 'parse')
// at the root of a tree of documents
var importer = {
documents: {},
cache: {},
preloadSelectors: [
'link[rel=' + IMPORT_LINK_TYPE + ']',
'script[src]',
'link[rel=stylesheet]'
].join(','),
load: function(inDocument, inNext) {
// construct a loader instance
loader = new Loader(importer.loaded, inNext);
// alias the loader cache (for debugging)
loader.cache = importer.cache;
// add nodes from document into loader queue
importer.preload(inDocument);
},
preload: function(inDocument) {
// all preloadable nodes in inDocument
var nodes = inDocument.querySelectorAll(importer.preloadSelectors);
// only load imports from the main document
// TODO(sjmiles): do this by altering the selector list instead
if (inDocument === document) {
nodes = Array.prototype.filter.call(nodes, function(n) {
return isDocumentLink(n);
});
}
// add these nodes to loader's queue
loader.addNodes(nodes);
},
loaded: function(inUrl, inElt, inResource) {
if (isDocumentLink(inElt)) {
var document = importer.documents[inUrl];
// if we've never seen a document at this url
if (!document) {
// generate an HTMLDocument from data
document = makeDocument(inResource, inUrl);
// resolve resource paths relative to host document
path.resolvePathsInHTML(document);
// cache document
importer.documents[inUrl] = document;
// add nodes from this document to the loader queue
importer.preload(document);
}
// store document resource
inElt.content = inElt.__resource = document;
} else {
inElt.__resource = inResource;
// resolve stylesheet resource paths relative to host document
if (isStylesheetLink(inElt)) {
path.resolvePathsInStylesheet(inElt);
}
}
}
};
function isDocumentLink(inElt) {
return isLinkRel(inElt, IMPORT_LINK_TYPE);
}
function isStylesheetLink(inElt) {
return isLinkRel(inElt, 'stylesheet');
}
function isLinkRel(inElt, inRel) {
return (inElt.localName === 'link' && inElt.getAttribute('rel') === inRel);
}
function inMainDocument(inElt) {
return inElt.ownerDocument === document ||
// TODO(sjmiles): ShadowDOMPolyfill intrusion
inElt.ownerDocument.impl === document;
}
function makeDocument(inHTML, inUrl) {
// create a new HTML document
var doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE);
// cache the new document's source url
doc._URL = inUrl;
// establish a relative path via <base>
var base = doc.createElement('base');
base.setAttribute('href', document.baseURI);
doc.head.appendChild(base);
// install html
doc.body.innerHTML = inHTML;
return doc;
}
var loader;
var Loader = function(inOnLoad, inOnComplete) {
this.onload = inOnLoad;
this.oncomplete = inOnComplete;
this.inflight = 0;
this.pending = {};
this.cache = {};
};
Loader.prototype = {
addNodes: function(inNodes) {
// number of transactions to complete
this.inflight += inNodes.length;
// commence transactions
forEach(inNodes, this.require, this);
// anything to do?
this.checkDone();
},
require: function(inElt) {
var url = path.nodeUrl(inElt);
// TODO(sjmiles): ad-hoc
inElt.__nodeUrl = url;
// deduplication
if (!this.dedupe(url, inElt)) {
// fetch this resource
this.fetch(url, inElt);
}
},
dedupe: function(inUrl, inElt) {
if (this.pending[inUrl]) {
// add to list of nodes waiting for inUrl
this.pending[inUrl].push(inElt);
// don't need fetch
return true;
}
if (this.cache[inUrl]) {
// complete load using cache data
this.onload(inUrl, inElt, loader.cache[inUrl]);
// finished this transaction
this.tail();
// don't need fetch
return true;
}
// first node waiting for inUrl
this.pending[inUrl] = [inElt];
// need fetch (not a dupe)
return false;
},
fetch: function(inUrl, inElt) {
xhr.load(inUrl, function(err, resource) {
this.receive(inUrl, inElt, err, resource);
}.bind(this));
},
receive: function(inUrl, inElt, inErr, inResource) {
if (!inErr) {
loader.cache[inUrl] = inResource;
}
loader.pending[inUrl].forEach(function(e) {
if (!inErr) {
this.onload(inUrl, e, inResource);
}
this.tail();
}, this);
loader.pending[inUrl] = null;
},
tail: function() {
--this.inflight;
this.checkDone();
},
checkDone: function() {
if (!this.inflight) {
this.oncomplete();
}
}
};
var path = {
nodeUrl: function(inNode) {
return path.resolveUrl(path.getDocumentUrl(document), path.hrefOrSrc(inNode));
},
hrefOrSrc: function(inNode) {
return inNode.getAttribute("href") || inNode.getAttribute("src");
},
documentUrlFromNode: function(inNode) {
var url = path.getDocumentUrl(inNode.ownerDocument);
// take only the left side if there is a #
url = url.split('#')[0];
return url;
},
getDocumentUrl: function(inDocument) {
return inDocument &&
// TODO(sjmiles): ShadowDOMPolyfill intrusion
(inDocument._URL || (inDocument.impl && inDocument.impl._URL)
|| inDocument.baseURI || inDocument.URL)
|| '';
},
resolveUrl: function(inBaseUrl, inUrl, inRelativeToDocument) {
if (this.isAbsUrl(inUrl)) {
return inUrl;
}
var url = this.compressUrl(this.urlToPath(inBaseUrl) + inUrl);
if (inRelativeToDocument) {
url = path.makeRelPath(path.getDocumentUrl(document), url);
}
return url;
},
isAbsUrl: function(inUrl) {
return /(^data:)|(^http[s]?:)|(^\/)/.test(inUrl);
},
urlToPath: function(inBaseUrl) {
var parts = inBaseUrl.split("/");
parts.pop();
parts.push('');
return parts.join("/");
},
compressUrl: function(inUrl) {
var parts = inUrl.split("/");
for (var i=0, p; i<parts.length; i++) {
p = parts[i];
if (p === "..") {
parts.splice(i-1, 2);
i -= 2;
}
}
return parts.join("/");
},
// make a relative path from source to target
makeRelPath: function(inSource, inTarget) {
var s, t;
s = this.compressUrl(inSource).split("/");
t = this.compressUrl(inTarget).split("/");
while (s.length && s[0] === t[0]){
s.shift();
t.shift();
}
for(var i = 0, l = s.length-1; i < l; i++) {
t.unshift("..");
}
var r = t.join("/");
return r;
},
resolvePathsInHTML: function(inRoot) {
var docUrl = path.documentUrlFromNode(inRoot.body);
// TODO(sorvell): MDV Polyfill Intrusion
if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) {
HTMLTemplateElement.bootstrap(inRoot);
}
var node = inRoot.body;
path._resolvePathsInHTML(node, docUrl);
},
_resolvePathsInHTML: function(inRoot, inUrl) {
path.resolveAttributes(inRoot, inUrl);
path.resolveStyleElts(inRoot, inUrl);
// handle templates, if supported
if (window.templateContent) {
var templates = inRoot.querySelectorAll('template');
if (templates) {
forEach(templates, function(t) {
path._resolvePathsInHTML(templateContent(t), inUrl);
});
}
}
},
resolvePathsInStylesheet: function(inSheet) {
var docUrl = path.nodeUrl(inSheet);
inSheet.__resource = path.resolveCssText(inSheet.__resource, docUrl);
},
resolveStyleElts: function(inRoot, inUrl) {
var styles = inRoot.querySelectorAll('style');
if (styles) {
forEach(styles, function(style) {
style.textContent = path.resolveCssText(style.textContent, inUrl);
});
}
},
resolveCssText: function(inCssText, inBaseUrl) {
return inCssText.replace(/url\([^)]*\)/g, function(inMatch) {
// find the url path, ignore quotes in url string
var urlPath = inMatch.replace(/["']/g, "").slice(4, -1);
urlPath = path.resolveUrl(inBaseUrl, urlPath, true);
return "url(" + urlPath + ")";
});
},
resolveAttributes: function(inRoot, inUrl) {
// search for attributes that host urls
var nodes = inRoot && inRoot.querySelectorAll(URL_ATTRS_SELECTOR);
if (nodes) {
forEach(nodes, function(n) {
this.resolveNodeAttributes(n, inUrl);
}, this);
}
},
resolveNodeAttributes: function(inNode, inUrl) {
URL_ATTRS.forEach(function(v) {
var attr = inNode.attributes[v];
if (attr && attr.value &&
(attr.value.search(URL_TEMPLATE_SEARCH) < 0)) {
var urlPath = path.resolveUrl(inUrl, attr.value, true);
attr.value = urlPath;
}
});
}
};
var URL_ATTRS = ['href', 'src', 'action'];
var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']';
var URL_TEMPLATE_SEARCH = '{{.*}}';
var xhr = {
async: true,
ok: function(inRequest) {
return (inRequest.status >= 200 && inRequest.status < 300)
|| (inRequest.status === 304);
},
load: function(url, next, nextContext) {
var request = new XMLHttpRequest();
if (scope.flags.debug || scope.flags.bust) {
url += '?' + Math.random();
}
request.open('GET', url, xhr.async);
request.addEventListener('readystatechange', function(e) {
if (request.readyState === 4) {
next.call(nextContext, !xhr.ok(request) && request,
request.response, url);
}
});
request.send();
}
};
var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
// exports
scope.importer = importer;
scope.getDocumentUrl = path.getDocumentUrl;
// bootstrap
// IE shim for CustomEvent
if (typeof window.CustomEvent !== 'function') {
window.CustomEvent = function(inType) {
var e = document.createEvent('HTMLEvents');
e.initEvent(inType, true, true);
return e;
};
}
window.addEventListener('load', function() {
// preload document resource trees
importer.load(document, function() {
// TODO(sjmiles): ShadowDOM polyfill pollution
var doc = window.ShadowDOMPolyfill ? ShadowDOMPolyfill.wrap(document)
: document;
HTMLImports.readyTime = new Date().getTime();
// send HTMLImportsLoaded when finished
doc.body.dispatchEvent(
new CustomEvent('HTMLImportsLoaded', {bubbles: true})
);
});
});
})(window.HTMLImports);
/*
* Copyright 2012 The Polymer Authors. All rights reserved.
* Use of this source code is goverened by a BSD-style
* license that can be found in the LICENSE file.
*/
(function(global) {
var registrationsTable = new SideTable();
// We use setImmediate or postMessage for our future callback.
var setImmediate = window.msSetImmediate;
// Use post message to emulate setImmediate.
if (!setImmediate) {
var setImmediateQueue = [];
var sentinel = String(Math.random());
window.addEventListener('message', function(e) {
if (e.data === sentinel) {
var queue = setImmediateQueue;
setImmediateQueue = [];
queue.forEach(function(func) {
func();
});
}
});
setImmediate = function(func) {
setImmediateQueue.push(func);
window.postMessage(sentinel, '*');
};
}
// This is used to ensure that we never schedule 2 callas to setImmediate
var isScheduled = false;
// Keep track of observers that needs to be notified next time.
var scheduledObservers = [];
/**
* Schedules |dispatchCallback| to be called in the future.
* @param {MutationObserver} observer
*/
function scheduleCallback(observer) {
scheduledObservers.push(observer);
if (!isScheduled) {
isScheduled = true;
setImmediate(dispatchCallbacks);
}
}
function wrapIfNeeded(node) {
return window.ShadowDOMPolyfill &&
window.ShadowDOMPolyfill.wrapIfNeeded(node) ||
node;
}
function dispatchCallbacks() {
// http://dom.spec.whatwg.org/#mutation-observers
isScheduled = false; // Used to allow a new setImmediate call above.
var observers = scheduledObservers;
scheduledObservers = [];
// Sort observers based on their creation UID (incremental).
observers.sort(function(o1, o2) {
return o1.uid_ - o2.uid_;
});
var anyNonEmpty = false;
observers.forEach(function(observer) {
// 2.1, 2.2
var queue = observer.takeRecords();
// 2.3. Remove all transient registered observers whose observer is mo.
removeTransientObserversFor(observer);
// 2.4
if (queue.length) {
observer.callback_(queue, observer);
anyNonEmpty = true;
}
});
// 3.
if (anyNonEmpty)
dispatchCallbacks();
}
function removeTransientObserversFor(observer) {
observer.nodes_.forEach(function(node) {
var registrations = registrationsTable.get(node);
if (!registrations)
return;
registrations.forEach(function(registration) {
if (registration.observer === observer)
registration.removeTransientObservers();
});
});
}
/**
* This function is used for the "For each registered observer observer (with
* observer's options as options) in target's list of registered observers,
* run these substeps:" and the "For each ancestor ancestor of target, and for
* each registered observer observer (with options options) in ancestor's list
* of registered observers, run these substeps:" part of the algorithms. The
* |options.subtree| is checked to ensure that the callback is called
* correctly.
*
* @param {Node} target
* @param {function(MutationObserverInit):MutationRecord} callback
*/
function forEachAncestorAndObserverEnqueueRecord(target, callback) {
for (var node = target; node; node = node.parentNode) {
var registrations = registrationsTable.get(node);
if (registrations) {
for (var j = 0; j < registrations.length; j++) {
var registration = registrations[j];
var options = registration.options;
// Only target ignores subtree.
if (node !== target && !options.subtree)
continue;
var record = callback(options);
if (record)
registration.enqueue(record);
}
}
}
}
var uidCounter = 0;
/**
* The class that maps to the DOM MutationObserver interface.
* @param {Function} callback.
* @constructor
*/
function JsMutationObserver(callback) {
this.callback_ = callback;
this.nodes_ = [];
this.records_ = [];
this.uid_ = ++uidCounter;
}
JsMutationObserver.prototype = {
observe: function(target, options) {
target = wrapIfNeeded(target);
// 1.1
if (!options.childList && !options.attributes && !options.characterData ||
// 1.2
options.attributeOldValue && !options.attributes ||
// 1.3
options.attributeFilter && options.attributeFilter.length &&
!options.attributes ||
// 1.4
options.characterDataOldValue && !options.characterData) {
throw new SyntaxError();
}
var registrations = registrationsTable.get(target);
if (!registrations)
registrationsTable.set(target, registrations = []);
// 2
// If target's list of registered observers already includes a registered
// observer associated with the context object, replace that registered
// observer's options with options.
var registration;
for (var i = 0; i < registrations.length; i++) {
if (registrations[i].observer === this) {
registration = registrations[i];
registration.removeListeners();
registration.options = options;
break;
}
}
// 3.
// Otherwise, add a new registered observer to target's list of registered
// observers with the context object as the observer and options as the
// options, and add target to context object's list of nodes on which it
// is registered.
if (!registration) {
registration = new Registration(this, target, options);
registrations.push(registration);
this.nodes_.push(target);
}
registration.addListeners();
},
disconnect: function() {
this.nodes_.forEach(function(node) {
var registrations = registrationsTable.get(node);
for (var i = 0; i < registrations.length; i++) {
var registration = registrations[i];
if (registration.observer === this) {
registration.removeListeners();
registrations.splice(i, 1);
// Each node can only have one registered observer associated with
// this observer.
break;
}
}
}, this);
this.records_ = [];
},
takeRecords: function() {
var copyOfRecords = this.records_;
this.records_ = [];
return copyOfRecords;
}
};
/**
* @param {string} type
* @param {Node} target
* @constructor
*/
function MutationRecord(type, target) {
this.type = type;
this.target = target;
this.addedNodes = [];
this.removedNodes = [];
this.previousSibling = null;
this.nextSibling = null;
this.attributeName = null;
this.attributeNamespace = null;
this.oldValue = null;
}
function copyMutationRecord(original) {
var record = new MutationRecord(original.type, original.target);
record.addedNodes = original.addedNodes.slice();
record.removedNodes = original.removedNodes.slice();
record.previousSibling = original.previousSibling;
record.nextSibling = original.nextSibling;
record.attributeName = original.attributeName;
record.attributeNamespace = original.attributeNamespace;
record.oldValue = original.oldValue;
return record;
};
// We keep track of the two (possibly one) records used in a single mutation.
var currentRecord, recordWithOldValue;
/**
* Creates a record without |oldValue| and caches it as |currentRecord| for
* later use.
* @param {string} oldValue
* @return {MutationRecord}
*/
function getRecord(type, target) {
return currentRecord = new MutationRecord(type, target);
}
/**
* Gets or creates a record with |oldValue| based in the |currentRecord|
* @param {string} oldValue
* @return {MutationRecord}
*/
function getRecordWithOldValue(oldValue) {
if (recordWithOldValue)
return recordWithOldValue;
recordWithOldValue = copyMutationRecord(currentRecord);
recordWithOldValue.oldValue = oldValue;
return recordWithOldValue;
}
function clearRecords() {
currentRecord = recordWithOldValue = undefined;
}
/**
* @param {MutationRecord} record
* @return {boolean} Whether the record represents a record from the current
* mutation event.
*/
function recordRepresentsCurrentMutation(record) {
return record === recordWithOldValue || record === currentRecord;
}
/**
* Selects which record, if any, to replace the last record in the queue.
* This returns |null| if no record should be replaced.
*
* @param {MutationRecord} lastRecord
* @param {MutationRecord} newRecord
* @param {MutationRecord}
*/
function selectRecord(lastRecord, newRecord) {
if (lastRecord === newRecord)
return lastRecord;
// Check if the the record we are adding represents the same record. If
// so, we keep the one with the oldValue in it.
if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord))
return recordWithOldValue;
return null;
}
/**
* Class used to represent a registered observer.
* @param {MutationObserver} observer
* @param {Node} target
* @param {MutationObserverInit} options
* @constructor
*/
function Registration(observer, target, options) {
this.observer = observer;
this.target = target;
this.options = options;
this.transientObservedNodes = [];
}
Registration.prototype = {
enqueue: function(record) {
var records = this.observer.records_;
var length = records.length;
// There are cases where we replace the last record with the new record.
// For example if the record represents the same mutation we need to use
// the one with the oldValue. If we get same record (this can happen as we
// walk up the tree) we ignore the new record.
if (records.length > 0) {
var lastRecord = records[length - 1];
var recordToReplaceLast = selectRecord(lastRecord, record);
if (recordToReplaceLast) {
records[length - 1] = recordToReplaceLast;
return;
}
} else {
scheduleCallback(this.observer);
}
records[length] = record;
},
addListeners: function() {
this.addListeners_(this.target);
},
addListeners_: function(node) {
var options = this.options;
if (options.attributes)
node.addEventListener('DOMAttrModified', this, true);
if (options.characterData)
node.addEventListener('DOMCharacterDataModified', this, true);
if (options.childList)
node.addEventListener('DOMNodeInserted', this, true);
if (options.childList || options.subtree)
node.addEventListener('DOMNodeRemoved', this, true);
},
removeListeners: function() {
this.removeListeners_(this.target);
},
removeListeners_: function(node) {
var options = this.options;
if (options.attributes)
node.removeEventListener('DOMAttrModified', this, true);
if (options.characterData)
node.removeEventListener('DOMCharacterDataModified', this, true);
if (options.childList)
node.removeEventListener('DOMNodeInserted', this, true);
if (options.childList || options.subtree)
node.removeEventListener('DOMNodeRemoved', this, true);
},
/**
* Adds a transient observer on node. The transient observer gets removed
* next time we deliver the change records.
* @param {Node} node
*/
addTransientObserver: function(node) {
// Don't add transient observers on the target itself. We already have all
// the required listeners set up on the target.
if (node === this.target)
return;
this.addListeners_(node);
this.transientObservedNodes.push(node);
var registrations = registrationsTable.get(node);
if (!registrations)
registrationsTable.set(node, registrations = []);
// We know that registrations does not contain this because we already
// checked if node === this.target.
registrations.push(this);
},
removeTransientObservers: function() {
var transientObservedNodes = this.transientObservedNodes;
this.transientObservedNodes = [];
transientObservedNodes.forEach(function(node) {
// Transient observers are never added to the target.
this.removeListeners_(node);
var registrations = registrationsTable.get(node);
for (var i = 0; i < registrations.length; i++) {
if (registrations[i] === this) {
registrations.splice(i, 1);
// Each node can only have one registered observer associated with
// this observer.
break;
}
}
}, this);
},
handleEvent: function(e) {
// Stop propagation since we are managing the propagation manually.
// This means that other mutation events on the page will not work
// correctly but that is by design.
e.stopImmediatePropagation();
switch (e.type) {
case 'DOMAttrModified':
// http://dom.spec.whatwg.org/#concept-mo-queue-attributes
var name = e.attrName;
var namespace = e.relatedNode.namespaceURI;
var target = e.target;
// 1.
var record = new getRecord('attributes', target);
record.attributeName = name;
record.attributeNamespace = namespace;
// 2.
var oldValue =
e.attrChange === MutationEvent.ADDITION ? null : e.prevValue;
forEachAncestorAndObserverEnqueueRecord(target, function(options) {
// 3.1, 4.2
if (!options.attributes)
return;
// 3.2, 4.3
if (options.attributeFilter && options.attributeFilter.length &&
options.attributeFilter.indexOf(name) === -1 &&
options.attributeFilter.indexOf(namespace) === -1) {
return;
}
// 3.3, 4.4
if (options.attributeOldValue)
return getRecordWithOldValue(oldValue);
// 3.4, 4.5
return record;
});
break;
case 'DOMCharacterDataModified':
// http://dom.spec.whatwg.org/#concept-mo-queue-characterdata
var target = e.target;
// 1.
var record = getRecord('characterData', target);
// 2.
var oldValue = e.prevValue;
forEachAncestorAndObserverEnqueueRecord(target, function(options) {
// 3.1, 4.2
if (!options.characterData)
return;
// 3.2, 4.3
if (options.characterDataOldValue)
return getRecordWithOldValue(oldValue);
// 3.3, 4.4
return record;
});
break;
case 'DOMNodeRemoved':
this.addTransientObserver(e.target);
// Fall through.
case 'DOMNodeInserted':
// http://dom.spec.whatwg.org/#concept-mo-queue-childlist
var target = e.relatedNode;
var changedNode = e.target;
var addedNodes, removedNodes;
if (e.type === 'DOMNodeInserted') {
addedNodes = [changedNode];
removedNodes = [];
} else {
addedNodes = [];
removedNodes = [changedNode];
}
var previousSibling = changedNode.previousSibling;
var nextSibling = changedNode.nextSibling;
// 1.
var record = getRecord('childList', target);
record.addedNodes = addedNodes;
record.removedNodes = removedNodes;
record.previousSibling = previousSibling;
record.nextSibling = nextSibling;
forEachAncestorAndObserverEnqueueRecord(target, function(options) {
// 2.1, 3.2
if (!options.childList)
return;
// 2.2, 3.3
return record;
});
}
clearRecords();
}
};
global.JsMutationObserver = JsMutationObserver;
})(this);
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
if (!window.MutationObserver) {
window.MutationObserver =
window.WebKitMutationObserver ||
window.JsMutationObserver;
if (!MutationObserver) {
throw new Error("no mutation observer support");
}
}
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
/**
* Implements `document.register`
* @module CustomElements
*/
/**
* Polyfilled extensions to the `document` object.
* @class Document
*/
(function(scope) {
if (!scope) {
scope = window.CustomElements = {flags:{}};
}
// native document.register?
scope.hasNative = (document.webkitRegister || document.register) && scope.flags.register === 'native';
if (scope.hasNative) {
// normalize
document.register = document.register || document.webkitRegister;
var nop = function() {};
// exports
scope.registry = {};
scope.upgradeElement = nop;
} else {
/**
* Registers a custom tag name with the document.
*
* When a registered element is created, a `readyCallback` method is called
* in the scope of the element. The `readyCallback` method can be specified on
* either `inOptions.prototype` or `inOptions.lifecycle` with the latter taking
* precedence.
*
* @method register
* @param {String} inName The tag name to register. Must include a dash ('-'),
* for example 'x-component'.
* @param {Object} inOptions
* @param {String} [inOptions.extends]
* (_off spec_) Tag name of an element to extend (or blank for a new
* element). This parameter is not part of the specification, but instead
* is a hint for the polyfill because the extendee is difficult to infer.
* Remember that the input prototype must chain to the extended element's
* prototype (or HTMLElement.prototype) regardless of the value of
* `extends`.
* @param {Object} inOptions.prototype The prototype to use for the new
* element. The prototype must inherit from HTMLElement.
* @param {Object} [inOptions.lifecycle]
* Callbacks that fire at important phases in the life of the custom
* element.
*
* @example
* FancyButton = document.register("fancy-button", {
* extends: 'button',
* prototype: Object.create(HTMLButtonElement.prototype, {
* readyCallback: {
* value: function() {
* console.log("a fancy-button was created",
* }
* }
* })
* });
* @return {Function} Constructor for the newly registered type.
*/
function register(inName, inOptions) {
//console.warn('document.register("' + inName + '", ', inOptions, ')');
// construct a defintion out of options
// TODO(sjmiles): probably should clone inOptions instead of mutating it
var definition = inOptions || {};
if (!inName) {
// TODO(sjmiles): replace with more appropriate error (Erik can probably
// offer guidance)
throw new Error('Name argument must not be empty');
}
// record name
definition.name = inName;
// must have a prototype, default to an extension of HTMLElement
// TODO(sjmiles): probably should throw if no prototype, check spec
if (!definition.prototype) {
// TODO(sjmiles): replace with more appropriate error (Erik can probably
// offer guidance)
throw new Error('Options missing required prototype property');
}
// ensure a lifecycle object so we don't have to null test it
definition.lifecycle = definition.lifecycle || {};
// build a list of ancestral custom elements (for native base detection)
// TODO(sjmiles): we used to need to store this, but current code only
// uses it in 'resolveTagName': it should probably be inlined
definition.ancestry = ancestry(definition.extends);
// extensions of native specializations of HTMLElement require localName
// to remain native, and use secondary 'is' specifier for extension type
resolveTagName(definition);
// some platforms require modifications to the user-supplied prototype
// chain
resolvePrototypeChain(definition);
// overrides to implement callbacks
// TODO(sjmiles): should support access via .attributes NamedNodeMap
definition.prototype.setAttribute = setAttribute;
definition.prototype.removeAttribute = removeAttribute;
// 7.1.5: Register the DEFINITION with DOCUMENT
registerDefinition(inName, definition);
// 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE
// 7.1.8. Return the output of the previous step.
definition.ctor = generateConstructor(definition);
definition.ctor.prototype = definition.prototype;
// if initial parsing is complete
if (scope.ready) {
// upgrade any pre-existing nodes of this type
scope.upgradeAll(document);
}
return definition.ctor;
}
function ancestry(inExtends) {
var extendee = registry[inExtends];
if (extendee) {
return ancestry(extendee.extends).concat([extendee]);
}
return [];
}
function resolveTagName(inDefinition) {
// if we are explicitly extending something, that thing is our
// baseTag, unless it represents a custom component
var baseTag = inDefinition.extends;
// if our ancestry includes custom components, we only have a
// baseTag if one of them does
for (var i=0, a; (a=inDefinition.ancestry[i]); i++) {
baseTag = a.is && a.tag;
}
// our tag is our baseTag, if it exists, and otherwise just our name
inDefinition.tag = baseTag || inDefinition.name;
if (baseTag) {
// if there is a base tag, use secondary 'is' specifier
inDefinition.is = inDefinition.name;
}
}
function resolvePrototypeChain(inDefinition) {
// if we don't support __proto__ we need to locate the native level
// prototype for precise mixing in
if (!Object.__proto__) {
if (inDefinition.is) {
// for non-trivial extensions, work out both prototypes
var inst = document.createElement(inDefinition.tag);
var native = Object.getPrototypeOf(inst);
} else {
// otherwise, use the default
native = HTMLElement.prototype;
}
}
// cache this in case of mixin
inDefinition.native = native;
}
// SECTION 4
function instantiate(inDefinition) {
// 4.a.1. Create a new object that implements PROTOTYPE
// 4.a.2. Let ELEMENT by this new object
//
// the custom element instantiation algorithm must also ensure that the
// output is a valid DOM element with the proper wrapper in place.
//
return upgrade(domCreateElement(inDefinition.tag), inDefinition);
}
function upgrade(inElement, inDefinition) {
// some definitions specify an 'is' attribute
if (inDefinition.is) {
inElement.setAttribute('is', inDefinition.is);
}
// make 'element' implement inDefinition.prototype
implement(inElement, inDefinition);
// flag as upgraded
inElement.__upgraded__ = true;
// there should never be a shadow root on inElement at this point
// we require child nodes be upgraded before ready
scope.upgradeSubtree(inElement);
// lifecycle management
ready(inElement);
// OUTPUT
return inElement;
}
function implement(inElement, inDefinition) {
// prototype swizzling is best
if (Object.__proto__) {
inElement.__proto__ = inDefinition.prototype;
} else {
// where above we can re-acquire inPrototype via
// getPrototypeOf(Element), we cannot do so when
// we use mixin, so we install a magic reference
customMixin(inElement, inDefinition.prototype, inDefinition.native);
inElement.__proto__ = inDefinition.prototype;
}
}
function customMixin(inTarget, inSrc, inNative) {
// TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of
// any property. This set should be precalculated. We also need to
// consider this for supporting 'super'.
var used = {};
// start with inSrc
var p = inSrc;
// sometimes the default is HTMLUnknownElement.prototype instead of
// HTMLElement.prototype, so we add a test
// the idea is to avoid mixing in native prototypes, so adding
// the second test is WLOG
while (p !== inNative && p !== HTMLUnknownElement.prototype) {
var keys = Object.getOwnPropertyNames(p);
for (var i=0, k; k=keys[i]; i++) {
if (!used[k]) {
Object.defineProperty(inTarget, k,
Object.getOwnPropertyDescriptor(p, k));
used[k] = 1;
}
}
p = Object.getPrototypeOf(p);
}
}
function ready(inElement) {
// invoke readyCallback
if (inElement.readyCallback) {
inElement.readyCallback();
}
}
// attribute watching
var originalSetAttribute = HTMLElement.prototype.setAttribute;
var originalRemoveAttribute = HTMLElement.prototype.removeAttribute;
function setAttribute(name, value) {
changeAttribute.call(this, name, value, originalSetAttribute);
}
function removeAttribute(name, value) {
changeAttribute.call(this, name, value, originalRemoveAttribute);
}
function changeAttribute(name, value, operation) {
var oldValue = this.getAttribute(name);
operation.apply(this, arguments);
if (this.attributeChangedCallback
&& (this.getAttribute(name) !== oldValue)) {
this.attributeChangedCallback(name, oldValue);
}
}
// element registry (maps tag names to definitions)
var registry = {};
function registerDefinition(inName, inDefinition) {
registry[inName] = inDefinition;
}
function generateConstructor(inDefinition) {
return function() {
return instantiate(inDefinition);
};
}
function createElement(inTag) {
var definition = registry[inTag];
if (definition) {
return new definition.ctor();
}
return domCreateElement(inTag);
}
function upgradeElement(inElement) {
if (!inElement.__upgraded__ && (inElement.nodeType === Node.ELEMENT_NODE)) {
var type = inElement.getAttribute('is') || inElement.localName;
var definition = registry[type];
return definition && upgrade(inElement, definition);
}
}
// capture native createElement before we override it
var domCreateElement = document.createElement.bind(document);
// exports
document.register = register;
document.createElement = createElement; // override
scope.registry = registry;
/**
* Upgrade an element to a custom element. Upgrading an element
* causes the custom prototype to be applied, an `is` attribute
* to be attached (as needed), and invocation of the `readyCallback`.
* `upgrade` does nothing if the element is already upgraded, or
* if it matches no registered custom tag name.
*
* @method ugprade
* @param {Element} inElement The element to upgrade.
* @return {Element} The upgraded element.
*/
scope.upgrade = upgradeElement;
}
})(window.CustomElements);
/*
Copyright 2013 The Polymer Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
(function(scope){
/*
if (HTMLElement.prototype.webkitShadowRoot) {
Object.defineProperty(HTMLElement.prototype, 'shadowRoot', {
get: function() {
return this.webkitShadowRoot;
}
};
}
*/
// walk the subtree rooted at node, applying 'find(element, data)' function
// to each element
// if 'find' returns true for 'element', do not search element's subtree
function findAll(node, find, data) {
var e = node.firstElementChild;
if (!e) {
e = node.firstChild;
while (e && e.nodeType !== Node.ELEMENT_NODE) {
e = e.nextSibling;
}
}
while (e) {
if (find(e, data) !== true) {
findAll(e, find, data);
}
e = e.nextElementSibling;
}
return null;
}
// walk the subtree rooted at node, including descent into shadow-roots,
// applying 'cb' to each element
function forSubtree(node, cb) {
//logFlags.dom && node.childNodes && node.childNodes.length && console.group('subTree: ', node);
findAll(node, function(e) {
if (cb(e)) {
return true;
}
if (e.webkitShadowRoot) {
forSubtree(e.webkitShadowRoot, cb);
}
});
if (node.webkitShadowRoot) {
forSubtree(node.webkitShadowRoot, cb);
}
//logFlags.dom && node.childNodes && node.childNodes.length && console.groupEnd();
}
// manage lifecycle on added node
function added(node) {
if (upgrade(node)) {
insertedNode(node);
return true;
}
inserted(node);
}
// manage lifecycle on added node's subtree only
function addedSubtree(node) {
forSubtree(node, function(e) {
if (added(e)) {
return true;
}
});
}
// manage lifecycle on added node and it's subtree
function addedNode(node) {
return added(node) || addedSubtree(node);
}
// upgrade custom elements at node, if applicable
function upgrade(node) {
if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) {
var type = node.getAttribute('is') || node.localName;
var definition = scope.registry[type];
if (definition) {
logFlags.dom && console.group('upgrade:', node.localName);
scope.upgrade(node);
logFlags.dom && console.groupEnd();
return true;
}
}
}
function insertedNode(node) {
inserted(node);
if (inDocument(node)) {
forSubtree(node, function(e) {
inserted(e);
});
}
}
// TODO(sjmiles): if there are descents into trees that can never have inDocument(*) true, fix this
function inserted(element) {
// TODO(sjmiles): it's possible we were inserted and removed in the space
// of one microtask, in which case we won't be 'inDocument' here
// But there are other cases where we are testing for inserted without
// specific knowledge of mutations, and must test 'inDocument' to determine
// whether to call inserted
// If we can factor these cases into separate code paths we can have
// better diagnostics.
// TODO(sjmiles): when logging, do work on all custom elements so we can
// track behavior even when callbacks not defined
//console.log('inserted: ', element.localName);
if (element.insertedCallback || (element.__upgraded__ && logFlags.dom)) {
logFlags.dom && console.group('inserted:', element.localName);
if (inDocument(element)) {
element.__inserted = (element.__inserted || 0) + 1;
// if we are in a 'removed' state, bluntly adjust to an 'inserted' state
if (element.__inserted < 1) {
element.__inserted = 1;
}
// if we are 'over inserted', squelch the callback
if (element.__inserted > 1) {
logFlags.dom && console.warn('inserted:', element.localName,
'insert/remove count:', element.__inserted)
} else if (element.insertedCallback) {
logFlags.dom && console.log('inserted:', element.localName);
element.insertedCallback();
}
}
logFlags.dom && console.groupEnd();
}
}
function removedNode(node) {
removed(node);
forSubtree(node, function(e) {
removed(e);
});
}
function removed(element) {
// TODO(sjmiles): temporary: do work on all custom elements so we can track
// behavior even when callbacks not defined
if (element.removedCallback || (element.__upgraded__ && logFlags.dom)) {
logFlags.dom && console.log('removed:', element.localName);
if (!inDocument(element)) {
element.__inserted = (element.__inserted || 0) - 1;
// if we are in a 'inserted' state, bluntly adjust to an 'removed' state
if (element.__inserted > 0) {
element.__inserted = 0;
}
// if we are 'over removed', squelch the callback
if (element.__inserted < 0) {
logFlags.dom && console.warn('removed:', element.localName,
'insert/remove count:', element.__inserted)
} else if (element.removedCallback) {
element.removedCallback();
}
}
}
}
function inDocument(element) {
var p = element;
while (p) {
if (p == element.ownerDocument) {
return true;
}
p = p.parentNode || p.host;
}
}
function watchShadow(node) {
if (node.webkitShadowRoot && !node.webkitShadowRoot.__watched) {
logFlags.dom && console.log('watching shadow-root for: ', node.localName);
observe(node.webkitShadowRoot);
node.webkitShadowRoot.__watched = true;
}
}
function watchAllShadows(node) {
watchShadow(node);
forSubtree(node, function(e) {
watchShadow(node);
});
}
function filter(inNode) {
switch (inNode.localName) {
case 'style':
case 'script':
case 'template':
case undefined:
return true;
}
}
function handler(mutations) {
//
if (logFlags.dom) {
var mx = mutations[0];
if (mx && mx.type === 'childList' && mx.addedNodes) {
if (mx.addedNodes) {
var d = mx.addedNodes[0];
while (d && d !== document && !d.host) {
d = d.parentNode;
}
var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || '';
u = u.split('/?').shift().split('/').pop();
}
}
console.group('mutations (%d) [%s]', mutations.length, u || '');
}
//
mutations.forEach(function(mx) {
//logFlags.dom && console.group('mutation');
if (mx.type === 'childList') {
forEach(mx.addedNodes, function(n) {
//logFlags.dom && console.log(n.localName);
if (filter(n)) {
return;
}
// watch shadow-roots on nodes that have had them attached manually
// TODO(sjmiles): remove if createShadowRoot is overridden
// TODO(sjmiles): removed as an optimization, manual shadow roots
// must be watched explicitly
//watchAllShadows(n);
// nodes added may need lifecycle management
addedNode(n);
});
// removed nodes may need lifecycle management
forEach(mx.removedNodes, function(n) {
//logFlags.dom && console.log(n.localName);
if (filter(n)) {
return;
}
removedNode(n);
});
}
//logFlags.dom && console.groupEnd();
});
logFlags.dom && console.groupEnd();
};
var observer = new MutationObserver(handler);
function takeRecords() {
// TODO(sjmiles): ask Raf why we have to call handler ourselves
handler(observer.takeRecords());
}
var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
function observe(inRoot) {
observer.observe(inRoot, {childList: true, subtree: true});
}
function observeDocument(document) {
observe(document);
}
function upgradeDocument(document) {
logFlags.dom && console.group('upgradeDocument: ', (document.URL || document._URL || '').split('/').pop());
addedNode(document);
logFlags.dom && console.groupEnd();
}
// exports
scope.watchShadow = watchShadow;
scope.watchAllShadows = watchAllShadows;
scope.upgradeAll = addedNode;
scope.upgradeSubtree = addedSubtree;
scope.observeDocument = observeDocument;
scope.upgradeDocument = upgradeDocument;
scope.takeRecords = takeRecords;
})(window.CustomElements);
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function(){
var HTMLElementElement = function(inElement) {
inElement.register = HTMLElementElement.prototype.register;
parseElementElement(inElement);
return inElement;
};
HTMLElementElement.prototype = {
register: function(inMore) {
if (inMore) {
this.options.lifecycle = inMore.lifecycle;
if (inMore.prototype) {
mixin(this.options.prototype, inMore.prototype);
}
}
}
};
function parseElementElement(inElement) {
// options to glean from inElement attributes
var options = {
name: '',
extends: null
};
// glean them
takeAttributes(inElement, options);
// default base
var base = HTMLElement.prototype;
// optional specified base
if (options.extends) {
// build an instance of options.extends
var archetype = document.createElement(options.extends);
// acquire the prototype
// TODO(sjmiles): __proto__ may be hinted by the custom element
// system on platforms that don't support native __proto__
// on those platforms the API is mixed into archetype and the
// effective base is not archetype's real prototype
base = archetype.__proto__ || Object.getPrototypeOf(archetype);
}
// extend base
options.prototype = Object.create(base);
// install options
inElement.options = options;
// locate user script
var script = inElement.querySelector('script,scripts');
if (script) {
// execute user script in 'inElement' context
executeComponentScript(script.textContent, inElement, options.name);
};
// register our new element
var ctor = document.register(options.name, options);
inElement.ctor = ctor;
// store optional constructor reference
var refName = inElement.getAttribute('constructor');
if (refName) {
window[refName] = ctor;
}
}
// each property in inDictionary takes a value
// from the matching attribute in inElement, if any
function takeAttributes(inElement, inDictionary) {
for (var n in inDictionary) {
var a = inElement.attributes[n];
if (a) {
inDictionary[n] = a.value;
}
}
}
// invoke inScript in inContext scope
function executeComponentScript(inScript, inContext, inName) {
// set (highlander) context
context = inContext;
// source location
var owner = context.ownerDocument;
var url = (owner._URL || owner.URL || owner.impl
&& (owner.impl._URL || owner.impl.URL));
// ensure the component has a unique source map so it can be debugged
// if the name matches the filename part of the owning document's url,
// use this, otherwise, add ":<name>" to the document url.
var match = url.match(/.*\/([^.]*)[.]?.*$/);
if (match) {
var name = match[1];
url += name != inName ? ':' + inName : '';
}
// compose script
var code = "__componentScript('"
+ inName
+ "', function(){"
+ inScript
+ "});"
+ "\n//@ sourceURL=" + url + "\n"
;
// inject script
eval(code);
}
var context;
// global necessary for script injection
window.__componentScript = function(inName, inFunc) {
inFunc.call(context);
};
// utility
// copy all properties from inProps (et al) to inObj
function mixin(inObj/*, inProps, inMoreProps, ...*/) {
var obj = inObj || {};
for (var i = 1; i < arguments.length; i++) {
var p = arguments[i];
try {
for (var n in p) {
copyProperty(n, p, obj);
}
} catch(x) {
}
}
return obj;
}
// copy property inName from inSource object to inTarget object
function copyProperty(inName, inSource, inTarget) {
var pd = getPropertyDescriptor(inSource, inName);
Object.defineProperty(inTarget, inName, pd);
}
// get property descriptor for inName on inObject, even if
// inName exists on some link in inObject's prototype chain
function getPropertyDescriptor(inObject, inName) {
if (inObject) {
var pd = Object.getOwnPropertyDescriptor(inObject, inName);
return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName);
}
}
// exports
window.HTMLElementElement = HTMLElementElement;
// TODO(sjmiles): completely ad-hoc, used by Polymer.register
window.mixin = mixin;
})();
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
var IMPORT_LINK_TYPE = 'import';
// highlander object for parsing a document tree
var componentParser = {
selectors: [
'link[rel=' + IMPORT_LINK_TYPE + ']',
'link[rel=stylesheet]',
'script[src]',
'script',
'style',
'element'
],
map: {
link: 'parseLink',
script: 'parseScript',
element: 'parseElement',
style: 'parseStyle'
},
parse: function(inDocument) {
if (!inDocument.__parsed) {
// only parse once
inDocument.__parsed = true;
// all parsable elements in inDocument (depth-first pre-order traversal)
var elts = inDocument.querySelectorAll(cp.selectors);
// for each parsable node type, call the mapped parsing method
forEach(elts, function(e) {
//console.log(map[e.localName] + ":", path.nodeUrl(e));
cp[cp.map[e.localName]](e);
});
// upgrade all upgradeable static elements, anything dynamically
// created should be caught by observer
CustomElements.upgradeDocument(inDocument);
// observe document for dom changes
CustomElements.observeDocument(inDocument);
}
},
parseLink: function(inLinkElt) {
// imports
if (isDocumentLink(inLinkElt)) {
if (inLinkElt.content) {
cp.parse(inLinkElt.content);
}
} else if (!inMainDocument(inLinkElt)
&& inLinkElt.parentNode
&& !isElementElementChild(inLinkElt)) {
document.head.appendChild(inLinkElt);
}
},
parseScript: function(inScriptElt) {
// ignore scripts in primary document, they are already loaded
if (inMainDocument(inScriptElt)) {
return;
}
// ignore scripts inside <element>
if (isElementElementChild(inScriptElt)) {
return;
}
// otherwise, evaluate now
var code = inScriptElt.__resource || inScriptElt.textContent;
if (code) {
code += "\n//@ sourceURL=" + inScriptElt.__nodeUrl + "\n";
eval.call(window, code);
}
},
parseStyle: function(inStyleElt) {
if (!inMainDocument(inStyleElt) && !isElementElementChild(inStyleElt)) {
document.querySelector('head').appendChild(inStyleElt);
}
},
parseElement: function(inElementElt) {
new HTMLElementElement(inElementElt);
}
};
var cp = componentParser;
function inMainDocument(inElt) {
return inElt.ownerDocument === document ||
// TODO(sjmiles): ShadowDOMPolyfill intrusion
inElt.ownerDocument.impl === document;
}
function isDocumentLink(inElt) {
return (inElt.localName === 'link'
&& inElt.getAttribute('rel') === IMPORT_LINK_TYPE);
}
function isElementElementChild(inElt) {
if (inElt.parentNode && inElt.parentNode.localName === 'element') {
return true;
}
}
var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
// exports
CustomElements.parser = componentParser;
})();
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function(){
// bootstrap parsing
// IE shim for CustomEvent
if (typeof window.CustomEvent !== 'function') {
window.CustomEvent = function(inType) {
var e = document.createEvent('HTMLEvents');
e.initEvent(inType, true, true);
return e;
};
}
function bootstrap() {
// go async so call stack can unwind
setTimeout(function() {
// parse document
CustomElements.parser.parse(document);
// set internal flag
CustomElements.ready = true;
CustomElements.readyTime = new Date().getTime();
if (window.HTMLImports) {
CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime;
}
// notify system
document.body.dispatchEvent(
new CustomEvent('WebComponentsReady', {bubbles: true})
);
}, 0);
}
// TODO(sjmiles): 'window' has no wrappability under ShadowDOM polyfill, so
// we are forced to split into two versions
if (window.HTMLImports) {
document.addEventListener('HTMLImportsLoaded', bootstrap);
} else {
window.addEventListener('load', bootstrap);
}
})();
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
// inject style sheet
document.write('<style>element {display: none;} /* injected by platform.js */</style>');
if (window.ShadowDOMPolyfill) {
function nop() {};
// disable shadow dom watching
CustomElements.watchShadow = nop;
CustomElements.watchAllShadows = nop;
// ensure wrapped inputs for these functions
var fns = ['upgradeAll', 'upgradeSubtree', 'observeDocument',
'upgradeDocument'];
// cache originals
var original = {};
fns.forEach(function(fn) {
original[fn] = CustomElements[fn];
});
// override
fns.forEach(function(fn) {
CustomElements[fn] = function(inNode) {
return original[fn](wrap(inNode));
};
});
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment