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

Commit

Permalink
Merge pull request #210 from Polymer/master
Browse files Browse the repository at this point in the history
8/15 master -> stable
  • Loading branch information
dfreedm committed Aug 15, 2013
2 parents 31eee3f + c84c4d8 commit 9d8aa3b
Show file tree
Hide file tree
Showing 3 changed files with 251 additions and 33 deletions.
106 changes: 77 additions & 29 deletions src/ShadowRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,33 +164,6 @@
}
}

// 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

Expand Down Expand Up @@ -278,6 +251,7 @@
function ShadowRenderer(host) {
this.host = host;
this.dirty = false;
this.invalidateAttributes();
this.associateNode(host);
}

Expand All @@ -290,14 +264,18 @@
return renderer;
}


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;
Expand Down Expand Up @@ -389,6 +367,76 @@
}, 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;
Expand Down Expand Up @@ -417,7 +465,7 @@
});
point = shadowInsertionPoint;

pool = distribute(tree, pool); // 4.2.
pool = this.distribute(tree, pool); // 4.2.
if (point) { // 4.3.
var nextOlderTree = tree.olderShadowRoot; // 4.3.1.
if (!nextOlderTree) {
Expand Down
49 changes: 45 additions & 4 deletions src/wrappers/Element.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@
OriginalElement.prototype.msMatchesSelector ||
OriginalElement.prototype.webkitMatchesSelector;


function invalidateRendererBasedOnAttribute(element, name) {
// Only invalidate if parent node is a shadow host.
var p = element.parentNode;
if (!p)
return;

var renderer = scope.getRendererForHost(p);
if (!renderer)
return;

if (renderer.dependsOnAttribute(name))
renderer.invalidate();
}

function Element(node) {
Node.call(this, node);
}
Expand All @@ -46,10 +61,12 @@

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) {
Expand All @@ -62,12 +79,36 @@
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);
mixin(Element.prototype, SelectorsInterface);

registerWrapper(OriginalElement, Element);

// TODO(arv): Export setterDirtiesAttribute and apply it to more bindings
// that reflect attributes.
scope.wrappers.Element = Element;
})(this.ShadowDOMPolyfill);
129 changes: 129 additions & 0 deletions test/js/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

suite('Shadow DOM', function() {

var getRendererForHost = ShadowDOMPolyfill.getRendererForHost;
var unwrap = ShadowDOMPolyfill.unwrap;

function getVisualInnerHtml(el) {
Expand Down Expand Up @@ -258,4 +259,132 @@ suite('Shadow DOM', function() {

});

suite('Tracking attributes', function() {

test('attribute selector', function() {
var host = document.createElement('div');
host.innerHTML = '<a></a>';
var a = host.firstChild;

var sr = host.createShadowRoot();
sr.innerHTML = '<content select="[foo]"></content>';

var calls = 0;
var renderer = getRendererForHost(host);
var originalRender = renderer.render;
renderer.render = function() {
calls++;
originalRender.call(this);
};

assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 1);

a.setAttribute('foo', 'bar');
assert.equal(getVisualInnerHtml(host), '<a foo="bar"></a>');
assert.equal(calls, 2);

a.setAttribute('foo', '');
assert.equal(getVisualInnerHtml(host), '<a foo=""></a>');
assert.equal(calls, 3);

a.removeAttribute('foo');
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 4);

a.setAttribute('bar', '');
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 4);
});

test('id selector', function() {
var host = document.createElement('div');
host.innerHTML = '<a></a>';
var a = host.firstChild;

var sr = host.createShadowRoot();
sr.innerHTML = '<content select="#a"></content>';

var calls = 0;
var renderer = getRendererForHost(host);
var originalRender = renderer.render;
renderer.render = function() {
calls++;
originalRender.call(this);
};

assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 1);

a.setAttribute('foo', 'bar');
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 1);

a.setAttribute('id', 'a');
assert.equal(getVisualInnerHtml(host), '<a foo="bar" id="a"></a>');
assert.equal(calls, 2);

a.removeAttribute('foo');
assert.equal(getVisualInnerHtml(host), '<a id="a"></a>');
assert.equal(calls, 2);

a.id = 'b';
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 3);

a.id = 'a';
assert.equal(getVisualInnerHtml(host), '<a id="a"></a>');
assert.equal(calls, 4);

a.id = null;
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 5);
});

test('class selector', function() {
var host = document.createElement('div');
host.innerHTML = '<a></a>';
var a = host.firstChild;

var sr = host.createShadowRoot();
sr.innerHTML = '<content select=".a"></content>';

var calls = 0;
var renderer = getRendererForHost(host);
var originalRender = renderer.render;
renderer.render = function() {
calls++;
originalRender.call(this);
};

assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 1);

a.setAttribute('foo', 'bar');
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 1);

a.setAttribute('class', 'a');
assert.equal(getVisualInnerHtml(host), '<a foo="bar" class="a"></a>');
assert.equal(calls, 2);

a.removeAttribute('foo');
assert.equal(getVisualInnerHtml(host), '<a class="a"></a>');
assert.equal(calls, 2);

a.className = 'b';
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 3);

a.className = 'a';
assert.equal(getVisualInnerHtml(host), '<a class="a"></a>');
assert.equal(calls, 4);

a.className = null;
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 5);
});

});

});

0 comments on commit 9d8aa3b

Please sign in to comment.