Wednesday, April 20, 2011

Prevent existing CSS from styling injected HTML/CSS

I'm working on a project which injects JS+CSS+HTML over web pages which I do not have control over.

I am concerned about the host page styling my injected code -- I want my injected code to only obey my styling, and not theirs.

At the moment the only method to do this I can think of involves explicitly specifying every possible tag for the container <div>'s class (using pre-defined, known browser defaults) and relying on inheritance for those rules to propagate down to the rest of my injected HTML. This CSS would need to appear at the bottom of the page's <head> tag.

I don't think that's the best solution, though, and I don't look forward to implementing it. Surely there are better ways.

From stackoverflow
  • You'll just have to be very specific with your CSS selectors. That is, much more specific than the host page's CSS rules. You might also want to add a kind of global CSS reset modified to only reset your HTML. By that, I mean something which resets all colours, backgrounds, fonts, padding, etc, back to a single standard. From there you can build up your own styles.

    alex : +1 for the Eric Meyer reset and not the dreaded * { padding: 0; margin: 0 }
    Daniel Lew : CSS reset is for standardizing across browsers, not across CSS stylesheets.
    nickf : yes, but a CSS reset (with more specificity than the host CSS) will override previously set rules (removing funky pink backgrounds and the like)
    alex : Pink backgrounds are a CSS stylist's friend!
    Daniel Lew : No, your standard CSS reset will not, unless your CSS reset does *every* style. Standard CSS resets are just to correct browser differences, such as margin and padding defaults.
    nickf : oh I see what you mean now. yes, you're right. editing answer...
  • Wrap your injected HTML with a specifically-classed <div>, such as

    <div class="my-super-specialized-unique-wrapper"><!--contents--></div>
    

    Then in your CSS, prefix all rules with .my-super-specialized-unique-wrapper and use !important on every rule. That will instruct the client browser to treat your rule as supreme for any elements which match, regardless of any other styles that might target them - even if other rules outside your control are more-specific.

    Tom the Junglist : Thank you for your answer! I'm already using super-specific class names for everything, so that's done and done. :-) I do have a few questions though: - Will CSS rules by specifying element names (e.g. `div layer table {}`, etc) override !important - Do I need to specify !important for every rule?
    Rex M : !important overrides *everything* else, no matter when, where or how it is specified. You need to specify it for each style on each rule where you have concern that style may be overridden by another one outside your control.
    Rex M : Of course, there's potentially an added concern if the stylesheets you don't control also employ !important. Then I believe it is a matter of which !important is declared last.
    nickf : @Rex, I believe that it would go to standard specificity rules if there are two !important rules competing. In any case, you'd still need a CSS reset on your wrapper.
  • Why not injecting html elements with inline formatting like this:

    <p style="color:red">This to be injected</p>
    

    inline styling takes precedence over any other rule, and you're already creating the markup in order to insert it.

    Other solution would be to use very specific DOM id's on your elements and then styling them on your added CSS

    nickf : this wouldn't help if the host CSS changes the styles in other ways, eg: p { font-weight: bold } ... you'd end up with red and bold paragraphs.
    Pablo Fernandez : True. I assumed he inlined all the style attributes he wants, not only the color. It was just an example.
  • Others have suggested very good ways to prevent your CSS from affecting the page's, as well as making sure that your CSS takes precedence, but you seem most concerned with the page's CSS affecting you - that is, adding a funky, unexpected style (like making all divs have a pink background).

    The only way I can think for you to prevent their styling from affecting your injected code would be for you to sandbox what you've injected, like in an iframe. Otherwise there's simply too many things that you'd have to define - you'd have to define every single style (padding, border, margin, background... the list goes on and on) on the "*" selector (or better yet, "#yourid *", so you just override your own content), just so that you can guarantee absolute control over your CSS.

    Edit: I realize that the latter solution wouldn't necessarily be too painful, if you're using the omni-selector to reset everything to a baseline:

    #myid * {
        somestyle1: default;
        somestyle2: default;
        ... 
    }
    

    I've found someone who has written up such a solution. If you scroll down far enough on the page, you'll see it (from SuzyUK). It doesn't look complete, but is a good start for sure.

    (I, too, would be interested to know if there's a good way to prevent this. I've written Greasemonkey scripts before that injects code onto the page, and have had to write CSS to override the page's CSS, but in those cases I knew who my target was and could tailor my CSS directly towards the page.)

  • If you're using a Mozilla browser you can actually (if illegally) add visible DOM content to the <html> element outside the <body> element. This will avoid any styles that match on body and are inherited by your content. It may be possible that some styles match on html, which would defeat this technique, but I've found it useful.

    eg. using jquery: $('html').append('<div id="mycontent"> blah blah blah </div>');

    Using a popular library is itself a challenge when you don't want your instance of the library to interfere with the uses of the library defined by the pages you're modifying. Greasemonkey's '@require' didn't work for me. I found it necessary (but possible!) to save any existing values of the two or three global names assigned by the jquery script, insert my instance of the library into the DOM, wait for it to be loaded (had to use a timer, that's annoying but I couldn't see a way around it), cache the values assigned to the globals within my own namespace for my own use, and restore the saved ones.

0 comments:

Post a Comment