Skip to content

Instantly share code, notes, and snippets.

@arv
Last active August 29, 2015 13:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arv/8857167 to your computer and use it in GitHub Desktop.
Save arv/8857167 to your computer and use it in GitHub Desktop.
Secure shadow dom
(function() {
'use strict';
function uncurryThis(f) {
return f.call.bind(f);
}
var ShadowRoot = window.ShadowRoot;
var createShadowRoot = uncurryThis(Element.prototype.createShadowRoot);
var ownerDocumentGetter = uncurryThis(Object.getOwnPropertyDescriptor(Node.prototype, 'ownerDocument').get);
var contains = uncurryThis(Node.prototype.contains);
var parentNodeGetter = uncurryThis(Object.getOwnPropertyDescriptor(Node.prototype, 'parentNode').get);
var hostGetter = uncurryThis(Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'host').get);
var shadowRootGetter = uncurryThis(Object.getOwnPropertyDescriptor(Element.prototype, 'shadowRoot').get);
var olderShadowRootGetter = uncurryThis(Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'olderShadowRoot').get);
var defineProperty = Object.defineProperty;
var weakMapSet = uncurryThis(WeakMap.prototype.set);
var weakMapGet = uncurryThis(WeakMap.prototype.get);
var disallowBrand = new WeakMap;
Element.prototype.createEncapsulatedShadowRoot = function() {
var sr = createShadowRoot(this);
weakMapSet(disallowBrand, sr, true);
return sr;
};
Object.defineProperty(Element.prototype, 'shadowRoot', {
get: function() {
var sr = shadowRootGetter(this);
if (weakMapGet(disallowBrand, sr))
return null;
return sr;
}
});
Object.defineProperty(ShadowRoot.prototype, 'olderShadowRoot', {
get: function() {
var sr = olderShadowRootGetter(this);
if (weakMapGet(disallowBrand, sr))
return null;
return sr;
}
});
function getShadowRootAncestor(node) {
// document.contains is O(1) and nodes in a shadow dom are not in document.
if (!node || contains(ownerDocumentGetter(node), node))
return null;
if (node instanceof ShadowRoot)
return node;
return getShadowRootAncestor(parentNodeGetter(node));
}
function inDisallowedRoot(node) {
var sr = getShadowRootAncestor(node);
if (!sr)
return false;
if (weakMapGet(disallowBrand, sr))
return true;
return inDisallowedRoot(hostGetter(sr));
}
function filterStaticNodeList(nodeList) {
var staticNodeList = {__proto__: NodeList.prototype}; // best effort
var len = 0;
for (var i = 0; i < nodeList.length; i++) {
if (!inDisallowedRoot(nodeList[i])) {
defineProperty(staticNodeList, len++, {
value: nodeList[i],
writable: false,
configurable: true,
enumerable: true
});
}
}
staticNodeList.length = len; // wrong, should be getter in proto
return staticNodeList;
}
function makeFilterElementFunction(prototype, name) {
var original = prototype[name];
prototype[name] = function() {
var element = original.apply(this, arguments); // FIXME: apply!
if (inDisallowedRoot(element))
return null;
return element;
};
}
function makeFilterNodeListFunction(prototype, name) {
var original = prototype[name];
prototype[name] = function() {
return filterStaticNodeList(original.apply(this, arguments)); // FIXME: apply!
};
}
makeFilterElementFunction(Document.prototype, 'querySelector');
makeFilterElementFunction(DocumentFragment.prototype, 'querySelector');
makeFilterElementFunction(Element.prototype, 'querySelector');
makeFilterNodeListFunction(Document.prototype, 'querySelectorAll');
makeFilterNodeListFunction(DocumentFragment.prototype, 'querySelectorAll');
makeFilterNodeListFunction(Element.prototype, 'querySelectorAll');
makeFilterNodeListFunction(Element.prototype, 'getDestinationInsertionPoints');
})();
(function() {
'use strict';
var shadowRootGetter = Object.getOwnPropertyDescriptor(Element.prototype, 'shadowRoot').get;
delete Element.prototype.shadowRoot;
var getDestinationInsertionPoints = Element.prototype.getDestinationInsertionPoints;
delete Element.prototype.getDestinationInsertionPoints;
var olderShadowRootGetter = Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'olderShadowRoot').get;
delete ShadowRoot.prototype.olderShadowRoot;
// TODO(arv): Return null from querySelector/querySelectorAll if the selector contains ^/^^
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment