Chrome is forcing a white background on frames only on some websites
Asked Answered
M

5

10

I'm building a Web Extension that injects a local page and I reached a strange Chrome limitation on some websites: I can't set the iframe to be transparent:

GitHub showing a white iframe; Twitter showing a transparent iframe

Try this on GitHub.com (after login) or on Google:

document.body.insertAdjacentHTML(
  'afterbegin',
  '<iframe style="background: red" srcdoc="<style>body {background:none}">'
)

You'll see that the iframed page’s background (white) is shown instead of the element’s background (in this case made red to highlight the issue).

If you try the same code on StackOverflow or Twitter you'll find that the iframe is red.

I assume this is due to security but strangely it's even happening on this simple website that lacks any security header: https://sjmulder.nl/en/textonly.html

Firefox and Safari are not affected.

Morris answered 15/10, 2021 at 22:12 Comment(2)
That’s by-design. Only iframe from the same-origin or CORS-safe origins can have transparent backgrounds. This is to prevent cross-origin information leakage through inspecting computed styles.Generally
edit: oh, I see the iframe is using inline HTML. Hmm, no idea 🤷‍♂️ - what are the CSP headers on the website requests?Generally
M
13

Read @neea’s answer for a broader explanation, but here's a quick fix that works anywhere: Add the CSS color-scheme: normal to the iframe, either via stylesheet or inline:

<iframe style="border: 0; color-scheme: normal" src="your-url.html">

Only do this if the iframed page will display correctly on both dark and white backgrounds. You might also need to set color-scheme: normal in your iframed page via meta tag, but this should not be necessary.

Why Chrome does this

Light schemes generally show black text and white background and dark schemes are the opposite. Chrome uses the color-scheme property to understand what schemes a website supports. The lack of it means it only supports the light scheme.

If the main page is being shown in a dark schemes and the iframed page isn't, with a transparent background you would get the iframed page’s black text on the main page’s black background.

This is a safety mechanism to ensure that the iframed content is still readable. If you drop it, you will need to manually ensure that the iframed page is readable on a dark background too.

Live demo

You can see that neither iframe has a background in both dark mode and regular mode.

  • Page only has one scheme, iframe never shows a white background: live link

    screencast

  • Page only 2 schemes, iframe never shows a white background: live link

    screencast

Morris answered 26/11, 2021 at 5:34 Comment(4)
omg, this answer deserves a thousand upvotesHildegardehildesheim
Absolutely amazing! Thanks a lot!Deettadeeyn
The second live link shows a white background for me in dark mode.Donaugh
@KerryJohnson I alse, use firefox browser.Fino
J
13

For anyone looking for a quick copy-paste answer: include a color-scheme meta header in the injected iframe. It must match the color-scheme header of the web page, if one exists. Do not add the meta header if a website does not have one. As this requires programmatic effort to figure out if such header exists a what the content value is, those steps are omitted, but once the scheme is known, the injection code would look something like this:

document.body.insertAdjacentHTML(
    'afterbegin',
    '<iframe style="background: red" srcdoc="' +
    '<meta name=\'color-scheme\' content=\'light dark\'>' + 
    '<style>body {background:none}">')

Next a more detailed answer, and how I discovered it.

The screenshots show macOS in dark mode. If you go to OS settings and toggle the appearance between light and dark, this should alternate the white color and red color behind the iframe. Try and see if this happens.

After some experimentation, I discovered iff a website has a meta header for color-scheme:

<meta name="color-scheme" content="light dark">

as is the case with Github.com and sjmulder.nl, an iframe without matching header is only able to handle light mode correctly, and renders a white background if user device is set to prefer dark mode. I suspect this is because "major browsers default to light mode if not otherwise configured".

You can experiment with this and switch the color mode header on a test website to "light" (only) and "dark" (only) without specifying anything in the injected iframe; or define it in the iframe and not on the website, then switch the device color mode preference to see the effect. The conclusion I reached was the headers must match for iframe background color to get applied correctly.

I reached a strange Chrome limitation on some websites

When a website has not defined a color-scheme meta header and the injected iframe has not defined one either, there is no mismatch, and there is no issue, which is why this problems only presents on some websites. I also tried other browsers (Edge, Opera) and they have this same issue, at least on macOS. But it can be fixed in an extension by first checking if there exists a meta header on the page, and if yes, applying that same header to the injected iframe.

Justajustemilieu answered 22/10, 2021 at 4:32 Comment(1)
Thank you so much! I think the drawback of the plain tag is that the page in the iframe must also actually support the dark mode, or else (I suppose) the browser might decide to offer dark-mode-only UI controls on a white page. I used the CSS :root {color-scheme: light dark} body {color-scheme: normal} to reset itMorris
M
13

Read @neea’s answer for a broader explanation, but here's a quick fix that works anywhere: Add the CSS color-scheme: normal to the iframe, either via stylesheet or inline:

<iframe style="border: 0; color-scheme: normal" src="your-url.html">

Only do this if the iframed page will display correctly on both dark and white backgrounds. You might also need to set color-scheme: normal in your iframed page via meta tag, but this should not be necessary.

Why Chrome does this

Light schemes generally show black text and white background and dark schemes are the opposite. Chrome uses the color-scheme property to understand what schemes a website supports. The lack of it means it only supports the light scheme.

If the main page is being shown in a dark schemes and the iframed page isn't, with a transparent background you would get the iframed page’s black text on the main page’s black background.

This is a safety mechanism to ensure that the iframed content is still readable. If you drop it, you will need to manually ensure that the iframed page is readable on a dark background too.

Live demo

You can see that neither iframe has a background in both dark mode and regular mode.

  • Page only has one scheme, iframe never shows a white background: live link

    screencast

  • Page only 2 schemes, iframe never shows a white background: live link

    screencast

Morris answered 26/11, 2021 at 5:34 Comment(4)
omg, this answer deserves a thousand upvotesHildegardehildesheim
Absolutely amazing! Thanks a lot!Deettadeeyn
The second live link shows a white background for me in dark mode.Donaugh
@KerryJohnson I alse, use firefox browser.Fino
G
2

I added color-scheme: auto; to my iframe's style and that fixed it.

Gilliland answered 16/2 at 18:58 Comment(1)
This also fixed it for me. The other solutions required either JS logic or didn't work across all pages (with or without a scheme).Winkler
U
0

Following what @neea has said, which is true, I want to propose another code fix, since the color-theme fix doesn't do anything for me.

Since you use the iframe for a chrome extension, you simply need to have the iframe injected with a content script and then get the value of the page of the meta tag, which can be done easily with document.querySelector('meta[name="color-scheme"]')?.content

Personally, in my project I used react and react-frame-component, and I got it by inserting this into the head (I also use TS):

        <meta
            key="1"
            content={
              (document.querySelector('meta[name="color-scheme"]') as any)
                ?.content ?? 'light'
            }
            name="color-scheme"
          />,

I verify if the site has that tag, then I take and if not, it defaults to light mode.

This needs to be injected into your iframe HEAD. Then it will work for sites such as google dark mode or github.

Uneven answered 20/11, 2021 at 11:20 Comment(1)
I got nothing else to say, you literally changed the code and now you say it worked. OkUneven
L
-1

Element.insertAdjacentHTML()

The insertAdjacentHTML() method of the Element interface parses the specified text as HTML or XML and inserts the resulting nodes into the DOM tree at a specified position. It does not reparse the element it is being used on, and thus it does not corrupt the existing elements inside that element. This avoids the extra step of serialization, making it much faster than direct innerHTML manipulation.

It has something to do with how chrome parses HTML and the order it uses.

Lax answered 20/10, 2021 at 23:32 Comment(1)
insertAdjacentHTML is just for the demo, but the effect was discovered with both a raw document.createElement and via ReactMorris

© 2022 - 2024 — McMap. All rights reserved.