4

I'm working on a Chrome extension that injects some UI react components into a page.

The UI components come from react-mdl. Using them requires me to include a css file in the top of my project.

Unfortunately, once the css is injected into the page, the entire page's font is changed.

Is there a way to limit the scope of the css used by react-mdl such that it doesn't affect the page into which I'm injecting?

Brandon
  • 7,126
  • 8
  • 41
  • 68
  • so this only happens with other webpages that use react? – Noam Hacker Feb 06 '17 at 16:45
  • No, I only inject into pages in one domain, and none of its pages use React (or any framework at all--just vanilla JS/jquery and css/html) – Brandon Feb 06 '17 at 16:46
  • ok, can you share some of the css file? maybe just use custom class names that you know won't be used anywhere else – Noam Hacker Feb 06 '17 at 16:48
  • actually, I see the issue. the css file you linked has very generic tags like `body`. you may need to manually rename these – Noam Hacker Feb 06 '17 at 16:49
  • ah i was afraid of that. no way as far as you know to forceably scope it? I can gut the `material.css` a little and get rid of the page-level stuff, but if i can avoid tampering with it, i'd love it. – Brandon Feb 06 '17 at 16:51
  • sounds like if you gut it you should be ok. let me know if it works :) – Noam Hacker Feb 06 '17 at 16:52
  • 1
    messy work, this web design. thanks @NoamHacker. I may expermient with [this a bit first](https://github.com/purifycss/purifycss) – Brandon Feb 06 '17 at 16:52
  • @NoamHacker turns out the `shadowDOM` mentioned by @Deliaz below works great. It encapsulates the styles i need entirely within my injected div. – Brandon Feb 06 '17 at 18:50
  • thanks, glad I got to learn something new! – Noam Hacker Feb 06 '17 at 20:28

3 Answers3

5

Just posting this for posterity as accepted answer deserves credit, but if anyone finds themselves in a similar predicament, here is a snippet of the code that worked for me:

// my injected code
window.addEventListener('load', () => {
    const injectDiv = document.createElement('div')
    const shadowRoot = injectDiv.attachShadow({ mode: 'open' })

    // note inline use of webpack raw-loader, so that the css
    // file gets inserted as raw text, instead of attached to <head>
    // as with the webpack style-loader

    shadowRoot.innerHTML = // just using template string
      `
       <style>${require('raw-loader!app/styles/extension-material.css')}</style>
       <div id='shadowReactRoot' />
       `
    document.body.appendChild(injectDiv)
    ReactDOM.render(
          <App />,
          // note you have to start your query in the shadow DOM
          // in order to find your root
          shadowRoot.querySelector('#shadowReactRoot')
        )
})

Then, sure enough:

shadow DOM inside the document

Brandon
  • 7,126
  • 8
  • 41
  • 68
4

I think you should use the Shadow DOM API. It is good practice for those cases when you just need to append your UI component to a webpage.

https://developers.google.com/web/fundamentals/getting-started/primers/shadowdom

Daniel Herr
  • 16,897
  • 4
  • 42
  • 57
Denis L
  • 2,996
  • 1
  • 20
  • 35
  • thanks. i am going to have to fiddle a little with my webpack config to make the css inlining work correctly, but when I paste the entire `material.css` into a `style` tag in a `shadow` root, this works great. – Brandon Feb 06 '17 at 18:49
2

As mentioned in this other SO post, <link> tag is also supported, so one can simply do as follows:

const injectedDiv = document.createElement('div');
const shadowRoot = injectedDiv.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `\
   <link rel="stylesheet" type="text/css" href="${chrome.extension.getURL("bootstrap.min.css")}"></link>\
   <link rel="stylesheet" type="text/css" href="${chrome.extension.getURL("whatever.css")}"></link>\
`;
document.body.appendChild(injectedDiv);

Notes:

  1. Using chrome.extension.getURL is required for getting an extension's local resource url, see e.g. in this answer.
  2. The linked .css resources must be declared under the web_accessible_resources property in your manifest.json (otherwise, you'll get this error)
OfirD
  • 7,020
  • 2
  • 33
  • 68