Skip to content

Commit d1320a5

Browse files
authored
Rollup merge of #107177 - thanatos:fix-doc-errant-light-theme, r=notriddle
Keep all theme-updating logic together Prior to this PR, if the page is restored from the browser bfcache¹, we call `switchToSavedTheme`. But `switchToSavedTheme` never looks at the `use-system-theme` preference. Further, if it can't find a saved theme, it will fall back to the default of "light". For a user with cookies disabled² whose preferred color scheme is dark, this means the theme will wobble back and forth between dark and light. The sequence that occurs is, 1. The page is loaded. During a page load, we consult `use-system-theme`: as cookies are disabled, this preference is unset. The default is true. Because the default is true, we look at the preferred color scheme: for our example user, that's "dark". **The page theme is set to dark.** We'll attempt to store these preferences in localStorage, but fail due to cookies being disabled. 2. The user navigates through the docs. Subsequent page loads happen, and the same process in step 1 recurs. Previous pages are (potentially) put into the bfcache. 3. The user navigates backwards/forwards, causing a page in bfcache to be pulled out of cache. The `pageShow` event handler is triggered. However, this calls `switchToSavedTheme`: this doesn't consider the system theme, as noted above. Instead, it only looks for a saved theme. However, with cookies disabled, there is none. It defaults to light. **The page theme is set to light!** The user wonders why the dark theme is lost. There are effectively two functions trying to determine and apply the correct theme: `updateSystemTheme` and `switchToSavedTheme`. Thus, we merge them into just one: `updateTheme`. This function contains all the logic for determining the correct theme, and is called in all circumstances where we need to set the theme: * The initial page load * If the browser preferred color scheme (i.e., light/dark mode) is changed * If the page is restored from bfcache * If the user updates the theme preferences (i.e., in `settings.js`) Fixes #94250. ¹bfcache: https://web.dev/bfcache/ The bfcache is used to sleep a page, if the user navigates away from it, and to restore it from cache if the user returns to it. ²Note that the browser preference that enables/disables cookies really controls many forms of storage. The same preference thus also affects localStorage. (This is so a normal browser user doesn't need to understand the distinction between "cookies" and "localStorage".)
2 parents db97749 + 727a1fd commit d1320a5

File tree

3 files changed

+55
-63
lines changed

3 files changed

+55
-63
lines changed

src/librustdoc/config.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ impl Options {
509509
// these values up both in `dataset` and in the storage API, so it needs to be able
510510
// to convert the names back and forth. Despite doing this kebab-case to
511511
// StudlyCaps transformation automatically, the JS DOM API does not provide a
512-
// mechanism for doing the just transformation on a string. So we want to avoid
512+
// mechanism for doing just the transformation on a string. So we want to avoid
513513
// the StudlyCaps representation in the `dataset` property.
514514
//
515515
// We solve this by replacing all the `-`s with `_`s. We do that here, when we

src/librustdoc/html/static/js/settings.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Local js definitions:
2-
/* global getSettingValue, getVirtualKey, updateLocalStorage, updateSystemTheme */
2+
/* global getSettingValue, getVirtualKey, updateLocalStorage, updateTheme */
33
/* global addClass, removeClass, onEach, onEachLazy, blurHandler, elemIsInParent */
44
/* global MAIN_ID, getVar, getSettingsButton */
55

@@ -19,7 +19,7 @@
1919
case "theme":
2020
case "preferred-dark-theme":
2121
case "preferred-light-theme":
22-
updateSystemTheme();
22+
updateTheme();
2323
updateLightAndDark();
2424
break;
2525
case "line-numbers":

src/librustdoc/html/static/js/storage.js

+52-60
Original file line numberDiff line numberDiff line change
@@ -153,79 +153,74 @@ function switchTheme(styleElem, mainStyleElem, newThemeName, saveTheme) {
153153
}
154154
}
155155

156-
// This function is called from "main.js".
157-
// eslint-disable-next-line no-unused-vars
158-
function useSystemTheme(value) {
159-
if (value === undefined) {
160-
value = true;
161-
}
162-
163-
updateLocalStorage("use-system-theme", value);
164-
165-
// update the toggle if we're on the settings page
166-
const toggle = document.getElementById("use-system-theme");
167-
if (toggle && toggle instanceof HTMLInputElement) {
168-
toggle.checked = value;
169-
}
170-
}
171-
172-
const updateSystemTheme = (function() {
173-
if (!window.matchMedia) {
174-
// fallback to the CSS computed value
175-
return () => {
176-
const cssTheme = getComputedStyle(document.documentElement)
177-
.getPropertyValue("content");
178-
179-
switchTheme(
180-
window.currentTheme,
181-
window.mainTheme,
182-
JSON.parse(cssTheme) || "light",
183-
true
184-
);
156+
const updateTheme = (function() {
157+
/**
158+
* Update the current theme to match whatever the current combination of
159+
* * the preference for using the system theme
160+
* (if this is the case, the value of preferred-light-theme, if the
161+
* system theme is light, otherwise if dark, the value of
162+
* preferred-dark-theme.)
163+
* * the preferred theme
164+
* … dictates that it should be.
165+
*/
166+
function updateTheme() {
167+
const use = (theme, saveTheme) => {
168+
switchTheme(window.currentTheme, window.mainTheme, theme, saveTheme);
185169
};
186-
}
187-
188-
// only listen to (prefers-color-scheme: dark) because light is the default
189-
const mql = window.matchMedia("(prefers-color-scheme: dark)");
190170

191-
function handlePreferenceChange(mql) {
192-
const use = theme => {
193-
switchTheme(window.currentTheme, window.mainTheme, theme, true);
194-
};
195171
// maybe the user has disabled the setting in the meantime!
196172
if (getSettingValue("use-system-theme") !== "false") {
197173
const lightTheme = getSettingValue("preferred-light-theme") || "light";
198174
const darkTheme = getSettingValue("preferred-dark-theme") || "dark";
199175

200-
if (mql.matches) {
201-
use(darkTheme);
176+
if (isDarkMode()) {
177+
use(darkTheme, true);
202178
} else {
203179
// prefers a light theme, or has no preference
204-
use(lightTheme);
180+
use(lightTheme, true);
205181
}
206182
// note: we save the theme so that it doesn't suddenly change when
207183
// the user disables "use-system-theme" and reloads the page or
208184
// navigates to another page
209185
} else {
210-
use(getSettingValue("theme"));
186+
use(getSettingValue("theme"), false);
211187
}
212188
}
213189

214-
mql.addListener(handlePreferenceChange);
190+
// This is always updated below to a function () => bool.
191+
let isDarkMode;
215192

216-
return () => {
217-
handlePreferenceChange(mql);
218-
};
219-
})();
193+
// Determine the function for isDarkMode, and if we have
194+
// `window.matchMedia`, set up an event listener on the preferred color
195+
// scheme.
196+
//
197+
// Otherwise, fall back to the prefers-color-scheme value CSS captured in
198+
// the "content" property.
199+
if (window.matchMedia) {
200+
// only listen to (prefers-color-scheme: dark) because light is the default
201+
const mql = window.matchMedia("(prefers-color-scheme: dark)");
220202

221-
function switchToSavedTheme() {
222-
switchTheme(
223-
window.currentTheme,
224-
window.mainTheme,
225-
getSettingValue("theme") || "light",
226-
false
227-
);
228-
}
203+
isDarkMode = () => mql.matches;
204+
205+
if (mql.addEventListener) {
206+
mql.addEventListener("change", updateTheme);
207+
} else {
208+
// This is deprecated, see:
209+
// https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener
210+
mql.addListener(updateTheme);
211+
}
212+
} else {
213+
// fallback to the CSS computed value
214+
const cssContent = getComputedStyle(document.documentElement)
215+
.getPropertyValue("content");
216+
// (Note: the double-quotes come from that this is a CSS value, which
217+
// might be a length, string, etc.)
218+
const cssColorScheme = cssContent || "\"light\"";
219+
isDarkMode = () => (cssColorScheme === "\"dark\"");
220+
}
221+
222+
return updateTheme;
223+
})();
229224

230225
if (getSettingValue("use-system-theme") !== "false" && window.matchMedia) {
231226
// update the preferred dark theme if the user is already using a dark theme
@@ -235,13 +230,10 @@ if (getSettingValue("use-system-theme") !== "false" && window.matchMedia) {
235230
&& darkThemes.indexOf(localStoredTheme) >= 0) {
236231
updateLocalStorage("preferred-dark-theme", localStoredTheme);
237232
}
238-
239-
// call the function to initialize the theme at least once!
240-
updateSystemTheme();
241-
} else {
242-
switchToSavedTheme();
243233
}
244234

235+
updateTheme();
236+
245237
if (getSettingValue("source-sidebar-show") === "true") {
246238
// At this point in page load, `document.body` is not available yet.
247239
// Set a class on the `<html>` element instead.
@@ -259,6 +251,6 @@ if (getSettingValue("source-sidebar-show") === "true") {
259251
// specifically when talking to a remote website with no caching.
260252
window.addEventListener("pageshow", ev => {
261253
if (ev.persisted) {
262-
setTimeout(switchToSavedTheme, 0);
254+
setTimeout(updateTheme, 0);
263255
}
264256
});

0 commit comments

Comments
 (0)