Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Unified Diff: pkg/shadow_dom/lib/shadow_dom.debug.js

Issue 23548014: update shadowdom polyfill to latest (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: updated README, add build.sh Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/shadow_dom/lib/shadow_dom.debug.js
diff --git a/pkg/shadow_dom/lib/shadow_dom.debug.js b/pkg/shadow_dom/lib/shadow_dom.debug.js
index eff86b3e6c2b6dc71f5385f2a51fd6e017426fa2..07b283e64de2933b022ee187a427d77c69176556 100644
--- a/pkg/shadow_dom/lib/shadow_dom.debug.js
+++ b/pkg/shadow_dom/lib/shadow_dom.debug.js
@@ -31,8 +31,7 @@ if (typeof WeakMap !== 'undefined' && navigator.userAgent.indexOf('Firefox/') <
} else {
(function() {
var defineProperty = Object.defineProperty;
- var hasOwnProperty = Object.hasOwnProperty;
- var counter = new Date().getTime() % 1e9;
+ var counter = Date.now() % 1e9;
SideTable = function() {
this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
@@ -40,10 +39,16 @@ if (typeof WeakMap !== 'undefined' && navigator.userAgent.indexOf('Firefox/') <
SideTable.prototype = {
set: function(key, value) {
- defineProperty(key, this.name, {value: value, writable: true});
+ var entry = key[this.name];
+ if (entry && entry[0] === key)
+ entry[1] = value;
+ else
+ defineProperty(key, this.name, {value: [key, value], writable: true});
},
get: function(key) {
- return hasOwnProperty.call(key, this.name) ? key[this.name] : undefined;
+ var entry;
+ return (entry = key[this.name]) && entry[0] === key ?
+ entry[1] : undefined;
},
delete: function(key) {
this.set(key, undefined);
@@ -63,6 +68,7 @@ var ShadowDOMPolyfill = {};
var wrapperTable = new SideTable();
var constructorTable = new SideTable();
+ var nativePrototypeTable = new SideTable();
var wrappers = Object.create(null);
function assert(b) {
@@ -95,6 +101,13 @@ var ShadowDOMPolyfill = {};
return to;
};
+ function oneOf(object, propertyNames) {
+ for (var i = 0; i < propertyNames.length; i++) {
+ if (propertyNames[i] in object)
+ return propertyNames[i];
+ }
+ }
+
// 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.
@@ -133,6 +146,10 @@ var ShadowDOMPolyfill = {};
enumerable: true
};
+ function isEventHandlerName(name) {
+ return /^on[a-z]+$/.test(name);
+ }
+
function installProperty(source, target, allowMethod) {
Object.getOwnPropertyNames(source).forEach(function(name) {
if (name in target)
@@ -146,8 +163,8 @@ var ShadowDOMPolyfill = {};
try {
descriptor = Object.getOwnPropertyDescriptor(source, name);
} catch (ex) {
- // JSC and V8 both use data properties instead accessors which can cause
- // getting the property desciptor throw an exception.
+ // 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
descriptor = dummyDescriptor;
}
@@ -159,14 +176,23 @@ var ShadowDOMPolyfill = {};
return;
}
- getter = function() {
- return this.impl[name];
- };
+ var isEvent = isEventHandlerName(name);
+ if (isEvent) {
+ getter = scope.getEventHandlerGetter(name);
+ } else {
+ getter = function() {
+ return this.impl[name];
+ };
+ }
if (descriptor.writable || descriptor.set) {
- setter = function(value) {
- this.impl[name] = value;
- };
+ if (isEvent) {
+ setter = scope.getEventHandlerSetter(name);
+ } else {
+ setter = function(value) {
+ this.impl[name] = value;
+ };
+ }
}
Object.defineProperty(target, name, {
@@ -181,9 +207,8 @@ var ShadowDOMPolyfill = {};
/**
* @param {Function} nativeConstructor
* @param {Function} wrapperConstructor
- * @param {string|Object=} opt_instance If present, this is used to extract
- * properties from an instance object. If this is a string
- * |document.createElement| is used to create an instance.
+ * @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;
@@ -194,7 +219,10 @@ var ShadowDOMPolyfill = {};
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);
@@ -232,10 +260,6 @@ var ShadowDOMPolyfill = {};
GeneratedWrapper.prototype =
Object.create(superWrapperConstructor.prototype);
GeneratedWrapper.prototype.constructor = GeneratedWrapper;
- // We add this property to work around a Firefox bug where HTMLFormElement
- // and HTMLInputElements sometimes have their constructor incorrectly
- // attached to GeneratedWrapper. See lib/patches-shadowdom-polyfill.js
- GeneratedWrapper._ShadowDOMPolyfill$isGeneratedWrapper = true;
return GeneratedWrapper;
}
@@ -244,10 +268,12 @@ var ShadowDOMPolyfill = {};
var OriginalEvent = Event;
var OriginalNode = Node;
var OriginalWindow = Window;
+ var OriginalRange = Range;
function isWrapper(object) {
return object instanceof wrappers.EventTarget ||
object instanceof wrappers.Event ||
+ object instanceof wrappers.Range ||
object instanceof wrappers.DOMImplementation;
}
@@ -255,6 +281,7 @@ var ShadowDOMPolyfill = {};
return object instanceof OriginalNode ||
object instanceof OriginalEvent ||
object instanceof OriginalWindow ||
+ object instanceof OriginalRange ||
object instanceof OriginalDOMImplementation;
}
@@ -270,11 +297,8 @@ var ShadowDOMPolyfill = {};
assert(isNative(impl));
var wrapper = wrapperTable.get(impl);
- if (!wrapper) {
- var wrapperConstructor = getWrapperConstructor(impl);
- wrapper = new wrapperConstructor(impl);
- wrapperTable.set(impl, wrapper);
- }
+ if (!wrapper)
+ wrapperTable.set(impl, wrapper = new (getWrapperConstructor(impl))(impl));
return wrapper;
}
@@ -347,7 +371,7 @@ var ShadowDOMPolyfill = {};
constructors.forEach(function(constructor) {
names.forEach(function(name) {
constructor.prototype[name] = function() {
- var w = wrap(this);
+ var w = wrapIfNeeded(this);
return w[name].apply(w, arguments);
};
});
@@ -355,12 +379,14 @@ var ShadowDOMPolyfill = {};
}
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;
@@ -371,7 +397,6 @@ var ShadowDOMPolyfill = {};
scope.wrappers = wrappers;
})(this.ShadowDOMPolyfill);
-
// 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.
@@ -395,6 +420,8 @@ var ShadowDOMPolyfill = {};
var eventPhaseTable = new SideTable();
var stopPropagationTable = new SideTable();
var stopImmediatePropagationTable = new SideTable();
+ var eventHandlersTable = new SideTable();
+ var eventPathTable = new SideTable();
function isShadowRoot(node) {
return node instanceof wrappers.ShadowRoot;
@@ -421,7 +448,7 @@ var ShadowDOMPolyfill = {};
// 1.
if (isShadowRoot(node))
- return node.insertionParent || scope.getHostForShadowRoot(node);
+ return getInsertionParent(node) || scope.getHostForShadowRoot(node);
// 2.
var eventParents = scope.eventParentsTable.get(node);
@@ -438,7 +465,7 @@ var ShadowDOMPolyfill = {};
var parentNode = node.parentNode;
if (parentNode && isShadowHost(parentNode)) {
var trees = scope.getShadowTrees(parentNode);
- var p = context.insertionParent;
+ var p = getInsertionParent(context);
for (var i = 0; i < trees.length; i++) {
if (trees[i].contains(p))
return p;
@@ -523,8 +550,12 @@ var ShadowDOMPolyfill = {};
}
}
+ function getInsertionParent(node) {
+ return scope.insertionParentTable.get(node);
+ }
+
function isDistributed(node) {
- return node.insertionParent;
+ return getInsertionParent(node);
}
function rootOfNode(node) {
@@ -539,6 +570,17 @@ var ShadowDOMPolyfill = {};
return rootOfNode(a) === rootOfNode(b);
}
+ function enclosedBy(a, b) {
+ if (a === b)
+ return true;
+ if (a instanceof wrappers.ShadowRoot) {
+ var host = scope.getHostForShadowRoot(a);
+ return enclosedBy(rootOfNode(host), b);
+ }
+ return false;
+
+ }
+
function isMutationEvent(type) {
switch (type) {
case 'DOMAttrModified':
@@ -580,13 +622,15 @@ var ShadowDOMPolyfill = {};
//
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-end
//
- // TODO(arv): Find a loess hacky way to do this.
+ // TODO(arv): Find a less hacky way to do this.
if (event.type === 'load' &&
eventPath.length === 2 &&
eventPath[0].target instanceof wrappers.Document) {
eventPath.shift();
}
+ eventPathTable.set(event, eventPath);
+
if (dispatchCapturing(event, eventPath)) {
if (dispatchAtTarget(event, eventPath)) {
dispatchBubbling(event, eventPath);
@@ -750,6 +794,27 @@ var ShadowDOMPolyfill = {};
get eventPhase() {
return eventPhaseTable.get(this);
},
+ get path() {
+ var nodeList = new wrappers.NodeList();
+ var eventPath = eventPathTable.get(this);
+ if (eventPath) {
+ var index = 0;
+ var lastIndex = eventPath.length - 1;
+ var baseRoot = rootOfNode(currentTargetTable.get(this));
+
+ for (var i = 0; i <= lastIndex; i++) {
+ var currentTarget = eventPath[i].currentTarget;
+ var currentRoot = rootOfNode(currentTarget);
+ if (enclosedBy(baseRoot, currentRoot) &&
+ // Make sure we do not add Window to the path.
+ (i !== lastIndex || currentTarget instanceof wrappers.Node)) {
+ nodeList[index++] = currentTarget;
+ }
+ }
+ nodeList.length = index;
+ }
+ return nodeList;
+ },
stopPropagation: function() {
stopPropagationTable.set(this, true);
},
@@ -779,10 +844,17 @@ var ShadowDOMPolyfill = {};
GenericEvent.prototype = Object.create(SuperEvent.prototype);
if (prototype)
mixin(GenericEvent.prototype, prototype);
- // Firefox does not support FocusEvent
- // https://bugzilla.mozilla.org/show_bug.cgi?id=855741
- if (OriginalEvent)
- registerWrapper(OriginalEvent, GenericEvent, document.createEvent(name));
+ if (OriginalEvent) {
+ // IE does not support event constructors but FocusEvent can only be
+ // created using new FocusEvent in Firefox.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=882165
+ if (OriginalEvent.prototype['init' + name]) {
+ registerWrapper(OriginalEvent, GenericEvent,
+ document.createEvent(name));
+ } else {
+ registerWrapper(OriginalEvent, GenericEvent, new OriginalEvent('temp'));
+ }
+ }
return GenericEvent;
}
@@ -912,7 +984,7 @@ var ShadowDOMPolyfill = {};
'dispatchEvent'
];
- [Element, Window, Document].forEach(function(constructor) {
+ [Node, Window].forEach(function(constructor) {
var p = constructor.prototype;
methodNames.forEach(function(name) {
Object.defineProperty(p, name + '_', {value: p[name]});
@@ -999,8 +1071,61 @@ var ShadowDOMPolyfill = {};
return null;
}
+ /**
+ * 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.adjustRelatedTarget = adjustRelatedTarget;
scope.elementFromPoint = elementFromPoint;
+ scope.getEventHandlerGetter = getEventHandlerGetter;
+ scope.getEventHandlerSetter = getEventHandlerSetter;
scope.wrapEventTargetMethods = wrapEventTargetMethods;
scope.wrappers.CustomEvent = CustomEvent;
scope.wrappers.Event = Event;
@@ -1073,6 +1198,7 @@ var ShadowDOMPolyfill = {};
var registerWrapper = scope.registerWrapper;
var unwrap = scope.unwrap;
var wrap = scope.wrap;
+ var wrapIfNeeded = scope.wrapIfNeeded;
function assertIsNodeWrapper(node) {
assert(node instanceof Node);
@@ -1119,30 +1245,72 @@ var ShadowDOMPolyfill = {};
return nodes;
}
- function unwrapNodesForInsertion(nodes) {
- if (nodes.length === 1)
- return unwrap(nodes[0]);
+ 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;
- var df = unwrap(document.createDocumentFragment());
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 removeAllChildNodes(wrapper) {
- 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;
+ 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;
+ }
}
- wrapper.firstChild_ = wrapper.lastChild_ = null;
+ }
+
+ function invalidateParent(node) {
+ var p = node.parentNode;
+ return p && p.invalidateShadowRenderer();
}
var OriginalNode = window.Node;
@@ -1205,22 +1373,22 @@ var ShadowDOMPolyfill = {};
appendChild: function(childWrapper) {
assertIsNodeWrapper(childWrapper);
- this.invalidateShadowRenderer();
+ if (this.invalidateShadowRenderer() || invalidateParent(childWrapper)) {
+ var previousNode = this.lastChild;
+ var nextNode = null;
+ var nodes = collectNodes(childWrapper, this, previousNode, nextNode);
- var previousNode = this.lastChild;
- var nextNode = null;
- var nodes = collectNodes(childWrapper, this,
- previousNode, nextNode);
+ this.lastChild_ = nodes[nodes.length - 1];
+ if (!previousNode)
+ this.firstChild_ = nodes[0];
- this.lastChild_ = nodes[nodes.length - 1];
- if (!previousNode)
- this.firstChild_ = nodes[0];
+ originalAppendChild.call(this.impl, unwrapNodesForInsertion(this, nodes));
+ } else {
+ ensureSameOwnerDocument(this, childWrapper);
+ originalAppendChild.call(this.impl, unwrap(childWrapper));
+ }
- // TODO(arv): It is unclear if we need to update the visual DOM here.
- // A better aproach might be to make sure we only get here for nodes that
- // are related to a shadow host and then invalidate that and re-render
- // the host (on reflow?).
- originalAppendChild.call(this.impl, unwrapNodesForInsertion(nodes));
+ childWrapper.nodeWasAdded_();
return childWrapper;
},
@@ -1234,27 +1402,34 @@ var ShadowDOMPolyfill = {};
assertIsNodeWrapper(refWrapper);
assert(refWrapper.parentNode === this);
- this.invalidateShadowRenderer();
-
- var previousNode = refWrapper.previousSibling;
- var nextNode = refWrapper;
- var nodes = collectNodes(childWrapper, this,
- previousNode, nextNode);
+ if (this.invalidateShadowRenderer() || invalidateParent(childWrapper)) {
+ var previousNode = refWrapper.previousSibling;
+ var nextNode = refWrapper;
+ var nodes = collectNodes(childWrapper, this, previousNode, nextNode);
+ if (this.firstChild === refWrapper)
+ this.firstChild_ = nodes[0];
- if (this.firstChild === refWrapper)
- this.firstChild_ = nodes[0];
+ // insertBefore refWrapper no matter what the parent is?
+ var refNode = unwrap(refWrapper);
+ var parentNode = refNode.parentNode;
- // insertBefore refWrapper no matter what the parent is?
- var refNode = unwrap(refWrapper);
- var parentNode = refNode.parentNode;
- if (parentNode) {
- originalInsertBefore.call(
- parentNode,
- unwrapNodesForInsertion(nodes),
- refNode);
+ if (parentNode) {
+ originalInsertBefore.call(
+ parentNode,
+ unwrapNodesForInsertion(this, nodes),
+ refNode);
+ } else {
+ adoptNodesIfNeeded(this, nodes);
+ }
+ } else {
+ ensureSameOwnerDocument(this, childWrapper);
+ originalInsertBefore.call(this.impl, unwrap(childWrapper),
+ unwrap(refWrapper));
}
+ childWrapper.nodeWasAdded_();
+
return childWrapper;
},
@@ -1265,31 +1440,37 @@ var ShadowDOMPolyfill = {};
throw new Error('NotFoundError');
}
- 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 childWrapperNextSibling = childWrapper.nextSibling;
- var childWrapperPreviousSibling = childWrapper.previousSibling;
-
var childNode = unwrap(childWrapper);
- var parentNode = childNode.parentNode;
- if (parentNode)
- originalRemoveChild.call(parentNode, childNode);
-
- if (thisFirstChild === childWrapper)
- this.firstChild_ = childWrapperNextSibling;
- if (thisLastChild === childWrapper)
- this.lastChild_ = childWrapperPreviousSibling;
- if (childWrapperPreviousSibling)
- childWrapperPreviousSibling.nextSibling_ = childWrapperNextSibling;
- if (childWrapperNextSibling)
- childWrapperNextSibling.previousSibling_ = childWrapperPreviousSibling;
+ 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 childWrapperNextSibling = childWrapper.nextSibling;
+ var childWrapperPreviousSibling = childWrapper.previousSibling;
+
+ var parentNode = childNode.parentNode;
+ if (parentNode)
+ originalRemoveChild.call(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_ = null;
+ childWrapper.previousSibling_ = childWrapper.nextSibling_ =
+ childWrapper.parentNode_ = undefined;
+ } else {
+ originalRemoveChild.call(this.impl, childNode);
+ }
return childWrapper;
},
@@ -1303,36 +1484,50 @@ var ShadowDOMPolyfill = {};
throw new Error('NotFoundError');
}
- this.invalidateShadowRenderer();
-
- var previousNode = oldChildWrapper.previousSibling;
- var nextNode = oldChildWrapper.nextSibling;
- if (nextNode === newChildWrapper)
- nextNode = newChildWrapper.nextSibling;
- var nodes = collectNodes(newChildWrapper, this,
- previousNode, nextNode);
-
- if (this.firstChild === oldChildWrapper)
- this.firstChild_ = nodes[0];
- if (this.lastChild === oldChildWrapper)
- this.lastChild_ = nodes[nodes.length - 1];
-
- oldChildWrapper.previousSibling_ = null;
- oldChildWrapper.nextSibling_ = null;
- oldChildWrapper.parentNode_ = null;
-
- // replaceChild no matter what the parent is?
var oldChildNode = unwrap(oldChildWrapper);
- if (oldChildNode.parentNode) {
- originalReplaceChild.call(
- oldChildNode.parentNode,
- unwrapNodesForInsertion(nodes),
- oldChildNode);
+
+ if (this.invalidateShadowRenderer() ||
+ invalidateParent(newChildWrapper)) {
+ var previousNode = oldChildWrapper.previousSibling;
+ var nextNode = oldChildWrapper.nextSibling;
+ if (nextNode === newChildWrapper)
+ nextNode = newChildWrapper.nextSibling;
+ var nodes = collectNodes(newChildWrapper, this,
+ previousNode, nextNode);
+
+ 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);
+ originalReplaceChild.call(this.impl, unwrap(newChildWrapper),
+ oldChildNode);
}
+ newChildWrapper.nodeWasAdded_();
+
return oldChildWrapper;
},
+ /**
+ * Called after a node was added. Subclasses override this to invalidate
+ * the renderer as needed.
+ * @private
+ */
+ nodeWasAdded_: function() {},
+
hasChildNodes: function() {
return this.firstChild === null;
},
@@ -1386,11 +1581,14 @@ var ShadowDOMPolyfill = {};
return s;
},
set textContent(textContent) {
- removeAllChildNodes(this);
- this.invalidateShadowRenderer();
- if (textContent !== '') {
- var textNode = this.impl.ownerDocument.createTextNode(textContent);
- this.appendChild(textNode);
+ if (this.invalidateShadowRenderer()) {
+ removeAllChildNodes(this);
+ if (textContent !== '') {
+ var textNode = this.impl.ownerDocument.createTextNode(textContent);
+ this.appendChild(textNode);
+ }
+ } else {
+ this.impl.textContent = textContent;
}
},
@@ -1418,12 +1616,12 @@ var ShadowDOMPolyfill = {};
return clone;
},
- // insertionParent is added in ShadowRender.js
-
contains: function(child) {
if (!child)
return false;
+ child = wrapIfNeeded(child);
+
// TODO(arv): Optimize using ownerDocument etc.
if (child === this)
return true;
@@ -1598,7 +1796,7 @@ var ShadowDOMPolyfill = {};
},
get previousElementSibling() {
- return backwardsElement(this.nextSibling);
+ return backwardsElement(this.previousSibling);
}
};
@@ -1656,17 +1854,33 @@ var ShadowDOMPolyfill = {};
var SelectorsInterface = scope.SelectorsInterface;
var addWrapNodeListMethod = scope.addWrapNodeListMethod;
var mixin = scope.mixin;
+ var oneOf = scope.oneOf;
var registerWrapper = scope.registerWrapper;
var wrappers = scope.wrappers;
var shadowRootTable = new SideTable();
var OriginalElement = window.Element;
- var originalMatches =
- OriginalElement.prototype.matches ||
- OriginalElement.prototype.mozMatchesSelector ||
- OriginalElement.prototype.msMatchesSelector ||
- OriginalElement.prototype.webkitMatchesSelector;
+
+ var matchesName = oneOf(OriginalElement.prototype, [
+ 'matches',
+ 'mozMatchesSelector',
+ 'msMatchesSelector',
+ 'webkitMatchesSelector',
+ ]);
+
+ 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 Element(node) {
Node.call(this, node);
@@ -1677,9 +1891,8 @@ var ShadowDOMPolyfill = {};
var newShadowRoot = new wrappers.ShadowRoot(this);
shadowRootTable.set(this, newShadowRoot);
- scope.getRendererForHost(this);
-
- this.invalidateShadowRenderer(true);
+ var renderer = scope.getRendererForHost(this);
+ renderer.invalidate();
return newShadowRoot;
},
@@ -1690,10 +1903,12 @@ var ShadowDOMPolyfill = {};
setAttribute: function(name, value) {
this.impl.setAttribute(name, value);
- // This is a bit agressive. We need to invalidate if it affects
- // the rendering content[select] or if it effects the value of a content
- // select.
- this.invalidateShadowRenderer();
+ invalidateRendererBasedOnAttribute(this, name);
+ },
+
+ removeAttribute: function(name) {
+ this.impl.removeAttribute(name);
+ invalidateRendererBasedOnAttribute(this, name);
},
matches: function(selector) {
@@ -1701,6 +1916,37 @@ var ShadowDOMPolyfill = {};
}
});
+ Element.prototype[matchesName] = function(selector) {
+ return this.matches(selector);
+ };
+
+ if (OriginalElement.prototype.webkitCreateShadowRoot) {
+ Element.prototype.webkitCreateShadowRoot =
+ Element.prototype.createShadowRoot;
+ }
+
+ /**
+ * Useful for generating the accessor pair for a property that reflects an
+ * attribute.
+ */
+ function setterDirtiesAttribute(prototype, propertyName, opt_attrName) {
+ var attrName = opt_attrName || propertyName;
+ Object.defineProperty(prototype, propertyName, {
+ get: function() {
+ return this.impl[propertyName];
+ },
+ set: function(v) {
+ this.impl[propertyName] = v;
+ invalidateRendererBasedOnAttribute(this, attrName);
+ },
+ configurable: true,
+ enumerable: true
+ });
+ }
+
+ setterDirtiesAttribute(Element.prototype, 'id');
+ setterDirtiesAttribute(Element.prototype, 'className', 'class');
+
mixin(Element.prototype, ChildNodeInterface);
mixin(Element.prototype, GetElementsByInterface);
mixin(Element.prototype, ParentNodeInterface);
@@ -1708,6 +1954,9 @@ var ShadowDOMPolyfill = {};
registerWrapper(OriginalElement, Element);
+ // TODO(arv): Export setterDirtiesAttribute and apply it to more bindings
+ // that reflect attributes.
+ scope.matchesName = matchesName;
scope.wrappers.Element = Element;
})(this.ShadowDOMPolyfill);
@@ -1802,7 +2051,7 @@ var ShadowDOMPolyfill = {};
function setInnerHTML(node, value, opt_tagName) {
var tagName = opt_tagName || 'div';
node.textContent = '';
- var tempElement =unwrap(node.ownerDocument.createElement(tagName));
+ var tempElement = unwrap(node.ownerDocument.createElement(tagName));
tempElement.innerHTML = value;
var firstChild;
while (firstChild = tempElement.firstChild) {
@@ -1823,7 +2072,10 @@ var ShadowDOMPolyfill = {};
return getInnerHTML(this);
},
set innerHTML(value) {
- setInnerHTML(this, value, this.tagName);
+ if (this.invalidateShadowRenderer())
+ setInnerHTML(this, value, this.tagName);
+ else
+ this.impl.innerHTML = value;
},
get outerHTML() {
@@ -1832,10 +2084,10 @@ var ShadowDOMPolyfill = {};
return getOuterHTML(this);
},
set outerHTML(value) {
- if (!this.invalidateShadowRenderer()) {
+ var p = this.parentNode;
+ if (p) {
+ p.invalidateShadowRenderer();
this.impl.outerHTML = value;
- } else {
- throw new Error('not implemented');
}
}
});
@@ -1945,18 +2197,9 @@ var ShadowDOMPolyfill = {};
function HTMLShadowElement(node) {
HTMLElement.call(this, node);
- this.olderShadowRoot_ = null;
}
HTMLShadowElement.prototype = Object.create(HTMLElement.prototype);
mixin(HTMLShadowElement.prototype, {
- get olderShadowRoot() {
- return this.olderShadowRoot_;
- },
-
- invalidateShadowRenderer: function() {
- HTMLElement.prototype.invalidateShadowRenderer.call(this, true);
- },
-
// TODO: attribute boolean resetStyleInheritance;
});
@@ -1965,6 +2208,7 @@ var ShadowDOMPolyfill = {};
scope.wrappers.HTMLShadowElement = HTMLShadowElement;
})(this.ShadowDOMPolyfill);
+
// 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.
@@ -1977,6 +2221,7 @@ var ShadowDOMPolyfill = {};
var mixin = scope.mixin;
var registerWrapper = scope.registerWrapper;
var setInnerHTML = scope.setInnerHTML;
+ var unwrap = scope.unwrap;
var wrap = scope.wrap;
var contentTable = new SideTable();
@@ -2000,9 +2245,9 @@ var ShadowDOMPolyfill = {};
}
function extractContent(templateElement) {
+ // templateElement is not a wrapper here.
var doc = getTemplateContentsOwner(templateElement.ownerDocument);
- var df = doc.createDocumentFragment();
- var nextSibling;
+ var df = unwrap(doc.createDocumentFragment());
var child;
while (child = templateElement.firstChild) {
df.appendChild(child);
@@ -2014,6 +2259,10 @@ var ShadowDOMPolyfill = {};
function HTMLTemplateElement(node) {
HTMLElement.call(this, node);
+ if (!OriginalHTMLTemplateElement) {
+ var content = extractContent(node);
+ contentTable.set(this, wrap(content));
+ }
}
HTMLTemplateElement.prototype = Object.create(HTMLElement.prototype);
@@ -2021,16 +2270,7 @@ var ShadowDOMPolyfill = {};
get content() {
if (OriginalHTMLTemplateElement)
return wrap(this.impl.content);
-
- // TODO(arv): This should be done in createCallback. I initially tried to
- // do this in the constructor but the wrapper is not yet created at that
- // point in time so we hit an iloop.
- var content = contentTable.get(this);
- if (!content) {
- content = extractContent(this);
- contentTable.set(this, content);
- }
- return content;
+ return contentTable.get(this);
},
get innerHTML() {
@@ -2038,7 +2278,6 @@ var ShadowDOMPolyfill = {};
},
set innerHTML(value) {
setInnerHTML(this.content, value);
- this.invalidateShadowRenderer();
}
// TODO(arv): cloneNode needs to clone content.
@@ -2124,6 +2363,7 @@ var ShadowDOMPolyfill = {};
var unwrap = scope.unwrap;
var shadowHostTable = new SideTable();
+ var nextOlderShadowTreeTable = new SideTable();
function ShadowRoot(hostWrapper) {
var node = unwrap(hostWrapper.impl.ownerDocument.createDocumentFragment());
@@ -2134,7 +2374,7 @@ var ShadowDOMPolyfill = {};
rewrap(node, this);
var oldShadowRoot = hostWrapper.shadowRoot;
- scope.nextOlderShadowTreeTable.set(this, oldShadowRoot);
+ nextOlderShadowTreeTable.set(this, oldShadowRoot);
shadowHostTable.set(this, hostWrapper);
}
@@ -2148,6 +2388,10 @@ var ShadowDOMPolyfill = {};
this.invalidateShadowRenderer();
},
+ get olderShadowRoot() {
+ return nextOlderShadowTreeTable.get(this) || null;
+ },
+
invalidateShadowRenderer: function() {
return shadowHostTable.get(this).invalidateShadowRenderer();
},
@@ -2174,9 +2418,13 @@ var ShadowDOMPolyfill = {};
'use strict';
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 getHostForShadowRoot = scope.getHostForShadowRoot;
var mixin = scope.mixin;
+ var oneOf = scope.oneOf;
var unwrap = scope.unwrap;
var wrap = scope.wrap;
@@ -2219,7 +2467,8 @@ var ShadowDOMPolyfill = {};
function removeAllChildNodes(parentNodeWrapper) {
var parentNode = unwrap(parentNodeWrapper);
updateAllChildNodes(parentNodeWrapper);
- parentNode.textContent = '';
+ if (parentNode.firstChild)
+ parentNode.textContent = '';
}
function appendChild(parentNodeWrapper, childWrapper) {
@@ -2274,7 +2523,6 @@ var ShadowDOMPolyfill = {};
var distributedChildNodesTable = new SideTable();
var eventParentsTable = new SideTable();
var insertionParentTable = new SideTable();
- var nextOlderShadowTreeTable = new SideTable();
var rendererForHostTable = new SideTable();
var shadowDOMRendererTable = new SideTable();
@@ -2288,7 +2536,7 @@ var ShadowDOMPolyfill = {};
function distributeChildToInsertionPoint(child, insertionPoint) {
getDistributedChildNodes(insertionPoint).push(child);
- insertionParentTable.set(child, insertionPoint);
+ assignToInsertionPoint(child, insertionPoint);
var eventParents = eventParentsTable.get(child);
if (!eventParents)
@@ -2333,33 +2581,6 @@ var ShadowDOMPolyfill = {};
}
}
- // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-distribution-algorithm
- function distribute(tree, pool) {
- var anyRemoved = false;
-
- visit(tree, isActiveInsertionPoint,
- function(insertionPoint) {
- resetDistributedChildNodes(insertionPoint);
- for (var i = 0; i < pool.length; i++) { // 1.2
- var node = pool[i]; // 1.2.1
- if (node === undefined) // removed
- continue;
- if (matchesCriteria(node, insertionPoint)) { // 1.2.2
- distributeChildToInsertionPoint(node, insertionPoint); // 1.2.2.1
- pool[i] = undefined; // 1.2.2.2
- anyRemoved = true;
- }
- }
- });
-
- if (!anyRemoved)
- return pool;
-
- return pool.filter(function(item) {
- return item !== undefined;
- });
- }
-
// Matching Insertion Points
// http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#matching-insertion-points
@@ -2386,13 +2607,6 @@ var ShadowDOMPolyfill = {};
].join('|') + ')');
- function oneOf(object, propertyNames) {
- for (var i = 0; i < propertyNames.length; i++) {
- if (propertyNames[i] in object)
- return propertyNames[i];
- }
- }
-
/**
* @param {Element} node
* @oaram {Element} point The insertion point element.
@@ -2447,9 +2661,15 @@ var ShadowDOMPolyfill = {};
function ShadowRenderer(host) {
this.host = host;
this.dirty = false;
+ this.invalidateAttributes();
this.associateNode(host);
}
+ /**
+ * 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) {
@@ -2459,17 +2679,30 @@ var ShadowDOMPolyfill = {};
return renderer;
}
+ function getShadowRootAncestor(node) {
+ for (; node; node = node.parentNode) {
+ if (node instanceof ShadowRoot)
+ return node;
+ }
+ return null;
+ }
+
+ function getRendererForShadowRoot(shadowRoot) {
+ return getRendererForHost(getHostForShadowRoot(shadowRoot));
+ }
+
ShadowRenderer.prototype = {
+
// http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#rendering-shadow-trees
render: function() {
if (!this.dirty)
return;
- var host = this.host;
+ this.invalidateAttributes();
this.treeComposition();
+
+ var host = this.host;
var shadowDOM = host.shadowRoot;
- if (!shadowDOM)
- return;
this.removeAllChildNodes(this.host);
@@ -2506,14 +2739,18 @@ var ShadowDOMPolyfill = {};
}
},
- renderAsAnyDomTree: function(visualParent, tree, child, isNested) {
- this.appendChild(visualParent, child);
+ renderAsAnyDomTree: function(visualParent, tree, node, isNested) {
+ this.appendChild(visualParent, node);
- if (isShadowHost(child)) {
- render(child);
+ if (isShadowHost(node)) {
+ render(node);
} else {
- var parent = child;
+ var parent = node;
var logicalChildNodes = getChildNodesSnapshot(parent);
+ // We associate the parent of a content/shadow with the renderer
+ // because we may need to remove stale childNodes.
+ if (shadowDOMRendererTable.get(parent))
+ this.removeAllChildNodes(parent);
logicalChildNodes.forEach(function(node) {
this.renderNode(parent, tree, node, isNested);
}, this);
@@ -2538,11 +2775,9 @@ var ShadowDOMPolyfill = {};
},
renderShadowInsertionPoint: function(visualParent, tree, shadowInsertionPoint) {
- var nextOlderTree = getNextOlderTree(tree);
+ var nextOlderTree = tree.olderShadowRoot;
if (nextOlderTree) {
- // This makes ShadowRoot have its insertionParent be the <shadow>.
- insertionParentTable.set(nextOlderTree, shadowInsertionPoint);
- shadowInsertionPoint.olderShadowRoot_ = nextOlderTree;
+ assignToInsertionPoint(nextOlderTree, shadowInsertionPoint);
this.remove(shadowInsertionPoint);
var shadowDOMChildNodes = getChildNodesSnapshot(nextOlderTree);
shadowDOMChildNodes.forEach(function(node) {
@@ -2555,11 +2790,82 @@ var ShadowDOMPolyfill = {};
renderFallbackContent: function (visualParent, fallbackHost) {
var logicalChildNodes = getChildNodesSnapshot(fallbackHost);
+ this.associateNode(fallbackHost);
+ this.remove(fallbackHost);
logicalChildNodes.forEach(function(node) {
this.appendChild(visualParent, node);
}, this);
},
+ /**
+ * 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];
+ },
+
+ // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-distribution-algorithm
+ distribute: function(tree, pool) {
+ var anyRemoved = false;
+ var self = this;
+
+ visit(tree, isActiveInsertionPoint,
+ function(insertionPoint) {
+ resetDistributedChildNodes(insertionPoint);
+ self.updateDependentAttributes(
+ insertionPoint.getAttribute('select'));
+
+ for (var i = 0; i < pool.length; i++) { // 1.2
+ var node = pool[i]; // 1.2.1
+ if (node === undefined) // removed
+ continue;
+ if (matchesCriteria(node, insertionPoint)) { // 1.2.2
+ distributeChildToInsertionPoint(node, insertionPoint); // 1.2.2.1
+ pool[i] = undefined; // 1.2.2.2
+ anyRemoved = true;
+ }
+ }
+ });
+
+ if (!anyRemoved)
+ return pool;
+
+ return pool.filter(function(item) {
+ return item !== undefined;
+ });
+ },
+
// http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-tree-composition
treeComposition: function () {
var shadowHost = this.host;
@@ -2588,14 +2894,14 @@ var ShadowDOMPolyfill = {};
});
point = shadowInsertionPoint;
- pool = distribute(tree, pool); // 4.2.
+ pool = this.distribute(tree, pool); // 4.2.
if (point) { // 4.3.
- var nextOlderTree = getNextOlderTree(tree); // 4.3.1.
+ var nextOlderTree = tree.olderShadowRoot; // 4.3.1.
if (!nextOlderTree) {
break; // 4.3.1.1.
} else {
tree = nextOlderTree; // 4.3.2.2.
- assignShadowTreeToShadowInsertionPoint(tree, point); // 4.3.2.2.
+ assignToInsertionPoint(tree, point); // 4.3.2.2.
continue; // 4.3.2.3.
}
} else {
@@ -2604,24 +2910,24 @@ var ShadowDOMPolyfill = {};
}
},
- // Visual DOM mutation.
appendChild: function(parent, child) {
+ // this.associateNode(child);
+ this.associateNode(parent);
appendChild(parent, child);
- this.associateNode(child);
},
remove: function(node) {
+ // this.associateNode(node);
+ this.associateNode(node.parentNode);
remove(node);
- this.associateNode(node);
},
removeAllChildNodes: function(parent) {
+ this.associateNode(parent);
removeAllChildNodes(parent);
- // TODO(arv): Does this need to associate all the nodes with this renderer?
},
associateNode: function(node) {
- // TODO: Clear when moved out of shadow tree.
shadowDOMRendererTable.set(node, this);
}
};
@@ -2646,28 +2952,19 @@ var ShadowDOMPolyfill = {};
}
function isShadowHost(shadowHost) {
- return !!shadowHost.shadowRoot;
- }
-
- /**
- * @param {WrapperShadowRoot} tree
- */
- function getNextOlderTree(tree) {
- return nextOlderShadowTreeTable.get(tree);
+ return shadowHost.shadowRoot;
}
function getShadowTrees(host) {
var trees = [];
- for (var tree = host.shadowRoot;
- tree;
- tree = nextOlderShadowTreeTable.get(tree)) {
+ for (var tree = host.shadowRoot; tree; tree = tree.olderShadowRoot) {
trees.push(tree);
}
return trees;
}
- function assignShadowTreeToShadowInsertionPoint(tree, point) {
+ function assignToInsertionPoint(tree, point) {
insertionParentTable.set(tree, point);
}
@@ -2676,41 +2973,54 @@ var ShadowDOMPolyfill = {};
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) {
- // TODO: If this is in light DOM we only need to invalidate renderer if this
- // is a direct child of a ShadowRoot.
- // Maybe we should only associate renderers with direct child nodes of a
- // shadow root (and all nodes in the shadow dom).
var renderer = shadowDOMRendererTable.get(this);
- if (!renderer)
- return false;
-
- var p;
- if (force || this.shadowRoot ||
- (p = this.parentNode) && (p.shadowRoot || p instanceof ShadowRoot)) {
+ if (renderer) {
renderer.invalidate();
+ return true;
}
- return true;
+ return false;
};
HTMLContentElement.prototype.getDistributedNodes = function() {
- // TODO(arv): We should associate the element with the shadow root so we
- // only have to rerender this ShadowRenderer.
- renderAllPending();
+ var renderer = shadowDOMRendererTable.get(this);
+ if (renderer)
+ renderer.render();
return getDistributedChildNodes(this);
};
- mixin(Node.prototype, {
- get insertionParent() {
- return insertionParentTable.get(this) || null;
- }
- });
+ HTMLShadowElement.prototype.nodeWasAdded_ =
+ HTMLContentElement.prototype.nodeWasAdded_ = function() {
+ // Invalidate old renderer if any.
+ this.invalidateShadowRenderer();
+
+ var shadowRoot = getShadowRootAncestor(this);
+ var renderer;
+ if (shadowRoot)
+ renderer = getRendererForShadowRoot(shadowRoot);
+ shadowDOMRendererTable.set(this, renderer);
+ if (renderer)
+ renderer.invalidate();
+ };
scope.eventParentsTable = eventParentsTable;
scope.getRendererForHost = getRendererForHost;
scope.getShadowTrees = getShadowTrees;
- scope.nextOlderShadowTreeTable = nextOlderShadowTreeTable;
+ scope.insertionParentTable = insertionParentTable;
scope.renderAllPending = renderAllPending;
// Exposed for testing
@@ -2728,13 +3038,70 @@ var ShadowDOMPolyfill = {};
(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',
+ 'HTMLOutputElement',
+ 'HTMLSelectElement',
+ '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);
+
+})(this.ShadowDOMPolyfill);
+
+// 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 SelectorsInterface = scope.SelectorsInterface;
+ var ShadowRoot = scope.wrappers.ShadowRoot;
var defineWrapGetter = scope.defineWrapGetter;
var elementFromPoint = scope.elementFromPoint;
var forwardMethodsToWrapper = scope.forwardMethodsToWrapper;
+ var matchesName = scope.matchesName;
var mixin = scope.mixin;
var registerWrapper = scope.registerWrapper;
var unwrap = scope.unwrap;
@@ -2767,48 +3134,145 @@ var ShadowDOMPolyfill = {};
}
[
- 'getElementById',
+ 'createComment',
+ 'createDocumentFragment',
'createElement',
'createElementNS',
- 'createTextNode',
- 'createDocumentFragment',
'createEvent',
'createEventNS',
+ 'createRange',
+ 'createTextNode',
+ 'getElementById',
].forEach(wrapMethod);
var originalAdoptNode = document.adoptNode;
- var originalWrite = document.write;
+
+ function adoptNodeNoRemove(node, doc) {
+ originalAdoptNode.call(doc.impl, 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);
+ }
mixin(Document.prototype, {
adoptNode: function(node) {
- originalAdoptNode.call(this.impl, unwrap(node));
+ if (node.parentNode)
+ node.parentNode.removeChild(node);
+ adoptNodeNoRemove(node, this);
return node;
},
elementFromPoint: function(x, y) {
return elementFromPoint(this, this, x, y);
- },
- write: function(s) {
- var all = this.querySelectorAll('*');
- var last = all[all.length - 1];
- while (last.nextSibling) {
- last = last.nextSibling;
- }
- var p = last.parentNode;
- p.lastChild_ = undefined;
- last.nextSibling_ = undefined;
- originalWrite.call(this.impl, s);
}
});
+ if (document.register) {
+ var originalRegister = document.register;
+ Document.prototype.register = function(tagName, object) {
+ var prototype = object.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',
+ 'enteredDocumentCallback',
+ 'leftDocumentCallback',
+ 'attributeChangedCallback',
+ ].forEach(function(name) {
+ var f = prototype[name];
+ if (!f)
+ return;
+ newPrototype[name] = function() {
+ f.apply(wrap(this), arguments);
+ };
+ });
+
+ var nativeConstructor = originalRegister.call(unwrap(this), tagName,
+ {prototype: newPrototype});
+
+ function GeneratedWrapper(node) {
+ if (!node)
+ return document.createElement(tagName);
+ this.impl = node;
+ }
+ GeneratedWrapper.prototype = prototype;
+ GeneratedWrapper.prototype.constructor = GeneratedWrapper;
+
+ scope.constructorTable.set(newPrototype, GeneratedWrapper);
+ scope.nativePrototypeTable.set(prototype, newPrototype);
+
+ return GeneratedWrapper;
+ };
+
+ forwardMethodsToWrapper([
+ window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument
+ ], [
+ 'register',
+ ]);
+ }
+
// 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',
@@ -2817,21 +3281,24 @@ var ShadowDOMPolyfill = {};
'querySelectorAll',
'removeChild',
'replaceChild',
+ matchesName,
]);
forwardMethodsToWrapper([
window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument
], [
'adoptNode',
+ 'contains',
+ 'createComment',
'createDocumentFragment',
'createElement',
'createElementNS',
'createEvent',
'createEventNS',
+ 'createRange',
'createTextNode',
'elementFromPoint',
'getElementById',
- 'write',
]);
mixin(Document.prototype, GetElementsByInterface);
@@ -2898,8 +3365,9 @@ var ShadowDOMPolyfill = {};
'hasFeature',
]);
- scope.wrappers.Document = Document;
+ scope.adoptNodeNoRemove = adoptNodeNoRemove;
scope.wrappers.DOMImplementation = DOMImplementation;
+ scope.wrappers.Document = Document;
})(this.ShadowDOMPolyfill);
@@ -3046,6 +3514,99 @@ var ShadowDOMPolyfill = {};
(function(scope) {
'use strict';
+ var registerWrapper = scope.registerWrapper;
+ var unwrap = scope.unwrap;
+ var unwrapIfNeeded = scope.unwrapIfNeeded;
+ var wrap = scope.wrap;
+
+ var OriginalRange = window.Range;
+
+ function Range(impl) {
+ this.impl = impl;
+ }
+ Range.prototype = {
+ get startContainer() {
+ return wrap(this.impl.startContainer);
+ },
+ get endContainer() {
+ return wrap(this.impl.endContainer);
+ },
+ get commonAncestorContainer() {
+ return wrap(this.impl.commonAncestorContainer);
+ },
+ setStart: function(refNode,offset) {
+ this.impl.setStart(unwrapIfNeeded(refNode), offset);
+ },
+ setEnd: function(refNode,offset) {
+ this.impl.setEnd(unwrapIfNeeded(refNode), offset);
+ },
+ setStartBefore: function(refNode) {
+ this.impl.setStartBefore(unwrapIfNeeded(refNode));
+ },
+ setStartAfter: function(refNode) {
+ this.impl.setStartAfter(unwrapIfNeeded(refNode));
+ },
+ setEndBefore: function(refNode) {
+ this.impl.setEndBefore(unwrapIfNeeded(refNode));
+ },
+ setEndAfter: function(refNode) {
+ this.impl.setEndAfter(unwrapIfNeeded(refNode));
+ },
+ selectNode: function(refNode) {
+ this.impl.selectNode(unwrapIfNeeded(refNode));
+ },
+ selectNodeContents: function(refNode) {
+ this.impl.selectNodeContents(unwrapIfNeeded(refNode));
+ },
+ compareBoundaryPoints: function(how, sourceRange) {
+ return this.impl.compareBoundaryPoints(how, unwrap(sourceRange));
+ },
+ extractContents: function() {
+ return wrap(this.impl.extractContents());
+ },
+ cloneContents: function() {
+ return wrap(this.impl.cloneContents());
+ },
+ insertNode: function(node) {
+ this.impl.insertNode(unwrapIfNeeded(node));
+ },
+ surroundContents: function(newParent) {
+ this.impl.surroundContents(unwrapIfNeeded(newParent));
+ },
+ cloneRange: function() {
+ return wrap(this.impl.cloneRange());
+ },
+ isPointInRange: function(node, offset) {
+ return this.impl.isPointInRange(unwrapIfNeeded(node), offset);
+ },
+ comparePoint: function(node, offset) {
+ return this.impl.comparePoint(unwrapIfNeeded(node), offset);
+ },
+ intersectsNode: function(node) {
+ return this.impl.intersectsNode(unwrapIfNeeded(node));
+ }
+ };
+
+ // IE9 does not have createContextualFragment.
+ if (OriginalRange.prototype.createContextualFragment) {
+ Range.prototype.createContextualFragment = function(html) {
+ return wrap(this.impl.createContextualFragment(html));
+ };
+ }
+
+ registerWrapper(window.Range, Range);
+
+ scope.wrappers.Range = Range;
+
+})(this.ShadowDOMPolyfill);
+
+// 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
@@ -3171,31 +3732,34 @@ var ShadowDOMPolyfill = {};
window.dartExperimentalFixupGetTag = function(originalGetTag) {
var NodeList = ShadowDOMPolyfill.wrappers.NodeList;
var ShadowRoot = ShadowDOMPolyfill.wrappers.ShadowRoot;
- var isWrapper = ShadowDOMPolyfill.isWrapper;
- var unwrap = ShadowDOMPolyfill.unwrap;
+ var unwrapIfNeeded = ShadowDOMPolyfill.unwrapIfNeeded;
function getTag(obj) {
+ // TODO(jmesserly): do we still need these?
if (obj instanceof NodeList) return 'NodeList';
if (obj instanceof ShadowRoot) return 'ShadowRoot';
if (obj instanceof MutationRecord) return 'MutationRecord';
if (obj instanceof MutationObserver) return 'MutationObserver';
- if (isWrapper(obj)) {
- obj = unwrap(obj);
-
- // Fix up class names for Firefox. For some of them like
- // HTMLFormElement and HTMLInputElement, the "constructor" property of
- // the unwrapped nodes points at the wrapper for some reason.
- // TODO(jmesserly): figure out why this is happening.
+ var unwrapped = unwrapIfNeeded(obj);
+ if (obj !== unwrapped) {
+ // Fix up class names for Firefox.
+ // For some of them (like HTMLFormElement and HTMLInputElement),
+ // the "constructor" property of the unwrapped nodes points at the
+ // wrapper.
+ // Note: it is safe to check for the GeneratedWrapper string because
+ // we know it is some kind of Shadow DOM wrapper object.
var ctor = obj.constructor;
- if (ctor && ctor._ShadowDOMPolyfill$isGeneratedWrapper) {
+ if (ctor && ctor.name == 'GeneratedWrapper') {
var name = ctor._ShadowDOMPolyfill$cacheTag_;
if (!name) {
- name = Object.prototype.toString.call(obj);
+ name = Object.prototype.toString.call(unwrapped);
name = name.substring(8, name.length - 1);
ctor._ShadowDOMPolyfill$cacheTag_ = name;
}
return name;
}
+
+ obj = unwrapped;
}
return originalGetTag(obj);
}
« no previous file with comments | « no previous file | pkg/shadow_dom/lib/shadow_dom.min.js » ('j') | pkg/shadow_dom/lib/src/platform/patches-shadowdom-polyfill.js » ('J')

Powered by Google App Engine
This is Rietveld 408576698