3. The Trouble With CSS

TL;DR: Content Security Policy (CSP) takes the fun out of CSS and inline style attributes. But do not fear! There is a remedy.

When developing a browser extension following the WebExtensions API, adding HTML elements can easily cause CSS pollution. Conflicting CSS rules either put the host page or the extension elements out of whack.

A simple solution is to use explicit style attributes on custom extension elements.

A better solution is to use the ShadowRoot.

The WebExtensions API provides for injecting CSS into web pages by various means, e.g.

However, none of them can affect or target ShadowRoot.

So, the method of choice used to be the simple JavaScript approach of style elements with extension URLs or verbatim text, e.g.

  • <link rel=”stylesheet” href=”URL”>,
  • <style src=”URL>”,
  • <style>CSS rules</style>.

Neither these methods, nor explicit style attributes work with CSP style-src ‘self’ anymore.

The remedy is to use constructable stylesheets (Web APIs) (see also Constructable Stylesheets (web.dev)).

3.1. ShadowRoot

The element tree in the shadow root is independent from the element tree in the main document. (See Using shadow DOM).

The shadow root can be created programatically, e.g.


listing 3.1 Create shadow root
const myStyleSheetText = "#kspan { color: blue; }";

const myDiv = document.createElement("div");
myDiv.setAttribute("id", "koeniglich");
document.querySelector("body").appendChild(myDiv);

const myShadow = myDiv.attachShadow({ mode: "open" });

let mySpan = document.createElement("span");
mySpan.setAttribute("id", "kspan");
mySpan.textContent = "I'm in the shadow DOM";

myShadow.appendChild(mySpan);

or with the template HTML element (see <template>: The Content Template element)

<div id="koeniglich">
  <template shadowrootmode="open">
    <span id="kspan">I'm in the shadow DOM</span>
  </template>
</div>

Shadow elements are not accessible from the main document, so

console.log("document #kspan:", document.querySelector("#kspan") ? document.querySelector("#kspan").textContent : document.querySelector("#kspan"));

returns null, while

console.log("myShadow #kspan:", myShadow.querySelector("#kspan") ? myShadow.querySelector("#kspan").textContent : myShadow.querySelector("#kspan"));

returns the shadow element

myShadow #kspan: I'm in the shadow DOM

After adding an element with the same ID in the regular DOM,

const myOuterDiv = document.createElement("div");
myOuterDiv.setAttribute("id", "visible");
document.querySelector("body").appendChild(myOuterDiv);
mySpan = mySpan.cloneNode();
mySpan.textContent = "So this is outside the shadow";
myOuterDiv.appendChild(mySpan);

the query for the ID kspan,

console.log("document #kspan:", document.querySelector("#kspan") ? document.querySelector("#kspan").textContent : document.querySelector("#kspan"));

delivers the outer element:

document #kspan: So this is outside the shadow

3.2. Violation of Content Security Policy “style-src ‘self’ …”

Test Websites:

Site Problem
https://www.pornhub.com/ assigns values to textarea, e.g. width
https://developer.mozilla.org/en-US/docs/Web/CSS/flex messes with the CSS
https://pypi.org Content Security Policy “style-src ‘self’”

Some Sources:

A CSS reference with an alphabetical index of all standard CSS properties is available at CSS reference - CSS | MDN

The Solution is to use CSSOM, see Konstruierbare Stylesheets  | Articles  |  web.dev

Investigate Window: getComputedStyle() method - Web APIs | MDN in regard to automatic CSS rule generation from DOM elements.

3.2.1. CSP - The Problem

The standard method of constructing a style element,

const style = document.createElement("style");
style.textContent = myStyleSheetText;
myShadow.appendChild(style)

results in the error message

Content-Security-Policy: Die Einstellungen der Seite haben die
Anwendung eines Styles (style-src-elem) blockiert, da er gegen
folgende Direktive verstößt: "style-src 'self' ..."

Using a link element instead,

const link = document.createElement("link");
link.setAttribute("rel", "stylesheet")
link.setAttribute("href", "https://sw-amt.ws/not-there.css")
document.querySelector("body").append(link)

also results in the error message

Content-Security-Policy: Die Einstellungen der Seite haben die
Anwendung eines Styles (style-src-elem) blockiert, da er gegen
folgende Direktive verstößt: "style-src 'self' ..."

Since the browser now hides the relevant CSP header, use wget(1) to find the Content-Security-Policy:

wget -O - --save-headers 'https://pypi.org/project/slimit/'
Content-Security-Policy:
 base-uri 'self';
 connect-src 'self' https://api.github.com/repos/ https://api.github.com/search/issues https://gitlab.com/api/ https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com fastly-insights.com *.fastly-insights.com *.ethicalads.io https://api.pwnedpasswords.com https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/sre/mathmaps/ https://2p66nmmycsj3.statuspage.io;
 default-src 'none';
 font-src 'self' fonts.gstatic.com;
 form-action 'self' https://checkout.stripe.com;
 frame-ancestors 'none';
 frame-src 'none';
 img-src 'self' https://pypi-camo.freetls.fastly.net/ https://*.google-analytics.com https://*.googletagmanager.com *.fastly-insights.com *.ethicalads.io ethicalads.blob.core.windows.net;
 script-src 'self' https://*.googletagmanager.com https://www.google-analytics.com https://ssl.google-analytics.com *.fastly-insights.com *.ethicalads.io 'sha256-U3hKDidudIaxBDEzwGJApJgPEf2mWk6cfMWghrAa6i0=' https://cdn.jsdelivr.net/npm/mathjax@3.2.2/ 'sha256-1CldwzdEg2k1wTmf7s5RWVd7NMXI/7nxxjJM2C4DqII=' 'sha256-0POaN8stWYQxhzjKS+/eOfbbJ/u4YHO5ZagJvLpMypo=';
 style-src 'self' fonts.googleapis.com *.ethicalads.io 'sha256-2YHqZokjiizkHi1Zt+6ar0XJ0OeEy/egBnlm+MDMtrM=' 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' 'sha256-JLEjeN9e5dGsz5475WyRaoA4eQOdNPxDIeUhclnJDCE=' 'sha256-mQyxHEuwZJqpxCw3SLmc4YOySNKXunyu2Oiz1r3/wAE=' 'sha256-OCf+kv5Asiwp++8PIevKBYSgnNLNUZvxAp4a7wMLuKA=' 'sha256-h5LOiLhk6wiJrGsG5ItM0KimwzWQH/yAcmoJDJL//bY=';
 worker-src *.fastly-insights.com

3.2.2. Element Style - Solution 1

One way around the CSS limitations is to programmatically access the style attribute of a DOM node directly (source javascript - How to position a DIV at specific coordinates? - Stack Overflow):

element.style.position = "absolute"
element.style.top = "0"
element.style.left = "99px"

3.2.3. Constructable Stylesheets - Solution 2

The bests remedy for CSP violations regarding styles, however, is to use constructable stylesheets, e.g:

const myStyleSheet = new CSSStyleSheet();
myStyleSheet.replaceSync(myStyleSheetText);

The stylesheet is applied with

myShadow.adoptedStyleSheets.push(myStyleSheet);

Now the shadow text is blue, while the document text is still black.

The stylesheet can also be shared at other places, like

document.adoptedStyleSheets.push(myStyleSheet);

Now the outer text is also blue.

Using ths technique with firefox or chrome does not trigger a CSP violation.

3.3. Inner Workings of CSS

Source CSSStyleDeclaration - Web APIs.

The CSSStyleDeclaration interface represents an object that is a CSS declaration block, and exposes style information and various style-related methods and properties.

A CSSStyleDeclaration object can be exposed using three different APIs: