-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathinject-css-in-shadow-dom.js
136 lines (125 loc) · 4.66 KB
/
inject-css-in-shadow-dom.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import { hit, logMessage, hijackAttachShadow } from '../helpers';
/* eslint-disable max-len */
/**
* @scriptlet inject-css-in-shadow-dom
*
* @description
* Injects CSS rule into selected Shadow DOM subtrees on a page
*
* ### Syntax
*
* ```text
* example.org#%#//scriptlet('inject-css-in-shadow-dom', cssRule[, hostSelector])
* ```
*
* - `cssRule` — required, string representing a single css rule
* - `hostSelector` — optional, string, selector to match shadow host elements.
* CSS rule will be only applied to shadow roots inside these elements.
* Defaults to injecting css rule into all available roots.
* - `cssInjectionMethod` — optional, string, method to inject css rule into shadow dom.
* Available methods are:
* - `adoptedStyleSheets` — injects the CSS rule using adopted style sheets (default option).
* - `styleTag` — injects the CSS rule using a `style` tag.
*
* ### Examples
*
* 1. Apply style to all shadow dom subtrees
*
* ```adblock
* example.org#%#//scriptlet('inject-css-in-shadow-dom', '#advertisement { display: none !important; }')
* ```
*
* 1. Apply style to a specific shadow dom subtree
*
* ```adblock
* example.org#%#//scriptlet('inject-css-in-shadow-dom', '#content { margin-top: 0 !important; }', '#banner')
* ```
*
* 1. Apply style to all shadow dom subtrees using style tag
*
* ```adblock
* example.org#%#//scriptlet('inject-css-in-shadow-dom', '.ads { display: none !important; }', '', 'styleTag')
* ```
*
* @added v1.8.2.
*/
/* eslint-enable max-len */
export function injectCssInShadowDom(
source,
cssRule,
hostSelector = '',
cssInjectionMethod = 'adoptedStyleSheets',
) {
// do nothing if browser does not support ShadowRoot, Proxy or Reflect
// https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot
if (!Element.prototype.attachShadow || typeof Proxy === 'undefined' || typeof Reflect === 'undefined') {
return;
}
if (cssInjectionMethod !== 'adoptedStyleSheets' && cssInjectionMethod !== 'styleTag') {
logMessage(source, `Unknown cssInjectionMethod: ${cssInjectionMethod}`);
return;
}
// Prevent url() and image-set() styles from being applied
if (cssRule.match(/(url|image-set)\(.*\)/i)) {
logMessage(source, '"url()" function is not allowed for css rules');
return;
}
const injectStyleTag = (shadowRoot) => {
try {
const styleTag = document.createElement('style');
styleTag.innerText = cssRule;
shadowRoot.appendChild(styleTag);
hit(source);
} catch (error) {
logMessage(source, `Unable to inject style tag due to: \n'${error.message}'`);
}
};
/**
* Injects CSS rules into a shadow root using the adoptedStyleSheets API
*
* @param {ShadowRoot} shadowRoot - The shadow root to inject styles into
* @private
*
* @description
* This function attempts to inject CSS using adoptedStyleSheets API.
* If successful, it adds the stylesheet to the shadow root's adoptedStyleSheets array.
* On failure, it falls back to using the injectStyleTag method.
*/
const injectAdoptedStyleSheets = (shadowRoot) => {
try {
// adoptedStyleSheets and CSSStyleSheet constructor are not supported by old browsers
// https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets
// https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet
const stylesheet = new CSSStyleSheet();
try {
stylesheet.insertRule(cssRule);
} catch (e) {
logMessage(source, `Unable to apply the rule '${cssRule}' due to: \n'${e.message}'`);
return;
}
shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets, stylesheet];
hit(source);
} catch (error) {
logMessage(source, `Unable to inject adopted style sheet due to: \n'${error.message}'`);
injectStyleTag(shadowRoot);
}
};
const callback = (shadowRoot) => {
if (cssInjectionMethod === 'adoptedStyleSheets') {
injectAdoptedStyleSheets(shadowRoot);
} else if (cssInjectionMethod === 'styleTag') {
injectStyleTag(shadowRoot);
}
};
hijackAttachShadow(window, hostSelector, callback);
}
export const injectCssInShadowDomNames = [
'inject-css-in-shadow-dom',
];
// eslint-disable-next-line prefer-destructuring
injectCssInShadowDom.primaryName = injectCssInShadowDomNames[0];
injectCssInShadowDom.injections = [
hit,
logMessage,
hijackAttachShadow,
];