Remove event listeners when no longer needed to prevent memory leaks
elem.addEventListener("change", () => {
1// Local js definitions:2/* global getSettingValue, updateLocalStorage, updateTheme */3/* global addClass, removeClass, onEach, onEachLazy */4/* global MAIN_ID, getVar, nonnull */56"use strict";78(function() {9 const isSettingsPage = window.location.pathname.endsWith("/settings.html");1011 /**12 * @overload {"theme"|"preferred-dark-theme"|"preferred-light-theme"}13 * @param {string} settingName14 * @param {string} value15 * @returns16 * @param {string} settingName17 * @param {string|boolean} value18 */19 function changeSetting(settingName, value) {20 if (settingName === "theme") {21 const useSystem = value === "system preference" ? "true" : "false";22 updateLocalStorage("use-system-theme", useSystem);23 }24 updateLocalStorage(settingName, "" + value);2526 switch (settingName) {27 case "theme":28 case "preferred-dark-theme":29 case "preferred-light-theme":30 updateTheme();31 updateLightAndDark();32 break;33 case "line-numbers":34 if (value === true) {35 const f = window.rustdoc_add_line_numbers_to_examples;36 if (f !== undefined) {37 f();38 }39 } else {40 const f = window.rustdoc_remove_line_numbers_from_examples;41 if (f !== undefined) {42 f();43 }44 }45 break;46 case "sans-serif-fonts":47 case "hide-sidebar":48 case "hide-toc":49 case "hide-modnav":50 case "word-wrap-source-code":51 case "hide-deprecated-items":52 if (value === true) {53 addClass(document.documentElement, settingName);54 } else {55 removeClass(document.documentElement, settingName);56 }57 }58 }5960 function showLightAndDark() {61 removeClass(document.getElementById("preferred-light-theme"), "hidden");62 removeClass(document.getElementById("preferred-dark-theme"), "hidden");63 }6465 function hideLightAndDark() {66 addClass(document.getElementById("preferred-light-theme"), "hidden");67 addClass(document.getElementById("preferred-dark-theme"), "hidden");68 }6970 function updateLightAndDark() {71 const useSystem = getSettingValue("use-system-theme");72 if (useSystem === "true" || (useSystem === null && getSettingValue("theme") === null)) {73 showLightAndDark();74 } else {75 hideLightAndDark();76 }77 }7879 /**80 * @param {HTMLElement} settingsElement81 */82 function setEvents(settingsElement) {83 updateLightAndDark();84 onEachLazy(settingsElement.querySelectorAll("input[type=\"checkbox\"]"), toggle => {85 const settingId = toggle.id;86 const settingValue = getSettingValue(settingId);87 if (settingValue !== null) {88 toggle.checked = settingValue === "true";89 }90 toggle.onchange = () => {91 changeSetting(toggle.id, toggle.checked);92 };93 });94 onEachLazy(95 settingsElement.querySelectorAll("input[type=\"radio\"]"),96 /** @param {HTMLInputElement} elem */97 elem => {98 const settingId = elem.name;99 let settingValue = getSettingValue(settingId);100 if (settingId === "theme") {101 const useSystem = getSettingValue("use-system-theme");102 if (useSystem === "true" || settingValue === null) {103 // "light" is the default theme104 settingValue = useSystem === "false" ? "light" : "system preference";105 }106 }107 if (settingValue !== null && settingValue !== "null") {108 elem.checked = settingValue === elem.value;109 }110 elem.addEventListener("change", () => {111 changeSetting(elem.name, elem.value);112 });113 },114 );115 }116117 /**118 * This function builds the sections inside the "settings page". It takes a `settings` list119 * as argument which describes each setting and how to render it. It returns a string120 * representing the raw HTML.121 *122 * @param {Array<rustdoc.Setting>} settings123 *124 * @return {string}125 */126 function buildSettingsPageSections(settings) {127 let output = "";128129 for (const setting of settings) {130 const js_data_name = setting["js_name"];131 const setting_name = setting["name"];132133 if (setting["options"] !== undefined) {134 // This is a select setting.135 output += `\136<div class="setting-line" id="${js_data_name}">137 <div class="setting-radio-name">${setting_name}</div>138 <div class="setting-radio-choices">`;139 onEach(setting["options"], option => {140 const checked = option === setting["default"] ? " checked" : "";141 const full = `${js_data_name}-${option.replace(/ /g,"-")}`;142143 output += `\144 <label for="${full}" class="setting-radio">145 <input type="radio" name="${js_data_name}"146 id="${full}" value="${option}"${checked}>147 <span>${option}</span>148 </label>`;149 });150 output += `\151 </div>152</div>`;153 } else {154 // This is a checkbox toggle.155 const checked = setting["default"] === true ? " checked" : "";156 output += `\157<div class="setting-line">\158 <label class="setting-check">\159 <input type="checkbox" id="${js_data_name}"${checked}>\160 <span>${setting_name}</span>\161 </label>\162</div>`;163 }164 }165 return output;166 }167168 /**169 * This function builds the "settings page" and returns the generated HTML element.170 *171 * @return {HTMLElement}172 */173 function buildSettingsPage() {174 const theme_list = getVar("themes");175 const theme_names = (theme_list === null ? "" : theme_list)176 .split(",").filter(t => t);177 theme_names.push("light", "dark", "ayu");178179 const settings = [180 {181 "name": "Theme",182 "js_name": "theme",183 "default": "system preference",184 "options": theme_names.concat("system preference"),185 },186 {187 "name": "Preferred light theme",188 "js_name": "preferred-light-theme",189 "default": "light",190 "options": theme_names,191 },192 {193 "name": "Preferred dark theme",194 "js_name": "preferred-dark-theme",195 "default": "dark",196 "options": theme_names,197 },198 {199 "name": "Auto-hide item contents for large items",200 "js_name": "auto-hide-large-items",201 "default": true,202 },203 {204 "name": "Auto-hide item methods' documentation",205 "js_name": "auto-hide-method-docs",206 "default": false,207 },208 {209 "name": "Auto-hide trait implementation documentation",210 "js_name": "auto-hide-trait-implementations",211 "default": false,212 },213 {214 "name": "Directly go to item in search if there is only one result",215 "js_name": "go-to-only-result",216 "default": false,217 },218 {219 "name": "Show line numbers on code examples",220 "js_name": "line-numbers",221 "default": false,222 },223 {224 "name": "Hide persistent navigation bar",225 "js_name": "hide-sidebar",226 "default": false,227 },228 {229 "name": "Hide table of contents",230 "js_name": "hide-toc",231 "default": false,232 },233 {234 "name": "Hide module navigation",235 "js_name": "hide-modnav",236 "default": false,237 },238 {239 "name": "Disable keyboard shortcuts",240 "js_name": "disable-shortcuts",241 "default": false,242 },243 {244 "name": "Use sans serif fonts",245 "js_name": "sans-serif-fonts",246 "default": false,247 },248 {249 "name": "Word wrap source code",250 "js_name": "word-wrap-source-code",251 "default": false,252 },253 {254 "name": "Hide deprecated items",255 "js_name": "hide-deprecated-items",256 "default": false,257 },258 ];259260 // Then we build the DOM.261 const elementKind = isSettingsPage ? "section" : "div";262 const innerHTML = `<div class="settings">${buildSettingsPageSections(settings)}</div>`;263 const el = document.createElement(elementKind);264 el.id = "settings";265 if (!isSettingsPage) {266 el.className = "popover";267 }268 el.innerHTML = innerHTML;269270 if (isSettingsPage) {271 const mainElem = document.getElementById(MAIN_ID);272 if (mainElem !== null) {273 mainElem.appendChild(el);274 }275 } else {276 el.setAttribute("tabindex", "-1");277 onEachLazy(document.querySelectorAll(".settings-menu"), menu => {278 if (menu.offsetWidth !== 0) {279 menu.appendChild(el);280 return true;281 }282 });283 }284 return el;285 }286287 const settingsMenu = buildSettingsPage();288289 function displaySettings() {290 settingsMenu.style.display = "";291 onEachLazy(document.querySelectorAll(".settings-menu"), menu => {292 if (menu.offsetWidth !== 0) {293 if (!menu.contains(settingsMenu) && settingsMenu.parentElement) {294 settingsMenu.parentElement.removeChild(settingsMenu);295 menu.appendChild(settingsMenu);296 }297 return true;298 }299 });300 onEachLazy(settingsMenu.querySelectorAll("input[type='checkbox']"), el => {301 const val = getSettingValue(el.id);302 const checked = val === "true";303 if (checked !== el.checked && val !== null) {304 el.checked = checked;305 }306 });307 }308309 /**310 * @param {FocusEvent} event311 */312 function settingsBlurHandler(event) {313 const isInPopover = onEachLazy(314 document.querySelectorAll(".settings-menu, .help-menu"),315 menu => {316 return menu.contains(document.activeElement) || menu.contains(event.relatedTarget);317 },318 );319 if (!isInPopover) {320 window.hidePopoverMenus();321 }322 }323324 if (!isSettingsPage) {325 // We replace the existing "onclick" callback.326 const settingsMenu = nonnull(document.getElementById("settings"));327 onEachLazy(document.querySelectorAll(".settings-menu"), settingsButton => {328 /** @param {MouseEvent} event */329 settingsButton.querySelector("a").onclick = event => {330 if (!(event.target instanceof Element) || settingsMenu.contains(event.target)) {331 return;332 }333 event.preventDefault();334 const shouldDisplaySettings = settingsMenu.style.display === "none";335336 window.hideAllModals(false);337 if (shouldDisplaySettings) {338 displaySettings();339 }340 };341 settingsButton.onblur = settingsBlurHandler;342 settingsButton.querySelector("a").onblur = settingsBlurHandler;343 });344 onEachLazy(settingsMenu.querySelectorAll("input"), el => {345 el.onblur = settingsBlurHandler;346 });347 settingsMenu.onblur = settingsBlurHandler;348 }349350 // We now wait a bit for the web browser to end re-computing the DOM...351 setTimeout(() => {352 setEvents(settingsMenu);353 // The setting menu is already displayed if we're on the settings page.354 if (!isSettingsPage) {355 displaySettings();356 }357 onEachLazy(document.querySelectorAll(".settings-menu"), settingsButton => {358 removeClass(settingsButton, "rotate");359 });360 }, 0);361})();
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.