]> git.proxmox.com Git - rustc.git/blob - src/librustdoc/html/static/js/storage.js
21de7d77d64e7f60a3b449051e1758539d64e337
[rustc.git] / src / librustdoc / html / static / js / storage.js
1 "use strict";
2
3 const darkThemes = ["dark", "ayu"];
4 window.currentTheme = document.getElementById("themeStyle");
5 window.mainTheme = document.getElementById("mainThemeStyle");
6
7 const settingsDataset = (function () {
8 const settingsElement = document.getElementById("default-settings");
9 if (settingsElement === null) {
10 return null;
11 }
12 const dataset = settingsElement.dataset;
13 if (dataset === undefined) {
14 return null;
15 }
16 return dataset;
17 })();
18
19 function getSettingValue(settingName) {
20 const current = getCurrentValue(settingName);
21 if (current !== null) {
22 return current;
23 }
24 if (settingsDataset !== null) {
25 // See the comment for `default_settings.into_iter()` etc. in
26 // `Options::from_matches` in `librustdoc/config.rs`.
27 const def = settingsDataset[settingName.replace(/-/g,"_")];
28 if (def !== undefined) {
29 return def;
30 }
31 }
32 return null;
33 }
34
35 const localStoredTheme = getSettingValue("theme");
36
37 const savedHref = [];
38
39 // eslint-disable-next-line no-unused-vars
40 function hasClass(elem, className) {
41 return elem && elem.classList && elem.classList.contains(className);
42 }
43
44 // eslint-disable-next-line no-unused-vars
45 function addClass(elem, className) {
46 if (!elem || !elem.classList) {
47 return;
48 }
49 elem.classList.add(className);
50 }
51
52 // eslint-disable-next-line no-unused-vars
53 function removeClass(elem, className) {
54 if (!elem || !elem.classList) {
55 return;
56 }
57 elem.classList.remove(className);
58 }
59
60 /**
61 * Run a callback for every element of an Array.
62 * @param {Array<?>} arr - The array to iterate over
63 * @param {function(?)} func - The callback
64 * @param {boolean} [reversed] - Whether to iterate in reverse
65 */
66 function onEach(arr, func, reversed) {
67 if (arr && arr.length > 0 && func) {
68 if (reversed) {
69 const length = arr.length;
70 for (let i = length - 1; i >= 0; --i) {
71 if (func(arr[i])) {
72 return true;
73 }
74 }
75 } else {
76 for (const elem of arr) {
77 if (func(elem)) {
78 return true;
79 }
80 }
81 }
82 }
83 return false;
84 }
85
86 /**
87 * Turn an HTMLCollection or a NodeList into an Array, then run a callback
88 * for every element. This is useful because iterating over an HTMLCollection
89 * or a "live" NodeList while modifying it can be very slow.
90 * https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection
91 * https://developer.mozilla.org/en-US/docs/Web/API/NodeList
92 * @param {NodeList<?>|HTMLCollection<?>} lazyArray - An array to iterate over
93 * @param {function(?)} func - The callback
94 * @param {boolean} [reversed] - Whether to iterate in reverse
95 */
96 function onEachLazy(lazyArray, func, reversed) {
97 return onEach(
98 Array.prototype.slice.call(lazyArray),
99 func,
100 reversed);
101 }
102
103 // eslint-disable-next-line no-unused-vars
104 function hasOwnPropertyRustdoc(obj, property) {
105 return Object.prototype.hasOwnProperty.call(obj, property);
106 }
107
108 function updateLocalStorage(name, value) {
109 try {
110 window.localStorage.setItem("rustdoc-" + name, value);
111 } catch(e) {
112 // localStorage is not accessible, do nothing
113 }
114 }
115
116 function getCurrentValue(name) {
117 try {
118 return window.localStorage.getItem("rustdoc-" + name);
119 } catch(e) {
120 return null;
121 }
122 }
123
124 function switchTheme(styleElem, mainStyleElem, newTheme, saveTheme) {
125 const newHref = mainStyleElem.href.replace(
126 /\/rustdoc([^/]*)\.css/, "/" + newTheme + "$1" + ".css");
127
128 // If this new value comes from a system setting or from the previously
129 // saved theme, no need to save it.
130 if (saveTheme) {
131 updateLocalStorage("theme", newTheme);
132 }
133
134 if (styleElem.href === newHref) {
135 return;
136 }
137
138 let found = false;
139 if (savedHref.length === 0) {
140 onEachLazy(document.getElementsByTagName("link"), el => {
141 savedHref.push(el.href);
142 });
143 }
144 onEach(savedHref, el => {
145 if (el === newHref) {
146 found = true;
147 return true;
148 }
149 });
150 if (found) {
151 styleElem.href = newHref;
152 }
153 }
154
155 // This function is called from "main.js".
156 // eslint-disable-next-line no-unused-vars
157 function useSystemTheme(value) {
158 if (value === undefined) {
159 value = true;
160 }
161
162 updateLocalStorage("use-system-theme", value);
163
164 // update the toggle if we're on the settings page
165 const toggle = document.getElementById("use-system-theme");
166 if (toggle && toggle instanceof HTMLInputElement) {
167 toggle.checked = value;
168 }
169 }
170
171 const updateSystemTheme = (function () {
172 if (!window.matchMedia) {
173 // fallback to the CSS computed value
174 return () => {
175 const cssTheme = getComputedStyle(document.documentElement)
176 .getPropertyValue("content");
177
178 switchTheme(
179 window.currentTheme,
180 window.mainTheme,
181 JSON.parse(cssTheme) || "light",
182 true
183 );
184 };
185 }
186
187 // only listen to (prefers-color-scheme: dark) because light is the default
188 const mql = window.matchMedia("(prefers-color-scheme: dark)");
189
190 function handlePreferenceChange(mql) {
191 const use = theme => {
192 switchTheme(window.currentTheme, window.mainTheme, theme, true);
193 };
194 // maybe the user has disabled the setting in the meantime!
195 if (getSettingValue("use-system-theme") !== "false") {
196 const lightTheme = getSettingValue("preferred-light-theme") || "light";
197 const darkTheme = getSettingValue("preferred-dark-theme") || "dark";
198
199 if (mql.matches) {
200 use(darkTheme);
201 } else {
202 // prefers a light theme, or has no preference
203 use(lightTheme);
204 }
205 // note: we save the theme so that it doesn't suddenly change when
206 // the user disables "use-system-theme" and reloads the page or
207 // navigates to another page
208 } else {
209 use(getSettingValue("theme"));
210 }
211 }
212
213 mql.addListener(handlePreferenceChange);
214
215 return () => {
216 handlePreferenceChange(mql);
217 };
218 })();
219
220 function switchToSavedTheme() {
221 switchTheme(
222 window.currentTheme,
223 window.mainTheme,
224 getSettingValue("theme") || "light",
225 false
226 );
227 }
228
229 if (getSettingValue("use-system-theme") !== "false" && window.matchMedia) {
230 // update the preferred dark theme if the user is already using a dark theme
231 // See https://github.com/rust-lang/rust/pull/77809#issuecomment-707875732
232 if (getSettingValue("use-system-theme") === null
233 && getSettingValue("preferred-dark-theme") === null
234 && darkThemes.indexOf(localStoredTheme) >= 0) {
235 updateLocalStorage("preferred-dark-theme", localStoredTheme);
236 }
237
238 // call the function to initialize the theme at least once!
239 updateSystemTheme();
240 } else {
241 switchToSavedTheme();
242 }
243
244 // If we navigate away (for example to a settings page), and then use the back or
245 // forward button to get back to a page, the theme may have changed in the meantime.
246 // But scripts may not be re-loaded in such a case due to the bfcache
247 // (https://web.dev/bfcache/). The "pageshow" event triggers on such navigations.
248 // Use that opportunity to update the theme.
249 // We use a setTimeout with a 0 timeout here to put the change on the event queue.
250 // For some reason, if we try to change the theme while the `pageshow` event is
251 // running, it sometimes fails to take effect. The problem manifests on Chrome,
252 // specifically when talking to a remote website with no caching.
253 window.addEventListener("pageshow", ev => {
254 if (ev.persisted) {
255 setTimeout(switchToSavedTheme, 0);
256 }
257 });