DEV Community

Why the most-installed "PDF dark mode" Chrome extensions do nothing on a web PDF

Chrome still ships no dark mode for its built-in PDF viewer. Open a white paper at 1am and you get a flashbang. So you go to the Web Store, install the extension with the most installs, click it, and… nothing happens. The page stays white. I went and read the manifests of the top results to find out why. Two reasons, and both are boring.

Reason 1: the popular ones only handle file://

The extension named "PDF Dark Mode" (about 10,000 users, rated 2.5) declares exactly this:

"permissions" : [ "scripting" , "declarativeContent" ] ,
"host_permissions" : [ "file:///*.pdf" ]

The runner-up, "PDF Dark Theme" (about 9,000 users, rated 2.9), does the same thing with a content script:

"content_scripts" : [{ "matches" : [ "file://*.pdf" ], "js" : [ "content-script.js" ] }]

file:///*.pdf matches a PDF you dragged in from your own disk. It does not match https://arxiv.org/pdf/1706.03762, or the invoice your bank linked, or the syllabus on a course site. That is where almost everyone actually meets a PDF. So the extension is installed, enabled, and structurally incapable of touching the document in front of you.

To be fair about the scope of this: it is the two most-installed ones that are scoped this way, not the whole category. Several smaller ones (a few hundred to a few thousand users) request <all_urls> or use activeTab and do handle web PDFs. The ranking is weighted by install count, so the two that cannot do the common case are the two you see first.

This is also why the reviews are full of people being told to flip "Allow access to file URLs" and reporting back that it changed nothing. It was never the missing piece. You can check any extension for this in ten seconds: chrome://extensions β†’ Details β†’ look at "Site access". If it says nothing beyond file URLs, that is your answer.

Reason 2: the CSS target moved

The other approach is a CSS filter on the viewer element:

embed[type="application/x-google-chrome-pdf"] { filter: invert(90%) hue-rotate(180deg); }

That used to be right. When you navigate straight to a PDF today, the document you are styling has no <embed> in it. The viewer lives in an out-of-process child frame that your CSS cannot reach. Your selector matches zero elements and fails silently, which is the worst way for CSS to fail.

What does reach it is a filter on the root element of the PDF document itself:

html { filter: invert(90%) hue-rotate(180deg); background-color: #17171a; }

So the two cases need different code, and you can tell them apart cheaply:

const topLevelPdf = document.contentType === "application/pdf";
const css = topLevelPdf
  ? `html { filter: ${f} !important; background-color: #17171a !important; }`
  : `embed[type="application/x-google-chrome-pdf"], embed[type="application/pdf"], object[type="application/pdf"] { filter: ${f} !important; }`;

An embedded PDF (a viewer inside a normal page) still wants the second form, because filtering html there would invert the surrounding page too.

Try it without installing anything

Open a PDF in Chrome, open DevTools on it, and paste this into the console:

document.documentElement.style.filter = "invert(90%) hue-rotate(180deg)";
document.documentElement.style.backgroundColor = "#17171a";

Two notes on the numbers, because they matter more than they look.

  • invert(90%) rather than invert(100%): full inversion turns black text into pure white on pure black, which vibrates unpleasantly at night. 90% lands on a soft grey.
  • hue-rotate(180deg) after the inversion: inverting alone also inverts hue, so a blue hyperlink comes out orange and a red warning box comes out cyan. Rotating the hue 180Β° puts colours back where they started while keeping the lightness flipped. Skip it and PDFs with charts become unreadable.

Sepia is the same trick with one more stage:

filter: invert(93%) hue-rotate(180deg) sepia(35%);

What this approach cannot do

Be honest about the limits before you build on it:

  • Photographs and diagrams invert along with the text. A CSS filter has no idea which pixels are content and which are background. Nobody solves this with filter; it needs the PDF parsed and re-rendered.
  • DRM-protected PDFs are outside your reach entirely.
  • The console trick dies on reload. Persisting it is the entire reason extensions exist here.

The extension

I got tired of this and wrote the small version: PDF Dark Mode - Night Reader for PDFs. Disclosure: mine, free, no account, no network calls at all. It does the branch above, so web PDFs work and not only local ones; it uses activeTab so it reads one tab when you click it and holds no standing permission on your browsing; the three themes are soft dark, true black and sepia, with brightness and contrast sliders. If you would rather not install a thing, the console snippet is genuinely the whole idea. Take it.

The frustrating part of this category was never the CSS. It was ten extensions all quietly scoped to file://.

Comments

No comments yet. Start the discussion.