How not to inherit styles in a chrome extension content script
I'm writing a Google Chrome extension that runs a content script on every page. In my content script, I inject a <div>
with some <ul>
and <li>
children into the page. I specify a few styles for these elements in a stylesheet.
But I've found that on some random pages my elements will inherit styles from those defined on the webpage since I haven't specified every single style property for my divs.
What's the best way I can stop my injected elements from inheriting these styles?
It see开发者_运维百科ms to me I could either:
- specify every single style in my stylesheet (eg. by looking at what the computed styles are when there is no interference), or
- I could put my
<div>
inside an<iframe>
. However, then I'll have to do hella message passing between my content script's iframe and the source page since thechrome://
URL of my iframe src and thehttp://
urls of the source pages would be considered cross-origin.
I would go with the first choice--to fully specify the style of the elements you use. But this is a bit more involved than I thought.
First, you have to completely specify the container element. Then, for its descendants, you have to say that they should also use the default values or inherit from their parent (up to the container). Finally, you have to specify the look of every other element so that they're not all plain spans.
The relevant APIs are getComputedStyle
and the CSSStyleSheet
interface from DOM Level 2 Style. You can use all the values there except width
and height
, which should be auto
by default. You also need to download a default stylesheet, such as the Webkit user agent stylesheet. Then you can call the following function to create a complete stylesheet that you can inject into the document.
Note that when you insert the stylesheet into the target document, you'll have to make the container selector as specific as possible because the webpage could conceivably give rules that have a higher specificity than your rules. For example, in <html id=a><head id=b><style>#a #b * {weird overrides}</style></head>
, #a #b *
has a higher specificity than #yourId div
would. But I imagine that this is uncommon.
Note: for some reason, Chrome is giving me error "Failed to load resource" when I load the CSS, unless it is already in a <link>
of the current document. So you should include html.css in the page that calls this function too.
// CSS 2.1 inherited prpoerties
var inheritedProperties = [
'azimuth', 'border-collapse', 'border-spacing', 'caption-side',
'color', 'cursor', 'direction', 'elevation', 'empty-cells',
'font-family', 'font-size', 'font-style', 'font-variant',
'font-weight', 'font', 'letter-spacing', 'line-height',
'list-style-image', 'list-style-position', 'list-style-type',
'list-style', 'orphans', 'pitch-range', 'pitch', 'quotes',
'richness', 'speak-header', 'speak-numeral', 'speak-punctuation',
'speak', 'speech-rate', 'stress', 'text-align', 'text-indent',
'text-transform', 'visibility', 'voice-family', 'volume',
'white-space', 'widows', 'word-spacing'];
// CSS Text Level 3 properties that inherit http://www.w3.org/TR/css3-text/
inheritedProperties.push(
'hanging-punctuation', 'line-break', 'punctuation-trim',
'text-align-last', 'text-autospace', 'text-decoration-skip',
'text-emphasis', 'text-emphasis-color', 'text-emphasis-position',
'text-emphasis-style', 'text-justify', 'text-outline',
'text-shadow', 'text-underline-position', 'text-wrap',
'white-space-collapsing', 'word-break', 'word-wrap');
/**
* Example usage:
var fullStylesheet = completeStylesheet('#container', 'html.css').map(
function(ruleInfo) {
return ruleInfo.selectorText + ' {' + ruleInfo.cssText + '}';
}).join('\n');
* @param {string} containerSelector The most specific selector you can think
* of for the container element; e.g. #container. It had better be more
* specific than any other selector that might affect the elements inside.
* @param {string=} defaultStylesheetLocation If specified, the location of the
* default stylesheet. Note that this script must be able to access that
* locatoin under same-origin policy.
* @return {Array.<{selectorText: string, cssText: string}>} rules
*/
var completeStylesheet = function(containerSelector,
defaultStylesheetLocation) {
var rules = [];
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe); // initializes contentDocument
try {
var span = iframe.contentDocument.createElement('span');
iframe.contentDocument.body.appendChild(span);
/** @type {CSSStyleDeclaration} */
var basicStyle = iframe.contentDocument.defaultView.getComputedStyle(span);
var allPropertyValues = {};
Array.prototype.forEach.call(basicStyle, function(property) {
allPropertyValues[property] = basicStyle[property];
});
// Properties whose used value differs from computed value, and that
// don't have a default value of 0, should stay at 'auto'.
allPropertyValues['width'] = allPropertyValues['height'] = 'auto';
var declarations = [];
for (var property in allPropertyValues) {
var declaration = property + ': ' + allPropertyValues[property] + ';';
declarations.push(declaration);
}
// Initial values of all properties for the container element and
// its descendants
rules.push({selectorText: containerSelector + ', ' +
containerSelector + ' *',
cssText: declarations.join(' ')});
// For descendants, some of the properties should inherit instead
// (mostly dealing with text).
rules.push({selectorText: containerSelector + ' *',
cssText: inheritedProperties.map(
function(property) {
return property + ': inherit;'
}).join(' ')});
if (defaultStylesheetLocation) {
var link = iframe.contentDocument.createElement('link');
link.rel = 'stylesheet';
link.href = defaultStylesheetLocation;
iframe.contentDocument.head.appendChild(link);
/** @type {CSSStyleSheet} */
var sheet = link.sheet;
Array.prototype.forEach.call(
sheet.cssRules,
/** @param {CSSStyleRule} cssRule */
function(cssRule) {
rules.push({
selectorText: containerSelector + ' ' + cssRule.selectorText,
cssText: cssRule.style.cssText});
});
}
return rules;
} finally {
document.body.removeChild(iframe);
}
};
.my-extension-frame {
all: initial;
/* style... */
}
UPDATE: I had to revisit this recently and it is quite the nightmare if the web page styles element types directly. My new method is something like this:
- Have a base div that everything is a child of.
- Use a wildcard style and do all:revert
- Exclude svg as "all:revert" will kill SVGs
- Reset contenteditable as well.
Example scss:
@namespace svg "http://www.w3.org/2000/svg";
#mypanel {
*:not(svg|*) {
all: revert;
}
// tested on chrome and not firefox
div[contenteditable] {
-webkit-user-modify: read-write;
overflow-wrap: break-word;
-webkit-line-break: after-white-space;
line-break: after-white-space;
}
}
More writing: https://blog.mukunda.com/cat/2023/getting-around-existing-css-for-overlays.txt
I recently created Boundary, a CSS+JS library to solve problems just like this. Boundary creates elements that are completely separate from the existing webpage's CSS.
Take creating a dialog for example. After installing Boundary, you can do this in your content script
var dialog = Boundary.createBox("yourDialogID", "yourDialogClassName");
Boundary.loadBoxCSS("#yourDialogID", "style-for-elems-in-dialog.css");
Boundary.appendToBox(
"#yourDialogID",
"<button id='submit_button'>submit</button>"
);
Boundary.find("#submit_button").click(function() {
// find() function returns a regular jQuery DOM element
// so you can do whatever you want with it.
// some js after button is clicked.
});
Elements within #yourDialogID will not be affected by the existing webpage.
Hope this helps. Please let me know if you have any question.
https://github.com/liviavinci/Boundary
精彩评论