Skip to content

Several dark mode theme switcher problems #2669

Description

@medmunds

There are a handful of problems in switch-dark-mode.js.

(Apologies for the compound bug report. These are all somewhat related, and I'm going to open a single PR covering all of them. I can split the issue report and the PR apart if that would be helpful, but it seemed like this might be easier for everyone involved.)

1. Leaked global variables

The script creates arrHost, prefix, host, and sameSiteAttribute variables on the window object. (Missing var/let/const keywords.)

To reproduce:

  1. Go to www.djangoproject.com
  2. Open devtools and observe arrHost and the other variables above are all defined as globals

2. Theme switcher gets stuck when cookies are blocked

The script's cycleTheme() logic relies on a stored cookie as the current source of truth, rather than the current <html data-theme> attribute that actually affects display. As a result, if cookies are blocked the theme switcher button will get stuck in dark mode (or light mode, depending on where it started).

To reproduce:

  1. Go to www.djangoproject.com and disable cookies
  2. Click the theme switcher button three times

Results: after the second click you will be stuck in either dark mode or light mode, and clicking again won't cycle the mode.
Expected: you should be able to cycle through all modes. (Your choice won't be persisted between pages, but the theme switcher should be fully functional on a single page.)

Related glitch: if cookies are blocked, your current theme selection will be reset to 'auto' when the browser/system prefers-dark-mode setting changes (e.g., at sunset and sunrise).

3. Cookie domain calculation can result in invalid domains

The script's setTheme() function computes a cookie domain from the hostname by removing everything up to the first .. (This is meant to share the theme cookie between code.djangoprojects.com, docs.djangoproject.com, etc.) That results in an invalid domain when used with (e.g.,) http://127.0.0.1 preventing storage of the cookie and causing problem 2 during local testing. It would also cause problems if we ever wanted to serve content at djangoproject.com rather than www.

Hostname → cookie domain in current code:

  • www.djangoproject.com → djangoproject.com ✅
  • djangoproject.com → com ❌ (cookie won't be stored)
  • 127.0.0.1 → 0.0.1 ❌ (cookie won't be stored)
  • localhost → (empty string) ⚠️ (sets the cookie with ; Domain= instead of omitting the Domain attribute)
  • docs.preview.djangoproject.com → preview.djangoproject.com ✅
  • preview.djangoproject.com → djangoproject.com ❌ (should remain preview.djangoproject.com)
  • djangoproject.localhost → localhost ❌
  • django--21416.org.readthedocs.build → org.readthedocs.build ⚠️ (we probably don't want to share the cookie across all .org RTD previews)

I think a safer approach would be to have a list of known base domains where cookies should be shared between subdomains (djangoproject.com, djangoproject.local, etc). For anything else, use a host-only cookie.

4. Theme appears to be lost during navigation

When the browser restores page state from the backward/forward cache, the original theme is restored even if it was changed on a later page.

To reproduce:

  1. Go to www.djangoproject.com and note your theme (e.g., light). (Make sure cookies are enabled.)
  2. Navigate to some other djangoproject page (e.g., the /start/ page)
  3. Use the theme switcher button to pick a visibly different theme (e.g., dark)
  4. Press the browser's Back button

Results: the page displays in the theme from step 1. Your change in step 3 appears to be lost.
Expected: the page should use the most recently chosen theme from step 3. (If you refresh the page, the new theme will apply.)

The solution is to reload the theme from the cookie when a persisted pageshow event (bfcache navigation) occurs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions