Chromium Code Reviews| 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; |
| + |
|
Emily Fortuna
2013/09/05 18:19:10
remove extra line here?
Jennifer Messerly
2013/09/05 19:47:02
I submitted a pull request for upstream:
https://g
|
| + } |
| + |
| 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); |
| } |