]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/static/js/storage.js
Rollup merge of #93966 - rkuhn:patch-1, r=tmandry
[rust.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 function updateLocalStorage(name, value) {
104     try {
105         window.localStorage.setItem("rustdoc-" + name, value);
106     } catch (e) {
107         // localStorage is not accessible, do nothing
108     }
109 }
110
111 function getCurrentValue(name) {
112     try {
113         return window.localStorage.getItem("rustdoc-" + name);
114     } catch (e) {
115         return null;
116     }
117 }
118
119 function switchTheme(styleElem, mainStyleElem, newTheme, saveTheme) {
120     const newHref = mainStyleElem.href.replace(
121         /\/rustdoc([^/]*)\.css/, "/" + newTheme + "$1" + ".css");
122
123     // If this new value comes from a system setting or from the previously
124     // saved theme, no need to save it.
125     if (saveTheme) {
126         updateLocalStorage("theme", newTheme);
127     }
128
129     if (styleElem.href === newHref) {
130         return;
131     }
132
133     let found = false;
134     if (savedHref.length === 0) {
135         onEachLazy(document.getElementsByTagName("link"), el => {
136             savedHref.push(el.href);
137         });
138     }
139     onEach(savedHref, el => {
140         if (el === newHref) {
141             found = true;
142             return true;
143         }
144     });
145     if (found) {
146         styleElem.href = newHref;
147     }
148 }
149
150 // This function is called from "main.js".
151 // eslint-disable-next-line no-unused-vars
152 function useSystemTheme(value) {
153     if (value === undefined) {
154         value = true;
155     }
156
157     updateLocalStorage("use-system-theme", value);
158
159     // update the toggle if we're on the settings page
160     const toggle = document.getElementById("use-system-theme");
161     if (toggle && toggle instanceof HTMLInputElement) {
162         toggle.checked = value;
163     }
164 }
165
166 const updateSystemTheme = (function () {
167     if (!window.matchMedia) {
168         // fallback to the CSS computed value
169         return () => {
170             const cssTheme = getComputedStyle(document.documentElement)
171                 .getPropertyValue("content");
172
173             switchTheme(
174                 window.currentTheme,
175                 window.mainTheme,
176                 JSON.parse(cssTheme) || "light",
177                 true
178             );
179         };
180     }
181
182     // only listen to (prefers-color-scheme: dark) because light is the default
183     const mql = window.matchMedia("(prefers-color-scheme: dark)");
184
185     function handlePreferenceChange(mql) {
186         const use = theme => {
187             switchTheme(window.currentTheme, window.mainTheme, theme, true);
188         };
189         // maybe the user has disabled the setting in the meantime!
190         if (getSettingValue("use-system-theme") !== "false") {
191             const lightTheme = getSettingValue("preferred-light-theme") || "light";
192             const darkTheme = getSettingValue("preferred-dark-theme") || "dark";
193
194             if (mql.matches) {
195                 use(darkTheme);
196             } else {
197                 // prefers a light theme, or has no preference
198                 use(lightTheme);
199             }
200             // note: we save the theme so that it doesn't suddenly change when
201             // the user disables "use-system-theme" and reloads the page or
202             // navigates to another page
203         } else {
204             use(getSettingValue("theme"));
205         }
206     }
207
208     mql.addListener(handlePreferenceChange);
209
210     return () => {
211         handlePreferenceChange(mql);
212     };
213 })();
214
215 function switchToSavedTheme() {
216     switchTheme(
217         window.currentTheme,
218         window.mainTheme,
219         getSettingValue("theme") || "light",
220         false
221     );
222 }
223
224 if (getSettingValue("use-system-theme") !== "false" && window.matchMedia) {
225     // update the preferred dark theme if the user is already using a dark theme
226     // See https://github.com/rust-lang/rust/pull/77809#issuecomment-707875732
227     if (getSettingValue("use-system-theme") === null
228         && getSettingValue("preferred-dark-theme") === null
229         && darkThemes.indexOf(localStoredTheme) >= 0) {
230         updateLocalStorage("preferred-dark-theme", localStoredTheme);
231     }
232
233     // call the function to initialize the theme at least once!
234     updateSystemTheme();
235 } else {
236     switchToSavedTheme();
237 }
238
239 // If we navigate away (for example to a settings page), and then use the back or
240 // forward button to get back to a page, the theme may have changed in the meantime.
241 // But scripts may not be re-loaded in such a case due to the bfcache
242 // (https://web.dev/bfcache/). The "pageshow" event triggers on such navigations.
243 // Use that opportunity to update the theme.
244 // We use a setTimeout with a 0 timeout here to put the change on the event queue.
245 // For some reason, if we try to change the theme while the `pageshow` event is
246 // running, it sometimes fails to take effect. The problem manifests on Chrome,
247 // specifically when talking to a remote website with no caching.
248 window.addEventListener("pageshow", ev => {
249     if (ev.persisted) {
250         setTimeout(switchToSavedTheme, 0);
251     }
252 });