6

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,736
  • 9
  • 47
  • 72
  • 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

4 Answers4

7

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,736
  • 9
  • 47
  • 72
  • For anyone trying to find a solution, since webpack 5, raw loader is not mandatory anymore, you should use asset/source – gBusato Dec 12 '22 at 10:47
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
  • 19,083
  • 6
  • 44
  • 61
Denis L
  • 3,209
  • 1
  • 25
  • 37
  • 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
3

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.runtime.getURL("bootstrap.min.css")}"></link>\
   <link rel="stylesheet" type="text/css" href="${chrome.runtime.getURL("whatever.css")}"></link>\
`;
document.body.appendChild(injectedDiv);

Notes:

  1. Using chrome.runtime.getURL is required for getting an extension's local resource url, see e.g. in this answer.
    • The linked answer uses chrome.extenstion.getURL, which is deprecated since Chrome 58, and won't work in manifest V3 (see this documentation).
  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
  • 9,442
  • 5
  • 47
  • 90
0

There're safer ways to import CSS instead of using link. Here's what I would do.

  const response = await fetch(chrome.runtime.getURL('your/path.css'))
  const cssText = await response.text();
  document.createElement('style').textContent = cssText;
Dave Lee
  • 316
  • 3
  • 9