Skip to content

Instantly share code, notes, and snippets.

@nbdd0121
Created August 9, 2015 12:53
Show Gist options
  • Save nbdd0121/941c95f6eb7432ae08a5 to your computer and use it in GitHub Desktop.
Save nbdd0121/941c95f6eb7432ae08a5 to your computer and use it in GitHub Desktop.
A CSS encapsulation polyfill that works on top of WebComponents.js
/*
* Copyright (c) 2015, Gary Guo
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
'use strict';
(function() {
var hasNativeShadowRoot = typeof document.documentElement.createShadowRoot !== 'undefined';
if (hasNativeShadowRoot) {
return;
}
function loadFile(url, callback) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
callback(undefined, this.response);
} else {
callback(new Error(this.statusText));
}
};
request.onerror = function(err) {
callback(new Error(err));
};
request.send();
}
// Very simple unique identifier creator
var uuid = 0;
function createUUID() {
return '_' + (uuid++);
}
// "Hijack" createShadowRoot
var originalCreateShadowRoot = Element.prototype.createShadowRoot;
Element.prototype.createShadowRoot = function() {
var uuid = createUUID();
var shadow = originalCreateShadowRoot.call(this);
shadow.__norlit_shadow = uuid;
registerRoot(shadow);
return shadow;
};
// Functions that operate on and translate CSS style sheets
function parseStyleSheet(text) {
var s = document.createElement('style');
s.setAttribute('norlit-shadow-options', 'no-translate');
s.textContent = text;
document.body.appendChild(s);
var sheet = s.sheet;
document.body.removeChild(s);
return sheet;
}
function translateCSSRule(rule, uuid) {
switch (rule.type) {
case CSSRule.STYLE_RULE:
var selectorSuffix = uuid ? '[norlit-shadow=' + uuid + ']' : ':not([norlit-shadow])';
return rule.selectorText + selectorSuffix + '{' + rule.style.cssText + '}';
default:
throw new Error(rule.constructor.name + ' is not yet supported');
}
}
function translateStyleSheet(styleText, uuid) {
var style = parseStyleSheet(styleText);
var rules = style.cssRules;
var translated = '';
for (var i = 0; i < rules.length; i++) {
translated += translateCSSRule(rules[i], uuid);
}
return translated;
}
// Update element and its children
function updateElement(root, uuid) {
if (uuid) {
root.setAttribute('norlit-shadow', uuid);
} else {
root.removeAttribute('norlit-shadow');
}
if (root.tagName === 'STYLE') {
if (!root.__norlit_shadow_css_translated) {
root.textContent = translateStyleSheet(root.textContent, uuid);
root.__norlit_shadow_css_translated = true;
}
} else if (root.tagName === 'LINK') {
if (root.rel === 'stylesheet' && root.type === 'text/css') {
loadFile(root.href, function(err, data) {
if (err) {
console.log('Failed to replace stylesheet ' + root.href + ': ' + err.message);
return;
}
var replace = document.createElement('style');
replace.textContent = translateStyleSheet(data, uuid);
replace.__norlit_shadow_css_translated = true;
root.parentNode.replaceChild(replace, root);
});
}
}
var children = root.children;
for (var i = 0; i < children.length; i++) {
updateElement(children[i], uuid);
}
}
// Observe DOM changes and perform updateElement
var rootObservers = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var nodes = mutation.addedNodes;
var uuid = mutation.target.__norlit_shadow;
if (uuid === undefined) {
uuid = mutation.target.getAttribute('norlit-shadow');
}
for (var i = 0; i < nodes.length; i++) {
// Skip TextNode
if (nodes[i] instanceof Element) {
if (nodes[i].getAttribute('norlit-shadow-options') === 'no-translate') {
continue;
}
updateElement(nodes[i], uuid);
}
}
});
});
function registerRoot(root) {
rootObservers.observe(root, {
childList: true,
subtree: true
});
}
function ready(callback) {
if (document.readyState != 'loading') {
callback();
} else {
document.addEventListener('DOMContentLoaded', callback);
}
}
ready(function() {
// This is neccessary since polyfills cannot override document.documentElement
var docElement = document.querySelector('html');
docElement.parentNode.__norlit_shadow = '';
updateElement(docElement);
registerRoot(docElement);
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment