mirror of
https://github.com/jlengrand/webcomponentsjs.git
synced 2026-03-10 08:51:22 +00:00
Consolidate web components polyfilsl for easier maintenance and distribution.
This commit is contained in:
50
src/CustomElements/CustomElements.js
Normal file
50
src/CustomElements/CustomElements.js
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
(function() {
|
||||
|
||||
// Estblish polyfill scope. We do this here to store flags. Flags are not
|
||||
// supported in the build.
|
||||
window.CustomElements = window.CustomElements || {flags:{}};
|
||||
|
||||
// Flags. Convert url arguments to flags
|
||||
var flags = {};
|
||||
if (!flags.noOpts) {
|
||||
location.search.slice(1).split('&').forEach(function(o) {
|
||||
o = o.split('=');
|
||||
o[0] && (flags[o[0]] = o[1] || true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Load.
|
||||
var file = 'CustomElements.js';
|
||||
|
||||
var modules = [
|
||||
'../WeakMap/WeakMap.js',
|
||||
'../MutationObserver/MutationObserver.js',
|
||||
'base.js',
|
||||
'traverse.js',
|
||||
'observe.js',
|
||||
'upgrade.js',
|
||||
'register.js',
|
||||
'boot.js'
|
||||
];
|
||||
|
||||
var src =
|
||||
document.querySelector('script[src*="' + file + '"]').getAttribute('src');
|
||||
var basePath = src.slice(0, src.indexOf(file));
|
||||
|
||||
modules.forEach(function(f) {
|
||||
document.write('<script src="' + basePath + f + '"></script>');
|
||||
});
|
||||
|
||||
// exports
|
||||
CustomElements.flags = flags;
|
||||
|
||||
})();
|
||||
38
src/CustomElements/base.js
Normal file
38
src/CustomElements/base.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
window.CustomElements = window.CustomElements || {flags:{}};
|
||||
|
||||
(function(scope) {
|
||||
|
||||
// imports
|
||||
var flags = scope.flags;
|
||||
|
||||
// world's simplest module initializer
|
||||
var modules = [];
|
||||
var addModule = function(module) {
|
||||
modules.push(module);
|
||||
};
|
||||
|
||||
var initializeModules = function() {
|
||||
modules.forEach(function(module) {
|
||||
module(scope);
|
||||
});
|
||||
};
|
||||
|
||||
// exports
|
||||
scope.addModule = addModule;
|
||||
scope.initializeModules = initializeModules;
|
||||
scope.hasNative = Boolean(document.registerElement);
|
||||
|
||||
// NOTE: For consistent timing, use native custom elements only when not
|
||||
// polyfilling other key related web components features.
|
||||
scope.useNative = !flags.register && scope.hasNative &&
|
||||
!window.ShadowDOMPolyfill && (!window.HTMLImports || HTMLImports.useNative);
|
||||
|
||||
})(CustomElements);
|
||||
113
src/CustomElements/boot.js
Normal file
113
src/CustomElements/boot.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
(function(scope){
|
||||
|
||||
// imports
|
||||
var useNative = scope.useNative;
|
||||
var initializeModules = scope.initializeModules;
|
||||
|
||||
// If native, setup stub api and bail.
|
||||
// NOTE: we fire `WebComponentsReady` under native for api compatibility
|
||||
if (useNative) {
|
||||
// stub
|
||||
var nop = function() {};
|
||||
|
||||
// exports
|
||||
scope.watchShadow = nop;
|
||||
scope.upgradeAll = nop;
|
||||
scope.upgradeDocumentTree = nop;
|
||||
scope.takeRecords = nop;
|
||||
scope.instanceof = function(obj, base) {
|
||||
return obj instanceof base;
|
||||
};
|
||||
|
||||
} else {
|
||||
// Initialize polyfill modules. Note, polyfill modules are loaded but not
|
||||
// executed; this is a convenient way to control which modules run when
|
||||
// the polyfill is required and allows the polyfill to load even when it's
|
||||
// not needed.
|
||||
initializeModules();
|
||||
}
|
||||
|
||||
// imports
|
||||
var upgradeDocumentTree = scope.upgradeDocumentTree;
|
||||
|
||||
// ShadowDOM polyfill wraps elements but some elements like `document`
|
||||
// cannot be wrapped so we help the polyfill by wrapping some elements.
|
||||
if (!window.wrap) {
|
||||
if (window.ShadowDOMPolyfill) {
|
||||
window.wrap = ShadowDOMPolyfill.wrapIfNeeded;
|
||||
window.unwrap = ShadowDOMPolyfill.unwrapIfNeeded;
|
||||
} else {
|
||||
window.wrap = window.unwrap = function(node) {
|
||||
return node;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// bootstrap parsing
|
||||
function bootstrap() {
|
||||
// parse document
|
||||
upgradeDocumentTree(wrap(document));
|
||||
// install upgrade hook if HTMLImports are available
|
||||
if (window.HTMLImports) {
|
||||
HTMLImports.__importsParsingHook = function(elt) {
|
||||
upgradeDocumentTree(wrap(elt.import));
|
||||
//CustomElements.parser.parse(elt.import);
|
||||
};
|
||||
}
|
||||
// set internal 'ready' flag, now document.registerElement will trigger
|
||||
// synchronous upgrades
|
||||
CustomElements.ready = true;
|
||||
// async to ensure *native* custom elements upgrade prior to this
|
||||
// DOMContentLoaded can fire before elements upgrade (e.g. when there's
|
||||
// an external script)
|
||||
setTimeout(function() {
|
||||
// capture blunt profiling data
|
||||
CustomElements.readyTime = Date.now();
|
||||
if (window.HTMLImports) {
|
||||
CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime;
|
||||
}
|
||||
// notify the system that we are bootstrapped
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('WebComponentsReady', {bubbles: true})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// CustomEvent shim for IE
|
||||
if (typeof window.CustomEvent !== 'function') {
|
||||
window.CustomEvent = function(inType, params) {
|
||||
params = params || {};
|
||||
var e = document.createEvent('CustomEvent');
|
||||
e.initCustomEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable), params.detail);
|
||||
return e;
|
||||
};
|
||||
window.CustomEvent.prototype = window.Event.prototype;
|
||||
}
|
||||
|
||||
// When loading at readyState complete time (or via flag), boot custom elements
|
||||
// immediately.
|
||||
// If relevant, HTMLImports must already be loaded.
|
||||
if (document.readyState === 'complete' || scope.flags.eager) {
|
||||
bootstrap();
|
||||
// When loading at readyState interactive time, bootstrap only if HTMLImports
|
||||
// are not pending. Also avoid IE as the semantics of this state are unreliable.
|
||||
} else if (document.readyState === 'interactive' && !window.attachEvent &&
|
||||
(!window.HTMLImports || window.HTMLImports.ready)) {
|
||||
bootstrap();
|
||||
// When loading at other readyStates, wait for the appropriate DOM event to
|
||||
// bootstrap.
|
||||
} else {
|
||||
var loadEvent = window.HTMLImports && !HTMLImports.ready ?
|
||||
'HTMLImportsLoaded' : 'DOMContentLoaded';
|
||||
window.addEventListener(loadEvent, bootstrap);
|
||||
}
|
||||
|
||||
})(window.CustomElements);
|
||||
307
src/CustomElements/observe.js
Normal file
307
src/CustomElements/observe.js
Normal file
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements custom element observation and attached/detached callbacks
|
||||
* @module observe
|
||||
*/
|
||||
|
||||
CustomElements.addModule(function(scope){
|
||||
|
||||
// imports
|
||||
var flags = scope.flags;
|
||||
var forSubtree = scope.forSubtree;
|
||||
var forDocumentTree = scope.forDocumentTree;
|
||||
|
||||
/*
|
||||
Manage nodes attached to document trees
|
||||
*/
|
||||
|
||||
// manage lifecycle on added node and it's subtree; upgrade the node and
|
||||
// entire subtree if necessary and process attached for the node and entire
|
||||
// subtree
|
||||
function addedNode(node) {
|
||||
return added(node) || addedSubtree(node);
|
||||
}
|
||||
|
||||
// manage lifecycle on added node; upgrade if necessary and process attached
|
||||
function added(node) {
|
||||
if (scope.upgrade(node)) {
|
||||
return true;
|
||||
}
|
||||
attached(node);
|
||||
}
|
||||
|
||||
// manage lifecycle on added node's subtree only; allows the entire subtree
|
||||
// to upgrade if necessary and process attached
|
||||
function addedSubtree(node) {
|
||||
forSubtree(node, function(e) {
|
||||
if (added(e)) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function attachedNode(node) {
|
||||
attached(node);
|
||||
// only check subtree if node is actually in document
|
||||
if (inDocument(node)) {
|
||||
forSubtree(node, function(e) {
|
||||
attached(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// On platforms without MutationObserver, mutations may not be
|
||||
// reliable and therefore attached/detached are not reliable.
|
||||
// To make these callbacks less likely to fail, we defer all inserts and removes
|
||||
// to give a chance for elements to be attached into dom.
|
||||
// This ensures attachedCallback fires for elements that are created and
|
||||
// immediately added to dom.
|
||||
var hasPolyfillMutations = (!window.MutationObserver ||
|
||||
(window.MutationObserver === window.JsMutationObserver));
|
||||
scope.hasPolyfillMutations = hasPolyfillMutations;
|
||||
|
||||
var isPendingMutations = false;
|
||||
var pendingMutations = [];
|
||||
function deferMutation(fn) {
|
||||
pendingMutations.push(fn);
|
||||
if (!isPendingMutations) {
|
||||
isPendingMutations = true;
|
||||
setTimeout(takeMutations);
|
||||
}
|
||||
}
|
||||
|
||||
function takeMutations() {
|
||||
isPendingMutations = false;
|
||||
var $p = pendingMutations;
|
||||
for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) {
|
||||
p();
|
||||
}
|
||||
pendingMutations = [];
|
||||
}
|
||||
|
||||
function attached(element) {
|
||||
if (hasPolyfillMutations) {
|
||||
deferMutation(function() {
|
||||
_attached(element);
|
||||
});
|
||||
} else {
|
||||
_attached(element);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: due to how MO works (see comments below), an element may be attached
|
||||
// multiple times so we protect against extra processing here.
|
||||
function _attached(element) {
|
||||
// track element for insertion if it's upgraded and cares about insertion
|
||||
if (element.__upgraded__ &&
|
||||
(element.attachedCallback || element.detachedCallback)) {
|
||||
// bail if the element is already marked as attached and proceed only
|
||||
// if it's actually in the document at this moment.
|
||||
if (!element.__attached && inDocument(element)) {
|
||||
element.__attached = true;
|
||||
if (element.attachedCallback) {
|
||||
element.attachedCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Manage nodes detached from document trees
|
||||
*/
|
||||
|
||||
// manage lifecycle on detached node and it's subtree; process detached
|
||||
// for the node and entire subtree
|
||||
function detachedNode(node) {
|
||||
detached(node);
|
||||
forSubtree(node, function(e) {
|
||||
detached(e);
|
||||
});
|
||||
}
|
||||
|
||||
function detached(element) {
|
||||
if (hasPolyfillMutations) {
|
||||
deferMutation(function() {
|
||||
_detached(element);
|
||||
});
|
||||
} else {
|
||||
_detached(element);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: due to how MO works (see comments below), an element may be detached
|
||||
// multiple times so we protect against extra processing here.
|
||||
function _detached(element) {
|
||||
// track element for removal if it's upgraded and cares about removal
|
||||
if (element.__upgraded__ &&
|
||||
(element.attachedCallback || element.detachedCallback)) {
|
||||
// bail if the element is already marked as not attached and proceed only
|
||||
// if it's actually *not* in the document at this moment.
|
||||
if (element.__attached && !inDocument(element)) {
|
||||
element.__attached = false;
|
||||
if (element.detachedCallback) {
|
||||
element.detachedCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recurse up the tree to check if an element is actually in the main document.
|
||||
function inDocument(element) {
|
||||
var p = element;
|
||||
var doc = wrap(document);
|
||||
while (p) {
|
||||
if (p == doc) {
|
||||
return true;
|
||||
}
|
||||
p = p.parentNode || p.host;
|
||||
}
|
||||
}
|
||||
|
||||
// Install an element observer on all shadowRoots owned by node.
|
||||
function watchShadow(node) {
|
||||
if (node.shadowRoot && !node.shadowRoot.__watched) {
|
||||
flags.dom && console.log('watching shadow-root for: ', node.localName);
|
||||
// watch all unwatched roots...
|
||||
var root = node.shadowRoot;
|
||||
while (root) {
|
||||
observe(root);
|
||||
root = root.olderShadowRoot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
NOTE: In order to process all mutations, it's necessary to recurse into
|
||||
any added nodes. However, it's not possible to determine a priori if a node
|
||||
will get its own mutation record. This means
|
||||
*nodes can be seen multiple times*.
|
||||
|
||||
Here's an example:
|
||||
|
||||
(1) In this case, recursion is required to see `child`:
|
||||
|
||||
node.innerHTML = '<div><child></child></div>'
|
||||
|
||||
(2) In this case, child will get its own mutation record:
|
||||
|
||||
node.appendChild(div).appendChild(child);
|
||||
*/
|
||||
function handler(mutations) {
|
||||
// for logging only
|
||||
if (flags.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 || '');
|
||||
}
|
||||
// handle mutations
|
||||
mutations.forEach(function(mx) {
|
||||
if (mx.type === 'childList') {
|
||||
forEach(mx.addedNodes, function(n) {
|
||||
if (!n.localName) {
|
||||
return;
|
||||
}
|
||||
addedNode(n);
|
||||
});
|
||||
forEach(mx.removedNodes, function(n) {
|
||||
if (!n.localName) {
|
||||
return;
|
||||
}
|
||||
detachedNode(n);
|
||||
});
|
||||
}
|
||||
});
|
||||
flags.dom && console.groupEnd();
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
When elements are added to the dom, upgrade and attached/detached may be
|
||||
asynchronous. `CustomElements.takeRecords` can be called to process any
|
||||
pending upgrades and attached/detached callbacks synchronously.
|
||||
*/
|
||||
function takeRecords(node) {
|
||||
node = wrap(node);
|
||||
// If the optional node is not supplied, assume we mean the whole document.
|
||||
if (!node) {
|
||||
node = wrap(document);
|
||||
}
|
||||
// Find the root of the tree, which will be an Document or ShadowRoot.
|
||||
while (node.parentNode) {
|
||||
node = node.parentNode;
|
||||
}
|
||||
var observer = node.__observer;
|
||||
if (observer) {
|
||||
handler(observer.takeRecords());
|
||||
takeMutations();
|
||||
}
|
||||
}
|
||||
|
||||
var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
|
||||
|
||||
|
||||
// observe a node tree; bail if it's already being observed.
|
||||
function observe(inRoot) {
|
||||
if (inRoot.__observer) {
|
||||
return;
|
||||
}
|
||||
// For each ShadowRoot, we create a new MutationObserver, so the root can be
|
||||
// garbage collected once all references to the `inRoot` node are gone.
|
||||
var observer = new MutationObserver(handler);
|
||||
observer.observe(inRoot, {childList: true, subtree: true});
|
||||
inRoot.__observer = observer;
|
||||
}
|
||||
|
||||
// upgrade an entire document and observe it for elements changes.
|
||||
function upgradeDocument(doc) {
|
||||
doc = wrap(doc);
|
||||
flags.dom && console.group('upgradeDocument: ', (doc.baseURI).split('/').pop());
|
||||
addedNode(doc);
|
||||
observe(doc);
|
||||
flags.dom && console.groupEnd();
|
||||
}
|
||||
|
||||
/*
|
||||
This method is intended to be called when the document tree (including imports)
|
||||
has pending custom elements to upgrade. It can be called multiple times and
|
||||
should do nothing if no elements are in need of upgrade.
|
||||
*/
|
||||
function upgradeDocumentTree(doc) {
|
||||
forDocumentTree(doc, upgradeDocument);
|
||||
}
|
||||
|
||||
|
||||
// ensure that all ShadowRoots watch for CustomElements.
|
||||
var originalCreateShadowRoot = Element.prototype.createShadowRoot;
|
||||
Element.prototype.createShadowRoot = function() {
|
||||
var root = originalCreateShadowRoot.call(this);
|
||||
CustomElements.watchShadow(this);
|
||||
return root;
|
||||
};
|
||||
|
||||
// exports
|
||||
scope.watchShadow = watchShadow;
|
||||
scope.upgradeDocumentTree = upgradeDocumentTree;
|
||||
scope.upgradeSubtree = addedSubtree;
|
||||
scope.upgradeAll = addedNode;
|
||||
scope.attachedNode = attachedNode;
|
||||
scope.takeRecords = takeRecords;
|
||||
|
||||
});
|
||||
347
src/CustomElements/register.js
Normal file
347
src/CustomElements/register.js
Normal file
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements `document.registerElement`
|
||||
* @module register
|
||||
*/
|
||||
|
||||
/**
|
||||
* Polyfilled extensions to the `document` object.
|
||||
* @class Document
|
||||
*/
|
||||
|
||||
CustomElements.addModule(function(scope) {
|
||||
|
||||
// imports
|
||||
var upgradeDocumentTree = scope.upgradeDocumentTree;
|
||||
var upgrade = scope.upgrade;
|
||||
var upgradeWithDefinition = scope.upgradeWithDefinition;
|
||||
var implementPrototype = scope.implementPrototype;
|
||||
var useNative = scope.useNative;
|
||||
|
||||
/**
|
||||
* 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 `options.prototype` or `options.lifecycle` with the latter taking
|
||||
* precedence.
|
||||
*
|
||||
* @method register
|
||||
* @param {String} name The tag name to register. Must include a dash ('-'),
|
||||
* for example 'x-component'.
|
||||
* @param {Object} options
|
||||
* @param {String} [options.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} options.prototype The prototype to use for the new
|
||||
* element. The prototype must inherit from HTMLElement.
|
||||
* @param {Object} [options.lifecycle]
|
||||
* Callbacks that fire at important phases in the life of the custom
|
||||
* element.
|
||||
*
|
||||
* @example
|
||||
* FancyButton = document.registerElement("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(name, options) {
|
||||
//console.warn('document.registerElement("' + name + '", ', options, ')');
|
||||
// construct a defintion out of options
|
||||
// TODO(sjmiles): probably should clone options instead of mutating it
|
||||
var definition = options || {};
|
||||
if (!name) {
|
||||
throw new Error('document.registerElement: first argument `name` must not be empty');
|
||||
}
|
||||
if (name.indexOf('-') < 0) {
|
||||
throw new Error('document.registerElement: first argument (\'name\') must contain a dash (\'-\'). Argument provided was \'' + String(name) + '\'.');
|
||||
}
|
||||
// prevent registering reserved names
|
||||
if (isReservedTag(name)) {
|
||||
throw new Error('Failed to execute \'registerElement\' on \'Document\': Registration failed for type \'' + String(name) + '\'. The type name is invalid.');
|
||||
}
|
||||
// elements may only be registered once
|
||||
if (getRegisteredDefinition(name)) {
|
||||
throw new Error('DuplicateDefinitionError: a type with name \'' + String(name) + '\' is already registered');
|
||||
}
|
||||
// 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 (EricB can probably
|
||||
// offer guidance)
|
||||
throw new Error('Options missing required prototype property');
|
||||
}
|
||||
// record name
|
||||
definition.__name = name.toLowerCase();
|
||||
// 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 attributeChanged callback
|
||||
overrideAttributeApi(definition.prototype);
|
||||
// 7.1.5: Register the DEFINITION with DOCUMENT
|
||||
registerDefinition(definition.__name, 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;
|
||||
// force our .constructor to be our actual constructor
|
||||
definition.prototype.constructor = definition.ctor;
|
||||
// if initial parsing is complete
|
||||
if (scope.ready) {
|
||||
// upgrade any pre-existing nodes of this type
|
||||
upgradeDocumentTree(document);
|
||||
}
|
||||
return definition.ctor;
|
||||
}
|
||||
|
||||
// attribute watching
|
||||
function overrideAttributeApi(prototype) {
|
||||
// overrides to implement callbacks
|
||||
// TODO(sjmiles): should support access via .attributes NamedNodeMap
|
||||
// TODO(sjmiles): preserves user defined overrides, if any
|
||||
if (prototype.setAttribute._polyfilled) {
|
||||
return;
|
||||
}
|
||||
var setAttribute = prototype.setAttribute;
|
||||
prototype.setAttribute = function(name, value) {
|
||||
changeAttribute.call(this, name, value, setAttribute);
|
||||
};
|
||||
var removeAttribute = prototype.removeAttribute;
|
||||
prototype.removeAttribute = function(name) {
|
||||
changeAttribute.call(this, name, null, removeAttribute);
|
||||
};
|
||||
prototype.setAttribute._polyfilled = true;
|
||||
}
|
||||
|
||||
// https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/
|
||||
// index.html#dfn-attribute-changed-callback
|
||||
function changeAttribute(name, value, operation) {
|
||||
name = name.toLowerCase();
|
||||
var oldValue = this.getAttribute(name);
|
||||
operation.apply(this, arguments);
|
||||
var newValue = this.getAttribute(name);
|
||||
if (this.attributeChangedCallback &&
|
||||
(newValue !== oldValue)) {
|
||||
this.attributeChangedCallback(name, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
function isReservedTag(name) {
|
||||
for (var i = 0; i < reservedTagList.length; i++) {
|
||||
if (name === reservedTagList[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var reservedTagList = [
|
||||
'annotation-xml', 'color-profile', 'font-face', 'font-face-src',
|
||||
'font-face-uri', 'font-face-format', 'font-face-name', 'missing-glyph'
|
||||
];
|
||||
|
||||
function ancestry(extnds) {
|
||||
var extendee = getRegisteredDefinition(extnds);
|
||||
if (extendee) {
|
||||
return ancestry(extendee.extends).concat([extendee]);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function resolveTagName(definition) {
|
||||
// if we are explicitly extending something, that thing is our
|
||||
// baseTag, unless it represents a custom component
|
||||
var baseTag = definition.extends;
|
||||
// if our ancestry includes custom components, we only have a
|
||||
// baseTag if one of them does
|
||||
for (var i=0, a; (a=definition.ancestry[i]); i++) {
|
||||
baseTag = a.is && a.tag;
|
||||
}
|
||||
// our tag is our baseTag, if it exists, and otherwise just our name
|
||||
definition.tag = baseTag || definition.__name;
|
||||
if (baseTag) {
|
||||
// if there is a base tag, use secondary 'is' specifier
|
||||
definition.is = definition.__name;
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePrototypeChain(definition) {
|
||||
// if we don't support __proto__ we need to locate the native level
|
||||
// prototype for precise mixing in
|
||||
if (!Object.__proto__) {
|
||||
// default prototype
|
||||
var nativePrototype = HTMLElement.prototype;
|
||||
// work out prototype when using type-extension
|
||||
if (definition.is) {
|
||||
var inst = document.createElement(definition.tag);
|
||||
var expectedPrototype = Object.getPrototypeOf(inst);
|
||||
// only set nativePrototype if it will actually appear in the definition's chain
|
||||
if (expectedPrototype === definition.prototype) {
|
||||
nativePrototype = expectedPrototype;
|
||||
}
|
||||
}
|
||||
// ensure __proto__ reference is installed at each point on the prototype
|
||||
// chain.
|
||||
// NOTE: On platforms without __proto__, a mixin strategy is used instead
|
||||
// of prototype swizzling. In this case, this generated __proto__ provides
|
||||
// limited support for prototype traversal.
|
||||
var proto = definition.prototype, ancestor;
|
||||
while (proto && (proto !== nativePrototype)) {
|
||||
ancestor = Object.getPrototypeOf(proto);
|
||||
proto.__proto__ = ancestor;
|
||||
proto = ancestor;
|
||||
}
|
||||
// cache this in case of mixin
|
||||
definition.native = nativePrototype;
|
||||
}
|
||||
}
|
||||
|
||||
// SECTION 4
|
||||
|
||||
function instantiate(definition) {
|
||||
// 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 upgradeWithDefinition(domCreateElement(definition.tag), definition);
|
||||
}
|
||||
|
||||
// element registry (maps tag names to definitions)
|
||||
|
||||
var registry = {};
|
||||
|
||||
function getRegisteredDefinition(name) {
|
||||
if (name) {
|
||||
return registry[name.toLowerCase()];
|
||||
}
|
||||
}
|
||||
|
||||
function registerDefinition(name, definition) {
|
||||
registry[name] = definition;
|
||||
}
|
||||
|
||||
function generateConstructor(definition) {
|
||||
return function() {
|
||||
return instantiate(definition);
|
||||
};
|
||||
}
|
||||
|
||||
var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
||||
function createElementNS(namespace, tag, typeExtension) {
|
||||
// NOTE: we do not support non-HTML elements,
|
||||
// just call createElementNS for non HTML Elements
|
||||
if (namespace === HTML_NAMESPACE) {
|
||||
return createElement(tag, typeExtension);
|
||||
} else {
|
||||
return domCreateElementNS(namespace, tag);
|
||||
}
|
||||
}
|
||||
|
||||
function createElement(tag, typeExtension) {
|
||||
// TODO(sjmiles): ignore 'tag' when using 'typeExtension', we could
|
||||
// error check it, or perhaps there should only ever be one argument
|
||||
var definition = getRegisteredDefinition(typeExtension || tag);
|
||||
if (definition) {
|
||||
if (tag == definition.tag && typeExtension == definition.is) {
|
||||
return new definition.ctor();
|
||||
}
|
||||
// Handle empty string for type extension.
|
||||
if (!typeExtension && !definition.is) {
|
||||
return new definition.ctor();
|
||||
}
|
||||
}
|
||||
var element;
|
||||
if (typeExtension) {
|
||||
element = createElement(tag);
|
||||
element.setAttribute('is', typeExtension);
|
||||
return element;
|
||||
}
|
||||
element = domCreateElement(tag);
|
||||
// Custom tags should be HTMLElements even if not upgraded.
|
||||
if (tag.indexOf('-') >= 0) {
|
||||
implementPrototype(element, HTMLElement);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
function cloneNode(deep) {
|
||||
// call original clone
|
||||
var n = domCloneNode.call(this, deep);
|
||||
// upgrade the element and subtree
|
||||
upgrade(n);
|
||||
// return the clone
|
||||
return n;
|
||||
}
|
||||
|
||||
// capture native createElement before we override it
|
||||
var domCreateElement = document.createElement.bind(document);
|
||||
var domCreateElementNS = document.createElementNS.bind(document);
|
||||
// capture native cloneNode before we override it
|
||||
var domCloneNode = Node.prototype.cloneNode;
|
||||
|
||||
// Create a custom 'instanceof'. This is necessary when CustomElements
|
||||
// are implemented via a mixin strategy, as for example on IE10.
|
||||
var isInstance;
|
||||
if (!Object.__proto__ && !useNative) {
|
||||
isInstance = function(obj, ctor) {
|
||||
var p = obj;
|
||||
while (p) {
|
||||
// NOTE: this is not technically correct since we're not checking if
|
||||
// an object is an instance of a constructor; however, this should
|
||||
// be good enough for the mixin strategy.
|
||||
if (p === ctor.prototype) {
|
||||
return true;
|
||||
}
|
||||
p = p.__proto__;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
} else {
|
||||
isInstance = function(obj, base) {
|
||||
return obj instanceof base;
|
||||
};
|
||||
}
|
||||
|
||||
// exports
|
||||
document.registerElement = register;
|
||||
document.createElement = createElement; // override
|
||||
document.createElementNS = createElementNS; // override
|
||||
Node.prototype.cloneNode = cloneNode; // override
|
||||
scope.registry = registry;
|
||||
scope.instanceof = isInstance;
|
||||
scope.reservedTagList = reservedTagList;
|
||||
scope.getRegisteredDefinition = getRegisteredDefinition;
|
||||
|
||||
// bc
|
||||
document.register = document.registerElement;
|
||||
|
||||
});
|
||||
92
src/CustomElements/traverse.js
Normal file
92
src/CustomElements/traverse.js
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
// helper methods for traversing through element trees
|
||||
CustomElements.addModule(function(scope){
|
||||
|
||||
// imports
|
||||
var IMPORT_LINK_TYPE = window.HTMLImports ? HTMLImports.IMPORT_LINK_TYPE : 'none';
|
||||
|
||||
// walk the subtree rooted at node, including descent into shadow-roots,
|
||||
// applying 'cb' to each element
|
||||
function forSubtree(node, cb) {
|
||||
//flags.dom && node.childNodes && node.childNodes.length && console.group('subTree: ', node);
|
||||
findAllElements(node, function(e) {
|
||||
if (cb(e)) {
|
||||
return true;
|
||||
}
|
||||
forRoots(e, cb);
|
||||
});
|
||||
forRoots(node, cb);
|
||||
//flags.dom && node.childNodes && node.childNodes.length && console.groupEnd();
|
||||
}
|
||||
|
||||
|
||||
// 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 findAllElements(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) {
|
||||
findAllElements(e, find, data);
|
||||
}
|
||||
e = e.nextElementSibling;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// walk all shadowRoots on a given node.
|
||||
function forRoots(node, cb) {
|
||||
var root = node.shadowRoot;
|
||||
while(root) {
|
||||
forSubtree(root, cb);
|
||||
root = root.olderShadowRoot;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Note that the import tree can consume itself and therefore special care
|
||||
must be taken to avoid recursion.
|
||||
*/
|
||||
var processingDocuments;
|
||||
function forDocumentTree(doc, cb) {
|
||||
processingDocuments = [];
|
||||
_forDocumentTree(doc, cb);
|
||||
processingDocuments = null;
|
||||
}
|
||||
|
||||
|
||||
function _forDocumentTree(doc, cb) {
|
||||
doc = wrap(doc);
|
||||
if (processingDocuments.indexOf(doc) >= 0) {
|
||||
return;
|
||||
}
|
||||
processingDocuments.push(doc);
|
||||
var imports = doc.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']');
|
||||
for (var i=0, l=imports.length, n; (i<l) && (n=imports[i]); i++) {
|
||||
if (n.import) {
|
||||
_forDocumentTree(n.import, cb);
|
||||
}
|
||||
}
|
||||
cb(doc);
|
||||
}
|
||||
|
||||
// exports
|
||||
scope.forDocumentTree = forDocumentTree;
|
||||
scope.forSubtree = forSubtree;
|
||||
|
||||
|
||||
});
|
||||
117
src/CustomElements/upgrade.js
Normal file
117
src/CustomElements/upgrade.js
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements custom element upgrading
|
||||
* @module upgrade
|
||||
*/
|
||||
|
||||
CustomElements.addModule(function(scope) {
|
||||
|
||||
// imports
|
||||
var flags = scope.flags;
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
* If the element is in the main document, the `attachedkCallback` method
|
||||
* will be invoked.
|
||||
* `upgrade` does nothing if the element is already upgraded, or
|
||||
* if it matches no registered custom tag name.
|
||||
*
|
||||
* @method ugprade
|
||||
* @param {Element} element The element to upgrade.
|
||||
* @return {Element} The upgraded element.
|
||||
*/
|
||||
// Upgrade a node if it can be upgraded and is not already.
|
||||
function upgrade(node) {
|
||||
if (!node.__upgraded__ && (node.nodeType === Node.ELEMENT_NODE)) {
|
||||
var is = node.getAttribute('is');
|
||||
var definition = scope.getRegisteredDefinition(is || node.localName);
|
||||
if (definition) {
|
||||
if (is && definition.tag == node.localName) {
|
||||
return upgradeWithDefinition(node, definition);
|
||||
} else if (!is && !definition.extends) {
|
||||
return upgradeWithDefinition(node, definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function upgradeWithDefinition(element, definition) {
|
||||
flags.upgrade && console.group('upgrade:', element.localName);
|
||||
// some definitions specify an 'is' attribute
|
||||
if (definition.is) {
|
||||
element.setAttribute('is', definition.is);
|
||||
}
|
||||
// make 'element' implement definition.prototype
|
||||
implementPrototype(element, definition);
|
||||
// flag as upgraded
|
||||
element.__upgraded__ = true;
|
||||
// lifecycle management
|
||||
created(element);
|
||||
// attachedCallback fires in tree order, call before recursing
|
||||
scope.attachedNode(element);
|
||||
// there should never be a shadow root on element at this point
|
||||
scope.upgradeSubtree(element);
|
||||
flags.upgrade && console.groupEnd();
|
||||
// OUTPUT
|
||||
return element;
|
||||
}
|
||||
|
||||
// Set __proto__ on supported platforms and use a mixin strategy when
|
||||
// this is not supported; e.g. on IE10.
|
||||
function implementPrototype(element, definition) {
|
||||
// prototype swizzling is best
|
||||
if (Object.__proto__) {
|
||||
element.__proto__ = definition.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(element, definition.prototype, definition.native);
|
||||
element.__proto__ = definition.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;
|
||||
// The default is HTMLElement.prototype, so we add a test to avoid mixing in
|
||||
// native prototypes
|
||||
while (p !== inNative && p !== HTMLElement.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 created(element) {
|
||||
// invoke createdCallback
|
||||
if (element.createdCallback) {
|
||||
element.createdCallback();
|
||||
}
|
||||
}
|
||||
|
||||
scope.upgrade = upgrade;
|
||||
scope.upgradeWithDefinition = upgradeWithDefinition;
|
||||
scope.implementPrototype = implementPrototype;
|
||||
|
||||
});
|
||||
52
src/HTMLImports/HTMLImports.js
Normal file
52
src/HTMLImports/HTMLImports.js
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
(function() {
|
||||
|
||||
// Estblish polyfill scope. We do this here to store flags. Flags are not
|
||||
// supported in the build.
|
||||
window.HTMLImports = window.HTMLImports || {flags:{}};
|
||||
|
||||
// Flags. Convert url arguments to flags
|
||||
var flags = {};
|
||||
if (!flags.noOpts) {
|
||||
location.search.slice(1).split('&').forEach(function(o) {
|
||||
o = o.split('=');
|
||||
o[0] && (flags[o[0]] = o[1] || true);
|
||||
});
|
||||
}
|
||||
|
||||
// Load.
|
||||
var file = 'HTMLImports.js';
|
||||
|
||||
var modules = [
|
||||
'base.js',
|
||||
'../WeakMap/WeakMap.js',
|
||||
'../MutationObserver/MutationObserver.js',
|
||||
'path.js',
|
||||
'xhr.js',
|
||||
'Loader.js',
|
||||
'Observer.js',
|
||||
'parser.js',
|
||||
'importer.js',
|
||||
'dynamic.js',
|
||||
'boot.js'
|
||||
];
|
||||
|
||||
var src =
|
||||
document.querySelector('script[src*="' + file + '"]').getAttribute('src');
|
||||
var basePath = src.slice(0, src.indexOf(file));
|
||||
|
||||
modules.forEach(function(f) {
|
||||
document.write('<script src="' + basePath + f + '"></script>');
|
||||
});
|
||||
|
||||
// exports
|
||||
HTMLImports.flags = flags;
|
||||
|
||||
})();
|
||||
135
src/HTMLImports/Loader.js
Normal file
135
src/HTMLImports/Loader.js
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
HTMLImports.addModule(function(scope) {
|
||||
|
||||
// imports
|
||||
var xhr = scope.xhr;
|
||||
var flags = scope.flags;
|
||||
|
||||
// This loader supports a dynamic list of urls
|
||||
// and an oncomplete callback that is called when the loader is done.
|
||||
// NOTE: 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;
|
||||
this.oncomplete = onComplete;
|
||||
this.inflight = 0;
|
||||
this.pending = {};
|
||||
};
|
||||
|
||||
Loader.prototype = {
|
||||
|
||||
addNodes: function(nodes) {
|
||||
// number of transactions to complete
|
||||
this.inflight += nodes.length;
|
||||
// commence transactions
|
||||
for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
|
||||
this.require(n);
|
||||
}
|
||||
// anything to do?
|
||||
this.checkDone();
|
||||
},
|
||||
|
||||
addNode: function(node) {
|
||||
// number of transactions to complete
|
||||
this.inflight++;
|
||||
// commence transactions
|
||||
this.require(node);
|
||||
// anything to do?
|
||||
this.checkDone();
|
||||
},
|
||||
|
||||
require: function(elt) {
|
||||
var url = elt.src || elt.href;
|
||||
// ensure we have a standard url that can be used
|
||||
// reliably for deduping.
|
||||
// TODO(sjmiles): ad-hoc
|
||||
elt.__nodeUrl = url;
|
||||
// deduplication
|
||||
if (!this.dedupe(url, elt)) {
|
||||
// fetch this resource
|
||||
this.fetch(url, elt);
|
||||
}
|
||||
},
|
||||
|
||||
dedupe: function(url, elt) {
|
||||
if (this.pending[url]) {
|
||||
// add to list of nodes waiting for inUrl
|
||||
this.pending[url].push(elt);
|
||||
// don't need fetch
|
||||
return true;
|
||||
}
|
||||
var resource;
|
||||
if (this.cache[url]) {
|
||||
this.onload(url, elt, this.cache[url]);
|
||||
// finished this transaction
|
||||
this.tail();
|
||||
// don't need fetch
|
||||
return true;
|
||||
}
|
||||
// first node waiting for inUrl
|
||||
this.pending[url] = [elt];
|
||||
// need fetch (not a dupe)
|
||||
return false;
|
||||
},
|
||||
|
||||
fetch: function(url, elt) {
|
||||
flags.load && console.log('fetch', url, elt);
|
||||
if (url.match(/^data:/)) {
|
||||
// Handle Data URI Scheme
|
||||
var pieces = url.split(',');
|
||||
var header = pieces[0];
|
||||
var body = pieces[1];
|
||||
if(header.indexOf(';base64') > -1) {
|
||||
body = atob(body);
|
||||
} else {
|
||||
body = decodeURIComponent(body);
|
||||
}
|
||||
setTimeout(function() {
|
||||
this.receive(url, elt, null, body);
|
||||
}.bind(this), 0);
|
||||
} else {
|
||||
var receiveXhr = function(err, resource, redirectedUrl) {
|
||||
this.receive(url, elt, err, resource, redirectedUrl);
|
||||
}.bind(this);
|
||||
xhr.load(url, receiveXhr);
|
||||
}
|
||||
},
|
||||
|
||||
receive: function(url, elt, err, resource, redirectedUrl) {
|
||||
this.cache[url] = resource;
|
||||
var $p = this.pending[url];
|
||||
for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) {
|
||||
// If url was redirected, use the redirected location so paths are
|
||||
// calculated relative to that.
|
||||
this.onload(url, p, resource, err, redirectedUrl);
|
||||
this.tail();
|
||||
}
|
||||
this.pending[url] = null;
|
||||
},
|
||||
|
||||
tail: function() {
|
||||
--this.inflight;
|
||||
this.checkDone();
|
||||
},
|
||||
|
||||
checkDone: function() {
|
||||
if (!this.inflight) {
|
||||
this.oncomplete();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// exports
|
||||
scope.Loader = Loader;
|
||||
|
||||
});
|
||||
50
src/HTMLImports/Observer.js
Normal file
50
src/HTMLImports/Observer.js
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
HTMLImports.addModule(function(scope) {
|
||||
|
||||
/*
|
||||
Use a mutation observer to call a callback for all added nodes.
|
||||
*/
|
||||
var Observer = function(addCallback) {
|
||||
this.addCallback = addCallback;
|
||||
this.mo = new MutationObserver(this.handler.bind(this));
|
||||
};
|
||||
|
||||
Observer.prototype = {
|
||||
|
||||
// we track mutations for addedNodes, looking for imports
|
||||
handler: function(mutations) {
|
||||
for (var i=0, l=mutations.length, m; (i<l) && (m=mutations[i]); i++) {
|
||||
if (m.type === 'childList' && m.addedNodes.length) {
|
||||
this.addedNodes(m.addedNodes);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addedNodes: function(nodes) {
|
||||
if (this.addCallback) {
|
||||
this.addCallback(nodes);
|
||||
}
|
||||
for (var i=0, l=nodes.length, n, loading; (i<l) && (n=nodes[i]); i++) {
|
||||
if (n.children && n.children.length) {
|
||||
this.addedNodes(n.children);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
observe: function(root) {
|
||||
this.mo.observe(root, {childList: true, subtree: true});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// exports
|
||||
scope.Observer = Observer;
|
||||
|
||||
});
|
||||
249
src/HTMLImports/base.js
Normal file
249
src/HTMLImports/base.js
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
/*
|
||||
Create polyfill scope and feature detect native support.
|
||||
*/
|
||||
window.HTMLImports = window.HTMLImports || {flags:{}};
|
||||
|
||||
(function(scope) {
|
||||
|
||||
/**
|
||||
Basic setup and simple module executer. We collect modules and then execute
|
||||
the code later, only if it's necessary for polyfilling.
|
||||
*/
|
||||
var IMPORT_LINK_TYPE = 'import';
|
||||
var useNative = Boolean(IMPORT_LINK_TYPE in document.createElement('link'));
|
||||
|
||||
// world's simplest module initializer
|
||||
var modules = [];
|
||||
var addModule = function(module) {
|
||||
modules.push(module);
|
||||
};
|
||||
|
||||
var initializeModules = function() {
|
||||
modules.forEach(function(module) {
|
||||
module(scope);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Support `currentScript` on all browsers as `document._currentScript.`
|
||||
|
||||
NOTE: We cannot polyfill `document.currentScript` because it's not possible
|
||||
both to override and maintain the ability to capture the native value.
|
||||
Therefore we choose to expose `_currentScript` both when native imports
|
||||
and the polyfill are in use.
|
||||
*/
|
||||
// NOTE: ShadowDOMPolyfill intrusion.
|
||||
var hasShadowDOMPolyfill = Boolean(window.ShadowDOMPolyfill);
|
||||
var wrap = function(node) {
|
||||
return hasShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) : node;
|
||||
};
|
||||
var rootDocument = wrap(document);
|
||||
|
||||
var currentScriptDescriptor = {
|
||||
get: function() {
|
||||
var script = HTMLImports.currentScript || document.currentScript ||
|
||||
// NOTE: only works when called in synchronously executing code.
|
||||
// readyState should check if `loading` but IE10 is
|
||||
// interactive when scripts run so we cheat.
|
||||
(document.readyState !== 'complete' ?
|
||||
document.scripts[document.scripts.length - 1] : null);
|
||||
return wrap(script);
|
||||
},
|
||||
configurable: true
|
||||
};
|
||||
|
||||
Object.defineProperty(document, '_currentScript', currentScriptDescriptor);
|
||||
Object.defineProperty(rootDocument, '_currentScript', currentScriptDescriptor);
|
||||
|
||||
/**
|
||||
Add support for the `HTMLImportsLoaded` event and the `HTMLImports.whenReady`
|
||||
method. This api is necessary because unlike the native implementation,
|
||||
script elements do not force imports to resolve. Instead, users should wrap
|
||||
code in either an `HTMLImportsLoaded` hander or after load time in an
|
||||
`HTMLImports.whenReady(callback)` call.
|
||||
|
||||
NOTE: This module also supports these apis under the native implementation.
|
||||
Therefore, if this file is loaded, the same code can be used under both
|
||||
the polyfill and native implementation.
|
||||
*/
|
||||
|
||||
var isIE = /Trident/.test(navigator.userAgent);
|
||||
|
||||
// call a callback when all HTMLImports in the document at call time
|
||||
// (or at least document ready) 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 whenReady(callback, doc) {
|
||||
doc = doc || rootDocument;
|
||||
// if document is loading, wait and try again
|
||||
whenDocumentReady(function() {
|
||||
watchImportsLoad(callback, doc);
|
||||
}, doc);
|
||||
}
|
||||
|
||||
// call the callback when the document is in a ready state (has dom)
|
||||
var requiredReadyState = 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(READY_EVENT, checkReady);
|
||||
} else if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
function markTargetLoaded(event) {
|
||||
event.target.__loaded = true;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if ((loaded == l) && callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
function loadedImport(e) {
|
||||
markTargetLoaded(e);
|
||||
loaded++;
|
||||
checkDone();
|
||||
}
|
||||
if (l) {
|
||||
for (var i=0, imp; (i<l) && (imp=imports[i]); i++) {
|
||||
if (isImportLoaded(imp)) {
|
||||
loadedImport.call(imp, {target: imp});
|
||||
} else {
|
||||
imp.addEventListener('load', loadedImport);
|
||||
imp.addEventListener('error', loadedImport);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checkDone();
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: test for native imports loading is based on explicitly watching
|
||||
// all imports (see below).
|
||||
// However, we cannot rely on this entirely without watching the entire document
|
||||
// for import links. For perf reasons, currently only head is watched.
|
||||
// Instead, we fallback to checking if the import property is available
|
||||
// and the document is not itself loading.
|
||||
function isImportLoaded(link) {
|
||||
return useNative ? link.__loaded ||
|
||||
(link.import && link.import.readyState !== 'loading') :
|
||||
link.__importParsed;
|
||||
}
|
||||
|
||||
// TODO(sorvell): Workaround for
|
||||
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=25007, should be removed when
|
||||
// this bug is addressed.
|
||||
// (1) Install a mutation observer to see when HTMLImports have loaded
|
||||
// (2) if this script is run during document load it will watch any existing
|
||||
// imports for loading.
|
||||
//
|
||||
// NOTE: The workaround has restricted functionality: (1) it's only compatible
|
||||
// with imports that are added to document.head since the mutation observer
|
||||
// watches only head for perf reasons, (2) it requires this script
|
||||
// to run before any imports have completed loading.
|
||||
if (useNative) {
|
||||
new MutationObserver(function(mxns) {
|
||||
for (var i=0, l=mxns.length, m; (i < l) && (m=mxns[i]); i++) {
|
||||
if (m.addedNodes) {
|
||||
handleImports(m.addedNodes);
|
||||
}
|
||||
}
|
||||
}).observe(document.head, {childList: true});
|
||||
|
||||
function handleImports(nodes) {
|
||||
for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
|
||||
if (isImport(n)) {
|
||||
handleImport(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isImport(element) {
|
||||
return element.localName === 'link' && element.rel === 'import';
|
||||
}
|
||||
|
||||
function handleImport(element) {
|
||||
var loaded = element.import;
|
||||
if (loaded) {
|
||||
markTargetLoaded({target: element});
|
||||
} else {
|
||||
element.addEventListener('load', markTargetLoaded);
|
||||
element.addEventListener('error', markTargetLoaded);
|
||||
}
|
||||
}
|
||||
|
||||
// make sure to catch any imports that are in the process of loading
|
||||
// when this script is run.
|
||||
(function() {
|
||||
if (document.readyState === 'loading') {
|
||||
var imports = document.querySelectorAll('link[rel=import]');
|
||||
for (var i=0, l=imports.length, imp; (i<l) && (imp=imports[i]); i++) {
|
||||
handleImport(imp);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
}
|
||||
|
||||
// IE shim for CustomEvent
|
||||
if (typeof window.CustomEvent !== 'function') {
|
||||
window.CustomEvent = function(inType, dictionary) {
|
||||
var e = document.createEvent('HTMLEvents');
|
||||
e.initEvent(inType,
|
||||
dictionary.bubbles === false ? false : true,
|
||||
dictionary.cancelable === false ? false : true,
|
||||
dictionary.detail);
|
||||
return e;
|
||||
};
|
||||
}
|
||||
|
||||
// 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.
|
||||
whenReady(function() {
|
||||
HTMLImports.ready = true;
|
||||
HTMLImports.readyTime = new Date().getTime();
|
||||
rootDocument.dispatchEvent(
|
||||
new CustomEvent('HTMLImportsLoaded', {bubbles: true})
|
||||
);
|
||||
});
|
||||
|
||||
// exports
|
||||
scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
|
||||
scope.useNative = useNative;
|
||||
scope.addModule = addModule;
|
||||
scope.initializeModules = initializeModules;
|
||||
scope.rootDocument = rootDocument;
|
||||
scope.whenReady = whenReady;
|
||||
scope.isIE = isIE;
|
||||
|
||||
})(window.HTMLImports);
|
||||
|
||||
53
src/HTMLImports/boot.js
Normal file
53
src/HTMLImports/boot.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
(function(scope){
|
||||
|
||||
// imports
|
||||
initializeModules = scope.initializeModules;
|
||||
|
||||
/*
|
||||
NOTE: Even when native HTMLImports exists, the following api is available by
|
||||
loading the polyfill. This provides api compabitility where the polyfill
|
||||
cannot be "correct":
|
||||
|
||||
* `document._currentScript`
|
||||
* `HTMLImportsLoaded` event
|
||||
* `HTMLImports.whenReady(callback)
|
||||
*/
|
||||
if (scope.useNative) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize polyfill modules. Note, polyfill modules are loaded but not
|
||||
// executed; this is a convenient way to control which modules run when
|
||||
// the polyfill is required and allows the polyfill to load even when it's
|
||||
// not needed.
|
||||
initializeModules();
|
||||
|
||||
// imports
|
||||
var rootDocument = scope.rootDocument;
|
||||
|
||||
/*
|
||||
Bootstrap the imports machine.
|
||||
*/
|
||||
function bootstrap() {
|
||||
HTMLImports.importer.bootDocument(rootDocument);
|
||||
}
|
||||
|
||||
// TODO(sorvell): SD polyfill does *not* generate mutations for nodes added
|
||||
// by the parser. For this reason, we must wait until the dom exists to
|
||||
// bootstrap.
|
||||
if (document.readyState === 'complete' ||
|
||||
(document.readyState === 'interactive' && !window.attachEvent)) {
|
||||
bootstrap();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', bootstrap);
|
||||
}
|
||||
|
||||
})(HTMLImports);
|
||||
64
src/HTMLImports/dynamic.js
Normal file
64
src/HTMLImports/dynamic.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
HTMLImports.addModule(function(scope) {
|
||||
|
||||
// imports
|
||||
var parser = scope.parser;
|
||||
var importer = scope.importer;
|
||||
|
||||
// dynamic
|
||||
// highlander object to manage elements dynamically added to imports
|
||||
// for any observed document, dynamic:
|
||||
// - tells the importer to load any imports that are added.
|
||||
// - tells the parser to parse any added elements that need to be parsed.
|
||||
// dynamic importer)
|
||||
var dynamic = {
|
||||
// process (load/parse) any nodes added to imported documents.
|
||||
added: function(nodes) {
|
||||
var owner, parsed;
|
||||
for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
|
||||
if (!owner) {
|
||||
owner = n.ownerDocument;
|
||||
parsed = parser.isParsed(owner);
|
||||
}
|
||||
// note: the act of loading kicks the parser, so we use parseDynamic's
|
||||
// 2nd argument to control if this added node needs to kick the parser.
|
||||
loading = this.shouldLoadNode(n);
|
||||
if (loading) {
|
||||
importer.loadNode(n);
|
||||
}
|
||||
if (this.shouldParseNode(n) && parsed) {
|
||||
parser.parseDynamic(n, loading);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
shouldLoadNode: function(node) {
|
||||
return (node.nodeType === 1) && matches.call(node,
|
||||
importer.loadSelectorsForNode(node));
|
||||
},
|
||||
|
||||
shouldParseNode: function(node) {
|
||||
return (node.nodeType === 1) && matches.call(node,
|
||||
parser.parseSelectorsForNode(node));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// let the dynamic element helper tie into the import observer.
|
||||
importer.observer.addCallback = dynamic.added.bind(dynamic);
|
||||
|
||||
// x-plat matches
|
||||
var matches = HTMLElement.prototype.matches ||
|
||||
HTMLElement.prototype.matchesSelector ||
|
||||
HTMLElement.prototype.webkitMatchesSelector ||
|
||||
HTMLElement.prototype.mozMatchesSelector ||
|
||||
HTMLElement.prototype.msMatchesSelector;
|
||||
|
||||
});
|
||||
167
src/HTMLImports/importer.js
Normal file
167
src/HTMLImports/importer.js
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
HTMLImports.addModule(function(scope) {
|
||||
|
||||
// imports
|
||||
var flags = scope.flags;
|
||||
var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE;
|
||||
var IMPORT_SELECTOR = scope.IMPORT_SELECTOR;
|
||||
var rootDocument = scope.rootDocument;
|
||||
var Loader = scope.Loader;
|
||||
var Observer = scope.Observer;
|
||||
var parser = scope.parser;
|
||||
|
||||
// importer
|
||||
// highlander object to manage loading of imports
|
||||
// for any document, importer:
|
||||
// - loads any linked import documents (with deduping)
|
||||
// - whenever an import is loaded, prompts the parser to try to parse
|
||||
// - observes imported documents for new elements (these are handled via the
|
||||
// dynamic importer)
|
||||
var importer = {
|
||||
|
||||
documents: {},
|
||||
|
||||
// nodes to load in the mian document
|
||||
documentPreloadSelectors: IMPORT_SELECTOR,
|
||||
|
||||
// nodes to load in imports
|
||||
importsPreloadSelectors: [
|
||||
IMPORT_SELECTOR
|
||||
].join(','),
|
||||
|
||||
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
|
||||
importLoader.addNodes(nodes);
|
||||
},
|
||||
|
||||
marshalNodes: function(parent) {
|
||||
// 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 === rootDocument ? this.documentPreloadSelectors :
|
||||
this.importsPreloadSelectors;
|
||||
},
|
||||
|
||||
loaded: function(url, elt, resource, err, redirectedUrl) {
|
||||
flags.load && console.log('loaded', url, elt);
|
||||
// store generic resource
|
||||
// TODO(sorvell): fails for nodes inside <template>.content
|
||||
// see https://code.google.com/p/chromium/issues/detail?id=249381.
|
||||
elt.__resource = resource;
|
||||
elt.__error = err;
|
||||
if (isImportLink(elt)) {
|
||||
var doc = this.documents[url];
|
||||
// if we've never seen a document at this url
|
||||
if (doc === undefined) {
|
||||
// generate an HTMLDocument from data
|
||||
doc = err ? null : makeDocument(resource, redirectedUrl || url);
|
||||
if (doc) {
|
||||
doc.__importLink = elt;
|
||||
// note, we cannot use MO to detect parsed nodes because
|
||||
// SD polyfill does not report these as mutations.
|
||||
this.bootDocument(doc);
|
||||
}
|
||||
// cache document
|
||||
this.documents[url] = doc;
|
||||
}
|
||||
// don't store import record until we're actually loaded
|
||||
// store document resource
|
||||
elt.import = doc;
|
||||
}
|
||||
parser.parseNext();
|
||||
},
|
||||
|
||||
bootDocument: function(doc) {
|
||||
this.loadSubtree(doc);
|
||||
// observe documents for new elements being added
|
||||
this.observer.observe(doc);
|
||||
parser.parseNext();
|
||||
},
|
||||
|
||||
loadedAll: function() {
|
||||
parser.parseNext();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// loader singleton to handle loading imports
|
||||
var importLoader = new Loader(importer.loaded.bind(importer),
|
||||
importer.loadedAll.bind(importer));
|
||||
|
||||
// observer singleton to handle observing elements in imports
|
||||
// NOTE: the observer has a node added callback and this is set
|
||||
// by the dynamic importer module.
|
||||
importer.observer = new Observer();
|
||||
|
||||
function isImportLink(elt) {
|
||||
return isLinkRel(elt, IMPORT_LINK_TYPE);
|
||||
}
|
||||
|
||||
function isLinkRel(elt, rel) {
|
||||
return elt.localName === 'link' && elt.getAttribute('rel') === rel;
|
||||
}
|
||||
|
||||
function makeDocument(resource, url) {
|
||||
// create a new HTML document
|
||||
var doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE);
|
||||
// cache the new document's source url
|
||||
doc._URL = url;
|
||||
// establish a relative path via <base>
|
||||
var base = doc.createElement('base');
|
||||
base.setAttribute('href', url);
|
||||
// add baseURI support to browsers (IE) that lack it.
|
||||
if (!doc.baseURI) {
|
||||
doc.baseURI = url;
|
||||
}
|
||||
// ensure UTF-8 charset
|
||||
var meta = doc.createElement('meta');
|
||||
meta.setAttribute('charset', 'utf-8');
|
||||
|
||||
doc.head.appendChild(meta);
|
||||
doc.head.appendChild(base);
|
||||
// install html
|
||||
doc.body.innerHTML = resource;
|
||||
// TODO(sorvell): ideally this code is not aware of Template polyfill,
|
||||
// but for now the polyfill needs help to bootstrap these templates
|
||||
if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) {
|
||||
HTMLTemplateElement.bootstrap(doc);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
// Polyfill document.baseURI for browsers without it.
|
||||
if (!document.baseURI) {
|
||||
var baseURIDescriptor = {
|
||||
get: function() {
|
||||
var base = document.querySelector('base');
|
||||
return base ? base.href : window.location.href;
|
||||
},
|
||||
configurable: true
|
||||
};
|
||||
|
||||
Object.defineProperty(document, 'baseURI', baseURIDescriptor);
|
||||
Object.defineProperty(rootDocument, 'baseURI', baseURIDescriptor);
|
||||
}
|
||||
|
||||
// exports
|
||||
scope.importer = importer;
|
||||
scope.importLoader = importLoader;
|
||||
|
||||
});
|
||||
334
src/HTMLImports/parser.js
Normal file
334
src/HTMLImports/parser.js
Normal file
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
HTMLImports.addModule(function(scope) {
|
||||
|
||||
// imports
|
||||
var path = scope.path;
|
||||
var rootDocument = scope.rootDocument;
|
||||
var flags = scope.flags;
|
||||
var isIE = scope.isIE;
|
||||
var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE;
|
||||
var IMPORT_SELECTOR = 'link[rel=' + IMPORT_LINK_TYPE + ']';
|
||||
|
||||
// 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;
|
||||
// elements can be dynamically added to imports. These are maintained in a
|
||||
// separate queue and parsed after all other elements.
|
||||
var importParser = {
|
||||
|
||||
// parse selectors for main document elements
|
||||
documentSelectors: IMPORT_SELECTOR,
|
||||
|
||||
// parse selectors for import document elements
|
||||
importsSelectors: [
|
||||
IMPORT_SELECTOR,
|
||||
'link[rel=stylesheet]',
|
||||
'style',
|
||||
'script:not([type])',
|
||||
'script[type="text/javascript"]'
|
||||
].join(','),
|
||||
|
||||
map: {
|
||||
link: 'parseLink',
|
||||
script: 'parseScript',
|
||||
style: 'parseStyle'
|
||||
},
|
||||
|
||||
dynamicElements: [],
|
||||
|
||||
// try to parse the next import in the tree
|
||||
parseNext: function() {
|
||||
var next = this.nextToParse();
|
||||
if (next) {
|
||||
this.parse(next);
|
||||
}
|
||||
},
|
||||
|
||||
parse: function(elt) {
|
||||
if (this.isParsed(elt)) {
|
||||
flags.parse && console.log('[%s] is already parsed', elt.localName);
|
||||
return;
|
||||
}
|
||||
var fn = this[this.map[elt.localName]];
|
||||
if (fn) {
|
||||
this.markParsing(elt);
|
||||
fn.call(this, elt);
|
||||
}
|
||||
},
|
||||
|
||||
// marks an element for dynamic parsing and attempts to parse the next element
|
||||
parseDynamic: function(elt, quiet) {
|
||||
this.dynamicElements.push(elt);
|
||||
if (!quiet) {
|
||||
this.parseNext();
|
||||
}
|
||||
},
|
||||
|
||||
// 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.
|
||||
// To prompt the system to parse the next element, parseNext should then be
|
||||
// called.
|
||||
// Note, parseNext used to be included at the end of markParsingComplete, but
|
||||
// we must not do this so that, for example, we can (1) mark parsing complete
|
||||
// then (2) fire an import load event, and then (3) parse the next resource.
|
||||
markParsing: function(elt) {
|
||||
flags.parse && console.log('parsing', elt);
|
||||
this.parsingElement = elt;
|
||||
},
|
||||
|
||||
markParsingComplete: function(elt) {
|
||||
elt.__importParsed = true;
|
||||
this.markDynamicParsingComplete(elt);
|
||||
if (elt.__importElement) {
|
||||
elt.__importElement.__importParsed = true;
|
||||
this.markDynamicParsingComplete(elt.__importElement);
|
||||
}
|
||||
this.parsingElement = null;
|
||||
flags.parse && console.log('completed', elt);
|
||||
},
|
||||
|
||||
markDynamicParsingComplete: function(elt) {
|
||||
var i = this.dynamicElements.indexOf(elt);
|
||||
if (i >= 0) {
|
||||
this.dynamicElements.splice(i, 1);
|
||||
}
|
||||
},
|
||||
|
||||
parseImport: function(elt) {
|
||||
// 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);
|
||||
}
|
||||
if (elt.import) {
|
||||
elt.import.__importParsed = true;
|
||||
}
|
||||
this.markParsingComplete(elt);
|
||||
// fire load event
|
||||
if (elt.__resource && !elt.__error) {
|
||||
elt.dispatchEvent(new CustomEvent('load', {bubbles: false}));
|
||||
} else {
|
||||
elt.dispatchEvent(new CustomEvent('error', {bubbles: false}));
|
||||
}
|
||||
// TODO(sorvell): workaround for Safari addEventListener not working
|
||||
// for elements not in the main document.
|
||||
if (elt.__pending) {
|
||||
var fn;
|
||||
while (elt.__pending.length) {
|
||||
fn = elt.__pending.shift();
|
||||
if (fn) {
|
||||
fn({target: elt});
|
||||
}
|
||||
}
|
||||
}
|
||||
this.parseNext();
|
||||
},
|
||||
|
||||
parseLink: function(linkElt) {
|
||||
if (nodeIsImport(linkElt)) {
|
||||
this.parseImport(linkElt);
|
||||
} else {
|
||||
// make href absolute
|
||||
linkElt.href = linkElt.href;
|
||||
this.parseGeneric(linkElt);
|
||||
}
|
||||
},
|
||||
|
||||
parseStyle: function(elt) {
|
||||
// TODO(sorvell): style element load event can just not fire so clone styles
|
||||
var src = elt;
|
||||
elt = cloneStyle(elt);
|
||||
elt.__importElement = src;
|
||||
this.parseGeneric(elt);
|
||||
},
|
||||
|
||||
parseGeneric: function(elt) {
|
||||
this.trackElement(elt);
|
||||
this.addElementToDocument(elt);
|
||||
},
|
||||
|
||||
rootImportForElement: function(elt) {
|
||||
var n = elt;
|
||||
while (n.ownerDocument.__importLink) {
|
||||
n = n.ownerDocument.__importLink;
|
||||
}
|
||||
return n;
|
||||
},
|
||||
|
||||
addElementToDocument: function(elt) {
|
||||
var port = this.rootImportForElement(elt.__importElement || elt);
|
||||
var l = port.__insertedElements = port.__insertedElements || 0;
|
||||
var refNode = port.nextElementSibling;
|
||||
for (var i=0; i < l; i++) {
|
||||
refNode = refNode && refNode.nextElementSibling;
|
||||
}
|
||||
port.parentNode.insertBefore(elt, refNode);
|
||||
},
|
||||
|
||||
// tracks when a loadable element has loaded
|
||||
trackElement: function(elt, callback) {
|
||||
var self = this;
|
||||
var done = function(e) {
|
||||
if (callback) {
|
||||
callback(e);
|
||||
}
|
||||
self.markParsingComplete(elt);
|
||||
self.parseNext();
|
||||
};
|
||||
elt.addEventListener('load', done);
|
||||
elt.addEventListener('error', done);
|
||||
|
||||
// NOTE: IE does not fire "load" event for styles that have already loaded
|
||||
// This is in violation of the spec, so we try our hardest to work around it
|
||||
if (isIE && elt.localName === 'style') {
|
||||
var fakeLoad = false;
|
||||
// If there's not @import in the textContent, assume it has loaded
|
||||
if (elt.textContent.indexOf('@import') == -1) {
|
||||
fakeLoad = true;
|
||||
// if we have a sheet, we have been parsed
|
||||
} else if (elt.sheet) {
|
||||
fakeLoad = true;
|
||||
var csr = elt.sheet.cssRules;
|
||||
var len = csr ? csr.length : 0;
|
||||
// search the rules for @import's
|
||||
for (var i = 0, r; (i < len) && (r = csr[i]); i++) {
|
||||
if (r.type === CSSRule.IMPORT_RULE) {
|
||||
// if every @import has resolved, fake the load
|
||||
fakeLoad = fakeLoad && Boolean(r.styleSheet);
|
||||
}
|
||||
}
|
||||
}
|
||||
// dispatch a fake load event and continue parsing
|
||||
if (fakeLoad) {
|
||||
elt.dispatchEvent(new CustomEvent('load', {bubbles: false}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// NOTE: execute scripts by injecting them and watching for the load/error
|
||||
// event. Inline scripts are handled via dataURL's because browsers tend to
|
||||
// provide correct parsing errors in this case. If this has any compatibility
|
||||
// issues, we can switch to injecting the inline script with textContent.
|
||||
parseScript: function(scriptElt) {
|
||||
var script = document.createElement('script');
|
||||
script.__importElement = scriptElt;
|
||||
script.src = scriptElt.src ? scriptElt.src :
|
||||
generateScriptDataUrl(scriptElt);
|
||||
// keep track of executing script to help polyfill `document.currentScript`
|
||||
scope.currentScript = scriptElt;
|
||||
this.trackElement(script, function(e) {
|
||||
script.parentNode.removeChild(script);
|
||||
scope.currentScript = null;
|
||||
});
|
||||
this.addElementToDocument(script);
|
||||
},
|
||||
|
||||
// determine the next element in the tree which should be parsed
|
||||
// crawl the document tree to find the next unparsed element
|
||||
// then process any dynamically added elements (these should process in 'add'
|
||||
// order.
|
||||
nextToParse: function() {
|
||||
this._mayParse = [];
|
||||
return !this.parsingElement && (this.nextToParseInDoc(rootDocument) ||
|
||||
this.nextToParseDynamic());
|
||||
},
|
||||
|
||||
nextToParseInDoc: function(doc, link) {
|
||||
// use `marParse` list to avoid looping into the same document again
|
||||
// since it could cause an iloop.
|
||||
if (doc && this._mayParse.indexOf(doc) < 0) {
|
||||
this._mayParse.push(doc);
|
||||
var nodes = doc.querySelectorAll(this.parseSelectorsForNode(doc));
|
||||
for (var i=0, l=nodes.length, p=0, n; (i<l) && (n=nodes[i]); i++) {
|
||||
if (!this.isParsed(n)) {
|
||||
if (this.hasResource(n)) {
|
||||
return nodeIsImport(n) ? this.nextToParseInDoc(n.import, n) : n;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// all nodes have been parsed, ready to parse import, if any
|
||||
return link;
|
||||
},
|
||||
|
||||
// note dynamically added elements are stored in a separate queue
|
||||
nextToParseDynamic: function() {
|
||||
return this.dynamicElements[0];
|
||||
},
|
||||
|
||||
// return the set of parse selectors relevant for this node.
|
||||
parseSelectorsForNode: function(node) {
|
||||
var doc = node.ownerDocument || node;
|
||||
return doc === rootDocument ? this.documentSelectors :
|
||||
this.importsSelectors;
|
||||
},
|
||||
|
||||
isParsed: function(node) {
|
||||
return node.__importParsed;
|
||||
},
|
||||
|
||||
needsDynamicParsing: function(elt) {
|
||||
return (this.dynamicElements.indexOf(elt) >= 0);
|
||||
},
|
||||
|
||||
hasResource: function(node) {
|
||||
if (nodeIsImport(node) && (node.import === undefined)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function nodeIsImport(elt) {
|
||||
return (elt.localName === 'link') && (elt.rel === IMPORT_LINK_TYPE);
|
||||
}
|
||||
|
||||
function generateScriptDataUrl(script) {
|
||||
var scriptContent = generateScriptContent(script);
|
||||
return 'data:text/javascript;charset=utf-8,' + encodeURIComponent(scriptContent);
|
||||
}
|
||||
|
||||
function generateScriptContent(script) {
|
||||
return script.textContent + generateSourceMapHint(script);
|
||||
}
|
||||
|
||||
// calculate source map hint
|
||||
function generateSourceMapHint(script) {
|
||||
var owner = script.ownerDocument;
|
||||
owner.__importedScripts = owner.__importedScripts || 0;
|
||||
var moniker = script.ownerDocument.baseURI;
|
||||
var num = owner.__importedScripts ? '-' + owner.__importedScripts : '';
|
||||
owner.__importedScripts++;
|
||||
return '\n//# sourceURL=' + moniker + num + '.js\n';
|
||||
}
|
||||
|
||||
// style/stylesheet handling
|
||||
|
||||
// clone style with proper path resolution for main document
|
||||
// NOTE: styles are the only elements that require direct path fixup.
|
||||
function cloneStyle(style) {
|
||||
var clone = style.ownerDocument.createElement('style');
|
||||
clone.textContent = style.textContent;
|
||||
path.resolveUrlsInStyle(clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
// exports
|
||||
scope.parser = importParser;
|
||||
scope.IMPORT_SELECTOR = IMPORT_SELECTOR;
|
||||
|
||||
});
|
||||
45
src/HTMLImports/path.js
Normal file
45
src/HTMLImports/path.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
HTMLImports.addModule(function(scope) {
|
||||
|
||||
var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g;
|
||||
var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g;
|
||||
|
||||
// path fixup: style elements in imports must be made relative to the main
|
||||
// document. We fixup url's in url() and @import.
|
||||
var path = {
|
||||
|
||||
resolveUrlsInStyle: function(style) {
|
||||
var doc = style.ownerDocument;
|
||||
var resolver = doc.createElement('a');
|
||||
style.textContent = this.resolveUrlsInCssText(style.textContent, resolver);
|
||||
return style;
|
||||
},
|
||||
|
||||
resolveUrlsInCssText: function(cssText, urlObj) {
|
||||
var r = this.replaceUrls(cssText, urlObj, CSS_URL_REGEXP);
|
||||
r = this.replaceUrls(r, urlObj, CSS_IMPORT_REGEXP);
|
||||
return r;
|
||||
},
|
||||
|
||||
replaceUrls: function(text, urlObj, regexp) {
|
||||
return text.replace(regexp, function(m, pre, url, post) {
|
||||
var urlPath = url.replace(/["']/g, '');
|
||||
urlObj.href = urlPath;
|
||||
urlPath = urlObj.href;
|
||||
return pre + '\'' + urlPath + '\'' + post;
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// exports
|
||||
scope.path = path;
|
||||
|
||||
});
|
||||
57
src/HTMLImports/xhr.js
Normal file
57
src/HTMLImports/xhr.js
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
HTMLImports.addModule(function(scope) {
|
||||
|
||||
/*
|
||||
xhr processor.
|
||||
*/
|
||||
xhr = {
|
||||
async: true,
|
||||
|
||||
ok: function(request) {
|
||||
return (request.status >= 200 && request.status < 300)
|
||||
|| (request.status === 304)
|
||||
|| (request.status === 0);
|
||||
},
|
||||
|
||||
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) {
|
||||
// Servers redirecting an import can add a Location header to help us
|
||||
// polyfill correctly.
|
||||
var locationHeader = request.getResponseHeader("Location");
|
||||
var redirectedUrl = null;
|
||||
if (locationHeader) {
|
||||
var redirectedUrl = (locationHeader.substr( 0, 1 ) === "/")
|
||||
? location.origin + locationHeader // Location is a relative path
|
||||
: locationHeader; // Full path
|
||||
}
|
||||
next.call(nextContext, !xhr.ok(request) && request,
|
||||
request.response || request.responseText, redirectedUrl);
|
||||
}
|
||||
});
|
||||
request.send();
|
||||
return request;
|
||||
},
|
||||
|
||||
loadDocument: function(url, next, nextContext) {
|
||||
this.load(url, next, nextContext).responseType = 'document';
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// exports
|
||||
scope.xhr = xhr;
|
||||
|
||||
});
|
||||
546
src/MutationObserver/MutationObserver.js
Normal file
546
src/MutationObserver/MutationObserver.js
Normal file
@@ -0,0 +1,546 @@
|
||||
/*
|
||||
* 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 WeakMap();
|
||||
|
||||
// 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;
|
||||
|
||||
if (!global.MutationObserver)
|
||||
global.MutationObserver = JsMutationObserver;
|
||||
|
||||
|
||||
})(this);
|
||||
780
src/ShadowCss/ShadowCSS.js
Normal file
780
src/ShadowCss/ShadowCSS.js
Normal file
@@ -0,0 +1,780 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/*
|
||||
This is a limited shim for ShadowDOM css styling.
|
||||
https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles
|
||||
|
||||
The intention here is to support only the styling features which can be
|
||||
relatively simply implemented. The goal is to allow users to avoid the
|
||||
most obvious pitfalls and do so without compromising performance significantly.
|
||||
For ShadowDOM styling that's not covered here, a set of best practices
|
||||
can be provided that should allow users to accomplish more complex styling.
|
||||
|
||||
The following is a list of specific ShadowDOM styling features and a brief
|
||||
discussion of the approach used to shim.
|
||||
|
||||
Shimmed features:
|
||||
|
||||
* :host, :host-context: ShadowDOM allows styling of the shadowRoot's host
|
||||
element using the :host rule. To shim this feature, the :host styles are
|
||||
reformatted and prefixed with a given scope name and promoted to a
|
||||
document level stylesheet.
|
||||
For example, given a scope name of .foo, a rule like this:
|
||||
|
||||
:host {
|
||||
background: red;
|
||||
}
|
||||
}
|
||||
|
||||
becomes:
|
||||
|
||||
.foo {
|
||||
background: red;
|
||||
}
|
||||
|
||||
* encapsultion: Styles defined within ShadowDOM, apply only to
|
||||
dom inside the ShadowDOM. Polymer uses one of two techniques to imlement
|
||||
this feature.
|
||||
|
||||
By default, rules are prefixed with the host element tag name
|
||||
as a descendant selector. This ensures styling does not leak out of the 'top'
|
||||
of the element's ShadowDOM. For example,
|
||||
|
||||
div {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
becomes:
|
||||
|
||||
x-foo div {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
becomes:
|
||||
|
||||
|
||||
Alternatively, if WebComponents.ShadowCSS.strictStyling is set to true then
|
||||
selectors are scoped by adding an attribute selector suffix to each
|
||||
simple selector that contains the host element tag name. Each element
|
||||
in the element's ShadowDOM template is also given the scope attribute.
|
||||
Thus, these rules match only elements that have the scope attribute.
|
||||
For example, given a scope name of x-foo, a rule like this:
|
||||
|
||||
div {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
becomes:
|
||||
|
||||
div[x-foo] {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
Note that elements that are dynamically added to a scope must have the scope
|
||||
selector added to them manually.
|
||||
|
||||
* upper/lower bound encapsulation: Styles which are defined outside a
|
||||
shadowRoot should not cross the ShadowDOM boundary and should not apply
|
||||
inside a shadowRoot.
|
||||
|
||||
This styling behavior is not emulated. Some possible ways to do this that
|
||||
were rejected due to complexity and/or performance concerns include: (1) reset
|
||||
every possible property for every possible selector for a given scope name;
|
||||
(2) re-implement css in javascript.
|
||||
|
||||
As an alternative, users should make sure to use selectors
|
||||
specific to the scope in which they are working.
|
||||
|
||||
* ::distributed: This behavior is not emulated. It's often not necessary
|
||||
to style the contents of a specific insertion point and instead, descendants
|
||||
of the host element can be styled selectively. Users can also create an
|
||||
extra node around an insertion point and style that node's contents
|
||||
via descendent selectors. For example, with a shadowRoot like this:
|
||||
|
||||
<style>
|
||||
::content(div) {
|
||||
background: red;
|
||||
}
|
||||
</style>
|
||||
<content></content>
|
||||
|
||||
could become:
|
||||
|
||||
<style>
|
||||
/ *@polyfill .content-container div * /
|
||||
::content(div) {
|
||||
background: red;
|
||||
}
|
||||
</style>
|
||||
<div class="content-container">
|
||||
<content></content>
|
||||
</div>
|
||||
|
||||
Note the use of @polyfill in the comment above a ShadowDOM specific style
|
||||
declaration. This is a directive to the styling shim to use the selector
|
||||
in comments in lieu of the next selector when running under polyfill.
|
||||
*/
|
||||
(function(scope) {
|
||||
|
||||
var ShadowCSS = {
|
||||
strictStyling: false,
|
||||
registry: {},
|
||||
// Shim styles for a given root associated with a name and extendsName
|
||||
// 1. cache root styles by name
|
||||
// 2. optionally tag root nodes with scope name
|
||||
// 3. shim polyfill directives /* @polyfill */ and /* @polyfill-rule */
|
||||
// 4. shim :host and scoping
|
||||
shimStyling: function(root, name, extendsName) {
|
||||
var scopeStyles = this.prepareRoot(root, name, extendsName);
|
||||
var typeExtension = this.isTypeExtension(extendsName);
|
||||
var scopeSelector = this.makeScopeSelector(name, typeExtension);
|
||||
// use caching to make working with styles nodes easier and to facilitate
|
||||
// lookup of extendee
|
||||
var cssText = stylesToCssText(scopeStyles, true);
|
||||
cssText = this.scopeCssText(cssText, scopeSelector);
|
||||
// cache shimmed css on root for user extensibility
|
||||
if (root) {
|
||||
root.shimmedStyle = cssText;
|
||||
}
|
||||
// add style to document
|
||||
this.addCssToDocument(cssText, name);
|
||||
},
|
||||
/*
|
||||
* Shim a style element with the given selector. Returns cssText that can
|
||||
* be included in the document via WebComponents.ShadowCSS.addCssToDocument(css).
|
||||
*/
|
||||
shimStyle: function(style, selector) {
|
||||
return this.shimCssText(style.textContent, selector);
|
||||
},
|
||||
/*
|
||||
* Shim some cssText with the given selector. Returns cssText that can
|
||||
* be included in the document via WebComponents.ShadowCSS.addCssToDocument(css).
|
||||
*/
|
||||
shimCssText: function(cssText, selector) {
|
||||
cssText = this.insertDirectives(cssText);
|
||||
return this.scopeCssText(cssText, selector);
|
||||
},
|
||||
makeScopeSelector: function(name, typeExtension) {
|
||||
if (name) {
|
||||
return typeExtension ? '[is=' + name + ']' : name;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
isTypeExtension: function(extendsName) {
|
||||
return extendsName && extendsName.indexOf('-') < 0;
|
||||
},
|
||||
prepareRoot: function(root, name, extendsName) {
|
||||
var def = this.registerRoot(root, name, extendsName);
|
||||
this.replaceTextInStyles(def.rootStyles, this.insertDirectives);
|
||||
// remove existing style elements
|
||||
this.removeStyles(root, def.rootStyles);
|
||||
// apply strict attr
|
||||
if (this.strictStyling) {
|
||||
this.applyScopeToContent(root, name);
|
||||
}
|
||||
return def.scopeStyles;
|
||||
},
|
||||
removeStyles: function(root, styles) {
|
||||
for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) {
|
||||
s.parentNode.removeChild(s);
|
||||
}
|
||||
},
|
||||
registerRoot: function(root, name, extendsName) {
|
||||
var def = this.registry[name] = {
|
||||
root: root,
|
||||
name: name,
|
||||
extendsName: extendsName
|
||||
}
|
||||
var styles = this.findStyles(root);
|
||||
def.rootStyles = styles;
|
||||
def.scopeStyles = def.rootStyles;
|
||||
var extendee = this.registry[def.extendsName];
|
||||
if (extendee) {
|
||||
def.scopeStyles = extendee.scopeStyles.concat(def.scopeStyles);
|
||||
}
|
||||
return def;
|
||||
},
|
||||
findStyles: function(root) {
|
||||
if (!root) {
|
||||
return [];
|
||||
}
|
||||
var styles = root.querySelectorAll('style');
|
||||
return Array.prototype.filter.call(styles, function(s) {
|
||||
return !s.hasAttribute(NO_SHIM_ATTRIBUTE);
|
||||
});
|
||||
},
|
||||
applyScopeToContent: function(root, name) {
|
||||
if (root) {
|
||||
// add the name attribute to each node in root.
|
||||
Array.prototype.forEach.call(root.querySelectorAll('*'),
|
||||
function(node) {
|
||||
node.setAttribute(name, '');
|
||||
});
|
||||
// and template contents too
|
||||
Array.prototype.forEach.call(root.querySelectorAll('template'),
|
||||
function(template) {
|
||||
this.applyScopeToContent(template.content, name);
|
||||
},
|
||||
this);
|
||||
}
|
||||
},
|
||||
insertDirectives: function(cssText) {
|
||||
cssText = this.insertPolyfillDirectivesInCssText(cssText);
|
||||
return this.insertPolyfillRulesInCssText(cssText);
|
||||
},
|
||||
/*
|
||||
* Process styles to convert native ShadowDOM rules that will trip
|
||||
* up the css parser; we rely on decorating the stylesheet with inert rules.
|
||||
*
|
||||
* For example, we convert this rule:
|
||||
*
|
||||
* polyfill-next-selector { content: ':host menu-item'; }
|
||||
* ::content menu-item {
|
||||
*
|
||||
* to this:
|
||||
*
|
||||
* scopeName menu-item {
|
||||
*
|
||||
**/
|
||||
insertPolyfillDirectivesInCssText: function(cssText) {
|
||||
// TODO(sorvell): remove either content or comment
|
||||
cssText = cssText.replace(cssCommentNextSelectorRe, function(match, p1) {
|
||||
// remove end comment delimiter and add block start
|
||||
return p1.slice(0, -2) + '{';
|
||||
});
|
||||
return cssText.replace(cssContentNextSelectorRe, function(match, p1) {
|
||||
return p1 + ' {';
|
||||
});
|
||||
},
|
||||
/*
|
||||
* Process styles to add rules which will only apply under the polyfill
|
||||
*
|
||||
* For example, we convert this rule:
|
||||
*
|
||||
* polyfill-rule {
|
||||
* content: ':host menu-item';
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* to this:
|
||||
*
|
||||
* scopeName menu-item {...}
|
||||
*
|
||||
**/
|
||||
insertPolyfillRulesInCssText: function(cssText) {
|
||||
// TODO(sorvell): remove either content or comment
|
||||
cssText = cssText.replace(cssCommentRuleRe, function(match, p1) {
|
||||
// remove end comment delimiter
|
||||
return p1.slice(0, -1);
|
||||
});
|
||||
return cssText.replace(cssContentRuleRe, function(match, p1, p2, p3) {
|
||||
var rule = match.replace(p1, '').replace(p2, '');
|
||||
return p3 + rule;
|
||||
});
|
||||
},
|
||||
/* Ensure styles are scoped. Pseudo-scoping takes a rule like:
|
||||
*
|
||||
* .foo {... }
|
||||
*
|
||||
* and converts this to
|
||||
*
|
||||
* scopeName .foo { ... }
|
||||
*/
|
||||
scopeCssText: function(cssText, scopeSelector) {
|
||||
var unscoped = this.extractUnscopedRulesFromCssText(cssText);
|
||||
cssText = this.insertPolyfillHostInCssText(cssText);
|
||||
cssText = this.convertColonHost(cssText);
|
||||
cssText = this.convertColonHostContext(cssText);
|
||||
cssText = this.convertShadowDOMSelectors(cssText);
|
||||
if (scopeSelector) {
|
||||
var self = this, cssText;
|
||||
withCssRules(cssText, function(rules) {
|
||||
cssText = self.scopeRules(rules, scopeSelector);
|
||||
});
|
||||
|
||||
}
|
||||
cssText = cssText + '\n' + unscoped;
|
||||
return cssText.trim();
|
||||
},
|
||||
/*
|
||||
* Process styles to add rules which will only apply under the polyfill
|
||||
* and do not process via CSSOM. (CSSOM is destructive to rules on rare
|
||||
* occasions, e.g. -webkit-calc on Safari.)
|
||||
* For example, we convert this rule:
|
||||
*
|
||||
* (comment start) @polyfill-unscoped-rule menu-item {
|
||||
* ... } (comment end)
|
||||
*
|
||||
* to this:
|
||||
*
|
||||
* menu-item {...}
|
||||
*
|
||||
**/
|
||||
extractUnscopedRulesFromCssText: function(cssText) {
|
||||
// TODO(sorvell): remove either content or comment
|
||||
var r = '', m;
|
||||
while (m = cssCommentUnscopedRuleRe.exec(cssText)) {
|
||||
r += m[1].slice(0, -1) + '\n\n';
|
||||
}
|
||||
while (m = cssContentUnscopedRuleRe.exec(cssText)) {
|
||||
r += m[0].replace(m[2], '').replace(m[1], m[3]) + '\n\n';
|
||||
}
|
||||
return r;
|
||||
},
|
||||
/*
|
||||
* convert a rule like :host(.foo) > .bar { }
|
||||
*
|
||||
* to
|
||||
*
|
||||
* scopeName.foo > .bar
|
||||
*/
|
||||
convertColonHost: function(cssText) {
|
||||
return this.convertColonRule(cssText, cssColonHostRe,
|
||||
this.colonHostPartReplacer);
|
||||
},
|
||||
/*
|
||||
* convert a rule like :host-context(.foo) > .bar { }
|
||||
*
|
||||
* to
|
||||
*
|
||||
* scopeName.foo > .bar, .foo scopeName > .bar { }
|
||||
*
|
||||
* and
|
||||
*
|
||||
* :host-context(.foo:host) .bar { ... }
|
||||
*
|
||||
* to
|
||||
*
|
||||
* scopeName.foo .bar { ... }
|
||||
*/
|
||||
convertColonHostContext: function(cssText) {
|
||||
return this.convertColonRule(cssText, cssColonHostContextRe,
|
||||
this.colonHostContextPartReplacer);
|
||||
},
|
||||
convertColonRule: function(cssText, regExp, partReplacer) {
|
||||
// p1 = :host, p2 = contents of (), p3 rest of rule
|
||||
return cssText.replace(regExp, function(m, p1, p2, p3) {
|
||||
p1 = polyfillHostNoCombinator;
|
||||
if (p2) {
|
||||
var parts = p2.split(','), r = [];
|
||||
for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) {
|
||||
p = p.trim();
|
||||
r.push(partReplacer(p1, p, p3));
|
||||
}
|
||||
return r.join(',');
|
||||
} else {
|
||||
return p1 + p3;
|
||||
}
|
||||
});
|
||||
},
|
||||
colonHostContextPartReplacer: function(host, part, suffix) {
|
||||
if (part.match(polyfillHost)) {
|
||||
return this.colonHostPartReplacer(host, part, suffix);
|
||||
} else {
|
||||
return host + part + suffix + ', ' + part + ' ' + host + suffix;
|
||||
}
|
||||
},
|
||||
colonHostPartReplacer: function(host, part, suffix) {
|
||||
return host + part.replace(polyfillHost, '') + suffix;
|
||||
},
|
||||
/*
|
||||
* Convert combinators like ::shadow and pseudo-elements like ::content
|
||||
* by replacing with space.
|
||||
*/
|
||||
convertShadowDOMSelectors: function(cssText) {
|
||||
for (var i=0; i < shadowDOMSelectorsRe.length; i++) {
|
||||
cssText = cssText.replace(shadowDOMSelectorsRe[i], ' ');
|
||||
}
|
||||
return cssText;
|
||||
},
|
||||
// change a selector like 'div' to 'name div'
|
||||
scopeRules: function(cssRules, scopeSelector) {
|
||||
var cssText = '';
|
||||
if (cssRules) {
|
||||
Array.prototype.forEach.call(cssRules, function(rule) {
|
||||
if (rule.selectorText && (rule.style && rule.style.cssText !== undefined)) {
|
||||
cssText += this.scopeSelector(rule.selectorText, scopeSelector,
|
||||
this.strictStyling) + ' {\n\t';
|
||||
cssText += this.propertiesFromRule(rule) + '\n}\n\n';
|
||||
} else if (rule.type === CSSRule.MEDIA_RULE) {
|
||||
cssText += '@media ' + rule.media.mediaText + ' {\n';
|
||||
cssText += this.scopeRules(rule.cssRules, scopeSelector);
|
||||
cssText += '\n}\n\n';
|
||||
} else {
|
||||
// KEYFRAMES_RULE in IE throws when we query cssText
|
||||
// when it contains a -webkit- property.
|
||||
// if this happens, we fallback to constructing the rule
|
||||
// from the CSSRuleSet
|
||||
// https://connect.microsoft.com/IE/feedbackdetail/view/955703/accessing-csstext-of-a-keyframe-rule-that-contains-a-webkit-property-via-cssom-generates-exception
|
||||
try {
|
||||
if (rule.cssText) {
|
||||
cssText += rule.cssText + '\n\n';
|
||||
}
|
||||
} catch(x) {
|
||||
if (rule.type === CSSRule.KEYFRAMES_RULE && rule.cssRules) {
|
||||
cssText += this.ieSafeCssTextFromKeyFrameRule(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
return cssText;
|
||||
},
|
||||
ieSafeCssTextFromKeyFrameRule: function(rule) {
|
||||
var cssText = '@keyframes ' + rule.name + ' {';
|
||||
Array.prototype.forEach.call(rule.cssRules, function(rule) {
|
||||
cssText += ' ' + rule.keyText + ' {' + rule.style.cssText + '}';
|
||||
});
|
||||
cssText += ' }';
|
||||
return cssText;
|
||||
},
|
||||
scopeSelector: function(selector, scopeSelector, strict) {
|
||||
var r = [], parts = selector.split(',');
|
||||
parts.forEach(function(p) {
|
||||
p = p.trim();
|
||||
if (this.selectorNeedsScoping(p, scopeSelector)) {
|
||||
p = (strict && !p.match(polyfillHostNoCombinator)) ?
|
||||
this.applyStrictSelectorScope(p, scopeSelector) :
|
||||
this.applySelectorScope(p, scopeSelector);
|
||||
}
|
||||
r.push(p);
|
||||
}, this);
|
||||
return r.join(', ');
|
||||
},
|
||||
selectorNeedsScoping: function(selector, scopeSelector) {
|
||||
if (Array.isArray(scopeSelector)) {
|
||||
return true;
|
||||
}
|
||||
var re = this.makeScopeMatcher(scopeSelector);
|
||||
return !selector.match(re);
|
||||
},
|
||||
makeScopeMatcher: function(scopeSelector) {
|
||||
scopeSelector = scopeSelector.replace(/\[/g, '\\[').replace(/\[/g, '\\]');
|
||||
return new RegExp('^(' + scopeSelector + ')' + selectorReSuffix, 'm');
|
||||
},
|
||||
applySelectorScope: function(selector, selectorScope) {
|
||||
return Array.isArray(selectorScope) ?
|
||||
this.applySelectorScopeList(selector, selectorScope) :
|
||||
this.applySimpleSelectorScope(selector, selectorScope);
|
||||
},
|
||||
// apply an array of selectors
|
||||
applySelectorScopeList: function(selector, scopeSelectorList) {
|
||||
var r = [];
|
||||
for (var i=0, s; (s=scopeSelectorList[i]); i++) {
|
||||
r.push(this.applySimpleSelectorScope(selector, s));
|
||||
}
|
||||
return r.join(', ');
|
||||
},
|
||||
// scope via name and [is=name]
|
||||
applySimpleSelectorScope: function(selector, scopeSelector) {
|
||||
if (selector.match(polyfillHostRe)) {
|
||||
selector = selector.replace(polyfillHostNoCombinator, scopeSelector);
|
||||
return selector.replace(polyfillHostRe, scopeSelector + ' ');
|
||||
} else {
|
||||
return scopeSelector + ' ' + selector;
|
||||
}
|
||||
},
|
||||
// return a selector with [name] suffix on each simple selector
|
||||
// e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]
|
||||
applyStrictSelectorScope: function(selector, scopeSelector) {
|
||||
scopeSelector = scopeSelector.replace(/\[is=([^\]]*)\]/g, '$1');
|
||||
var splits = [' ', '>', '+', '~'],
|
||||
scoped = selector,
|
||||
attrName = '[' + scopeSelector + ']';
|
||||
splits.forEach(function(sep) {
|
||||
var parts = scoped.split(sep);
|
||||
scoped = parts.map(function(p) {
|
||||
// remove :host since it should be unnecessary
|
||||
var t = p.trim().replace(polyfillHostRe, '');
|
||||
if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) {
|
||||
p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3')
|
||||
}
|
||||
return p;
|
||||
}).join(sep);
|
||||
});
|
||||
return scoped;
|
||||
},
|
||||
insertPolyfillHostInCssText: function(selector) {
|
||||
return selector.replace(colonHostContextRe, polyfillHostContext).replace(
|
||||
colonHostRe, polyfillHost);
|
||||
},
|
||||
propertiesFromRule: function(rule) {
|
||||
var cssText = rule.style.cssText;
|
||||
// TODO(sorvell): Safari cssom incorrectly removes quotes from the content
|
||||
// property. (https://bugs.webkit.org/show_bug.cgi?id=118045)
|
||||
// don't replace attr rules
|
||||
if (rule.style.content && !rule.style.content.match(/['"]+|attr/)) {
|
||||
cssText = cssText.replace(/content:[^;]*;/g, 'content: \'' +
|
||||
rule.style.content + '\';');
|
||||
}
|
||||
// TODO(sorvell): we can workaround this issue here, but we need a list
|
||||
// of troublesome properties to fix https://github.com/Polymer/platform/issues/53
|
||||
//
|
||||
// inherit rules can be omitted from cssText
|
||||
// TODO(sorvell): remove when Blink bug is fixed:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=358273
|
||||
var style = rule.style;
|
||||
for (var i in style) {
|
||||
if (style[i] === 'initial') {
|
||||
cssText += i + ': initial; ';
|
||||
}
|
||||
}
|
||||
return cssText;
|
||||
},
|
||||
replaceTextInStyles: function(styles, action) {
|
||||
if (styles && action) {
|
||||
if (!(styles instanceof Array)) {
|
||||
styles = [styles];
|
||||
}
|
||||
Array.prototype.forEach.call(styles, function(s) {
|
||||
s.textContent = action.call(this, s.textContent);
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
addCssToDocument: function(cssText, name) {
|
||||
if (cssText.match('@import')) {
|
||||
addOwnSheet(cssText, name);
|
||||
} else {
|
||||
addCssToDocument(cssText);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var selectorRe = /([^{]*)({[\s\S]*?})/gim,
|
||||
cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
|
||||
// TODO(sorvell): remove either content or comment
|
||||
cssCommentNextSelectorRe = /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,
|
||||
cssContentNextSelectorRe = /polyfill-next-selector[^}]*content\:[\s]*?['"](.*?)['"][;\s]*}([^{]*?){/gim,
|
||||
// TODO(sorvell): remove either content or comment
|
||||
cssCommentRuleRe = /\/\*\s@polyfill-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim,
|
||||
cssContentRuleRe = /(polyfill-rule)[^}]*(content\:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim,
|
||||
// TODO(sorvell): remove either content or comment
|
||||
cssCommentUnscopedRuleRe = /\/\*\s@polyfill-unscoped-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim,
|
||||
cssContentUnscopedRuleRe = /(polyfill-unscoped-rule)[^}]*(content\:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim,
|
||||
cssPseudoRe = /::(x-[^\s{,(]*)/gim,
|
||||
cssPartRe = /::part\(([^)]*)\)/gim,
|
||||
// note: :host pre-processed to -shadowcsshost.
|
||||
polyfillHost = '-shadowcsshost',
|
||||
// note: :host-context pre-processed to -shadowcsshostcontext.
|
||||
polyfillHostContext = '-shadowcsscontext',
|
||||
parenSuffix = ')(?:\\((' +
|
||||
'(?:\\([^)(]*\\)|[^)(]*)+?' +
|
||||
')\\))?([^,{]*)';
|
||||
cssColonHostRe = new RegExp('(' + polyfillHost + parenSuffix, 'gim'),
|
||||
cssColonHostContextRe = new RegExp('(' + polyfillHostContext + parenSuffix, 'gim'),
|
||||
selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$',
|
||||
colonHostRe = /\:host/gim,
|
||||
colonHostContextRe = /\:host-context/gim,
|
||||
/* host name without combinator */
|
||||
polyfillHostNoCombinator = polyfillHost + '-no-combinator',
|
||||
polyfillHostRe = new RegExp(polyfillHost, 'gim'),
|
||||
polyfillHostContextRe = new RegExp(polyfillHostContext, 'gim'),
|
||||
shadowDOMSelectorsRe = [
|
||||
/\^\^/g,
|
||||
/\^/g,
|
||||
/\/shadow\//g,
|
||||
/\/shadow-deep\//g,
|
||||
/::shadow/g,
|
||||
/\/deep\//g,
|
||||
/::content/g
|
||||
];
|
||||
|
||||
function stylesToCssText(styles, preserveComments) {
|
||||
var cssText = '';
|
||||
Array.prototype.forEach.call(styles, function(s) {
|
||||
cssText += s.textContent + '\n\n';
|
||||
});
|
||||
// strip comments for easier processing
|
||||
if (!preserveComments) {
|
||||
cssText = cssText.replace(cssCommentRe, '');
|
||||
}
|
||||
return cssText;
|
||||
}
|
||||
|
||||
function cssTextToStyle(cssText) {
|
||||
var style = document.createElement('style');
|
||||
style.textContent = cssText;
|
||||
return style;
|
||||
}
|
||||
|
||||
function cssToRules(cssText) {
|
||||
var style = cssTextToStyle(cssText);
|
||||
document.head.appendChild(style);
|
||||
var rules = [];
|
||||
if (style.sheet) {
|
||||
// TODO(sorvell): Firefox throws when accessing the rules of a stylesheet
|
||||
// with an @import
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=625013
|
||||
try {
|
||||
rules = style.sheet.cssRules;
|
||||
} catch(e) {
|
||||
//
|
||||
}
|
||||
} else {
|
||||
console.warn('sheet not found', style);
|
||||
}
|
||||
style.parentNode.removeChild(style);
|
||||
return rules;
|
||||
}
|
||||
|
||||
var frame = document.createElement('iframe');
|
||||
frame.style.display = 'none';
|
||||
|
||||
function initFrame() {
|
||||
frame.initialized = true;
|
||||
document.body.appendChild(frame);
|
||||
var doc = frame.contentDocument;
|
||||
var base = doc.createElement('base');
|
||||
base.href = document.baseURI;
|
||||
doc.head.appendChild(base);
|
||||
}
|
||||
|
||||
function inFrame(fn) {
|
||||
if (!frame.initialized) {
|
||||
initFrame();
|
||||
}
|
||||
document.body.appendChild(frame);
|
||||
fn(frame.contentDocument);
|
||||
document.body.removeChild(frame);
|
||||
}
|
||||
|
||||
// TODO(sorvell): use an iframe if the cssText contains an @import to workaround
|
||||
// https://code.google.com/p/chromium/issues/detail?id=345114
|
||||
var isChrome = navigator.userAgent.match('Chrome');
|
||||
function withCssRules(cssText, callback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
var rules;
|
||||
if (cssText.match('@import') && isChrome) {
|
||||
var style = cssTextToStyle(cssText);
|
||||
inFrame(function(doc) {
|
||||
doc.head.appendChild(style.impl);
|
||||
rules = Array.prototype.slice.call(style.sheet.cssRules, 0);
|
||||
callback(rules);
|
||||
});
|
||||
} else {
|
||||
rules = cssToRules(cssText);
|
||||
callback(rules);
|
||||
}
|
||||
}
|
||||
|
||||
function rulesToCss(cssRules) {
|
||||
for (var i=0, css=[]; i < cssRules.length; i++) {
|
||||
css.push(cssRules[i].cssText);
|
||||
}
|
||||
return css.join('\n\n');
|
||||
}
|
||||
|
||||
function addCssToDocument(cssText) {
|
||||
if (cssText) {
|
||||
getSheet().appendChild(document.createTextNode(cssText));
|
||||
}
|
||||
}
|
||||
|
||||
function addOwnSheet(cssText, name) {
|
||||
var style = cssTextToStyle(cssText);
|
||||
style.setAttribute(name, '');
|
||||
style.setAttribute(SHIMMED_ATTRIBUTE, '');
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
var SHIM_ATTRIBUTE = 'shim-shadowdom';
|
||||
var SHIMMED_ATTRIBUTE = 'shim-shadowdom-css';
|
||||
var NO_SHIM_ATTRIBUTE = 'no-shim';
|
||||
|
||||
var sheet;
|
||||
function getSheet() {
|
||||
if (!sheet) {
|
||||
sheet = document.createElement("style");
|
||||
sheet.setAttribute(SHIMMED_ATTRIBUTE, '');
|
||||
sheet[SHIMMED_ATTRIBUTE] = true;
|
||||
}
|
||||
return sheet;
|
||||
}
|
||||
|
||||
// add polyfill stylesheet to document
|
||||
if (window.ShadowDOMPolyfill) {
|
||||
addCssToDocument('style { display: none !important; }\n');
|
||||
var doc = wrap(document);
|
||||
var head = doc.querySelector('head');
|
||||
head.insertBefore(getSheet(), head.childNodes[0]);
|
||||
|
||||
// TODO(sorvell): monkey-patching HTMLImports is abusive;
|
||||
// consider a better solution.
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var urlResolver = scope.urlResolver;
|
||||
|
||||
if (window.HTMLImports && !HTMLImports.useNative) {
|
||||
var SHIM_SHEET_SELECTOR = 'link[rel=stylesheet]' +
|
||||
'[' + SHIM_ATTRIBUTE + ']';
|
||||
var SHIM_STYLE_SELECTOR = 'style[' + SHIM_ATTRIBUTE + ']';
|
||||
HTMLImports.importer.documentPreloadSelectors += ',' + SHIM_SHEET_SELECTOR;
|
||||
HTMLImports.importer.importsPreloadSelectors += ',' + SHIM_SHEET_SELECTOR;
|
||||
|
||||
HTMLImports.parser.documentSelectors = [
|
||||
HTMLImports.parser.documentSelectors,
|
||||
SHIM_SHEET_SELECTOR,
|
||||
SHIM_STYLE_SELECTOR
|
||||
].join(',');
|
||||
|
||||
var originalParseGeneric = HTMLImports.parser.parseGeneric;
|
||||
|
||||
HTMLImports.parser.parseGeneric = function(elt) {
|
||||
if (elt[SHIMMED_ATTRIBUTE]) {
|
||||
return;
|
||||
}
|
||||
var style = elt.__importElement || elt;
|
||||
if (!style.hasAttribute(SHIM_ATTRIBUTE)) {
|
||||
originalParseGeneric.call(this, elt);
|
||||
return;
|
||||
}
|
||||
if (elt.__resource) {
|
||||
style = elt.ownerDocument.createElement('style');
|
||||
style.textContent = elt.__resource;
|
||||
}
|
||||
// relay on HTMLImports for path fixup
|
||||
HTMLImports.path.resolveUrlsInStyle(style);
|
||||
style.textContent = ShadowCSS.shimStyle(style);
|
||||
style.removeAttribute(SHIM_ATTRIBUTE, '');
|
||||
style.setAttribute(SHIMMED_ATTRIBUTE, '');
|
||||
style[SHIMMED_ATTRIBUTE] = true;
|
||||
// place in document
|
||||
if (style.parentNode !== head) {
|
||||
// replace links in head
|
||||
if (elt.parentNode === head) {
|
||||
head.replaceChild(style, elt);
|
||||
} else {
|
||||
this.addElementToDocument(style);
|
||||
}
|
||||
}
|
||||
style.__importParsed = true;
|
||||
this.markParsingComplete(elt);
|
||||
this.parseNext();
|
||||
}
|
||||
|
||||
var hasResource = HTMLImports.parser.hasResource;
|
||||
HTMLImports.parser.hasResource = function(node) {
|
||||
if (node.localName === 'link' && node.rel === 'stylesheet' &&
|
||||
node.hasAttribute(SHIM_ATTRIBUTE)) {
|
||||
return (node.__resource);
|
||||
} else {
|
||||
return hasResource.call(this, node);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// exports
|
||||
scope.ShadowCSS = ShadowCSS;
|
||||
|
||||
})(window.WebComponents);
|
||||
255
src/ShadowDOM/ArraySplice.js
Normal file
255
src/ShadowDOM/ArraySplice.js
Normal file
@@ -0,0 +1,255 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
function newSplice(index, removed, addedCount) {
|
||||
return {
|
||||
index: index,
|
||||
removed: removed,
|
||||
addedCount: addedCount
|
||||
};
|
||||
}
|
||||
|
||||
var EDIT_LEAVE = 0;
|
||||
var EDIT_UPDATE = 1;
|
||||
var EDIT_ADD = 2;
|
||||
var EDIT_DELETE = 3;
|
||||
|
||||
function ArraySplice() {}
|
||||
|
||||
ArraySplice.prototype = {
|
||||
|
||||
// Note: This function is *based* on the computation of the Levenshtein
|
||||
// "edit" distance. The one change is that "updates" are treated as two
|
||||
// edits - not one. With Array splices, an update is really a delete
|
||||
// followed by an add. By retaining this, we optimize for "keeping" the
|
||||
// maximum array items in the original array. For example:
|
||||
//
|
||||
// 'xxxx123' -> '123yyyy'
|
||||
//
|
||||
// With 1-edit updates, the shortest path would be just to update all seven
|
||||
// characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
|
||||
// leaves the substring '123' intact.
|
||||
calcEditDistances: function(current, currentStart, currentEnd,
|
||||
old, oldStart, oldEnd) {
|
||||
// "Deletion" columns
|
||||
var rowCount = oldEnd - oldStart + 1;
|
||||
var columnCount = currentEnd - currentStart + 1;
|
||||
var distances = new Array(rowCount);
|
||||
|
||||
// "Addition" rows. Initialize null column.
|
||||
for (var i = 0; i < rowCount; i++) {
|
||||
distances[i] = new Array(columnCount);
|
||||
distances[i][0] = i;
|
||||
}
|
||||
|
||||
// Initialize null row
|
||||
for (var j = 0; j < columnCount; j++)
|
||||
distances[0][j] = j;
|
||||
|
||||
for (var i = 1; i < rowCount; i++) {
|
||||
for (var j = 1; j < columnCount; j++) {
|
||||
if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1]))
|
||||
distances[i][j] = distances[i - 1][j - 1];
|
||||
else {
|
||||
var north = distances[i - 1][j] + 1;
|
||||
var west = distances[i][j - 1] + 1;
|
||||
distances[i][j] = north < west ? north : west;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return distances;
|
||||
},
|
||||
|
||||
// This starts at the final weight, and walks "backward" by finding
|
||||
// the minimum previous weight recursively until the origin of the weight
|
||||
// matrix.
|
||||
spliceOperationsFromEditDistances: function(distances) {
|
||||
var i = distances.length - 1;
|
||||
var j = distances[0].length - 1;
|
||||
var current = distances[i][j];
|
||||
var edits = [];
|
||||
while (i > 0 || j > 0) {
|
||||
if (i == 0) {
|
||||
edits.push(EDIT_ADD);
|
||||
j--;
|
||||
continue;
|
||||
}
|
||||
if (j == 0) {
|
||||
edits.push(EDIT_DELETE);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
var northWest = distances[i - 1][j - 1];
|
||||
var west = distances[i - 1][j];
|
||||
var north = distances[i][j - 1];
|
||||
|
||||
var min;
|
||||
if (west < north)
|
||||
min = west < northWest ? west : northWest;
|
||||
else
|
||||
min = north < northWest ? north : northWest;
|
||||
|
||||
if (min == northWest) {
|
||||
if (northWest == current) {
|
||||
edits.push(EDIT_LEAVE);
|
||||
} else {
|
||||
edits.push(EDIT_UPDATE);
|
||||
current = northWest;
|
||||
}
|
||||
i--;
|
||||
j--;
|
||||
} else if (min == west) {
|
||||
edits.push(EDIT_DELETE);
|
||||
i--;
|
||||
current = west;
|
||||
} else {
|
||||
edits.push(EDIT_ADD);
|
||||
j--;
|
||||
current = north;
|
||||
}
|
||||
}
|
||||
|
||||
edits.reverse();
|
||||
return edits;
|
||||
},
|
||||
|
||||
/**
|
||||
* Splice Projection functions:
|
||||
*
|
||||
* A splice map is a representation of how a previous array of items
|
||||
* was transformed into a new array of items. Conceptually it is a list of
|
||||
* tuples of
|
||||
*
|
||||
* <index, removed, addedCount>
|
||||
*
|
||||
* which are kept in ascending index order of. The tuple represents that at
|
||||
* the |index|, |removed| sequence of items were removed, and counting forward
|
||||
* from |index|, |addedCount| items were added.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Lacking individual splice mutation information, the minimal set of
|
||||
* splices can be synthesized given the previous state and final state of an
|
||||
* array. The basic approach is to calculate the edit distance matrix and
|
||||
* choose the shortest path through it.
|
||||
*
|
||||
* Complexity: O(l * p)
|
||||
* l: The length of the current array
|
||||
* p: The length of the old array
|
||||
*/
|
||||
calcSplices: function(current, currentStart, currentEnd,
|
||||
old, oldStart, oldEnd) {
|
||||
var prefixCount = 0;
|
||||
var suffixCount = 0;
|
||||
|
||||
var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
|
||||
if (currentStart == 0 && oldStart == 0)
|
||||
prefixCount = this.sharedPrefix(current, old, minLength);
|
||||
|
||||
if (currentEnd == current.length && oldEnd == old.length)
|
||||
suffixCount = this.sharedSuffix(current, old, minLength - prefixCount);
|
||||
|
||||
currentStart += prefixCount;
|
||||
oldStart += prefixCount;
|
||||
currentEnd -= suffixCount;
|
||||
oldEnd -= suffixCount;
|
||||
|
||||
if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0)
|
||||
return [];
|
||||
|
||||
if (currentStart == currentEnd) {
|
||||
var splice = newSplice(currentStart, [], 0);
|
||||
while (oldStart < oldEnd)
|
||||
splice.removed.push(old[oldStart++]);
|
||||
|
||||
return [ splice ];
|
||||
} else if (oldStart == oldEnd)
|
||||
return [ newSplice(currentStart, [], currentEnd - currentStart) ];
|
||||
|
||||
var ops = this.spliceOperationsFromEditDistances(
|
||||
this.calcEditDistances(current, currentStart, currentEnd,
|
||||
old, oldStart, oldEnd));
|
||||
|
||||
var splice = undefined;
|
||||
var splices = [];
|
||||
var index = currentStart;
|
||||
var oldIndex = oldStart;
|
||||
for (var i = 0; i < ops.length; i++) {
|
||||
switch(ops[i]) {
|
||||
case EDIT_LEAVE:
|
||||
if (splice) {
|
||||
splices.push(splice);
|
||||
splice = undefined;
|
||||
}
|
||||
|
||||
index++;
|
||||
oldIndex++;
|
||||
break;
|
||||
case EDIT_UPDATE:
|
||||
if (!splice)
|
||||
splice = newSplice(index, [], 0);
|
||||
|
||||
splice.addedCount++;
|
||||
index++;
|
||||
|
||||
splice.removed.push(old[oldIndex]);
|
||||
oldIndex++;
|
||||
break;
|
||||
case EDIT_ADD:
|
||||
if (!splice)
|
||||
splice = newSplice(index, [], 0);
|
||||
|
||||
splice.addedCount++;
|
||||
index++;
|
||||
break;
|
||||
case EDIT_DELETE:
|
||||
if (!splice)
|
||||
splice = newSplice(index, [], 0);
|
||||
|
||||
splice.removed.push(old[oldIndex]);
|
||||
oldIndex++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (splice) {
|
||||
splices.push(splice);
|
||||
}
|
||||
return splices;
|
||||
},
|
||||
|
||||
sharedPrefix: function(current, old, searchLength) {
|
||||
for (var i = 0; i < searchLength; i++)
|
||||
if (!this.equals(current[i], old[i]))
|
||||
return i;
|
||||
return searchLength;
|
||||
},
|
||||
|
||||
sharedSuffix: function(current, old, searchLength) {
|
||||
var index1 = current.length;
|
||||
var index2 = old.length;
|
||||
var count = 0;
|
||||
while (count < searchLength && this.equals(current[--index1], old[--index2]))
|
||||
count++;
|
||||
|
||||
return count;
|
||||
},
|
||||
|
||||
calculateSplices: function(current, previous) {
|
||||
return this.calcSplices(current, 0, current.length, previous, 0,
|
||||
previous.length);
|
||||
},
|
||||
|
||||
equals: function(currentValue, previousValue) {
|
||||
return currentValue === previousValue;
|
||||
}
|
||||
};
|
||||
|
||||
// exports
|
||||
scope.ArraySplice = ArraySplice;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
381
src/ShadowDOM/MutationObserver.js
Normal file
381
src/ShadowDOM/MutationObserver.js
Normal file
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var setEndOfMicrotask = scope.setEndOfMicrotask
|
||||
var wrapIfNeeded = scope.wrapIfNeeded
|
||||
var wrappers = scope.wrappers;
|
||||
|
||||
var registrationsTable = new WeakMap();
|
||||
var globalMutationObservers = [];
|
||||
var isScheduled = false;
|
||||
|
||||
function scheduleCallback(observer) {
|
||||
if (observer.scheduled_)
|
||||
return;
|
||||
|
||||
observer.scheduled_ = true;
|
||||
globalMutationObservers.push(observer);
|
||||
|
||||
if (isScheduled)
|
||||
return;
|
||||
setEndOfMicrotask(notifyObservers);
|
||||
isScheduled = true;
|
||||
}
|
||||
|
||||
// http://dom.spec.whatwg.org/#mutation-observers
|
||||
function notifyObservers() {
|
||||
isScheduled = false;
|
||||
|
||||
while (globalMutationObservers.length) {
|
||||
var notifyList = globalMutationObservers;
|
||||
globalMutationObservers = [];
|
||||
|
||||
// Deliver changes in birth order of the MutationObservers.
|
||||
notifyList.sort(function(x, y) { return x.uid_ - y.uid_; });
|
||||
|
||||
for (var i = 0; i < notifyList.length; i++) {
|
||||
var mo = notifyList[i];
|
||||
mo.scheduled_ = false;
|
||||
var queue = mo.takeRecords();
|
||||
removeTransientObserversFor(mo);
|
||||
if (queue.length) {
|
||||
mo.callback_(queue, mo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {Node} target
|
||||
* @constructor
|
||||
*/
|
||||
function MutationRecord(type, target) {
|
||||
this.type = type;
|
||||
this.target = target;
|
||||
this.addedNodes = new wrappers.NodeList();
|
||||
this.removedNodes = new wrappers.NodeList();
|
||||
this.previousSibling = null;
|
||||
this.nextSibling = null;
|
||||
this.attributeName = null;
|
||||
this.attributeNamespace = null;
|
||||
this.oldValue = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers transient observers to ancestor and its ancesors for the node
|
||||
* which was removed.
|
||||
* @param {!Node} ancestor
|
||||
* @param {!Node} node
|
||||
*/
|
||||
function registerTransientObservers(ancestor, node) {
|
||||
for (; ancestor; ancestor = ancestor.parentNode) {
|
||||
var registrations = registrationsTable.get(ancestor);
|
||||
if (!registrations)
|
||||
continue;
|
||||
for (var i = 0; i < registrations.length; i++) {
|
||||
var registration = registrations[i];
|
||||
if (registration.options.subtree)
|
||||
registration.addTransientObserver(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeTransientObserversFor(observer) {
|
||||
for (var i = 0; i < observer.nodes_.length; i++) {
|
||||
var node = observer.nodes_[i];
|
||||
var registrations = registrationsTable.get(node);
|
||||
if (!registrations)
|
||||
return;
|
||||
for (var j = 0; j < registrations.length; j++) {
|
||||
var registration = registrations[j];
|
||||
if (registration.observer === observer)
|
||||
registration.removeTransientObservers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// http://dom.spec.whatwg.org/#queue-a-mutation-record
|
||||
function enqueueMutation(target, type, data) {
|
||||
// 1.
|
||||
var interestedObservers = Object.create(null);
|
||||
var associatedStrings = Object.create(null);
|
||||
|
||||
// 2.
|
||||
for (var node = target; node; node = node.parentNode) {
|
||||
// 3.
|
||||
var registrations = registrationsTable.get(node);
|
||||
if (!registrations)
|
||||
continue;
|
||||
for (var j = 0; j < registrations.length; j++) {
|
||||
var registration = registrations[j];
|
||||
var options = registration.options;
|
||||
// 1.
|
||||
if (node !== target && !options.subtree)
|
||||
continue;
|
||||
|
||||
// 2.
|
||||
if (type === 'attributes' && !options.attributes)
|
||||
continue;
|
||||
|
||||
// 3. If type is "attributes", options's attributeFilter is present, and
|
||||
// either options's attributeFilter does not contain name or namespace
|
||||
// is non-null, continue.
|
||||
if (type === 'attributes' && options.attributeFilter &&
|
||||
(data.namespace !== null ||
|
||||
options.attributeFilter.indexOf(data.name) === -1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4.
|
||||
if (type === 'characterData' && !options.characterData)
|
||||
continue;
|
||||
|
||||
// 5.
|
||||
if (type === 'childList' && !options.childList)
|
||||
continue;
|
||||
|
||||
// 6.
|
||||
var observer = registration.observer;
|
||||
interestedObservers[observer.uid_] = observer;
|
||||
|
||||
// 7. If either type is "attributes" and options's attributeOldValue is
|
||||
// true, or type is "characterData" and options's characterDataOldValue
|
||||
// is true, set the paired string of registered observer's observer in
|
||||
// interested observers to oldValue.
|
||||
if (type === 'attributes' && options.attributeOldValue ||
|
||||
type === 'characterData' && options.characterDataOldValue) {
|
||||
associatedStrings[observer.uid_] = data.oldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4.
|
||||
for (var uid in interestedObservers) {
|
||||
var observer = interestedObservers[uid];
|
||||
var record = new MutationRecord(type, target);
|
||||
|
||||
// 2.
|
||||
if ('name' in data && 'namespace' in data) {
|
||||
record.attributeName = data.name;
|
||||
record.attributeNamespace = data.namespace;
|
||||
}
|
||||
|
||||
// 3.
|
||||
if (data.addedNodes)
|
||||
record.addedNodes = data.addedNodes;
|
||||
|
||||
// 4.
|
||||
if (data.removedNodes)
|
||||
record.removedNodes = data.removedNodes;
|
||||
|
||||
// 5.
|
||||
if (data.previousSibling)
|
||||
record.previousSibling = data.previousSibling;
|
||||
|
||||
// 6.
|
||||
if (data.nextSibling)
|
||||
record.nextSibling = data.nextSibling;
|
||||
|
||||
// 7.
|
||||
if (associatedStrings[uid] !== undefined)
|
||||
record.oldValue = associatedStrings[uid];
|
||||
|
||||
// 8.
|
||||
scheduleCallback(observer);
|
||||
observer.records_.push(record);
|
||||
}
|
||||
}
|
||||
|
||||
var slice = Array.prototype.slice;
|
||||
|
||||
/**
|
||||
* @param {!Object} options
|
||||
* @constructor
|
||||
*/
|
||||
function MutationObserverOptions(options) {
|
||||
this.childList = !!options.childList;
|
||||
this.subtree = !!options.subtree;
|
||||
|
||||
// 1. If either options' attributeOldValue or attributeFilter is present
|
||||
// and options' attributes is omitted, set options' attributes to true.
|
||||
if (!('attributes' in options) &&
|
||||
('attributeOldValue' in options || 'attributeFilter' in options)) {
|
||||
this.attributes = true;
|
||||
} else {
|
||||
this.attributes = !!options.attributes;
|
||||
}
|
||||
|
||||
// 2. If options' characterDataOldValue is present and options'
|
||||
// characterData is omitted, set options' characterData to true.
|
||||
if ('characterDataOldValue' in options && !('characterData' in options))
|
||||
this.characterData = true;
|
||||
else
|
||||
this.characterData = !!options.characterData;
|
||||
|
||||
// 3. & 4.
|
||||
if (!this.attributes &&
|
||||
(options.attributeOldValue || 'attributeFilter' in options) ||
|
||||
// 5.
|
||||
!this.characterData && options.characterDataOldValue) {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
this.characterData = !!options.characterData;
|
||||
this.attributeOldValue = !!options.attributeOldValue;
|
||||
this.characterDataOldValue = !!options.characterDataOldValue;
|
||||
if ('attributeFilter' in options) {
|
||||
if (options.attributeFilter == null ||
|
||||
typeof options.attributeFilter !== 'object') {
|
||||
throw new TypeError();
|
||||
}
|
||||
this.attributeFilter = slice.call(options.attributeFilter);
|
||||
} else {
|
||||
this.attributeFilter = null;
|
||||
}
|
||||
}
|
||||
|
||||
var uidCounter = 0;
|
||||
|
||||
/**
|
||||
* The class that maps to the DOM MutationObserver interface.
|
||||
* @param {Function} callback.
|
||||
* @constructor
|
||||
*/
|
||||
function MutationObserver(callback) {
|
||||
this.callback_ = callback;
|
||||
this.nodes_ = [];
|
||||
this.records_ = [];
|
||||
this.uid_ = ++uidCounter;
|
||||
this.scheduled_ = false;
|
||||
}
|
||||
|
||||
MutationObserver.prototype = {
|
||||
constructor: MutationObserver,
|
||||
|
||||
// http://dom.spec.whatwg.org/#dom-mutationobserver-observe
|
||||
observe: function(target, options) {
|
||||
target = wrapIfNeeded(target);
|
||||
|
||||
var newOptions = new MutationObserverOptions(options);
|
||||
|
||||
// 6.
|
||||
var registration;
|
||||
var registrations = registrationsTable.get(target);
|
||||
if (!registrations)
|
||||
registrationsTable.set(target, registrations = []);
|
||||
|
||||
for (var i = 0; i < registrations.length; i++) {
|
||||
if (registrations[i].observer === this) {
|
||||
registration = registrations[i];
|
||||
// 6.1.
|
||||
registration.removeTransientObservers();
|
||||
// 6.2.
|
||||
registration.options = newOptions;
|
||||
}
|
||||
}
|
||||
|
||||
// 7.
|
||||
if (!registration) {
|
||||
registration = new Registration(this, target, newOptions);
|
||||
registrations.push(registration);
|
||||
this.nodes_.push(target);
|
||||
}
|
||||
},
|
||||
|
||||
// http://dom.spec.whatwg.org/#dom-mutationobserver-disconnect
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class used to represent a registered observer.
|
||||
* @param {MutationObserver} observer
|
||||
* @param {Node} target
|
||||
* @param {MutationObserverOptions} options
|
||||
* @constructor
|
||||
*/
|
||||
function Registration(observer, target, options) {
|
||||
this.observer = observer;
|
||||
this.target = target;
|
||||
this.options = options;
|
||||
this.transientObservedNodes = [];
|
||||
}
|
||||
|
||||
Registration.prototype = {
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// Make sure we remove transient observers at the end of microtask, even
|
||||
// if we didn't get any change records.
|
||||
scheduleCallback(this.observer);
|
||||
|
||||
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 = [];
|
||||
|
||||
for (var i = 0; i < transientObservedNodes.length; i++) {
|
||||
var node = transientObservedNodes[i];
|
||||
var registrations = registrationsTable.get(node);
|
||||
for (var j = 0; j < registrations.length; j++) {
|
||||
if (registrations[j] === this) {
|
||||
registrations.splice(j, 1);
|
||||
// Each node can only have one registered observer associated with
|
||||
// this observer.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scope.enqueueMutation = enqueueMutation;
|
||||
scope.registerTransientObservers = registerTransientObservers;
|
||||
scope.wrappers.MutationObserver = MutationObserver;
|
||||
scope.wrappers.MutationRecord = MutationRecord;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
72
src/ShadowDOM/ShadowDOM.js
Normal file
72
src/ShadowDOM/ShadowDOM.js
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2013 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() {
|
||||
|
||||
var thisFile = 'ShadowDOM.js';
|
||||
var base = '';
|
||||
Array.prototype.forEach.call(document.querySelectorAll('script[src]'), function(s) {
|
||||
var src = s.getAttribute('src');
|
||||
var re = new RegExp(thisFile + '[^\\\\]*');
|
||||
var match = src.match(re);
|
||||
if (match) {
|
||||
base = src.slice(0, -match[0].length);
|
||||
}
|
||||
});
|
||||
|
||||
[
|
||||
'../WeakMap/WeakMap.js',
|
||||
'wrappers.js',
|
||||
'ArraySplice.js',
|
||||
'microtask.js',
|
||||
'MutationObserver.js',
|
||||
'TreeScope.js',
|
||||
'wrappers/events.js',
|
||||
'wrappers/TouchEvent.js',
|
||||
'wrappers/NodeList.js',
|
||||
'wrappers/HTMLCollection.js',
|
||||
'wrappers/Node.js',
|
||||
'querySelector.js',
|
||||
'wrappers/node-interfaces.js',
|
||||
'wrappers/CharacterData.js',
|
||||
'wrappers/Text.js',
|
||||
'wrappers/DOMTokenList.js',
|
||||
'wrappers/Element.js',
|
||||
'wrappers/HTMLElement.js',
|
||||
'wrappers/HTMLCanvasElement.js',
|
||||
'wrappers/HTMLContentElement.js',
|
||||
'wrappers/HTMLFormElement.js',
|
||||
'wrappers/HTMLImageElement.js',
|
||||
'wrappers/HTMLShadowElement.js',
|
||||
'wrappers/HTMLTemplateElement.js',
|
||||
'wrappers/HTMLMediaElement.js',
|
||||
'wrappers/HTMLAudioElement.js',
|
||||
'wrappers/HTMLOptionElement.js',
|
||||
'wrappers/HTMLSelectElement.js',
|
||||
'wrappers/HTMLTableElement.js',
|
||||
'wrappers/HTMLTableSectionElement.js',
|
||||
'wrappers/HTMLTableRowElement.js',
|
||||
'wrappers/HTMLUnknownElement.js',
|
||||
'wrappers/SVGElement.js',
|
||||
'wrappers/SVGUseElement.js',
|
||||
'wrappers/SVGElementInstance.js',
|
||||
'wrappers/CanvasRenderingContext2D.js',
|
||||
'wrappers/WebGLRenderingContext.js',
|
||||
'wrappers/Range.js',
|
||||
'wrappers/generic.js',
|
||||
'wrappers/ShadowRoot.js',
|
||||
'ShadowRenderer.js',
|
||||
'wrappers/elements-with-form-property.js',
|
||||
'wrappers/Selection.js',
|
||||
'wrappers/Document.js',
|
||||
'wrappers/Window.js',
|
||||
'wrappers/DataTransfer.js',
|
||||
'wrappers/FormData.js',
|
||||
'wrappers/XMLHttpRequest.js',
|
||||
'wrappers/override-constructors.js'
|
||||
].forEach(function(src) {
|
||||
document.write('<script src="' + base + src + '"></script>');
|
||||
});
|
||||
|
||||
})();
|
||||
665
src/ShadowDOM/ShadowRenderer.js
Normal file
665
src/ShadowDOM/ShadowRenderer.js
Normal file
@@ -0,0 +1,665 @@
|
||||
// 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) {
|
||||
'use strict';
|
||||
|
||||
var Element = scope.wrappers.Element;
|
||||
var HTMLContentElement = scope.wrappers.HTMLContentElement;
|
||||
var HTMLShadowElement = scope.wrappers.HTMLShadowElement;
|
||||
var Node = scope.wrappers.Node;
|
||||
var ShadowRoot = scope.wrappers.ShadowRoot;
|
||||
var assert = scope.assert;
|
||||
var getTreeScope = scope.getTreeScope;
|
||||
var mixin = scope.mixin;
|
||||
var oneOf = scope.oneOf;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var unwrap = scope.unwrap;
|
||||
var wrap = scope.wrap;
|
||||
var ArraySplice = scope.ArraySplice;
|
||||
|
||||
/**
|
||||
* Updates the fields of a wrapper to a snapshot of the logical DOM as needed.
|
||||
* Up means parentNode
|
||||
* Sideways means previous and next sibling.
|
||||
* @param {!Node} wrapper
|
||||
*/
|
||||
function updateWrapperUpAndSideways(wrapper) {
|
||||
wrapper.previousSibling_ = wrapper.previousSibling;
|
||||
wrapper.nextSibling_ = wrapper.nextSibling;
|
||||
wrapper.parentNode_ = wrapper.parentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the fields of a wrapper to a snapshot of the logical DOM as needed.
|
||||
* Down means first and last child
|
||||
* @param {!Node} wrapper
|
||||
*/
|
||||
function updateWrapperDown(wrapper) {
|
||||
wrapper.firstChild_ = wrapper.firstChild;
|
||||
wrapper.lastChild_ = wrapper.lastChild;
|
||||
}
|
||||
|
||||
function updateAllChildNodes(parentNodeWrapper) {
|
||||
assert(parentNodeWrapper instanceof Node);
|
||||
for (var childWrapper = parentNodeWrapper.firstChild;
|
||||
childWrapper;
|
||||
childWrapper = childWrapper.nextSibling) {
|
||||
updateWrapperUpAndSideways(childWrapper);
|
||||
}
|
||||
updateWrapperDown(parentNodeWrapper);
|
||||
}
|
||||
|
||||
function insertBefore(parentNodeWrapper, newChildWrapper, refChildWrapper) {
|
||||
var parentNode = unwrap(parentNodeWrapper);
|
||||
var newChild = unwrap(newChildWrapper);
|
||||
var refChild = refChildWrapper ? unwrap(refChildWrapper) : null;
|
||||
|
||||
remove(newChildWrapper);
|
||||
updateWrapperUpAndSideways(newChildWrapper);
|
||||
|
||||
if (!refChildWrapper) {
|
||||
parentNodeWrapper.lastChild_ = parentNodeWrapper.lastChild;
|
||||
if (parentNodeWrapper.lastChild === parentNodeWrapper.firstChild)
|
||||
parentNodeWrapper.firstChild_ = parentNodeWrapper.firstChild;
|
||||
|
||||
var lastChildWrapper = wrap(parentNode.lastChild);
|
||||
if (lastChildWrapper)
|
||||
lastChildWrapper.nextSibling_ = lastChildWrapper.nextSibling;
|
||||
} else {
|
||||
if (parentNodeWrapper.firstChild === refChildWrapper)
|
||||
parentNodeWrapper.firstChild_ = refChildWrapper;
|
||||
|
||||
refChildWrapper.previousSibling_ = refChildWrapper.previousSibling;
|
||||
}
|
||||
|
||||
scope.originalInsertBefore.call(parentNode, newChild, refChild);
|
||||
}
|
||||
|
||||
function remove(nodeWrapper) {
|
||||
var node = unwrap(nodeWrapper)
|
||||
var parentNode = node.parentNode;
|
||||
if (!parentNode)
|
||||
return;
|
||||
|
||||
var parentNodeWrapper = wrap(parentNode);
|
||||
updateWrapperUpAndSideways(nodeWrapper);
|
||||
|
||||
if (nodeWrapper.previousSibling)
|
||||
nodeWrapper.previousSibling.nextSibling_ = nodeWrapper;
|
||||
if (nodeWrapper.nextSibling)
|
||||
nodeWrapper.nextSibling.previousSibling_ = nodeWrapper;
|
||||
|
||||
if (parentNodeWrapper.lastChild === nodeWrapper)
|
||||
parentNodeWrapper.lastChild_ = nodeWrapper;
|
||||
if (parentNodeWrapper.firstChild === nodeWrapper)
|
||||
parentNodeWrapper.firstChild_ = nodeWrapper;
|
||||
|
||||
scope.originalRemoveChild.call(parentNode, node);
|
||||
}
|
||||
|
||||
var distributedNodesTable = new WeakMap();
|
||||
var destinationInsertionPointsTable = new WeakMap();
|
||||
var rendererForHostTable = new WeakMap();
|
||||
|
||||
function resetDistributedNodes(insertionPoint) {
|
||||
distributedNodesTable.set(insertionPoint, []);
|
||||
}
|
||||
|
||||
function getDistributedNodes(insertionPoint) {
|
||||
var rv = distributedNodesTable.get(insertionPoint);
|
||||
if (!rv)
|
||||
distributedNodesTable.set(insertionPoint, rv = []);
|
||||
return rv;
|
||||
}
|
||||
|
||||
function getChildNodesSnapshot(node) {
|
||||
var result = [], i = 0;
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
result[i++] = child;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var request = oneOf(window, [
|
||||
'requestAnimationFrame',
|
||||
'mozRequestAnimationFrame',
|
||||
'webkitRequestAnimationFrame',
|
||||
'setTimeout'
|
||||
]);
|
||||
|
||||
var pendingDirtyRenderers = [];
|
||||
var renderTimer;
|
||||
|
||||
function renderAllPending() {
|
||||
// TODO(arv): Order these in document order. That way we do not have to
|
||||
// render something twice.
|
||||
for (var i = 0; i < pendingDirtyRenderers.length; i++) {
|
||||
var renderer = pendingDirtyRenderers[i];
|
||||
var parentRenderer = renderer.parentRenderer;
|
||||
if (parentRenderer && parentRenderer.dirty)
|
||||
continue;
|
||||
renderer.render();
|
||||
}
|
||||
|
||||
pendingDirtyRenderers = [];
|
||||
}
|
||||
|
||||
function handleRequestAnimationFrame() {
|
||||
renderTimer = null;
|
||||
renderAllPending();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns existing shadow renderer for a host or creates it if it is needed.
|
||||
* @params {!Element} host
|
||||
* @return {!ShadowRenderer}
|
||||
*/
|
||||
function getRendererForHost(host) {
|
||||
var renderer = rendererForHostTable.get(host);
|
||||
if (!renderer) {
|
||||
renderer = new ShadowRenderer(host);
|
||||
rendererForHostTable.set(host, renderer);
|
||||
}
|
||||
return renderer;
|
||||
}
|
||||
|
||||
function getShadowRootAncestor(node) {
|
||||
var root = getTreeScope(node).root;
|
||||
if (root instanceof ShadowRoot)
|
||||
return root;
|
||||
return null;
|
||||
}
|
||||
|
||||
function getRendererForShadowRoot(shadowRoot) {
|
||||
return getRendererForHost(shadowRoot.host);
|
||||
}
|
||||
|
||||
var spliceDiff = new ArraySplice();
|
||||
spliceDiff.equals = function(renderNode, rawNode) {
|
||||
return unwrap(renderNode.node) === rawNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* RenderNode is used as an in memory "render tree". When we render the
|
||||
* composed tree we create a tree of RenderNodes, then we diff this against
|
||||
* the real DOM tree and make minimal changes as needed.
|
||||
*/
|
||||
function RenderNode(node) {
|
||||
this.skip = false;
|
||||
this.node = node;
|
||||
this.childNodes = [];
|
||||
}
|
||||
|
||||
RenderNode.prototype = {
|
||||
append: function(node) {
|
||||
var rv = new RenderNode(node);
|
||||
this.childNodes.push(rv);
|
||||
return rv;
|
||||
},
|
||||
|
||||
sync: function(opt_added) {
|
||||
if (this.skip)
|
||||
return;
|
||||
|
||||
var nodeWrapper = this.node;
|
||||
// plain array of RenderNodes
|
||||
var newChildren = this.childNodes;
|
||||
// plain array of real nodes.
|
||||
var oldChildren = getChildNodesSnapshot(unwrap(nodeWrapper));
|
||||
var added = opt_added || new WeakMap();
|
||||
|
||||
var splices = spliceDiff.calculateSplices(newChildren, oldChildren);
|
||||
|
||||
var newIndex = 0, oldIndex = 0;
|
||||
var lastIndex = 0;
|
||||
for (var i = 0; i < splices.length; i++) {
|
||||
var splice = splices[i];
|
||||
for (; lastIndex < splice.index; lastIndex++) {
|
||||
oldIndex++;
|
||||
newChildren[newIndex++].sync(added);
|
||||
}
|
||||
|
||||
var removedCount = splice.removed.length;
|
||||
for (var j = 0; j < removedCount; j++) {
|
||||
var wrapper = wrap(oldChildren[oldIndex++]);
|
||||
if (!added.get(wrapper))
|
||||
remove(wrapper);
|
||||
}
|
||||
|
||||
var addedCount = splice.addedCount;
|
||||
var refNode = oldChildren[oldIndex] && wrap(oldChildren[oldIndex]);
|
||||
for (var j = 0; j < addedCount; j++) {
|
||||
var newChildRenderNode = newChildren[newIndex++];
|
||||
var newChildWrapper = newChildRenderNode.node;
|
||||
insertBefore(nodeWrapper, newChildWrapper, refNode);
|
||||
|
||||
// Keep track of added so that we do not remove the node after it
|
||||
// has been added.
|
||||
added.set(newChildWrapper, true);
|
||||
|
||||
newChildRenderNode.sync(added);
|
||||
}
|
||||
|
||||
lastIndex += addedCount;
|
||||
}
|
||||
|
||||
for (var i = lastIndex; i < newChildren.length; i++) {
|
||||
newChildren[i].sync(added);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function ShadowRenderer(host) {
|
||||
this.host = host;
|
||||
this.dirty = false;
|
||||
this.invalidateAttributes();
|
||||
this.associateNode(host);
|
||||
}
|
||||
|
||||
ShadowRenderer.prototype = {
|
||||
|
||||
// http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#rendering-shadow-trees
|
||||
render: function(opt_renderNode) {
|
||||
if (!this.dirty)
|
||||
return;
|
||||
|
||||
this.invalidateAttributes();
|
||||
|
||||
var host = this.host;
|
||||
|
||||
this.distribution(host);
|
||||
var renderNode = opt_renderNode || new RenderNode(host);
|
||||
this.buildRenderTree(renderNode, host);
|
||||
|
||||
var topMostRenderer = !opt_renderNode;
|
||||
if (topMostRenderer)
|
||||
renderNode.sync();
|
||||
|
||||
this.dirty = false;
|
||||
},
|
||||
|
||||
get parentRenderer() {
|
||||
return getTreeScope(this.host).renderer;
|
||||
},
|
||||
|
||||
invalidate: function() {
|
||||
if (!this.dirty) {
|
||||
this.dirty = true;
|
||||
var parentRenderer = this.parentRenderer;
|
||||
if (parentRenderer)
|
||||
parentRenderer.invalidate();
|
||||
pendingDirtyRenderers.push(this);
|
||||
if (renderTimer)
|
||||
return;
|
||||
renderTimer = window[request](handleRequestAnimationFrame, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// http://w3c.github.io/webcomponents/spec/shadow/#distribution-algorithms
|
||||
distribution: function(root) {
|
||||
this.resetAllSubtrees(root);
|
||||
this.distributionResolution(root);
|
||||
},
|
||||
|
||||
resetAll: function(node) {
|
||||
if (isInsertionPoint(node))
|
||||
resetDistributedNodes(node);
|
||||
else
|
||||
resetDestinationInsertionPoints(node);
|
||||
|
||||
this.resetAllSubtrees(node);
|
||||
},
|
||||
|
||||
resetAllSubtrees: function(node) {
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
this.resetAll(child);
|
||||
}
|
||||
|
||||
if (node.shadowRoot)
|
||||
this.resetAll(node.shadowRoot);
|
||||
|
||||
if (node.olderShadowRoot)
|
||||
this.resetAll(node.olderShadowRoot);
|
||||
},
|
||||
|
||||
// http://w3c.github.io/webcomponents/spec/shadow/#distribution-results
|
||||
distributionResolution: function(node) {
|
||||
if (isShadowHost(node)) {
|
||||
var shadowHost = node;
|
||||
// 1.1
|
||||
var pool = poolPopulation(shadowHost);
|
||||
|
||||
var shadowTrees = getShadowTrees(shadowHost);
|
||||
|
||||
// 1.2
|
||||
for (var i = 0; i < shadowTrees.length; i++) {
|
||||
// 1.2.1
|
||||
this.poolDistribution(shadowTrees[i], pool);
|
||||
}
|
||||
|
||||
// 1.3
|
||||
for (var i = shadowTrees.length - 1; i >= 0; i--) {
|
||||
var shadowTree = shadowTrees[i];
|
||||
|
||||
// 1.3.1
|
||||
// TODO(arv): We should keep the shadow insertion points on the
|
||||
// shadow root (or renderer) so we don't have to search the tree
|
||||
// every time.
|
||||
var shadow = getShadowInsertionPoint(shadowTree);
|
||||
|
||||
// 1.3.2
|
||||
if (shadow) {
|
||||
|
||||
// 1.3.2.1
|
||||
var olderShadowRoot = shadowTree.olderShadowRoot;
|
||||
if (olderShadowRoot) {
|
||||
// 1.3.2.1.1
|
||||
pool = poolPopulation(olderShadowRoot);
|
||||
}
|
||||
|
||||
// 1.3.2.2
|
||||
for (var j = 0; j < pool.length; j++) {
|
||||
// 1.3.2.2.1
|
||||
destributeNodeInto(pool[j], shadow);
|
||||
}
|
||||
}
|
||||
|
||||
// 1.3.3
|
||||
this.distributionResolution(shadowTree);
|
||||
}
|
||||
}
|
||||
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
this.distributionResolution(child);
|
||||
}
|
||||
},
|
||||
|
||||
// http://w3c.github.io/webcomponents/spec/shadow/#dfn-pool-distribution-algorithm
|
||||
poolDistribution: function (node, pool) {
|
||||
if (node instanceof HTMLShadowElement)
|
||||
return;
|
||||
|
||||
if (node instanceof HTMLContentElement) {
|
||||
var content = node;
|
||||
this.updateDependentAttributes(content.getAttribute('select'));
|
||||
|
||||
var anyDistributed = false;
|
||||
|
||||
// 1.1
|
||||
for (var i = 0; i < pool.length; i++) {
|
||||
var node = pool[i];
|
||||
if (!node)
|
||||
continue;
|
||||
if (matches(node, content)) {
|
||||
destributeNodeInto(node, content);
|
||||
pool[i] = undefined;
|
||||
anyDistributed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 1.2
|
||||
// Fallback content
|
||||
if (!anyDistributed) {
|
||||
for (var child = content.firstChild;
|
||||
child;
|
||||
child = child.nextSibling) {
|
||||
destributeNodeInto(child, content);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
this.poolDistribution(child, pool);
|
||||
}
|
||||
},
|
||||
|
||||
buildRenderTree: function(renderNode, node) {
|
||||
var children = this.compose(node);
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
var childRenderNode = renderNode.append(child);
|
||||
this.buildRenderTree(childRenderNode, child);
|
||||
}
|
||||
|
||||
if (isShadowHost(node)) {
|
||||
var renderer = getRendererForHost(node);
|
||||
renderer.dirty = false;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
compose: function(node) {
|
||||
var children = [];
|
||||
var p = node.shadowRoot || node;
|
||||
for (var child = p.firstChild; child; child = child.nextSibling) {
|
||||
if (isInsertionPoint(child)) {
|
||||
this.associateNode(p);
|
||||
var distributedNodes = getDistributedNodes(child);
|
||||
for (var j = 0; j < distributedNodes.length; j++) {
|
||||
var distributedNode = distributedNodes[j];
|
||||
if (isFinalDestination(child, distributedNode))
|
||||
children.push(distributedNode);
|
||||
}
|
||||
} else {
|
||||
children.push(child);
|
||||
}
|
||||
}
|
||||
return children;
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidates the attributes used to keep track of which attributes may
|
||||
* cause the renderer to be invalidated.
|
||||
*/
|
||||
invalidateAttributes: function() {
|
||||
this.attributes = Object.create(null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Parses the selector and makes this renderer dependent on the attribute
|
||||
* being used in the selector.
|
||||
* @param {string} selector
|
||||
*/
|
||||
updateDependentAttributes: function(selector) {
|
||||
if (!selector)
|
||||
return;
|
||||
|
||||
var attributes = this.attributes;
|
||||
|
||||
// .class
|
||||
if (/\.\w+/.test(selector))
|
||||
attributes['class'] = true;
|
||||
|
||||
// #id
|
||||
if (/#\w+/.test(selector))
|
||||
attributes['id'] = true;
|
||||
|
||||
selector.replace(/\[\s*([^\s=\|~\]]+)/g, function(_, name) {
|
||||
attributes[name] = true;
|
||||
});
|
||||
|
||||
// Pseudo selectors have been removed from the spec.
|
||||
},
|
||||
|
||||
dependsOnAttribute: function(name) {
|
||||
return this.attributes[name];
|
||||
},
|
||||
|
||||
associateNode: function(node) {
|
||||
unsafeUnwrap(node).polymerShadowRenderer_ = this;
|
||||
}
|
||||
};
|
||||
|
||||
// http://w3c.github.io/webcomponents/spec/shadow/#dfn-pool-population-algorithm
|
||||
function poolPopulation(node) {
|
||||
var pool = [];
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
if (isInsertionPoint(child)) {
|
||||
pool.push.apply(pool, getDistributedNodes(child));
|
||||
} else {
|
||||
pool.push(child);
|
||||
}
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
function getShadowInsertionPoint(node) {
|
||||
if (node instanceof HTMLShadowElement)
|
||||
return node;
|
||||
if (node instanceof HTMLContentElement)
|
||||
return null;
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
var res = getShadowInsertionPoint(child);
|
||||
if (res)
|
||||
return res;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function destributeNodeInto(child, insertionPoint) {
|
||||
getDistributedNodes(insertionPoint).push(child);
|
||||
var points = destinationInsertionPointsTable.get(child);
|
||||
if (!points)
|
||||
destinationInsertionPointsTable.set(child, [insertionPoint]);
|
||||
else
|
||||
points.push(insertionPoint);
|
||||
}
|
||||
|
||||
function getDestinationInsertionPoints(node) {
|
||||
return destinationInsertionPointsTable.get(node);
|
||||
}
|
||||
|
||||
function resetDestinationInsertionPoints(node) {
|
||||
// IE11 crashes when delete is used.
|
||||
destinationInsertionPointsTable.set(node, undefined);
|
||||
}
|
||||
|
||||
// AllowedSelectors :
|
||||
// TypeSelector
|
||||
// *
|
||||
// ClassSelector
|
||||
// IDSelector
|
||||
// AttributeSelector
|
||||
// negation
|
||||
var selectorStartCharRe = /^(:not\()?[*.#[a-zA-Z_|]/;
|
||||
|
||||
function matches(node, contentElement) {
|
||||
var select = contentElement.getAttribute('select');
|
||||
if (!select)
|
||||
return true;
|
||||
|
||||
// Here we know the select attribute is a non empty string.
|
||||
select = select.trim();
|
||||
if (!select)
|
||||
return true;
|
||||
|
||||
if (!(node instanceof Element))
|
||||
return false;
|
||||
|
||||
if (!selectorStartCharRe.test(select))
|
||||
return false;
|
||||
|
||||
try {
|
||||
return node.matches(select);
|
||||
} catch (ex) {
|
||||
// Invalid selector.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isFinalDestination(insertionPoint, node) {
|
||||
var points = getDestinationInsertionPoints(node);
|
||||
return points && points[points.length - 1] === insertionPoint;
|
||||
}
|
||||
|
||||
function isInsertionPoint(node) {
|
||||
return node instanceof HTMLContentElement ||
|
||||
node instanceof HTMLShadowElement;
|
||||
}
|
||||
|
||||
function isShadowHost(shadowHost) {
|
||||
return shadowHost.shadowRoot;
|
||||
}
|
||||
|
||||
// Returns the shadow trees as an array, with the youngest tree at the
|
||||
// beginning of the array.
|
||||
function getShadowTrees(host) {
|
||||
var trees = [];
|
||||
|
||||
for (var tree = host.shadowRoot; tree; tree = tree.olderShadowRoot) {
|
||||
trees.push(tree);
|
||||
}
|
||||
return trees;
|
||||
}
|
||||
|
||||
function render(host) {
|
||||
new ShadowRenderer(host).render();
|
||||
};
|
||||
|
||||
// Need to rerender shadow host when:
|
||||
//
|
||||
// - a direct child to the ShadowRoot is added or removed
|
||||
// - a direct child to the host is added or removed
|
||||
// - a new shadow root is created
|
||||
// - a direct child to a content/shadow element is added or removed
|
||||
// - a sibling to a content/shadow element is added or removed
|
||||
// - content[select] is changed
|
||||
// - an attribute in a direct child to a host is modified
|
||||
|
||||
/**
|
||||
* This gets called when a node was added or removed to it.
|
||||
*/
|
||||
Node.prototype.invalidateShadowRenderer = function(force) {
|
||||
var renderer = unsafeUnwrap(this).polymerShadowRenderer_;
|
||||
if (renderer) {
|
||||
renderer.invalidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
HTMLContentElement.prototype.getDistributedNodes =
|
||||
HTMLShadowElement.prototype.getDistributedNodes = function() {
|
||||
// TODO(arv): We should only rerender the dirty ancestor renderers (from
|
||||
// the root and down).
|
||||
renderAllPending();
|
||||
return getDistributedNodes(this);
|
||||
};
|
||||
|
||||
Element.prototype.getDestinationInsertionPoints = function() {
|
||||
renderAllPending();
|
||||
return getDestinationInsertionPoints(this) || [];
|
||||
};
|
||||
|
||||
HTMLContentElement.prototype.nodeIsInserted_ =
|
||||
HTMLShadowElement.prototype.nodeIsInserted_ = function() {
|
||||
// Invalidate old renderer if any.
|
||||
this.invalidateShadowRenderer();
|
||||
|
||||
var shadowRoot = getShadowRootAncestor(this);
|
||||
var renderer;
|
||||
if (shadowRoot)
|
||||
renderer = getRendererForShadowRoot(shadowRoot);
|
||||
unsafeUnwrap(this).polymerShadowRenderer_ = renderer;
|
||||
if (renderer)
|
||||
renderer.invalidate();
|
||||
};
|
||||
|
||||
scope.getRendererForHost = getRendererForHost;
|
||||
scope.getShadowTrees = getShadowTrees;
|
||||
scope.renderAllPending = renderAllPending;
|
||||
|
||||
scope.getDestinationInsertionPoints = getDestinationInsertionPoints;
|
||||
|
||||
// Exposed for testing
|
||||
scope.visual = {
|
||||
insertBefore: insertBefore,
|
||||
remove: remove,
|
||||
};
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
81
src/ShadowDOM/TreeScope.js
Normal file
81
src/ShadowDOM/TreeScope.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A tree scope represents the root of a tree. All nodes in a tree point to
|
||||
* the same TreeScope object. The tree scope of a node get set the first time
|
||||
* it is accessed or when a node is added or remove to a tree.
|
||||
*
|
||||
* The root is a Node that has no parent.
|
||||
*
|
||||
* The parent is another TreeScope. For ShadowRoots, it is the TreeScope of
|
||||
* the host of the ShadowRoot.
|
||||
*
|
||||
* @param {!Node} root
|
||||
* @param {TreeScope} parent
|
||||
* @constructor
|
||||
*/
|
||||
function TreeScope(root, parent) {
|
||||
/** @type {!Node} */
|
||||
this.root = root;
|
||||
|
||||
/** @type {TreeScope} */
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
TreeScope.prototype = {
|
||||
get renderer() {
|
||||
if (this.root instanceof scope.wrappers.ShadowRoot) {
|
||||
return scope.getRendererForHost(this.root.host);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
contains: function(treeScope) {
|
||||
for (; treeScope; treeScope = treeScope.parent) {
|
||||
if (treeScope === this)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
function setTreeScope(node, treeScope) {
|
||||
if (node.treeScope_ !== treeScope) {
|
||||
node.treeScope_ = treeScope;
|
||||
for (var sr = node.shadowRoot; sr; sr = sr.olderShadowRoot) {
|
||||
sr.treeScope_.parent = treeScope;
|
||||
}
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
setTreeScope(child, treeScope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getTreeScope(node) {
|
||||
if (node instanceof scope.wrappers.Window) {
|
||||
debugger;
|
||||
}
|
||||
|
||||
if (node.treeScope_)
|
||||
return node.treeScope_;
|
||||
var parent = node.parentNode;
|
||||
var treeScope;
|
||||
if (parent)
|
||||
treeScope = getTreeScope(parent);
|
||||
else
|
||||
treeScope = new TreeScope(node, null);
|
||||
return node.treeScope_ = treeScope;
|
||||
}
|
||||
|
||||
scope.TreeScope = TreeScope;
|
||||
scope.getTreeScope = getTreeScope;
|
||||
scope.setTreeScope = setTreeScope;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
49
src/ShadowDOM/microtask.js
Normal file
49
src/ShadowDOM/microtask.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2013 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(context) {
|
||||
'use strict';
|
||||
|
||||
var OriginalMutationObserver = window.MutationObserver;
|
||||
var callbacks = [];
|
||||
var pending = false;
|
||||
var timerFunc;
|
||||
|
||||
function handle() {
|
||||
pending = false;
|
||||
var copies = callbacks.slice(0);
|
||||
callbacks = [];
|
||||
for (var i = 0; i < copies.length; i++) {
|
||||
(0, copies[i])();
|
||||
}
|
||||
}
|
||||
|
||||
if (OriginalMutationObserver) {
|
||||
var counter = 1;
|
||||
var observer = new OriginalMutationObserver(handle);
|
||||
var textNode = document.createTextNode(counter);
|
||||
observer.observe(textNode, {characterData: true});
|
||||
|
||||
timerFunc = function() {
|
||||
counter = (counter + 1) % 2;
|
||||
textNode.data = counter;
|
||||
};
|
||||
|
||||
} else {
|
||||
timerFunc = window.setImmediate || window.setTimeout;
|
||||
}
|
||||
|
||||
function setEndOfMicrotask(func) {
|
||||
callbacks.push(func);
|
||||
if (pending)
|
||||
return;
|
||||
pending = true;
|
||||
timerFunc(handle, 0);
|
||||
}
|
||||
|
||||
context.setEndOfMicrotask = setEndOfMicrotask;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
270
src/ShadowDOM/querySelector.js
Normal file
270
src/ShadowDOM/querySelector.js
Normal file
@@ -0,0 +1,270 @@
|
||||
// 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) {
|
||||
'use strict';
|
||||
|
||||
var HTMLCollection = scope.wrappers.HTMLCollection;
|
||||
var NodeList = scope.wrappers.NodeList;
|
||||
var getTreeScope = scope.getTreeScope;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var originalDocumentQuerySelector = document.querySelector;
|
||||
var originalElementQuerySelector = document.documentElement.querySelector;
|
||||
|
||||
var originalDocumentQuerySelectorAll = document.querySelectorAll;
|
||||
var originalElementQuerySelectorAll = document.documentElement.querySelectorAll;
|
||||
|
||||
var originalDocumentGetElementsByTagName = document.getElementsByTagName;
|
||||
var originalElementGetElementsByTagName = document.documentElement.getElementsByTagName;
|
||||
|
||||
var originalDocumentGetElementsByTagNameNS = document.getElementsByTagNameNS;
|
||||
var originalElementGetElementsByTagNameNS = document.documentElement.getElementsByTagNameNS;
|
||||
|
||||
var OriginalElement = window.Element;
|
||||
var OriginalDocument = window.HTMLDocument || window.Document;
|
||||
|
||||
function filterNodeList(list, index, result, deep) {
|
||||
var wrappedItem = null;
|
||||
var root = null;
|
||||
for (var i = 0, length = list.length; i < length; i++) {
|
||||
wrappedItem = wrap(list[i]);
|
||||
if (!deep && (root = getTreeScope(wrappedItem).root)) {
|
||||
if (root instanceof scope.wrappers.ShadowRoot) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result[index++] = wrappedItem;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
function shimSelector(selector) {
|
||||
return String(selector).replace(/\/deep\//g, ' ');
|
||||
}
|
||||
|
||||
function findOne(node, selector) {
|
||||
var m, el = node.firstElementChild;
|
||||
while (el) {
|
||||
if (el.matches(selector))
|
||||
return el;
|
||||
m = findOne(el, selector);
|
||||
if (m)
|
||||
return m;
|
||||
el = el.nextElementSibling;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function matchesSelector(el, selector) {
|
||||
return el.matches(selector);
|
||||
}
|
||||
|
||||
var XHTML_NS = 'http://www.w3.org/1999/xhtml';
|
||||
|
||||
function matchesTagName(el, localName, localNameLowerCase) {
|
||||
var ln = el.localName;
|
||||
return ln === localName ||
|
||||
ln === localNameLowerCase && el.namespaceURI === XHTML_NS;
|
||||
}
|
||||
|
||||
function matchesEveryThing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function matchesLocalNameOnly(el, ns, localName) {
|
||||
return el.localName === localName;
|
||||
}
|
||||
|
||||
function matchesNameSpace(el, ns) {
|
||||
return el.namespaceURI === ns;
|
||||
}
|
||||
|
||||
function matchesLocalNameNS(el, ns, localName) {
|
||||
return el.namespaceURI === ns && el.localName === localName;
|
||||
}
|
||||
|
||||
function findElements(node, index, result, p, arg0, arg1) {
|
||||
var el = node.firstElementChild;
|
||||
while (el) {
|
||||
if (p(el, arg0, arg1))
|
||||
result[index++] = el;
|
||||
index = findElements(el, index, result, p, arg0, arg1);
|
||||
el = el.nextElementSibling;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
// find and findAll will only match Simple Selectors,
|
||||
// Structural Pseudo Classes are not guarenteed to be correct
|
||||
// http://www.w3.org/TR/css3-selectors/#simple-selectors
|
||||
|
||||
function querySelectorAllFiltered(p, index, result, selector, deep) {
|
||||
var target = unsafeUnwrap(this);
|
||||
var list;
|
||||
var root = getTreeScope(this).root;
|
||||
if (root instanceof scope.wrappers.ShadowRoot) {
|
||||
// We are in the shadow tree and the logical tree is
|
||||
// going to be disconnected so we do a manual tree traversal
|
||||
return findElements(this, index, result, p, selector, null);
|
||||
} else if (target instanceof OriginalElement) {
|
||||
list = originalElementQuerySelectorAll.call(target, selector);
|
||||
} else if (target instanceof OriginalDocument) {
|
||||
list = originalDocumentQuerySelectorAll.call(target, selector);
|
||||
} else {
|
||||
// When we get a ShadowRoot the logical tree is going to be disconnected
|
||||
// so we do a manual tree traversal
|
||||
return findElements(this, index, result, p, selector, null);
|
||||
}
|
||||
|
||||
return filterNodeList(list, index, result, deep);
|
||||
}
|
||||
|
||||
var SelectorsInterface = {
|
||||
querySelector: function(selector) {
|
||||
var shimmed = shimSelector(selector);
|
||||
var deep = shimmed !== selector;
|
||||
selector = shimmed;
|
||||
|
||||
var target = unsafeUnwrap(this);
|
||||
var wrappedItem;
|
||||
var root = getTreeScope(this).root;
|
||||
if (root instanceof scope.wrappers.ShadowRoot) {
|
||||
// We are in the shadow tree and the logical tree is
|
||||
// going to be disconnected so we do a manual tree traversal
|
||||
return findOne(this, selector);
|
||||
} else if (target instanceof OriginalElement) {
|
||||
wrappedItem = wrap(originalElementQuerySelector.call(target, selector));
|
||||
} else if (target instanceof OriginalDocument) {
|
||||
wrappedItem = wrap(originalDocumentQuerySelector.call(target, selector));
|
||||
} else {
|
||||
// When we get a ShadowRoot the logical tree is going to be disconnected
|
||||
// so we do a manual tree traversal
|
||||
return findOne(this, selector);
|
||||
}
|
||||
|
||||
if (!wrappedItem) {
|
||||
// When the original query returns nothing
|
||||
// we return nothing (to be consistent with the other wrapped calls)
|
||||
return wrappedItem;
|
||||
} else if (!deep && (root = getTreeScope(wrappedItem).root)) {
|
||||
if (root instanceof scope.wrappers.ShadowRoot) {
|
||||
// When the original query returns an element in the ShadowDOM
|
||||
// we must do a manual tree traversal
|
||||
return findOne(this, selector);
|
||||
}
|
||||
}
|
||||
|
||||
return wrappedItem;
|
||||
},
|
||||
querySelectorAll: function(selector) {
|
||||
var shimmed = shimSelector(selector);
|
||||
var deep = shimmed !== selector;
|
||||
selector = shimmed;
|
||||
|
||||
var result = new NodeList();
|
||||
|
||||
result.length = querySelectorAllFiltered.call(this,
|
||||
matchesSelector,
|
||||
0,
|
||||
result,
|
||||
selector,
|
||||
deep);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
function getElementsByTagNameFiltered(p, index, result, localName,
|
||||
lowercase) {
|
||||
var target = unsafeUnwrap(this);
|
||||
var list;
|
||||
var root = getTreeScope(this).root;
|
||||
if (root instanceof scope.wrappers.ShadowRoot) {
|
||||
// We are in the shadow tree and the logical tree is
|
||||
// going to be disconnected so we do a manual tree traversal
|
||||
return findElements(this, index, result, p, localName, lowercase);
|
||||
} else if (target instanceof OriginalElement) {
|
||||
list = originalElementGetElementsByTagName.call(target, localName,
|
||||
lowercase);
|
||||
} else if (target instanceof OriginalDocument) {
|
||||
list = originalDocumentGetElementsByTagName.call(target, localName,
|
||||
lowercase);
|
||||
} else {
|
||||
// When we get a ShadowRoot the logical tree is going to be disconnected
|
||||
// so we do a manual tree traversal
|
||||
return findElements(this, index, result, p, localName, lowercase);
|
||||
}
|
||||
|
||||
return filterNodeList(list, index, result, false);
|
||||
}
|
||||
|
||||
function getElementsByTagNameNSFiltered(p, index, result, ns, localName) {
|
||||
var target = unsafeUnwrap(this);
|
||||
var list;
|
||||
var root = getTreeScope(this).root;
|
||||
if (root instanceof scope.wrappers.ShadowRoot) {
|
||||
// We are in the shadow tree and the logical tree is
|
||||
// going to be disconnected so we do a manual tree traversal
|
||||
return findElements(this, index, result, p, ns, localName);
|
||||
} else if (target instanceof OriginalElement) {
|
||||
list = originalElementGetElementsByTagNameNS.call(target, ns, localName);
|
||||
} else if (target instanceof OriginalDocument) {
|
||||
list = originalDocumentGetElementsByTagNameNS.call(target, ns, localName);
|
||||
} else {
|
||||
// When we get a ShadowRoot the logical tree is going to be disconnected
|
||||
// so we do a manual tree traversal
|
||||
return findElements(this, index, result, p, ns, localName);
|
||||
}
|
||||
|
||||
return filterNodeList(list, index, result, false);
|
||||
}
|
||||
|
||||
var GetElementsByInterface = {
|
||||
getElementsByTagName: function(localName) {
|
||||
var result = new HTMLCollection();
|
||||
var match = localName === '*' ? matchesEveryThing : matchesTagName;
|
||||
|
||||
result.length = getElementsByTagNameFiltered.call(this,
|
||||
match,
|
||||
0,
|
||||
result,
|
||||
localName,
|
||||
localName.toLowerCase());
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
getElementsByClassName: function(className) {
|
||||
// TODO(arv): Check className?
|
||||
return this.querySelectorAll('.' + className);
|
||||
},
|
||||
|
||||
getElementsByTagNameNS: function(ns, localName) {
|
||||
var result = new HTMLCollection();
|
||||
var match = null;
|
||||
|
||||
if (ns === '*') {
|
||||
match = localName === '*' ? matchesEveryThing : matchesLocalNameOnly;
|
||||
} else {
|
||||
match = localName === '*' ? matchesNameSpace : matchesLocalNameNS;
|
||||
}
|
||||
|
||||
result.length = getElementsByTagNameNSFiltered.call(this,
|
||||
match,
|
||||
0,
|
||||
result,
|
||||
ns || null,
|
||||
localName);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
scope.GetElementsByInterface = GetElementsByInterface;
|
||||
scope.SelectorsInterface = SelectorsInterface;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
425
src/ShadowDOM/wrappers.js
Normal file
425
src/ShadowDOM/wrappers.js
Normal file
@@ -0,0 +1,425 @@
|
||||
// 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.
|
||||
|
||||
window.ShadowDOMPolyfill = {};
|
||||
|
||||
(function(scope) {
|
||||
'use strict';
|
||||
|
||||
var constructorTable = new WeakMap();
|
||||
var nativePrototypeTable = new WeakMap();
|
||||
var wrappers = Object.create(null);
|
||||
|
||||
function detectEval() {
|
||||
// Don't test for eval if we're running in a Chrome App environment.
|
||||
// We check for APIs set that only exist in a Chrome App context.
|
||||
if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Firefox OS Apps do not allow eval. This feature detection is very hacky
|
||||
// but even if some other platform adds support for this function this code
|
||||
// will continue to work.
|
||||
if (navigator.getDeviceStorage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var f = new Function('return true;');
|
||||
return f();
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var hasEval = detectEval();
|
||||
|
||||
function assert(b) {
|
||||
if (!b)
|
||||
throw new Error('Assertion failed');
|
||||
};
|
||||
|
||||
var defineProperty = Object.defineProperty;
|
||||
var getOwnPropertyNames = Object.getOwnPropertyNames;
|
||||
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
||||
|
||||
function mixin(to, from) {
|
||||
var names = getOwnPropertyNames(from);
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
defineProperty(to, name, getOwnPropertyDescriptor(from, name));
|
||||
}
|
||||
return to;
|
||||
};
|
||||
|
||||
function mixinStatics(to, from) {
|
||||
var names = getOwnPropertyNames(from);
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
switch (name) {
|
||||
case 'arguments':
|
||||
case 'caller':
|
||||
case 'length':
|
||||
case 'name':
|
||||
case 'prototype':
|
||||
case 'toString':
|
||||
continue;
|
||||
}
|
||||
defineProperty(to, name, getOwnPropertyDescriptor(from, name));
|
||||
}
|
||||
return to;
|
||||
};
|
||||
|
||||
function oneOf(object, propertyNames) {
|
||||
for (var i = 0; i < propertyNames.length; i++) {
|
||||
if (propertyNames[i] in object)
|
||||
return propertyNames[i];
|
||||
}
|
||||
}
|
||||
|
||||
var nonEnumerableDataDescriptor = {
|
||||
value: undefined,
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true
|
||||
};
|
||||
|
||||
function defineNonEnumerableDataProperty(object, name, value) {
|
||||
nonEnumerableDataDescriptor.value = value;
|
||||
defineProperty(object, name, nonEnumerableDataDescriptor);
|
||||
}
|
||||
|
||||
// Mozilla's old DOM bindings are bretty busted:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=855844
|
||||
// Make sure they are create before we start modifying things.
|
||||
getOwnPropertyNames(window);
|
||||
|
||||
function getWrapperConstructor(node) {
|
||||
var nativePrototype = node.__proto__ || Object.getPrototypeOf(node);
|
||||
var wrapperConstructor = constructorTable.get(nativePrototype);
|
||||
if (wrapperConstructor)
|
||||
return wrapperConstructor;
|
||||
|
||||
var parentWrapperConstructor = getWrapperConstructor(nativePrototype);
|
||||
|
||||
var GeneratedWrapper = createWrapperConstructor(parentWrapperConstructor);
|
||||
registerInternal(nativePrototype, GeneratedWrapper, node);
|
||||
|
||||
return GeneratedWrapper;
|
||||
}
|
||||
|
||||
function addForwardingProperties(nativePrototype, wrapperPrototype) {
|
||||
installProperty(nativePrototype, wrapperPrototype, true);
|
||||
}
|
||||
|
||||
function registerInstanceProperties(wrapperPrototype, instanceObject) {
|
||||
installProperty(instanceObject, wrapperPrototype, false);
|
||||
}
|
||||
|
||||
var isFirefox = /Firefox/.test(navigator.userAgent);
|
||||
|
||||
// This is used as a fallback when getting the descriptor fails in
|
||||
// installProperty.
|
||||
var dummyDescriptor = {
|
||||
get: function() {},
|
||||
set: function(v) {},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
};
|
||||
|
||||
function isEventHandlerName(name) {
|
||||
return /^on[a-z]+$/.test(name);
|
||||
}
|
||||
|
||||
function isIdentifierName(name) {
|
||||
return /^\w[a-zA-Z_0-9]*$/.test(name);
|
||||
}
|
||||
|
||||
// The name of the implementation property is intentionally hard to
|
||||
// remember. Unfortunately, browsers are slower doing obj[expr] than
|
||||
// obj.foo so we resort to repeat this ugly name. This ugly name is never
|
||||
// used outside of this file though.
|
||||
|
||||
function getGetter(name) {
|
||||
return hasEval && isIdentifierName(name) ?
|
||||
new Function('return this.__impl4cf1e782hg__.' + name) :
|
||||
function() { return this.__impl4cf1e782hg__[name]; };
|
||||
}
|
||||
|
||||
function getSetter(name) {
|
||||
return hasEval && isIdentifierName(name) ?
|
||||
new Function('v', 'this.__impl4cf1e782hg__.' + name + ' = v') :
|
||||
function(v) { this.__impl4cf1e782hg__[name] = v; };
|
||||
}
|
||||
|
||||
function getMethod(name) {
|
||||
return hasEval && isIdentifierName(name) ?
|
||||
new Function('return this.__impl4cf1e782hg__.' + name +
|
||||
'.apply(this.__impl4cf1e782hg__, arguments)') :
|
||||
function() {
|
||||
return this.__impl4cf1e782hg__[name].apply(
|
||||
this.__impl4cf1e782hg__, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
function getDescriptor(source, name) {
|
||||
try {
|
||||
return Object.getOwnPropertyDescriptor(source, name);
|
||||
} catch (ex) {
|
||||
// JSC and V8 both use data properties instead of accessors which can
|
||||
// cause getting the property desciptor to throw an exception.
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=49739
|
||||
return dummyDescriptor;
|
||||
}
|
||||
}
|
||||
|
||||
// Safari 8 exposes WebIDL attributes as an invalid accessor property. Its
|
||||
// descriptor has {get: undefined, set: undefined}. We therefore ignore the
|
||||
// shape of the descriptor and make all properties read-write.
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=49739
|
||||
var isBrokenSafari = function() {
|
||||
var descr = Object.getOwnPropertyDescriptor(Node.prototype, 'nodeType');
|
||||
return descr && !descr.get && !descr.set;
|
||||
}();
|
||||
|
||||
function installProperty(source, target, allowMethod, opt_blacklist) {
|
||||
var names = getOwnPropertyNames(source);
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
if (name === 'polymerBlackList_')
|
||||
continue;
|
||||
|
||||
if (name in target)
|
||||
continue;
|
||||
|
||||
if (source.polymerBlackList_ && source.polymerBlackList_[name])
|
||||
continue;
|
||||
|
||||
if (isFirefox) {
|
||||
// Tickle Firefox's old bindings.
|
||||
source.__lookupGetter__(name);
|
||||
}
|
||||
var descriptor = getDescriptor(source, name);
|
||||
var getter, setter;
|
||||
if (allowMethod && typeof descriptor.value === 'function') {
|
||||
target[name] = getMethod(name);
|
||||
continue;
|
||||
}
|
||||
|
||||
var isEvent = isEventHandlerName(name);
|
||||
if (isEvent)
|
||||
getter = scope.getEventHandlerGetter(name);
|
||||
else
|
||||
getter = getGetter(name);
|
||||
|
||||
if (descriptor.writable || descriptor.set || isBrokenSafari) {
|
||||
if (isEvent)
|
||||
setter = scope.getEventHandlerSetter(name);
|
||||
else
|
||||
setter = getSetter(name);
|
||||
}
|
||||
|
||||
defineProperty(target, name, {
|
||||
get: getter,
|
||||
set: setter,
|
||||
configurable: descriptor.configurable,
|
||||
enumerable: descriptor.enumerable
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Function} nativeConstructor
|
||||
* @param {Function} wrapperConstructor
|
||||
* @param {Object=} opt_instance If present, this is used to extract
|
||||
* properties from an instance object.
|
||||
*/
|
||||
function register(nativeConstructor, wrapperConstructor, opt_instance) {
|
||||
var nativePrototype = nativeConstructor.prototype;
|
||||
registerInternal(nativePrototype, wrapperConstructor, opt_instance);
|
||||
mixinStatics(wrapperConstructor, nativeConstructor);
|
||||
}
|
||||
|
||||
function registerInternal(nativePrototype, wrapperConstructor, opt_instance) {
|
||||
var wrapperPrototype = wrapperConstructor.prototype;
|
||||
assert(constructorTable.get(nativePrototype) === undefined);
|
||||
|
||||
constructorTable.set(nativePrototype, wrapperConstructor);
|
||||
nativePrototypeTable.set(wrapperPrototype, nativePrototype);
|
||||
|
||||
addForwardingProperties(nativePrototype, wrapperPrototype);
|
||||
if (opt_instance)
|
||||
registerInstanceProperties(wrapperPrototype, opt_instance);
|
||||
|
||||
defineNonEnumerableDataProperty(
|
||||
wrapperPrototype, 'constructor', wrapperConstructor);
|
||||
// Set it again. Some VMs optimizes objects that are used as prototypes.
|
||||
wrapperConstructor.prototype = wrapperPrototype;
|
||||
}
|
||||
|
||||
function isWrapperFor(wrapperConstructor, nativeConstructor) {
|
||||
return constructorTable.get(nativeConstructor.prototype) ===
|
||||
wrapperConstructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a generic wrapper constructor based on |object| and its
|
||||
* constructor.
|
||||
* @param {Node} object
|
||||
* @return {Function} The generated constructor.
|
||||
*/
|
||||
function registerObject(object) {
|
||||
var nativePrototype = Object.getPrototypeOf(object);
|
||||
|
||||
var superWrapperConstructor = getWrapperConstructor(nativePrototype);
|
||||
var GeneratedWrapper = createWrapperConstructor(superWrapperConstructor);
|
||||
registerInternal(nativePrototype, GeneratedWrapper, object);
|
||||
|
||||
return GeneratedWrapper;
|
||||
}
|
||||
|
||||
function createWrapperConstructor(superWrapperConstructor) {
|
||||
function GeneratedWrapper(node) {
|
||||
superWrapperConstructor.call(this, node);
|
||||
}
|
||||
var p = Object.create(superWrapperConstructor.prototype);
|
||||
p.constructor = GeneratedWrapper;
|
||||
GeneratedWrapper.prototype = p;
|
||||
|
||||
return GeneratedWrapper;
|
||||
}
|
||||
|
||||
function isWrapper(object) {
|
||||
return object && object.__impl4cf1e782hg__;
|
||||
}
|
||||
|
||||
function isNative(object) {
|
||||
return !isWrapper(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a node in a WrapperNode. If there already exists a wrapper for the
|
||||
* |node| that wrapper is returned instead.
|
||||
* @param {Node} node
|
||||
* @return {WrapperNode}
|
||||
*/
|
||||
function wrap(impl) {
|
||||
if (impl === null)
|
||||
return null;
|
||||
|
||||
assert(isNative(impl));
|
||||
return impl.__wrapper8e3dd93a60__ ||
|
||||
(impl.__wrapper8e3dd93a60__ = new (getWrapperConstructor(impl))(impl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps a wrapper and returns the node it is wrapping.
|
||||
* @param {WrapperNode} wrapper
|
||||
* @return {Node}
|
||||
*/
|
||||
function unwrap(wrapper) {
|
||||
if (wrapper === null)
|
||||
return null;
|
||||
assert(isWrapper(wrapper));
|
||||
return wrapper.__impl4cf1e782hg__;
|
||||
}
|
||||
|
||||
function unsafeUnwrap(wrapper) {
|
||||
return wrapper.__impl4cf1e782hg__;
|
||||
}
|
||||
|
||||
function setWrapper(impl, wrapper) {
|
||||
wrapper.__impl4cf1e782hg__ = impl;
|
||||
impl.__wrapper8e3dd93a60__ = wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps object if it is a wrapper.
|
||||
* @param {Object} object
|
||||
* @return {Object} The native implementation object.
|
||||
*/
|
||||
function unwrapIfNeeded(object) {
|
||||
return object && isWrapper(object) ? unwrap(object) : object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps object if it is not a wrapper.
|
||||
* @param {Object} object
|
||||
* @return {Object} The wrapper for object.
|
||||
*/
|
||||
function wrapIfNeeded(object) {
|
||||
return object && !isWrapper(object) ? wrap(object) : object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the current wrapper (if any) for node.
|
||||
* @param {Node} node
|
||||
* @param {WrapperNode=} wrapper If left out the wrapper will be created as
|
||||
* needed next time someone wraps the node.
|
||||
*/
|
||||
function rewrap(node, wrapper) {
|
||||
if (wrapper === null)
|
||||
return;
|
||||
assert(isNative(node));
|
||||
assert(wrapper === undefined || isWrapper(wrapper));
|
||||
node.__wrapper8e3dd93a60__ = wrapper;
|
||||
}
|
||||
|
||||
var getterDescriptor = {
|
||||
get: undefined,
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
};
|
||||
|
||||
function defineGetter(constructor, name, getter) {
|
||||
getterDescriptor.get = getter;
|
||||
defineProperty(constructor.prototype, name, getterDescriptor);
|
||||
}
|
||||
|
||||
function defineWrapGetter(constructor, name) {
|
||||
defineGetter(constructor, name, function() {
|
||||
return wrap(this.__impl4cf1e782hg__[name]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards existing methods on the native object to the wrapper methods.
|
||||
* This does not wrap any of the arguments or the return value since the
|
||||
* wrapper implementation already takes care of that.
|
||||
* @param {Array.<Function>} constructors
|
||||
* @parem {Array.<string>} names
|
||||
*/
|
||||
function forwardMethodsToWrapper(constructors, names) {
|
||||
constructors.forEach(function(constructor) {
|
||||
names.forEach(function(name) {
|
||||
constructor.prototype[name] = function() {
|
||||
var w = wrapIfNeeded(this);
|
||||
return w[name].apply(w, arguments);
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
scope.assert = assert;
|
||||
scope.constructorTable = constructorTable;
|
||||
scope.defineGetter = defineGetter;
|
||||
scope.defineWrapGetter = defineWrapGetter;
|
||||
scope.forwardMethodsToWrapper = forwardMethodsToWrapper;
|
||||
scope.isWrapper = isWrapper;
|
||||
scope.isWrapperFor = isWrapperFor;
|
||||
scope.mixin = mixin;
|
||||
scope.nativePrototypeTable = nativePrototypeTable;
|
||||
scope.oneOf = oneOf;
|
||||
scope.registerObject = registerObject;
|
||||
scope.registerWrapper = register;
|
||||
scope.rewrap = rewrap;
|
||||
scope.setWrapper = setWrapper;
|
||||
scope.unsafeUnwrap = unsafeUnwrap;
|
||||
scope.unwrap = unwrap;
|
||||
scope.unwrapIfNeeded = unwrapIfNeeded;
|
||||
scope.wrap = wrap;
|
||||
scope.wrapIfNeeded = wrapIfNeeded;
|
||||
scope.wrappers = wrappers;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
42
src/ShadowDOM/wrappers/CanvasRenderingContext2D.js
Normal file
42
src/ShadowDOM/wrappers/CanvasRenderingContext2D.js
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var setWrapper = scope.setWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var unwrap = scope.unwrap;
|
||||
var unwrapIfNeeded = scope.unwrapIfNeeded;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D;
|
||||
|
||||
function CanvasRenderingContext2D(impl) {
|
||||
setWrapper(impl, this);
|
||||
}
|
||||
|
||||
mixin(CanvasRenderingContext2D.prototype, {
|
||||
get canvas() {
|
||||
return wrap(unsafeUnwrap(this).canvas);
|
||||
},
|
||||
|
||||
drawImage: function() {
|
||||
arguments[0] = unwrapIfNeeded(arguments[0]);
|
||||
unsafeUnwrap(this).drawImage.apply(unsafeUnwrap(this), arguments);
|
||||
},
|
||||
|
||||
createPattern: function() {
|
||||
arguments[0] = unwrap(arguments[0]);
|
||||
return unsafeUnwrap(this).createPattern.apply(unsafeUnwrap(this), arguments);
|
||||
}
|
||||
});
|
||||
|
||||
registerWrapper(OriginalCanvasRenderingContext2D, CanvasRenderingContext2D,
|
||||
document.createElement('canvas').getContext('2d'));
|
||||
|
||||
scope.wrappers.CanvasRenderingContext2D = CanvasRenderingContext2D;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
46
src/ShadowDOM/wrappers/CharacterData.js
Normal file
46
src/ShadowDOM/wrappers/CharacterData.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var ChildNodeInterface = scope.ChildNodeInterface;
|
||||
var Node = scope.wrappers.Node;
|
||||
var enqueueMutation = scope.enqueueMutation;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
|
||||
var OriginalCharacterData = window.CharacterData;
|
||||
|
||||
function CharacterData(node) {
|
||||
Node.call(this, node);
|
||||
}
|
||||
CharacterData.prototype = Object.create(Node.prototype);
|
||||
mixin(CharacterData.prototype, {
|
||||
get textContent() {
|
||||
return this.data;
|
||||
},
|
||||
set textContent(value) {
|
||||
this.data = value;
|
||||
},
|
||||
get data() {
|
||||
return unsafeUnwrap(this).data;
|
||||
},
|
||||
set data(value) {
|
||||
var oldValue = unsafeUnwrap(this).data;
|
||||
enqueueMutation(this, 'characterData', {
|
||||
oldValue: oldValue
|
||||
});
|
||||
unsafeUnwrap(this).data = value;
|
||||
}
|
||||
});
|
||||
|
||||
mixin(CharacterData.prototype, ChildNodeInterface);
|
||||
|
||||
registerWrapper(OriginalCharacterData, CharacterData,
|
||||
document.createTextNode(''));
|
||||
|
||||
scope.wrappers.CharacterData = CharacterData;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
50
src/ShadowDOM/wrappers/DOMTokenList.js
Normal file
50
src/ShadowDOM/wrappers/DOMTokenList.js
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var setWrapper = scope.setWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
|
||||
function invalidateClass(el) {
|
||||
scope.invalidateRendererBasedOnAttribute(el, 'class');
|
||||
}
|
||||
|
||||
function DOMTokenList(impl, ownerElement) {
|
||||
setWrapper(impl, this);
|
||||
this.ownerElement_ = ownerElement;
|
||||
}
|
||||
|
||||
DOMTokenList.prototype = {
|
||||
constructor: DOMTokenList,
|
||||
get length() {
|
||||
return unsafeUnwrap(this).length;
|
||||
},
|
||||
item: function(index) {
|
||||
return unsafeUnwrap(this).item(index);
|
||||
},
|
||||
contains: function(token) {
|
||||
return unsafeUnwrap(this).contains(token);
|
||||
},
|
||||
add: function() {
|
||||
unsafeUnwrap(this).add.apply(unsafeUnwrap(this), arguments);
|
||||
invalidateClass(this.ownerElement_);
|
||||
},
|
||||
remove: function() {
|
||||
unsafeUnwrap(this).remove.apply(unsafeUnwrap(this), arguments);
|
||||
invalidateClass(this.ownerElement_);
|
||||
},
|
||||
toggle: function(token) {
|
||||
var rv = unsafeUnwrap(this).toggle.apply(unsafeUnwrap(this), arguments);
|
||||
invalidateClass(this.ownerElement_);
|
||||
return rv;
|
||||
},
|
||||
toString: function() {
|
||||
return unsafeUnwrap(this).toString();
|
||||
}
|
||||
};
|
||||
|
||||
scope.wrappers.DOMTokenList = DOMTokenList;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
26
src/ShadowDOM/wrappers/DataTransfer.js
Normal file
26
src/ShadowDOM/wrappers/DataTransfer.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var unwrap = scope.unwrap;
|
||||
|
||||
// DataTransfer (Clipboard in old Blink/WebKit) has a single method that
|
||||
// requires wrapping. Since it is only a method we do not need a real wrapper,
|
||||
// we can just override the method.
|
||||
|
||||
var OriginalDataTransfer = window.DataTransfer || window.Clipboard;
|
||||
var OriginalDataTransferSetDragImage =
|
||||
OriginalDataTransfer.prototype.setDragImage;
|
||||
|
||||
if (OriginalDataTransferSetDragImage) {
|
||||
OriginalDataTransfer.prototype.setDragImage = function(image, x, y) {
|
||||
OriginalDataTransferSetDragImage.call(this, unwrap(image), x, y);
|
||||
};
|
||||
}
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
333
src/ShadowDOM/wrappers/Document.js
Normal file
333
src/ShadowDOM/wrappers/Document.js
Normal file
@@ -0,0 +1,333 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var GetElementsByInterface = scope.GetElementsByInterface;
|
||||
var Node = scope.wrappers.Node;
|
||||
var ParentNodeInterface = scope.ParentNodeInterface;
|
||||
var Selection = scope.wrappers.Selection;
|
||||
var SelectorsInterface = scope.SelectorsInterface;
|
||||
var ShadowRoot = scope.wrappers.ShadowRoot;
|
||||
var TreeScope = scope.TreeScope;
|
||||
var cloneNode = scope.cloneNode;
|
||||
var defineWrapGetter = scope.defineWrapGetter;
|
||||
var elementFromPoint = scope.elementFromPoint;
|
||||
var forwardMethodsToWrapper = scope.forwardMethodsToWrapper;
|
||||
var matchesNames = scope.matchesNames;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var renderAllPending = scope.renderAllPending;
|
||||
var rewrap = scope.rewrap;
|
||||
var setWrapper = scope.setWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var unwrap = scope.unwrap;
|
||||
var wrap = scope.wrap;
|
||||
var wrapEventTargetMethods = scope.wrapEventTargetMethods;
|
||||
var wrapNodeList = scope.wrapNodeList;
|
||||
|
||||
var implementationTable = new WeakMap();
|
||||
|
||||
function Document(node) {
|
||||
Node.call(this, node);
|
||||
this.treeScope_ = new TreeScope(this, null);
|
||||
}
|
||||
Document.prototype = Object.create(Node.prototype);
|
||||
|
||||
defineWrapGetter(Document, 'documentElement');
|
||||
|
||||
// Conceptually both body and head can be in a shadow but suporting that seems
|
||||
// overkill at this point.
|
||||
defineWrapGetter(Document, 'body');
|
||||
defineWrapGetter(Document, 'head');
|
||||
|
||||
// document cannot be overridden so we override a bunch of its methods
|
||||
// directly on the instance.
|
||||
|
||||
function wrapMethod(name) {
|
||||
var original = document[name];
|
||||
Document.prototype[name] = function() {
|
||||
return wrap(original.apply(unsafeUnwrap(this), arguments));
|
||||
};
|
||||
}
|
||||
|
||||
[
|
||||
'createComment',
|
||||
'createDocumentFragment',
|
||||
'createElement',
|
||||
'createElementNS',
|
||||
'createEvent',
|
||||
'createEventNS',
|
||||
'createRange',
|
||||
'createTextNode',
|
||||
'getElementById'
|
||||
].forEach(wrapMethod);
|
||||
|
||||
var originalAdoptNode = document.adoptNode;
|
||||
|
||||
function adoptNodeNoRemove(node, doc) {
|
||||
originalAdoptNode.call(unsafeUnwrap(doc), unwrap(node));
|
||||
adoptSubtree(node, doc);
|
||||
}
|
||||
|
||||
function adoptSubtree(node, doc) {
|
||||
if (node.shadowRoot)
|
||||
doc.adoptNode(node.shadowRoot);
|
||||
if (node instanceof ShadowRoot)
|
||||
adoptOlderShadowRoots(node, doc);
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
adoptSubtree(child, doc);
|
||||
}
|
||||
}
|
||||
|
||||
function adoptOlderShadowRoots(shadowRoot, doc) {
|
||||
var oldShadowRoot = shadowRoot.olderShadowRoot;
|
||||
if (oldShadowRoot)
|
||||
doc.adoptNode(oldShadowRoot);
|
||||
}
|
||||
|
||||
var originalGetSelection = document.getSelection;
|
||||
|
||||
mixin(Document.prototype, {
|
||||
adoptNode: function(node) {
|
||||
if (node.parentNode)
|
||||
node.parentNode.removeChild(node);
|
||||
adoptNodeNoRemove(node, this);
|
||||
return node;
|
||||
},
|
||||
elementFromPoint: function(x, y) {
|
||||
return elementFromPoint(this, this, x, y);
|
||||
},
|
||||
importNode: function(node, deep) {
|
||||
return cloneNode(node, deep, unsafeUnwrap(this));
|
||||
},
|
||||
getSelection: function() {
|
||||
renderAllPending();
|
||||
return new Selection(originalGetSelection.call(unwrap(this)));
|
||||
},
|
||||
getElementsByName: function(name) {
|
||||
return SelectorsInterface.querySelectorAll.call(this,
|
||||
'[name=' + JSON.stringify(String(name)) + ']');
|
||||
}
|
||||
});
|
||||
|
||||
if (document.registerElement) {
|
||||
var originalRegisterElement = document.registerElement;
|
||||
Document.prototype.registerElement = function(tagName, object) {
|
||||
var prototype, extendsOption;
|
||||
if (object !== undefined) {
|
||||
prototype = object.prototype;
|
||||
extendsOption = object.extends;
|
||||
}
|
||||
|
||||
if (!prototype)
|
||||
prototype = Object.create(HTMLElement.prototype);
|
||||
|
||||
|
||||
// If we already used the object as a prototype for another custom
|
||||
// element.
|
||||
if (scope.nativePrototypeTable.get(prototype)) {
|
||||
// TODO(arv): DOMException
|
||||
throw new Error('NotSupportedError');
|
||||
}
|
||||
|
||||
// Find first object on the prototype chain that already have a native
|
||||
// prototype. Keep track of all the objects before that so we can create
|
||||
// a similar structure for the native case.
|
||||
var proto = Object.getPrototypeOf(prototype);
|
||||
var nativePrototype;
|
||||
var prototypes = [];
|
||||
while (proto) {
|
||||
nativePrototype = scope.nativePrototypeTable.get(proto);
|
||||
if (nativePrototype)
|
||||
break;
|
||||
prototypes.push(proto);
|
||||
proto = Object.getPrototypeOf(proto);
|
||||
}
|
||||
|
||||
if (!nativePrototype) {
|
||||
// TODO(arv): DOMException
|
||||
throw new Error('NotSupportedError');
|
||||
}
|
||||
|
||||
// This works by creating a new prototype object that is empty, but has
|
||||
// the native prototype as its proto. The original prototype object
|
||||
// passed into register is used as the wrapper prototype.
|
||||
|
||||
var newPrototype = Object.create(nativePrototype);
|
||||
for (var i = prototypes.length - 1; i >= 0; i--) {
|
||||
newPrototype = Object.create(newPrototype);
|
||||
}
|
||||
|
||||
// Add callbacks if present.
|
||||
// Names are taken from:
|
||||
// https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/bindings/v8/CustomElementConstructorBuilder.cpp&sq=package:chromium&type=cs&l=156
|
||||
// and not from the spec since the spec is out of date.
|
||||
[
|
||||
'createdCallback',
|
||||
'attachedCallback',
|
||||
'detachedCallback',
|
||||
'attributeChangedCallback',
|
||||
].forEach(function(name) {
|
||||
var f = prototype[name];
|
||||
if (!f)
|
||||
return;
|
||||
newPrototype[name] = function() {
|
||||
// if this element has been wrapped prior to registration,
|
||||
// the wrapper is stale; in this case rewrap
|
||||
if (!(wrap(this) instanceof CustomElementConstructor)) {
|
||||
rewrap(this);
|
||||
}
|
||||
f.apply(wrap(this), arguments);
|
||||
};
|
||||
});
|
||||
|
||||
var p = {prototype: newPrototype};
|
||||
if (extendsOption)
|
||||
p.extends = extendsOption;
|
||||
|
||||
function CustomElementConstructor(node) {
|
||||
if (!node) {
|
||||
if (extendsOption) {
|
||||
return document.createElement(extendsOption, tagName);
|
||||
} else {
|
||||
return document.createElement(tagName);
|
||||
}
|
||||
}
|
||||
setWrapper(node, this);
|
||||
}
|
||||
CustomElementConstructor.prototype = prototype;
|
||||
CustomElementConstructor.prototype.constructor = CustomElementConstructor;
|
||||
|
||||
scope.constructorTable.set(newPrototype, CustomElementConstructor);
|
||||
scope.nativePrototypeTable.set(prototype, newPrototype);
|
||||
|
||||
// registration is synchronous so do it last
|
||||
var nativeConstructor = originalRegisterElement.call(unwrap(this),
|
||||
tagName, p);
|
||||
return CustomElementConstructor;
|
||||
};
|
||||
|
||||
forwardMethodsToWrapper([
|
||||
window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument
|
||||
], [
|
||||
'registerElement',
|
||||
]);
|
||||
}
|
||||
|
||||
// We also override some of the methods on document.body and document.head
|
||||
// for convenience.
|
||||
forwardMethodsToWrapper([
|
||||
window.HTMLBodyElement,
|
||||
window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument
|
||||
window.HTMLHeadElement,
|
||||
window.HTMLHtmlElement,
|
||||
], [
|
||||
'appendChild',
|
||||
'compareDocumentPosition',
|
||||
'contains',
|
||||
'getElementsByClassName',
|
||||
'getElementsByTagName',
|
||||
'getElementsByTagNameNS',
|
||||
'insertBefore',
|
||||
'querySelector',
|
||||
'querySelectorAll',
|
||||
'removeChild',
|
||||
'replaceChild',
|
||||
].concat(matchesNames));
|
||||
|
||||
forwardMethodsToWrapper([
|
||||
window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument
|
||||
], [
|
||||
'adoptNode',
|
||||
'importNode',
|
||||
'contains',
|
||||
'createComment',
|
||||
'createDocumentFragment',
|
||||
'createElement',
|
||||
'createElementNS',
|
||||
'createEvent',
|
||||
'createEventNS',
|
||||
'createRange',
|
||||
'createTextNode',
|
||||
'elementFromPoint',
|
||||
'getElementById',
|
||||
'getElementsByName',
|
||||
'getSelection',
|
||||
]);
|
||||
|
||||
mixin(Document.prototype, GetElementsByInterface);
|
||||
mixin(Document.prototype, ParentNodeInterface);
|
||||
mixin(Document.prototype, SelectorsInterface);
|
||||
|
||||
mixin(Document.prototype, {
|
||||
get implementation() {
|
||||
var implementation = implementationTable.get(this);
|
||||
if (implementation)
|
||||
return implementation;
|
||||
implementation =
|
||||
new DOMImplementation(unwrap(this).implementation);
|
||||
implementationTable.set(this, implementation);
|
||||
return implementation;
|
||||
},
|
||||
|
||||
get defaultView() {
|
||||
return wrap(unwrap(this).defaultView);
|
||||
}
|
||||
});
|
||||
|
||||
registerWrapper(window.Document, Document,
|
||||
document.implementation.createHTMLDocument(''));
|
||||
|
||||
// Both WebKit and Gecko uses HTMLDocument for document. HTML5/DOM only has
|
||||
// one Document interface and IE implements the standard correctly.
|
||||
if (window.HTMLDocument)
|
||||
registerWrapper(window.HTMLDocument, Document);
|
||||
|
||||
wrapEventTargetMethods([
|
||||
window.HTMLBodyElement,
|
||||
window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument
|
||||
window.HTMLHeadElement,
|
||||
]);
|
||||
|
||||
function DOMImplementation(impl) {
|
||||
setWrapper(impl, this);
|
||||
}
|
||||
|
||||
function wrapImplMethod(constructor, name) {
|
||||
var original = document.implementation[name];
|
||||
constructor.prototype[name] = function() {
|
||||
return wrap(original.apply(unsafeUnwrap(this), arguments));
|
||||
};
|
||||
}
|
||||
|
||||
function forwardImplMethod(constructor, name) {
|
||||
var original = document.implementation[name];
|
||||
constructor.prototype[name] = function() {
|
||||
return original.apply(unsafeUnwrap(this), arguments);
|
||||
};
|
||||
}
|
||||
|
||||
wrapImplMethod(DOMImplementation, 'createDocumentType');
|
||||
wrapImplMethod(DOMImplementation, 'createDocument');
|
||||
wrapImplMethod(DOMImplementation, 'createHTMLDocument');
|
||||
forwardImplMethod(DOMImplementation, 'hasFeature');
|
||||
|
||||
registerWrapper(window.DOMImplementation, DOMImplementation);
|
||||
|
||||
forwardMethodsToWrapper([
|
||||
window.DOMImplementation,
|
||||
], [
|
||||
'createDocumentType',
|
||||
'createDocument',
|
||||
'createHTMLDocument',
|
||||
'hasFeature',
|
||||
]);
|
||||
|
||||
scope.adoptNodeNoRemove = adoptNodeNoRemove;
|
||||
scope.wrappers.DOMImplementation = DOMImplementation;
|
||||
scope.wrappers.Document = Document;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
150
src/ShadowDOM/wrappers/Element.js
Normal file
150
src/ShadowDOM/wrappers/Element.js
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var ChildNodeInterface = scope.ChildNodeInterface;
|
||||
var GetElementsByInterface = scope.GetElementsByInterface;
|
||||
var Node = scope.wrappers.Node;
|
||||
var DOMTokenList = scope.wrappers.DOMTokenList;
|
||||
var ParentNodeInterface = scope.ParentNodeInterface;
|
||||
var SelectorsInterface = scope.SelectorsInterface;
|
||||
var addWrapNodeListMethod = scope.addWrapNodeListMethod;
|
||||
var enqueueMutation = scope.enqueueMutation;
|
||||
var mixin = scope.mixin;
|
||||
var oneOf = scope.oneOf;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var wrappers = scope.wrappers;
|
||||
|
||||
var OriginalElement = window.Element;
|
||||
|
||||
var matchesNames = [
|
||||
'matches', // needs to come first.
|
||||
'mozMatchesSelector',
|
||||
'msMatchesSelector',
|
||||
'webkitMatchesSelector',
|
||||
].filter(function(name) {
|
||||
return OriginalElement.prototype[name];
|
||||
});
|
||||
|
||||
var matchesName = matchesNames[0];
|
||||
|
||||
var originalMatches = OriginalElement.prototype[matchesName];
|
||||
|
||||
function invalidateRendererBasedOnAttribute(element, name) {
|
||||
// Only invalidate if parent node is a shadow host.
|
||||
var p = element.parentNode;
|
||||
if (!p || !p.shadowRoot)
|
||||
return;
|
||||
|
||||
var renderer = scope.getRendererForHost(p);
|
||||
if (renderer.dependsOnAttribute(name))
|
||||
renderer.invalidate();
|
||||
}
|
||||
|
||||
function enqueAttributeChange(element, name, oldValue) {
|
||||
// This is not fully spec compliant. We should use localName (which might
|
||||
// have a different case than name) and the namespace (which requires us
|
||||
// to get the Attr object).
|
||||
enqueueMutation(element, 'attributes', {
|
||||
name: name,
|
||||
namespace: null,
|
||||
oldValue: oldValue
|
||||
});
|
||||
}
|
||||
|
||||
var classListTable = new WeakMap();
|
||||
|
||||
function Element(node) {
|
||||
Node.call(this, node);
|
||||
}
|
||||
Element.prototype = Object.create(Node.prototype);
|
||||
mixin(Element.prototype, {
|
||||
createShadowRoot: function() {
|
||||
var newShadowRoot = new wrappers.ShadowRoot(this);
|
||||
unsafeUnwrap(this).polymerShadowRoot_ = newShadowRoot;
|
||||
|
||||
var renderer = scope.getRendererForHost(this);
|
||||
renderer.invalidate();
|
||||
|
||||
return newShadowRoot;
|
||||
},
|
||||
|
||||
get shadowRoot() {
|
||||
return unsafeUnwrap(this).polymerShadowRoot_ || null;
|
||||
},
|
||||
|
||||
// getDestinationInsertionPoints added in ShadowRenderer.js
|
||||
|
||||
setAttribute: function(name, value) {
|
||||
var oldValue = unsafeUnwrap(this).getAttribute(name);
|
||||
unsafeUnwrap(this).setAttribute(name, value);
|
||||
enqueAttributeChange(this, name, oldValue);
|
||||
invalidateRendererBasedOnAttribute(this, name);
|
||||
},
|
||||
|
||||
removeAttribute: function(name) {
|
||||
var oldValue = unsafeUnwrap(this).getAttribute(name);
|
||||
unsafeUnwrap(this).removeAttribute(name);
|
||||
enqueAttributeChange(this, name, oldValue);
|
||||
invalidateRendererBasedOnAttribute(this, name);
|
||||
},
|
||||
|
||||
matches: function(selector) {
|
||||
return originalMatches.call(unsafeUnwrap(this), selector);
|
||||
},
|
||||
|
||||
get classList() {
|
||||
var list = classListTable.get(this);
|
||||
if (!list) {
|
||||
classListTable.set(this,
|
||||
list = new DOMTokenList(unsafeUnwrap(this).classList, this));
|
||||
}
|
||||
return list;
|
||||
},
|
||||
|
||||
get className() {
|
||||
return unsafeUnwrap(this).className;
|
||||
},
|
||||
|
||||
set className(v) {
|
||||
this.setAttribute('class', v);
|
||||
},
|
||||
|
||||
get id() {
|
||||
return unsafeUnwrap(this).id;
|
||||
},
|
||||
|
||||
set id(v) {
|
||||
this.setAttribute('id', v);
|
||||
}
|
||||
});
|
||||
|
||||
matchesNames.forEach(function(name) {
|
||||
if (name !== 'matches') {
|
||||
Element.prototype[name] = function(selector) {
|
||||
return this.matches(selector);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (OriginalElement.prototype.webkitCreateShadowRoot) {
|
||||
Element.prototype.webkitCreateShadowRoot =
|
||||
Element.prototype.createShadowRoot;
|
||||
}
|
||||
|
||||
mixin(Element.prototype, ChildNodeInterface);
|
||||
mixin(Element.prototype, GetElementsByInterface);
|
||||
mixin(Element.prototype, ParentNodeInterface);
|
||||
mixin(Element.prototype, SelectorsInterface);
|
||||
|
||||
registerWrapper(OriginalElement, Element,
|
||||
document.createElementNS(null, 'x'));
|
||||
|
||||
scope.invalidateRendererBasedOnAttribute = invalidateRendererBasedOnAttribute;
|
||||
scope.matchesNames = matchesNames;
|
||||
scope.wrappers.Element = Element;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
31
src/ShadowDOM/wrappers/FormData.js
Normal file
31
src/ShadowDOM/wrappers/FormData.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var setWrapper = scope.setWrapper;
|
||||
var unwrap = scope.unwrap;
|
||||
|
||||
var OriginalFormData = window.FormData;
|
||||
if (!OriginalFormData) return;
|
||||
|
||||
function FormData(formElement) {
|
||||
var impl;
|
||||
if (formElement instanceof OriginalFormData) {
|
||||
impl = formElement;
|
||||
} else {
|
||||
impl = new OriginalFormData(formElement && unwrap(formElement));
|
||||
}
|
||||
setWrapper(impl, this);
|
||||
}
|
||||
|
||||
registerWrapper(OriginalFormData, FormData, new OriginalFormData());
|
||||
|
||||
scope.wrappers.FormData = FormData;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
44
src/ShadowDOM/wrappers/HTMLAudioElement.js
Normal file
44
src/ShadowDOM/wrappers/HTMLAudioElement.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLMediaElement = scope.wrappers.HTMLMediaElement;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var unwrap = scope.unwrap;
|
||||
var rewrap = scope.rewrap;
|
||||
|
||||
var OriginalHTMLAudioElement = window.HTMLAudioElement;
|
||||
|
||||
if (!OriginalHTMLAudioElement) return;
|
||||
|
||||
function HTMLAudioElement(node) {
|
||||
HTMLMediaElement.call(this, node);
|
||||
}
|
||||
HTMLAudioElement.prototype = Object.create(HTMLMediaElement.prototype);
|
||||
|
||||
registerWrapper(OriginalHTMLAudioElement, HTMLAudioElement,
|
||||
document.createElement('audio'));
|
||||
|
||||
function Audio(src) {
|
||||
if (!(this instanceof Audio)) {
|
||||
throw new TypeError(
|
||||
'DOM object constructor cannot be called as a function.');
|
||||
}
|
||||
|
||||
var node = unwrap(document.createElement('audio'));
|
||||
HTMLMediaElement.call(this, node);
|
||||
rewrap(node, this);
|
||||
|
||||
node.setAttribute('preload', 'auto');
|
||||
if (src !== undefined)
|
||||
node.setAttribute('src', src);
|
||||
}
|
||||
|
||||
Audio.prototype = HTMLAudioElement.prototype;
|
||||
|
||||
scope.wrappers.HTMLAudioElement = HTMLAudioElement;
|
||||
scope.wrappers.Audio = Audio;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
32
src/ShadowDOM/wrappers/HTMLCanvasElement.js
Normal file
32
src/ShadowDOM/wrappers/HTMLCanvasElement.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var OriginalHTMLCanvasElement = window.HTMLCanvasElement;
|
||||
|
||||
function HTMLCanvasElement(node) {
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
HTMLCanvasElement.prototype = Object.create(HTMLElement.prototype);
|
||||
|
||||
mixin(HTMLCanvasElement.prototype, {
|
||||
getContext: function() {
|
||||
var context = unsafeUnwrap(this).getContext.apply(unsafeUnwrap(this), arguments);
|
||||
return context && wrap(context);
|
||||
}
|
||||
});
|
||||
|
||||
registerWrapper(OriginalHTMLCanvasElement, HTMLCanvasElement,
|
||||
document.createElement('canvas'));
|
||||
|
||||
scope.wrappers.HTMLCanvasElement = HTMLCanvasElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
15
src/ShadowDOM/wrappers/HTMLCollection.js
Normal file
15
src/ShadowDOM/wrappers/HTMLCollection.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
// TODO(arv): Implement.
|
||||
|
||||
scope.wrapHTMLCollection = scope.wrapNodeList;
|
||||
scope.wrappers.HTMLCollection = scope.wrappers.NodeList;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
41
src/ShadowDOM/wrappers/HTMLContentElement.js
Normal file
41
src/ShadowDOM/wrappers/HTMLContentElement.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
|
||||
var OriginalHTMLContentElement = window.HTMLContentElement;
|
||||
|
||||
function HTMLContentElement(node) {
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
HTMLContentElement.prototype = Object.create(HTMLElement.prototype);
|
||||
mixin(HTMLContentElement.prototype, {
|
||||
constructor: HTMLContentElement,
|
||||
|
||||
get select() {
|
||||
return this.getAttribute('select');
|
||||
},
|
||||
set select(value) {
|
||||
this.setAttribute('select', value);
|
||||
},
|
||||
|
||||
setAttribute: function(n, v) {
|
||||
HTMLElement.prototype.setAttribute.call(this, n, v);
|
||||
if (String(n).toLowerCase() === 'select')
|
||||
this.invalidateShadowRenderer(true);
|
||||
}
|
||||
|
||||
// getDistributedNodes is added in ShadowRenderer
|
||||
});
|
||||
|
||||
if (OriginalHTMLContentElement)
|
||||
registerWrapper(OriginalHTMLContentElement, HTMLContentElement);
|
||||
|
||||
scope.wrappers.HTMLContentElement = HTMLContentElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
327
src/ShadowDOM/wrappers/HTMLElement.js
Normal file
327
src/ShadowDOM/wrappers/HTMLElement.js
Normal file
@@ -0,0 +1,327 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var Element = scope.wrappers.Element;
|
||||
var defineGetter = scope.defineGetter;
|
||||
var enqueueMutation = scope.enqueueMutation;
|
||||
var mixin = scope.mixin;
|
||||
var nodesWereAdded = scope.nodesWereAdded;
|
||||
var nodesWereRemoved = scope.nodesWereRemoved;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var snapshotNodeList = scope.snapshotNodeList;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var unwrap = scope.unwrap;
|
||||
var wrap = scope.wrap;
|
||||
var wrappers = scope.wrappers;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// innerHTML and outerHTML
|
||||
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#escapingString
|
||||
var escapeAttrRegExp = /[&\u00A0"]/g;
|
||||
var escapeDataRegExp = /[&\u00A0<>]/g;
|
||||
|
||||
function escapeReplace(c) {
|
||||
switch (c) {
|
||||
case '&':
|
||||
return '&';
|
||||
case '<':
|
||||
return '<';
|
||||
case '>':
|
||||
return '>';
|
||||
case '"':
|
||||
return '"'
|
||||
case '\u00A0':
|
||||
return ' ';
|
||||
}
|
||||
}
|
||||
|
||||
function escapeAttr(s) {
|
||||
return s.replace(escapeAttrRegExp, escapeReplace);
|
||||
}
|
||||
|
||||
function escapeData(s) {
|
||||
return s.replace(escapeDataRegExp, escapeReplace);
|
||||
}
|
||||
|
||||
function makeSet(arr) {
|
||||
var set = {};
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
set[arr[i]] = true;
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/#void-elements
|
||||
var voidElements = makeSet([
|
||||
'area',
|
||||
'base',
|
||||
'br',
|
||||
'col',
|
||||
'command',
|
||||
'embed',
|
||||
'hr',
|
||||
'img',
|
||||
'input',
|
||||
'keygen',
|
||||
'link',
|
||||
'meta',
|
||||
'param',
|
||||
'source',
|
||||
'track',
|
||||
'wbr'
|
||||
]);
|
||||
|
||||
var plaintextParents = makeSet([
|
||||
'style',
|
||||
'script',
|
||||
'xmp',
|
||||
'iframe',
|
||||
'noembed',
|
||||
'noframes',
|
||||
'plaintext',
|
||||
'noscript'
|
||||
]);
|
||||
|
||||
function getOuterHTML(node, parentNode) {
|
||||
switch (node.nodeType) {
|
||||
case Node.ELEMENT_NODE:
|
||||
var tagName = node.tagName.toLowerCase();
|
||||
var s = '<' + tagName;
|
||||
var attrs = node.attributes;
|
||||
for (var i = 0, attr; attr = attrs[i]; i++) {
|
||||
s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"';
|
||||
}
|
||||
s += '>';
|
||||
if (voidElements[tagName])
|
||||
return s;
|
||||
|
||||
return s + getInnerHTML(node) + '</' + tagName + '>';
|
||||
|
||||
case Node.TEXT_NODE:
|
||||
var data = node.data;
|
||||
if (parentNode && plaintextParents[parentNode.localName])
|
||||
return data;
|
||||
return escapeData(data);
|
||||
|
||||
case Node.COMMENT_NODE:
|
||||
return '<!--' + node.data + '-->';
|
||||
|
||||
default:
|
||||
console.error(node);
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
}
|
||||
|
||||
function getInnerHTML(node) {
|
||||
if (node instanceof wrappers.HTMLTemplateElement)
|
||||
node = node.content;
|
||||
|
||||
var s = '';
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
s += getOuterHTML(child, node);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function setInnerHTML(node, value, opt_tagName) {
|
||||
var tagName = opt_tagName || 'div';
|
||||
node.textContent = '';
|
||||
var tempElement = unwrap(node.ownerDocument.createElement(tagName));
|
||||
tempElement.innerHTML = value;
|
||||
var firstChild;
|
||||
while (firstChild = tempElement.firstChild) {
|
||||
node.appendChild(wrap(firstChild));
|
||||
}
|
||||
}
|
||||
|
||||
// IE11 does not have MSIE in the user agent string.
|
||||
var oldIe = /MSIE/.test(navigator.userAgent);
|
||||
|
||||
var OriginalHTMLElement = window.HTMLElement;
|
||||
var OriginalHTMLTemplateElement = window.HTMLTemplateElement;
|
||||
|
||||
function HTMLElement(node) {
|
||||
Element.call(this, node);
|
||||
}
|
||||
HTMLElement.prototype = Object.create(Element.prototype);
|
||||
mixin(HTMLElement.prototype, {
|
||||
get innerHTML() {
|
||||
return getInnerHTML(this);
|
||||
},
|
||||
set innerHTML(value) {
|
||||
// IE9 does not handle set innerHTML correctly on plaintextParents. It
|
||||
// creates element children. For example
|
||||
//
|
||||
// scriptElement.innerHTML = '<a>test</a>'
|
||||
//
|
||||
// Creates a single HTMLAnchorElement child.
|
||||
if (oldIe && plaintextParents[this.localName]) {
|
||||
this.textContent = value;
|
||||
return;
|
||||
}
|
||||
|
||||
var removedNodes = snapshotNodeList(this.childNodes);
|
||||
|
||||
if (this.invalidateShadowRenderer()) {
|
||||
if (this instanceof wrappers.HTMLTemplateElement)
|
||||
setInnerHTML(this.content, value);
|
||||
else
|
||||
setInnerHTML(this, value, this.tagName);
|
||||
|
||||
// If we have a non native template element we need to handle this
|
||||
// manually since setting impl.innerHTML would add the html as direct
|
||||
// children and not be moved over to the content fragment.
|
||||
} else if (!OriginalHTMLTemplateElement &&
|
||||
this instanceof wrappers.HTMLTemplateElement) {
|
||||
setInnerHTML(this.content, value);
|
||||
} else {
|
||||
unsafeUnwrap(this).innerHTML = value;
|
||||
}
|
||||
|
||||
var addedNodes = snapshotNodeList(this.childNodes);
|
||||
|
||||
enqueueMutation(this, 'childList', {
|
||||
addedNodes: addedNodes,
|
||||
removedNodes: removedNodes
|
||||
});
|
||||
|
||||
nodesWereRemoved(removedNodes);
|
||||
nodesWereAdded(addedNodes, this);
|
||||
},
|
||||
|
||||
get outerHTML() {
|
||||
return getOuterHTML(this, this.parentNode);
|
||||
},
|
||||
set outerHTML(value) {
|
||||
var p = this.parentNode;
|
||||
if (p) {
|
||||
p.invalidateShadowRenderer();
|
||||
var df = frag(p, value);
|
||||
p.replaceChild(df, this);
|
||||
}
|
||||
},
|
||||
|
||||
insertAdjacentHTML: function(position, text) {
|
||||
var contextElement, refNode;
|
||||
switch (String(position).toLowerCase()) {
|
||||
case 'beforebegin':
|
||||
contextElement = this.parentNode;
|
||||
refNode = this;
|
||||
break;
|
||||
case 'afterend':
|
||||
contextElement = this.parentNode;
|
||||
refNode = this.nextSibling;
|
||||
break;
|
||||
case 'afterbegin':
|
||||
contextElement = this;
|
||||
refNode = this.firstChild;
|
||||
break;
|
||||
case 'beforeend':
|
||||
contextElement = this;
|
||||
refNode = null;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
var df = frag(contextElement, text);
|
||||
contextElement.insertBefore(df, refNode);
|
||||
},
|
||||
|
||||
get hidden() {
|
||||
return this.hasAttribute('hidden');
|
||||
},
|
||||
set hidden(v) {
|
||||
if (v) {
|
||||
this.setAttribute('hidden', '');
|
||||
} else {
|
||||
this.removeAttribute('hidden');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function frag(contextElement, html) {
|
||||
// TODO(arv): This does not work with SVG and other non HTML elements.
|
||||
var p = unwrap(contextElement.cloneNode(false));
|
||||
p.innerHTML = html;
|
||||
var df = unwrap(document.createDocumentFragment());
|
||||
var c;
|
||||
while (c = p.firstChild) {
|
||||
df.appendChild(c);
|
||||
}
|
||||
return wrap(df);
|
||||
}
|
||||
|
||||
function getter(name) {
|
||||
return function() {
|
||||
scope.renderAllPending();
|
||||
return unsafeUnwrap(this)[name];
|
||||
};
|
||||
}
|
||||
|
||||
function getterRequiresRendering(name) {
|
||||
defineGetter(HTMLElement, name, getter(name));
|
||||
}
|
||||
|
||||
[
|
||||
'clientHeight',
|
||||
'clientLeft',
|
||||
'clientTop',
|
||||
'clientWidth',
|
||||
'offsetHeight',
|
||||
'offsetLeft',
|
||||
'offsetTop',
|
||||
'offsetWidth',
|
||||
'scrollHeight',
|
||||
'scrollWidth',
|
||||
].forEach(getterRequiresRendering);
|
||||
|
||||
function getterAndSetterRequiresRendering(name) {
|
||||
Object.defineProperty(HTMLElement.prototype, name, {
|
||||
get: getter(name),
|
||||
set: function(v) {
|
||||
scope.renderAllPending();
|
||||
unsafeUnwrap(this)[name] = v;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
});
|
||||
}
|
||||
|
||||
[
|
||||
'scrollLeft',
|
||||
'scrollTop',
|
||||
].forEach(getterAndSetterRequiresRendering);
|
||||
|
||||
function methodRequiresRendering(name) {
|
||||
Object.defineProperty(HTMLElement.prototype, name, {
|
||||
value: function() {
|
||||
scope.renderAllPending();
|
||||
return unsafeUnwrap(this)[name].apply(unsafeUnwrap(this), arguments);
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
});
|
||||
}
|
||||
|
||||
[
|
||||
'getBoundingClientRect',
|
||||
'getClientRects',
|
||||
'scrollIntoView'
|
||||
].forEach(methodRequiresRendering);
|
||||
|
||||
// HTMLElement is abstract so we use a subclass that has no members.
|
||||
registerWrapper(OriginalHTMLElement, HTMLElement,
|
||||
document.createElement('b'));
|
||||
|
||||
scope.wrappers.HTMLElement = HTMLElement;
|
||||
|
||||
// TODO: Find a better way to share these two with WrapperShadowRoot.
|
||||
scope.getInnerHTML = getInnerHTML;
|
||||
scope.setInnerHTML = setInnerHTML
|
||||
})(window.ShadowDOMPolyfill);
|
||||
35
src/ShadowDOM/wrappers/HTMLFormElement.js
Normal file
35
src/ShadowDOM/wrappers/HTMLFormElement.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2014 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) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var wrapHTMLCollection = scope.wrapHTMLCollection;
|
||||
var unwrap = scope.unwrap;
|
||||
|
||||
var OriginalHTMLFormElement = window.HTMLFormElement;
|
||||
|
||||
function HTMLFormElement(node) {
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
HTMLFormElement.prototype = Object.create(HTMLElement.prototype);
|
||||
mixin(HTMLFormElement.prototype, {
|
||||
get elements() {
|
||||
// Note: technically this should be an HTMLFormControlsCollection, but
|
||||
// that inherits from HTMLCollection, so should be good enough. Spec:
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#htmlformcontrolscollection
|
||||
return wrapHTMLCollection(unwrap(this).elements);
|
||||
}
|
||||
});
|
||||
|
||||
registerWrapper(OriginalHTMLFormElement, HTMLFormElement,
|
||||
document.createElement('form'));
|
||||
|
||||
scope.wrappers.HTMLFormElement = HTMLFormElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
43
src/ShadowDOM/wrappers/HTMLImageElement.js
Normal file
43
src/ShadowDOM/wrappers/HTMLImageElement.js
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var unwrap = scope.unwrap;
|
||||
var rewrap = scope.rewrap;
|
||||
|
||||
var OriginalHTMLImageElement = window.HTMLImageElement;
|
||||
|
||||
function HTMLImageElement(node) {
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
HTMLImageElement.prototype = Object.create(HTMLElement.prototype);
|
||||
|
||||
registerWrapper(OriginalHTMLImageElement, HTMLImageElement,
|
||||
document.createElement('img'));
|
||||
|
||||
function Image(width, height) {
|
||||
if (!(this instanceof Image)) {
|
||||
throw new TypeError(
|
||||
'DOM object constructor cannot be called as a function.');
|
||||
}
|
||||
|
||||
var node = unwrap(document.createElement('img'));
|
||||
HTMLElement.call(this, node);
|
||||
rewrap(node, this);
|
||||
|
||||
if (width !== undefined)
|
||||
node.width = width;
|
||||
if (height !== undefined)
|
||||
node.height = height;
|
||||
}
|
||||
|
||||
Image.prototype = HTMLImageElement.prototype;
|
||||
|
||||
scope.wrappers.HTMLImageElement = HTMLImageElement;
|
||||
scope.wrappers.Image = Image;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
24
src/ShadowDOM/wrappers/HTMLMediaElement.js
Normal file
24
src/ShadowDOM/wrappers/HTMLMediaElement.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
|
||||
var OriginalHTMLMediaElement = window.HTMLMediaElement;
|
||||
|
||||
if (!OriginalHTMLMediaElement) return;
|
||||
|
||||
function HTMLMediaElement(node) {
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
HTMLMediaElement.prototype = Object.create(HTMLElement.prototype);
|
||||
|
||||
registerWrapper(OriginalHTMLMediaElement, HTMLMediaElement,
|
||||
document.createElement('audio'));
|
||||
|
||||
scope.wrappers.HTMLMediaElement = HTMLMediaElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
63
src/ShadowDOM/wrappers/HTMLOptionElement.js
Normal file
63
src/ShadowDOM/wrappers/HTMLOptionElement.js
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var rewrap = scope.rewrap;
|
||||
var unwrap = scope.unwrap;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var OriginalHTMLOptionElement = window.HTMLOptionElement;
|
||||
|
||||
function trimText(s) {
|
||||
return s.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function HTMLOptionElement(node) {
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
HTMLOptionElement.prototype = Object.create(HTMLElement.prototype);
|
||||
mixin(HTMLOptionElement.prototype, {
|
||||
get text() {
|
||||
return trimText(this.textContent);
|
||||
},
|
||||
set text(value) {
|
||||
this.textContent = trimText(String(value));
|
||||
},
|
||||
get form() {
|
||||
return wrap(unwrap(this).form);
|
||||
}
|
||||
});
|
||||
|
||||
registerWrapper(OriginalHTMLOptionElement, HTMLOptionElement,
|
||||
document.createElement('option'));
|
||||
|
||||
function Option(text, value, defaultSelected, selected) {
|
||||
if (!(this instanceof Option)) {
|
||||
throw new TypeError(
|
||||
'DOM object constructor cannot be called as a function.');
|
||||
}
|
||||
|
||||
var node = unwrap(document.createElement('option'));
|
||||
HTMLElement.call(this, node);
|
||||
rewrap(node, this);
|
||||
|
||||
if (text !== undefined)
|
||||
node.text = text;
|
||||
if (value !== undefined)
|
||||
node.setAttribute('value', value);
|
||||
if (defaultSelected === true)
|
||||
node.setAttribute('selected', '');
|
||||
node.selected = selected === true;
|
||||
}
|
||||
|
||||
Option.prototype = HTMLOptionElement.prototype;
|
||||
|
||||
scope.wrappers.HTMLOptionElement = HTMLOptionElement;
|
||||
scope.wrappers.Option = Option;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
50
src/ShadowDOM/wrappers/HTMLSelectElement.js
Normal file
50
src/ShadowDOM/wrappers/HTMLSelectElement.js
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var unwrap = scope.unwrap;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var OriginalHTMLSelectElement = window.HTMLSelectElement;
|
||||
|
||||
function HTMLSelectElement(node) {
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
HTMLSelectElement.prototype = Object.create(HTMLElement.prototype);
|
||||
mixin(HTMLSelectElement.prototype, {
|
||||
add: function(element, before) {
|
||||
if (typeof before === 'object') // also includes null
|
||||
before = unwrap(before);
|
||||
unwrap(this).add(unwrap(element), before);
|
||||
},
|
||||
|
||||
remove: function(indexOrNode) {
|
||||
// Spec only allows index but implementations allow index or node.
|
||||
// remove() is also allowed which is same as remove(undefined)
|
||||
if (indexOrNode === undefined) {
|
||||
HTMLElement.prototype.remove.call(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof indexOrNode === 'object')
|
||||
indexOrNode = unwrap(indexOrNode);
|
||||
|
||||
unwrap(this).remove(indexOrNode);
|
||||
},
|
||||
|
||||
get form() {
|
||||
return wrap(unwrap(this).form);
|
||||
}
|
||||
});
|
||||
|
||||
registerWrapper(OriginalHTMLSelectElement, HTMLSelectElement,
|
||||
document.createElement('select'));
|
||||
|
||||
scope.wrappers.HTMLSelectElement = HTMLSelectElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
27
src/ShadowDOM/wrappers/HTMLShadowElement.js
Normal file
27
src/ShadowDOM/wrappers/HTMLShadowElement.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var mixin = scope.mixin;
|
||||
var NodeList = scope.wrappers.NodeList;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
|
||||
var OriginalHTMLShadowElement = window.HTMLShadowElement;
|
||||
|
||||
function HTMLShadowElement(node) {
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
HTMLShadowElement.prototype = Object.create(HTMLElement.prototype);
|
||||
HTMLShadowElement.prototype.constructor = HTMLShadowElement;
|
||||
|
||||
// getDistributedNodes is added in ShadowRenderer
|
||||
|
||||
if (OriginalHTMLShadowElement)
|
||||
registerWrapper(OriginalHTMLShadowElement, HTMLShadowElement);
|
||||
|
||||
scope.wrappers.HTMLShadowElement = HTMLShadowElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
64
src/ShadowDOM/wrappers/HTMLTableElement.js
Normal file
64
src/ShadowDOM/wrappers/HTMLTableElement.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var unwrap = scope.unwrap;
|
||||
var wrap = scope.wrap;
|
||||
var wrapHTMLCollection = scope.wrapHTMLCollection;
|
||||
|
||||
var OriginalHTMLTableElement = window.HTMLTableElement;
|
||||
|
||||
function HTMLTableElement(node) {
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
HTMLTableElement.prototype = Object.create(HTMLElement.prototype);
|
||||
mixin(HTMLTableElement.prototype, {
|
||||
get caption() {
|
||||
return wrap(unwrap(this).caption);
|
||||
},
|
||||
createCaption: function() {
|
||||
return wrap(unwrap(this).createCaption());
|
||||
},
|
||||
|
||||
get tHead() {
|
||||
return wrap(unwrap(this).tHead);
|
||||
},
|
||||
createTHead: function() {
|
||||
return wrap(unwrap(this).createTHead());
|
||||
},
|
||||
|
||||
createTFoot: function() {
|
||||
return wrap(unwrap(this).createTFoot());
|
||||
},
|
||||
get tFoot() {
|
||||
return wrap(unwrap(this).tFoot);
|
||||
},
|
||||
|
||||
get tBodies() {
|
||||
return wrapHTMLCollection(unwrap(this).tBodies);
|
||||
},
|
||||
createTBody: function() {
|
||||
return wrap(unwrap(this).createTBody());
|
||||
},
|
||||
|
||||
get rows() {
|
||||
return wrapHTMLCollection(unwrap(this).rows);
|
||||
},
|
||||
insertRow: function(index) {
|
||||
return wrap(unwrap(this).insertRow(index));
|
||||
}
|
||||
});
|
||||
|
||||
registerWrapper(OriginalHTMLTableElement, HTMLTableElement,
|
||||
document.createElement('table'));
|
||||
|
||||
scope.wrappers.HTMLTableElement = HTMLTableElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
37
src/ShadowDOM/wrappers/HTMLTableRowElement.js
Normal file
37
src/ShadowDOM/wrappers/HTMLTableRowElement.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var wrapHTMLCollection = scope.wrapHTMLCollection;
|
||||
var unwrap = scope.unwrap;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var OriginalHTMLTableRowElement = window.HTMLTableRowElement;
|
||||
|
||||
function HTMLTableRowElement(node) {
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
HTMLTableRowElement.prototype = Object.create(HTMLElement.prototype);
|
||||
mixin(HTMLTableRowElement.prototype, {
|
||||
get cells() {
|
||||
return wrapHTMLCollection(unwrap(this).cells);
|
||||
},
|
||||
|
||||
insertCell: function(index) {
|
||||
return wrap(unwrap(this).insertCell(index));
|
||||
}
|
||||
});
|
||||
|
||||
registerWrapper(OriginalHTMLTableRowElement, HTMLTableRowElement,
|
||||
document.createElement('tr'));
|
||||
|
||||
scope.wrappers.HTMLTableRowElement = HTMLTableRowElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
37
src/ShadowDOM/wrappers/HTMLTableSectionElement.js
Normal file
37
src/ShadowDOM/wrappers/HTMLTableSectionElement.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var wrapHTMLCollection = scope.wrapHTMLCollection;
|
||||
var unwrap = scope.unwrap;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var OriginalHTMLTableSectionElement = window.HTMLTableSectionElement;
|
||||
|
||||
function HTMLTableSectionElement(node) {
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
HTMLTableSectionElement.prototype = Object.create(HTMLElement.prototype);
|
||||
mixin(HTMLTableSectionElement.prototype, {
|
||||
constructor: HTMLTableSectionElement,
|
||||
get rows() {
|
||||
return wrapHTMLCollection(unwrap(this).rows);
|
||||
},
|
||||
insertRow: function(index) {
|
||||
return wrap(unwrap(this).insertRow(index));
|
||||
}
|
||||
});
|
||||
|
||||
registerWrapper(OriginalHTMLTableSectionElement, HTMLTableSectionElement,
|
||||
document.createElement('thead'));
|
||||
|
||||
scope.wrappers.HTMLTableSectionElement = HTMLTableSectionElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
73
src/ShadowDOM/wrappers/HTMLTemplateElement.js
Normal file
73
src/ShadowDOM/wrappers/HTMLTemplateElement.js
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var unwrap = scope.unwrap;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var contentTable = new WeakMap();
|
||||
var templateContentsOwnerTable = new WeakMap();
|
||||
|
||||
// http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#dfn-template-contents-owner
|
||||
function getTemplateContentsOwner(doc) {
|
||||
if (!doc.defaultView)
|
||||
return doc;
|
||||
var d = templateContentsOwnerTable.get(doc);
|
||||
if (!d) {
|
||||
// TODO(arv): This should either be a Document or HTMLDocument depending
|
||||
// on doc.
|
||||
d = doc.implementation.createHTMLDocument('');
|
||||
while (d.lastChild) {
|
||||
d.removeChild(d.lastChild);
|
||||
}
|
||||
templateContentsOwnerTable.set(doc, d);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
function extractContent(templateElement) {
|
||||
// templateElement is not a wrapper here.
|
||||
var doc = getTemplateContentsOwner(templateElement.ownerDocument);
|
||||
var df = unwrap(doc.createDocumentFragment());
|
||||
var child;
|
||||
while (child = templateElement.firstChild) {
|
||||
df.appendChild(child);
|
||||
}
|
||||
return df;
|
||||
}
|
||||
|
||||
var OriginalHTMLTemplateElement = window.HTMLTemplateElement;
|
||||
|
||||
function HTMLTemplateElement(node) {
|
||||
HTMLElement.call(this, node);
|
||||
if (!OriginalHTMLTemplateElement) {
|
||||
var content = extractContent(node);
|
||||
contentTable.set(this, wrap(content));
|
||||
}
|
||||
}
|
||||
HTMLTemplateElement.prototype = Object.create(HTMLElement.prototype);
|
||||
|
||||
mixin(HTMLTemplateElement.prototype, {
|
||||
constructor: HTMLTemplateElement,
|
||||
get content() {
|
||||
if (OriginalHTMLTemplateElement)
|
||||
return wrap(unsafeUnwrap(this).content);
|
||||
return contentTable.get(this);
|
||||
},
|
||||
|
||||
// TODO(arv): cloneNode needs to clone content.
|
||||
|
||||
});
|
||||
|
||||
if (OriginalHTMLTemplateElement)
|
||||
registerWrapper(OriginalHTMLTemplateElement, HTMLTemplateElement);
|
||||
|
||||
scope.wrappers.HTMLTemplateElement = HTMLTemplateElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
31
src/ShadowDOM/wrappers/HTMLUnknownElement.js
Normal file
31
src/ShadowDOM/wrappers/HTMLUnknownElement.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLContentElement = scope.wrappers.HTMLContentElement;
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var HTMLShadowElement = scope.wrappers.HTMLShadowElement;
|
||||
var HTMLTemplateElement = scope.wrappers.HTMLTemplateElement;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
|
||||
var OriginalHTMLUnknownElement = window.HTMLUnknownElement;
|
||||
|
||||
function HTMLUnknownElement(node) {
|
||||
switch (node.localName) {
|
||||
case 'content':
|
||||
return new HTMLContentElement(node);
|
||||
case 'shadow':
|
||||
return new HTMLShadowElement(node);
|
||||
case 'template':
|
||||
return new HTMLTemplateElement(node);
|
||||
}
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
HTMLUnknownElement.prototype = Object.create(HTMLElement.prototype);
|
||||
registerWrapper(OriginalHTMLUnknownElement, HTMLUnknownElement);
|
||||
scope.wrappers.HTMLUnknownElement = HTMLUnknownElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
739
src/ShadowDOM/wrappers/Node.js
Normal file
739
src/ShadowDOM/wrappers/Node.js
Normal file
@@ -0,0 +1,739 @@
|
||||
/**
|
||||
* 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var EventTarget = scope.wrappers.EventTarget;
|
||||
var NodeList = scope.wrappers.NodeList;
|
||||
var TreeScope = scope.TreeScope;
|
||||
var assert = scope.assert;
|
||||
var defineWrapGetter = scope.defineWrapGetter;
|
||||
var enqueueMutation = scope.enqueueMutation;
|
||||
var getTreeScope = scope.getTreeScope;
|
||||
var isWrapper = scope.isWrapper;
|
||||
var mixin = scope.mixin;
|
||||
var registerTransientObservers = scope.registerTransientObservers;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var setTreeScope = scope.setTreeScope;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var unwrap = scope.unwrap;
|
||||
var unwrapIfNeeded = scope.unwrapIfNeeded;
|
||||
var wrap = scope.wrap;
|
||||
var wrapIfNeeded = scope.wrapIfNeeded;
|
||||
var wrappers = scope.wrappers;
|
||||
|
||||
function assertIsNodeWrapper(node) {
|
||||
assert(node instanceof Node);
|
||||
}
|
||||
|
||||
function createOneElementNodeList(node) {
|
||||
var nodes = new NodeList();
|
||||
nodes[0] = node;
|
||||
nodes.length = 1;
|
||||
return nodes;
|
||||
}
|
||||
|
||||
var surpressMutations = false;
|
||||
|
||||
/**
|
||||
* Called before node is inserted into a node to enqueue its removal from its
|
||||
* old parent.
|
||||
* @param {!Node} node The node that is about to be removed.
|
||||
* @param {!Node} parent The parent node that the node is being removed from.
|
||||
* @param {!NodeList} nodes The collected nodes.
|
||||
*/
|
||||
function enqueueRemovalForInsertedNodes(node, parent, nodes) {
|
||||
enqueueMutation(parent, 'childList', {
|
||||
removedNodes: nodes,
|
||||
previousSibling: node.previousSibling,
|
||||
nextSibling: node.nextSibling
|
||||
});
|
||||
}
|
||||
|
||||
function enqueueRemovalForInsertedDocumentFragment(df, nodes) {
|
||||
enqueueMutation(df, 'childList', {
|
||||
removedNodes: nodes
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects nodes from a DocumentFragment or a Node for removal followed
|
||||
* by an insertion.
|
||||
*
|
||||
* This updates the internal pointers for node, previousNode and nextNode.
|
||||
*/
|
||||
function collectNodes(node, parentNode, previousNode, nextNode) {
|
||||
if (node instanceof DocumentFragment) {
|
||||
var nodes = collectNodesForDocumentFragment(node);
|
||||
|
||||
// The extra loop is to work around bugs with DocumentFragments in IE.
|
||||
surpressMutations = true;
|
||||
for (var i = nodes.length - 1; i >= 0; i--) {
|
||||
node.removeChild(nodes[i]);
|
||||
nodes[i].parentNode_ = parentNode;
|
||||
}
|
||||
surpressMutations = false;
|
||||
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
nodes[i].previousSibling_ = nodes[i - 1] || previousNode;
|
||||
nodes[i].nextSibling_ = nodes[i + 1] || nextNode;
|
||||
}
|
||||
|
||||
if (previousNode)
|
||||
previousNode.nextSibling_ = nodes[0];
|
||||
if (nextNode)
|
||||
nextNode.previousSibling_ = nodes[nodes.length - 1];
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
var nodes = createOneElementNodeList(node);
|
||||
var oldParent = node.parentNode;
|
||||
if (oldParent) {
|
||||
// This will enqueue the mutation record for the removal as needed.
|
||||
oldParent.removeChild(node);
|
||||
}
|
||||
|
||||
node.parentNode_ = parentNode;
|
||||
node.previousSibling_ = previousNode;
|
||||
node.nextSibling_ = nextNode;
|
||||
if (previousNode)
|
||||
previousNode.nextSibling_ = node;
|
||||
if (nextNode)
|
||||
nextNode.previousSibling_ = node;
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function collectNodesNative(node) {
|
||||
if (node instanceof DocumentFragment)
|
||||
return collectNodesForDocumentFragment(node);
|
||||
|
||||
var nodes = createOneElementNodeList(node);
|
||||
var oldParent = node.parentNode;
|
||||
if (oldParent)
|
||||
enqueueRemovalForInsertedNodes(node, oldParent, nodes);
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function collectNodesForDocumentFragment(node) {
|
||||
var nodes = new NodeList();
|
||||
var i = 0;
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
nodes[i++] = child;
|
||||
}
|
||||
nodes.length = i;
|
||||
enqueueRemovalForInsertedDocumentFragment(node, nodes);
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function snapshotNodeList(nodeList) {
|
||||
// NodeLists are not live at the moment so just return the same object.
|
||||
return nodeList;
|
||||
}
|
||||
|
||||
// http://dom.spec.whatwg.org/#node-is-inserted
|
||||
function nodeWasAdded(node, treeScope) {
|
||||
setTreeScope(node, treeScope);
|
||||
node.nodeIsInserted_();
|
||||
}
|
||||
|
||||
function nodesWereAdded(nodes, parent) {
|
||||
var treeScope = getTreeScope(parent);
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
nodeWasAdded(nodes[i], treeScope);
|
||||
}
|
||||
}
|
||||
|
||||
// http://dom.spec.whatwg.org/#node-is-removed
|
||||
function nodeWasRemoved(node) {
|
||||
setTreeScope(node, new TreeScope(node, null));
|
||||
}
|
||||
|
||||
function nodesWereRemoved(nodes) {
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
nodeWasRemoved(nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSameOwnerDocument(parent, child) {
|
||||
var ownerDoc = parent.nodeType === Node.DOCUMENT_NODE ?
|
||||
parent : parent.ownerDocument;
|
||||
if (ownerDoc !== child.ownerDocument)
|
||||
ownerDoc.adoptNode(child);
|
||||
}
|
||||
|
||||
function adoptNodesIfNeeded(owner, nodes) {
|
||||
if (!nodes.length)
|
||||
return;
|
||||
|
||||
var ownerDoc = owner.ownerDocument;
|
||||
|
||||
// All nodes have the same ownerDocument when we get here.
|
||||
if (ownerDoc === nodes[0].ownerDocument)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
scope.adoptNodeNoRemove(nodes[i], ownerDoc);
|
||||
}
|
||||
}
|
||||
|
||||
function unwrapNodesForInsertion(owner, nodes) {
|
||||
adoptNodesIfNeeded(owner, nodes);
|
||||
var length = nodes.length;
|
||||
|
||||
if (length === 1)
|
||||
return unwrap(nodes[0]);
|
||||
|
||||
var df = unwrap(owner.ownerDocument.createDocumentFragment());
|
||||
for (var i = 0; i < length; i++) {
|
||||
df.appendChild(unwrap(nodes[i]));
|
||||
}
|
||||
return df;
|
||||
}
|
||||
|
||||
function clearChildNodes(wrapper) {
|
||||
if (wrapper.firstChild_ !== undefined) {
|
||||
var child = wrapper.firstChild_;
|
||||
while (child) {
|
||||
var tmp = child;
|
||||
child = child.nextSibling_;
|
||||
tmp.parentNode_ = tmp.previousSibling_ = tmp.nextSibling_ = undefined;
|
||||
}
|
||||
}
|
||||
wrapper.firstChild_ = wrapper.lastChild_ = undefined;
|
||||
}
|
||||
|
||||
function removeAllChildNodes(wrapper) {
|
||||
if (wrapper.invalidateShadowRenderer()) {
|
||||
var childWrapper = wrapper.firstChild;
|
||||
while (childWrapper) {
|
||||
assert(childWrapper.parentNode === wrapper);
|
||||
var nextSibling = childWrapper.nextSibling;
|
||||
var childNode = unwrap(childWrapper);
|
||||
var parentNode = childNode.parentNode;
|
||||
if (parentNode)
|
||||
originalRemoveChild.call(parentNode, childNode);
|
||||
childWrapper.previousSibling_ = childWrapper.nextSibling_ =
|
||||
childWrapper.parentNode_ = null;
|
||||
childWrapper = nextSibling;
|
||||
}
|
||||
wrapper.firstChild_ = wrapper.lastChild_ = null;
|
||||
} else {
|
||||
var node = unwrap(wrapper);
|
||||
var child = node.firstChild;
|
||||
var nextSibling;
|
||||
while (child) {
|
||||
nextSibling = child.nextSibling;
|
||||
originalRemoveChild.call(node, child);
|
||||
child = nextSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function invalidateParent(node) {
|
||||
var p = node.parentNode;
|
||||
return p && p.invalidateShadowRenderer();
|
||||
}
|
||||
|
||||
function cleanupNodes(nodes) {
|
||||
for (var i = 0, n; i < nodes.length; i++) {
|
||||
n = nodes[i];
|
||||
n.parentNode.removeChild(n);
|
||||
}
|
||||
}
|
||||
|
||||
var originalImportNode = document.importNode;
|
||||
var originalCloneNode = window.Node.prototype.cloneNode;
|
||||
|
||||
function cloneNode(node, deep, opt_doc) {
|
||||
var clone;
|
||||
if (opt_doc)
|
||||
clone = wrap(originalImportNode.call(opt_doc, unsafeUnwrap(node), false));
|
||||
else
|
||||
clone = wrap(originalCloneNode.call(unsafeUnwrap(node), false));
|
||||
|
||||
if (deep) {
|
||||
for (var child = node.firstChild; child; child = child.nextSibling) {
|
||||
clone.appendChild(cloneNode(child, true, opt_doc));
|
||||
}
|
||||
|
||||
if (node instanceof wrappers.HTMLTemplateElement) {
|
||||
var cloneContent = clone.content;
|
||||
for (var child = node.content.firstChild;
|
||||
child;
|
||||
child = child.nextSibling) {
|
||||
cloneContent.appendChild(cloneNode(child, true, opt_doc));
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(arv): Some HTML elements also clone other data like value.
|
||||
return clone;
|
||||
}
|
||||
|
||||
function contains(self, child) {
|
||||
if (!child || getTreeScope(self) !== getTreeScope(child))
|
||||
return false;
|
||||
|
||||
for (var node = child; node; node = node.parentNode) {
|
||||
if (node === self)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var OriginalNode = window.Node;
|
||||
|
||||
/**
|
||||
* This represents a wrapper of a native DOM node.
|
||||
* @param {!Node} original The original DOM node, aka, the visual DOM node.
|
||||
* @constructor
|
||||
* @extends {EventTarget}
|
||||
*/
|
||||
function Node(original) {
|
||||
assert(original instanceof OriginalNode);
|
||||
|
||||
EventTarget.call(this, original);
|
||||
|
||||
// These properties are used to override the visual references with the
|
||||
// logical ones. If the value is undefined it means that the logical is the
|
||||
// same as the visual.
|
||||
|
||||
/**
|
||||
* @type {Node|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.parentNode_ = undefined;
|
||||
|
||||
/**
|
||||
* @type {Node|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.firstChild_ = undefined;
|
||||
|
||||
/**
|
||||
* @type {Node|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.lastChild_ = undefined;
|
||||
|
||||
/**
|
||||
* @type {Node|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.nextSibling_ = undefined;
|
||||
|
||||
/**
|
||||
* @type {Node|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.previousSibling_ = undefined;
|
||||
|
||||
this.treeScope_ = undefined;
|
||||
}
|
||||
|
||||
var OriginalDocumentFragment = window.DocumentFragment;
|
||||
var originalAppendChild = OriginalNode.prototype.appendChild;
|
||||
var originalCompareDocumentPosition =
|
||||
OriginalNode.prototype.compareDocumentPosition;
|
||||
var originalInsertBefore = OriginalNode.prototype.insertBefore;
|
||||
var originalRemoveChild = OriginalNode.prototype.removeChild;
|
||||
var originalReplaceChild = OriginalNode.prototype.replaceChild;
|
||||
|
||||
var isIe = /Trident/.test(navigator.userAgent);
|
||||
|
||||
var removeChildOriginalHelper = isIe ?
|
||||
function(parent, child) {
|
||||
try {
|
||||
originalRemoveChild.call(parent, child);
|
||||
} catch (ex) {
|
||||
if (!(parent instanceof OriginalDocumentFragment))
|
||||
throw ex;
|
||||
}
|
||||
} :
|
||||
function(parent, child) {
|
||||
originalRemoveChild.call(parent, child);
|
||||
};
|
||||
|
||||
Node.prototype = Object.create(EventTarget.prototype);
|
||||
mixin(Node.prototype, {
|
||||
appendChild: function(childWrapper) {
|
||||
return this.insertBefore(childWrapper, null);
|
||||
},
|
||||
|
||||
insertBefore: function(childWrapper, refWrapper) {
|
||||
assertIsNodeWrapper(childWrapper);
|
||||
|
||||
var refNode;
|
||||
if (refWrapper) {
|
||||
if (isWrapper(refWrapper)) {
|
||||
refNode = unwrap(refWrapper);
|
||||
} else {
|
||||
refNode = refWrapper;
|
||||
refWrapper = wrap(refNode);
|
||||
}
|
||||
} else {
|
||||
refWrapper = null;
|
||||
refNode = null;
|
||||
}
|
||||
|
||||
refWrapper && assert(refWrapper.parentNode === this);
|
||||
|
||||
var nodes;
|
||||
var previousNode =
|
||||
refWrapper ? refWrapper.previousSibling : this.lastChild;
|
||||
|
||||
var useNative = !this.invalidateShadowRenderer() &&
|
||||
!invalidateParent(childWrapper);
|
||||
|
||||
if (useNative)
|
||||
nodes = collectNodesNative(childWrapper);
|
||||
else
|
||||
nodes = collectNodes(childWrapper, this, previousNode, refWrapper);
|
||||
|
||||
if (useNative) {
|
||||
ensureSameOwnerDocument(this, childWrapper);
|
||||
clearChildNodes(this);
|
||||
originalInsertBefore.call(unsafeUnwrap(this), unwrap(childWrapper), refNode);
|
||||
} else {
|
||||
if (!previousNode)
|
||||
this.firstChild_ = nodes[0];
|
||||
if (!refWrapper) {
|
||||
this.lastChild_ = nodes[nodes.length - 1];
|
||||
if (this.firstChild_ === undefined)
|
||||
this.firstChild_ = this.firstChild;
|
||||
}
|
||||
|
||||
var parentNode = refNode ? refNode.parentNode : unsafeUnwrap(this);
|
||||
|
||||
// insertBefore refWrapper no matter what the parent is?
|
||||
if (parentNode) {
|
||||
originalInsertBefore.call(parentNode,
|
||||
unwrapNodesForInsertion(this, nodes), refNode);
|
||||
} else {
|
||||
adoptNodesIfNeeded(this, nodes);
|
||||
}
|
||||
}
|
||||
|
||||
enqueueMutation(this, 'childList', {
|
||||
addedNodes: nodes,
|
||||
nextSibling: refWrapper,
|
||||
previousSibling: previousNode
|
||||
});
|
||||
|
||||
nodesWereAdded(nodes, this);
|
||||
|
||||
return childWrapper;
|
||||
},
|
||||
|
||||
removeChild: function(childWrapper) {
|
||||
assertIsNodeWrapper(childWrapper);
|
||||
if (childWrapper.parentNode !== this) {
|
||||
// IE has invalid DOM trees at times.
|
||||
var found = false;
|
||||
var childNodes = this.childNodes;
|
||||
for (var ieChild = this.firstChild; ieChild;
|
||||
ieChild = ieChild.nextSibling) {
|
||||
if (ieChild === childWrapper) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// TODO(arv): DOMException
|
||||
throw new Error('NotFoundError');
|
||||
}
|
||||
}
|
||||
|
||||
var childNode = unwrap(childWrapper);
|
||||
var childWrapperNextSibling = childWrapper.nextSibling;
|
||||
var childWrapperPreviousSibling = childWrapper.previousSibling;
|
||||
|
||||
if (this.invalidateShadowRenderer()) {
|
||||
// We need to remove the real node from the DOM before updating the
|
||||
// pointers. This is so that that mutation event is dispatched before
|
||||
// the pointers have changed.
|
||||
var thisFirstChild = this.firstChild;
|
||||
var thisLastChild = this.lastChild;
|
||||
|
||||
var parentNode = childNode.parentNode;
|
||||
if (parentNode)
|
||||
removeChildOriginalHelper(parentNode, childNode);
|
||||
|
||||
if (thisFirstChild === childWrapper)
|
||||
this.firstChild_ = childWrapperNextSibling;
|
||||
if (thisLastChild === childWrapper)
|
||||
this.lastChild_ = childWrapperPreviousSibling;
|
||||
if (childWrapperPreviousSibling)
|
||||
childWrapperPreviousSibling.nextSibling_ = childWrapperNextSibling;
|
||||
if (childWrapperNextSibling) {
|
||||
childWrapperNextSibling.previousSibling_ =
|
||||
childWrapperPreviousSibling;
|
||||
}
|
||||
|
||||
childWrapper.previousSibling_ = childWrapper.nextSibling_ =
|
||||
childWrapper.parentNode_ = undefined;
|
||||
} else {
|
||||
clearChildNodes(this);
|
||||
removeChildOriginalHelper(unsafeUnwrap(this), childNode);
|
||||
}
|
||||
|
||||
if (!surpressMutations) {
|
||||
enqueueMutation(this, 'childList', {
|
||||
removedNodes: createOneElementNodeList(childWrapper),
|
||||
nextSibling: childWrapperNextSibling,
|
||||
previousSibling: childWrapperPreviousSibling
|
||||
});
|
||||
}
|
||||
|
||||
registerTransientObservers(this, childWrapper);
|
||||
|
||||
return childWrapper;
|
||||
},
|
||||
|
||||
replaceChild: function(newChildWrapper, oldChildWrapper) {
|
||||
assertIsNodeWrapper(newChildWrapper);
|
||||
|
||||
var oldChildNode;
|
||||
if (isWrapper(oldChildWrapper)) {
|
||||
oldChildNode = unwrap(oldChildWrapper);
|
||||
} else {
|
||||
oldChildNode = oldChildWrapper;
|
||||
oldChildWrapper = wrap(oldChildNode);
|
||||
}
|
||||
|
||||
if (oldChildWrapper.parentNode !== this) {
|
||||
// TODO(arv): DOMException
|
||||
throw new Error('NotFoundError');
|
||||
}
|
||||
|
||||
var nextNode = oldChildWrapper.nextSibling;
|
||||
var previousNode = oldChildWrapper.previousSibling;
|
||||
var nodes;
|
||||
|
||||
var useNative = !this.invalidateShadowRenderer() &&
|
||||
!invalidateParent(newChildWrapper);
|
||||
|
||||
if (useNative) {
|
||||
nodes = collectNodesNative(newChildWrapper);
|
||||
} else {
|
||||
if (nextNode === newChildWrapper)
|
||||
nextNode = newChildWrapper.nextSibling;
|
||||
nodes = collectNodes(newChildWrapper, this, previousNode, nextNode);
|
||||
}
|
||||
|
||||
if (!useNative) {
|
||||
if (this.firstChild === oldChildWrapper)
|
||||
this.firstChild_ = nodes[0];
|
||||
if (this.lastChild === oldChildWrapper)
|
||||
this.lastChild_ = nodes[nodes.length - 1];
|
||||
|
||||
oldChildWrapper.previousSibling_ = oldChildWrapper.nextSibling_ =
|
||||
oldChildWrapper.parentNode_ = undefined;
|
||||
|
||||
// replaceChild no matter what the parent is?
|
||||
if (oldChildNode.parentNode) {
|
||||
originalReplaceChild.call(
|
||||
oldChildNode.parentNode,
|
||||
unwrapNodesForInsertion(this, nodes),
|
||||
oldChildNode);
|
||||
}
|
||||
} else {
|
||||
ensureSameOwnerDocument(this, newChildWrapper);
|
||||
clearChildNodes(this);
|
||||
originalReplaceChild.call(unsafeUnwrap(this), unwrap(newChildWrapper),
|
||||
oldChildNode);
|
||||
}
|
||||
|
||||
enqueueMutation(this, 'childList', {
|
||||
addedNodes: nodes,
|
||||
removedNodes: createOneElementNodeList(oldChildWrapper),
|
||||
nextSibling: nextNode,
|
||||
previousSibling: previousNode
|
||||
});
|
||||
|
||||
nodeWasRemoved(oldChildWrapper);
|
||||
nodesWereAdded(nodes, this);
|
||||
|
||||
return oldChildWrapper;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called after a node was inserted. Subclasses override this to invalidate
|
||||
* the renderer as needed.
|
||||
* @private
|
||||
*/
|
||||
nodeIsInserted_: function() {
|
||||
for (var child = this.firstChild; child; child = child.nextSibling) {
|
||||
child.nodeIsInserted_();
|
||||
}
|
||||
},
|
||||
|
||||
hasChildNodes: function() {
|
||||
return this.firstChild !== null;
|
||||
},
|
||||
|
||||
/** @type {Node} */
|
||||
get parentNode() {
|
||||
// If the parentNode has not been overridden, use the original parentNode.
|
||||
return this.parentNode_ !== undefined ?
|
||||
this.parentNode_ : wrap(unsafeUnwrap(this).parentNode);
|
||||
},
|
||||
|
||||
/** @type {Node} */
|
||||
get firstChild() {
|
||||
return this.firstChild_ !== undefined ?
|
||||
this.firstChild_ : wrap(unsafeUnwrap(this).firstChild);
|
||||
},
|
||||
|
||||
/** @type {Node} */
|
||||
get lastChild() {
|
||||
return this.lastChild_ !== undefined ?
|
||||
this.lastChild_ : wrap(unsafeUnwrap(this).lastChild);
|
||||
},
|
||||
|
||||
/** @type {Node} */
|
||||
get nextSibling() {
|
||||
return this.nextSibling_ !== undefined ?
|
||||
this.nextSibling_ : wrap(unsafeUnwrap(this).nextSibling);
|
||||
},
|
||||
|
||||
/** @type {Node} */
|
||||
get previousSibling() {
|
||||
return this.previousSibling_ !== undefined ?
|
||||
this.previousSibling_ : wrap(unsafeUnwrap(this).previousSibling);
|
||||
},
|
||||
|
||||
get parentElement() {
|
||||
var p = this.parentNode;
|
||||
while (p && p.nodeType !== Node.ELEMENT_NODE) {
|
||||
p = p.parentNode;
|
||||
}
|
||||
return p;
|
||||
},
|
||||
|
||||
get textContent() {
|
||||
// TODO(arv): This should fallback to unsafeUnwrap(this).textContent if there
|
||||
// are no shadow trees below or above the context node.
|
||||
var s = '';
|
||||
for (var child = this.firstChild; child; child = child.nextSibling) {
|
||||
if (child.nodeType != Node.COMMENT_NODE) {
|
||||
s += child.textContent;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
},
|
||||
set textContent(textContent) {
|
||||
if (textContent == null) textContent = '';
|
||||
var removedNodes = snapshotNodeList(this.childNodes);
|
||||
|
||||
if (this.invalidateShadowRenderer()) {
|
||||
removeAllChildNodes(this);
|
||||
if (textContent !== '') {
|
||||
var textNode = unsafeUnwrap(this).ownerDocument.createTextNode(textContent);
|
||||
this.appendChild(textNode);
|
||||
}
|
||||
} else {
|
||||
clearChildNodes(this);
|
||||
unsafeUnwrap(this).textContent = textContent;
|
||||
}
|
||||
|
||||
var addedNodes = snapshotNodeList(this.childNodes);
|
||||
|
||||
enqueueMutation(this, 'childList', {
|
||||
addedNodes: addedNodes,
|
||||
removedNodes: removedNodes
|
||||
});
|
||||
|
||||
nodesWereRemoved(removedNodes);
|
||||
nodesWereAdded(addedNodes, this);
|
||||
},
|
||||
|
||||
get childNodes() {
|
||||
var wrapperList = new NodeList();
|
||||
var i = 0;
|
||||
for (var child = this.firstChild; child; child = child.nextSibling) {
|
||||
wrapperList[i++] = child;
|
||||
}
|
||||
wrapperList.length = i;
|
||||
return wrapperList;
|
||||
},
|
||||
|
||||
cloneNode: function(deep) {
|
||||
return cloneNode(this, deep);
|
||||
},
|
||||
|
||||
contains: function(child) {
|
||||
return contains(this, wrapIfNeeded(child));
|
||||
},
|
||||
|
||||
compareDocumentPosition: function(otherNode) {
|
||||
// This only wraps, it therefore only operates on the composed DOM and not
|
||||
// the logical DOM.
|
||||
return originalCompareDocumentPosition.call(unsafeUnwrap(this),
|
||||
unwrapIfNeeded(otherNode));
|
||||
},
|
||||
|
||||
normalize: function() {
|
||||
var nodes = snapshotNodeList(this.childNodes);
|
||||
var remNodes = [];
|
||||
var s = '';
|
||||
var modNode;
|
||||
|
||||
for (var i = 0, n; i < nodes.length; i++) {
|
||||
n = nodes[i];
|
||||
if (n.nodeType === Node.TEXT_NODE) {
|
||||
if (!modNode && !n.data.length)
|
||||
this.removeNode(n);
|
||||
else if (!modNode)
|
||||
modNode = n;
|
||||
else {
|
||||
s += n.data;
|
||||
remNodes.push(n);
|
||||
}
|
||||
} else {
|
||||
if (modNode && remNodes.length) {
|
||||
modNode.data += s;
|
||||
cleanupNodes(remNodes);
|
||||
}
|
||||
remNodes = [];
|
||||
s = '';
|
||||
modNode = null;
|
||||
if (n.childNodes.length)
|
||||
n.normalize();
|
||||
}
|
||||
}
|
||||
|
||||
// handle case where >1 text nodes are the last children
|
||||
if (modNode && remNodes.length) {
|
||||
modNode.data += s;
|
||||
cleanupNodes(remNodes);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
defineWrapGetter(Node, 'ownerDocument');
|
||||
|
||||
// We use a DocumentFragment as a base and then delete the properties of
|
||||
// DocumentFragment.prototype from the wrapper Node. Since delete makes
|
||||
// objects slow in some JS engines we recreate the prototype object.
|
||||
registerWrapper(OriginalNode, Node, document.createDocumentFragment());
|
||||
delete Node.prototype.querySelector;
|
||||
delete Node.prototype.querySelectorAll;
|
||||
Node.prototype = mixin(Object.create(EventTarget.prototype), Node.prototype);
|
||||
|
||||
scope.cloneNode = cloneNode;
|
||||
scope.nodeWasAdded = nodeWasAdded;
|
||||
scope.nodeWasRemoved = nodeWasRemoved;
|
||||
scope.nodesWereAdded = nodesWereAdded;
|
||||
scope.nodesWereRemoved = nodesWereRemoved;
|
||||
scope.originalInsertBefore = originalInsertBefore;
|
||||
scope.originalRemoveChild = originalRemoveChild;
|
||||
scope.snapshotNodeList = snapshotNodeList;
|
||||
scope.wrappers.Node = Node;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
50
src/ShadowDOM/wrappers/NodeList.js
Normal file
50
src/ShadowDOM/wrappers/NodeList.js
Normal file
@@ -0,0 +1,50 @@
|
||||
// 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var nonEnumDescriptor = {enumerable: false};
|
||||
|
||||
function nonEnum(obj, prop) {
|
||||
Object.defineProperty(obj, prop, nonEnumDescriptor);
|
||||
}
|
||||
|
||||
function NodeList() {
|
||||
this.length = 0;
|
||||
nonEnum(this, 'length');
|
||||
}
|
||||
NodeList.prototype = {
|
||||
item: function(index) {
|
||||
return this[index];
|
||||
}
|
||||
};
|
||||
nonEnum(NodeList.prototype, 'item');
|
||||
|
||||
function wrapNodeList(list) {
|
||||
if (list == null)
|
||||
return list;
|
||||
var wrapperList = new NodeList();
|
||||
for (var i = 0, length = list.length; i < length; i++) {
|
||||
wrapperList[i] = wrap(list[i]);
|
||||
}
|
||||
wrapperList.length = length;
|
||||
return wrapperList;
|
||||
}
|
||||
|
||||
function addWrapNodeListMethod(wrapperConstructor, name) {
|
||||
wrapperConstructor.prototype[name] = function() {
|
||||
return wrapNodeList(
|
||||
unsafeUnwrap(this)[name].apply(unsafeUnwrap(this), arguments));
|
||||
};
|
||||
}
|
||||
|
||||
scope.wrappers.NodeList = NodeList;
|
||||
scope.addWrapNodeListMethod = addWrapNodeListMethod;
|
||||
scope.wrapNodeList = wrapNodeList;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
97
src/ShadowDOM/wrappers/Range.js
Normal file
97
src/ShadowDOM/wrappers/Range.js
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var setWrapper = scope.setWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var unwrap = scope.unwrap;
|
||||
var unwrapIfNeeded = scope.unwrapIfNeeded;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var OriginalRange = window.Range;
|
||||
|
||||
function Range(impl) {
|
||||
setWrapper(impl, this);
|
||||
}
|
||||
Range.prototype = {
|
||||
get startContainer() {
|
||||
return wrap(unsafeUnwrap(this).startContainer);
|
||||
},
|
||||
get endContainer() {
|
||||
return wrap(unsafeUnwrap(this).endContainer);
|
||||
},
|
||||
get commonAncestorContainer() {
|
||||
return wrap(unsafeUnwrap(this).commonAncestorContainer);
|
||||
},
|
||||
setStart: function(refNode,offset) {
|
||||
unsafeUnwrap(this).setStart(unwrapIfNeeded(refNode), offset);
|
||||
},
|
||||
setEnd: function(refNode,offset) {
|
||||
unsafeUnwrap(this).setEnd(unwrapIfNeeded(refNode), offset);
|
||||
},
|
||||
setStartBefore: function(refNode) {
|
||||
unsafeUnwrap(this).setStartBefore(unwrapIfNeeded(refNode));
|
||||
},
|
||||
setStartAfter: function(refNode) {
|
||||
unsafeUnwrap(this).setStartAfter(unwrapIfNeeded(refNode));
|
||||
},
|
||||
setEndBefore: function(refNode) {
|
||||
unsafeUnwrap(this).setEndBefore(unwrapIfNeeded(refNode));
|
||||
},
|
||||
setEndAfter: function(refNode) {
|
||||
unsafeUnwrap(this).setEndAfter(unwrapIfNeeded(refNode));
|
||||
},
|
||||
selectNode: function(refNode) {
|
||||
unsafeUnwrap(this).selectNode(unwrapIfNeeded(refNode));
|
||||
},
|
||||
selectNodeContents: function(refNode) {
|
||||
unsafeUnwrap(this).selectNodeContents(unwrapIfNeeded(refNode));
|
||||
},
|
||||
compareBoundaryPoints: function(how, sourceRange) {
|
||||
return unsafeUnwrap(this).compareBoundaryPoints(how, unwrap(sourceRange));
|
||||
},
|
||||
extractContents: function() {
|
||||
return wrap(unsafeUnwrap(this).extractContents());
|
||||
},
|
||||
cloneContents: function() {
|
||||
return wrap(unsafeUnwrap(this).cloneContents());
|
||||
},
|
||||
insertNode: function(node) {
|
||||
unsafeUnwrap(this).insertNode(unwrapIfNeeded(node));
|
||||
},
|
||||
surroundContents: function(newParent) {
|
||||
unsafeUnwrap(this).surroundContents(unwrapIfNeeded(newParent));
|
||||
},
|
||||
cloneRange: function() {
|
||||
return wrap(unsafeUnwrap(this).cloneRange());
|
||||
},
|
||||
isPointInRange: function(node, offset) {
|
||||
return unsafeUnwrap(this).isPointInRange(unwrapIfNeeded(node), offset);
|
||||
},
|
||||
comparePoint: function(node, offset) {
|
||||
return unsafeUnwrap(this).comparePoint(unwrapIfNeeded(node), offset);
|
||||
},
|
||||
intersectsNode: function(node) {
|
||||
return unsafeUnwrap(this).intersectsNode(unwrapIfNeeded(node));
|
||||
},
|
||||
toString: function() {
|
||||
return unsafeUnwrap(this).toString();
|
||||
}
|
||||
};
|
||||
|
||||
// IE9 does not have createContextualFragment.
|
||||
if (OriginalRange.prototype.createContextualFragment) {
|
||||
Range.prototype.createContextualFragment = function(html) {
|
||||
return wrap(unsafeUnwrap(this).createContextualFragment(html));
|
||||
};
|
||||
}
|
||||
|
||||
registerWrapper(window.Range, Range, document.createRange());
|
||||
|
||||
scope.wrappers.Range = Range;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
28
src/ShadowDOM/wrappers/SVGElement.js
Normal file
28
src/ShadowDOM/wrappers/SVGElement.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var Element = scope.wrappers.Element;
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var registerObject = scope.registerObject;
|
||||
|
||||
var SVG_NS = 'http://www.w3.org/2000/svg';
|
||||
var svgTitleElement = document.createElementNS(SVG_NS, 'title');
|
||||
var SVGTitleElement = registerObject(svgTitleElement);
|
||||
var SVGElement = Object.getPrototypeOf(SVGTitleElement.prototype).constructor;
|
||||
|
||||
// IE11 does not have classList for SVG elements. The spec says that classList
|
||||
// is an accessor on Element, but IE11 puts classList on HTMLElement, leaving
|
||||
// SVGElement without a classList property. We therefore move the accessor for
|
||||
// IE11.
|
||||
if (!('classList' in svgTitleElement)) {
|
||||
var descr = Object.getOwnPropertyDescriptor(Element.prototype, 'classList');
|
||||
Object.defineProperty(HTMLElement.prototype, 'classList', descr);
|
||||
delete Element.prototype.classList;
|
||||
}
|
||||
|
||||
scope.wrappers.SVGElement = SVGElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
68
src/ShadowDOM/wrappers/SVGElementInstance.js
Normal file
68
src/ShadowDOM/wrappers/SVGElementInstance.js
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var EventTarget = scope.wrappers.EventTarget;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var OriginalSVGElementInstance = window.SVGElementInstance;
|
||||
if (!OriginalSVGElementInstance)
|
||||
return;
|
||||
|
||||
function SVGElementInstance(impl) {
|
||||
EventTarget.call(this, impl);
|
||||
}
|
||||
|
||||
SVGElementInstance.prototype = Object.create(EventTarget.prototype);
|
||||
mixin(SVGElementInstance.prototype, {
|
||||
/** @type {SVGElement} */
|
||||
get correspondingElement() {
|
||||
return wrap(unsafeUnwrap(this).correspondingElement);
|
||||
},
|
||||
|
||||
/** @type {SVGUseElement} */
|
||||
get correspondingUseElement() {
|
||||
return wrap(unsafeUnwrap(this).correspondingUseElement);
|
||||
},
|
||||
|
||||
/** @type {SVGElementInstance} */
|
||||
get parentNode() {
|
||||
return wrap(unsafeUnwrap(this).parentNode);
|
||||
},
|
||||
|
||||
/** @type {SVGElementInstanceList} */
|
||||
get childNodes() {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/** @type {SVGElementInstance} */
|
||||
get firstChild() {
|
||||
return wrap(unsafeUnwrap(this).firstChild);
|
||||
},
|
||||
|
||||
/** @type {SVGElementInstance} */
|
||||
get lastChild() {
|
||||
return wrap(unsafeUnwrap(this).lastChild);
|
||||
},
|
||||
|
||||
/** @type {SVGElementInstance} */
|
||||
get previousSibling() {
|
||||
return wrap(unsafeUnwrap(this).previousSibling);
|
||||
},
|
||||
|
||||
/** @type {SVGElementInstance} */
|
||||
get nextSibling() {
|
||||
return wrap(unsafeUnwrap(this).nextSibling);
|
||||
}
|
||||
});
|
||||
|
||||
registerWrapper(OriginalSVGElementInstance, SVGElementInstance);
|
||||
|
||||
scope.wrappers.SVGElementInstance = SVGElementInstance;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
46
src/ShadowDOM/wrappers/SVGUseElement.js
Normal file
46
src/ShadowDOM/wrappers/SVGUseElement.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var unwrap = scope.unwrap;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var OriginalSVGUseElement = window.SVGUseElement;
|
||||
|
||||
// IE uses SVGElement as parent interface, SVG2 (Blink & Gecko) uses
|
||||
// SVGGraphicsElement. Use the <g> element to get the right prototype.
|
||||
|
||||
var SVG_NS = 'http://www.w3.org/2000/svg';
|
||||
var gWrapper = wrap(document.createElementNS(SVG_NS, 'g'));
|
||||
var useElement = document.createElementNS(SVG_NS, 'use');
|
||||
var SVGGElement = gWrapper.constructor;
|
||||
var parentInterfacePrototype = Object.getPrototypeOf(SVGGElement.prototype);
|
||||
var parentInterface = parentInterfacePrototype.constructor;
|
||||
|
||||
function SVGUseElement(impl) {
|
||||
parentInterface.call(this, impl);
|
||||
}
|
||||
|
||||
SVGUseElement.prototype = Object.create(parentInterfacePrototype);
|
||||
|
||||
// Firefox does not expose instanceRoot.
|
||||
if ('instanceRoot' in useElement) {
|
||||
mixin(SVGUseElement.prototype, {
|
||||
get instanceRoot() {
|
||||
return wrap(unwrap(this).instanceRoot);
|
||||
},
|
||||
get animatedInstanceRoot() {
|
||||
return wrap(unwrap(this).animatedInstanceRoot);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
registerWrapper(OriginalSVGUseElement, SVGUseElement, useElement);
|
||||
|
||||
scope.wrappers.SVGUseElement = SVGUseElement;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
69
src/ShadowDOM/wrappers/Selection.js
Normal file
69
src/ShadowDOM/wrappers/Selection.js
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var setWrapper = scope.setWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var unwrap = scope.unwrap;
|
||||
var unwrapIfNeeded = scope.unwrapIfNeeded;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var OriginalSelection = window.Selection;
|
||||
|
||||
function Selection(impl) {
|
||||
setWrapper(impl, this);
|
||||
}
|
||||
Selection.prototype = {
|
||||
get anchorNode() {
|
||||
return wrap(unsafeUnwrap(this).anchorNode);
|
||||
},
|
||||
get focusNode() {
|
||||
return wrap(unsafeUnwrap(this).focusNode);
|
||||
},
|
||||
addRange: function(range) {
|
||||
unsafeUnwrap(this).addRange(unwrap(range));
|
||||
},
|
||||
collapse: function(node, index) {
|
||||
unsafeUnwrap(this).collapse(unwrapIfNeeded(node), index);
|
||||
},
|
||||
containsNode: function(node, allowPartial) {
|
||||
return unsafeUnwrap(this).containsNode(unwrapIfNeeded(node), allowPartial);
|
||||
},
|
||||
extend: function(node, offset) {
|
||||
unsafeUnwrap(this).extend(unwrapIfNeeded(node), offset);
|
||||
},
|
||||
getRangeAt: function(index) {
|
||||
return wrap(unsafeUnwrap(this).getRangeAt(index));
|
||||
},
|
||||
removeRange: function(range) {
|
||||
unsafeUnwrap(this).removeRange(unwrap(range));
|
||||
},
|
||||
selectAllChildren: function(node) {
|
||||
unsafeUnwrap(this).selectAllChildren(unwrapIfNeeded(node));
|
||||
},
|
||||
toString: function() {
|
||||
return unsafeUnwrap(this).toString();
|
||||
}
|
||||
};
|
||||
|
||||
// WebKit extensions. Not implemented.
|
||||
// readonly attribute Node baseNode;
|
||||
// readonly attribute long baseOffset;
|
||||
// readonly attribute Node extentNode;
|
||||
// readonly attribute long extentOffset;
|
||||
// [RaisesException] void setBaseAndExtent([Default=Undefined] optional Node baseNode,
|
||||
// [Default=Undefined] optional long baseOffset,
|
||||
// [Default=Undefined] optional Node extentNode,
|
||||
// [Default=Undefined] optional long extentOffset);
|
||||
// [RaisesException, ImplementedAs=collapse] void setPosition([Default=Undefined] optional Node node,
|
||||
// [Default=Undefined] optional long offset);
|
||||
|
||||
registerWrapper(window.Selection, Selection, window.getSelection());
|
||||
|
||||
scope.wrappers.Selection = Selection;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
77
src/ShadowDOM/wrappers/ShadowRoot.js
Normal file
77
src/ShadowDOM/wrappers/ShadowRoot.js
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var DocumentFragment = scope.wrappers.DocumentFragment;
|
||||
var TreeScope = scope.TreeScope;
|
||||
var elementFromPoint = scope.elementFromPoint;
|
||||
var getInnerHTML = scope.getInnerHTML;
|
||||
var getTreeScope = scope.getTreeScope;
|
||||
var mixin = scope.mixin;
|
||||
var rewrap = scope.rewrap;
|
||||
var setInnerHTML = scope.setInnerHTML;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var unwrap = scope.unwrap;
|
||||
|
||||
var shadowHostTable = new WeakMap();
|
||||
var nextOlderShadowTreeTable = new WeakMap();
|
||||
|
||||
var spaceCharRe = /[ \t\n\r\f]/;
|
||||
|
||||
function ShadowRoot(hostWrapper) {
|
||||
var node = unwrap(unsafeUnwrap(hostWrapper).ownerDocument.createDocumentFragment());
|
||||
DocumentFragment.call(this, node);
|
||||
|
||||
// createDocumentFragment associates the node with a wrapper
|
||||
// DocumentFragment instance. Override that.
|
||||
rewrap(node, this);
|
||||
|
||||
var oldShadowRoot = hostWrapper.shadowRoot;
|
||||
nextOlderShadowTreeTable.set(this, oldShadowRoot);
|
||||
|
||||
this.treeScope_ =
|
||||
new TreeScope(this, getTreeScope(oldShadowRoot || hostWrapper));
|
||||
|
||||
shadowHostTable.set(this, hostWrapper);
|
||||
}
|
||||
ShadowRoot.prototype = Object.create(DocumentFragment.prototype);
|
||||
mixin(ShadowRoot.prototype, {
|
||||
constructor: ShadowRoot,
|
||||
|
||||
get innerHTML() {
|
||||
return getInnerHTML(this);
|
||||
},
|
||||
set innerHTML(value) {
|
||||
setInnerHTML(this, value);
|
||||
this.invalidateShadowRenderer();
|
||||
},
|
||||
|
||||
get olderShadowRoot() {
|
||||
return nextOlderShadowTreeTable.get(this) || null;
|
||||
},
|
||||
|
||||
get host() {
|
||||
return shadowHostTable.get(this) || null;
|
||||
},
|
||||
|
||||
invalidateShadowRenderer: function() {
|
||||
return shadowHostTable.get(this).invalidateShadowRenderer();
|
||||
},
|
||||
|
||||
elementFromPoint: function(x, y) {
|
||||
return elementFromPoint(this, this.ownerDocument, x, y);
|
||||
},
|
||||
|
||||
getElementById: function(id) {
|
||||
if (spaceCharRe.test(id))
|
||||
return null;
|
||||
return this.querySelector('[id="' + id + '"]');
|
||||
}
|
||||
});
|
||||
|
||||
scope.wrappers.ShadowRoot = ShadowRoot;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
42
src/ShadowDOM/wrappers/Text.js
Normal file
42
src/ShadowDOM/wrappers/Text.js
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var CharacterData = scope.wrappers.CharacterData;
|
||||
var enqueueMutation = scope.enqueueMutation;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
|
||||
function toUInt32(x) {
|
||||
return x >>> 0;
|
||||
}
|
||||
|
||||
var OriginalText = window.Text;
|
||||
|
||||
function Text(node) {
|
||||
CharacterData.call(this, node);
|
||||
}
|
||||
Text.prototype = Object.create(CharacterData.prototype);
|
||||
mixin(Text.prototype, {
|
||||
splitText: function(offset) {
|
||||
offset = toUInt32(offset);
|
||||
var s = this.data;
|
||||
if (offset > s.length)
|
||||
throw new Error('IndexSizeError');
|
||||
var head = s.slice(0, offset);
|
||||
var tail = s.slice(offset);
|
||||
this.data = head;
|
||||
var newTextNode = this.ownerDocument.createTextNode(tail);
|
||||
if (this.parentNode)
|
||||
this.parentNode.insertBefore(newTextNode, this.nextSibling);
|
||||
return newTextNode;
|
||||
}
|
||||
});
|
||||
|
||||
registerWrapper(OriginalText, Text, document.createTextNode(''));
|
||||
|
||||
scope.wrappers.Text = Text;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
126
src/ShadowDOM/wrappers/TouchEvent.js
Normal file
126
src/ShadowDOM/wrappers/TouchEvent.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var UIEvent = scope.wrappers.UIEvent;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var setWrapper = scope.setWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
// TouchEvent is WebKit/Blink only.
|
||||
var OriginalTouchEvent = window.TouchEvent;
|
||||
if (!OriginalTouchEvent)
|
||||
return;
|
||||
|
||||
var nativeEvent;
|
||||
try {
|
||||
nativeEvent = document.createEvent('TouchEvent');
|
||||
} catch (ex) {
|
||||
// In Chrome creating a TouchEvent fails if the feature is not turned on
|
||||
// which it isn't on desktop Chrome.
|
||||
return;
|
||||
}
|
||||
|
||||
var nonEnumDescriptor = {enumerable: false};
|
||||
|
||||
function nonEnum(obj, prop) {
|
||||
Object.defineProperty(obj, prop, nonEnumDescriptor);
|
||||
}
|
||||
|
||||
function Touch(impl) {
|
||||
setWrapper(impl, this);
|
||||
}
|
||||
|
||||
Touch.prototype = {
|
||||
get target() {
|
||||
return wrap(unsafeUnwrap(this).target);
|
||||
}
|
||||
};
|
||||
|
||||
var descr = {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: null
|
||||
};
|
||||
|
||||
[
|
||||
'clientX',
|
||||
'clientY',
|
||||
'screenX',
|
||||
'screenY',
|
||||
'pageX',
|
||||
'pageY',
|
||||
'identifier',
|
||||
'webkitRadiusX',
|
||||
'webkitRadiusY',
|
||||
'webkitRotationAngle',
|
||||
'webkitForce'
|
||||
].forEach(function(name) {
|
||||
descr.get = function() {
|
||||
return unsafeUnwrap(this)[name];
|
||||
};
|
||||
Object.defineProperty(Touch.prototype, name, descr);
|
||||
});
|
||||
|
||||
function TouchList() {
|
||||
this.length = 0;
|
||||
nonEnum(this, 'length');
|
||||
}
|
||||
|
||||
TouchList.prototype = {
|
||||
item: function(index) {
|
||||
return this[index];
|
||||
}
|
||||
};
|
||||
|
||||
function wrapTouchList(nativeTouchList) {
|
||||
var list = new TouchList();
|
||||
for (var i = 0; i < nativeTouchList.length; i++) {
|
||||
list[i] = new Touch(nativeTouchList[i]);
|
||||
}
|
||||
list.length = i;
|
||||
return list;
|
||||
}
|
||||
|
||||
function TouchEvent(impl) {
|
||||
UIEvent.call(this, impl);
|
||||
}
|
||||
|
||||
TouchEvent.prototype = Object.create(UIEvent.prototype);
|
||||
|
||||
mixin(TouchEvent.prototype, {
|
||||
get touches() {
|
||||
return wrapTouchList(unsafeUnwrap(this).touches);
|
||||
},
|
||||
|
||||
get targetTouches() {
|
||||
return wrapTouchList(unsafeUnwrap(this).targetTouches);
|
||||
},
|
||||
|
||||
get changedTouches() {
|
||||
return wrapTouchList(unsafeUnwrap(this).changedTouches);
|
||||
},
|
||||
|
||||
initTouchEvent: function() {
|
||||
// The only way to use this is to reuse the TouchList from an existing
|
||||
// TouchEvent. Since this is WebKit/Blink proprietary API we will not
|
||||
// implement this until someone screams.
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
});
|
||||
|
||||
registerWrapper(OriginalTouchEvent, TouchEvent, nativeEvent);
|
||||
|
||||
scope.wrappers.Touch = Touch;
|
||||
scope.wrappers.TouchEvent = TouchEvent;
|
||||
scope.wrappers.TouchList = TouchList;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
|
||||
52
src/ShadowDOM/wrappers/WebGLRenderingContext.js
Normal file
52
src/ShadowDOM/wrappers/WebGLRenderingContext.js
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var setWrapper = scope.setWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var unwrapIfNeeded = scope.unwrapIfNeeded;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var OriginalWebGLRenderingContext = window.WebGLRenderingContext;
|
||||
|
||||
// IE10 does not have WebGL.
|
||||
if (!OriginalWebGLRenderingContext)
|
||||
return;
|
||||
|
||||
function WebGLRenderingContext(impl) {
|
||||
setWrapper(impl, this);
|
||||
}
|
||||
|
||||
mixin(WebGLRenderingContext.prototype, {
|
||||
get canvas() {
|
||||
return wrap(unsafeUnwrap(this).canvas);
|
||||
},
|
||||
|
||||
texImage2D: function() {
|
||||
arguments[5] = unwrapIfNeeded(arguments[5]);
|
||||
unsafeUnwrap(this).texImage2D.apply(unsafeUnwrap(this), arguments);
|
||||
},
|
||||
|
||||
texSubImage2D: function() {
|
||||
arguments[6] = unwrapIfNeeded(arguments[6]);
|
||||
unsafeUnwrap(this).texSubImage2D.apply(unsafeUnwrap(this), arguments);
|
||||
}
|
||||
});
|
||||
|
||||
// Blink/WebKit has broken DOM bindings. Usually we would create an instance
|
||||
// of the object and pass it into registerWrapper as a "blueprint" but
|
||||
// creating WebGL contexts is expensive and might fail so we use a dummy
|
||||
// object with dummy instance properties for these broken browsers.
|
||||
var instanceProperties = /WebKit/.test(navigator.userAgent) ?
|
||||
{drawingBufferHeight: null, drawingBufferWidth: null} : {};
|
||||
|
||||
registerWrapper(OriginalWebGLRenderingContext, WebGLRenderingContext,
|
||||
instanceProperties);
|
||||
|
||||
scope.wrappers.WebGLRenderingContext = WebGLRenderingContext;
|
||||
})(window.ShadowDOMPolyfill);
|
||||
88
src/ShadowDOM/wrappers/Window.js
Normal file
88
src/ShadowDOM/wrappers/Window.js
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var EventTarget = scope.wrappers.EventTarget;
|
||||
var Selection = scope.wrappers.Selection;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var renderAllPending = scope.renderAllPending;
|
||||
var unwrap = scope.unwrap;
|
||||
var unwrapIfNeeded = scope.unwrapIfNeeded;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var OriginalWindow = window.Window;
|
||||
var originalGetComputedStyle = window.getComputedStyle;
|
||||
var originalGetDefaultComputedStyle = window.getDefaultComputedStyle;
|
||||
var originalGetSelection = window.getSelection;
|
||||
|
||||
function Window(impl) {
|
||||
EventTarget.call(this, impl);
|
||||
}
|
||||
Window.prototype = Object.create(EventTarget.prototype);
|
||||
|
||||
OriginalWindow.prototype.getComputedStyle = function(el, pseudo) {
|
||||
return wrap(this || window).getComputedStyle(unwrapIfNeeded(el), pseudo);
|
||||
};
|
||||
|
||||
// Mozilla proprietary extension.
|
||||
if (originalGetDefaultComputedStyle) {
|
||||
OriginalWindow.prototype.getDefaultComputedStyle = function(el, pseudo) {
|
||||
return wrap(this || window).getDefaultComputedStyle(
|
||||
unwrapIfNeeded(el), pseudo);
|
||||
};
|
||||
}
|
||||
|
||||
OriginalWindow.prototype.getSelection = function() {
|
||||
return wrap(this || window).getSelection();
|
||||
};
|
||||
|
||||
// Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065
|
||||
delete window.getComputedStyle;
|
||||
delete window.getDefaultComputedStyle;
|
||||
delete window.getSelection;
|
||||
|
||||
['addEventListener', 'removeEventListener', 'dispatchEvent'].forEach(
|
||||
function(name) {
|
||||
OriginalWindow.prototype[name] = function() {
|
||||
var w = wrap(this || window);
|
||||
return w[name].apply(w, arguments);
|
||||
};
|
||||
|
||||
// Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065
|
||||
delete window[name];
|
||||
});
|
||||
|
||||
mixin(Window.prototype, {
|
||||
getComputedStyle: function(el, pseudo) {
|
||||
renderAllPending();
|
||||
return originalGetComputedStyle.call(unwrap(this), unwrapIfNeeded(el),
|
||||
pseudo);
|
||||
},
|
||||
getSelection: function() {
|
||||
renderAllPending();
|
||||
return new Selection(originalGetSelection.call(unwrap(this)));
|
||||
},
|
||||
|
||||
get document() {
|
||||
return wrap(unwrap(this).document);
|
||||
}
|
||||
});
|
||||
|
||||
// Mozilla proprietary extension.
|
||||
if (originalGetDefaultComputedStyle) {
|
||||
Window.prototype.getDefaultComputedStyle = function(el, pseudo) {
|
||||
renderAllPending();
|
||||
return originalGetDefaultComputedStyle.call(unwrap(this),
|
||||
unwrapIfNeeded(el),pseudo);
|
||||
};
|
||||
}
|
||||
|
||||
registerWrapper(OriginalWindow, Window, window);
|
||||
|
||||
scope.wrappers.Window = Window;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
19
src/ShadowDOM/wrappers/XMLHttpRequest.js
Normal file
19
src/ShadowDOM/wrappers/XMLHttpRequest.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2014 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var unwrapIfNeeded = scope.unwrapIfNeeded;
|
||||
var originalSend = XMLHttpRequest.prototype.send;
|
||||
|
||||
// Since we only need to adjust XHR.send, we just patch it instead of wrapping
|
||||
// the entire object. This happens when FormData is passed.
|
||||
XMLHttpRequest.prototype.send = function(obj) {
|
||||
return originalSend.call(this, unwrapIfNeeded(obj));
|
||||
};
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
54
src/ShadowDOM/wrappers/elements-with-form-property.js
Normal file
54
src/ShadowDOM/wrappers/elements-with-form-property.js
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var HTMLElement = scope.wrappers.HTMLElement;
|
||||
var assert = scope.assert;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var unwrap = scope.unwrap;
|
||||
var wrap = scope.wrap;
|
||||
|
||||
var elementsWithFormProperty = [
|
||||
'HTMLButtonElement',
|
||||
'HTMLFieldSetElement',
|
||||
'HTMLInputElement',
|
||||
'HTMLKeygenElement',
|
||||
'HTMLLabelElement',
|
||||
'HTMLLegendElement',
|
||||
'HTMLObjectElement',
|
||||
// HTMLOptionElement is handled in HTMLOptionElement.js
|
||||
'HTMLOutputElement',
|
||||
// HTMLSelectElement is handled in HTMLSelectElement.js
|
||||
'HTMLTextAreaElement',
|
||||
];
|
||||
|
||||
function createWrapperConstructor(name) {
|
||||
if (!window[name])
|
||||
return;
|
||||
|
||||
// Ensure we are not overriding an already existing constructor.
|
||||
assert(!scope.wrappers[name]);
|
||||
|
||||
var GeneratedWrapper = function(node) {
|
||||
// At this point all of them extend HTMLElement.
|
||||
HTMLElement.call(this, node);
|
||||
}
|
||||
GeneratedWrapper.prototype = Object.create(HTMLElement.prototype);
|
||||
mixin(GeneratedWrapper.prototype, {
|
||||
get form() {
|
||||
return wrap(unwrap(this).form);
|
||||
},
|
||||
});
|
||||
|
||||
registerWrapper(window[name], GeneratedWrapper,
|
||||
document.createElement(name.slice(4, -7)));
|
||||
scope.wrappers[name] = GeneratedWrapper;
|
||||
}
|
||||
|
||||
elementsWithFormProperty.forEach(createWrapperConstructor);
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
920
src/ShadowDOM/wrappers/events.js
Normal file
920
src/ShadowDOM/wrappers/events.js
Normal file
@@ -0,0 +1,920 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var forwardMethodsToWrapper = scope.forwardMethodsToWrapper;
|
||||
var getTreeScope = scope.getTreeScope;
|
||||
var mixin = scope.mixin;
|
||||
var registerWrapper = scope.registerWrapper;
|
||||
var setWrapper = scope.setWrapper;
|
||||
var unsafeUnwrap = scope.unsafeUnwrap;
|
||||
var unwrap = scope.unwrap;
|
||||
var wrap = scope.wrap;
|
||||
var wrappers = scope.wrappers;
|
||||
|
||||
var wrappedFuns = new WeakMap();
|
||||
var listenersTable = new WeakMap();
|
||||
var handledEventsTable = new WeakMap();
|
||||
var currentlyDispatchingEvents = new WeakMap();
|
||||
var targetTable = new WeakMap();
|
||||
var currentTargetTable = new WeakMap();
|
||||
var relatedTargetTable = new WeakMap();
|
||||
var eventPhaseTable = new WeakMap();
|
||||
var stopPropagationTable = new WeakMap();
|
||||
var stopImmediatePropagationTable = new WeakMap();
|
||||
var eventHandlersTable = new WeakMap();
|
||||
var eventPathTable = new WeakMap();
|
||||
|
||||
function isShadowRoot(node) {
|
||||
return node instanceof wrappers.ShadowRoot;
|
||||
}
|
||||
|
||||
function rootOfNode(node) {
|
||||
return getTreeScope(node).root;
|
||||
}
|
||||
|
||||
// http://w3c.github.io/webcomponents/spec/shadow/#event-paths
|
||||
function getEventPath(node, event) {
|
||||
var path = [];
|
||||
var current = node;
|
||||
path.push(current);
|
||||
while (current) {
|
||||
// 4.1.
|
||||
var destinationInsertionPoints = getDestinationInsertionPoints(current);
|
||||
if (destinationInsertionPoints && destinationInsertionPoints.length > 0) {
|
||||
// 4.1.1
|
||||
for (var i = 0; i < destinationInsertionPoints.length; i++) {
|
||||
var insertionPoint = destinationInsertionPoints[i];
|
||||
// 4.1.1.1
|
||||
if (isShadowInsertionPoint(insertionPoint)) {
|
||||
var shadowRoot = rootOfNode(insertionPoint);
|
||||
// 4.1.1.1.2
|
||||
var olderShadowRoot = shadowRoot.olderShadowRoot;
|
||||
if (olderShadowRoot)
|
||||
path.push(olderShadowRoot);
|
||||
}
|
||||
|
||||
// 4.1.1.2
|
||||
path.push(insertionPoint);
|
||||
}
|
||||
|
||||
// 4.1.2
|
||||
current = destinationInsertionPoints[
|
||||
destinationInsertionPoints.length - 1];
|
||||
|
||||
// 4.2
|
||||
} else {
|
||||
if (isShadowRoot(current)) {
|
||||
if (inSameTree(node, current) && eventMustBeStopped(event)) {
|
||||
// Stop this algorithm
|
||||
break;
|
||||
}
|
||||
current = current.host;
|
||||
path.push(current);
|
||||
|
||||
// 4.2.2
|
||||
} else {
|
||||
current = current.parentNode;
|
||||
if (current)
|
||||
path.push(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// http://w3c.github.io/webcomponents/spec/shadow/#dfn-events-always-stopped
|
||||
function eventMustBeStopped(event) {
|
||||
if (!event)
|
||||
return false;
|
||||
|
||||
switch (event.type) {
|
||||
case 'abort':
|
||||
case 'error':
|
||||
case 'select':
|
||||
case 'change':
|
||||
case 'load':
|
||||
case 'reset':
|
||||
case 'resize':
|
||||
case 'scroll':
|
||||
case 'selectstart':
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// http://w3c.github.io/webcomponents/spec/shadow/#dfn-shadow-insertion-point
|
||||
function isShadowInsertionPoint(node) {
|
||||
return node instanceof HTMLShadowElement;
|
||||
// and make sure that there are no shadow precing this?
|
||||
// and that there is no content ancestor?
|
||||
}
|
||||
|
||||
function getDestinationInsertionPoints(node) {
|
||||
return scope.getDestinationInsertionPoints(node);
|
||||
}
|
||||
|
||||
// http://w3c.github.io/webcomponents/spec/shadow/#event-retargeting
|
||||
function eventRetargetting(path, currentTarget) {
|
||||
if (path.length === 0)
|
||||
return currentTarget;
|
||||
|
||||
// The currentTarget might be the window object. Use its document for the
|
||||
// purpose of finding the retargetted node.
|
||||
if (currentTarget instanceof wrappers.Window)
|
||||
currentTarget = currentTarget.document;
|
||||
|
||||
var currentTargetTree = getTreeScope(currentTarget);
|
||||
var originalTarget = path[0];
|
||||
var originalTargetTree = getTreeScope(originalTarget);
|
||||
var relativeTargetTree =
|
||||
lowestCommonInclusiveAncestor(currentTargetTree, originalTargetTree);
|
||||
|
||||
for (var i = 0; i < path.length; i++) {
|
||||
var node = path[i];
|
||||
if (getTreeScope(node) === relativeTargetTree)
|
||||
return node;
|
||||
}
|
||||
|
||||
return path[path.length - 1];
|
||||
}
|
||||
|
||||
function getTreeScopeAncestors(treeScope) {
|
||||
var ancestors = [];
|
||||
for (;treeScope; treeScope = treeScope.parent) {
|
||||
ancestors.push(treeScope);
|
||||
}
|
||||
return ancestors;
|
||||
}
|
||||
|
||||
function lowestCommonInclusiveAncestor(tsA, tsB) {
|
||||
var ancestorsA = getTreeScopeAncestors(tsA);
|
||||
var ancestorsB = getTreeScopeAncestors(tsB);
|
||||
|
||||
var result = null;
|
||||
while (ancestorsA.length > 0 && ancestorsB.length > 0) {
|
||||
var a = ancestorsA.pop();
|
||||
var b = ancestorsB.pop();
|
||||
if (a === b)
|
||||
result = a;
|
||||
else
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getTreeScopeRoot(ts) {
|
||||
if (!ts.parent)
|
||||
return ts;
|
||||
return getTreeScopeRoot(ts.parent);
|
||||
}
|
||||
|
||||
function relatedTargetResolution(event, currentTarget, relatedTarget) {
|
||||
// In case the current target is a window use its document for the purpose
|
||||
// of retargetting the related target.
|
||||
if (currentTarget instanceof wrappers.Window)
|
||||
currentTarget = currentTarget.document;
|
||||
|
||||
var currentTargetTree = getTreeScope(currentTarget);
|
||||
var relatedTargetTree = getTreeScope(relatedTarget);
|
||||
|
||||
var relatedTargetEventPath = getEventPath(relatedTarget, event);
|
||||
|
||||
var lowestCommonAncestorTree;
|
||||
|
||||
// 4
|
||||
var lowestCommonAncestorTree =
|
||||
lowestCommonInclusiveAncestor(currentTargetTree, relatedTargetTree);
|
||||
|
||||
// 5
|
||||
if (!lowestCommonAncestorTree)
|
||||
lowestCommonAncestorTree = relatedTargetTree.root;
|
||||
|
||||
// 6
|
||||
for (var commonAncestorTree = lowestCommonAncestorTree;
|
||||
commonAncestorTree;
|
||||
commonAncestorTree = commonAncestorTree.parent) {
|
||||
// 6.1
|
||||
var adjustedRelatedTarget;
|
||||
for (var i = 0; i < relatedTargetEventPath.length; i++) {
|
||||
var node = relatedTargetEventPath[i];
|
||||
if (getTreeScope(node) === commonAncestorTree)
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function inSameTree(a, b) {
|
||||
return getTreeScope(a) === getTreeScope(b);
|
||||
}
|
||||
|
||||
var NONE = 0;
|
||||
var CAPTURING_PHASE = 1;
|
||||
var AT_TARGET = 2;
|
||||
var BUBBLING_PHASE = 3;
|
||||
|
||||
// pendingError is used to rethrow the first error we got during an event
|
||||
// dispatch. The browser actually reports all errors but to do that we would
|
||||
// need to rethrow the error asynchronously.
|
||||
var pendingError;
|
||||
|
||||
function dispatchOriginalEvent(originalEvent) {
|
||||
// Make sure this event is only dispatched once.
|
||||
if (handledEventsTable.get(originalEvent))
|
||||
return;
|
||||
handledEventsTable.set(originalEvent, true);
|
||||
dispatchEvent(wrap(originalEvent), wrap(originalEvent.target));
|
||||
if (pendingError) {
|
||||
var err = pendingError;
|
||||
pendingError = null;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function isLoadLikeEvent(event) {
|
||||
switch (event.type) {
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#events-and-the-window-object
|
||||
case 'load':
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/browsers.html#unloading-documents
|
||||
case 'beforeunload':
|
||||
case 'unload':
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function dispatchEvent(event, originalWrapperTarget) {
|
||||
if (currentlyDispatchingEvents.get(event))
|
||||
throw new Error('InvalidStateError');
|
||||
|
||||
currentlyDispatchingEvents.set(event, true);
|
||||
|
||||
// Render to ensure that the event path is correct.
|
||||
scope.renderAllPending();
|
||||
var eventPath;
|
||||
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#events-and-the-window-object
|
||||
// All events dispatched on Nodes with a default view, except load events,
|
||||
// should propagate to the Window.
|
||||
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-end
|
||||
var overrideTarget;
|
||||
var win;
|
||||
|
||||
// Should really be not cancelable too but since Firefox has a bug there
|
||||
// we skip that check.
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=999456
|
||||
if (isLoadLikeEvent(event) && !event.bubbles) {
|
||||
var doc = originalWrapperTarget;
|
||||
if (doc instanceof wrappers.Document && (win = doc.defaultView)) {
|
||||
overrideTarget = doc;
|
||||
eventPath = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!eventPath) {
|
||||
if (originalWrapperTarget instanceof wrappers.Window) {
|
||||
win = originalWrapperTarget;
|
||||
eventPath = [];
|
||||
} else {
|
||||
eventPath = getEventPath(originalWrapperTarget, event);
|
||||
|
||||
if (!isLoadLikeEvent(event)) {
|
||||
var doc = eventPath[eventPath.length - 1];
|
||||
if (doc instanceof wrappers.Document)
|
||||
win = doc.defaultView;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eventPathTable.set(event, eventPath);
|
||||
|
||||
if (dispatchCapturing(event, eventPath, win, overrideTarget)) {
|
||||
if (dispatchAtTarget(event, eventPath, win, overrideTarget)) {
|
||||
dispatchBubbling(event, eventPath, win, overrideTarget);
|
||||
}
|
||||
}
|
||||
|
||||
eventPhaseTable.set(event, NONE);
|
||||
currentTargetTable.delete(event, null);
|
||||
currentlyDispatchingEvents.delete(event);
|
||||
|
||||
return event.defaultPrevented;
|
||||
}
|
||||
|
||||
function dispatchCapturing(event, eventPath, win, overrideTarget) {
|
||||
var phase = CAPTURING_PHASE;
|
||||
|
||||
if (win) {
|
||||
if (!invoke(win, event, phase, eventPath, overrideTarget))
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = eventPath.length - 1; i > 0; i--) {
|
||||
if (!invoke(eventPath[i], event, phase, eventPath, overrideTarget))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function dispatchAtTarget(event, eventPath, win, overrideTarget) {
|
||||
var phase = AT_TARGET;
|
||||
var currentTarget = eventPath[0] || win;
|
||||
return invoke(currentTarget, event, phase, eventPath, overrideTarget);
|
||||
}
|
||||
|
||||
function dispatchBubbling(event, eventPath, win, overrideTarget) {
|
||||
var phase = BUBBLING_PHASE;
|
||||
for (var i = 1; i < eventPath.length; i++) {
|
||||
if (!invoke(eventPath[i], event, phase, eventPath, overrideTarget))
|
||||
return;
|
||||
}
|
||||
|
||||
if (win && eventPath.length > 0) {
|
||||
invoke(win, event, phase, eventPath, overrideTarget);
|
||||
}
|
||||
}
|
||||
|
||||
function invoke(currentTarget, event, phase, eventPath, overrideTarget) {
|
||||
var listeners = listenersTable.get(currentTarget);
|
||||
if (!listeners)
|
||||
return true;
|
||||
|
||||
var target = overrideTarget || eventRetargetting(eventPath, currentTarget);
|
||||
|
||||
if (target === currentTarget) {
|
||||
if (phase === CAPTURING_PHASE)
|
||||
return true;
|
||||
|
||||
if (phase === BUBBLING_PHASE)
|
||||
phase = AT_TARGET;
|
||||
|
||||
} else if (phase === BUBBLING_PHASE && !event.bubbles) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ('relatedTarget' in event) {
|
||||
var originalEvent = unwrap(event);
|
||||
var unwrappedRelatedTarget = originalEvent.relatedTarget;
|
||||
|
||||
// X-Tag sets relatedTarget on a CustomEvent. If they do that there is no
|
||||
// way to have relatedTarget return the adjusted target but worse is that
|
||||
// the originalEvent might not have a relatedTarget so we hit an assert
|
||||
// when we try to wrap it.
|
||||
if (unwrappedRelatedTarget) {
|
||||
// In IE we can get objects that are not EventTargets at this point.
|
||||
// Safari does not have an EventTarget interface so revert to checking
|
||||
// for addEventListener as an approximation.
|
||||
if (unwrappedRelatedTarget instanceof Object &&
|
||||
unwrappedRelatedTarget.addEventListener) {
|
||||
var relatedTarget = wrap(unwrappedRelatedTarget);
|
||||
|
||||
var adjusted =
|
||||
relatedTargetResolution(event, currentTarget, relatedTarget);
|
||||
if (adjusted === target)
|
||||
return true;
|
||||
} else {
|
||||
adjusted = null;
|
||||
}
|
||||
relatedTargetTable.set(event, adjusted);
|
||||
}
|
||||
}
|
||||
|
||||
eventPhaseTable.set(event, phase);
|
||||
var type = event.type;
|
||||
|
||||
var anyRemoved = false;
|
||||
targetTable.set(event, target);
|
||||
currentTargetTable.set(event, currentTarget);
|
||||
|
||||
// Keep track of the invoke depth so that we only clean up the removed
|
||||
// listeners if we are in the outermost invoke.
|
||||
listeners.depth++;
|
||||
|
||||
for (var i = 0, len = listeners.length; i < len; i++) {
|
||||
var listener = listeners[i];
|
||||
if (listener.removed) {
|
||||
anyRemoved = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (listener.type !== type ||
|
||||
!listener.capture && phase === CAPTURING_PHASE ||
|
||||
listener.capture && phase === BUBBLING_PHASE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof listener.handler === 'function')
|
||||
listener.handler.call(currentTarget, event);
|
||||
else
|
||||
listener.handler.handleEvent(event);
|
||||
|
||||
if (stopImmediatePropagationTable.get(event))
|
||||
return false;
|
||||
|
||||
} catch (ex) {
|
||||
if (!pendingError)
|
||||
pendingError = ex;
|
||||
}
|
||||
}
|
||||
|
||||
listeners.depth--;
|
||||
|
||||
if (anyRemoved && listeners.depth === 0) {
|
||||
var copy = listeners.slice();
|
||||
listeners.length = 0;
|
||||
for (var i = 0; i < copy.length; i++) {
|
||||
if (!copy[i].removed)
|
||||
listeners.push(copy[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return !stopPropagationTable.get(event);
|
||||
}
|
||||
|
||||
function Listener(type, handler, capture) {
|
||||
this.type = type;
|
||||
this.handler = handler;
|
||||
this.capture = Boolean(capture);
|
||||
}
|
||||
Listener.prototype = {
|
||||
equals: function(that) {
|
||||
return this.handler === that.handler && this.type === that.type &&
|
||||
this.capture === that.capture;
|
||||
},
|
||||
get removed() {
|
||||
return this.handler === null;
|
||||
},
|
||||
remove: function() {
|
||||
this.handler = null;
|
||||
}
|
||||
};
|
||||
|
||||
var OriginalEvent = window.Event;
|
||||
OriginalEvent.prototype.polymerBlackList_ = {
|
||||
returnValue: true,
|
||||
// TODO(arv): keyLocation is part of KeyboardEvent but Firefox does not
|
||||
// support constructable KeyboardEvent so we keep it here for now.
|
||||
keyLocation: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new Event wrapper or wraps an existin native Event object.
|
||||
* @param {string|Event} type
|
||||
* @param {Object=} options
|
||||
* @constructor
|
||||
*/
|
||||
function Event(type, options) {
|
||||
if (type instanceof OriginalEvent) {
|
||||
var impl = type;
|
||||
// In browsers that do not correctly support BeforeUnloadEvent we get to
|
||||
// the generic Event wrapper but we still want to ensure we create a
|
||||
// BeforeUnloadEvent. Since BeforeUnloadEvent calls super, we need to
|
||||
// prevent reentrancty.
|
||||
if (!OriginalBeforeUnloadEvent && impl.type === 'beforeunload' &&
|
||||
!(this instanceof BeforeUnloadEvent)) {
|
||||
return new BeforeUnloadEvent(impl);
|
||||
}
|
||||
setWrapper(impl, this);
|
||||
} else {
|
||||
return wrap(constructEvent(OriginalEvent, 'Event', type, options));
|
||||
}
|
||||
}
|
||||
Event.prototype = {
|
||||
get target() {
|
||||
return targetTable.get(this);
|
||||
},
|
||||
get currentTarget() {
|
||||
return currentTargetTable.get(this);
|
||||
},
|
||||
get eventPhase() {
|
||||
return eventPhaseTable.get(this);
|
||||
},
|
||||
get path() {
|
||||
var eventPath = eventPathTable.get(this);
|
||||
if (!eventPath)
|
||||
return [];
|
||||
// TODO(arv): Event path should contain window.
|
||||
return eventPath.slice();
|
||||
},
|
||||
stopPropagation: function() {
|
||||
stopPropagationTable.set(this, true);
|
||||
},
|
||||
stopImmediatePropagation: function() {
|
||||
stopPropagationTable.set(this, true);
|
||||
stopImmediatePropagationTable.set(this, true);
|
||||
}
|
||||
};
|
||||
registerWrapper(OriginalEvent, Event, document.createEvent('Event'));
|
||||
|
||||
function unwrapOptions(options) {
|
||||
if (!options || !options.relatedTarget)
|
||||
return options;
|
||||
return Object.create(options, {
|
||||
relatedTarget: {value: unwrap(options.relatedTarget)}
|
||||
});
|
||||
}
|
||||
|
||||
function registerGenericEvent(name, SuperEvent, prototype) {
|
||||
var OriginalEvent = window[name];
|
||||
var GenericEvent = function(type, options) {
|
||||
if (type instanceof OriginalEvent)
|
||||
setWrapper(type, this);
|
||||
else
|
||||
return wrap(constructEvent(OriginalEvent, name, type, options));
|
||||
};
|
||||
GenericEvent.prototype = Object.create(SuperEvent.prototype);
|
||||
if (prototype)
|
||||
mixin(GenericEvent.prototype, prototype);
|
||||
if (OriginalEvent) {
|
||||
// - Old versions of Safari fails on new FocusEvent (and others?).
|
||||
// - IE does not support event constructors.
|
||||
// - createEvent('FocusEvent') throws in Firefox.
|
||||
// => Try the best practice solution first and fallback to the old way
|
||||
// if needed.
|
||||
try {
|
||||
registerWrapper(OriginalEvent, GenericEvent, new OriginalEvent('temp'));
|
||||
} catch (ex) {
|
||||
registerWrapper(OriginalEvent, GenericEvent,
|
||||
document.createEvent(name));
|
||||
}
|
||||
}
|
||||
return GenericEvent;
|
||||
}
|
||||
|
||||
var UIEvent = registerGenericEvent('UIEvent', Event);
|
||||
var CustomEvent = registerGenericEvent('CustomEvent', Event);
|
||||
|
||||
var relatedTargetProto = {
|
||||
get relatedTarget() {
|
||||
var relatedTarget = relatedTargetTable.get(this);
|
||||
// relatedTarget can be null.
|
||||
if (relatedTarget !== undefined)
|
||||
return relatedTarget;
|
||||
return wrap(unwrap(this).relatedTarget);
|
||||
}
|
||||
};
|
||||
|
||||
function getInitFunction(name, relatedTargetIndex) {
|
||||
return function() {
|
||||
arguments[relatedTargetIndex] = unwrap(arguments[relatedTargetIndex]);
|
||||
var impl = unwrap(this);
|
||||
impl[name].apply(impl, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
var mouseEventProto = mixin({
|
||||
initMouseEvent: getInitFunction('initMouseEvent', 14)
|
||||
}, relatedTargetProto);
|
||||
|
||||
var focusEventProto = mixin({
|
||||
initFocusEvent: getInitFunction('initFocusEvent', 5)
|
||||
}, relatedTargetProto);
|
||||
|
||||
var MouseEvent = registerGenericEvent('MouseEvent', UIEvent, mouseEventProto);
|
||||
var FocusEvent = registerGenericEvent('FocusEvent', UIEvent, focusEventProto);
|
||||
|
||||
// In case the browser does not support event constructors we polyfill that
|
||||
// by calling `createEvent('Foo')` and `initFooEvent` where the arguments to
|
||||
// `initFooEvent` are derived from the registered default event init dict.
|
||||
var defaultInitDicts = Object.create(null);
|
||||
|
||||
var supportsEventConstructors = (function() {
|
||||
try {
|
||||
new window.FocusEvent('focus');
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Constructs a new native event.
|
||||
*/
|
||||
function constructEvent(OriginalEvent, name, type, options) {
|
||||
if (supportsEventConstructors)
|
||||
return new OriginalEvent(type, unwrapOptions(options));
|
||||
|
||||
// Create the arguments from the default dictionary.
|
||||
var event = unwrap(document.createEvent(name));
|
||||
var defaultDict = defaultInitDicts[name];
|
||||
var args = [type];
|
||||
Object.keys(defaultDict).forEach(function(key) {
|
||||
var v = options != null && key in options ?
|
||||
options[key] : defaultDict[key];
|
||||
if (key === 'relatedTarget')
|
||||
v = unwrap(v);
|
||||
args.push(v);
|
||||
});
|
||||
event['init' + name].apply(event, args);
|
||||
return event;
|
||||
}
|
||||
|
||||
if (!supportsEventConstructors) {
|
||||
var configureEventConstructor = function(name, initDict, superName) {
|
||||
if (superName) {
|
||||
var superDict = defaultInitDicts[superName];
|
||||
initDict = mixin(mixin({}, superDict), initDict);
|
||||
}
|
||||
|
||||
defaultInitDicts[name] = initDict;
|
||||
};
|
||||
|
||||
// The order of the default event init dictionary keys is important, the
|
||||
// arguments to initFooEvent is derived from that.
|
||||
configureEventConstructor('Event', {bubbles: false, cancelable: false});
|
||||
configureEventConstructor('CustomEvent', {detail: null}, 'Event');
|
||||
configureEventConstructor('UIEvent', {view: null, detail: 0}, 'Event');
|
||||
configureEventConstructor('MouseEvent', {
|
||||
screenX: 0,
|
||||
screenY: 0,
|
||||
clientX: 0,
|
||||
clientY: 0,
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
metaKey: false,
|
||||
button: 0,
|
||||
relatedTarget: null
|
||||
}, 'UIEvent');
|
||||
configureEventConstructor('FocusEvent', {relatedTarget: null}, 'UIEvent');
|
||||
}
|
||||
|
||||
// Safari 7 does not yet have BeforeUnloadEvent.
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=120849
|
||||
var OriginalBeforeUnloadEvent = window.BeforeUnloadEvent;
|
||||
|
||||
function BeforeUnloadEvent(impl) {
|
||||
Event.call(this, impl);
|
||||
}
|
||||
BeforeUnloadEvent.prototype = Object.create(Event.prototype);
|
||||
mixin(BeforeUnloadEvent.prototype, {
|
||||
get returnValue() {
|
||||
return unsafeUnwrap(this).returnValue;
|
||||
},
|
||||
set returnValue(v) {
|
||||
unsafeUnwrap(this).returnValue = v;
|
||||
}
|
||||
});
|
||||
|
||||
if (OriginalBeforeUnloadEvent)
|
||||
registerWrapper(OriginalBeforeUnloadEvent, BeforeUnloadEvent);
|
||||
|
||||
function isValidListener(fun) {
|
||||
if (typeof fun === 'function')
|
||||
return true;
|
||||
return fun && fun.handleEvent;
|
||||
}
|
||||
|
||||
function isMutationEvent(type) {
|
||||
switch (type) {
|
||||
case 'DOMAttrModified':
|
||||
case 'DOMAttributeNameChanged':
|
||||
case 'DOMCharacterDataModified':
|
||||
case 'DOMElementNameChanged':
|
||||
case 'DOMNodeInserted':
|
||||
case 'DOMNodeInsertedIntoDocument':
|
||||
case 'DOMNodeRemoved':
|
||||
case 'DOMNodeRemovedFromDocument':
|
||||
case 'DOMSubtreeModified':
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var OriginalEventTarget = window.EventTarget;
|
||||
|
||||
/**
|
||||
* This represents a wrapper for an EventTarget.
|
||||
* @param {!EventTarget} impl The original event target.
|
||||
* @constructor
|
||||
*/
|
||||
function EventTarget(impl) {
|
||||
setWrapper(impl, this);
|
||||
}
|
||||
|
||||
// Node and Window have different internal type checks in WebKit so we cannot
|
||||
// use the same method as the original function.
|
||||
var methodNames = [
|
||||
'addEventListener',
|
||||
'removeEventListener',
|
||||
'dispatchEvent'
|
||||
];
|
||||
|
||||
[Node, Window].forEach(function(constructor) {
|
||||
var p = constructor.prototype;
|
||||
methodNames.forEach(function(name) {
|
||||
Object.defineProperty(p, name + '_', {value: p[name]});
|
||||
});
|
||||
});
|
||||
|
||||
function getTargetToListenAt(wrapper) {
|
||||
if (wrapper instanceof wrappers.ShadowRoot)
|
||||
wrapper = wrapper.host;
|
||||
return unwrap(wrapper);
|
||||
}
|
||||
|
||||
EventTarget.prototype = {
|
||||
addEventListener: function(type, fun, capture) {
|
||||
if (!isValidListener(fun) || isMutationEvent(type))
|
||||
return;
|
||||
|
||||
var listener = new Listener(type, fun, capture);
|
||||
var listeners = listenersTable.get(this);
|
||||
if (!listeners) {
|
||||
listeners = [];
|
||||
listeners.depth = 0;
|
||||
listenersTable.set(this, listeners);
|
||||
} else {
|
||||
// Might have a duplicate.
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
if (listener.equals(listeners[i]))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
listeners.push(listener);
|
||||
|
||||
var target = getTargetToListenAt(this);
|
||||
target.addEventListener_(type, dispatchOriginalEvent, true);
|
||||
},
|
||||
removeEventListener: function(type, fun, capture) {
|
||||
capture = Boolean(capture);
|
||||
var listeners = listenersTable.get(this);
|
||||
if (!listeners)
|
||||
return;
|
||||
var count = 0, found = false;
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
if (listeners[i].type === type && listeners[i].capture === capture) {
|
||||
count++;
|
||||
if (listeners[i].handler === fun) {
|
||||
found = true;
|
||||
listeners[i].remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found && count === 1) {
|
||||
var target = getTargetToListenAt(this);
|
||||
target.removeEventListener_(type, dispatchOriginalEvent, true);
|
||||
}
|
||||
},
|
||||
dispatchEvent: function(event) {
|
||||
// We want to use the native dispatchEvent because it triggers the default
|
||||
// actions (like checking a checkbox). However, if there are no listeners
|
||||
// in the composed tree then there are no events that will trigger and
|
||||
// listeners in the non composed tree that are part of the event path are
|
||||
// not notified.
|
||||
//
|
||||
// If we find out that there are no listeners in the composed tree we add
|
||||
// a temporary listener to the target which makes us get called back even
|
||||
// in that case.
|
||||
|
||||
var nativeEvent = unwrap(event);
|
||||
var eventType = nativeEvent.type;
|
||||
|
||||
// Allow dispatching the same event again. This is safe because if user
|
||||
// code calls this during an existing dispatch of the same event the
|
||||
// native dispatchEvent throws (that is required by the spec).
|
||||
handledEventsTable.set(nativeEvent, false);
|
||||
|
||||
// Force rendering since we prefer native dispatch and that works on the
|
||||
// composed tree.
|
||||
scope.renderAllPending();
|
||||
|
||||
var tempListener;
|
||||
if (!hasListenerInAncestors(this, eventType)) {
|
||||
tempListener = function() {};
|
||||
this.addEventListener(eventType, tempListener, true);
|
||||
}
|
||||
|
||||
try {
|
||||
return unwrap(this).dispatchEvent_(nativeEvent);
|
||||
} finally {
|
||||
if (tempListener)
|
||||
this.removeEventListener(eventType, tempListener, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function hasListener(node, type) {
|
||||
var listeners = listenersTable.get(node);
|
||||
if (listeners) {
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
if (!listeners[i].removed && listeners[i].type === type)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasListenerInAncestors(target, type) {
|
||||
for (var node = unwrap(target); node; node = node.parentNode) {
|
||||
if (hasListener(wrap(node), type))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OriginalEventTarget)
|
||||
registerWrapper(OriginalEventTarget, EventTarget);
|
||||
|
||||
function wrapEventTargetMethods(constructors) {
|
||||
forwardMethodsToWrapper(constructors, methodNames);
|
||||
}
|
||||
|
||||
var originalElementFromPoint = document.elementFromPoint;
|
||||
|
||||
function elementFromPoint(self, document, x, y) {
|
||||
scope.renderAllPending();
|
||||
|
||||
var element =
|
||||
wrap(originalElementFromPoint.call(unsafeUnwrap(document), x, y));
|
||||
if (!element)
|
||||
return null;
|
||||
var path = getEventPath(element, null);
|
||||
|
||||
// scope the path to this TreeScope
|
||||
var idx = path.lastIndexOf(self);
|
||||
if (idx == -1)
|
||||
return null;
|
||||
else
|
||||
path = path.slice(0, idx);
|
||||
|
||||
// TODO(dfreedm): pass idx to eventRetargetting to avoid array copy
|
||||
return eventRetargetting(path, self);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that is to be used as a getter for `onfoo` properties.
|
||||
* @param {string} name
|
||||
* @return {Function}
|
||||
*/
|
||||
function getEventHandlerGetter(name) {
|
||||
return function() {
|
||||
var inlineEventHandlers = eventHandlersTable.get(this);
|
||||
return inlineEventHandlers && inlineEventHandlers[name] &&
|
||||
inlineEventHandlers[name].value || null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that is to be used as a setter for `onfoo` properties.
|
||||
* @param {string} name
|
||||
* @return {Function}
|
||||
*/
|
||||
function getEventHandlerSetter(name) {
|
||||
var eventType = name.slice(2);
|
||||
return function(value) {
|
||||
var inlineEventHandlers = eventHandlersTable.get(this);
|
||||
if (!inlineEventHandlers) {
|
||||
inlineEventHandlers = Object.create(null);
|
||||
eventHandlersTable.set(this, inlineEventHandlers);
|
||||
}
|
||||
|
||||
var old = inlineEventHandlers[name];
|
||||
if (old)
|
||||
this.removeEventListener(eventType, old.wrapped, false);
|
||||
|
||||
if (typeof value === 'function') {
|
||||
var wrapped = function(e) {
|
||||
var rv = value.call(this, e);
|
||||
if (rv === false)
|
||||
e.preventDefault();
|
||||
else if (name === 'onbeforeunload' && typeof rv === 'string')
|
||||
e.returnValue = rv;
|
||||
// mouseover uses true for preventDefault but preventDefault for
|
||||
// mouseover is ignored by browsers these day.
|
||||
};
|
||||
|
||||
this.addEventListener(eventType, wrapped, false);
|
||||
inlineEventHandlers[name] = {
|
||||
value: value,
|
||||
wrapped: wrapped
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
scope.elementFromPoint = elementFromPoint;
|
||||
scope.getEventHandlerGetter = getEventHandlerGetter;
|
||||
scope.getEventHandlerSetter = getEventHandlerSetter;
|
||||
scope.wrapEventTargetMethods = wrapEventTargetMethods;
|
||||
scope.wrappers.BeforeUnloadEvent = BeforeUnloadEvent;
|
||||
scope.wrappers.CustomEvent = CustomEvent;
|
||||
scope.wrappers.Event = Event;
|
||||
scope.wrappers.EventTarget = EventTarget;
|
||||
scope.wrappers.FocusEvent = FocusEvent;
|
||||
scope.wrappers.MouseEvent = MouseEvent;
|
||||
scope.wrappers.UIEvent = UIEvent;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
24
src/ShadowDOM/wrappers/generic.js
Normal file
24
src/ShadowDOM/wrappers/generic.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var GetElementsByInterface = scope.GetElementsByInterface;
|
||||
var ParentNodeInterface = scope.ParentNodeInterface;
|
||||
var SelectorsInterface = scope.SelectorsInterface;
|
||||
var mixin = scope.mixin;
|
||||
var registerObject = scope.registerObject;
|
||||
|
||||
var DocumentFragment = registerObject(document.createDocumentFragment());
|
||||
mixin(DocumentFragment.prototype, ParentNodeInterface);
|
||||
mixin(DocumentFragment.prototype, SelectorsInterface);
|
||||
mixin(DocumentFragment.prototype, GetElementsByInterface);
|
||||
|
||||
var Comment = registerObject(document.createComment(''));
|
||||
|
||||
scope.wrappers.Comment = Comment;
|
||||
scope.wrappers.DocumentFragment = DocumentFragment;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
75
src/ShadowDOM/wrappers/node-interfaces.js
Normal file
75
src/ShadowDOM/wrappers/node-interfaces.js
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var NodeList = scope.wrappers.NodeList;
|
||||
|
||||
function forwardElement(node) {
|
||||
while (node && node.nodeType !== Node.ELEMENT_NODE) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function backwardsElement(node) {
|
||||
while (node && node.nodeType !== Node.ELEMENT_NODE) {
|
||||
node = node.previousSibling;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
var ParentNodeInterface = {
|
||||
get firstElementChild() {
|
||||
return forwardElement(this.firstChild);
|
||||
},
|
||||
|
||||
get lastElementChild() {
|
||||
return backwardsElement(this.lastChild);
|
||||
},
|
||||
|
||||
get childElementCount() {
|
||||
var count = 0;
|
||||
for (var child = this.firstElementChild;
|
||||
child;
|
||||
child = child.nextElementSibling) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
},
|
||||
|
||||
get children() {
|
||||
var wrapperList = new NodeList();
|
||||
var i = 0;
|
||||
for (var child = this.firstElementChild;
|
||||
child;
|
||||
child = child.nextElementSibling) {
|
||||
wrapperList[i++] = child;
|
||||
}
|
||||
wrapperList.length = i;
|
||||
return wrapperList;
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
var p = this.parentNode;
|
||||
if (p)
|
||||
p.removeChild(this);
|
||||
}
|
||||
};
|
||||
|
||||
var ChildNodeInterface = {
|
||||
get nextElementSibling() {
|
||||
return forwardElement(this.nextSibling);
|
||||
},
|
||||
|
||||
get previousElementSibling() {
|
||||
return backwardsElement(this.previousSibling);
|
||||
}
|
||||
};
|
||||
|
||||
scope.ChildNodeInterface = ChildNodeInterface;
|
||||
scope.ParentNodeInterface = ParentNodeInterface;
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
108
src/ShadowDOM/wrappers/override-constructors.js
Normal file
108
src/ShadowDOM/wrappers/override-constructors.js
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2013 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(scope) {
|
||||
'use strict';
|
||||
|
||||
var isWrapperFor = scope.isWrapperFor;
|
||||
|
||||
// This is a list of the elements we currently override the global constructor
|
||||
// for.
|
||||
var elements = {
|
||||
'a': 'HTMLAnchorElement',
|
||||
// Do not create an applet element by default since it shows a warning in
|
||||
// IE.
|
||||
// https://github.com/Polymer/polymer/issues/217
|
||||
// 'applet': 'HTMLAppletElement',
|
||||
'area': 'HTMLAreaElement',
|
||||
'audio': 'HTMLAudioElement',
|
||||
'base': 'HTMLBaseElement',
|
||||
'body': 'HTMLBodyElement',
|
||||
'br': 'HTMLBRElement',
|
||||
'button': 'HTMLButtonElement',
|
||||
'canvas': 'HTMLCanvasElement',
|
||||
'caption': 'HTMLTableCaptionElement',
|
||||
'col': 'HTMLTableColElement',
|
||||
// 'command': 'HTMLCommandElement', // Not fully implemented in Gecko.
|
||||
'content': 'HTMLContentElement',
|
||||
'data': 'HTMLDataElement',
|
||||
'datalist': 'HTMLDataListElement',
|
||||
'del': 'HTMLModElement',
|
||||
'dir': 'HTMLDirectoryElement',
|
||||
'div': 'HTMLDivElement',
|
||||
'dl': 'HTMLDListElement',
|
||||
'embed': 'HTMLEmbedElement',
|
||||
'fieldset': 'HTMLFieldSetElement',
|
||||
'font': 'HTMLFontElement',
|
||||
'form': 'HTMLFormElement',
|
||||
'frame': 'HTMLFrameElement',
|
||||
'frameset': 'HTMLFrameSetElement',
|
||||
'h1': 'HTMLHeadingElement',
|
||||
'head': 'HTMLHeadElement',
|
||||
'hr': 'HTMLHRElement',
|
||||
'html': 'HTMLHtmlElement',
|
||||
'iframe': 'HTMLIFrameElement',
|
||||
'img': 'HTMLImageElement',
|
||||
'input': 'HTMLInputElement',
|
||||
'keygen': 'HTMLKeygenElement',
|
||||
'label': 'HTMLLabelElement',
|
||||
'legend': 'HTMLLegendElement',
|
||||
'li': 'HTMLLIElement',
|
||||
'link': 'HTMLLinkElement',
|
||||
'map': 'HTMLMapElement',
|
||||
'marquee': 'HTMLMarqueeElement',
|
||||
'menu': 'HTMLMenuElement',
|
||||
'menuitem': 'HTMLMenuItemElement',
|
||||
'meta': 'HTMLMetaElement',
|
||||
'meter': 'HTMLMeterElement',
|
||||
'object': 'HTMLObjectElement',
|
||||
'ol': 'HTMLOListElement',
|
||||
'optgroup': 'HTMLOptGroupElement',
|
||||
'option': 'HTMLOptionElement',
|
||||
'output': 'HTMLOutputElement',
|
||||
'p': 'HTMLParagraphElement',
|
||||
'param': 'HTMLParamElement',
|
||||
'pre': 'HTMLPreElement',
|
||||
'progress': 'HTMLProgressElement',
|
||||
'q': 'HTMLQuoteElement',
|
||||
'script': 'HTMLScriptElement',
|
||||
'select': 'HTMLSelectElement',
|
||||
'shadow': 'HTMLShadowElement',
|
||||
'source': 'HTMLSourceElement',
|
||||
'span': 'HTMLSpanElement',
|
||||
'style': 'HTMLStyleElement',
|
||||
'table': 'HTMLTableElement',
|
||||
'tbody': 'HTMLTableSectionElement',
|
||||
// WebKit and Moz are wrong:
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=111469
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=848096
|
||||
// 'td': 'HTMLTableCellElement',
|
||||
'template': 'HTMLTemplateElement',
|
||||
'textarea': 'HTMLTextAreaElement',
|
||||
'thead': 'HTMLTableSectionElement',
|
||||
'time': 'HTMLTimeElement',
|
||||
'title': 'HTMLTitleElement',
|
||||
'tr': 'HTMLTableRowElement',
|
||||
'track': 'HTMLTrackElement',
|
||||
'ul': 'HTMLUListElement',
|
||||
'video': 'HTMLVideoElement',
|
||||
};
|
||||
|
||||
function overrideConstructor(tagName) {
|
||||
var nativeConstructorName = elements[tagName];
|
||||
var nativeConstructor = window[nativeConstructorName];
|
||||
if (!nativeConstructor)
|
||||
return;
|
||||
var element = document.createElement(tagName);
|
||||
var wrapperConstructor = element.constructor;
|
||||
window[nativeConstructorName] = wrapperConstructor;
|
||||
}
|
||||
|
||||
Object.keys(elements).forEach(overrideConstructor);
|
||||
|
||||
Object.getOwnPropertyNames(scope.wrappers).forEach(function(name) {
|
||||
window[name] = scope.wrappers[name]
|
||||
});
|
||||
|
||||
})(window.ShadowDOMPolyfill);
|
||||
46
src/WeakMap/WeakMap.js
Normal file
46
src/WeakMap/WeakMap.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2012 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 (typeof WeakMap === 'undefined') {
|
||||
(function() {
|
||||
var defineProperty = Object.defineProperty;
|
||||
var counter = Date.now() % 1e9;
|
||||
|
||||
var WeakMap = function() {
|
||||
this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
|
||||
};
|
||||
|
||||
WeakMap.prototype = {
|
||||
set: function(key, value) {
|
||||
var entry = key[this.name];
|
||||
if (entry && entry[0] === key)
|
||||
entry[1] = value;
|
||||
else
|
||||
defineProperty(key, this.name, {value: [key, value], writable: true});
|
||||
return this;
|
||||
},
|
||||
get: function(key) {
|
||||
var entry;
|
||||
return (entry = key[this.name]) && entry[0] === key ?
|
||||
entry[1] : undefined;
|
||||
},
|
||||
delete: function(key) {
|
||||
var entry = key[this.name];
|
||||
if (!entry) return false;
|
||||
var hasValue = entry[0] === key;
|
||||
entry[0] = entry[1] = undefined;
|
||||
return hasValue;
|
||||
},
|
||||
has: function(key) {
|
||||
var entry = key[this.name];
|
||||
if (!entry) return false;
|
||||
return entry[0] === key;
|
||||
}
|
||||
};
|
||||
|
||||
window.WeakMap = WeakMap;
|
||||
})();
|
||||
}
|
||||
97
src/WebComponents/dom.js
Normal file
97
src/WebComponents/dom.js
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
(function(scope) {
|
||||
|
||||
'use strict';
|
||||
|
||||
// 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);
|
||||
};
|
||||
})();
|
||||
}
|
||||
|
||||
// Make a stub for Polymer() for polyfill purposes; under the HTMLImports
|
||||
// polyfill, scripts in the main document run before imports. That means
|
||||
// if (1) polymer is imported and (2) Polymer() is called in the main document
|
||||
// in a script after the import, 2 occurs before 1. We correct this here
|
||||
// by specfiically patching Polymer(); this is not necessary under native
|
||||
// HTMLImports.
|
||||
var elementDeclarations = [];
|
||||
|
||||
var polymerStub = function(name, dictionary) {
|
||||
if ((typeof name !== 'string') && (arguments.length === 1)) {
|
||||
Array.prototype.push.call(arguments, document._currentScript);
|
||||
}
|
||||
elementDeclarations.push(arguments);
|
||||
};
|
||||
window.Polymer = polymerStub;
|
||||
|
||||
// deliver queued delcarations
|
||||
scope.consumeDeclarations = function(callback) {
|
||||
scope.consumeDeclarations = function() {
|
||||
throw 'Possible attempt to load Polymer twice';
|
||||
};
|
||||
if (callback) {
|
||||
callback(elementDeclarations);
|
||||
}
|
||||
elementDeclarations = null;
|
||||
};
|
||||
|
||||
function installPolymerWarning() {
|
||||
if (window.Polymer === polymerStub) {
|
||||
window.Polymer = function() {
|
||||
throw new Error('You tried to use polymer without loading it first. To ' +
|
||||
'load polymer, <link rel="import" href="' +
|
||||
'components/polymer/polymer.html">');
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Once DOMContent has loaded, any main document scripts that depend on
|
||||
// Polymer() should have run. Calling Polymer() now is an error until
|
||||
// polymer is imported.
|
||||
if (HTMLImports.useNative) {
|
||||
installPolymerWarning();
|
||||
} else {
|
||||
addEventListener('DOMContentLoaded', installPolymerWarning);
|
||||
}
|
||||
|
||||
})(window.WebComponents);
|
||||
26
src/WebComponents/lang.js
Normal file
26
src/WebComponents/lang.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
(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);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
})(window.WebComponents);
|
||||
52
src/WebComponents/patches-html-imports-csp.js
Normal file
52
src/WebComponents/patches-html-imports-csp.js
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
(function(scope) {
|
||||
|
||||
if (!scope) {
|
||||
scope = window.HTMLImports = {flags:{}};
|
||||
}
|
||||
|
||||
// Patches for running in a sandboxed iframe.
|
||||
// The origin is set to null when we're running in a sandbox, so we
|
||||
// ask the parent window to fetch the resources.
|
||||
|
||||
var xhr = {
|
||||
callbacks: {},
|
||||
load: function(url, next, nextContext) {
|
||||
xhr.callbacks[url] = {
|
||||
next: next,
|
||||
nextContext: nextContext
|
||||
};
|
||||
parent.postMessage({
|
||||
url: url,
|
||||
bust: scope.flags.debug || scope.flags.bust
|
||||
}, '*');
|
||||
},
|
||||
receive: function(url, err, resource) {
|
||||
var cb = xhr.callbacks[url];
|
||||
if (cb) {
|
||||
var next = cb.next;
|
||||
var nextContext = cb.nextContext;
|
||||
next.call(nextContext, err, resource, url);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.loadDocument = xhr.load;
|
||||
|
||||
window.addEventListener('message', function(e) {
|
||||
xhr.receive(e.data.url, e.data.err, e.data.resource);
|
||||
});
|
||||
|
||||
// exports
|
||||
|
||||
scope.xhr = xhr;
|
||||
|
||||
})(window.HTMLImports);
|
||||
29
src/WebComponents/shadowdom.js
Normal file
29
src/WebComponents/shadowdom.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
(function(scope) {
|
||||
/**
|
||||
The ShadowDOM polyfill uses a wrapping strategy on dom elements. This is
|
||||
99% transparent but there are a few nodes (e.g. document) that cannot be
|
||||
automatically wrapped. Therefore, rarely it's necessary to wrap nodes in
|
||||
user code. Here we're choosing to make convenient globals `wrap` and
|
||||
`unwrap` that can be used whether or not the polyfill is in use.
|
||||
*/
|
||||
// convenient global
|
||||
if (window.ShadowDOMPolyfill) {
|
||||
window.wrap = ShadowDOMPolyfill.wrapIfNeeded;
|
||||
window.unwrap = ShadowDOMPolyfill.unwrapIfNeeded;
|
||||
} else {
|
||||
// so we can call wrap/unwrap without testing for ShadowDOMPolyfill
|
||||
window.wrap = window.unwrap = function(n) {
|
||||
return n;
|
||||
};
|
||||
}
|
||||
|
||||
})(window.WebComponents);
|
||||
33
src/WebComponents/unresolved.js
Normal file
33
src/WebComponents/unresolved.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
(function(scope) {
|
||||
|
||||
// It's desireable to provide a default stylesheet
|
||||
// that's convenient for styling unresolved elements, but
|
||||
// it's cumbersome to have to include this manually in every page.
|
||||
// It would make sense to put inside some HTMLImport but
|
||||
// the HTMLImports polyfill does not allow loading of stylesheets
|
||||
// that block rendering. Therefore this injection is tolerated here.
|
||||
//
|
||||
// NOTE: position: relative fixes IE's failure to inherit opacity
|
||||
// when a child is not statically positioned.
|
||||
var style = document.createElement('style');
|
||||
style.textContent = ''
|
||||
+ 'body {'
|
||||
+ 'transition: opacity ease-in 0.2s;'
|
||||
+ ' } \n'
|
||||
+ 'body[unresolved] {'
|
||||
+ 'opacity: 0; display: block; overflow: hidden; position: relative;'
|
||||
+ ' } \n'
|
||||
;
|
||||
var head = document.querySelector('head');
|
||||
head.insertBefore(style, head.firstChild);
|
||||
|
||||
})(window.WebComponents);
|
||||
54
tests/CustomElements/html/attributes.html
Normal file
54
tests/CustomElements/html/attributes.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Custom Elements: attributes</title>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script src="../../../src/CustomElements/CustomElements.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
var prototype = Object.create(HTMLElement.prototype);
|
||||
prototype.removeAttributeOk = false;
|
||||
prototype.setAttributeOk = false;
|
||||
prototype.attributeChangedOk = false;
|
||||
prototype.removeAttribute = function(name) {
|
||||
this.removeAttributeOk = true;
|
||||
return HTMLElement.prototype.removeAttribute.call(this, name);
|
||||
};
|
||||
prototype.setAttribute = function(name, value) {
|
||||
this.setAttributeOk = true;
|
||||
HTMLElement.prototype.setAttribute.call(this, name, value);
|
||||
};
|
||||
prototype.attributeChangedCallback = function(name, oldValue, newValue) {
|
||||
this.attributeChangedOk = (name === 'squid') && (oldValue === null)
|
||||
&& (newValue === 'tentacles');
|
||||
};
|
||||
document.registerElement('x-foo', {prototype: prototype});
|
||||
|
||||
addEventListener('WebComponentsReady', function() {
|
||||
var xfoo = document.createElement('x-foo');
|
||||
chai.assert.isFalse(xfoo.removeAttributeOk);
|
||||
chai.assert.isFalse(xfoo.setAttributeOk);
|
||||
chai.assert.isFalse(xfoo.attributeChangedOk);
|
||||
xfoo.setAttribute('Squid', 'tentacles');
|
||||
chai.assert.isTrue(xfoo.setAttributeOk);
|
||||
chai.assert.isTrue(xfoo.attributeChangedOk);
|
||||
xfoo.attributeChangedOk = false;
|
||||
xfoo.removeAttribute('squid');
|
||||
chai.assert.isTrue(xfoo.removeAttributeOk);
|
||||
chai.assert.isTrue(xfoo.setAttributeOk);
|
||||
done();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
55
tests/CustomElements/html/imports.html
Normal file
55
tests/CustomElements/html/imports.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Custom Elements: imports integration</title>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script>
|
||||
HTMLImports = {};
|
||||
|
||||
var elementsCreated = 0;
|
||||
|
||||
addEventListener('load', function() {
|
||||
setTimeout(function() {
|
||||
document.dispatchEvent(new CustomEvent('HTMLImportsLoaded', {bubbles: true}));
|
||||
// parsing hook available
|
||||
chai.assert.ok(HTMLImports.__importsParsingHook, 'imports parsing hook installed');
|
||||
//
|
||||
var doc = document.implementation.createHTMLDocument('Foo');
|
||||
doc.body.innerHTML = '<x-foo></x-foo>';
|
||||
var link = document.createElement('mock-link');
|
||||
link.import = doc;
|
||||
HTMLImports.__importsParsingHook(link);
|
||||
//
|
||||
addEventListener('WebComponentsReady', function() {
|
||||
chai.assert.equal(2, elementsCreated, 'HTML hook allows main document and import document to upgrade');
|
||||
done();
|
||||
})
|
||||
}, 1);
|
||||
});
|
||||
</script>
|
||||
<script src="../../../src/CustomElements/CustomElements.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
proto.createdCallback = function() {
|
||||
elementsCreated++;
|
||||
}
|
||||
|
||||
document.registerElement('x-foo', {prototype: proto});
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<x-foo></x-foo>
|
||||
</body>
|
||||
</html>
|
||||
59
tests/CustomElements/html/shadowdom.html
Normal file
59
tests/CustomElements/html/shadowdom.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Custom Elements: shadowdom integration</title>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script>
|
||||
hasShadowDOM = Boolean(Element.prototype.createShadowRoot);
|
||||
</script>
|
||||
<script src="../../../src/CustomElements/CustomElements.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<x-host></x-host>
|
||||
<script>
|
||||
(function() {
|
||||
if (hasShadowDOM) {
|
||||
var elementsCreated = 0;
|
||||
// basic proto
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
proto.createdCallback = function() {
|
||||
elementsCreated++;
|
||||
};
|
||||
// extend to create shadowRoot containing a custom element
|
||||
var hostProto = Object.create(proto);
|
||||
hostProto.createdCallback = function() {
|
||||
proto.createdCallback.call(this);
|
||||
chai.assert.equal(elementsCreated, 1, 'upgraded element');
|
||||
var root = this.createShadowRoot();
|
||||
// create element via innerHTML
|
||||
root.innerHTML = '<x-foo></x-foo>';
|
||||
CustomElements.takeRecords(root);
|
||||
chai.assert.equal(elementsCreated, 2, 'upgraded element inside shadowRoot');
|
||||
// now create async to test if the shadowRoot MutationObserver sees
|
||||
setTimeout(function() {
|
||||
root.innerHTML = '<x-foo></x-foo>';
|
||||
CustomElements.takeRecords(root);
|
||||
chai.assert.equal(elementsCreated, 3, 'upgraded element created async inside shadowRoot');
|
||||
done();
|
||||
}, 50);
|
||||
|
||||
};
|
||||
document.registerElement('x-foo', {prototype: proto});
|
||||
document.registerElement('x-host', {prototype: hostProto});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
44
tests/CustomElements/html/upgrade-dcl.html
Normal file
44
tests/CustomElements/html/upgrade-dcl.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Custom Elements: upgrade order</title>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script src="../../../src/CustomElements/CustomElements.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
var created = [];
|
||||
function registerTestElement(name) {
|
||||
var p = Object.create(HTMLElement.prototype);
|
||||
p.createdCallback = function() {
|
||||
created.push(this.localName);
|
||||
};
|
||||
document.registerElement(name, {prototype: p});
|
||||
}
|
||||
|
||||
registerTestElement('x-foo');
|
||||
|
||||
addEventListener('DOMContentLoaded', function() {
|
||||
registerTestElement('x-bar');
|
||||
});
|
||||
|
||||
addEventListener('WebComponentsReady', function() {
|
||||
chai.assert.equal(created.length, 2, 'elements registered a DOMContentLoaded time are upgraded');
|
||||
done();
|
||||
});
|
||||
</script>
|
||||
|
||||
<x-foo></x-foo>
|
||||
<x-bar></x-bar>
|
||||
</body>
|
||||
</html>
|
||||
69
tests/CustomElements/html/upgrade-order.html
Normal file
69
tests/CustomElements/html/upgrade-order.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Custom Elements: upgrade order</title>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script src="../../../src/CustomElements/CustomElements.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
var created = [];
|
||||
var attached = [];
|
||||
|
||||
function registerTestElement(name) {
|
||||
var p = Object.create(HTMLElement.prototype);
|
||||
p.createdCallback = function() {
|
||||
created.push(this.localName);
|
||||
};
|
||||
p.attachedCallback = function() {
|
||||
attached.push(this.localName);
|
||||
};
|
||||
p.custom = true;
|
||||
document.registerElement(name, {prototype: p});
|
||||
}
|
||||
|
||||
registerTestElement('x-parent');
|
||||
registerTestElement('x-child');
|
||||
registerTestElement('x-grandchild');
|
||||
|
||||
var order = [
|
||||
'x-parent',
|
||||
'x-child', 'x-grandchild', 'x-grandchild',
|
||||
'x-child', 'x-grandchild', 'x-grandchild',
|
||||
'x-parent'
|
||||
];
|
||||
|
||||
addEventListener('WebComponentsReady', function() {
|
||||
chai.assert.equal(created.length, order.length, 'created length is correct');
|
||||
chai.assert.equal(attached.length, order.length, 'attached length is correct');
|
||||
for (var i=0; i< order.length; i++) {
|
||||
chai.assert.equal(created[i], order[i]);
|
||||
chai.assert.equal(attached[i], order[i]);
|
||||
}
|
||||
done();
|
||||
});
|
||||
</script>
|
||||
|
||||
<x-parent>
|
||||
<x-child>
|
||||
<x-grandChild></x-grandChild>
|
||||
<x-grandChild></x-grandChild>
|
||||
</x-child>
|
||||
<x-child>
|
||||
<x-grandChild></x-grandChild>
|
||||
<x-grandChild></x-grandChild>
|
||||
</x-child>
|
||||
</x-parent>
|
||||
<x-parent></x-parent>
|
||||
</body>
|
||||
</html>
|
||||
527
tests/CustomElements/js/customElements.js
Normal file
527
tests/CustomElements/js/customElements.js
Normal file
@@ -0,0 +1,527 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
suite('customElements', function() {
|
||||
var work;
|
||||
var assert = chai.assert;
|
||||
var HTMLNS = 'http://www.w3.org/1999/xhtml';
|
||||
|
||||
setup(function() {
|
||||
work = document.createElement('div');
|
||||
document.body.appendChild(work);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.removeChild(work);
|
||||
});
|
||||
|
||||
test('document.registerElement requires name argument', function() {
|
||||
try {
|
||||
document.registerElement();
|
||||
} catch(x) {
|
||||
return;
|
||||
}
|
||||
assert.ok(false, 'document.registerElement failed to throw when given no arguments');
|
||||
});
|
||||
|
||||
test('document.registerElement requires name argument to contain a dash', function() {
|
||||
try {
|
||||
document.registerElement('xfoo', {prototype: Object.create(HTMLElement.prototype)});
|
||||
} catch(x) {
|
||||
return;
|
||||
}
|
||||
assert.ok(false, 'document.registerElement failed to throw when given no arguments');
|
||||
});
|
||||
|
||||
// http://w3c.github.io/webcomponents/spec/custom/#extensions-to-document-interface-to-register
|
||||
test('document.registerElement second argument is optional', function() {
|
||||
try {
|
||||
document.registerElement('x-no-proto');
|
||||
} catch(x) {
|
||||
return;
|
||||
}
|
||||
assert.ok(true, 'document.registerElement failed to function without ElementRegistionOptions argument');
|
||||
});
|
||||
|
||||
test('document.registerElement requires name argument to not conflict with a reserved name', function() {
|
||||
try {
|
||||
document.registerElement('font-face', {prototype: Object.create(HTMLElement.prototype)});
|
||||
} catch(x) {
|
||||
return;
|
||||
}
|
||||
assert.ok(false, 'Failed to execute \'registerElement\' on \'Document\': Registration failed for type \'font-face\'. The type name is invalid.');
|
||||
});
|
||||
|
||||
test('document.registerElement requires name argument to be unique', function() {
|
||||
var proto = {prototype: Object.create(HTMLElement.prototype)};
|
||||
document.registerElement('x-duplicate', proto);
|
||||
try {
|
||||
document.registerElement('x-duplicate', proto);
|
||||
} catch(x) {
|
||||
return;
|
||||
}
|
||||
assert.ok(false, 'document.registerElement failed to throw when called multiple times with the same element name');
|
||||
});
|
||||
|
||||
test('document.registerElement create via new', function() {
|
||||
// register x-foo
|
||||
var XFoo = document.registerElement('x-foo', {prototype: Object.create(HTMLElement.prototype)});
|
||||
// create an instance via new
|
||||
var xfoo = new XFoo();
|
||||
// test localName
|
||||
assert.equal(xfoo.localName, 'x-foo');
|
||||
// attach content
|
||||
work.appendChild(xfoo).textContent = '[x-foo]';
|
||||
// reacquire
|
||||
var xfoo = work.querySelector('x-foo');
|
||||
// test textContent
|
||||
assert.equal(xfoo.textContent, '[x-foo]');
|
||||
});
|
||||
|
||||
test('document.registerElement create via createElement', function() {
|
||||
// register x-foo
|
||||
var XFoo = document.registerElement('x-foo2', {prototype: Object.create(HTMLElement.prototype)});
|
||||
// create an instance via createElement
|
||||
var xfoo = document.createElement('x-foo2');
|
||||
// test localName
|
||||
assert.equal(xfoo.localName, 'x-foo2');
|
||||
// attach content
|
||||
xfoo.textContent = '[x-foo2]';
|
||||
// test textContent
|
||||
assert.equal(xfoo.textContent, '[x-foo2]');
|
||||
});
|
||||
|
||||
test('document.registerElement create via createElementNS', function() {
|
||||
// create an instance via createElementNS
|
||||
var xfoo = document.createElementNS(HTMLNS, 'x-foo2');
|
||||
// test localName
|
||||
assert.equal(xfoo.localName, 'x-foo2');
|
||||
// attach content
|
||||
xfoo.textContent = '[x-foo2]';
|
||||
// test textContent
|
||||
assert.equal(xfoo.textContent, '[x-foo2]');
|
||||
});
|
||||
|
||||
test('document.registerElement treats names as case insensitive', function() {
|
||||
var proto = {prototype: Object.create(HTMLElement.prototype)};
|
||||
proto.prototype.isXCase = true;
|
||||
var XCase = document.registerElement('X-CASE', proto);
|
||||
// createElement
|
||||
var x = document.createElement('X-CASE');
|
||||
assert.equal(x.isXCase, true);
|
||||
x = document.createElement('x-case');
|
||||
assert.equal(x.isXCase, true);
|
||||
// createElementNS
|
||||
// NOTE: createElementNS is case sensitive, disable tests
|
||||
// x = document.createElementNS(HTMLNS, 'X-CASE');
|
||||
// assert.equal(x.isXCase, true);
|
||||
// x = document.createElementNS(HTMLNS, 'x-case');
|
||||
// assert.equal(x.isXCase, true);
|
||||
// upgrade
|
||||
work.innerHTML = '<X-CASE></X-CASE><x-CaSe></x-CaSe>';
|
||||
CustomElements.takeRecords();
|
||||
assert.equal(work.firstChild.isXCase, true);
|
||||
assert.equal(work.firstChild.nextSibling.isXCase, true);
|
||||
});
|
||||
|
||||
test('document.registerElement create multiple instances', function() {
|
||||
var XFooPrototype = Object.create(HTMLElement.prototype);
|
||||
XFooPrototype.bluate = function() {
|
||||
this.color = 'lightblue';
|
||||
};
|
||||
var XFoo = document.registerElement('x-foo3', {
|
||||
prototype: XFooPrototype
|
||||
});
|
||||
// create an instance
|
||||
var xfoo1 = new XFoo();
|
||||
// create another instance
|
||||
var xfoo2 = new XFoo();
|
||||
// test textContent
|
||||
xfoo1.textContent = '[x-foo1]';
|
||||
xfoo2.textContent = '[x-foo2]';
|
||||
assert.equal(xfoo1.textContent, '[x-foo1]');
|
||||
assert.equal(xfoo2.textContent, '[x-foo2]');
|
||||
// test bluate
|
||||
xfoo1.bluate();
|
||||
assert.equal(xfoo1.color, 'lightblue');
|
||||
assert.isUndefined(xfoo2.color);
|
||||
});
|
||||
|
||||
test('document.registerElement extend native element', function() {
|
||||
// test native element extension
|
||||
var XBarPrototype = Object.create(HTMLButtonElement.prototype);
|
||||
var XBar = document.registerElement('x-bar', {
|
||||
prototype: XBarPrototype,
|
||||
extends: 'button'
|
||||
});
|
||||
var xbar = new XBar();
|
||||
work.appendChild(xbar).textContent = 'x-bar';
|
||||
xbar = work.querySelector('button[is=x-bar]');
|
||||
assert(xbar);
|
||||
assert.equal(xbar.textContent, 'x-bar');
|
||||
// test extension of native element extension
|
||||
var XBarBarPrototype = Object.create(XBarPrototype);
|
||||
var XBarBar = document.registerElement('x-barbar', {
|
||||
prototype: XBarBarPrototype,
|
||||
extends: 'button'
|
||||
});
|
||||
var xbarbar = new XBarBar();
|
||||
work.appendChild(xbarbar).textContent = 'x-barbar';
|
||||
xbarbar = work.querySelector('button[is=x-barbar]');
|
||||
assert(xbarbar);
|
||||
assert.equal(xbarbar.textContent, 'x-barbar');
|
||||
// test extension^3
|
||||
var XBarBarBarPrototype = Object.create(XBarBarPrototype);
|
||||
var XBarBarBar = document.registerElement('x-barbarbar', {
|
||||
prototype: XBarBarBarPrototype,
|
||||
extends: 'button'
|
||||
});
|
||||
var xbarbarbar = new XBarBarBar();
|
||||
work.appendChild(xbarbarbar).textContent = 'x-barbarbar';
|
||||
xbarbarbar = work.querySelector('button[is=x-barbarbar]');
|
||||
assert(xbarbarbar);
|
||||
assert.equal(xbarbarbar.textContent, 'x-barbarbar');
|
||||
});
|
||||
|
||||
test('document.registerElement createdCallback in prototype', function() {
|
||||
var XBooPrototype = Object.create(HTMLElement.prototype);
|
||||
XBooPrototype.createdCallback = function() {
|
||||
this.style.fontStyle = 'italic';
|
||||
}
|
||||
var XBoo = document.registerElement('x-boo', {
|
||||
prototype: XBooPrototype
|
||||
});
|
||||
var xboo = new XBoo();
|
||||
assert.equal(xboo.style.fontStyle, 'italic');
|
||||
//
|
||||
var XBooBooPrototype = Object.create(XBooPrototype);
|
||||
XBooBooPrototype.createdCallback = function() {
|
||||
XBoo.prototype.createdCallback.call(this);
|
||||
this.style.fontSize = '32pt';
|
||||
};
|
||||
var XBooBoo = document.registerElement('x-booboo', {
|
||||
prototype: XBooBooPrototype
|
||||
});
|
||||
var xbooboo = new XBooBoo();
|
||||
assert.equal(xbooboo.style.fontStyle, 'italic');
|
||||
assert.equal(xbooboo.style.fontSize, '32pt');
|
||||
});
|
||||
|
||||
test('document.registerElement [created|attached|detached]Callbacks in prototype', function(done) {
|
||||
var ready, inserted, removed;
|
||||
var XBooPrototype = Object.create(HTMLElement.prototype);
|
||||
XBooPrototype.createdCallback = function() {
|
||||
ready = true;
|
||||
}
|
||||
XBooPrototype.attachedCallback = function() {
|
||||
inserted = true;
|
||||
}
|
||||
XBooPrototype.detachedCallback = function() {
|
||||
removed = true;
|
||||
}
|
||||
var XBoo = document.registerElement('x-boo-ir', {
|
||||
prototype: XBooPrototype
|
||||
});
|
||||
var xboo = new XBoo();
|
||||
assert(ready, 'ready must be true [XBoo]');
|
||||
assert(!inserted, 'inserted must be false [XBoo]');
|
||||
assert(!removed, 'removed must be false [XBoo]');
|
||||
work.appendChild(xboo);
|
||||
CustomElements.takeRecords();
|
||||
assert(inserted, 'inserted must be true [XBoo]');
|
||||
work.removeChild(xboo);
|
||||
CustomElements.takeRecords();
|
||||
assert(removed, 'removed must be true [XBoo]');
|
||||
//
|
||||
ready = inserted = removed = false;
|
||||
var XBooBooPrototype = Object.create(XBooPrototype);
|
||||
XBooBooPrototype.createdCallback = function() {
|
||||
XBoo.prototype.createdCallback.call(this);
|
||||
};
|
||||
XBooBooPrototype.attachedCallback = function() {
|
||||
XBoo.prototype.attachedCallback.call(this);
|
||||
};
|
||||
XBooBooPrototype.detachedCallback = function() {
|
||||
XBoo.prototype.detachedCallback.call(this);
|
||||
};
|
||||
var XBooBoo = document.registerElement('x-booboo-ir', {
|
||||
prototype: XBooBooPrototype
|
||||
});
|
||||
var xbooboo = new XBooBoo();
|
||||
assert(ready, 'ready must be true [XBooBoo]');
|
||||
assert(!inserted, 'inserted must be false [XBooBoo]');
|
||||
assert(!removed, 'removed must be false [XBooBoo]');
|
||||
work.appendChild(xbooboo);
|
||||
CustomElements.takeRecords();
|
||||
assert(inserted, 'inserted must be true [XBooBoo]');
|
||||
work.removeChild(xbooboo);
|
||||
CustomElements.takeRecords();
|
||||
assert(removed, 'removed must be true [XBooBoo]');
|
||||
done();
|
||||
});
|
||||
|
||||
test('document.registerElement attributeChangedCallback in prototype', function(done) {
|
||||
var XBooPrototype = Object.create(HTMLElement.prototype);
|
||||
XBooPrototype.attributeChangedCallback = function(inName, inOldValue) {
|
||||
if (inName == 'foo' && inOldValue=='bar'
|
||||
&& this.attributes.foo.value == 'zot') {
|
||||
done();
|
||||
}
|
||||
}
|
||||
var XBoo = document.registerElement('x-boo-acp', {
|
||||
prototype: XBooPrototype
|
||||
});
|
||||
var xboo = new XBoo();
|
||||
xboo.setAttribute('foo', 'bar');
|
||||
xboo.setAttribute('foo', 'zot');
|
||||
});
|
||||
|
||||
test('document.registerElement attachedCallbacks in prototype', function(done) {
|
||||
var inserted = 0;
|
||||
var XBooPrototype = Object.create(HTMLElement.prototype);
|
||||
XBooPrototype.attachedCallback = function() {
|
||||
inserted++;
|
||||
};
|
||||
var XBoo = document.registerElement('x-boo-at', {
|
||||
prototype: XBooPrototype
|
||||
});
|
||||
var xboo = new XBoo();
|
||||
assert.equal(inserted, 0, 'inserted must be 0');
|
||||
work.appendChild(xboo);
|
||||
CustomElements.takeRecords();
|
||||
assert.equal(inserted, 1, 'inserted must be 1');
|
||||
work.removeChild(xboo);
|
||||
CustomElements.takeRecords();
|
||||
assert(!xboo.parentNode);
|
||||
work.appendChild(xboo);
|
||||
CustomElements.takeRecords();
|
||||
assert.equal(inserted, 2, 'inserted must be 2');
|
||||
done();
|
||||
});
|
||||
|
||||
test('document.registerElement detachedCallbacks in prototype', function(done) {
|
||||
var ready, inserted, removed;
|
||||
var XBooPrototype = Object.create(HTMLElement.prototype);
|
||||
XBooPrototype.detachedCallback = function() {
|
||||
removed = true;
|
||||
}
|
||||
var XBoo = document.registerElement('x-boo-ir2', {
|
||||
prototype: XBooPrototype
|
||||
});
|
||||
var xboo = new XBoo();
|
||||
assert(!removed, 'removed must be false [XBoo]');
|
||||
work.appendChild(xboo);
|
||||
CustomElements.takeRecords();
|
||||
work.removeChild(xboo);
|
||||
CustomElements.takeRecords();
|
||||
assert(removed, 'removed must be true [XBoo]');
|
||||
//
|
||||
ready = inserted = removed = false;
|
||||
var XBooBooPrototype = Object.create(XBooPrototype);
|
||||
XBooBooPrototype.detachedCallback = function() {
|
||||
XBoo.prototype.detachedCallback.call(this);
|
||||
};
|
||||
var XBooBoo = document.registerElement('x-booboo-ir2', {
|
||||
prototype: XBooBooPrototype
|
||||
});
|
||||
var xbooboo = new XBooBoo();
|
||||
assert(!removed, 'removed must be false [XBooBoo]');
|
||||
work.appendChild(xbooboo);
|
||||
CustomElements.takeRecords();
|
||||
work.removeChild(xbooboo);
|
||||
CustomElements.takeRecords();
|
||||
assert(removed, 'removed must be true [XBooBoo]');
|
||||
done();
|
||||
});
|
||||
|
||||
test('document.registerElement can use Functions as definitions', function() {
|
||||
// function used as Custom Element defintion
|
||||
function A$A() {
|
||||
this.alive = true;
|
||||
}
|
||||
A$A.prototype = Object.create(HTMLElement.prototype);
|
||||
// bind createdCallback to function body
|
||||
A$A.prototype.createdCallback = A$A;
|
||||
A$A = document.registerElement('a-a', A$A);
|
||||
// test via new
|
||||
var a = new A$A();
|
||||
assert.equal(a.alive, true);
|
||||
// test via parser upgrade
|
||||
work.innerHTML = '<a-a></a-a>';
|
||||
CustomElements.takeRecords();
|
||||
assert.equal(work.firstElementChild.alive, true);
|
||||
});
|
||||
|
||||
test('node.cloneNode upgrades', function(done) {
|
||||
var XBooPrototype = Object.create(HTMLElement.prototype);
|
||||
XBooPrototype.createdCallback = function() {
|
||||
this.__ready__ = true;
|
||||
};
|
||||
var XBoo = document.registerElement('x-boo-clone', {
|
||||
prototype: XBooPrototype
|
||||
});
|
||||
var xboo = new XBoo();
|
||||
work.appendChild(xboo);
|
||||
CustomElements.takeRecords();
|
||||
var xboo2 = xboo.cloneNode(true);
|
||||
assert(xboo2.__ready__, 'clone createdCallback must be called');
|
||||
done();
|
||||
});
|
||||
|
||||
test('entered left apply to view', function() {
|
||||
var invocations = [];
|
||||
var elementProto = Object.create(HTMLElement.prototype);
|
||||
elementProto.createdCallback = function() {
|
||||
invocations.push('created');
|
||||
}
|
||||
elementProto.attachedCallback = function() {
|
||||
invocations.push('entered');
|
||||
}
|
||||
elementProto.detachedCallback = function() {
|
||||
invocations.push('left');
|
||||
}
|
||||
var tagName = 'x-entered-left-view';
|
||||
var CustomElement = document.registerElement(tagName, { prototype: elementProto });
|
||||
|
||||
var docB = document.implementation.createHTMLDocument('');
|
||||
docB.body.innerHTML = '<' + tagName + '></' + tagName + '>';
|
||||
CustomElements.upgradeDocumentTree(docB);
|
||||
CustomElements.takeRecords();
|
||||
assert.deepEqual(invocations, ['created'], 'created but not entered view');
|
||||
|
||||
var element = docB.body.childNodes[0];
|
||||
// note, cannot use instanceof due to IE
|
||||
assert.equal(element.__proto__, CustomElement.prototype, 'element is correct type');
|
||||
|
||||
work.appendChild(element)
|
||||
CustomElements.takeRecords();
|
||||
assert.deepEqual(invocations, ['created', 'entered'],
|
||||
'created and entered view');
|
||||
|
||||
docB.body.appendChild(element);
|
||||
CustomElements.takeRecords();
|
||||
assert.deepEqual(invocations, ['created', 'entered', 'left'],
|
||||
'created, entered then left view');
|
||||
});
|
||||
|
||||
test('attachedCallback ordering', function() {
|
||||
var log = [];
|
||||
var p = Object.create(HTMLElement.prototype);
|
||||
p.attachedCallback = function() {
|
||||
log.push(this.id);
|
||||
};
|
||||
document.registerElement('x-boo-ordering', {prototype: p});
|
||||
|
||||
work.innerHTML =
|
||||
'<x-boo-ordering id=a>' +
|
||||
'<x-boo-ordering id=b></x-boo-ordering>' +
|
||||
'<x-boo-ordering id=c>' +
|
||||
'<x-boo-ordering id=d></x-boo-ordering>' +
|
||||
'<x-boo-ordering id=e></x-boo-ordering>' +
|
||||
'</x-boo-ordering>' +
|
||||
'</x-boo-ordering>';
|
||||
|
||||
CustomElements.takeRecords();
|
||||
assert.deepEqual(['a', 'b', 'c', 'd', 'e'], log);
|
||||
});
|
||||
|
||||
test('detachedCallback ordering', function() {
|
||||
var log = [];
|
||||
var p = Object.create(HTMLElement.prototype);
|
||||
p.detachedCallback = function() {
|
||||
log.push(this.id);
|
||||
};
|
||||
document.registerElement('x-boo2-ordering', {prototype: p});
|
||||
|
||||
work.innerHTML =
|
||||
'<x-boo2-ordering id=a>' +
|
||||
'<x-boo2-ordering id=b></x-boo2-ordering>' +
|
||||
'<x-boo2-ordering id=c>' +
|
||||
'<x-boo2-ordering id=d></x-boo2-ordering>' +
|
||||
'<x-boo2-ordering id=e></x-boo2-ordering>' +
|
||||
'</x-boo2-ordering>' +
|
||||
'</x-boo2-ordering>';
|
||||
|
||||
CustomElements.takeRecords();
|
||||
work.removeChild(work.firstElementChild);
|
||||
CustomElements.takeRecords();
|
||||
assert.deepEqual(['a', 'b', 'c', 'd', 'e'], log);
|
||||
});
|
||||
|
||||
test('instanceof', function() {
|
||||
var p = Object.create(HTMLElement.prototype);
|
||||
var PCtor = document.registerElement('x-instance', {prototype: p});
|
||||
var x = document.createElement('x-instance');
|
||||
assert.isTrue(CustomElements.instanceof(x, PCtor), 'instanceof failed for x-instance');
|
||||
x = document.createElementNS(HTMLNS, 'x-instance');
|
||||
assert.isTrue(CustomElements.instanceof(x, PCtor), 'instanceof failed for x-instance');
|
||||
|
||||
var p2 = Object.create(PCtor.prototype);
|
||||
var P2Ctor = document.registerElement('x-instance2', {prototype: p2});
|
||||
var x2 = document.createElement('x-instance2');
|
||||
assert.isTrue(CustomElements.instanceof(x2, P2Ctor), 'instanceof failed for x-instance2');
|
||||
assert.isTrue(CustomElements.instanceof(x2, PCtor), 'instanceof failed for x-instance2');
|
||||
x2 = document.createElementNS(HTMLNS, 'x-instance2');
|
||||
assert.isTrue(CustomElements.instanceof(x2, P2Ctor), 'instanceof failed for x-instance2');
|
||||
assert.isTrue(CustomElements.instanceof(x2, PCtor), 'instanceof failed for x-instance2');
|
||||
});
|
||||
|
||||
|
||||
test('instanceof typeExtension', function() {
|
||||
var p = Object.create(HTMLButtonElement.prototype);
|
||||
var PCtor = document.registerElement('x-button-instance', {prototype: p, extends: 'button'});
|
||||
var x = document.createElement('button', 'x-button-instance');
|
||||
assert.isTrue(CustomElements.instanceof(x, PCtor), 'instanceof failed for x-button-instance');
|
||||
assert.isTrue(CustomElements.instanceof(x, HTMLButtonElement), 'instanceof failed for x-button-instance');
|
||||
x = document.createElementNS(HTMLNS, 'button', 'x-button-instance');
|
||||
assert.isTrue(CustomElements.instanceof(x, PCtor), 'instanceof failed for x-button-instance');
|
||||
assert.isTrue(CustomElements.instanceof(x, HTMLButtonElement), 'instanceof failed for x-button-instance');
|
||||
|
||||
var p2 = Object.create(PCtor.prototype);
|
||||
var P2Ctor = document.registerElement('x-button-instance2', {prototype: p2, extends: 'button'});
|
||||
var x2 = document.createElement('button','x-button-instance2');
|
||||
assert.isTrue(CustomElements.instanceof(x2, P2Ctor), 'instanceof failed for x-button-instance2');
|
||||
assert.isTrue(CustomElements.instanceof(x2, PCtor), 'instanceof failed for x-button-instance2');
|
||||
assert.isTrue(CustomElements.instanceof(x2, HTMLButtonElement), 'instanceof failed for x-button-instance2');
|
||||
x2 = document.createElementNS(HTMLNS, 'button','x-button-instance2');
|
||||
assert.isTrue(CustomElements.instanceof(x2, P2Ctor), 'instanceof failed for x-button-instance2');
|
||||
assert.isTrue(CustomElements.instanceof(x2, PCtor), 'instanceof failed for x-button-instance2');
|
||||
assert.isTrue(CustomElements.instanceof(x2, HTMLButtonElement), 'instanceof failed for x-button-instance2');
|
||||
});
|
||||
|
||||
test('extends and prototype mismatch', function() {
|
||||
var p = Object.create(HTMLElement.prototype);
|
||||
var PCtor = document.registerElement('not-button', {
|
||||
extends: 'button',
|
||||
prototype: p
|
||||
});
|
||||
|
||||
var e = document.createElement('button', 'not-button');
|
||||
|
||||
// NOTE: firefox has a hack for instanceof that uses element tagname mapping
|
||||
// Work around by checking prototype manually
|
||||
//
|
||||
var ff = document.createElement('button'); ff.__proto__ = null;
|
||||
if (ff instanceof HTMLButtonElement) {
|
||||
// Base proto will be one below custom proto
|
||||
var proto = e.__proto__.__proto__;
|
||||
assert.isFalse(proto === HTMLButtonElement.prototype);
|
||||
assert.isTrue(proto === HTMLElement.prototype);
|
||||
} else {
|
||||
assert.isFalse(CustomElements.instanceof(e, HTMLButtonElement));
|
||||
assert.isTrue(CustomElements.instanceof(e, HTMLElement));
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
htmlSuite('customElements (html)', function() {
|
||||
htmlTest('../html/attributes.html');
|
||||
htmlTest('../html/upgrade-order.html');
|
||||
htmlTest('../html/upgrade-dcl.html');
|
||||
htmlTest('../html/imports.html');
|
||||
htmlTest('../html/shadowdom.html');
|
||||
});
|
||||
242
tests/CustomElements/js/documentRegister.js
Normal file
242
tests/CustomElements/js/documentRegister.js
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
// Adapted from:
|
||||
// https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/LayoutTests/fast/dom/custom/document-register-type-extensions.html
|
||||
var testForm = document.createElement('form');
|
||||
|
||||
function isFormControl(element)
|
||||
{
|
||||
testForm.appendChild(element);
|
||||
return element.form == testForm;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Work around IE's insertion of XML Namespace elements into .outerHTML of HTMLUnknownElements
|
||||
*
|
||||
* Clone the input node, insert it into a div, and then read back the outerHTML, which is now stripped of the XML *
|
||||
* Namespace element
|
||||
*/
|
||||
var isIE = navigator.userAgent.indexOf('Trident') > -1;
|
||||
function assertOuterHTML(element, expected) {
|
||||
var outerHTML = element.outerHTML;
|
||||
if (isIE) {
|
||||
var div = document.createElement('div');
|
||||
div.appendChild(element.cloneNode(true));
|
||||
outerHTML = div.firstChild.outerHTML;
|
||||
}
|
||||
chai.assert.equal(outerHTML, expected);
|
||||
}
|
||||
|
||||
var hasProto = ({}.__proto__);
|
||||
function assertInstanceOf(element, constructor) {
|
||||
if (hasProto) {
|
||||
chai.assert.instanceOf(element, constructor);
|
||||
}
|
||||
}
|
||||
|
||||
function assertNotInstanceOf(element, constructor) {
|
||||
if (hasProto) {
|
||||
chai.assert.notInstanceOf(element, constructor);
|
||||
}
|
||||
}
|
||||
|
||||
suite('register-type-extensions', function() {
|
||||
var assert = chai.assert;
|
||||
|
||||
var fooConstructor = document.registerElement('x-foo-x', {
|
||||
prototype: Object.create(HTMLElement.prototype) });
|
||||
var fooOuterHTML = '<x-foo-x></x-foo-x>';
|
||||
var barConstructor = document.registerElement('x-bar-x', {
|
||||
prototype: Object.create(HTMLInputElement.prototype),
|
||||
extends:'input'});
|
||||
var barOuterHTML = '<input is="x-bar-x">';
|
||||
var bazConstructor = document.registerElement('x-baz', {
|
||||
prototype: Object.create(fooConstructor.prototype) });
|
||||
var quxConstructor = document.registerElement('x-qux', {
|
||||
prototype: Object.create(barConstructor.prototype),
|
||||
extends:'input'});
|
||||
|
||||
test('cannot register twice', function() {
|
||||
assert.throws(function() {
|
||||
document.registerElement('x-foo-x', {
|
||||
prototype: Object.create(HTMLDivElement.prototype) });
|
||||
});
|
||||
});
|
||||
|
||||
suite('generated constructors', function() {
|
||||
test('custom tag', function() {
|
||||
var fooNewed = new fooConstructor();
|
||||
assertOuterHTML(fooNewed, fooOuterHTML);
|
||||
assertInstanceOf(fooNewed, fooConstructor);
|
||||
assertInstanceOf(fooNewed, HTMLElement);
|
||||
// This is part of the Blink tests, but not supported in Firefox with
|
||||
// polyfill. Similar assertions are also commented out below.
|
||||
// assertNotInstanceOf(fooNewed, HTMLUnknownElement);
|
||||
|
||||
test('custom tag constructor', function() {
|
||||
assert.equal('a', 'b');
|
||||
});
|
||||
});
|
||||
|
||||
test('type extension', function() {
|
||||
var barNewed = new barConstructor();
|
||||
assertOuterHTML(barNewed, barOuterHTML);
|
||||
assertInstanceOf(barNewed, barConstructor);
|
||||
assertInstanceOf(barNewed, HTMLInputElement);
|
||||
assert.ok(isFormControl(barNewed));
|
||||
});
|
||||
|
||||
test('custom tag deriving from custom tag', function() {
|
||||
var bazNewed = new bazConstructor();
|
||||
var bazOuterHTML = '<x-baz></x-baz>';
|
||||
assertOuterHTML(bazNewed, bazOuterHTML);
|
||||
assertInstanceOf(bazNewed, bazConstructor);
|
||||
assertInstanceOf(bazNewed, HTMLElement);
|
||||
// assertNotInstanceOf(bazNewed, HTMLUnknownElement);
|
||||
});
|
||||
|
||||
test('type extension deriving from custom tag', function() {
|
||||
var quxNewed = new quxConstructor();
|
||||
var quxOuterHTML = '<input is="x-qux">';
|
||||
assertInstanceOf(quxNewed, quxConstructor);
|
||||
assertInstanceOf(quxNewed, barConstructor);
|
||||
assertInstanceOf(quxNewed, HTMLInputElement);
|
||||
assertOuterHTML(quxNewed, quxOuterHTML);
|
||||
assert.ok(isFormControl(quxNewed));
|
||||
});
|
||||
});
|
||||
|
||||
suite('single-parameter createElement', function() {
|
||||
test('custom tag', function() {
|
||||
var fooCreated = document.createElement('x-foo-x');
|
||||
assertOuterHTML(fooCreated, fooOuterHTML);
|
||||
assertInstanceOf(fooCreated, fooConstructor);
|
||||
});
|
||||
|
||||
test('type extension', function() {
|
||||
var barCreated = document.createElement('x-bar-x');
|
||||
assertOuterHTML(barCreated, '<x-bar-x></x-bar-x>');
|
||||
assertNotInstanceOf(barCreated, barConstructor);
|
||||
// assertNotInstanceOf(barCreated, HTMLUnknownElement);
|
||||
assertInstanceOf(barCreated, HTMLElement);
|
||||
});
|
||||
|
||||
test('custom tag deriving from custom tag', function() {
|
||||
bazCreated = document.createElement('x-baz');
|
||||
assertOuterHTML(bazCreated, '<x-baz></x-baz>');
|
||||
assertInstanceOf(bazCreated, bazConstructor);
|
||||
// assertNotInstanceOf(bazCreated, HTMLUnknownElement);
|
||||
});
|
||||
|
||||
test('type extension deriving from custom tag', function() {
|
||||
quxCreated = document.createElement('x-qux');
|
||||
assertOuterHTML(quxCreated, '<x-qux></x-qux>');
|
||||
assertNotInstanceOf(quxCreated, quxConstructor);
|
||||
// assertNotInstanceOf(quxCreated, HTMLUnknownElement);
|
||||
assertInstanceOf(quxCreated, HTMLElement);
|
||||
});
|
||||
});
|
||||
|
||||
suite('createElement with type extensions', function() {
|
||||
test('extension is custom tag', function() {
|
||||
var divFooCreated = document.createElement('div', 'x-foo-x');
|
||||
assertOuterHTML(divFooCreated, '<div is="x-foo-x"></div>');
|
||||
assertNotInstanceOf(divFooCreated, fooConstructor);
|
||||
assertInstanceOf(divFooCreated, HTMLDivElement);
|
||||
});
|
||||
|
||||
test('valid extension', function() {
|
||||
var inputBarCreated = document.createElement('input', 'x-bar-x');
|
||||
assertOuterHTML(inputBarCreated, barOuterHTML);
|
||||
assertInstanceOf(inputBarCreated, barConstructor);
|
||||
assertNotInstanceOf(inputBarCreated, HTMLUnknownElement);
|
||||
assert.ok(isFormControl(inputBarCreated));
|
||||
});
|
||||
|
||||
test('type extension of incorrect tag', function() {
|
||||
var divBarCreated = document.createElement('div', 'x-bar-x');
|
||||
assertOuterHTML(divBarCreated, '<div is="x-bar-x"></div>');
|
||||
assertNotInstanceOf(divBarCreated, barConstructor);
|
||||
assertInstanceOf(divBarCreated, HTMLDivElement);
|
||||
});
|
||||
|
||||
test('incorrect extension of custom tag', function() {
|
||||
var fooBarCreated = document.createElement('x-foo-x', 'x-bar-x');
|
||||
assertOuterHTML(fooBarCreated, '<x-foo-x is="x-bar-x"></x-foo-x>');
|
||||
assertInstanceOf(fooBarCreated, fooConstructor);
|
||||
});
|
||||
|
||||
test('incorrect extension of type extension', function() {
|
||||
var barFooCreated = document.createElement('x-bar-x', 'x-foo-x');
|
||||
assertOuterHTML(barFooCreated, '<x-bar-x is="x-foo-x"></x-bar-x>');
|
||||
// assertNotInstanceOf(barFooCreated, HTMLUnknownElement);
|
||||
assertInstanceOf(barFooCreated, HTMLElement);
|
||||
});
|
||||
|
||||
test('null type extension', function() {
|
||||
var fooCreatedNull = document.createElement('x-foo-x', null);
|
||||
assertOuterHTML(fooCreatedNull, fooOuterHTML);
|
||||
assertInstanceOf(fooCreatedNull, fooConstructor);
|
||||
});
|
||||
|
||||
test('empty type extension', function() {
|
||||
fooCreatedEmpty = document.createElement('x-foo-x', '');
|
||||
assertOuterHTML(fooCreatedEmpty, fooOuterHTML);
|
||||
assertInstanceOf(fooCreatedEmpty, fooConstructor);
|
||||
});
|
||||
|
||||
test('invalid tag name', function() {
|
||||
assert.throws(function() {
|
||||
document.createElement('@invalid', 'x-bar-x');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('parser', function() {
|
||||
function createElementFromHTML(html) {
|
||||
var container = document.createElement('div');
|
||||
container.innerHTML = html;
|
||||
if (window.CustomElements) {
|
||||
window.CustomElements.upgradeAll(container);
|
||||
}
|
||||
return container.firstChild;
|
||||
}
|
||||
|
||||
test('custom tag', function() {
|
||||
var fooParsed = createElementFromHTML('<x-foo-x>');
|
||||
assertInstanceOf(fooParsed, fooConstructor);
|
||||
});
|
||||
|
||||
test('type extension', function() {
|
||||
var barParsed = createElementFromHTML('<input is="x-bar-x">');
|
||||
assertInstanceOf(barParsed, barConstructor);
|
||||
assert.ok(isFormControl(barParsed));
|
||||
});
|
||||
|
||||
test('custom tag as type extension', function() {
|
||||
var divFooParsed = createElementFromHTML('<div is="x-foo-x">');
|
||||
assertNotInstanceOf(divFooParsed, fooConstructor);
|
||||
assertInstanceOf(divFooParsed, HTMLDivElement);
|
||||
});
|
||||
|
||||
// Should we upgrade invalid tags to HTMLElement?
|
||||
/*test('type extension as custom tag', function() {
|
||||
var namedBarParsed = createElementFromHTML('<x-bar-x>')
|
||||
assertNotInstanceOf(namedBarParsed, barConstructor);
|
||||
assertNotInstanceOf(namedBarParsed, HTMLUnknownElement);
|
||||
assertInstanceOf(namedBarParsed, HTMLElement);
|
||||
});*/
|
||||
|
||||
test('type extension of incorrect tag', function() {
|
||||
var divBarParsed = createElementFromHTML('<div is="x-bar-x">');
|
||||
assertNotInstanceOf(divBarParsed, barConstructor);
|
||||
assertInstanceOf(divBarParsed, HTMLDivElement);
|
||||
});
|
||||
});
|
||||
});
|
||||
14
tests/CustomElements/js/karma-defer-tests.js
Normal file
14
tests/CustomElements/js/karma-defer-tests.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// defer start of tests until after WebComponentsReady event
|
||||
if (window.__karma__) {
|
||||
window.__karma__.loaded = function() {
|
||||
window.addEventListener('WebComponentsReady', function() {
|
||||
window.__karma__.start();
|
||||
});
|
||||
};
|
||||
}
|
||||
79
tests/CustomElements/js/observe.js
Normal file
79
tests/CustomElements/js/observe.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
suite('observe', function() {
|
||||
var work;
|
||||
var assert = chai.assert;
|
||||
|
||||
setup(function() {
|
||||
work = document.createElement('div');
|
||||
document.body.appendChild(work);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.removeChild(work);
|
||||
});
|
||||
|
||||
function registerTestComponent(inName, inValue) {
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
proto.value = inValue || 'value';
|
||||
document.registerElement(inName, {
|
||||
prototype: proto
|
||||
});
|
||||
}
|
||||
|
||||
function testElements(node, selector, value) {
|
||||
Array.prototype.forEach.call(node.querySelectorAll(selector), function(n) {
|
||||
assert.equal(n.value, value);
|
||||
});
|
||||
}
|
||||
|
||||
test('custom element automatically upgrades', function(done) {
|
||||
work.innerHTML = '<x-auto></x-auto>';
|
||||
var x = work.firstChild;
|
||||
assert.isUndefined(x.value);
|
||||
registerTestComponent('x-auto', 'auto');
|
||||
assert.equal(x.value, 'auto');
|
||||
done();
|
||||
});
|
||||
|
||||
test('custom element automatically upgrades in subtree', function(done) {
|
||||
work.innerHTML = '<div></div>';
|
||||
var target = work.firstChild;
|
||||
target.innerHTML = '<x-auto-sub></x-auto-sub>';
|
||||
var x = target.firstChild;
|
||||
assert.isUndefined(x.value);
|
||||
registerTestComponent('x-auto-sub', 'auto-sub');
|
||||
assert.equal(x.value, 'auto-sub');
|
||||
done();
|
||||
});
|
||||
|
||||
test('custom elements automatically upgrade', function(done) {
|
||||
registerTestComponent('x-auto1', 'auto1');
|
||||
registerTestComponent('x-auto2', 'auto2');
|
||||
work.innerHTML = '<div><div><x-auto1></x-auto1><x-auto1></x-auto1>' +
|
||||
'</div></div><div><x-auto2><x-auto1></x-auto1></x-auto2>' +
|
||||
'<x-auto2><x-auto1></x-auto1></x-auto2></div>';
|
||||
CustomElements.takeRecords();
|
||||
testElements(work, 'x-auto1', 'auto1');
|
||||
testElements(work, 'x-auto2', 'auto2');
|
||||
done();
|
||||
});
|
||||
|
||||
test('custom elements automatically upgrade in subtree', function(done) {
|
||||
registerTestComponent('x-auto-sub1', 'auto-sub1');
|
||||
registerTestComponent('x-auto-sub2', 'auto-sub2');
|
||||
work.innerHTML = '<div></div>';
|
||||
var target = work.firstChild;
|
||||
target.innerHTML = '<div><div><x-auto-sub1></x-auto-sub1><x-auto-sub1></x-auto-sub1>' +
|
||||
'</div></div><div><x-auto-sub2><x-auto-sub1></x-auto-sub1></x-auto-sub2>' +
|
||||
'<x-auto-sub2><x-auto-sub1></x-auto-sub1></x-auto-sub2></div>';
|
||||
CustomElements.takeRecords();
|
||||
testElements(target, 'x-auto-sub1', 'auto-sub1');
|
||||
testElements(target, 'x-auto-sub2', 'auto-sub2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
117
tests/CustomElements/js/upgrade.js
Normal file
117
tests/CustomElements/js/upgrade.js
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
suite('upgradeElements', function() {
|
||||
var work;
|
||||
var assert = chai.assert;
|
||||
|
||||
setup(function() {
|
||||
work = document.createElement('div');
|
||||
document.body.appendChild(work);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.removeChild(work);
|
||||
});
|
||||
|
||||
function registerTestComponent(inName, inValue) {
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
proto.value = inValue || 'value';
|
||||
document.registerElement(inName, {
|
||||
prototype: proto
|
||||
});
|
||||
}
|
||||
|
||||
test('CustomElements.upgrade upgrades custom element syntax', function() {
|
||||
registerTestComponent('x-foo31', 'foo');
|
||||
work.innerHTML = '<x-foo31>Foo</x-foo31>';
|
||||
var xfoo = work.firstChild;
|
||||
CustomElements.upgradeAll(xfoo);
|
||||
assert.equal(xfoo.value, 'foo');
|
||||
});
|
||||
|
||||
test('mutation observer upgrades custom element syntax', function(done) {
|
||||
registerTestComponent('x-foo32', 'foo');
|
||||
work.innerHTML = '<x-foo32>Foo</x-foo32>';
|
||||
CustomElements.takeRecords();
|
||||
var xfoo = work.firstChild;
|
||||
assert.equal(xfoo.value, 'foo');
|
||||
done();
|
||||
});
|
||||
|
||||
test('document.register upgrades custom element syntax', function() {
|
||||
work.innerHTML = '<x-foo33>Foo</x-foo33>';
|
||||
registerTestComponent('x-foo33', 'foo');
|
||||
var xfoo = work.firstChild;
|
||||
assert.equal(xfoo.value, 'foo');
|
||||
});
|
||||
|
||||
test('CustomElements.upgrade upgrades custom element syntax', function() {
|
||||
registerTestComponent('x-zot', 'zot');
|
||||
registerTestComponent('x-zim', 'zim');
|
||||
work.innerHTML = '<x-zot><x-zim></x-zim></x-zot>';
|
||||
var xzot = work.firstChild, xzim = xzot.firstChild;
|
||||
CustomElements.upgradeAll(work);
|
||||
assert.equal(xzot.value, 'zot');
|
||||
assert.equal(xzim.value, 'zim');
|
||||
});
|
||||
|
||||
test('CustomElements.upgrade upgrades native extendor', function() {
|
||||
var XButtonProto = Object.create(HTMLButtonElement.prototype);
|
||||
XButtonProto.test = 'xbutton';
|
||||
document.registerElement('x-button', {
|
||||
extends: 'button',
|
||||
prototype: XButtonProto
|
||||
});
|
||||
|
||||
work.innerHTML = '<button is="x-button"></button>';
|
||||
var xbutton = work.firstChild;
|
||||
CustomElements.upgradeAll(xbutton);
|
||||
assert.equal(xbutton.test, 'xbutton');
|
||||
});
|
||||
|
||||
|
||||
test('CustomElements.upgrade upgrades extendor of native extendor', function() {
|
||||
var XInputProto = Object.create(HTMLInputElement.prototype);
|
||||
XInputProto.xInput = 'xInput';
|
||||
var XInput = document.registerElement('x-input', {
|
||||
extends: 'input',
|
||||
prototype: XInputProto
|
||||
});
|
||||
var XSpecialInputProto = Object.create(XInput.prototype);
|
||||
XSpecialInputProto.xSpecialInput = 'xSpecialInput';
|
||||
var XSpecialInput = document.registerElement('x-special-input', {
|
||||
extends: 'input',
|
||||
prototype: XSpecialInputProto
|
||||
});
|
||||
work.innerHTML = '<input is="x-special-input">';
|
||||
var x = work.firstChild;
|
||||
CustomElements.upgradeAll(x);
|
||||
assert.equal(x.xInput, 'xInput');
|
||||
assert.equal(x.xSpecialInput, 'xSpecialInput');
|
||||
});
|
||||
|
||||
|
||||
test('CustomElements.upgrade upgrades native extendor', function() {
|
||||
var YButtonProto = Object.create(HTMLButtonElement.prototype);
|
||||
YButtonProto.test = 'ybutton';
|
||||
document.registerElement('y-button', {
|
||||
extends: 'button',
|
||||
prototype: YButtonProto
|
||||
});
|
||||
|
||||
work.innerHTML = '<button is="y-button">0</button>' +
|
||||
'<div><button is="y-button">1</button></div>' +
|
||||
'<div><div><button is="y-button">2</button></div></div>';
|
||||
CustomElements.upgradeAll(work);
|
||||
var b$ = work.querySelectorAll('[is=y-button]');
|
||||
Array.prototype.forEach.call(b$, function(b, i) {
|
||||
assert.equal(b.test, 'ybutton');
|
||||
assert.equal(b.textContent, i);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
30
tests/CustomElements/runner.html
Normal file
30
tests/CustomElements/runner.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<title>CustomElements Tests</title>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- Mocha/Chai -->
|
||||
<link rel="stylesheet" href="../tools/mocha/mocha.css">
|
||||
<script src="../tools/mocha/mocha.js"></script>
|
||||
<script src="../tools/chai/chai.js"></script>
|
||||
<script src="../tools/mocha-htmltest.js"></script>
|
||||
|
||||
<!-- CustomElements -->
|
||||
<script src="../../src/CustomElements/CustomElements.js"></script>
|
||||
|
||||
<div id="mocha"></div>
|
||||
<script>
|
||||
mocha.setup({ui: 'tdd', slow: 1000});
|
||||
</script>
|
||||
|
||||
<script src="tests.js"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('WebComponentsReady', function() {
|
||||
mocha.run();
|
||||
});
|
||||
</script>
|
||||
28
tests/CustomElements/tests.js
Normal file
28
tests/CustomElements/tests.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 file = 'tests.js';
|
||||
var base;
|
||||
|
||||
var src =
|
||||
document.querySelector('script[src*="' + file + '"]').getAttribute('src');
|
||||
var base = src.slice(0, src.indexOf(file));
|
||||
|
||||
|
||||
var modules = [
|
||||
'customElements.js',
|
||||
'upgrade.js',
|
||||
'observe.js',
|
||||
'documentRegister.js'
|
||||
];
|
||||
|
||||
modules.forEach(function(src) {
|
||||
document.write('<script src="' + base + 'js/' + src + '"></script>');
|
||||
});
|
||||
|
||||
})();
|
||||
51
tests/HTMLImports/html/HTMLImports.html
Normal file
51
tests/HTMLImports/html/HTMLImports.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>HTML Imports Test</title>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script src="../../../src/HTMLImports/HTMLImports.js"></script>
|
||||
<link rel="import" href="imports/import-1.html">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
// polyfill only test
|
||||
if (HTMLImports.useNative) {
|
||||
done();
|
||||
} else {
|
||||
addEventListener('HTMLImportsLoaded', function() {
|
||||
chai.assert.equal(5, Object.keys(HTMLImports.importLoader.cache).length,
|
||||
'must cache exactly nine resources');
|
||||
chai.assert.equal(5, Object.keys(HTMLImports.importer.documents).length,
|
||||
'must cache exactly nine documents');
|
||||
|
||||
Object.keys(HTMLImports.importer.documents).forEach(function(key) {
|
||||
var doc = HTMLImports.importer.documents[key];
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
var links = doc.querySelectorAll('link[rel=import]');
|
||||
Array.prototype.forEach.call(links, function(link) {
|
||||
var href = link.getAttribute('href');
|
||||
if (href.indexOf('404') <= 0) {
|
||||
chai.assert.isDefined(link.import, 'import should have an import property');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
36
tests/HTMLImports/html/HTMLImportsLoaded-native.html
Normal file
36
tests/HTMLImports/html/HTMLImportsLoaded-native.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>HTMLImportsLoaded, native</title>
|
||||
<script>
|
||||
HTMLImports = {
|
||||
useNative: ('import' in document.createElement('link'))
|
||||
};
|
||||
</script>
|
||||
<script src="../../../src/HTMLImports/base.js"></script>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="import" href="imports/import-1.html">
|
||||
<script>
|
||||
if (!HTMLImports.useNative) {
|
||||
done();
|
||||
} else {
|
||||
addEventListener('HTMLImportsLoaded', function(e) {
|
||||
chai.assert.ok(true, 'HTMLImportsLoaded');
|
||||
done();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
59
tests/HTMLImports/html/base/load-base.html
Normal file
59
tests/HTMLImports/html/base/load-base.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>base load test</title>
|
||||
<base href="../../html/">
|
||||
<script>
|
||||
window.loadEvents = 0;
|
||||
(function() {
|
||||
function importLoaded(event) {
|
||||
window.loadEvents++;
|
||||
if (event.type === 'load' && event.target.import) {
|
||||
var s = event.target.import.querySelector('script');
|
||||
chai.assert.ok(s, 'load event target can be used to find element in import');
|
||||
}
|
||||
}
|
||||
|
||||
function importError(event) {
|
||||
window.loadEvents++;
|
||||
}
|
||||
|
||||
window.importLoaded = importLoaded;
|
||||
window.importError = importError;
|
||||
})();
|
||||
</script>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script src="../../../src/HTMLImports/HTMLImports.js"></script>
|
||||
<link rel="import" href="imports/load-1.html" onload="importLoaded(event)">
|
||||
<link rel="import" href="imports/load-2.html" onload="importLoaded(event)">
|
||||
<link rel="import" id="willError" href="imports/404.html" onerror="importError(event)">
|
||||
</head>
|
||||
<body>
|
||||
<div id="test1" class="red">Test</div>
|
||||
<div id="test2" class="blue">Test</div>
|
||||
<div id="test3" class="image"></div>
|
||||
<script>
|
||||
document.addEventListener('HTMLImportsLoaded', function() {
|
||||
var baseURI = location.href.replace('base/load-base.html', '').replace(location.search, '');
|
||||
chai.assert.equal(document.baseURI, baseURI, 'document.baseURI is correctly modified by base element');
|
||||
//
|
||||
chai.assert.equal(loadEvents, 3, 'expected # of load events');
|
||||
var test1 = getComputedStyle(document.querySelector('#test1')).backgroundColor;
|
||||
chai.assert.equal(test1, 'rgb(255, 0, 0)');
|
||||
var test2 = getComputedStyle(document.querySelector('#test2')).backgroundColor;
|
||||
chai.assert.equal(test2, 'rgb(0, 0, 255)');
|
||||
done();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
24
tests/HTMLImports/html/csp.html
Normal file
24
tests/HTMLImports/html/csp.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>parser Test</title>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script src="../../../src/HTMLImports/HTMLImports.js"></script>
|
||||
|
||||
<meta http-equiv='Content-Security-Policy' content="script-src 'self';">
|
||||
<link rel="import" href="imports/csp-import-1.html">
|
||||
</head>
|
||||
<body>
|
||||
<script src="csp.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
15
tests/HTMLImports/html/csp.js
Normal file
15
tests/HTMLImports/html/csp.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* @license
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
addEventListener('HTMLImportsLoaded', function() {
|
||||
chai.assert.ok(window.externalScriptParsed1, 'externalScriptParsed1');
|
||||
chai.assert.ok(window.externalScriptParsed2, 'externalScriptParsed2');
|
||||
done();
|
||||
});
|
||||
29
tests/HTMLImports/html/currentScript.html
Normal file
29
tests/HTMLImports/html/currentScript.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>_currentScript Test</title>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script src="../../../src/HTMLImports/HTMLImports.js"></script>
|
||||
<link rel="import" href="imports/script-1.html">
|
||||
<script src="imports/current-script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
chai.assert.equal(document._currentScript.parentNode, document.body, '_currentScript set in main document');
|
||||
addEventListener('HTMLImportsLoaded', function() {
|
||||
chai.assert.equal(window.remoteCurrentScriptExecuted, 2, 'remote script executed from import and main document');
|
||||
done();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
36
tests/HTMLImports/html/dedupe.html
Normal file
36
tests/HTMLImports/html/dedupe.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>HTML dedupe Test</title>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script src="../../../src/HTMLImports/HTMLImports.js"></script>
|
||||
<link rel="import" href="imports/dedupe.html">
|
||||
<script>
|
||||
var link = document.createElement('link');
|
||||
link.href = 'imports/dedupe.html';
|
||||
link.rel = 'import';
|
||||
link.addEventListener('load', function(e) {
|
||||
console.log('dynamic link loaded', e.target.href);
|
||||
})
|
||||
document.head.appendChild(link);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
addEventListener('HTMLImportsLoaded', function() {
|
||||
chai.assert.equal(dedupe, 1, 'import loaded');
|
||||
done();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
33
tests/HTMLImports/html/dynamic-elements.html
Normal file
33
tests/HTMLImports/html/dynamic-elements.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>HTML Imports Dynamic Elements</title>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script src="../../../src/HTMLImports/HTMLImports.js"></script>
|
||||
<link rel="import" href="imports/dynamic-elements-import.html">
|
||||
</head>
|
||||
<body>
|
||||
<div id="asyncStyled">red?</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('asyncElementsAdded', function() {
|
||||
setTimeout(function() {
|
||||
chai.assert.isTrue(window.asyncScript, 'async added script ran');
|
||||
var computed = getComputedStyle(document.querySelector('#asyncStyled'));
|
||||
chai.assert.equal(computed.backgroundColor, 'rgb(255, 0, 0)', 'async added style applied');
|
||||
done();
|
||||
}, 1);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
44
tests/HTMLImports/html/dynamic.html
Normal file
44
tests/HTMLImports/html/dynamic.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>HTML Imports Dynamic</title>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script src="../../../src/HTMLImports/HTMLImports.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// some time later
|
||||
setTimeout(function() {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = '<link rel="import" href="imports/load-1.html">' +
|
||||
'<link rel="import" href="imports/load-2.html">';
|
||||
document.body.appendChild(div);
|
||||
var ports = document.querySelectorAll('link[rel=import]');
|
||||
var loads = 0;
|
||||
for (var i=0, l=ports.length, n; (i<l) && (n=ports[i]); i++) {
|
||||
n.addEventListener('load', function(e) {
|
||||
loads++;
|
||||
chai.assert.ok(e.target.import);
|
||||
});
|
||||
}
|
||||
HTMLImports.whenReady(function() {
|
||||
chai.assert.equal(loads, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
37
tests/HTMLImports/html/encoding.html
Normal file
37
tests/HTMLImports/html/encoding.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
<script src="../../tools/htmltest.js"></script>
|
||||
<script src="../../tools/chai/chai.js"></script>
|
||||
<script src="../../../src/HTMLImports/HTMLImports.js"></script>
|
||||
<link rel="import" href="imports/encoding-import.html">
|
||||
</head>
|
||||
<body>
|
||||
<span></span>
|
||||
<script>
|
||||
function test() {
|
||||
run();
|
||||
var span = document.querySelector('span');
|
||||
chai.assert.equal(span.textContent, 'Róbert Viðar Bjarnason');
|
||||
done();
|
||||
}
|
||||
|
||||
if (HTMLImports.ready) {
|
||||
test();
|
||||
} else {
|
||||
addEventListener('HTMLImportsLoaded', test);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
10
tests/HTMLImports/html/imports/abs.html
Normal file
10
tests/HTMLImports/html/imports/abs.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<img src="../../foo.png">
|
||||
11
tests/HTMLImports/html/imports/csp-import-1.html
Normal file
11
tests/HTMLImports/html/imports/csp-import-1.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<link rel="import" href="csp-import-2.html">
|
||||
<script src="csp-script-1.js"></script>
|
||||
10
tests/HTMLImports/html/imports/csp-import-2.html
Normal file
10
tests/HTMLImports/html/imports/csp-import-2.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<script src="csp-script-2.js"></script>
|
||||
11
tests/HTMLImports/html/imports/csp-script-1.js
Normal file
11
tests/HTMLImports/html/imports/csp-script-1.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* @license
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
window.externalScriptParsed1 = new Date().getTime();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user