Skip to content

Commit

Permalink
add css injection via style tag and add testcase
Browse files Browse the repository at this point in the history
  • Loading branch information
stanislav-atr committed Jan 17, 2023
1 parent f1a6ff8 commit ed308a9
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 8 deletions.
33 changes: 25 additions & 8 deletions src/scriptlets/inject-css-in-shadow-dom.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
hit,
logMessage,
hijackAttachShadow,
} from '../helpers/index';

Expand All @@ -11,39 +12,54 @@ import {
*
* **Syntax**
* ```
* example.org#%#//scriptlet('inject-css-in-shadow-dom', cssRule)
* example.org#%#//scriptlet('inject-css-in-shadow-dom', cssText)
* ```
*
* - `cssRule` - required, string of comma-separated css rules
* - `cssText` - required, string of comma-separated css rules
*
* **Examples**
* ```
* ! apply single style
* example.org#%#//scriptlet('inject-css-in-shadow-dom', '#advertisement { display: none !important; }')
*
* ! apply multiple css rules
* example.org#%#//scriptlet('inject-css-in-shadow-dom', '#advertisement { display: none !important; }, #content { margin-top: 0 !important; }')
* example.org#%#//scriptlet('inject-css-in-shadow-dom', '#advertisement { display: none !important; }|#content { margin-top: 0 !important; }')
* ```
*/
/* eslint-enable max-len */

export function injectCssInShadowDom(source, cssRule) {
export function injectCssInShadowDom(source, cssText) {
// do nothing if browser does not support ShadowRoot
// https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot
if (!Element.prototype.attachShadow) {
return;
}

const parsedStyleRules = cssRule.split(',');
const parsedStyleRules = cssText.split('|');
const callback = (shadowRoot) => {
const stylesheet = new CSSStyleSheet();

// fill stylesheet with rules
parsedStyleRules.forEach((rule) => stylesheet.insertRule(rule));
parsedStyleRules.forEach((rule) => {
try {
stylesheet.insertRule(rule);
} catch {
logMessage(source, `Failed to parse the rule: ${rule}`);
}
});

// attach stylesheet to shadow root so the whole subtree would be affected
// https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets
shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets, stylesheet];
if (shadowRoot.adoptedStyleSheets) {
// adoptedStyleSheets is not yet supported by Safari
// https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets
shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets, stylesheet];
} else {
const styleTag = document.createElement('style');
styleTag.innerText = cssText;
shadowRoot.appendChild(styleTag);
}

hit(source);
};

hijackAttachShadow(window, callback);
Expand All @@ -55,5 +71,6 @@ injectCssInShadowDom.names = [

injectCssInShadowDom.injections = [
hit,
logMessage,
hijackAttachShadow,
];
1 change: 1 addition & 0 deletions tests/scriptlets/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ import './trusted-set-cookie.test';
import './trusted-replace-fetch-response.test';
import './trusted-set-local-storage-item.test';
import './trusted-set-constant.test';
import './inject-css-in-shadow-dom.test';
63 changes: 63 additions & 0 deletions tests/scriptlets/inject-css-in-shadow-dom.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* eslint-disable no-underscore-dangle */
import { runScriptlet, clearGlobalProps } from '../helpers';

const { test, module } = QUnit;
const name = 'inject-css-in-shadow-dom';

const HOST_ID = 'host';
const TARGET_ID = 'target';
const TARGET_CSS_PROP = 'color';
const TARGET_CSS_VALUE = 'rgb(255, 0, 0)';
const CSS_TEXT = `#target { ${TARGET_CSS_PROP}: ${TARGET_CSS_VALUE} !important}`;
// const CSS_TEXT = '#target { color: red !important}';

const appendTarget = (parent) => {
const target = document.createElement('h1');
target.id = TARGET_ID;
target.innerText = 'Target element';
return parent.appendChild(target);
};

const appendHost = () => {
const host = document.createElement('div');
host.id = HOST_ID;
return document.body.appendChild(host);
};

const removeHost = () => document.getElementById(HOST_ID)?.remove();

const beforeEach = () => {
window.__debug = () => {
window.hit = 'FIRED';
};
};

const afterEach = () => {
clearGlobalProps('hit', '__debug');
removeHost();
};

module(name, { beforeEach, afterEach });

// some browsers do not support ShadowRoot
// for example, Firefox 52 which is used for browserstack tests
// https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot
const isSupported = typeof Element.prototype.attachShadow !== 'undefined';

if (!isSupported) {
test('unsupported', (assert) => {
assert.ok(true, 'Browser does not support it');
});
} else {
test('styles applied to shadow root subtree with adoptedStyleSheets', (assert) => {
runScriptlet(name, [CSS_TEXT]);

const host = appendHost();
const shadowRoot = host.attachShadow({ mode: 'closed' });
appendTarget(shadowRoot);

const target = shadowRoot.getElementById(TARGET_ID);
assert.strictEqual(getComputedStyle(target)[TARGET_CSS_PROP], TARGET_CSS_VALUE, 'style was applied on target');
assert.strictEqual(window.hit, 'FIRED', 'hit function was executed');
});
}

0 comments on commit ed308a9

Please sign in to comment.