From 1637a929e4e35cf4374f4115146aadea88a67dc3 Mon Sep 17 00:00:00 2001 From: Erik Arvidsson Date: Thu, 27 Feb 2014 12:51:04 -0500 Subject: [PATCH] Introduce a TreeScope so we can get to the root node in O(1) --- build.json | 1 + shadowdom.js | 1 + src/ShadowRenderer.js | 21 ++++++++----- src/TreeScope.js | 63 +++++++++++++++++++++++++++++++++++++ src/wrappers/Document.js | 2 ++ src/wrappers/HTMLElement.js | 2 +- src/wrappers/Node.js | 39 +++++++++++++++-------- src/wrappers/ShadowRoot.js | 4 +++ src/wrappers/events.js | 26 +++------------ test/js/TreeScope.js | 49 +++++++++++++++++++++++++++++ test/test.main.js | 3 +- 11 files changed, 168 insertions(+), 43 deletions(-) create mode 100644 src/TreeScope.js create mode 100644 test/js/TreeScope.js diff --git a/build.json b/build.json index 938304b..e1f86bd 100644 --- a/build.json +++ b/build.json @@ -4,6 +4,7 @@ "src/wrappers.js", "src/microtask.js", "src/MutationObserver.js", + "src/TreeScope.js", "src/wrappers/events.js", "src/wrappers/NodeList.js", "src/wrappers/HTMLCollection.js", diff --git a/shadowdom.js b/shadowdom.js index d752c37..4c75063 100644 --- a/shadowdom.js +++ b/shadowdom.js @@ -20,6 +20,7 @@ 'src/wrappers.js', 'src/microtask.js', 'src/MutationObserver.js', + "src/TreeScope.js", 'src/wrappers/events.js', 'src/wrappers/NodeList.js', 'src/wrappers/HTMLCollection.js', diff --git a/src/ShadowRenderer.js b/src/ShadowRenderer.js index d8d6b85..0cc0db4 100644 --- a/src/ShadowRenderer.js +++ b/src/ShadowRenderer.js @@ -11,6 +11,7 @@ var Node = scope.wrappers.Node; var ShadowRoot = scope.wrappers.ShadowRoot; var assert = scope.assert; + var getTreeScope = scope.getTreeScope; var mixin = scope.mixin; var oneOf = scope.oneOf; var unwrap = scope.unwrap; @@ -230,7 +231,11 @@ // TODO(arv): Order these in document order. That way we do not have to // render something twice. for (var i = 0; i < pendingDirtyRenderers.length; i++) { - pendingDirtyRenderers[i].render(); + var renderer = pendingDirtyRenderers[i]; + var parentRenderer = renderer.parentRenderer; + if (parentRenderer && parentRenderer.dirty) + continue; + renderer.render(); } pendingDirtyRenderers = []; @@ -256,10 +261,9 @@ } function getShadowRootAncestor(node) { - for (; node; node = node.parentNode) { - if (node instanceof ShadowRoot) - return node; - } + var root = getTreeScope(node).root; + if (root instanceof ShadowRoot) + return root; return null; } @@ -376,6 +380,10 @@ this.dirty = false; }, + get parentRenderer() { + return getTreeScope(this.host).renderer; + }, + invalidate: function() { if (!this.dirty) { this.dirty = true; @@ -406,8 +414,7 @@ if (isShadowHost(node)) { var renderer = getRendererForHost(node); - // renderNode.skip = !renderer.dirty; - renderer.invalidate(); + renderNode.skip = !renderer.dirty; renderer.render(renderNode); } else { for (var child = node.firstChild; child; child = child.nextSibling) { diff --git a/src/TreeScope.js b/src/TreeScope.js new file mode 100644 index 0000000..f883fd8 --- /dev/null +++ b/src/TreeScope.js @@ -0,0 +1,63 @@ +/** + * Copyright 2014 The Polymer Authors. All rights reserved. + * Use of this source code is goverened by a BSD-style + * license that can be found in the LICENSE file. + */ + +(function(scope) { + 'use strict'; + + /** + * A tree scope represents the root of a tree. All nodes in a tree point to + * the same TreeScope object. The tree scope of a node get set the first time + * it is accessed or when a node is added or remove to a tree. + * @constructor + */ + function TreeScope(root, parent) { + this.root = root; + this.parent = parent; + } + + TreeScope.prototype = { + get renderer() { + if (this.root instanceof scope.wrappers.ShadowRoot) { + return scope.getRendererForHost(this.root.host); + } + return null; + }, + + contains: function(treeScope) { + for (; treeScope; treeScope = treeScope.parent) { + if (treeScope === this) + return true; + } + return false; + } + }; + + function setTreeScope(node, treeScope) { + if (node.treeScope_ !== treeScope) { + node.treeScope_ = treeScope; + for (var child = node.firstChild; child; child = child.nextSibling) { + setTreeScope(child, treeScope); + } + } + } + + function getTreeScope(node) { + if (node.treeScope_) + return node.treeScope_; + var parent = node.parentNode; + var treeScope; + if (parent) + treeScope = getTreeScope(parent); + else + treeScope = new TreeScope(node, null); + return node.treeScope_ = treeScope; + } + + scope.TreeScope = TreeScope; + scope.getTreeScope = getTreeScope; + scope.setTreeScope = setTreeScope; + +})(window.ShadowDOMPolyfill); diff --git a/src/wrappers/Document.js b/src/wrappers/Document.js index fa28355..53e09b6 100644 --- a/src/wrappers/Document.js +++ b/src/wrappers/Document.js @@ -11,6 +11,7 @@ var Selection = scope.wrappers.Selection; var SelectorsInterface = scope.SelectorsInterface; var ShadowRoot = scope.wrappers.ShadowRoot; + var TreeScope = scope.TreeScope; var cloneNode = scope.cloneNode; var defineWrapGetter = scope.defineWrapGetter; var elementFromPoint = scope.elementFromPoint; @@ -29,6 +30,7 @@ function Document(node) { Node.call(this, node); + this.treeScope_ = new TreeScope(this, null); } Document.prototype = Object.create(Node.prototype); diff --git a/src/wrappers/HTMLElement.js b/src/wrappers/HTMLElement.js index 5f76ac6..86167c8 100644 --- a/src/wrappers/HTMLElement.js +++ b/src/wrappers/HTMLElement.js @@ -190,7 +190,7 @@ }); nodesWereRemoved(removedNodes); - nodesWereAdded(addedNodes); + nodesWereAdded(addedNodes, this); }, get outerHTML() { diff --git a/src/wrappers/Node.js b/src/wrappers/Node.js index 3110adf..96911b1 100644 --- a/src/wrappers/Node.js +++ b/src/wrappers/Node.js @@ -1,19 +1,24 @@ -// Copyright 2012 The Polymer Authors. All rights reserved. -// Use of this source code is goverened by a BSD-style -// license that can be found in the LICENSE file. +/** + * Copyright 2012 The Polymer Authors. All rights reserved. + * Use of this source code is goverened by a BSD-style + * license that can be found in the LICENSE file. + */ (function(scope) { 'use strict'; var EventTarget = scope.wrappers.EventTarget; var NodeList = scope.wrappers.NodeList; + var TreeScope = scope.TreeScope; var assert = scope.assert; var defineWrapGetter = scope.defineWrapGetter; var enqueueMutation = scope.enqueueMutation; + var getTreeScope = scope.getTreeScope; var isWrapper = scope.isWrapper; var mixin = scope.mixin; var registerTransientObservers = scope.registerTransientObservers; var registerWrapper = scope.registerWrapper; + var setTreeScope = scope.setTreeScope; var unwrap = scope.unwrap; var wrap = scope.wrap; var wrapIfNeeded = scope.wrapIfNeeded; @@ -130,23 +135,27 @@ } // http://dom.spec.whatwg.org/#node-is-inserted - function nodeWasAdded(node) { + function nodeWasAdded(node, treeScope) { + setTreeScope(node, treeScope); node.nodeIsInserted_(); } - function nodesWereAdded(nodes) { + function nodesWereAdded(nodes, parent) { + var treeScope = getTreeScope(parent); for (var i = 0; i < nodes.length; i++) { - nodeWasAdded(nodes[i]); + nodeWasAdded(nodes[i], treeScope); } } // http://dom.spec.whatwg.org/#node-is-removed function nodeWasRemoved(node) { - // Nothing at this point in time. + setTreeScope(node, new TreeScope(node, null)); } function nodesWereRemoved(nodes) { - // Nothing at this point in time. + for (var i = 0; i < nodes.length; i++) { + nodeWasRemoved(nodes[i]); + } } function ensureSameOwnerDocument(parent, child) { @@ -265,7 +274,9 @@ } function contains(self, child) { - // TODO(arv): Optimize using ownerDocument etc. + if (!child || getTreeScope(self) !== getTreeScope(child)) + return false; + for (var node = child; node; node = node.parentNode) { if (node === self) return true; @@ -319,6 +330,8 @@ * @private */ this.previousSibling_ = undefined; + + this.treeScope_ = undefined; } var OriginalDocumentFragment = window.DocumentFragment; @@ -407,7 +420,7 @@ previousSibling: previousNode }); - nodesWereAdded(nodes); + nodesWereAdded(nodes, this); return childWrapper; }, @@ -539,7 +552,7 @@ }); nodeWasRemoved(oldChildWrapper); - nodesWereAdded(nodes); + nodesWereAdded(nodes, this); return oldChildWrapper; }, @@ -631,7 +644,7 @@ }); nodesWereRemoved(removedNodes); - nodesWereAdded(addedNodes); + nodesWereAdded(addedNodes, this); }, get childNodes() { @@ -706,12 +719,12 @@ delete Node.prototype.querySelectorAll; Node.prototype = mixin(Object.create(EventTarget.prototype), Node.prototype); + scope.cloneNode = cloneNode; scope.nodeWasAdded = nodeWasAdded; scope.nodeWasRemoved = nodeWasRemoved; scope.nodesWereAdded = nodesWereAdded; scope.nodesWereRemoved = nodesWereRemoved; scope.snapshotNodeList = snapshotNodeList; scope.wrappers.Node = Node; - scope.cloneNode = cloneNode; })(window.ShadowDOMPolyfill); diff --git a/src/wrappers/ShadowRoot.js b/src/wrappers/ShadowRoot.js index 31ae047..ccc845a 100644 --- a/src/wrappers/ShadowRoot.js +++ b/src/wrappers/ShadowRoot.js @@ -6,8 +6,10 @@ 'use strict'; var DocumentFragment = scope.wrappers.DocumentFragment; + var TreeScope = scope.TreeScope; var elementFromPoint = scope.elementFromPoint; var getInnerHTML = scope.getInnerHTML; + var getTreeScope = scope.getTreeScope; var mixin = scope.mixin; var rewrap = scope.rewrap; var setInnerHTML = scope.setInnerHTML; @@ -26,6 +28,8 @@ // DocumentFragment instance. Override that. rewrap(node, this); + this.treeScope_ = new TreeScope(this, getTreeScope(hostWrapper)); + var oldShadowRoot = hostWrapper.shadowRoot; nextOlderShadowTreeTable.set(this, oldShadowRoot); diff --git a/src/wrappers/events.js b/src/wrappers/events.js index 268b2c8..a4da32e 100644 --- a/src/wrappers/events.js +++ b/src/wrappers/events.js @@ -6,6 +6,7 @@ 'use strict'; var forwardMethodsToWrapper = scope.forwardMethodsToWrapper; + var getTreeScope = scope.getTreeScope; var mixin = scope.mixin; var registerWrapper = scope.registerWrapper; var unwrap = scope.unwrap; @@ -160,27 +161,10 @@ return getInsertionParent(node); } - function rootOfNode(node) { - var p; - while (p = node.parentNode) { - node = p; - } - return node; - } - function inSameTree(a, b) { - return rootOfNode(a) === rootOfNode(b); - } - - function enclosedBy(a, b) { - if (a === b) - return true; - if (a instanceof wrappers.ShadowRoot) - return enclosedBy(rootOfNode(a.host), b); - return false; + return getTreeScope(a) === getTreeScope(b); } - function dispatchOriginalEvent(originalEvent) { // Make sure this event is only dispatched once. if (handledEventsTable.get(originalEvent)) @@ -395,12 +379,12 @@ if (eventPath) { var index = 0; var lastIndex = eventPath.length - 1; - var baseRoot = rootOfNode(currentTargetTable.get(this)); + var baseRoot = getTreeScope(currentTargetTable.get(this)); for (var i = 0; i <= lastIndex; i++) { var currentTarget = eventPath[i].currentTarget; - var currentRoot = rootOfNode(currentTarget); - if (enclosedBy(baseRoot, currentRoot) && + var currentRoot = getTreeScope(currentTarget); + if (currentRoot.contains(baseRoot) && // Make sure we do not add Window to the path. (i !== lastIndex || currentTarget instanceof wrappers.Node)) { nodeList[index++] = currentTarget; diff --git a/test/js/TreeScope.js b/test/js/TreeScope.js new file mode 100644 index 0000000..36b768d --- /dev/null +++ b/test/js/TreeScope.js @@ -0,0 +1,49 @@ +/** + * Copyright 2014 The Polymer Authors. All rights reserved. + * Use of this source code is goverened by a BSD-style + * license that can be found in the LICENSE file. + */ + +suite('TreeScope', function() { + + var getTreeScope = ShadowDOMPolyfill.getTreeScope; + + test('Basic', function() { + var div = document.createElement('div'); + + var ts = getTreeScope(div); + assert.equal(ts.root, div); + + div.innerHTML = ''; + var a = div.firstChild; + var b = a.firstChild; + + assert.equal(getTreeScope(a), ts); + assert.equal(getTreeScope(b), ts); + }); + + test('ShadowRoot', function() { + var div = document.createElement('div'); + + var ts = getTreeScope(div); + assert.equal(ts.root, div); + + div.innerHTML = ''; + var a = div.firstChild; + var b = a.firstChild; + + var sr = a.createShadowRoot(); + + var srTs = getTreeScope(sr); + assert.equal(srTs.root, sr); + assert.equal(srTs.parent, ts); + + sr.innerHTML = ''; + var c = sr.firstChild; + var d = c.firstChild; + + assert.equal(getTreeScope(c), srTs); + assert.equal(getTreeScope(d), srTs); + }); + +}); diff --git a/test/test.main.js b/test/test.main.js index 6d0e169..2e47dc9 100644 --- a/test/test.main.js +++ b/test/test.main.js @@ -96,7 +96,6 @@ var modules = [ 'HTMLOutputElement.js', 'HTMLSelectElement.js', 'HTMLShadowElement.js', - 'createTable.js', 'HTMLTableElement.js', 'HTMLTableRowElement.js', 'HTMLTableSectionElement.js', @@ -119,7 +118,9 @@ var modules = [ 'Selection.js', 'ShadowRoot.js', 'Text.js', + 'TreeScope.js', 'Window.js', + 'createTable.js', 'custom-element.js', 'events.js', 'microtask.js',