Skip to content
This repository has been archived by the owner on Mar 13, 2018. It is now read-only.

Commit

Permalink
Introduce a TreeScope so we can get to the root node in O(1)
Browse files Browse the repository at this point in the history
  • Loading branch information
arv committed Feb 27, 2014
1 parent 4cc6fd4 commit 1637a92
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 43 deletions.
1 change: 1 addition & 0 deletions build.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions shadowdom.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
21 changes: 14 additions & 7 deletions src/ShadowRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 = [];
Expand All @@ -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;
}

Expand Down Expand Up @@ -376,6 +380,10 @@
this.dirty = false;
},

get parentRenderer() {
return getTreeScope(this.host).renderer;
},

invalidate: function() {
if (!this.dirty) {
this.dirty = true;
Expand Down Expand Up @@ -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) {
Expand Down
63 changes: 63 additions & 0 deletions src/TreeScope.js
Original file line number Diff line number Diff line change
@@ -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);
2 changes: 2 additions & 0 deletions src/wrappers/Document.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,6 +30,7 @@

function Document(node) {
Node.call(this, node);
this.treeScope_ = new TreeScope(this, null);
}
Document.prototype = Object.create(Node.prototype);

Expand Down
2 changes: 1 addition & 1 deletion src/wrappers/HTMLElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@
});

nodesWereRemoved(removedNodes);
nodesWereAdded(addedNodes);
nodesWereAdded(addedNodes, this);
},

get outerHTML() {
Expand Down
39 changes: 26 additions & 13 deletions src/wrappers/Node.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -319,6 +330,8 @@
* @private
*/
this.previousSibling_ = undefined;

this.treeScope_ = undefined;
}

var OriginalDocumentFragment = window.DocumentFragment;
Expand Down Expand Up @@ -407,7 +420,7 @@
previousSibling: previousNode
});

nodesWereAdded(nodes);
nodesWereAdded(nodes, this);

return childWrapper;
},
Expand Down Expand Up @@ -539,7 +552,7 @@
});

nodeWasRemoved(oldChildWrapper);
nodesWereAdded(nodes);
nodesWereAdded(nodes, this);

return oldChildWrapper;
},
Expand Down Expand Up @@ -631,7 +644,7 @@
});

nodesWereRemoved(removedNodes);
nodesWereAdded(addedNodes);
nodesWereAdded(addedNodes, this);
},

get childNodes() {
Expand Down Expand Up @@ -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);
4 changes: 4 additions & 0 deletions src/wrappers/ShadowRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);

Expand Down
26 changes: 5 additions & 21 deletions src/wrappers/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 1637a92

Please sign in to comment.