Style shadowDOM

Allow stylus to target elements in shadow-root

  1. // ==UserScript==
  2. // @name Style shadowDOM
  3. // @namespace https://github.com/Procyon-b
  4. // @version 0.2.2
  5. // @description Allow stylus to target elements in shadow-root
  6. // @author Achernar
  7. // @match http://NOTHING/
  8. // @run-at document-start
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. "use strict";
  14. var id=Date.now()
  15.  
  16. // patch attachShadow()
  17. var AS=HTMLElement.prototype.attachShadow;
  18. // bail out if no function
  19. if (!AS) return;
  20.  
  21. HTMLElement.prototype.attachShadow=function(m){/*[native */
  22. var e=this;
  23. e.sr=AS.apply(e,arguments)
  24. shadows.push(e.sr);
  25. inject(e.sr);
  26. shadowMod(e.sr);
  27. return e.sr;
  28. }
  29. // END patch
  30.  
  31. var sheets={}, order=[],
  32. shadows=[], sCSS={}, injCSS='', oldInjCSS='';
  33.  
  34. // catch stylus injections
  35. const obs=new MutationObserver(function(muts){
  36. for (let mut of muts) {
  37. for (let n of mut.addedNodes) {
  38. if ( (n.tagName == 'STYLE') && (
  39. ( (n.className == 'stylus') && n.id.startsWith('stylus-') ) ||
  40. ( n.className.startsWith('stylish') && n.id.startsWith('stylish-') ) ||
  41. ( (n.className == 'xstyle') && n.id.startsWith('xstyle-') )
  42. )) {
  43. addCSS(n);
  44. }
  45. }
  46.  
  47. for (let n of mut.removedNodes) {
  48. if ( (n.tagName == 'STYLE') && (
  49. ( (n.className == 'stylus') && n.id.startsWith('stylus-') ) ||
  50. ( n.className.startsWith('stylish') && n.id.startsWith('stylish-') ) ||
  51. ( (n.className == 'xstyle') && n.id.startsWith('xstyle-') )
  52. )) {
  53. remCSS(n);
  54. }
  55. }
  56. }
  57. });
  58. // END catch
  59.  
  60. function init() {
  61.  
  62. // find open shadows if userscript is ran after page load.
  63. function findShadows(r) {
  64. let a=r.querySelectorAll('*');
  65. for (let s, i=0; i < a.length; i++) {
  66. if (s=a[i].shadowRoot) {
  67. shadows.push(s);
  68. shadowMod(s)
  69. findShadows(s);
  70. }
  71. }
  72. }
  73. if (document.readyState == 'complete') findShadows(document);
  74. // END findShadows
  75.  
  76. if (!document.documentElement) {
  77. setTimeout(init, 0);
  78. return;
  79. }
  80.  
  81. obs.observe(document.documentElement, {attributes: false, subtree: false, childList: true });
  82. // check for stylesheets
  83. document.documentElement.querySelectorAll('style[id^="stylus-"].stylus, style[id^="stylish-"][class^="stylish"], style[id^="xstyle-"].xstyle, head style[data-source^="User Java"]').forEach( (e) => addCSS(e,2) );
  84. window.addEventListener('load', function(){
  85. document.documentElement.querySelectorAll('head style[data-source^="User Java"]').forEach( (e) => addCSS(e,3) );
  86. });
  87. }
  88.  
  89. init();
  90.  
  91. // get only shadow-specific css
  92. function parseSheet(s) {
  93. var R=s.sheet.cssRules, sels, sel, shCSS='';
  94. for (let i=0; i < R.length; i++) {
  95. sel=R[i].selectorText;
  96. if (!sel || !sel.includes(':host')) continue;
  97. sels=sel.split(',').filter(function(v){ return v.includes(':host') }).join(',');
  98. shCSS+=sels.trim()+ R[i].cssText.substr(sel.length)+'\n';
  99. }
  100.  
  101. if (shCSS) shCSS='/*'+s.id+'*/\n'+shCSS;
  102. return shCSS;
  103. }
  104.  
  105. // inject in this shadow
  106. function inject(e) {
  107. if (injCSS == '') {
  108. if (e._inj_) {
  109. e._inj_.remove();
  110. delete e._inj_;
  111. }
  112. return;
  113. }
  114. if (!e._inj_ || !e._inj_.parentNode ) {
  115. let s=e._inj_=document.createElement('style');
  116. s.className='sh-stylus';
  117. e.appendChild(s);
  118. }
  119. e._inj_.textContent=injCSS;
  120. }
  121.  
  122. // inject style
  123. function injectAll() {
  124. for (let i=0; i < shadows.length; i++) inject(shadows[i]);
  125. }
  126.  
  127. // create & inject style in shadows
  128. function injUpd() {
  129. oldInjCSS=injCSS;
  130. injCSS='';
  131. for (let i=0; i < order.length; i++) injCSS+=sCSS[order[i]];
  132. if (injCSS !== oldInjCSS) injectAll();
  133. }
  134.  
  135. // get stylus stylesheet order
  136. function getStylusOrder() {
  137. order=[];
  138. for (let i=0; i < document.styleSheets.length; i++) {
  139. let s=document.styleSheets[i].ownerNode;
  140. if (s.nodeName != 'STYLE') continue;
  141. if ( (s.className == 'stylus') || (s.className.startsWith('stylish')) || (s.className == 'xstyle') ||
  142. (s.dataset.source && s.dataset.source.startsWith('User Java'))
  143. )
  144. order.push(s.id);
  145. }
  146. }
  147.  
  148. // handle new stylesheet
  149. function addCSS(n, k=1) {
  150. if (!n.id) n.id=Math.random();
  151. if (n.id in sheets) return;
  152. sheets[n.id]=n;
  153. if (n.__k__) ;
  154. else {
  155. n.__k__=k;
  156.  
  157. // monitor modification STYLE element
  158. const obs=new MutationObserver(function(muts){
  159. if (muts[0].addedNodes.length) {
  160. sCSS[muts[0].target.id]=parseSheet(muts[0].target);
  161. injUpd();
  162. }
  163. });
  164. obs.observe(n, {attributes: false, subtree: false, childList: true });
  165. // monitor modification text node
  166. const obs2=new MutationObserver(function(muts){
  167. if (muts[0].type == 'characterData') {
  168. // === reparse stylesheet and reinject it
  169. let e=muts[0].target.parentNode;
  170. sCSS[e.id]=parseSheet(e);
  171. injUpd();
  172. }
  173. });
  174. obs2.observe(n, {characterData: true, attributes: false, childList: false, subtree: true});
  175. }
  176.  
  177. sCSS[n.id]=parseSheet(n);
  178. getStylusOrder();
  179. injUpd();
  180. }
  181.  
  182. // handle stylesheet removal
  183. function remCSS(n) {
  184. // is it only moved?
  185. if (n.parentNode == null) delete sheets[n.id];
  186. getStylusOrder();
  187. injUpd();
  188. }
  189.  
  190. // monitor shadow modification
  191. function shadowMod(e) {
  192. const obs=new MutationObserver(function(muts){
  193. if (e._inj_ && (!e._inj_.parentNode || e._inj_.nextElementSibling) ) e.appendChild(e._inj_);
  194. });
  195. obs.observe(e, {attributes: false, subtree: false, childList: true });
  196. }
  197.  
  198. })();