Remove event listeners when no longer needed to prevent memory leaks
search_input.addEventListener("focus", () => {
1// Local js definitions:2/* global addClass, getSettingValue, hasClass, updateLocalStorage */3/* global onEachLazy, removeClass, getVar, nonnull */45"use strict";67// The amount of time that the cursor must remain still over a hover target before8// revealing a tooltip.9//10// https://www.nngroup.com/articles/timing-exposing-content/11window.RUSTDOC_TOOLTIP_HOVER_MS = 300;12window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS = 450;1314/**15 * Given a basename (e.g. "storage") and an extension (e.g. ".js"), return a URL16 * for a resource under the root-path, with the resource-suffix.17 *18 * @param {string} basename19 * @param {string} extension20 */21function resourcePath(basename, extension) {22 return getVar("root-path") + basename + getVar("resource-suffix") + extension;23}2425function hideMain() {26 addClass(document.getElementById(MAIN_ID), "hidden");27 const toggle = document.getElementById("toggle-all-docs");28 if (toggle) {29 toggle.setAttribute("disabled", "disabled");30 }31}3233function showMain() {34 const main = document.getElementById(MAIN_ID);35 if (!main) {36 return;37 }38 removeClass(main, "hidden");39 const mainHeading = main.querySelector(".main-heading");40 if (mainHeading && window.searchState.rustdocToolbar) {41 if (window.searchState.rustdocToolbar.parentElement) {42 window.searchState.rustdocToolbar.parentElement.removeChild(43 window.searchState.rustdocToolbar,44 );45 }46 mainHeading.appendChild(window.searchState.rustdocToolbar);47 }48 const toggle = document.getElementById("toggle-all-docs");49 if (toggle) {50 toggle.removeAttribute("disabled");51 }52}5354window.rootPath = getVar("root-path");55window.currentCrate = getVar("current-crate");5657/**58 * Gets the human-readable string for the virtual-key code of the59 * given KeyboardEvent, ev.60 *61 * This function is meant as a polyfill for KeyboardEvent#key,62 * since it is not supported in IE 11 or Chrome for Android. We also test for63 * KeyboardEvent#keyCode because the handleShortcut handler is64 * also registered for the keydown event, because Blink doesn't fire65 * keypress on hitting the Escape key.66 *67 * So I guess you could say things are getting pretty interoperable.68 *69 * @param {KeyboardEvent} ev70 * @returns {string}71 */72function getVirtualKey(ev) {73 if ("key" in ev && typeof ev.key !== "undefined") {74 return ev.key;75 }7677 const c = ev.charCode || ev.keyCode;78 if (c === 27) {79 return "Escape";80 }81 return String.fromCharCode(c);82}8384const MAIN_ID = "main-content";85const ALTERNATIVE_DISPLAY_ID = "alternative-display";86const NOT_DISPLAYED_ID = "not-displayed";8788// Returns the current URL without any query parameter or hash.89function getNakedUrl() {90 return window.location.href.split("?")[0].split("#")[0];91}9293/**94 * This function inserts `newNode` after `referenceNode`. It doesn't work if `referenceNode`95 * doesn't have a parent node.96 *97 * @param {HTMLElement} newNode98 * @param {HTMLElement & { parentNode: HTMLElement }} referenceNode99 */100function insertAfter(newNode, referenceNode) {101 referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);102}103104/**105 * This function creates a new `<section>` with the given `id` and `classes` if it doesn't already106 * exist.107 *108 * More information about this in `switchDisplayedElement` documentation.109 *110 * @param {string} id111 * @param {string} classes112 */113function getOrCreateSection(id, classes) {114 let el = document.getElementById(id);115116 if (!el) {117 el = document.createElement("section");118 el.id = id;119 el.className = classes;120 // @ts-expect-error121 insertAfter(el, document.getElementById(MAIN_ID));122 }123 return el;124}125126/**127 * Returns the `<section>` element which contains the displayed element.128 *129 * @return {HTMLElement}130 */131function getAlternativeDisplayElem() {132 return getOrCreateSection(ALTERNATIVE_DISPLAY_ID, "content hidden");133}134135/**136 * Returns the `<section>` element which contains the not-displayed elements.137 *138 * @return {HTMLElement}139 */140function getNotDisplayedElem() {141 return getOrCreateSection(NOT_DISPLAYED_ID, "hidden");142}143144/**145 * To nicely switch between displayed "extra" elements (such as search results or settings menu)146 * and to alternate between the displayed and not displayed elements, we hold them in two different147 * `<section>` elements. They work in pair: one holds the hidden elements while the other148 * contains the displayed element (there can be only one at the same time!). So basically, we switch149 * elements between the two `<section>` elements.150 *151 * @param {Element|null} elemToDisplay152 */153function switchDisplayedElement(elemToDisplay) {154 const el = getAlternativeDisplayElem();155156 if (el.children.length > 0) {157 // @ts-expect-error158 getNotDisplayedElem().appendChild(el.firstElementChild);159 }160 if (elemToDisplay === null) {161 addClass(el, "hidden");162 showMain();163 return;164 }165 el.appendChild(elemToDisplay);166 hideMain();167 removeClass(el, "hidden");168169 const mainHeading = elemToDisplay.querySelector(".main-heading");170 if (mainHeading && window.searchState.rustdocToolbar) {171 if (window.searchState.rustdocToolbar.parentElement) {172 window.searchState.rustdocToolbar.parentElement.removeChild(173 window.searchState.rustdocToolbar,174 );175 }176 mainHeading.appendChild(window.searchState.rustdocToolbar);177 }178}179180function browserSupportsHistoryApi() {181 return window.history && typeof window.history.pushState === "function";182}183184/**185 * Download CSS from the web without making it the active stylesheet.186 * We use this in the settings popover so that you don't get FOUC when switching.187 *188 * @param {string} cssUrl189 */190function preLoadCss(cssUrl) {191 // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload192 const link = document.createElement("link");193 link.href = cssUrl;194 link.rel = "preload";195 link.as = "style";196 document.getElementsByTagName("head")[0].appendChild(link);197}198199(function() {200 const isHelpPage = window.location.pathname.endsWith("/help.html");201202 /**203 * Run a JavaScript file asynchronously.204 * @param {string} url205 * @param {function(): any} errorCallback206 */207 function loadScript(url, errorCallback) {208 const script = document.createElement("script");209 script.src = url;210 if (errorCallback !== undefined) {211 script.onerror = errorCallback;212 }213 document.head.append(script);214 }215216 onEachLazy(document.querySelectorAll(".settings-menu"), settingsMenu => {217 /** @param {MouseEvent} event */218 settingsMenu.querySelector("a").onclick = event => {219 if (event.ctrlKey || event.altKey || event.metaKey) {220 return;221 }222 window.hideAllModals(false);223 addClass(settingsMenu, "rotate");224 event.preventDefault();225 // Sending request for the CSS and the JS files at the same time so it will226 // hopefully be loaded when the JS will generate the settings content.227 // @ts-expect-error228 loadScript(getVar("static-root-path") + getVar("settings-js"));229 // Pre-load all theme CSS files, so that switching feels seamless.230 //231 // When loading settings.html as a standalone page, the equivalent HTML is232 // generated in context.rs.233 setTimeout(() => {234 const themes = getVar("themes").split(",");235 for (const theme of themes) {236 // if there are no themes, do nothing237 // "".split(",") == [""]238 if (theme !== "") {239 preLoadCss(getVar("root-path") + theme + ".css");240 }241 }242 }, 0);243 };244 });245246 window.searchState = {247 rustdocToolbar: document.querySelector("rustdoc-toolbar"),248 loadingText: "Loading search results...",249 inputElement: () => {250 let el = document.getElementsByClassName("search-input")[0];251 if (!el) {252 const out = nonnull(nonnull(window.searchState.outputElement()).parentElement);253 const hdr = document.createElement("div");254 hdr.className = "main-heading search-results-main-heading";255 const params = window.searchState.getQueryStringParams();256 const autofocusParam = params.search === "" ? "autofocus" : "";257 hdr.innerHTML = `<nav class="sub">258 <form class="search-form loading">259 <span></span> <!-- This empty span is a hacky fix for Safari: see #93184 -->260 <input261 ${autofocusParam}262 class="search-input"263 name="search"264 aria-label="Run search in the documentation"265 autocomplete="off"266 spellcheck="false"267 placeholder="Type ‘S’ or ‘/’ to search, ‘?’ for more options…"268 type="search">269 </form>270 </nav><div class="search-switcher"></div>`;271 out.insertBefore(hdr, window.searchState.outputElement());272 el = document.getElementsByClassName("search-input")[0];273 }274 if (el instanceof HTMLInputElement) {275 return el;276 }277 return null;278 },279 containerElement: () => {280 let el = document.getElementById("search");281 if (!el) {282 el = document.createElement("section");283 el.id = "search";284 getNotDisplayedElem().appendChild(el);285 }286 return el;287 },288 outputElement: () => {289 const container = window.searchState.containerElement();290 if (!container) {291 return null;292 }293 let el = container.querySelector(".search-out");294 if (!el) {295 el = document.createElement("div");296 el.className = "search-out";297 container.appendChild(el);298 }299 return el;300 },301 title: document.title,302 titleBeforeSearch: document.title,303 timeout: null,304 // On the search screen, so you remain on the last tab you opened.305 //306 // 0 for "In Names"307 // 1 for "In Parameters"308 // 2 for "In Return Types"309 currentTab: 0,310 // tab and back preserves the element that was focused.311 focusedByTab: [null, null, null],312 clearInputTimeout: () => {313 if (window.searchState.timeout !== null) {314 clearTimeout(window.searchState.timeout);315 window.searchState.timeout = null;316 }317 },318 isDisplayed: () => {319 const container = window.searchState.containerElement();320 if (!container) {321 return false;322 }323 return !!container.parentElement && container.parentElement.id ===324 ALTERNATIVE_DISPLAY_ID;325 },326 // Sets the focus on the search bar at the top of the page327 focus: () => {328 const inputElement = window.searchState.inputElement();329 window.searchState.showResults();330 if (inputElement) {331 inputElement.focus();332 // Avoid glitch if something focuses the search button after clicking.333 requestAnimationFrame(() => inputElement.focus());334 }335 },336 // Removes the focus from the search bar.337 defocus: () => {338 nonnull(window.searchState.inputElement()).blur();339 },340 toggle: () => {341 if (window.searchState.isDisplayed()) {342 window.searchState.defocus();343 window.searchState.hideResults();344 } else {345 window.searchState.focus();346 }347 },348 showResults: () => {349 document.title = window.searchState.title;350 if (window.searchState.isDisplayed()) {351 return;352 }353 const search = window.searchState.containerElement();354 switchDisplayedElement(search);355 const btn = document.querySelector("#search-button a");356 if (browserSupportsHistoryApi() && btn instanceof HTMLAnchorElement &&357 window.searchState.getQueryStringParams().search === undefined358 ) {359 history.pushState(null, "", btn.href);360 }361 const btnLabel = document.querySelector("#search-button a span.label");362 if (btnLabel) {363 btnLabel.innerHTML = "Exit";364 }365 },366 removeQueryParameters: () => {367 // We change the document title.368 document.title = window.searchState.titleBeforeSearch;369 if (browserSupportsHistoryApi()) {370 history.replaceState(null, "", getNakedUrl() + window.location.hash);371 }372 },373 hideResults: () => {374 switchDisplayedElement(null);375 // We also remove the query parameter from the URL.376 window.searchState.removeQueryParameters();377 const btnLabel = document.querySelector("#search-button a span.label");378 if (btnLabel) {379 btnLabel.innerHTML = "Search";380 }381 },382 getQueryStringParams: () => {383 /** @type {Object.<any, string>} */384 const params = {};385 window.location.search.substring(1).split("&").386 map(s => {387 // https://github.com/rust-lang/rust/issues/119219388 const pair = s.split("=").map(x => x.replace(/\+/g, " "));389 params[decodeURIComponent(pair[0])] =390 typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]);391 });392 return params;393 },394 setup: () => {395 let searchLoaded = false;396 const search_input = window.searchState.inputElement();397 if (!search_input) {398 return;399 }400 // If you're browsing the nightly docs, the page might need to be refreshed for the401 // search to work because the hash of the JS scripts might have changed.402 function sendSearchForm() {403 // @ts-expect-error404 document.getElementsByClassName("search-form")[0].submit();405 }406 function loadSearch() {407 if (!searchLoaded) {408 searchLoaded = true;409 window.rr_ = data => {410 window.searchIndex = data;411 };412 if (!window.StringdexOnload) {413 window.StringdexOnload = [];414 }415 window.StringdexOnload.push(() => {416 loadScript(417 getVar("static-root-path") + getVar("search-js"),418 sendSearchForm,419 );420 });421 loadScript(getVar("static-root-path") + getVar("stringdex-js"), sendSearchForm);422 loadScript(resourcePath("search.index/root", ".js"), sendSearchForm);423 }424 }425426 search_input.addEventListener("focus", () => {427 loadSearch();428 });429430 const btn = document.getElementById("search-button");431 if (btn) {432 btn.onclick = event => {433 if (event.ctrlKey || event.altKey || event.metaKey) {434 return;435 }436 event.preventDefault();437 window.searchState.toggle();438 loadSearch();439 };440 }441442 // Push and pop states are used to add search results to the browser443 // history.444 if (browserSupportsHistoryApi()) {445 // Store the previous <title> so we can revert back to it later.446 const previousTitle = document.title;447448 window.addEventListener("popstate", e => {449 const params = window.searchState.getQueryStringParams();450 // Revert to the previous title manually since the History451 // API ignores the title parameter.452 document.title = previousTitle;453 // Synchronize search bar with query string state and454 // perform the search. This will empty the bar if there's455 // nothing there, which lets you really go back to a456 // previous state with nothing in the bar.457 const inputElement = window.searchState.inputElement();458 if (params.search !== undefined && inputElement !== null) {459 loadSearch();460 inputElement.value = params.search;461 // Some browsers fire "onpopstate" for every page load462 // (Chrome), while others fire the event only when actually463 // popping a state (Firefox), which is why search() is464 // called both here and at the end of the startSearch()465 // function.466 e.preventDefault();467 window.searchState.showResults();468 if (params.search === "") {469 window.searchState.focus();470 }471 } else {472 // When browsing back from search results the main page473 // visibility must be reset.474 window.searchState.hideResults();475 }476 });477 }478479 // This is required in firefox to avoid this problem: Navigating to a search result480 // with the keyboard, hitting enter, and then hitting back would take you back to481 // the doc page, rather than the search that should overlay it.482 // This was an interaction between the back-forward cache and our handlers483 // that try to sync state between the URL and the search input. To work around it,484 // do a small amount of re-init on page show.485 window.onpageshow = () => {486 const inputElement = window.searchState.inputElement();487 const qSearch = window.searchState.getQueryStringParams().search;488 if (qSearch !== undefined && inputElement !== null) {489 if (inputElement.value === "") {490 inputElement.value = qSearch;491 }492 window.searchState.showResults();493 if (qSearch === "") {494 loadSearch();495 window.searchState.focus();496 }497 } else {498 window.searchState.hideResults();499 }500 };501502 const params = window.searchState.getQueryStringParams();503 if (params.search !== undefined) {504 window.searchState.setLoadingSearch();505 loadSearch();506 }507 },508 setLoadingSearch: () => {509 const search = window.searchState.outputElement();510 nonnull(search).innerHTML = "<h3 class=\"search-loading\">" +511 window.searchState.loadingText + "</h3>";512 window.searchState.showResults();513 },514 descShards: new Map(),515 loadDesc: async function({descShard, descIndex}) {516 if (descShard.promise === null) {517 descShard.promise = new Promise((resolve, reject) => {518 // The `resolve` callback is stored in the `descShard`519 // object, which is itself stored in `this.descShards` map.520 // It is called in `loadedDescShard` by the521 // search.desc script.522 descShard.resolve = resolve;523 const ds = descShard;524 const fname = `${ds.crate}-desc-${ds.shard}-`;525 const url = resourcePath(526 `search.desc/${descShard.crate}/${fname}`,527 ".js",528 );529 loadScript(url, reject);530 });531 }532 const list = await descShard.promise;533 return list[descIndex];534 },535 loadedDescShard: function(crate, shard, data) {536 // If loadedDescShard gets called, then the library must have been declared.537 // @ts-expect-error538 this.descShards.get(crate)[shard].resolve(data.split("\n"));539 },540 };541542 const toggleAllDocsId = "toggle-all-docs";543 let savedHash = "";544545 /**546 * @param {HashChangeEvent|null} ev547 */548 function handleHashes(ev) {549 if (ev !== null && window.searchState.isDisplayed() && ev.newURL) {550 // This block occurs when clicking on an element in the navbar while551 // in a search.552 switchDisplayedElement(null);553 const hash = ev.newURL.slice(ev.newURL.indexOf("#") + 1);554 if (browserSupportsHistoryApi()) {555 // `window.location.search`` contains all the query parameters, not just `search`.556 history.replaceState(null, "",557 getNakedUrl() + window.location.search + "#" + hash);558 }559 const elem = document.getElementById(hash);560 if (elem) {561 elem.scrollIntoView();562 }563 }564 // This part is used in case an element is not visible.565 const pageId = window.location.hash.replace(/^#/, "");566 if (savedHash !== pageId) {567 savedHash = pageId;568 if (pageId !== "") {569 expandSection(pageId);570 }571 }572 if (savedHash.startsWith("impl-")) {573 // impl-disambiguated links, used by the search engine574 // format: impl-X[-for-Y]/method.WHATEVER575 // turn this into method.WHATEVER[-NUMBER]576 const splitAt = savedHash.indexOf("/");577 if (splitAt !== -1) {578 const implId = savedHash.slice(0, splitAt);579 const assocId = savedHash.slice(splitAt + 1);580 const implElems = document.querySelectorAll(581 `details > summary > section[id^="${implId}"]`,582 );583 onEachLazy(implElems, implElem => {584 const numbered = /^(.+?)-([0-9]+)$/.exec(implElem.id);585 if (implElem.id !== implId && (!numbered || numbered[1] !== implId)) {586 return false;587 }588 return onEachLazy(implElem.parentElement.parentElement.querySelectorAll(589 `[id^="${assocId}"]`),590 item => {591 const numbered = /^(.+?)-([0-9]+)$/.exec(item.id);592 if (item.id === assocId || (numbered && numbered[1] === assocId)) {593 openParentDetails(item);594 item.scrollIntoView();595 // Let the section expand itself before trying to highlight596 setTimeout(() => {597 window.location.replace("#" + item.id);598 }, 0);599 return true;600 }601 },602 );603 });604 }605 }606 }607608 /**609 * @param {HashChangeEvent|null} ev610 */611 function onHashChange(ev) {612 // If we're in mobile mode, we should hide the sidebar in any case.613 hideSidebar();614 handleHashes(ev);615 }616617 /**618 * @param {HTMLElement|null} elem619 */620 function openParentDetails(elem) {621 while (elem) {622 if (elem instanceof HTMLDetailsElement) {623 elem.open = true;624 }625 elem = elem.parentElement;626 }627 }628629 /**630 * @param {string} id631 */632 function expandSection(id) {633 openParentDetails(document.getElementById(id));634 }635636 /**637 * @param {KeyboardEvent} ev638 */639 function handleEscape(ev) {640 window.searchState.clearInputTimeout();641 window.searchState.hideResults();642 ev.preventDefault();643 window.searchState.defocus();644 window.hideAllModals(true); // true = reset focus for tooltips645 }646647 /**648 * @param {KeyboardEvent} ev649 */650 function handleShortcut(ev) {651 // Don't interfere with browser shortcuts652 const disableShortcuts = getSettingValue("disable-shortcuts") === "true";653 if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) {654 return;655 }656657 if (document.activeElement &&658 document.activeElement instanceof HTMLInputElement &&659 document.activeElement.type !== "checkbox" &&660 document.activeElement.type !== "radio") {661 switch (getVirtualKey(ev)) {662 case "Escape":663 handleEscape(ev);664 break;665 }666 } else {667 switch (getVirtualKey(ev)) {668 case "Escape":669 handleEscape(ev);670 break;671672 case "s":673 case "S":674 case "/":675 ev.preventDefault();676 window.searchState.focus();677 break;678679 case "+":680 case "=":681 ev.preventDefault();682 expandAllDocs();683 break;684 case "-":685 ev.preventDefault();686 collapseAllDocs(false);687 break;688 case "_":689 ev.preventDefault();690 collapseAllDocs(true);691 break;692693 case "?":694 showHelp();695 break;696697 default:698 break;699 }700 }701 }702703 document.addEventListener("keypress", handleShortcut);704 document.addEventListener("keydown", handleShortcut);705706 function addSidebarItems() {707 if (!window.SIDEBAR_ITEMS) {708 return;709 }710 const sidebar = document.getElementById("rustdoc-modnav");711712 /**713 * Append to the sidebar a "block" of links - a heading along with a list (`<ul>`) of items.714 *715 * @param {string} shortty - A short type name, like "primitive", "mod", or "macro"716 * @param {string} id - The HTML id of the corresponding section on the module page.717 * @param {string} longty - A long, capitalized, plural name, like "Primitive Types",718 * "Modules", or "Macros".719 */720 function block(shortty, id, longty) {721 // @ts-expect-error722 const filtered = window.SIDEBAR_ITEMS[shortty];723 if (!filtered) {724 return;725 }726727 const modpath = hasClass(document.querySelector(".rustdoc"), "mod") ? "../" : "";728729 const h3 = document.createElement("h3");730 h3.innerHTML = `<a href="${modpath}index.html#${id}">${longty}</a>`;731 const ul = document.createElement("ul");732 ul.className = "block " + shortty;733734 for (const name of filtered) {735 let path;736 if (shortty === "mod") {737 path = `${modpath}${name}/index.html`;738 } else {739 path = `${modpath}${shortty}.${name}.html`;740 }741 let current_page = document.location.href.toString();742 if (current_page.endsWith("/")) {743 current_page += "index.html";744 }745 const link = document.createElement("a");746 link.href = path;747 link.textContent = name;748 const li = document.createElement("li");749 // Don't "optimize" this to just use `path`.750 // We want the browser to normalize this into an absolute URL.751 if (link.href === current_page) {752 li.classList.add("current");753 }754 li.appendChild(link);755 ul.appendChild(li);756 }757 // @ts-expect-error758 sidebar.appendChild(h3);759 // @ts-expect-error760 sidebar.appendChild(ul);761 }762763 if (sidebar) {764 // keep this synchronized with ItemSection::ALL in html/render/mod.rs765 // Re-exports aren't shown here, because they don't have child pages766 //block("reexport", "reexports", "Re-exports");767 block("primitive", "primitives", "Primitive Types");768 block("mod", "modules", "Modules");769 block("macro", "macros", "Macros");770 block("struct", "structs", "Structs");771 block("enum", "enums", "Enums");772 block("constant", "constants", "Constants");773 block("static", "static", "Statics");774 block("trait", "traits", "Traits");775 block("fn", "functions", "Functions");776 block("type", "types", "Type Aliases");777 block("union", "unions", "Unions");778 // No point, because these items don't appear in modules779 //block("impl", "impls", "Implementations");780 //block("tymethod", "tymethods", "Type Methods");781 //block("method", "methods", "Methods");782 //block("structfield", "fields", "Fields");783 //block("variant", "variants", "Variants");784 //block("associatedtype", "associated-types", "Associated Types");785 //block("associatedconstant", "associated-consts", "Associated Constants");786 block("foreigntype", "foreign-types", "Foreign Types");787 block("keyword", "keywords", "Keywords");788 block("attribute", "attributes", "Attributes");789 block("attr", "attributes", "Attribute Macros");790 block("derive", "derives", "Derive Macros");791 block("traitalias", "trait-aliases", "Trait Aliases");792 }793 }794795 // <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+trait.impl&type=code>796 window.register_implementors = imp => {797 /** Takes an ID as input and returns a list of two elements. The first element is the DOM798 * element with the given ID and the second is the "negative marker", meaning the location799 * between the negative and non-negative impls.800 *801 * @param {string} id: ID of the DOM element.802 *803 * @return {[HTMLElement|null, HTMLElement|null]}804 */805 function implementorsElems(id) {806 const elem = document.getElementById(id);807 return [elem, elem ? elem.querySelector(".negative-marker") : null];808 }809 const implementors = implementorsElems("implementors-list");810 const syntheticImplementors = implementorsElems("synthetic-implementors-list");811 const inlined_types = new Set();812813 const TEXT_IDX = 0;814 const IS_NEG_IDX = 1;815 const SYNTHETIC_IDX = 2;816 const TYPES_IDX = 3;817818 if (syntheticImplementors[0]) {819 // This `inlined_types` variable is used to avoid having the same implementation820 // showing up twice. For example "String" in the "Sync" doc page.821 //822 // By the way, this is only used by and useful for traits implemented automatically823 // (like "Send" and "Sync").824 onEachLazy(syntheticImplementors[0].getElementsByClassName("impl"), el => {825 const aliases = el.getAttribute("data-aliases");826 if (!aliases) {827 return;828 }829 // @ts-expect-error830 aliases.split(",").forEach(alias => {831 inlined_types.add(alias);832 });833 });834 }835836 // @ts-expect-error837 let currentNbImpls = implementors[0].getElementsByClassName("impl").length;838 // @ts-expect-error839 const traitName = document.querySelector(".main-heading h1 > .trait").textContent;840 const baseIdName = "impl-" + traitName + "-";841 const libs = Object.getOwnPropertyNames(imp);842 // We don't want to include impls from this JS file, when the HTML already has them.843 // The current crate should always be ignored. Other crates that should also be844 // ignored are included in the attribute `data-ignore-extern-crates`.845 const script = document846 .querySelector("script[data-ignore-extern-crates]");847 const ignoreExternCrates = new Set(848 // @ts-expect-error849 (script ? script.getAttribute("data-ignore-extern-crates") : "").split(","),850 );851 for (const lib of libs) {852 if (lib === window.currentCrate || ignoreExternCrates.has(lib)) {853 continue;854 }855 const structs = imp[lib];856857 struct_loop:858 for (const struct of structs) {859 const list = struct[SYNTHETIC_IDX] ? syntheticImplementors : implementors;860861 // The types list is only used for synthetic impls.862 // If this changes, `main.js` and `write_shared.rs` both need changed.863 if (struct[SYNTHETIC_IDX]) {864 for (const struct_type of struct[TYPES_IDX]) {865 if (inlined_types.has(struct_type)) {866 continue struct_loop;867 }868 inlined_types.add(struct_type);869 }870 }871872 const code = document.createElement("h3");873 code.innerHTML = struct[TEXT_IDX];874 addClass(code, "code-header");875876 onEachLazy(code.getElementsByTagName("a"), elem => {877 const href = elem.getAttribute("href");878879 if (href && !href.startsWith("#") && !/^(?:[a-z+]+:)?\/\//.test(href)) {880 elem.setAttribute("href", window.rootPath + href);881 }882 });883884 const currentId = baseIdName + currentNbImpls;885 const anchor = document.createElement("a");886 anchor.href = "#" + currentId;887 addClass(anchor, "anchor");888889 const display = document.createElement("div");890 display.id = currentId;891 addClass(display, "impl");892 display.appendChild(anchor);893 display.appendChild(code);894895 // If this is a negative implementor, we put it into the right location (just896 // before the negative impl marker).897 if (struct[IS_NEG_IDX]) {898 // @ts-expect-error899 list[1].before(display);900 } else {901 // @ts-expect-error902 list[0].appendChild(display);903 }904 currentNbImpls += 1;905 }906 }907 if (implementors[0]) {908 implementors[0].classList.add("loaded");909 }910 if (syntheticImplementors[0]) {911 syntheticImplementors[0].classList.add("loaded");912 }913 };914 if (window.pending_implementors) {915 window.register_implementors(window.pending_implementors);916 }917918 /**919 * <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+type.impl&type=code>920 *921 * [RUSTDOCIMPL] type.impl922 *923 * This code inlines implementations into the type alias docs at runtime. It's done at924 * runtime because some crates have many type aliases and many methods, and we don't want925 * to generate *O*`(types*methods)` HTML text. The data inside is mostly HTML fragments,926 * wrapped in JSON.927 *928 * - It only includes docs generated for the current crate. This function accepts an929 * object mapping crate names to the set of impls.930 *931 * - It filters down to the set of applicable impls. The Rust type checker is used to932 * tag each HTML blob with the set of type aliases that can actually use it, so the933 * JS only needs to consult the attached list of type aliases.934 *935 * - It renames the ID attributes, to avoid conflicting IDs in the resulting DOM.936 *937 * - It adds the necessary items to the sidebar. If it's an inherent impl, that means938 * adding methods, associated types, and associated constants. If it's a trait impl,939 * that means adding it to the trait impl sidebar list.940 *941 * - It adds the HTML block itself. If it's an inherent impl, it goes after the type942 * alias's own inherent impls. If it's a trait impl, it goes in the Trait943 * Implementations section.944 *945 * - After processing all of the impls, it sorts the sidebar items by name.946 *947 * @param {rustdoc.TypeImpls} imp948 */949 window.register_type_impls = imp => {950 // @ts-expect-error951 if (!imp || !imp[window.currentCrate]) {952 return;953 }954 window.pending_type_impls = undefined;955 const idMap = new Map();956957 let implementations = document.getElementById("implementations-list");958 let trait_implementations = document.getElementById("trait-implementations-list");959 let trait_implementations_header = document.getElementById("trait-implementations");960961 // We want to include the current type alias's impls, and no others.962 const script = document.querySelector("script[data-self-path]");963 const selfPath = script ? script.getAttribute("data-self-path") : null;964965 // These sidebar blocks need filled in, too.966 const mainContent = nonnull(document.querySelector("#main-content"));967 const sidebarSection = nonnull(document.querySelector(".sidebar section"));968 let methods = document.querySelector(".sidebar .block.method");969 let associatedTypes = document.querySelector(".sidebar .block.associatedtype");970 let associatedConstants = document.querySelector(".sidebar .block.associatedconstant");971 let sidebarTraitList = document.querySelector(".sidebar .block.trait-implementation");972973 for (const impList of imp[nonnull(window.currentCrate)]) {974 const types = impList.slice(2);975 const text = impList[0];976 const traitName = impList[1];977 const isTrait = typeof traitName === "string";978 // @ts-expect-error979 if (types.indexOf(selfPath) === -1) {980 continue;981 }982 let outputList = isTrait ? trait_implementations : implementations;983 if (outputList === null) {984 const outputListName = isTrait ? "Trait Implementations" : "Implementations";985 const outputListId = isTrait ?986 "trait-implementations-list" :987 "implementations-list";988 const outputListHeaderId = isTrait ? "trait-implementations" : "implementations";989 const outputListHeader = document.createElement("h2");990 outputListHeader.id = outputListHeaderId;991 outputListHeader.innerText = outputListName;992 outputList = document.createElement("div");993 outputList.id = outputListId;994 if (isTrait) {995 const link = document.createElement("a");996 link.href = `#${outputListHeaderId}`;997 link.innerText = "Trait Implementations";998 const h = document.createElement("h3");999 h.appendChild(link);1000 trait_implementations = outputList;1001 trait_implementations_header = outputListHeader;1002 sidebarSection.appendChild(h);1003 sidebarTraitList = document.createElement("ul");1004 sidebarTraitList.className = "block trait-implementation";1005 sidebarSection.appendChild(sidebarTraitList);1006 mainContent.appendChild(outputListHeader);1007 mainContent.appendChild(outputList);1008 } else {1009 implementations = outputList;1010 if (trait_implementations) {1011 mainContent.insertBefore(outputListHeader, trait_implementations_header);1012 mainContent.insertBefore(outputList, trait_implementations_header);1013 } else {1014 mainContent.appendChild(outputListHeader);1015 mainContent.appendChild(outputList);1016 }1017 }1018 }1019 const template = document.createElement("template");1020 template.innerHTML = text;10211022 onEachLazy(template.content.querySelectorAll("a"), elem => {1023 const href = elem.getAttribute("href");10241025 if (href && !href.startsWith("#") && !/^(?:[a-z+]+:)?\/\//.test(href)) {1026 elem.setAttribute("href", window.rootPath + href);1027 }1028 });1029 onEachLazy(template.content.querySelectorAll("[id]"), el => {1030 let i = 0;1031 if (idMap.has(el.id)) {1032 i = idMap.get(el.id);1033 } else if (document.getElementById(el.id)) {1034 i = 1;1035 while (document.getElementById(`${el.id}-${2 * i}`)) {1036 i = 2 * i;1037 }1038 while (document.getElementById(`${el.id}-${i}`)) {1039 i += 1;1040 }1041 }1042 if (i !== 0) {1043 const oldHref = `#${el.id}`;1044 const newHref = `#${el.id}-${i}`;1045 el.id = `${el.id}-${i}`;1046 onEachLazy(template.content.querySelectorAll("a[href]"), link => {1047 if (link.getAttribute("href") === oldHref) {1048 link.href = newHref;1049 }1050 });1051 }1052 idMap.set(el.id, i + 1);1053 });1054 const templateAssocItems = template.content.querySelectorAll("section.tymethod, " +1055 "section.method, section.associatedtype, section.associatedconstant");1056 if (isTrait) {1057 const li = document.createElement("li");1058 const a = document.createElement("a");1059 a.href = `#${nonnull(template.content.querySelector(".impl")).id}`;1060 a.textContent = traitName;1061 li.appendChild(a);1062 // @ts-expect-error1063 sidebarTraitList.append(li);1064 } else {1065 onEachLazy(templateAssocItems, item => {1066 let block = hasClass(item, "associatedtype") ? associatedTypes : (1067 hasClass(item, "associatedconstant") ? associatedConstants : (1068 methods));1069 if (!block) {1070 const blockTitle = hasClass(item, "associatedtype") ? "Associated Types" : (1071 hasClass(item, "associatedconstant") ? "Associated Constants" : (1072 "Methods"));1073 const blockClass = hasClass(item, "associatedtype") ? "associatedtype" : (1074 hasClass(item, "associatedconstant") ? "associatedconstant" : (1075 "method"));1076 const blockHeader = document.createElement("h3");1077 const blockLink = document.createElement("a");1078 blockLink.href = "#implementations";1079 blockLink.innerText = blockTitle;1080 blockHeader.appendChild(blockLink);1081 block = document.createElement("ul");1082 block.className = `block ${blockClass}`;1083 const insertionReference = methods || sidebarTraitList;1084 if (insertionReference) {1085 const insertionReferenceH = insertionReference.previousElementSibling;1086 sidebarSection.insertBefore(blockHeader, insertionReferenceH);1087 sidebarSection.insertBefore(block, insertionReferenceH);1088 } else {1089 sidebarSection.appendChild(blockHeader);1090 sidebarSection.appendChild(block);1091 }1092 if (hasClass(item, "associatedtype")) {1093 associatedTypes = block;1094 } else if (hasClass(item, "associatedconstant")) {1095 associatedConstants = block;1096 } else {1097 methods = block;1098 }1099 }1100 const li = document.createElement("li");1101 const a = document.createElement("a");1102 a.innerText = item.id.split("-")[0].split(".")[1];1103 a.href = `#${item.id}`;1104 li.appendChild(a);1105 block.appendChild(li);1106 });1107 }1108 outputList.appendChild(template.content);1109 }11101111 for (const list of [methods, associatedTypes, associatedConstants, sidebarTraitList]) {1112 if (!list) {1113 continue;1114 }1115 const newChildren = Array.prototype.slice.call(list.children);1116 newChildren.sort((a, b) => {1117 const aI = a.innerText;1118 const bI = b.innerText;1119 return aI < bI ? -1 :1120 aI > bI ? 1 :1121 0;1122 });1123 list.replaceChildren(...newChildren);1124 }1125 };1126 if (window.pending_type_impls) {1127 window.register_type_impls(window.pending_type_impls);1128 }11291130 function addSidebarCrates() {1131 // @ts-expect-error1132 if (!window.ALL_CRATES) {1133 return;1134 }1135 const sidebarElems = document.getElementById("rustdoc-modnav");1136 if (!sidebarElems) {1137 return;1138 }1139 // Draw a convenient sidebar of known crates if we have a listing1140 const h3 = document.createElement("h3");1141 h3.innerHTML = "Crates";1142 const ul = document.createElement("ul");1143 ul.className = "block crate";11441145 // @ts-expect-error1146 for (const crate of window.ALL_CRATES) {1147 const link = document.createElement("a");1148 link.href = window.rootPath + crate + "/index.html";1149 link.textContent = crate;11501151 const li = document.createElement("li");1152 if (window.rootPath !== "./" && crate === window.currentCrate) {1153 li.className = "current";1154 }1155 li.appendChild(link);1156 ul.appendChild(li);1157 }1158 sidebarElems.appendChild(h3);1159 sidebarElems.appendChild(ul);1160 }11611162 function expandAllDocs() {1163 const innerToggle = document.getElementById(toggleAllDocsId);1164 removeClass(innerToggle, "will-expand");1165 onEachLazy(document.getElementsByClassName("toggle"), e => {1166 if (!hasClass(e, "type-contents-toggle") && !hasClass(e, "more-examples-toggle")) {1167 e.open = true;1168 }1169 });1170 // @ts-expect-error1171 innerToggle.children[0].innerText = "Summary";1172 }11731174 /**1175 * @param {boolean} collapseImpls - also collapse impl blocks if set to true1176 */1177 function collapseAllDocs(collapseImpls) {1178 const innerToggle = document.getElementById(toggleAllDocsId);1179 addClass(innerToggle, "will-expand");1180 onEachLazy(document.getElementsByClassName("toggle"), e => {1181 if ((collapseImpls || e.parentNode.id !== "implementations-list") ||1182 (!hasClass(e, "implementors-toggle") &&1183 !hasClass(e, "type-contents-toggle"))1184 ) {1185 e.open = false;1186 }1187 });1188 // @ts-expect-error1189 innerToggle.children[0].innerText = "Show all";1190 }11911192 /**1193 * @param {MouseEvent=} ev1194 */1195 function toggleAllDocs(ev) {1196 const innerToggle = document.getElementById(toggleAllDocsId);1197 if (!innerToggle) {1198 return;1199 }1200 if (hasClass(innerToggle, "will-expand")) {1201 expandAllDocs();1202 } else {1203 collapseAllDocs(ev !== undefined && ev.shiftKey);1204 }1205 }12061207 (function() {1208 const toggles = document.getElementById(toggleAllDocsId);1209 if (toggles) {1210 toggles.onclick = toggleAllDocs;1211 }12121213 const hideMethodDocs = getSettingValue("auto-hide-method-docs") === "true";1214 const hideImplementations = getSettingValue("auto-hide-trait-implementations") === "true";1215 const hideLargeItemContents = getSettingValue("auto-hide-large-items") !== "false";12161217 // @ts-expect-error1218 function setImplementorsTogglesOpen(id, open) {1219 const list = document.getElementById(id);1220 if (list !== null) {1221 onEachLazy(list.getElementsByClassName("implementors-toggle"), e => {1222 e.open = open;1223 });1224 }1225 }12261227 if (hideImplementations) {1228 setImplementorsTogglesOpen("trait-implementations-list", false);1229 setImplementorsTogglesOpen("blanket-implementations-list", false);1230 }12311232 onEachLazy(document.getElementsByClassName("toggle"), e => {1233 if (!hideLargeItemContents && hasClass(e, "type-contents-toggle")) {1234 e.open = true;1235 }1236 if (hideMethodDocs && hasClass(e, "method-toggle")) {1237 e.open = false;1238 }12391240 });1241 }());12421243 window.rustdoc_add_line_numbers_to_examples = () => {1244 // @ts-expect-error1245 function generateLine(nb) {1246 return `<span data-nosnippet>${nb}</span>`;1247 }12481249 onEachLazy(document.querySelectorAll(1250 ".rustdoc:not(.src) :not(.scraped-example) > .example-wrap > pre > code",1251 ), code => {1252 if (hasClass(code.parentElement.parentElement, "hide-lines")) {1253 removeClass(code.parentElement.parentElement, "hide-lines");1254 return;1255 }1256 const lines = code.innerHTML.split("\n");1257 const digits = (lines.length + "").length;1258 // @ts-expect-error1259 code.innerHTML = lines.map((line, index) => generateLine(index + 1) + line).join("\n");1260 addClass(code.parentElement.parentElement, `digits-${digits}`);1261 });1262 };12631264 window.rustdoc_remove_line_numbers_from_examples = () => {1265 onEachLazy(1266 document.querySelectorAll(".rustdoc:not(.src) :not(.scraped-example) > .example-wrap"),1267 x => addClass(x, "hide-lines"),1268 );1269 };12701271 if (getSettingValue("line-numbers") === "true") {1272 window.rustdoc_add_line_numbers_to_examples();1273 }12741275 function showSidebar() {1276 window.hideAllModals(false);1277 const sidebar = document.getElementsByClassName("sidebar")[0];1278 addClass(sidebar, "shown");1279 }12801281 function hideSidebar() {1282 const sidebar = document.getElementsByClassName("sidebar")[0];1283 removeClass(sidebar, "shown");1284 }12851286 window.addEventListener("resize", () => {1287 if (window.CURRENT_TOOLTIP_ELEMENT) {1288 // As a workaround to the behavior of `contains: layout` used in doc togglers,1289 // tooltip popovers are positioned using javascript.1290 //1291 // This means when the window is resized, we need to redo the layout.1292 const base = window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE;1293 const force_visible = base.TOOLTIP_FORCE_VISIBLE;1294 hideTooltip(false);1295 if (force_visible) {1296 showTooltip(base);1297 base.TOOLTIP_FORCE_VISIBLE = true;1298 }1299 }1300 });13011302 const mainElem = document.getElementById(MAIN_ID);1303 if (mainElem) {1304 mainElem.addEventListener("click", hideSidebar);1305 }13061307 onEachLazy(document.querySelectorAll("a[href^='#']"), el => {1308 // For clicks on internal links (<A> tags with a hash property), we expand the section we're1309 // jumping to *before* jumping there. We can't do this in onHashChange, because it changes1310 // the height of the document so we wind up scrolled to the wrong place.1311 el.addEventListener("click", () => {1312 expandSection(el.hash.slice(1));1313 hideSidebar();1314 });1315 });13161317 onEachLazy(document.querySelectorAll(".toggle > summary:not(.hideme)"), el => {1318 // @ts-expect-error1319 // Clicking on the summary's contents should not collapse it,1320 // but links within should still fire.1321 el.addEventListener("click", e => {1322 if (!e.target.matches("summary, a, a *")) {1323 e.preventDefault();1324 }1325 });1326 });13271328 /**1329 * Show a tooltip immediately.1330 *1331 * @param {HTMLElement} e - The tooltip's anchor point. The DOM is consulted to figure1332 * out what the tooltip should contain, and where it should be1333 * positioned.1334 */1335 function showTooltip(e) {1336 const notable_ty = e.getAttribute("data-notable-ty");1337 if (!window.NOTABLE_TRAITS && notable_ty) {1338 const data = document.getElementById("notable-traits-data");1339 if (data) {1340 window.NOTABLE_TRAITS = JSON.parse(data.innerText);1341 } else {1342 throw new Error("showTooltip() called with notable without any notable traits!");1343 }1344 }1345 // Make this function idempotent. If the tooltip is already shown, avoid doing extra work1346 // and leave it alone.1347 if (window.CURRENT_TOOLTIP_ELEMENT && window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE === e) {1348 clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);1349 return;1350 }1351 window.hideAllModals(false);1352 // use Object.assign to make sure the object has the correct type1353 // with all of the correct fields before it is assigned to a variable,1354 // as typescript has no way to change the type of a variable once it is initialized.1355 const wrapper = Object.assign(document.createElement("div"), {TOOLTIP_BASE: e});1356 if (notable_ty) {1357 wrapper.innerHTML = "<div class=\"content\">" +1358 // @ts-expect-error1359 window.NOTABLE_TRAITS[notable_ty] + "</div>";1360 } else {1361 // Replace any `title` attribute with `data-title` to avoid double tooltips.1362 const ttl = e.getAttribute("title");1363 if (ttl !== null) {1364 e.setAttribute("data-title", ttl);1365 e.removeAttribute("title");1366 }1367 const dttl = e.getAttribute("data-title");1368 if (dttl !== null) {1369 const titleContent = document.createElement("div");1370 titleContent.className = "content";1371 titleContent.appendChild(document.createTextNode(dttl));1372 wrapper.appendChild(titleContent);1373 }1374 }1375 wrapper.className = "tooltip popover";1376 const focusCatcher = document.createElement("div");1377 focusCatcher.setAttribute("tabindex", "0");1378 // @ts-expect-error1379 focusCatcher.onfocus = hideTooltip;1380 wrapper.appendChild(focusCatcher);1381 const pos = e.getBoundingClientRect();1382 // 5px overlap so that the mouse can easily travel from place to place1383 wrapper.style.top = (pos.top + window.scrollY + pos.height) + "px";1384 // @ts-expect-error1385 wrapper.style.left = 0;1386 wrapper.style.right = "auto";1387 wrapper.style.visibility = "hidden";1388 document.body.appendChild(wrapper);1389 const wrapperPos = wrapper.getBoundingClientRect();1390 // offset so that the arrow points at the center of the "(i)"1391 const finalPos = pos.left + window.scrollX - wrapperPos.width + 24;1392 if (finalPos > 0) {1393 wrapper.style.left = finalPos + "px";1394 } else {1395 wrapper.style.setProperty(1396 "--popover-arrow-offset",1397 (wrapperPos.right - pos.right + 4) + "px",1398 );1399 }1400 wrapper.style.visibility = "";1401 window.CURRENT_TOOLTIP_ELEMENT = wrapper;1402 clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);1403 wrapper.onpointerenter = ev => {1404 // If this is a synthetic touch event, ignore it. A click event will be along shortly.1405 if (ev.pointerType !== "mouse") {1406 return;1407 }1408 clearTooltipHoverTimeout(e);1409 };1410 wrapper.onpointerleave = ev => {1411 // If this is a synthetic touch event, ignore it. A click event will be along shortly.1412 if (ev.pointerType !== "mouse" || !(ev.relatedTarget instanceof HTMLElement)) {1413 return;1414 }1415 if (!e.TOOLTIP_FORCE_VISIBLE && !e.contains(ev.relatedTarget)) {1416 // See "Tooltip pointer leave gesture" below.1417 setTooltipHoverTimeout(e, false);1418 addClass(wrapper, "fade-out");1419 }1420 };1421 }14221423 /**1424 * Show or hide the tooltip after a timeout. If a timeout was already set before this function1425 * was called, that timeout gets cleared. If the tooltip is already in the requested state,1426 * this function will still clear any pending timeout, but otherwise do nothing.1427 *1428 * @param {HTMLElement} element - The tooltip's anchor point. The DOM is consulted to figure1429 * out what the tooltip should contain, and where it should be1430 * positioned.1431 * @param {boolean} show - If true, the tooltip will be made visible. If false, it will1432 * be hidden.1433 */1434 function setTooltipHoverTimeout(element, show) {1435 clearTooltipHoverTimeout(element);1436 if (!show && !window.CURRENT_TOOLTIP_ELEMENT) {1437 // To "hide" an already hidden element, just cancel its timeout.1438 return;1439 }1440 if (show && window.CURRENT_TOOLTIP_ELEMENT) {1441 // To "show" an already visible element, just cancel its timeout.1442 return;1443 }1444 if (window.CURRENT_TOOLTIP_ELEMENT &&1445 window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE !== element) {1446 // Don't do anything if another tooltip is already visible.1447 return;1448 }1449 element.TOOLTIP_HOVER_TIMEOUT = setTimeout(() => {1450 if (show) {1451 showTooltip(element);1452 } else if (!element.TOOLTIP_FORCE_VISIBLE) {1453 hideTooltip(false);1454 }1455 }, show ? window.RUSTDOC_TOOLTIP_HOVER_MS : window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS);1456 }14571458 /**1459 * If a show/hide timeout was set by `setTooltipHoverTimeout`, cancel it. If none exists,1460 * do nothing.1461 *1462 * @param {HTMLElement} element - The tooltip's anchor point,1463 * as passed to `setTooltipHoverTimeout`.1464 */1465 function clearTooltipHoverTimeout(element) {1466 if (element.TOOLTIP_HOVER_TIMEOUT !== undefined) {1467 removeClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out");1468 clearTimeout(element.TOOLTIP_HOVER_TIMEOUT);1469 delete element.TOOLTIP_HOVER_TIMEOUT;1470 }1471 }14721473 /**1474 * @param {Event & { relatedTarget: Node }} event1475 */1476 function tooltipBlurHandler(event) {1477 if (window.CURRENT_TOOLTIP_ELEMENT &&1478 !window.CURRENT_TOOLTIP_ELEMENT.contains(document.activeElement) &&1479 !window.CURRENT_TOOLTIP_ELEMENT.contains(event.relatedTarget) &&1480 !window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(document.activeElement) &&1481 !window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(event.relatedTarget)1482 ) {1483 // Work around a difference in the focus behaviour between Firefox, Chrome, and Safari.1484 // When I click the button on an already-opened tooltip popover, Safari1485 // hides the popover and then immediately shows it again, while everyone else hides it1486 // and it stays hidden.1487 //1488 // To work around this, make sure the click finishes being dispatched before1489 // hiding the popover. Since `hideTooltip()` is idempotent, this makes Safari behave1490 // consistently with the other two.1491 setTimeout(() => hideTooltip(false), 0);1492 }1493 }14941495 /**1496 * Hide the current tooltip immediately.1497 *1498 * @param {boolean} focus - If set to `true`, move keyboard focus to the tooltip anchor point.1499 * If set to `false`, leave keyboard focus alone.1500 */1501 function hideTooltip(focus) {1502 if (window.CURRENT_TOOLTIP_ELEMENT) {1503 if (window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE) {1504 if (focus) {1505 window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.focus();1506 }1507 window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE = false;1508 }1509 document.body.removeChild(window.CURRENT_TOOLTIP_ELEMENT);1510 clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);1511 window.CURRENT_TOOLTIP_ELEMENT = undefined;1512 }1513 }15141515 onEachLazy(document.getElementsByClassName("tooltip"), e => {1516 e.onclick = () => {1517 e.TOOLTIP_FORCE_VISIBLE = e.TOOLTIP_FORCE_VISIBLE ? false : true;1518 if (window.CURRENT_TOOLTIP_ELEMENT && !e.TOOLTIP_FORCE_VISIBLE) {1519 hideTooltip(true);1520 } else {1521 showTooltip(e);1522 // @ts-expect-error1523 window.CURRENT_TOOLTIP_ELEMENT.setAttribute("tabindex", "0");1524 // @ts-expect-error1525 window.CURRENT_TOOLTIP_ELEMENT.focus();1526 // @ts-expect-error1527 window.CURRENT_TOOLTIP_ELEMENT.onblur = tooltipBlurHandler;1528 }1529 return false;1530 };1531 // @ts-expect-error1532 e.onpointerenter = ev => {1533 // If this is a synthetic touch event, ignore it. A click event will be along shortly.1534 if (ev.pointerType !== "mouse") {1535 return;1536 }1537 setTooltipHoverTimeout(e, true);1538 };1539 // @ts-expect-error1540 e.onpointermove = ev => {1541 // If this is a synthetic touch event, ignore it. A click event will be along shortly.1542 if (ev.pointerType !== "mouse") {1543 return;1544 }1545 setTooltipHoverTimeout(e, true);1546 };1547 // @ts-expect-error1548 e.onpointerleave = ev => {1549 // If this is a synthetic touch event, ignore it. A click event will be along shortly.1550 if (ev.pointerType !== "mouse") {1551 return;1552 }1553 if (!e.TOOLTIP_FORCE_VISIBLE && window.CURRENT_TOOLTIP_ELEMENT &&1554 !window.CURRENT_TOOLTIP_ELEMENT.contains(ev.relatedTarget)) {1555 // Tooltip pointer leave gesture:1556 //1557 // Designing a good hover microinteraction is a matter of guessing user1558 // intent from what are, literally, vague gestures. In this case, guessing if1559 // hovering in or out of the tooltip base is intentional or not.1560 //1561 // To figure this out, a few different techniques are used:1562 //1563 // * When the mouse pointer enters a tooltip anchor point, its hitbox is grown1564 // on the bottom, where the popover is/will appear. Search "hover tunnel" in1565 // rustdoc.css for the implementation.1566 // * There's a delay when the mouse pointer enters the popover base anchor, in1567 // case the mouse pointer was just passing through and the user didn't want1568 // to open it.1569 // * Similarly, a delay is added when exiting the anchor, or the popover1570 // itself, before hiding it.1571 // * A fade-out animation is layered onto the pointer exit delay to immediately1572 // inform the user that they successfully dismissed the popover, while still1573 // providing a way for them to cancel it if it was a mistake and they still1574 // wanted to interact with it.1575 // * No animation is used for revealing it, because we don't want people to try1576 // to interact with an element while it's in the middle of fading in: either1577 // they're allowed to interact with it while it's fading in, meaning it can't1578 // serve as mistake-proofing for the popover, or they can't, but1579 // they might try and be frustrated.1580 //1581 // See also:1582 // * https://www.nngroup.com/articles/timing-exposing-content/1583 // * https://www.nngroup.com/articles/tooltip-guidelines/1584 // * https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown1585 setTooltipHoverTimeout(e, false);1586 addClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out");1587 }1588 };1589 });15901591 const sidebar_menu_toggle = document.getElementsByClassName("sidebar-menu-toggle")[0];1592 if (sidebar_menu_toggle) {1593 sidebar_menu_toggle.addEventListener("click", () => {1594 const sidebar = document.getElementsByClassName("sidebar")[0];1595 // @ts-expect-error1596 if (!hasClass(sidebar, "shown")) {1597 showSidebar();1598 } else {1599 hideSidebar();1600 }1601 });1602 }16031604 // @ts-expect-error1605 function helpBlurHandler(event) {1606 const isInPopover = onEachLazy(1607 document.querySelectorAll(".settings-menu, .help-menu"),1608 menu => {1609 return menu.contains(document.activeElement) || menu.contains(event.relatedTarget);1610 },1611 );1612 if (!isInPopover) {1613 window.hidePopoverMenus();1614 }1615 }16161617 function buildHelpMenu() {1618 const book_info = document.createElement("span");1619 const drloChannel = `https://doc.rust-lang.org/${getVar("channel")}`;1620 book_info.className = "top";1621 book_info.innerHTML = `You can find more information in \1622<a href="${drloChannel}/rustdoc/">the rustdoc book</a>.`;16231624 const shortcuts = [1625 ["?", "Show this help dialog"],1626 ["S / /", "Focus the search field"],1627 ["↑", "Move up in search results"],1628 ["↓", "Move down in search results"],1629 ["← / →", "Switch result tab (when results focused)"],1630 ["⏎", "Go to active search result"],1631 ["+ / =", "Expand all sections"],1632 ["-", "Collapse all sections"],1633 // for the sake of brevity, we don't say "inherit impl blocks",1634 // although that would be more correct,1635 // since trait impl blocks are collapsed by -1636 ["_", "Collapse all sections, including impl blocks"],1637 ].map(x => "<dt>" +1638 x[0].split(" ")1639 .map((y, index) => ((index & 1) === 0 ? "<kbd>" + y + "</kbd>" : " " + y + " "))1640 .join("") + "</dt><dd>" + x[1] + "</dd>").join("");1641 const div_shortcuts = document.createElement("div");1642 addClass(div_shortcuts, "shortcuts");1643 div_shortcuts.innerHTML = "<h2>Keyboard Shortcuts</h2><dl>" + shortcuts + "</dl></div>";16441645 const infos = [1646 `For a full list of all search features, take a look \1647 <a href="${drloChannel}/rustdoc/read-documentation/search.html">here</a>.`,1648 "Prefix searches with a type followed by a colon (e.g., <code>fn:</code>) to \1649 restrict the search to a given item kind.",1650 "Accepted kinds are: <code>fn</code>, <code>mod</code>, <code>struct</code>, \1651 <code>enum</code>, <code>trait</code>, <code>type</code>, <code>macro</code>, \1652 and <code>constant</code>.",1653 "Search functions by type signature (e.g., <code>vec -> usize</code> or \1654 <code>-> vec</code> or <code>String, enum:Cow -> bool</code>)",1655 "You can look for items with an exact name by putting double quotes around \1656 your request: <code>\"string\"</code>",1657 `Look for functions that accept or return \1658 <a href="${drloChannel}/std/primitive.slice.html">slices</a> and \1659 <a href="${drloChannel}/std/primitive.array.html">arrays</a> by writing square \1660 brackets (e.g., <code>-> [u8]</code> or <code>[] -> Option</code>)`,1661 "Look for items inside another one by searching for a path: <code>vec::Vec</code>",1662 ].map(x => "<p>" + x + "</p>").join("");1663 const div_infos = document.createElement("div");1664 addClass(div_infos, "infos");1665 div_infos.innerHTML = "<h2>Search Tricks</h2>" + infos;16661667 const rustdoc_version = document.createElement("span");1668 rustdoc_version.className = "bottom";1669 const rustdoc_version_code = document.createElement("code");1670 rustdoc_version_code.innerText = "rustdoc " + getVar("rustdoc-version");1671 rustdoc_version.appendChild(rustdoc_version_code);16721673 const container = document.createElement("div");1674 if (!isHelpPage) {1675 container.className = "popover";1676 }1677 container.id = "help";16781679 const side_by_side = document.createElement("div");1680 side_by_side.className = "side-by-side";1681 side_by_side.appendChild(div_shortcuts);1682 side_by_side.appendChild(div_infos);16831684 const content = document.createElement("div");1685 content.className = "content";16861687 content.appendChild(book_info);1688 content.appendChild(side_by_side);1689 content.appendChild(rustdoc_version);16901691 container.appendChild(content);16921693 if (isHelpPage) {1694 const help_section = document.createElement("section");1695 help_section.appendChild(container);1696 nonnull(document.getElementById("main-content")).appendChild(help_section);1697 } else {1698 onEachLazy(document.getElementsByClassName("help-menu"), menu => {1699 if (menu.offsetWidth !== 0) {1700 menu.appendChild(container);1701 container.onblur = helpBlurHandler;1702 menu.onblur = helpBlurHandler;1703 menu.children[0].onblur = helpBlurHandler;1704 return true;1705 }1706 });1707 }17081709 return container;1710 }17111712 /**1713 * Hide popover menus, clickable tooltips, and the sidebar (if applicable).1714 *1715 * Pass `true` to reset focus for tooltip popovers.1716 */1717 window.hideAllModals = switchFocus => {1718 hideSidebar();1719 window.hidePopoverMenus();1720 hideTooltip(switchFocus);1721 };17221723 /**1724 * Hide all the popover menus.1725 */1726 window.hidePopoverMenus = () => {1727 onEachLazy(document.querySelectorAll(".settings-menu .popover"), elem => {1728 elem.style.display = "none";1729 });1730 onEachLazy(document.querySelectorAll(".help-menu .popover"), elem => {1731 elem.parentElement.removeChild(elem);1732 });1733 };17341735 /**1736 * Show the help popup menu.1737 */1738 function showHelp() {1739 window.hideAllModals(false);1740 // Prevent `blur` events from being dispatched as a result of closing1741 // other modals.1742 onEachLazy(document.querySelectorAll(".help-menu a"), menu => {1743 if (menu.offsetWidth !== 0) {1744 menu.focus();1745 return true;1746 }1747 });1748 buildHelpMenu();1749 }17501751 if (isHelpPage) {1752 buildHelpMenu();1753 } else {1754 onEachLazy(document.querySelectorAll(".help-menu > a"), helpLink => {1755 helpLink.addEventListener(1756 "click",1757 /** @param {MouseEvent} event */1758 event => {1759 // By default, have help button open docs in a popover.1760 // If user clicks with a moderator, though, use default browser behavior,1761 // probably opening in a new window or tab.1762 if (event.ctrlKey ||1763 event.altKey ||1764 event.metaKey) {1765 return;1766 }1767 event.preventDefault();1768 if (document.getElementById("help")) {1769 window.hidePopoverMenus();1770 } else {1771 showHelp();1772 }1773 },1774 );1775 });1776 }17771778 addSidebarItems();1779 addSidebarCrates();1780 onHashChange(null);1781 window.addEventListener("hashchange", onHashChange);1782 window.searchState.setup();1783}());17841785// Hide, show, and resize the sidebar1786//1787// The body class and CSS variable are initially set up in storage.js,1788// but in this file, we implement:1789//1790// * the show sidebar button, which appears if the sidebar is hidden1791// and, by clicking on it, will bring it back1792// * the sidebar resize handle, which appears only on large viewports1793// with a [fine precision pointer] to allow the user to change1794// the size of the sidebar1795//1796// [fine precision pointer]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer1797(function() {1798 // 100 is the size of the logo1799 // don't let the sidebar get smaller than that, or it'll get squished1800 const SIDEBAR_MIN = 100;1801 // Don't let the sidebar get bigger than this1802 const SIDEBAR_MAX = 500;1803 // Don't let the body (including the gutter) get smaller than this1804 //1805 // WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY1806 // Acceptable values for BODY_MIN are constrained by the mobile breakpoint1807 // (which is the minimum size of the whole page where the sidebar exists)1808 // and the default sidebar width:1809 //1810 // BODY_MIN <= RUSTDOC_MOBILE_BREAKPOINT - DEFAULT_SIDEBAR_WIDTH1811 //1812 // At the time of this writing, the DEFAULT_SIDEBAR_WIDTH on src pages is1813 // 300px, and the RUSTDOC_MOBILE_BREAKPOINT is 700px, so BODY_MIN must be1814 // at most 400px. Otherwise, it would start out at the default size, then1815 // grabbing the resize handle would suddenly cause it to jank to1816 // its constraint-generated maximum.1817 const RUSTDOC_MOBILE_BREAKPOINT = 700;1818 const BODY_MIN = 400;1819 // At half-way past the minimum size, vanish the sidebar entirely1820 const SIDEBAR_VANISH_THRESHOLD = SIDEBAR_MIN / 2;18211822 // Toolbar button to show the sidebar.1823 //1824 // On small, "mobile-sized" viewports, it's not persistent and it1825 // can only be activated by going into Settings and hiding the nav bar.1826 // On larger, "desktop-sized" viewports (though that includes many1827 // tablets), it's fixed-position, appears in the left side margin,1828 // and it can be activated by resizing the sidebar into nothing.1829 let sidebarButton = document.getElementById("sidebar-button");1830 const body = document.querySelector(".main-heading");1831 if (!sidebarButton && body) {1832 sidebarButton = document.createElement("div");1833 sidebarButton.id = "sidebar-button";1834 const path = `${window.rootPath}${window.currentCrate}/all.html`;1835 sidebarButton.innerHTML = `<a href="${path}" title="show sidebar"></a>`;1836 body.insertBefore(sidebarButton, body.firstChild);1837 }1838 if (sidebarButton) {1839 sidebarButton.addEventListener("click", e => {1840 removeClass(document.documentElement, "hide-sidebar");1841 updateLocalStorage("hide-sidebar", "false");1842 if (window.rustdocToggleSrcSidebar) {1843 window.rustdocToggleSrcSidebar();1844 }1845 e.preventDefault();1846 });1847 }18481849 /**1850 * Pointer capture.1851 *1852 * Resizing is a single-pointer gesture. Any secondary pointer is ignored1853 *1854 * @type {null|number}1855 */1856 let currentPointerId = null;18571858 /**1859 * "Desired" sidebar size.1860 *1861 * This is stashed here for window resizing. If the sidebar gets1862 * shrunk to maintain BODY_MIN, and then the user grows the window again,1863 * it gets the sidebar to restore its size.1864 *1865 * @type {null|number}1866 */1867 let desiredSidebarSize = null;18681869 /**1870 * Sidebar resize debouncer.1871 *1872 * The sidebar itself is resized instantly, but the body HTML can be too1873 * big for that, causing reflow jank. To reduce this, we queue up a separate1874 * animation frame and throttle it.1875 *1876 * @type {false|ReturnType<typeof setTimeout>}1877 */1878 let pendingSidebarResizingFrame = false;18791880 /** @type {HTMLElement|null} */1881 const resizer = document.querySelector(".sidebar-resizer");1882 /** @type {HTMLElement|null} */1883 const sidebar = document.querySelector(".sidebar");1884 // If this page has no sidebar at all, bail out.1885 if (!resizer || !sidebar) {1886 return;1887 }18881889 // src page and docs page use different variables, because the contents of1890 // the sidebar are so different that it's reasonable to thing the user1891 // would want them to have different sizes1892 const isSrcPage = hasClass(document.body, "src");18931894 // Call this function to hide the sidebar when using the resize handle1895 //1896 // This function also nulls out the sidebar width CSS variable and setting,1897 // causing it to return to its default. This does not happen if you do it1898 // from settings.js, which uses a separate function. It's done here because1899 // the minimum sidebar size is rather uncomfortable, and it must pass1900 // through that size when using the shrink-to-nothing gesture.1901 const hideSidebar = function() {1902 if (isSrcPage) {1903 window.rustdocCloseSourceSidebar();1904 updateLocalStorage("src-sidebar-width", null);1905 // [RUSTDOCIMPL] CSS variable fast path1906 //1907 // The sidebar width variable is attached to the <html> element by1908 // storage.js, because the sidebar and resizer don't exist yet.1909 // But the resize code, in `resize()`, sets the property on the1910 // sidebar and resizer elements (which are the only elements that1911 // use the variable) to avoid recalculating CSS on the entire1912 // document on every frame.1913 //1914 // So, to clear it, we need to clear all three.1915 document.documentElement.style.removeProperty("--src-sidebar-width");1916 sidebar.style.removeProperty("--src-sidebar-width");1917 resizer.style.removeProperty("--src-sidebar-width");1918 } else {1919 addClass(document.documentElement, "hide-sidebar");1920 updateLocalStorage("hide-sidebar", "true");1921 updateLocalStorage("desktop-sidebar-width", null);1922 document.documentElement.style.removeProperty("--desktop-sidebar-width");1923 sidebar.style.removeProperty("--desktop-sidebar-width");1924 resizer.style.removeProperty("--desktop-sidebar-width");1925 }1926 };19271928 // Call this function to show the sidebar from the resize handle.1929 // On docs pages, this can only happen if the user has grabbed the resize1930 // handle, shrunk the sidebar down to nothing, and then pulls back into1931 // the visible range without releasing it. You can, however, grab the1932 // resize handle on a source page with the sidebar closed, because it1933 // remains visible all the time on there.1934 const showSidebar = function() {1935 if (isSrcPage) {1936 window.rustdocShowSourceSidebar();1937 } else {1938 removeClass(document.documentElement, "hide-sidebar");1939 updateLocalStorage("hide-sidebar", "false");1940 }1941 };19421943 /**1944 * Call this to set the correct CSS variable and setting.1945 * This function doesn't enforce size constraints. Do that before calling it!1946 *1947 * @param {number} size - CSS px width of the sidebar.1948 */1949 const changeSidebarSize = function(size) {1950 if (isSrcPage) {1951 updateLocalStorage("src-sidebar-width", size.toString());1952 // [RUSTDOCIMPL] CSS variable fast path1953 //1954 // While this property is set on the HTML element at load time,1955 // because the sidebar isn't actually loaded yet,1956 // we scope this update to the sidebar to avoid hitting a slow1957 // path in WebKit.1958 sidebar.style.setProperty("--src-sidebar-width", size + "px");1959 resizer.style.setProperty("--src-sidebar-width", size + "px");1960 } else {1961 updateLocalStorage("desktop-sidebar-width", size.toString());1962 sidebar.style.setProperty("--desktop-sidebar-width", size + "px");1963 resizer.style.setProperty("--desktop-sidebar-width", size + "px");1964 }1965 };19661967 // Check if the sidebar is hidden. Since src pages and doc pages have1968 // different settings, this function has to check that.1969 const isSidebarHidden = function() {1970 return isSrcPage ?1971 !hasClass(document.documentElement, "src-sidebar-expanded") :1972 hasClass(document.documentElement, "hide-sidebar");1973 };19741975 /**1976 * Respond to the resize handle event.1977 * This function enforces size constraints, and implements the1978 * shrink-to-nothing gesture based on thresholds defined above.1979 *1980 * @param {PointerEvent} e1981 */1982 const resize = function(e) {1983 if (currentPointerId === null || currentPointerId !== e.pointerId) {1984 return;1985 }1986 e.preventDefault();1987 const pos = e.clientX - 3;1988 if (pos < SIDEBAR_VANISH_THRESHOLD) {1989 hideSidebar();1990 } else if (pos >= SIDEBAR_MIN) {1991 if (isSidebarHidden()) {1992 showSidebar();1993 }1994 // don't let the sidebar get wider than SIDEBAR_MAX, or the body narrower1995 // than BODY_MIN1996 const constrainedPos = Math.min(pos, window.innerWidth - BODY_MIN, SIDEBAR_MAX);1997 changeSidebarSize(constrainedPos);1998 desiredSidebarSize = constrainedPos;1999 if (pendingSidebarResizingFrame !== false) {2000 clearTimeout(pendingSidebarResizingFrame);
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.