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 // MAIN_ID exists, and is not the root121 // @ts-expect-error122 insertAfter(el, document.getElementById(MAIN_ID));123 }124 return el;125}126127/**128 * Returns the `<section>` element which contains the displayed element.129 *130 * @return {HTMLElement}131 */132function getAlternativeDisplayElem() {133 return getOrCreateSection(ALTERNATIVE_DISPLAY_ID, "content hidden");134}135136/**137 * Returns the `<section>` element which contains the not-displayed elements.138 *139 * @return {HTMLElement}140 */141function getNotDisplayedElem() {142 return getOrCreateSection(NOT_DISPLAYED_ID, "hidden");143}144145/**146 * To nicely switch between displayed "extra" elements (such as search results or settings menu)147 * and to alternate between the displayed and not displayed elements, we hold them in two different148 * `<section>` elements. They work in pair: one holds the hidden elements while the other149 * contains the displayed element (there can be only one at the same time!). So basically, we switch150 * elements between the two `<section>` elements.151 *152 * @param {Element|null} elemToDisplay153 */154function switchDisplayedElement(elemToDisplay) {155 const el = getAlternativeDisplayElem();156157 if (el.children.length > 0) {158 getNotDisplayedElem().appendChild(nonnull(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} [errorCallback]206 */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 loadScript(getVar("static-root-path") + getVar("settings-js"));228 // Pre-load all theme CSS files, so that switching feels seamless.229 //230 // When loading settings.html as a standalone page, the equivalent HTML is231 // generated in context.rs.232 setTimeout(() => {233 const themes = getVar("themes").split(",");234 for (const theme of themes) {235 // if there are no themes, do nothing236 // "".split(",") == [""]237 if (theme !== "") {238 preLoadCss(getVar("root-path") + theme + ".css");239 }240 }241 }, 0);242 };243 });244245 window.searchState = {246 rustdocToolbar: document.querySelector("rustdoc-toolbar"),247 loadingText: "Loading search results...",248 inputElement: () => {249 let el = document.getElementsByClassName("search-input")[0];250 if (!el) {251 const out = nonnull(nonnull(window.searchState.outputElement()).parentElement);252 const hdr = document.createElement("div");253 hdr.className = "main-heading search-results-main-heading";254 const params = window.searchState.getQueryStringParams();255 const autofocusParam = params.search === "" ? "autofocus" : "";256 hdr.innerHTML = `<nav class="sub">257 <form class="search-form loading">258 <span></span> <!-- This empty span is a hacky fix for Safari: see #93184 -->259 <input260 ${autofocusParam}261 class="search-input"262 name="search"263 aria-label="Run search in the documentation"264 autocomplete="off"265 spellcheck="false"266 placeholder="Type ‘S’ or ‘/’ to search, ‘?’ for more options…"267 type="search">268 </form>269 </nav><div class="search-switcher"></div>`;270 out.insertBefore(hdr, window.searchState.outputElement());271 el = document.getElementsByClassName("search-input")[0];272 }273 if (el instanceof HTMLInputElement) {274 return el;275 }276 return null;277 },278 containerElement: () => {279 let el = document.getElementById("search");280 if (!el) {281 el = document.createElement("section");282 el.id = "search";283 getNotDisplayedElem().appendChild(el);284 }285 return el;286 },287 outputElement: () => {288 const container = window.searchState.containerElement();289 if (!container) {290 return null;291 }292 let el = container.querySelector(".search-out");293 if (!el) {294 el = document.createElement("div");295 el.className = "search-out";296 container.appendChild(el);297 }298 return el;299 },300 title: document.title,301 titleBeforeSearch: document.title,302 timeout: null,303 // On the search screen, so you remain on the last tab you opened.304 //305 // 0 for "In Names"306 // 1 for "In Parameters"307 // 2 for "In Return Types"308 currentTab: 0,309 // tab and back preserves the element that was focused.310 focusedByTab: [null, null, null],311 clearInputTimeout: () => {312 if (window.searchState.timeout !== null) {313 clearTimeout(window.searchState.timeout);314 window.searchState.timeout = null;315 }316 },317 isDisplayed: () => {318 const container = window.searchState.containerElement();319 if (!container) {320 return false;321 }322 return !!container.parentElement && container.parentElement.id ===323 ALTERNATIVE_DISPLAY_ID;324 },325 // Sets the focus on the search bar at the top of the page326 focus: () => {327 const inputElement = window.searchState.inputElement();328 window.searchState.showResults();329 if (inputElement) {330 inputElement.focus();331 // Avoid glitch if something focuses the search button after clicking.332 requestAnimationFrame(() => inputElement.focus());333 }334 },335 // Removes the focus from the search bar.336 defocus: () => {337 nonnull(window.searchState.inputElement()).blur();338 },339 toggle: () => {340 if (window.searchState.isDisplayed()) {341 window.searchState.defocus();342 window.searchState.hideResults();343 } else {344 window.searchState.focus();345 }346 },347 showResults: () => {348 document.title = window.searchState.title;349 if (window.searchState.isDisplayed()) {350 return;351 }352 const search = window.searchState.containerElement();353 switchDisplayedElement(search);354 const btn = document.querySelector("#search-button a");355 if (browserSupportsHistoryApi() && btn instanceof HTMLAnchorElement &&356 window.searchState.getQueryStringParams().search === undefined357 ) {358 history.pushState(null, "", btn.href);359 }360 const btnLabel = document.querySelector("#search-button a span.label");361 if (btnLabel) {362 btnLabel.innerHTML = "Exit";363 }364 },365 removeQueryParameters: () => {366 // We change the document title.367 document.title = window.searchState.titleBeforeSearch;368 if (browserSupportsHistoryApi()) {369 history.replaceState(null, "", getNakedUrl() + window.location.hash);370 }371 },372 hideResults: () => {373 switchDisplayedElement(null);374 // We also remove the query parameter from the URL.375 window.searchState.removeQueryParameters();376 const btnLabel = document.querySelector("#search-button a span.label");377 if (btnLabel) {378 btnLabel.innerHTML = "Search";379 }380 },381 getQueryStringParams: () => {382 /** @type {Object.<any, string>} */383 const params = {};384 window.location.search.substring(1).split("&").385 map(s => {386 // https://github.com/rust-lang/rust/issues/119219387 const pair = s.split("=").map(x => x.replace(/\+/g, " "));388 params[decodeURIComponent(pair[0])] =389 typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]);390 });391 return params;392 },393 setup: () => {394 let searchLoaded = false;395 const search_input = window.searchState.inputElement();396 if (!search_input) {397 return;398 }399 // If you're browsing the nightly docs, the page might need to be refreshed for the400 // search to work because the hash of the JS scripts might have changed.401 function sendSearchForm() {402 // @ts-expect-error403 document.getElementsByClassName("search-form")[0].submit();404 }405 function loadSearch() {406 if (!searchLoaded) {407 searchLoaded = true;408 window.rr_ = data => {409 window.searchIndex = data;410 };411 if (!window.StringdexOnload) {412 window.StringdexOnload = [];413 }414 window.StringdexOnload.push(() => {415 loadScript(416 getVar("static-root-path") + getVar("search-js"),417 sendSearchForm,418 );419 });420 loadScript(getVar("static-root-path") + getVar("stringdex-js"), sendSearchForm);421 loadScript(resourcePath("search.index/root", ".js"), sendSearchForm);422 }423 }424425 search_input.addEventListener("focus", () => {426 loadSearch();427 });428429 const btn = document.getElementById("search-button");430 if (btn) {431 btn.onclick = event => {432 if (event.ctrlKey || event.altKey || event.metaKey) {433 return;434 }435 event.preventDefault();436 window.searchState.toggle();437 loadSearch();438 };439 }440441 // Push and pop states are used to add search results to the browser442 // history.443 if (browserSupportsHistoryApi()) {444 // Store the previous <title> so we can revert back to it later.445 const previousTitle = document.title;446447 window.addEventListener("popstate", e => {448 const params = window.searchState.getQueryStringParams();449 // Revert to the previous title manually since the History450 // API ignores the title parameter.451 document.title = previousTitle;452 // Synchronize search bar with query string state and453 // perform the search. This will empty the bar if there's454 // nothing there, which lets you really go back to a455 // previous state with nothing in the bar.456 const inputElement = window.searchState.inputElement();457 if (params.search !== undefined && inputElement !== null) {458 loadSearch();459 inputElement.value = params.search;460 // Some browsers fire "onpopstate" for every page load461 // (Chrome), while others fire the event only when actually462 // popping a state (Firefox), which is why search() is463 // called both here and at the end of the startSearch()464 // function.465 e.preventDefault();466 window.searchState.showResults();467 if (params.search === "") {468 window.searchState.focus();469 }470 } else {471 // When browsing back from search results the main page472 // visibility must be reset.473 window.searchState.hideResults();474 }475 });476 }477478 // This is required in firefox to avoid this problem: Navigating to a search result479 // with the keyboard, hitting enter, and then hitting back would take you back to480 // the doc page, rather than the search that should overlay it.481 // This was an interaction between the back-forward cache and our handlers482 // that try to sync state between the URL and the search input. To work around it,483 // do a small amount of re-init on page show.484 window.onpageshow = () => {485 const inputElement = window.searchState.inputElement();486 const qSearch = window.searchState.getQueryStringParams().search;487 if (qSearch !== undefined && inputElement !== null) {488 if (inputElement.value === "") {489 inputElement.value = qSearch;490 }491 window.searchState.showResults();492 if (qSearch === "") {493 loadSearch();494 window.searchState.focus();495 }496 } else {497 window.searchState.hideResults();498 }499 };500501 const params = window.searchState.getQueryStringParams();502 if (params.search !== undefined) {503 window.searchState.setLoadingSearch();504 loadSearch();505 }506 },507 setLoadingSearch: () => {508 const search = window.searchState.outputElement();509 nonnull(search).innerHTML = "<h3 class=\"search-loading\">" +510 window.searchState.loadingText + "</h3>";511 window.searchState.showResults();512 },513 descShards: new Map(),514 loadDesc: async function({descShard, descIndex}) {515 if (descShard.promise === null) {516 descShard.promise = new Promise((resolve, reject) => {517 // The `resolve` callback is stored in the `descShard`518 // object, which is itself stored in `this.descShards` map.519 // It is called in `loadedDescShard` by the520 // search.desc script.521 descShard.resolve = resolve;522 const ds = descShard;523 const fname = `${ds.crate}-desc-${ds.shard}-`;524 const url = resourcePath(525 `search.desc/${descShard.crate}/${fname}`,526 ".js",527 );528 loadScript(url, reject);529 });530 }531 const list = await descShard.promise;532 return list[descIndex];533 },534 loadedDescShard: function(crate, shard, data) {535 // If loadedDescShard gets called, then the library must have been declared.536 // @ts-expect-error537 this.descShards.get(crate)[shard].resolve(data.split("\n"));538 },539 };540541 const toggleAllDocsId = "toggle-all-docs";542 let savedHash = "";543544 /**545 * @param {HashChangeEvent|null} ev546 */547 function handleHashes(ev) {548 if (ev !== null && window.searchState.isDisplayed() && ev.newURL) {549 // This block occurs when clicking on an element in the navbar while550 // in a search.551 switchDisplayedElement(null);552 const hash = ev.newURL.slice(ev.newURL.indexOf("#") + 1);553 if (browserSupportsHistoryApi()) {554 // `window.location.search`` contains all the query parameters, not just `search`.555 history.replaceState(null, "",556 getNakedUrl() + window.location.search + "#" + hash);557 }558 const elem = document.getElementById(hash);559 if (elem) {560 elem.scrollIntoView();561 }562 }563 // This part is used in case an element is not visible.564 const pageId = window.location.hash.replace(/^#/, "");565 if (savedHash !== pageId) {566 savedHash = pageId;567 if (pageId !== "") {568 expandSection(pageId);569 }570 }571 if (savedHash.startsWith("impl-")) {572 // impl-disambiguated links, used by the search engine573 // format: impl-X[-for-Y]/method.WHATEVER574 // turn this into method.WHATEVER[-NUMBER]575 const splitAt = savedHash.indexOf("/");576 if (splitAt !== -1) {577 const implId = savedHash.slice(0, splitAt);578 const assocId = savedHash.slice(splitAt + 1);579 const implElems = document.querySelectorAll(580 `details > summary > section[id^="${implId}"]`,581 );582 onEachLazy(implElems, implElem => {583 const numbered = /^(.+?)-([0-9]+)$/.exec(implElem.id);584 if (implElem.id !== implId && (!numbered || numbered[1] !== implId)) {585 return false;586 }587 return onEachLazy(implElem.parentElement.parentElement.querySelectorAll(588 `[id^="${assocId}"]`),589 item => {590 const numbered = /^(.+?)-([0-9]+)$/.exec(item.id);591 if (item.id === assocId || (numbered && numbered[1] === assocId)) {592 openParentDetails(item);593 item.scrollIntoView();594 // Let the section expand itself before trying to highlight595 setTimeout(() => {596 window.location.replace("#" + item.id);597 }, 0);598 return true;599 }600 },601 );602 });603 }604 }605 }606607 /**608 * @param {HashChangeEvent|null} ev609 */610 function onHashChange(ev) {611 // If we're in mobile mode, we should hide the sidebar in any case.612 hideSidebar();613 handleHashes(ev);614 }615616 /**617 * @param {HTMLElement|null} elem618 */619 function openParentDetails(elem) {620 while (elem) {621 if (elem instanceof HTMLDetailsElement) {622 elem.open = true;623 }624 elem = elem.parentElement;625 }626 }627628 /**629 * @param {string} id630 */631 function expandSection(id) {632 openParentDetails(document.getElementById(id));633 }634635 /**636 * @param {KeyboardEvent} ev637 */638 function handleEscape(ev) {639 window.searchState.clearInputTimeout();640 window.searchState.hideResults();641 ev.preventDefault();642 window.searchState.defocus();643 window.hideAllModals(true); // true = reset focus for tooltips644 }645646 /**647 * @param {KeyboardEvent} ev648 */649 function handleShortcut(ev) {650 // Don't interfere with browser shortcuts651 const disableShortcuts = getSettingValue("disable-shortcuts") === "true";652 if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) {653 return;654 }655656 if (document.activeElement &&657 document.activeElement instanceof HTMLInputElement &&658 document.activeElement.type !== "checkbox" &&659 document.activeElement.type !== "radio") {660 switch (getVirtualKey(ev)) {661 case "Escape":662 handleEscape(ev);663 break;664 }665 } else {666 switch (getVirtualKey(ev)) {667 case "Escape":668 handleEscape(ev);669 break;670671 case "s":672 case "S":673 case "/":674 ev.preventDefault();675 window.searchState.focus();676 break;677678 case "+":679 case "=":680 ev.preventDefault();681 expandAllDocs();682 break;683 case "-":684 ev.preventDefault();685 collapseAllDocs(false);686 break;687 case "_":688 ev.preventDefault();689 collapseAllDocs(true);690 break;691692 case "?":693 showHelp();694 break;695696 default:697 break;698 }699 }700 }701702 document.addEventListener("keypress", handleShortcut);703 document.addEventListener("keydown", handleShortcut);704705 function addSidebarItems() {706 if (!window.SIDEBAR_ITEMS) {707 return;708 }709 const sidebar = document.getElementById("rustdoc-modnav");710711 /**712 * Append to the sidebar a "block" of links - a heading along with a list (`<ul>`) of items.713 *714 * @param {string} shortty - A short type name, like "primitive", "mod", or "macro"715 * @param {string} id - The HTML id of the corresponding section on the module page.716 * @param {string} longty - A long, capitalized, plural name, like "Primitive Types",717 * "Modules", or "Macros".718 */719 function block(shortty, id, longty) {720 // @ts-expect-error721 const filtered = window.SIDEBAR_ITEMS[shortty];722 if (!filtered) {723 return;724 }725726 const modpath = hasClass(document.querySelector(".rustdoc"), "mod") ? "../" : "";727728 const h3 = document.createElement("h3");729 h3.innerHTML = `<a href="${modpath}index.html#${id}">${longty}</a>`;730 const ul = document.createElement("ul");731 ul.className = "block " + shortty;732733 for (const item of filtered) {734 const [name, isMacro] = Array.isArray(item) ? [item[0], true] : [item, false];735 let path;736 if (shortty === "mod") {737 path = `${modpath}${name}/index.html`;738 } else if (!isMacro) {739 path = `${modpath}${shortty}.${name}.html`;740 } else {741 path = `${modpath}macro.${name}.html`;742 }743 let current_page = document.location.href.toString();744 if (current_page.endsWith("/")) {745 current_page += "index.html";746 }747 const link = document.createElement("a");748 link.href = path;749 link.textContent = name;750 const li = document.createElement("li");751 // Don't "optimize" this to just use `path`.752 // We want the browser to normalize this into an absolute URL.753 if (link.href === current_page) {754 li.classList.add("current");755 }756 li.appendChild(link);757 ul.appendChild(li);758 }759 // @ts-expect-error760 sidebar.appendChild(h3);761 // @ts-expect-error762 sidebar.appendChild(ul);763 }764765 if (sidebar) {766 // keep this synchronized with ItemSection::ALL in html/render/mod.rs767 // Re-exports aren't shown here, because they don't have child pages768 //block("reexport", "reexports", "Re-exports");769 block("primitive", "primitives", "Primitive Types");770 block("mod", "modules", "Modules");771 block("macro", "macros", "Macros");772 block("struct", "structs", "Structs");773 block("enum", "enums", "Enums");774 block("constant", "constants", "Constants");775 block("static", "static", "Statics");776 block("trait", "traits", "Traits");777 block("fn", "functions", "Functions");778 block("type", "types", "Type Aliases");779 block("union", "unions", "Unions");780 // No point, because these items don't appear in modules781 //block("impl", "impls", "Implementations");782 //block("tymethod", "tymethods", "Type Methods");783 //block("method", "methods", "Methods");784 //block("structfield", "fields", "Fields");785 //block("variant", "variants", "Variants");786 //block("associatedtype", "associated-types", "Associated Types");787 //block("associatedconstant", "associated-consts", "Associated Constants");788 block("foreigntype", "foreign-types", "Foreign Types");789 block("keyword", "keywords", "Keywords");790 block("attribute", "attribute-docs", "Attributes");791 block("attr", "attributes", "Attribute Macros");792 block("derive", "derives", "Derive Macros");793 block("traitalias", "trait-aliases", "Trait Aliases");794 }795 }796797 // <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+trait.impl&type=code>798 window.register_implementors = imp => {799 /** Takes an ID as input and returns a list of two elements. The first element is the DOM800 * element with the given ID and the second is the "negative marker", meaning the location801 * between the negative and non-negative impls.802 *803 * @param {string} id: ID of the DOM element.804 *805 * @return {[HTMLElement|null, HTMLElement|null]}806 */807 function implementorsElems(id) {808 const elem = document.getElementById(id);809 return [elem, elem ? elem.querySelector(".negative-marker") : null];810 }811 const implementors = implementorsElems("implementors-list");812 const syntheticImplementors = implementorsElems("synthetic-implementors-list");813 /** @type {Set<string>} */814 const inlined_types = new Set();815816 const TEXT_IDX = 0;817 const IS_NEG_IDX = 1;818 const SYNTHETIC_IDX = 2;819 const TYPES_IDX = 3;820821 if (syntheticImplementors[0]) {822 // This `inlined_types` variable is used to avoid having the same implementation823 // showing up twice. For example "String" in the "Sync" doc page.824 //825 // By the way, this is only used by and useful for traits implemented automatically826 // (like "Send" and "Sync").827 onEachLazy(syntheticImplementors[0].getElementsByClassName("impl"), el => {828 const aliases = el.getAttribute("data-aliases");829 if (!aliases) {830 return;831 }832 aliases.split(",").forEach(/** @param {string} alias */ alias => {833 inlined_types.add(alias);834 });835 });836 }837838 let currentNbImpls = nonnull(implementors[0]).getElementsByClassName("impl").length;839 const traitName = nonnull(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 [impList, negImpMarker] =860 struct[SYNTHETIC_IDX] ? syntheticImplementors : implementors;861862 // The types list is only used for synthetic impls.863 // If this changes, `main.js` and `write_shared.rs` both need changed.864 if (struct[SYNTHETIC_IDX]) {865 for (const struct_type of struct[TYPES_IDX]) {866 if (inlined_types.has(struct_type)) {867 continue struct_loop;868 }869 inlined_types.add(struct_type);870 }871 }872873 const code = document.createElement("h3");874 code.innerHTML = struct[TEXT_IDX];875 addClass(code, "code-header");876877 onEachLazy(code.getElementsByTagName("a"), elem => {878 const href = elem.getAttribute("href");879880 if (href && !href.startsWith("#") && !/^(?:[a-z+]+:)?\/\//.test(href)) {881 elem.setAttribute("href", window.rootPath + href);882 }883 });884885 const currentId = baseIdName + currentNbImpls;886 const anchor = document.createElement("a");887 anchor.href = "#" + currentId;888 addClass(anchor, "anchor");889890 const display = document.createElement("div");891 display.id = currentId;892 addClass(display, "impl");893 display.appendChild(anchor);894 display.appendChild(code);895896 // If this is a negative implementor, we put it into the right location (just897 // before the negative impl marker).898 if (struct[IS_NEG_IDX]) {899 nonnull(negImpMarker).before(display);900 } else {901 nonnull(impList).appendChild(display);902 }903 currentNbImpls += 1;904 }905 }906 if (implementors[0]) {907 implementors[0].classList.add("loaded");908 }909 if (syntheticImplementors[0]) {910 syntheticImplementors[0].classList.add("loaded");911 }912 };913 if (window.pending_implementors) {914 window.register_implementors(window.pending_implementors);915 }916917 /**918 * <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+type.impl&type=code>919 *920 * [RUSTDOCIMPL] type.impl921 *922 * This code inlines implementations into the type alias docs at runtime. It's done at923 * runtime because some crates have many type aliases and many methods, and we don't want924 * to generate *O*`(types*methods)` HTML text. The data inside is mostly HTML fragments,925 * wrapped in JSON.926 *927 * - It only includes docs generated for the current crate. This function accepts an928 * object mapping crate names to the set of impls.929 *930 * - It filters down to the set of applicable impls. The Rust type checker is used to931 * tag each HTML blob with the set of type aliases that can actually use it, so the932 * JS only needs to consult the attached list of type aliases.933 *934 * - It renames the ID attributes, to avoid conflicting IDs in the resulting DOM.935 *936 * - It adds the necessary items to the sidebar. If it's an inherent impl, that means937 * adding methods, associated types, and associated constants. If it's a trait impl,938 * that means adding it to the trait impl sidebar list.939 *940 * - It adds the HTML block itself. If it's an inherent impl, it goes after the type941 * alias's own inherent impls. If it's a trait impl, it goes in the Trait942 * Implementations section.943 *944 * - After processing all of the impls, it sorts the sidebar items by name.945 *946 * @param {rustdoc.TypeImpls} imp947 */948 window.register_type_impls = imp => {949 // @ts-expect-error950 if (!imp || !imp[window.currentCrate]) {951 return;952 }953 window.pending_type_impls = undefined;954 const idMap = new Map();955956 let implementations = document.getElementById("implementations-list");957 let trait_implementations = document.getElementById("trait-implementations-list");958 let trait_implementations_header = document.getElementById("trait-implementations");959960 // We want to include the current type alias's impls, and no others.961 const script = document.querySelector("script[data-self-path]");962 const selfPath = script ? script.getAttribute("data-self-path") : null;963964 // These sidebar blocks need filled in, too.965 const mainContent = nonnull(document.querySelector("#main-content"));966 const sidebarSection = nonnull(document.querySelector(".sidebar section"));967 let methods = document.querySelector(".sidebar .block.method");968 let associatedTypes = document.querySelector(".sidebar .block.associatedtype");969 let associatedConstants = document.querySelector(".sidebar .block.associatedconstant");970 let sidebarTraitList = document.querySelector(".sidebar .block.trait-implementation");971972 for (const impList of imp[nonnull(window.currentCrate)]) {973 const types = impList.slice(2);974 const text = impList[0];975 const traitName = impList[1];976 const isTrait = typeof traitName === "string";977 if (selfPath === null || types.indexOf(selfPath) === -1) {978 continue;979 }980 let outputList = isTrait ? trait_implementations : implementations;981 if (outputList === null) {982 const outputListName = isTrait ? "Trait Implementations" : "Implementations";983 const outputListId = isTrait ?984 "trait-implementations-list" :985 "implementations-list";986 const outputListHeaderId = isTrait ? "trait-implementations" : "implementations";987 const outputListHeader = document.createElement("h2");988 outputListHeader.id = outputListHeaderId;989 outputListHeader.innerText = outputListName;990 outputList = document.createElement("div");991 outputList.id = outputListId;992 if (isTrait) {993 const link = document.createElement("a");994 link.href = `#${outputListHeaderId}`;995 link.innerText = "Trait Implementations";996 const h = document.createElement("h3");997 h.appendChild(link);998 trait_implementations = outputList;999 trait_implementations_header = outputListHeader;1000 sidebarSection.appendChild(h);1001 sidebarTraitList = document.createElement("ul");1002 sidebarTraitList.className = "block trait-implementation";1003 sidebarSection.appendChild(sidebarTraitList);1004 mainContent.appendChild(outputListHeader);1005 mainContent.appendChild(outputList);1006 } else {1007 implementations = outputList;1008 if (trait_implementations) {1009 mainContent.insertBefore(outputListHeader, trait_implementations_header);1010 mainContent.insertBefore(outputList, trait_implementations_header);1011 } else {1012 mainContent.appendChild(outputListHeader);1013 mainContent.appendChild(outputList);1014 }1015 }1016 }1017 const template = document.createElement("template");1018 template.innerHTML = text;10191020 onEachLazy(template.content.querySelectorAll("a"), elem => {1021 const href = elem.getAttribute("href");10221023 if (href && !href.startsWith("#") && !/^(?:[a-z+]+:)?\/\//.test(href)) {1024 elem.setAttribute("href", window.rootPath + href);1025 }1026 });1027 onEachLazy(template.content.querySelectorAll("[id]"), el => {1028 let i = 0;1029 if (idMap.has(el.id)) {1030 i = idMap.get(el.id);1031 } else if (document.getElementById(el.id)) {1032 i = 1;1033 while (document.getElementById(`${el.id}-${2 * i}`)) {1034 i = 2 * i;1035 }1036 while (document.getElementById(`${el.id}-${i}`)) {1037 i += 1;1038 }1039 }1040 if (i !== 0) {1041 const oldHref = `#${el.id}`;1042 const newHref = `#${el.id}-${i}`;1043 el.id = `${el.id}-${i}`;1044 onEachLazy(template.content.querySelectorAll("a[href]"), link => {1045 if (link.getAttribute("href") === oldHref) {1046 link.href = newHref;1047 }1048 });1049 }1050 idMap.set(el.id, i + 1);1051 });1052 const templateAssocItems = template.content.querySelectorAll("section.tymethod, " +1053 "section.method, section.associatedtype, section.associatedconstant");1054 if (isTrait) {1055 const li = document.createElement("li");1056 const a = document.createElement("a");1057 a.href = `#${nonnull(template.content.querySelector(".impl")).id}`;1058 a.textContent = traitName;1059 li.appendChild(a);1060 // @ts-expect-error1061 sidebarTraitList.append(li);1062 } else {1063 onEachLazy(templateAssocItems, item => {1064 let block = hasClass(item, "associatedtype") ? associatedTypes : (1065 hasClass(item, "associatedconstant") ? associatedConstants : (1066 methods));1067 if (!block) {1068 const blockTitle = hasClass(item, "associatedtype") ? "Associated Types" : (1069 hasClass(item, "associatedconstant") ? "Associated Constants" : (1070 "Methods"));1071 const blockClass = hasClass(item, "associatedtype") ? "associatedtype" : (1072 hasClass(item, "associatedconstant") ? "associatedconstant" : (1073 "method"));1074 const blockHeader = document.createElement("h3");1075 const blockLink = document.createElement("a");1076 blockLink.href = "#implementations";1077 blockLink.innerText = blockTitle;1078 blockHeader.appendChild(blockLink);1079 block = document.createElement("ul");1080 block.className = `block ${blockClass}`;1081 const insertionReference = methods || sidebarTraitList;1082 if (insertionReference) {1083 const insertionReferenceH = insertionReference.previousElementSibling;1084 sidebarSection.insertBefore(blockHeader, insertionReferenceH);1085 sidebarSection.insertBefore(block, insertionReferenceH);1086 } else {1087 sidebarSection.appendChild(blockHeader);1088 sidebarSection.appendChild(block);1089 }1090 if (hasClass(item, "associatedtype")) {1091 associatedTypes = block;1092 } else if (hasClass(item, "associatedconstant")) {1093 associatedConstants = block;1094 } else {1095 methods = block;1096 }1097 }1098 const li = document.createElement("li");1099 const a = document.createElement("a");1100 a.innerText = item.id.split("-")[0].split(".")[1];1101 a.href = `#${item.id}`;1102 li.appendChild(a);1103 block.appendChild(li);1104 });1105 }1106 outputList.appendChild(template.content);1107 }11081109 for (const list of [methods, associatedTypes, associatedConstants, sidebarTraitList]) {1110 if (!list) {1111 continue;1112 }1113 const newChildren = Array.prototype.slice.call(list.children);1114 newChildren.sort((a, b) => {1115 const aI = a.innerText;1116 const bI = b.innerText;1117 return aI < bI ? -1 :1118 aI > bI ? 1 :1119 0;1120 });1121 list.replaceChildren(...newChildren);1122 }1123 };1124 if (window.pending_type_impls) {1125 window.register_type_impls(window.pending_type_impls);1126 }11271128 function addSidebarCrates() {1129 // @ts-expect-error1130 if (!window.ALL_CRATES) {1131 return;1132 }1133 const sidebarElems = document.getElementById("rustdoc-modnav");1134 if (!sidebarElems) {1135 return;1136 }1137 // Draw a convenient sidebar of known crates if we have a listing1138 const h3 = document.createElement("h3");1139 h3.innerHTML = "Crates";1140 const ul = document.createElement("ul");1141 ul.className = "block crate";11421143 // @ts-expect-error1144 for (const crate of window.ALL_CRATES) {1145 const link = document.createElement("a");1146 link.href = window.rootPath + crate + "/index.html";1147 link.textContent = crate;11481149 const li = document.createElement("li");1150 if (window.rootPath !== "./" && crate === window.currentCrate) {1151 li.className = "current";1152 }1153 li.appendChild(link);1154 ul.appendChild(li);1155 }1156 sidebarElems.appendChild(h3);1157 sidebarElems.appendChild(ul);1158 }11591160 function expandAllDocs() {1161 const innerToggle = document.getElementById(toggleAllDocsId);1162 removeClass(innerToggle, "will-expand");1163 onEachLazy(document.getElementsByClassName("toggle"), e => {1164 if (!hasClass(e, "type-contents-toggle") && !hasClass(e, "more-examples-toggle")) {1165 e.open = true;1166 }1167 });1168 // @ts-expect-error1169 innerToggle.children[0].innerText = "Summary";1170 }11711172 /**1173 * @param {boolean} collapseImpls - also collapse impl blocks if set to true1174 */1175 function collapseAllDocs(collapseImpls) {1176 const innerToggle = document.getElementById(toggleAllDocsId);1177 addClass(innerToggle, "will-expand");1178 onEachLazy(document.getElementsByClassName("toggle"), e => {1179 if ((collapseImpls || e.parentNode.id !== "implementations-list") ||1180 (!hasClass(e, "implementors-toggle") &&1181 !hasClass(e, "type-contents-toggle"))1182 ) {1183 e.open = false;1184 }1185 });1186 // @ts-expect-error1187 innerToggle.children[0].innerText = "Show all";1188 }11891190 /**1191 * @param {MouseEvent=} ev1192 */1193 function toggleAllDocs(ev) {1194 const innerToggle = document.getElementById(toggleAllDocsId);1195 if (!innerToggle) {1196 return;1197 }1198 if (hasClass(innerToggle, "will-expand")) {1199 expandAllDocs();1200 } else {1201 collapseAllDocs(ev !== undefined && ev.shiftKey);1202 }1203 }12041205 (function() {1206 const toggles = document.getElementById(toggleAllDocsId);1207 if (toggles) {1208 toggles.onclick = toggleAllDocs;1209 }12101211 const hideMethodDocs = getSettingValue("auto-hide-method-docs") === "true";1212 const hideImplementations = getSettingValue("auto-hide-trait-implementations") === "true";1213 const hideLargeItemContents = getSettingValue("auto-hide-large-items") !== "false";12141215 // @ts-expect-error1216 function setImplementorsTogglesOpen(id, open) {1217 const list = document.getElementById(id);1218 if (list !== null) {1219 onEachLazy(list.getElementsByClassName("implementors-toggle"), e => {1220 e.open = open;1221 });1222 }1223 }12241225 if (hideImplementations) {1226 setImplementorsTogglesOpen("trait-implementations-list", false);1227 setImplementorsTogglesOpen("blanket-implementations-list", false);1228 }12291230 onEachLazy(document.getElementsByClassName("toggle"), e => {1231 if (!hideLargeItemContents && hasClass(e, "type-contents-toggle")) {1232 e.open = true;1233 }1234 if (hideMethodDocs && hasClass(e, "method-toggle")) {1235 e.open = false;1236 }12371238 });1239 }());12401241 window.rustdoc_add_line_numbers_to_examples = () => {1242 // @ts-expect-error1243 function generateLine(nb) {1244 return `<span data-nosnippet>${nb}</span>`;1245 }12461247 onEachLazy(document.querySelectorAll(1248 ".rustdoc:not(.src) :not(.scraped-example) > .example-wrap > pre > code",1249 ), code => {1250 if (hasClass(code.parentElement.parentElement, "hide-lines")) {1251 removeClass(code.parentElement.parentElement, "hide-lines");1252 return;1253 }1254 const lines = code.innerHTML.split("\n");1255 const digits = (lines.length + "").length;1256 // @ts-expect-error1257 code.innerHTML = lines.map((line, index) => generateLine(index + 1) + line).join("\n");1258 addClass(code.parentElement.parentElement, `digits-${digits}`);1259 });1260 };12611262 window.rustdoc_remove_line_numbers_from_examples = () => {1263 onEachLazy(1264 document.querySelectorAll(".rustdoc:not(.src) :not(.scraped-example) > .example-wrap"),1265 x => addClass(x, "hide-lines"),1266 );1267 };12681269 if (getSettingValue("line-numbers") === "true") {1270 window.rustdoc_add_line_numbers_to_examples();1271 }12721273 function showSidebar() {1274 window.hideAllModals(false);1275 const sidebar = document.getElementsByClassName("sidebar")[0];1276 addClass(sidebar, "shown");1277 }12781279 function hideSidebar() {1280 const sidebar = document.getElementsByClassName("sidebar")[0];1281 removeClass(sidebar, "shown");1282 }12831284 window.addEventListener("resize", () => {1285 if (window.CURRENT_TOOLTIP_ELEMENT) {1286 // As a workaround to the behavior of `contains: layout` used in doc togglers,1287 // tooltip popovers are positioned using javascript.1288 //1289 // This means when the window is resized, we need to redo the layout.1290 const base = window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE;1291 const force_visible = base.TOOLTIP_FORCE_VISIBLE;1292 hideTooltip(false);1293 if (force_visible) {1294 showTooltip(base);1295 base.TOOLTIP_FORCE_VISIBLE = true;1296 }1297 }1298 });12991300 const mainElem = document.getElementById(MAIN_ID);1301 if (mainElem) {1302 mainElem.addEventListener("click", hideSidebar);1303 }13041305 onEachLazy(document.querySelectorAll("a[href^='#']"), el => {1306 // For clicks on internal links (<A> tags with a hash property), we expand the section we're1307 // jumping to *before* jumping there. We can't do this in onHashChange, because it changes1308 // the height of the document so we wind up scrolled to the wrong place.1309 el.addEventListener("click", () => {1310 expandSection(el.hash.slice(1));1311 hideSidebar();1312 });1313 });13141315 onEachLazy(document.querySelectorAll(".toggle > summary:not(.hideme)"), el => {1316 // @ts-expect-error1317 // Clicking on the summary's contents should not collapse it,1318 // but links within should still fire.1319 el.addEventListener("click", e => {1320 if (!e.target.matches("summary, a, a *")) {1321 e.preventDefault();1322 }1323 });1324 });13251326 /**1327 * Show a tooltip immediately.1328 *1329 * @param {HTMLElement} e - The tooltip's anchor point. The DOM is consulted to figure1330 * out what the tooltip should contain, and where it should be1331 * positioned.1332 */1333 function showTooltip(e) {1334 const notable_ty = e.getAttribute("data-notable-ty");1335 if (!window.NOTABLE_TRAITS && notable_ty) {1336 const data = document.getElementById("notable-traits-data");1337 if (data) {1338 window.NOTABLE_TRAITS = JSON.parse(data.innerText);1339 } else {1340 throw new Error("showTooltip() called with notable without any notable traits!");1341 }1342 }1343 // Make this function idempotent. If the tooltip is already shown, avoid doing extra work1344 // and leave it alone.1345 if (window.CURRENT_TOOLTIP_ELEMENT && window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE === e) {1346 clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);1347 return;1348 }1349 window.hideAllModals(false);1350 // use Object.assign to make sure the object has the correct type1351 // with all of the correct fields before it is assigned to a variable,1352 // as typescript has no way to change the type of a variable once it is initialized.1353 const wrapper = Object.assign(document.createElement("div"), {TOOLTIP_BASE: e});1354 if (notable_ty) {1355 wrapper.innerHTML = "<div class=\"content\">" +1356 // @ts-expect-error1357 window.NOTABLE_TRAITS[notable_ty] + "</div>";1358 } else {1359 // Replace any `title` attribute with `data-title` to avoid double tooltips.1360 const ttl = e.getAttribute("title");1361 if (ttl !== null) {1362 e.setAttribute("data-title", ttl);1363 e.removeAttribute("title");1364 }1365 const dttl = e.getAttribute("data-title");1366 if (dttl !== null) {1367 const titleContent = document.createElement("div");1368 titleContent.className = "content";1369 titleContent.appendChild(document.createTextNode(dttl));1370 wrapper.appendChild(titleContent);1371 }1372 }1373 wrapper.className = "tooltip popover";1374 const focusCatcher = document.createElement("div");1375 focusCatcher.setAttribute("tabindex", "0");1376 // @ts-expect-error1377 focusCatcher.onfocus = hideTooltip;1378 wrapper.appendChild(focusCatcher);1379 const pos = e.getBoundingClientRect();1380 // 5px overlap so that the mouse can easily travel from place to place1381 wrapper.style.top = (pos.top + window.scrollY + pos.height) + "px";1382 // @ts-expect-error1383 wrapper.style.left = 0;1384 wrapper.style.right = "auto";1385 wrapper.style.visibility = "hidden";1386 document.body.appendChild(wrapper);1387 const wrapperPos = wrapper.getBoundingClientRect();1388 // offset so that the arrow points at the center of the "(i)"1389 const finalPos = pos.left + window.scrollX - wrapperPos.width + 24;1390 if (finalPos > 0) {1391 wrapper.style.left = finalPos + "px";1392 } else {1393 wrapper.style.setProperty(1394 "--popover-arrow-offset",1395 (wrapperPos.right - pos.right + 4) + "px",1396 );1397 }1398 wrapper.style.visibility = "";1399 window.CURRENT_TOOLTIP_ELEMENT = wrapper;1400 clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);1401 wrapper.onpointerenter = ev => {1402 // If this is a synthetic touch event, ignore it. A click event will be along shortly.1403 if (ev.pointerType !== "mouse") {1404 return;1405 }1406 clearTooltipHoverTimeout(e);1407 };1408 wrapper.onpointerleave = ev => {1409 // If this is a synthetic touch event, ignore it. A click event will be along shortly.1410 if (ev.pointerType !== "mouse" || !(ev.relatedTarget instanceof HTMLElement)) {1411 return;1412 }1413 if (!e.TOOLTIP_FORCE_VISIBLE && !e.contains(ev.relatedTarget)) {1414 // See "Tooltip pointer leave gesture" below.1415 setTooltipHoverTimeout(e, false);1416 addClass(wrapper, "fade-out");1417 }1418 };1419 }14201421 /**1422 * Show or hide the tooltip after a timeout. If a timeout was already set before this function1423 * was called, that timeout gets cleared. If the tooltip is already in the requested state,1424 * this function will still clear any pending timeout, but otherwise do nothing.1425 *1426 * @param {HTMLElement} element - The tooltip's anchor point. The DOM is consulted to figure1427 * out what the tooltip should contain, and where it should be1428 * positioned.1429 * @param {boolean} show - If true, the tooltip will be made visible. If false, it will1430 * be hidden.1431 */1432 function setTooltipHoverTimeout(element, show) {1433 clearTooltipHoverTimeout(element);1434 if (!show && !window.CURRENT_TOOLTIP_ELEMENT) {1435 // To "hide" an already hidden element, just cancel its timeout.1436 return;1437 }1438 if (show && window.CURRENT_TOOLTIP_ELEMENT) {1439 // To "show" an already visible element, just cancel its timeout.1440 return;1441 }1442 if (window.CURRENT_TOOLTIP_ELEMENT &&1443 window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE !== element) {1444 // Don't do anything if another tooltip is already visible.1445 return;1446 }1447 element.TOOLTIP_HOVER_TIMEOUT = setTimeout(() => {1448 if (show) {1449 showTooltip(element);1450 } else if (!element.TOOLTIP_FORCE_VISIBLE) {1451 hideTooltip(false);1452 }1453 }, show ? window.RUSTDOC_TOOLTIP_HOVER_MS : window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS);1454 }14551456 /**1457 * If a show/hide timeout was set by `setTooltipHoverTimeout`, cancel it. If none exists,1458 * do nothing.1459 *1460 * @param {HTMLElement} element - The tooltip's anchor point,1461 * as passed to `setTooltipHoverTimeout`.1462 */1463 function clearTooltipHoverTimeout(element) {1464 if (element.TOOLTIP_HOVER_TIMEOUT !== undefined) {1465 removeClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out");1466 clearTimeout(element.TOOLTIP_HOVER_TIMEOUT);1467 delete element.TOOLTIP_HOVER_TIMEOUT;1468 }1469 }14701471 /**1472 * @param {Event & { relatedTarget: Node }} event1473 */1474 function tooltipBlurHandler(event) {1475 if (window.CURRENT_TOOLTIP_ELEMENT &&1476 !window.CURRENT_TOOLTIP_ELEMENT.contains(document.activeElement) &&1477 !window.CURRENT_TOOLTIP_ELEMENT.contains(event.relatedTarget) &&1478 !window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(document.activeElement) &&1479 !window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(event.relatedTarget)1480 ) {1481 // Work around a difference in the focus behaviour between Firefox, Chrome, and Safari.1482 // When I click the button on an already-opened tooltip popover, Safari1483 // hides the popover and then immediately shows it again, while everyone else hides it1484 // and it stays hidden.1485 //1486 // To work around this, make sure the click finishes being dispatched before1487 // hiding the popover. Since `hideTooltip()` is idempotent, this makes Safari behave1488 // consistently with the other two.1489 setTimeout(() => hideTooltip(false), 0);1490 }1491 }14921493 /**1494 * Hide the current tooltip immediately.1495 *1496 * @param {boolean} focus - If set to `true`, move keyboard focus to the tooltip anchor point.1497 * If set to `false`, leave keyboard focus alone.1498 */1499 function hideTooltip(focus) {1500 if (window.CURRENT_TOOLTIP_ELEMENT) {1501 if (window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE) {1502 if (focus) {1503 window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.focus();1504 }1505 window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE = false;1506 }1507 document.body.removeChild(window.CURRENT_TOOLTIP_ELEMENT);1508 clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);1509 window.CURRENT_TOOLTIP_ELEMENT = undefined;1510 }1511 }15121513 onEachLazy(document.getElementsByClassName("tooltip"), e => {1514 e.onclick = () => {1515 e.TOOLTIP_FORCE_VISIBLE = e.TOOLTIP_FORCE_VISIBLE ? false : true;1516 if (window.CURRENT_TOOLTIP_ELEMENT && !e.TOOLTIP_FORCE_VISIBLE) {1517 hideTooltip(true);1518 } else {1519 showTooltip(e);1520 // @ts-expect-error1521 window.CURRENT_TOOLTIP_ELEMENT.setAttribute("tabindex", "0");1522 // @ts-expect-error1523 window.CURRENT_TOOLTIP_ELEMENT.focus();1524 // @ts-expect-error1525 window.CURRENT_TOOLTIP_ELEMENT.onblur = tooltipBlurHandler;1526 }1527 return false;1528 };1529 // @ts-expect-error1530 e.onpointerenter = ev => {1531 // If this is a synthetic touch event, ignore it. A click event will be along shortly.1532 if (ev.pointerType !== "mouse") {1533 return;1534 }1535 setTooltipHoverTimeout(e, true);1536 };1537 // @ts-expect-error1538 e.onpointermove = ev => {1539 // If this is a synthetic touch event, ignore it. A click event will be along shortly.1540 if (ev.pointerType !== "mouse") {1541 return;1542 }1543 setTooltipHoverTimeout(e, true);1544 };1545 // @ts-expect-error1546 e.onpointerleave = ev => {1547 // If this is a synthetic touch event, ignore it. A click event will be along shortly.1548 if (ev.pointerType !== "mouse") {1549 return;1550 }1551 if (!e.TOOLTIP_FORCE_VISIBLE && window.CURRENT_TOOLTIP_ELEMENT &&1552 !window.CURRENT_TOOLTIP_ELEMENT.contains(ev.relatedTarget)) {1553 // Tooltip pointer leave gesture:1554 //1555 // Designing a good hover microinteraction is a matter of guessing user1556 // intent from what are, literally, vague gestures. In this case, guessing if1557 // hovering in or out of the tooltip base is intentional or not.1558 //1559 // To figure this out, a few different techniques are used:1560 //1561 // * When the mouse pointer enters a tooltip anchor point, its hitbox is grown1562 // on the bottom, where the popover is/will appear. Search "hover tunnel" in1563 // rustdoc.css for the implementation.1564 // * There's a delay when the mouse pointer enters the popover base anchor, in1565 // case the mouse pointer was just passing through and the user didn't want1566 // to open it.1567 // * Similarly, a delay is added when exiting the anchor, or the popover1568 // itself, before hiding it.1569 // * A fade-out animation is layered onto the pointer exit delay to immediately1570 // inform the user that they successfully dismissed the popover, while still1571 // providing a way for them to cancel it if it was a mistake and they still1572 // wanted to interact with it.1573 // * No animation is used for revealing it, because we don't want people to try1574 // to interact with an element while it's in the middle of fading in: either1575 // they're allowed to interact with it while it's fading in, meaning it can't1576 // serve as mistake-proofing for the popover, or they can't, but1577 // they might try and be frustrated.1578 //1579 // See also:1580 // * https://www.nngroup.com/articles/timing-exposing-content/1581 // * https://www.nngroup.com/articles/tooltip-guidelines/1582 // * https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown1583 setTooltipHoverTimeout(e, false);1584 addClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out");1585 }1586 };1587 });15881589 const sidebar_menu_toggle = document.getElementsByClassName("sidebar-menu-toggle")[0];1590 if (sidebar_menu_toggle) {1591 sidebar_menu_toggle.addEventListener("click", () => {1592 const sidebar = document.getElementsByClassName("sidebar")[0];1593 // @ts-expect-error1594 if (!hasClass(sidebar, "shown")) {1595 showSidebar();1596 } else {1597 hideSidebar();1598 }1599 });1600 }16011602 // @ts-expect-error1603 function helpBlurHandler(event) {1604 const isInPopover = onEachLazy(1605 document.querySelectorAll(".settings-menu, .help-menu"),1606 menu => {1607 return menu.contains(document.activeElement) || menu.contains(event.relatedTarget);1608 },1609 );1610 if (!isInPopover) {1611 window.hidePopoverMenus();1612 }1613 }16141615 function buildHelpMenu() {1616 const book_info = document.createElement("span");1617 const drloChannel = `https://doc.rust-lang.org/${getVar("channel")}`;1618 book_info.className = "top";1619 book_info.innerHTML = `You can find more information in \1620<a href="${drloChannel}/rustdoc/">the rustdoc book</a>.`;16211622 const shortcuts = [1623 ["?", "Show this help dialog"],1624 ["S / /", "Focus the search field"],1625 ["↑", "Move up in search results"],1626 ["↓", "Move down in search results"],1627 ["← / →", "Switch result tab (when results focused)"],1628 ["⏎", "Go to active search result"],1629 ["+ / =", "Expand all sections"],1630 ["-", "Collapse all sections"],1631 // for the sake of brevity, we don't say "inherit impl blocks",1632 // although that would be more correct,1633 // since trait impl blocks are collapsed by -1634 ["_", "Collapse all sections, including impl blocks"],1635 ].map(x => "<dt>" +1636 x[0].split(" ")1637 .map((y, index) => ((index & 1) === 0 ? "<kbd>" + y + "</kbd>" : " " + y + " "))1638 .join("") + "</dt><dd>" + x[1] + "</dd>").join("");1639 const div_shortcuts = document.createElement("div");1640 addClass(div_shortcuts, "shortcuts");1641 div_shortcuts.innerHTML = "<h2>Keyboard Shortcuts</h2><dl>" + shortcuts + "</dl></div>";16421643 const infos = [1644 `For a full list of all search features, take a look \1645 <a href="${drloChannel}/rustdoc/read-documentation/search.html">here</a>.`,1646 "Prefix searches with a type followed by a colon (e.g., <code>fn:</code>) to \1647 restrict the search to a given item kind.",1648 "Accepted kinds are: <code>fn</code>, <code>mod</code>, <code>struct</code>, \1649 <code>enum</code>, <code>trait</code>, <code>type</code>, <code>macro</code>, \1650 and <code>constant</code>.",1651 "Search functions by type signature (e.g., <code>vec -> usize</code> or \1652 <code>-> vec</code> or <code>String, enum:Cow -> bool</code>)",1653 "You can look for items with an exact name by putting double quotes around \1654 your request: <code>\"string\"</code>",1655 `Look for functions that accept or return \1656 <a href="${drloChannel}/std/primitive.slice.html">slices</a> and \1657 <a href="${drloChannel}/std/primitive.array.html">arrays</a> by writing square \1658 brackets (e.g., <code>-> [u8]</code> or <code>[] -> Option</code>)`,1659 "Look for items inside another one by searching for a path: <code>vec::Vec</code>",1660 ].map(x => "<p>" + x + "</p>").join("");1661 const div_infos = document.createElement("div");1662 addClass(div_infos, "infos");1663 div_infos.innerHTML = "<h2>Search Tricks</h2>" + infos;16641665 const rustdoc_version = document.createElement("span");1666 rustdoc_version.className = "bottom";1667 const rustdoc_version_code = document.createElement("code");1668 rustdoc_version_code.innerText = "rustdoc " + getVar("rustdoc-version");1669 rustdoc_version.appendChild(rustdoc_version_code);16701671 const container = document.createElement("div");1672 if (!isHelpPage) {1673 container.className = "popover";1674 }1675 container.id = "help";16761677 const side_by_side = document.createElement("div");1678 side_by_side.className = "side-by-side";1679 side_by_side.appendChild(div_shortcuts);1680 side_by_side.appendChild(div_infos);16811682 const content = document.createElement("div");1683 content.className = "content";16841685 content.appendChild(book_info);1686 content.appendChild(side_by_side);1687 content.appendChild(rustdoc_version);16881689 container.appendChild(content);16901691 if (isHelpPage) {1692 const help_section = document.createElement("section");1693 help_section.appendChild(container);1694 nonnull(document.getElementById("main-content")).appendChild(help_section);1695 } else {1696 onEachLazy(document.getElementsByClassName("help-menu"), menu => {1697 if (menu.offsetWidth !== 0) {1698 menu.appendChild(container);1699 container.onblur = helpBlurHandler;1700 menu.onblur = helpBlurHandler;1701 menu.children[0].onblur = helpBlurHandler;1702 return true;1703 }1704 });1705 }17061707 return container;1708 }17091710 /**1711 * Hide popover menus, clickable tooltips, and the sidebar (if applicable).1712 *1713 * Pass `true` to reset focus for tooltip popovers.1714 */1715 window.hideAllModals = switchFocus => {1716 hideSidebar();1717 window.hidePopoverMenus();1718 hideTooltip(switchFocus);1719 };17201721 /**1722 * Hide all the popover menus.1723 */1724 window.hidePopoverMenus = () => {1725 onEachLazy(document.querySelectorAll(".settings-menu .popover"), elem => {1726 elem.style.display = "none";1727 });1728 onEachLazy(document.querySelectorAll(".help-menu .popover"), elem => {1729 elem.parentElement.removeChild(elem);1730 });1731 };17321733 /**1734 * Show the help popup menu.1735 */1736 function showHelp() {1737 window.hideAllModals(false);1738 // Prevent `blur` events from being dispatched as a result of closing1739 // other modals.1740 onEachLazy(document.querySelectorAll(".help-menu a"), menu => {1741 if (menu.offsetWidth !== 0) {1742 menu.focus();1743 return true;1744 }1745 });1746 buildHelpMenu();1747 }17481749 if (isHelpPage) {1750 buildHelpMenu();1751 } else {1752 onEachLazy(document.querySelectorAll(".help-menu > a"), helpLink => {1753 helpLink.addEventListener(1754 "click",1755 /** @param {MouseEvent} event */1756 event => {1757 // By default, have help button open docs in a popover.1758 // If user clicks with a moderator, though, use default browser behavior,1759 // probably opening in a new window or tab.1760 if (event.ctrlKey ||1761 event.altKey ||1762 event.metaKey) {1763 return;1764 }1765 event.preventDefault();1766 if (document.getElementById("help")) {1767 window.hidePopoverMenus();1768 } else {1769 showHelp();1770 }1771 },1772 );1773 });1774 }17751776 addSidebarItems();1777 addSidebarCrates();1778 onHashChange(null);1779 window.addEventListener("hashchange", onHashChange);1780 window.searchState.setup();1781}());17821783// Hide, show, and resize the sidebar1784//1785// The body class and CSS variable are initially set up in storage.js,1786// but in this file, we implement:1787//1788// * the show sidebar button, which appears if the sidebar is hidden1789// and, by clicking on it, will bring it back1790// * the sidebar resize handle, which appears only on large viewports1791// with a [fine precision pointer] to allow the user to change1792// the size of the sidebar1793//1794// [fine precision pointer]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer1795(function() {1796 // 100 is the size of the logo1797 // don't let the sidebar get smaller than that, or it'll get squished1798 const SIDEBAR_MIN = 100;1799 // Don't let the sidebar get bigger than this1800 const SIDEBAR_MAX = 500;1801 // Don't let the body (including the gutter) get smaller than this1802 //1803 // WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY1804 // Acceptable values for BODY_MIN are constrained by the mobile breakpoint1805 // (which is the minimum size of the whole page where the sidebar exists)1806 // and the default sidebar width:1807 //1808 // BODY_MIN <= RUSTDOC_MOBILE_BREAKPOINT - DEFAULT_SIDEBAR_WIDTH1809 //1810 // At the time of this writing, the DEFAULT_SIDEBAR_WIDTH on src pages is1811 // 300px, and the RUSTDOC_MOBILE_BREAKPOINT is 700px, so BODY_MIN must be1812 // at most 400px. Otherwise, it would start out at the default size, then1813 // grabbing the resize handle would suddenly cause it to jank to1814 // its constraint-generated maximum.1815 const RUSTDOC_MOBILE_BREAKPOINT = 700;1816 const BODY_MIN = 400;1817 // At half-way past the minimum size, vanish the sidebar entirely1818 const SIDEBAR_VANISH_THRESHOLD = SIDEBAR_MIN / 2;18191820 // Toolbar button to show the sidebar.1821 //1822 // On small, "mobile-sized" viewports, it's not persistent and it1823 // can only be activated by going into Settings and hiding the nav bar.1824 // On larger, "desktop-sized" viewports (though that includes many1825 // tablets), it's fixed-position, appears in the left side margin,1826 // and it can be activated by resizing the sidebar into nothing.1827 let sidebarButton = document.getElementById("sidebar-button");1828 const body = document.querySelector(".main-heading");1829 if (!sidebarButton && body) {1830 sidebarButton = document.createElement("div");1831 sidebarButton.id = "sidebar-button";1832 const path = `${window.rootPath}${window.currentCrate}/all.html`;1833 sidebarButton.innerHTML = `<a href="${path}" title="show sidebar"></a>`;1834 body.insertBefore(sidebarButton, body.firstChild);1835 }1836 if (sidebarButton) {1837 sidebarButton.addEventListener("click", e => {1838 removeClass(document.documentElement, "hide-sidebar");1839 updateLocalStorage("hide-sidebar", "false");1840 if (window.rustdocToggleSrcSidebar) {1841 window.rustdocToggleSrcSidebar();1842 }1843 e.preventDefault();1844 });1845 }18461847 /**1848 * Pointer capture.1849 *1850 * Resizing is a single-pointer gesture. Any secondary pointer is ignored1851 *1852 * @type {null|number}1853 */1854 let currentPointerId = null;18551856 /**1857 * "Desired" sidebar size.1858 *1859 * This is stashed here for window resizing. If the sidebar gets1860 * shrunk to maintain BODY_MIN, and then the user grows the window again,1861 * it gets the sidebar to restore its size.1862 *1863 * @type {null|number}1864 */1865 let desiredSidebarSize = null;18661867 /**1868 * Sidebar resize debouncer.1869 *1870 * The sidebar itself is resized instantly, but the body HTML can be too1871 * big for that, causing reflow jank. To reduce this, we queue up a separate1872 * animation frame and throttle it.1873 *1874 * @type {false|ReturnType<typeof setTimeout>}1875 */1876 let pendingSidebarResizingFrame = false;18771878 /** @type {HTMLElement|null} */1879 const resizer = document.querySelector(".sidebar-resizer");1880 /** @type {HTMLElement|null} */1881 const sidebar = document.querySelector(".sidebar");1882 // If this page has no sidebar at all, bail out.1883 if (!resizer || !sidebar) {1884 return;1885 }18861887 // src page and docs page use different variables, because the contents of1888 // the sidebar are so different that it's reasonable to thing the user1889 // would want them to have different sizes1890 const isSrcPage = hasClass(document.body, "src");18911892 // Call this function to hide the sidebar when using the resize handle1893 //1894 // This function also nulls out the sidebar width CSS variable and setting,1895 // causing it to return to its default. This does not happen if you do it1896 // from settings.js, which uses a separate function. It's done here because1897 // the minimum sidebar size is rather uncomfortable, and it must pass1898 // through that size when using the shrink-to-nothing gesture.1899 const hideSidebar = function() {1900 if (isSrcPage) {1901 window.rustdocCloseSourceSidebar();1902 updateLocalStorage("src-sidebar-width", null);1903 // [RUSTDOCIMPL] CSS variable fast path1904 //1905 // The sidebar width variable is attached to the <html> element by1906 // storage.js, because the sidebar and resizer don't exist yet.1907 // But the resize code, in `resize()`, sets the property on the1908 // sidebar and resizer elements (which are the only elements that1909 // use the variable) to avoid recalculating CSS on the entire1910 // document on every frame.1911 //1912 // So, to clear it, we need to clear all three.1913 document.documentElement.style.removeProperty("--src-sidebar-width");1914 sidebar.style.removeProperty("--src-sidebar-width");1915 resizer.style.removeProperty("--src-sidebar-width");1916 } else {1917 addClass(document.documentElement, "hide-sidebar");1918 updateLocalStorage("hide-sidebar", "true");1919 updateLocalStorage("desktop-sidebar-width", null);1920 document.documentElement.style.removeProperty("--desktop-sidebar-width");1921 sidebar.style.removeProperty("--desktop-sidebar-width");1922 resizer.style.removeProperty("--desktop-sidebar-width");1923 }1924 };19251926 // Call this function to show the sidebar from the resize handle.1927 // On docs pages, this can only happen if the user has grabbed the resize1928 // handle, shrunk the sidebar down to nothing, and then pulls back into1929 // the visible range without releasing it. You can, however, grab the1930 // resize handle on a source page with the sidebar closed, because it1931 // remains visible all the time on there.1932 const showSidebar = function() {1933 if (isSrcPage) {1934 window.rustdocShowSourceSidebar();1935 } else {1936 removeClass(document.documentElement, "hide-sidebar");1937 updateLocalStorage("hide-sidebar", "false");1938 }1939 };19401941 /**1942 * Call this to set the correct CSS variable and setting.1943 * This function doesn't enforce size constraints. Do that before calling it!1944 *1945 * @param {number} size - CSS px width of the sidebar.1946 */1947 const changeSidebarSize = function(size) {1948 if (isSrcPage) {1949 updateLocalStorage("src-sidebar-width", size.toString());1950 // [RUSTDOCIMPL] CSS variable fast path1951 //1952 // While this property is set on the HTML element at load time,1953 // because the sidebar isn't actually loaded yet,1954 // we scope this update to the sidebar to avoid hitting a slow1955 // path in WebKit.1956 sidebar.style.setProperty("--src-sidebar-width", size + "px");1957 resizer.style.setProperty("--src-sidebar-width", size + "px");1958 } else {1959 updateLocalStorage("desktop-sidebar-width", size.toString());1960 sidebar.style.setProperty("--desktop-sidebar-width", size + "px");1961 resizer.style.setProperty("--desktop-sidebar-width", size + "px");1962 }1963 };19641965 // Check if the sidebar is hidden. Since src pages and doc pages have1966 // different settings, this function has to check that.1967 const isSidebarHidden = function() {1968 return isSrcPage ?1969 !hasClass(document.documentElement, "src-sidebar-expanded") :1970 hasClass(document.documentElement, "hide-sidebar");1971 };19721973 /**1974 * Respond to the resize handle event.1975 * This function enforces size constraints, and implements the1976 * shrink-to-nothing gesture based on thresholds defined above.1977 *1978 * @param {PointerEvent} e1979 */1980 const resize = function(e) {1981 if (currentPointerId === null || currentPointerId !== e.pointerId) {1982 return;1983 }1984 e.preventDefault();1985 const pos = e.clientX - 3;1986 if (pos < SIDEBAR_VANISH_THRESHOLD) {1987 hideSidebar();1988 } else if (pos >= SIDEBAR_MIN) {1989 if (isSidebarHidden()) {1990 showSidebar();1991 }1992 // don't let the sidebar get wider than SIDEBAR_MAX, or the body narrower1993 // than BODY_MIN1994 const constrainedPos = Math.min(pos, window.innerWidth - BODY_MIN, SIDEBAR_MAX);1995 changeSidebarSize(constrainedPos);1996 desiredSidebarSize = constrainedPos;1997 if (pendingSidebarResizingFrame !== false) {1998 clearTimeout(pendingSidebarResizingFrame);1999 }2000 pendingSidebarResizingFrame = setTimeout(() => {
Findings
✓ No findings reported for this file.