Light DOM style leaking into Shadow DOM - javascript

I am trying to create a Chrome extension with a floating widget. To do that, I have to isolate my element from the rest of the page. Shadow DOM looks like a perfect fit for that, but it isn't doing what I expected.
Here is my content script:
content.js
var lightDom = document.createElement('style');
lightDom.innerText = 'div { color: red }';
document.body.appendChild(lightDom);
var shadowDom = document.createElement('div');
document.body.appendChild(shadowDom);
var shadowRoot = shadowDom.attachShadow({'mode': 'open'});
shadowRoot.innerHTML = `
<style>
div {
background-color: blue;
}
</style>
<div>Shadow!</div>
`;
The div inside the shadow DOM should have black text, but it has red text instead. Am I doing something wrong?

Why is this happening?
CSS selectors in Light DOM are prevented from reaching elements inside shadow DOM. But when a CSS property has the value inherit, which is the default value of color, the shadow DOM will still inherit those from the light DOM.
From the CSS Scoping specification
3.3.2 Inheritance
The top-level elements of a shadow tree inherit from their host element.
The elements in a distribution list inherit from the parent of the content element they are ultimately distributed to, rather than from their normal parent.
How to prevent it from happening?
You can prevent the values of properties form being inherited by using the initial value.
From the CSS Cascading and Inheritance specification
7.3.1. Resetting a Property: the initial keyword
If the cascaded value is the initial keyword, the property’s initial value becomes its specified value.
The initial value of each property is documented in the specification it is defined in. color is documented in the CSS Color specification and its initial value is depends on user agent, for most user agents this will be black.
You can assign initial to each property you don't want to inherit its value. Or you can set the value of all to initial, like so:
.selector
{
all: initial;
}
The all property is defined as follows in the same spec as the initial keyword.
3.1. Resetting All Properties: the all property
The all property is a shorthand that resets all CSS properties except direction and unicode-bidi. It only accepts the CSS-wide keywords.
"CSS-wide keywords" is defined in CSS 3 values specification in section 3.1.1, but comes down to the values initial, inherit and unset.

Related

Protect element from global css

My js is embedded on a third party website and it creates an <iframe> which contains a simple comments panel , but apparently on this specific website there is a CSS stylesheet which styles every <iframe> tag in the dom with the !important flag , so i can't change those css rules and the website dev team won't change this behaviour, there is another way to overcome this? can i change the tagname and still be an iframe? anything?
You can use the all property with the initial value to default the styles for that element.
From the docs:
The all CSS shorthand property sets all of an element's properties
(other than unicode-bidi and direction) to their initial or inherited
values, or to the values specified in another stylesheet origin.
A code example:
#div-to-reset-styles {
all: initial;
* {
all: unset;
}
}
Just target your specific iframe and you should be fine.
CSS Specificity is your friend here. From this overview:
Specificity determines, which CSS rule is applied by the browsers.
Specificity is usually the reason why your CSS-rules don’t apply to some elements, although you think they should.
Every selector has its place in the specificity hierarchy.
If two selectors apply to the same element, the one with higher specificity wins.
There are four distinct categories which define the specificity level of a given selector: inline styles, IDs, classes, attributes, and elements.
...
The part in bold means that you can add a class to your element(s) in question and override the more generic iframe css definition like that.

How to get computed background color style inherited from parent element

I have this HTML page:
document.addEventListener("DOMContentLoaded", function (event) {
var inner_div = document.getElementById("innerDiv");
console.log(getComputedStyle(inner_div, null)["backgroundColor"]);
});
#outterDiv {
background-color: papayawhip;
}
<div id="outterDiv">
<div id="innerDiv">
I am an inner div
</div>
</div>
I want to find out the computed background color of the inner div without having to look into parent(s) element's computed style. Is that possible?
Solution
Here's some vanilla js that will get the effective background color for a given element:
function getInheritedBackgroundColor(el) {
// get default style for current browser
var defaultStyle = getDefaultBackground() // typically "rgba(0, 0, 0, 0)"
// get computed color for el
var backgroundColor = window.getComputedStyle(el).backgroundColor
// if we got a real value, return it
if (backgroundColor != defaultStyle) return backgroundColor
// if we've reached the top parent el without getting an explicit color, return default
if (!el.parentElement) return defaultStyle
// otherwise, recurse and try again on parent element
return getInheritedBackgroundColor(el.parentElement)
}
function getDefaultBackground() {
// have to add to the document in order to use getComputedStyle
var div = document.createElement("div")
document.head.appendChild(div)
var bg = window.getComputedStyle(div).backgroundColor
document.head.removeChild(div)
return bg
}
Then you can call it like this:
var myEl = document.getElementById("a")
var bgColor = getInheritedBackgroundColor(myEl)
Demo in jsFiddle
Explanation
The solution works by checking the resolved value for a particular element. If the background is transparent, it'll start over again with the element's parent until it finds a defined color or reaches the top.
There are two main concepts to be familiar with:
How background-color is set?
How resolved values work?
According to MDN, background-color has an initial value of transparent and is not an inherited property. Meaning even if a parent div has a color applied, any unstyled child div will have a background-color of transparent. Most browsers, will convert this to an rgb color space so will return the value of rgba(0,0,0,0) with the alpha channel / transparency set to 0%.
This distinction may seem trivial, but it's important to understand that most divs on a page are probably transparent. They do not paint any colors, they just don't mask parent elements. If background values were inherited, colors with transparency would stack and get stronger on child elements
According to MDN, resolved values are what is returned by the browser from .getComputedStyle() which includes computed values and used values. Using the resolved value helps handle special values like inherit, initial, etc. and coverts any relative values to absolute values. Plus also provides a consistent API for getting styling info from attributes vs those applied via stylesheets.
So window.getComputedStyle() isn't used to determine the color that appears in the background; it's just there to get the actual value for the div. And then we'll traverse up the DOM tree until we find an actual value.
Further Reading
MDN - Color Value
MDN - background-color
MDN - .getComputedStyle()
SO - How to get the background color of an HTML element?
SO - Getting the real background-color of an element?
SO - How do I detect the inherited background-color of an element using JS?
SO - getComputedStyle gives "transparent" instead of actual background color
SO - How do I get the element's background color in JavaScript?
You apparently mean to ask
How do I find the background color for some element which is used by virtue of that color being set as the background color of some ancestor which shows through because all intervening elements having transparent background color?
However, your question is confusing because you are using the word "inherited", which has a very specific meaning in CSS, which is not relevant here.
The background-color property is not inherited in the CSS sense. Every element has its own background color, which by default is transparent.
The reason that you inner div looks like it has a papayawhip background is that actually it has a transparent background, which lets the papayawhip background of the outer div show through. There is nothing about the inner div that knows or cares about papayawhip, or that can be queried to return papayawhip.
The only way to find that the inner div is going to have a papayawhip background is to traverse the DOM tree and find the closest parent that has a non-transparent background color. This is explained in the question proposed as a dup target.
By the way, what is your underlying problem here? Why are you trying to do this? There are probably better ways.
add background-color: inherit; to your #innerDiv
document.addEventListener("DOMContentLoaded", function (event) {
var inner_div = document.getElementById("innerDiv");
console.log(getComputedStyle(inner_div, null)["backgroundColor"]);
});
#outterDiv {
background-color: papayawhip;
}
#innerDiv {
background-color: inherit;
}
<div id="outterDiv">
<div id="innerDiv">
I am an inner div
</div>
</div>

How to determine the value in effect

Is there a JS way to determine the value in effect for an attribute of an element?
It is well understood that there is a precedence from referenced CSS files in order of declaration through embedded CSS and values defined in the style attribute of an element, with the most recent/most local taking effect.
Browser element inspection tools make it possible to determine the value in effect for an element styling attribute like (for example) background-color.
Is there a way to discover the value in effect programmatically?
I have used a canvas and js to creates what amounts to image maps with hover and click visual feedback, and I would love to make this respond to CSS styling on the canvas.
For example, if the canvas color is set to red then red should be the base color for hover highlights and selection marking.
At the moment this is all done by changing the values of "constants", but responding to CSS would be... elegant.
You can determine the current effective style by using window.getComputedStyle(). It returns an exotic array-like object that has all active style properties as well as some helper functions. To use:
var el = document.getElementById('el1');
var computedStyle = window.getComputedStyle(el);
document.getElementById('style').innerHTML = computedStyle.cssText;
#el1 {
background-color: blue;
}
div:first-of-type {
height: 50px;
}
<div id="el1"></div>
<hr>
<div id="style"></div>

How do I get the perceived styling of a text node?

window.getComputedStyle() method accepts only element nodes. Is there any way to reliably get the style that determines the visual representation of a text node?
I realize that nodes can't have style attributes, but they certainly are styled, since they inherit the parent element's styles. Is there, perhaps, a way to get all computed styles from the parent element that are relevant to the text node's visual representation?
Note that wrapping the node in a span is out of the question: this would affect CSS rules such as span:nth-child or span + span, etc.
If the text node is the only node in an element, you could simply use getComputedStyle() on its parentNode.
However, consider the following:
div {border: 1px solid black;}
<div>This <em>is</em> a <strong>test</strong></div>
You cannot say that "This" and "a" each have a border. Would it be accurate to say "This" has top-bottom-left borders, and "a" has top-bottom borders only? That's questionable.
If you limit yourself to styles that specifically apply to text (color, background, textDecoration, font, etc.), applying getComputedStyle() on parentNode should always work.
I'll give it a try myself.
Use window.getComputedStyle() on the parent element and store the tag name and the style information.
Create a new element with the stored tag name and assign the styles to it via style attribute.
Add a child <foo> element (strictly speaking, it should be of a tag name that is not mentioned in the current CSS rules, so that they don't affect it).
Attach the parent element to the <head> of the document (Webkit-specific).
Use window.getComputedStyle() on the child element.
Set inline as the value of display property (as text nodes are always inline).
Note the results of the code snippet. color is red, margin-left is zero, despite the parent having a left margin, and width (and height, too) is auto.
var source = document.querySelector('.bar');
var sourceStyle = window.getComputedStyle(source);
var sourceTag = source.tagName;
var clone = document.createElement(sourceTag);
var child = document.createElement('foo');
var head = document.querySelector('head');
child.innerHTML = 1;
child.setAttribute('style', 'display: inline;');
clone.appendChild(child);
clone.setAttribute('style', sourceStyle.cssText);
head.appendChild(clone);
alert(window.getComputedStyle(source).marginLeft); // 100px
alert(window.getComputedStyle(child).color); // rgb(255, 0, 0);
alert(window.getComputedStyle(child).marginLeft); // 0px
alert(window.getComputedStyle(child).width); // auto
.bar {
color: red;
margin-left: 100px
}
<html>
<head>
<title>An example</title>
</head>
<body>
<div class="bar">
foo
</div>
</body>
</html>
The answer to your question is, you can't. Styles are applied to the elements and are shaped by their content. The text content of an element is not styled directly because it is just data. The other answers and comments are only suggestions to obtain the styling of the element which isn't what you asked for. So what you are asking for can't be done because there is no styling applied to the data, the text node, of an element.

How to get min-height CSS property? [duplicate]

This question already has answers here:
How to get computed style of a HTMLElement
(2 answers)
Closed 9 years ago.
As far as I was aware CSS properties of the DOM element style are camel-cased, such that min-height would be element.style.minHeight, but in my example below the style property is empty, but jQuery's abstraction gets it correctly.
// element with css: #test{ min-height: 100px; }
var el = document.getElementById('test');
console.log( el.style.minHeight );
// ""
console.log( $(el).css('min-height') );
// "100px"
See fiddle for this in action.
What is jQuery doing to pull the correct style property that I'm not doing?
interestingly enough, adding a style attribute directly on the html element does work.
You can use window.getComputedStyle(el,null).getPropertyValue("min-height") and see if it returns the right value.
WARNING: It might not work on IE < 8.
From Mozilla's website...
CSS 2.0 defined only computed value as the last step in a property's calculation. Then, CSS 2.1 introduced the distinct definition of used value so that an element could explicitly inherit a width/height of a parent whose computed value is a percentage. For CSS properties that don't depend on layout (e.g. display, font-size, line-height), the computed values and used values are the same. These are the properties that do depend on layout so have a different computed value and used value: (taken from CSS 2.1 Changes: Specified, computed, and actual values):
background-position
bottom, left, right, top
height, width
margin-bottom, margin-left, margin-right, margin-top,
min-height, min-width
padding-bottom, padding-left, padding-right, padding-top
text-indent
Try this:
style = window.getComputedStyle(el),
console.log( style.getPropertyValue('min-height'));
Check an update of your fiddle:
http://jsfiddle.net/VEuJe/4/
JQuery method css is not so simple. You can check on this link jquery css
The .css() method is a convenient way to get a style property from the
first matched element, especially in light of the different ways
browsers access most of those properties (the getComputedStyle()
method in standards-based browsers versus the currentStyle and
runtimeStyle properties in Internet Explorer) and the different terms
browsers use for certain properties. For example, Internet Explorer's
DOM implementation refers to the float property as styleFloat, while
W3C standards-compliant browsers refer to it as cssFloat. For
consistency, you can simply use "float", and jQuery will translate it
to the correct value for each browser.

Categories