Created
September 30, 2022 09:54
-
-
Save alekseykulikov/2da13fa95dc2dd892a074ff6345e74ed to your computer and use it in GitHub Desktop.
Bookmark version of [CT](https://github.com/csswizardry/ct) with some improvements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Credit: https://github.com/csswizardry/ct | |
* | |
* Main changes: | |
* - use as a bookmark (toggle on/off) | |
* - work with any CSP (no extrnal scripts/styles) | |
* - fix an issue with async scripts added using inline JS (impossible to detect with css) | |
* - improved styles detection with [rel='stylesheet'][href] | |
* | |
* Add JS bookmark -> javascript:(<code below>) | |
*/ | |
/* prettier-ignore */ | |
(function () { | |
let css = String.raw; | |
let ct = document.querySelector('style.ct'); | |
if (ct) return ct.remove(); | |
document.querySelectorAll('script').forEach(s => { | |
if (s.async && s.getAttribute('async') === null && s.getAttribute('defer') === null) { | |
s.setAttribute('async', ''); | |
} | |
}); | |
ct = document.createElement('style'); | |
ct.className = 'ct'; | |
ct.innerHTML = css` | |
/** | |
* It's slightly easier to remember topics than it is colours. Set up some | |
* custom properties for use later on. | |
*/ | |
head { | |
--ct-is-problematic: solid; | |
--ct-is-affected: dashed; | |
--ct-notify: #0bce6b; | |
--ct-warn: #ffa400; | |
--ct-error: #ff4e42; | |
} | |
/** | |
* Show the <head> and set up the items we might be interested in. | |
*/ | |
head, | |
head script, | |
head script:not([src])[async], | |
head script:not([src])[defer], | |
head style, | |
head [rel='stylesheet'][href], | |
head script ~ meta[http-equiv='content-security-policy'], | |
head > meta[charset]:not(:nth-child(-n + 5)) { | |
display: block; | |
} | |
head script, | |
head style, | |
head [rel='stylesheet'][href], | |
head title, | |
head script ~ meta[http-equiv='content-security-policy'], | |
head > meta[charset]:not(:nth-child(-n + 5)) { | |
margin: 2px; | |
padding: 2px; | |
border-width: 2px; | |
background-color: white; | |
color: #333; | |
} | |
head ::before, | |
head script, | |
head style { | |
font: 16px/1.5 monospace, monospace; | |
display: block; | |
} | |
head ::before { | |
font-weight: bold; | |
} | |
/** | |
* External Script and Style | |
*/ | |
head script[src], | |
head link[rel='stylesheet'] { | |
border-style: var(--ct-is-problematic); | |
border-color: var(--ct-warn); | |
} | |
head script[src]::before { | |
content: '[Blocking Script – ' attr(src) ']'; | |
} | |
head link[rel='stylesheet']::before { | |
content: '[Blocking Stylesheet – ' attr(href) ']'; | |
} | |
/** | |
* Inline Script and Style. | |
*/ | |
head style:not(:empty), | |
head script:not(:empty) { | |
max-height: 5em; | |
overflow: auto; | |
background-color: #ffd; | |
white-space: pre; | |
border-color: var(--ct-notify); | |
border-style: var(--ct-is-problematic); | |
} | |
head script:not(:empty)::before { | |
content: '[Inline Script] '; | |
} | |
head style:not(:empty)::before { | |
content: '[Inline Style] '; | |
} | |
/** | |
* Blocked Title. | |
* | |
* These selectors are generally more complex because the Key Selector ('title') | |
* depends on the specific conditions of preceding JS--we can't cast a wide net | |
* and narrow it down later as we can when targeting elements directly. | |
*/ | |
head script[src]:not([async]):not([defer]):not([type='module']) ~ title, | |
head script:not(:empty) ~ title { | |
display: block; | |
border-style: var(--ct-is-affected); | |
border-color: var(--ct-error); | |
} | |
head script[src]:not([async]):not([defer]):not([type='module']) ~ title::before, | |
head script:not(:empty) ~ title::before { | |
content: '[<title> blocked by JS] '; | |
} | |
/** | |
* Blocked Scripts. | |
* | |
* These selectors are generally more complex because the Key Selector | |
* ('script') depends on the specific conditions of preceding CSS--we can't cast | |
* a wide net and narrow it down later as we can when targeting elements | |
* directly. | |
*/ | |
head [rel='stylesheet']:not([media='print']):not(.ct) ~ script, | |
head style:not(:empty) ~ script { | |
border-style: var(--ct-is-affected); | |
border-color: var(--ct-warn); | |
} | |
head [rel='stylesheet']:not([media='print']):not(.ct) ~ script::before, | |
head style:not(:empty) ~ script::before { | |
content: '[JS blocked by CSS – ' attr(src) ']'; | |
} | |
/** | |
* Using both 'async' and 'defer' is redundant (an anti-pattern, even). Let's | |
* flag that. | |
*/ | |
head script[src][src][async][defer] { | |
display: block; | |
border-style: var(--ct-is-problematic); | |
border-color: var(--ct-warn); | |
} | |
head script[src][src][async][defer]::before { | |
content: '[async and defer is redundant: prefer defer – ' attr(src) ']'; | |
} | |
/** | |
* Async and defer simply do not work on inline scripts. It won't do any harm, | |
* but it's useful to know about. | |
*/ | |
head script:not([src])[async], | |
head script:not([src])[defer] { | |
border-style: var(--ct-is-problematic); | |
border-color: var(--ct-warn); | |
} | |
head script:not([src])[async]::before { | |
content: 'The async attribute is redundant on inline scripts'; | |
} | |
head script:not([src])[defer]::before { | |
content: 'The defer attribute is redundant on inline scripts'; | |
} | |
/** | |
* Third Party blocking resources. | |
* | |
* Expect false-positives here… it's a crude proxy at best. | |
* | |
* Selector-chaining (e.g. '[src][src]') is used to bump up specificity. | |
*/ | |
head script[src][src][src^="//"], | |
head script[src][src][src^="http"], | |
head [rel="stylesheet"][href^="//"], | |
head [rel="stylesheet"][href^="http"] { | |
border-style: var(--ct-is-problematic); | |
border-color: var(--ct-error); | |
} | |
head script[src][src][src^="//"]::before, | |
head script[src][src][src^="http"]::before { | |
content: '[Third Party Blocking Script – ' attr(src) ']'; | |
} | |
head [rel="stylesheet"][href^="//"]::before, | |
head [rel="stylesheet"][href^="http"]::before { | |
content: '[Third Party Blocking Stylesheet – ' attr(href) ']'; | |
} | |
/** | |
* Mid-HEAD CSP disables the Preload Scanner | |
*/ | |
head script ~ meta[http-equiv='content-security-policy'] { | |
border-style: var(--ct-is-problematic); | |
border-color: var(--ct-error); | |
} | |
head script ~ meta[http-equiv='content-security-policy']::before { | |
content: '[Meta CSP defined after JS]'; | |
} | |
/** | |
* Charset should appear as early as possible | |
*/ | |
head > meta[charset]:not(:nth-child(-n + 5)) { | |
border-style: var(--ct-is-problematic); | |
border-color: var(--ct-warn); | |
} | |
head > meta[charset]:not(:nth-child(-n + 5))::before { | |
content: '[Charset should appear as early as possible]'; | |
} | |
/** | |
* Hide all irrelevant or non-matching scripts and styles (including ct.css). | |
* | |
* We're done! | |
*/ | |
link[rel='stylesheet'][media='print'], | |
link[rel='stylesheet'].ct, | |
style.ct, | |
script[async], | |
script[defer], | |
script[type='module'] { | |
display: none; | |
} | |
`; | |
document.head.appendChild(ct); | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment