Chrome Extension: Removing all styles using Javascript - javascript

I'm admittedly a bit new to this; I know HTML/CSS more then I know Javascript. I know enough to code an extension, but I'm having problems that I believe may be specific to Chrome. I don't want to be too specific, but as part of my extension I'd like to remove styles from all webpages.
Here's what I have:
manifest.json
{
"manifest_version": 2,
"name": "[redacted]",
"description": "[redacted]",
"version": "1.0",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["script.js"],
"run_at": "document_start"
}
]
}
script.js
function lolxd() {
var hs = document.getElementsByTagName('style');
for (var i=0, max = hs.length; i < max; i++) {
hs[i].parentNode.removeChild(hs[i]);
}
window.onload = lolxd();
I've tried multiple different scripts, none of which have worked. Strangely the extension loads fine, but the Javascript doesn't work and I can still see styles on all webpages. Any help?

You can recursively iterate through all elements and remove the style attribute, remove all linked (external) stylesheets and embedded stylesheets.
BEFORE
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://www.w3schools.com/html/styles.css">
<style>
p {
font-size: 150%;
}
</style>
</head>
<body>
<h1>This is a heading</h1>
<p style="font-weight: bold;">This is a paragraph.</p>
</body>
</html>
AFTER
function removeStyles(el) {
// code to remove inline styles
el.removeAttribute('style');
if (el.childNodes.length > 0) {
for (var child in el.childNodes) {
/* filter element nodes only */
if (el.childNodes[child].nodeType == 1)
removeStyles(el.childNodes[child]);
}
}
// code to remove embedded style sheets
var styletag = document.getElementsByTagName('style');
var i = 0;
for (; i < styletag.length; i++) {
styletag[i].remove();
}
// code to remove external stylesheets
var stylesheets = document.getElementsByTagName('link'),
sheet;
for (i = 0; i < stylesheets.length; i++) {
sheet = stylesheets[i];
if (sheet.getAttribute('rel').toLowerCase() == 'stylesheet')
sheet.parentNode.removeChild(sheet);
}
}
removeStyles(document.body);
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://www.w3schools.com/html/styles.css">
<style>
p {
font-size: 150%;
}
</style>
</head>
<body>
<h1>This is a heading</h1>
<p style="font-weight: bold;">This is a paragraph.</p>
</body>
</html>

A simple answer that removes most styles is pretty basic.
document.body.parentElement.innerHTML = document.body.innerHTML;
This works by kinda placing all HTML inside the head element, so I'll give props to any screen reader that can do any decent job on this new webpage. However, there are two main problems with this method.
As mentioned before, accessibility will be ruined.
The style attribute will still work.
So, To solve both these problems, we might me able to do something like this:
document.querySelectorAll("*").forEach(elem => { //this gets ALL the elements on the page.
elem.removeAttribute('style') // fix problem 2
if (elem.nodeName.toLowerCase() === "style") { // if element is a style element, remove it.
elem.remove()
return;
};
if (elem.nodeName.toLowerCase() === "link" && elem.hasAttribute("href") && elem.hasAttribute("rel")) { // Check if the element is a link, has href and rel attributes
if (elem.getAttribute('rel') === "stylesheet") {
elem.remove();
return;
};
};
});
This, for most practical applications is pretty good, of course, if there were children in the style and/or link elements, it would also remove them, so there may be a way to retain those, and a few other minor things. If your target site is large, and thus has many elements, this could drastically add to the loading time, because it has to loop over every element, and do some (admittedly quite quick) operations to them So, I devised a new solution using the Intersection Observer API. This only executes on elements that are visible to the user, so it should increase performance.
const observer = new IntersectionObserver((entries)=>{
entries.forEach(entry => {
entry.target.removeAttribute('style')
});
}, {
rootMargin:"1%",
threshold:0.01
});
document.querySelectorAll("*").forEach(elem=>{
if (elem.nodeName.toLowerCase() !== "style" && elem.nodeName.toLowerCase !== "link") {
observer.observe(elem);
} else {
if (elem.nodeName.toLowerCase() === "style") {
elem.remove();
return;
};
if (elem.nodeName.toLowerCase() === "link" && elem.hasAttribute("href") && elem.hasAttribute("rel")) {
if (elem.getAttribute("rel") === "stylesheet") {
elem.remove();
return;
};
};
};
});
Note: We do not need the root to be supplied, because by default, it is the browser viewport, which is what we want. We only call this function when 1% of the element is visible. We still need to have the style and link elements checked, because they are never shown, so this is not a major performance improvement, but it is there, you do need quite a bit of elements for it to pay off though, otherwise it would be slower.
One interesting thing I noticed, when I tried it on Google, was that the Search mangifer icon is huge, as well as the X, also you can see all of the 'I'm feeling lucky' options. When you hover over your icon, which I took 5 minutes to find, was that it flashes the tooltip, so possibly google is creating elements here, but most likely is that they are setting "style=display:block;" but then the code realizes that it is visible, removing the style attribute. Also tried it on StackOverflow and it does some crazy stuff, absolutely nothing, but the slow version works? I have no idea why, but that's interesting. This could be used for nefarious means, because you could use it to see data that you weren't supposed to, though I think that DevTools and the View Source feature do that better. However with this you don't have to deal with hundreds of lines of HTML,CSS, and JavaScript, its just right there. This does not interfere with the functionality of the page, just makes most look horrible, but I guess that sometimes it could make some look better. Please someone fix the version that uses the Intersection Observer API, because on some sites it works fine, but others, like StackOverflow, not so much.
I also think that this is the best approach because, while all the others (as of current) don't take into account that the link element is not entirely for stylesheets, but that is of course the main use of it. It can be used to render icons, including the favicon. The docs of the link element is here. So, if you did want to remove some icons and possibly the favicon, then just remove the filter for rel=stylesheet and if it has an href.

like this:
function removeCSS() {
// remove `link`
var links = document.getElementsByTagName('link');
for(var i = 0, len = links.length; i < len; i++) {
links[i].parentNode.removeChild(links[i]);
}
// remove `style`
var styles = document.getElementsByTagName('style');
for(var j = 0, len = styles.length; j < len; j++) {
styles[j].parentNode.removeChild(styles[j]);
}
// remove inline style
var nodes = document.querySelectorAll('[style]');
for(var h = 0, len = nodes.length; h < len; h++) {
nodes[h].removeAttribute('style');
}
}

Related

Extract Content from a Pseudo Element [duplicate]

I need to get :after and assign it to variable. It is possible?
querySelectorAll doesn't work.
alert(some_div_with_pseudo.querySelectorAll('::after')[0]) // undefined
The short answer is that you can’t. It’s not there yet.
JavaScript has access to the DOM, which is built when the page is loaded from HTML, and modified further when JavaScript manipulates it.
A pseudo element is generated by CSS, rather than HTML or JavaScript. It is there purely to give CSS something to hang on to, but it all happens without JavaScript having any idea.
This is how it should be. In the overall scheme of things, the pages starts off as HTML. JavaScript can be used to modify its behaviour and to manipulate the content on one hand, and CSS can be used to control the presentation of the result:
HTML [→ JavaScript] → CSS → Result
You’ll see that CSS, complete with pseudo elements, comes at the end, so JavaScript doesn’t get a look in.
See also:
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#Usage_notes
https://www.w3.org/TR/selectors-api/#grammar
Edit
It seems that in modern JavaScript there is a workaround using window.getComputedStyle(element,pseudoElement):
var element = document.querySelector(' … ');
var styles = window.getComputedStyle(element,':after')
var content = styles['content'];
You can do this:
window.getComputedStyle(
document.querySelector('somedivId'), ':after'
);
Sample here: https://jsfiddle.net/cfwmqbvn/
I use an arrow pointing in the direction that the content and sidebar will toggle to/from via a CSS pseudo-element. The code below is effectively a write mode however it is entirely possible to read CSS pseudo-element content as well.
Since there is a bit involved I'll also post the prerequisites (source: JAB Creations web platform JavaScript documentation, if anything missing look it up there) so those who wish to try it out can fairly quickly do so.
CSS
#menu a[href*='sidebar']::after {content: '\2192' !important;}
JavaScript Use
css_rule_set('#menu a[href*="sidebar"]::after','content','"\u2192"','important');
JavaScript Prerequisites
var sidebar = 20;
function id_(id)
{
return (document.getElementById(id)) ? document.getElementById(id) : false;
}
function css_rule_set(selector,property,value,important)
{
try
{
for (var i = 0; i<document.styleSheets.length; i++)
{
var ss = document.styleSheets[i];
var r = ss.cssRules ? ss.cssRules : ss.rules;
for (var j = 0; j<r.length; j++)
{
if (r[j].selectorText && r[j].selectorText==selector)
{
if (typeof important=='undefined') {r[j].style.setProperty(property,value);}
else {r[j].style.setProperty(property,value,'important');}
break;
}
}
}
}
catch(e) {if (e.name !== 'SecurityError') {console.log('Developer: '+e);}}
}
function sidebar_toggle()
{
if (id_('menu_mobile')) {id_('menu_mobile').checked = false;}
if (getComputedStyle(id_('side')).getPropertyValue('display') == 'none')
{
css_rule_set('#menu a[href*="sidebar"]::after','content','"\u2192"','important');
if (is_mobile())
{
css_rule_set('main','display','none','important');
css_rule_set('#side','width','100%','important');
css_rule_set('#side','display','block','important');
}
else
{
css_rule_set('main','width',(100 - sidebar)+'%');
css_rule_set('#side','display','block');
}
}
else
{
css_rule_set('#menu a[href*="sidebar"]::after','content','"\u2190"','important');
if (is_mobile())
{
css_rule_set('main','display','block','important');
css_rule_set('main','width','100%','important');
css_rule_set('#side','display','none','important');
}
else
{
css_rule_set('main','width','100%','important');
css_rule_set('#side','display','none');
}
}
There is a way in JavaScript to access value of pseudo elements without any library. To get the value, you need to use the 'getComputedStyle' function. The second parameter is optional.
let elem = window.getComputedStyle(parent, ':before');
alert(elem.getPropertyValue('background'))
This will do alert the value of pseudo element.
let elem = window.getComputedStyle(document.querySelector('#item'), ':after');
console.log(elem.getPropertyValue('content'))

How to access : :before element [duplicate]

I need to get :after and assign it to variable. It is possible?
querySelectorAll doesn't work.
alert(some_div_with_pseudo.querySelectorAll('::after')[0]) // undefined
The short answer is that you can’t. It’s not there yet.
JavaScript has access to the DOM, which is built when the page is loaded from HTML, and modified further when JavaScript manipulates it.
A pseudo element is generated by CSS, rather than HTML or JavaScript. It is there purely to give CSS something to hang on to, but it all happens without JavaScript having any idea.
This is how it should be. In the overall scheme of things, the pages starts off as HTML. JavaScript can be used to modify its behaviour and to manipulate the content on one hand, and CSS can be used to control the presentation of the result:
HTML [→ JavaScript] → CSS → Result
You’ll see that CSS, complete with pseudo elements, comes at the end, so JavaScript doesn’t get a look in.
See also:
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#Usage_notes
https://www.w3.org/TR/selectors-api/#grammar
Edit
It seems that in modern JavaScript there is a workaround using window.getComputedStyle(element,pseudoElement):
var element = document.querySelector(' … ');
var styles = window.getComputedStyle(element,':after')
var content = styles['content'];
You can do this:
window.getComputedStyle(
document.querySelector('somedivId'), ':after'
);
Sample here: https://jsfiddle.net/cfwmqbvn/
I use an arrow pointing in the direction that the content and sidebar will toggle to/from via a CSS pseudo-element. The code below is effectively a write mode however it is entirely possible to read CSS pseudo-element content as well.
Since there is a bit involved I'll also post the prerequisites (source: JAB Creations web platform JavaScript documentation, if anything missing look it up there) so those who wish to try it out can fairly quickly do so.
CSS
#menu a[href*='sidebar']::after {content: '\2192' !important;}
JavaScript Use
css_rule_set('#menu a[href*="sidebar"]::after','content','"\u2192"','important');
JavaScript Prerequisites
var sidebar = 20;
function id_(id)
{
return (document.getElementById(id)) ? document.getElementById(id) : false;
}
function css_rule_set(selector,property,value,important)
{
try
{
for (var i = 0; i<document.styleSheets.length; i++)
{
var ss = document.styleSheets[i];
var r = ss.cssRules ? ss.cssRules : ss.rules;
for (var j = 0; j<r.length; j++)
{
if (r[j].selectorText && r[j].selectorText==selector)
{
if (typeof important=='undefined') {r[j].style.setProperty(property,value);}
else {r[j].style.setProperty(property,value,'important');}
break;
}
}
}
}
catch(e) {if (e.name !== 'SecurityError') {console.log('Developer: '+e);}}
}
function sidebar_toggle()
{
if (id_('menu_mobile')) {id_('menu_mobile').checked = false;}
if (getComputedStyle(id_('side')).getPropertyValue('display') == 'none')
{
css_rule_set('#menu a[href*="sidebar"]::after','content','"\u2192"','important');
if (is_mobile())
{
css_rule_set('main','display','none','important');
css_rule_set('#side','width','100%','important');
css_rule_set('#side','display','block','important');
}
else
{
css_rule_set('main','width',(100 - sidebar)+'%');
css_rule_set('#side','display','block');
}
}
else
{
css_rule_set('#menu a[href*="sidebar"]::after','content','"\u2190"','important');
if (is_mobile())
{
css_rule_set('main','display','block','important');
css_rule_set('main','width','100%','important');
css_rule_set('#side','display','none','important');
}
else
{
css_rule_set('main','width','100%','important');
css_rule_set('#side','display','none');
}
}
There is a way in JavaScript to access value of pseudo elements without any library. To get the value, you need to use the 'getComputedStyle' function. The second parameter is optional.
let elem = window.getComputedStyle(parent, ':before');
alert(elem.getPropertyValue('background'))
This will do alert the value of pseudo element.
let elem = window.getComputedStyle(document.querySelector('#item'), ':after');
console.log(elem.getPropertyValue('content'))

Javascript accessing a classes :after condition [duplicate]

I need to get :after and assign it to variable. It is possible?
querySelectorAll doesn't work.
alert(some_div_with_pseudo.querySelectorAll('::after')[0]) // undefined
The short answer is that you can’t. It’s not there yet.
JavaScript has access to the DOM, which is built when the page is loaded from HTML, and modified further when JavaScript manipulates it.
A pseudo element is generated by CSS, rather than HTML or JavaScript. It is there purely to give CSS something to hang on to, but it all happens without JavaScript having any idea.
This is how it should be. In the overall scheme of things, the pages starts off as HTML. JavaScript can be used to modify its behaviour and to manipulate the content on one hand, and CSS can be used to control the presentation of the result:
HTML [→ JavaScript] → CSS → Result
You’ll see that CSS, complete with pseudo elements, comes at the end, so JavaScript doesn’t get a look in.
See also:
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#Usage_notes
https://www.w3.org/TR/selectors-api/#grammar
Edit
It seems that in modern JavaScript there is a workaround using window.getComputedStyle(element,pseudoElement):
var element = document.querySelector(' … ');
var styles = window.getComputedStyle(element,':after')
var content = styles['content'];
You can do this:
window.getComputedStyle(
document.querySelector('somedivId'), ':after'
);
Sample here: https://jsfiddle.net/cfwmqbvn/
I use an arrow pointing in the direction that the content and sidebar will toggle to/from via a CSS pseudo-element. The code below is effectively a write mode however it is entirely possible to read CSS pseudo-element content as well.
Since there is a bit involved I'll also post the prerequisites (source: JAB Creations web platform JavaScript documentation, if anything missing look it up there) so those who wish to try it out can fairly quickly do so.
CSS
#menu a[href*='sidebar']::after {content: '\2192' !important;}
JavaScript Use
css_rule_set('#menu a[href*="sidebar"]::after','content','"\u2192"','important');
JavaScript Prerequisites
var sidebar = 20;
function id_(id)
{
return (document.getElementById(id)) ? document.getElementById(id) : false;
}
function css_rule_set(selector,property,value,important)
{
try
{
for (var i = 0; i<document.styleSheets.length; i++)
{
var ss = document.styleSheets[i];
var r = ss.cssRules ? ss.cssRules : ss.rules;
for (var j = 0; j<r.length; j++)
{
if (r[j].selectorText && r[j].selectorText==selector)
{
if (typeof important=='undefined') {r[j].style.setProperty(property,value);}
else {r[j].style.setProperty(property,value,'important');}
break;
}
}
}
}
catch(e) {if (e.name !== 'SecurityError') {console.log('Developer: '+e);}}
}
function sidebar_toggle()
{
if (id_('menu_mobile')) {id_('menu_mobile').checked = false;}
if (getComputedStyle(id_('side')).getPropertyValue('display') == 'none')
{
css_rule_set('#menu a[href*="sidebar"]::after','content','"\u2192"','important');
if (is_mobile())
{
css_rule_set('main','display','none','important');
css_rule_set('#side','width','100%','important');
css_rule_set('#side','display','block','important');
}
else
{
css_rule_set('main','width',(100 - sidebar)+'%');
css_rule_set('#side','display','block');
}
}
else
{
css_rule_set('#menu a[href*="sidebar"]::after','content','"\u2190"','important');
if (is_mobile())
{
css_rule_set('main','display','block','important');
css_rule_set('main','width','100%','important');
css_rule_set('#side','display','none','important');
}
else
{
css_rule_set('main','width','100%','important');
css_rule_set('#side','display','none');
}
}
There is a way in JavaScript to access value of pseudo elements without any library. To get the value, you need to use the 'getComputedStyle' function. The second parameter is optional.
let elem = window.getComputedStyle(parent, ':before');
alert(elem.getPropertyValue('background'))
This will do alert the value of pseudo element.
let elem = window.getComputedStyle(document.querySelector('#item'), ':after');
console.log(elem.getPropertyValue('content'))

Clone, Play and Replace the Cloned Dom - Javascript / Jquery

I'm trying to export the whole page as PDF. During certain situation's like, if the CSS is loaded from separate file is not applied in exported PDF. So I'm trying to convert all CSS as inline using this code.
(function ($) {
var rules = document.styleSheets;
for(var rl in rules){
var rule = rules[rl].cssRules;
try{
for (var idx = 0, len = rule.length; idx < len; idx++) {
$(rule[idx].selectorText).each(function (i, elem) {
if($(elem).is(":visible"))
elem.style.cssText += rule[idx].style.cssText;
});
}
}catch(e){
console.log(e);
}
}
})(jQuery);
After I ran this code, my exported PDF is working good. But my DOM is not as before. So is there anyway where I can clone my DOM before operations, and replace the cloned DOM as before after playing with DOM. Hope my question is clear. Thanks in anticipation for the help.
In this Snippet there are 2 much more simpler ways than modifying a stylesheet:
Isolate the <iframe>,<embed>, or <object> by wrapping an element around it then apply styles referencing the wrapper element. This is demonstrated in the Snippet with div.jframe as the wrapper.
Inject a <style> block with new rulesets.
If either one is done with moderate care, you shouldn't be left with conflicting styles.
Note: The PDF in the iframe is sandboxed, so it's not there but everything still applies.
SNIPPET
function injectStyles(rule) {
document.body.insertAdjacentHTML('afterbegin',
'­<style>' + rule + '</style>');
}
injectStyles('iframe:hover { border: 5px solid blue; }');
.jframe iframe {
outline: 10px solid tomato;
}
<div class='jframe'>
<iframe src='http://che.org.il/wp-content/uploads/2016/12/pdf-sample.pdf' height='400' width='400'></iframe>
</div>

Defining a for loop in Javascript

I can't seem to define a for loop function, what am I doing wrong?
My HTML code:
<body onload="generate()">
My Javascript code:
function generate(){
for(i = 0; i < 150; i++) {
document.write("<div></div>");
}
};
Your loop is fine (other than that you don't declare i, and so you fall prey to the Horror of Implicit Globals), it's document.write that's the problem. You can only use document.write in an inline script, not after the page has been loaded (e.g., not in the body load event). If you use document.write after the page is loaded, it tears down the page and replaces it with what you output (because there's an implicit document.open call). So in your case, your page disappears and 150 blank divs are there instead.
To manipulate the page after load, you'll want to use the DOM, references:
DOM2 Core - Widely supported by browsers
DOM HTML bindings - Widely suppored by browsers
DOM3 Core - Fairly well supported, some gaps
For instance, here's how you'd write your generate function to append 150 blank divs to the page:
function generate() {
var i;
for (i = 0; i < 150; i++){
document.body.appendChild(document.createElement('div'));
}
}
Or more usefully, 150 divs with their numbers in:
function generate() {
var i, div;
for (i = 0; i < 150; i++){
div = document.createElement('div');
div.innerHTML = "I'm div #" + i;
document.body.appendChild(div);
}
}
Live copy
Separately, if you're going to do any significant DOM manipulation, it's well worth using a good JavaScript browser library like jQuery, Prototype, YUI, Closure, or any of several others. These smooth over browser differences (and outright bugs), provide useful utility functions, and generally let you concentrate on what you're actually trying to do rather than fiddling about with inconsistencies between IE and Chrome, Opera and Safari...
The document.write mentioned in https://stackoverflow.com/q/8257414/295783 is the reason it does not work. The first document.write wipes the page including the script that is executing.
A better way is
<html>
<head>
<script type="text/javascript">
var max = 150;
window.onload=function() {
var div, container = document.createElement('div');
for (var i=0;i<max;i++) {
div = document.createElement('div');
container.appendChild(div);
}
document.body.appendChind(container);
}
</script>
</head>
<body>
</body>
</html>
window.addEventListener("DOMContentLoaded", function() {
for(var i = 0; i < 150; i++) {
document.body.appendChild( document.createElement("div") );
}
}, false);
This should work, didn't test it though.
It's important to wait for the 'DOMContenLoaded' event, because else some elements might not exist at the time your script was executed.
do the folowing;
function generate(){
var echo="";
for(i = 0; i < 150; i++){
echo+="<div></div>";
}
document.getElementById("someID").innerHTML=echo;
//document.write(echo); //only do this, if you want to replace the ENTIRE DOM structure
};

Categories